From 53da43a10948802eb1891a471909776dc6e0f07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Mon, 23 Oct 2023 17:00:58 +0200 Subject: [PATCH] Reformat src/aliceVision with latest clang-format rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Candice Bentéjac --- src/aliceVision/alicevision_omp.hpp | 10 +- src/aliceVision/calibration/bestImages.cpp | 218 +- src/aliceVision/calibration/bestImages.hpp | 23 +- src/aliceVision/calibration/calibration.cpp | 295 +- src/aliceVision/calibration/calibration.hpp | 26 +- .../calibration/checkerDetector.cpp | 418 +- .../calibration/checkerDetector.hpp | 187 +- .../calibration/checkerDetector_io.cpp | 42 +- .../calibration/checkerDetector_io.hpp | 11 +- .../calibration/distortionEstimation.cpp | 33 +- .../calibration/distortionEstimation.hpp | 12 +- .../distortioncalibration_test.cpp | 468 +- src/aliceVision/calibration/exportData.cpp | 365 +- src/aliceVision/calibration/exportData.hpp | 31 +- src/aliceVision/calibration/patternDetect.cpp | 356 +- src/aliceVision/calibration/patternDetect.hpp | 44 +- src/aliceVision/camera/Distortion.hpp | 65 +- src/aliceVision/camera/Distortion3DE.cpp | 60 +- src/aliceVision/camera/Distortion3DE.hpp | 107 +- src/aliceVision/camera/DistortionBrown.cpp | 11 +- src/aliceVision/camera/DistortionBrown.hpp | 22 +- src/aliceVision/camera/DistortionFisheye.cpp | 103 +- src/aliceVision/camera/DistortionFisheye.hpp | 31 +- src/aliceVision/camera/DistortionFisheye1.cpp | 10 +- src/aliceVision/camera/DistortionFisheye1.hpp | 20 +- src/aliceVision/camera/DistortionRadial.cpp | 147 +- src/aliceVision/camera/DistortionRadial.hpp | 117 +- src/aliceVision/camera/Equidistant.cpp | 132 +- src/aliceVision/camera/Equidistant.hpp | 94 +- src/aliceVision/camera/IntrinsicBase.cpp | 27 +- src/aliceVision/camera/IntrinsicBase.hpp | 90 +- src/aliceVision/camera/IntrinsicInitMode.hpp | 41 +- .../camera/IntrinsicScaleOffset.cpp | 24 +- .../camera/IntrinsicScaleOffset.hpp | 26 +- .../camera/IntrinsicScaleOffsetDisto.cpp | 17 +- .../camera/IntrinsicScaleOffsetDisto.hpp | 94 +- src/aliceVision/camera/Pinhole.cpp | 127 +- src/aliceVision/camera/Pinhole.hpp | 98 +- src/aliceVision/camera/Undistortion.cpp | 15 +- src/aliceVision/camera/Undistortion.hpp | 55 +- src/aliceVision/camera/Undistortion3DE.cpp | 32 +- src/aliceVision/camera/Undistortion3DE.hpp | 20 +- src/aliceVision/camera/camera.hpp | 170 +- src/aliceVision/camera/cameraCommon.hpp | 106 +- .../camera/cameraUndistortImage.hpp | 46 +- src/aliceVision/camera/distortion_test.cpp | 6 +- src/aliceVision/camera/equidistant_test.cpp | 63 +- src/aliceVision/camera/pinhole3DE_test.cpp | 98 +- src/aliceVision/camera/pinholeBrown_test.cpp | 42 +- .../camera/pinholeFisheye1_test.cpp | 42 +- .../camera/pinholeFisheye_test.cpp | 42 +- src/aliceVision/camera/pinholeRadial_test.cpp | 87 +- src/aliceVision/cmdline/cmdline.cpp | 15 +- src/aliceVision/cmdline/cmdline.hpp | 120 +- .../colorHarmonization/CommonDataByPair.hpp | 86 +- .../CommonDataByPair_fullFrame.hpp | 50 +- .../CommonDataByPair_matchedPoints.hpp | 88 +- .../CommonDataByPair_vldSegment.hpp | 165 +- .../GainOffsetConstraintBuilder.cpp | 222 +- .../GainOffsetConstraintBuilder.hpp | 139 +- .../gainOffsetConstraintBuilder_test.cpp | 324 +- src/aliceVision/dataio/FeedProvider.cpp | 58 +- src/aliceVision/dataio/FeedProvider.hpp | 23 +- src/aliceVision/dataio/IFeed.cpp | 19 +- src/aliceVision/dataio/IFeed.hpp | 21 +- src/aliceVision/dataio/ImageFeed.cpp | 110 +- src/aliceVision/dataio/ImageFeed.hpp | 23 +- src/aliceVision/dataio/SfMDataFeed.cpp | 89 +- src/aliceVision/dataio/SfMDataFeed.hpp | 23 +- src/aliceVision/dataio/VideoFeed.cpp | 145 +- src/aliceVision/dataio/VideoFeed.hpp | 23 +- src/aliceVision/depthMap/BufPtr.hpp | 31 +- .../depthMap/CustomPatchPatternParams.cpp | 18 +- .../depthMap/CustomPatchPatternParams.hpp | 8 +- .../depthMap/DepthMapEstimator.cpp | 97 +- .../depthMap/DepthMapEstimator.hpp | 18 +- src/aliceVision/depthMap/DepthMapParams.hpp | 22 +- .../depthMap/NormalMapEstimator.cpp | 20 +- .../depthMap/NormalMapEstimator.hpp | 12 +- src/aliceVision/depthMap/Refine.cpp | 112 +- src/aliceVision/depthMap/Refine.hpp | 25 +- src/aliceVision/depthMap/RefineParams.hpp | 62 +- src/aliceVision/depthMap/Sgm.cpp | 111 +- src/aliceVision/depthMap/Sgm.hpp | 29 +- src/aliceVision/depthMap/SgmDepthList.cpp | 219 +- src/aliceVision/depthMap/SgmDepthList.hpp | 46 +- src/aliceVision/depthMap/SgmParams.hpp | 79 +- src/aliceVision/depthMap/Tile.hpp | 16 +- .../depthMap/computeOnMultiGPUs.cpp | 10 +- .../depthMap/computeOnMultiGPUs.hpp | 7 +- .../cuda/device/DeviceCameraParams.hpp | 6 +- .../cuda/device/DevicePatchPattern.hpp | 22 +- .../depthMap/cuda/host/DeviceCache.cpp | 83 +- .../depthMap/cuda/host/DeviceCache.hpp | 17 +- .../depthMap/cuda/host/DeviceMipmapImage.cpp | 44 +- .../depthMap/cuda/host/DeviceMipmapImage.hpp | 24 +- .../cuda/host/DeviceStreamManager.cpp | 33 +- .../cuda/host/DeviceStreamManager.hpp | 14 +- .../depthMap/cuda/host/LRUCache.hpp | 93 +- .../depthMap/cuda/host/LRUCameraCache.hpp | 26 +- src/aliceVision/depthMap/cuda/host/divUp.hpp | 10 +- src/aliceVision/depthMap/cuda/host/memory.hpp | 914 +- .../depthMap/cuda/host/patchPattern.cpp | 81 +- .../depthMap/cuda/host/patchPattern.hpp | 4 +- src/aliceVision/depthMap/cuda/host/utils.cpp | 45 +- src/aliceVision/depthMap/cuda/host/utils.hpp | 66 +- .../imageProcessing/deviceColorConversion.hpp | 5 +- .../imageProcessing/deviceGaussianFilter.hpp | 70 +- .../imageProcessing/deviceMipmappedArray.hpp | 5 +- .../deviceDepthSimilarityMap.hpp | 12 +- .../planeSweeping/deviceSimilarityVolume.hpp | 38 +- .../cuda/planeSweeping/similarity.hpp | 21 +- src/aliceVision/depthMap/depthMapUtils.cpp | 537 +- src/aliceVision/depthMap/depthMapUtils.hpp | 47 +- src/aliceVision/depthMap/volumeIO.cpp | 181 +- src/aliceVision/depthMap/volumeIO.hpp | 48 +- src/aliceVision/feature/Descriptor.hpp | 377 +- src/aliceVision/feature/FeaturesPerView.cpp | 48 +- src/aliceVision/feature/FeaturesPerView.hpp | 159 +- src/aliceVision/feature/Hamming.hpp | 220 +- src/aliceVision/feature/ImageDescriber.cpp | 236 +- src/aliceVision/feature/ImageDescriber.hpp | 229 +- src/aliceVision/feature/KeypointSet.hpp | 95 +- src/aliceVision/feature/PointFeature.hpp | 195 +- src/aliceVision/feature/Regions.hpp | 386 +- src/aliceVision/feature/RegionsPerView.hpp | 177 +- src/aliceVision/feature/akaze/AKAZE.cpp | 684 +- src/aliceVision/feature/akaze/AKAZE.hpp | 216 +- .../feature/akaze/ImageDescriber_AKAZE.cpp | 259 +- .../feature/akaze/ImageDescriber_AKAZE.hpp | 296 +- .../feature/akaze/descriptorLIOP.cpp | 611 +- .../feature/akaze/descriptorLIOP.hpp | 45 +- .../feature/akaze/descriptorMLDB.hpp | 343 +- .../feature/akaze/descriptorMSURF.hpp | 212 +- .../apriltag/ImageDescriber_APRILTAG.cpp | 142 +- .../apriltag/ImageDescriber_APRILTAG.hpp | 191 +- .../feature/cctag/ImageDescriber_CCTAG.cpp | 176 +- .../feature/cctag/ImageDescriber_CCTAG.hpp | 222 +- src/aliceVision/feature/feature.hpp | 2 - src/aliceVision/feature/features_test.cpp | 160 +- .../feature/imageDescriberCommon.cpp | 178 +- .../feature/imageDescriberCommon.hpp | 156 +- src/aliceVision/feature/imageStats.cpp | 14 +- src/aliceVision/feature/imageStats.hpp | 1 - src/aliceVision/feature/metric.hpp | 170 +- src/aliceVision/feature/metric_test.cpp | 194 +- .../openCV/ImageDescriber_AKAZE_OCV.cpp | 66 +- .../openCV/ImageDescriber_AKAZE_OCV.hpp | 137 +- .../openCV/ImageDescriber_SIFT_OCV.cpp | 286 +- .../openCV/ImageDescriber_SIFT_OCV.hpp | 173 +- src/aliceVision/feature/regionsFactory.hpp | 16 +- .../sift/ImageDescriber_DSPSIFT_vlfeat.cpp | 134 +- .../sift/ImageDescriber_DSPSIFT_vlfeat.hpp | 193 +- .../feature/sift/ImageDescriber_SIFT.hpp | 264 +- .../sift/ImageDescriber_SIFT_popSIFT.cpp | 109 +- .../sift/ImageDescriber_SIFT_popSIFT.hpp | 177 +- .../sift/ImageDescriber_SIFT_vlfeat.hpp | 190 +- .../sift/ImageDescriber_SIFT_vlfeatFloat.hpp | 182 +- src/aliceVision/feature/sift/SIFT.cpp | 266 +- src/aliceVision/feature/sift/SIFT.hpp | 146 +- .../featureEngine/FeatureExtractor.cpp | 93 +- .../featureEngine/FeatureExtractor.hpp | 69 +- src/aliceVision/fuseCut/DelaunayGraphCut.cpp | 1694 +-- src/aliceVision/fuseCut/DelaunayGraphCut.hpp | 220 +- .../fuseCut/DelaunayGraphCut_test.cpp | 33 +- src/aliceVision/fuseCut/Fuser.cpp | 491 +- src/aliceVision/fuseCut/Fuser.hpp | 26 +- src/aliceVision/fuseCut/LargeScale.cpp | 75 +- src/aliceVision/fuseCut/LargeScale.hpp | 13 +- src/aliceVision/fuseCut/LargeScale_test.cpp | 16 +- src/aliceVision/fuseCut/MaxFlow_AdjList.cpp | 50 +- src/aliceVision/fuseCut/MaxFlow_AdjList.hpp | 78 +- src/aliceVision/fuseCut/MaxFlow_CSR.cpp | 7 +- src/aliceVision/fuseCut/MaxFlow_CSR.hpp | 117 +- src/aliceVision/fuseCut/OctreeTracks.cpp | 253 +- src/aliceVision/fuseCut/OctreeTracks.hpp | 12 +- .../fuseCut/ReconstructionPlan.cpp | 108 +- .../fuseCut/ReconstructionPlan.hpp | 9 +- src/aliceVision/fuseCut/VoxelsGrid.cpp | 185 +- src/aliceVision/fuseCut/VoxelsGrid.hpp | 16 +- .../fuseCut/delaunayGraphCutTypes.hpp | 34 +- src/aliceVision/geometry/Frustum.hpp | 186 +- src/aliceVision/geometry/HalfPlane.hpp | 91 +- src/aliceVision/geometry/Pose3.hpp | 76 +- src/aliceVision/geometry/Pose3d_test.cpp | 38 +- src/aliceVision/geometry/Similarity3.hpp | 36 +- .../geometry/frustumIntersection_test.cpp | 417 +- .../geometry/halfSpaceIntersection_test.cpp | 64 +- src/aliceVision/geometry/lie.hpp | 43 +- .../geometry/rigidTransformation3D.cpp | 238 +- .../geometry/rigidTransformation3D.hpp | 103 +- .../geometry/rigidTransformation3D_test.cpp | 442 +- src/aliceVision/gpu/gpu.cpp | 131 +- src/aliceVision/gpu/gpu.hpp | 9 +- src/aliceVision/graph/IndexedGraph.hpp | 114 +- src/aliceVision/graph/Triplet.hpp | 204 +- src/aliceVision/graph/connectedComponent.hpp | 180 +- .../graph/connectedComponent_test.cpp | 143 +- .../graph/indexedGraphGraphvizExport.hpp | 156 +- src/aliceVision/graph/triplet_test.cpp | 439 +- src/aliceVision/half.hpp | 14 +- src/aliceVision/hdr/DebevecCalibrate.cpp | 47 +- src/aliceVision/hdr/DebevecCalibrate.hpp | 39 +- src/aliceVision/hdr/GrossbergCalibrate.cpp | 53 +- src/aliceVision/hdr/GrossbergCalibrate.hpp | 42 +- src/aliceVision/hdr/LaguerreBACalibration.cpp | 106 +- src/aliceVision/hdr/LaguerreBACalibration.hpp | 41 +- src/aliceVision/hdr/QuadProg++.cpp | 192 +- src/aliceVision/hdr/QuadProg++.hpp | 65 +- src/aliceVision/hdr/brackets.cpp | 74 +- src/aliceVision/hdr/brackets.hpp | 26 +- src/aliceVision/hdr/emorCurve.cpp | 9682 +++++----------- src/aliceVision/hdr/emorCurve.hpp | 5 +- src/aliceVision/hdr/emorCurveInv.cpp | 9683 +++++------------ src/aliceVision/hdr/hdrDebevec_test.cpp | 24 +- src/aliceVision/hdr/hdrGrossberg_test.cpp | 29 +- src/aliceVision/hdr/hdrLaguerre_test.cpp | 21 +- src/aliceVision/hdr/hdrMerge.cpp | 289 +- src/aliceVision/hdr/hdrMerge.hpp | 55 +- src/aliceVision/hdr/hdrTestCommon.hpp | 51 +- src/aliceVision/hdr/rgbCurve.cpp | 394 +- src/aliceVision/hdr/rgbCurve.hpp | 753 +- src/aliceVision/hdr/sampling.cpp | 143 +- src/aliceVision/hdr/sampling.hpp | 38 +- src/aliceVision/image/Image.hpp | 498 +- src/aliceVision/image/ImageCache.cpp | 41 +- src/aliceVision/image/ImageCache.hpp | 126 +- src/aliceVision/image/Rgb.hpp | 12 +- src/aliceVision/image/Sampler.hpp | 667 +- src/aliceVision/image/all.hpp | 2 +- src/aliceVision/image/cache.cpp | 731 +- src/aliceVision/image/cache.hpp | 562 +- src/aliceVision/image/colorspace.cpp | 144 +- src/aliceVision/image/colorspace.hpp | 22 +- src/aliceVision/image/concat.hpp | 56 +- src/aliceVision/image/convertion.hpp | 116 +- src/aliceVision/image/convertionOpenCV.hpp | 50 +- src/aliceVision/image/convolution.cpp | 103 +- src/aliceVision/image/convolution.hpp | 214 +- src/aliceVision/image/convolutionBase.hpp | 40 +- src/aliceVision/image/dcp.cpp | 680 +- src/aliceVision/image/dcp.hpp | 158 +- src/aliceVision/image/diffusion.hpp | 464 +- src/aliceVision/image/drawing.hpp | 701 +- src/aliceVision/image/drawing_test.cpp | 360 +- src/aliceVision/image/filtering.cpp | 60 +- src/aliceVision/image/filtering.hpp | 477 +- src/aliceVision/image/filtering_test.cpp | 210 +- src/aliceVision/image/imageAlgo.cpp | 397 +- src/aliceVision/image/imageAlgo.hpp | 182 +- src/aliceVision/image/imageCaching_test.cpp | 12 +- src/aliceVision/image/image_test.cpp | 116 +- src/aliceVision/image/io.cpp | 867 +- src/aliceVision/image/io.hpp | 145 +- src/aliceVision/image/io_test.cpp | 423 +- src/aliceVision/image/jetColorMap.cpp | 49 +- src/aliceVision/image/jetColorMap.hpp | 2 +- src/aliceVision/image/pixelTypes.hpp | 820 +- src/aliceVision/image/resampling.hpp | 142 +- src/aliceVision/image/resampling_test.cpp | 141 +- src/aliceVision/image/warping.hpp | 49 +- .../imageMasking/eigen2cvHelpers.hpp | 26 +- src/aliceVision/imageMasking/imageMasking.cpp | 74 +- src/aliceVision/imageMasking/imageMasking.hpp | 23 +- .../imageMatching/ImageMatching.cpp | 138 +- .../imageMatching/ImageMatching.hpp | 22 +- src/aliceVision/keyframe/KeyframeSelector.cpp | 671 +- src/aliceVision/keyframe/KeyframeSelector.hpp | 133 +- src/aliceVision/lensCorrectionProfile/lcp.cpp | 417 +- src/aliceVision/lensCorrectionProfile/lcp.hpp | 321 +- .../lightingEstimation/augmentedNormals.cpp | 6 +- .../lightingEstimation/augmentedNormals.hpp | 104 +- .../lightingCalibration.cpp | 78 +- .../lightingCalibration.hpp | 39 +- .../lightingEstimation/lightingEstimation.cpp | 23 +- .../lightingEstimation/lightingEstimation.hpp | 16 +- .../lightingEstimation_test.cpp | 147 +- src/aliceVision/linearProgramming/ISolver.hpp | 92 +- .../linearProgramming/MOSEKSolver.cpp | 727 +- .../linearProgramming/MOSEKSolver.hpp | 38 +- .../linearProgramming/OSIXSolver.hpp | 383 +- .../linearProgramming/bisectionLP.hpp | 95 +- .../global_translations_fromTij.hpp | 302 +- .../global_translations_fromTriplets.hpp | 304 +- .../lInfinityCV/lInfinityCV.hpp | 2 +- ...ftyCV_global_translations_fromTij_test.cpp | 159 +- ..._global_translations_fromTriplets_test.cpp | 155 +- .../lInftyCV_resection_robust_test.cpp | 187 +- .../lInfinityCV/lInftyCV_resection_test.cpp | 212 +- .../lInftyCV_tijsAndXis_outlier_test.cpp | 318 +- .../lInfinityCV/lInftyCV_tijsAndXis_test.cpp | 450 +- .../lInftyCV_triangulation_test.cpp | 198 +- .../lInfinityCV/resection.hpp | 259 +- .../lInfinityCV/resection_kernel.cpp | 73 +- .../lInfinityCV/resection_kernel.hpp | 68 +- .../lInfinityCV/tijsAndXis_From_xi_Ri.hpp | 273 +- .../tijsAndXis_From_xi_Ri_noise.hpp | 333 +- .../lInfinityCV/triangulation.hpp | 140 +- .../lInfinityCV/triplet_tijsAndXis_kernel.hpp | 175 +- .../linearProgramming/linearProgramming.hpp | 3 +- .../linearProgramming_test.cpp | 279 +- .../localization/BoundedBuffer.hpp | 124 +- .../localization/CCTagLocalizer.cpp | 1754 ++- .../localization/CCTagLocalizer.hpp | 348 +- src/aliceVision/localization/ILocalizer.hpp | 191 +- .../localization/LocalizationResult.cpp | 560 +- .../localization/LocalizationResult.hpp | 314 +- .../localization/LocalizationResult_test.cpp | 232 +- .../localization/VoctreeLocalizer.cpp | 2691 +++-- .../localization/VoctreeLocalizer.hpp | 663 +- src/aliceVision/localization/optimization.cpp | 1456 ++- src/aliceVision/localization/optimization.hpp | 110 +- .../localization/reconstructed_regions.hpp | 17 +- src/aliceVision/localization/rigResection.cpp | 374 +- src/aliceVision/localization/rigResection.hpp | 46 +- .../localization/rigResection_test.cpp | 576 +- src/aliceVision/matching/ArrayMatcher.hpp | 86 +- .../matching/ArrayMatcher_bruteForce.hpp | 243 +- .../matching/ArrayMatcher_cascadeHashing.hpp | 197 +- .../matching/ArrayMatcher_kdtreeFlann.hpp | 226 +- src/aliceVision/matching/CascadeHasher.hpp | 519 +- src/aliceVision/matching/IndMatch.hpp | 93 +- .../matching/IndMatchDecorator.hpp | 187 +- src/aliceVision/matching/RegionsMatcher.cpp | 257 +- src/aliceVision/matching/RegionsMatcher.hpp | 330 +- src/aliceVision/matching/filters.hpp | 308 +- src/aliceVision/matching/filters_test.cpp | 30 +- src/aliceVision/matching/guidedMatching.cpp | 32 +- src/aliceVision/matching/guidedMatching.hpp | 492 +- src/aliceVision/matching/indMatch_test.cpp | 238 +- src/aliceVision/matching/io.cpp | 536 +- src/aliceVision/matching/io.hpp | 1 - src/aliceVision/matching/kvld/algorithm.cpp | 61 +- src/aliceVision/matching/kvld/algorithm.h | 227 +- src/aliceVision/matching/kvld/kvld.cpp | 698 +- src/aliceVision/matching/kvld/kvld.h | 201 +- src/aliceVision/matching/kvld/kvld_draw.h | 57 +- src/aliceVision/matching/matcherType.cpp | 46 +- src/aliceVision/matching/matcherType.hpp | 18 +- src/aliceVision/matching/matchesFiltering.cpp | 90 +- src/aliceVision/matching/matchesFiltering.hpp | 16 +- src/aliceVision/matching/matching_test.cpp | 221 +- .../matching/pairwiseAdjacencyDisplay.hpp | 85 +- .../matching/supportEstimation.cpp | 21 +- .../matching/supportEstimation.hpp | 6 +- src/aliceVision/matching/svgVisualization.cpp | 1395 +-- src/aliceVision/matching/svgVisualization.hpp | 224 +- .../GeometricFilter.cpp | 9 +- .../GeometricFilter.hpp | 84 +- .../GeometricFilterMatrix.hpp | 62 +- .../GeometricFilterMatrix_E_AC.hpp | 283 +- .../GeometricFilterMatrix_F_AC.hpp | 680 +- .../GeometricFilterMatrix_HGrowing.cpp | 330 +- .../GeometricFilterMatrix_HGrowing.hpp | 440 +- .../GeometricFilterMatrix_H_AC.hpp | 374 +- .../GeometricFilterType.hpp | 92 +- .../IImageCollectionMatcher.hpp | 21 +- .../ImageCollectionMatcher_cascadeHashing.cpp | 382 +- .../ImageCollectionMatcher_cascadeHashing.hpp | 28 +- .../ImageCollectionMatcher_generic.cpp | 179 +- .../ImageCollectionMatcher_generic.hpp | 35 +- .../ImagePairListIO.cpp | 39 +- .../ImagePairListIO.hpp | 11 +- .../ImagePairListIO_test.cpp | 29 +- .../geometricFilterUtils.cpp | 587 +- .../geometricFilterUtils.hpp | 250 +- .../geometricFilterUtils_test.cpp | 106 +- .../matchingCommon.cpp | 46 +- .../matchingCommon.hpp | 11 +- .../matchingImageCollection/pairBuilder.cpp | 46 +- .../matchingImageCollection/pairBuilder.hpp | 4 +- .../pairBuilder_test.cpp | 58 +- src/aliceVision/mesh/Material.cpp | 17 +- src/aliceVision/mesh/Material.hpp | 8 +- src/aliceVision/mesh/Mesh.cpp | 1017 +- src/aliceVision/mesh/Mesh.hpp | 158 +- src/aliceVision/mesh/MeshAnalyze.cpp | 67 +- src/aliceVision/mesh/MeshAnalyze.hpp | 6 +- src/aliceVision/mesh/MeshClean.cpp | 282 +- src/aliceVision/mesh/MeshClean.hpp | 14 +- src/aliceVision/mesh/MeshEnergyOpt.cpp | 41 +- src/aliceVision/mesh/MeshEnergyOpt.hpp | 8 +- src/aliceVision/mesh/Texturing.cpp | 648 +- src/aliceVision/mesh/Texturing.hpp | 90 +- src/aliceVision/mesh/UVAtlas.cpp | 154 +- src/aliceVision/mesh/UVAtlas.hpp | 35 +- src/aliceVision/mesh/geoMesh.hpp | 17 +- src/aliceVision/mesh/meshPostProcessing.cpp | 47 +- src/aliceVision/mesh/meshPostProcessing.hpp | 14 +- src/aliceVision/mesh/meshVisibility.cpp | 49 +- src/aliceVision/mesh/meshVisibility.hpp | 21 +- src/aliceVision/multiview/NViewDataSet.cpp | 276 +- src/aliceVision/multiview/NViewDataSet.hpp | 71 +- .../multiview/RelativePoseKernel.hpp | 279 +- src/aliceVision/multiview/ResectionKernel.hpp | 205 +- src/aliceVision/multiview/Unnormalizer.cpp | 19 +- src/aliceVision/multiview/Unnormalizer.hpp | 26 +- src/aliceVision/multiview/affineSolver.cpp | 154 +- src/aliceVision/multiview/affineSolver.hpp | 4 +- .../multiview/affineSolver_test.cpp | 373 +- .../multiview/epipolarEquation.hpp | 71 +- src/aliceVision/multiview/essential.cpp | 197 +- src/aliceVision/multiview/essential.hpp | 33 +- .../knownRotationTranslationKernel.hpp | 282 +- .../knownRotationTranslationKernel_test.cpp | 69 +- .../relativePose/Essential5PSolver.cpp | 301 +- .../relativePose/Essential5PSolver.hpp | 109 +- .../relativePose/Essential8PSolver.cpp | 49 +- .../relativePose/Essential8PSolver.hpp | 77 +- .../relativePose/EssentialKernel.hpp | 93 +- .../relativePose/Fundamental10PSolver.cpp | 565 +- .../relativePose/Fundamental10PSolver.hpp | 128 +- .../relativePose/Fundamental7PSolver.cpp | 165 +- .../relativePose/Fundamental7PSolver.hpp | 87 +- .../relativePose/Fundamental8PSolver.cpp | 75 +- .../relativePose/Fundamental8PSolver.hpp | 65 +- .../relativePose/FundamentalError.hpp | 62 +- .../relativePose/FundamentalKernel.hpp | 6 +- .../relativePose/Homography4PSolver.cpp | 101 +- .../relativePose/Homography4PSolver.hpp | 79 +- .../relativePose/HomographyKernel.hpp | 3 +- .../relativePose/ISolverErrorRelativePose.hpp | 5 +- .../relativePose/Rotation3PSolver.cpp | 11 +- .../relativePose/Rotation3PSolver.hpp | 78 +- .../relativePose/essential5PSolver_test.cpp | 339 +- .../relativePose/essentialKernel_test.cpp | 392 +- .../fundamental10PSolver_test.cpp | 288 +- .../relativePose/fundamentalKernel_test.cpp | 119 +- .../relativePose/homographyKernel_test.cpp | 122 +- .../multiview/resection/EPnPKernel.hpp | 63 +- .../multiview/resection/EPnPSolver.cpp | 740 +- .../multiview/resection/EPnPSolver.hpp | 97 +- .../resection/ISolverErrorResection.hpp | 4 +- .../multiview/resection/P3PSolver.cpp | 410 +- .../multiview/resection/P3PSolver.hpp | 75 +- .../multiview/resection/P4PfSolver.cpp | 2257 ++-- .../multiview/resection/P4PfSolver.hpp | 152 +- .../multiview/resection/P5PfrSolver.cpp | 600 +- .../multiview/resection/P5PfrSolver.hpp | 179 +- .../resection/ProjectionDistanceError.hpp | 17 +- .../multiview/resection/Resection6PSolver.cpp | 46 +- .../multiview/resection/Resection6PSolver.hpp | 81 +- .../resection/Resection6PSolver_test.cpp | 41 +- .../multiview/resection/ResectionKernel.hpp | 2 +- .../multiview/resection/p4pfSolver_test.cpp | 159 +- .../multiview/resection/p5pfrSolver_test.cpp | 899 +- .../resection/resectionKernel_test.cpp | 221 +- .../resection/resectionLORansac_test.cpp | 421 +- .../multiview/rotationAveraging/common.hpp | 48 +- .../multiview/rotationAveraging/l1.cpp | 1128 +- .../multiview/rotationAveraging/l1.hpp | 91 +- .../multiview/rotationAveraging/l2.cpp | 414 +- .../multiview/rotationAveraging/l2.hpp | 28 +- .../rotationAveraging_test.cpp | 483 +- .../multiview/translationAveraging/common.hpp | 43 +- .../multiview/translationAveraging/solver.hpp | 39 +- .../translationAveraging/solverL1Soft.cpp | 325 +- .../translationAveraging/solverL2Chordal.cpp | 258 +- .../translationAveragingTest.hpp | 218 +- .../translationAveraging_test.cpp | 322 +- .../NViewsTriangulationLORansac.hpp | 379 +- .../multiview/triangulation/Triangulation.cpp | 180 +- .../multiview/triangulation/Triangulation.hpp | 177 +- .../triangulation/triangulationDLT.cpp | 63 +- .../triangulation/triangulationDLT.hpp | 30 +- .../triangulation/triangulationDLT_test.cpp | 22 +- .../triangulation/triangulation_test.cpp | 325 +- src/aliceVision/mvsData/Matrix3x3.hpp | 21 +- src/aliceVision/mvsData/Matrix3x4.hpp | 19 +- src/aliceVision/mvsData/OrientedPoint.hpp | 13 +- src/aliceVision/mvsData/Pixel.hpp | 48 +- src/aliceVision/mvsData/Point2d.hpp | 42 +- src/aliceVision/mvsData/Point3d.hpp | 79 +- src/aliceVision/mvsData/Point4d.hpp | 49 +- src/aliceVision/mvsData/ROI.hpp | 103 +- src/aliceVision/mvsData/Stat3d.cpp | 86 +- src/aliceVision/mvsData/Stat3d.hpp | 4 +- src/aliceVision/mvsData/StaticVector.cpp | 10 +- src/aliceVision/mvsData/StaticVector.hpp | 309 +- src/aliceVision/mvsData/Universe.cpp | 23 +- src/aliceVision/mvsData/Universe.hpp | 11 +- src/aliceVision/mvsData/Voxel.hpp | 24 +- src/aliceVision/mvsData/geometry.cpp | 133 +- src/aliceVision/mvsData/geometry.hpp | 234 +- src/aliceVision/mvsData/geometryTriTri.cpp | 520 +- src/aliceVision/mvsData/geometryTriTri.hpp | 13 +- src/aliceVision/mvsData/structures.cpp | 36 +- src/aliceVision/mvsData/structures.hpp | 36 +- src/aliceVision/mvsUtils/ImagesCache.cpp | 71 +- src/aliceVision/mvsUtils/ImagesCache.hpp | 21 +- src/aliceVision/mvsUtils/MultiViewParams.cpp | 485 +- src/aliceVision/mvsUtils/MultiViewParams.hpp | 145 +- src/aliceVision/mvsUtils/TileParams.cpp | 126 +- src/aliceVision/mvsUtils/TileParams.hpp | 42 +- src/aliceVision/mvsUtils/common.cpp | 185 +- src/aliceVision/mvsUtils/common.hpp | 29 +- src/aliceVision/mvsUtils/fileIO.cpp | 680 +- src/aliceVision/mvsUtils/fileIO.hpp | 7 +- src/aliceVision/mvsUtils/mapIO.cpp | 239 +- src/aliceVision/mvsUtils/mapIO.hpp | 27 +- src/aliceVision/numeric/Accumulator.hpp | 42 +- src/aliceVision/numeric/Container.cpp | 68 +- src/aliceVision/numeric/Container.hpp | 145 +- src/aliceVision/numeric/LMFunctor.hpp | 54 +- src/aliceVision/numeric/MathTrait.hpp | 695 +- src/aliceVision/numeric/algebra.hpp | 43 +- src/aliceVision/numeric/gps.cpp | 18 +- src/aliceVision/numeric/gps.hpp | 2 +- src/aliceVision/numeric/gps_test.cpp | 6 +- src/aliceVision/numeric/lmFunctor_test.cpp | 163 +- src/aliceVision/numeric/numeric.cpp | 220 +- src/aliceVision/numeric/numeric.hpp | 356 +- src/aliceVision/numeric/numeric_test.cpp | 181 +- src/aliceVision/numeric/polynomial.hpp | 150 +- src/aliceVision/numeric/polynomial_test.cpp | 99 +- src/aliceVision/numeric/projection.cpp | 376 +- src/aliceVision/numeric/projection.hpp | 18 +- src/aliceVision/numeric/projection_test.cpp | 10 +- src/aliceVision/panorama/alphaCompositer.hpp | 39 +- src/aliceVision/panorama/boundingBox.cpp | 5 +- src/aliceVision/panorama/boundingBox.hpp | 73 +- src/aliceVision/panorama/cachedImage.cpp | 109 +- src/aliceVision/panorama/cachedImage.hpp | 222 +- src/aliceVision/panorama/compositer.hpp | 68 +- src/aliceVision/panorama/coordinatesMap.cpp | 34 +- src/aliceVision/panorama/coordinatesMap.hpp | 15 +- src/aliceVision/panorama/distance.cpp | 48 +- src/aliceVision/panorama/distance.hpp | 5 +- src/aliceVision/panorama/feathering.cpp | 153 +- src/aliceVision/panorama/feathering.hpp | 8 +- src/aliceVision/panorama/gaussian.cpp | 31 +- src/aliceVision/panorama/gaussian.hpp | 54 +- src/aliceVision/panorama/graphcut.hpp | 361 +- src/aliceVision/panorama/imageOps.cpp | 13 +- src/aliceVision/panorama/imageOps.hpp | 179 +- .../panorama/laplacianCompositer.hpp | 75 +- src/aliceVision/panorama/laplacianPyramid.cpp | 156 +- src/aliceVision/panorama/laplacianPyramid.hpp | 30 +- src/aliceVision/panorama/panoramaMap.cpp | 96 +- src/aliceVision/panorama/panoramaMap.hpp | 71 +- src/aliceVision/panorama/remapBbox.cpp | 134 +- src/aliceVision/panorama/remapBbox.hpp | 7 +- src/aliceVision/panorama/seams.cpp | 170 +- src/aliceVision/panorama/seams.hpp | 69 +- src/aliceVision/panorama/sphericalMapping.cpp | 11 +- src/aliceVision/panorama/sphericalMapping.hpp | 10 +- src/aliceVision/panorama/warper.cpp | 41 +- src/aliceVision/panorama/warper.hpp | 12 +- .../photometricStereo/normalIntegration.cpp | 139 +- .../photometricStereo/normalIntegration.hpp | 56 +- .../photometricStereo/photometricDataIO.cpp | 162 +- .../photometricStereo/photometricDataIO.hpp | 32 +- .../photometricStereo/photometricStereo.cpp | 125 +- .../photometricStereo/photometricStereo.hpp | 46 +- src/aliceVision/prettyprint.hpp | 690 +- src/aliceVision/rig/ResidualError.hpp | 722 +- src/aliceVision/rig/Rig.cpp | 1214 +-- src/aliceVision/rig/Rig.hpp | 198 +- src/aliceVision/robustEstimation/ACRansac.hpp | 352 +- .../robustEstimation/IRansacKernel.hpp | 178 +- src/aliceVision/robustEstimation/ISolver.hpp | 136 +- src/aliceVision/robustEstimation/LORansac.hpp | 578 +- .../robustEstimation/LineKernel.hpp | 400 +- .../robustEstimation/PointFittingKernel.hpp | 230 +- .../PointFittingRansacKernel.hpp | 221 +- src/aliceVision/robustEstimation/Ransac.hpp | 153 +- .../robustEstimation/ScoreEvaluator.hpp | 78 +- .../robustEstimation/acRansac_test.cpp | 366 +- .../robustEstimation/conditioning.cpp | 88 +- .../robustEstimation/conditioning.hpp | 6 +- .../robustEstimation/estimators.hpp | 114 +- .../robustEstimation/leastMedianOfSquares.hpp | 126 +- .../leastMedianOfSquares_test.cpp | 200 +- .../robustEstimation/lineKernel_test.cpp | 31 +- .../robustEstimation/lineTestGenerator.hpp | 192 +- .../robustEstimation/loRansac_test.cpp | 146 +- .../robustEstimation/maxConsensus.hpp | 70 +- .../robustEstimation/maxConsensus_test.cpp | 155 +- .../robustEstimation/randSampling.hpp | 157 +- .../robustEstimation/randSampling_test.cpp | 173 +- .../robustEstimation/ransacTools.hpp | 10 +- .../robustEstimation/ransac_test.cpp | 118 +- src/aliceVision/segmentation/segmentation.cpp | 211 +- src/aliceVision/segmentation/segmentation.hpp | 70 +- src/aliceVision/sensorDB/Datasheet.cpp | 52 +- src/aliceVision/sensorDB/Datasheet.hpp | 44 +- src/aliceVision/sensorDB/parseDatabase.cpp | 58 +- src/aliceVision/sensorDB/parseDatabase.hpp | 4 +- .../sensorDB/parseDatabase_test.cpp | 115 +- src/aliceVision/sfm/FrustumFilter.cpp | 305 +- src/aliceVision/sfm/FrustumFilter.hpp | 47 +- .../sfm/LocalBundleAdjustmentGraph.cpp | 1239 ++- .../sfm/LocalBundleAdjustmentGraph.hpp | 689 +- .../sfm/ResidualErrorConstraintFunctor.hpp | 1545 +-- src/aliceVision/sfm/ResidualErrorFunctor.hpp | 2533 +++-- .../sfm/ResidualErrorRotationPriorFunctor.hpp | 75 +- .../sfm/bundle/BundleAdjustment.hpp | 156 +- .../sfm/bundle/BundleAdjustmentCeres.cpp | 1809 ++- .../sfm/bundle/BundleAdjustmentCeres.hpp | 512 +- .../bundle/BundleAdjustmentSymbolicCeres.cpp | 1279 +-- .../bundle/BundleAdjustmentSymbolicCeres.hpp | 554 +- .../sfm/bundle/bundleAdjustment_test.cpp | 464 +- .../costfunctions/panoramaEquidistant.hpp | 192 +- .../bundle/costfunctions/panoramaPinhole.hpp | 206 +- .../sfm/bundle/costfunctions/projection.hpp | 144 +- .../bundle/costfunctions/projectionSimple.hpp | 104 +- .../bundle/costfunctions/rotationPrior.hpp | 121 +- .../sfm/bundle/manifolds/intrinsics.hpp | 285 +- src/aliceVision/sfm/bundle/manifolds/se3.hpp | 206 +- src/aliceVision/sfm/bundle/manifolds/so2.hpp | 65 +- src/aliceVision/sfm/bundle/manifolds/so3.hpp | 110 +- .../sfm/bundle/manifolds/so3vec.hpp | 208 +- src/aliceVision/sfm/filters.hpp | 123 +- src/aliceVision/sfm/generateReport.cpp | 256 +- src/aliceVision/sfm/generateReport.hpp | 6 +- .../sfm/pipeline/ReconstructionEngine.cpp | 21 +- .../sfm/pipeline/ReconstructionEngine.hpp | 118 +- .../sfm/pipeline/RelativePoseInfo.cpp | 195 +- .../sfm/pipeline/RelativePoseInfo.hpp | 80 +- src/aliceVision/sfm/pipeline/RigSequence.cpp | 508 +- src/aliceVision/sfm/pipeline/RigSequence.hpp | 113 +- .../GlobalSfMRotationAveragingSolver.cpp | 406 +- .../GlobalSfMRotationAveragingSolver.hpp | 91 +- .../GlobalSfMTranslationAveragingSolver.cpp | 1286 ++- .../GlobalSfMTranslationAveragingSolver.hpp | 157 +- .../sfm/pipeline/global/MutexSet.hpp | 60 +- .../global/ReconstructionEngine_globalSfM.cpp | 1141 +- .../global/ReconstructionEngine_globalSfM.hpp | 79 +- .../TranslationTripletKernelACRansac.hpp | 270 +- .../sfm/pipeline/global/globalSfM_test.cpp | 276 +- .../sfm/pipeline/global/reindexGlobalSfM.hpp | 48 +- .../pipeline/localization/SfMLocalizer.cpp | 316 +- .../pipeline/localization/SfMLocalizer.hpp | 117 +- .../sfm/pipeline/pairwiseMatchesIO.hpp | 63 +- .../ReconstructionEngine_panorama.cpp | 424 +- .../ReconstructionEngine_panorama.hpp | 88 +- .../panoramaSfM_equidistant_outliers_test.cpp | 6 +- .../panorama/panoramaSfM_equidistant_test.cpp | 6 +- .../panoramaSfM_radial3_outliers_test.cpp | 6 +- .../panorama/panoramaSfM_radial3_test.cpp | 6 +- .../panorama/panoramaSfM_test_common.hpp | 234 +- src/aliceVision/sfm/pipeline/regionsIO.cpp | 432 +- src/aliceVision/sfm/pipeline/regionsIO.hpp | 7 +- .../sfm/pipeline/relativePoses.hpp | 77 +- .../ReconstructionEngine_sequentialSfM.cpp | 3256 +++--- .../ReconstructionEngine_sequentialSfM.hpp | 715 +- .../sequential/sequentialSfM_test.cpp | 259 +- .../StructureEstimationFromKnownPoses.cpp | 457 +- .../StructureEstimationFromKnownPoses.hpp | 68 +- src/aliceVision/sfm/sfmFilters.cpp | 419 +- src/aliceVision/sfm/sfmFilters.hpp | 28 +- src/aliceVision/sfm/sfmStatistics.cpp | 325 +- src/aliceVision/sfm/sfmStatistics.hpp | 50 +- src/aliceVision/sfm/sfmTriangulation.cpp | 358 +- src/aliceVision/sfm/sfmTriangulation.hpp | 57 +- src/aliceVision/sfm/utils/alignment.cpp | 430 +- src/aliceVision/sfm/utils/alignment.hpp | 218 +- src/aliceVision/sfm/utils/alignment_test.cpp | 108 +- src/aliceVision/sfm/utils/statistics.cpp | 44 +- src/aliceVision/sfm/utils/statistics.hpp | 6 +- src/aliceVision/sfm/utils/syntheticScene.cpp | 288 +- src/aliceVision/sfm/utils/syntheticScene.hpp | 81 +- src/aliceVision/sfmData/CameraPose.hpp | 120 +- src/aliceVision/sfmData/Constraint2D.hpp | 55 +- src/aliceVision/sfmData/HashMapPtr.hpp | 12 +- src/aliceVision/sfmData/Landmark.hpp | 76 +- src/aliceVision/sfmData/Rig.hpp | 287 +- src/aliceVision/sfmData/RotationPrior.hpp | 42 +- src/aliceVision/sfmData/SfMData.cpp | 47 +- src/aliceVision/sfmData/SfMData.hpp | 153 +- src/aliceVision/sfmData/View.cpp | 9 +- src/aliceVision/sfmData/View.hpp | 505 +- src/aliceVision/sfmData/colorize.cpp | 178 +- src/aliceVision/sfmData/colorize.hpp | 4 +- src/aliceVision/sfmData/exif.cpp | 42 +- src/aliceVision/sfmData/exif.hpp | 29 +- src/aliceVision/sfmData/exposureSetting.hpp | 52 +- src/aliceVision/sfmData/imageInfo.cpp | 59 +- src/aliceVision/sfmData/imageInfo.hpp | 1128 +- src/aliceVision/sfmData/sfmData_test.cpp | 77 +- src/aliceVision/sfmData/uid.cpp | 309 +- src/aliceVision/sfmData/uid.hpp | 31 +- src/aliceVision/sfmData/view_test.cpp | 55 +- src/aliceVision/sfmDataIO/AlembicExporter.cpp | 918 +- src/aliceVision/sfmDataIO/AlembicExporter.hpp | 186 +- src/aliceVision/sfmDataIO/AlembicImporter.cpp | 1603 ++- src/aliceVision/sfmDataIO/AlembicImporter.hpp | 31 +- src/aliceVision/sfmDataIO/alembicIO_test.cpp | 274 +- src/aliceVision/sfmDataIO/bafIO.cpp | 173 +- src/aliceVision/sfmDataIO/bafIO.hpp | 8 +- src/aliceVision/sfmDataIO/colmap.cpp | 152 +- src/aliceVision/sfmDataIO/colmap.hpp | 20 +- src/aliceVision/sfmDataIO/colmap_test.cpp | 139 +- src/aliceVision/sfmDataIO/gtIO.cpp | 345 +- src/aliceVision/sfmDataIO/gtIO.hpp | 4 +- src/aliceVision/sfmDataIO/jsonIO.cpp | 1109 +- src/aliceVision/sfmDataIO/jsonIO.hpp | 99 +- src/aliceVision/sfmDataIO/middlebury.cpp | 50 +- src/aliceVision/sfmDataIO/middlebury.hpp | 16 +- src/aliceVision/sfmDataIO/middlebury_test.cpp | 23 +- src/aliceVision/sfmDataIO/plyIO.cpp | 119 +- src/aliceVision/sfmDataIO/plyIO.hpp | 8 +- src/aliceVision/sfmDataIO/sceneSample.cpp | 35 +- src/aliceVision/sfmDataIO/sceneSample.hpp | 6 +- src/aliceVision/sfmDataIO/sfmDataIO.cpp | 289 +- src/aliceVision/sfmDataIO/sfmDataIO.hpp | 37 +- .../sfmDataIO/sfmDataIOCompatibility_test.cpp | 70 +- src/aliceVision/sfmDataIO/sfmDataIO_test.cpp | 87 +- src/aliceVision/sfmDataIO/viewIO.cpp | 145 +- src/aliceVision/sfmDataIO/viewIO.hpp | 84 +- src/aliceVision/sfmMvsUtils/visibility.cpp | 7 +- src/aliceVision/sfmMvsUtils/visibility.hpp | 5 +- .../sphereDetection/sphereDetection.cpp | 33 +- .../sphereDetection/sphereDetection.hpp | 10 +- src/aliceVision/stl/DynamicBitset.hpp | 74 +- src/aliceVision/stl/FlatMap.hpp | 10 +- src/aliceVision/stl/FlatSet.hpp | 9 +- src/aliceVision/stl/bitmask.hpp | 65 +- src/aliceVision/stl/dynamicBitset_test.cpp | 88 +- src/aliceVision/stl/hash.hpp | 11 +- src/aliceVision/stl/indexedSort.hpp | 66 +- src/aliceVision/stl/mapUtils.hpp | 28 +- src/aliceVision/stl/regex.hpp | 7 +- src/aliceVision/system/Logger.cpp | 166 +- src/aliceVision/system/Logger.hpp | 95 +- src/aliceVision/system/Logger_test.cpp | 21 +- src/aliceVision/system/MemoryInfo.cpp | 71 +- src/aliceVision/system/MemoryInfo.hpp | 5 +- src/aliceVision/system/ProgressDisplay.cpp | 37 +- src/aliceVision/system/ProgressDisplay.hpp | 47 +- src/aliceVision/system/Timer.cpp | 104 +- src/aliceVision/system/Timer.hpp | 37 +- src/aliceVision/system/cpu.cpp | 195 +- src/aliceVision/system/cpu.hpp | 6 +- src/aliceVision/system/hardwareContext.cpp | 19 +- src/aliceVision/system/hardwareContext.hpp | 36 +- src/aliceVision/system/main.hpp | 4 +- src/aliceVision/system/nvtx.cpp | 20 +- src/aliceVision/system/nvtx.hpp | 10 +- src/aliceVision/system/system.hpp | 40 +- src/aliceVision/track/Track.hpp | 55 +- src/aliceVision/track/TracksBuilder.cpp | 275 +- src/aliceVision/track/TracksBuilder.hpp | 45 +- src/aliceVision/track/trackIO.cpp | 9 +- src/aliceVision/track/trackIO.hpp | 8 +- src/aliceVision/track/track_test.cpp | 379 +- src/aliceVision/track/tracksUtils.cpp | 384 +- src/aliceVision/track/tracksUtils.hpp | 73 +- src/aliceVision/types.hpp | 21 +- src/aliceVision/unitTest.hpp | 136 +- src/aliceVision/utils/Histogram.hpp | 171 +- src/aliceVision/utils/convert.hpp | 10 +- src/aliceVision/utils/filesIO.hpp | 16 +- src/aliceVision/utils/regexFilter.hpp | 20 +- src/aliceVision/utils/regexFilter_test.cpp | 13 +- src/aliceVision/version.hpp | 28 +- src/aliceVision/voctree/Database.cpp | 198 +- src/aliceVision/voctree/Database.hpp | 250 +- src/aliceVision/voctree/DefaultAllocator.hpp | 10 +- .../voctree/MutableVocabularyTree.hpp | 52 +- src/aliceVision/voctree/SimpleKmeans.hpp | 874 +- src/aliceVision/voctree/TreeBuilder.hpp | 270 +- src/aliceVision/voctree/VocabularyTree.cpp | 425 +- src/aliceVision/voctree/VocabularyTree.hpp | 495 +- src/aliceVision/voctree/databaseIO.hpp | 18 +- src/aliceVision/voctree/descriptorLoader.cpp | 176 +- src/aliceVision/voctree/descriptorLoader.hpp | 12 +- src/aliceVision/voctree/distance.hpp | 43 +- src/aliceVision/voctree/kmeans_test.cpp | 457 +- .../voctree/vocabularyTreeBuild_test.cpp | 109 +- .../voctree/vocabularyTree_test.cpp | 70 +- 771 files changed, 76483 insertions(+), 87765 deletions(-) diff --git a/src/aliceVision/alicevision_omp.hpp b/src/aliceVision/alicevision_omp.hpp index 74c70c4ce7..8475e1b6b1 100644 --- a/src/aliceVision/alicevision_omp.hpp +++ b/src/aliceVision/alicevision_omp.hpp @@ -9,7 +9,7 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENMP) -#include + #include #else using omp_lock_t = char; @@ -19,9 +19,9 @@ inline void omp_set_num_threads(int num_threads) {} inline int omp_get_num_procs() { return 1; } inline void omp_set_nested(int nested) {} -inline void omp_init_lock(omp_lock_t *lock) {} -inline void omp_destroy_lock(omp_lock_t *lock) {} +inline void omp_init_lock(omp_lock_t* lock) {} +inline void omp_destroy_lock(omp_lock_t* lock) {} -inline void omp_set_lock(omp_lock_t *lock) {} -inline void omp_unset_lock(omp_lock_t *lock) {} +inline void omp_set_lock(omp_lock_t* lock) {} +inline void omp_unset_lock(omp_lock_t* lock) {} #endif diff --git a/src/aliceVision/calibration/bestImages.cpp b/src/aliceVision/calibration/bestImages.cpp index 3e5f3f221e..0f796f4da3 100644 --- a/src/aliceVision/calibration/bestImages.cpp +++ b/src/aliceVision/calibration/bestImages.cpp @@ -12,156 +12,156 @@ #include #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { -void precomputeCellIndexes(const std::vector >& imagePoints, +void precomputeCellIndexes(const std::vector>& imagePoints, const cv::Size& imageSize, std::size_t calibGridSize, - std::vector >& cellIndexesPerImage) + std::vector>& cellIndexesPerImage) { - float cellWidth = float(imageSize.width) / float(calibGridSize); - float cellHeight = float(imageSize.height) / float(calibGridSize); - - for (const auto& pointbuf : imagePoints) - { - std::vector imageCellIndexes; - // Points repartition in image - for (cv::Point2f point : pointbuf) + float cellWidth = float(imageSize.width) / float(calibGridSize); + float cellHeight = float(imageSize.height) / float(calibGridSize); + + for (const auto& pointbuf : imagePoints) { - // Compute the index of the point - std::size_t cellPointX = std::floor(point.x / cellWidth); - std::size_t cellPointY = std::floor(point.y / cellHeight); - std::size_t cellIndex = cellPointY * calibGridSize + cellPointX; - imageCellIndexes.push_back(cellIndex); + std::vector imageCellIndexes; + // Points repartition in image + for (cv::Point2f point : pointbuf) + { + // Compute the index of the point + std::size_t cellPointX = std::floor(point.x / cellWidth); + std::size_t cellPointY = std::floor(point.y / cellHeight); + std::size_t cellIndex = cellPointY * calibGridSize + cellPointX; + imageCellIndexes.push_back(cellIndex); + } + cellIndexesPerImage.push_back(imageCellIndexes); } - cellIndexesPerImage.push_back(imageCellIndexes); - } } void computeCellsWeight(const std::vector& imagesIndexes, - const std::vector >& cellIndexesPerImage, + const std::vector>& cellIndexesPerImage, std::size_t calibGridSize, std::map& cellsWeight) { - //Init cell's weight to 0 - for (std::size_t i = 0; i < calibGridSize * calibGridSize; ++i) - cellsWeight[i] = 0; - - // Add weight into cells - for (const auto& imagesIndex : imagesIndexes) - { - std::vector uniqueCellIndexes = cellIndexesPerImage[imagesIndex]; - std::sort(uniqueCellIndexes.begin(), uniqueCellIndexes.end()); - auto last = std::unique(uniqueCellIndexes.begin(), uniqueCellIndexes.end()); - uniqueCellIndexes.erase(last, uniqueCellIndexes.end()); - - for (std::size_t cellIndex : uniqueCellIndexes) + // Init cell's weight to 0 + for (std::size_t i = 0; i < calibGridSize * calibGridSize; ++i) + cellsWeight[i] = 0; + + // Add weight into cells + for (const auto& imagesIndex : imagesIndexes) { - ++cellsWeight[cellIndex]; + std::vector uniqueCellIndexes = cellIndexesPerImage[imagesIndex]; + std::sort(uniqueCellIndexes.begin(), uniqueCellIndexes.end()); + auto last = std::unique(uniqueCellIndexes.begin(), uniqueCellIndexes.end()); + uniqueCellIndexes.erase(last, uniqueCellIndexes.end()); + + for (std::size_t cellIndex : uniqueCellIndexes) + { + ++cellsWeight[cellIndex]; + } } - } } void computeImageScores(const std::vector& inputImagesIndexes, - const std::vector >& cellIndexesPerImage, + const std::vector>& cellIndexesPerImage, const std::map& cellsWeight, - std::vector >& imageScores) + std::vector>& imageScores) { - // Compute the score of each image - for (const auto& inputImagesIndex : inputImagesIndexes) - { - const std::vector& imageCellIndexes = cellIndexesPerImage[inputImagesIndex]; - float imageScore = 0; - for (std::size_t cellIndex : imageCellIndexes) + // Compute the score of each image + for (const auto& inputImagesIndex : inputImagesIndexes) { - imageScore += cellsWeight.at(cellIndex); + const std::vector& imageCellIndexes = cellIndexesPerImage[inputImagesIndex]; + float imageScore = 0; + for (std::size_t cellIndex : imageCellIndexes) + { + imageScore += cellsWeight.at(cellIndex); + } + // Normalize by the number of checker items. + // If the detector support occlusions of the checker the number of items may vary. + imageScore /= float(imageCellIndexes.size()); + imageScores.emplace_back(imageScore, inputImagesIndex); } - // Normalize by the number of checker items. - // If the detector support occlusions of the checker the number of items may vary. - imageScore /= float(imageCellIndexes.size()); - imageScores.emplace_back(imageScore, inputImagesIndex); - } } -void selectBestImages(const std::vector >& imagePoints, +void selectBestImages(const std::vector>& imagePoints, const cv::Size& imageSize, std::size_t maxCalibFrames, std::size_t calibGridSize, std::vector& calibImageScore, std::vector& calibInputFrames, - std::vector >& calibImagePoints, + std::vector>& calibImagePoints, std::vector& remainingImagesIndexes) { - std::vector > cellIndexesPerImage; + std::vector> cellIndexesPerImage; - // Precompute cell indexes per image - precomputeCellIndexes(imagePoints, imageSize, calibGridSize, cellIndexesPerImage); + // Precompute cell indexes per image + precomputeCellIndexes(imagePoints, imageSize, calibGridSize, cellIndexesPerImage); - // Init with 0, 1, 2, ... - remainingImagesIndexes.resize(imagePoints.size()); - std::iota(remainingImagesIndexes.begin(), remainingImagesIndexes.end(), 0); + // Init with 0, 1, 2, ... + remainingImagesIndexes.resize(imagePoints.size()); + std::iota(remainingImagesIndexes.begin(), remainingImagesIndexes.end(), 0); - std::vector bestImagesIndexes; - if (maxCalibFrames < imagePoints.size()) - { - while (bestImagesIndexes.size() < maxCalibFrames ) + std::vector bestImagesIndexes; + if (maxCalibFrames < imagePoints.size()) { - std::map cellsWeight; - std::vector > imageScores; - // Count points in each cell of the grid - if (bestImagesIndexes.empty()) + while (bestImagesIndexes.size() < maxCalibFrames) + { + std::map cellsWeight; + std::vector> imageScores; + // Count points in each cell of the grid + if (bestImagesIndexes.empty()) + computeCellsWeight(remainingImagesIndexes, cellIndexesPerImage, calibGridSize, cellsWeight); + else + computeCellsWeight(bestImagesIndexes, cellIndexesPerImage, calibGridSize, cellsWeight); + + computeImageScores(remainingImagesIndexes, cellIndexesPerImage, cellsWeight, imageScores); + + // Find best score + std::size_t bestImageIndex = std::numeric_limits::max(); + float bestScore = std::numeric_limits::max(); + for (const auto& imageScore : imageScores) + { + if (imageScore.first < bestScore) + { + bestScore = imageScore.first; + bestImageIndex = imageScore.second; + } + } + auto eraseIt = std::find(remainingImagesIndexes.begin(), remainingImagesIndexes.end(), bestImageIndex); + assert(bestScore != std::numeric_limits::max()); + assert(eraseIt != remainingImagesIndexes.end()); + remainingImagesIndexes.erase(eraseIt); + bestImagesIndexes.push_back(bestImageIndex); + calibImageScore.push_back(bestScore); + } + } + else + { + ALICEVISION_LOG_DEBUG("Info: Less valid frames (" << imagePoints.size() << ") than specified maxCalibFrames (" << maxCalibFrames << ")."); + bestImagesIndexes.resize(imagePoints.size()); + std::iota(bestImagesIndexes.begin(), bestImagesIndexes.end(), 0); + + std::map cellsWeight; computeCellsWeight(remainingImagesIndexes, cellIndexesPerImage, calibGridSize, cellsWeight); - else - computeCellsWeight(bestImagesIndexes, cellIndexesPerImage, calibGridSize, cellsWeight); - computeImageScores(remainingImagesIndexes, cellIndexesPerImage, cellsWeight, imageScores); + std::vector> imageScores; + computeImageScores(remainingImagesIndexes, cellIndexesPerImage, cellsWeight, imageScores); - // Find best score - std::size_t bestImageIndex = std::numeric_limits::max(); - float bestScore = std::numeric_limits::max(); - for (const auto& imageScore: imageScores) - { - if (imageScore.first < bestScore) + for (auto imgScore : imageScores) { - bestScore = imageScore.first; - bestImageIndex = imageScore.second; + calibImageScore.push_back(imgScore.first); } - } - auto eraseIt = std::find(remainingImagesIndexes.begin(), remainingImagesIndexes.end(), bestImageIndex); - assert(bestScore != std::numeric_limits::max()); - assert(eraseIt != remainingImagesIndexes.end()); - remainingImagesIndexes.erase(eraseIt); - bestImagesIndexes.push_back(bestImageIndex); - calibImageScore.push_back(bestScore); } - } - else - { - ALICEVISION_LOG_DEBUG("Info: Less valid frames (" << imagePoints.size() << ") than specified maxCalibFrames (" << maxCalibFrames << ")."); - bestImagesIndexes.resize(imagePoints.size()); - std::iota(bestImagesIndexes.begin(), bestImagesIndexes.end(), 0); - - std::map cellsWeight; - computeCellsWeight(remainingImagesIndexes, cellIndexesPerImage, calibGridSize, cellsWeight); - - std::vector > imageScores; - computeImageScores(remainingImagesIndexes, cellIndexesPerImage, cellsWeight, imageScores); - - for(auto imgScore: imageScores) - { - calibImageScore.push_back(imgScore.first); - } - } - assert(bestImagesIndexes.size() == std::min(maxCalibFrames, imagePoints.size())); + assert(bestImagesIndexes.size() == std::min(maxCalibFrames, imagePoints.size())); - for (const auto& origI : bestImagesIndexes) - { - calibImagePoints.push_back(imagePoints[origI]); - calibInputFrames.push_back(origI); - } + for (const auto& origI : bestImagesIndexes) + { + calibImagePoints.push_back(imagePoints[origI]); + calibInputFrames.push_back(origI); + } } -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/bestImages.hpp b/src/aliceVision/calibration/bestImages.hpp index 636061ea22..af134b09a8 100644 --- a/src/aliceVision/calibration/bestImages.hpp +++ b/src/aliceVision/calibration/bestImages.hpp @@ -10,8 +10,8 @@ #include #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { /** * @brief This function computes cell indexes per image. @@ -21,10 +21,10 @@ namespace calibration{ * @param[in] calibGridSize The number of cells per each image dimension. * @param[out] cellIndexesPerImage The id of the cell for each point of the image sequence. */ -void precomputeCellIndexes(const std::vector >& imagePoints, +void precomputeCellIndexes(const std::vector>& imagePoints, const cv::Size& imageSize, std::size_t calibGridSize, - std::vector >& cellIndexesPerImage); + std::vector>& cellIndexesPerImage); /** * @brief This function counts the number of points in each cell of the grid. @@ -35,7 +35,7 @@ void precomputeCellIndexes(const std::vector >& imagePo * @param[out] cellsWeight The number of points for each cell id. */ void computeCellsWeight(const std::vector& imagesIndexes, - const std::vector >& cellIndexesPerImage, + const std::vector>& cellIndexesPerImage, std::size_t calibGridSize, std::map& cellsWeight); @@ -48,9 +48,9 @@ void computeCellsWeight(const std::vector& imagesIndexes, * @param[out] imageScores The score of each image. */ void computeImageScores(const std::vector& inputImagesIndexes, - const std::vector >& cellIndexesPerImage, + const std::vector>& cellIndexesPerImage, const std::map& cellsWeight, - std::vector >& imageScores); + std::vector>& imageScores); /** * @brief This function selects the best images based on distribution of calibration landmarks in images. @@ -64,15 +64,14 @@ void computeImageScores(const std::vector& inputImagesIndexes, * @param[out] calibImagePoints Set of points for each selected image. * @param[out] remainingImagesIndexes Indexes of non-selected images from validFrames. */ -void selectBestImages(const std::vector >& imagePoints, +void selectBestImages(const std::vector>& imagePoints, const cv::Size& imageSize, std::size_t maxCalibFrames, std::size_t calibGridSize, std::vector& calibImageScore, std::vector& calibInputFrames, - std::vector >& calibImagePoints, + std::vector>& calibImagePoints, std::vector& remainingImagesIndexes); -}//namespace calibration -}//namespace aliceVision - +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/calibration.cpp b/src/aliceVision/calibration/calibration.cpp index e0e3cd5764..3630fb88f4 100644 --- a/src/aliceVision/calibration/calibration.cpp +++ b/src/aliceVision/calibration/calibration.cpp @@ -15,35 +15,36 @@ #include #include -namespace aliceVision{ -namespace calibration{ - -double computeReprojectionErrors(const std::vector >& objectPoints, - const std::vector >& imagePoints, - const std::vector& rvecs, const std::vector& tvecs, - const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, +namespace aliceVision { +namespace calibration { + +double computeReprojectionErrors(const std::vector>& objectPoints, + const std::vector>& imagePoints, + const std::vector& rvecs, + const std::vector& tvecs, + const cv::Mat& cameraMatrix, + const cv::Mat& distCoeffs, std::vector& perViewErrors) { - std::vector imagePoints2; - int totalPoints = 0; - double totalErr = 0; - perViewErrors.resize(objectPoints.size()); - - for (std::size_t i = 0; i < (int) objectPoints.size(); i++) - { - cv::projectPoints(cv::Mat(objectPoints[i]), rvecs[i], tvecs[i], - cameraMatrix, distCoeffs, imagePoints2); - const double err = cv::norm(cv::Mat(imagePoints[i]), cv::Mat(imagePoints2), cv::NORM_L2); - const std::size_t n = (int) objectPoints[i].size(); - perViewErrors[i] = (float) std::sqrt(err * err / n); - totalErr += err*err; - totalPoints += n; - } - return std::sqrt(totalErr / totalPoints); + std::vector imagePoints2; + int totalPoints = 0; + double totalErr = 0; + perViewErrors.resize(objectPoints.size()); + + for (std::size_t i = 0; i < (int)objectPoints.size(); i++) + { + cv::projectPoints(cv::Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2); + const double err = cv::norm(cv::Mat(imagePoints[i]), cv::Mat(imagePoints2), cv::NORM_L2); + const std::size_t n = (int)objectPoints[i].size(); + perViewErrors[i] = (float)std::sqrt(err * err / n); + totalErr += err * err; + totalPoints += n; + } + return std::sqrt(totalErr / totalPoints); } -bool runCalibration(const std::vector >& imagePoints, - const std::vector >& objectPoints, +bool runCalibration(const std::vector>& imagePoints, + const std::vector>& objectPoints, const cv::Size& imageSize, float aspectRatio, int cvCalibFlags, @@ -54,143 +55,147 @@ bool runCalibration(const std::vector >& imagePoints, std::vector& reprojErrs, double& totalAvgErr) { - rvecs.resize(0); - tvecs.resize(0); - reprojErrs.resize(0); - cameraMatrix = cv::Mat::eye(3, 3, CV_64F); - if (cvCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) - cameraMatrix.at(0, 0) = aspectRatio; + rvecs.resize(0); + tvecs.resize(0); + reprojErrs.resize(0); + cameraMatrix = cv::Mat::eye(3, 3, CV_64F); + if (cvCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) + cameraMatrix.at(0, 0) = aspectRatio; - distCoeffs = cv::Mat::zeros(8, 1, CV_64F); + distCoeffs = cv::Mat::zeros(8, 1, CV_64F); - system::Timer durationrC; + system::Timer durationrC; - const double rms = cv::calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, - distCoeffs, rvecs, tvecs, cvCalibFlags | cv::CALIB_FIX_K4 | cv::CALIB_FIX_K5 | cv::CALIB_FIX_K6); - ALICEVISION_LOG_DEBUG("\tcalibrateCamera duration: " << system::prettyTime(durationrC.elapsedMs())); + const double rms = cv::calibrateCamera(objectPoints, + imagePoints, + imageSize, + cameraMatrix, + distCoeffs, + rvecs, + tvecs, + cvCalibFlags | cv::CALIB_FIX_K4 | cv::CALIB_FIX_K5 | cv::CALIB_FIX_K6); + ALICEVISION_LOG_DEBUG("\tcalibrateCamera duration: " << system::prettyTime(durationrC.elapsedMs())); - ALICEVISION_LOG_DEBUG("\tRMS error reported by calibrateCamera: " << rms); - bool ok = cv::checkRange(cameraMatrix) && cv::checkRange(distCoeffs); + ALICEVISION_LOG_DEBUG("\tRMS error reported by calibrateCamera: " << rms); + bool ok = cv::checkRange(cameraMatrix) && cv::checkRange(distCoeffs); - durationrC.reset(); + durationrC.reset(); - totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints, - rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs); + totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints, rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs); - ALICEVISION_LOG_DEBUG("\tcomputeReprojectionErrors duration: " << durationrC.elapsedMs() << "ms"); + ALICEVISION_LOG_DEBUG("\tcomputeReprojectionErrors duration: " << durationrC.elapsedMs() << "ms"); - return ok; + return ok; } -bool calibrationIterativeOptimization( - const cv::Size& imageSize, - float aspectRatio, - int cvCalibFlags, - cv::Mat& cameraMatrix, - cv::Mat& distCoeffs, - std::vector& rvecs, - std::vector& tvecs, - std::vector& reprojErrs, - double& totalAvgErr, - const double& maxTotalAvgErr, - const std::size_t& minInputFrames, - std::vector& calibInputFrames, - std::vector >& calibImagePoints, - std::vector >& calibObjectPoints, - std::vector& calibImageScore, - std::vector& rejectInputFrames) +bool calibrationIterativeOptimization(const cv::Size& imageSize, + float aspectRatio, + int cvCalibFlags, + cv::Mat& cameraMatrix, + cv::Mat& distCoeffs, + std::vector& rvecs, + std::vector& tvecs, + std::vector& reprojErrs, + double& totalAvgErr, + const double& maxTotalAvgErr, + const std::size_t& minInputFrames, + std::vector& calibInputFrames, + std::vector>& calibImagePoints, + std::vector>& calibObjectPoints, + std::vector& calibImageScore, + std::vector& rejectInputFrames) { - std::size_t calibIteration = 0; - bool calibSucceeded = false; - do - { - // Estimate the camera calibration - ALICEVISION_LOG_DEBUG("Calibration iteration " << calibIteration << " with " << calibImagePoints.size() << " frames."); - calibSucceeded = runCalibration(calibImagePoints, calibObjectPoints, imageSize, - aspectRatio, cvCalibFlags, cameraMatrix, distCoeffs, - rvecs, tvecs, reprojErrs, totalAvgErr); - - if (totalAvgErr <= maxTotalAvgErr) - { - ALICEVISION_LOG_DEBUG("The calibration succeed with an average error that respects the maxTotalAvgErr."); - break; - } - else if (calibInputFrames.size() < minInputFrames) + std::size_t calibIteration = 0; + bool calibSucceeded = false; + do { - ALICEVISION_LOG_DEBUG("Not enough valid input image (" << calibInputFrames.size() << ") to continue the refinement."); - break; - } - else if (calibSucceeded) - { - // Filter the successfully calibrated images to keep the best ones - // in order to refine the calibration. - // For instance, remove blurry images which introduce imprecision. - - std::vector globalScores; - for (std::size_t i = 0; i < calibInputFrames.size(); ++i) - { - globalScores.push_back(reprojErrs[i] * calibImageScore[i]); - } - - const auto minMaxError = std::minmax_element(globalScores.begin(), globalScores.end()); - ALICEVISION_LOG_DEBUG("\terror min: " << *minMaxError.first << ", max: " << *minMaxError.second); - if (*minMaxError.first == *minMaxError.second) - { - ALICEVISION_LOG_DEBUG("Same error on all images: " << *minMaxError.first); - for (float f : globalScores) - ALICEVISION_LOG_DEBUG("f: " << f); - break; - } - // We only keep the frames with N% of the largest error. - const float errorThreshold = *minMaxError.first + 0.8f * (*minMaxError.second - *minMaxError.first); - std::vector > filteredImagePoints; - std::vector > filteredObjectPoints; - std::vector filteredInputFrames; - std::vector tmpRejectInputFrames; - std::vector filteredImageScores; - - for (std::size_t i = 0; i < calibImagePoints.size(); ++i) - { - if (globalScores[i] < errorThreshold) + // Estimate the camera calibration + ALICEVISION_LOG_DEBUG("Calibration iteration " << calibIteration << " with " << calibImagePoints.size() << " frames."); + calibSucceeded = runCalibration( + calibImagePoints, calibObjectPoints, imageSize, aspectRatio, cvCalibFlags, cameraMatrix, distCoeffs, rvecs, tvecs, reprojErrs, totalAvgErr); + + if (totalAvgErr <= maxTotalAvgErr) { - filteredImagePoints.push_back(calibImagePoints[i]); - filteredObjectPoints.push_back(calibObjectPoints[i]); - filteredInputFrames.push_back(calibInputFrames[i]); - filteredImageScores.push_back(calibImageScore[i]); + ALICEVISION_LOG_DEBUG("The calibration succeed with an average error that respects the maxTotalAvgErr."); + break; } - else + else if (calibInputFrames.size() < minInputFrames) { - // We collect rejected frames for debug purpose - tmpRejectInputFrames.push_back(calibInputFrames[i]); + ALICEVISION_LOG_DEBUG("Not enough valid input image (" << calibInputFrames.size() << ") to continue the refinement."); + break; } - } - if (filteredImagePoints.size() < minInputFrames) - { - ALICEVISION_LOG_DEBUG("Not enough filtered input images (filtered: " << filteredImagePoints.size() << ", rejected:" << tmpRejectInputFrames.size() << ") to continue the refinement."); - break; - } - if (calibImagePoints.size() == filteredImagePoints.size()) - { - // Convergence reached - ALICEVISION_LOG_DEBUG("Convergence reached."); - break; - } - calibImagePoints.swap(filteredImagePoints); - calibObjectPoints.swap(filteredObjectPoints); - calibInputFrames.swap(filteredInputFrames); - calibImageScore.swap(filteredImageScores); - rejectInputFrames.insert(rejectInputFrames.end(), tmpRejectInputFrames.begin(), tmpRejectInputFrames.end()); - } - ++calibIteration; - } - while (calibSucceeded); + else if (calibSucceeded) + { + // Filter the successfully calibrated images to keep the best ones + // in order to refine the calibration. + // For instance, remove blurry images which introduce imprecision. + + std::vector globalScores; + for (std::size_t i = 0; i < calibInputFrames.size(); ++i) + { + globalScores.push_back(reprojErrs[i] * calibImageScore[i]); + } + + const auto minMaxError = std::minmax_element(globalScores.begin(), globalScores.end()); + ALICEVISION_LOG_DEBUG("\terror min: " << *minMaxError.first << ", max: " << *minMaxError.second); + if (*minMaxError.first == *minMaxError.second) + { + ALICEVISION_LOG_DEBUG("Same error on all images: " << *minMaxError.first); + for (float f : globalScores) + ALICEVISION_LOG_DEBUG("f: " << f); + break; + } + // We only keep the frames with N% of the largest error. + const float errorThreshold = *minMaxError.first + 0.8f * (*minMaxError.second - *minMaxError.first); + std::vector> filteredImagePoints; + std::vector> filteredObjectPoints; + std::vector filteredInputFrames; + std::vector tmpRejectInputFrames; + std::vector filteredImageScores; + + for (std::size_t i = 0; i < calibImagePoints.size(); ++i) + { + if (globalScores[i] < errorThreshold) + { + filteredImagePoints.push_back(calibImagePoints[i]); + filteredObjectPoints.push_back(calibObjectPoints[i]); + filteredInputFrames.push_back(calibInputFrames[i]); + filteredImageScores.push_back(calibImageScore[i]); + } + else + { + // We collect rejected frames for debug purpose + tmpRejectInputFrames.push_back(calibInputFrames[i]); + } + } + if (filteredImagePoints.size() < minInputFrames) + { + ALICEVISION_LOG_DEBUG("Not enough filtered input images (filtered: " << filteredImagePoints.size() + << ", rejected:" << tmpRejectInputFrames.size() + << ") to continue the refinement."); + break; + } + if (calibImagePoints.size() == filteredImagePoints.size()) + { + // Convergence reached + ALICEVISION_LOG_DEBUG("Convergence reached."); + break; + } + calibImagePoints.swap(filteredImagePoints); + calibObjectPoints.swap(filteredObjectPoints); + calibInputFrames.swap(filteredInputFrames); + calibImageScore.swap(filteredImageScores); + rejectInputFrames.insert(rejectInputFrames.end(), tmpRejectInputFrames.begin(), tmpRejectInputFrames.end()); + } + ++calibIteration; + } while (calibSucceeded); - ALICEVISION_LOG_DEBUG("Calibration done with " << calibIteration << " iterations."); - ALICEVISION_LOG_DEBUG("Average reprojection error is " << totalAvgErr); - ALICEVISION_LOG_DEBUG((calibSucceeded ? "Calibration succeeded" : "Calibration failed")); + ALICEVISION_LOG_DEBUG("Calibration done with " << calibIteration << " iterations."); + ALICEVISION_LOG_DEBUG("Average reprojection error is " << totalAvgErr); + ALICEVISION_LOG_DEBUG((calibSucceeded ? "Calibration succeeded" : "Calibration failed")); - return calibSucceeded; + return calibSucceeded; } -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/calibration.hpp b/src/aliceVision/calibration/calibration.hpp index 4e0b3705c4..9757ce5964 100644 --- a/src/aliceVision/calibration/calibration.hpp +++ b/src/aliceVision/calibration/calibration.hpp @@ -9,8 +9,8 @@ #include #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { /** * @brief This function computes the average of the reprojection errors. @@ -24,10 +24,12 @@ namespace calibration{ * @param[out] perViewErrors The reprojection errors for each image. * @return The average of the reprojection errors. */ -double computeReprojectionErrors(const std::vector >& objectPoints, - const std::vector >& imagePoints, - const std::vector& rvecs, const std::vector& tvecs, - const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, +double computeReprojectionErrors(const std::vector>& objectPoints, + const std::vector>& imagePoints, + const std::vector& rvecs, + const std::vector& tvecs, + const cv::Mat& cameraMatrix, + const cv::Mat& distCoeffs, std::vector& perViewErrors); /** @@ -46,8 +48,8 @@ double computeReprojectionErrors(const std::vector >& o * @param[out] totalAvgErr The average of the reprojection errors. * @return True if the calibration is a success, otherwise false. */ -bool runCalibration(const std::vector >& imagePoints, - const std::vector >& objectPoints, +bool runCalibration(const std::vector>& imagePoints, + const std::vector>& objectPoints, const cv::Size& imageSize, float aspectRatio, int cvCalibFlags, @@ -91,10 +93,10 @@ bool calibrationIterativeOptimization(const cv::Size& imageSize, const double& maxTotalAvgErr, const std::size_t& minInputFrames, std::vector& calibInputFrames, - std::vector >& calibImagePoints, - std::vector >& calibObjectPoints, + std::vector>& calibImagePoints, + std::vector>& calibObjectPoints, std::vector& calibImageScore, std::vector& rejectInputFrames); -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/checkerDetector.cpp b/src/aliceVision/calibration/checkerDetector.cpp index 06d12695f2..d81644ca7d 100644 --- a/src/aliceVision/calibration/checkerDetector.cpp +++ b/src/aliceVision/calibration/checkerDetector.cpp @@ -16,13 +16,12 @@ #include // TODO: to remove when moving to eigen 3.4 -namespace Eigen -{ -template +namespace Eigen { +template using Vector = Matrix; -template +template using RowVector = Matrix; -} // namespace Eigen +} // namespace Eigen namespace aliceVision { namespace calibration { @@ -55,10 +54,10 @@ bool CheckerDetector::process(const image::Image& source, bool { bool keep = true; - for (const IntermediateCorner & oc : allCorners) + for (const IntermediateCorner& oc : allCorners) { const double dist = (oc.center - c).norm(); - + if (dist < distMerge) { keep = false; @@ -66,7 +65,8 @@ bool CheckerDetector::process(const image::Image& source, bool } } - if (!keep) continue; + if (!keep) + continue; allCorners.emplace_back(c, scale); } @@ -85,12 +85,12 @@ bool CheckerDetector::process(const image::Image& source, bool const double distSamePosition = 2.0; for (std::size_t i = 0; i < fittedCorners.size(); ++i) { - const CheckerBoardCorner & ci = fittedCorners[i]; + const CheckerBoardCorner& ci = fittedCorners[i]; bool found = false; for (std::size_t j = i + 1; j < fittedCorners.size(); ++j) { - const CheckerBoardCorner & cj = fittedCorners[j]; + const CheckerBoardCorner& cj = fittedCorners[j]; if ((ci.center - cj.center).norm() < distSamePosition) { @@ -147,10 +147,8 @@ bool CheckerDetector::process(const image::Image& source, bool return true; } - - -bool CheckerDetector::processLevel(std::vector & corners, const image::Image & input, double scale) const -{ +bool CheckerDetector::processLevel(std::vector& corners, const image::Image& input, double scale) const +{ // Get resized size const unsigned int w = input.Width(); const unsigned int h = input.Height(); @@ -166,7 +164,6 @@ bool CheckerDetector::processLevel(std::vector & corners, const image::Ima oiio::ImageBuf outBuf(imageSpecResized, rescaled.data()); oiio::ImageBufAlgo::resize(outBuf, inBuf); - // Normalize image between 0 and 1 image::Image normalized; normalizeImage(normalized, rescaled); @@ -182,16 +179,16 @@ bool CheckerDetector::processLevel(std::vector & corners, const image::Ima pruneCorners(corners, refinedCorners, normalized); - for (Vec2 & v : corners) + for (Vec2& v : corners) { v.x() /= scale; v.y() /= scale; - } + } return true; } -void CheckerDetector::pruneCorners(std::vector & prunedCorners, const std::vector & rawCorners, const image::Image & input) const +void CheckerDetector::pruneCorners(std::vector& prunedCorners, const std::vector& rawCorners, const image::Image& input) const { const image::Sampler2d sampler; const int radius = 5; @@ -199,7 +196,7 @@ void CheckerDetector::pruneCorners(std::vector & prunedCorners, const std: float vector[samples]; - for (const Vec2 & corner : rawCorners) + for (const Vec2& corner : rawCorners) { float min = std::numeric_limits::max(); float max = std::numeric_limits::min(); @@ -229,7 +226,8 @@ void CheckerDetector::pruneCorners(std::vector & prunedCorners, const std: for (int sample = 0; sample < samples; ++sample) { int next = sample + 1; - if (next == samples) next = 0; + if (next == samples) + next = 0; float test = vector[sample] * vector[next]; if (test < 0.0) @@ -239,17 +237,18 @@ void CheckerDetector::pruneCorners(std::vector & prunedCorners, const std: } // Count should be 4 to ensure the corner corresponds to an intersection of black and white tiles - if (count != 4) continue; + if (count != 4) + continue; prunedCorners.push_back(corner); } } -void CheckerDetector::normalizeImage(image::Image & output, const image::Image & input) const +void CheckerDetector::normalizeImage(image::Image& output, const image::Image& input) const { float min = 0.0f, max = 0.0f; getMinMax(min, max, input); - + output.resize(input.Width(), input.Height()); for (int y = 0; y < output.Height(); ++y) { @@ -260,7 +259,7 @@ void CheckerDetector::normalizeImage(image::Image & output, const image:: } } -void CheckerDetector::computeHessianResponse(image::Image & output, const image::Image & input) const +void CheckerDetector::computeHessianResponse(image::Image& output, const image::Image& input) const { image::Image smoothed; image::ImageGaussianFilter(input, 1.5, smoothed, 2); @@ -286,7 +285,7 @@ void CheckerDetector::computeHessianResponse(image::Image & output, const } } -void CheckerDetector::extractCorners(std::vector & rawCorners, const image::Image & hessianResponse) const +void CheckerDetector::extractCorners(std::vector& rawCorners, const image::Image& hessianResponse) const { float min = 0.0f, max = 0.0f; getMinMax(min, max, hessianResponse); @@ -315,7 +314,8 @@ void CheckerDetector::extractCorners(std::vector & rawCorners, const image } } - if (!isMaximal) continue; + if (!isMaximal) + continue; // Peak must be higher than a global threshold if (val > threshold) @@ -326,7 +326,7 @@ void CheckerDetector::extractCorners(std::vector & rawCorners, const image } } -void CheckerDetector::getMinMax(float &min, float &max, const image::Image & input) const +void CheckerDetector::getMinMax(float& min, float& max, const image::Image& input) const { min = std::numeric_limits::max(); max = std::numeric_limits::min(); @@ -340,7 +340,7 @@ void CheckerDetector::getMinMax(float &min, float &max, const image::Image & refinedCorners, const std::vector & rawCorners, const image::Image & input) const +void CheckerDetector::refineCorners(std::vector& refinedCorners, const std::vector& rawCorners, const image::Image& input) const { image::Image gx, gy; ImageXDerivative(input, gx, true); @@ -348,12 +348,16 @@ void CheckerDetector::refineCorners(std::vector & refinedCorners, const st const int radius = 5; - for (const Vec2 & pt: rawCorners) + for (const Vec2& pt : rawCorners) { - if (pt.x() < radius) continue; - if (pt.y() < radius) continue; - if (pt.x() >= gx.Width() - radius) continue; - if (pt.y() >= gx.Height() - radius) continue; + if (pt.x() < radius) + continue; + if (pt.y() < radius) + continue; + if (pt.x() >= gx.Width() - radius) + continue; + if (pt.y() >= gx.Height() - radius) + continue; Eigen::Matrix2d A = Eigen::Matrix2d::Zero(); Eigen::Vector2d b = Eigen::Vector2d::Zero(); @@ -363,14 +367,16 @@ void CheckerDetector::refineCorners(std::vector & refinedCorners, const st for (int l = -radius; l <= radius; ++l) { - if (l == 0 && k == 0) continue; + if (l == 0 && k == 0) + continue; const int j = pt.x() + l; float du = gx(i, j); float dv = gy(i, j); const float norm = std::sqrt(du * du + dv * dv); - if (norm < 0.01) continue; + if (norm < 0.01) + continue; du /= norm; dv /= norm; @@ -389,13 +395,16 @@ void CheckerDetector::refineCorners(std::vector & refinedCorners, const st // Make sure the update is coherent const double dist = (update - pt).norm(); - if (dist > radius) continue; + if (dist > radius) + continue; refinedCorners.push_back(update); } } -void CheckerDetector::fitCorners(std::vector & refinedCorners, const std::vector & rawCorners, const image::Image & input) const +void CheckerDetector::fitCorners(std::vector& refinedCorners, + const std::vector& rawCorners, + const image::Image& input) const { // Build kernel const int radius = 4; @@ -427,7 +436,7 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner const image::Sampler2d sampler; - for (const IntermediateCorner & sc : rawCorners) + for (const IntermediateCorner& sc : rawCorners) { Vec2 corner = sc.center; bool isValid = true; @@ -438,7 +447,7 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner for (int iter = 0; iter < 20; ++iter) { AtA.fill(0); - Atb.fill(0); + Atb.fill(0); for (int i = -radius; i <= radius; ++i) { @@ -462,14 +471,14 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner Eigen::Vector x = AtA.inverse() * Atb; - //f(x,y) = a1x**2 + a2xy + a3y**2 + a4x + a5y + a6 - //df(x)/dx = 2a1x + a2y + a4 - //df(x)/dy = 2a3y + a2x + a5 - //H = |2a1 a2 | - // |a2 2a3| - //det(H) = 2*2*a1*a3 - 2*a2 - //inv(H) = |2a3 -a2|/det(H) - // |-a2 2a1| + // f(x,y) = a1x**2 + a2xy + a3y**2 + a4x + a5y + a6 + // df(x)/dx = 2a1x + a2y + a4 + // df(x)/dy = 2a3y + a2x + a5 + // H = |2a1 a2 | + // |a2 2a3| + // det(H) = 2*2*a1*a3 - 2*a2 + // inv(H) = |2a3 -a2|/det(H) + // |-a2 2a1| const double a1 = x(0); const double a2 = x(1); const double a3 = x(2); @@ -512,7 +521,8 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner break; } - if (update.norm() < 1e-5) break; + if (update.norm() < 1e-5) + break; } if (isValid) @@ -521,7 +531,7 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner } } - for (CheckerBoardCorner & corner : refinedCorners) + for (CheckerBoardCorner& corner : refinedCorners) { const double cx = corner.center(0); const double cy = corner.center(1); @@ -534,8 +544,9 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner const int di = corner.center(1) + i; for (int j = -radius; j <= radius; ++j) { - if (i == j) continue; - + if (i == j) + continue; + const int dj = corner.center(0) + j; Eigen::Vector rowA; @@ -548,10 +559,10 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner AtA += rowA * rowA.transpose(); Atb += rowA * (2.0 * sampler(filtered, di, dj) - 1.0); - } + } } - - Eigen::Vector x = AtA.inverse() * Atb; + + Eigen::Vector x = AtA.inverse() * Atb; const double c1 = x(0); const double c2 = x(1); const double c3 = x(2); @@ -568,7 +579,8 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner const double phi = acos(cos2phi) * 0.5; const double theta = atan2(sin2theta, cos2theta) * 0.5; - if (theta != theta || phi != phi) continue; + if (theta != theta || phi != phi) + continue; double angle = theta - phi; corner.dir1(0) = cos(angle); @@ -580,7 +592,7 @@ void CheckerDetector::fitCorners(std::vector & refinedCorner } } -IndexT CheckerDetector::findClosestCorner(const Vec2 & center, const Vec2 & dir, const std::vector & refinedCorners) const +IndexT CheckerDetector::findClosestCorner(const Vec2& center, const Vec2& dir, const std::vector& refinedCorners) const { IndexT ret = UndefinedIndexT; double min = std::numeric_limits::max(); @@ -588,12 +600,13 @@ IndexT CheckerDetector::findClosestCorner(const Vec2 & center, const Vec2 & dir, for (IndexT cid = 0; cid < refinedCorners.size(); ++cid) { - const CheckerBoardCorner & c = refinedCorners[cid]; + const CheckerBoardCorner& c = refinedCorners[cid]; Vec2 diff = c.center - center; const double dist = diff.norm(); - if (dist < 5.0) continue; - + if (dist < 5.0) + continue; + // Check that corner belongs to cone starting at center, directed at dir, with angle PI/8 if (std::abs(std::atan2(diff.y(), diff.x()) - std::atan2(dir.y(), dir.x())) > boost::math::constants::pi() * 0.125) { @@ -611,10 +624,10 @@ IndexT CheckerDetector::findClosestCorner(const Vec2 & center, const Vec2 & dir, return ret; } -bool CheckerDetector::getSeedCheckerboard(CheckerBoard & board, IndexT seed, const std::vector & refinedCorners) const +bool CheckerDetector::getSeedCheckerboard(CheckerBoard& board, IndexT seed, const std::vector& refinedCorners) const { IndexT right, bottom, bottom_right, check; - const CheckerBoardCorner & referenceCorner = refinedCorners[seed]; + const CheckerBoardCorner& referenceCorner = refinedCorners[seed]; right = findClosestCorner(referenceCorner.center, referenceCorner.dir1, refinedCorners); if (right == UndefinedIndexT) @@ -628,15 +641,15 @@ bool CheckerDetector::getSeedCheckerboard(CheckerBoard & board, IndexT seed, con return false; } - const CheckerBoardCorner & bottomCorner = refinedCorners[bottom]; - const CheckerBoardCorner & rightCorner = refinedCorners[right]; + const CheckerBoardCorner& bottomCorner = refinedCorners[bottom]; + const CheckerBoardCorner& rightCorner = refinedCorners[right]; bottom_right = findClosestCorner(bottomCorner.center, bottomCorner.dir2, refinedCorners); if (bottom_right == UndefinedIndexT) { return false; } - + check = findClosestCorner(rightCorner.center, -rightCorner.dir1, refinedCorners); if (check == UndefinedIndexT) { @@ -657,9 +670,8 @@ bool CheckerDetector::getSeedCheckerboard(CheckerBoard & board, IndexT seed, con return true; } - -bool CheckerDetector::getCandidates(std::vector & candidates, const CheckerBoard & board, bool inside) const -{ +bool CheckerDetector::getCandidates(std::vector& candidates, const CheckerBoard& board, bool inside) const +{ if (board.rows() < 2) { return false; @@ -676,14 +688,18 @@ bool CheckerDetector::getCandidates(std::vector & candidates, const Ch for (int row = 0; row < board.rows() - 2; ++row) { - if (board(row, col) != UndefinedIndexT) break; + if (board(row, col) != UndefinedIndexT) + break; srow = row; } - if (srow < 0) continue; - if (board(srow + 1, col) == UndefinedIndexT) continue; - if (board(srow + 2, col) == UndefinedIndexT) continue; + if (srow < 0) + continue; + if (board(srow + 1, col) == UndefinedIndexT) + continue; + if (board(srow + 2, col) == UndefinedIndexT) + continue; p.row = srow; candidates.push_back(p); @@ -699,11 +715,14 @@ bool CheckerDetector::getCandidates(std::vector & candidates, const Ch for (int row = board.rows() - 3; row >= 0; --row) { - if (board(row, col) != UndefinedIndexT) continue; + if (board(row, col) != UndefinedIndexT) + continue; - if (board(row + 1, col) == UndefinedIndexT) continue; + if (board(row + 1, col) == UndefinedIndexT) + continue; - if (board(row + 2, col) == UndefinedIndexT) continue; + if (board(row + 2, col) == UndefinedIndexT) + continue; p.row = row; candidates.push_back(p); @@ -714,13 +733,12 @@ bool CheckerDetector::getCandidates(std::vector & candidates, const Ch return true; } - -bool CheckerDetector::growIterationUp(CheckerBoard & board, const std::vector & refinedCorners, bool nested) const +bool CheckerDetector::growIterationUp(CheckerBoard& board, const std::vector& refinedCorners, bool nested) const { const double referenceEnergy = computeEnergy(board, refinedCorners); // Enlarge board and fill with empty indices - CheckerBoard nboard(board.rows() + 1, board.cols()); + CheckerBoard nboard(board.rows() + 1, board.cols()); nboard.fill(UndefinedIndexT); nboard.block(1, 0, board.rows(), board.cols()) = board; board = nboard; @@ -741,25 +759,25 @@ bool CheckerDetector::growIterationUp(CheckerBoard & board, const std::vector::max(); for (std::size_t id = 0; id < refinedCorners.size(); ++id) - { - if (used.find(id) != used.end()) continue; + { + if (used.find(id) != used.end()) + continue; - const CheckerBoardCorner & ck = refinedCorners[id]; + const CheckerBoardCorner& ck = refinedCorners[id]; - const double E = - ((ci.center - cj.center) + (ck.center - cj.center)).norm() / (ck.center - ci.center).norm(); + const double E = ((ci.center - cj.center) + (ck.center - cj.center)).norm() / (ck.center - ci.center).norm(); if (E < minE) { minE = E; @@ -769,11 +787,12 @@ bool CheckerDetector::growIterationUp(CheckerBoard & board, const std::vector::max(); } - if (alreadyTook) continue; + if (alreadyTook) + continue; board(candidate.row, candidate.col) = minIndex; candidate.score = minE; } - if (computeEnergy(board, refinedCorners) < referenceEnergy) { return true; } - - std::sort(candidates.begin(), candidates.end(), [](const NewPoint & p1, const NewPoint p2) { return p1.score < p2.score; }); + std::sort(candidates.begin(), candidates.end(), [](const NewPoint& p1, const NewPoint p2) { return p1.score < p2.score; }); while (!candidates.empty()) { const NewPoint c = candidates.back(); @@ -809,7 +827,8 @@ bool CheckerDetector::growIterationUp(CheckerBoard & board, const std::vector 1e6) continue; + if (c.score > 1e6) + continue; // After removal, some corners may be isolated, they should be removed bool changed = false; @@ -845,10 +864,10 @@ bool CheckerDetector::growIterationUp(CheckerBoard & board, const std::vector & refinedCorners) const +double CheckerDetector::computeEnergy(const CheckerBoard& board, const std::vector& refinedCorners) const { // Count valid corners in board size_t countValid = 0; @@ -897,12 +916,11 @@ double CheckerDetector::computeEnergy(const CheckerBoard & board, const std::vec continue; } - const CheckerBoardCorner & ci = refinedCorners[id1]; - const CheckerBoardCorner & cj = refinedCorners[id2]; - const CheckerBoardCorner & ck = refinedCorners[id3]; + const CheckerBoardCorner& ci = refinedCorners[id1]; + const CheckerBoardCorner& cj = refinedCorners[id2]; + const CheckerBoardCorner& ck = refinedCorners[id3]; - const double E = - ((ci.center - cj.center) + (ck.center - cj.center)).norm() / (ck.center - ci.center).norm(); + const double E = ((ci.center - cj.center) + (ck.center - cj.center)).norm() / (ck.center - ci.center).norm(); if (E > maxE) { maxE = E; @@ -926,12 +944,11 @@ double CheckerDetector::computeEnergy(const CheckerBoard & board, const std::vec continue; } - const CheckerBoardCorner & ci = refinedCorners[id1]; - const CheckerBoardCorner & cj = refinedCorners[id2]; - const CheckerBoardCorner & ck = refinedCorners[id3]; + const CheckerBoardCorner& ci = refinedCorners[id1]; + const CheckerBoardCorner& cj = refinedCorners[id2]; + const CheckerBoardCorner& ck = refinedCorners[id3]; - const double E = - ((ci.center - cj.center) + (ck.center - cj.center)).norm() / (ck.center - ci.center).norm(); + const double E = ((ci.center - cj.center) + (ck.center - cj.center)).norm() / (ck.center - ci.center).norm(); if (E > maxE) { maxE = E; @@ -944,10 +961,12 @@ double CheckerDetector::computeEnergy(const CheckerBoard & board, const std::vec return static_cast(countValid) * (maxE - 1); } -bool CheckerDetector::growIteration(CheckerBoard & board, const std::vector & refinedCorners) const +bool CheckerDetector::growIteration(CheckerBoard& board, const std::vector& refinedCorners) const { - if (board.rows() < 2) return false; - if (board.cols() < 2) return false; + if (board.rows() < 2) + return false; + if (board.cols() < 2) + return false; const double originalE = computeEnergy(board, refinedCorners); double minE = std::numeric_limits::max(); @@ -1008,15 +1027,18 @@ bool CheckerDetector::growIteration(CheckerBoard & board, const std::vector & boards, const std::vector & refinedCorners, const image::Image & input) const +void CheckerDetector::buildCheckerboards(std::vector& boards, + const std::vector& refinedCorners, + const image::Image& input) const { double minE = std::numeric_limits::max(); std::vector used(refinedCorners.size(), false); for (IndexT cid = 0; cid < refinedCorners.size(); ++cid) { - const CheckerBoardCorner & seed = refinedCorners[cid]; - if (used[cid]) continue; + const CheckerBoardCorner& seed = refinedCorners[cid]; + if (used[cid]) + continue; // Check if corner can be used as seed CheckerBoard board; @@ -1038,12 +1060,11 @@ void CheckerDetector::buildCheckerboards(std::vector & boards, con } } - if (!valid) continue; + if (!valid) + continue; // Extend board as much as possible - while (growIteration(board, refinedCorners)) - { - } + while (growIteration(board, refinedCorners)) {} // Check that board has more than 10 corners int count = 0; @@ -1051,19 +1072,22 @@ void CheckerDetector::buildCheckerboards(std::vector & boards, con { for (int j = 0; j < board.cols(); ++j) { - if (board(i, j) == UndefinedIndexT) continue; + if (board(i, j) == UndefinedIndexT) + continue; count++; } } - if (count < 10) continue; + if (count < 10) + continue; // Update used corners for (int i = 0; i < board.rows(); ++i) { for (int j = 0; j < board.cols(); ++j) { - if (board(i, j) == UndefinedIndexT) continue; + if (board(i, j) == UndefinedIndexT) + continue; const IndexT id = board(i, j); used[id] = true; @@ -1080,15 +1104,14 @@ void CheckerDetector::buildCheckerboards(std::vector & boards, con } } -void CheckerDetector::drawCheckerBoard(image::Image & img, bool nestedCheckers) const +void CheckerDetector::drawCheckerBoard(image::Image& img, bool nestedCheckers) const { for (auto c : _corners) { image::DrawLine(c.center.x() + 2.0, c.center.y(), c.center.x() - 2.0, c.center.y(), image::RGBColor(255, 255, 0), &img); - image::DrawLine(c.center.x(), c.center.y() + 2.0, c.center.x(), c.center.y()- 2.0, image::RGBColor(255, 255, 0), &img); + image::DrawLine(c.center.x(), c.center.y() + 2.0, c.center.x(), c.center.y() - 2.0, image::RGBColor(255, 255, 0), &img); } - std::vector colors; colors.emplace_back(255, 0, 0); colors.emplace_back(255, 255, 0); @@ -1107,7 +1130,8 @@ void CheckerDetector::drawCheckerBoard(image::Image & img, bool for (int j = 0; j < board.cols() - delta.x(); ++j) { const IndexT p1 = board(i, j); - if (p1 == UndefinedIndexT) continue; + if (p1 == UndefinedIndexT) + continue; IndexT p2 = board(i + delta.y(), j + delta.x()); @@ -1121,10 +1145,11 @@ void CheckerDetector::drawCheckerBoard(image::Image & img, bool } } - if (p2 == UndefinedIndexT) continue; + if (p2 == UndefinedIndexT) + continue; - const CheckerBoardCorner & c1 = _corners[p1]; - const CheckerBoardCorner & c2 = _corners[p2]; + const CheckerBoardCorner& c1 = _corners[p1]; + const CheckerBoardCorner& c2 = _corners[p2]; image::DrawLineThickness(c1.center.x(), c1.center.y(), c2.center.x(), c2.center.y(), color, 5, &img); } @@ -1138,11 +1163,12 @@ void CheckerDetector::drawCheckerBoard(image::Image & img, bool drawEdges(board, colors[idx], false); idx++; - if (idx >= _boards.size()) idx = 0; + if (idx >= _boards.size()) + idx = 0; } } -CheckerDetector::CheckerBoard CheckerDetector::trimEmptyBorders(const CheckerBoard & board) const +CheckerDetector::CheckerBoard CheckerDetector::trimEmptyBorders(const CheckerBoard& board) const { int miny = board.rows() - 1; int maxy = 0; @@ -1169,7 +1195,10 @@ CheckerDetector::CheckerBoard CheckerDetector::trimEmptyBorders(const CheckerBoa return board.block(miny, minx, height, width); } -bool CheckerDetector::findCommonCorner(const CheckerBoard& board, const std::unordered_map& cornerLookup, Vec2i& coordsRef, Vec2i& coordsBoard) const +bool CheckerDetector::findCommonCorner(const CheckerBoard& board, + const std::unordered_map& cornerLookup, + Vec2i& coordsRef, + Vec2i& coordsBoard) const { for (int i = 0; i < board.rows(); ++i) { @@ -1195,7 +1224,11 @@ bool CheckerDetector::findCommonCorner(const CheckerBoard& board, const std::uno return false; } -bool CheckerDetector::mergeCheckerboardsPair(const CheckerBoard& boardRef, const CheckerBoard& board, const Vec2i& coordsRef, const Vec2i& coordsBoard, CheckerBoard& boardMerge) const +bool CheckerDetector::mergeCheckerboardsPair(const CheckerBoard& boardRef, + const CheckerBoard& board, + const Vec2i& coordsRef, + const Vec2i& coordsBoard, + CheckerBoard& boardMerge) const { const Vec2i offset = coordsRef - coordsBoard; @@ -1219,9 +1252,10 @@ bool CheckerDetector::mergeCheckerboardsPair(const CheckerBoard& boardRef, const for (int i = 0; i < board.rows(); ++i) { for (int j = 0; j < board.cols(); ++j) - { + { const IndexT curval = board(i, j); - if (curval == UndefinedIndexT) continue; + if (curval == UndefinedIndexT) + continue; const int rx = j + shiftCurX; const int ry = i + shiftCurY; @@ -1244,14 +1278,15 @@ void CheckerDetector::filterOverlapping(const std::vector usedCorners; for (int i = 0; i < baseBoard.rows(); ++i) { for (int j = 0; j < baseBoard.cols(); ++j) { const IndexT curval = baseBoard(i, j); - if (curval == UndefinedIndexT) continue; + if (curval == UndefinedIndexT) + continue; usedCorners.insert(curval); } @@ -1260,7 +1295,8 @@ void CheckerDetector::filterOverlapping(const std::vector toKeep; @@ -1407,7 +1448,7 @@ bool CheckerDetector::removeInvalidCheckerboards() for (int i = 0; i < board.rows() - delta.y(); ++i) { - Vec2 sum {0, 0}; + Vec2 sum{0, 0}; int count = 0; std::vector dirs; @@ -1418,7 +1459,8 @@ bool CheckerDetector::removeInvalidCheckerboards() const IndexT c1 = board(i, j); const IndexT c2 = board(i + delta.y(), j + delta.x()); - if (c1 == UndefinedIndexT || c2 == UndefinedIndexT) continue; + if (c1 == UndefinedIndexT || c2 == UndefinedIndexT) + continue; const Vec2 dir = (_corners[c2].center - _corners[c1].center).normalized(); dirs.push_back(dir); @@ -1437,7 +1479,8 @@ bool CheckerDetector::removeInvalidCheckerboards() } } - if (isInvalid) break; + if (isInvalid) + break; } } @@ -1467,7 +1510,8 @@ double CheckerDetector::minDistanceToCenter(const CheckerBoard& board, const Vec for (int j = 0; j < board.cols(); ++j) { const IndexT cid = board(i, j); - if (cid == UndefinedIndexT) continue; + if (cid == UndefinedIndexT) + continue; const CheckerBoardCorner& c = _corners[cid]; double dist = (c.center - center).norm(); @@ -1477,18 +1521,15 @@ double CheckerDetector::minDistanceToCenter(const CheckerBoard& board, const Vec return mindist; } -void CheckerDetector::sortCheckerBoardsByDistanceToCenter(const Vec2 & center) +void CheckerDetector::sortCheckerBoardsByDistanceToCenter(const Vec2& center) { // Sort boards by their minimal distance to image center - std::sort(_boards.begin(), _boards.end(), - [this, ¢er](const CheckerBoard& first, const CheckerBoard& second) - { - return minDistanceToCenter(first, center) < minDistanceToCenter(second, center); - } - ); + std::sort(_boards.begin(), _boards.end(), [this, ¢er](const CheckerBoard& first, const CheckerBoard& second) { + return minDistanceToCenter(first, center) < minDistanceToCenter(second, center); + }); } -void CheckerDetector::filterNestedCheckerBoards(const size_t & height, const size_t & width) +void CheckerDetector::filterNestedCheckerBoards(const size_t& height, const size_t& width) { std::vector updated_boards; @@ -1501,13 +1542,14 @@ void CheckerDetector::filterNestedCheckerBoards(const size_t & height, const siz // Count the number of valid corners size_t count_points = 0; - Vec2 mean = { 0, 0 }; + Vec2 mean = {0, 0}; for (int i = 0; i < board.rows(); ++i) { for (int j = 0; j < board.cols(); ++j) { const IndexT cid = board(i, j); - if (board(i, j) == UndefinedIndexT) continue; + if (board(i, j) == UndefinedIndexT) + continue; mean += _corners[cid].center; @@ -1518,13 +1560,14 @@ void CheckerDetector::filterNestedCheckerBoards(const size_t & height, const siz mean /= static_cast(count_points); // Filter out small checkerboards - if (count_points < 30) continue; + if (count_points < 30) + continue; // The first checkerboard must be in the center of the image // Other checkerboards more distant may have been separated because of point of view if (updated_boards.size() == 0) { - const Vec2 size = { width, height }; + const Vec2 size = {width, height}; const Vec2 dist = (mean - (size / 2)); // Make it relative to the image size @@ -1542,20 +1585,25 @@ void CheckerDetector::filterNestedCheckerBoards(const size_t & height, const siz { for (int j = 0; j < board.cols() - 1; ++j) { - if (board(i, j) == UndefinedIndexT) continue; + if (board(i, j) == UndefinedIndexT) + continue; - if (board(i, j + 1) == UndefinedIndexT) continue; + if (board(i, j + 1) == UndefinedIndexT) + continue; - if (board(i + 1, j) == UndefinedIndexT) continue; + if (board(i + 1, j) == UndefinedIndexT) + continue; - if (board(i + 1, j + 1) == UndefinedIndexT) continue; + if (board(i + 1, j + 1) == UndefinedIndexT) + continue; const Vec2 pt11 = _corners[board(i, j)].center; const Vec2 pt12 = _corners[board(i, j + 1)].center; const Vec2 pt21 = _corners[board(i + 1, j)].center; const Vec2 pt22 = _corners[board(i + 1, j + 1)].center; - const double area = 0.5 * std::abs(((pt11.x() * pt12.y() + pt12.x() * pt21.y() + pt21.x() * pt22.y() + pt22.x() * pt11.y()) - (pt12.x() * pt11.y() + pt21.x() * pt12.y() + pt22.x() * pt21.y() + pt11.x() * pt22.y()))); + const double area = 0.5 * std::abs(((pt11.x() * pt12.y() + pt12.x() * pt21.y() + pt21.x() * pt22.y() + pt22.x() * pt11.y()) - + (pt12.x() * pt11.y() + pt21.x() * pt12.y() + pt22.x() * pt21.y() + pt11.x() * pt22.y()))); mean_squares += area; count_squares++; } @@ -1563,8 +1611,9 @@ void CheckerDetector::filterNestedCheckerBoards(const size_t & height, const siz double mean_area = mean_squares / count_squares; - // Check that square size function is increasing - if (mean_area < previous_area * 0.8) continue; + // Check that square size function is increasing + if (mean_area < previous_area * 0.8) + continue; previous_area = mean_area; @@ -1584,7 +1633,7 @@ void CheckerDetector::buildNestedConnectors() // Try to extend boards with checkerboards links for (std::size_t idx_board = 0; idx_board < _boards.size(); ++idx_board) { - auto & board_original = _boards[idx_board]; + auto& board_original = _boards[idx_board]; double minE = computeEnergy(board_original, _corners); CheckerBoard board_up = board_original; @@ -1615,7 +1664,8 @@ void CheckerDetector::buildNestedConnectors() const double E = computeEnergy(board_right, _corners); if (E < minE) { - board_original = board_right.colwise().reverse().transpose();; + board_original = board_right.colwise().reverse().transpose(); + ; minE = E; } } @@ -1633,7 +1683,7 @@ void CheckerDetector::buildNestedConnectors() } } -bool CheckerDetector::groupNestedCheckerboardsPair(Vec2i & ref_center, const IndexT& other, int scale) +bool CheckerDetector::groupNestedCheckerboardsPair(Vec2i& ref_center, const IndexT& other, int scale) { const CheckerBoard ref_board = _boards[0]; CheckerBoard cur_board = _boards[other]; @@ -1646,7 +1696,8 @@ bool CheckerDetector::groupNestedCheckerboardsPair(Vec2i & ref_center, const Ind { bool found = false; const IndexT ref_val = ref_board(ref_i, ref_j); - if (ref_val == UndefinedIndexT) continue; + if (ref_val == UndefinedIndexT) + continue; for (int cur_i = 0; cur_i < cur_board.rows(); ++cur_i) { @@ -1661,7 +1712,8 @@ bool CheckerDetector::groupNestedCheckerboardsPair(Vec2i & ref_center, const Ind } } - if (found) break; + if (found) + break; } } } @@ -1670,10 +1722,7 @@ bool CheckerDetector::groupNestedCheckerboardsPair(Vec2i & ref_center, const Ind std::map, int> votes; for (auto& p : list_same_corners) { - std::pair offset{ - p.first.x() - (p.second.x() * scale), - p.first.y() - (p.second.y() * scale) - }; + std::pair offset{p.first.x() - (p.second.x() * scale), p.first.y() - (p.second.y() * scale)}; if (votes.find(offset) == votes.end()) { @@ -1794,12 +1843,12 @@ void CheckerDetector::groupNestedCheckerboards() return; } - Vec2i center = { _boards[0].cols() / 2, _boards[0].rows() / 2 }; + Vec2i center = {_boards[0].cols() / 2, _boards[0].rows() / 2}; while (change) { change = false; - + // Try to find another checkerboard with the same level for (std::size_t idx_compare = 1; idx_compare < _boards.size(); ++idx_compare) { @@ -1810,7 +1859,8 @@ void CheckerDetector::groupNestedCheckerboards() } } - if (change) continue; + if (change) + continue; current_scale *= 2; @@ -1825,11 +1875,10 @@ void CheckerDetector::groupNestedCheckerboards() } } - const int max_x = std::max(center.x(), static_cast(_boards[0].cols() - 1 - center.x())); const int max_y = std::max(center.y(), static_cast(_boards[0].rows() - 1 - center.y())); - const Vec2i newcenter = { max_x, max_y}; + const Vec2i newcenter = {max_x, max_y}; const Vec2i update = newcenter - center; CheckerBoard newboard(max_y * 2 + 1, max_x * 2 + 1); @@ -1838,6 +1887,5 @@ void CheckerDetector::groupNestedCheckerboards() _boards[0] = newboard; } - -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/checkerDetector.hpp b/src/aliceVision/calibration/checkerDetector.hpp index dbd472fccc..29580656a0 100644 --- a/src/aliceVision/calibration/checkerDetector.hpp +++ b/src/aliceVision/calibration/checkerDetector.hpp @@ -17,31 +17,31 @@ namespace calibration { /** * @brief Detect checkerboard structures in an image. - * + * * This class provides an interface for detecting checkerboards in a given image. * It provides access to the detected corners and checkerboards, each board being represented as a matrix of corner IDs. - * + * * A corner is described by: * - its center * - its two principal dimensions * - a scale of detection (higher scale means better detection quality). - * + * * Checkerboards may be: * - heavily distorted * - with large blur * - partially occluded (even at the center) * - without any size information. - * + * * Also, the detection algorithm supports both simple and nested grids. * Note however that in this case the checkerboards must be centered on the image center. - * + * * It is possible to visualize the output of the detection by either drawing it on an image or directly retrieving a "debug image". - * + * * References: [ROCHADE], [Geiger], [Chen], [Liu], [Abdulrahman], [Bok] */ class CheckerDetector { -public: + public: /** * @brief A checkerboard is represented as a matrix of indices, each index referencing a corner. */ @@ -62,7 +62,10 @@ class CheckerDetector double scale; IntermediateCorner() = default; - IntermediateCorner(const Vec2& c, double s) : center(c), scale(s) {} + IntermediateCorner(const Vec2& c, double s) + : center(c), + scale(s) + {} }; /** @@ -76,14 +79,22 @@ class CheckerDetector double scale; CheckerBoardCorner() = default; - CheckerBoardCorner(const Vec2& c, double s) : center(c), scale(s) {} - CheckerBoardCorner(const Vec2& c, const Vec2& d1, const Vec2& d2, double s) : center(c), dir1(d1), dir2(d2), scale(s) {} + CheckerBoardCorner(const Vec2& c, double s) + : center(c), + scale(s) + {} + CheckerBoardCorner(const Vec2& c, const Vec2& d1, const Vec2& d2, double s) + : center(c), + dir1(d1), + dir2(d2), + scale(s) + {} }; -public: + public: /** * @brief Checkerboard detection routine. - * + * * Algorithm steps: * 1. convert image to grayscale * 2. extract corner positions from image at different scales and merge them to avoid redundancies @@ -92,17 +103,17 @@ class CheckerDetector * 5. build checkerboards by connecting these corners * 6. merge connected boards * 7. remove invalid boards - * 8. if we are using nested calibration grids: + * 8. if we are using nested calibration grids: * 8.1. sort the boards by distance to image center * 8.2. filter boards to only keep a sequence of nested boards * 8.3. connect and group these nested boards - * + * * @param source[in] Input image containing checkerboards. * @param useNestedGrid[in] Indicate if the image contains nested calibration grids. * @param debug[in] Indicate if debug images should be drawn. * @return False if a problem occured during detection, otherwise true. */ - bool process(const image::Image & source, bool useNestedGrids = false, bool debug = false); + bool process(const image::Image& source, bool useNestedGrids = false, bool debug = false); /// Return a copy of detected checkerboards. std::vector getBoards() const { return _boards; } @@ -111,209 +122,213 @@ class CheckerDetector std::vector getCorners() const { return _corners; } /// Return a reference to detected checkerboards. - std::vector & getBoards() { return _boards; } + std::vector& getBoards() { return _boards; } /// Return a reference to detected corners. - std::vector & getCorners() { return _corners; } + std::vector& getCorners() { return _corners; } /** - * @brief Draw detected checkerboards on an image. - * + * @brief Draw detected checkerboards on an image. + * * A checkerboard is visually represented by lines connecting its corners (i.e. by its edges). - * + * * @param[in,out] img Image on which to draw the grid lines. * @param[in] nestedCheckers Indicate if the input image contained nested calibration grids.. */ - void drawCheckerBoard(image::Image & img, bool nestedCheckers = false) const; - + void drawCheckerBoard(image::Image& img, bool nestedCheckers = false) const; + /// Return a reference to the debug image. const image::Image& getDebugImage() const { return _debugImage; } -private: + private: /** * @brief Extract corners positions from the image at the given scale. - * + * * Algorithm steps: * 1. apply scale and normalize the input image * 2. compute Hessian response of the image * 3. extract corners positions using the Hessian response * 4. refine the corners positions using the image * 5. prune corners - * + * * @param[out] corners Container for extracted corners. * @param[in] input Input grayscale image. * @param[in] scale Scale applied to the image before the extraction. * @return False if a problem occured during extraction, otherwise true. */ - bool processLevel(std::vector & corners, const image::Image & input, double scale) const; + bool processLevel(std::vector& corners, const image::Image& input, double scale) const; /** * @brief Retrieve min and max pixel values of a grayscale image. - * + * * @param[out] min Minimum pixel value. * @param[out] max Maximum pixel value. * @param[in] input Input grayscale image. */ - void getMinMax(float &min, float &max, const image::Image & input) const; + void getMinMax(float& min, float& max, const image::Image& input) const; /** * @brief Normalize a grayscale image so that min pixel value is 0 and max pixel value is 1. - * + * * @param[out] output Normalized image. * @param[in] input Input grayscale image. */ - void normalizeImage(image::Image & output, const image::Image & input) const; + void normalizeImage(image::Image& output, const image::Image& input) const; /** * @brief Compute Hessian response at each pixel of an image. * @see [Chen] - * + * * Hessian response formula is close to the Hessian matrix determinant absolute value, * but yields better results in practice. - * + * * @param[out] output Hessian response at each pixel of input image. * @param[in] input Input grayscale image. */ - void computeHessianResponse(image::Image & output, const image::Image & input) const; + void computeHessianResponse(image::Image& output, const image::Image& input) const; /** * @brief Extract corner positions by searching local maxima (on a 7x7 patch) in the Hessian response. * @see [Liu] - * + * * @param[out] rawCorners Corners positions. * @param[in] hessianResponse Hessian response of input image. */ - void extractCorners(std::vector & rawCorners, const image::Image & hessianResponse) const; + void extractCorners(std::vector& rawCorners, const image::Image& hessianResponse) const; /** * @brief Refine corners positions using image gradient (on a 5x5 patch) around the initial positions. * @see [Geiger] * @see [Abdulrahman] - * + * * @param[out] refinedCorners Refined corners positions. * @param[in] rawCorners Initial corners positions. * @param[in] input Input grayscale image. */ - void refineCorners(std::vector & refinedCorners, const std::vector & rawCorners, const image::Image & input) const; + void refineCorners(std::vector& refinedCorners, const std::vector& rawCorners, const image::Image& input) const; /** * @brief Analyze grayscale values in the neighborhood of given corners positions and select the ones that match a checkerboard pattern. * @see [Bok] - * + * * Criteria to match the pattern at a corner position: * - sample grayscale values on a circle of 5px radius around the position * - normalize these values * - count the number of sign changes between consecutive samples * - to match the pattern, this count should be equal to 4 (alterning black and white tiles around a corner). - * + * * @param[out] prunedCorners Selected corners positions. * @param[in] rawCorners Initial corners positions. * @param[in] input Input grayscale image. */ - void pruneCorners(std::vector & prunedCorners, const std::vector & rawCorners, const image::Image & input) const; + void pruneCorners(std::vector& prunedCorners, const std::vector& rawCorners, const image::Image& input) const; /** * @brief Given corners positions, refine their positions and compute their directions. * @see [ROCHADE] * @see [Geiger] - * + * * @param[out] refinedCorners Container for corners with computed directions. * @param[in] rawCorners Corners positions. * @param[in] input Input grayscale image. */ - void fitCorners(std::vector & refinedCorners, const std::vector & rawCorners, const image::Image & input) const; + void fitCorners(std::vector& refinedCorners, + const std::vector& rawCorners, + const image::Image& input) const; /** * @brief Build checkerboards by connecting corners. * @see [Geiger] - * - * Algorithm for building a checkerboard: + * + * Algorithm for building a checkerboard: * 1. find a suitable seed corner and initialize the board with it * 2. while it is possible, perform a grow step on the board: find corners that can be connected to the board and include them * 3. compute the board's energy and reject it if it is above a certain threshold - * + * * @param[out] boards Container for the built checkerboards. * @param[in] refinedCorners Corners with directions information. * @param[in] input Input grayscale image. */ - void buildCheckerboards(std::vector & boards, const std::vector & refinedCorners, const image::Image & input) const; + void buildCheckerboards(std::vector& boards, + const std::vector& refinedCorners, + const image::Image& input) const; /** * @brief Find corner closest to a given position in an area constrained to a small cone around a given direction. - * + * * @param[in] center Input position. * @param[in] dir Input direction in which to look for a corner. * @param[in] refinedCorners Array of corners. * @return Index of closest corner in the input array, UndefinedIndexT if none was found. */ - IndexT findClosestCorner(const Vec2 & center, const Vec2 & dir, const std::vector & refinedCorners) const; + IndexT findClosestCorner(const Vec2& center, const Vec2& dir, const std::vector& refinedCorners) const; /** * @brief Check if a given corner can be used as seed for checkerboard detection. - * + * * @param[out] board Initialized board if the corner can be used as a seed. * @param[in] seed Index of the corner to check. * @param[in] refinedCorners Array of corners. * @return True if the corner can be used as a seed, otherwise false. */ - bool getSeedCheckerboard(CheckerBoard & board, IndexT seed, const std::vector & refinedCorners) const; + bool getSeedCheckerboard(CheckerBoard& board, IndexT seed, const std::vector& refinedCorners) const; /** * @brief Compute the "energy" of a checkerboard using the number of valid corners and the maximum local distortion along rows and columns. - * + * * @param[in] board Input checkerboard. * @param[in] refinedCorners Checkerboard corners. * @return Checkerboard's energy value. */ - double computeEnergy(const CheckerBoard & board, const std::vector & refinedCorners) const; + double computeEnergy(const CheckerBoard& board, const std::vector& refinedCorners) const; /** * @brief Find positions on a board that do not reference a corner yet but can be used to extend the board. - * + * * @param[out] candidates Selected candidate points. * @param[in] board Input checkerboard. * @param[in] inside Search points for extending the board inwards or outwards. * @return False if the board has less than two rows, otherwise true. */ - bool getCandidates(std::vector & candidates, const CheckerBoard & board, bool inside) const; + bool getCandidates(std::vector& candidates, const CheckerBoard& board, bool inside) const; /** * @brief Extend a board in the up direction. - * + * * Algorithm steps: * 1. enlarge board in the up direction with empty indices * 2. find candidate points for extension * 3. for each candidate point, find a corner that minimizes local distortion with the board * (note: each corner can be associated to at most one candidate point) * 4. filter extended board and check that its energy is lower than before. - * + * * @param[in,out] board Checkerboard to extend. * @param[in] refinedCorners All detected corners. * @param[in] nested Extend the board inwards or outwards. * @return False if a problem occured or if the energy of the extended board is higher than before, otherwise true. */ - bool growIterationUp(CheckerBoard & board, const std::vector & refinedCorners, bool nested) const; + bool growIterationUp(CheckerBoard& board, const std::vector& refinedCorners, bool nested) const; /** * @brief Extend a board outwards in the up, down, right and left directions (only if that creates a board with lower energy). - * + * * @param[in,out] board Checkerboard to extend. * @param[in] refinedCorners All detected corners. * @return False if the energy of the extended board is higher than before, otherwise true. */ - bool growIteration(CheckerBoard & board, const std::vector & refinedCorners) const; + bool growIteration(CheckerBoard& board, const std::vector& refinedCorners) const; /** * @brief Merge connected checkboards. - * + * * Algorithm steps: * 1. sort checkerboards by energy - * 2. for each board: + * 2. for each board: * 2.1. try to find a board with higher energy, common corners and no conflict * 2.2. create a merged board from these two boards * 2.3. if the new board energy is lower than the first board energy, replace the latter * 3. remove overlapping boards - * + * * @return False if a problem occured during merging, otherwise true. */ bool mergeCheckerboards(); @@ -324,7 +339,7 @@ class CheckerDetector * @param[in] board Input checkerboard. * @return Same checkerboard without empty borders. */ - CheckerBoard trimEmptyBorders(const CheckerBoard & board) const; + CheckerBoard trimEmptyBorders(const CheckerBoard& board) const; /** * @brief Find a common corner between two checkerboards. @@ -335,11 +350,14 @@ class CheckerDetector * @param[out] coordsBoard Coordinates of common corner in input board. * @return True if a common corner was found, otherwise false. */ - bool findCommonCorner(const CheckerBoard& board, const std::unordered_map& cornerLookup, Vec2i& coordsRef, Vec2i& coordsBoard) const; + bool findCommonCorner(const CheckerBoard& board, + const std::unordered_map& cornerLookup, + Vec2i& coordsRef, + Vec2i& coordsBoard) const; /** * @brief Merge two checkeroards that share a common corner. - * + * * @param[in] boardRef Reference checkerboard. * @param[in] board Checkerboard to merge with the reference board. * @param[in] coordsRef Common corner coordinates on reference board. @@ -347,13 +365,17 @@ class CheckerDetector * @param[out] boardMerge Resulting checkerboard after merge. * @return False if a conflict was detected, otherwise true. */ - bool mergeCheckerboardsPair(const CheckerBoard& boardRef, const CheckerBoard& board, const Vec2i& coordsRef, const Vec2i& coordsBoard, CheckerBoard& boardMerge) const; + bool mergeCheckerboardsPair(const CheckerBoard& boardRef, + const CheckerBoard& board, + const Vec2i& coordsRef, + const Vec2i& coordsBoard, + CheckerBoard& boardMerge) const; /** * @brief Filter out overlapping checkerboards. - * + * * When two checkerboards share at least 80% of their corners, keep only the one with lowest energy. - * + * * @param[in] checkersWithScore Checkerboards with their corresponding energy. * @param[out] toKeep Indices of checkerboards to keep. */ @@ -361,24 +383,24 @@ class CheckerDetector /** * @brief Filter out checkboards based on a geometric invalidation criteria. - * - * A checkboard is considered invalid if one of its rows or columns contains two edges + * + * A checkboard is considered invalid if one of its rows or columns contains two edges * that have an absolute angle between them that is above a certain threshold (currently PI/4). - * + * * @return False if a problem occured during filtering, otherwise true. */ bool removeInvalidCheckerboards(); /** * @brief Sort checkerboards using minimal distance between their corners and image center as criteria. - * + * * @param[in] center Image center. */ void sortCheckerBoardsByDistanceToCenter(const Vec2& center); /** * @brief Compute minimal distance between a center point and the corners of a checkerboard. - * + * * @param[in] board Input checkerboard. * @param[in] center Center position. * @return Minimal distance between checkerboard and center position. @@ -387,11 +409,11 @@ class CheckerDetector /** * @brief Keep only a sequence of nested boards and discard the other boards. - * + * * This method assumes that checkboards are sorted by distance to center. - * + * * Also, boards must be centered on image center to be recognized as nested. - * + * * @param[in] height Image height. * @param[in] width Image width. */ @@ -409,15 +431,15 @@ class CheckerDetector /** * @brief Merge given board with reference board (first in the nesting sequence). - * + * * @param[in] ref_center Center point of reference board. * @param[in] other Index of board to merge. * @param[in] scale Checkboard scale within nesting sequence (power of 2). * @return False if merging failed, otherwise true. */ - bool groupNestedCheckerboardsPair(Vec2i& ref_center, const IndexT & other, int scale); - -private: + bool groupNestedCheckerboardsPair(Vec2i& ref_center, const IndexT& other, int scale); + + private: /// Detected checkerboards. std::vector _boards; @@ -426,8 +448,7 @@ class CheckerDetector /// Input image with detected checkerboards drawn over it. image::Image _debugImage; - }; -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/checkerDetector_io.cpp b/src/aliceVision/calibration/checkerDetector_io.cpp index 211907c143..ef31283122 100644 --- a/src/aliceVision/calibration/checkerDetector_io.cpp +++ b/src/aliceVision/calibration/checkerDetector_io.cpp @@ -9,8 +9,7 @@ namespace aliceVision { namespace calibration { -CheckerDetector::CheckerBoardCorner tag_invoke(boost::json::value_to_tag, - boost::json::value const& jv) +CheckerDetector::CheckerBoardCorner tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) { const boost::json::object& obj = jv.as_object(); @@ -26,15 +25,15 @@ CheckerDetector::CheckerBoardCorner tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) @@ -43,17 +42,17 @@ CheckerDetector tag_invoke(boost::json::value_to_tag, boost::js boost::json::object const& obj = jv.as_object(); ret.getCorners() = boost::json::value_to>(obj.at("corners")); - + std::vector jvs = boost::json::value_to>(obj.at("boards")); - for(boost::json::value & ljv : jvs) + for (boost::json::value& ljv : jvs) { - boost::json::object const & lobj = ljv.as_object(); + boost::json::object const& lobj = ljv.as_object(); const int rows = boost::json::value_to(lobj.at("rows")); const int cols = boost::json::value_to(lobj.at("cols")); const std::vector values = boost::json::value_to>(lobj.at("data")); - + CheckerDetector::CheckerBoard board(rows, cols); std::size_t pos = 0; for (int i = 0; i < rows; ++i) @@ -75,7 +74,7 @@ void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, Chec { std::vector jvs; - for(auto b : t.getBoards()) + for (auto b : t.getBoards()) { std::vector values; for (int i = 0; i < b.rows(); ++i) @@ -86,18 +85,13 @@ void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, Chec } } - boost::json::value sjv = { - {"rows", b.rows()}, {"cols", b.cols()}, {"data", boost::json::value_from(values)} - }; + boost::json::value sjv = {{"rows", b.rows()}, {"cols", b.cols()}, {"data", boost::json::value_from(values)}}; jvs.push_back(sjv); } - jv = { - {"corners", boost::json::value_from(t.getCorners())}, - {"boards", boost::json::value_from(jvs)} - }; + jv = {{"corners", boost::json::value_from(t.getCorners())}, {"boards", boost::json::value_from(jvs)}}; } -} // namespace calibration -} // namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/checkerDetector_io.hpp b/src/aliceVision/calibration/checkerDetector_io.hpp index 13eeda9751..c2dc564ecd 100644 --- a/src/aliceVision/calibration/checkerDetector_io.hpp +++ b/src/aliceVision/calibration/checkerDetector_io.hpp @@ -16,14 +16,12 @@ namespace calibration { /** * @brief Deserialize JSON object to checkerboard corner. */ -CheckerDetector::CheckerBoardCorner tag_invoke(boost::json::value_to_tag, - boost::json::value const& jv); +CheckerDetector::CheckerBoardCorner tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); /** * @brief Serialize checkerboard corner to JSON object. */ -void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, - CheckerDetector::CheckerBoardCorner const& t); +void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, CheckerDetector::CheckerBoardCorner const& t); /** * @brief Deserialize JSON object to checkerboard detector. @@ -35,6 +33,5 @@ CheckerDetector tag_invoke(boost::json::value_to_tag, boost::js */ void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, CheckerDetector const& t); -} // namespace calibration -} // namespace aliceVision - +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/distortionEstimation.cpp b/src/aliceVision/calibration/distortionEstimation.cpp index f988a5c740..2017a589f1 100644 --- a/src/aliceVision/calibration/distortionEstimation.cpp +++ b/src/aliceVision/calibration/distortionEstimation.cpp @@ -17,9 +17,10 @@ namespace calibration { class CostLine : public ceres::CostFunction { -public: - CostLine(const std::shared_ptr& undistortion, const Vec2& pt) : - _pt(pt), _undistortion(undistortion) + public: + CostLine(const std::shared_ptr& undistortion, const Vec2& pt) + : _pt(pt), + _undistortion(undistortion) { set_num_residuals(1); @@ -41,7 +42,7 @@ class CostLine : public ceres::CostFunction const double cangle = std::cos(angle); const double sangle = std::sin(angle); - + std::vector cameraDistortionParams = _undistortion->getParameters(); const std::size_t distortionSize = cameraDistortionParams.size(); for (std::size_t idParam = 0; idParam < distortionSize; ++idParam) @@ -102,16 +103,16 @@ class CostLine : public ceres::CostFunction return true; } -private: + private: std::shared_ptr _undistortion; Vec2 _pt; }; bool estimate(std::shared_ptr undistortionToEstimate, - Statistics & statistics, - std::vector & lines, + Statistics& statistics, + std::vector& lines, bool lockCenter, - const std::vector & lockDistortions) + const std::vector& lockDistortions) { if (!undistortionToEstimate) { @@ -132,8 +133,8 @@ bool estimate(std::shared_ptr undistortionToEstimate, if (lockDistortions.size() != countUndistortionParams) { - ALICEVISION_LOG_ERROR("Invalid number of distortion parameters (lockDistortions=" << lockDistortions.size() - << ", countDistortionParams=" << countUndistortionParams << ")."); + ALICEVISION_LOG_ERROR("Invalid number of distortion parameters (lockDistortions=" << lockDistortions.size() << ", countDistortionParams=" + << countUndistortionParams << ")."); return false; } @@ -184,14 +185,14 @@ bool estimate(std::shared_ptr undistortionToEstimate, } } - for (auto & l : lines) + for (auto& l : lines) { problem.AddParameterBlock(&l.angle, 1); problem.AddParameterBlock(&l.dist, 1); for (Vec2 pt : l.points) { - ceres::CostFunction * costFunction = new CostLine(undistortionToEstimate, pt); + ceres::CostFunction* costFunction = new CostLine(undistortionToEstimate, pt); problem.AddResidualBlock(costFunction, lossFunction, &l.angle, &l.dist, center, ptrUndistortionParameters); } } @@ -216,7 +217,7 @@ bool estimate(std::shared_ptr undistortionToEstimate, undistortionToEstimate->setParameters(undistortionParameters); std::vector errors; - for (auto & l : lines) + for (auto& l : lines) { const double sangle = std::sin(l.angle); const double cangle = std::cos(l.angle); @@ -232,7 +233,7 @@ bool estimate(std::shared_ptr undistortionToEstimate, const double mean = std::accumulate(errors.begin(), errors.end(), 0.0) / static_cast(errors.size()); const double sqSum = std::inner_product(errors.begin(), errors.end(), errors.begin(), 0.0); const double stddev = std::sqrt(sqSum / errors.size() - mean * mean); - std::nth_element(errors.begin(), errors.begin() + errors.size()/2, errors.end()); + std::nth_element(errors.begin(), errors.begin() + errors.size() / 2, errors.end()); const double median = errors[errors.size() / 2]; statistics.mean = mean; @@ -242,5 +243,5 @@ bool estimate(std::shared_ptr undistortionToEstimate, return true; } -} // namespace calibration -} // namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/distortionEstimation.hpp b/src/aliceVision/calibration/distortionEstimation.hpp index e628362509..8a042134d3 100644 --- a/src/aliceVision/calibration/distortionEstimation.hpp +++ b/src/aliceVision/calibration/distortionEstimation.hpp @@ -18,10 +18,10 @@ namespace calibration { /** * @brief Set of 2D points that belong to the same line. - * + * * The 2D points correspond to real-world points that are aligned and evenly spaced * observed with a camera which applies some distortion on them. - * + * * Therefore these 2D points may not actually be aligned and evenly spaced, * and this difference with the ideal line model is used to estimate distortion. */ @@ -44,9 +44,9 @@ struct Statistics /** * @brief Estimate the undistortion parameters of a camera using a set of line aligned points. - * + * * This algorithms minimizes a distance between points and lines using distortion. - * + * * @param[out] undistortionToEstimate Undistortion object with the parameters to estimate. * @param[out] statistics Statistics on the estimation error. * @param[in] lines Set of line aligned points used to estimate distortion. @@ -60,5 +60,5 @@ bool estimate(std::shared_ptr undistortionToEstimate, bool lockCenter, const std::vector& lockDistortions); -} // namespace calibration -} // namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/distortioncalibration_test.cpp b/src/aliceVision/calibration/distortioncalibration_test.cpp index a5b2425ce6..65af8076d3 100644 --- a/src/aliceVision/calibration/distortioncalibration_test.cpp +++ b/src/aliceVision/calibration/distortioncalibration_test.cpp @@ -32,55 +32,53 @@ using namespace aliceVision; // Checks that these points are close to the original points //----------------- BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_classicld) -{ - std::shared_ptr cam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, - 1000, 1000, 1000, 1000, 0, 0, - {-0.34768564335290314, 1.5809150001711287, - -0.17204522667665839, -0.15541950225726325, - 1.1240093674337683}); - - // Create points - std::vector pts; - for (int i = 0; i < 1000; i+=10) - { - for (int j = 0; j < 1000; j+=10) +{ + std::shared_ptr cam = + camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, + 1000, + 1000, + 1000, + 1000, + 0, + 0, + {-0.34768564335290314, 1.5809150001711287, -0.17204522667665839, -0.15541950225726325, 1.1240093674337683}); + + // Create points + std::vector pts; + for (int i = 0; i < 1000; i += 10) { - const Vec2 pt(j, i); - const Vec2 cpt = cam->ima2cam(pt); - const Vec2 upt = cam->addDistortion(cpt); - const Vec2 distortedPoint = cam->cam2ima(upt); - - calibration::PointPair pp; - pp.distortedPoint = distortedPoint; - pp.undistortedPoint = pt; - pts.push_back(pp); + for (int j = 0; j < 1000; j += 10) + { + const Vec2 pt(j, i); + const Vec2 cpt = cam->ima2cam(pt); + const Vec2 upt = cam->addDistortion(cpt); + const Vec2 distortedPoint = cam->cam2ima(upt); + + calibration::PointPair pp; + pp.distortedPoint = distortedPoint; + pp.undistortedPoint = pt; + pts.push_back(pp); + } } - } - - // Calibrate - calibration::Statistics st; - std::shared_ptr estimatedCam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, - 1000, 1000, 1000, 1000, 0, 0, - {0, boost::math::constants::pi() * .5, - 0, 0, - 0}); + // Calibrate + calibration::Statistics st; + std::shared_ptr estimatedCam = camera::createPinhole( + camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, 1000, 1000, 1000, 1000, 0, 0, {0, boost::math::constants::pi() * .5, 0, 0, 0}); - std::vector lockedDistortions = {false, false, false, false, false}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, pts, true, false, lockedDistortions)); + std::vector lockedDistortions = {false, false, false, false, false}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, pts, true, false, lockedDistortions)); - for (const calibration::PointPair & pair : pts) - { - const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); - const Vec2 upt = estimatedCam->removeDistortion(cpt); - const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); + for (const calibration::PointPair& pair : pts) + { + const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); + const Vec2 upt = estimatedCam->removeDistortion(cpt); + const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); - const double residual = (undistortedPoint - pair.undistortedPoint).norm(); + const double residual = (undistortedPoint - pair.undistortedPoint).norm(); - BOOST_CHECK_SMALL(residual, 1e-2); - } + BOOST_CHECK_SMALL(residual, 1e-2); + } } //----------------- @@ -93,55 +91,54 @@ BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_classicld) //----------------- BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_radial4) { - std::shared_ptr cam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, - 1000, 1000, 1000, 1000, 0, 0, - {-0.4839495643487452, 1.0301284234642258, - 0.014928332802185664, -0.0007797104872758904, - -0.038994206396183909, 8.0474385001183646e-05}); - - // Create points - std::vector pts; - for (int i = 0; i < 1000; i+=10) - { - for (int j = 0; j < 1000; j+=10) + std::shared_ptr cam = camera::createPinhole( + camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, + 1000, + 1000, + 1000, + 1000, + 0, + 0, + {-0.4839495643487452, 1.0301284234642258, 0.014928332802185664, -0.0007797104872758904, -0.038994206396183909, 8.0474385001183646e-05}); + + // Create points + std::vector pts; + for (int i = 0; i < 1000; i += 10) { - const Vec2 pt(j, i); - const Vec2 cpt = cam->ima2cam(pt); - const Vec2 upt = cam->addDistortion(cpt); - const Vec2 distortedPoint = cam->cam2ima(upt); - - calibration::PointPair pp; - pp.distortedPoint = distortedPoint; - pp.undistortedPoint = pt; - pts.push_back(pp); + for (int j = 0; j < 1000; j += 10) + { + const Vec2 pt(j, i); + const Vec2 cpt = cam->ima2cam(pt); + const Vec2 upt = cam->addDistortion(cpt); + const Vec2 distortedPoint = cam->cam2ima(upt); + + calibration::PointPair pp; + pp.distortedPoint = distortedPoint; + pp.undistortedPoint = pt; + pts.push_back(pp); + } } - } + // Calibrate + calibration::Statistics st; + std::shared_ptr estimatedCam = + camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, 1000, 1000, 1000, 1000, 0, 0, {0, 0, 0, 0, 0, 0}); - // Calibrate - calibration::Statistics st; - std::shared_ptr estimatedCam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, - 1000, 1000, 1000, 1000, 0, 0, - {0, 0, 0, 0, 0, 0}); - - std::vector lockedDistortions = {false, false, false, false, false, false}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, pts, true, false, lockedDistortions)); + std::vector lockedDistortions = {false, false, false, false, false, false}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, pts, true, false, lockedDistortions)); - for (const calibration::PointPair & pair : pts) - { - const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); - const Vec2 upt = estimatedCam->removeDistortion(cpt); - const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); + for (const calibration::PointPair& pair : pts) + { + const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); + const Vec2 upt = estimatedCam->removeDistortion(cpt); + const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); - const double residual = (undistortedPoint - pair.undistortedPoint).norm(); + const double residual = (undistortedPoint - pair.undistortedPoint).norm(); - BOOST_CHECK_SMALL(residual, 1e-2); - } + BOOST_CHECK_SMALL(residual, 1e-2); + } } - //----------------- // Test summary: //----------------- @@ -153,94 +150,92 @@ BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_radial4) //----------------- BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_lines_classicld) { - std::shared_ptr cam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, - 1000, 1000, 1000, 1000, 0, 0, - {-0.34768564335290314, 1.5809150001711287, - -0.17204522667665839, -0.15541950225726325, - 1.1240093674337683}); - - // Create points - std::vector pts; - std::vector lines; - for (int i = 0; i < 1000; i+=10) - { - calibration::LineWithPoints line; - line.angle = boost::math::constants::pi() * .25; - line.dist = 0; - line.horizontal = true; - - for (int j = 0; j < 1000; j+=10) + std::shared_ptr cam = + camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, + 1000, + 1000, + 1000, + 1000, + 0, + 0, + {-0.34768564335290314, 1.5809150001711287, -0.17204522667665839, -0.15541950225726325, 1.1240093674337683}); + + // Create points + std::vector pts; + std::vector lines; + for (int i = 0; i < 1000; i += 10) { - const Vec2 pt(j, i); - const Vec2 cpt = cam->ima2cam(pt); - const Vec2 upt = cam->removeDistortion(cpt); - const Vec2 distortedPoint = cam->cam2ima(upt); - - calibration::PointPair pp; - pp.distortedPoint = distortedPoint; - pp.undistortedPoint = pt; - pts.push_back(pp); - - line.points.push_back(distortedPoint); + calibration::LineWithPoints line; + line.angle = boost::math::constants::pi() * .25; + line.dist = 0; + line.horizontal = true; + + for (int j = 0; j < 1000; j += 10) + { + const Vec2 pt(j, i); + const Vec2 cpt = cam->ima2cam(pt); + const Vec2 upt = cam->removeDistortion(cpt); + const Vec2 distortedPoint = cam->cam2ima(upt); + + calibration::PointPair pp; + pp.distortedPoint = distortedPoint; + pp.undistortedPoint = pt; + pts.push_back(pp); + + line.points.push_back(distortedPoint); + } + + lines.push_back(line); } - lines.push_back(line); - } + for (int j = 0; j < 1000; j += 10) + { + calibration::LineWithPoints line; + line.angle = boost::math::constants::pi() * .25; + line.dist = 0; + line.horizontal = false; + + for (int i = 0; i < 1000; i += 10) + { + const Vec2 pt(j, i); + const Vec2 cpt = cam->ima2cam(pt); + const Vec2 upt = cam->removeDistortion(cpt); + const Vec2 distortedPoint = cam->cam2ima(upt); + + line.points.push_back(distortedPoint); + } + + lines.push_back(line); + } - for (int j = 0; j < 1000; j+=10) - { - calibration::LineWithPoints line; - line.angle = boost::math::constants::pi() * .25; - line.dist = 0; - line.horizontal = false; + // Calibrate + calibration::Statistics st; + std::shared_ptr estimatedCam = camera::createPinhole( + camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, 1000, 1000, 1000, 1000, 0, 0, {0, boost::math::constants::pi() * .5, 0, 0, 0}); - for (int i = 0; i < 1000; i+=10) { - const Vec2 pt(j, i); - const Vec2 cpt = cam->ima2cam(pt); - const Vec2 upt = cam->removeDistortion(cpt); - const Vec2 distortedPoint = cam->cam2ima(upt); - - line.points.push_back(distortedPoint); + std::vector lockedDistortions = {true, true, true, true, true}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, true, lockedDistortions)); + } + { + std::vector lockedDistortions = {false, true, true, true, true}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); + } + { + std::vector lockedDistortions = {false, false, false, false, false}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); } - lines.push_back(line); - } - - - // Calibrate - calibration::Statistics st; - std::shared_ptr estimatedCam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DECLASSICLD, - 1000, 1000, 1000, 1000, 0, 0, - {0, boost::math::constants::pi() * .5, - 0, 0, - 0}); - - { - std::vector lockedDistortions = {true, true, true, true, true}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, true, lockedDistortions)); - } - { - std::vector lockedDistortions = {false, true, true, true, true}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); - } - { - std::vector lockedDistortions = {false, false, false, false, false}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); - } - - for (const calibration::PointPair & pair : pts) - { - const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); - const Vec2 upt = estimatedCam->addDistortion(cpt); - const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); + for (const calibration::PointPair& pair : pts) + { + const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); + const Vec2 upt = estimatedCam->addDistortion(cpt); + const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); - const double residual = (undistortedPoint - pair.undistortedPoint).norm(); + const double residual = (undistortedPoint - pair.undistortedPoint).norm(); - BOOST_CHECK_SMALL(residual, 1e-2); - } + BOOST_CHECK_SMALL(residual, 1e-2); + } } //----------------- @@ -254,91 +249,90 @@ BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_lines_classicld) //----------------- BOOST_AUTO_TEST_CASE(distortionCalibration_calibrate_lines_radial4) { - std::shared_ptr cam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, - 1000, 1000, 1000, 1000, 0, 0, - {-0.4839495643487452, 1.0301284234642258, - 0.014928332802185664, -0.0007797104872758904, - -0.038994206396183909, 8.0474385001183646e-05}); - - // Create points - std::vector pts; - std::vector lines; - for (int i = 0; i < 1000; i+=10) - { - calibration::LineWithPoints line; - line.angle = boost::math::constants::pi() * .25; - line.dist = 0; - line.horizontal = true; - - for (int j = 0; j < 1000; j+=10) + std::shared_ptr cam = camera::createPinhole( + camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, + 1000, + 1000, + 1000, + 1000, + 0, + 0, + {-0.4839495643487452, 1.0301284234642258, 0.014928332802185664, -0.0007797104872758904, -0.038994206396183909, 8.0474385001183646e-05}); + + // Create points + std::vector pts; + std::vector lines; + for (int i = 0; i < 1000; i += 10) { - const Vec2 pt(j, i); - const Vec2 cpt = cam->ima2cam(pt); - const Vec2 upt = cam->removeDistortion(cpt); - const Vec2 distortedPoint = cam->cam2ima(upt); - - calibration::PointPair pp; - pp.distortedPoint = distortedPoint; - pp.undistortedPoint = pt; - pts.push_back(pp); - - line.points.push_back(distortedPoint); + calibration::LineWithPoints line; + line.angle = boost::math::constants::pi() * .25; + line.dist = 0; + line.horizontal = true; + + for (int j = 0; j < 1000; j += 10) + { + const Vec2 pt(j, i); + const Vec2 cpt = cam->ima2cam(pt); + const Vec2 upt = cam->removeDistortion(cpt); + const Vec2 distortedPoint = cam->cam2ima(upt); + + calibration::PointPair pp; + pp.distortedPoint = distortedPoint; + pp.undistortedPoint = pt; + pts.push_back(pp); + + line.points.push_back(distortedPoint); + } + + lines.push_back(line); } - lines.push_back(line); - } + for (int j = 0; j < 1000; j += 10) + { + calibration::LineWithPoints line; + line.angle = boost::math::constants::pi() * .25; + line.dist = 0; + line.horizontal = false; + + for (int i = 0; i < 1000; i += 10) + { + const Vec2 pt(j, i); + const Vec2 cpt = cam->ima2cam(pt); + const Vec2 upt = cam->removeDistortion(cpt); + const Vec2 distortedPoint = cam->cam2ima(upt); + + line.points.push_back(distortedPoint); + } + + lines.push_back(line); + } - for (int j = 0; j < 1000; j+=10) - { - calibration::LineWithPoints line; - line.angle = boost::math::constants::pi() * .25; - line.dist = 0; - line.horizontal = false; + // Calibrate + calibration::Statistics st; + std::shared_ptr estimatedCam = + camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, 1000, 1000, 1000, 1000, 0, 0, {0, 0, 0, 0, 0, 0}); - for (int i = 0; i < 1000; i+=10) { - const Vec2 pt(j, i); - const Vec2 cpt = cam->ima2cam(pt); - const Vec2 upt = cam->removeDistortion(cpt); - const Vec2 distortedPoint = cam->cam2ima(upt); - - line.points.push_back(distortedPoint); + std::vector lockedDistortions = {true, true, true, true, true, true}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, true, lockedDistortions)); + } + { + std::vector lockedDistortions = {false, true, true, true, true, true}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); + } + { + std::vector lockedDistortions = {false, false, false, false, false, false}; + BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); } - lines.push_back(line); - } - - - // Calibrate - calibration::Statistics st; - std::shared_ptr estimatedCam = - camera::createPinhole(camera::EINTRINSICS::PINHOLE_CAMERA_3DERADIAL4, - 1000, 1000, 1000, 1000, 0, 0, - {0, 0, 0, 0, 0, 0}); - - { - std::vector lockedDistortions = {true, true, true, true, true, true}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, true, lockedDistortions)); - } - { - std::vector lockedDistortions = {false, true, true, true, true, true}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); - } - { - std::vector lockedDistortions = {false, false, false, false, false, false}; - BOOST_CHECK(calibration::estimate(estimatedCam, st, lines, true, false, lockedDistortions)); - } - - for (const calibration::PointPair & pair : pts) - { - const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); - const Vec2 upt = estimatedCam->addDistortion(cpt); - const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); + for (const calibration::PointPair& pair : pts) + { + const Vec2 cpt = estimatedCam->ima2cam(pair.distortedPoint); + const Vec2 upt = estimatedCam->addDistortion(cpt); + const Vec2 undistortedPoint = estimatedCam->cam2ima(upt); - const double residual = (undistortedPoint - pair.undistortedPoint).norm(); + const double residual = (undistortedPoint - pair.undistortedPoint).norm(); - BOOST_CHECK_SMALL(residual, 1e-2); - } + BOOST_CHECK_SMALL(residual, 1e-2); + } } - diff --git a/src/aliceVision/calibration/exportData.cpp b/src/aliceVision/calibration/exportData.cpp index 1f6e5d9be2..d4e9d57a79 100644 --- a/src/aliceVision/calibration/exportData.cpp +++ b/src/aliceVision/calibration/exportData.cpp @@ -20,8 +20,8 @@ #include #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { void exportImages(aliceVision::dataio::FeedProvider& feed, const std::string& debugFolder, @@ -31,36 +31,38 @@ void exportImages(aliceVision::dataio::FeedProvider& feed, const cv::Size& imageSize, const std::string& suffix) { - std::vector export_params; - aliceVision::image::Image inputImage; - aliceVision::image::Image outputImage; - std::string currentImgName; - aliceVision::camera::Pinhole queryIntrinsics; - - export_params.push_back(cv::IMWRITE_JPEG_QUALITY); - export_params.push_back(100); - - aliceVision::camera::Pinhole camera( - imageSize.width, imageSize.height, - cameraMatrix.at(0, 0), cameraMatrix.at(0, 0), - cameraMatrix.at(0, 2), cameraMatrix.at(1, 2), - std::make_shared( - distCoeffs.at(0), distCoeffs.at(1), distCoeffs.at(4))); - ALICEVISION_LOG_DEBUG("Coefficients matrix :\n " << distCoeffs); - ALICEVISION_LOG_DEBUG("Exporting images ..."); - for (std::size_t currentFrame : exportFrames) - { - feed.goToFrame(currentFrame); - bool hasIntrinsics = true; - feed.readImage(inputImage, queryIntrinsics, currentImgName, hasIntrinsics); - - // drawChessboardCorners(view, boardSize, cv::Mat(pointbuf), found); - - aliceVision::camera::UndistortImage(inputImage, &camera, outputImage, static_cast(0)); - const boost::filesystem::path imagePath = boost::filesystem::path(debugFolder) / (std::to_string(currentFrame) + suffix); - aliceVision::image::writeImage(imagePath.string(), outputImage, image::ImageWriteOptions()); - } - ALICEVISION_LOG_DEBUG("... finished"); + std::vector export_params; + aliceVision::image::Image inputImage; + aliceVision::image::Image outputImage; + std::string currentImgName; + aliceVision::camera::Pinhole queryIntrinsics; + + export_params.push_back(cv::IMWRITE_JPEG_QUALITY); + export_params.push_back(100); + + aliceVision::camera::Pinhole camera( + imageSize.width, + imageSize.height, + cameraMatrix.at(0, 0), + cameraMatrix.at(0, 0), + cameraMatrix.at(0, 2), + cameraMatrix.at(1, 2), + std::make_shared(distCoeffs.at(0), distCoeffs.at(1), distCoeffs.at(4))); + ALICEVISION_LOG_DEBUG("Coefficients matrix :\n " << distCoeffs); + ALICEVISION_LOG_DEBUG("Exporting images ..."); + for (std::size_t currentFrame : exportFrames) + { + feed.goToFrame(currentFrame); + bool hasIntrinsics = true; + feed.readImage(inputImage, queryIntrinsics, currentImgName, hasIntrinsics); + + // drawChessboardCorners(view, boardSize, cv::Mat(pointbuf), found); + + aliceVision::camera::UndistortImage(inputImage, &camera, outputImage, static_cast(0)); + const boost::filesystem::path imagePath = boost::filesystem::path(debugFolder) / (std::to_string(currentFrame) + suffix); + aliceVision::image::writeImage(imagePath.string(), outputImage, image::ImageWriteOptions()); + } + ALICEVISION_LOG_DEBUG("... finished"); } void exportDebug(const std::string& debugSelectedImgFolder, @@ -73,168 +75,167 @@ void exportDebug(const std::string& debugSelectedImgFolder, const cv::Mat& distCoeffs, const cv::Size& imageSize) { - std::clock_t startDebug = std::clock(); - double durationDebug = 0.0; - - if (!debugSelectedImgFolder.empty()) - { - - startDebug = std::clock(); - exportImages(feed, debugSelectedImgFolder, calibInputFrames, - cameraMatrix, distCoeffs, imageSize, "_undistort.png"); - durationDebug = (std::clock() - startDebug) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Export debug of selected frames, duration: " << durationDebug); - } - - if (!debugRejectedImgFolder.empty()) - { - startDebug = std::clock(); - exportImages(feed, debugRejectedImgFolder, rejectedInputFrames, - cameraMatrix, distCoeffs, imageSize, "_rejected_undistort.png"); - durationDebug = (std::clock() - startDebug) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Export debug of rejected frames, duration: " << durationDebug); - } - - if (!debugRejectedImgFolder.empty()) - { - startDebug = std::clock(); - exportImages(feed, debugRejectedImgFolder, unusedImagesIndexes, - cameraMatrix, distCoeffs, imageSize, "_not_selected_undistort.png"); - durationDebug = (std::clock() - startDebug) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Export debug of not selected frames, duration: " << durationDebug); - } + std::clock_t startDebug = std::clock(); + double durationDebug = 0.0; + + if (!debugSelectedImgFolder.empty()) + { + startDebug = std::clock(); + exportImages(feed, debugSelectedImgFolder, calibInputFrames, cameraMatrix, distCoeffs, imageSize, "_undistort.png"); + durationDebug = (std::clock() - startDebug) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Export debug of selected frames, duration: " << durationDebug); + } + + if (!debugRejectedImgFolder.empty()) + { + startDebug = std::clock(); + exportImages(feed, debugRejectedImgFolder, rejectedInputFrames, cameraMatrix, distCoeffs, imageSize, "_rejected_undistort.png"); + durationDebug = (std::clock() - startDebug) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Export debug of rejected frames, duration: " << durationDebug); + } + + if (!debugRejectedImgFolder.empty()) + { + startDebug = std::clock(); + exportImages(feed, debugRejectedImgFolder, unusedImagesIndexes, cameraMatrix, distCoeffs, imageSize, "_not_selected_undistort.png"); + durationDebug = (std::clock() - startDebug) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Export debug of not selected frames, duration: " << durationDebug); + } } -void saveCameraParamsToPlainTxt(const cv::Size& imageSize, - const cv::Mat& cameraMatrix, - const cv::Mat& distCoeffs, - const std::string& filename) +void saveCameraParamsToPlainTxt(const cv::Size& imageSize, const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, const std::string& filename) { - std::ofstream fs(filename, std::ios::out); - if (!fs.is_open()) - { - ALICEVISION_LOG_WARNING("Unable to create the calibration file " << filename); - throw std::invalid_argument("Unable to create the calibration file " + filename); - } - - // the structure of the file is - // int #image width - // int #image height - // double #focal - // double #ppx principal point x-coord - // double #ppy principal point y-coord - // double #k0 - // double #k1 - // double #k2 - fs << imageSize.width << std::endl; - fs << imageSize.height << std::endl; - if (cameraMatrix.type() == cv::DataType::type) - { - fs << (cameraMatrix.at(0, 0) + cameraMatrix.at(1, 1)) / 2 << std::endl; - fs << cameraMatrix.at(0, 2) << std::endl; - fs << cameraMatrix.at(1, 2) << std::endl; - } - else - { - fs << (cameraMatrix.at(0, 0) + cameraMatrix.at(1, 1)) / 2 << std::endl; - fs << cameraMatrix.at(0, 2) << std::endl; - fs << cameraMatrix.at(1, 2) << std::endl; - } - if (distCoeffs.type() == cv::DataType::type) - { - fs << distCoeffs.at(0) << std::endl; - fs << distCoeffs.at(1) << std::endl; - fs << distCoeffs.at(4) << std::endl; - } - else - { - fs << distCoeffs.at(0) << std::endl; - fs << distCoeffs.at(1) << std::endl; - fs << distCoeffs.at(4) << std::endl; - } - fs.close(); + std::ofstream fs(filename, std::ios::out); + if (!fs.is_open()) + { + ALICEVISION_LOG_WARNING("Unable to create the calibration file " << filename); + throw std::invalid_argument("Unable to create the calibration file " + filename); + } + + // the structure of the file is + // int #image width + // int #image height + // double #focal + // double #ppx principal point x-coord + // double #ppy principal point y-coord + // double #k0 + // double #k1 + // double #k2 + fs << imageSize.width << std::endl; + fs << imageSize.height << std::endl; + if (cameraMatrix.type() == cv::DataType::type) + { + fs << (cameraMatrix.at(0, 0) + cameraMatrix.at(1, 1)) / 2 << std::endl; + fs << cameraMatrix.at(0, 2) << std::endl; + fs << cameraMatrix.at(1, 2) << std::endl; + } + else + { + fs << (cameraMatrix.at(0, 0) + cameraMatrix.at(1, 1)) / 2 << std::endl; + fs << cameraMatrix.at(0, 2) << std::endl; + fs << cameraMatrix.at(1, 2) << std::endl; + } + if (distCoeffs.type() == cv::DataType::type) + { + fs << distCoeffs.at(0) << std::endl; + fs << distCoeffs.at(1) << std::endl; + fs << distCoeffs.at(4) << std::endl; + } + else + { + fs << distCoeffs.at(0) << std::endl; + fs << distCoeffs.at(1) << std::endl; + fs << distCoeffs.at(4) << std::endl; + } + fs.close(); } void saveCameraParams(const std::string& filename, - const cv::Size& imageSize, const cv::Size& boardSize, - float squareSize, float aspectRatio, int cvCalibFlags, - const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, - const std::vector& rvecs, const std::vector& tvecs, + const cv::Size& imageSize, + const cv::Size& boardSize, + float squareSize, + float aspectRatio, + int cvCalibFlags, + const cv::Mat& cameraMatrix, + const cv::Mat& distCoeffs, + const std::vector& rvecs, + const std::vector& tvecs, const std::vector& reprojErrs, - const std::vector >& imagePoints, + const std::vector>& imagePoints, double totalAvgErr) { - cv::FileStorage fs(filename, cv::FileStorage::WRITE); - - time_t tt; - time(&tt); - struct tm *t2 = localtime(&tt); - - fs << "calibration_time" << asctime(t2); - - if (!rvecs.empty() || !reprojErrs.empty()) - fs << "nbFrames" << (int) std::max(rvecs.size(), reprojErrs.size()); - fs << "image_width" << imageSize.width; - fs << "image_height" << imageSize.height; - fs << "board_width" << boardSize.width; - fs << "board_height" << boardSize.height; - fs << "square_size" << squareSize; - - if (cvCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) - fs << "aspectRatio" << aspectRatio; - - if (cvCalibFlags != 0) - { - sprintf(asctime(t2), "flags: %s%s%s%s", - (cvCalibFlags & cv::CALIB_USE_INTRINSIC_GUESS) ? "+use_intrinsic_guess" : "", - (cvCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) ? "+fix_aspectRatio" : "", - (cvCalibFlags & cv::CALIB_FIX_PRINCIPAL_POINT) ? "+fix_principal_point" : "", - (cvCalibFlags & cv::CALIB_ZERO_TANGENT_DIST) ? "+zero_tangent_dist" : ""); - fs.writeComment(asctime(t2), 0); - } - - fs << "flags" << cvCalibFlags; - - fs << "camera_matrix" << cameraMatrix; - fs << "distortion_coefficients" << distCoeffs; - - fs << "avg_reprojection_error" << totalAvgErr; - if (!reprojErrs.empty()) - fs << "per_view_reprojection_errors" << cv::Mat(reprojErrs); - - if (!rvecs.empty() && !tvecs.empty()) - { - CV_Assert(rvecs[0].type() == tvecs[0].type()); - cv::Mat bigmat((int) rvecs.size(), 6, rvecs[0].type()); - for (std::size_t i = 0; i < rvecs.size(); i++) + cv::FileStorage fs(filename, cv::FileStorage::WRITE); + + time_t tt; + time(&tt); + struct tm* t2 = localtime(&tt); + + fs << "calibration_time" << asctime(t2); + + if (!rvecs.empty() || !reprojErrs.empty()) + fs << "nbFrames" << (int)std::max(rvecs.size(), reprojErrs.size()); + fs << "image_width" << imageSize.width; + fs << "image_height" << imageSize.height; + fs << "board_width" << boardSize.width; + fs << "board_height" << boardSize.height; + fs << "square_size" << squareSize; + + if (cvCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) + fs << "aspectRatio" << aspectRatio; + + if (cvCalibFlags != 0) { - cv::Mat r = bigmat(cv::Range(i, i + 1), cv::Range(0, 3)); - cv::Mat t = bigmat(cv::Range(i, i + 1), cv::Range(3, 6)); - - CV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1); - CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1); - //*.t() is MatExpr (not Mat) so we can use assignment operator - r = rvecs[i].t(); - t = tvecs[i].t(); + sprintf(asctime(t2), + "flags: %s%s%s%s", + (cvCalibFlags & cv::CALIB_USE_INTRINSIC_GUESS) ? "+use_intrinsic_guess" : "", + (cvCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) ? "+fix_aspectRatio" : "", + (cvCalibFlags & cv::CALIB_FIX_PRINCIPAL_POINT) ? "+fix_principal_point" : "", + (cvCalibFlags & cv::CALIB_ZERO_TANGENT_DIST) ? "+zero_tangent_dist" : ""); + fs.writeComment(asctime(t2), 0); } - fs.writeComment("a set of 6-tuples (rotation vector + translation vector) for each view", 0); - fs << "extrinsic_parameters" << bigmat; - } - - if (!imagePoints.empty()) - { - cv::Mat imagePtMat((int) imagePoints.size(), (int) imagePoints[0].size(), CV_32FC2); - for (std::size_t i = 0; i < imagePoints.size(); i++) + + fs << "flags" << cvCalibFlags; + + fs << "camera_matrix" << cameraMatrix; + fs << "distortion_coefficients" << distCoeffs; + + fs << "avg_reprojection_error" << totalAvgErr; + if (!reprojErrs.empty()) + fs << "per_view_reprojection_errors" << cv::Mat(reprojErrs); + + if (!rvecs.empty() && !tvecs.empty()) + { + CV_Assert(rvecs[0].type() == tvecs[0].type()); + cv::Mat bigmat((int)rvecs.size(), 6, rvecs[0].type()); + for (std::size_t i = 0; i < rvecs.size(); i++) + { + cv::Mat r = bigmat(cv::Range(i, i + 1), cv::Range(0, 3)); + cv::Mat t = bigmat(cv::Range(i, i + 1), cv::Range(3, 6)); + + CV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1); + CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1); + //*.t() is MatExpr (not Mat) so we can use assignment operator + r = rvecs[i].t(); + t = tvecs[i].t(); + } + fs.writeComment("a set of 6-tuples (rotation vector + translation vector) for each view", 0); + fs << "extrinsic_parameters" << bigmat; + } + + if (!imagePoints.empty()) { - cv::Mat r = imagePtMat.row(i).reshape(2, imagePtMat.cols); - cv::Mat imgpti(imagePoints[i]); - imgpti.copyTo(r); + cv::Mat imagePtMat((int)imagePoints.size(), (int)imagePoints[0].size(), CV_32FC2); + for (std::size_t i = 0; i < imagePoints.size(); i++) + { + cv::Mat r = imagePtMat.row(i).reshape(2, imagePtMat.cols); + cv::Mat imgpti(imagePoints[i]); + imgpti.copyTo(r); + } + fs << "image_points" << imagePtMat; } - fs << "image_points" << imagePtMat; - } - const std::string txtfilename = filename.substr(0, filename.find_last_of(".")) + ".cal.txt"; - saveCameraParamsToPlainTxt(imageSize, cameraMatrix, distCoeffs, txtfilename); + const std::string txtfilename = filename.substr(0, filename.find_last_of(".")) + ".cal.txt"; + saveCameraParamsToPlainTxt(imageSize, cameraMatrix, distCoeffs, txtfilename); } -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/exportData.hpp b/src/aliceVision/calibration/exportData.hpp index 8e8b33972a..a393d6b865 100644 --- a/src/aliceVision/calibration/exportData.hpp +++ b/src/aliceVision/calibration/exportData.hpp @@ -13,8 +13,8 @@ #include #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { /** * @brief This function exports undistorted images. @@ -64,12 +64,9 @@ void exportDebug(const std::string& debugSelectedImgFolder, * @param[in] imageSize The size of the image. * @param[in] cameraMatrix The calibration matrix K of the camera. * @param[in] distCoeffs The distortion coefficients. - * @param[out] filename The name of the camera parameters file. + * @param[out] filename The name of the camera parameters file. */ -void saveCameraParamsToPlainTxt(const cv::Size& imageSize, - const cv::Mat& cameraMatrix, - const cv::Mat& distCoeffs, - const std::string& filename); +void saveCameraParamsToPlainTxt(const cv::Size& imageSize, const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, const std::string& filename); /** * @brief This function saves some parameters' camera into a txt file. @@ -89,14 +86,18 @@ void saveCameraParamsToPlainTxt(const cv::Size& imageSize, * @param[in] totalAvgErr The average of the reprojection errors. */ void saveCameraParams(const std::string& filename, - const cv::Size& imageSize, const cv::Size& boardSize, - float squareSize, float aspectRatio, int cvCalibFlags, - const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, - const std::vector& rvecs, const std::vector& tvecs, + const cv::Size& imageSize, + const cv::Size& boardSize, + float squareSize, + float aspectRatio, + int cvCalibFlags, + const cv::Mat& cameraMatrix, + const cv::Mat& distCoeffs, + const std::vector& rvecs, + const std::vector& tvecs, const std::vector& reprojErrs, - const std::vector >& imagePoints, + const std::vector>& imagePoints, double totalAvgErr); -}//namespace calibration -}//namespace aliceVision - +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/patternDetect.cpp b/src/aliceVision/calibration/patternDetect.cpp index 749c7f9adc..2bedbc2f2c 100644 --- a/src/aliceVision/calibration/patternDetect.cpp +++ b/src/aliceVision/calibration/patternDetect.cpp @@ -14,9 +14,9 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) -#include -#include -#include + #include + #include + #include #endif #include @@ -27,213 +27,219 @@ #include #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { -std::istream& operator>>(std::istream &stream, Pattern &pattern) +std::istream& operator>>(std::istream& stream, Pattern& pattern) { - std::string token; - stream >> token; - boost::to_upper(token); - - if (token == "CHESSBOARD") - pattern = aliceVision::calibration::Pattern::CHESSBOARD; - else if (token == "CIRCLES") - pattern = aliceVision::calibration::Pattern::CIRCLES_GRID; - else if (token == "ASYMMETRIC_CIRCLES") - pattern = aliceVision::calibration::Pattern::ASYMMETRIC_CIRCLES_GRID; - else if (token == "ASYMMETRIC_CCTAG") + std::string token; + stream >> token; + boost::to_upper(token); + + if (token == "CHESSBOARD") + pattern = aliceVision::calibration::Pattern::CHESSBOARD; + else if (token == "CIRCLES") + pattern = aliceVision::calibration::Pattern::CIRCLES_GRID; + else if (token == "ASYMMETRIC_CIRCLES") + pattern = aliceVision::calibration::Pattern::ASYMMETRIC_CIRCLES_GRID; + else if (token == "ASYMMETRIC_CCTAG") #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - pattern = ASYMMETRIC_CCTAG_GRID; + pattern = ASYMMETRIC_CCTAG_GRID; #else - throw boost::program_options::invalid_option_value("Not builded with CCTag support."); + throw boost::program_options::invalid_option_value("Not builded with CCTag support."); #endif - else - throw boost::program_options::invalid_option_value(std::string("Invalid pattern: ") + token); - return stream; + else + throw boost::program_options::invalid_option_value(std::string("Invalid pattern: ") + token); + return stream; } -std::ostream& operator<<(std::ostream &stream, const Pattern pattern) +std::ostream& operator<<(std::ostream& stream, const Pattern pattern) { - switch(pattern) - { - case aliceVision::calibration::Pattern::CHESSBOARD: - stream << "CHESSBOARD"; - break; - case aliceVision::calibration::Pattern::CIRCLES_GRID: - stream << "CIRCLES"; - break; - case aliceVision::calibration::Pattern::ASYMMETRIC_CIRCLES_GRID: - stream << "ASYMMETRIC_CIRCLES"; - break; + switch (pattern) + { + case aliceVision::calibration::Pattern::CHESSBOARD: + stream << "CHESSBOARD"; + break; + case aliceVision::calibration::Pattern::CIRCLES_GRID: + stream << "CIRCLES"; + break; + case aliceVision::calibration::Pattern::ASYMMETRIC_CIRCLES_GRID: + stream << "ASYMMETRIC_CIRCLES"; + break; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case aliceVision::calibration::Pattern::ASYMMETRIC_CCTAG_GRID: - stream << "ASYMMETRIC_CCTAG"; - break; + case aliceVision::calibration::Pattern::ASYMMETRIC_CCTAG_GRID: + stream << "ASYMMETRIC_CCTAG"; + break; #endif - } - return stream; + } + return stream; } -bool findPattern(const Pattern& pattern, const cv::Mat& viewGray, const cv::Size& boardSize, std::vector& detectedId, std::vector& pointbuf) +bool findPattern(const Pattern& pattern, + const cv::Mat& viewGray, + const cv::Size& boardSize, + std::vector& detectedId, + std::vector& pointbuf) { - bool found = false; - std::clock_t startCh; - double durationCh; + bool found = false; + std::clock_t startCh; + double durationCh; - switch (pattern) - { - case CHESSBOARD: + switch (pattern) { - startCh = std::clock(); - - found = cv::findChessboardCorners(viewGray, boardSize, pointbuf, - cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE); - durationCh = (std::clock() - startCh) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Find chessboard corners' duration: " << durationCh); - - // improve the found corners' coordinate accuracy - if (found) - { - startCh = std::clock(); - cv::cornerSubPix(viewGray, pointbuf, cv::Size(11, 11), cv::Size(-1, -1), - cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.1)); - - durationCh = (std::clock() - startCh) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Refine chessboard corners' duration: " << durationCh); - } - break; - } - case CIRCLES_GRID: - { - startCh = std::clock(); + case CHESSBOARD: + { + startCh = std::clock(); + + found = cv::findChessboardCorners( + viewGray, boardSize, pointbuf, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE); + durationCh = (std::clock() - startCh) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Find chessboard corners' duration: " << durationCh); + + // improve the found corners' coordinate accuracy + if (found) + { + startCh = std::clock(); + cv::cornerSubPix(viewGray, + pointbuf, + cv::Size(11, 11), + cv::Size(-1, -1), + cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.1)); + + durationCh = (std::clock() - startCh) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Refine chessboard corners' duration: " << durationCh); + } + break; + } + case CIRCLES_GRID: + { + startCh = std::clock(); - found = cv::findCirclesGrid(viewGray, boardSize, pointbuf); + found = cv::findCirclesGrid(viewGray, boardSize, pointbuf); - durationCh = (std::clock() - startCh) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Find circles grid duration: " << durationCh); - break; - } - case ASYMMETRIC_CIRCLES_GRID: - { - startCh = std::clock(); + durationCh = (std::clock() - startCh) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Find circles grid duration: " << durationCh); + break; + } + case ASYMMETRIC_CIRCLES_GRID: + { + startCh = std::clock(); - found = cv::findCirclesGrid(viewGray, boardSize, pointbuf, cv::CALIB_CB_ASYMMETRIC_GRID); + found = cv::findCirclesGrid(viewGray, boardSize, pointbuf, cv::CALIB_CB_ASYMMETRIC_GRID); - durationCh = (std::clock() - startCh) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Find asymmetric circles grid duration: " << durationCh); - break; - } - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case ASYMMETRIC_CCTAG_GRID: - { - startCh = std::clock(); - const std::size_t nRings = 3; - const int pipeId = 0; - const std::size_t frame = 1; - cctag::Parameters cctagParams(nRings); - boost::ptr_list cctags; - cctag::logtime::Mgmt durations( 25 ); - - cctag::cctagDetection(cctags, pipeId, frame, viewGray, cctagParams, &durations); - - boost::ptr_list::iterator iterCCTags = cctags.begin(); - for(; iterCCTags != cctags.end(); iterCCTags++) - { - // Ignore CCTags without identification - if(iterCCTags->id() < 0) - continue; - // Store detected Id - detectedId.push_back(iterCCTags->id()); - - // Store pixel coordinates of detected markers - cv::Point2f detectedPoint((float) iterCCTags->x(), (float) iterCCTags->y()); - pointbuf.push_back(detectedPoint); - } - assert(detectedId.size() == pointbuf.size()); - found = true; - - durationCh = (std::clock() - startCh) / (double) CLOCKS_PER_SEC; - ALICEVISION_LOG_DEBUG("Find asymmetric CCTag grid duration: " << durationCh); - break; - } - #endif + durationCh = (std::clock() - startCh) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Find asymmetric circles grid duration: " << durationCh); + break; + } +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) + case ASYMMETRIC_CCTAG_GRID: + { + startCh = std::clock(); + const std::size_t nRings = 3; + const int pipeId = 0; + const std::size_t frame = 1; + cctag::Parameters cctagParams(nRings); + boost::ptr_list cctags; + cctag::logtime::Mgmt durations(25); + + cctag::cctagDetection(cctags, pipeId, frame, viewGray, cctagParams, &durations); + + boost::ptr_list::iterator iterCCTags = cctags.begin(); + for (; iterCCTags != cctags.end(); iterCCTags++) + { + // Ignore CCTags without identification + if (iterCCTags->id() < 0) + continue; + // Store detected Id + detectedId.push_back(iterCCTags->id()); + + // Store pixel coordinates of detected markers + cv::Point2f detectedPoint((float)iterCCTags->x(), (float)iterCCTags->y()); + pointbuf.push_back(detectedPoint); + } + assert(detectedId.size() == pointbuf.size()); + found = true; + + durationCh = (std::clock() - startCh) / (double)CLOCKS_PER_SEC; + ALICEVISION_LOG_DEBUG("Find asymmetric CCTag grid duration: " << durationCh); + break; + } +#endif - default: - throw std::logic_error("LensCalibration: Unknown pattern type."); - } + default: + throw std::logic_error("LensCalibration: Unknown pattern type."); + } - if(detectedId.empty()) - { - // default indexes - for(std::size_t i = 0; i < pointbuf.size(); ++i) + if (detectedId.empty()) { - detectedId.push_back(i); + // default indexes + for (std::size_t i = 0; i < pointbuf.size(); ++i) + { + detectedId.push_back(i); + } } - } - return found; + return found; } -void calcChessboardCorners(std::vector& corners, const cv::Size& boardSize, - const float squareSize, Pattern pattern = Pattern::CHESSBOARD) +void calcChessboardCorners(std::vector& corners, + const cv::Size& boardSize, + const float squareSize, + Pattern pattern = Pattern::CHESSBOARD) { - corners.resize(0); + corners.resize(0); - switch (pattern) - { - case CHESSBOARD: - case CIRCLES_GRID: - { - for (int y = 0; y < boardSize.height; ++y) - for (int x = 0; x < boardSize.width; ++x) - corners.push_back(cv::Point3f(float(x * squareSize), - float(y * squareSize), 0)); - break; - } - case ASYMMETRIC_CIRCLES_GRID: - { - for (int y = 0; y < boardSize.height; ++y) - for (int x = 0; x < boardSize.width; ++x) - corners.push_back(cv::Point3f(float((2 * x + y % 2) * squareSize), - float(y * squareSize), - 0)); - break; - } - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case ASYMMETRIC_CCTAG_GRID: + switch (pattern) { - // The real number of lines in the cctag asymmetric grid - std::size_t height = 2 * boardSize.height - 1; - for (int y = 0; y < height; ++y) + case CHESSBOARD: + case CIRCLES_GRID: { - // Less markers on odd lines if odd width - int width = (y % 2) ? boardSize.width : boardSize.width - (boardSize.width % 2); - for (int x = 0; x < width; ++x) - corners.push_back(cv::Point3f(float((2 * x + y % 2) * squareSize), - float(y * squareSize), - 0)); + for (int y = 0; y < boardSize.height; ++y) + for (int x = 0; x < boardSize.width; ++x) + corners.push_back(cv::Point3f(float(x * squareSize), float(y * squareSize), 0)); + break; } - break; - } - #endif + case ASYMMETRIC_CIRCLES_GRID: + { + for (int y = 0; y < boardSize.height; ++y) + for (int x = 0; x < boardSize.width; ++x) + corners.push_back(cv::Point3f(float((2 * x + y % 2) * squareSize), float(y * squareSize), 0)); + break; + } +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) + case ASYMMETRIC_CCTAG_GRID: + { + // The real number of lines in the cctag asymmetric grid + std::size_t height = 2 * boardSize.height - 1; + for (int y = 0; y < height; ++y) + { + // Less markers on odd lines if odd width + int width = (y % 2) ? boardSize.width : boardSize.width - (boardSize.width % 2); + for (int x = 0; x < width; ++x) + corners.push_back(cv::Point3f(float((2 * x + y % 2) * squareSize), float(y * squareSize), 0)); + } + break; + } +#endif - default: - throw std::invalid_argument("Unknown pattern type."); - } + default: + throw std::invalid_argument("Unknown pattern type."); + } } -void computeObjectPoints(const cv::Size& boardSize, Pattern pattern, const float squareSize, - const std::vector >& imagePoints, - std::vector >& objectPoints) +void computeObjectPoints(const cv::Size& boardSize, + Pattern pattern, + const float squareSize, + const std::vector>& imagePoints, + std::vector>& objectPoints) { - std::vector templateObjectPoints; + std::vector templateObjectPoints; - // Generate the object points coordinates - calcChessboardCorners(templateObjectPoints, boardSize, squareSize, pattern); + // Generate the object points coordinates + calcChessboardCorners(templateObjectPoints, boardSize, squareSize, pattern); - // Assign the corners to all items - objectPoints.resize(imagePoints.size(), templateObjectPoints); + // Assign the corners to all items + objectPoints.resize(imagePoints.size(), templateObjectPoints); } -}//namespace calibration -}//namespace aliceVision +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/calibration/patternDetect.hpp b/src/aliceVision/calibration/patternDetect.hpp index a98ad6aaf4..fb1e8ed1be 100644 --- a/src/aliceVision/calibration/patternDetect.hpp +++ b/src/aliceVision/calibration/patternDetect.hpp @@ -13,36 +13,37 @@ #include -namespace aliceVision{ -namespace calibration{ +namespace aliceVision { +namespace calibration { enum Pattern { - CHESSBOARD = 0, - CIRCLES_GRID = 1, - ASYMMETRIC_CIRCLES_GRID = 2 + CHESSBOARD = 0, + CIRCLES_GRID = 1, + ASYMMETRIC_CIRCLES_GRID = 2 #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - , ASYMMETRIC_CCTAG_GRID = 4 + , + ASYMMETRIC_CCTAG_GRID = 4 #endif }; /** * @brief Read pattern from console. - * + * * @param[in,out] stream * @param[in] pattern * @return stream */ -std::istream& operator>>(std::istream &stream, Pattern &pattern); +std::istream& operator>>(std::istream& stream, Pattern& pattern); /** * @brief Write pattern to console. - * + * * @param[out] stream * @param[in] pattern * @return stream */ -std::ostream& operator<<(std::ostream &stream, const Pattern pattern); +std::ostream& operator<<(std::ostream& stream, const Pattern pattern); /** * @brief This function detects the checkerboard in images @@ -54,7 +55,11 @@ std::ostream& operator<<(std::ostream &stream, const Pattern pattern); * @param[out] pointbuf Coordinates of the 2D points in each image. * @return True if the pattern is found, otherwise false. */ -bool findPattern(const Pattern& pattern, const cv::Mat& viewGray, const cv::Size& boardSize, std::vector& detectedId, std::vector& pointbuf); +bool findPattern(const Pattern& pattern, + const cv::Mat& viewGray, + const cv::Size& boardSize, + std::vector& detectedId, + std::vector& pointbuf); /** * @brief This function computes the points' coordinates of the checkerboard. @@ -64,8 +69,7 @@ bool findPattern(const Pattern& pattern, const cv::Mat& viewGray, const cv::Size * @param[in] squareSize The distance between two points of the calibration pattern. * @param[in] pattern The type of pattern used for the calibration. */ -void calcChessboardCorners(std::vector& corners, const cv::Size& boardSize, - const float squareSize, Pattern pattern); +void calcChessboardCorners(std::vector& corners, const cv::Size& boardSize, const float squareSize, Pattern pattern); /** * @brief This function creates an object which stores all the points of the images. @@ -76,11 +80,11 @@ void calcChessboardCorners(std::vector& corners, const cv::Size& bo * @param[in] imagePoints Coordinates of the 2D points in each image of the sequence. * @param[out] objectPoints Coordinates of the 3D points in each image of the sequence. */ -void computeObjectPoints(const cv::Size& boardSize, Pattern pattern, const float squareSize, - const std::vector >& imagePoints, - std::vector >& objectPoints); - -}//namespace calibration -}//namespace aliceVision - +void computeObjectPoints(const cv::Size& boardSize, + Pattern pattern, + const float squareSize, + const std::vector>& imagePoints, + std::vector>& objectPoints); +} // namespace calibration +} // namespace aliceVision diff --git a/src/aliceVision/camera/Distortion.hpp b/src/aliceVision/camera/Distortion.hpp index 056056cbc0..9002b37b17 100644 --- a/src/aliceVision/camera/Distortion.hpp +++ b/src/aliceVision/camera/Distortion.hpp @@ -22,7 +22,7 @@ namespace camera { */ class Distortion { -public: + public: Distortion() = default; virtual EDISTORTION getType() const = 0; @@ -30,10 +30,7 @@ class Distortion virtual Distortion* clone() const = 0; // not virtual as child classes do not hold any data - bool operator==(const Distortion& other) const - { - return _distortionParams == other._distortionParams; - } + bool operator==(const Distortion& other) const { return _distortionParams == other._distortionParams; } void setParameters(const std::vector& params) { @@ -41,70 +38,40 @@ class Distortion { return; } - + for (int i = 0; i < _distortionParams.size(); i++) { _distortionParams[i] = params[i]; } } - inline std::vector& getParameters() - { - return _distortionParams; - } + inline std::vector& getParameters() { return _distortionParams; } - inline const std::vector& getParameters() const - { - return _distortionParams; - } + inline const std::vector& getParameters() const { return _distortionParams; } - inline std::size_t getDistortionParametersCount() const - { - return _distortionParams.size(); - } + inline std::size_t getDistortionParametersCount() const { return _distortionParams.size(); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - virtual Vec2 addDistortion(const Vec2& p) const - { - return p; - } + virtual Vec2 addDistortion(const Vec2& p) const { return p; } /// Remove distortion (return p' such that disto(p') = p) - virtual Vec2 removeDistortion(const Vec2& p) const - { - return p; - } + virtual Vec2 removeDistortion(const Vec2& p) const { return p; } - virtual double getUndistortedRadius(double r) const - { - return r; - } + virtual double getUndistortedRadius(double r) const { return r; } - virtual Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const - { - return Eigen::Matrix2d::Identity(); - } + virtual Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const { return Eigen::Matrix2d::Identity(); } - virtual Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const - { - return Eigen::MatrixXd(0, 0); - } + virtual Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const { return Eigen::MatrixXd(0, 0); } - virtual Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const - { - return Eigen::Matrix2d::Identity(); - } + virtual Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const { return Eigen::Matrix2d::Identity(); } - virtual Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const - { - return Eigen::MatrixXd(0, 0); - } + virtual Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const { return Eigen::MatrixXd(0, 0); } virtual ~Distortion() = default; -protected: + protected: std::vector _distortionParams{}; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Distortion3DE.cpp b/src/aliceVision/camera/Distortion3DE.cpp index 5cf521750e..50bd0f6af2 100644 --- a/src/aliceVision/camera/Distortion3DE.cpp +++ b/src/aliceVision/camera/Distortion3DE.cpp @@ -9,7 +9,7 @@ namespace aliceVision { namespace camera { -Vec2 Distortion3DERadial4::addDistortion(const Vec2 & p) const +Vec2 Distortion3DERadial4::addDistortion(const Vec2& p) const { const double& c2 = _distortionParams[0]; const double& c4 = _distortionParams[1]; @@ -42,7 +42,7 @@ Vec2 Distortion3DERadial4::addDistortion(const Vec2 & p) const return np; } -Eigen::Matrix2d Distortion3DERadial4::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d Distortion3DERadial4::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double& c2 = _distortionParams[0]; const double& c4 = _distortionParams[1]; @@ -61,7 +61,8 @@ Eigen::Matrix2d Distortion3DERadial4::getDerivativeAddDistoWrtPt(const Vec2 & p) const double r4 = r2 * r2; const double eps = 1e-16; - if (r2 < eps) { + if (r2 < eps) + { return Eigen::Matrix::Zero(); } @@ -107,17 +108,13 @@ Eigen::Matrix2d Distortion3DERadial4::getDerivativeAddDistoWrtPt(const Vec2 & p) d_y_d_p(0, 1) = 1.0; Eigen::Matrix ret; - ret.block<1, 2>(0, 0) = (x * d_p1_d_p + d_x_d_p * p1) + - (p2 * d_p4_d_p + d_p2_d_p * p4) + - (p6 * d_p5_d_p + d_p6_d_p * p5); - ret.block<1, 2>(1, 0) = (y * d_p1_d_p + d_y_d_p * p1) + - (p3 * d_p5_d_p + d_p3_d_p * p5) + - (p6 * d_p4_d_p + d_p6_d_p * p4); + ret.block<1, 2>(0, 0) = (x * d_p1_d_p + d_x_d_p * p1) + (p2 * d_p4_d_p + d_p2_d_p * p4) + (p6 * d_p5_d_p + d_p6_d_p * p5); + ret.block<1, 2>(1, 0) = (y * d_p1_d_p + d_y_d_p * p1) + (p3 * d_p5_d_p + d_p3_d_p * p5) + (p6 * d_p4_d_p + d_p6_d_p * p4); return ret; } -Eigen::MatrixXd Distortion3DERadial4::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd Distortion3DERadial4::getDerivativeAddDistoWrtDisto(const Vec2& p) const { const double& c2 = _distortionParams[0]; const double& c4 = _distortionParams[1]; @@ -136,7 +133,8 @@ Eigen::MatrixXd Distortion3DERadial4::getDerivativeAddDistoWrtDisto(const Vec2 & const double r4 = r2 * r2; const double eps = 1e-8; - if (r2 < eps) { + if (r2 < eps) + { return Eigen::Matrix::Zero(); } @@ -193,13 +191,14 @@ Vec2 Distortion3DERadial4::removeDistortion(const Vec2& p) const undistorted_value = undistorted_value - getDerivativeAddDistoWrtPt(undistorted_value).inverse() * diff; diff = addDistortion(undistorted_value) - p; iter++; - if (iter > 100) break; + if (iter > 100) + break; } return undistorted_value; } -Vec2 Distortion3DEAnamorphic4::addDistortion(const Vec2 & p) const +Vec2 Distortion3DEAnamorphic4::addDistortion(const Vec2& p) const { const double& cx02 = _distortionParams[0]; const double& cy02 = _distortionParams[1]; @@ -234,7 +233,7 @@ Vec2 Distortion3DEAnamorphic4::addDistortion(const Vec2 & p) const const double& y = p.y(); // First rotate axis - const double xr = cphi* x + sphi * y; + const double xr = cphi * x + sphi * y; const double yr = -sphi * x + cphi * y; const double xx = xr * xr; @@ -252,15 +251,12 @@ Vec2 Distortion3DEAnamorphic4::addDistortion(const Vec2 & p) const const double squizzed_y = yd * sqy; // Unrotate axis - Vec2 np{ - cphi* squizzed_x - sphi * squizzed_y, - sphi* squizzed_x + cphi * squizzed_y - }; + Vec2 np{cphi * squizzed_x - sphi * squizzed_y, sphi * squizzed_x + cphi * squizzed_y}; return np; } -Eigen::Matrix2d Distortion3DEAnamorphic4::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d Distortion3DEAnamorphic4::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double& cx02 = _distortionParams[0]; const double& cy02 = _distortionParams[1]; @@ -295,7 +291,7 @@ Eigen::Matrix2d Distortion3DEAnamorphic4::getDerivativeAddDistoWrtPt(const Vec2 const double& y = p.y(); // First rotate axis - double xr = cphi* x + sphi * y; + double xr = cphi * x + sphi * y; double yr = -sphi * x + cphi * y; const double xx = xr * xr; @@ -332,7 +328,7 @@ Eigen::Matrix2d Distortion3DEAnamorphic4::getDerivativeAddDistoWrtPt(const Vec2 return d_np_d_squizzed * d_squizzed_d_d * d_d_d_r * d_r_d_p; } -Eigen::MatrixXd Distortion3DEAnamorphic4::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd Distortion3DEAnamorphic4::getDerivativeAddDistoWrtDisto(const Vec2& p) const { const double& cx02 = _distortionParams[0]; const double& cy02 = _distortionParams[1]; @@ -366,7 +362,7 @@ Eigen::MatrixXd Distortion3DEAnamorphic4::getDerivativeAddDistoWrtDisto(const Ve const double& y = p.y(); // First rotate axis - const double xr = cphi* x + sphi * y; + const double xr = cphi * x + sphi * y; const double yr = -sphi * x + cphi * y; const double xx = xr * xr; @@ -453,9 +449,7 @@ Eigen::MatrixXd Distortion3DEAnamorphic4::getDerivativeAddDistoWrtDisto(const Ve d_distop_d_disto(9, 7) = -1.0; d_distop_d_disto(9, 9) = 1.0; - Eigen::Matrix J = - (d_np_d_squizzed * d_squizzed_d_disto) + - (d_np_d_squizzed * d_squizzed_d_d * d_d_d_distop * d_distop_d_disto); + Eigen::Matrix J = (d_np_d_squizzed * d_squizzed_d_disto) + (d_np_d_squizzed * d_squizzed_d_d * d_d_d_distop * d_distop_d_disto); J.block(0, 10, 2, 4) = Eigen::Matrix::Zero(); @@ -475,13 +469,14 @@ Vec2 Distortion3DEAnamorphic4::removeDistortion(const Vec2& p) const undistorted_value = undistorted_value - getDerivativeAddDistoWrtPt(undistorted_value).inverse() * diff; diff = addDistortion(undistorted_value) - p; iter++; - if (iter > 10) break; + if (iter > 10) + break; } return undistorted_value; } -Vec2 Distortion3DEClassicLD::addDistortion(const Vec2 & p) const +Vec2 Distortion3DEClassicLD::addDistortion(const Vec2& p) const { const double& delta = _distortionParams[0]; const double& invepsilon = _distortionParams[1]; @@ -518,7 +513,7 @@ Vec2 Distortion3DEClassicLD::addDistortion(const Vec2 & p) const return np; } -Eigen::Matrix2d Distortion3DEClassicLD::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d Distortion3DEClassicLD::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double& delta = _distortionParams[0]; const double& invepsilon = _distortionParams[1]; @@ -568,7 +563,7 @@ Eigen::Matrix2d Distortion3DEClassicLD::getDerivativeAddDistoWrtPt(const Vec2 & return ret; } -Eigen::MatrixXd Distortion3DEClassicLD::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd Distortion3DEClassicLD::getDerivativeAddDistoWrtDisto(const Vec2& p) const { const double& delta = _distortionParams[0]; const double& invepsilon = _distortionParams[1]; @@ -670,11 +665,12 @@ Vec2 Distortion3DEClassicLD::removeDistortion(const Vec2& p) const undistorted_value = undistorted_value - getDerivativeAddDistoWrtPt(undistorted_value).inverse() * diff; diff = addDistortion(undistorted_value) - p; iter++; - if (iter > 1000) break; + if (iter > 1000) + break; } return undistorted_value; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Distortion3DE.hpp b/src/aliceVision/camera/Distortion3DE.hpp index 3b438c81c0..2b8d0f8999 100644 --- a/src/aliceVision/camera/Distortion3DE.hpp +++ b/src/aliceVision/camera/Distortion3DE.hpp @@ -19,53 +19,44 @@ namespace camera { * @class DistortionRadial3DE * @brief 3DE radial distortion */ -class Distortion3DERadial4 : public Distortion { -public: +class Distortion3DERadial4 : public Distortion +{ + public: /** * @brief Default constructor, no distortion. */ - Distortion3DERadial4() - { - _distortionParams = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - } + Distortion3DERadial4() { _distortionParams = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; } /** * @brief Constructor with the three coefficients */ - explicit Distortion3DERadial4(double c2, double c4, double u1, double v1, double u3, double v3) - { - _distortionParams = {c2, c4, u1, v1, u3, v3}; - } + explicit Distortion3DERadial4(double c2, double c4, double u1, double v1, double u3, double v3) { _distortionParams = {c2, c4, u1, v1, u3, v3}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_3DERADIAL4; } Distortion3DERadial4* clone() const override { return new Distortion3DERadial4(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; - - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override { ALICEVISION_THROW_ERROR("Invalid class for getDerivativeRemoveDistoWrtPt"); } - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override { ALICEVISION_THROW_ERROR("Invalid class for getDerivativeRemoveDistoWrtDisto"); } - double getUndistortedRadius(double r) const override - { - ALICEVISION_THROW_ERROR("Invalid class for getUndistortedRadius"); - } + double getUndistortedRadius(double r) const override { ALICEVISION_THROW_ERROR("Invalid class for getUndistortedRadius"); } ~Distortion3DERadial4() override = default; }; @@ -74,26 +65,30 @@ class Distortion3DERadial4 : public Distortion { * @class DistortionRadialAnamorphic4 * @brief Anamorphic radial distortion */ -class Distortion3DEAnamorphic4 : public Distortion { -public: +class Distortion3DEAnamorphic4 : public Distortion +{ + public: /** * @brief Default constructor, no distortion. */ - Distortion3DEAnamorphic4() - { - _distortionParams = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; - } + Distortion3DEAnamorphic4() { _distortionParams = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; } /** * @brief Constructor with the three coefficients */ - explicit Distortion3DEAnamorphic4(double cx02, double cy02, - double cx22, double cy22, - double cx04, double cy04, - double cx24, double cy24, - double cx44, double cy44, + explicit Distortion3DEAnamorphic4(double cx02, + double cy02, + double cx22, + double cy22, + double cx04, + double cy04, + double cx24, + double cy24, + double cx44, + double cy44, double phi, - double sqx, double sqy, + double sqx, + double sqy, double ps) { _distortionParams = {cx02, cy02, cx22, cy22, cx04, cy04, cx24, cy24, cx44, cy44, phi, sqx, sqy, ps}; @@ -104,29 +99,26 @@ class Distortion3DEAnamorphic4 : public Distortion { Distortion3DEAnamorphic4* clone() const override { return new Distortion3DEAnamorphic4(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override { ALICEVISION_THROW_ERROR("Invalid class for getDerivativeRemoveDistoWrtPt"); } - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override { ALICEVISION_THROW_ERROR("Invalid class for getDerivativeRemoveDistoWrtDisto"); } - double getUndistortedRadius(double r) const override - { - ALICEVISION_THROW_ERROR("Invalid class for getUndistortedRadius"); - } + double getUndistortedRadius(double r) const override { ALICEVISION_THROW_ERROR("Invalid class for getUndistortedRadius"); } ~Distortion3DEAnamorphic4() override = default; }; @@ -135,22 +127,18 @@ class Distortion3DEAnamorphic4 : public Distortion { * @class Distortion3DEClassicLD * @brief Anamorphic radial distortion */ -class Distortion3DEClassicLD : public Distortion { -public: +class Distortion3DEClassicLD : public Distortion +{ + public: /** * @brief Default constructor, no distortion. */ - Distortion3DEClassicLD() - { - _distortionParams = {0.0, 1.0, 0.0, 0.0, 0.0}; - } + Distortion3DEClassicLD() { _distortionParams = {0.0, 1.0, 0.0, 0.0, 0.0}; } /** * @brief Constructor with the three coefficients */ - explicit Distortion3DEClassicLD(double delta, double epsilon, - double mux, double muy, - double q) + explicit Distortion3DEClassicLD(double delta, double epsilon, double mux, double muy, double q) { _distortionParams = {delta, 1.0 / epsilon, mux, muy, q}; } @@ -160,32 +148,29 @@ class Distortion3DEClassicLD : public Distortion { Distortion3DEClassicLD* clone() const override { return new Distortion3DEClassicLD(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override { ALICEVISION_THROW_ERROR("Invalid class for getDerivativeRemoveDistoWrtPt"); } - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override { ALICEVISION_THROW_ERROR("Invalid class for getDerivativeRemoveDistoWrtDisto"); } - double getUndistortedRadius(double r) const override - { - ALICEVISION_THROW_ERROR("Invalid class for getUndistortedRadius"); - } + double getUndistortedRadius(double r) const override { ALICEVISION_THROW_ERROR("Invalid class for getUndistortedRadius"); } ~Distortion3DEClassicLD() override = default; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionBrown.cpp b/src/aliceVision/camera/DistortionBrown.cpp index 998df859bc..9f75607305 100644 --- a/src/aliceVision/camera/DistortionBrown.cpp +++ b/src/aliceVision/camera/DistortionBrown.cpp @@ -13,7 +13,7 @@ namespace camera { Vec2 DistortionBrown::distoFunction(const std::vector& params, const Vec2& p) { - const double& k1 = params[0], k2 = params[1], k3 = params[2], t1 = params[3], t2 = params[4]; + const double &k1 = params[0], k2 = params[1], k3 = params[2], t1 = params[3], t2 = params[4]; const double r2 = p(0) * p(0) + p(1) * p(1); const double r4 = r2 * r2; const double r6 = r4 * r2; @@ -25,10 +25,7 @@ Vec2 DistortionBrown::distoFunction(const std::vector& params, const Vec return d; } -Vec2 DistortionBrown::addDistortion(const Vec2& p) const -{ - return (p + distoFunction(_distortionParams, p)); -} +Vec2 DistortionBrown::addDistortion(const Vec2& p) const { return (p + distoFunction(_distortionParams, p)); } Vec2 DistortionBrown::removeDistortion(const Vec2& p) const { @@ -43,5 +40,5 @@ Vec2 DistortionBrown::removeDistortion(const Vec2& p) const return p_u; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionBrown.hpp b/src/aliceVision/camera/DistortionBrown.hpp index 1f96cebee5..5de17004f2 100644 --- a/src/aliceVision/camera/DistortionBrown.hpp +++ b/src/aliceVision/camera/DistortionBrown.hpp @@ -14,24 +14,14 @@ namespace camera { class DistortionBrown : public Distortion { -public: - DistortionBrown() - { - _distortionParams = {0.0, 0.0, 0.0, 0.0, 0.0}; - } - - DistortionBrown(double p1, double p2, double p3, double p4, double p5) - { - _distortionParams = {p1, p2, p3, p4, p5}; - } + public: + DistortionBrown() { _distortionParams = {0.0, 0.0, 0.0, 0.0, 0.0}; } + DistortionBrown(double p1, double p2, double p3, double p4, double p5) { _distortionParams = {p1, p2, p3, p4, p5}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_BROWN; } - DistortionBrown* clone() const override - { - return new DistortionBrown(*this); - } + DistortionBrown* clone() const override { return new DistortionBrown(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) Vec2 addDistortion(const Vec2& p) const override; @@ -45,5 +35,5 @@ class DistortionBrown : public Distortion ~DistortionBrown() override = default; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionFisheye.cpp b/src/aliceVision/camera/DistortionFisheye.cpp index 2d1670b60e..fbe6443a38 100644 --- a/src/aliceVision/camera/DistortionFisheye.cpp +++ b/src/aliceVision/camera/DistortionFisheye.cpp @@ -9,7 +9,7 @@ namespace aliceVision { namespace camera { -Vec2 DistortionFisheye::addDistortion(const Vec2 & p) const +Vec2 DistortionFisheye::addDistortion(const Vec2& p) const { const double eps = 1e-8; const double r = std::hypot(p(0), p(1)); @@ -24,21 +24,21 @@ Vec2 DistortionFisheye::addDistortion(const Vec2 & p) const const double& k4 = _distortionParams.at(3); const double theta = std::atan(r); - const double theta2 = theta*theta; - const double theta3 = theta2*theta; - const double theta4 = theta2*theta2; - const double theta5 = theta4*theta; - const double theta6 = theta3*theta3; - const double theta7 = theta6*theta; - const double theta8 = theta4*theta4; - const double theta9 = theta8*theta; - const double theta_dist = theta + k1*theta3 + k2*theta5 + k3*theta7 + k4*theta9; + const double theta2 = theta * theta; + const double theta3 = theta2 * theta; + const double theta4 = theta2 * theta2; + const double theta5 = theta4 * theta; + const double theta6 = theta3 * theta3; + const double theta7 = theta6 * theta; + const double theta8 = theta4 * theta4; + const double theta9 = theta8 * theta; + const double theta_dist = theta + k1 * theta3 + k2 * theta5 + k3 * theta7 + k4 * theta9; const double cdist = theta_dist / r; return p * cdist; } -Eigen::Matrix2d DistortionFisheye::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionFisheye::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double eps = 1e-8; const double r = sqrt(p(0) * p(0) + p(1) * p(1)); @@ -73,31 +73,30 @@ Eigen::Matrix2d DistortionFisheye::getDerivativeAddDistoWrtPt(const Vec2 & p) co const double d_cdist_d_theta_dist = 1.0 / r; const double d_theta_d_r = 1.0 / (r * r + 1.0); - const Eigen::Matrix d_cdist_d_p = - d_cdist_d_r * d_r_d_p + d_cdist_d_theta_dist * d_theta_dist_d_theta * d_theta_d_r * d_r_d_p; + const Eigen::Matrix d_cdist_d_p = d_cdist_d_r * d_r_d_p + d_cdist_d_theta_dist * d_theta_dist_d_theta * d_theta_d_r * d_r_d_p; return Eigen::Matrix2d::Identity() * cdist + p * d_cdist_d_p; } -Eigen::MatrixXd DistortionFisheye::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionFisheye::getDerivativeAddDistoWrtDisto(const Vec2& p) const { const double eps = 1e-8; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); if (r < eps) { return Eigen::Matrix::Zero(); } const double theta = std::atan(r); - const double theta2 = theta*theta; - const double theta3 = theta2*theta; - const double theta4 = theta2*theta2; - const double theta5 = theta4*theta; - const double theta6 = theta3*theta3; - const double theta7 = theta6*theta; - const double theta8 = theta4*theta4; - const double theta9 = theta8*theta; + const double theta2 = theta * theta; + const double theta3 = theta2 * theta; + const double theta4 = theta2 * theta2; + const double theta5 = theta4 * theta; + const double theta6 = theta3 * theta3; + const double theta7 = theta6 * theta; + const double theta8 = theta4 * theta4; + const double theta9 = theta8 * theta; const double d_cdist_d_theta_dist = 1.0 / r; @@ -122,21 +121,22 @@ Vec2 DistortionFisheye::removeDistortion(const Vec2& p) const double theta = theta_dist; for (int j = 0; j < 10; ++j) { - const double theta2 = theta*theta; - const double theta4 = theta2*theta2; - const double theta6 = theta4*theta2; - const double theta8 = theta6*theta2; - theta = theta_dist / (1 + _distortionParams.at(0) * theta2 + _distortionParams.at(1) * theta4 + _distortionParams.at(2) * theta6 + _distortionParams.at(3) * theta8); + const double theta2 = theta * theta; + const double theta4 = theta2 * theta2; + const double theta6 = theta4 * theta2; + const double theta8 = theta6 * theta2; + theta = theta_dist / (1 + _distortionParams.at(0) * theta2 + _distortionParams.at(1) * theta4 + _distortionParams.at(2) * theta6 + + _distortionParams.at(3) * theta8); } scale = std::tan(theta) / theta_dist; } return p * scale; } -Eigen::Matrix2d DistortionFisheye::getDerivativeRemoveDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionFisheye::getDerivativeRemoveDistoWrtPt(const Vec2& p) const { const double eps = 1e-8; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); if (r < eps) { return Eigen::Matrix2d::Identity(); @@ -149,11 +149,12 @@ Eigen::Matrix2d DistortionFisheye::getDerivativeRemoveDistoWrtPt(const Vec2 & p) return Jinv.inverse(); } -Eigen::MatrixXd DistortionFisheye::getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionFisheye::getDerivativeRemoveDistoWrtDisto(const Vec2& p) const { - double r_dist = sqrt(p(0)*p(0) + p(1)*p(1)); + double r_dist = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; - if (r_dist < eps) { + if (r_dist < eps) + { return Eigen::Matrix::Zero(); } @@ -166,14 +167,14 @@ Eigen::MatrixXd DistortionFisheye::getDerivativeRemoveDistoWrtDisto(const Vec2 & const double& k4 = _distortionParams.at(3); const double theta = std::atan(r); - const double theta2 = theta*theta; - const double theta3 = theta2*theta; - const double theta4 = theta2*theta2; - const double theta5 = theta4*theta; - const double theta6 = theta3*theta3; - const double theta7 = theta6*theta; - const double theta8 = theta4*theta4; - const double theta9 = theta8*theta; + const double theta2 = theta * theta; + const double theta3 = theta2 * theta; + const double theta4 = theta2 * theta2; + const double theta5 = theta4 * theta; + const double theta6 = theta3 * theta3; + const double theta7 = theta6 * theta; + const double theta8 = theta4 * theta4; + const double theta9 = theta8 * theta; const double theta_dist = theta + k1 * theta3 + k2 * theta5 + k3 * theta7 + k4 * theta9; const double r_coeff = theta_dist / r; double d_r_coeff_d_theta_dist = 1.0 / r; @@ -187,17 +188,17 @@ Eigen::MatrixXd DistortionFisheye::getDerivativeRemoveDistoWrtDisto(const Vec2 & Eigen::Matrix d_rcoeff_d_params = d_r_coeff_d_theta_dist * d_r_theta_dist_d_params; Eigen::Matrix ret; - ret(0, 0) = - (p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(0, 1) = - (p(0) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); - ret(0, 2) = - (p(0) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); - ret(0, 3) = - (p(0) * d_rcoeff_d_params(0, 3)) / (r_coeff * r_coeff); - ret(1, 0) = - (p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(1, 1) = - (p(1) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); - ret(1, 2) = - (p(1) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); - ret(1, 3) = - (p(1) * d_rcoeff_d_params(0, 3)) / (r_coeff * r_coeff); + ret(0, 0) = -(p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(0, 1) = -(p(0) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); + ret(0, 2) = -(p(0) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); + ret(0, 3) = -(p(0) * d_rcoeff_d_params(0, 3)) / (r_coeff * r_coeff); + ret(1, 0) = -(p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(1, 1) = -(p(1) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); + ret(1, 2) = -(p(1) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); + ret(1, 3) = -(p(1) * d_rcoeff_d_params(0, 3)) / (r_coeff * r_coeff); return ret; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionFisheye.hpp b/src/aliceVision/camera/DistortionFisheye.hpp index f64ded2591..0d67b0c460 100644 --- a/src/aliceVision/camera/DistortionFisheye.hpp +++ b/src/aliceVision/camera/DistortionFisheye.hpp @@ -14,40 +14,31 @@ namespace camera { class DistortionFisheye : public Distortion { -public: - DistortionFisheye() - { - _distortionParams = {0.0, 0.0, 0.0, 0.0}; - } + public: + DistortionFisheye() { _distortionParams = {0.0, 0.0, 0.0, 0.0}; } - DistortionFisheye(double p1, double p2, double p3, double p4) - { - _distortionParams = {p1, p2, p3, p4}; - } + DistortionFisheye(double p1, double p2, double p3, double p4) { _distortionParams = {p1, p2, p3, p4}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_FISHEYE; } - DistortionFisheye* clone() const override - { - return new DistortionFisheye(*this); - } + DistortionFisheye* clone() const override { return new DistortionFisheye(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override; ~DistortionFisheye() override = default; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionFisheye1.cpp b/src/aliceVision/camera/DistortionFisheye1.cpp index 2cb90a9591..f1ae6a70da 100644 --- a/src/aliceVision/camera/DistortionFisheye1.cpp +++ b/src/aliceVision/camera/DistortionFisheye1.cpp @@ -9,12 +9,12 @@ namespace aliceVision { namespace camera { -Vec2 DistortionFisheye1::addDistortion(const Vec2 & p) const +Vec2 DistortionFisheye1::addDistortion(const Vec2& p) const { const double& k1 = _distortionParams.at(0); const double r = std::hypot(p(0), p(1)); const double coef = (std::atan(2.0 * r * std::tan(0.5 * k1)) / k1) / r; - return p * coef; + return p * coef; } Vec2 DistortionFisheye1::removeDistortion(const Vec2& p) const @@ -22,8 +22,8 @@ Vec2 DistortionFisheye1::removeDistortion(const Vec2& p) const const double& k1 = _distortionParams.at(0); const double r = std::hypot(p(0), p(1)); const double coef = 0.5 * std::tan(r * k1) / (std::tan(0.5 * k1) * r); - return p * coef; + return p * coef; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionFisheye1.hpp b/src/aliceVision/camera/DistortionFisheye1.hpp index 5d043e6451..039d7b7399 100644 --- a/src/aliceVision/camera/DistortionFisheye1.hpp +++ b/src/aliceVision/camera/DistortionFisheye1.hpp @@ -14,29 +14,23 @@ namespace camera { class DistortionFisheye1 : public Distortion { -public: - DistortionFisheye1() - { - _distortionParams = {0.0}; - } + public: + DistortionFisheye1() { _distortionParams = {0.0}; } - explicit DistortionFisheye1(double p1) - { - _distortionParams = {p1}; - } + explicit DistortionFisheye1(double p1) { _distortionParams = {p1}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_FISHEYE1; } DistortionFisheye1* clone() const override { return new DistortionFisheye1(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - ~DistortionFisheye1() override = default; + ~DistortionFisheye1() override = default; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionRadial.cpp b/src/aliceVision/camera/DistortionRadial.cpp index 32906bbfe8..47075c9f74 100644 --- a/src/aliceVision/camera/DistortionRadial.cpp +++ b/src/aliceVision/camera/DistortionRadial.cpp @@ -9,29 +9,30 @@ namespace aliceVision { namespace camera { -double DistortionRadialK1::distoFunctor(const std::vector & params, double r2) +double DistortionRadialK1::distoFunctor(const std::vector& params, double r2) { const double& k1 = params[0]; - return r2 * Square(1.+r2*k1); + return r2 * Square(1. + r2 * k1); } -Vec2 DistortionRadialK1::addDistortion(const Vec2 & p) const +Vec2 DistortionRadialK1::addDistortion(const Vec2& p) const { const double& k1 = _distortionParams.at(0); - const double r2 = p(0)*p(0) + p(1)*p(1); - const double r_coeff = (1. + k1*r2); + const double r2 = p(0) * p(0) + p(1) * p(1); + const double r_coeff = (1. + k1 * r2); return (p * r_coeff); } -Eigen::Matrix2d DistortionRadialK1::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionRadialK1::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double k1 = _distortionParams[0]; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; - if (r < eps) { + if (r < eps) + { return Eigen::Matrix2d::Identity(); } @@ -48,11 +49,11 @@ Eigen::Matrix2d DistortionRadialK1::getDerivativeAddDistoWrtPt(const Vec2 & p) c return Eigen::Matrix2d::Identity() * r_coeff + p * d_r_coeff_d_p; } -Eigen::MatrixXd DistortionRadialK1::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionRadialK1::getDerivativeAddDistoWrtDisto(const Vec2& p) const { const double& k1 = _distortionParams[0]; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; if (r < eps) { @@ -69,14 +70,14 @@ Vec2 DistortionRadialK1::removeDistortion(const Vec2& p) const // Compute the radius from which the point p comes from thanks to a bisection // Minimize disto(radius(p')^2) == actual Squared(radius(p)) - const double r2 = p(0)*p(0) + p(1)*p(1); + const double r2 = p(0) * p(0) + p(1) * p(1); const double radius = (r2 == 0) ? 1. : ::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r2, distoFunctor) / r2); return radius * p; } -Eigen::Matrix2d DistortionRadialK1::getDerivativeRemoveDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionRadialK1::getDerivativeRemoveDistoWrtPt(const Vec2& p) const { - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; if (r < eps) { @@ -90,9 +91,9 @@ Eigen::Matrix2d DistortionRadialK1::getDerivativeRemoveDistoWrtPt(const Vec2 & p return Jinv.inverse(); } -Eigen::MatrixXd DistortionRadialK1::getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionRadialK1::getDerivativeRemoveDistoWrtDisto(const Vec2& p) const { - double r_dist = sqrt(p(0)*p(0) + p(1)*p(1)); + double r_dist = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; if (r_dist < eps) { @@ -103,7 +104,7 @@ Eigen::MatrixXd DistortionRadialK1::getDerivativeRemoveDistoWrtDisto(const Vec2 const double& k1 = _distortionParams[0]; - const double r = sqrt(p_undist(0)*p_undist(0) + p_undist(1)*p_undist(1)); + const double r = sqrt(p_undist(0) * p_undist(0) + p_undist(1) * p_undist(1)); const double r2 = r * r; const double r_coeff = 1.0 + k1 * r2; @@ -112,8 +113,8 @@ Eigen::MatrixXd DistortionRadialK1::getDerivativeRemoveDistoWrtDisto(const Vec2 d_rcoeff_d_params(0, 0) = r2; Eigen::Matrix ret; - ret(0, 0) = - (p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(1, 0) = - (p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(0, 0) = -(p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(1, 0) = -(p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); return ret; } @@ -123,19 +124,19 @@ double DistortionRadialK1::getUndistortedRadius(double r) const return std::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r * r, distoFunctor)); } -double DistortionRadialK3::distoFunctor(const std::vector & params, double r2) +double DistortionRadialK3::distoFunctor(const std::vector& params, double r2) { const double k1 = params[0], k2 = params[1], k3 = params[2]; - return r2 * Square(1.+r2*(k1+r2*(k2+r2*k3))); + return r2 * Square(1. + r2 * (k1 + r2 * (k2 + r2 * k3))); } -Vec2 DistortionRadialK3::addDistortion(const Vec2 & p) const +Vec2 DistortionRadialK3::addDistortion(const Vec2& p) const { const double& k1 = _distortionParams[0]; const double& k2 = _distortionParams[1]; const double& k3 = _distortionParams[2]; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double r2 = r * r; const double r4 = r2 * r2; @@ -145,7 +146,7 @@ Vec2 DistortionRadialK3::addDistortion(const Vec2 & p) const return (p * r_coeff); } -Eigen::Matrix2d DistortionRadialK3::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionRadialK3::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double& k1 = _distortionParams[0]; const double& k2 = _distortionParams[1]; @@ -173,9 +174,9 @@ Eigen::Matrix2d DistortionRadialK3::getDerivativeAddDistoWrtPt(const Vec2 & p) c return ret; } -Eigen::MatrixXd DistortionRadialK3::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionRadialK3::getDerivativeAddDistoWrtDisto(const Vec2& p) const { - const double r2 = p(0)*p(0) + p(1)*p(1); + const double r2 = p(0) * p(0) + p(1) * p(1); const double eps = 1e-21; if (r2 < eps) { @@ -201,18 +202,19 @@ Vec2 DistortionRadialK3::removeDistortion(const Vec2& p) const // Compute the radius from which the point p comes from thanks to a bisection // Minimize disto(radius(p')^2) == actual Squared(radius(p)) - const double r2 = p(0)*p(0) + p(1)*p(1); - const double radius = (r2 == 0) ? //1. : ::sqrt(bisectionSolve(_distortionParams, r2) / r2); - 1. : - ::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r2, distoFunctor) / r2); + const double r2 = p(0) * p(0) + p(1) * p(1); + const double radius = (r2 == 0) ? // 1. : ::sqrt(bisectionSolve(_distortionParams, r2) / r2); + 1. + : ::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r2, distoFunctor) / r2); return radius * p; } -Eigen::Matrix2d DistortionRadialK3::getDerivativeRemoveDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionRadialK3::getDerivativeRemoveDistoWrtPt(const Vec2& p) const { - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; - if (r < eps) { + if (r < eps) + { return Eigen::Matrix2d::Identity(); } @@ -223,11 +225,12 @@ Eigen::Matrix2d DistortionRadialK3::getDerivativeRemoveDistoWrtPt(const Vec2 & p return Jinv.inverse(); } -Eigen::MatrixXd DistortionRadialK3::getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionRadialK3::getDerivativeRemoveDistoWrtDisto(const Vec2& p) const { - double r_dist = sqrt(p(0)*p(0) + p(1)*p(1)); + double r_dist = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; - if (r_dist < eps) { + if (r_dist < eps) + { return Eigen::Matrix::Zero(); } @@ -250,12 +253,12 @@ Eigen::MatrixXd DistortionRadialK3::getDerivativeRemoveDistoWrtDisto(const Vec2 d_rcoeff_d_params(0, 2) = r6; Eigen::Matrix ret; - ret(0, 0) = - (p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(0, 1) = - (p(0) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); - ret(0, 2) = - (p(0) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); - ret(1, 0) = - (p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(1, 1) = - (p(1) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); - ret(1, 2) = - (p(1) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); + ret(0, 0) = -(p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(0, 1) = -(p(0) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); + ret(0, 2) = -(p(0) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); + ret(1, 0) = -(p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(1, 1) = -(p(1) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); + ret(1, 2) = -(p(1) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); return ret; } @@ -265,7 +268,7 @@ double DistortionRadialK3::getUndistortedRadius(double r) const return std::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r * r, distoFunctor)); } -double DistortionRadialK3PT::distoFunctor(const std::vector & params, double r2) +double DistortionRadialK3PT::distoFunctor(const std::vector& params, double r2) { const double& k1 = params[0]; const double& k2 = params[1]; @@ -274,36 +277,37 @@ double DistortionRadialK3PT::distoFunctor(const std::vector & params, do const double r4 = r2 * r2; const double r6 = r4 * r2; - const double r_coeff = (1.0 + k1*r2 + k2*r4 + k3*r6) / (1.0 + k1 + k2 + k3); + const double r_coeff = (1.0 + k1 * r2 + k2 * r4 + k3 * r6) / (1.0 + k1 + k2 + k3); return r2 * Square(r_coeff); } -Vec2 DistortionRadialK3PT::addDistortion(const Vec2 & p) const +Vec2 DistortionRadialK3PT::addDistortion(const Vec2& p) const { const double& k1 = _distortionParams[0]; const double& k2 = _distortionParams[1]; const double& k3 = _distortionParams[2]; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double r2 = r * r; const double r4 = r2 * r2; const double r6 = r4 * r2; - const double r_coeff = (1.0 + k1*r2 + k2*r4 + k3*r6) / (1.0 + k1 + k2 + k3); + const double r_coeff = (1.0 + k1 * r2 + k2 * r4 + k3 * r6) / (1.0 + k1 + k2 + k3); return (p * r_coeff); } -Eigen::Matrix2d DistortionRadialK3PT::getDerivativeAddDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionRadialK3PT::getDerivativeAddDistoWrtPt(const Vec2& p) const { const double& k1 = _distortionParams[0]; const double& k2 = _distortionParams[1]; const double& k3 = _distortionParams[2]; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); - if (r < 1e-12) { + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); + if (r < 1e-12) + { return Eigen::Matrix2d::Identity(); } @@ -317,7 +321,7 @@ Eigen::Matrix2d DistortionRadialK3PT::getDerivativeAddDistoWrtPt(const Vec2 & p) const double r5 = r4 * r; const double r6 = r4 * r2; - const double r_coeff = (1.0 + k1*r2 + k2*r4 + k3*r6) / (1.0 + k1 + k2 + k3); + const double r_coeff = (1.0 + k1 * r2 + k2 * r4 + k3 * r6) / (1.0 + k1 + k2 + k3); double d_r_coeff_d_r = (2.0 * k1 * r + 4.0 * k2 * r3 + 6.0 * k3 * r5) / (1.0 + k1 + k2 + k3); Eigen::Matrix d_r_coeff_d_p = d_r_coeff_d_r * d_r_d_p; @@ -325,13 +329,13 @@ Eigen::Matrix2d DistortionRadialK3PT::getDerivativeAddDistoWrtPt(const Vec2 & p) return Eigen::Matrix2d::Identity() * r_coeff + p * d_r_coeff_d_p; } -Eigen::MatrixXd DistortionRadialK3PT::getDerivativeAddDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionRadialK3PT::getDerivativeAddDistoWrtDisto(const Vec2& p) const { const double& k1 = _distortionParams[0]; const double& k2 = _distortionParams[1]; const double& k3 = _distortionParams[2]; - const double r = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; if (r < eps) { @@ -343,7 +347,7 @@ Eigen::MatrixXd DistortionRadialK3PT::getDerivativeAddDistoWrtDisto(const Vec2 & const double r6 = r4 * r2; const double denum = (1.0 + k1 + k2 + k3); - const double num = (1.0 + k1*r2 + k2*r4 + k3*r6); + const double num = (1.0 + k1 * r2 + k2 * r4 + k3 * r6); /*const double r_coeff = num / denum;*/ const double denum2 = denum * denum; @@ -363,9 +367,9 @@ Eigen::MatrixXd DistortionRadialK3PT::getDerivativeAddDistoWrtDisto(const Vec2 & return ret; } -Eigen::Matrix2d DistortionRadialK3PT::getDerivativeRemoveDistoWrtPt(const Vec2 & p) const +Eigen::Matrix2d DistortionRadialK3PT::getDerivativeRemoveDistoWrtPt(const Vec2& p) const { - const double r_dist = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r_dist = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; if (r_dist < eps) { @@ -379,15 +383,16 @@ Eigen::Matrix2d DistortionRadialK3PT::getDerivativeRemoveDistoWrtPt(const Vec2 & return Jinv.inverse(); } -Eigen::MatrixXd DistortionRadialK3PT::getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const +Eigen::MatrixXd DistortionRadialK3PT::getDerivativeRemoveDistoWrtDisto(const Vec2& p) const { const double& k1 = _distortionParams[0]; const double& k2 = _distortionParams[1]; const double& k3 = _distortionParams[2]; - const double r_dist = sqrt(p(0)*p(0) + p(1)*p(1)); + const double r_dist = sqrt(p(0) * p(0) + p(1) * p(1)); const double eps = 1e-8; - if (r_dist < eps) { + if (r_dist < eps) + { return Eigen::Matrix::Zero(); } @@ -399,7 +404,7 @@ Eigen::MatrixXd DistortionRadialK3PT::getDerivativeRemoveDistoWrtDisto(const Vec const double r6 = r4 * r2; const double denum = (1.0 + k1 + k2 + k3); - const double num = (1.0 + k1*r2 + k2*r4 + k3*r6); + const double num = (1.0 + k1 * r2 + k2 * r4 + k3 * r6); const double r_coeff = num / denum; double denum2 = denum * denum; @@ -416,12 +421,12 @@ Eigen::MatrixXd DistortionRadialK3PT::getDerivativeRemoveDistoWrtDisto(const Vec Eigen::Matrix d_rcoeff_d_params = (denum * d_num_d_params - num * d_denum_d_params) / denum2; Eigen::Matrix ret; - ret(0, 0) = - (p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(0, 1) = - (p(0) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); - ret(0, 2) = - (p(0) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); - ret(1, 0) = - (p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); - ret(1, 1) = - (p(1) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); - ret(1, 2) = - (p(1) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); + ret(0, 0) = -(p(0) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(0, 1) = -(p(0) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); + ret(0, 2) = -(p(0) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); + ret(1, 0) = -(p(1) * d_rcoeff_d_params(0, 0)) / (r_coeff * r_coeff); + ret(1, 1) = -(p(1) * d_rcoeff_d_params(0, 1)) / (r_coeff * r_coeff); + ret(1, 2) = -(p(1) * d_rcoeff_d_params(0, 2)) / (r_coeff * r_coeff); return ret; } @@ -431,10 +436,10 @@ Vec2 DistortionRadialK3PT::removeDistortion(const Vec2& p) const // Compute the radius from which the point p comes from thanks to a bisection // Minimize disto(radius(p')^2) == actual Squared(radius(p)) - const double r2 = p(0)*p(0) + p(1)*p(1); - const double radius = (r2 == 0) ? //1. : ::sqrt(bisectionSolve(_distortionParams, r2) / r2); - 1. : - ::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r2, distoFunctor, 1e-12) / r2); + const double r2 = p(0) * p(0) + p(1) * p(1); + const double radius = (r2 == 0) ? // 1. : ::sqrt(bisectionSolve(_distortionParams, r2) / r2); + 1. + : ::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r2, distoFunctor, 1e-12) / r2); const Vec2 p_undist = radius * p; return p_undist; @@ -445,5 +450,5 @@ double DistortionRadialK3PT::getUndistortedRadius(double r) const return std::sqrt(radial_distortion::bisection_Radius_Solve(_distortionParams, r * r, distoFunctor)); } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/DistortionRadial.hpp b/src/aliceVision/camera/DistortionRadial.hpp index cd0f1b617a..7a826eca74 100644 --- a/src/aliceVision/camera/DistortionRadial.hpp +++ b/src/aliceVision/camera/DistortionRadial.hpp @@ -15,38 +15,37 @@ namespace camera { namespace radial_distortion { /** -* @brief Solve by bisection the p' radius such that Square(disto(radius(p'))) = r^2 -* @param[in] params radial distortion parameters. -* @param[in] r2 targeted radius -* @param[in] functor functor to solve Square(disto(radius(p'))) = r^2 -* @param epsilon minimum error to stop the bisection -* @return optimal radius -*/ -template -double bisection_Radius_Solve(const std::vector & params, - double r2, - Disto_Functor & functor, - double epsilon = 1e-8) + * @brief Solve by bisection the p' radius such that Square(disto(radius(p'))) = r^2 + * @param[in] params radial distortion parameters. + * @param[in] r2 targeted radius + * @param[in] functor functor to solve Square(disto(radius(p'))) = r^2 + * @param epsilon minimum error to stop the bisection + * @return optimal radius + */ +template +double bisection_Radius_Solve(const std::vector& params, double r2, Disto_Functor& functor, double epsilon = 1e-8) { // Guess plausible upper and lower bound double lowerbound = r2, upbound = r2; - while (functor(params, lowerbound) > r2) lowerbound /= 1.05; - while (functor(params, upbound) < r2) upbound *= 1.05; + while (functor(params, lowerbound) > r2) + lowerbound /= 1.05; + while (functor(params, upbound) < r2) + upbound *= 1.05; // Perform a bisection until epsilon accuracy is not reached while (epsilon < (upbound - lowerbound)) { - const double mid = .5*(lowerbound + upbound); + const double mid = .5 * (lowerbound + upbound); if (functor(params, mid) > r2) - upbound = mid; + upbound = mid; else - lowerbound = mid; + lowerbound = mid; } - return .5*(lowerbound+upbound); + return .5 * (lowerbound + upbound); } -} // namespace radial_distortion +} // namespace radial_distortion /** * @class DistortionRadialK1 @@ -55,65 +54,56 @@ double bisection_Radius_Solve(const std::vector & params, */ class DistortionRadialK1 : public Distortion { -public: + public: /** * @brief Default contructor, no distortion */ - DistortionRadialK1() - { - _distortionParams = {0.0}; - } + DistortionRadialK1() { _distortionParams = {0.0}; } /** * @brief Constructor with the single distortion coefficient. * @param[in] k1 the distortion coefficient */ - explicit DistortionRadialK1(double k1) - { - _distortionParams = {k1}; - } + explicit DistortionRadialK1(double k1) { _distortionParams = {k1}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_RADIALK1; } DistortionRadialK1* clone() const override { return new DistortionRadialK1(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override; double getUndistortedRadius(double r) const override; /// Functor to solve Square(disto(radius(p'))) = r^2 - static double distoFunctor(const std::vector & params, double r2); + static double distoFunctor(const std::vector& params, double r2); ~DistortionRadialK1() override = default; }; - /** * @class DistortionRadialK3 * @brief Radial distortion modeled with a 6th degree polynomial with three distortion coefficients. * \f$ x_d = x_u (1 + K_1 r^2 + K_2 r^4 + K_3 r^6) \f$ */ -class DistortionRadialK3 : public Distortion { -public: +class DistortionRadialK3 : public Distortion +{ + public: /** * @brief Default constructor, no distortion. */ - DistortionRadialK3() - { - _distortionParams = {0.0, 0.0, 0.0}; - } + DistortionRadialK3() { _distortionParams = {0.0, 0.0, 0.0}; } /** * @brief Constructor with the three coefficients @@ -121,64 +111,55 @@ class DistortionRadialK3 : public Distortion { * @param[in] k2 second coefficient * @param[in] k3 third coefficient */ - explicit DistortionRadialK3(double k1, double k2, double k3) - { - _distortionParams = {k1, k2, k3}; - } + explicit DistortionRadialK3(double k1, double k2, double k3) { _distortionParams = {k1, k2, k3}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_RADIALK3; } DistortionRadialK3* clone() const override { return new DistortionRadialK3(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override; double getUndistortedRadius(double r) const override; /// Functor to solve Square(disto(radius(p'))) = r^2 - static double distoFunctor(const std::vector & params, double r2); + static double distoFunctor(const std::vector& params, double r2); ~DistortionRadialK3() override = default; }; class DistortionRadialK3PT : public Distortion { -public: - DistortionRadialK3PT() - { - _distortionParams = {0.0, 0.0, 0.0}; - } + public: + DistortionRadialK3PT() { _distortionParams = {0.0, 0.0, 0.0}; } - explicit DistortionRadialK3PT(double k1, double k2, double k3) - { - _distortionParams = {k1, k2, k3}; - } + explicit DistortionRadialK3PT(double k1, double k2, double k3) { _distortionParams = {k1, k2, k3}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_RADIALK3PT; } DistortionRadialK3PT* clone() const override { return new DistortionRadialK3PT(*this); } /// Add distortion to the point p (assume p is in the camera frame [normalized coordinates]) - Vec2 addDistortion(const Vec2 & p) const override; + Vec2 addDistortion(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeAddDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& p) const override; - Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2 & p) const override; + Eigen::Matrix2d getDerivativeRemoveDistoWrtPt(const Vec2& p) const override; - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & p) const override; + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& p) const override; /// Remove distortion (return p' such that disto(p') = p) Vec2 removeDistortion(const Vec2& p) const override; @@ -186,10 +167,10 @@ class DistortionRadialK3PT : public Distortion double getUndistortedRadius(double r) const override; /// Functor to solve Square(disto(radius(p'))) = r^2 - static double distoFunctor(const std::vector & params, double r2); + static double distoFunctor(const std::vector& params, double r2); ~DistortionRadialK3PT() override = default; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Equidistant.cpp b/src/aliceVision/camera/Equidistant.cpp index 1d7a6f2d8c..931c388fa2 100644 --- a/src/aliceVision/camera/Equidistant.cpp +++ b/src/aliceVision/camera/Equidistant.cpp @@ -38,10 +38,10 @@ Vec2 Equidistant::project(const Eigen::Matrix4d& pose, const Vec4& pt, bool appl return pt_ima; } -Eigen::Matrix Equidistant::getDerivativeProjectWrtRotation(const Eigen::Matrix4d & pose, const Vec4 & pt) +Eigen::Matrix Equidistant::getDerivativeProjectWrtRotation(const Eigen::Matrix4d& pose, const Vec4& pt) { Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose const Eigen::Matrix d_X_d_R = getJacobian_AB_wrt_A<3, 3, 1>(pose.block<3, 3>(0, 0), pt.head(3)); @@ -52,19 +52,19 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtRotation(const E d_len2d_d_X(1) = X(1) / len2d; const double angle_Z = std::atan2(len2d, X(2)); - const double d_angle_Z_d_len2d = X(2) / (len2d*len2d + X(2) * X(2)); + const double d_angle_Z_d_len2d = X(2) / (len2d * len2d + X(2) * X(2)); /* Ignore depth component and compute radial angle */ const double angle_radial = std::atan2(X(1), X(0)); Eigen::Matrix d_angles_d_X; - d_angles_d_X(0, 0) = - X(1) / (X(0) * X(0) + X(1) * X(1)); + d_angles_d_X(0, 0) = -X(1) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 1) = X(0) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 2) = 0.0; d_angles_d_X(1, 0) = d_angle_Z_d_len2d * d_len2d_d_X(0); d_angles_d_X(1, 1) = d_angle_Z_d_len2d * d_len2d_d_X(1); - d_angles_d_X(1, 2) = - len2d / (len2d * len2d + X(2) * X(2)); + d_angles_d_X(1, 2) = -len2d / (len2d * len2d + X(2) * X(2)); const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -79,7 +79,7 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtRotation(const E const Vec2 P{cos(angle_radial) * radius, sin(angle_radial) * radius}; Eigen::Matrix d_P_d_angles; - d_P_d_angles(0, 0) = - sin(angle_radial) * radius; + d_P_d_angles(0, 0) = -sin(angle_radial) * radius; d_P_d_angles(0, 1) = cos(angle_radial) * d_radius_d_angle_Z; d_P_d_angles(1, 0) = cos(angle_radial) * radius; d_P_d_angles(1, 1) = sin(angle_radial) * d_radius_d_angle_Z; @@ -87,10 +87,10 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtRotation(const E return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_angles * d_angles_d_X * d_X_d_R; } -Eigen::Matrix Equidistant::getDerivativeProjectWrtPose(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Equidistant::getDerivativeProjectWrtPose(const Eigen::Matrix4d& pose, const Vec4& pt) const { Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose const Eigen::Matrix d_X_d_T = getJacobian_AB_wrt_A<4, 4, 1>(T, pt); @@ -101,19 +101,19 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPose(const Eige d_len2d_d_X(1) = X(1) / len2d; const double angle_Z = std::atan2(len2d, X(2)); - const double d_angle_Z_d_len2d = X(2) / (len2d*len2d + X(2) * X(2)); + const double d_angle_Z_d_len2d = X(2) / (len2d * len2d + X(2) * X(2)); /* Ignore depth component and compute radial angle */ const double angle_radial = std::atan2(X(1), X(0)); Eigen::Matrix d_angles_d_X; - d_angles_d_X(0, 0) = - X(1) / (X(0) * X(0) + X(1) * X(1)); + d_angles_d_X(0, 0) = -X(1) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 1) = X(0) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 2) = 0.0; d_angles_d_X(1, 0) = d_angle_Z_d_len2d * d_len2d_d_X(0); d_angles_d_X(1, 1) = d_angle_Z_d_len2d * d_len2d_d_X(1); - d_angles_d_X(1, 2) = - len2d / (len2d * len2d + X(2) * X(2)); + d_angles_d_X(1, 2) = -len2d / (len2d * len2d + X(2) * X(2)); const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -128,7 +128,7 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPose(const Eige const Vec2 P{cos(angle_radial) * radius, sin(angle_radial) * radius}; Eigen::Matrix d_P_d_angles; - d_P_d_angles(0, 0) = - sin(angle_radial) * radius; + d_P_d_angles(0, 0) = -sin(angle_radial) * radius; d_P_d_angles(0, 1) = cos(angle_radial) * d_radius_d_angle_Z; d_P_d_angles(1, 0) = cos(angle_radial) * radius; d_P_d_angles(1, 1) = sin(angle_radial) * d_radius_d_angle_Z; @@ -136,10 +136,10 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPose(const Eige return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_angles * d_angles_d_X * d_X_d_T.block<3, 16>(0, 0); } -Eigen::Matrix Equidistant::getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Equidistant::getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d& pose, const Vec4& pt) const { Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose const Eigen::Matrix d_X_d_T = getJacobian_AB_wrt_A<4, 4, 1>(T, pt); @@ -150,19 +150,19 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoseLeft(const d_len2d_d_X(1) = X(1) / len2d; const double angle_Z = std::atan2(len2d, X(2)); - const double d_angle_Z_d_len2d = X(2) / (len2d*len2d + X(2) * X(2)); + const double d_angle_Z_d_len2d = X(2) / (len2d * len2d + X(2) * X(2)); /* Ignore depth component and compute radial angle */ const double angle_radial = std::atan2(X(1), X(0)); Eigen::Matrix d_angles_d_X; - d_angles_d_X(0, 0) = - X(1) / (X(0) * X(0) + X(1) * X(1)); + d_angles_d_X(0, 0) = -X(1) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 1) = X(0) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 2) = 0.0; d_angles_d_X(1, 0) = d_angle_Z_d_len2d * d_len2d_d_X(0); d_angles_d_X(1, 1) = d_angle_Z_d_len2d * d_len2d_d_X(1); - d_angles_d_X(1, 2) = - len2d / (len2d * len2d + X(2) * X(2)); + d_angles_d_X(1, 2) = -len2d / (len2d * len2d + X(2) * X(2)); const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -177,7 +177,7 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoseLeft(const const Vec2 P{cos(angle_radial) * radius, sin(angle_radial) * radius}; Eigen::Matrix d_P_d_angles; - d_P_d_angles(0, 0) = - sin(angle_radial) * radius; + d_P_d_angles(0, 0) = -sin(angle_radial) * radius; d_P_d_angles(0, 1) = cos(angle_radial) * d_radius_d_angle_Z; d_P_d_angles(1, 0) = cos(angle_radial) * radius; d_P_d_angles(1, 1) = sin(angle_radial) * d_radius_d_angle_Z; @@ -185,10 +185,10 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoseLeft(const return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_angles * d_angles_d_X * d_X_d_T.block<3, 16>(0, 0); } -Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint(const Eigen::Matrix4d& pose, const Vec4& pt) const { Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose const Eigen::Matrix4d& d_X_d_pt = T; @@ -199,20 +199,20 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint(const Eige d_len2d_d_X(1) = X(1) / len2d; const double angle_Z = std::atan2(len2d, X(2)); - const double d_angle_Z_d_len2d = X(2) / (len2d*len2d + X(2) * X(2)); + const double d_angle_Z_d_len2d = X(2) / (len2d * len2d + X(2) * X(2)); /* Ignore depth component and compute radial angle */ const double angle_radial = std::atan2(X(1), X(0)); Eigen::Matrix d_angles_d_X; - d_angles_d_X(0, 0) = - X(1) / (X(0) * X(0) + X(1) * X(1)); + d_angles_d_X(0, 0) = -X(1) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 1) = X(0) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 2) = 0.0; d_angles_d_X(0, 3) = 0.0; d_angles_d_X(1, 0) = d_angle_Z_d_len2d * d_len2d_d_X(0); d_angles_d_X(1, 1) = d_angle_Z_d_len2d * d_len2d_d_X(1); - d_angles_d_X(1, 2) = - len2d / (len2d * len2d + X(2) * X(2)); + d_angles_d_X(1, 2) = -len2d / (len2d * len2d + X(2) * X(2)); d_angles_d_X(1, 3) = 0.0; const double rsensor = std::min(sensorWidth(), sensorHeight()); @@ -227,7 +227,7 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint(const Eige const Vec2 P{cos(angle_radial) * radius, sin(angle_radial) * radius}; Eigen::Matrix d_P_d_angles; - d_P_d_angles(0, 0) = - sin(angle_radial) * radius; + d_P_d_angles(0, 0) = -sin(angle_radial) * radius; d_P_d_angles(0, 1) = cos(angle_radial) * d_radius_d_angle_Z; d_P_d_angles(1, 0) = cos(angle_radial) * radius; d_P_d_angles(1, 1) = sin(angle_radial) * d_radius_d_angle_Z; @@ -235,11 +235,11 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint(const Eige return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_angles * d_angles_d_X * d_X_d_pt; } -Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint3(const Eigen::Matrix4d & T, const Vec4 & pt) const +Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint3(const Eigen::Matrix4d& T, const Vec4& pt) const { - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose - const Eigen::Matrix & d_X_d_pt = T.block<4, 3>(0, 0); + const Eigen::Matrix& d_X_d_pt = T.block<4, 3>(0, 0); /* Compute angle with optical center */ const double len2d = sqrt(X(0) * X(0) + X(1) * X(1)); @@ -248,20 +248,20 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint3(const Eig d_len2d_d_X(1) = X(1) / len2d; const double angle_Z = std::atan2(len2d, X(2)); - const double d_angle_Z_d_len2d = X(2) / (len2d*len2d + X(2) * X(2)); + const double d_angle_Z_d_len2d = X(2) / (len2d * len2d + X(2) * X(2)); /* Ignore depth component and compute radial angle */ const double angle_radial = std::atan2(X(1), X(0)); Eigen::Matrix d_angles_d_X; - d_angles_d_X(0, 0) = - X(1) / (X(0) * X(0) + X(1) * X(1)); + d_angles_d_X(0, 0) = -X(1) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 1) = X(0) / (X(0) * X(0) + X(1) * X(1)); d_angles_d_X(0, 2) = 0.0; d_angles_d_X(0, 3) = 0.0; d_angles_d_X(1, 0) = d_angle_Z_d_len2d * d_len2d_d_X(0); d_angles_d_X(1, 1) = d_angle_Z_d_len2d * d_len2d_d_X(1); - d_angles_d_X(1, 2) = - len2d / (len2d * len2d + X(2) * X(2)); + d_angles_d_X(1, 2) = -len2d / (len2d * len2d + X(2) * X(2)); d_angles_d_X(1, 3) = 0.0; const double rsensor = std::min(sensorWidth(), sensorHeight()); @@ -276,7 +276,7 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint3(const Eig const Vec2 P{cos(angle_radial) * radius, sin(angle_radial) * radius}; Eigen::Matrix d_P_d_angles; - d_P_d_angles(0, 0) = - sin(angle_radial) * radius; + d_P_d_angles(0, 0) = -sin(angle_radial) * radius; d_P_d_angles(0, 1) = cos(angle_radial) * d_radius_d_angle_Z; d_P_d_angles(1, 0) = cos(angle_radial) * radius; d_P_d_angles(1, 1) = sin(angle_radial) * d_radius_d_angle_Z; @@ -284,10 +284,10 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtPoint3(const Eig return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_angles * d_angles_d_X * d_X_d_pt; } -Eigen::Matrix Equidistant::getDerivativeProjectWrtDisto(const Eigen::Matrix4d & pose, const Vec4 & pt) +Eigen::Matrix Equidistant::getDerivativeProjectWrtDisto(const Eigen::Matrix4d& pose, const Vec4& pt) { Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose /* Compute angle with optical center */ const double len2d = sqrt(X(0) * X(0) + X(1) * X(1)); @@ -308,10 +308,10 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtDisto(const Eige return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtDisto(P); } -Eigen::Matrix Equidistant::getDerivativeProjectWrtScale(const Eigen::Matrix4d & pose, const Vec4 & pt) +Eigen::Matrix Equidistant::getDerivativeProjectWrtScale(const Eigen::Matrix4d& pose, const Vec4& pt) { Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose /* Compute angle with optical center */ const double len2d = sqrt(X(0) * X(0) + X(1) * X(1)); @@ -334,7 +334,7 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtScale(const Eige d_P_d_radius(1, 0) = sin(angle_radial); Eigen::Matrix d_radius_d_fov; - d_radius_d_fov(0, 0) = (- 2.0 * angle_Z / (fov * fov)); + d_radius_d_fov(0, 0) = (-2.0 * angle_Z / (fov * fov)); Eigen::Matrix d_fov_d_scale; d_fov_d_scale(0, 0) = -rsensor / (_scale(0) * _scale(0) * rscale); @@ -343,17 +343,17 @@ Eigen::Matrix Equidistant::getDerivativeProjectWrtScale(const Eige return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_radius * d_radius_d_fov * d_fov_d_scale; } -Eigen::Matrix Equidistant::getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) +Eigen::Matrix Equidistant::getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d& pose, const Vec4& pt) { return getDerivativeCam2ImaWrtPrincipalPoint(); } -Eigen::Matrix Equidistant::getDerivativeProjectWrtParams(const Eigen::Matrix4d & pose, const Vec4& pt3D) const +Eigen::Matrix Equidistant::getDerivativeProjectWrtParams(const Eigen::Matrix4d& pose, const Vec4& pt3D) const { return Eigen::Matrix(2, 6); } -Vec3 Equidistant::toUnitSphere(const Vec2 & pt) const +Vec3 Equidistant::toUnitSphere(const Vec2& pt) const { const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -363,14 +363,12 @@ Vec3 Equidistant::toUnitSphere(const Vec2 & pt) const const double angle_radial = atan2(pt(1), pt(0)); const double angle_Z = pt.norm() * 0.5 * fov; - const Vec3 ret{cos(angle_radial) /** / 1.0 / **/ * sin(angle_Z), - sin(angle_radial) /** / 1.0 / **/ * sin(angle_Z), - cos(angle_Z)}; + const Vec3 ret{cos(angle_radial) /** / 1.0 / **/ * sin(angle_Z), sin(angle_radial) /** / 1.0 / **/ * sin(angle_Z), cos(angle_Z)}; return ret; } -Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtPoint(const Vec2 & pt) +Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtPoint(const Vec2& pt) { const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -389,7 +387,7 @@ Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtPoint(const d_ret_d_angles(2, 1) = -sin(angle_Z); Eigen::Matrix d_angles_d_pt; - d_angles_d_pt(0, 0) = - pt(1) / (pt(0) * pt(0) + pt(1) * pt(1)); + d_angles_d_pt(0, 0) = -pt(1) / (pt(0) * pt(0) + pt(1) * pt(1)); d_angles_d_pt(0, 1) = pt(0) / (pt(0) * pt(0) + pt(1) * pt(1)); d_angles_d_pt(1, 0) = 0.5 * fov * pt(0) / pt.norm(); d_angles_d_pt(1, 1) = 0.5 * fov * pt(1) / pt.norm(); @@ -397,7 +395,7 @@ Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtPoint(const return d_ret_d_angles * d_angles_d_pt; } -Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtScale(const Vec2 & pt) +Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtScale(const Vec2& pt) { const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -427,37 +425,19 @@ Eigen::Matrix Equidistant::getDerivativetoUnitSphereWrtScale(const return d_ret_d_angles * d_angles_d_fov * d_fov_d_scale; } -double Equidistant::imagePlaneToCameraPlaneError(double value) const -{ - return value / _scale(0); -} +double Equidistant::imagePlaneToCameraPlaneError(double value) const { return value / _scale(0); } -Vec2 Equidistant::cam2ima(const Vec2& p) const -{ - return _circleRadius * p + getPrincipalPoint(); -} +Vec2 Equidistant::cam2ima(const Vec2& p) const { return _circleRadius * p + getPrincipalPoint(); } -Eigen::Matrix2d Equidistant::getDerivativeCam2ImaWrtPoint() const -{ - return Eigen::Matrix2d::Identity() * _circleRadius; -} +Eigen::Matrix2d Equidistant::getDerivativeCam2ImaWrtPoint() const { return Eigen::Matrix2d::Identity() * _circleRadius; } -Vec2 Equidistant::ima2cam(const Vec2& p) const -{ - return (p - getPrincipalPoint()) / _circleRadius; -} +Vec2 Equidistant::ima2cam(const Vec2& p) const { return (p - getPrincipalPoint()) / _circleRadius; } -Eigen::Matrix2d Equidistant::getDerivativeIma2CamWrtPoint() const -{ - return Eigen::Matrix2d::Identity() * (1.0 / _circleRadius); -} +Eigen::Matrix2d Equidistant::getDerivativeIma2CamWrtPoint() const { return Eigen::Matrix2d::Identity() * (1.0 / _circleRadius); } -Eigen::Matrix2d Equidistant::getDerivativeIma2CamWrtPrincipalPoint() const -{ - return Eigen::Matrix2d::Identity() * (-1.0 / _circleRadius); -} +Eigen::Matrix2d Equidistant::getDerivativeIma2CamWrtPrincipalPoint() const { return Eigen::Matrix2d::Identity() * (-1.0 / _circleRadius); } -bool Equidistant::isVisibleRay(const Vec3 & ray) const +bool Equidistant::isVisibleRay(const Vec3& ray) const { const double rsensor = std::min(sensorWidth(), sensorHeight()); const double rscale = sensorWidth() / std::max(w(), h()); @@ -470,7 +450,7 @@ bool Equidistant::isVisibleRay(const Vec3 & ray) const const Vec2 proj = project(Eigen::Matrix4d::Identity(), ray.homogeneous(), true); const Vec2 centered = proj - Vec2(_circleCenter(0), _circleCenter(1)); - return centered.norm() <= _circleRadius; + return centered.norm() <= _circleRadius; } EINTRINSIC Equidistant::getType() const @@ -479,13 +459,15 @@ EINTRINSIC Equidistant::getType() const { switch (_pDistortion->getType()) { - case EDISTORTION::DISTORTION_RADIALK3PT: return EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3; - default: throw std::out_of_range("Invalid distortion model for equidistant camera."); + case EDISTORTION::DISTORTION_RADIALK3PT: + return EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3; + default: + throw std::out_of_range("Invalid distortion model for equidistant camera."); } } return EINTRINSIC::EQUIDISTANT_CAMERA; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Equidistant.hpp b/src/aliceVision/camera/Equidistant.hpp index 1c5ea30738..b48696fbd4 100644 --- a/src/aliceVision/camera/Equidistant.hpp +++ b/src/aliceVision/camera/Equidistant.hpp @@ -23,84 +23,78 @@ namespace camera { /** * @brief Equidistant is a camera model used for fisheye optics. * See https://en.wikipedia.org/wiki/Fisheye_lens - * + * */ class Equidistant : public IntrinsicScaleOffsetDisto { -public: - Equidistant() : - Equidistant(1, 1, 1.0, 0.0, 0.0) - { - } + public: + Equidistant() + : Equidistant(1, 1, 1.0, 0.0, 0.0) + {} - Equidistant(unsigned int w, unsigned int h, + Equidistant(unsigned int w, + unsigned int h, double focalLengthPix, - double offsetX, double offsetY, - std::shared_ptr distortion = nullptr) : - IntrinsicScaleOffsetDisto(w, h, focalLengthPix, focalLengthPix, offsetX, offsetY, distortion), - _circleRadius(std::min(w, h) * 0.5), _circleCenter(w / 2.0, h / 2.0) - { - } - - Equidistant(unsigned int w, unsigned int h, + double offsetX, + double offsetY, + std::shared_ptr distortion = nullptr) + : IntrinsicScaleOffsetDisto(w, h, focalLengthPix, focalLengthPix, offsetX, offsetY, distortion), + _circleRadius(std::min(w, h) * 0.5), + _circleCenter(w / 2.0, h / 2.0) + {} + + Equidistant(unsigned int w, + unsigned int h, double focalLengthPix, - double offsetX, double offsetY, + double offsetX, + double offsetY, double circleRadiusPix, - std::shared_ptr distortion = nullptr) : - IntrinsicScaleOffsetDisto(w, h, focalLengthPix, focalLengthPix, offsetX, offsetY, distortion), - _circleRadius(circleRadiusPix != 0.0 ? circleRadiusPix : std::min(w, h) * 0.5), _circleCenter(w / 2.0, h / 2.0) - { - } + std::shared_ptr distortion = nullptr) + : IntrinsicScaleOffsetDisto(w, h, focalLengthPix, focalLengthPix, offsetX, offsetY, distortion), + _circleRadius(circleRadiusPix != 0.0 ? circleRadiusPix : std::min(w, h) * 0.5), + _circleCenter(w / 2.0, h / 2.0) + {} ~Equidistant() override = default; - Equidistant* clone() const override - { - return new Equidistant(*this); - } + Equidistant* clone() const override { return new Equidistant(*this); } - void assign(const IntrinsicBase& other) override - { - *this = dynamic_cast(other); - } + void assign(const IntrinsicBase& other) override { *this = dynamic_cast(other); } - bool isValid() const override - { - return _scale(0) > 0 && IntrinsicBase::isValid(); - } + bool isValid() const override { return _scale(0) > 0 && IntrinsicBase::isValid(); } EINTRINSIC getType() const override; - Vec2 project(const Eigen::Matrix4d & pose, const Vec4& pt, bool applyDistortion = true) const override; + Vec2 project(const Eigen::Matrix4d& pose, const Vec4& pt, bool applyDistortion = true) const override; Vec2 project(const geometry::Pose3& pose, const Vec4& pt3D, bool applyDistortion = true) const { return project(pose.getHomogeneous(), pt3D, applyDistortion); } - Eigen::Matrix getDerivativeProjectWrtRotation(const Eigen::Matrix4d & pose, const Vec4 & pt); + Eigen::Matrix getDerivativeProjectWrtRotation(const Eigen::Matrix4d& pose, const Vec4& pt); - Eigen::Matrix getDerivativeProjectWrtPose(const Eigen::Matrix4d & pose, const Vec4 & pt) const override; + Eigen::Matrix getDerivativeProjectWrtPose(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d & pose, const Vec4 & pt) const override; + Eigen::Matrix getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) const override; + Eigen::Matrix getDerivativeProjectWrtPoint(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPoint3(const Eigen::Matrix4d & pose, const Vec4 & pt) const override; + Eigen::Matrix getDerivativeProjectWrtPoint3(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtDisto(const Eigen::Matrix4d & pose, const Vec4 & pt); + Eigen::Matrix getDerivativeProjectWrtDisto(const Eigen::Matrix4d& pose, const Vec4& pt); - Eigen::Matrix getDerivativeProjectWrtScale(const Eigen::Matrix4d & pose, const Vec4 & pt); + Eigen::Matrix getDerivativeProjectWrtScale(const Eigen::Matrix4d& pose, const Vec4& pt); - Eigen::Matrix getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d & pose, const Vec4 & pt); + Eigen::Matrix getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d& pose, const Vec4& pt); - Eigen::Matrix getDerivativeProjectWrtParams(const Eigen::Matrix4d & pose, const Vec4& pt3D) const override; + Eigen::Matrix getDerivativeProjectWrtParams(const Eigen::Matrix4d& pose, const Vec4& pt3D) const override; - Vec3 toUnitSphere(const Vec2 & pt) const override; + Vec3 toUnitSphere(const Vec2& pt) const override; - Eigen::Matrix getDerivativetoUnitSphereWrtPoint(const Vec2 & pt); + Eigen::Matrix getDerivativetoUnitSphereWrtPoint(const Vec2& pt); - Eigen::Matrix getDerivativetoUnitSphereWrtScale(const Vec2 & pt); + Eigen::Matrix getDerivativetoUnitSphereWrtScale(const Vec2& pt); double imagePlaneToCameraPlaneError(double value) const override; @@ -120,7 +114,7 @@ class Equidistant : public IntrinsicScaleOffsetDisto * @brief Return true if this ray should be visible in the image * @return true if this ray is visible theorically */ - bool isVisibleRay(const Vec3 & ray) const override; + bool isVisibleRay(const Vec3& ray) const override; inline double getCircleRadius() const { return _circleRadius; } @@ -134,10 +128,10 @@ class Equidistant : public IntrinsicScaleOffsetDisto inline void setCircleCenterY(double y) { _circleCenter(1) = y; } -protected: + protected: double _circleRadius{0.0}; Vec2 _circleCenter{0.0, 0.0}; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/IntrinsicBase.cpp b/src/aliceVision/camera/IntrinsicBase.cpp index ac833eeb66..ff50c29c42 100644 --- a/src/aliceVision/camera/IntrinsicBase.cpp +++ b/src/aliceVision/camera/IntrinsicBase.cpp @@ -13,13 +13,8 @@ namespace camera { bool IntrinsicBase::operator==(const IntrinsicBase& other) const { - return _w == other._w && - _h == other._h && - _sensorWidth == other._sensorWidth && - _sensorHeight == other._sensorHeight && - _serialNumber == other._serialNumber && - _initializationMode == other._initializationMode && - getType() == other.getType(); + return _w == other._w && _h == other._h && _sensorWidth == other._sensorWidth && _sensorHeight == other._sensorHeight && + _serialNumber == other._serialNumber && _initializationMode == other._initializationMode && getType() == other.getType(); } Vec3 IntrinsicBase::backproject(const Vec2& pt2D, bool applyUndistortion, const geometry::Pose3& pose, double depth) const @@ -32,7 +27,7 @@ Vec3 IntrinsicBase::backproject(const Vec2& pt2D, bool applyUndistortion, const return output; } -Vec4 IntrinsicBase::getCartesianfromSphericalCoordinates(const Vec3 & pt) +Vec4 IntrinsicBase::getCartesianfromSphericalCoordinates(const Vec3& pt) { Vec4 rpt; rpt.x() = pt(0); @@ -43,7 +38,7 @@ Vec4 IntrinsicBase::getCartesianfromSphericalCoordinates(const Vec3 & pt) return rpt; } -Eigen::Matrix IntrinsicBase::getDerivativeCartesianfromSphericalCoordinates(const Vec3 & pt) +Eigen::Matrix IntrinsicBase::getDerivativeCartesianfromSphericalCoordinates(const Vec3& pt) { Eigen::Matrix ret = Eigen::Matrix::Zero(); @@ -54,18 +49,20 @@ Eigen::Matrix IntrinsicBase::getDerivativeCartesianfromSphericalCo return ret; } -bool IntrinsicBase::isVisible(const Vec2 & pix) const +bool IntrinsicBase::isVisible(const Vec2& pix) const { - if (pix(0) < 0 || pix(0) >= _w || pix(1) < 0 || pix(1) >= _h) { + if (pix(0) < 0 || pix(0) >= _w || pix(1) < 0 || pix(1) >= _h) + { return false; } return true; } -bool IntrinsicBase::isVisible(const Vec2f & pix) const +bool IntrinsicBase::isVisible(const Vec2f& pix) const { - if (pix(0) < 0 || pix(0) >= _w || pix(1) < 0 || pix(1) >= _h) { + if (pix(0) < 0 || pix(0) >= _w || pix(1) < 0 || pix(1) >= _h) + { return false; } @@ -101,5 +98,5 @@ void IntrinsicBase::rescale(float factor) _h = static_cast(floor(static_cast(_h) * factor)); } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/IntrinsicBase.hpp b/src/aliceVision/camera/IntrinsicBase.hpp index 3fddcd1f25..b13e86c059 100644 --- a/src/aliceVision/camera/IntrinsicBase.hpp +++ b/src/aliceVision/camera/IntrinsicBase.hpp @@ -20,25 +20,26 @@ namespace camera { /** * @brief Basis class for all intrinsic parameters of a camera. - * + * * Store the image size & define all basis optical modelization of a camera */ class IntrinsicBase { -public: - explicit IntrinsicBase(unsigned int width, unsigned int height, const std::string& serialNumber = "") : - _w(width), _h(height), _serialNumber(serialNumber) - { - } + public: + explicit IntrinsicBase(unsigned int width, unsigned int height, const std::string& serialNumber = "") + : _w(width), + _h(height), + _serialNumber(serialNumber) + {} virtual ~IntrinsicBase() = default; - + /** * @brief Get the lock state of the intrinsic * @return true if the intrinsic is locked */ inline bool isLocked() const { return _locked; } - + /** * @brief Get the intrinsic image width * @return The intrinsic image width @@ -103,7 +104,7 @@ class IntrinsicBase * @param[in] applyDistortion If true apply distrortion if any * @return The 2d projection in the camera plane */ - virtual Vec2 project(const Eigen::Matrix4d & pose, const Vec4& pt3D, bool applyDistortion = true) const = 0; + virtual Vec2 project(const Eigen::Matrix4d& pose, const Vec4& pt3D, bool applyDistortion = true) const = 0; /** * @brief Back-projection of a 2D point at a specific depth into a 3D point @@ -115,9 +116,9 @@ class IntrinsicBase */ Vec3 backproject(const Vec2& pt2D, bool applyUndistortion = true, const geometry::Pose3& pose = geometry::Pose3(), double depth = 1.0) const; - Vec4 getCartesianfromSphericalCoordinates(const Vec3 & pt); + Vec4 getCartesianfromSphericalCoordinates(const Vec3& pt); - Eigen::Matrix getDerivativeCartesianfromSphericalCoordinates(const Vec3 & pt); + Eigen::Matrix getDerivativeCartesianfromSphericalCoordinates(const Vec3& pt); /** * @brief get derivative of a projection of a 3D point into the camera plane @@ -126,7 +127,7 @@ class IntrinsicBase * @param[in] applyDistortion If true apply distrortion if any * @return The projection jacobian wrt pose */ - virtual Eigen::Matrix getDerivativeProjectWrtPose(const Eigen::Matrix4d & pose, const Vec4& pt3D) const = 0; + virtual Eigen::Matrix getDerivativeProjectWrtPose(const Eigen::Matrix4d& pose, const Vec4& pt3D) const = 0; /** * @brief get derivative of a projection of a 3D point into the camera plane @@ -135,7 +136,7 @@ class IntrinsicBase * @param[in] applyDistortion If true apply distrortion if any * @return The projection jacobian wrt pose */ - virtual Eigen::Matrix getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d & pose, const Vec4& pt3D) const = 0; + virtual Eigen::Matrix getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d& pose, const Vec4& pt3D) const = 0; /** * @brief get derivative of a projection of a 3D point into the camera plane @@ -144,7 +145,7 @@ class IntrinsicBase * @param[in] applyDistortion If true apply distrortion if any * @return The projection jacobian wrt point */ - virtual Eigen::Matrix getDerivativeProjectWrtPoint(const Eigen::Matrix4d & pose, const Vec4& pt3D) const = 0; + virtual Eigen::Matrix getDerivativeProjectWrtPoint(const Eigen::Matrix4d& pose, const Vec4& pt3D) const = 0; /** * @brief get derivative of a projection of a 3D point into the camera plane @@ -162,7 +163,7 @@ class IntrinsicBase * @param[in] applyDistortion If true apply distrortion if any * @return The projection jacobian wrt params */ - virtual Eigen::Matrix getDerivativeProjectWrtParams(const Eigen::Matrix4d & pos, const Vec4& pt3D) const = 0; + virtual Eigen::Matrix getDerivativeProjectWrtParams(const Eigen::Matrix4d& pos, const Vec4& pt3D) const = 0; /** * @brief Compute the residual between the 3D projected point X and an image observation x @@ -189,7 +190,7 @@ class IntrinsicBase assert(X.cols() == x.cols()); const std::size_t numPts = x.cols(); Mat2X residuals = Mat2X::Zero(2, numPts); - for(std::size_t i = 0; i < numPts; ++i) + for (std::size_t i = 0; i < numPts; ++i) { residuals.col(i) = residual(pose, ((const Vec3&)X.col(i)).homogeneous(), x.col(i)); } @@ -199,12 +200,12 @@ class IntrinsicBase /** * @brief lock the intrinsic */ - inline void lock() { _locked = true; } + inline void lock() { _locked = true; } /** * @brief unlock the intrinsic */ - inline void unlock() { _locked = false; } + inline void unlock() { _locked = false; } /** * @brief Set intrinsic image width @@ -229,7 +230,7 @@ class IntrinsicBase * @param[in] height The sensor height */ inline void setSensorHeight(double height) { _sensorHeight = height; } - + /** * @brief Set the serial number * @param[in] serialNumber The serial number @@ -292,7 +293,7 @@ class IntrinsicBase * @param[in] inputVersion input source version (for optional transformation) * @return true if done */ - virtual bool importFromParams(const std::vector& params, const Version & inputVersion) = 0; + virtual bool importFromParams(const std::vector& params, const Version& inputVersion) = 0; /** * @brief Transform a point from the camera plane to the image plane @@ -372,24 +373,24 @@ class IntrinsicBase * @param ray input ray to check for visibility * @return true if this ray is visible theorically */ - virtual bool isVisibleRay(const Vec3 & ray) const = 0; + virtual bool isVisibleRay(const Vec3& ray) const = 0; /** * @brief Return true if these pixel coordinates should be visible in the image * @param pix input pixel coordinates to check for visibility * @return true if visible */ - virtual bool isVisible(const Vec2 & pix) const; + virtual bool isVisible(const Vec2& pix) const; /** * @brief Return true if these pixel coordinates should be visible in the image * @param pix input pixel coordinates to check for visibility * @return true if visible */ - virtual bool isVisible(const Vec2f & pix) const; + virtual bool isVisible(const Vec2f& pix) const; /** - * @brief Assuming the distortion is a function of radius, estimate the + * @brief Assuming the distortion is a function of radius, estimate the * maximal undistorted radius for a range of distorted radius. * @param min_radius the minimal radius to consider * @param max_radius the maximal radius to consider @@ -414,9 +415,9 @@ class IntrinsicBase * @param pt the input point * @return a point on the unit sphere */ - virtual Vec3 toUnitSphere(const Vec2 & pt) const = 0; + virtual Vec3 toUnitSphere(const Vec2& pt) const = 0; -protected: + protected: /// initialization mode EInitMode _initializationMode = EInitMode::NONE; /// intrinsic lock @@ -426,7 +427,6 @@ class IntrinsicBase double _sensorWidth = 36.0; double _sensorHeight = 24.0; std::string _serialNumber; - }; /** @@ -437,9 +437,7 @@ class IntrinsicBase * @param[in] x Point in image * @return The unit vector in 3D space pointing out from the camera to the point */ -inline Vec3 applyIntrinsicExtrinsic(const geometry::Pose3& pose, - const IntrinsicBase* intrinsic, - const Vec2& x) +inline Vec3 applyIntrinsicExtrinsic(const geometry::Pose3& pose, const IntrinsicBase* intrinsic, const Vec2& x) { // x = (u, v, 1.0) // image coordinates // X = R.t() * K.inv() * x + C // Camera world point @@ -458,7 +456,7 @@ inline double angleBetweenRays(const Vec3& ray1, const Vec3& ray2) { const double mag = ray1.norm() * ray2.norm(); const double dotAngle = ray1.dot(ray2); - return radianToDegree(acos(clamp(dotAngle/mag, -1.0 + 1.e-8, 1.0 - 1.e-8))); + return radianToDegree(acos(clamp(dotAngle / mag, -1.0 + 1.e-8, 1.0 - 1.e-8))); } /** @@ -490,21 +488,19 @@ inline double angleBetweenRays(const geometry::Pose3& pose1, * @param[in] pt3D The 3d point * @return The angle (degree) between two poses and a 3D point. */ -inline double angleBetweenRays(const geometry::Pose3& pose1, - const geometry::Pose3& pose2, - const Vec3& pt3D) +inline double angleBetweenRays(const geometry::Pose3& pose1, const geometry::Pose3& pose2, const Vec3& pt3D) { const Vec3 ray1 = pt3D - pose1.center(); const Vec3 ray2 = pt3D - pose2.center(); return angleBetweenRays(ray1, ray2); } -} // namespace camera +} // namespace camera -template -Eigen::Matrix getJacobian_At_wrt_A() +template +Eigen::Matrix getJacobian_At_wrt_A() { - Eigen::Matrix ret; + Eigen::Matrix ret; /** vec(M1*M2*M3) = kron(M3.t, M1) * vec(M2) */ /** vec(IAtB) = kron(B.t, I) * vec(A) */ @@ -527,10 +523,10 @@ Eigen::Matrix getJacobian_At_wrt_A() return ret; } -template -Eigen::Matrix getJacobian_AB_wrt_A(const Eigen::Matrix & A, const Eigen::Matrix & B) +template +Eigen::Matrix getJacobian_AB_wrt_A(const Eigen::Matrix& A, const Eigen::Matrix& B) { - Eigen::Matrix ret; + Eigen::Matrix ret; /** vec(M1*M2*M3) = kron(M3.t, M1) * vec(M2) */ /** vec(IAB) = kron(B.t, I) * vec(A) */ @@ -552,16 +548,16 @@ Eigen::Matrix getJacobian_AB_wrt_A(const Eigen::Matrix -Eigen::Matrix getJacobian_AtB_wrt_A(const Eigen::Matrix & A, const Eigen::Matrix & B) +template +Eigen::Matrix getJacobian_AtB_wrt_A(const Eigen::Matrix& A, const Eigen::Matrix& B) { return getJacobian_AB_wrt_A(A.transpose(), B) * getJacobian_At_wrt_A(); } -template -Eigen::Matrix getJacobian_AB_wrt_B(const Eigen::Matrix & A, const Eigen::Matrix & B) +template +Eigen::Matrix getJacobian_AB_wrt_B(const Eigen::Matrix& A, const Eigen::Matrix& B) { - Eigen::Matrix ret; + Eigen::Matrix ret; /** vec(M1*M2*M3) = kron(M3.t, M1) * vec(M2) */ /** vec(ABI) = kron(I, A) * vec(B) */ @@ -578,4 +574,4 @@ Eigen::Matrix getJacobian_AB_wrt_B(const Eigen::Matrix>(std::istream& in, EInitMode &initMode) +inline std::istream& operator>>(std::istream& in, EInitMode& initMode) { std::string token; in >> token; @@ -74,5 +83,5 @@ inline std::istream& operator>>(std::istream& in, EInitMode &initMode) return in; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/IntrinsicScaleOffset.cpp b/src/aliceVision/camera/IntrinsicScaleOffset.cpp index 907aa8fd5d..1dd92baeeb 100644 --- a/src/aliceVision/camera/IntrinsicScaleOffset.cpp +++ b/src/aliceVision/camera/IntrinsicScaleOffset.cpp @@ -17,21 +17,18 @@ bool IntrinsicScaleOffset::operator==(const IntrinsicBase& otherBase) const { return false; } - + if (typeid(*this) != typeid(otherBase)) { return false; } - + const IntrinsicScaleOffset& other = static_cast(otherBase); return _scale.isApprox(other._scale) && _offset.isApprox(other._offset); } -Vec2 IntrinsicScaleOffset::cam2ima(const Vec2& p) const -{ - return p.cwiseProduct(_scale) + getPrincipalPoint(); -} +Vec2 IntrinsicScaleOffset::cam2ima(const Vec2& p) const { return p.cwiseProduct(_scale) + getPrincipalPoint(); } Eigen::Matrix2d IntrinsicScaleOffset::getDerivativeCam2ImaWrtScale(const Vec2& p) const { @@ -53,10 +50,7 @@ Eigen::Matrix2d IntrinsicScaleOffset::getDerivativeCam2ImaWrtPoint() const return M; } -Eigen::Matrix2d IntrinsicScaleOffset::getDerivativeCam2ImaWrtPrincipalPoint() const -{ - return Eigen::Matrix2d::Identity(); -} +Eigen::Matrix2d IntrinsicScaleOffset::getDerivativeCam2ImaWrtPrincipalPoint() const { return Eigen::Matrix2d::Identity(); } Vec2 IntrinsicScaleOffset::ima2cam(const Vec2& p) const { @@ -96,8 +90,8 @@ Eigen::Matrix2d IntrinsicScaleOffset::getDerivativeIma2CamWrtPrincipalPoint() co { Eigen::Matrix2d M = Eigen::Matrix2d::Zero(); - M(0, 0) = - 1.0 / _scale(0); - M(1, 1) = - 1.0 / _scale(1); + M(0, 0) = -1.0 / _scale(0); + M(1, 1) = -1.0 / _scale(1); return M; } @@ -125,7 +119,7 @@ bool IntrinsicScaleOffset::updateFromParams(const std::vector& params) return true; } -bool IntrinsicScaleOffset::importFromParams(const std::vector& params, const Version & inputVersion) +bool IntrinsicScaleOffset::importFromParams(const std::vector& params, const Version& inputVersion) { std::vector paramsLocal; if (inputVersion < Version(1, 2, 0)) @@ -158,5 +152,5 @@ bool IntrinsicScaleOffset::importFromParams(const std::vector& params, c return true; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/IntrinsicScaleOffset.hpp b/src/aliceVision/camera/IntrinsicScaleOffset.hpp index 55ac6d6cb9..3e2f28d416 100644 --- a/src/aliceVision/camera/IntrinsicScaleOffset.hpp +++ b/src/aliceVision/camera/IntrinsicScaleOffset.hpp @@ -15,16 +15,14 @@ namespace camera { /** * @brief Class with "focal" (scale) and center offset */ -class IntrinsicScaleOffset: public IntrinsicBase +class IntrinsicScaleOffset : public IntrinsicBase { -public: - IntrinsicScaleOffset(unsigned int w, unsigned int h, - double scaleX, double scaleY, - double offsetX, double offsetY) : - IntrinsicBase(w, h), - _scale(scaleX, scaleY), _offset(offsetX, offsetY) - { - } + public: + IntrinsicScaleOffset(unsigned int w, unsigned int h, double scaleX, double scaleY, double offsetX, double offsetY) + : IntrinsicBase(w, h), + _scale(scaleX, scaleY), + _offset(offsetX, offsetY) + {} ~IntrinsicScaleOffset() override = default; @@ -82,12 +80,12 @@ class IntrinsicScaleOffset: public IntrinsicBase /** * @brief Import a vector of params loaded from a file. It is similar to updateFromParams but it deals with file compatibility. */ - bool importFromParams(const std::vector& params, const Version & inputVersion) override; + bool importFromParams(const std::vector& params, const Version& inputVersion) override; /** * @brief Set initial Scale (for constraining minimization) */ - inline void setInitialScale(const Vec2 & initialScale) { _initialScale = initialScale; } + inline void setInitialScale(const Vec2& initialScale) { _initialScale = initialScale; } /** * @brief Get the intrinsic initial scale @@ -103,12 +101,12 @@ class IntrinsicScaleOffset: public IntrinsicBase inline bool isRatioLocked() const { return _ratioLocked; } -protected: + protected: Vec2 _scale{1.0, 1.0}; Vec2 _offset{0.0, 0.0}; Vec2 _initialScale{-1.0, -1.0}; bool _ratioLocked{true}; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/IntrinsicScaleOffsetDisto.cpp b/src/aliceVision/camera/IntrinsicScaleOffsetDisto.cpp index 4abf887daf..a9f194a60c 100644 --- a/src/aliceVision/camera/IntrinsicScaleOffsetDisto.cpp +++ b/src/aliceVision/camera/IntrinsicScaleOffsetDisto.cpp @@ -38,19 +38,12 @@ bool IntrinsicScaleOffsetDisto::operator==(const IntrinsicBase& otherBase) const return (*_pUndistortion) == (*other._pUndistortion); } - return _pDistortion == nullptr && other._pDistortion == nullptr - && _pUndistortion == nullptr && other._pUndistortion == nullptr; + return _pDistortion == nullptr && other._pDistortion == nullptr && _pUndistortion == nullptr && other._pUndistortion == nullptr; } -Vec2 IntrinsicScaleOffsetDisto::get_ud_pixel(const Vec2& p) const -{ - return cam2ima(removeDistortion(ima2cam(p))); -} +Vec2 IntrinsicScaleOffsetDisto::get_ud_pixel(const Vec2& p) const { return cam2ima(removeDistortion(ima2cam(p))); } -Vec2 IntrinsicScaleOffsetDisto::get_d_pixel(const Vec2& p) const -{ - return cam2ima(addDistortion(ima2cam(p))); -} +Vec2 IntrinsicScaleOffsetDisto::get_d_pixel(const Vec2& p) const { return cam2ima(addDistortion(ima2cam(p))); } bool IntrinsicScaleOffsetDisto::updateFromParams(const std::vector& params) { @@ -67,5 +60,5 @@ bool IntrinsicScaleOffsetDisto::updateFromParams(const std::vector& para return true; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/IntrinsicScaleOffsetDisto.hpp b/src/aliceVision/camera/IntrinsicScaleOffsetDisto.hpp index 052f9e27be..3ea400de94 100644 --- a/src/aliceVision/camera/IntrinsicScaleOffsetDisto.hpp +++ b/src/aliceVision/camera/IntrinsicScaleOffsetDisto.hpp @@ -22,28 +22,29 @@ namespace camera { */ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset { -public: - - IntrinsicScaleOffsetDisto(unsigned int w, unsigned int h, - double scaleX, double scaleY, - double offsetX, double offsetY, - std::shared_ptr distortion = nullptr, - std::shared_ptr undistortion = nullptr) : - IntrinsicScaleOffset(w, h, scaleX, scaleY, offsetX, offsetY), - _pDistortion(distortion), _pUndistortion(undistortion) - { - } - - IntrinsicScaleOffsetDisto(const IntrinsicScaleOffsetDisto &other) - : - IntrinsicScaleOffset(other), + public: + IntrinsicScaleOffsetDisto(unsigned int w, + unsigned int h, + double scaleX, + double scaleY, + double offsetX, + double offsetY, + std::shared_ptr distortion = nullptr, + std::shared_ptr undistortion = nullptr) + : IntrinsicScaleOffset(w, h, scaleX, scaleY, offsetX, offsetY), + _pDistortion(distortion), + _pUndistortion(undistortion) + {} + + IntrinsicScaleOffsetDisto(const IntrinsicScaleOffsetDisto& other) + : IntrinsicScaleOffset(other), _distortionInitializationMode(other._distortionInitializationMode) { if (other._pDistortion) { _pDistortion = std::shared_ptr(other._pDistortion->clone()); } - else + else { _pDistortion = nullptr; } @@ -52,28 +53,19 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset { _pUndistortion = std::shared_ptr(other._pUndistortion->clone()); } - else + else { _pUndistortion = nullptr; } } - void assign(const IntrinsicBase& other) override - { - *this = dynamic_cast(other); - } + void assign(const IntrinsicBase& other) override { *this = dynamic_cast(other); } bool operator==(const IntrinsicBase& otherBase) const override; - void setDistortionObject(std::shared_ptr object) - { - _pDistortion = object; - } + void setDistortionObject(std::shared_ptr object) { _pDistortion = object; } - bool hasDistortion() const override - { - return _pDistortion != nullptr || _pUndistortion != nullptr; - } + bool hasDistortion() const override { return _pDistortion != nullptr || _pUndistortion != nullptr; } /** * @brief Create a new point from a given point by adding distortion. @@ -92,7 +84,7 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset } return p; } - + /** * @brief Create a new point from a given point by removing distortion. * @param[in] p Point in the camera plane. @@ -128,13 +120,14 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset std::vector getDistortionParams() const { - if (!_pDistortion) { + if (!_pDistortion) + { return std::vector(); } return _pDistortion->getParameters(); } - void setDistortionParams(const std::vector & distortionParams) + void setDistortionParams(const std::vector& distortionParams) { int expected = 0; if (_pDistortion != nullptr) @@ -226,7 +219,7 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset return _pDistortion->getUndistortedRadius(max_radius); } - Eigen::Matrix getDerivativeAddDistoWrtPt(const Vec2 & pt) const + Eigen::Matrix getDerivativeAddDistoWrtPt(const Vec2& pt) const { if (this->_pDistortion == nullptr) { @@ -235,7 +228,7 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset return this->_pDistortion->getDerivativeAddDistoWrtPt(pt); } - Eigen::Matrix getDerivativeRemoveDistoWrtPt(const Vec2 & pt) const + Eigen::Matrix getDerivativeRemoveDistoWrtPt(const Vec2& pt) const { if (this->_pDistortion == nullptr) { @@ -245,7 +238,7 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset return this->_pDistortion->getDerivativeRemoveDistoWrtPt(pt); } - Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2 & pt) const + Eigen::MatrixXd getDerivativeAddDistoWrtDisto(const Vec2& pt) const { if (this->_pDistortion == nullptr) { @@ -255,7 +248,7 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset return this->_pDistortion->getDerivativeAddDistoWrtDisto(pt); } - Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2 & pt) const + Eigen::MatrixXd getDerivativeRemoveDistoWrtDisto(const Vec2& pt) const { if (this->_pDistortion == nullptr) { @@ -278,35 +271,22 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset * @brief Get the intrinsic disto initialization mode * @return The intrinsic disto initialization mode */ - inline EInitMode getDistortionInitializationMode() const override - { - return _distortionInitializationMode; - } + inline EInitMode getDistortionInitializationMode() const override { return _distortionInitializationMode; } - std::shared_ptr getDistortion() const - { - return _pDistortion; - } + std::shared_ptr getDistortion() const { return _pDistortion; } ~IntrinsicScaleOffsetDisto() override = default; - void setUndistortionObject(std::shared_ptr object) - { - _pUndistortion = object; - } + void setUndistortionObject(std::shared_ptr object) { _pUndistortion = object; } - std::shared_ptr getUndistortion() const - { - return _pUndistortion; - } + std::shared_ptr getUndistortion() const { return _pUndistortion; } -protected: + protected: void throwSetDistortionParamsCountError(std::size_t expected, std::size_t received) { std::stringstream s; s << "IntrinsicScaleOffsetDisto::setDistortionParams*: " - << "wrong number of distortion parameters (expected: " - << expected << ", given:" << received << ")."; + << "wrong number of distortion parameters (expected: " << expected << ", given:" << received << ")."; throw std::runtime_error(s.str()); } @@ -317,5 +297,5 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset EInitMode _distortionInitializationMode = EInitMode::NONE; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Pinhole.cpp b/src/aliceVision/camera/Pinhole.cpp index 10d6ab2f54..060e3d0eac 100644 --- a/src/aliceVision/camera/Pinhole.cpp +++ b/src/aliceVision/camera/Pinhole.cpp @@ -15,9 +15,7 @@ Mat3 Pinhole::K() const Vec2 pp = getPrincipalPoint(); - K << _scale(0), 0.0, pp(0), - 0.0, _scale(1), pp(1), - 0.0, 0.0, 1.0; + K << _scale(0), 0.0, pp(0), 0.0, _scale(1), pp(1), 0.0, 0.0, 1.0; return K; } @@ -29,7 +27,7 @@ void Pinhole::setK(double focalLengthPixX, double focalLengthPixY, double ppx, d _offset(1) = ppy - static_cast(_h) * 0.5; } -void Pinhole::setK(const Mat3 & K) +void Pinhole::setK(const Mat3& K) { _scale(0) = K(0, 0); _scale(1) = K(1, 1); @@ -37,9 +35,9 @@ void Pinhole::setK(const Mat3 & K) _offset(1) = K(1, 2) - static_cast(_h) * 0.5; } -Vec2 Pinhole::project(const Eigen::Matrix4d & pose, const Vec4& pt, bool applyDistortion) const +Vec2 Pinhole::project(const Eigen::Matrix4d& pose, const Vec4& pt, bool applyDistortion) const { - const Vec4 X = pose * pt; // apply pose + const Vec4 X = pose * pt; // apply pose const Vec2 P = X.head<2>() / X(2); const Vec2 distorted = this->addDistortion(P); @@ -48,30 +46,30 @@ Vec2 Pinhole::project(const Eigen::Matrix4d & pose, const Vec4& pt, bool applyDi return impt; } -Eigen::Matrix Pinhole::getDerivativeProjectWrtRotation(const Eigen::Matrix4d & pose, const Vec4 & pt) +Eigen::Matrix Pinhole::getDerivativeProjectWrtRotation(const Eigen::Matrix4d& pose, const Vec4& pt) { - const Vec4 X = pose * pt; // apply pose + const Vec4 X = pose * pt; // apply pose - const Eigen::Matrix d_X_d_R = getJacobian_AB_wrt_A<3, 3, 1>(pose.block<3,3>(0, 0), pt.head(3)); + const Eigen::Matrix d_X_d_R = getJacobian_AB_wrt_A<3, 3, 1>(pose.block<3, 3>(0, 0), pt.head(3)); const Vec2 P = X.head<2>() / X(2); Eigen::Matrix d_P_d_X; d_P_d_X(0, 0) = 1 / X(2); d_P_d_X(0, 1) = 0; - d_P_d_X(0, 2) = - X(0) / (X(2) * X(2)); + d_P_d_X(0, 2) = -X(0) / (X(2) * X(2)); d_P_d_X(1, 0) = 0; d_P_d_X(1, 1) = 1 / X(2); - d_P_d_X(1, 2) = - X(1) / (X(2) * X(2)); + d_P_d_X(1, 2) = -X(1) / (X(2) * X(2)); return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_X * d_X_d_R; } -Eigen::Matrix Pinhole::getDerivativeProjectWrtPose(const Eigen::Matrix4d & pose, const Vec4& pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtPose(const Eigen::Matrix4d& pose, const Vec4& pt) const { const Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose const Eigen::Matrix d_X_d_T = getJacobian_AB_wrt_A<4, 4, 1>(T, pt); @@ -80,19 +78,19 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtPose(const Eigen::M Eigen::Matrix d_P_d_X; d_P_d_X(0, 0) = 1 / X(2); d_P_d_X(0, 1) = 0; - d_P_d_X(0, 2) = - X(0) / (X(2) * X(2)); + d_P_d_X(0, 2) = -X(0) / (X(2) * X(2)); d_P_d_X(1, 0) = 0; d_P_d_X(1, 1) = 1 / X(2); - d_P_d_X(1, 2) = - X(1) / (X(2) * X(2)); + d_P_d_X(1, 2) = -X(1) / (X(2) * X(2)); return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_X * d_X_d_T.block<3, 16>(0, 0); } -Eigen::Matrix Pinhole::getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d & pose, const Vec4& pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d& pose, const Vec4& pt) const { const Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose const Eigen::Matrix d_X_d_T = getJacobian_AB_wrt_A<4, 4, 1>(Eigen::Matrix4d::Identity(), X); @@ -102,18 +100,17 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtPoseLeft(const Eige double y = X(1); double z = X(2); double invz = 1.0 / z; - double invzsq = invz*invz; + double invzsq = invz * invz; double mxinvzsq = -x * invzsq; double myinvzsq = -y * invzsq; - Eigen::Matrix d_P_d_X; d_P_d_X(0, 0) = 1 / X(2); d_P_d_X(0, 1) = 0; - d_P_d_X(0, 2) = - X(0) / (X(2) * X(2)); + d_P_d_X(0, 2) = -X(0) / (X(2) * X(2)); d_P_d_X(1, 0) = 0; d_P_d_X(1, 1) = 1 / X(2); - d_P_d_X(1, 2) = - X(1) / (X(2) * X(2)); + d_P_d_X(1, 2) = -X(1) / (X(2) * X(2)); Eigen::Matrix d_P_d_T; d_P_d_T(0, 0) = invz * X(0); @@ -149,38 +146,37 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtPoseLeft(const Eige d_P_d_T(1, 13) = invz; d_P_d_T(1, 14) = myinvzsq; d_P_d_T(1, 15) = 0.0; - return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_T; } -Eigen::Matrix Pinhole::getDerivativeProjectWrtPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtPoint(const Eigen::Matrix4d& pose, const Vec4& pt) const { const Eigen::Matrix4d T = pose; - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose - const Eigen::Matrix & d_X_d_P = getJacobian_AB_wrt_B<4, 4, 1>(T, pt); + const Eigen::Matrix& d_X_d_P = getJacobian_AB_wrt_B<4, 4, 1>(T, pt); const Vec2 P = X.head<2>() / X(2); Eigen::Matrix d_P_d_X; d_P_d_X(0, 0) = 1 / X(2); d_P_d_X(0, 1) = 0; - d_P_d_X(0, 2) = - X(0) / (X(2) * X(2)); + d_P_d_X(0, 2) = -X(0) / (X(2) * X(2)); d_P_d_X(0, 3) = 0; d_P_d_X(1, 0) = 0; d_P_d_X(1, 1) = 1 / X(2); - d_P_d_X(1, 2) = - X(1) / (X(2) * X(2)); + d_P_d_X(1, 2) = -X(1) / (X(2) * X(2)); d_P_d_X(1, 3) = 0; return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_X * d_X_d_P; } -Eigen::Matrix Pinhole::getDerivativeProjectWrtPoint3(const Eigen::Matrix4d& T, const Vec4 & pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtPoint3(const Eigen::Matrix4d& T, const Vec4& pt) const { - const Vec4 X = T * pt; // apply pose + const Vec4 X = T * pt; // apply pose - //const Eigen::Matrix & d_X_d_P = getJacobian_AB_wrt_B<4, 4, 1>(T, pt).block<4, 3>(0, 0); + // const Eigen::Matrix & d_X_d_P = getJacobian_AB_wrt_B<4, 4, 1>(T, pt).block<4, 3>(0, 0); const Vec2 P = X.head<2>() / X(2); @@ -198,7 +194,7 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtPoint3(const Eigen:: double y = X(1); double z = X(2); double invz = 1.0 / z; - double invzsq = invz*invz; + double invzsq = invz * invz; double mxinvzsq = -x * invzsq; double myinvzsq = -y * invzsq; @@ -213,22 +209,22 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtPoint3(const Eigen:: return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtPt(P) * d_P_d_Pt; } -Eigen::Matrix Pinhole::getDerivativeProjectWrtDisto(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtDisto(const Eigen::Matrix4d& pose, const Vec4& pt) const { - const Vec4 X = pose * pt; // apply pose + const Vec4 X = pose * pt; // apply pose const Vec2 P = X.head<2>() / X(2); return getDerivativeCam2ImaWrtPoint() * getDerivativeAddDistoWrtDisto(P); } -Eigen::Matrix Pinhole::getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d& pose, const Vec4& pt) const { return getDerivativeCam2ImaWrtPrincipalPoint(); } -Eigen::Matrix Pinhole::getDerivativeProjectWrtScale(const Eigen::Matrix4d & pose, const Vec4 & pt) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtScale(const Eigen::Matrix4d& pose, const Vec4& pt) const { - const Vec4 X = pose * pt; // apply pose + const Vec4 X = pose * pt; // apply pose const Vec2 P = X.head<2>() / X(2); const Vec2 distorted = this->addDistortion(P); @@ -236,7 +232,7 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtScale(const Eigen::M return getDerivativeCam2ImaWrtScale(distorted); } -Eigen::Matrix Pinhole::getDerivativeProjectWrtParams(const Eigen::Matrix4d & pose, const Vec4& pt3D) const +Eigen::Matrix Pinhole::getDerivativeProjectWrtParams(const Eigen::Matrix4d& pose, const Vec4& pt3D) const { Eigen::Matrix ret(2, getParams().size()); @@ -252,14 +248,11 @@ Eigen::Matrix Pinhole::getDerivativeProjectWrtParams( return ret; } -Vec3 Pinhole::toUnitSphere(const Vec2 & pt) const -{ - return pt.homogeneous().normalized(); -} +Vec3 Pinhole::toUnitSphere(const Vec2& pt) const { return pt.homogeneous().normalized(); } -Eigen::Matrix Pinhole::getDerivativetoUnitSphereWrtPoint(const Vec2 & pt) const +Eigen::Matrix Pinhole::getDerivativetoUnitSphereWrtPoint(const Vec2& pt) const { - const double norm2 = pt(0)*pt(0) + pt(1)*pt(1) + 1.0; + const double norm2 = pt(0) * pt(0) + pt(1) * pt(1) + 1.0; const double norm = sqrt(norm2); const Vec3 ptcam = pt.homogeneous(); @@ -279,12 +272,9 @@ Eigen::Matrix Pinhole::getDerivativetoUnitSphereWrtPoint(const Vec return (norm * d_ptcam_d_pt - ptcam * d_norm_d_pt) / norm2; } -double Pinhole::imagePlaneToCameraPlaneError(double value) const -{ - return value / _scale(0); -} +double Pinhole::imagePlaneToCameraPlaneError(double value) const { return value / _scale(0); } -Mat34 Pinhole::getProjectiveEquivalent(const geometry::Pose3 & pose) const +Mat34 Pinhole::getProjectiveEquivalent(const geometry::Pose3& pose) const { Mat34 P; @@ -292,7 +282,7 @@ Mat34 Pinhole::getProjectiveEquivalent(const geometry::Pose3 & pose) const return P; } -bool Pinhole::isVisibleRay(const Vec3 & ray) const +bool Pinhole::isVisibleRay(const Vec3& ray) const { // if(ray(2) <= 0.0) if (ray(2) < std::numeric_limits::epsilon()) @@ -326,29 +316,40 @@ EINTRINSIC Pinhole::getType() const { switch (_pDistortion->getType()) { - case EDISTORTION::DISTORTION_RADIALK1: return EINTRINSIC::PINHOLE_CAMERA_RADIAL1; - case EDISTORTION::DISTORTION_RADIALK3: return EINTRINSIC::PINHOLE_CAMERA_RADIAL3; - case EDISTORTION::DISTORTION_BROWN: return EINTRINSIC::PINHOLE_CAMERA_BROWN; - case EDISTORTION::DISTORTION_FISHEYE: return EINTRINSIC::PINHOLE_CAMERA_FISHEYE; - case EDISTORTION::DISTORTION_FISHEYE1: return EINTRINSIC::PINHOLE_CAMERA_FISHEYE1; - case EDISTORTION::DISTORTION_3DERADIAL4: return EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4; - case EDISTORTION::DISTORTION_3DECLASSICLD: return EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD; - case EDISTORTION::DISTORTION_3DEANAMORPHIC4: return EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4; - default: throw std::out_of_range("Invalid distortion model for pinhole camera."); + case EDISTORTION::DISTORTION_RADIALK1: + return EINTRINSIC::PINHOLE_CAMERA_RADIAL1; + case EDISTORTION::DISTORTION_RADIALK3: + return EINTRINSIC::PINHOLE_CAMERA_RADIAL3; + case EDISTORTION::DISTORTION_BROWN: + return EINTRINSIC::PINHOLE_CAMERA_BROWN; + case EDISTORTION::DISTORTION_FISHEYE: + return EINTRINSIC::PINHOLE_CAMERA_FISHEYE; + case EDISTORTION::DISTORTION_FISHEYE1: + return EINTRINSIC::PINHOLE_CAMERA_FISHEYE1; + case EDISTORTION::DISTORTION_3DERADIAL4: + return EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4; + case EDISTORTION::DISTORTION_3DECLASSICLD: + return EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD; + case EDISTORTION::DISTORTION_3DEANAMORPHIC4: + return EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4; + default: + throw std::out_of_range("Invalid distortion model for pinhole camera."); } } - + if (_pUndistortion) { switch (_pUndistortion->getType()) { - case EDISTORTION::DISTORTION_3DEANAMORPHIC4: return EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4; - default: throw std::out_of_range("Invalid undistortion model for pinhole camera."); + case EDISTORTION::DISTORTION_3DEANAMORPHIC4: + return EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4; + default: + throw std::out_of_range("Invalid undistortion model for pinhole camera."); } } return EINTRINSIC::PINHOLE_CAMERA; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Pinhole.hpp b/src/aliceVision/camera/Pinhole.hpp index ea7c69d82b..1878489435 100644 --- a/src/aliceVision/camera/Pinhole.hpp +++ b/src/aliceVision/camera/Pinhole.hpp @@ -17,59 +17,43 @@ #include #include - namespace aliceVision { namespace camera { /// Define a classic Pinhole camera class Pinhole : public IntrinsicScaleOffsetDisto { -public: - Pinhole() : - Pinhole(1, 1, 1.0, 1.0, 0.0, 0.0) - { - } - - Pinhole(unsigned int w, unsigned int h, const Mat3& K) : - IntrinsicScaleOffsetDisto(w, h, K(0, 0), K(1, 1), K(0, 2), K(1, 2)) - { - } - - Pinhole(unsigned int w, unsigned int h, - double focalLengthPixX, double focalLengthPixY, - double offsetX, double offsetY, + public: + Pinhole() + : Pinhole(1, 1, 1.0, 1.0, 0.0, 0.0) + {} + + Pinhole(unsigned int w, unsigned int h, const Mat3& K) + : IntrinsicScaleOffsetDisto(w, h, K(0, 0), K(1, 1), K(0, 2), K(1, 2)) + {} + + Pinhole(unsigned int w, + unsigned int h, + double focalLengthPixX, + double focalLengthPixY, + double offsetX, + double offsetY, std::shared_ptr distortion = nullptr, - std::shared_ptr undistortion = nullptr) : - IntrinsicScaleOffsetDisto(w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY, distortion, undistortion) - { - } + std::shared_ptr undistortion = nullptr) + : IntrinsicScaleOffsetDisto(w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY, distortion, undistortion) + {} ~Pinhole() override = default; - Pinhole* clone() const override - { - return new Pinhole(*this); - } + Pinhole* clone() const override { return new Pinhole(*this); } - void assign(const IntrinsicBase& other) override - { - *this = dynamic_cast(other); - } + void assign(const IntrinsicBase& other) override { *this = dynamic_cast(other); } - double getFocalLengthPixX() const - { - return _scale(0); - } + double getFocalLengthPixX() const { return _scale(0); } - double getFocalLengthPixY() const - { - return _scale(1); - } + double getFocalLengthPixY() const { return _scale(1); } - bool isValid() const override - { - return getFocalLengthPixX() > 0 && getFocalLengthPixY() > 0 && IntrinsicBase::isValid(); - } + bool isValid() const override { return getFocalLengthPixX() > 0 && getFocalLengthPixY() > 0 && IntrinsicBase::isValid(); } EINTRINSIC getType() const override; @@ -77,47 +61,47 @@ class Pinhole : public IntrinsicScaleOffsetDisto void setK(double focalLengthPixX, double focalLengthPixY, double ppx, double ppy); - void setK(const Mat3 & K); + void setK(const Mat3& K); Vec2 project(const geometry::Pose3& pose, const Vec4& pt3D, bool applyDistortion = true) const { return project(pose.getHomogeneous(), pt3D, applyDistortion); } - Vec2 project(const Eigen::Matrix4d & pose, const Vec4& pt, bool applyDistortion = true) const override; + Vec2 project(const Eigen::Matrix4d& pose, const Vec4& pt, bool applyDistortion = true) const override; + + Eigen::Matrix getDerivativeProjectWrtRotation(const Eigen::Matrix4d& pose, const Vec4& pt); - Eigen::Matrix getDerivativeProjectWrtRotation(const Eigen::Matrix4d & pose, const Vec4 & pt); + Eigen::Matrix getDerivativeProjectWrtPose(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPose(const Eigen::Matrix4d & pose, const Vec4& pt) const override; + Eigen::Matrix getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPoseLeft(const Eigen::Matrix4d & pose, const Vec4& pt) const override; + Eigen::Matrix getDerivativeProjectWrtPoint(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) const override; + Eigen::Matrix getDerivativeProjectWrtPoint3(const Eigen::Matrix4d& pose, const Vec4& pt) const override; - Eigen::Matrix getDerivativeProjectWrtPoint3(const Eigen::Matrix4d & pose, const Vec4 & pt) const override; + Eigen::Matrix getDerivativeProjectWrtDisto(const Eigen::Matrix4d& pose, const Vec4& pt) const; - Eigen::Matrix getDerivativeProjectWrtDisto(const Eigen::Matrix4d & pose, const Vec4 & pt) const; + Eigen::Matrix getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d& pose, const Vec4& pt) const; - Eigen::Matrix getDerivativeProjectWrtPrincipalPoint(const Eigen::Matrix4d & pose, const Vec4 & pt) const; + Eigen::Matrix getDerivativeProjectWrtScale(const Eigen::Matrix4d& pose, const Vec4& pt) const; - Eigen::Matrix getDerivativeProjectWrtScale(const Eigen::Matrix4d & pose, const Vec4 & pt) const; + Eigen::Matrix getDerivativeProjectWrtParams(const Eigen::Matrix4d& pose, const Vec4& pt3D) const override; - Eigen::Matrix getDerivativeProjectWrtParams(const Eigen::Matrix4d & pose, const Vec4& pt3D) const override; + Vec3 toUnitSphere(const Vec2& pt) const override; - Vec3 toUnitSphere(const Vec2 & pt) const override; + Eigen::Matrix getDerivativetoUnitSphereWrtPoint(const Vec2& pt) const; - Eigen::Matrix getDerivativetoUnitSphereWrtPoint(const Vec2 & pt) const; - double imagePlaneToCameraPlaneError(double value) const override; - Mat34 getProjectiveEquivalent(const geometry::Pose3 & pose) const; + Mat34 getProjectiveEquivalent(const geometry::Pose3& pose) const; /** * @brief Return true if this ray should be visible in the image * @return true if this ray is visible theoretically */ - bool isVisibleRay(const Vec3 & ray) const override; + bool isVisibleRay(const Vec3& ray) const override; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Undistortion.cpp b/src/aliceVision/camera/Undistortion.cpp index 943663f7f4..fd3b098a7a 100644 --- a/src/aliceVision/camera/Undistortion.cpp +++ b/src/aliceVision/camera/Undistortion.cpp @@ -13,8 +13,8 @@ Vec2 Undistortion::undistort(const Vec2& p) const { Vec2 centered; - centered(0) = p(0) - _center(0) -_offset(0); - centered(1) = p(1) - _center(1) -_offset(1); + centered(0) = p(0) - _center(0) - _offset(0); + centered(1) = p(1) - _center(1) - _offset(1); Vec2 normalized; normalized(0) = centered(0) / _diagonal; @@ -29,7 +29,7 @@ Vec2 Undistortion::undistort(const Vec2& p) const return unnormalized; } -Eigen::Matrix Undistortion::getDerivativeUndistortWrtParameters(const Vec2 &p) +Eigen::Matrix Undistortion::getDerivativeUndistortWrtParameters(const Vec2& p) { Vec2 centered = p - _center - _offset; Vec2 normalized = centered / _diagonal; @@ -45,7 +45,7 @@ Eigen::Matrix Undistortion::getDerivativeUndistortWrt return d_unnormalized_d_undistorted * getDerivativeUndistortNormalizedwrtParameters(normalized); } -Eigen::Matrix Undistortion::getDerivativeUndistortWrtOffset(const Vec2 &p) +Eigen::Matrix Undistortion::getDerivativeUndistortWrtOffset(const Vec2& p) { Vec2 centered = p - _center - _offset; Vec2 normalized = centered / _diagonal; @@ -64,7 +64,8 @@ Eigen::Matrix Undistortion::getDerivativeUndistortWrtOffset(const d_normalized_d_centered(1, 0) = 0; d_normalized_d_centered(1, 1) = 1.0 / _diagonal; - return Eigen::Matrix2d::Identity() + d_unnormalized_d_undistorted * getDerivativeUndistortNormalizedwrtPoint(normalized) * d_normalized_d_centered * -1.0; + return Eigen::Matrix2d::Identity() + + d_unnormalized_d_undistorted * getDerivativeUndistortNormalizedwrtPoint(normalized) * d_normalized_d_centered * -1.0; } Vec2 Undistortion::inverse(const Vec2& p) const @@ -87,5 +88,5 @@ Vec2 Undistortion::inverse(const Vec2& p) const return unnormalized; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Undistortion.hpp b/src/aliceVision/camera/Undistortion.hpp index 87c23303f9..c28c8d09ac 100644 --- a/src/aliceVision/camera/Undistortion.hpp +++ b/src/aliceVision/camera/Undistortion.hpp @@ -19,21 +19,20 @@ namespace camera { /** * @brief Abstract class to represent the inverse operation of a distortion model. - * + * * Contrary to distortion models, the undistortion process is a pixel-to-pixel operation * and thus it does not depend on the camera's focal length. - * + * * Its main purpose is to undistort images before they are used in other calculations * so that we can virtually "remove" distortion parameters from the camera. */ class Undistortion { -public: - + public: Undistortion(int width, int height) { setSize(width, height); - setOffset({ 0.0, 0.0 }); + setOffset({0.0, 0.0}); } virtual EDISTORTION getType() const = 0; @@ -41,37 +40,22 @@ class Undistortion virtual Undistortion* clone() const = 0; // not virtual as child classes do not hold any data - bool operator==(const Undistortion& other) const - { - return _undistortionParams == other._undistortionParams; - } + bool operator==(const Undistortion& other) const { return _undistortionParams == other._undistortionParams; } - void setOffset(const Vec2& offset) - { - _offset = offset; - } + void setOffset(const Vec2& offset) { _offset = offset; } void setSize(int width, int height) { - _size = { width, height }; + _size = {width, height}; _diagonal = sqrt(width * width + height * height) * 0.5; _center = {width / 2, height / 2}; } - inline Vec2 getOffset() const - { - return _offset; - } + inline Vec2 getOffset() const { return _offset; } - Vec2 getSize() const - { - return _size; - } + Vec2 getSize() const { return _size; } - const std::vector& getParameters() const - { - return _undistortionParams; - } + const std::vector& getParameters() const { return _undistortionParams; } void setParameters(const std::vector& params) { @@ -86,20 +70,17 @@ class Undistortion } } - std::size_t getUndistortionParametersCount() const - { - return _undistortionParams.size(); - } - + std::size_t getUndistortionParametersCount() const { return _undistortionParams.size(); } + Vec2 undistort(const Vec2& p) const; - Eigen::Matrix getDerivativeUndistortWrtParameters(const Vec2 &p); + Eigen::Matrix getDerivativeUndistortWrtParameters(const Vec2& p); - Eigen::Matrix getDerivativeUndistortWrtOffset(const Vec2 &p); + Eigen::Matrix getDerivativeUndistortWrtOffset(const Vec2& p); /// add distortion (return p' such that undisto(p') = p) Vec2 inverse(const Vec2& p) const; - + virtual Vec2 inverseNormalized(const Vec2& p) const = 0; virtual Vec2 undistortNormalized(const Vec2& p) const = 0; virtual Eigen::Matrix getDerivativeUndistortNormalizedwrtParameters(const Vec2& p) const = 0; @@ -107,7 +88,7 @@ class Undistortion virtual ~Undistortion() = default; -protected: + protected: Vec2 _size; Vec2 _center; Vec2 _offset; @@ -115,5 +96,5 @@ class Undistortion std::vector _undistortionParams{}; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Undistortion3DE.cpp b/src/aliceVision/camera/Undistortion3DE.cpp index 54731b3a46..b14b3ddb1b 100644 --- a/src/aliceVision/camera/Undistortion3DE.cpp +++ b/src/aliceVision/camera/Undistortion3DE.cpp @@ -33,7 +33,7 @@ Vec2 Undistortion3DEAnamorphic4::undistortNormalized(const Vec2& p) const const double cx_xxyy = 2 * cx04 - 6 * cx44; const double cx_xxxx = cx04 + cx24 + cx44; const double cx_yyyy = cx04 - cx24 + cx44; - + const double cy_xx = cy02 + cy22; const double cy_yy = cy02 - cy22; const double cy_xxyy = 2 * cy04 - 6 * cy44; @@ -44,7 +44,7 @@ Vec2 Undistortion3DEAnamorphic4::undistortNormalized(const Vec2& p) const const double& y = p.y(); // First rotate axis - const double xr = cphi* x + sphi * y; + const double xr = cphi * x + sphi * y; const double yr = -sphi * x + cphi * y; const double xx = xr * xr; @@ -56,20 +56,20 @@ Vec2 Undistortion3DEAnamorphic4::undistortNormalized(const Vec2& p) const // Compute dist const double xd = xr * (1.0 + xx * cx_xx + yy * cx_yy + xxxx * cx_xxxx + xxyy * cx_xxyy + yyyy * cx_yyyy); const double yd = yr * (1.0 + xx * cy_xx + yy * cy_yy + xxxx * cy_xxxx + xxyy * cy_xxyy + yyyy * cy_yyyy); - + // Squeeze axis const double squizzed_x = xd * sqx; const double squizzed_y = yd * sqy; // Unrotate axis Vec2 np; - np.x() = cphi* squizzed_x - sphi * squizzed_y; - np.y() = sphi* squizzed_x + cphi * squizzed_y; + np.x() = cphi * squizzed_x - sphi * squizzed_y; + np.y() = sphi * squizzed_x + cphi * squizzed_y; return np; } -Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNormalizedwrtPoint(const Vec2 &p) const +Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNormalizedwrtPoint(const Vec2& p) const { const double& cx02 = _undistortionParams[0]; const double& cy02 = _undistortionParams[1]; @@ -93,7 +93,7 @@ Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNo const double cx_xxyy = 2 * cx04 - 6 * cx44; const double cx_xxxx = cx04 + cx24 + cx44; const double cx_yyyy = cx04 - cx24 + cx44; - + const double cy_xx = cy02 + cy22; const double cy_yy = cy02 - cy22; const double cy_xxyy = 2 * cy04 - 6 * cy44; @@ -104,7 +104,7 @@ Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNo const double& y = p.y(); // First rotate axis - const double xr = cphi* x + sphi * y; + const double xr = cphi * x + sphi * y; const double yr = -sphi * x + cphi * y; const double xx = xr * xr; @@ -151,7 +151,7 @@ Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNo return d_np_d_squizzed * d_squizzed_d_d * d_d_d_r * d_r_d_p; } -Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNormalizedwrtParameters(const Vec2 &p) const +Eigen::Matrix Undistortion3DEAnamorphic4::getDerivativeUndistortNormalizedwrtParameters(const Vec2& p) const { const double& cx02 = _undistortionParams[0]; const double& cy02 = _undistortionParams[1]; @@ -186,7 +186,7 @@ Eigen::Matrix Undistortion3DEAnamorphic4::getDerivati const double& y = p.y(); // First rotate axis - const double xr = cphi* x + sphi * y; + const double xr = cphi * x + sphi * y; const double yr = -sphi * x + cphi * y; const double xx = xr * xr; @@ -224,7 +224,6 @@ Eigen::Matrix Undistortion3DEAnamorphic4::getDerivati d_np_d_squizzed(1, 0) = 0; d_np_d_squizzed(1, 1) = 1; - Eigen::Matrix2d d_squizzed_d_d; d_squizzed_d_d(0, 0) = sqx; d_squizzed_d_d(0, 1) = 0; @@ -280,9 +279,9 @@ Eigen::Matrix Undistortion3DEAnamorphic4::getDerivati d_distop_d_disto(8, 9) = 1.0; d_distop_d_disto(9, 5) = 1.0; d_distop_d_disto(9, 7) = -1.0; - d_distop_d_disto(9, 9) = 1.0; + d_distop_d_disto(9, 9) = 1.0; - Eigen::Matrix J = (d_np_d_squizzed * d_squizzed_d_disto) + (d_np_d_squizzed * d_squizzed_d_d * d_d_d_distop * d_distop_d_disto) ; + Eigen::Matrix J = (d_np_d_squizzed * d_squizzed_d_disto) + (d_np_d_squizzed * d_squizzed_d_d * d_d_d_distop * d_distop_d_disto); return J; } @@ -300,11 +299,12 @@ Vec2 Undistortion3DEAnamorphic4::inverseNormalized(const Vec2& p) const distorted_value = distorted_value - getDerivativeUndistortNormalizedwrtPoint(distorted_value).inverse() * diff; diff = undistortNormalized(distorted_value) - p; iter++; - if (iter > 100) break; + if (iter > 100) + break; } return distorted_value; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/Undistortion3DE.hpp b/src/aliceVision/camera/Undistortion3DE.hpp index 2ce7674ef0..0ac6fa351e 100644 --- a/src/aliceVision/camera/Undistortion3DE.hpp +++ b/src/aliceVision/camera/Undistortion3DE.hpp @@ -12,30 +12,30 @@ #include #include -namespace aliceVision{ -namespace camera{ +namespace aliceVision { +namespace camera { class Undistortion3DEAnamorphic4 : public Undistortion { -public: - + public: /** * @brief Default constructor, no distortion. */ - Undistortion3DEAnamorphic4(int width, int height) : Undistortion(width, height) + Undistortion3DEAnamorphic4(int width, int height) + : Undistortion(width, height) { _undistortionParams = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; } EDISTORTION getType() const override { return EDISTORTION::DISTORTION_3DEANAMORPHIC4; } - Undistortion * clone() const override { return new Undistortion3DEAnamorphic4(*this); } + Undistortion* clone() const override { return new Undistortion3DEAnamorphic4(*this); } Vec2 undistortNormalized(const Vec2& p) const override; - Eigen::Matrix getDerivativeUndistortNormalizedwrtPoint(const Vec2 &p) const override; + Eigen::Matrix getDerivativeUndistortNormalizedwrtPoint(const Vec2& p) const override; - Eigen::Matrix getDerivativeUndistortNormalizedwrtParameters(const Vec2 &p) const override; + Eigen::Matrix getDerivativeUndistortNormalizedwrtParameters(const Vec2& p) const override; /// add distortion (return p' such that undisto(p') = p) Vec2 inverseNormalized(const Vec2& p) const override; @@ -43,5 +43,5 @@ class Undistortion3DEAnamorphic4 : public Undistortion virtual ~Undistortion3DEAnamorphic4() = default; }; -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/camera.hpp b/src/aliceVision/camera/camera.hpp index 02f7000dc2..4579a058c9 100644 --- a/src/aliceVision/camera/camera.hpp +++ b/src/aliceVision/camera/camera.hpp @@ -33,41 +33,41 @@ namespace camera { * @param[in] params Distortion parameters. * @return Shared pointer of initialized distortion object. */ -inline std::shared_ptr createDistortion(EDISTORTION distortionType, - std::initializer_list params = {}) +inline std::shared_ptr createDistortion(EDISTORTION distortionType, std::initializer_list params = {}) { std::shared_ptr distortion = nullptr; switch (distortionType) { - case EDISTORTION::DISTORTION_RADIALK1: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_RADIALK3: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_RADIALK3PT: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_BROWN: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_FISHEYE: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_FISHEYE1: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_3DECLASSICLD: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_3DERADIAL4: - distortion = std::make_shared(); - break; - case EDISTORTION::DISTORTION_3DEANAMORPHIC4: - distortion = std::make_shared(); - break; - default: break; + case EDISTORTION::DISTORTION_RADIALK1: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_RADIALK3: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_RADIALK3PT: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_BROWN: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_FISHEYE: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_FISHEYE1: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_3DECLASSICLD: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_3DERADIAL4: + distortion = std::make_shared(); + break; + case EDISTORTION::DISTORTION_3DEANAMORPHIC4: + distortion = std::make_shared(); + break; + default: + break; } if (distortion && params.size() > 0) @@ -90,17 +90,19 @@ inline std::shared_ptr createDistortion(EDISTORTION distortionType, * @return Shared pointer of initialized undistortion object. */ inline std::shared_ptr createUndistortion(EDISTORTION distortionType, - unsigned int w = 0, unsigned int h = 0, - std::initializer_list params = {}) + unsigned int w = 0, + unsigned int h = 0, + std::initializer_list params = {}) { std::shared_ptr undistortion = nullptr; switch (distortionType) { - case EDISTORTION::DISTORTION_3DEANAMORPHIC4: - undistortion = std::make_shared(w, h); - break; - default: break; + case EDISTORTION::DISTORTION_3DEANAMORPHIC4: + undistortion = std::make_shared(w, h); + break; + default: + break; } if (undistortion && params.size() > 0) @@ -125,18 +127,30 @@ inline EDISTORTION getDistortionType(EINTRINSIC intrinsicType) { switch (intrinsicType) { - case EINTRINSIC::PINHOLE_CAMERA: return EDISTORTION::NONE; - case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: return EDISTORTION::DISTORTION_RADIALK1; - case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: return EDISTORTION::DISTORTION_RADIALK3; - case EINTRINSIC::PINHOLE_CAMERA_BROWN: return EDISTORTION::DISTORTION_BROWN; - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: return EDISTORTION::DISTORTION_FISHEYE; - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: return EDISTORTION::DISTORTION_FISHEYE1; - case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: return EDISTORTION::DISTORTION_3DEANAMORPHIC4; - case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: return EDISTORTION::DISTORTION_3DECLASSICLD; - case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: return EDISTORTION::DISTORTION_3DERADIAL4; - case EINTRINSIC::EQUIDISTANT_CAMERA: return EDISTORTION::NONE; - case EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3: return EDISTORTION::DISTORTION_RADIALK3PT; - default: break; + case EINTRINSIC::PINHOLE_CAMERA: + return EDISTORTION::NONE; + case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: + return EDISTORTION::DISTORTION_RADIALK1; + case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: + return EDISTORTION::DISTORTION_RADIALK3; + case EINTRINSIC::PINHOLE_CAMERA_BROWN: + return EDISTORTION::DISTORTION_BROWN; + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: + return EDISTORTION::DISTORTION_FISHEYE; + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: + return EDISTORTION::DISTORTION_FISHEYE1; + case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: + return EDISTORTION::DISTORTION_3DEANAMORPHIC4; + case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: + return EDISTORTION::DISTORTION_3DECLASSICLD; + case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: + return EDISTORTION::DISTORTION_3DERADIAL4; + case EINTRINSIC::EQUIDISTANT_CAMERA: + return EDISTORTION::NONE; + case EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3: + return EDISTORTION::DISTORTION_RADIALK3PT; + default: + break; } throw std::out_of_range("Invalid intrinsic type"); @@ -154,30 +168,24 @@ inline EDISTORTION getDistortionType(EINTRINSIC intrinsicType) * @return Shared pointer of initialized intrinsic object. */ inline std::shared_ptr createIntrinsic(EINTRINSIC intrinsicType, - unsigned int w = 0, unsigned int h = 0, - double focalLengthPixX = 0.0, double focalLengthPixY = 0.0, - double offsetX = 0.0, double offsetY = 0.0) + unsigned int w = 0, + unsigned int h = 0, + double focalLengthPixX = 0.0, + double focalLengthPixY = 0.0, + double offsetX = 0.0, + double offsetY = 0.0) { auto distortion = createDistortion(getDistortionType(intrinsicType)); auto undistortion = createUndistortion(getDistortionType(intrinsicType), w, h); if (isPinhole(intrinsicType)) { - return std::make_shared( - w, h, - focalLengthPixX, focalLengthPixY, - offsetX, offsetY, - distortion, - undistortion); + return std::make_shared(w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY, distortion, undistortion); } if (isEquidistant(intrinsicType)) { - return std::make_shared( - w, h, - focalLengthPixX, - offsetX, offsetY, - distortion); + return std::make_shared(w, h, focalLengthPixX, offsetX, offsetY, distortion); } return nullptr; @@ -196,10 +204,13 @@ inline std::shared_ptr createIntrinsic(EINTRINSIC intrinsicType, * @return Shared pointer of initialized pinhole camera object. */ inline std::shared_ptr createPinhole(EINTRINSIC intrinsicType, - unsigned int w = 0, unsigned int h = 0, - double focalLengthPixX = 0.0, double focalLengthPixY = 0.0, - double offsetX = 0.0, double offsetY = 0.0, - std::initializer_list distortionParams = {}) + unsigned int w = 0, + unsigned int h = 0, + double focalLengthPixX = 0.0, + double focalLengthPixY = 0.0, + double offsetX = 0.0, + double offsetY = 0.0, + std::initializer_list distortionParams = {}) { if (!isPinhole(intrinsicType)) { @@ -209,12 +220,7 @@ inline std::shared_ptr createPinhole(EINTRINSIC intrinsicType, auto distortion = createDistortion(getDistortionType(intrinsicType), distortionParams); auto undistortion = createUndistortion(getDistortionType(intrinsicType), w, h, distortionParams); - return std::make_shared( - w, h, - focalLengthPixX, focalLengthPixY, - offsetX, offsetY, - distortion, - undistortion); + return std::make_shared(w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY, distortion, undistortion); } /** @@ -230,10 +236,12 @@ inline std::shared_ptr createPinhole(EINTRINSIC intrinsicType, * @return Shared pointer of initialized equidistant camera object. */ inline std::shared_ptr createEquidistant(EINTRINSIC intrinsicType, - unsigned int w = 0, unsigned int h = 0, - double focalLengthPix = 0.0, - double offsetX = 0.0, double offsetY = 0.0, - std::initializer_list distortionParams = {}) + unsigned int w = 0, + unsigned int h = 0, + double focalLengthPix = 0.0, + double offsetX = 0.0, + double offsetY = 0.0, + std::initializer_list distortionParams = {}) { if (!isEquidistant(intrinsicType)) { @@ -242,12 +250,8 @@ inline std::shared_ptr createEquidistant(EINTRINSIC intrinsicType, auto distortion = createDistortion(getDistortionType(intrinsicType), distortionParams); - return std::make_shared( - w, h, - focalLengthPix, - offsetX, offsetY, - distortion); + return std::make_shared(w, h, focalLengthPix, offsetX, offsetY, distortion); } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/cameraCommon.hpp b/src/aliceVision/camera/cameraCommon.hpp index 07b2c9af16..faec67949c 100644 --- a/src/aliceVision/camera/cameraCommon.hpp +++ b/src/aliceVision/camera/cameraCommon.hpp @@ -48,7 +48,7 @@ enum EINTRINSIC EQUIDISTANT_CAMERA = (1u << 10), // plain equidistant model EQUIDISTANT_CAMERA_RADIAL3 = (1u << 11), // equidistant model with radial distortion VALID_PINHOLE = PINHOLE_CAMERA | PINHOLE_CAMERA_RADIAL1 | PINHOLE_CAMERA_RADIAL3 | PINHOLE_CAMERA_3DERADIAL4 | PINHOLE_CAMERA_BROWN | - PINHOLE_CAMERA_3DEANAMORPHIC4 | PINHOLE_CAMERA_3DECLASSICLD| PINHOLE_CAMERA_FISHEYE | PINHOLE_CAMERA_FISHEYE1, + PINHOLE_CAMERA_3DEANAMORPHIC4 | PINHOLE_CAMERA_3DECLASSICLD | PINHOLE_CAMERA_FISHEYE | PINHOLE_CAMERA_FISHEYE1, VALID_EQUIDISTANT = EQUIDISTANT_CAMERA | EQUIDISTANT_CAMERA_RADIAL3, VALID_CAMERA_MODEL = VALID_PINHOLE | VALID_EQUIDISTANT, }; @@ -59,22 +59,33 @@ inline std::string EINTRINSIC_enumToString(EINTRINSIC intrinsic) { switch (intrinsic) { - case EINTRINSIC::PINHOLE_CAMERA: return "pinhole"; - case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: return "radial1"; - case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: return "radial3"; - case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: return "3deradial4"; - case EINTRINSIC::PINHOLE_CAMERA_BROWN: return "brown"; - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: return "fisheye4"; - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: return "fisheye1"; - case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: return "3deanamorphic4"; - case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: return "3declassicld"; - case EINTRINSIC::EQUIDISTANT_CAMERA: return "equidistant"; - case EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3: return "equidistant_r3"; - case EINTRINSIC::UNKNOWN: - case EINTRINSIC::VALID_PINHOLE: - case EINTRINSIC::VALID_EQUIDISTANT: - case EINTRINSIC::VALID_CAMERA_MODEL: - break; + case EINTRINSIC::PINHOLE_CAMERA: + return "pinhole"; + case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: + return "radial1"; + case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: + return "radial3"; + case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: + return "3deradial4"; + case EINTRINSIC::PINHOLE_CAMERA_BROWN: + return "brown"; + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: + return "fisheye4"; + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: + return "fisheye1"; + case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: + return "3deanamorphic4"; + case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: + return "3declassicld"; + case EINTRINSIC::EQUIDISTANT_CAMERA: + return "equidistant"; + case EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3: + return "equidistant_r3"; + case EINTRINSIC::UNKNOWN: + case EINTRINSIC::VALID_PINHOLE: + case EINTRINSIC::VALID_EQUIDISTANT: + case EINTRINSIC::VALID_CAMERA_MODEL: + break; } throw std::out_of_range("Invalid Intrinsic Enum"); } @@ -82,27 +93,35 @@ inline std::string EINTRINSIC_enumToString(EINTRINSIC intrinsic) inline EINTRINSIC EINTRINSIC_stringToEnum(const std::string& intrinsic) { std::string type = intrinsic; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower - - if (type == "pinhole") return EINTRINSIC::PINHOLE_CAMERA; - if (type == "radial1") return EINTRINSIC::PINHOLE_CAMERA_RADIAL1; - if (type == "radial3") return EINTRINSIC::PINHOLE_CAMERA_RADIAL3; - if (type == "3deradial4") return EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4; - if (type == "brown") return EINTRINSIC::PINHOLE_CAMERA_BROWN; - if (type == "fisheye4") return EINTRINSIC::PINHOLE_CAMERA_FISHEYE; - if (type == "fisheye1") return EINTRINSIC::PINHOLE_CAMERA_FISHEYE1; - if (type == "3deanamorphic4") return EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4; - if (type == "3declassicld") return EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD; - if (type == "equidistant") return EINTRINSIC::EQUIDISTANT_CAMERA; - if (type == "equidistant_r3") return EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); // tolower + + if (type == "pinhole") + return EINTRINSIC::PINHOLE_CAMERA; + if (type == "radial1") + return EINTRINSIC::PINHOLE_CAMERA_RADIAL1; + if (type == "radial3") + return EINTRINSIC::PINHOLE_CAMERA_RADIAL3; + if (type == "3deradial4") + return EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4; + if (type == "brown") + return EINTRINSIC::PINHOLE_CAMERA_BROWN; + if (type == "fisheye4") + return EINTRINSIC::PINHOLE_CAMERA_FISHEYE; + if (type == "fisheye1") + return EINTRINSIC::PINHOLE_CAMERA_FISHEYE1; + if (type == "3deanamorphic4") + return EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4; + if (type == "3declassicld") + return EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD; + if (type == "equidistant") + return EINTRINSIC::EQUIDISTANT_CAMERA; + if (type == "equidistant_r3") + return EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3; throw std::out_of_range(intrinsic); } -inline std::ostream& operator<<(std::ostream& os, EINTRINSIC e) -{ - return os << EINTRINSIC_enumToString(e); -} +inline std::ostream& operator<<(std::ostream& os, EINTRINSIC e) { return os << EINTRINSIC_enumToString(e); } inline std::istream& operator>>(std::istream& in, EINTRINSIC& e) { @@ -113,20 +132,11 @@ inline std::istream& operator>>(std::istream& in, EINTRINSIC& e) } // Return if the camera type is a valid enum -inline bool isValid(EINTRINSIC eintrinsic) -{ - return EINTRINSIC::VALID_CAMERA_MODEL & eintrinsic; -} +inline bool isValid(EINTRINSIC eintrinsic) { return EINTRINSIC::VALID_CAMERA_MODEL & eintrinsic; } -inline bool isPinhole(EINTRINSIC eintrinsic) -{ - return EINTRINSIC::VALID_PINHOLE & eintrinsic; -} +inline bool isPinhole(EINTRINSIC eintrinsic) { return EINTRINSIC::VALID_PINHOLE & eintrinsic; } -inline bool isEquidistant(EINTRINSIC eintrinsic) -{ - return EINTRINSIC::VALID_EQUIDISTANT & eintrinsic; -} +inline bool isEquidistant(EINTRINSIC eintrinsic) { return EINTRINSIC::VALID_EQUIDISTANT & eintrinsic; } inline EINTRINSIC EINTRINSIC_parseStringToBitmask(const std::string& str, const std::string& joinChar = ",") { @@ -144,5 +154,5 @@ inline EINTRINSIC EINTRINSIC_parseStringToBitmask(const std::string& str, const return mask; } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/cameraUndistortImage.hpp b/src/aliceVision/camera/cameraUndistortImage.hpp index 8a25e6390d..f69d351f5a 100644 --- a/src/aliceVision/camera/cameraUndistortImage.hpp +++ b/src/aliceVision/camera/cameraUndistortImage.hpp @@ -24,15 +24,14 @@ namespace aliceVision { namespace camera { /// Undistort an image according a given camera and its distortion model -template -void UndistortImage( - const image::Image& imageIn, - const camera::IntrinsicBase * intrinsicSource, - const camera::IntrinsicBase * intrinsicOutput, - const camera::Undistortion * undistortionOutput, - image::Image& image_ud, - T fillcolor, - const oiio::ROI & roi = oiio::ROI()) +template +void UndistortImage(const image::Image& imageIn, + const camera::IntrinsicBase* intrinsicSource, + const camera::IntrinsicBase* intrinsicOutput, + const camera::Undistortion* undistortionOutput, + image::Image& image_ud, + T fillcolor, + const oiio::ROI& roi = oiio::ROI()) { if (!intrinsicSource->hasDistortion()) // no distortion, perform a direct copy { @@ -58,7 +57,7 @@ void UndistortImage( image_ud.resize(widthRoi, heightRoi, true, fillcolor); const image::Sampler2d sampler; - #pragma omp parallel for +#pragma omp parallel for for (int y = 0; y < heightRoi; ++y) { for (int x = 0; x < widthRoi; ++x) @@ -66,10 +65,8 @@ void UndistortImage( const Vec2 undisto_pix(x + xOffset, y + yOffset); // compute coordinates with distortion - const Vec2 disto_pix = intrinsicSource->cam2ima( - intrinsicSource->addDistortion( - intrinsicOutput->ima2cam( - (undistortionOutput) ? undistortionOutput->inverse(undisto_pix) : undisto_pix))); + const Vec2 disto_pix = intrinsicSource->cam2ima(intrinsicSource->addDistortion( + intrinsicOutput->ima2cam((undistortionOutput) ? undistortionOutput->inverse(undisto_pix) : undisto_pix))); // pick pixel if it is in the image domain if (imageIn.Contains(disto_pix(1), disto_pix(0))) @@ -81,14 +78,13 @@ void UndistortImage( } /// Undistort an image according a given camera and its distortion model -template -void UndistortImage( - const image::Image& imageIn, - const camera::IntrinsicBase* intrinsicPtr, - image::Image& image_ud, - T fillcolor, - bool correctPrincipalPoint = false, - const oiio::ROI & roi = oiio::ROI()) +template +void UndistortImage(const image::Image& imageIn, + const camera::IntrinsicBase* intrinsicPtr, + image::Image& image_ud, + T fillcolor, + bool correctPrincipalPoint = false, + const oiio::ROI& roi = oiio::ROI()) { if (!intrinsicPtr->hasDistortion()) // no distortion, perform a direct copy { @@ -124,7 +120,7 @@ void UndistortImage( image_ud.resize(widthRoi, heightRoi, true, fillcolor); const image::Sampler2d sampler; - #pragma omp parallel for +#pragma omp parallel for for (int y = 0; y < heightRoi; ++y) { for (int x = 0; x < widthRoi; ++x) @@ -142,5 +138,5 @@ void UndistortImage( } } -} // namespace camera -} // namespace aliceVision +} // namespace camera +} // namespace aliceVision diff --git a/src/aliceVision/camera/distortion_test.cpp b/src/aliceVision/camera/distortion_test.cpp index 9302d14f68..c4cd58335a 100644 --- a/src/aliceVision/camera/distortion_test.cpp +++ b/src/aliceVision/camera/distortion_test.cpp @@ -33,13 +33,13 @@ BOOST_AUTO_TEST_CASE(distortion_distort_undistort) const double epsilon = 1e-4; const std::size_t numPts{1000}; - for(std::size_t i = 0; i < numPts; ++i) + for (std::size_t i = 0; i < numPts; ++i) { // random point in [-lim, lim]x[-lim, lim] const double lim{0.8}; - const Vec2 ptImage = lim*Vec2::Random(); + const Vec2 ptImage = lim * Vec2::Random(); - for(const auto& model : distortionsModels) + for (const auto& model : distortionsModels) { const auto distorted = model->addDistortion(ptImage); const auto undistorted = model->removeDistortion(distorted); diff --git a/src/aliceVision/camera/equidistant_test.cpp b/src/aliceVision/camera/equidistant_test.cpp index fad46ecb9b..7362839831 100644 --- a/src/aliceVision/camera/equidistant_test.cpp +++ b/src/aliceVision/camera/equidistant_test.cpp @@ -26,46 +26,43 @@ using namespace aliceVision::camera; //----------------- BOOST_AUTO_TEST_CASE(cameraEquidistant_disto_undisto_Radial) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const int w = 1000; - const int h = 800; - const double focal = 800.0; - const double offsetX = 0.0; - const double offsetY = 0.0; - const double radius = 0.0; // defined automatically - const double k1 = 0.3; - const double k2 = 0.2; - const double k3 = 0.1; + const int w = 1000; + const int h = 800; + const double focal = 800.0; + const double offsetX = 0.0; + const double offsetY = 0.0; + const double radius = 0.0; // defined automatically + const double k1 = 0.3; + const double k2 = 0.2; + const double k3 = 0.1; - std::shared_ptr distortion = std::make_shared(k1, k2, k3); + std::shared_ptr distortion = std::make_shared(k1, k2, k3); - std::shared_ptr cam = std::make_shared(w, h, focal, offsetX, offsetY, radius, distortion); + std::shared_ptr cam = std::make_shared(w, h, focal, offsetX, offsetY, radius, distortion); - const double epsilon = 1e-4; - for (int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ) ; + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } - - - diff --git a/src/aliceVision/camera/pinhole3DE_test.cpp b/src/aliceVision/camera/pinhole3DE_test.cpp index 84732ea8fd..93c33d1450 100644 --- a/src/aliceVision/camera/pinhole3DE_test.cpp +++ b/src/aliceVision/camera/pinhole3DE_test.cpp @@ -26,41 +26,36 @@ using namespace aliceVision::camera; //----------------- BOOST_AUTO_TEST_CASE(cameraPinhole3DE_disto_undisto_Radial4) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared( - -0.4839495643487452, - 1.0301284234642258, - 0.014928332802185664, - -0.0007797104872758904, - -0.038994206396183909, - 8.0474385001183646e-05); + std::shared_ptr distortion = std::make_shared( + -0.4839495643487452, 1.0301284234642258, 0.014928332802185664, -0.0007797104872758904, -0.038994206396183909, 8.0474385001183646e-05); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - const double epsilon = 1e-4; - for(int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ) ; + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } //----------------- @@ -74,39 +69,34 @@ BOOST_AUTO_TEST_CASE(cameraPinhole3DE_disto_undisto_Radial4) //----------------- BOOST_AUTO_TEST_CASE(cameraPinhole3DE_disto_undisto_ClassicLD) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared( - -0.34768564335290314, - 1.5809150001711287, - -0.17204522667665839, - -0.15541950225726325, - 1.1240093674337683); + std::shared_ptr distortion = std::make_shared( + -0.34768564335290314, 1.5809150001711287, -0.17204522667665839, -0.15541950225726325, 1.1240093674337683); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - const double epsilon = 1e-4; - for(int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ) ; + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } - diff --git a/src/aliceVision/camera/pinholeBrown_test.cpp b/src/aliceVision/camera/pinholeBrown_test.cpp index 574ed558d2..ddebae63f9 100644 --- a/src/aliceVision/camera/pinholeBrown_test.cpp +++ b/src/aliceVision/camera/pinholeBrown_test.cpp @@ -27,32 +27,32 @@ using namespace aliceVision::camera; //----------------- BOOST_AUTO_TEST_CASE(cameraPinholeBrown_disto_undisto_T2) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared(-0.054, 0.014, 0.006, 0.001, -0.001); + std::shared_ptr distortion = std::make_shared(-0.054, 0.014, 0.006, 0.001, -0.001); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - const double epsilon = 1e-4; - for (int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ); + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } diff --git a/src/aliceVision/camera/pinholeFisheye1_test.cpp b/src/aliceVision/camera/pinholeFisheye1_test.cpp index 43e861b009..a80e151a8f 100644 --- a/src/aliceVision/camera/pinholeFisheye1_test.cpp +++ b/src/aliceVision/camera/pinholeFisheye1_test.cpp @@ -26,33 +26,33 @@ using namespace aliceVision::camera; //----------------- BOOST_AUTO_TEST_CASE(cameraPinholeFisheye_disto_undisto_Fisheye1) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared(0.1); + std::shared_ptr distortion = std::make_shared(0.1); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - const double epsilon = 1e-4; - for (int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ); + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } diff --git a/src/aliceVision/camera/pinholeFisheye_test.cpp b/src/aliceVision/camera/pinholeFisheye_test.cpp index f4079b91cb..1d6ce3ef8e 100644 --- a/src/aliceVision/camera/pinholeFisheye_test.cpp +++ b/src/aliceVision/camera/pinholeFisheye_test.cpp @@ -27,32 +27,32 @@ using namespace aliceVision::camera; //----------------- BOOST_AUTO_TEST_CASE(cameraPinholeFisheye_disto_undisto_Fisheye) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared(-0.054, 0.014, 0.006, 0.011); + std::shared_ptr distortion = std::make_shared(-0.054, 0.014, 0.006, 0.011); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - const double epsilon = 1e-4; - for (int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ); + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } diff --git a/src/aliceVision/camera/pinholeRadial_test.cpp b/src/aliceVision/camera/pinholeRadial_test.cpp index d1360da6a5..5ab7da44da 100644 --- a/src/aliceVision/camera/pinholeRadial_test.cpp +++ b/src/aliceVision/camera/pinholeRadial_test.cpp @@ -27,35 +27,35 @@ using namespace aliceVision::camera; //----------------- BOOST_AUTO_TEST_CASE(cameraPinholeRadial_disto_undisto_K1) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared(0.1); + std::shared_ptr distortion = std::make_shared(0.1); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - const double epsilon = 1e-4; - for(int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera))) ) ; + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } //----------------- @@ -69,36 +69,35 @@ BOOST_AUTO_TEST_CASE(cameraPinholeRadial_disto_undisto_K1) //----------------- BOOST_AUTO_TEST_CASE(cameraPinholeRadial_disto_undisto_K3) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::shared_ptr distortion = std::make_shared(-0.245539, 0.255195, 0.163773); + std::shared_ptr distortion = std::make_shared(-0.245539, 0.255195, 0.163773); - std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); + std::shared_ptr cam = std::make_shared(1000, 1000, 1000, 1000, 0, 0, distortion); - // Check that adding and removing distortion give the same coordinates + // Check that adding and removing distortion give the same coordinates - const double epsilon = 1e-4; - for(int i = 0; i < 10; ++i) - { - // generate random point inside the image domain (last random to avoid 0,0) - const Vec2 ptImage_gt = (Vec2::Random() * 800./2.) + Vec2(500,500) + Vec2::Random(); + const double epsilon = 1e-4; + for (int i = 0; i < 10; ++i) + { + // generate random point inside the image domain (last random to avoid 0,0) + const Vec2 ptImage_gt = (Vec2::Random() * 800. / 2.) + Vec2(500, 500) + Vec2::Random(); - const Vec2 ptCamera = cam->ima2cam(ptImage_gt); - // Check that adding and removing distortion allow to recover the provided point - EXPECT_MATRIX_NEAR( ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); - EXPECT_MATRIX_NEAR( ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); + const Vec2 ptCamera = cam->ima2cam(ptImage_gt); + // Check that adding and removing distortion allow to recover the provided point + EXPECT_MATRIX_NEAR(ptCamera, cam->removeDistortion(cam->addDistortion(ptCamera)), epsilon); + EXPECT_MATRIX_NEAR(ptImage_gt, cam->cam2ima(cam->removeDistortion(cam->addDistortion(ptCamera))), epsilon); - // Assert that distortion field is not null and it has moved the initial provided point - BOOST_CHECK(! (cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); + // Assert that distortion field is not null and it has moved the initial provided point + BOOST_CHECK(!(cam->addDistortion(ptCamera) == cam->removeDistortion(cam->addDistortion(ptCamera)))); - // Check projection / back-projection - const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; - const geometry::Pose3 pose(geometry::randomPose()); + // Check projection / back-projection + const double depth_gt = std::abs(Vec2::Random()(0)) * 100.0; + const geometry::Pose3 pose(geometry::randomPose()); - const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); - const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); + const Vec3 pt3d = cam->backproject(ptImage_gt, true, pose, depth_gt); + const Vec2 pt2d_proj = cam->project(pose, pt3d.homogeneous(), true); - EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); - - } + EXPECT_MATRIX_NEAR(ptImage_gt, pt2d_proj, epsilon); + } } diff --git a/src/aliceVision/cmdline/cmdline.cpp b/src/aliceVision/cmdline/cmdline.cpp index 86aeac7c79..d0573c6a5d 100644 --- a/src/aliceVision/cmdline/cmdline.cpp +++ b/src/aliceVision/cmdline/cmdline.cpp @@ -10,19 +10,20 @@ bool CmdLine::execute(int argc, char** argv) std::string verboseLevel = system::EVerboseLevel_enumToString(system::Logger::getDefaultVerboseLevel()); boost::program_options::options_description logParams("Log parameters"); - logParams.add_options() - ("verboseLevel,v", boost::program_options::value(&verboseLevel)->default_value(verboseLevel), "verbosity level (fatal, error, warning, info, debug, trace)."); + logParams.add_options()("verboseLevel,v", + boost::program_options::value(&verboseLevel)->default_value(verboseLevel), + "verbosity level (fatal, error, warning, info, debug, trace)."); _allParams.add(logParams); boost::program_options::options_description hardwareParams("Hardware parameters"); - + size_t uma = _hContext.getUserMaxMemoryAvailable(); unsigned int uca = _hContext.getUserMaxCoresAvailable(); - hardwareParams.add_options() - ("maxMemoryAvailable", boost::program_options::value(&uma)->default_value(uma), "User specified available RAM") - ("maxCoresAvailable", boost::program_options::value(&uca)->default_value(uca), "User specified available number of cores"); + hardwareParams.add_options()( + "maxMemoryAvailable", boost::program_options::value(&uma)->default_value(uma), "User specified available RAM")( + "maxCoresAvailable", boost::program_options::value(&uca)->default_value(uca), "User specified available number of cores"); _allParams.add(hardwareParams); @@ -64,4 +65,4 @@ bool CmdLine::execute(int argc, char** argv) return true; } -} \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/cmdline/cmdline.hpp b/src/aliceVision/cmdline/cmdline.hpp index 32c7387010..006fa0773f 100644 --- a/src/aliceVision/cmdline/cmdline.hpp +++ b/src/aliceVision/cmdline/cmdline.hpp @@ -19,48 +19,47 @@ #include #include -#define ALICEVISION_COMMANDLINE_START \ -\ -try { \ -system::Timer commandLineTimer; \ - - -#define ALICEVISION_COMMANDLINE_END \ -\ - ALICEVISION_LOG_INFO("Task done in (s): " + std::to_string(commandLineTimer.elapsed())); \ - return EXIT_SUCCESS; \ -\ -} catch(std::exception& e) \ -{ \ - ALICEVISION_CERR("================================================================================"); \ - ALICEVISION_CERR("====================== Command line failed with an error ======================="); \ - ALICEVISION_CERR(e.what()); \ - ALICEVISION_CERR("================================================================================"); \ - return EXIT_FAILURE; \ -} catch(...) \ -{ \ - ALICEVISION_CERR("================================================================================"); \ - ALICEVISION_CERR("============== Command line failed with an unrecognized exception =============="); \ - ALICEVISION_CERR("================================================================================"); \ - return EXIT_FAILURE; \ -} - +#define ALICEVISION_COMMANDLINE_START \ + \ + try \ + { \ + system::Timer commandLineTimer; + +#define ALICEVISION_COMMANDLINE_END \ + \ + ALICEVISION_LOG_INFO("Task done in (s): " + std::to_string(commandLineTimer.elapsed())); \ + return EXIT_SUCCESS; \ + } \ + catch (std::exception & e) \ + { \ + ALICEVISION_CERR("================================================================================"); \ + ALICEVISION_CERR("====================== Command line failed with an error ======================="); \ + ALICEVISION_CERR(e.what()); \ + ALICEVISION_CERR("================================================================================"); \ + return EXIT_FAILURE; \ + } \ + catch (...) \ + { \ + ALICEVISION_CERR("================================================================================"); \ + ALICEVISION_CERR("============== Command line failed with an unrecognized exception =============="); \ + ALICEVISION_CERR("================================================================================"); \ + return EXIT_FAILURE; \ + } namespace aliceVision { -template +template std::function optInRange(T min, T max, const char* opt_name) { return [=](T v) { - if(v < min || v > max) + if (v < min || v > max) { throw boost::program_options::validation_error( - boost::program_options::validation_error::invalid_option_value, opt_name, - std::to_string(v)); + boost::program_options::validation_error::invalid_option_value, opt_name, std::to_string(v)); } }; }; -} +} // namespace aliceVision namespace boost { @@ -69,10 +68,10 @@ inline std::ostream& operator<<(std::ostream& os, const boost::any& value) bool is_char; try { - boost::any_cast(value); + boost::any_cast(value); is_char = true; } - catch(const boost::bad_any_cast &) + catch (const boost::bad_any_cast&) { is_char = false; } @@ -82,36 +81,36 @@ inline std::ostream& operator<<(std::ostream& os, const boost::any& value) boost::any_cast(value); is_str = true; } - catch(const boost::bad_any_cast &) + catch (const boost::bad_any_cast&) { is_str = false; } - if(value.type() == typeid(int)) + if (value.type() == typeid(int)) { os << boost::any_cast(value); } - else if(value.type() == typeid(std::size_t)) + else if (value.type() == typeid(std::size_t)) { os << boost::any_cast(value); } - else if(((boost::any)value).type() == typeid(bool)) + else if (((boost::any)value).type() == typeid(bool)) { os << boost::any_cast(value); } - else if(value.type() == typeid(float)) + else if (value.type() == typeid(float)) { os << boost::any_cast(value); } - else if(value.type() == typeid(double)) + else if (value.type() == typeid(double)) { os << boost::any_cast(value); } - else if(is_char) + else if (is_char) { - os << "\"" << boost::any_cast(value) << "\""; + os << "\"" << boost::any_cast(value) << "\""; } - else if(is_str) + else if (is_str) { os << "\"" << boost::any_cast(value) << "\""; } @@ -122,15 +121,15 @@ inline std::ostream& operator<<(std::ostream& os, const boost::any& value) { std::vector vect = boost::any_cast>(value); os << " = ["; - if(!vect.empty()) + if (!vect.empty()) { os << vect[0]; - for(int i = 1; i < vect.size(); ++i) + for (int i = 1; i < vect.size(); ++i) os << ", " << vect[i]; } os << "]"; } - catch(const boost::bad_any_cast &) + catch (const boost::bad_any_cast&) { os << " Unknown Type \"" << value.type().name() << "\""; } @@ -142,14 +141,14 @@ namespace program_options { inline std::ostream& operator<<(std::ostream& os, const variables_map& vm) { - for(const auto& v: vm) + for (const auto& v : vm) { const std::string& optionName = v.first; const boost::program_options::variable_value& var = v.second; os << " * " << optionName << " = " << var.value(); - if(var.value().empty()) + if (var.value().empty()) { os << " (empty)"; } @@ -162,34 +161,27 @@ inline std::ostream& operator<<(std::ostream& os, const variables_map& vm) return os; } -} -} +} // namespace program_options +} // namespace boost namespace aliceVision { class CmdLine { -public: - CmdLine(const std::string& name) : - _allParams(name) - { - } + public: + CmdLine(const std::string& name) + : _allParams(name) + {} - void add(const boost::program_options::options_description& options) - { - _allParams.add(options); - } + void add(const boost::program_options::options_description& options) { _allParams.add(options); } bool execute(int argc, char** argv); - HardwareContext getHardwareContext() - { - return _hContext; - } + HardwareContext getHardwareContext() { return _hContext; } -private: + private: boost::program_options::options_description _allParams; HardwareContext _hContext; }; -} +} // namespace aliceVision diff --git a/src/aliceVision/colorHarmonization/CommonDataByPair.hpp b/src/aliceVision/colorHarmonization/CommonDataByPair.hpp index fbb7013d2b..f8cb8008c6 100644 --- a/src/aliceVision/colorHarmonization/CommonDataByPair.hpp +++ b/src/aliceVision/colorHarmonization/CommonDataByPair.hpp @@ -12,62 +12,60 @@ #include - namespace aliceVision { namespace colorHarmonization { class CommonDataByPair { -public: - CommonDataByPair( const std::string & sLeftImage, - const std::string & sRightImage ): - _sLeftImage( sLeftImage ), _sRightImage( sRightImage ) - {} + public: + CommonDataByPair(const std::string& sLeftImage, const std::string& sRightImage) + : _sLeftImage(sLeftImage), + _sRightImage(sRightImage) + {} - virtual ~CommonDataByPair() {} + virtual ~CommonDataByPair() {} - /** - * Compute mask forthe two images - * - * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). - * \param[out] maskRight Mask of the right image (initialized to corresponding image size). - * - * \return True if(mask not empty). - */ - virtual bool computeMask( image::Image< unsigned char > & maskLeft, image::Image< unsigned char > & maskRight ) = 0; + /** + * Compute mask forthe two images + * + * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). + * \param[out] maskRight Mask of the right image (initialized to corresponding image size). + * + * \return True if(mask not empty). + */ + virtual bool computeMask(image::Image& maskLeft, image::Image& maskRight) = 0; - /** - * Compute Histogram for the color's masked data - * - * \param[in] mask Binary image to determine acceptable zones - * \param[in] channelIndex selected channel : 0 = red; 1 = green; 2 = blue - * \param[in] image Image with RGB or LAB type - * \param[out] histo Histogram of the left image. - * - */ - template< typename ImageType > - static void computeHisto( - utils::Histogram< double > & histo, - const image::Image< unsigned char >& mask, - std::size_t channelIndex, - const image::Image< ImageType >& image ) - { - for(int j = 0; j < mask.Height(); ++j) + /** + * Compute Histogram for the color's masked data + * + * \param[in] mask Binary image to determine acceptable zones + * \param[in] channelIndex selected channel : 0 = red; 1 = green; 2 = blue + * \param[in] image Image with RGB or LAB type + * \param[out] histo Histogram of the left image. + * + */ + template + static void computeHisto(utils::Histogram& histo, + const image::Image& mask, + std::size_t channelIndex, + const image::Image& image) { - for(int i = 0; i < mask.Width(); ++i) - { - if((int)mask(j,i) != 0) - histo.Add(image(j,i)(channelIndex)); - } + for (int j = 0; j < mask.Height(); ++j) + { + for (int i = 0; i < mask.Width(); ++i) + { + if ((int)mask(j, i) != 0) + histo.Add(image(j, i)(channelIndex)); + } + } } - } - const std::string & getLeftImage()const{ return _sLeftImage; } - const std::string & getRightImage()const{ return _sRightImage; } + const std::string& getLeftImage() const { return _sLeftImage; } + const std::string& getRightImage() const { return _sRightImage; } -protected: - // Left and Right image filenames - std::string _sLeftImage, _sRightImage; + protected: + // Left and Right image filenames + std::string _sLeftImage, _sRightImage; }; } // namespace colorHarmonization diff --git a/src/aliceVision/colorHarmonization/CommonDataByPair_fullFrame.hpp b/src/aliceVision/colorHarmonization/CommonDataByPair_fullFrame.hpp index 6aac540708..83c4714b72 100644 --- a/src/aliceVision/colorHarmonization/CommonDataByPair_fullFrame.hpp +++ b/src/aliceVision/colorHarmonization/CommonDataByPair_fullFrame.hpp @@ -12,33 +12,31 @@ namespace aliceVision { namespace colorHarmonization { -class CommonDataByPair_fullFrame : public CommonDataByPair +class CommonDataByPair_fullFrame : public CommonDataByPair { -public: - CommonDataByPair_fullFrame( const std::string & sLeftImage, - const std::string & sRightImage ): - CommonDataByPair( sLeftImage, sRightImage ) - {} - - virtual ~CommonDataByPair_fullFrame() {} - - /** - * Put masks to white, all image is considered as valid pixel selection - * - * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). - * \param[out] maskRight Mask of the right image (initialized to corresponding image size). - * - * \return True. - */ - virtual bool computeMask( image::Image< unsigned char > & maskLeft, image::Image< unsigned char > & maskRight ) - { - maskLeft.fill( image::WHITE ); - maskRight.fill( image::WHITE ); - return true; - } - -private: - + public: + CommonDataByPair_fullFrame(const std::string& sLeftImage, const std::string& sRightImage) + : CommonDataByPair(sLeftImage, sRightImage) + {} + + virtual ~CommonDataByPair_fullFrame() {} + + /** + * Put masks to white, all image is considered as valid pixel selection + * + * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). + * \param[out] maskRight Mask of the right image (initialized to corresponding image size). + * + * \return True. + */ + virtual bool computeMask(image::Image& maskLeft, image::Image& maskRight) + { + maskLeft.fill(image::WHITE); + maskRight.fill(image::WHITE); + return true; + } + + private: }; } // namespace colorHarmonization diff --git a/src/aliceVision/colorHarmonization/CommonDataByPair_matchedPoints.hpp b/src/aliceVision/colorHarmonization/CommonDataByPair_matchedPoints.hpp index 563bb0f26e..7118943b22 100644 --- a/src/aliceVision/colorHarmonization/CommonDataByPair_matchedPoints.hpp +++ b/src/aliceVision/colorHarmonization/CommonDataByPair_matchedPoints.hpp @@ -17,59 +17,57 @@ namespace aliceVision { namespace colorHarmonization { -class CommonDataByPair_matchedPoints : public CommonDataByPair +class CommonDataByPair_matchedPoints : public CommonDataByPair { -public: - CommonDataByPair_matchedPoints(const std::string& sLeftImage, - const std::string& sRightImage, - const matching::MatchesPerDescType& matchesPerDesc, - const feature::MapRegionsPerDesc& regionsL, - const feature::MapRegionsPerDesc& regionsR, - const size_t radius = 1 ) - : CommonDataByPair( sLeftImage, sRightImage ) - , _matchesPerDesc( matchesPerDesc ) - , _regionsL( regionsL ) - , _regionsR( regionsR ) - , _radius( radius ) - {} + public: + CommonDataByPair_matchedPoints(const std::string& sLeftImage, + const std::string& sRightImage, + const matching::MatchesPerDescType& matchesPerDesc, + const feature::MapRegionsPerDesc& regionsL, + const feature::MapRegionsPerDesc& regionsR, + const size_t radius = 1) + : CommonDataByPair(sLeftImage, sRightImage), + _matchesPerDesc(matchesPerDesc), + _regionsL(regionsL), + _regionsR(regionsR), + _radius(radius) + {} - virtual ~CommonDataByPair_matchedPoints() - {} + virtual ~CommonDataByPair_matchedPoints() {} - /** - * Fill mask from corresponding points (each point pictured by a disk of radius _radius) - * - * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). - * \param[out] maskRight Mask of the right image (initialized to corresponding image size). - * - * \return True if some pixel have been set to true. - */ - virtual bool computeMask( image::Image< unsigned char > & maskLeft, image::Image< unsigned char > & maskRight ) - { - maskLeft.fill(0); - maskRight.fill(0); - for(const auto& matchesPerDescIt : _matchesPerDesc) //< loop over descType + /** + * Fill mask from corresponding points (each point pictured by a disk of radius _radius) + * + * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). + * \param[out] maskRight Mask of the right image (initialized to corresponding image size). + * + * \return True if some pixel have been set to true. + */ + virtual bool computeMask(image::Image& maskLeft, image::Image& maskRight) { - const feature::EImageDescriberType descType = matchesPerDescIt.first; + maskLeft.fill(0); + maskRight.fill(0); + for (const auto& matchesPerDescIt : _matchesPerDesc) //< loop over descType + { + const feature::EImageDescriberType descType = matchesPerDescIt.first; - for(const matching::IndMatch& match : matchesPerDescIt.second) //< loop over matches - { - const feature::PointFeature& L = _regionsL.at(descType)->Features().at(match._i); - const feature::PointFeature& R = _regionsR.at(descType)->Features().at(match._j); - - image::FilledCircle( L.x(), L.y(), ( int )_radius, ( unsigned char ) 255, &maskLeft ); - image::FilledCircle( R.x(), R.y(), ( int )_radius, ( unsigned char ) 255, &maskRight ); - } + for (const matching::IndMatch& match : matchesPerDescIt.second) //< loop over matches + { + const feature::PointFeature& L = _regionsL.at(descType)->Features().at(match._i); + const feature::PointFeature& R = _regionsR.at(descType)->Features().at(match._j); + image::FilledCircle(L.x(), L.y(), (int)_radius, (unsigned char)255, &maskLeft); + image::FilledCircle(R.x(), R.y(), (int)_radius, (unsigned char)255, &maskRight); + } + } + return _matchesPerDesc.getNbAllMatches() > 0; } - return _matchesPerDesc.getNbAllMatches() > 0; - } -private: - size_t _radius; - matching::MatchesPerDescType _matchesPerDesc; - const feature::MapRegionsPerDesc& _regionsL; - const feature::MapRegionsPerDesc& _regionsR; + private: + size_t _radius; + matching::MatchesPerDescType _matchesPerDesc; + const feature::MapRegionsPerDesc& _regionsL; + const feature::MapRegionsPerDesc& _regionsR; }; } // namespace colorHarmonization diff --git a/src/aliceVision/colorHarmonization/CommonDataByPair_vldSegment.hpp b/src/aliceVision/colorHarmonization/CommonDataByPair_vldSegment.hpp index 947fe8977c..67f7194577 100644 --- a/src/aliceVision/colorHarmonization/CommonDataByPair_vldSegment.hpp +++ b/src/aliceVision/colorHarmonization/CommonDataByPair_vldSegment.hpp @@ -14,100 +14,87 @@ namespace aliceVision { namespace colorHarmonization { -class CommonDataByPair_vldSegment : public CommonDataByPair +class CommonDataByPair_vldSegment : public CommonDataByPair { public: - CommonDataByPair_vldSegment( const std::string& sLeftImage, - const std::string& sRightImage, - const matching::IndMatches& matchesPerDesc, - const std::vector& featsL, - const std::vector& featsR) - : CommonDataByPair( sLeftImage, sRightImage ) - , _matches( matchesPerDesc ) - , _featsL( featsL ) - , _featsR( featsR ) - {} - - virtual ~CommonDataByPair_vldSegment() - {} - - /** - * Put masks to white, images are conserved - * - * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). - * \param[out] maskRight Mask of the right image (initialized to corresponding image size). - * - * \return True. - */ - virtual bool computeMask( - image::Image< unsigned char > & maskLeft, - image::Image< unsigned char > & maskRight ) - { - image::Image< unsigned char > imageL, imageR; - image::readImage( _sLeftImage, imageL, image::EImageColorSpace::LINEAR); - image::readImage( _sRightImage, imageR, image::EImageColorSpace::LINEAR); - - image::Image< float > imgA ( imageL.GetMat().cast< float >() ); - image::Image< float > imgB(imageR.GetMat().cast< float >()); - - std::vector< Pair > matchesFiltered, matchesPair; - - for(const matching::IndMatch& match : _matches) + CommonDataByPair_vldSegment(const std::string& sLeftImage, + const std::string& sRightImage, + const matching::IndMatches& matchesPerDesc, + const std::vector& featsL, + const std::vector& featsR) + : CommonDataByPair(sLeftImage, sRightImage), + _matches(matchesPerDesc), + _featsL(featsL), + _featsR(featsR) + {} + + virtual ~CommonDataByPair_vldSegment() {} + + /** + * Put masks to white, images are conserved + * + * \param[out] maskLeft Mask of the left image (initialized to corresponding image size). + * \param[out] maskRight Mask of the right image (initialized to corresponding image size). + * + * \return True. + */ + virtual bool computeMask(image::Image& maskLeft, image::Image& maskRight) { - matchesPair.emplace_back(match._i, match._j); + image::Image imageL, imageR; + image::readImage(_sLeftImage, imageL, image::EImageColorSpace::LINEAR); + image::readImage(_sRightImage, imageR, image::EImageColorSpace::LINEAR); + + image::Image imgA(imageL.GetMat().cast()); + image::Image imgB(imageR.GetMat().cast()); + + std::vector matchesFiltered, matchesPair; + + for (const matching::IndMatch& match : _matches) + { + matchesPair.emplace_back(match._i, match._j); + } + + std::vector vec_score; + + // In order to illustrate the gvld(or vld)-consistant neighbors, the following two parameters has been externalized as inputs of the function + // KVLD. + // gvld-consistancy matrix, intitialized to -1, >0 consistancy value, -1=unknow, -2=false + aliceVision::Mat E = aliceVision::Mat::Ones(_matches.size(), _matches.size()) * (-1); + + // indices of match in the initial matches, if true at the end of KVLD, a match is kept. + std::vector valid(_matches.size(), true); + + size_t it_num = 0; + KvldParameters kvldparameters; // initial parameters of KVLD + // kvldparameters.K = 5; + while (it_num < 5 && + kvldparameters.inlierRate > KVLD(imgA, imgB, _featsL, _featsR, matchesPair, matchesFiltered, vec_score, E, valid, kvldparameters)) + { + kvldparameters.inlierRate /= 2; + ALICEVISION_LOG_DEBUG("low inlier rate, re-select matches with new rate=" << kvldparameters.inlierRate); + kvldparameters.K = 2; + it_num++; + } + + if (matchesPair.empty()) + { + maskLeft.fill(0); + maskRight.fill(0); + return false; + } + + // Get mask + getKVLDMask(&maskLeft, &maskRight, _featsL, _featsR, matchesPair, valid, E); + + return true; } - std::vector< double > vec_score; - - //In order to illustrate the gvld(or vld)-consistant neighbors, the following two parameters has been externalized as inputs of the function KVLD. - // gvld-consistancy matrix, intitialized to -1, >0 consistancy value, -1=unknow, -2=false - aliceVision::Mat E = aliceVision::Mat::Ones( _matches.size(), _matches.size() ) * ( -1 ); - - // indices of match in the initial matches, if true at the end of KVLD, a match is kept. - std::vector< bool > valid( _matches.size(), true ); - - size_t it_num = 0; - KvldParameters kvldparameters;//initial parameters of KVLD - //kvldparameters.K = 5; - while ( - it_num < 5 && - kvldparameters.inlierRate > - KVLD( - imgA, imgB, - _featsL, _featsR, - matchesPair, matchesFiltered, - vec_score, E, valid, kvldparameters ) ) - { - kvldparameters.inlierRate /= 2; - ALICEVISION_LOG_DEBUG("low inlier rate, re-select matches with new rate=" << kvldparameters.inlierRate); - kvldparameters.K = 2; - it_num++; - } - - if(matchesPair.empty()) - { - maskLeft.fill(0); - maskRight.fill(0); - return false; - } - - // Get mask - getKVLDMask( - &maskLeft, &maskRight, - _featsL, _featsR, - matchesPair, - valid, - E); - - return true; - } - -private: - // Left and Right features - const std::vector& _featsL; - const std::vector& _featsR; - // Left and Right corresponding index (putatives matches) - matching::IndMatches _matches; + private: + // Left and Right features + const std::vector& _featsL; + const std::vector& _featsR; + // Left and Right corresponding index (putatives matches) + matching::IndMatches _matches; }; } // namespace colorHarmonization diff --git a/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.cpp b/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.cpp index 3898fcdea6..204da4d6c1 100644 --- a/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.cpp +++ b/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.cpp @@ -10,148 +10,146 @@ namespace aliceVision { namespace lInfinity { -void Encode_histo_relation( - const size_t nImage, - const std::vector & vec_relativeHistograms, - const std::vector & vec_indexToFix, - sRMat & A, Vec & C, - std::vector & vec_sign, - std::vector & vec_costs, - std::vector< std::pair > & vec_bounds) +void Encode_histo_relation(const size_t nImage, + const std::vector& vec_relativeHistograms, + const std::vector& vec_indexToFix, + sRMat& A, + Vec& C, + std::vector& vec_sign, + std::vector& vec_costs, + std::vector>& vec_bounds) { - const size_t Nima = (size_t) nImage; - const size_t Nrelative = vec_relativeHistograms.size(); + const size_t Nima = (size_t)nImage; + const size_t Nrelative = vec_relativeHistograms.size(); -# define GVAR(i) (2*(i)) -# define OFFSETVAR(i) (2*(i)+1) -# define GAMMAVAR (2*Nima) +#define GVAR(i) (2 * (i)) +#define OFFSETVAR(i) (2 * (i) + 1) +#define GAMMAVAR (2 * Nima) - const size_t nbQuantile = 10; + const size_t nbQuantile = 10; - const size_t Nconstraint = nbQuantile * Nrelative * 2; - const size_t NVar = 2 * Nima+ 1; + const size_t Nconstraint = nbQuantile * Nrelative * 2; + const size_t NVar = 2 * Nima + 1; - A.resize(Nconstraint, NVar); + A.resize(Nconstraint, NVar); - C.resize(Nconstraint, 1); - C.fill(0.0); - vec_sign.resize(Nconstraint); + C.resize(Nconstraint, 1); + C.fill(0.0); + vec_sign.resize(Nconstraint); - // By default set free variable: - vec_bounds = std::vector< std::pair >(NVar); - fill( vec_bounds.begin(), vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); + // By default set free variable: + vec_bounds = std::vector>(NVar); + fill(vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); - // Set gain as positive values - for (size_t i = 0; i < Nima; ++i) - { - vec_bounds[GVAR(i)].first = 0.0; - } + // Set gain as positive values + for (size_t i = 0; i < Nima; ++i) + { + vec_bounds[GVAR(i)].first = 0.0; + } - //-- Fix the required image to known gain and offset value - for (std::vector::const_iterator iter = vec_indexToFix.begin(); - iter != vec_indexToFix.end(); ++iter) - { - vec_bounds[GVAR(*iter)] = std::make_pair(1.0, 1.0); // gain = 1.0 - vec_bounds[OFFSETVAR(*iter)] = std::make_pair(0.0, 0.0); // offset = 0 - } + //-- Fix the required image to known gain and offset value + for (std::vector::const_iterator iter = vec_indexToFix.begin(); iter != vec_indexToFix.end(); ++iter) + { + vec_bounds[GVAR(*iter)] = std::make_pair(1.0, 1.0); // gain = 1.0 + vec_bounds[OFFSETVAR(*iter)] = std::make_pair(0.0, 0.0); // offset = 0 + } - // Setup gamma >= 0 - vec_bounds[GAMMAVAR].first = 0.0; + // Setup gamma >= 0 + vec_bounds[GAMMAVAR].first = 0.0; - //-- Minimize gamma - vec_costs.resize(NVar); - std::fill(vec_costs.begin(), vec_costs.end(), 0.0); - vec_costs[GAMMAVAR] = 1.0; - //-- + //-- Minimize gamma + vec_costs.resize(NVar); + std::fill(vec_costs.begin(), vec_costs.end(), 0.0); + vec_costs[GAMMAVAR] = 1.0; + //-- - size_t rowPos = 0; - double incrementPourcentile = 1./(double) nbQuantile; + size_t rowPos = 0; + double incrementPourcentile = 1. / (double)nbQuantile; - for (size_t i = 0; i < Nrelative; ++i) - { - std::vector::const_iterator iter = vec_relativeHistograms.begin(); - std::advance(iter, i); + for (size_t i = 0; i < Nrelative; ++i) + { + std::vector::const_iterator iter = vec_relativeHistograms.begin(); + std::advance(iter, i); - const relativeColorHistogramEdge & edge = *iter; + const relativeColorHistogramEdge& edge = *iter; - //-- compute the two cumulated and normalized histogram + //-- compute the two cumulated and normalized histogram - const std::vector< size_t > & vec_histoI = edge.histoI; - const std::vector< size_t > & vec_histoJ = edge.histoJ; + const std::vector& vec_histoI = edge.histoI; + const std::vector& vec_histoJ = edge.histoJ; - const size_t nBuckets = vec_histoI.size(); + const size_t nBuckets = vec_histoI.size(); - // Normalize histogram - std::vector ndf_I(nBuckets), ndf_J(nBuckets); - histogram::normalizeHisto(vec_histoI, ndf_I); - histogram::normalizeHisto(vec_histoJ, ndf_J); + // Normalize histogram + std::vector ndf_I(nBuckets), ndf_J(nBuckets); + histogram::normalizeHisto(vec_histoI, ndf_I); + histogram::normalizeHisto(vec_histoJ, ndf_J); - // Compute cumulative distribution functions (cdf) - std::vector cdf_I(nBuckets), cdf_J(nBuckets); - histogram::cdf(ndf_I, cdf_I); - histogram::cdf(ndf_J, cdf_J); + // Compute cumulative distribution functions (cdf) + std::vector cdf_I(nBuckets), cdf_J(nBuckets); + histogram::cdf(ndf_I, cdf_I); + histogram::cdf(ndf_J, cdf_J); - double currentPourcentile = 5./100.; + double currentPourcentile = 5. / 100.; - //-- Compute pourcentile and their positions - std::vector vec_pourcentilePositionI, vec_pourcentilePositionJ; - vec_pourcentilePositionI.reserve(1.0/incrementPourcentile); - vec_pourcentilePositionJ.reserve(1.0/incrementPourcentile); + //-- Compute pourcentile and their positions + std::vector vec_pourcentilePositionI, vec_pourcentilePositionJ; + vec_pourcentilePositionI.reserve(1.0 / incrementPourcentile); + vec_pourcentilePositionJ.reserve(1.0 / incrementPourcentile); - std::vector::const_iterator cdf_I_IterBegin = cdf_I.begin(); - std::vector::const_iterator cdf_J_IterBegin = cdf_J.begin(); - while( currentPourcentile < 1.0) - { - std::vector::const_iterator iterFI = std::lower_bound(cdf_I.begin(), cdf_I.end(), currentPourcentile); - const size_t positionI = std::distance(cdf_I_IterBegin, iterFI); + std::vector::const_iterator cdf_I_IterBegin = cdf_I.begin(); + std::vector::const_iterator cdf_J_IterBegin = cdf_J.begin(); + while (currentPourcentile < 1.0) + { + std::vector::const_iterator iterFI = std::lower_bound(cdf_I.begin(), cdf_I.end(), currentPourcentile); + const size_t positionI = std::distance(cdf_I_IterBegin, iterFI); - std::vector::const_iterator iterFJ = std::lower_bound(cdf_J.begin(), cdf_J.end(), currentPourcentile); - const size_t positionJ = std::distance(cdf_J_IterBegin, iterFJ); + std::vector::const_iterator iterFJ = std::lower_bound(cdf_J.begin(), cdf_J.end(), currentPourcentile); + const size_t positionJ = std::distance(cdf_J_IterBegin, iterFJ); - vec_pourcentilePositionI.push_back(positionI); - vec_pourcentilePositionJ.push_back(positionJ); + vec_pourcentilePositionI.push_back(positionI); + vec_pourcentilePositionJ.push_back(positionJ); - currentPourcentile += incrementPourcentile; - } + currentPourcentile += incrementPourcentile; + } - //-- Add the constraints: - // pos * ga + offa - pos * gb - offb <= gamma - // pos * ga + offa - pos * gb - offb >= - gamma + //-- Add the constraints: + // pos * ga + offa - pos * gb - offb <= gamma + // pos * ga + offa - pos * gb - offb >= - gamma - for(size_t k = 0; k < vec_pourcentilePositionI.size(); ++k) - { - A.coeffRef(rowPos, GVAR(edge.I)) = vec_pourcentilePositionI[k]; - A.coeffRef(rowPos, OFFSETVAR(edge.I)) = 1.0; - - A.coeffRef(rowPos, GVAR(edge.J)) = - vec_pourcentilePositionJ[k]; - A.coeffRef(rowPos, OFFSETVAR(edge.J)) = - 1.0; - - // - gamma (side change) - A.coeffRef(rowPos, GAMMAVAR) = -1; - // <= gamma - vec_sign[rowPos] = linearProgramming::LPConstraints::LP_LESS_OR_EQUAL; - C(rowPos) = 0; - ++rowPos; - - A.coeffRef(rowPos, GVAR(edge.I)) = vec_pourcentilePositionI[k]; - A.coeffRef(rowPos, OFFSETVAR(edge.I)) = 1.0; - - A.coeffRef(rowPos, GVAR(edge.J)) = - vec_pourcentilePositionJ[k]; - A.coeffRef(rowPos, OFFSETVAR(edge.J)) = - 1.0; - - // + gamma (side change) - A.coeffRef(rowPos, GAMMAVAR) = 1; - // >= - gamma - vec_sign[rowPos] = linearProgramming::LPConstraints::LP_GREATER_OR_EQUAL; - C(rowPos) = 0; - ++rowPos; + for (size_t k = 0; k < vec_pourcentilePositionI.size(); ++k) + { + A.coeffRef(rowPos, GVAR(edge.I)) = vec_pourcentilePositionI[k]; + A.coeffRef(rowPos, OFFSETVAR(edge.I)) = 1.0; + + A.coeffRef(rowPos, GVAR(edge.J)) = -vec_pourcentilePositionJ[k]; + A.coeffRef(rowPos, OFFSETVAR(edge.J)) = -1.0; + + // - gamma (side change) + A.coeffRef(rowPos, GAMMAVAR) = -1; + // <= gamma + vec_sign[rowPos] = linearProgramming::LPConstraints::LP_LESS_OR_EQUAL; + C(rowPos) = 0; + ++rowPos; + + A.coeffRef(rowPos, GVAR(edge.I)) = vec_pourcentilePositionI[k]; + A.coeffRef(rowPos, OFFSETVAR(edge.I)) = 1.0; + + A.coeffRef(rowPos, GVAR(edge.J)) = -vec_pourcentilePositionJ[k]; + A.coeffRef(rowPos, OFFSETVAR(edge.J)) = -1.0; + + // + gamma (side change) + A.coeffRef(rowPos, GAMMAVAR) = 1; + // >= - gamma + vec_sign[rowPos] = linearProgramming::LPConstraints::LP_GREATER_OR_EQUAL; + C(rowPos) = 0; + ++rowPos; + } } - } #undef GVAR #undef OFFSETVAR #undef GAMMAVAR } -}; // namespace lInfinity -}; // namespace aliceVision +}; // namespace lInfinity +}; // namespace aliceVision diff --git a/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.hpp b/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.hpp index 1c6ed4ff97..76f012fbf9 100644 --- a/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.hpp +++ b/src/aliceVision/colorHarmonization/GainOffsetConstraintBuilder.hpp @@ -11,7 +11,7 @@ #include "aliceVision/linearProgramming/ISolver.hpp" #include "aliceVision/linearProgramming/OSIXSolver.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" #endif #include "aliceVision/linearProgramming/bisectionLP.hpp" @@ -20,99 +20,94 @@ namespace lInfinity { struct relativeColorHistogramEdge { - std::size_t I,J; - std::vector histoI, histoJ; - - relativeColorHistogramEdge() {} - - relativeColorHistogramEdge( - size_t i, size_t j, - const std::vector & histogramI, - const std::vector & histogramJ): - I(i), J(j), - histoI(histogramI), - histoJ(histogramJ) - { } + std::size_t I, J; + std::vector histoI, histoJ; + + relativeColorHistogramEdge() {} + + relativeColorHistogramEdge(size_t i, size_t j, const std::vector& histogramI, const std::vector& histogramJ) + : I(i), + J(j), + histoI(histogramI), + histoJ(histogramJ) + {} }; namespace histogram { // Normalize a distribution function template -inline void normalizeHisto(const std::vector & vec_df, std::vector & vec_normalized_df) +inline void normalizeHisto(const std::vector& vec_df, std::vector& vec_normalized_df) { - double totalCount = static_cast(std::accumulate(vec_df.begin(), vec_df.end(), 0)); - vec_normalized_df.resize(vec_df.size(), 0.0); - for(std::size_t i=0; i(std::accumulate(vec_df.begin(), vec_df.end(), 0)); + vec_normalized_df.resize(vec_df.size(), 0.0); + for (std::size_t i = 0; i < vec_df.size(); ++i) + vec_normalized_df[i] = vec_df[i] / totalCount; } // Compute cumulative distribution functions (cdf) template -inline void cdf(const std::vector & vec_df, std::vector & vec_cdf) +inline void cdf(const std::vector& vec_df, std::vector& vec_cdf) { - vec_cdf = vec_df; - for(size_t i=1; i & vec_relativeHistograms, - const std::vector & vec_indexToFix, - sRMat & A, Vec & C, - std::vector & vec_sign, - std::vector & vec_costs, - std::vector< std::pair > & vec_bounds); + const std::vector& vec_relativeHistograms, + const std::vector& vec_indexToFix, + sRMat& A, + Vec& C, + std::vector& vec_sign, + std::vector& vec_costs, + std::vector>& vec_bounds); struct GainOffsetConstraintBuilder { - GainOffsetConstraintBuilder( - const std::vector & vec_relativeHistograms, - const std::vector & vec_indexToFix): - _vec_relative(vec_relativeHistograms), - _vec_indexToFix(vec_indexToFix) - { - //Count the number of images - std::set countSet; - for (int i = 0; i < _vec_relative.size(); ++i) + GainOffsetConstraintBuilder(const std::vector& vec_relativeHistograms, const std::vector& vec_indexToFix) + : _vec_relative(vec_relativeHistograms), + _vec_indexToFix(vec_indexToFix) { - countSet.insert(_vec_relative[i].I); - countSet.insert(_vec_relative[i].J); + // Count the number of images + std::set countSet; + for (int i = 0; i < _vec_relative.size(); ++i) + { + countSet.insert(_vec_relative[i].I); + countSet.insert(_vec_relative[i].J); + } + _Nima = countSet.size(); } - _Nima = countSet.size(); - } - - /// Setup constraints for the translation and structure problem, - /// in the LPConstraints object. - bool Build(linearProgramming::LPConstraintsSparse & constraint) - { - Encode_histo_relation( - _Nima, - _vec_relative, - _vec_indexToFix, - constraint._constraintMat, - constraint._Cst_objective, - constraint._vec_sign, - constraint._vec_cost, - constraint._vec_bounds); - - // it's a minimization problem over the gamma variable - constraint._bminimize = true; - - //-- Setup additional information about the Linear Program constraint - constraint._nbParams = _Nima * 2 + 1; - return true; - } - // Internal data - size_t _Nima; - const std::vector< relativeColorHistogramEdge > & _vec_relative; - const std::vector & _vec_indexToFix; -}; + /// Setup constraints for the translation and structure problem, + /// in the LPConstraints object. + bool Build(linearProgramming::LPConstraintsSparse& constraint) + { + Encode_histo_relation(_Nima, + _vec_relative, + _vec_indexToFix, + constraint._constraintMat, + constraint._Cst_objective, + constraint._vec_sign, + constraint._vec_cost, + constraint._vec_bounds); + + // it's a minimization problem over the gamma variable + constraint._bminimize = true; + + //-- Setup additional information about the Linear Program constraint + constraint._nbParams = _Nima * 2 + 1; + return true; + } + // Internal data + size_t _Nima; + const std::vector& _vec_relative; + const std::vector& _vec_indexToFix; +}; -}; // namespace lInfinity -}; // namespace aliceVision +}; // namespace lInfinity +}; // namespace aliceVision diff --git a/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp b/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp index c816caf9c1..547602a3a9 100644 --- a/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp +++ b/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp @@ -11,7 +11,7 @@ #include #include -//ColorHarmonization solver +// ColorHarmonization solver #include #include #include @@ -28,180 +28,182 @@ using namespace aliceVision; using namespace aliceVision::linearProgramming; using namespace aliceVision::lInfinity; -double const pi = 4.0 *std::atan(1.0); +double const pi = 4.0 * std::atan(1.0); // simple functor for normal distribution class normal_distribution { -public: - normal_distribution(double m, double s): mu(m), sigma(s) {} - - double operator()() const // returns a single normally distributed number - { - double r1 = (std::rand() + 1.0)/(RAND_MAX + 1.0); // gives equal distribution in (0, 1] - double r2 = (std::rand() + 1.0)/(RAND_MAX + 1.0); - return mu + sigma * std::sqrt(-2*std::log(r1))*std::cos(2*pi*r2); - } -private: - const double mu, sigma; + public: + normal_distribution(double m, double s) + : mu(m), + sigma(s) + {} + + double operator()() const // returns a single normally distributed number + { + double r1 = (std::rand() + 1.0) / (RAND_MAX + 1.0); // gives equal distribution in (0, 1] + double r2 = (std::rand() + 1.0) / (RAND_MAX + 1.0); + return mu + sigma * std::sqrt(-2 * std::log(r1)) * std::cos(2 * pi * r2); + } + + private: + const double mu, sigma; }; -BOOST_AUTO_TEST_CASE(ColorHarmonisation_Simple_offset) { - - utils::Histogram< double > histo( 0, 256, 255); - for (std::size_t i=0; i < 6000; i++) - { - histo.Add(normal_distribution(127, 10)()); - } - - const size_t OFFET_VALUE = 20; - std::vector vec_reference = histo.GetHist(); - std::vector vec_shifted = vec_reference; - rotate(vec_shifted.begin(), vec_shifted.begin() + OFFET_VALUE, vec_shifted.end()); - - //-- Try to solve the color consistency between the two histograms - //-- We are looking for gain and offet parameter for each image {g;o} - //-- and the upper bound precision found by Linfinity minimization. - std::vector vec_solution(2 * 2 + 1); - - //-- Setup the problem data in the container - std::vector vec_relativeHistograms; - vec_relativeHistograms.push_back(relativeColorHistogramEdge(0,1, vec_reference, vec_shifted)); - //-- First image will be considered as reference and don't move - std::vector vec_indexToFix(1,0); +BOOST_AUTO_TEST_CASE(ColorHarmonisation_Simple_offset) +{ + utils::Histogram histo(0, 256, 255); + for (std::size_t i = 0; i < 6000; i++) + { + histo.Add(normal_distribution(127, 10)()); + } + + const size_t OFFET_VALUE = 20; + std::vector vec_reference = histo.GetHist(); + std::vector vec_shifted = vec_reference; + rotate(vec_shifted.begin(), vec_shifted.begin() + OFFET_VALUE, vec_shifted.end()); + + //-- Try to solve the color consistency between the two histograms + //-- We are looking for gain and offet parameter for each image {g;o} + //-- and the upper bound precision found by Linfinity minimization. + std::vector vec_solution(2 * 2 + 1); + + //-- Setup the problem data in the container + std::vector vec_relativeHistograms; + vec_relativeHistograms.push_back(relativeColorHistogramEdge(0, 1, vec_reference, vec_shifted)); + //-- First image will be considered as reference and don't move + std::vector vec_indexToFix(1, 0); #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) - typedef MOSEKSolver SOLVER_LP_T; + typedef MOSEKSolver SOLVER_LP_T; #else - typedef OSI_CISolverWrapper SOLVER_LP_T; + typedef OSI_CISolverWrapper SOLVER_LP_T; #endif - // Red channel - { - SOLVER_LP_T lpSolver(vec_solution.size()); - - GainOffsetConstraintBuilder cstBuilder(vec_relativeHistograms, vec_indexToFix); - LPConstraintsSparse constraint; - cstBuilder.Build(constraint); - lpSolver.setup(constraint); - lpSolver.solve(); - lpSolver.getSolution(vec_solution); - } - - ALICEVISION_LOG_DEBUG("Found solution:"); - std::copy(vec_solution.begin(), vec_solution.end(), - std::ostream_iterator(std::cout, " ")); - - double g0 = vec_solution[0]; - double o0 = vec_solution[1]; - double g1 = vec_solution[2]; - double o1 = vec_solution[3]; - double gamma = vec_solution[4]; - - BOOST_CHECK_SMALL(1.-g0, 1e-2); - BOOST_CHECK_SMALL(0.-o0, 1e-2); - BOOST_CHECK_SMALL(1.-g1, 1e-2); - BOOST_CHECK_SMALL(OFFET_VALUE-o1, 1e-2); - BOOST_CHECK_SMALL(0.-gamma, 1e-2); // Alignment must be perfect + // Red channel + { + SOLVER_LP_T lpSolver(vec_solution.size()); + + GainOffsetConstraintBuilder cstBuilder(vec_relativeHistograms, vec_indexToFix); + LPConstraintsSparse constraint; + cstBuilder.Build(constraint); + lpSolver.setup(constraint); + lpSolver.solve(); + lpSolver.getSolution(vec_solution); + } + + ALICEVISION_LOG_DEBUG("Found solution:"); + std::copy(vec_solution.begin(), vec_solution.end(), std::ostream_iterator(std::cout, " ")); + + double g0 = vec_solution[0]; + double o0 = vec_solution[1]; + double g1 = vec_solution[2]; + double o1 = vec_solution[3]; + double gamma = vec_solution[4]; + + BOOST_CHECK_SMALL(1. - g0, 1e-2); + BOOST_CHECK_SMALL(0. - o0, 1e-2); + BOOST_CHECK_SMALL(1. - g1, 1e-2); + BOOST_CHECK_SMALL(OFFET_VALUE - o1, 1e-2); + BOOST_CHECK_SMALL(0. - gamma, 1e-2); // Alignment must be perfect } -BOOST_AUTO_TEST_CASE(ColorHarmonisation_Offset_gain) { - - utils::Histogram< double > histo_ref( 0, 256, 255); - utils::Histogram< double > histo_offset_gain( 0, 256, 255); - const double GAIN = 3.0; - const double OFFSET = 160; - //const double GAIN = 2.0; - //const double OFFSET = 50; - for (std::size_t i=0; i < 10000; i++) - { - double val = normal_distribution(127, 10)(); - histo_ref.Add(val); - histo_offset_gain.Add( (val-127) * GAIN + OFFSET); - } - std::vector vec_reference = histo_ref.GetHist(); - std::vector vec_shifted = histo_offset_gain.GetHist(); - - //-- Try to solve the color consistency between the two histograms - //-- We are looking for gain and offet parameter for each image {g;o} - //-- and the upper bound precision found by Linfinity minimization. - std::vector vec_solution(3 * 2 + 1); - - //-- Setup the problem data in the container - std::vector vec_relativeHistograms; - vec_relativeHistograms.push_back(relativeColorHistogramEdge(0,1, vec_reference, vec_shifted)); - vec_relativeHistograms.push_back(relativeColorHistogramEdge(1,2, vec_shifted, vec_reference)); - vec_relativeHistograms.push_back(relativeColorHistogramEdge(0,2, vec_reference, vec_reference)); - //-- First image will be considered as reference and don't move - std::vector vec_indexToFix(1,0); +BOOST_AUTO_TEST_CASE(ColorHarmonisation_Offset_gain) +{ + utils::Histogram histo_ref(0, 256, 255); + utils::Histogram histo_offset_gain(0, 256, 255); + const double GAIN = 3.0; + const double OFFSET = 160; + // const double GAIN = 2.0; + // const double OFFSET = 50; + for (std::size_t i = 0; i < 10000; i++) + { + double val = normal_distribution(127, 10)(); + histo_ref.Add(val); + histo_offset_gain.Add((val - 127) * GAIN + OFFSET); + } + std::vector vec_reference = histo_ref.GetHist(); + std::vector vec_shifted = histo_offset_gain.GetHist(); + + //-- Try to solve the color consistency between the two histograms + //-- We are looking for gain and offet parameter for each image {g;o} + //-- and the upper bound precision found by Linfinity minimization. + std::vector vec_solution(3 * 2 + 1); + + //-- Setup the problem data in the container + std::vector vec_relativeHistograms; + vec_relativeHistograms.push_back(relativeColorHistogramEdge(0, 1, vec_reference, vec_shifted)); + vec_relativeHistograms.push_back(relativeColorHistogramEdge(1, 2, vec_shifted, vec_reference)); + vec_relativeHistograms.push_back(relativeColorHistogramEdge(0, 2, vec_reference, vec_reference)); + //-- First image will be considered as reference and don't move + std::vector vec_indexToFix(1, 0); #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) - typedef MOSEKSolver SOLVER_LP_T; + typedef MOSEKSolver SOLVER_LP_T; #else - typedef OSI_CISolverWrapper SOLVER_LP_T; + typedef OSI_CISolverWrapper SOLVER_LP_T; #endif - // Red channel - { - SOLVER_LP_T lpSolver(vec_solution.size()); - - GainOffsetConstraintBuilder cstBuilder(vec_relativeHistograms, vec_indexToFix); - LPConstraintsSparse constraint; - cstBuilder.Build(constraint); - lpSolver.setup(constraint); - lpSolver.solve(); - lpSolver.getSolution(vec_solution); - } - - ALICEVISION_LOG_DEBUG("Found solution:"); - std::copy(vec_solution.begin(), vec_solution.end(), - std::ostream_iterator(std::cout, " ")); - - double g0 = vec_solution[0]; - double o0 = vec_solution[1]; - double g1 = vec_solution[2]; - double o1 = vec_solution[3]; - double g2 = vec_solution[4]; - double o2 = vec_solution[5]; - double gamma = vec_solution[6]; - - // The minimal solution must be {0,1,1/gain, 127-offset/gain,1,0} - // gain and offset 2 must not move since it is equal to reference and link to the reference. - - BOOST_CHECK_SMALL(1.-g0, 1e-2); - BOOST_CHECK_SMALL(0.-o0, 1e-2); - BOOST_CHECK_SMALL((1./GAIN)-g1, 1e-1); - BOOST_CHECK_SMALL((127-OFFSET/GAIN)-o1, 2.); // +/- quantization error (2 gray levels) - BOOST_CHECK_SMALL(1.-g2, 1e-2); - BOOST_CHECK_SMALL(0.-o2, 1e-2); - BOOST_CHECK(gamma < 1.0); // Alignment must be below one gray level - - //-- Visual HTML export - using namespace htmlDocument; - htmlDocument::htmlDocumentStream _htmlDocStream ("Global Multiple-View Color Consistency."); - // Reference histogram - { - htmlDocument::JSXGraphWrapper jsxGraph; - jsxGraph.init("test0",600,300); - jsxGraph.addYChart(histo_ref.GetHist(), "point"); - jsxGraph.UnsuspendUpdate(); - std::vector xBin = histo_ref.GetXbinsValue(); - std::pair< std::pair, std::pair > range = autoJSXGraphViewport(xBin, histo_ref.GetHist()); - jsxGraph.setViewport(range); - jsxGraph.close(); - _htmlDocStream.pushInfo(jsxGraph.toStr()); - } - // Histogram with gain and offset change - { - htmlDocument::JSXGraphWrapper jsxGraph; - jsxGraph.init("test1",600,300); - jsxGraph.addYChart(histo_offset_gain.GetHist(), "point"); - jsxGraph.UnsuspendUpdate(); - std::vector xBin = histo_offset_gain.GetXbinsValue(); - std::pair< std::pair, std::pair > range = autoJSXGraphViewport(xBin, histo_offset_gain.GetHist()); - jsxGraph.setViewport(range); - jsxGraph.close(); - _htmlDocStream.pushInfo(jsxGraph.toStr()); - } - - std::ofstream htmlFileStream( "test.html"); - htmlFileStream << _htmlDocStream.getDoc(); + // Red channel + { + SOLVER_LP_T lpSolver(vec_solution.size()); + + GainOffsetConstraintBuilder cstBuilder(vec_relativeHistograms, vec_indexToFix); + LPConstraintsSparse constraint; + cstBuilder.Build(constraint); + lpSolver.setup(constraint); + lpSolver.solve(); + lpSolver.getSolution(vec_solution); + } + + ALICEVISION_LOG_DEBUG("Found solution:"); + std::copy(vec_solution.begin(), vec_solution.end(), std::ostream_iterator(std::cout, " ")); + + double g0 = vec_solution[0]; + double o0 = vec_solution[1]; + double g1 = vec_solution[2]; + double o1 = vec_solution[3]; + double g2 = vec_solution[4]; + double o2 = vec_solution[5]; + double gamma = vec_solution[6]; + + // The minimal solution must be {0,1,1/gain, 127-offset/gain,1,0} + // gain and offset 2 must not move since it is equal to reference and link to the reference. + + BOOST_CHECK_SMALL(1. - g0, 1e-2); + BOOST_CHECK_SMALL(0. - o0, 1e-2); + BOOST_CHECK_SMALL((1. / GAIN) - g1, 1e-1); + BOOST_CHECK_SMALL((127 - OFFSET / GAIN) - o1, 2.); // +/- quantization error (2 gray levels) + BOOST_CHECK_SMALL(1. - g2, 1e-2); + BOOST_CHECK_SMALL(0. - o2, 1e-2); + BOOST_CHECK(gamma < 1.0); // Alignment must be below one gray level + + //-- Visual HTML export + using namespace htmlDocument; + htmlDocument::htmlDocumentStream _htmlDocStream("Global Multiple-View Color Consistency."); + // Reference histogram + { + htmlDocument::JSXGraphWrapper jsxGraph; + jsxGraph.init("test0", 600, 300); + jsxGraph.addYChart(histo_ref.GetHist(), "point"); + jsxGraph.UnsuspendUpdate(); + std::vector xBin = histo_ref.GetXbinsValue(); + std::pair, std::pair> range = autoJSXGraphViewport(xBin, histo_ref.GetHist()); + jsxGraph.setViewport(range); + jsxGraph.close(); + _htmlDocStream.pushInfo(jsxGraph.toStr()); + } + // Histogram with gain and offset change + { + htmlDocument::JSXGraphWrapper jsxGraph; + jsxGraph.init("test1", 600, 300); + jsxGraph.addYChart(histo_offset_gain.GetHist(), "point"); + jsxGraph.UnsuspendUpdate(); + std::vector xBin = histo_offset_gain.GetXbinsValue(); + std::pair, std::pair> range = autoJSXGraphViewport(xBin, histo_offset_gain.GetHist()); + jsxGraph.setViewport(range); + jsxGraph.close(); + _htmlDocStream.pushInfo(jsxGraph.toStr()); + } + + std::ofstream htmlFileStream("test.html"); + htmlFileStream << _htmlDocStream.getDoc(); } diff --git a/src/aliceVision/dataio/FeedProvider.cpp b/src/aliceVision/dataio/FeedProvider.cpp index 5b0b4d3b5e..8353aa4172 100644 --- a/src/aliceVision/dataio/FeedProvider.cpp +++ b/src/aliceVision/dataio/FeedProvider.cpp @@ -9,7 +9,7 @@ #include "ImageFeed.hpp" #include "SfMDataFeed.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#include "VideoFeed.hpp" + #include "VideoFeed.hpp" #endif #include @@ -20,38 +20,36 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { FeedProvider::FeedProvider(const std::string& feedPath, const std::string& calibPath) - : _isVideo(false) - , _isLiveFeed(false) - , _isSfmData(false) + : _isVideo(false), + _isLiveFeed(false), + _isSfmData(false) { namespace bf = boost::filesystem; - if(feedPath.empty()) + if (feedPath.empty()) { throw std::invalid_argument("Empty filepath."); } - if(bf::is_regular_file(bf::path(feedPath))) + if (bf::is_regular_file(bf::path(feedPath))) { // Image or video file const std::string extension = bf::path(feedPath).extension().string(); - if(SfMDataFeed::isSupported(extension)) + if (SfMDataFeed::isSupported(extension)) { _feeder.reset(new SfMDataFeed(feedPath, calibPath)); _isSfmData = true; } - else if(ImageFeed::isSupported(extension)) + else if (ImageFeed::isSupported(extension)) { _feeder.reset(new ImageFeed(feedPath, calibPath)); } else { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) - if(VideoFeed::isSupported(extension)) + if (VideoFeed::isSupported(extension)) { // let's try it with a video _feeder.reset(new VideoFeed(feedPath, calibPath)); @@ -69,13 +67,13 @@ FeedProvider::FeedProvider(const std::string& feedPath, const std::string& calib } // parent_path() returns "/foo/bar/" when input path equals to "/foo/bar/" // if the user just gives the relative path as "bar", throws invalid argument exception. - else if(bf::is_directory(bf::path(feedPath)) || bf::is_directory(bf::path(feedPath).parent_path())) + else if (bf::is_directory(bf::path(feedPath)) || bf::is_directory(bf::path(feedPath).parent_path())) { // Folder or sequence of images _feeder.reset(new ImageFeed(feedPath, calibPath)); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) - else if(isdigit(feedPath[0])) + else if (isdigit(feedPath[0])) { // let's try it with a video const int deviceNumber = std::stoi(feedPath); @@ -90,48 +88,36 @@ FeedProvider::FeedProvider(const std::string& feedPath, const std::string& calib } } -bool FeedProvider::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool FeedProvider::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_feeder->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool FeedProvider::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool FeedProvider::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool FeedProvider::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool FeedProvider::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } std::size_t FeedProvider::nbFrames() const { - if(_isLiveFeed) + if (_isLiveFeed) return std::numeric_limits::infinity(); return _feeder->nbFrames(); } -bool FeedProvider::goToFrame(const unsigned int frame) -{ - return _feeder->goToFrame(frame); -} +bool FeedProvider::goToFrame(const unsigned int frame) { return _feeder->goToFrame(frame); } -bool FeedProvider::goToNextFrame() -{ - return _feeder->goToNextFrame(); -} +bool FeedProvider::goToNextFrame() { return _feeder->goToNextFrame(); } -bool FeedProvider::isInit() const -{ - return (_feeder->isInit()); -} +bool FeedProvider::isInit() const { return (_feeder->isInit()); } FeedProvider::~FeedProvider() {} -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/FeedProvider.hpp b/src/aliceVision/dataio/FeedProvider.hpp index c3cd888bf7..a40cdc4b10 100644 --- a/src/aliceVision/dataio/FeedProvider.hpp +++ b/src/aliceVision/dataio/FeedProvider.hpp @@ -11,14 +11,12 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class FeedProvider { -public: + public: FeedProvider(const std::string& feedPath, const std::string& calibPath = ""); /** @@ -32,8 +30,7 @@ class FeedProvider * is no intrinsics associated to \p imageRGB. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new float grayscale image from the feed. @@ -46,8 +43,7 @@ class FeedProvider * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, - bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new grayscale image from the feed. @@ -60,8 +56,7 @@ class FeedProvider * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief It returns the number of frames contained of the video. It return infinity @@ -113,12 +108,12 @@ class FeedProvider virtual ~FeedProvider(); -private: + private: std::unique_ptr _feeder; bool _isVideo; bool _isLiveFeed; bool _isSfmData; }; -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/IFeed.cpp b/src/aliceVision/dataio/IFeed.cpp index 36ecb4669f..8ca2db0a16 100644 --- a/src/aliceVision/dataio/IFeed.cpp +++ b/src/aliceVision/dataio/IFeed.cpp @@ -11,10 +11,8 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { // the structure of the file is // int #image width @@ -28,7 +26,7 @@ namespace dataio void readCalibrationFromFile(const std::string& filename, camera::Pinhole& camIntrinsics) { std::ifstream fs(filename, std::ios::in); - if(!fs.is_open()) + if (!fs.is_open()) { ALICEVISION_LOG_WARNING("Unable to open the calibration file " << filename); throw std::invalid_argument("Unable to open the calibration file " + filename); @@ -40,16 +38,15 @@ void readCalibrationFromFile(const std::string& filename, camera::Pinhole& camIn fs >> width; fs >> height; - for(size_t i = 0; i < numParam; ++i) + for (size_t i = 0; i < numParam; ++i) { fs >> params[i]; } - camIntrinsics = - camera::Pinhole(width, height, params[0], params[0], params[1], params[2], - std::make_shared(params[3], params[4], params[5])); + camIntrinsics = camera::Pinhole( + width, height, params[0], params[0], params[1], params[2], std::make_shared(params[3], params[4], params[5])); fs.close(); } -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/IFeed.hpp b/src/aliceVision/dataio/IFeed.hpp index a7d3cb55ac..0fcf869a4c 100644 --- a/src/aliceVision/dataio/IFeed.hpp +++ b/src/aliceVision/dataio/IFeed.hpp @@ -10,14 +10,12 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class IFeed { -public: + public: IFeed(){}; /** @@ -35,8 +33,7 @@ class IFeed * is no intrinsics associated to \p imageRGB. * @return True if there is a new image, false otherwise. */ - virtual bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) = 0; + virtual bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) = 0; /** * @brief Provide a new float grayscale image from the feed @@ -47,8 +44,7 @@ class IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - virtual bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) = 0; + virtual bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) = 0; /** * @brief Provide a new grayscale image from the feed @@ -59,8 +55,7 @@ class IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - virtual bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) = 0; + virtual bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) = 0; virtual std::size_t nbFrames() const = 0; @@ -79,5 +74,5 @@ class IFeed */ void readCalibrationFromFile(const std::string& filename, camera::Pinhole& camIntrinsics); -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/ImageFeed.cpp b/src/aliceVision/dataio/ImageFeed.cpp index 38125f8543..aa41565173 100644 --- a/src/aliceVision/dataio/ImageFeed.cpp +++ b/src/aliceVision/dataio/ImageFeed.cpp @@ -20,37 +20,33 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class ImageFeed::FeederImpl { -public: + public: FeederImpl() - : _isInit(false) - { - } + : _isInit(false) + {} FeederImpl(const std::string& imagePath, const std::string& calibPath); - template - bool readImage(image::Image& image, camera::Pinhole& camIntrinsics, std::string& imageName, - bool& hasIntrinsics) + template + bool readImage(image::Image& image, camera::Pinhole& camIntrinsics, std::string& imageName, bool& hasIntrinsics) { - if(!_isInit) + if (!_isInit) { ALICEVISION_LOG_WARNING("Image feed is not initialized "); return false; } - if(_images.empty()) + if (_images.empty()) return false; - if(_currentImageIndex >= _images.size()) + if (_currentImageIndex >= _images.size()) return false; - if(_withCalibration) + if (_withCalibration) { // get the calibration camIntrinsics = _camIntrinsics; @@ -76,7 +72,7 @@ class ImageFeed::FeederImpl bool isInit() const { return _isInit; } -private: + private: bool _isInit; bool _withCalibration; // It contains the images to be fed @@ -87,31 +83,31 @@ class ImageFeed::FeederImpl }; ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::string& calibPath) - : _isInit(false) - , _withCalibration(false) + : _isInit(false), + _withCalibration(false) { namespace bf = boost::filesystem; // ALICEVISION_LOG_DEBUG(imagePath); // if it is a json, calibPath is neglected - if(bf::is_regular_file(imagePath)) + if (bf::is_regular_file(imagePath)) { const std::string ext = bf::path(imagePath).extension().string(); // if it is an image file - if(image::isSupported(ext) && !image::isVideoExtension(ext)) + if (image::isSupported(ext) && !image::isVideoExtension(ext)) { _images.push_back(imagePath); _withCalibration = !calibPath.empty(); _isInit = true; } // if it is an image file - else if(ext == ".txt") + else if (ext == ".txt") { // we expect a simple txt file with a list of path to images relative to the // location of the txt file itself std::fstream fs(imagePath, std::ios::in); std::string line; // parse each line of the text file - while(getline(fs, line)) + while (getline(fs, line)) { // compose the file name as the base path of the inputPath and // the filename just read @@ -129,13 +125,13 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin throw std::invalid_argument("File or mode not yet implemented"); } } - else if(bf::is_directory(imagePath) || bf::is_directory(bf::path(imagePath).parent_path())) + else if (bf::is_directory(imagePath) || bf::is_directory(bf::path(imagePath).parent_path())) { std::string folder = imagePath; // Recover the pattern : img.@.png (for example) std::string filePattern; std::regex re; - if(!bf::is_directory(imagePath)) + if (!bf::is_directory(imagePath)) { filePattern = bf::path(imagePath).filename().string(); folder = bf::path(imagePath).parent_path().string(); @@ -154,26 +150,25 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin // in a priority queue and then fill the _image queue with the alphabetical // order from the priority queue std::priority_queue, std::greater> tmpSorter; - for(; iterator != bf::directory_iterator(); ++iterator) + for (; iterator != bf::directory_iterator(); ++iterator) { // get the extension of the current file to check whether it is an image const std::string ext = iterator->path().extension().string(); - if(image::isSupported(ext) && !image::isVideoExtension(ext)) + if (image::isSupported(ext) && !image::isVideoExtension(ext)) { const std::string filepath = iterator->path().string(); const std::string filename = iterator->path().filename().string(); // If we have a filePattern (a sequence of images), we have to match the regex. - if(filePattern.empty() || std::regex_match(filename, re)) + if (filePattern.empty() || std::regex_match(filename, re)) tmpSorter.push(filepath); } else { - ALICEVISION_LOG_WARNING("Unsupported file extension " << ext << " for " << iterator->path().string() - << "."); + ALICEVISION_LOG_WARNING("Unsupported file extension " << ext << " for " << iterator->path().string() << "."); } } // put all the retrieve files inside the queue - while(!tmpSorter.empty()) + while (!tmpSorter.empty()) { _images.push_back(tmpSorter.top()); tmpSorter.pop(); @@ -189,7 +184,7 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin // last thing: if _withCalibration is true it means that a path to a calibration file has been passed // then load the calibration - if(_withCalibration) + if (_withCalibration) { // load the calibration from calibPath readCalibrationFromFile(calibPath, _camIntrinsics); @@ -198,7 +193,7 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin std::size_t ImageFeed::FeederImpl::nbFrames() const { - if(!_isInit) + if (!_isInit) return 0; return _images.size(); @@ -206,7 +201,7 @@ std::size_t ImageFeed::FeederImpl::nbFrames() const bool ImageFeed::FeederImpl::goToFrame(const unsigned int frame) { - if(!_isInit) + if (!_isInit) { _currentImageIndex = frame; ALICEVISION_LOG_WARNING("Image feed is not initialized"); @@ -215,7 +210,7 @@ bool ImageFeed::FeederImpl::goToFrame(const unsigned int frame) _currentImageIndex = frame; // Image list mode - if(frame >= _images.size()) + if (frame >= _images.size()) { ALICEVISION_LOG_WARNING("The current frame is out of the range."); return false; @@ -228,7 +223,7 @@ bool ImageFeed::FeederImpl::goToNextFrame() { ++_currentImageIndex; ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); - if(_currentImageIndex >= _images.size()) + if (_currentImageIndex >= _images.size()) return false; return true; @@ -239,57 +234,40 @@ bool ImageFeed::FeederImpl::goToNextFrame() /*******************************************************************************/ ImageFeed::ImageFeed() - : _imageFeed(new FeederImpl()) -{ -} + : _imageFeed(new FeederImpl()) +{} ImageFeed::ImageFeed(const std::string& imagePath, const std::string& calibPath) - : _imageFeed(new FeederImpl(imagePath, calibPath)) -{ -} + : _imageFeed(new FeederImpl(imagePath, calibPath)) +{} -bool ImageFeed::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool ImageFeed::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_imageFeed->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool ImageFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool ImageFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_imageFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool ImageFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool ImageFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_imageFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -std::size_t ImageFeed::nbFrames() const -{ - return _imageFeed->nbFrames(); -} +std::size_t ImageFeed::nbFrames() const { return _imageFeed->nbFrames(); } -bool ImageFeed::goToFrame(const unsigned int frame) -{ - return _imageFeed->goToFrame(frame); -} +bool ImageFeed::goToFrame(const unsigned int frame) { return _imageFeed->goToFrame(frame); } -bool ImageFeed::goToNextFrame() -{ - return _imageFeed->goToNextFrame(); -} +bool ImageFeed::goToNextFrame() { return _imageFeed->goToNextFrame(); } -bool ImageFeed::isInit() const -{ - return (_imageFeed->isInit()); -} +bool ImageFeed::isInit() const { return (_imageFeed->isInit()); } bool ImageFeed::isSupported(const std::string& extension) { std::string ext = boost::to_lower_copy(extension); - if(ext == ".txt") + if (ext == ".txt") { return true; } @@ -301,5 +279,5 @@ bool ImageFeed::isSupported(const std::string& extension) ImageFeed::~ImageFeed() {} -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/ImageFeed.hpp b/src/aliceVision/dataio/ImageFeed.hpp index c69fe68ece..ae01277fd0 100644 --- a/src/aliceVision/dataio/ImageFeed.hpp +++ b/src/aliceVision/dataio/ImageFeed.hpp @@ -11,14 +11,12 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class ImageFeed : public IFeed { -public: + public: /** * @brief Empty constructor */ @@ -56,8 +54,7 @@ class ImageFeed : public IFeed * is no intrinsics associated to \p imageRGB. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new float grayscale image from the feed @@ -69,8 +66,7 @@ class ImageFeed : public IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, - bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new grayscale image from the feed @@ -82,8 +78,7 @@ class ImageFeed : public IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); std::size_t nbFrames() const; @@ -109,10 +104,10 @@ class ImageFeed : public IFeed */ static bool isSupported(const std::string& extension); -private: + private: class FeederImpl; std::unique_ptr _imageFeed; }; -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/SfMDataFeed.cpp b/src/aliceVision/dataio/SfMDataFeed.cpp index ec445077f9..d12f7a934d 100644 --- a/src/aliceVision/dataio/SfMDataFeed.cpp +++ b/src/aliceVision/dataio/SfMDataFeed.cpp @@ -15,32 +15,28 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class SfMDataFeed::FeederImpl { -public: + public: FeederImpl() - : _isInit(false) - { - } + : _isInit(false) + {} FeederImpl(const std::string& imagePath, const std::string& calibPath); - template - bool readImage(image::Image& image, camera::Pinhole& camIntrinsics, std::string& imageName, - bool& hasIntrinsics) + template + bool readImage(image::Image& image, camera::Pinhole& camIntrinsics, std::string& imageName, bool& hasIntrinsics) { - if(!_isInit) + if (!_isInit) { ALICEVISION_LOG_WARNING("SfMData feed is not initialized "); return false; } - if(_currentImageIndex >= _views.size()) + if (_currentImageIndex >= _views.size()) { ALICEVISION_LOG_WARNING("No more images to process"); return false; @@ -62,7 +58,7 @@ class SfMDataFeed::FeederImpl bool isInit() const { return _isInit; } -private: + private: bool _isInit; sfmData::SfMData _sfmData; @@ -71,17 +67,16 @@ class SfMDataFeed::FeederImpl }; SfMDataFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::string& calibPath) - : _isInit(false) + : _isInit(false) { - _isInit = sfmDataIO::Load(_sfmData, imagePath, - sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); + _isInit = sfmDataIO::Load(_sfmData, imagePath, sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); // Order the views according to the frame ID and the intrinsics serial number std::map> viewSequences; // Separate the views depending on their intrinsics' serial number auto& intrinsics = _sfmData.getIntrinsics(); - for(auto it = _sfmData.getViews().begin(); it != _sfmData.getViews().end(); ++it) + for (auto it = _sfmData.getViews().begin(); it != _sfmData.getViews().end(); ++it) { auto view = it->second.get(); auto serialNumber = intrinsics.at(view->getIntrinsicId())->serialNumber(); @@ -89,17 +84,18 @@ SfMDataFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::str } // Sort the views with the same intrinsics together based on their frame ID and add them to the final global vector - for(auto& view : viewSequences) + for (auto& view : viewSequences) { - std::sort(view.second.begin(), view.second.end(), - [](const sfmData::View* v1, const sfmData::View* v2) { return v1->getFrameId() < v2->getFrameId(); }); + std::sort(view.second.begin(), view.second.end(), [](const sfmData::View* v1, const sfmData::View* v2) { + return v1->getFrameId() < v2->getFrameId(); + }); _views.insert(_views.end(), view.second.begin(), view.second.end()); } } std::size_t SfMDataFeed::FeederImpl::nbFrames() const { - if(!_isInit) + if (!_isInit) return 0; return _views.size(); @@ -108,13 +104,13 @@ std::size_t SfMDataFeed::FeederImpl::nbFrames() const bool SfMDataFeed::FeederImpl::goToFrame(const unsigned int frame) { _currentImageIndex = frame; - if(!_isInit) + if (!_isInit) { ALICEVISION_LOG_WARNING("SfmData feed is not initialized"); return false; } - if(frame >= _views.size()) + if (frame >= _views.size()) { ALICEVISION_LOG_WARNING("The current frame is out of the range."); return false; @@ -127,7 +123,7 @@ bool SfMDataFeed::FeederImpl::goToNextFrame() { ++_currentImageIndex; ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); - if(_currentImageIndex >= _views.size()) + if (_currentImageIndex >= _views.size()) return false; return true; @@ -140,52 +136,35 @@ bool SfMDataFeed::FeederImpl::goToNextFrame() /*******************************************************************************/ SfMDataFeed::SfMDataFeed() - : _sfmDataFeed(new FeederImpl()) -{ -} + : _sfmDataFeed(new FeederImpl()) +{} SfMDataFeed::SfMDataFeed(const std::string& imagePath, const std::string& calibPath) - : _sfmDataFeed(new FeederImpl(imagePath, calibPath)) -{ -} + : _sfmDataFeed(new FeederImpl(imagePath, calibPath)) +{} -bool SfMDataFeed::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool SfMDataFeed::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_sfmDataFeed->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool SfMDataFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool SfMDataFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_sfmDataFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool SfMDataFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool SfMDataFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_sfmDataFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -std::size_t SfMDataFeed::nbFrames() const -{ - return _sfmDataFeed->nbFrames(); -} +std::size_t SfMDataFeed::nbFrames() const { return _sfmDataFeed->nbFrames(); } -bool SfMDataFeed::goToFrame(const unsigned int frame) -{ - return _sfmDataFeed->goToFrame(frame); -} +bool SfMDataFeed::goToFrame(const unsigned int frame) { return _sfmDataFeed->goToFrame(frame); } -bool SfMDataFeed::goToNextFrame() -{ - return _sfmDataFeed->goToNextFrame(); -} +bool SfMDataFeed::goToNextFrame() { return _sfmDataFeed->goToNextFrame(); } -bool SfMDataFeed::isInit() const -{ - return (_sfmDataFeed->isInit()); -} +bool SfMDataFeed::isInit() const { return (_sfmDataFeed->isInit()); } bool SfMDataFeed::isSupported(const std::string& extension) { @@ -195,5 +174,5 @@ bool SfMDataFeed::isSupported(const std::string& extension) SfMDataFeed::~SfMDataFeed() {} -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/SfMDataFeed.hpp b/src/aliceVision/dataio/SfMDataFeed.hpp index ae7f578112..92337ba39d 100644 --- a/src/aliceVision/dataio/SfMDataFeed.hpp +++ b/src/aliceVision/dataio/SfMDataFeed.hpp @@ -11,14 +11,12 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class SfMDataFeed : public IFeed { -public: + public: /** * @brief Empty constructor */ @@ -56,8 +54,7 @@ class SfMDataFeed : public IFeed * is no intrinsics associated to \p imageRGB. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new float grayscale image from the feed @@ -69,8 +66,7 @@ class SfMDataFeed : public IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, - bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new grayscale image from the feed @@ -82,8 +78,7 @@ class SfMDataFeed : public IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); std::size_t nbFrames() const; @@ -109,10 +104,10 @@ class SfMDataFeed : public IFeed */ static bool isSupported(const std::string& extension); -private: + private: class FeederImpl; std::unique_ptr _sfmDataFeed; }; -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/VideoFeed.cpp b/src/aliceVision/dataio/VideoFeed.cpp index 186be9f166..65b9de31fd 100644 --- a/src/aliceVision/dataio/VideoFeed.cpp +++ b/src/aliceVision/dataio/VideoFeed.cpp @@ -18,18 +18,15 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class VideoFeed::FeederImpl { -public: + public: FeederImpl() - : _isInit(false) - { - } + : _isInit(false) + {} FeederImpl(const std::string& videoPath, const std::string& calibPath); @@ -37,14 +34,11 @@ class VideoFeed::FeederImpl bool isInit() const { return _isInit; } - bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, - bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); bool goToFrame(const unsigned int frame); @@ -52,7 +46,7 @@ class VideoFeed::FeederImpl std::size_t nbFrames() const; -private: + private: bool _isInit; bool _isLive; bool _withIntrinsics; @@ -62,14 +56,14 @@ class VideoFeed::FeederImpl }; VideoFeed::FeederImpl::FeederImpl(const std::string& videoPath, const std::string& calibPath) - : _isInit(false) - , _isLive(false) - , _withIntrinsics(false) - , _videoPath(videoPath) + : _isInit(false), + _isLive(false), + _withIntrinsics(false), + _videoPath(videoPath) { // load the video _videoCapture.open(videoPath); - if(!_videoCapture.isOpened()) + if (!_videoCapture.isOpened()) { ALICEVISION_LOG_WARNING("Unable to open the video : " << videoPath); throw std::invalid_argument("Unable to open the video : " + videoPath); @@ -79,21 +73,21 @@ VideoFeed::FeederImpl::FeederImpl(const std::string& videoPath, const std::strin // load the calibration path _withIntrinsics = !calibPath.empty(); - if(_withIntrinsics) + if (_withIntrinsics) readCalibrationFromFile(calibPath, _camIntrinsics); _isInit = true; } VideoFeed::FeederImpl::FeederImpl(int videoDevice, const std::string& calibPath) - : _isInit(false) - , _isLive(true) - , _withIntrinsics(false) - , _videoPath(std::to_string(videoDevice)) + : _isInit(false), + _isLive(true), + _withIntrinsics(false), + _videoPath(std::to_string(videoDevice)) { // load the video _videoCapture.open(videoDevice); - if(!_videoCapture.isOpened()) + if (!_videoCapture.isOpened()) { ALICEVISION_LOG_WARNING("Unable to open the video : " << _videoPath); throw std::invalid_argument("Unable to open the video : " + _videoPath); @@ -103,24 +97,26 @@ VideoFeed::FeederImpl::FeederImpl(int videoDevice, const std::string& calibPath) // load the calibration path _withIntrinsics = !calibPath.empty(); - if(_withIntrinsics) + if (_withIntrinsics) readCalibrationFromFile(calibPath, _camIntrinsics); _isInit = true; } -bool VideoFeed::FeederImpl::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool VideoFeed::FeederImpl::readImage(image::Image& imageRGB, + camera::Pinhole& camIntrinsics, + std::string& mediaPath, + bool& hasIntrinsics) { cv::Mat frame; const bool grabStatus = _videoCapture.retrieve(frame); - if(!grabStatus || !frame.data) + if (!grabStatus || !frame.data) { return false; } - if(frame.channels() == 3) + if (frame.channels() == 3) { cv::Mat color; resize(frame, color, cv::Size(frame.cols, frame.rows)); @@ -129,9 +125,9 @@ bool VideoFeed::FeederImpl::readImage(image::Image& imageRGB, c imageRGB.resize(color.cols, color.rows); unsigned char* pixelPtr = (unsigned char*)color.data; - for(int i = 0; i < color.rows; i++) + for (int i = 0; i < color.rows; i++) { - for(int j = 0; j < color.cols; j++) + for (int j = 0; j < color.cols; j++) { const size_t index = i * color.cols * 3 + j * 3; imageRGB(i, j) = image::RGBColor(pixelPtr[index], pixelPtr[index + 1], pixelPtr[index + 2]); @@ -145,18 +141,17 @@ bool VideoFeed::FeederImpl::readImage(image::Image& imageRGB, c } hasIntrinsics = _withIntrinsics; - if(_withIntrinsics) + if (_withIntrinsics) camIntrinsics = _camIntrinsics; mediaPath = _videoPath; return true; } -bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { image::Image imageGrayUChar; - if(FeederImpl::readImage(imageGrayUChar, camIntrinsics, mediaPath, hasIntrinsics)) + if (FeederImpl::readImage(imageGrayUChar, camIntrinsics, mediaPath, hasIntrinsics)) { imageGray = (imageGrayUChar.GetMat().cast() / 255.f); return true; @@ -164,18 +159,20 @@ bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, camera::Pi return false; } -bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, + camera::Pinhole& camIntrinsics, + std::string& mediaPath, + bool& hasIntrinsics) { cv::Mat frame; const bool grabStatus = _videoCapture.retrieve(frame); - if(!grabStatus || !frame.data) + if (!grabStatus || !frame.data) { return false; } - if(frame.channels() == 3) + if (frame.channels() == 3) { // convert to gray cv::Mat grey; @@ -191,7 +188,7 @@ bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, ca } hasIntrinsics = _withIntrinsics; - if(_withIntrinsics) + if (_withIntrinsics) camIntrinsics = _camIntrinsics; mediaPath = _videoPath; @@ -200,7 +197,7 @@ bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, ca std::size_t VideoFeed::FeederImpl::nbFrames() const { - if(!_videoCapture.isOpened()) + if (!_videoCapture.isOpened()) { ALICEVISION_LOG_WARNING("The video file could not be opened."); return 0; @@ -210,87 +207,63 @@ std::size_t VideoFeed::FeederImpl::nbFrames() const bool VideoFeed::FeederImpl::goToFrame(const unsigned int frame) { - if(!_videoCapture.isOpened()) + if (!_videoCapture.isOpened()) { ALICEVISION_LOG_WARNING("We cannot open the video file."); return false; } - if(_isLive) + if (_isLive) return goToNextFrame(); _videoCapture.set(cv::CAP_PROP_POS_FRAMES, frame); return _videoCapture.grab(); } -bool VideoFeed::FeederImpl::goToNextFrame() -{ - return _videoCapture.grab(); -} +bool VideoFeed::FeederImpl::goToNextFrame() { return _videoCapture.grab(); } /*******************************************************************************/ /* VideoFeed */ /*******************************************************************************/ VideoFeed::VideoFeed() - : _feeder(new FeederImpl()) -{ -} + : _feeder(new FeederImpl()) +{} VideoFeed::VideoFeed(const std::string& videoPath, const std::string& calibPath) - : _feeder(new FeederImpl(videoPath, calibPath)) -{ -} + : _feeder(new FeederImpl(videoPath, calibPath)) +{} VideoFeed::VideoFeed(int videoDevice, const std::string& calibPath) - : _feeder(new FeederImpl(videoDevice, calibPath)) -{ -} + : _feeder(new FeederImpl(videoDevice, calibPath)) +{} -bool VideoFeed::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool VideoFeed::readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_feeder->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool VideoFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool VideoFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool VideoFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics) +bool VideoFeed::readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics) { return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -std::size_t VideoFeed::nbFrames() const -{ - return _feeder->nbFrames(); -} +std::size_t VideoFeed::nbFrames() const { return _feeder->nbFrames(); } -bool VideoFeed::goToFrame(const unsigned int frame) -{ - return _feeder->goToFrame(frame); -} +bool VideoFeed::goToFrame(const unsigned int frame) { return _feeder->goToFrame(frame); } -bool VideoFeed::goToNextFrame() -{ - return _feeder->goToNextFrame(); -} +bool VideoFeed::goToNextFrame() { return _feeder->goToNextFrame(); } -bool VideoFeed::isInit() const -{ - return (_feeder->isInit()); -} +bool VideoFeed::isInit() const { return (_feeder->isInit()); } -bool VideoFeed::isSupported(const std::string& extension) -{ - return image::isVideoExtension(extension); -} +bool VideoFeed::isSupported(const std::string& extension) { return image::isVideoExtension(extension); } VideoFeed::~VideoFeed() {} -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/VideoFeed.hpp b/src/aliceVision/dataio/VideoFeed.hpp index 8519f987f6..d21509c186 100644 --- a/src/aliceVision/dataio/VideoFeed.hpp +++ b/src/aliceVision/dataio/VideoFeed.hpp @@ -11,14 +11,12 @@ #include #include -namespace aliceVision -{ -namespace dataio -{ +namespace aliceVision { +namespace dataio { class VideoFeed : public IFeed { -public: + public: VideoFeed(); /** @@ -69,8 +67,7 @@ class VideoFeed : public IFeed * is no intrinsics associated to \p imageRGB. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageRGB, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new float grayscale image from the feed @@ -82,8 +79,7 @@ class VideoFeed : public IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, - bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); /** * @brief Provide a new grayscale image from the feed @@ -95,8 +91,7 @@ class VideoFeed : public IFeed * is no intrinsics associated to \p imageGray. * @return True if there is a new image, false otherwise. */ - bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, - std::string& mediaPath, bool& hasIntrinsics); + bool readImage(image::Image& imageGray, camera::Pinhole& camIntrinsics, std::string& mediaPath, bool& hasIntrinsics); std::size_t nbFrames() const; @@ -120,10 +115,10 @@ class VideoFeed : public IFeed */ static bool isSupported(const std::string& extension); -private: + private: class FeederImpl; std::unique_ptr _feeder; }; -} // namespace dataio -} // namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/BufPtr.hpp b/src/aliceVision/depthMap/BufPtr.hpp index 769428b8a9..6ead556466 100644 --- a/src/aliceVision/depthMap/BufPtr.hpp +++ b/src/aliceVision/depthMap/BufPtr.hpp @@ -8,27 +8,26 @@ // allows code sharing between NVCC and other compilers #if defined(__NVCC__) -#define CUDA_HOST_DEVICE __host__ __device__ -#define CUDA_HOST __host__ + #define CUDA_HOST_DEVICE __host__ __device__ + #define CUDA_HOST __host__ #else -#define CUDA_HOST_DEVICE -#define CUDA_HOST + #define CUDA_HOST_DEVICE + #define CUDA_HOST #endif namespace aliceVision { namespace depthMap { -template +template class BufPtr { -public: - + public: CUDA_HOST_DEVICE BufPtr(T* ptr, size_t pitch) - : _ptr( (unsigned char*)ptr ) - , _pitch( pitch ) + : _ptr((unsigned char*)ptr), + _pitch(pitch) {} - CUDA_HOST_DEVICE inline T* ptr() { return (T*)(_ptr); } + CUDA_HOST_DEVICE inline T* ptr() { return (T*)(_ptr); } CUDA_HOST_DEVICE inline T* row(size_t y) { return (T*)(_ptr + y * _pitch); } CUDA_HOST_DEVICE inline T& at(size_t x, size_t y) { return row(y)[x]; } @@ -36,7 +35,7 @@ class BufPtr CUDA_HOST_DEVICE inline const T* row(size_t y) const { return (const T*)(_ptr + y * _pitch); } CUDA_HOST_DEVICE inline const T& at(size_t x, size_t y) const { return row(y)[x]; } -private: + private: BufPtr(); BufPtr(const BufPtr&); BufPtr& operator*=(const BufPtr&); @@ -45,19 +44,17 @@ class BufPtr const size_t _pitch; }; - -template +template static inline T* get3DBufferAt_h(T* ptr, size_t spitch, size_t pitch, size_t x, size_t y, size_t z) { return ((T*)(((char*)ptr) + z * spitch + y * pitch)) + x; } -template +template static inline const T* get3DBufferAt_h(const T* ptr, size_t spitch, size_t pitch, size_t x, size_t y, size_t z) { return ((const T*)(((const char*)ptr) + z * spitch + y * pitch)) + x; } -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/CustomPatchPatternParams.cpp b/src/aliceVision/depthMap/CustomPatchPatternParams.cpp index 163a885f3e..5489e594cc 100644 --- a/src/aliceVision/depthMap/CustomPatchPatternParams.cpp +++ b/src/aliceVision/depthMap/CustomPatchPatternParams.cpp @@ -19,18 +19,18 @@ std::istream& operator>>(std::istream& is, CustomPatchPatternParams::SubpartPara std::vector splitParams; boost::split(splitParams, token, boost::algorithm::is_any_of(":")); - if(splitParams.size() != 5) + if (splitParams.size() != 5) throw std::invalid_argument("Failed to parse CustomPatchPatternParams::SubpartParams from: " + token); try { - sp.isCircle = (boost::to_lower_copy(splitParams[0]) == "circle"); - sp.radius = std::stof(splitParams[1]); - sp.nbCoordinates = std::stoi(splitParams[2]); - sp.level = std::stoi(splitParams[3]); - sp.weight = std::stof(splitParams[4]); + sp.isCircle = (boost::to_lower_copy(splitParams[0]) == "circle"); + sp.radius = std::stof(splitParams[1]); + sp.nbCoordinates = std::stoi(splitParams[2]); + sp.level = std::stoi(splitParams[3]); + sp.weight = std::stof(splitParams[4]); } - catch(const std::exception& e) + catch (const std::exception& e) { throw std::invalid_argument("Failed to parse CustomPatchPatternParams::SubpartParams from: " + token); } @@ -45,5 +45,5 @@ std::ostream& operator<<(std::ostream& os, const CustomPatchPatternParams::Subpa return os; } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/CustomPatchPatternParams.hpp b/src/aliceVision/depthMap/CustomPatchPatternParams.hpp index 93d0bb1101..b47b2026cd 100644 --- a/src/aliceVision/depthMap/CustomPatchPatternParams.hpp +++ b/src/aliceVision/depthMap/CustomPatchPatternParams.hpp @@ -31,8 +31,8 @@ struct CustomPatchPatternParams float weight; }; - std::vector subpartsParams; - bool groupSubpartsPerLevel; + std::vector subpartsParams; + bool groupSubpartsPerLevel; }; // from istream @@ -43,5 +43,5 @@ std::istream& operator>>(std::istream& is, CustomPatchPatternParams::SubpartPara // note: useful for command-line std::ostream& operator<<(std::ostream& os, const CustomPatchPatternParams::SubpartParams& sp); -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/DepthMapEstimator.cpp b/src/aliceVision/depthMap/DepthMapEstimator.cpp index aeb9b48a9f..a55e3d6b77 100644 --- a/src/aliceVision/depthMap/DepthMapEstimator.cpp +++ b/src/aliceVision/depthMap/DepthMapEstimator.cpp @@ -34,11 +34,11 @@ DepthMapEstimator::DepthMapEstimator(const mvsUtils::MultiViewParams& mp, const DepthMapParams& depthMapParams, const SgmParams& sgmParams, const RefineParams& refineParams) - : _mp(mp) - , _tileParams(tileParams) - , _depthMapParams(depthMapParams) - , _sgmParams(sgmParams) - , _refineParams(refineParams) + : _mp(mp), + _tileParams(tileParams), + _depthMapParams(depthMapParams), + _sgmParams(sgmParams), + _refineParams(refineParams) { // compute maximum downscale (scaleStep) const int maxDownscale = std::max(_sgmParams.scale * _sgmParams.stepXY, _refineParams.scale * _refineParams.stepXY); @@ -50,14 +50,12 @@ DepthMapEstimator::DepthMapEstimator(const mvsUtils::MultiViewParams& mp, logTileRoiList(_tileParams, _mp.getMaxImageWidth(), _mp.getMaxImageHeight(), maxDownscale, _tileRoiList); // log SGM downscale & stepXY - ALICEVISION_LOG_INFO("SGM parameters:" << std::endl - << "\t- scale: " << _sgmParams.scale << std::endl - << "\t- stepXY: " <<_sgmParams.stepXY); + ALICEVISION_LOG_INFO("SGM parameters:" << std::endl << "\t- scale: " << _sgmParams.scale << std::endl << "\t- stepXY: " << _sgmParams.stepXY); // log Refine downscale & stepXY ALICEVISION_LOG_INFO("Refine parameters:" << std::endl - << "\t- scale: " << _refineParams.scale << std::endl - << "\t- stepXY: " <<_refineParams.stepXY); + << "\t- scale: " << _refineParams.scale << std::endl + << "\t- stepXY: " << _refineParams.stepXY); } int DepthMapEstimator::getNbSimultaneousTiles() const @@ -67,8 +65,8 @@ int DepthMapEstimator::getNbSimultaneousTiles() const // mipmap image cost // mipmap image should not exceed (1.5 * max_width) * max_height // TODO: special case first mipmap level != 1 - const double mipmapCostMB = ((_mp.getMaxImageWidth() * 1.5) * _mp.getMaxImageHeight() * sizeof(CudaRGBA)) - / (1024.0 * 1024.0); // process downscale apply + const double mipmapCostMB = + ((_mp.getMaxImageWidth() * 1.5) * _mp.getMaxImageHeight() * sizeof(CudaRGBA)) / (1024.0 * 1024.0); // process downscale apply // cameras cost per R camera computation // Rc mipmap + Tcs mipmaps @@ -78,8 +76,7 @@ int DepthMapEstimator::getNbSimultaneousTiles() const // (Rc + Tcs) * 2 (SGM + Refine downscale) + 1 (SGM needs downscale 1) // note: special case SGM downsccale = Refine downscale not handle const int rcNbCameraParams = (_depthMapParams.useRefine) ? 2 : 1; - const int rcCamParams = (1 /* rc */ + _depthMapParams.maxTCams) * rcNbCameraParams + - ((_refineParams.scale > 1) ? 1 : 0); + const int rcCamParams = (1 /* rc */ + _depthMapParams.maxTCams) * rcNbCameraParams + ((_refineParams.scale > 1) ? 1 : 0); // single tile SGM cost double sgmTileCostMB = 0.0; @@ -135,15 +132,13 @@ int DepthMapEstimator::getNbSimultaneousTiles() const } // check that we do not need more constant camera parameters than the ones in device constant memory - if (ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS < - ((nbSimultaneousFullRc + ((nbRemainingTiles > 0) ? 1 : 0)) * rcCamParams)) + if (ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS < ((nbSimultaneousFullRc + ((nbRemainingTiles > 0) ? 1 : 0)) * rcCamParams)) { const int previousNbSimultaneousFullRc = nbSimultaneousFullRc; nbSimultaneousFullRc = static_cast(ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS / rcCamParams); ALICEVISION_LOG_INFO("DepthMapEstimator::getNbSimultaneousTiles(): limit the number of simultaneous RC due to " - << "the max constant memory for camera params from " << previousNbSimultaneousFullRc - << " to " << nbSimultaneousFullRc); + << "the max constant memory for camera params from " << previousNbSimultaneousFullRc << " to " << nbSimultaneousFullRc); nbRemainingTiles = 0; } @@ -152,22 +147,23 @@ int DepthMapEstimator::getNbSimultaneousTiles() const // log memory information ALICEVISION_LOG_INFO("Device memory:" << std::endl - << "\t- available: " << deviceMemoryMB << " MB" << std::endl - << "\t- requirement for the first tile: " << rcMinCostMB << " MB" << std::endl - << "\t- # computation buffers per tile: " << tileCostMB << " MB" << " (Sgm: " - << sgmTileCostMB << " MB" << ", Refine: " << refineTileCostMB << " MB)" << std::endl - << "\t- # input images (R + " << _depthMapParams.maxTCams << " Ts): " << rcCamsCostMB - << " MB (single mipmap image size: " << mipmapCostMB << " MB)"); - - ALICEVISION_LOG_DEBUG("Theoretical device memory cost for a tile without padding: " << tileCostUnpaddedMB - << " MB" << " (Sgm: " << sgmTileCostUnpaddedMB << " MB" << ", Refine: " - << refineTileCostUnpaddedMB << " MB)"); + << "\t- available: " << deviceMemoryMB << " MB" << std::endl + << "\t- requirement for the first tile: " << rcMinCostMB << " MB" << std::endl + << "\t- # computation buffers per tile: " << tileCostMB << " MB" + << " (Sgm: " << sgmTileCostMB << " MB" + << ", Refine: " << refineTileCostMB << " MB)" << std::endl + << "\t- # input images (R + " << _depthMapParams.maxTCams << " Ts): " << rcCamsCostMB + << " MB (single mipmap image size: " << mipmapCostMB << " MB)"); + + ALICEVISION_LOG_DEBUG("Theoretical device memory cost for a tile without padding: " << tileCostUnpaddedMB << " MB" + << " (Sgm: " << sgmTileCostUnpaddedMB << " MB" + << ", Refine: " << refineTileCostUnpaddedMB << " MB)"); ALICEVISION_LOG_INFO("Parallelization:" << std::endl - << "\t- # tiles per image: " << nbTilesPerCamera << std::endl - << "\t- # simultaneous depth maps computation: " << ((nbRemainingTiles < 1) ? - nbSimultaneousFullRc : (nbSimultaneousFullRc + 1)) << std::endl - << "\t- # simultaneous tiles computation: " << out_nbSimultaneousTiles); + << "\t- # tiles per image: " << nbTilesPerCamera << std::endl + << "\t- # simultaneous depth maps computation: " + << ((nbRemainingTiles < 1) ? nbSimultaneousFullRc : (nbSimultaneousFullRc + 1)) << std::endl + << "\t- # simultaneous tiles computation: " << out_nbSimultaneousTiles); // check at least one single tile computation if (rcCamParams > ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS || out_nbSimultaneousTiles < 1) @@ -196,7 +192,7 @@ void DepthMapEstimator::getTilesList(const std::vector& cams, std::vector& cams) // constants const bool hasRcSameDownscale = (_sgmParams.scale == _refineParams.scale); // we only need one camera params per image - const bool hasRcWithoutDownscale = _sgmParams.scale == 1 || (_depthMapParams.useRefine && _refineParams.scale == 1); // we need R camera params SGM (downscale = 1) - const int nbCameraParamsPerSgm = (1 + _depthMapParams.maxTCams) + (hasRcWithoutDownscale ? 0 : 1); // number of Sgm camera parameters per R camera - const int nbCameraParamsPerRefine = (_depthMapParams.useRefine && !hasRcSameDownscale) ? - (1 + _depthMapParams.maxTCams) : 0; // number of Refine camera parameters per R camera + const bool hasRcWithoutDownscale = + _sgmParams.scale == 1 || (_depthMapParams.useRefine && _refineParams.scale == 1); // we need R camera params SGM (downscale = 1) + const int nbCameraParamsPerSgm = + (1 + _depthMapParams.maxTCams) + (hasRcWithoutDownscale ? 0 : 1); // number of Sgm camera parameters per R camera + const int nbCameraParamsPerRefine = + (_depthMapParams.useRefine && !hasRcSameDownscale) ? (1 + _depthMapParams.maxTCams) : 0; // number of Refine camera parameters per R camera // build device cache const int nbTilesPerCamera = static_cast(_tileRoiList.size()); int nbRcPerBatch = divideRoundUp(nbStreams, nbTilesPerCamera); // number of R cameras in the same batch - if (nbRcPerBatch * (nbCameraParamsPerSgm + nbCameraParamsPerRefine) > - ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS) + if (nbRcPerBatch * (nbCameraParamsPerSgm + nbCameraParamsPerRefine) > ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS) { int previousNbRcPerBatch = nbRcPerBatch; - nbRcPerBatch = ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS / - (nbCameraParamsPerSgm + nbCameraParamsPerRefine); + nbRcPerBatch = ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS / (nbCameraParamsPerSgm + nbCameraParamsPerRefine); ALICEVISION_LOG_INFO("DepthMapEstimator::compute(): limit the number of simultaneous RC due to the max constant" << " memory for camera params from " << previousNbRcPerBatch << " to " << nbRcPerBatch); } - const int nbCamerasParamsPerBatch = nbRcPerBatch * (nbCameraParamsPerSgm + nbCameraParamsPerRefine); // number of camera parameters in the same batch - const int nbTilesPerBatch = nbRcPerBatch * nbTilesPerCamera; // number of tiles in the same batch + const int nbCamerasParamsPerBatch = + nbRcPerBatch * (nbCameraParamsPerSgm + nbCameraParamsPerRefine); // number of camera parameters in the same batch + const int nbTilesPerBatch = nbRcPerBatch * nbTilesPerCamera; // number of tiles in the same batch const int nbMipmapImagesPerBatch = nbRcPerBatch * (1 + _depthMapParams.maxTCams); // number of camera mipmap image in the same batch DeviceCache& deviceCache = DeviceCache::getInstance(); @@ -294,8 +291,7 @@ void DepthMapEstimator::compute(int cudaDeviceId, const std::vector& cams) // initialize Sgm objects for (int i = 0; i < nbStreams; ++i) - sgmPerStream.emplace_back(_mp, _tileParams, _sgmParams, sgmComputeDepthSimMap, sgmComputeNormalMap, - deviceStreamManager.getStream(i)); + sgmPerStream.emplace_back(_mp, _tileParams, _sgmParams, sgmComputeDepthSimMap, sgmComputeNormalMap, deviceStreamManager.getStream(i)); // initialize Refine objects if (_depthMapParams.useRefine) @@ -330,8 +326,7 @@ void DepthMapEstimator::compute(int cudaDeviceId, const std::vector& cams) // compute number of batches const int nbBatches = divideRoundUp(static_cast(tiles.size()), nbTilesPerBatch); const int minMipmapDownscale = std::min(_refineParams.scale, _sgmParams.scale); - const int maxMipmapDownscale = std::max(_refineParams.scale, _sgmParams.scale) - * std::pow(2, 6); // we add 6 downscale levels + const int maxMipmapDownscale = std::max(_refineParams.scale, _sgmParams.scale) * std::pow(2, 6); // we add 6 downscale levels // compute each batch of R cameras for (int b = 0; b < nbBatches; ++b) @@ -469,11 +464,11 @@ void DepthMapEstimator::compute(int cudaDeviceId, const std::vector& cams) const int batchCamIndex = c % nbRcPerBatch; if (_depthMapParams.useRefine) - writeDepthSimMapFromTileList(c, _mp, _tileParams, _tileRoiList, depthSimMapTilePerCam.at(batchCamIndex), - _refineParams.scale, _refineParams.stepXY); + writeDepthSimMapFromTileList( + c, _mp, _tileParams, _tileRoiList, depthSimMapTilePerCam.at(batchCamIndex), _refineParams.scale, _refineParams.stepXY); else - writeDepthSimMapFromTileList(c, _mp, _tileParams, _tileRoiList, depthSimMapTilePerCam.at(batchCamIndex), - _sgmParams.scale, _sgmParams.stepXY); + writeDepthSimMapFromTileList( + c, _mp, _tileParams, _tileRoiList, depthSimMapTilePerCam.at(batchCamIndex), _sgmParams.scale, _sgmParams.stepXY); if (_depthMapParams.exportTilePattern) exportDepthSimMapTilePatternObj(c, _mp, _tileRoiList, depthMinMaxTilePerCam.at(batchCamIndex)); diff --git a/src/aliceVision/depthMap/DepthMapEstimator.hpp b/src/aliceVision/depthMap/DepthMapEstimator.hpp index 9072ea6d62..33469ae334 100644 --- a/src/aliceVision/depthMap/DepthMapEstimator.hpp +++ b/src/aliceVision/depthMap/DepthMapEstimator.hpp @@ -26,8 +26,7 @@ namespace depthMap { */ class DepthMapEstimator : public IGPUJob { -public: - + public: /** * @brief Depth Map Estimator constructor. * @param[in] mp the multi-view parameters @@ -58,8 +57,7 @@ class DepthMapEstimator : public IGPUJob */ void compute(int cudaDeviceId, const std::vector& cams) override; -private: - + private: // private methods /** @@ -78,12 +76,12 @@ class DepthMapEstimator : public IGPUJob // private members - const mvsUtils::MultiViewParams& _mp; //< multi-view parameters - const mvsUtils::TileParams& _tileParams; //< tiling parameters - const DepthMapParams& _depthMapParams; //< depth map estimation parameters - const SgmParams& _sgmParams; //< parameters of Sgm process - const RefineParams& _refineParams; //< parameters of Refine process - std::vector _tileRoiList; //< depth maps region-of-interest list + const mvsUtils::MultiViewParams& _mp; //< multi-view parameters + const mvsUtils::TileParams& _tileParams; //< tiling parameters + const DepthMapParams& _depthMapParams; //< depth map estimation parameters + const SgmParams& _sgmParams; //< parameters of Sgm process + const RefineParams& _refineParams; //< parameters of Refine process + std::vector _tileRoiList; //< depth maps region-of-interest list }; } // namespace depthMap diff --git a/src/aliceVision/depthMap/DepthMapParams.hpp b/src/aliceVision/depthMap/DepthMapParams.hpp index 71b862e873..6363189059 100644 --- a/src/aliceVision/depthMap/DepthMapParams.hpp +++ b/src/aliceVision/depthMap/DepthMapParams.hpp @@ -19,20 +19,20 @@ namespace depthMap { */ struct DepthMapParams { - // user parameters + // user parameters - int maxTCams = 10; //< global T cameras maximum - bool chooseTCamsPerTile = true; //< choose T cameras per R tile or for the entire R image - bool exportTilePattern = false; //< export tile pattern obj - bool autoAdjustSmallImage = true; //< allow program to override parameters for the single tile case + int maxTCams = 10; //< global T cameras maximum + bool chooseTCamsPerTile = true; //< choose T cameras per R tile or for the entire R image + bool exportTilePattern = false; //< export tile pattern obj + bool autoAdjustSmallImage = true; //< allow program to override parameters for the single tile case - /// user custom patch pattern for similarity volume computation (both SGM & Refine) - CustomPatchPatternParams customPatchPattern; + /// user custom patch pattern for similarity volume computation (both SGM & Refine) + CustomPatchPatternParams customPatchPattern; - // constant parameters + // constant parameters - const bool useRefine = true; //< for debug purposes: enable or disable Refine process + const bool useRefine = true; //< for debug purposes: enable or disable Refine process }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/NormalMapEstimator.cpp b/src/aliceVision/depthMap/NormalMapEstimator.cpp index 52bbdfa7ed..da01e1fbfa 100644 --- a/src/aliceVision/depthMap/NormalMapEstimator.cpp +++ b/src/aliceVision/depthMap/NormalMapEstimator.cpp @@ -22,24 +22,24 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace depthMap { -NormalMapEstimator::NormalMapEstimator(const mvsUtils::MultiViewParams &mp) +NormalMapEstimator::NormalMapEstimator(const mvsUtils::MultiViewParams& mp) : _mp(mp) {} void NormalMapEstimator::compute(int cudaDeviceId, const std::vector& cams) { // set the device to use for GPU executions - // the CUDA runtime API is thread-safe, it maintains per-thread state about the current device + // the CUDA runtime API is thread-safe, it maintains per-thread state about the current device setCudaDeviceId(cudaDeviceId); DeviceCache& deviceCache = DeviceCache::getInstance(); - deviceCache.build(0, 1); // 0 mipmap image, 1 camera parameters + deviceCache.build(0, 1); // 0 mipmap image, 1 camera parameters - for(const int rc : cams) + for (const int rc : cams) { const std::string normalMapFilepath = getFileNameFromIndex(_mp, rc, mvsUtils::EFileType::normalMapFiltered); - if(!fs::exists(normalMapFilepath)) + if (!fs::exists(normalMapFilepath)) { const system::Timer timer; @@ -57,7 +57,7 @@ void NormalMapEstimator::compute(int cudaDeviceId, const std::vector& cams) mvsUtils::readMap(rc, _mp, mvsUtils::EFileType::depthMapFiltered, in_depthMap); // get input depth map width / height - const int width = in_depthMap.Width(); + const int width = in_depthMap.Width(); const int height = in_depthMap.Height(); // default tile parameters, no tiles @@ -73,8 +73,8 @@ void NormalMapEstimator::compute(int cudaDeviceId, const std::vector& cams) { CudaHostMemoryHeap in_depthSimMap_hmh(in_depthSimMap_dmp.getSize()); - for(int x = 0; x < width; ++x) - for(int y = 0; y < height; ++y) + for (int x = 0; x < width; ++x) + for (int y = 0; y < height; ++y) in_depthSimMap_hmh(size_t(x), size_t(y)) = make_float2(in_depthMap(y, x), 1.f); in_depthSimMap_dmp.copyFrom(in_depthSimMap_hmh); @@ -98,5 +98,5 @@ void NormalMapEstimator::compute(int cudaDeviceId, const std::vector& cams) DeviceCache::getInstance().clear(); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/NormalMapEstimator.hpp b/src/aliceVision/depthMap/NormalMapEstimator.hpp index 8002c8af0a..8acb7b5152 100644 --- a/src/aliceVision/depthMap/NormalMapEstimator.hpp +++ b/src/aliceVision/depthMap/NormalMapEstimator.hpp @@ -21,8 +21,7 @@ namespace depthMap { */ class NormalMapEstimator : public IGPUJob { -public: - + public: /** * @brief Normal Map Estimator constructor. * @param[in] mp the multi-view parameters @@ -45,12 +44,11 @@ class NormalMapEstimator : public IGPUJob */ void compute(int cudaDeviceId, const std::vector& cams) override; -private: - + private: // private members - const mvsUtils::MultiViewParams& _mp; //< multi-view parameters + const mvsUtils::MultiViewParams& _mp; //< multi-view parameters }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/Refine.cpp b/src/aliceVision/depthMap/Refine.cpp index 10fe15bb21..c27cb88239 100644 --- a/src/aliceVision/depthMap/Refine.cpp +++ b/src/aliceVision/depthMap/Refine.cpp @@ -20,18 +20,15 @@ namespace aliceVision { namespace depthMap { -Refine::Refine(const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, - const RefineParams& refineParams, - cudaStream_t stream) - : _mp(mp) - , _tileParams(tileParams) - , _refineParams(refineParams) - , _stream(stream) +Refine::Refine(const mvsUtils::MultiViewParams& mp, const mvsUtils::TileParams& tileParams, const RefineParams& refineParams, cudaStream_t stream) + : _mp(mp), + _tileParams(tileParams), + _refineParams(refineParams), + _stream(stream) { // get tile maximum dimensions const int downscale = _refineParams.scale * _refineParams.stepXY; - const int maxTileWidth = divideRoundUp(tileParams.bufferWidth , downscale); + const int maxTileWidth = divideRoundUp(tileParams.bufferWidth, downscale); const int maxTileHeight = divideRoundUp(tileParams.bufferHeight, downscale); // compute depth/sim map maximum dimensions @@ -43,11 +40,11 @@ Refine::Refine(const mvsUtils::MultiViewParams& mp, _optimizedDepthSimMap_dmp.allocate(depthSimMapDim); // allocate SGM upscaled normal map in device memory - if(_refineParams.useSgmNormalMap) + if (_refineParams.useSgmNormalMap) _sgmNormalMap_dmp.allocate(depthSimMapDim); // allocate normal map in device memory - if(_refineParams.exportIntermediateNormalMaps) + if (_refineParams.exportIntermediateNormalMaps) _normalMap_dmp.allocate(depthSimMapDim); // compute volume maximum dimensions @@ -58,7 +55,7 @@ Refine::Refine(const mvsUtils::MultiViewParams& mp, _volumeRefineSim_dmp.allocate(volDim); // allocate depth/sim map optimization buffers - if(_refineParams.useColorOptimization) + if (_refineParams.useColorOptimization) { _optTmpDepthMap_dmp.allocate(depthSimMapDim); _optImgVariance_dmp.allocate(depthSimMapDim); @@ -97,11 +94,14 @@ double Refine::getDeviceMemoryConsumptionUnpadded() const return (double(bytes) / (1024.0 * 1024.0)); } -void Refine::refineRc(const Tile& tile, const CudaDeviceMemoryPitched& in_sgmDepthThicknessMap_dmp, const CudaDeviceMemoryPitched& in_sgmNormalMap_dmp) +void Refine::refineRc(const Tile& tile, + const CudaDeviceMemoryPitched& in_sgmDepthThicknessMap_dmp, + const CudaDeviceMemoryPitched& in_sgmNormalMap_dmp) { const IndexT viewId = _mp.getViewId(tile.rc); - ALICEVISION_LOG_INFO(tile << "Refine depth/sim map of view id: " << viewId << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " << _mp.ncams << ")."); + ALICEVISION_LOG_INFO(tile << "Refine depth/sim map of view id: " << viewId << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " << _mp.ncams + << ")."); // compute upscaled SGM depth/pixSize map // compute upscaled SGM normal map @@ -122,27 +122,23 @@ void Refine::refineRc(const Tile& tile, const CudaDeviceMemoryPitched // - upscale SGM depth/thickness map // - filter masked pixels (alpha) // - compute pixSize from SGM thickness - cuda_computeSgmUpscaledDepthPixSizeMap(_sgmDepthPixSizeMap_dmp, - in_sgmDepthThicknessMap_dmp, - rcDeviceCameraParamsId, - rcDeviceMipmapImage, - _refineParams, - downscaledRoi, - _stream); + cuda_computeSgmUpscaledDepthPixSizeMap( + _sgmDepthPixSizeMap_dmp, in_sgmDepthThicknessMap_dmp, rcDeviceCameraParamsId, rcDeviceMipmapImage, _refineParams, downscaledRoi, _stream); // export intermediate depth/pixSize map (if requested by user) - if(_refineParams.exportIntermediateDepthSimMaps) - writeDepthPixSizeMap(tile.rc, _mp, _tileParams, tile.roi, _sgmDepthPixSizeMap_dmp, _refineParams.scale, _refineParams.stepXY, "sgmUpscaled"); + if (_refineParams.exportIntermediateDepthSimMaps) + writeDepthPixSizeMap( + tile.rc, _mp, _tileParams, tile.roi, _sgmDepthPixSizeMap_dmp, _refineParams.scale, _refineParams.stepXY, "sgmUpscaled"); // upscale SGM normal map (if needed) - if(_refineParams.useSgmNormalMap && in_sgmNormalMap_dmp.getBuffer() != nullptr) + if (_refineParams.useSgmNormalMap && in_sgmNormalMap_dmp.getBuffer() != nullptr) { cuda_normalMapUpscale(_sgmNormalMap_dmp, in_sgmNormalMap_dmp, downscaledRoi, _stream); } } // refine and fuse depth/sim map - if(_refineParams.useRefineFuse) + if (_refineParams.useRefineFuse) { // refine and fuse with volume strategy refineAndFuseDepthSimMap(tile); @@ -154,15 +150,15 @@ void Refine::refineRc(const Tile& tile, const CudaDeviceMemoryPitched } // export intermediate depth/sim map (if requested by user) - if(_refineParams.exportIntermediateDepthSimMaps) - writeDepthSimMap(tile.rc, _mp, _tileParams, tile.roi, _refinedDepthSimMap_dmp, _refineParams.scale, _refineParams.stepXY, "refinedFused"); + if (_refineParams.exportIntermediateDepthSimMaps) + writeDepthSimMap(tile.rc, _mp, _tileParams, tile.roi, _refinedDepthSimMap_dmp, _refineParams.scale, _refineParams.stepXY, "refinedFused"); // export intermediate normal map (if requested by user) - if(_refineParams.exportIntermediateNormalMaps) - computeAndWriteNormalMap(tile, _refinedDepthSimMap_dmp, "refinedFused"); + if (_refineParams.exportIntermediateNormalMaps) + computeAndWriteNormalMap(tile, _refinedDepthSimMap_dmp, "refinedFused"); // optimize depth/sim map - if(_refineParams.useColorOptimization && _refineParams.optimizationNbIterations > 0) + if (_refineParams.useColorOptimization && _refineParams.optimizationNbIterations > 0) { optimizeDepthSimMap(tile); } @@ -173,8 +169,8 @@ void Refine::refineRc(const Tile& tile, const CudaDeviceMemoryPitched } // export intermediate normal map (if requested by user) - if(_refineParams.exportIntermediateNormalMaps) - computeAndWriteNormalMap(tile, _optimizedDepthSimMap_dmp); + if (_refineParams.exportIntermediateNormalMaps) + computeAndWriteNormalMap(tile, _optimizedDepthSimMap_dmp); ALICEVISION_LOG_INFO(tile << "Refine depth/sim map done."); } @@ -204,7 +200,7 @@ void Refine::refineAndFuseDepthSimMap(const Tile& tile) // compute for each RcTc each similarity value for each depth to refine // sum the inverted / filtered similarity value, best value is the HIGHEST - for(std::size_t tci = 0; tci < tile.refineTCams.size(); ++tci) + for (std::size_t tci = 0; tci < tile.refineTCams.size(); ++tci) { const int tc = tile.refineTCams.at(tci); @@ -222,16 +218,16 @@ void Refine::refineAndFuseDepthSimMap(const Tile& tile) << "\t- tile range x: [" << downscaledRoi.x.begin << " - " << downscaledRoi.x.end << "]" << std::endl << "\t- tile range y: [" << downscaledRoi.y.begin << " - " << downscaledRoi.y.end << "]" << std::endl); - cuda_volumeRefineSimilarity(_volumeRefineSim_dmp, + cuda_volumeRefineSimilarity(_volumeRefineSim_dmp, _sgmDepthPixSizeMap_dmp, (_refineParams.useSgmNormalMap) ? &_sgmNormalMap_dmp : nullptr, rcDeviceCameraParamsId, tcDeviceCameraParamsId, rcDeviceMipmapImage, tcDeviceMipmapImage, - _refineParams, + _refineParams, depthRange, - downscaledRoi, + downscaledRoi, _stream); } @@ -239,14 +235,9 @@ void Refine::refineAndFuseDepthSimMap(const Tile& tile) exportVolumeInformation(tile, "afterRefine"); // retrieve the best depth/sim in the volume - // compute sub-pixel sample using a sliding gaussian - cuda_volumeRefineBestDepth(_refinedDepthSimMap_dmp, - _sgmDepthPixSizeMap_dmp, - _volumeRefineSim_dmp, - _refineParams, - downscaledRoi, - _stream); - + // compute sub-pixel sample using a sliding gaussian + cuda_volumeRefineBestDepth(_refinedDepthSimMap_dmp, _sgmDepthPixSizeMap_dmp, _volumeRefineSim_dmp, _refineParams, downscaledRoi, _stream); + ALICEVISION_LOG_INFO(tile << "Refine and fuse depth/sim map volume done."); } @@ -256,7 +247,7 @@ void Refine::optimizeDepthSimMap(const Tile& tile) // downscale the region of interest const ROI downscaledRoi = downscaleROI(tile.roi, _refineParams.scale * _refineParams.stepXY); - + // get R device camera from cache DeviceCache& deviceCache = DeviceCache::getInstance(); @@ -266,11 +257,11 @@ void Refine::optimizeDepthSimMap(const Tile& tile) // get R device mipmap image from cache const DeviceMipmapImage& rcDeviceMipmapImage = deviceCache.requestMipmapImage(tile.rc, _mp); - cuda_depthSimMapOptimizeGradientDescent(_optimizedDepthSimMap_dmp, // output depth/sim map optimized - _optImgVariance_dmp, // image variance buffer pre-allocate - _optTmpDepthMap_dmp, // temporary depth map buffer pre-allocate - _sgmDepthPixSizeMap_dmp, // input SGM upscaled depth/pixSize map - _refinedDepthSimMap_dmp, // input refined and fused depth/sim map + cuda_depthSimMapOptimizeGradientDescent(_optimizedDepthSimMap_dmp, // output depth/sim map optimized + _optImgVariance_dmp, // image variance buffer pre-allocate + _optTmpDepthMap_dmp, // temporary depth map buffer pre-allocate + _sgmDepthPixSizeMap_dmp, // input SGM upscaled depth/pixSize map + _refinedDepthSimMap_dmp, // input refined and fused depth/sim map rcDeviceCameraParamsId, rcDeviceMipmapImage, _refineParams, @@ -289,7 +280,8 @@ void Refine::computeAndWriteNormalMap(const Tile& tile, const CudaDeviceMemoryPi DeviceCache& deviceCache = DeviceCache::getInstance(); const int rcDeviceCameraParamsId = deviceCache.requestCameraParamsId(tile.rc, _refineParams.scale, _mp); - ALICEVISION_LOG_INFO(tile << "Refine compute normal map of view id: " << _mp.getViewId(tile.rc) << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " << _mp.ncams << ")."); + ALICEVISION_LOG_INFO(tile << "Refine compute normal map of view id: " << _mp.getViewId(tile.rc) << ", rc: " << tile.rc << " (" << (tile.rc + 1) + << " / " << _mp.ncams << ")."); cuda_depthSimMapComputeNormal(_normalMap_dmp, in_depthSimMap_dmp, rcDeviceCameraParamsId, _refineParams.stepXY, downscaledRoi, _stream); @@ -298,8 +290,7 @@ void Refine::computeAndWriteNormalMap(const Tile& tile, const CudaDeviceMemoryPi void Refine::exportVolumeInformation(const Tile& tile, const std::string& name) const { - if(!_refineParams.exportIntermediateCrossVolumes && - !_refineParams.exportIntermediateVolume9pCsv) + if (!_refineParams.exportIntermediateCrossVolumes && !_refineParams.exportIntermediateVolume9pCsv) { // nothing to do return; @@ -309,7 +300,7 @@ void Refine::exportVolumeInformation(const Tile& tile, const std::string& name) int tileBeginX = -1; int tileBeginY = -1; - if(tile.nbTiles > 1) + if (tile.nbTiles > 1) { tileBeginX = tile.roi.x.begin; tileBeginY = tile.roi.y.begin; @@ -323,7 +314,7 @@ void Refine::exportVolumeInformation(const Tile& tile, const std::string& name) CudaHostMemoryHeap depthPixSizeMapSgmUpscale_hmh(_sgmDepthPixSizeMap_dmp.getSize()); depthPixSizeMapSgmUpscale_hmh.copyFrom(_sgmDepthPixSizeMap_dmp); - if(_refineParams.exportIntermediateCrossVolumes) + if (_refineParams.exportIntermediateCrossVolumes) { ALICEVISION_LOG_INFO(tile << "Export similarity volume cross (" << name << ")."); @@ -334,18 +325,19 @@ void Refine::exportVolumeInformation(const Tile& tile, const std::string& name) ALICEVISION_LOG_INFO(tile << "Export similarity volume cross (" << name << ") done."); } - if(_refineParams.exportIntermediateTopographicCutVolumes) + if (_refineParams.exportIntermediateTopographicCutVolumes) { ALICEVISION_LOG_INFO(tile << "Export similarity volume topographic cut (" << name << ")."); - const std::string volumeCutPath = getFileNameFromIndex(_mp, tile.rc, mvsUtils::EFileType::volumeTopographicCut, "_" + name, tileBeginX, tileBeginY); + const std::string volumeCutPath = + getFileNameFromIndex(_mp, tile.rc, mvsUtils::EFileType::volumeTopographicCut, "_" + name, tileBeginX, tileBeginY); exportSimilarityVolumeTopographicCut(volumeSim_hmh, depthPixSizeMapSgmUpscale_hmh, _mp, tile.rc, _refineParams, volumeCutPath, tile.roi); ALICEVISION_LOG_INFO(tile << "Export similarity volume topographic cut (" << name << ") done."); } - if(_refineParams.exportIntermediateVolume9pCsv) + if (_refineParams.exportIntermediateVolume9pCsv) { ALICEVISION_LOG_INFO(tile << "Export similarity volume 9 points CSV (" << name << ")."); @@ -357,5 +349,5 @@ void Refine::exportVolumeInformation(const Tile& tile, const std::string& name) } } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/Refine.hpp b/src/aliceVision/depthMap/Refine.hpp index aae72efb28..4bfec9d86e 100644 --- a/src/aliceVision/depthMap/Refine.hpp +++ b/src/aliceVision/depthMap/Refine.hpp @@ -26,8 +26,7 @@ namespace depthMap { */ class Refine { -public: - + public: /** * @brief Refine constructor. * @param[in] mp the multi-view parameters @@ -35,10 +34,7 @@ class Refine * @param[in] refineParams the Refine parameters * @param[in] stream the stream for gpu execution */ - Refine(const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, - const RefineParams& refineParams, - cudaStream_t stream); + Refine(const mvsUtils::MultiViewParams& mp, const mvsUtils::TileParams& tileParams, const RefineParams& refineParams, cudaStream_t stream); // no default constructor Refine() = delete; @@ -67,10 +63,11 @@ class Refine * @param[in] in_sgmDepthThicknessMap_dmp the SGM result depth/thickness map in device memory * @param[in] in_sgmNormalMap_dmp the SGM result normal map in device memory (or empty) */ - void refineRc(const Tile& tile, const CudaDeviceMemoryPitched& in_sgmDepthThicknessMap_dmp, const CudaDeviceMemoryPitched& in_sgmNormalMap_dmp); - -private: + void refineRc(const Tile& tile, + const CudaDeviceMemoryPitched& in_sgmDepthThicknessMap_dmp, + const CudaDeviceMemoryPitched& in_sgmNormalMap_dmp); + private: // private methods /** @@ -102,9 +99,9 @@ class Refine // private members - const mvsUtils::MultiViewParams& _mp; //< Multi-view parameters - const mvsUtils::TileParams& _tileParams; //< tile workflow parameters - const RefineParams& _refineParams; //< Refine parameters + const mvsUtils::MultiViewParams& _mp; //< Multi-view parameters + const mvsUtils::TileParams& _tileParams; //< tile workflow parameters + const RefineParams& _refineParams; //< Refine parameters // private members in device memory @@ -119,5 +116,5 @@ class Refine cudaStream_t _stream; //< stream for gpu execution }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/RefineParams.hpp b/src/aliceVision/depthMap/RefineParams.hpp index 81d9b0e15d..b15bdc404b 100644 --- a/src/aliceVision/depthMap/RefineParams.hpp +++ b/src/aliceVision/depthMap/RefineParams.hpp @@ -14,36 +14,36 @@ namespace depthMap { */ struct RefineParams { - // user parameters - - int scale = 1; - int stepXY = 1; - int wsh = 3; - int halfNbDepths = 15; - int nbSubsamples = 10; - int maxTCamsPerTile = 4; - int optimizationNbIterations = 100; - double sigma = 15.0; - double gammaC = 15.5; - double gammaP = 8.0; - bool interpolateMiddleDepth = false; - bool useConsistentScale = false; - bool useCustomPatchPattern = false; - bool useRefineFuse = true; - bool useColorOptimization = true; - - // intermediate results export parameters - - bool exportIntermediateDepthSimMaps = false; - bool exportIntermediateNormalMaps = false; - bool exportIntermediateCrossVolumes = false; - bool exportIntermediateTopographicCutVolumes = false; - bool exportIntermediateVolume9pCsv = false; - - // constant parameters - - const bool useSgmNormalMap = false; // for experimentation purposes + // user parameters + + int scale = 1; + int stepXY = 1; + int wsh = 3; + int halfNbDepths = 15; + int nbSubsamples = 10; + int maxTCamsPerTile = 4; + int optimizationNbIterations = 100; + double sigma = 15.0; + double gammaC = 15.5; + double gammaP = 8.0; + bool interpolateMiddleDepth = false; + bool useConsistentScale = false; + bool useCustomPatchPattern = false; + bool useRefineFuse = true; + bool useColorOptimization = true; + + // intermediate results export parameters + + bool exportIntermediateDepthSimMaps = false; + bool exportIntermediateNormalMaps = false; + bool exportIntermediateCrossVolumes = false; + bool exportIntermediateTopographicCutVolumes = false; + bool exportIntermediateVolume9pCsv = false; + + // constant parameters + + const bool useSgmNormalMap = false; // for experimentation purposes }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/Sgm.cpp b/src/aliceVision/depthMap/Sgm.cpp index abbb54de02..81da04ceda 100644 --- a/src/aliceVision/depthMap/Sgm.cpp +++ b/src/aliceVision/depthMap/Sgm.cpp @@ -21,22 +21,22 @@ namespace aliceVision { namespace depthMap { -Sgm::Sgm(const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, +Sgm::Sgm(const mvsUtils::MultiViewParams& mp, + const mvsUtils::TileParams& tileParams, const SgmParams& sgmParams, bool computeDepthSimMap, bool computeNormalMap, cudaStream_t stream) - : _mp(mp) - , _tileParams(tileParams) - , _sgmParams(sgmParams) - , _computeDepthSimMap(computeDepthSimMap || sgmParams.exportIntermediateDepthSimMaps) - , _computeNormalMap(computeNormalMap || sgmParams.exportIntermediateNormalMaps) - , _stream(stream) + : _mp(mp), + _tileParams(tileParams), + _sgmParams(sgmParams), + _computeDepthSimMap(computeDepthSimMap || sgmParams.exportIntermediateDepthSimMaps), + _computeNormalMap(computeNormalMap || sgmParams.exportIntermediateNormalMaps), + _stream(stream) { // get tile maximum dimensions const int downscale = _sgmParams.scale * _sgmParams.stepXY; - const int maxTileWidth = divideRoundUp(tileParams.bufferWidth , downscale); + const int maxTileWidth = divideRoundUp(tileParams.bufferWidth, downscale); const int maxTileHeight = divideRoundUp(tileParams.bufferHeight, downscale); // compute map maximum dimensions @@ -54,11 +54,11 @@ Sgm::Sgm(const mvsUtils::MultiViewParams& mp, _depthThicknessMap_dmp.allocate(mapDim); // allocate depth/sim map in device memory - if(_computeDepthSimMap) + if (_computeDepthSimMap) _depthSimMap_dmp.allocate(mapDim); // allocate normal map in device memory - if(_computeNormalMap) + if (_computeNormalMap) _normalMap_dmp.allocate(mapDim); // allocate similarity volumes in device memory @@ -70,7 +70,7 @@ Sgm::Sgm(const mvsUtils::MultiViewParams& mp, } // allocate similarity volume optimization buffers - if(sgmParams.doSgmOptimizeVolume) + if (sgmParams.doSgmOptimizeVolume) { const size_t maxTileSide = std::max(maxTileWidth, maxTileHeight); @@ -118,14 +118,15 @@ void Sgm::sgmRc(const Tile& tile, const SgmDepthList& tileDepthList) { const IndexT viewId = _mp.getViewId(tile.rc); - ALICEVISION_LOG_INFO(tile << "SGM depth/thickness map of view id: " << viewId << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " << _mp.ncams << ")."); + ALICEVISION_LOG_INFO(tile << "SGM depth/thickness map of view id: " << viewId << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " + << _mp.ncams << ")."); // check SGM depth list and T cameras - if(tile.sgmTCams.empty() || tileDepthList.getDepths().empty()) + if (tile.sgmTCams.empty() || tileDepthList.getDepths().empty()) ALICEVISION_THROW_ERROR(tile << "Cannot compute Semi-Global Matching, no depths or no T cameras (viewId: " << viewId << ")."); - + // copy rc depth data in page-locked host memory - for(int i = 0; i < tileDepthList.getDepths().size(); ++i) + for (int i = 0; i < tileDepthList.getDepths().size(); ++i) _depths_hmh(i, 0) = tileDepthList.getDepths()[i]; // copy rc depth data in device memory @@ -140,7 +141,7 @@ void Sgm::sgmRc(const Tile& tile, const SgmDepthList& tileDepthList) // this is here for experimental purposes // to show how SGGC work on non optimized depthmaps // it must equals to true in normal case - if(_sgmParams.doSgmOptimizeVolume) + if (_sgmParams.doSgmOptimizeVolume) { optimizeSimilarityVolume(tile, tileDepthList); } @@ -157,13 +158,13 @@ void Sgm::sgmRc(const Tile& tile, const SgmDepthList& tileDepthList) retrieveBestDepth(tile, tileDepthList); // export intermediate depth/sim map (if requested by user) - if(_sgmParams.exportIntermediateDepthSimMaps) + if (_sgmParams.exportIntermediateDepthSimMaps) { writeDepthSimMap(tile.rc, _mp, _tileParams, tile.roi, _depthSimMap_dmp, _sgmParams.scale, _sgmParams.stepXY, "sgm"); } // compute normal map from depth/sim map if needed - if(_computeNormalMap) + if (_computeNormalMap) { // downscale the region of interest const ROI downscaledRoi = downscaleROI(tile.roi, _sgmParams.scale * _sgmParams.stepXY); @@ -172,11 +173,12 @@ void Sgm::sgmRc(const Tile& tile, const SgmDepthList& tileDepthList) DeviceCache& deviceCache = DeviceCache::getInstance(); const int rcDeviceCameraParamsId = deviceCache.requestCameraParamsId(tile.rc, _sgmParams.scale, _mp); - ALICEVISION_LOG_INFO(tile << "SGM compute normal map of view id: " << viewId << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " << _mp.ncams << ")."); + ALICEVISION_LOG_INFO(tile << "SGM compute normal map of view id: " << viewId << ", rc: " << tile.rc << " (" << (tile.rc + 1) << " / " + << _mp.ncams << ")."); cuda_depthSimMapComputeNormal(_normalMap_dmp, _depthSimMap_dmp, rcDeviceCameraParamsId, _sgmParams.stepXY, downscaledRoi, _stream); // export intermediate normal map (if requested by user) - if(_sgmParams.exportIntermediateNormalMaps) + if (_sgmParams.exportIntermediateNormalMaps) { writeNormalMap(tile.rc, _mp, _tileParams, tile.roi, _normalMap_dmp, _sgmParams.scale, _sgmParams.stepXY, "sgm"); } @@ -208,7 +210,7 @@ void Sgm::computeSimilarityVolumes(const Tile& tile, const SgmDepthList& tileDep // initialize the two similarity volumes at 255 cuda_volumeInitialize(_volumeBestSim_dmp, 255.f, _stream); cuda_volumeInitialize(_volumeSecBestSim_dmp, 255.f, _stream); - + // get device cache instance DeviceCache& deviceCache = DeviceCache::getInstance(); @@ -219,12 +221,12 @@ void Sgm::computeSimilarityVolumes(const Tile& tile, const SgmDepthList& tileDep const DeviceMipmapImage& rcDeviceMipmapImage = deviceCache.requestMipmapImage(tile.rc, _mp); // compute similarity volume per Rc Tc - for(std::size_t tci = 0; tci < tile.sgmTCams.size(); ++tci) + for (std::size_t tci = 0; tci < tile.sgmTCams.size(); ++tci) { const int tc = tile.sgmTCams.at(tci); const int firstDepth = tileDepthList.getDepthsTcLimits()[tci].x; - const int lastDepth = firstDepth + tileDepthList.getDepthsTcLimits()[tci].y; + const int lastDepth = firstDepth + tileDepthList.getDepthsTcLimits()[tci].y; const Range tcDepthRange(firstDepth, lastDepth); @@ -244,29 +246,29 @@ void Sgm::computeSimilarityVolumes(const Tile& tile, const SgmDepthList& tileDep << "\t- tile range x: [" << downscaledRoi.x.begin << " - " << downscaledRoi.x.end << "]" << std::endl << "\t- tile range y: [" << downscaledRoi.y.begin << " - " << downscaledRoi.y.end << "]" << std::endl); - cuda_volumeComputeSimilarity(_volumeBestSim_dmp, - _volumeSecBestSim_dmp, - _depths_dmp, + cuda_volumeComputeSimilarity(_volumeBestSim_dmp, + _volumeSecBestSim_dmp, + _depths_dmp, rcDeviceCameraParamsId, tcDeviceCameraParamsId, rcDeviceMipmapImage, tcDeviceMipmapImage, - _sgmParams, + _sgmParams, tcDepthRange, - downscaledRoi, + downscaledRoi, _stream); } // update second best uninitialized similarity volume values with first best similarity volume values // - allows to avoid the particular case with a single tc (second best volume has no valid similarity values) // - useful if a tc alone contributes to the calculation of a subpart of the similarity volume - if(_sgmParams.updateUninitializedSim) // should always be true, false for debug purposes + if (_sgmParams.updateUninitializedSim) // should always be true, false for debug purposes { ALICEVISION_LOG_DEBUG(tile << "SGM Update uninitialized similarity volume values from best similarity volume."); cuda_volumeUpdateUninitializedSimilarity(_volumeBestSim_dmp, _volumeSecBestSim_dmp, _stream); } - + ALICEVISION_LOG_INFO(tile << "SGM Compute similarity volume done."); } @@ -280,14 +282,14 @@ void Sgm::optimizeSimilarityVolume(const Tile& tile, const SgmDepthList& tileDep // get R device mipmap image from cache DeviceCache& deviceCache = DeviceCache::getInstance(); const DeviceMipmapImage& rcDeviceMipmapImage = deviceCache.requestMipmapImage(tile.rc, _mp); - - cuda_volumeOptimize(_volumeBestSim_dmp, // output volume (reuse best sim to put optimized similarity) - _volumeSliceAccA_dmp, // slice A accumulation buffer pre-allocate - _volumeSliceAccB_dmp, // slice B accumulation buffer pre-allocate - _volumeAxisAcc_dmp, // axis accumulation buffer pre-allocate - _volumeSecBestSim_dmp, // input volume + + cuda_volumeOptimize(_volumeBestSim_dmp, // output volume (reuse best sim to put optimized similarity) + _volumeSliceAccA_dmp, // slice A accumulation buffer pre-allocate + _volumeSliceAccB_dmp, // slice B accumulation buffer pre-allocate + _volumeAxisAcc_dmp, // axis accumulation buffer pre-allocate + _volumeSecBestSim_dmp, // input volume rcDeviceMipmapImage, - _sgmParams, + _sgmParams, tileDepthList.getDepths().size(), downscaledRoi, _stream); @@ -309,14 +311,14 @@ void Sgm::retrieveBestDepth(const Tile& tile, const SgmDepthList& tileDepthList) DeviceCache& deviceCache = DeviceCache::getInstance(); const int rcDeviceCameraParamsId = deviceCache.requestCameraParamsId(tile.rc, 1, _mp); - cuda_volumeRetrieveBestDepth(_depthThicknessMap_dmp, // output depth thickness map - _depthSimMap_dmp, // output depth/sim map (or empty) - _depths_dmp, // rc depth - _volumeBestSim_dmp, // second best sim volume optimized in best sim volume + cuda_volumeRetrieveBestDepth(_depthThicknessMap_dmp, // output depth thickness map + _depthSimMap_dmp, // output depth/sim map (or empty) + _depths_dmp, // rc depth + _volumeBestSim_dmp, // second best sim volume optimized in best sim volume rcDeviceCameraParamsId, _sgmParams, depthRange, - downscaledRoi, + downscaledRoi, _stream); ALICEVISION_LOG_INFO(tile << "SGM Retrieve best depth in volume done."); @@ -327,9 +329,7 @@ void Sgm::exportVolumeInformation(const Tile& tile, const CudaDeviceMemoryPitched& in_volume_dmp, const std::string& name) const { - if(!_sgmParams.exportIntermediateVolumes && - !_sgmParams.exportIntermediateCrossVolumes && - !_sgmParams.exportIntermediateVolume9pCsv) + if (!_sgmParams.exportIntermediateVolumes && !_sgmParams.exportIntermediateCrossVolumes && !_sgmParams.exportIntermediateVolume9pCsv) { // nothing to do return; @@ -339,7 +339,7 @@ void Sgm::exportVolumeInformation(const Tile& tile, int tileBeginX = -1; int tileBeginY = -1; - if(tile.nbTiles > 1) + if (tile.nbTiles > 1) { tileBeginX = tile.roi.x.begin; tileBeginY = tile.roi.y.begin; @@ -349,18 +349,18 @@ void Sgm::exportVolumeInformation(const Tile& tile, CudaHostMemoryHeap volumeSim_hmh(in_volume_dmp.getSize()); volumeSim_hmh.copyFrom(in_volume_dmp); - if(_sgmParams.exportIntermediateVolumes) + if (_sgmParams.exportIntermediateVolumes) { ALICEVISION_LOG_INFO(tile << "Export similarity volume (" << name << ")."); const std::string volumePath = getFileNameFromIndex(_mp, tile.rc, mvsUtils::EFileType::volume, "_" + name, tileBeginX, tileBeginY); - + exportSimilarityVolume(volumeSim_hmh, tileDepthList.getDepths(), _mp, tile.rc, _sgmParams, volumePath, tile.roi); ALICEVISION_LOG_INFO(tile << "Export similarity volume (" << name << ") done."); } - if(_sgmParams.exportIntermediateCrossVolumes) + if (_sgmParams.exportIntermediateCrossVolumes) { ALICEVISION_LOG_INFO(tile << "Export similarity volume cross (" << name << ")."); @@ -371,18 +371,19 @@ void Sgm::exportVolumeInformation(const Tile& tile, ALICEVISION_LOG_INFO(tile << "Export similarity volume cross (" << name << ") done."); } - if(_sgmParams.exportIntermediateTopographicCutVolumes) + if (_sgmParams.exportIntermediateTopographicCutVolumes) { ALICEVISION_LOG_INFO(tile << "Export similarity volume topographic cut (" << name << ")."); - const std::string volumeCutPath = getFileNameFromIndex(_mp, tile.rc, mvsUtils::EFileType::volumeTopographicCut, "_" + name, tileBeginX, tileBeginY); + const std::string volumeCutPath = + getFileNameFromIndex(_mp, tile.rc, mvsUtils::EFileType::volumeTopographicCut, "_" + name, tileBeginX, tileBeginY); exportSimilarityVolumeTopographicCut(volumeSim_hmh, tileDepthList.getDepths(), _mp, tile.rc, _sgmParams, volumeCutPath, tile.roi); ALICEVISION_LOG_INFO(tile << "Export similarity volume topographic cut (" << name << ") done."); } - if(_sgmParams.exportIntermediateVolume9pCsv) + if (_sgmParams.exportIntermediateVolume9pCsv) { ALICEVISION_LOG_INFO(tile << "Export similarity volume 9 points CSV (" << name << ")."); @@ -394,5 +395,5 @@ void Sgm::exportVolumeInformation(const Tile& tile, } } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/Sgm.hpp b/src/aliceVision/depthMap/Sgm.hpp index 223e0bd960..f4bbd83f26 100644 --- a/src/aliceVision/depthMap/Sgm.hpp +++ b/src/aliceVision/depthMap/Sgm.hpp @@ -28,8 +28,7 @@ namespace depthMap { */ class Sgm { -public: - + public: /** * @brief Sgm constructor. * @param[in] mp the multi-view parameters @@ -39,9 +38,9 @@ class Sgm * @param[in] computeNormalMap Enable final normal map computation * @param[in] stream the stream for gpu execution */ - Sgm(const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, - const SgmParams& sgmParams, + Sgm(const mvsUtils::MultiViewParams& mp, + const mvsUtils::TileParams& tileParams, + const SgmParams& sgmParams, bool computeDepthSimMap, bool computeNormalMap, cudaStream_t stream); @@ -88,8 +87,7 @@ class Sgm */ void smoothThicknessMap(const Tile& tile, const RefineParams& refineParams); -private: - + private: // private methods /** @@ -128,14 +126,13 @@ class Sgm const CudaDeviceMemoryPitched& in_volume_dmp, const std::string& name) const; + // private members - // private members - - const mvsUtils::MultiViewParams& _mp; //< Multi-view parameters - const mvsUtils::TileParams& _tileParams; //< tile workflow parameters - const SgmParams& _sgmParams; //< Semi Global Matching parameters - const bool _computeDepthSimMap; //< needs to compute a final depth/sim map - const bool _computeNormalMap; //< needs to compute a final normal map + const mvsUtils::MultiViewParams& _mp; //< Multi-view parameters + const mvsUtils::TileParams& _tileParams; //< tile workflow parameters + const SgmParams& _sgmParams; //< Semi Global Matching parameters + const bool _computeDepthSimMap; //< needs to compute a final depth/sim map + const bool _computeNormalMap; //< needs to compute a final normal map // private members in device memory @@ -152,5 +149,5 @@ class Sgm cudaStream_t _stream; //< stream for gpu execution }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/SgmDepthList.cpp b/src/aliceVision/depthMap/SgmDepthList.cpp index 34677ac59b..35a5fafa01 100644 --- a/src/aliceVision/depthMap/SgmDepthList.cpp +++ b/src/aliceVision/depthMap/SgmDepthList.cpp @@ -27,10 +27,10 @@ int indexOfNearestSorted(const std::vector& in_vector, const float value) // retrieve the first element >= value in _data auto it = std::lower_bound(in_vector.begin(), in_vector.end(), value); - if(it == in_vector.end()) + if (it == in_vector.end()) return -1; - if(it != in_vector.begin()) + if (it != in_vector.begin()) { // select the index of the closest value between it (>= value) and prevIt (< value) const auto prevIt = std::prev(it); @@ -40,9 +40,9 @@ int indexOfNearestSorted(const std::vector& in_vector, const float value) } SgmDepthList::SgmDepthList(const mvsUtils::MultiViewParams& mp, const SgmParams& sgmParams, const Tile& tile) - : _mp(mp) - , _sgmParams(sgmParams) - , _tile(tile) + : _mp(mp), + _sgmParams(sgmParams), + _tile(tile) {} void SgmDepthList::computeListRc() @@ -58,23 +58,23 @@ void SgmDepthList::computeListRc() float minObsDepth, maxObsDepth, midObsDepth; getMinMaxMidNbDepthFromSfM(minObsDepth, maxObsDepth, midObsDepth, nbObsDepths); - if(nbObsDepths < 2) + if (nbObsDepths < 2) { - ALICEVISION_LOG_INFO(_tile << "Cannot get min/max/middle depth from SfM."); - return; // nothing to do + ALICEVISION_LOG_INFO(_tile << "Cannot get min/max/middle depth from SfM."); + return; // nothing to do } // compute depth list for each T cameras std::vector> depthsPerTc(_tile.sgmTCams.size()); - for(std::size_t c = 0; c < _tile.sgmTCams.size(); ++c) - { + for (std::size_t c = 0; c < _tile.sgmTCams.size(); ++c) + { std::vector& tcDepths = depthsPerTc.at(c); // compute depths of all meaningful points on the principal ray of the R camera regarding each T cameras computeRcTcDepths(_tile.sgmTCams.at(c), (nbObsDepths < 10) ? -1 : midObsDepth, tcDepths); - if(tcDepths.size() < 10) // fallback if we don't have enough valid samples over the epipolar line + if (tcDepths.size() < 10) // fallback if we don't have enough valid samples over the epipolar line { ALICEVISION_LOG_DEBUG(_tile << "Not enough valid samples over the epipolar line. Compute depth list from R camera pixel size."); @@ -88,9 +88,9 @@ void SgmDepthList::computeListRc() float minDepthAll = std::numeric_limits::max(); float maxDepthAll = std::numeric_limits::min(); - for(const std::vector& tcDepths : depthsPerTc) + for (const std::vector& tcDepths : depthsPerTc) { - for(const float depth : tcDepths) + for (const float depth : tcDepths) { minDepthAll = std::min(minDepthAll, depth); maxDepthAll = std::max(maxDepthAll, depth); @@ -98,28 +98,28 @@ void SgmDepthList::computeListRc() } // no depths found - if(minDepthAll > maxDepthAll) + if (minDepthAll > maxDepthAll) { ALICEVISION_LOG_INFO(_tile << "No depths found."); - return; // nothing to do + return; // nothing to do } ALICEVISION_LOG_DEBUG(_tile << "Depth candidates from seeds for R camera:" << std::endl - << "\t- nb observations: " << nbObsDepths << std::endl + << "\t- nb observations: " << nbObsDepths << std::endl << "\t- all depth range: [" << minDepthAll << "-" << maxDepthAll << "]" << std::endl << "\t- sfm depth range: [" << minObsDepth << "-" << maxObsDepth << "]"); float firstDepth = minDepthAll; - float lastDepth = maxDepthAll; + float lastDepth = maxDepthAll; // if we want to use SfM seeds anf if we get enough information from these seeds, adjust min/maxDepth - if(_sgmParams.useSfmSeeds && !_mp.getInputSfMData().getLandmarks().empty() && nbObsDepths > 10) + if (_sgmParams.useSfmSeeds && !_mp.getInputSfMData().getLandmarks().empty() && nbObsDepths > 10) { - const float margin = _sgmParams.seedsRangeInflate * (maxObsDepth-minObsDepth); + const float margin = _sgmParams.seedsRangeInflate * (maxObsDepth - minObsDepth); firstDepth = std::max(0.f, minObsDepth - margin); - lastDepth = maxObsDepth + margin; + lastDepth = maxObsDepth + margin; - if(maxDepthAll < firstDepth || minDepthAll > lastDepth) + if (maxDepthAll < firstDepth || minDepthAll > lastDepth) { // no intersection between min/maxDepth and min/maxDepthSample // keep min/maxDepth value as is @@ -128,16 +128,17 @@ void SgmDepthList::computeListRc() { // min/maxDepth intersection with min/maxDepthAll firstDepth = std::max(minDepthAll, firstDepth); - lastDepth = std::min(maxDepthAll, lastDepth ); + lastDepth = std::min(maxDepthAll, lastDepth); } - ALICEVISION_LOG_DEBUG(_tile << "Final depth range (intersection: frustums / landmarks with margin): [" << firstDepth << "-" << lastDepth << "]"); + ALICEVISION_LOG_DEBUG(_tile << "Final depth range (intersection: frustums / landmarks with margin): [" << firstDepth << "-" << lastDepth + << "]"); } // build the list of "best" depths for rc, from all tc cameras depths computeRcDepthList(firstDepth, lastDepth, (_sgmParams.stepZ > 0.0f ? _sgmParams.stepZ : 1.0f), depthsPerTc); // filter out depths if computeDepths gave too many values - if(_sgmParams.maxDepths > 0 && _depths.size() > _sgmParams.maxDepths) + if (_sgmParams.maxDepths > 0 && _depths.size() > _sgmParams.maxDepths) { const float scaleFactor = float(_depths.size()) / float(_sgmParams.maxDepths); @@ -149,23 +150,21 @@ void SgmDepthList::computeListRc() computeRcDepthList(firstDepth, lastDepth, scaleFactor, depthsPerTc); // ensure depth list size is not greater than maxDepths - if(_depths.size() > _sgmParams.maxDepths) - _depths.resize(_sgmParams.maxDepths); // reduce to depth list first maxDepths elements + if (_depths.size() > _sgmParams.maxDepths) + _depths.resize(_sgmParams.maxDepths); // reduce to depth list first maxDepths elements } - ALICEVISION_LOG_DEBUG(_tile << "Final depth range for R camera:" << std::endl << "\t- nb selected depths: " << _depths.size() << std::endl << "\t- selected depth range: [" << firstDepth << "-" << lastDepth << "]"); - // update depth tc limits _depthsTcLimits.resize(_tile.sgmTCams.size()); // fill depthsTcamsLimits member variable with index range of depths to sweep - for(std::size_t c = 0; c < _tile.sgmTCams.size(); ++c) + for (std::size_t c = 0; c < _tile.sgmTCams.size(); ++c) { - if(depthsPerTc.empty()) + if (depthsPerTc.empty()) { _depthsTcLimits[c] = Pixel(-1, -1); continue; @@ -177,22 +176,22 @@ void SgmDepthList::computeListRc() int id1 = indexOfNearestSorted(_depths, d1); int id2 = indexOfNearestSorted(_depths, d2); - if(id1 == -1) + if (id1 == -1) id1 = 0; - if(id2 == -1) + if (id2 == -1) id2 = _depths.size() - 1; _depthsTcLimits[c] = Pixel(id1, id2 - id1 + 1); } - if(_sgmParams.exportDepthsTxtFiles) + if (_sgmParams.exportDepthsTxtFiles) exportTxtFiles(depthsPerTc); ALICEVISION_LOG_DEBUG(_tile << "Compute SGM depths list done."); } -void SgmDepthList::removeTcWithNoDepth(Tile& tile) +void SgmDepthList::removeTcWithNoDepth(Tile& tile) { assert(tile.rc == _tile.rc); assert(tile.sgmTCams.size() == _tile.sgmTCams.size()); @@ -200,12 +199,12 @@ void SgmDepthList::removeTcWithNoDepth(Tile& tile) std::vector out_tCams; std::vector out_depthsTcLimits; - for(size_t c = 0; c < tile.sgmTCams.size(); ++c) + for (size_t c = 0; c < tile.sgmTCams.size(); ++c) { const Pixel& tcLimits = _depthsTcLimits.at(c); const int tc = tile.sgmTCams.at(c); - if(tcLimits.x != -1 && tcLimits.y != -1) + if (tcLimits.x != -1 && tcLimits.y != -1) { out_tCams.push_back(tc); out_depthsTcLimits.push_back(tcLimits); @@ -231,13 +230,14 @@ void SgmDepthList::logRcTcDepthInformation() const << "\t - depths range: [" << _depths[0] << "-" << _depths[_depths.size() - 1] << "]" << std::endl << "\t- T cameras:" << std::endl; - for(std::size_t c = 0; c < _tile.sgmTCams.size(); ++c) + for (std::size_t c = 0; c < _tile.sgmTCams.size(); ++c) { ostr << "\t - T camera (" << (c + 1) << "/" << _tile.sgmTCams.size() << "):" << std::endl << "\t - id: " << _tile.sgmTCams.at(c) << std::endl << "\t - view id: " << _mp.getViewId(_tile.sgmTCams.at(c)) << std::endl << "\t - depth planes: " << _depthsTcLimits[c].y << std::endl - << "\t - depths range: [" << _depths[_depthsTcLimits[c].x] << "-" << _depths[_depthsTcLimits[c].x + _depthsTcLimits[c].y - 1] << "]" << std::endl + << "\t - depths range: [" << _depths[_depthsTcLimits[c].x] << "-" << _depths[_depthsTcLimits[c].x + _depthsTcLimits[c].y - 1] << "]" + << std::endl << "\t - depth indexes range: [" << _depthsTcLimits[c].x << "-" << _depthsTcLimits[c].x + _depthsTcLimits[c].y << "]" << std::endl; } @@ -269,10 +269,7 @@ void SgmDepthList::checkStartingAndStoppingDepth() const assert(_depths.size() >= stoppingDepth); } -void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, - float& out_max, - float& out_mid, - std::size_t& out_nbDepths) const +void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, float& out_max, float& out_mid, std::size_t& out_nbDepths) const { using namespace boost::accumulators; @@ -282,8 +279,9 @@ void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, const IndexT viewId = _mp.getViewId(_tile.rc); - const ROI fullsizeRoi = upscaleROI(_tile.roi, _mp.getProcessDownscale()); // landmark observations are in the full-size image coordinate system - //const ROI selectionRoi = inflateROI(fullsizeRoi, 1.4f); // we can inflate the image full-size roi to be more permissive for common landmark selection + const ROI fullsizeRoi = upscaleROI(_tile.roi, _mp.getProcessDownscale()); // landmark observations are in the full-size image coordinate system + // const ROI selectionRoi = inflateROI(fullsizeRoi, 1.4f); // we can inflate the image full-size roi to be more permissive for common landmark + // selection OrientedPoint cameraPlane; cameraPlane.p = _mp.CArr[_tile.rc]; @@ -294,7 +292,7 @@ void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, out_nbDepths = 0; // for each landmark - for(const auto& landmarkPair : _mp.getInputSfMData().getLandmarks()) + for (const auto& landmarkPair : _mp.getInputSfMData().getLandmarks()) { const sfmData::Landmark& landmark = landmarkPair.second; const Point3d point(landmark.X(0), landmark.X(1), landmark.X(2)); @@ -303,14 +301,14 @@ void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, const auto it = landmark.observations.find(viewId); // no rc observation - if(it == landmark.observations.end()) - continue; + if (it == landmark.observations.end()) + continue; // get rc 2d observation const Vec2& obs2d = it->second.x; // if we compute depth list per tile keep only observation located inside the inflated image full-size ROI - if(!_sgmParams.depthListPerTile || fullsizeRoi.contains(obs2d.x(), obs2d.y())) + if (!_sgmParams.depthListPerTile || fullsizeRoi.contains(obs2d.x(), obs2d.y())) { const float distance = static_cast(pointPlaneDistance(point, cameraPlane.p, cameraPlane.n)); accDistanceMin(distance); @@ -320,18 +318,18 @@ void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, } } - if(out_nbDepths > 0) + if (out_nbDepths > 0) { - out_min = quantile(accDistanceMin, quantile_probability = 1.0 - _sgmParams.seedsRangePercentile); - out_max = quantile(accDistanceMax, quantile_probability = _sgmParams.seedsRangePercentile); - midDepthPoint = midDepthPoint / static_cast(out_nbDepths); - out_mid = pointPlaneDistance(midDepthPoint, cameraPlane.p, cameraPlane.n); + out_min = quantile(accDistanceMin, quantile_probability = 1.0 - _sgmParams.seedsRangePercentile); + out_max = quantile(accDistanceMax, quantile_probability = _sgmParams.seedsRangePercentile); + midDepthPoint = midDepthPoint / static_cast(out_nbDepths); + out_mid = pointPlaneDistance(midDepthPoint, cameraPlane.p, cameraPlane.n); } else { - out_min = 0.f; - out_max = 0.f; - out_mid = 0.f; + out_min = 0.f; + out_max = 0.f; + out_mid = 0.f; } ALICEVISION_LOG_DEBUG(_tile << "Compute min/max/mid/nb observation depth from SfM for R camera:" << std::endl @@ -343,9 +341,7 @@ void SgmDepthList::getMinMaxMidNbDepthFromSfM(float& out_min, << "\t- percentile: " << _sgmParams.seedsRangePercentile); } -void SgmDepthList::getRcTcDepthRangeFromSfM(int tc, - double& out_zmin, - double& out_zmax) const +void SgmDepthList::getRcTcDepthRangeFromSfM(int tc, double& out_zmin, double& out_zmax) const { // get Rc/Tc view ids const IndexT rcViewId = _mp.getViewId(_tile.rc); @@ -354,7 +350,8 @@ void SgmDepthList::getRcTcDepthRangeFromSfM(int tc, // get R region-of-interest // landmark observations are in the full-size image coordinate system, we need to upcscale the tile ROI const ROI fullsizeRoi = upscaleROI(_tile.roi, _mp.getProcessDownscale()); - //const ROI selectionRoi = inflateROI(fullsizeRoi, 1.4f); // we can inflate the image full-size roi to be more permissive for common landmark selection + // const ROI selectionRoi = inflateROI(fullsizeRoi, 1.4f); // we can inflate the image full-size roi to be more permissive for common landmark + // selection // build R camera plane OrientedPoint cameraPlane; @@ -367,27 +364,27 @@ void SgmDepthList::getRcTcDepthRangeFromSfM(int tc, out_zmax = std::numeric_limits::min(); // for each landmark - for(const auto& landmarkPair : _mp.getInputSfMData().getLandmarks()) + for (const auto& landmarkPair : _mp.getInputSfMData().getLandmarks()) { const sfmData::Landmark& landmark = landmarkPair.second; const Point3d point(landmark.X(0), landmark.X(1), landmark.X(2)); // no tc observation - if(landmark.observations.find(tcViewId) == landmark.observations.end()) - continue; + if (landmark.observations.find(tcViewId) == landmark.observations.end()) + continue; // find rc observation const auto it = landmark.observations.find(rcViewId); // no rc observation - if(it == landmark.observations.end()) - continue; + if (it == landmark.observations.end()) + continue; // get rc 2d observation const Vec2& obs2d = it->second.x; // observation located inside the inflated image full-size ROI - if(!_sgmParams.depthListPerTile || fullsizeRoi.contains(obs2d.x(), obs2d.y())) + if (!_sgmParams.depthListPerTile || fullsizeRoi.contains(obs2d.x(), obs2d.y())) { // compute related depth const double depth = pointPlaneDistance(point, cameraPlane.p, cameraPlane.n); @@ -399,7 +396,7 @@ void SgmDepthList::getRcTcDepthRangeFromSfM(int tc, } // no common observations found - if(out_zmin > out_zmax) + if (out_zmin > out_zmax) { ALICEVISION_THROW_ERROR(_tile << "Cannot compute min/max depth from common Rc/Tc SfM observations." << std::endl << "No common observations found (tc view id: " << tcViewId << ")."); @@ -407,14 +404,12 @@ void SgmDepthList::getRcTcDepthRangeFromSfM(int tc, ALICEVISION_LOG_DEBUG(_tile << "Compute min/max depth from common Rc/Tc SfM observations:" << std::endl << "\t- rc: " << _tile.rc << " (view id: " << rcViewId << ")" << std::endl - << "\t- tc: " << tc << " (view id: " << tcViewId << ")" << std::endl - << "\t- min depth: " << out_zmin << std::endl - << "\t- max depth: " << out_zmax); + << "\t- tc: " << tc << " (view id: " << tcViewId << ")" << std::endl + << "\t- min depth: " << out_zmin << std::endl + << "\t- max depth: " << out_zmax); } -void SgmDepthList::computeRcTcDepths(int tc, - float midDepth, - std::vector& out_depths) const +void SgmDepthList::computeRcTcDepths(int tc, float midDepth, std::vector& out_depths) const { assert(out_depths.empty()); @@ -423,12 +418,12 @@ void SgmDepthList::computeRcTcDepths(int tc, rcplane.n = _mp.iRArr[_tile.rc] * Point3d(0.0, 0.0, 1.0); rcplane.n = rcplane.n.normalize(); - // ROI center + // ROI center const Point2d roiCenter((_tile.roi.x.begin + (_tile.roi.width() * 0.5)), _tile.roi.y.begin + (_tile.roi.height() * 0.5)); // principal point of the rc camera const Point2d principalPoint(_mp.getWidth(_tile.rc) * 0.5, _mp.getHeight(_tile.rc) * 0.5); - + // reference point for the epipolar line const Point2d referencePoint = (!_sgmParams.depthListPerTile) ? principalPoint : roiCenter; @@ -436,7 +431,7 @@ void SgmDepthList::computeRcTcDepths(int tc, Point2d tcMidDepthPoint; // segment of epipolar line - Point2d tcFromPoint, tcToPoint; + Point2d tcFromPoint, tcToPoint; { const Matrix3x4& rP = _mp.camArr[_tile.rc]; @@ -476,34 +471,34 @@ void SgmDepthList::computeRcTcDepths(int tc, Point3d p; // triangulate middle depth point - if(!triangulateMatch(p, referencePoint, tcMidDepthPoint, _tile.rc, tc, _mp)) + if (!triangulateMatch(p, referencePoint, tcMidDepthPoint, _tile.rc, tc, _mp)) return; const float depth = orientedPointPlaneDistance(p, rcplane.p, rcplane.n); // triangulate middle depth point + 1 pixelVect - if(!triangulateMatch(p, referencePoint, tcMidDepthPoint + pixelVect, _tile.rc, tc, _mp)) + if (!triangulateMatch(p, referencePoint, tcMidDepthPoint + pixelVect, _tile.rc, tc, _mp)) return; const float depthP1 = orientedPointPlaneDistance(p, rcplane.p, rcplane.n); - if(depth > depthP1) + if (depth > depthP1) depthDirection = -1; } - + out_depths.reserve(nbSegmentPointsAtSgmScale); const Point3d refVect = _mp.iCamArr[_tile.rc] * referencePoint; float previousDepth = -1.0f; // compute depths for all pixels from one side of the epipolar segment to the other - for(int i = 0; i < nbSegmentPointsAtSgmScale; ++i) + for (int i = 0; i < nbSegmentPointsAtSgmScale; ++i) { const Point2d tcPoint = ((depthDirection > 0) ? tcFromPoint : tcToPoint) + (pixelVect * double(i) * double(depthDirection)); // check if the epipolar segment point is in T camera // note: get2dLineImageIntersection can give points slightly out of the picture - if(!_mp.isPixelInImage(tcPoint, tc)) + if (!_mp.isPixelInImage(tcPoint, tc)) continue; const Point3d tarVect = _mp.iCamArr[tc] * tcPoint; @@ -511,28 +506,28 @@ void SgmDepthList::computeRcTcDepths(int tc, // if vects are near parallel then this results to strange angles // this is the proper angle because it does not depend on the triangulated p - if(refTarVectAngle < _mp.getMinViewAngle() || refTarVectAngle > _mp.getMaxViewAngle()) + if (refTarVectAngle < _mp.getMinViewAngle() || refTarVectAngle > _mp.getMaxViewAngle()) continue; // epipolar segment point related 3d point Point3d p; // triangulate principal point from rc with tcPoint - if(!triangulateMatch(p, referencePoint, tcPoint, _tile.rc, tc, _mp)) + if (!triangulateMatch(p, referencePoint, tcPoint, _tile.rc, tc, _mp)) continue; // check the difference in pixel size between R and T and the angle size of p // note: disabled for now, this test is too strict and rejects too many points. - //if(!checkPair(p, _tile.rc, tc, _mp, _mp.getMinViewAngle(), _mp.getMaxViewAngle())) + // if(!checkPair(p, _tile.rc, tc, _mp, _mp.getMinViewAngle(), _mp.getMaxViewAngle())) // continue; // compute related 3d point depth const float depth = float(orientedPointPlaneDistance(p, rcplane.p, rcplane.n)); - if((depth > 0.0f) && (depth > previousDepth)) + if ((depth > 0.0f) && (depth > previousDepth)) { - out_depths.push_back(depth); - previousDepth = depth + std::numeric_limits::epsilon(); + out_depths.push_back(depth); + previousDepth = depth + std::numeric_limits::epsilon(); } } @@ -545,14 +540,11 @@ void SgmDepthList::computeRcTcDepths(int tc, << "\t- # points of the epipolar segment at SGM scale: " << nbSegmentPointsAtSgmScale << std::endl << "\t- # depths to use: " << out_depths.size()); - if(!out_depths.empty()) - ALICEVISION_LOG_DEBUG(_tile << "Depth to use range [" << out_depths.front() << "-" << out_depths.back() << "]" << std::endl); + if (!out_depths.empty()) + ALICEVISION_LOG_DEBUG(_tile << "Depth to use range [" << out_depths.front() << "-" << out_depths.back() << "]" << std::endl); } -void SgmDepthList::computePixelSizeDepths(float minObsDepth, - float midObsDepth, - float maxObsDepth, - std::vector& out_depths) const +void SgmDepthList::computePixelSizeDepths(float minObsDepth, float midObsDepth, float maxObsDepth, std::vector& out_depths) const { assert(out_depths.empty()); @@ -568,7 +560,7 @@ void SgmDepthList::computePixelSizeDepths(float minObsDepth, int ndepthsMidMax = 0; float maxdepth = midObsDepth; - while((maxdepth < maxObsDepth) && (ndepthsMidMax < maxDepthsHalf)) + while ((maxdepth < maxObsDepth) && (ndepthsMidMax < maxDepthsHalf)) { Point3d p = rcplane.p + rcplane.n * maxdepth; float pixSize = _mp.getCamPixelSize(p, _tile.rc, d); @@ -578,7 +570,7 @@ void SgmDepthList::computePixelSizeDepths(float minObsDepth, int ndepthsMidMin = 0; float mindepth = midObsDepth; - while((mindepth > minObsDepth) && (ndepthsMidMin < maxDepthsHalf * 2 - ndepthsMidMax)) + while ((mindepth > minObsDepth) && (ndepthsMidMin < maxDepthsHalf * 2 - ndepthsMidMax)) { Point3d p = rcplane.p + rcplane.n * mindepth; float pixSize = _mp.getCamPixelSize(p, _tile.rc, d); @@ -590,7 +582,7 @@ void SgmDepthList::computePixelSizeDepths(float minObsDepth, float depth = mindepth; int ndepths = 0; float pixSize = 1.0f; - while((depth < maxdepth) && (pixSize > 0.0f) && (ndepths < 2 * maxDepthsHalf)) + while ((depth < maxdepth) && (pixSize > 0.0f) && (ndepths < 2 * maxDepthsHalf)) { Point3d p = rcplane.p + rcplane.n * depth; pixSize = _mp.getCamPixelSize(p, _tile.rc, d); @@ -604,7 +596,7 @@ void SgmDepthList::computePixelSizeDepths(float minObsDepth, depth = mindepth; pixSize = 1.0f; ndepths = 0; - while((depth < maxdepth) && (pixSize > 0.0f) && (ndepths < 2 * maxDepthsHalf)) + while ((depth < maxdepth) && (pixSize > 0.0f) && (ndepths < 2 * maxDepthsHalf)) { out_depths.push_back(depth); Point3d p = rcplane.p + rcplane.n * depth; @@ -614,11 +606,11 @@ void SgmDepthList::computePixelSizeDepths(float minObsDepth, } // check if it is asc - for(int i = 0; i < out_depths.size() - 1; i++) + for (int i = 0; i < out_depths.size() - 1; i++) { - if(out_depths[i] >= out_depths[i + 1]) + if (out_depths[i] >= out_depths[i + 1]) { - for(int j = 0; j <= i + 1; j++) + for (int j = 0; j <= i + 1; j++) { ALICEVISION_LOG_TRACE(_tile << "getDepthsByPixelSize: check if it is asc: " << out_depths[j]); } @@ -627,16 +619,13 @@ void SgmDepthList::computePixelSizeDepths(float minObsDepth, } } -void SgmDepthList::computeRcDepthList(float firstDepth, - float lastDepth, - float scaleFactor, - const std::vector>& dephtsPerTc) +void SgmDepthList::computeRcDepthList(float firstDepth, float lastDepth, float scaleFactor, const std::vector>& dephtsPerTc) { _depths.clear(); float depth = firstDepth; - while(depth < lastDepth) + while (depth < lastDepth) { _depths.push_back(depth); @@ -644,17 +633,17 @@ void SgmDepthList::computeRcDepthList(float firstDepth, float minTcStep = lastDepth - firstDepth; // for each tc camera - for(const std::vector& tcDepths : dephtsPerTc) + for (const std::vector& tcDepths : dephtsPerTc) { // get the tc depth closest to the current depth const int id = indexOfNearestSorted(tcDepths, depth); // continue on no result or last element (we need id + 1) - if(id < 0 || id >= tcDepths.size() - 1) + if (id < 0 || id >= tcDepths.size() - 1) continue; // enclosing depth range - const float tcStep = fabs(tcDepths.at(id) - tcDepths.at(id + 1)); // (closest - next) depths distance + const float tcStep = fabs(tcDepths.at(id) - tcDepths.at(id + 1)); // (closest - next) depths distance // keep this value if smallest step so far minTcStep = std::min(minTcStep, tcStep); @@ -673,7 +662,7 @@ void SgmDepthList::exportTxtFiles(const std::vector>& dephtsP { const std::string fn = prefix + "depthsTcLimits" + suffix; FILE* f = fopen(fn.c_str(), "w"); - for(int j = 0; j < _depthsTcLimits.size(); j++) + for (int j = 0; j < _depthsTcLimits.size(); j++) { Pixel l = _depthsTcLimits[j]; fprintf(f, "%i %i\n", l.x, l.y); @@ -685,7 +674,7 @@ void SgmDepthList::exportTxtFiles(const std::vector>& dephtsP { const std::string fn = prefix + "depths" + suffix; FILE* f = fopen(fn.c_str(), "w"); - for(int j = 0; j < _depths.size(); j++) + for (int j = 0; j < _depths.size(); j++) { fprintf(f, "%f\n", _depths[j]); } @@ -694,11 +683,11 @@ void SgmDepthList::exportTxtFiles(const std::vector>& dephtsP // export all depths per tc txt files { - for(int c = 0; c < dephtsPerTc.size(); ++c) + for (int c = 0; c < dephtsPerTc.size(); ++c) { const std::string fn = prefix + "depths_tc_" + mvsUtils::num2str(_mp.getViewId(_tile.sgmTCams.at(c))) + suffix; FILE* f = fopen(fn.c_str(), "w"); - for(const float depth : dephtsPerTc.at(c)) + for (const float depth : dephtsPerTc.at(c)) { fprintf(f, "%f\n", depth); } @@ -707,5 +696,5 @@ void SgmDepthList::exportTxtFiles(const std::vector>& dephtsP } } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/SgmDepthList.hpp b/src/aliceVision/depthMap/SgmDepthList.hpp index 3360c0b3a2..461bfbd8ad 100644 --- a/src/aliceVision/depthMap/SgmDepthList.hpp +++ b/src/aliceVision/depthMap/SgmDepthList.hpp @@ -19,8 +19,7 @@ namespace depthMap { */ class SgmDepthList { -public: - + public: /** * @brief SgmDepthList constructor. * @param[in] mp the multi-view parameters @@ -63,8 +62,7 @@ class SgmDepthList */ void checkStartingAndStoppingDepth() const; -private: - + private: // private methods /** @@ -74,10 +72,7 @@ class SgmDepthList * @param[out] out_mid The middle depth observation * @param[out] out_nbDepths The number of depth observation */ - void getMinMaxMidNbDepthFromSfM(float& out_min, - float& out_max, - float& out_mid, - std::size_t& out_nbDepths) const; + void getMinMaxMidNbDepthFromSfM(float& out_min, float& out_max, float& out_mid, std::size_t& out_nbDepths) const; /** * @brief Compute min/max depth from common Rc/Tc SfM observations. @@ -85,9 +80,7 @@ class SgmDepthList * @param[out] out_zmin The minimum depth * @param[out] out_zmax The maximum depth */ - void getRcTcDepthRangeFromSfM(int tc, - double& out_zmin, - double& out_zmax) const; + void getRcTcDepthRangeFromSfM(int tc, double& out_zmin, double& out_zmax) const; /** * @brief Compute depths of the principal ray of reference camera rc visible by a pixel in a target camera tc @@ -96,9 +89,7 @@ class SgmDepthList * @param[in] midDepth The middle depth observation * @param[out] out_depths the output depth list */ - void computeRcTcDepths(int tc, - float midObsDepth, - std::vector& out_depths) const; + void computeRcTcDepths(int tc, float midObsDepth, std::vector& out_depths) const; /** * @brief Compute a depth list from R camera pixel size. @@ -107,23 +98,16 @@ class SgmDepthList * @param[in] maxObsDepth The max depth observation * @param[out] out_depths the output depth list */ - void computePixelSizeDepths(float minObsDepth, - float midObsDepth, - float maxObsDepth, - std::vector& out_depths) const; + void computePixelSizeDepths(float minObsDepth, float midObsDepth, float maxObsDepth, std::vector& out_depths) const; /** * @brief Fill the list of "best" depths (_depths) for rc, from all tc cameras depths. - * @param[in] firstDepth The first depth + * @param[in] firstDepth The first depth * @param[in] lastDepth The last depth * @param[in] scaleFactor The scale factor to apply between each depth * @param[in] dephtsPerTc The depth list per T camera */ - void computeRcDepthList(float firstDepth, - float lastDepth, - float scaleFactor, - const std::vector>& dephtsPerTc); - + void computeRcDepthList(float firstDepth, float lastDepth, float scaleFactor, const std::vector>& dephtsPerTc); /** * @brief Export multiple intermediate depth list txt files. @@ -133,13 +117,13 @@ class SgmDepthList // private members - const mvsUtils::MultiViewParams& _mp; //< Multi-view parameters - const SgmParams& _sgmParams; //< Semi Global Matching parameters - const Tile& _tile; //< Tile for depth list computation + const mvsUtils::MultiViewParams& _mp; //< Multi-view parameters + const SgmParams& _sgmParams; //< Semi Global Matching parameters + const Tile& _tile; //< Tile for depth list computation - std::vector _depths; //< R camera depth list - std::vector _depthsTcLimits; //< T camera depth limits + std::vector _depths; //< R camera depth list + std::vector _depthsTcLimits; //< T camera depth limits }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/SgmParams.hpp b/src/aliceVision/depthMap/SgmParams.hpp index 1c702162f7..c1ac410134 100644 --- a/src/aliceVision/depthMap/SgmParams.hpp +++ b/src/aliceVision/depthMap/SgmParams.hpp @@ -16,45 +16,44 @@ namespace depthMap { */ struct SgmParams { - // user parameters - - int scale = 2; - int stepXY = 2; - int stepZ = -1; - int wsh = 4; - int maxDepths = 1500; - int maxTCamsPerTile = 4; - double seedsRangeInflate = 0.2; - double depthThicknessInflate = 0.0; - double maxSimilarity = 1.0; - double gammaC = 5.5; - double gammaP = 8.0; - double p1 = 10; - double p2Weighting = 100.0; - std::string filteringAxes = "YX"; - bool useSfmSeeds = true; - bool depthListPerTile = false; - bool useConsistentScale = false; - bool useCustomPatchPattern = false; - - // intermediate results export parameters - - bool exportIntermediateDepthSimMaps = false; - bool exportIntermediateNormalMaps = false; - bool exportIntermediateVolumes = false; - bool exportIntermediateCrossVolumes = false; - bool exportIntermediateTopographicCutVolumes = false; - bool exportIntermediateVolume9pCsv = false; - const bool exportDepthsTxtFiles = false; - - // constant parameters - - const bool updateUninitializedSim = true; // should always be true, false for debug purposes - const float prematchingMaxDepthScale = 1.5f; - const double seedsRangePercentile = 0.999; - const bool doSgmOptimizeVolume = true; - + // user parameters + + int scale = 2; + int stepXY = 2; + int stepZ = -1; + int wsh = 4; + int maxDepths = 1500; + int maxTCamsPerTile = 4; + double seedsRangeInflate = 0.2; + double depthThicknessInflate = 0.0; + double maxSimilarity = 1.0; + double gammaC = 5.5; + double gammaP = 8.0; + double p1 = 10; + double p2Weighting = 100.0; + std::string filteringAxes = "YX"; + bool useSfmSeeds = true; + bool depthListPerTile = false; + bool useConsistentScale = false; + bool useCustomPatchPattern = false; + + // intermediate results export parameters + + bool exportIntermediateDepthSimMaps = false; + bool exportIntermediateNormalMaps = false; + bool exportIntermediateVolumes = false; + bool exportIntermediateCrossVolumes = false; + bool exportIntermediateTopographicCutVolumes = false; + bool exportIntermediateVolume9pCsv = false; + const bool exportDepthsTxtFiles = false; + + // constant parameters + + const bool updateUninitializedSim = true; // should always be true, false for debug purposes + const float prematchingMaxDepthScale = 1.5f; + const double seedsRangePercentile = 0.999; + const bool doSgmOptimizeVolume = true; }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/Tile.hpp b/src/aliceVision/depthMap/Tile.hpp index da911d3101..db401e518d 100644 --- a/src/aliceVision/depthMap/Tile.hpp +++ b/src/aliceVision/depthMap/Tile.hpp @@ -20,12 +20,12 @@ namespace depthMap { */ struct Tile { - int id; //< tile index - int nbTiles; //< number of tiles per image - int rc; //< related R camera index - std::vector sgmTCams; //< SGM T camera index list - std::vector refineTCams; //< Refine T camera index list - ROI roi; //< 2d region of interest of the R image + int id; //< tile index + int nbTiles; //< number of tiles per image + int rc; //< related R camera index + std::vector sgmTCams; //< SGM T camera index list + std::vector refineTCams; //< Refine T camera index list + ROI roi; //< 2d region of interest of the R image }; inline std::ostream& operator<<(std::ostream& os, const Tile& tile) @@ -34,5 +34,5 @@ inline std::ostream& operator<<(std::ostream& os, const Tile& tile) return os; } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/computeOnMultiGPUs.cpp b/src/aliceVision/depthMap/computeOnMultiGPUs.cpp index 37becd58a8..bb5d24018e 100644 --- a/src/aliceVision/depthMap/computeOnMultiGPUs.cpp +++ b/src/aliceVision/depthMap/computeOnMultiGPUs.cpp @@ -36,9 +36,9 @@ void computeOnMultiGPUs(const std::vector& cams, IGPUJob& gpujob, int nbGPU } else { - //backup max threads to keep potentially previously set value + // backup max threads to keep potentially previously set value int previous_count_threads = omp_get_max_threads(); - omp_set_num_threads(nbThreads); // create as many CPU threads as there are CUDA devices + omp_set_num_threads(nbThreads); // create as many CPU threads as there are CUDA devices #pragma omp parallel { const int cpuThreadId = omp_get_thread_num(); @@ -49,7 +49,7 @@ void computeOnMultiGPUs(const std::vector& cams, IGPUJob& gpujob, int nbGPU const int nbCamsPerThread = (cams.size() / nbThreads); const int rcFrom = cudaDeviceId * nbCamsPerThread; int rcTo = (cudaDeviceId + 1) * nbCamsPerThread; - if(cudaDeviceId == nbThreads - 1) + if (cudaDeviceId == nbThreads - 1) { rcTo = cams.size(); } @@ -68,5 +68,5 @@ void computeOnMultiGPUs(const std::vector& cams, IGPUJob& gpujob, int nbGPU } } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/computeOnMultiGPUs.hpp b/src/aliceVision/depthMap/computeOnMultiGPUs.hpp index 85cf52a772..635cff1d50 100644 --- a/src/aliceVision/depthMap/computeOnMultiGPUs.hpp +++ b/src/aliceVision/depthMap/computeOnMultiGPUs.hpp @@ -17,8 +17,7 @@ namespace depthMap { */ class IGPUJob { -public: - + public: /** * @brief Perform computation from the given cameras. * @param[in] cudaDeviceId the CUDA device id @@ -35,5 +34,5 @@ class IGPUJob */ void computeOnMultiGPUs(const std::vector& cams, IGPUJob& gpujob, int nbGPUsToUse); -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/device/DeviceCameraParams.hpp b/src/aliceVision/depthMap/cuda/device/DeviceCameraParams.hpp index 92e1eab041..373d947d01 100644 --- a/src/aliceVision/depthMap/cuda/device/DeviceCameraParams.hpp +++ b/src/aliceVision/depthMap/cuda/device/DeviceCameraParams.hpp @@ -29,9 +29,9 @@ struct DeviceCameraParams // global / constant data structures -#define ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS 100 // CUDA constant memory is limited to 65K +#define ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS 100 // CUDA constant memory is limited to 65K extern __constant__ DeviceCameraParams constantCameraParametersArray_d[ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS]; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/device/DevicePatchPattern.hpp b/src/aliceVision/depthMap/cuda/device/DevicePatchPattern.hpp index bd8f337af9..002e56cb2e 100644 --- a/src/aliceVision/depthMap/cuda/device/DevicePatchPattern.hpp +++ b/src/aliceVision/depthMap/cuda/device/DevicePatchPattern.hpp @@ -27,13 +27,13 @@ namespace depthMap { */ struct DevicePatchPatternSubpart { - float2 coordinates[ALICEVISION_DEVICE_PATCH_MAX_COORDS_PER_SUBPARTS]; //< subpart coordinate list - int nbCoordinates; //< subpart number of coordinate - float level; //< subpart related mipmap level (>=0) - float downscale; //< subpart related mipmap downscale (>=1) - float weight; //< subpart related similarity weight in range (0, 1) - bool isCircle; //< subpart is a circle - int wsh; //< subpart half-width (full and circle) + float2 coordinates[ALICEVISION_DEVICE_PATCH_MAX_COORDS_PER_SUBPARTS]; //< subpart coordinate list + int nbCoordinates; //< subpart number of coordinate + float level; //< subpart related mipmap level (>=0) + float downscale; //< subpart related mipmap downscale (>=1) + float weight; //< subpart related similarity weight in range (0, 1) + bool isCircle; //< subpart is a circle + int wsh; //< subpart half-width (full and circle) }; /** @@ -42,12 +42,12 @@ struct DevicePatchPatternSubpart */ struct DevicePatchPattern { - DevicePatchPatternSubpart subparts[ALICEVISION_DEVICE_PATCH_MAX_SUBPARTS]; //< patch pattern subparts (one similarity per subpart) - int nbSubparts; //< patch pattern number of subparts (>0) + DevicePatchPatternSubpart subparts[ALICEVISION_DEVICE_PATCH_MAX_SUBPARTS]; //< patch pattern subparts (one similarity per subpart) + int nbSubparts; //< patch pattern number of subparts (>0) }; // patch pattern symbol in CUDA constant memory extern __constant__ DevicePatchPattern constantPatchPattern_d; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/DeviceCache.cpp b/src/aliceVision/depthMap/cuda/host/DeviceCache.cpp index 93cd7b53ce..6dee45d671 100644 --- a/src/aliceVision/depthMap/cuda/host/DeviceCache.cpp +++ b/src/aliceVision/depthMap/cuda/host/DeviceCache.cpp @@ -12,16 +12,15 @@ #include // maximum pre-computed Gaussian scales -#define DEVICE_MAX_DOWNSCALE ( MAX_CONSTANT_GAUSS_SCALES - 1 ) +#define DEVICE_MAX_DOWNSCALE (MAX_CONSTANT_GAUSS_SCALES - 1) namespace aliceVision { namespace depthMap { float3 M3x3mulV3(const float* M3x3, const float3& V) { - return make_float3(M3x3[0] * V.x + M3x3[3] * V.y + M3x3[6] * V.z, - M3x3[1] * V.x + M3x3[4] * V.y + M3x3[7] * V.z, - M3x3[2] * V.x + M3x3[5] * V.y + M3x3[8] * V.z); + return make_float3( + M3x3[0] * V.x + M3x3[3] * V.y + M3x3[6] * V.z, M3x3[1] * V.x + M3x3[4] * V.y + M3x3[7] * V.z, M3x3[2] * V.x + M3x3[5] * V.y + M3x3[8] * V.z); } void normalize(float3& a) @@ -33,12 +32,12 @@ void normalize(float3& a) } /** - * @brief Fill the host-side camera parameters from multi-view parameters. - * @param[in,out] cameraParameters_h the host-side camera parameters - * @param[in] camId the camera index in the ImagesCache / MultiViewParams - * @param[in] downscale the downscale to apply on parameters - * @param[in] mp the multi-view parameters - */ + * @brief Fill the host-side camera parameters from multi-view parameters. + * @param[in,out] cameraParameters_h the host-side camera parameters + * @param[in] camId the camera index in the ImagesCache / MultiViewParams + * @param[in] downscale the downscale to apply on parameters + * @param[in] mp the multi-view parameters + */ void fillHostCameraParameters(DeviceCameraParams& cameraParameters_h, int camId, int downscale, const mvsUtils::MultiViewParams& mp) { Matrix3x3 scaleM; @@ -135,29 +134,30 @@ void fillHostCameraParameters(DeviceCameraParams& cameraParameters_h, int camId, } /** - * @brief Fill the device-side camera parameters array (constant memory) - * with the given host-side camera parameters. - * @param[in] cameraParameters_h the host-side camera parameters - * @param[in] deviceCameraParamsId the constant camera parameters array - */ + * @brief Fill the device-side camera parameters array (constant memory) + * with the given host-side camera parameters. + * @param[in] cameraParameters_h the host-side camera parameters + * @param[in] deviceCameraParamsId the constant camera parameters array + */ void fillDeviceCameraParameters(const DeviceCameraParams& cameraParameters_h, int deviceCameraParamsId) { const cudaMemcpyKind kind = cudaMemcpyHostToDevice; - const cudaError_t err = cudaMemcpyToSymbol(constantCameraParametersArray_d, &cameraParameters_h, sizeof(DeviceCameraParams), deviceCameraParamsId * sizeof(DeviceCameraParams), kind); + const cudaError_t err = cudaMemcpyToSymbol( + constantCameraParametersArray_d, &cameraParameters_h, sizeof(DeviceCameraParams), deviceCameraParamsId * sizeof(DeviceCameraParams), kind); CHECK_CUDA_RETURN_ERROR(err); THROW_ON_CUDA_ERROR(err, "Failed to copy camera parameters from host to device."); } DeviceCache::SingleDeviceCache::SingleDeviceCache(int maxMipmapImages, int maxCameraParams) - : mipmapCache(maxMipmapImages) - , cameraParamCache(maxCameraParams) + : mipmapCache(maxMipmapImages), + cameraParamCache(maxCameraParams) { // get the current device id const int cudaDeviceId = getCudaDeviceId(); ALICEVISION_LOG_TRACE("Initialize device cache (device id: " << cudaDeviceId << "):" << std::endl - << "\t - # mipmap images: " << maxMipmapImages << std::endl - << "\t - # cameras parameters: " << maxCameraParams); + << "\t - # mipmap images: " << maxMipmapImages << std::endl + << "\t - # cameras parameters: " << maxCameraParams); // initialize Gaussian filters in GPU constant memory // force at compilation to build with maximum pre-computed Gaussian scales @@ -166,12 +166,14 @@ DeviceCache::SingleDeviceCache::SingleDeviceCache(int maxMipmapImages, int maxCa // the maximum number of camera parameters in device cache cannot be superior // to the number of camera parameters in the array in device constant memory - if(maxCameraParams > ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS) - ALICEVISION_THROW_ERROR("Cannot initialize device cache with more than " << ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS << " camera parameters (device id: " << cudaDeviceId << ", # cameras parameters: " << maxCameraParams << ").") + if (maxCameraParams > ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS) + ALICEVISION_THROW_ERROR("Cannot initialize device cache with more than " << ALICEVISION_DEVICE_MAX_CONSTANT_CAMERA_PARAM_SETS + << " camera parameters (device id: " << cudaDeviceId + << ", # cameras parameters: " << maxCameraParams << ").") // initialize cached mipmap image containers mipmaps.reserve(maxMipmapImages); - for(int i = 0; i < maxMipmapImages; ++i) + for (int i = 0; i < maxMipmapImages; ++i) { mipmaps.push_back(std::make_unique()); } @@ -186,7 +188,7 @@ void DeviceCache::clear() auto it = _cachePerDevice.find(cudaDeviceId); // if found, erase SingleDeviceCache data - if(it != _cachePerDevice.end()) + if (it != _cachePerDevice.end()) _cachePerDevice.erase(it); } @@ -208,9 +210,9 @@ DeviceCache::SingleDeviceCache& DeviceCache::getCurrentDeviceCache() auto it = _cachePerDevice.find(cudaDeviceId); // check found and initialized - if(it == _cachePerDevice.end() || it->second == nullptr) + if (it == _cachePerDevice.end() || it->second == nullptr) { - ALICEVISION_THROW_ERROR("Device cache is not initialized (cuda device id: " << cudaDeviceId <<").") + ALICEVISION_THROW_ERROR("Device cache is not initialized (cuda device id: " << cudaDeviceId << ").") } // return current SingleDeviceCache reference @@ -235,10 +237,10 @@ void DeviceCache::addMipmapImage(int camId, const bool newInsertion = currentDeviceCache.mipmapCache.insert(camId, &deviceMipmapId); // check if the camera is already in cache - if(!newInsertion) + if (!newInsertion) { ALICEVISION_LOG_TRACE("Add mipmap image on device cache: already on cache (id: " << camId << ", view id: " << viewId << ")."); - return; // nothing to do + return; // nothing to do } ALICEVISION_LOG_TRACE("Add mipmap image on device cache (id: " << camId << ", view id: " << viewId << ")."); @@ -252,9 +254,9 @@ void DeviceCache::addMipmapImage(int camId, // copy image from imageCache to CUDA host-side image buffer #pragma omp parallel for - for(int y = 0; y < imgSize.y(); ++y) + for (int y = 0; y < imgSize.y(); ++y) { - for(int x = 0; x < imgSize.x(); ++x) + for (int x = 0; x < imgSize.x(); ++x) { const image::RGBAfColor& floatRGBA = (*img)(y, x); CudaRGBA& cudaRGBA = img_hmh(x, y); @@ -292,10 +294,11 @@ void DeviceCache::addCameraParams(int camId, int downscale, const mvsUtils::Mult const bool newInsertion = currentDeviceCache.cameraParamCache.insert(CameraPair(camId, downscale), &deviceCameraParamsId); // check if the camera is already in cache - if(!newInsertion) + if (!newInsertion) { - ALICEVISION_LOG_TRACE("Add camera parameters on device cache: already on cache (id: " << camId << ", view id: " << viewId << ", downscale: " << downscale << ")."); - return; // nothing to do + ALICEVISION_LOG_TRACE("Add camera parameters on device cache: already on cache (id: " << camId << ", view id: " << viewId + << ", downscale: " << downscale << ")."); + return; // nothing to do } ALICEVISION_LOG_TRACE("Add camera parameters on device cache (id: " << camId << ", view id: " << viewId << ", downscale: " << downscale << ")."); @@ -337,7 +340,7 @@ const DeviceMipmapImage& DeviceCache::requestMipmapImage(int camId, const mvsUti const bool notFound = currentDeviceCache.mipmapCache.insert(camId, &deviceMipmapId); // check if the mipmap image is in the cache - if(notFound) + if (notFound) ALICEVISION_THROW_ERROR("Request mipmap image on device cache: Not found (id: " << camId << ", view id: " << viewId << ").") // return the cached device mipmap image @@ -352,7 +355,8 @@ const int DeviceCache::requestCameraParamsId(int camId, int downscale, const mvs // get view id for logs const IndexT viewId = mp.getViewId(camId); - ALICEVISION_LOG_TRACE("Request camera parameters on device cache (id: " << camId << ", view id: " << viewId << ", downscale: " << downscale << ")."); + ALICEVISION_LOG_TRACE("Request camera parameters on device cache (id: " << camId << ", view id: " << viewId << ", downscale: " << downscale + << ")."); // find out with the LRU (Least Recently Used) strategy if the camera parameters object is already in the cache // note: if not found in cache we need to throw an error: in that case we insert an orphan id in the cache @@ -360,12 +364,13 @@ const int DeviceCache::requestCameraParamsId(int camId, int downscale, const mvs const bool notFound = currentDeviceCache.cameraParamCache.insert(CameraPair(camId, downscale), &deviceCameraParamsId); // check if the camera is in the cache - if(notFound) - ALICEVISION_THROW_ERROR("Request camera parameters on device cache: Not found (id: " << camId << ", view id: " << viewId << ", downscale: " << downscale << ").") + if (notFound) + ALICEVISION_THROW_ERROR("Request camera parameters on device cache: Not found (id: " << camId << ", view id: " << viewId + << ", downscale: " << downscale << ").") // return the cached camera parameters id return deviceCameraParamsId; } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/DeviceCache.hpp b/src/aliceVision/depthMap/cuda/host/DeviceCache.hpp index 096ebe7082..32d0d4516b 100644 --- a/src/aliceVision/depthMap/cuda/host/DeviceCache.hpp +++ b/src/aliceVision/depthMap/cuda/host/DeviceCache.hpp @@ -23,7 +23,7 @@ namespace depthMap { */ class DeviceCache { -public: + public: static DeviceCache& getInstance() { static DeviceCache instance; @@ -88,8 +88,7 @@ class DeviceCache */ const int requestCameraParamsId(int camId, int downscale, const mvsUtils::MultiViewParams& mp); -private: - + private: // private members /* @@ -102,12 +101,12 @@ class DeviceCache ~SingleDeviceCache() = default; // caches Least Recently Used - LRUCameraIdCache mipmapCache; //< device mipmap image id cached per (camera id) - LRUCameraCache cameraParamCache; //< device camera parameters id cached per (camera id, downscale) + LRUCameraIdCache mipmapCache; //< device mipmap image id cached per (camera id) + LRUCameraCache cameraParamCache; //< device camera parameters id cached per (camera id, downscale) - std::vector> mipmaps; //< cached device mipmap images + std::vector> mipmaps; //< cached device mipmap images }; - std::map > _cachePerDevice; // + std::map> _cachePerDevice; // // private methods @@ -124,5 +123,5 @@ class DeviceCache SingleDeviceCache& getCurrentDeviceCache(); }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.cpp b/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.cpp index bb8cf3b7d3..51697cddd5 100644 --- a/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.cpp +++ b/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.cpp @@ -17,12 +17,12 @@ namespace depthMap { DeviceMipmapImage::~DeviceMipmapImage() { // destroy mipmapped array texture object - if(_textureObject != 0) - CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaDestroyTextureObject(_textureObject)); + if (_textureObject != 0) + CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaDestroyTextureObject(_textureObject)); // free mipmapped array - if(_mipmappedArray != nullptr) - CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaFreeMipmappedArray(_mipmappedArray)); + if (_mipmappedArray != nullptr) + CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaFreeMipmappedArray(_mipmappedArray)); } void DeviceMipmapImage::fill(const CudaHostMemoryHeap& in_img_hmh, int minDownscale, int maxDownscale) @@ -30,17 +30,17 @@ void DeviceMipmapImage::fill(const CudaHostMemoryHeap& in_img_hmh, // update private members _minDownscale = minDownscale; _maxDownscale = maxDownscale; - _width = in_img_hmh.getSize().x(); + _width = in_img_hmh.getSize().x(); _height = in_img_hmh.getSize().y(); _levels = log2(maxDownscale / minDownscale) + 1; // destroy previous texture object - if(_textureObject != 0) - CHECK_CUDA_RETURN_ERROR(cudaDestroyTextureObject(_textureObject)); + if (_textureObject != 0) + CHECK_CUDA_RETURN_ERROR(cudaDestroyTextureObject(_textureObject)); // destroy previous mipmapped array - if(_mipmappedArray != nullptr) - CHECK_CUDA_RETURN_ERROR(cudaFreeMipmappedArray(_mipmappedArray)); + if (_mipmappedArray != nullptr) + CHECK_CUDA_RETURN_ERROR(cudaFreeMipmappedArray(_mipmappedArray)); // allocate the device-sided full-size input image buffer auto img_dmpPtr = std::make_shared>(in_img_hmh.getSize()); @@ -49,13 +49,13 @@ void DeviceMipmapImage::fill(const CudaHostMemoryHeap& in_img_hmh, img_dmpPtr->copyFrom(in_img_hmh); // downscale device-sided full-size input image buffer to min downscale - if(minDownscale > 1) + if (minDownscale > 1) { // create full-size input image buffer texture CudaRGBATexture fullSizeImg(*img_dmpPtr); // create downscaled image buffer - const size_t downscaledWidth = size_t(divideRoundUp(int(_width), int(minDownscale))); + const size_t downscaledWidth = size_t(divideRoundUp(int(_width), int(minDownscale))); const size_t downscaledHeight = size_t(divideRoundUp(int(_height), int(minDownscale))); auto downscaledImg_dmpPtr = std::make_shared>(CudaSize<2>(downscaledWidth, downscaledHeight)); @@ -89,25 +89,23 @@ void DeviceMipmapImage::fill(const CudaHostMemoryHeap& in_img_hmh, CHECK_CUDA_ERROR(); } - float DeviceMipmapImage::getLevel(unsigned int downscale) const { - // check given downscale - if(downscale < _minDownscale || downscale > _maxDownscale) - ALICEVISION_THROW_ERROR("Cannot get device mipmap image level (downscale: " << downscale << ")"); + // check given downscale + if (downscale < _minDownscale || downscale > _maxDownscale) + ALICEVISION_THROW_ERROR("Cannot get device mipmap image level (downscale: " << downscale << ")"); - return log2(float(downscale) / float(_minDownscale)); + return log2(float(downscale) / float(_minDownscale)); } - CudaSize<2> DeviceMipmapImage::getDimensions(unsigned int downscale) const { - // check given downscale - if(downscale < _minDownscale || downscale > _maxDownscale) - ALICEVISION_THROW_ERROR("Cannot get device mipmap image level dimensions (downscale: " << downscale << ")"); + // check given downscale + if (downscale < _minDownscale || downscale > _maxDownscale) + ALICEVISION_THROW_ERROR("Cannot get device mipmap image level dimensions (downscale: " << downscale << ")"); - return CudaSize<2>(divideRoundUp(int(_width), int(downscale)), divideRoundUp(int(_height), int(downscale))); + return CudaSize<2>(divideRoundUp(int(_width), int(downscale)), divideRoundUp(int(_height), int(downscale))); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.hpp b/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.hpp index a3076b4d77..de61a70180 100644 --- a/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.hpp +++ b/src/aliceVision/depthMap/cuda/host/DeviceMipmapImage.hpp @@ -17,8 +17,7 @@ namespace depthMap { */ class DeviceMipmapImage { -public: - + public: // default constructor DeviceMipmapImage() = default; @@ -72,18 +71,17 @@ class DeviceMipmapImage */ inline cudaTextureObject_t getTextureObject() const { return _textureObject; } -private: - + private: // private members - cudaMipmappedArray_t _mipmappedArray = nullptr; //< mipmapped array in device memory - cudaTextureObject_t _textureObject = 0; //< mipmapped array texture object with normalized coordinates - unsigned int _minDownscale = 0; //< the min downscale factor (must be power of two), first downscale level - unsigned int _maxDownscale = 0; //< the max downscale factor (must be power of two), last downscale level - unsigned int _levels = 0; //< the number of downscale levels in the mipmapped array - size_t _width = 0; //< original image buffer width (no downscale) - size_t _height = 0; //< original image buffer heigh (no downscale) + cudaMipmappedArray_t _mipmappedArray = nullptr; //< mipmapped array in device memory + cudaTextureObject_t _textureObject = 0; //< mipmapped array texture object with normalized coordinates + unsigned int _minDownscale = 0; //< the min downscale factor (must be power of two), first downscale level + unsigned int _maxDownscale = 0; //< the max downscale factor (must be power of two), last downscale level + unsigned int _levels = 0; //< the number of downscale levels in the mipmapped array + size_t _width = 0; //< original image buffer width (no downscale) + size_t _height = 0; //< original image buffer heigh (no downscale) }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.cpp b/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.cpp index c582eea9fa..5a6922b2b7 100644 --- a/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.cpp +++ b/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.cpp @@ -6,51 +6,46 @@ #include "DeviceStreamManager.hpp" -#include +#include namespace aliceVision { namespace depthMap { -DeviceStreamManager::DeviceStreamManager(int nbStreams) - : _nbStreams(nbStreams) +DeviceStreamManager::DeviceStreamManager(int nbStreams) + : _nbStreams(nbStreams) { assert(nbStreams > 0); _streams.resize(nbStreams); - for(int i = 0; i < nbStreams; ++i) + for (int i = 0; i < nbStreams; ++i) { cudaError_t err = cudaStreamCreate(&_streams.at(i)); - if(err != cudaSuccess) + if (err != cudaSuccess) { - ALICEVISION_LOG_WARNING("DeviceStreamManager: Failed to create a CUDA stream object " << i << "/" << nbStreams << ", " << cudaGetErrorString(err)); + ALICEVISION_LOG_WARNING("DeviceStreamManager: Failed to create a CUDA stream object " << i << "/" << nbStreams << ", " + << cudaGetErrorString(err)); _streams.at(i) = 0; } } } -DeviceStreamManager::~DeviceStreamManager() +DeviceStreamManager::~DeviceStreamManager() { - for(cudaStream_t& stream : _streams) + for (cudaStream_t& stream : _streams) { cudaStreamSynchronize(stream); - if(stream != 0) + if (stream != 0) { cudaStreamDestroy(stream); } } } -cudaStream_t DeviceStreamManager::getStream(int streamIndex) -{ - return _streams.at(streamIndex % _nbStreams); -} +cudaStream_t DeviceStreamManager::getStream(int streamIndex) { return _streams.at(streamIndex % _nbStreams); } -void DeviceStreamManager::waitStream(int streamIndex) -{ - cudaStreamSynchronize(getStream(streamIndex)); -} +void DeviceStreamManager::waitStream(int streamIndex) { cudaStreamSynchronize(getStream(streamIndex)); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.hpp b/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.hpp index e4944a9edd..577bf1cc40 100644 --- a/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.hpp +++ b/src/aliceVision/depthMap/cuda/host/DeviceStreamManager.hpp @@ -19,8 +19,7 @@ namespace depthMap { */ class DeviceStreamManager { -public: - + public: /** * @brief DeviceStreamManager constructor. * @param[in] nbStreams the number of gpu streams managed @@ -46,21 +45,20 @@ class DeviceStreamManager * @brief Get the stream object associated with the given index. * @param[in] streamIndex the stream index in the DeviceStreamManager * @note if streamIndex > nbStream, this function returns the stream object associated with streamIndex % nbStream - * @return the associated stream object + * @return the associated stream object */ cudaStream_t getStream(int streamIndex); /** - * @brief Waits for stream tasks to complete. + * @brief Waits for stream tasks to complete. * @param[in] streamIndex the stream index in the DeviceStreamManager */ void waitStream(int streamIndex); -private: - + private: const int _nbStreams; std::vector _streams; }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/LRUCache.hpp b/src/aliceVision/depthMap/cuda/host/LRUCache.hpp index baefe2c3bf..21e9d5ebfa 100644 --- a/src/aliceVision/depthMap/cuda/host/LRUCache.hpp +++ b/src/aliceVision/depthMap/cuda/host/LRUCache.hpp @@ -26,30 +26,30 @@ namespace depthMap { * The LRUCache class may be more generally useful, for example for the host-sided * image cache. */ -template +template class LRUCache { -public: - LRUCache( int size ) - : _owner( size, -1 ) - , _max_size( size ) - { } + public: + LRUCache(int size) + : _owner(size, -1), + _max_size(size) + {} - LRUCache( ) - : _max_size( 0 ) - { } + LRUCache() + : _max_size(0) + {} - void resize( int size ) + void resize(int size) { _max_size = size; - _owner.resize( size, -1 ); + _owner.resize(size, -1); } - inline int getIndex( const T& val ) + inline int getIndex(const T& val) { - typename std::vector::iterator o_it = std::find( _owner.begin(), _owner.end(), val ); - if( o_it != _owner.end() ) - return ( o_it - _owner.begin() ); + typename std::vector::iterator o_it = std::find(_owner.begin(), _owner.end(), val); + if (o_it != _owner.end()) + return (o_it - _owner.begin()); else return -1; } @@ -65,9 +65,9 @@ class LRUCache * oldVal contains the value that was removed from the index set. * The replacement strategy is LRU. */ - inline bool insert( const T& val, int& position, T& oldVal ) + inline bool insert(const T& val, int& position, T& oldVal) { - if( _max_size == 0 ) + if (_max_size == 0) { std::stringstream ss; ss << __FILE__ << ":" << __LINE__ << " ERROR: LRUCache max size is 0, so we cannot insert any element." << std::endl; @@ -76,21 +76,21 @@ class LRUCache int cell; typename std::vector::iterator o_it; - - o_it = std::find( _owner.begin(), _owner.end(), val ); - if( o_it != _owner.end() ) + + o_it = std::find(_owner.begin(), _owner.end(), val); + if (o_it != _owner.end()) { oldVal = -1; - typename std::list::iterator c_it = std::find( _cache.begin(), _cache.end(), val ); - _cache.erase( c_it ); - _cache.push_back( val ); - position = ( o_it - _owner.begin() ); + typename std::list::iterator c_it = std::find(_cache.begin(), _cache.end(), val); + _cache.erase(c_it); + _cache.push_back(val); + position = (o_it - _owner.begin()); return false; } - else if( ( cell = _cache.size() ) < _max_size ) + else if ((cell = _cache.size()) < _max_size) { oldVal = -1; - _cache.push_back( val ); + _cache.push_back(val); _owner[cell] = val; position = cell; return true; @@ -99,47 +99,44 @@ class LRUCache { oldVal = _cache.front(); _cache.pop_front(); - _cache.push_back( val ); + _cache.push_back(val); - o_it = std::find( _owner.begin(), _owner.end(), oldVal ); + o_it = std::find(_owner.begin(), _owner.end(), oldVal); *o_it = val; - position = ( o_it - _owner.begin() ); + position = (o_it - _owner.begin()); return true; } } - inline bool insert( const T& val, int* position ) + inline bool insert(const T& val, int* position) { T dummy; - return insert( val, *position, dummy ); + return insert(val, *position, dummy); } - inline bool insert( const T& val, int* position, T* oldVal ) - { - return insert( val, *position, *oldVal ); - } + inline bool insert(const T& val, int* position, T* oldVal) { return insert(val, *position, *oldVal); } inline void clear() { _cache.clear(); - _owner.assign( _max_size, -1 ); + _owner.assign(_max_size, -1); } - inline std::ostream& dump( std::ostream& ostr ) + inline std::ostream& dump(std::ostream& ostr) { - typename std::ostream_iterator out_it( ostr ," "); - std::copy( _cache.begin(), _cache.end(), out_it ); + typename std::ostream_iterator out_it(ostr, " "); + std::copy(_cache.begin(), _cache.end(), out_it); return ostr; } -private: - std::list _cache; + private: + std::list _cache; std::vector _owner; - int _max_size; + int _max_size; }; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision /* * a little test program: @@ -148,17 +145,17 @@ class LRUCache * #include * #include * #include "lrucache.hpp" - * + * * using namespace aliceVision::depthMap; - * + * * int main() * { * LRUCache cache(10); * int oldValue, position; - * + * * std::vector v(100); * generate( v.begin(), v.end(), [](){ return (int)(rand()%20); } ); - * + * * for( auto newValue : v ) { * if( cache.insert( newValue, position, oldValue ) ) { * cout << newValue << " inserted at " << position; diff --git a/src/aliceVision/depthMap/cuda/host/LRUCameraCache.hpp b/src/aliceVision/depthMap/cuda/host/LRUCameraCache.hpp index 41cf392471..3cd51dbacf 100644 --- a/src/aliceVision/depthMap/cuda/host/LRUCameraCache.hpp +++ b/src/aliceVision/depthMap/cuda/host/LRUCameraCache.hpp @@ -18,9 +18,15 @@ namespace depthMap { */ struct CameraPair : public std::pair { - CameraPair() : std::pair(0, 0) {} - CameraPair(int i) : std::pair(i, i) {} - CameraPair(int i, int j) : std::pair(i, j) {} + CameraPair() + : std::pair(0, 0) + {} + CameraPair(int i) + : std::pair(i, i) + {} + CameraPair(int i, int j) + : std::pair(i, j) + {} CameraPair& operator=(int i) { @@ -29,18 +35,12 @@ struct CameraPair : public std::pair } }; -inline bool operator==(const CameraPair& l, const CameraPair& r) -{ - return (l.first == r.first && l.second == r.second); -} +inline bool operator==(const CameraPair& l, const CameraPair& r) { return (l.first == r.first && l.second == r.second); } -inline bool operator<(const CameraPair& l, const CameraPair& r) -{ - return (l.first < r.first || (l.first == r.first && l.second < r.second)); -} +inline bool operator<(const CameraPair& l, const CameraPair& r) { return (l.first < r.first || (l.first == r.first && l.second < r.second)); } using LRUCameraCache = LRUCache; using LRUCameraIdCache = LRUCache; -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/divUp.hpp b/src/aliceVision/depthMap/cuda/host/divUp.hpp index 2e1488fbdb..cc0f6eb3d4 100644 --- a/src/aliceVision/depthMap/cuda/host/divUp.hpp +++ b/src/aliceVision/depthMap/cuda/host/divUp.hpp @@ -15,11 +15,7 @@ namespace depthMap { * @param[in] b an integer value * @return nearest higher integer value of round a / b. */ -__host__ inline unsigned int divUp(unsigned int a, unsigned int b) -{ - return (a % b != 0) ? (a / b + 1) : (a / b); -} - -} // namespace depthMap -} // namespace aliceVision +__host__ inline unsigned int divUp(unsigned int a, unsigned int b) { return (a % b != 0) ? (a / b + 1) : (a / b); } +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/memory.hpp b/src/aliceVision/depthMap/cuda/host/memory.hpp index 40894139b7..294259fc06 100644 --- a/src/aliceVision/depthMap/cuda/host/memory.hpp +++ b/src/aliceVision/depthMap/cuda/host/memory.hpp @@ -11,8 +11,8 @@ #define ALICEVISION_DEPTHMAP_TEXTURE_USE_INTERPOLATION #ifdef ALICEVISION_DEPTHMAP_TEXTURE_USE_HALF -#define CUDA_NO_HALF -#include + #define CUDA_NO_HALF + #include #endif #include @@ -36,54 +36,61 @@ namespace depthMap { using CudaColorBaseType = unsigned char; using CudaRGBA = uchar4; #else -#ifdef ALICEVISION_DEPTHMAP_TEXTURE_USE_HALF -struct CudaRGBA { __half x, y, z, w; }; + #ifdef ALICEVISION_DEPTHMAP_TEXTURE_USE_HALF +struct CudaRGBA +{ + __half x, y, z, w; +}; using CudaColorBaseType = __half; -#else + #else using CudaColorBaseType = float; -using CudaRGBA = float4; -#endif // ALICEVISION_DEPTHMAP_TEXTURE_USE_HALF -#endif // ALICEVISION_DEPTHMAP_TEXTURE_USE_UCHAR +using CudaRGBA = float4; + #endif // ALICEVISION_DEPTHMAP_TEXTURE_USE_HALF +#endif // ALICEVISION_DEPTHMAP_TEXTURE_USE_UCHAR /********************************************************************************* * forward declarations *********************************************************************************/ -template class CudaDeviceMemoryPitched; +template +class CudaDeviceMemoryPitched; /********************************************************************************* * CudaSizeBase *********************************************************************************/ -template class CudaSizeBase +template +class CudaSizeBase { -public: - CudaSizeBase() - { - #pragma unroll - for(int i = Dim; i--;) - size[i] = 0; - } - inline size_t operator[](size_t i) const { return size[i]; } - inline size_t &operator[](size_t i) { return size[i]; } - inline CudaSizeBase operator+(const CudaSizeBase &s) const { - CudaSizeBase r; + public: + CudaSizeBase() + { +#pragma unroll + for (int i = Dim; i--;) + size[i] = 0; + } + inline size_t operator[](size_t i) const { return size[i]; } + inline size_t& operator[](size_t i) { return size[i]; } + inline CudaSizeBase operator+(const CudaSizeBase& s) const + { + CudaSizeBase r; - #pragma unroll - for(size_t i = Dim; i--;) - r[i] = (*this)[i] + s[i]; +#pragma unroll + for (size_t i = Dim; i--;) + r[i] = (*this)[i] + s[i]; - return r; - } - inline CudaSizeBase operator-(const CudaSizeBase &s) const { - CudaSizeBase r; + return r; + } + inline CudaSizeBase operator-(const CudaSizeBase& s) const + { + CudaSizeBase r; - #pragma unroll - for(size_t i = Dim; i--;) - r[i] = (*this)[i] - s[i]; +#pragma unroll + for (size_t i = Dim; i--;) + r[i] = (*this)[i] - s[i]; - return r; - } + return r; + } #if 0 inline size_t getSize() const { @@ -97,116 +104,128 @@ template class CudaSizeBase } #endif -protected: - size_t size[Dim]; + protected: + size_t size[Dim]; }; -template -bool operator==(const CudaSizeBase &s1, const CudaSizeBase &s2) +template +bool operator==(const CudaSizeBase& s1, const CudaSizeBase& s2) { - for(int i = Dim; i--;) - if(s1[i] != s2[i]) - return false; + for (int i = Dim; i--;) + if (s1[i] != s2[i]) + return false; - return true; + return true; } -template -bool operator!=(const CudaSizeBase &s1, const CudaSizeBase &s2) +template +bool operator!=(const CudaSizeBase& s1, const CudaSizeBase& s2) { - for(size_t i = Dim; i--;) - if(s1[i] != s2[i]) - return true; + for (size_t i = Dim; i--;) + if (s1[i] != s2[i]) + return true; - return false; + return false; } /********************************************************************************* * CudaSize *********************************************************************************/ -template -class CudaSize: public CudaSizeBase +template +class CudaSize : public CudaSizeBase { CudaSize() {} }; -template <> -class CudaSize<1>: public CudaSizeBase<1> +template<> +class CudaSize<1> : public CudaSizeBase<1> { -public: + public: CudaSize() {} explicit CudaSize(size_t s0) { size[0] = s0; } }; -template <> -class CudaSize<2>: public CudaSizeBase<2> +template<> +class CudaSize<2> : public CudaSizeBase<2> { -public: + public: CudaSize() {} - CudaSize(size_t s0, size_t s1) { size[0] = s0; size[1] = s1; } + CudaSize(size_t s0, size_t s1) + { + size[0] = s0; + size[1] = s1; + } inline size_t x() const { return size[0]; } inline size_t y() const { return size[1]; } }; -template <> -class CudaSize<3>: public CudaSizeBase<3> +template<> +class CudaSize<3> : public CudaSizeBase<3> { -public: + public: CudaSize() {} - CudaSize(size_t s0, size_t s1, size_t s2) { size[0] = s0; size[1] = s1; size[2] = s2; } + CudaSize(size_t s0, size_t s1, size_t s2) + { + size[0] = s0; + size[1] = s1; + size[2] = s2; + } inline size_t x() const { return size[0]; } inline size_t y() const { return size[1]; } inline size_t z() const { return size[2]; } }; -template -CudaSize operator/(const CudaSize &lhs, const float &rhs) { - if (rhs == 0) - fprintf(stderr, "Division by zero!!\n"); - CudaSize out = lhs; - for(size_t i = 0; i < Dim; ++i) - out[i] /= rhs; +template +CudaSize operator/(const CudaSize& lhs, const float& rhs) +{ + if (rhs == 0) + fprintf(stderr, "Division by zero!!\n"); + CudaSize out = lhs; + for (size_t i = 0; i < Dim; ++i) + out[i] /= rhs; - return out; + return out; } -template -CudaSize operator-(const CudaSize &lhs, const CudaSize &rhs) { - CudaSize out = lhs; - for(size_t i = Dim; i--;) - out[i]-= rhs[i]; - return out; +template +CudaSize operator-(const CudaSize& lhs, const CudaSize& rhs) +{ + CudaSize out = lhs; + for (size_t i = Dim; i--;) + out[i] -= rhs[i]; + return out; } /********************************************************************************* * CudaMemorySizeBase *********************************************************************************/ -template class CudaMemorySizeBase +template +class CudaMemorySizeBase { CudaSize _size; - size_t _pitch; -public: - CudaMemorySizeBase( ) - { } + size_t _pitch; + + public: + CudaMemorySizeBase() {} - explicit CudaMemorySizeBase( const CudaSize &size ) - : _size( size ) - , _pitch( size[0] * sizeof(Type) ) - { } + explicit CudaMemorySizeBase(const CudaSize& size) + : _size(size), + _pitch(size[0] * sizeof(Type)) + {} /* Initialize or change the contained _size value. As a * convenience for the many subclasses whose pitch is always * size[0] * sizeof(Type), true can be passed as second * parameter. */ - void setSize( const CudaSize &size, bool computePitch ) + void setSize(const CudaSize& size, bool computePitch) { _size = size; - if( computePitch ) + if (computePitch) { _pitch = size[0] * sizeof(Type); } @@ -215,19 +234,13 @@ template class CudaMemorySizeBase /* Return the Size struct. * It is best to use this as an opaque type. */ - inline const CudaSize& getSize() const - { - return _size; - } + inline const CudaSize& getSize() const { return _size; } /* Return the byte size of dimension 0 with padding. * This function may return useless info until the * actual pitch has been initiated by the subclass. */ - inline size_t getPitch() const - { - return _pitch; - } + inline size_t getPitch() const { return _pitch; } /* Return the number of bytes that are required by the data * contained in the subclass. The pitch may be different. @@ -236,7 +249,8 @@ template class CudaMemorySizeBase inline size_t getBytesUnpadded() const { size_t prod = _size[0] * sizeof(Type); - for( int i=1; i class CudaMemorySizeBase inline size_t getBytesPadded() const { size_t prod = _pitch; - for( int i=1; i= Dim, return 1. */ - inline size_t getUnitsInDim( int dim ) const - { - return ( dim < Dim ? _size[dim] : 1 ); - } + inline size_t getUnitsInDim(int dim) const { return (dim < Dim ? _size[dim] : 1); } /* For dim 0, return the pitch. * For all other dimensions, return the number of units in that dimension. */ - inline size_t getPaddedBytesInRow( ) const - { - return getPitch(); - } + inline size_t getPaddedBytesInRow() const { return getPitch(); } /* For dim 0, return the number of meaning bytes. * For all other dimensions, return the number of units in that dimension. */ - inline size_t getUnpaddedBytesInRow( ) const - { - return _size[0] * sizeof(Type); - } + inline size_t getUnpaddedBytesInRow() const { return _size[0] * sizeof(Type); } /* Return the number of bytes that are required for an n-dimensional * slice of the subclass, always starting at dimension 0. * * Note that "dim" itself is included in the computation. */ - inline size_t getBytesPaddedUpToDim( int dim ) const + inline size_t getBytesPaddedUpToDim(int dim) const { size_t prod = _pitch; - for( int i=1; i<=dim; i++ ) prod *= getUnitsInDim(i); + for (int i = 1; i <= dim; i++) + prod *= getUnitsInDim(i); return prod; } -protected: + protected: /* Use to set the pitch when it has been returned by an allocation * function. */ - inline void setPitch( size_t pitch ) - { - _pitch = pitch; - } + inline void setPitch(size_t pitch) { _pitch = pitch; } /* Allows the child class to pass the pitch to functions such as * CudaMallocPitched for initialization to the true value. */ - inline size_t& getPitchRef() - { - return _pitch; - } + inline size_t& getPitchRef() { return _pitch; } }; /********************************************************************************* * CudaHostMemoryHeap *********************************************************************************/ -template class CudaHostMemoryHeap : public CudaMemorySizeBase +template +class CudaHostMemoryHeap : public CudaMemorySizeBase { Type* buffer = nullptr; -public: - CudaHostMemoryHeap( ) - : buffer( nullptr ) - { } - explicit CudaHostMemoryHeap(const CudaSize &size ) - : buffer( nullptr ) + public: + CudaHostMemoryHeap() + : buffer(nullptr) + {} + + explicit CudaHostMemoryHeap(const CudaSize& size) + : buffer(nullptr) { - allocate( size ); + allocate(size); } - CudaHostMemoryHeap& operator=(const CudaHostMemoryHeap& rhs) + CudaHostMemoryHeap& operator=(const CudaHostMemoryHeap& rhs) { - if( buffer != nullptr ) + if (buffer != nullptr) { - allocate( rhs.getSize() ); + allocate(rhs.getSize()); } - else if( this->getSize() != rhs.getSize() ) + else if (this->getSize() != rhs.getSize()) { deallocate(); - allocate( rhs.getSize() ); + allocate(rhs.getSize()); } - memcpy(buffer, rhs.buffer, rhs.getBytesPadded() ); + memcpy(buffer, rhs.buffer, rhs.getBytesPadded()); return *this; } - ~CudaHostMemoryHeap() - { - deallocate(); - } + ~CudaHostMemoryHeap() { deallocate(); } - void initBuffer() - { - memset(buffer, 0, this->getBytesPadded()); - } + void initBuffer() { memset(buffer, 0, this->getBytesPadded()); } // see below with copy() functions - void copyFrom( const CudaDeviceMemoryPitched& src, cudaStream_t stream = 0); + void copyFrom(const CudaDeviceMemoryPitched& src, cudaStream_t stream = 0); - inline Type *getBuffer() - { - return buffer; - } - inline const Type *getBuffer() const - { - return buffer; - } - inline Type& operator()(size_t x) - { - return buffer[x]; - } - inline const Type& operator()(size_t x) const - { - return buffer[x]; - } - inline Type& operator()(size_t x, size_t y) - { - return getRow(y)[x]; - } - inline const Type& operator()(size_t x, size_t y) const - { - return getRow(y)[x]; - } + inline Type* getBuffer() { return buffer; } + inline const Type* getBuffer() const { return buffer; } + inline Type& operator()(size_t x) { return buffer[x]; } + inline const Type& operator()(size_t x) const { return buffer[x]; } + inline Type& operator()(size_t x, size_t y) { return getRow(y)[x]; } + inline const Type& operator()(size_t x, size_t y) const { return getRow(y)[x]; } - inline unsigned char* getBytePtr() - { - return (unsigned char*)buffer; - } - inline const unsigned char* getBytePtr() const - { - return (unsigned char*)buffer; - } + inline unsigned char* getBytePtr() { return (unsigned char*)buffer; } + inline const unsigned char* getBytePtr() const { return (unsigned char*)buffer; } -private: + private: inline Type* getRow(size_t row) { unsigned char* ptr = getBytePtr(); @@ -411,19 +385,20 @@ template class CudaHostMemoryHeap : public CudaMemory return (Type*)ptr; } -public: - void allocate( const CudaSize &size ) + public: + void allocate(const CudaSize& size) { - this->setSize( size, true ); + this->setSize(size, true); - cudaError_t err = cudaMallocHost( &buffer, this->getBytesUnpadded() ); + cudaError_t err = cudaMallocHost(&buffer, this->getBytesUnpadded()); - THROW_ON_CUDA_ERROR( err, "Could not allocate pinned host memory in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err) ); + THROW_ON_CUDA_ERROR(err, "Could not allocate pinned host memory in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err)); } - void deallocate( ) + void deallocate() { - if( buffer == nullptr ) return; + if (buffer == nullptr) + return; cudaFreeHost(buffer); buffer = nullptr; } @@ -433,141 +408,121 @@ template class CudaHostMemoryHeap : public CudaMemory * CudaDeviceMemoryPitched *********************************************************************************/ -template class CudaDeviceMemoryPitched : public CudaMemorySizeBase +template +class CudaDeviceMemoryPitched : public CudaMemorySizeBase { Type* buffer = nullptr; -public: - CudaDeviceMemoryPitched( ) - : buffer( nullptr ) - { } - explicit CudaDeviceMemoryPitched(const CudaSize &size) - { - allocate( size ); - } + public: + CudaDeviceMemoryPitched() + : buffer(nullptr) + {} - explicit CudaDeviceMemoryPitched(const CudaHostMemoryHeap &rhs) - { - allocate( rhs.getSize() ); - copyFrom( rhs ); - } + explicit CudaDeviceMemoryPitched(const CudaSize& size) { allocate(size); } - ~CudaDeviceMemoryPitched() + explicit CudaDeviceMemoryPitched(const CudaHostMemoryHeap& rhs) { - deallocate(); + allocate(rhs.getSize()); + copyFrom(rhs); } - CudaDeviceMemoryPitched& operator=(const CudaDeviceMemoryPitched & rhs) + ~CudaDeviceMemoryPitched() { deallocate(); } + + CudaDeviceMemoryPitched& operator=(const CudaDeviceMemoryPitched& rhs) { - if( buffer == nullptr ) + if (buffer == nullptr) { - allocate( rhs.getSize() ); + allocate(rhs.getSize()); } - else if( this->getSize() != rhs.getSize() ) + else if (this->getSize() != rhs.getSize()) { - deallocate( ); - allocate( rhs.getSize() ); + deallocate(); + allocate(rhs.getSize()); } - copyFrom( rhs ); + copyFrom(rhs); return *this; } // see below with copy() functions - void copyFrom( const CudaDeviceMemoryPitched& src, cudaStream_t stream = 0 ); - void copyFrom( const CudaHostMemoryHeap& src, cudaStream_t stream = 0 ); - void copyFrom( const Type* src, size_t sx, size_t sy ); - + void copyFrom(const CudaDeviceMemoryPitched& src, cudaStream_t stream = 0); + void copyFrom(const CudaHostMemoryHeap& src, cudaStream_t stream = 0); + void copyFrom(const Type* src, size_t sx, size_t sy); - void copyTo( Type* dst, size_t sx, size_t sy ) const; + void copyTo(Type* dst, size_t sx, size_t sy) const; - Type* getBuffer() - { - return buffer; - } + Type* getBuffer() { return buffer; } - const Type* getBuffer() const - { - return buffer; - } + const Type* getBuffer() const { return buffer; } - Type& operator()(size_t x) - { - return buffer[x]; - } + Type& operator()(size_t x) { return buffer[x]; } Type& operator()(size_t x, size_t y) { - Type* row = getRow( y ); + Type* row = getRow(y); return row[x]; } - inline unsigned char* getBytePtr() - { - return (unsigned char*)buffer; - } + inline unsigned char* getBytePtr() { return (unsigned char*)buffer; } - inline const unsigned char* getBytePtr() const - { - return (unsigned char*)buffer; - } + inline const unsigned char* getBytePtr() const { return (unsigned char*)buffer; } - inline Type* getRow( size_t row ) + inline Type* getRow(size_t row) { unsigned char* ptr = getBytePtr(); ptr += row * this->getPitch(); return (Type*)ptr; } - void allocate( const CudaSize& size ) + void allocate(const CudaSize& size) { - this->setSize( size, false ); + this->setSize(size, false); - if(Dim == 2) + if (Dim == 2) { - cudaError_t err = cudaMallocPitch(&buffer, - &this->getPitchRef(), - this->getUnpaddedBytesInRow(), - this->getUnitsInDim(1) ); - if( err != cudaSuccess ) + cudaError_t err = cudaMallocPitch(&buffer, &this->getPitchRef(), this->getUnpaddedBytesInRow(), this->getUnitsInDim(1)); + if (err != cudaSuccess) { int devid; - cudaGetDevice( &devid ); + cudaGetDevice(&devid); std::stringstream ss; ss << "Could not allocate pitched device memory.\n" - << "Device " << devid << " alloc " << this->getBytesUnpadded() << " bytes failed in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err); + << "Device " << devid << " alloc " << this->getBytesUnpadded() << " bytes failed in " << __FILE__ << ":" << __LINE__ << ", " + << cudaGetErrorString(err); throw std::runtime_error(ss.str()); } } - else if(Dim == 3) + else if (Dim == 3) { cudaExtent extent; - extent.width = this->getUnpaddedBytesInRow(); + extent.width = this->getUnpaddedBytesInRow(); extent.height = this->getUnitsInDim(1); - extent.depth = this->getUnitsInDim(2); + extent.depth = this->getUnitsInDim(2); cudaPitchedPtr pitchDevPtr; cudaError_t err = cudaMalloc3D(&pitchDevPtr, extent); - if( err != cudaSuccess ) + if (err != cudaSuccess) { int devid; - cudaGetDevice( &devid ); + cudaGetDevice(&devid); size_t bytes = this->getBytesUnpadded(); - size_t sx = this->getUnitsInDim(0); - size_t sy = this->getUnitsInDim(1); - size_t sz = this->getUnitsInDim(2); + size_t sx = this->getUnitsInDim(0); + size_t sy = this->getUnitsInDim(1); + size_t sz = this->getUnitsInDim(2); std::stringstream ss; ss << "Could not allocate 3D device memory.\n" - << "Device " << devid << " alloc " - << sx << "x" << sy << "x" << sz << "x" << sizeof(Type) << " = " - << bytes << " bytes (" - << (int)(bytes/1024.0f/1024.0f) << " MB) failed in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err); + << "Device " << devid << " alloc " << sx << "x" << sy << "x" << sz << "x" << sizeof(Type) << " = " << bytes << " bytes (" + << (int)(bytes / 1024.0f / 1024.0f) << " MB) failed in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err); throw std::runtime_error(ss.str()); } buffer = (Type*)pitchDevPtr.ptr; - this->setPitch( pitchDevPtr.pitch ); - - ALICEVISION_LOG_DEBUG("GPU 3D allocation: " << this->getUnitsInDim(0) << "x" << this->getUnitsInDim(1) << "x" << this->getUnitsInDim(2) << ", type size=" << sizeof(Type) << ", pitch=" << pitchDevPtr.pitch); - ALICEVISION_LOG_DEBUG(" : " << this->getBytesUnpadded() << ", padded=" << this->getBytesPadded() << ", wasted=" << this->getBytesPadded() - this->getBytesUnpadded() << ", wasted ratio=" << ((this->getBytesPadded() - this->getBytesUnpadded()) / double(this->getBytesUnpadded())) * 100.0 << "%"); + this->setPitch(pitchDevPtr.pitch); + + ALICEVISION_LOG_DEBUG("GPU 3D allocation: " << this->getUnitsInDim(0) << "x" << this->getUnitsInDim(1) << "x" << this->getUnitsInDim(2) + << ", type size=" << sizeof(Type) << ", pitch=" << pitchDevPtr.pitch); + ALICEVISION_LOG_DEBUG(" : " + << this->getBytesUnpadded() << ", padded=" << this->getBytesPadded() + << ", wasted=" << this->getBytesPadded() - this->getBytesUnpadded() << ", wasted ratio=" + << ((this->getBytesPadded() - this->getBytesUnpadded()) / double(this->getBytesUnpadded())) * 100.0 << "%"); } else { @@ -577,10 +532,11 @@ template class CudaDeviceMemoryPitched : public CudaM void deallocate() { - if( buffer == nullptr ) return; + if (buffer == nullptr) + return; cudaError_t err = cudaFree(buffer); - if( err != cudaSuccess ) + if (err != cudaSuccess) { std::stringstream ss; ss << "CudaDeviceMemoryPitched: Device free failed, " << cudaGetErrorString(err); @@ -595,122 +551,97 @@ template class CudaDeviceMemoryPitched : public CudaM * CudaDeviceMemory *********************************************************************************/ -template class CudaDeviceMemory : public CudaMemorySizeBase +template +class CudaDeviceMemory : public CudaMemorySizeBase { Type* buffer = nullptr; -public: - CudaDeviceMemory() - : buffer( nullptr ) - { } + public: + CudaDeviceMemory() + : buffer(nullptr) + {} - explicit CudaDeviceMemory(const size_t size) - { - allocate( size ); - } + explicit CudaDeviceMemory(const size_t size) { allocate(size); } - explicit inline CudaDeviceMemory(const CudaHostMemoryHeap &rhs) + explicit inline CudaDeviceMemory(const CudaHostMemoryHeap& rhs) { - allocate( rhs.getSize() ); + allocate(rhs.getSize()); copy(*this, rhs); } // constructor with synchronous copy - CudaDeviceMemory(const Type* inbuf, const size_t size ) + CudaDeviceMemory(const Type* inbuf, const size_t size) { - allocate( size ); - copyFrom( inbuf, size ); + allocate(size); + copyFrom(inbuf, size); } // constructor with asynchronous copy - CudaDeviceMemory(const Type* inbuf, const size_t size, cudaStream_t stream ) + CudaDeviceMemory(const Type* inbuf, const size_t size, cudaStream_t stream) { - allocate( size ); - copyFrom( inbuf, size, stream ); + allocate(size); + copyFrom(inbuf, size, stream); } - ~CudaDeviceMemory() - { - deallocate( ); - } + ~CudaDeviceMemory() { deallocate(); } - CudaDeviceMemory & operator=(const CudaDeviceMemory & rhs) + CudaDeviceMemory& operator=(const CudaDeviceMemory& rhs) { - if( buffer == nullptr ) + if (buffer == nullptr) { - allocate( rhs.getSize() ); + allocate(rhs.getSize()); } - else if( this->getSize() != rhs.getSize() ) + else if (this->getSize() != rhs.getSize()) { - deallocate( ); - allocate( rhs.getSize() ); + deallocate(); + allocate(rhs.getSize()); } copy(*this, rhs); return *this; } - Type *getBuffer() - { - return buffer; - } - const Type *getBuffer() const - { - return buffer; - } + Type* getBuffer() { return buffer; } + const Type* getBuffer() const { return buffer; } - unsigned char* getBytePtr() - { - return (unsigned char*)buffer; - } - const unsigned char* getBytePtr() const - { - return (unsigned char*)buffer; - } + unsigned char* getBytePtr() { return (unsigned char*)buffer; } + const unsigned char* getBytePtr() const { return (unsigned char*)buffer; } - void allocate( const CudaSize<1> &size ) + void allocate(const CudaSize<1>& size) { - this->setSize( size, true ); + this->setSize(size, true); - cudaError_t err = cudaMalloc(&buffer, this->getBytesUnpadded() ); + cudaError_t err = cudaMalloc(&buffer, this->getBytesUnpadded()); - THROW_ON_CUDA_ERROR( err, "Could not allocate pinned host memory in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err) ); - } - void allocate( const size_t size ) - { - allocate(CudaSize<1>(size)); + THROW_ON_CUDA_ERROR(err, "Could not allocate pinned host memory in " << __FILE__ << ":" << __LINE__ << ", " << cudaGetErrorString(err)); } + void allocate(const size_t size) { allocate(CudaSize<1>(size)); } void deallocate() { - if(buffer == nullptr) - return; + if (buffer == nullptr) + return; CHECK_CUDA_RETURN_ERROR(cudaFree(buffer)); buffer = nullptr; } - void copyFrom( const Type* inbuf, const size_t num ) + void copyFrom(const Type* inbuf, const size_t num) { cudaMemcpyKind kind = cudaMemcpyHostToDevice; - cudaError_t err = cudaMemcpy( buffer, - inbuf, - num * sizeof(Type), - kind ); + cudaError_t err = cudaMemcpy(buffer, inbuf, num * sizeof(Type), kind); - THROW_ON_CUDA_ERROR( err, "Failed to copy from flat host buffer to CudaDeviceMemory in " << __FILE__ << ":" << __LINE__ << ": " << cudaGetErrorString(err) ); + THROW_ON_CUDA_ERROR( + err, "Failed to copy from flat host buffer to CudaDeviceMemory in " << __FILE__ << ":" << __LINE__ << ": " << cudaGetErrorString(err)); } - void copyFrom( const Type* inbuf, const size_t num, cudaStream_t stream ) + void copyFrom(const Type* inbuf, const size_t num, cudaStream_t stream) { cudaMemcpyKind kind = cudaMemcpyHostToDevice; - cudaError_t err = cudaMemcpyAsync( buffer, - inbuf, - num * sizeof(Type), - kind, - stream ); + cudaError_t err = cudaMemcpyAsync(buffer, inbuf, num * sizeof(Type), kind, stream); - THROW_ON_CUDA_ERROR( err, "Failed to copy from flat host buffer to CudaDeviceMemory in " << __FILE__ << ":" << __LINE__ << ": " << cudaGetErrorString(err) ); + THROW_ON_CUDA_ERROR( + err, "Failed to copy from flat host buffer to CudaDeviceMemory in " << __FILE__ << ":" << __LINE__ << ": " << cudaGetErrorString(err)); } }; @@ -723,110 +654,69 @@ void CudaDeviceMemoryPitched::copyFrom(const CudaDeviceMemoryPitched< { const cudaMemcpyKind kind = cudaMemcpyDeviceToDevice; cudaError_t err; - if(Dim == 1) + if (Dim == 1) { - if( stream == 0 ) - err = cudaMemcpy( this->getBytePtr(), - src.getBytePtr(), - src.getUnpaddedBytesInRow(), - kind ); + if (stream == 0) + err = cudaMemcpy(this->getBytePtr(), src.getBytePtr(), src.getUnpaddedBytesInRow(), kind); else - err = cudaMemcpyAsync( this->getBytePtr(), - src.getBytePtr(), - src.getUnpaddedBytesInRow(), - kind, - stream ); + err = cudaMemcpyAsync(this->getBytePtr(), src.getBytePtr(), src.getUnpaddedBytesInRow(), kind, stream); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } - else if(Dim >= 2) + else if (Dim >= 2) { size_t number_of_rows = 1; - for( int i=1; igetBytePtr(), - this->getPitch(), - src.getBytePtr(), - src.getPitch(), - src.getUnpaddedBytesInRow(), - number_of_rows, - kind ); + for (int i = 1; i < Dim; i++) + number_of_rows *= src.getUnitsInDim(i); + + if (stream == 0) + err = + cudaMemcpy2D(this->getBytePtr(), this->getPitch(), src.getBytePtr(), src.getPitch(), src.getUnpaddedBytesInRow(), number_of_rows, kind); else - err = cudaMemcpy2DAsync( this->getBytePtr(), - this->getPitch(), - src.getBytePtr(), - src.getPitch(), - src.getUnpaddedBytesInRow(), - number_of_rows, - kind, - stream ); + err = cudaMemcpy2DAsync( + this->getBytePtr(), this->getPitch(), src.getBytePtr(), src.getPitch(), src.getUnpaddedBytesInRow(), number_of_rows, kind, stream); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } } template -void CudaDeviceMemoryPitched::copyFrom( const CudaHostMemoryHeap& src, cudaStream_t stream ) +void CudaDeviceMemoryPitched::copyFrom(const CudaHostMemoryHeap& src, cudaStream_t stream) { const cudaMemcpyKind kind = cudaMemcpyHostToDevice; cudaError_t err; - if(Dim == 1) + if (Dim == 1) { - if( stream == 0 ) - err = cudaMemcpy( this->getBytePtr(), - src.getBytePtr(), - src.getBytesUnpadded(), - kind ); + if (stream == 0) + err = cudaMemcpy(this->getBytePtr(), src.getBytePtr(), src.getBytesUnpadded(), kind); else - err = cudaMemcpyAsync( this->getBytePtr(), - src.getBytePtr(), - src.getBytesUnpadded(), - kind, - stream ); + err = cudaMemcpyAsync(this->getBytePtr(), src.getBytePtr(), src.getBytesUnpadded(), kind, stream); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } - else if(Dim >= 2) + else if (Dim >= 2) { size_t number_of_rows = 1; - for( int i=1; igetBytePtr(), - this->getPitch(), - src.getBytePtr(), - src.getPitch(), - src.getUnpaddedBytesInRow(), - number_of_rows, - kind ); + for (int i = 1; i < Dim; i++) + number_of_rows *= src.getUnitsInDim(i); + + if (stream == 0) + err = + cudaMemcpy2D(this->getBytePtr(), this->getPitch(), src.getBytePtr(), src.getPitch(), src.getUnpaddedBytesInRow(), number_of_rows, kind); else - err = cudaMemcpy2DAsync( this->getBytePtr(), - this->getPitch(), - src.getBytePtr(), - src.getPitch(), - src.getUnpaddedBytesInRow(), - number_of_rows, - kind, - stream ); + err = cudaMemcpy2DAsync( + this->getBytePtr(), this->getPitch(), src.getBytePtr(), src.getPitch(), src.getUnpaddedBytesInRow(), number_of_rows, kind, stream); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } } - template -void CudaDeviceMemoryPitched::copyFrom( const Type* src, size_t sx, size_t sy ) +void CudaDeviceMemoryPitched::copyFrom(const Type* src, size_t sx, size_t sy) { - if(Dim == 2) + if (Dim == 2) { - const size_t src_pitch = sx * sizeof(Type); - const size_t src_width = sx * sizeof(Type); + const size_t src_pitch = sx * sizeof(Type); + const size_t src_width = sx * sizeof(Type); const size_t src_height = sy; - cudaError_t err = cudaMemcpy2D(this->getBytePtr(), - this->getPitch(), - src, - src_pitch, - src_width, - src_height, - cudaMemcpyHostToDevice); + cudaError_t err = cudaMemcpy2D(this->getBytePtr(), this->getPitch(), src, src_pitch, src_width, src_height, cudaMemcpyHostToDevice); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } @@ -837,62 +727,39 @@ void CudaHostMemoryHeap::copyFrom(const CudaDeviceMemoryPitchedgetBytePtr(), - src.getBytePtr(), - src.getUnpaddedBytesInRow(), - kind ); + if (stream == 0) + err = cudaMemcpy(this->getBytePtr(), src.getBytePtr(), src.getUnpaddedBytesInRow(), kind); else - err = cudaMemcpyAsync( this->getBytePtr(), - src.getBytePtr(), - src.getUnpaddedBytesInRow(), - kind, - stream ); + err = cudaMemcpyAsync(this->getBytePtr(), src.getBytePtr(), src.getUnpaddedBytesInRow(), kind, stream); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } - else if(Dim >= 2) + else if (Dim >= 2) { size_t number_of_rows = 1; - for( int i=1; igetBytePtr(), - this->getPitch(), - src.getBytePtr(), - src.getPitch(), - src.getUnpaddedBytesInRow(), - number_of_rows, - kind ); + for (int i = 1; i < Dim; i++) + number_of_rows *= src.getUnitsInDim(i); + + if (stream == 0) + err = + cudaMemcpy2D(this->getBytePtr(), this->getPitch(), src.getBytePtr(), src.getPitch(), src.getUnpaddedBytesInRow(), number_of_rows, kind); else - err = cudaMemcpy2DAsync( this->getBytePtr(), - this->getPitch(), - src.getBytePtr(), - src.getPitch(), - src.getUnpaddedBytesInRow(), - number_of_rows, - kind, - stream ); + err = cudaMemcpy2DAsync( + this->getBytePtr(), this->getPitch(), src.getBytePtr(), src.getPitch(), src.getUnpaddedBytesInRow(), number_of_rows, kind, stream); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } } template -void CudaDeviceMemoryPitched::copyTo( Type* dst, size_t sx, size_t sy ) const +void CudaDeviceMemoryPitched::copyTo(Type* dst, size_t sx, size_t sy) const { - if(Dim == 2) + if (Dim == 2) { - const size_t dst_pitch = sx * sizeof(Type); - const size_t dst_width = sx * sizeof(Type); + const size_t dst_pitch = sx * sizeof(Type); + const size_t dst_width = sx * sizeof(Type); const size_t dst_height = sy; - cudaError_t err = cudaMemcpy2D(dst, - dst_pitch, - this->getBytePtr(), - this->getPitch(), - dst_width, - dst_height, - cudaMemcpyDeviceToHost); + cudaError_t err = cudaMemcpy2D(dst, dst_pitch, this->getBytePtr(), this->getPitch(), dst_width, dst_height, cudaMemcpyDeviceToHost); THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } @@ -902,130 +769,104 @@ void CudaDeviceMemoryPitched::copyTo( Type* dst, size_t sx, size_t sy * copy functions *********************************************************************************/ -template void copy(CudaHostMemoryHeap& dst, const CudaDeviceMemoryPitched& src) +template +void copy(CudaHostMemoryHeap& dst, const CudaDeviceMemoryPitched& src) { - dst.copyFrom( src ); + dst.copyFrom(src); } -template void copy(CudaHostMemoryHeap& _dst, const CudaDeviceMemory& _src) +template +void copy(CudaHostMemoryHeap& _dst, const CudaDeviceMemory& _src) { - cudaMemcpyKind kind = cudaMemcpyDeviceToHost; - cudaError_t err = cudaMemcpy(_dst.getBytePtr(), - _src.getBytePtr(), - _dst.getUnpaddedBytesInRow(), - kind); - THROW_ON_CUDA_ERROR(err, "Failed to copy from CudaHostMemoryHeap to CudaDeviceMemory"); + cudaMemcpyKind kind = cudaMemcpyDeviceToHost; + cudaError_t err = cudaMemcpy(_dst.getBytePtr(), _src.getBytePtr(), _dst.getUnpaddedBytesInRow(), kind); + THROW_ON_CUDA_ERROR(err, "Failed to copy from CudaHostMemoryHeap to CudaDeviceMemory"); } -template void copy(CudaDeviceMemoryPitched& _dst, const CudaHostMemoryHeap& _src) +template +void copy(CudaDeviceMemoryPitched& _dst, const CudaHostMemoryHeap& _src) { - _dst.copyFrom( _src ); + _dst.copyFrom(_src); } -template void copy(CudaDeviceMemoryPitched& _dst, const CudaDeviceMemoryPitched& _src) +template +void copy(CudaDeviceMemoryPitched& _dst, const CudaDeviceMemoryPitched& _src) { - _dst.copyFrom( _src ); + _dst.copyFrom(_src); } -template void copy(CudaDeviceMemory& _dst, const CudaHostMemoryHeap& _src) +template +void copy(CudaDeviceMemory& _dst, const CudaHostMemoryHeap& _src) { - const cudaMemcpyKind kind = cudaMemcpyHostToDevice; - cudaError_t err = cudaMemcpy(_dst.getBytePtr(), - _src.getBytePtr(), - _src.getUnpaddedBytesInRow(), - kind); - THROW_ON_CUDA_ERROR(err, "Failed to copy from CudaHostMemoryHeap to CudaDeviceMemory"); + const cudaMemcpyKind kind = cudaMemcpyHostToDevice; + cudaError_t err = cudaMemcpy(_dst.getBytePtr(), _src.getBytePtr(), _src.getUnpaddedBytesInRow(), kind); + THROW_ON_CUDA_ERROR(err, "Failed to copy from CudaHostMemoryHeap to CudaDeviceMemory"); } -template void copy(CudaDeviceMemory& _dst, const Type* buffer, const size_t numelems ) +template +void copy(CudaDeviceMemory& _dst, const Type* buffer, const size_t numelems) { - _dst.copyFrom( buffer, numelems ); + _dst.copyFrom(buffer, numelems); } -template void copy(Type* _dst, size_t sx, size_t sy, const CudaDeviceMemoryPitched& _src) +template +void copy(Type* _dst, size_t sx, size_t sy, const CudaDeviceMemoryPitched& _src) { - if(Dim == 2) { - cudaError_t err = cudaMemcpy2D(_dst, - sx * sizeof (Type), - _src.getBytePtr(), - _src.getPitch(), - sx * sizeof (Type), - sy, - cudaMemcpyDeviceToHost); - THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); - } + if (Dim == 2) + { + cudaError_t err = cudaMemcpy2D(_dst, sx * sizeof(Type), _src.getBytePtr(), _src.getPitch(), sx * sizeof(Type), sy, cudaMemcpyDeviceToHost); + THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); + } } -template void copy(CudaDeviceMemoryPitched& dst, const Type* src, size_t sx, size_t sy) +template +void copy(CudaDeviceMemoryPitched& dst, const Type* src, size_t sx, size_t sy) { - dst.copyFrom( src, sx, sy ); + dst.copyFrom(src, sx, sy); } -template void copy(Type* _dst, size_t sx, size_t sy, size_t sz, const CudaDeviceMemoryPitched& _src) +template +void copy(Type* _dst, size_t sx, size_t sy, size_t sz, const CudaDeviceMemoryPitched& _src) { - if(Dim >= 3) + if (Dim >= 3) { - cudaError_t err = cudaMemcpy2D( _dst, - sx * sizeof (Type), - _src.getBytePtr(), - _src.getPitch(), - sx * sizeof (Type), - sy * sz, - cudaMemcpyDeviceToHost); - THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); + cudaError_t err = + cudaMemcpy2D(_dst, sx * sizeof(Type), _src.getBytePtr(), _src.getPitch(), sx * sizeof(Type), sy * sz, cudaMemcpyDeviceToHost); + THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } } -template void copy(CudaDeviceMemoryPitched& _dst, const Type* _src, size_t sx, size_t sy, size_t sz) +template +void copy(CudaDeviceMemoryPitched& _dst, const Type* _src, size_t sx, size_t sy, size_t sz) { - if(Dim >= 3) + if (Dim >= 3) { - const size_t src_pitch = sx * sizeof(Type); - const size_t width_in_bytes = sx * sizeof(Type); - const size_t height = sy * sz; - cudaError_t err = cudaMemcpy2D( _dst.getBytePtr(), - _dst.getPitch(), - _src, - src_pitch, - width_in_bytes, - height, - cudaMemcpyHostToDevice); - THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); + const size_t src_pitch = sx * sizeof(Type); + const size_t width_in_bytes = sx * sizeof(Type); + const size_t height = sy * sz; + cudaError_t err = cudaMemcpy2D(_dst.getBytePtr(), _dst.getPitch(), _src, src_pitch, width_in_bytes, height, cudaMemcpyHostToDevice); + THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ")"); } } -template void copy2D( Type* dst, size_t sx, size_t sy, - Type* src, size_t src_pitch, - cudaStream_t stream ) +template +void copy2D(Type* dst, size_t sx, size_t sy, Type* src, size_t src_pitch, cudaStream_t stream) { - const size_t dst_pitch = sx * sizeof(Type); + const size_t dst_pitch = sx * sizeof(Type); const size_t width_in_bytes = sx * sizeof(Type); - const size_t height = sy; - cudaError_t err = cudaMemcpy2DAsync( dst, - dst_pitch, - src, - src_pitch, - width_in_bytes, - height, - cudaMemcpyDeviceToHost, - stream ); - THROW_ON_CUDA_ERROR( err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ", " << cudaGetErrorString(err) << ")" ); + const size_t height = sy; + cudaError_t err = cudaMemcpy2DAsync(dst, dst_pitch, src, src_pitch, width_in_bytes, height, cudaMemcpyDeviceToHost, stream); + THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ", " << cudaGetErrorString(err) << ")"); } -template void copy2D( Type* dst, size_t sx, size_t sy, - Type* src, size_t src_pitch ) +template +void copy2D(Type* dst, size_t sx, size_t sy, Type* src, size_t src_pitch) { - const size_t dst_pitch = sx * sizeof(Type); + const size_t dst_pitch = sx * sizeof(Type); const size_t width_in_bytes = sx * sizeof(Type); - const size_t height = sy; - cudaError_t err = cudaMemcpy2D( dst, - dst_pitch, - src, - src_pitch, - width_in_bytes, - height, - cudaMemcpyDeviceToHost ); - THROW_ON_CUDA_ERROR( err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ", " << cudaGetErrorString(err) << ")" ); + const size_t height = sy; + cudaError_t err = cudaMemcpy2D(dst, dst_pitch, src, src_pitch, width_in_bytes, height, cudaMemcpyDeviceToHost); + THROW_ON_CUDA_ERROR(err, "Failed to copy (" << __FILE__ << " " << __LINE__ << ", " << cudaGetErrorString(err) << ")"); } /* @@ -1042,14 +883,14 @@ template void copy2D( Type* dst, size_t sx, size_t sy, * - if true addressed (x,y) in [0, 1] * - if false addressed (x,y) in [width, height] */ -template +template struct CudaTexture { cudaTextureObject_t textureObj = 0; CudaTexture(CudaDeviceMemoryPitched& buffer_dmp) { - cudaTextureDesc texDesc; + cudaTextureDesc texDesc; memset(&texDesc, 0, sizeof(cudaTextureDesc)); texDesc.normalizedCoords = normalizedCoords; texDesc.addressMode[0] = cudaAddressModeClamp; @@ -1071,10 +912,7 @@ struct CudaTexture CHECK_CUDA_RETURN_ERROR(cudaCreateTextureObject(&textureObj, &resDesc, &texDesc, nullptr)); } - ~CudaTexture() - { - CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaDestroyTextureObject(textureObj)); - } + ~CudaTexture() { CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaDestroyTextureObject(textureObj)); } }; struct CudaRGBATexture @@ -1083,7 +921,7 @@ struct CudaRGBATexture CudaRGBATexture(CudaDeviceMemoryPitched& buffer_dmp) { - cudaTextureDesc texDesc; + cudaTextureDesc texDesc; memset(&texDesc, 0, sizeof(cudaTextureDesc)); texDesc.normalizedCoords = false; texDesc.addressMode[0] = cudaAddressModeClamp; @@ -1091,7 +929,7 @@ struct CudaRGBATexture texDesc.addressMode[2] = cudaAddressModeClamp; #if defined(ALICEVISION_DEPTHMAP_TEXTURE_USE_UCHAR) && defined(ALICEVISION_DEPTHMAP_TEXTURE_USE_INTERPOLATION) - texDesc.readMode = cudaReadModeNormalizedFloat; // uchar to float [0:1], see tex2d_float4 function + texDesc.readMode = cudaReadModeNormalizedFloat; // uchar to float [0:1], see tex2d_float4 function #else texDesc.readMode = cudaReadModeElementType; @@ -1122,12 +960,8 @@ struct CudaRGBATexture CHECK_CUDA_RETURN_ERROR(cudaCreateTextureObject(&textureObj, &resDesc, &texDesc, nullptr)); } - ~CudaRGBATexture() - { - CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaDestroyTextureObject(textureObj)); - } + ~CudaRGBATexture() { CHECK_CUDA_RETURN_ERROR_NOEXCEPT(cudaDestroyTextureObject(textureObj)); } }; -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/patchPattern.cpp b/src/aliceVision/depthMap/cuda/host/patchPattern.cpp index c74bf3f90c..8711c61eb6 100644 --- a/src/aliceVision/depthMap/cuda/host/patchPattern.cpp +++ b/src/aliceVision/depthMap/cuda/host/patchPattern.cpp @@ -18,27 +18,29 @@ namespace depthMap { void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) { // check at least one patch subpart - if(patchParams.subpartsParams.empty()) + if (patchParams.subpartsParams.empty()) { ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: No patch pattern subpart given."); } // build nb coordinates per subpart map - std::map nbCoordsPerSubparts; // - for(int i = 0; i < patchParams.subpartsParams.size(); ++i) + std::map nbCoordsPerSubparts; // + for (int i = 0; i < patchParams.subpartsParams.size(); ++i) { const auto& subpartParams = patchParams.subpartsParams.at(i); - if(subpartParams.radius <= 0.f) + if (subpartParams.radius <= 0.f) ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: A patch pattern subpart radius is incorrect."); - if(subpartParams.isCircle && subpartParams.nbCoordinates <= 0) - ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: A patch pattern subpart circle number of coordinates is incorrect."); + if (subpartParams.isCircle && subpartParams.nbCoordinates <= 0) + ALICEVISION_THROW_ERROR( + "Cannot build custom patch pattern in device constant memory: A patch pattern subpart circle number of coordinates is incorrect."); - if(patchParams.groupSubpartsPerLevel) + if (patchParams.groupSubpartsPerLevel) { - if(!subpartParams.isCircle && nbCoordsPerSubparts.find(subpartParams.level) != nbCoordsPerSubparts.end()) - ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: Cannot group more than one full patch pattern subpart."); + if (!subpartParams.isCircle && nbCoordsPerSubparts.find(subpartParams.level) != nbCoordsPerSubparts.end()) + ALICEVISION_THROW_ERROR( + "Cannot build custom patch pattern in device constant memory: Cannot group more than one full patch pattern subpart."); nbCoordsPerSubparts[subpartParams.level] += ((subpartParams.isCircle) ? subpartParams.nbCoordinates : 0); } @@ -50,25 +52,29 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) // get the maximum number of coordinates int maxSubpartCoords = 0; - for(const auto& nbCoordsPerSubpart : nbCoordsPerSubparts) - maxSubpartCoords = std::max(maxSubpartCoords, nbCoordsPerSubpart.second); + for (const auto& nbCoordsPerSubpart : nbCoordsPerSubparts) + maxSubpartCoords = std::max(maxSubpartCoords, nbCoordsPerSubpart.second); // get true number of subparts const int nbSubparts = nbCoordsPerSubparts.size(); // check max number of subparts - if(nbSubparts > ALICEVISION_DEVICE_PATCH_MAX_SUBPARTS) + if (nbSubparts > ALICEVISION_DEVICE_PATCH_MAX_SUBPARTS) { - ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: Too many patch pattern subpart given." << std::endl - << "\t- # given patch pattern subparts: " << nbSubparts << "(group by level is " << ((patchParams.groupSubpartsPerLevel) ? "ON" : "OFF") << ")" << std::endl + ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: Too many patch pattern subpart given." + << std::endl + << "\t- # given patch pattern subparts: " << nbSubparts << "(group by level is " + << ((patchParams.groupSubpartsPerLevel) ? "ON" : "OFF") << ")" << std::endl << "\t- maximum number of patch pattern subparts: " << ALICEVISION_DEVICE_PATCH_MAX_SUBPARTS); } // check max number of subpart coordinates - if(maxSubpartCoords > ALICEVISION_DEVICE_PATCH_MAX_COORDS_PER_SUBPARTS) + if (maxSubpartCoords > ALICEVISION_DEVICE_PATCH_MAX_COORDS_PER_SUBPARTS) { - ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: Too many patch pattern subpart coordinates given." << std::endl - << "\t- # given patch pattern subpart coordinates: " << maxSubpartCoords << "(group by level is " << ((patchParams.groupSubpartsPerLevel) ? "ON" : "OFF") << ")" << std::endl + ALICEVISION_THROW_ERROR("Cannot build custom patch pattern in device constant memory: Too many patch pattern subpart coordinates given." + << std::endl + << "\t- # given patch pattern subpart coordinates: " << maxSubpartCoords << "(group by level is " + << ((patchParams.groupSubpartsPerLevel) ? "ON" : "OFF") << ")" << std::endl << "\t- maximum number of patch pattern subpart coordinates: " << ALICEVISION_DEVICE_PATCH_MAX_COORDS_PER_SUBPARTS); } @@ -80,13 +86,13 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) << "\t- Group patch pattern subparts per level: " << ((patchParams.groupSubpartsPerLevel) ? "ON" : "OFF") << std::endl << "\t- Subparts:" << std::endl; - for(const auto& subpartParams : patchParams.subpartsParams) + for (const auto& subpartParams : patchParams.subpartsParams) { ss << "\t - subpart:" << std::endl << "\t - level: " << subpartParams.level << std::endl << "\t - weight: " << subpartParams.weight << std::endl; - if(subpartParams.isCircle) + if (subpartParams.isCircle) { ss << "\t - type: circle" << std::endl << "\t - radius: " << subpartParams.radius << std::endl @@ -94,8 +100,7 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) } else { - ss << "\t - type: full" << std::endl - << "\t - wsh: " << int(subpartParams.radius) << std::endl; + ss << "\t - type: full" << std::endl << "\t - wsh: " << int(subpartParams.radius) << std::endl; } } ALICEVISION_LOG_DEBUG(ss.str()); @@ -111,29 +116,29 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) patchPattern_h->nbSubparts = nbSubparts; // fill the host-side patch pattern struct - if(patchParams.groupSubpartsPerLevel) + if (patchParams.groupSubpartsPerLevel) { // ensure that nbCoordinates and wsh are set to zero // CUDA doesn't support default initialization for constant memory struct - for(int i = 0; i < patchPattern_h->nbSubparts; ++i) + for (int i = 0; i < patchPattern_h->nbSubparts; ++i) { DevicePatchPatternSubpart& subpart = patchPattern_h->subparts[i]; subpart.nbCoordinates = 0; subpart.wsh = 0; } - for(const auto& subpartParams : patchParams.subpartsParams) + for (const auto& subpartParams : patchParams.subpartsParams) { const auto it = nbCoordsPerSubparts.find(subpartParams.level); DevicePatchPatternSubpart& subpart = patchPattern_h->subparts[std::distance(nbCoordsPerSubparts.begin(), it)]; - if(subpartParams.isCircle) + if (subpartParams.isCircle) { const float radiusValue = subpartParams.radius; const float angleDifference = (M_PI * 2.f) / subpartParams.nbCoordinates; // compute patch pattern relative coordinates - for(int i = 0; i < subpartParams.nbCoordinates; ++i) + for (int i = 0; i < subpartParams.nbCoordinates; ++i) { float2& coords = subpart.coordinates[subpart.nbCoordinates + i]; @@ -142,7 +147,7 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) coords.y = std::sin(radians) * radiusValue; } - subpart.wsh = std::max(subpart.wsh, int(subpartParams.radius + std::pow(2.f,subpartParams.level - 1.f))); + subpart.wsh = std::max(subpart.wsh, int(subpartParams.radius + std::pow(2.f, subpartParams.level - 1.f))); subpart.nbCoordinates += subpartParams.nbCoordinates; } else @@ -159,19 +164,19 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) } else { - for(int i = 0; i < patchPattern_h->nbSubparts; ++i) + for (int i = 0; i < patchPattern_h->nbSubparts; ++i) { DevicePatchPatternSubpart& subpart = patchPattern_h->subparts[i]; const auto& subpartParams = patchParams.subpartsParams.at(i); - if(subpartParams.isCircle) + if (subpartParams.isCircle) { const float radiusValue = subpartParams.radius; const float angleDifference = (M_PI * 2.f) / subpart.nbCoordinates; // compute patch pattern relative coordinates - for(int j = 0; j < subpart.nbCoordinates; ++j) + for (int j = 0; j < subpart.nbCoordinates; ++j) { float2& coords = subpart.coordinates[j]; @@ -180,7 +185,7 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) coords.y = std::sin(radians) * radiusValue; } - subpart.wsh = int(subpartParams.radius + std::pow(2.f,subpartParams.level - 1.f)); + subpart.wsh = int(subpartParams.radius + std::pow(2.f, subpartParams.level - 1.f)); subpart.nbCoordinates = subpartParams.nbCoordinates; } else @@ -201,10 +206,9 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) { std::stringstream ss; - ss << "Build custom patch pattern:" << std::endl - << "\t- Subparts:" << std::endl; + ss << "Build custom patch pattern:" << std::endl << "\t- Subparts:" << std::endl; - for(int i = 0; i < patchPattern_h->nbSubparts; ++i) + for (int i = 0; i < patchPattern_h->nbSubparts; ++i) { const DevicePatchPatternSubpart& subpart = patchPattern_h->subparts[i]; @@ -214,13 +218,13 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) << "\t - weight: " << subpart.weight << std::endl << "\t - wsh: " << subpart.wsh << std::endl; - if(subpart.isCircle) + if (subpart.isCircle) { ss << "\t - type: circle(s)" << std::endl << "\t - # coordinates: " << subpart.nbCoordinates << std::endl << "\t - coordinates list:" << std::endl; - for(int j = 0; j < subpart.nbCoordinates; ++j) + for (int j = 0; j < subpart.nbCoordinates; ++j) { const float2& coords = subpart.coordinates[j]; ss << "\t - [x: " << coords.x << ", y: " << coords.y << "]" << std::endl; @@ -234,7 +238,6 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) ALICEVISION_LOG_DEBUG(ss.str()); } - // fill the device-side (constant memory) patch pattern struct const cudaError_t err = cudaMemcpyToSymbol(&constantPatchPattern_d, patchPattern_h, sizeof(DevicePatchPattern), 0, cudaMemcpyHostToDevice); CHECK_CUDA_RETURN_ERROR(err); @@ -247,5 +250,5 @@ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams) CHECK_CUDA_ERROR(); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/patchPattern.hpp b/src/aliceVision/depthMap/cuda/host/patchPattern.hpp index ee7422d71c..f514f57228 100644 --- a/src/aliceVision/depthMap/cuda/host/patchPattern.hpp +++ b/src/aliceVision/depthMap/cuda/host/patchPattern.hpp @@ -17,5 +17,5 @@ namespace depthMap { */ void buildCustomPatchPattern(const CustomPatchPatternParams& patchParams); -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/utils.cpp b/src/aliceVision/depthMap/cuda/host/utils.cpp index 2ed8c7bc8b..6e7c702af3 100644 --- a/src/aliceVision/depthMap/cuda/host/utils.cpp +++ b/src/aliceVision/depthMap/cuda/host/utils.cpp @@ -15,27 +15,26 @@ namespace depthMap { int listCudaDevices() { - int nbDevices = 0; // number of CUDA GPUs + int nbDevices = 0; // number of CUDA GPUs // determine the number of CUDA capable GPUs cudaError_t err = cudaGetDeviceCount(&nbDevices); CHECK_CUDA_ERROR(); - if(err != cudaSuccess) + if (err != cudaSuccess) { ALICEVISION_LOG_ERROR("Cannot get CUDA device count."); return 0; } - if(nbDevices < 1) + if (nbDevices < 1) { ALICEVISION_LOG_ERROR("No CUDA capable devices detected."); return 0; } - // display CPU and GPU configuration - std::stringstream s; - for(int i = 0; i < nbDevices; ++i) + std::stringstream s; + for (int i = 0; i < nbDevices; ++i) { cudaDeviceProp dprop; cudaGetDeviceProperties(&dprop, i); @@ -50,19 +49,19 @@ int getCudaDeviceId() { int currentCudaDeviceId; - if(cudaGetDevice(¤tCudaDeviceId) != cudaSuccess) + if (cudaGetDevice(¤tCudaDeviceId) != cudaSuccess) { ALICEVISION_LOG_ERROR("Cannot get current CUDA device id."); } CHECK_CUDA_ERROR(); - + return currentCudaDeviceId; } void setCudaDeviceId(int cudaDeviceId) { - if(cudaSetDevice(cudaDeviceId) != cudaSuccess) + if (cudaSetDevice(cudaDeviceId) != cudaSuccess) { ALICEVISION_LOG_ERROR("Cannot set device id " << cudaDeviceId << " as current CUDA device."); } @@ -72,14 +71,14 @@ void setCudaDeviceId(int cudaDeviceId) bool testCudaDeviceId(int cudaDeviceId) { - int currentCudaDeviceId; - cudaGetDevice(¤tCudaDeviceId); - if(currentCudaDeviceId != cudaDeviceId) - { - ALICEVISION_LOG_WARNING("CUDA device id should be: " << cudaDeviceId << ", program curently use device id: " << currentCudaDeviceId << "."); - return false; - } - return true; + int currentCudaDeviceId; + cudaGetDevice(¤tCudaDeviceId); + if (currentCudaDeviceId != cudaDeviceId) + { + ALICEVISION_LOG_WARNING("CUDA device id should be: " << cudaDeviceId << ", program curently use device id: " << currentCudaDeviceId << "."); + return false; + } + return true; } void logDeviceMemoryInfo() @@ -96,10 +95,10 @@ void logDeviceMemoryInfo() int cudaDeviceId; cudaGetDevice(&cudaDeviceId); - ALICEVISION_LOG_INFO("Device memory (device id: "<< cudaDeviceId <<"):" << std::endl - << "\t- used: " << usedMB << " MB" << std::endl - << "\t- available: " << availableMB << " MB" << std::endl - << "\t- total: " << totalMB << " MB"); + ALICEVISION_LOG_INFO("Device memory (device id: " << cudaDeviceId << "):" << std::endl + << "\t- used: " << usedMB << " MB" << std::endl + << "\t- available: " << availableMB << " MB" << std::endl + << "\t- total: " << totalMB << " MB"); } void getDeviceMemoryInfo(double& availableMB, double& usedMB, double& totalMB) @@ -114,5 +113,5 @@ void getDeviceMemoryInfo(double& availableMB, double& usedMB, double& totalMB) usedMB = double(itotal - iavail) / (1024.0 * 1024.0); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/host/utils.hpp b/src/aliceVision/depthMap/cuda/host/utils.hpp index fd63bdb9a2..9f15709538 100644 --- a/src/aliceVision/depthMap/cuda/host/utils.hpp +++ b/src/aliceVision/depthMap/cuda/host/utils.hpp @@ -7,46 +7,43 @@ #pragma once // Macros for checking cuda errors -#define CHECK_CUDA_RETURN_ERROR(err) \ - if(err != cudaSuccess) \ - { \ - fprintf(stderr, "\n\nCUDAError: %s\n", cudaGetErrorString(err)); \ - fprintf(stderr, " file: %s\n", __FILE__); \ - fprintf(stderr, " function: %s\n", __FUNCTION__); \ - fprintf(stderr, " line: %d\n\n", __LINE__); \ - std::stringstream s; \ - s << "\n CUDA Error: " << cudaGetErrorString(err) \ - << "\n file: " << __FILE__ \ - << "\n function: " << __FUNCTION__ \ - << "\n line: " << __LINE__ \ - << "\n"; \ - throw std::runtime_error(s.str()); \ - } \ +#define CHECK_CUDA_RETURN_ERROR(err) \ + if (err != cudaSuccess) \ + { \ + fprintf(stderr, "\n\nCUDAError: %s\n", cudaGetErrorString(err)); \ + fprintf(stderr, " file: %s\n", __FILE__); \ + fprintf(stderr, " function: %s\n", __FUNCTION__); \ + fprintf(stderr, " line: %d\n\n", __LINE__); \ + std::stringstream s; \ + s << "\n CUDA Error: " << cudaGetErrorString(err) << "\n file: " << __FILE__ << "\n function: " << __FUNCTION__ \ + << "\n line: " << __LINE__ << "\n"; \ + throw std::runtime_error(s.str()); \ + } -#define CHECK_CUDA_RETURN_ERROR_NOEXCEPT(err) \ - if(err != cudaSuccess) \ - { \ - fprintf(stderr, "\n\nCUDAError: %s\n", cudaGetErrorString(err)); \ - fprintf(stderr, " file: %s\n", __FILE__); \ - fprintf(stderr, " function: %s\n", __FUNCTION__); \ - fprintf(stderr, " line: %d\n\n", __LINE__); \ - } \ +#define CHECK_CUDA_RETURN_ERROR_NOEXCEPT(err) \ + if (err != cudaSuccess) \ + { \ + fprintf(stderr, "\n\nCUDAError: %s\n", cudaGetErrorString(err)); \ + fprintf(stderr, " file: %s\n", __FILE__); \ + fprintf(stderr, " function: %s\n", __FUNCTION__); \ + fprintf(stderr, " line: %d\n\n", __LINE__); \ + } #define CHECK_CUDA_ERROR() CHECK_CUDA_RETURN_ERROR(cudaGetLastError()); -#define THROW_ON_CUDA_ERROR(rcode, message) \ - if(rcode != cudaSuccess) \ - { \ - std::stringstream s; \ - s << message << ": " << cudaGetErrorString(err); \ - throw std::runtime_error(s.str()); \ - } \ +#define THROW_ON_CUDA_ERROR(rcode, message) \ + if (rcode != cudaSuccess) \ + { \ + std::stringstream s; \ + s << message << ": " << cudaGetErrorString(err); \ + throw std::runtime_error(s.str()); \ + } namespace aliceVision { namespace depthMap { /** - * @brief Get and log available CUDA devices. + * @brief Get and log available CUDA devices. * @return the number of CUDA devices */ int listCudaDevices(); @@ -64,7 +61,7 @@ int getCudaDeviceId(); void setCudaDeviceId(int cudaDeviceId); /** - * @brief Test if the device id currently used for GPU executions + * @brief Test if the device id currently used for GPU executions * is the same as the one given. * @param[in] cudaDeviceId the given CUDA device id to test */ @@ -83,6 +80,5 @@ void logDeviceMemoryInfo(); */ void getDeviceMemoryInfo(double& availableMB, double& usedMB, double& totalMB); -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/imageProcessing/deviceColorConversion.hpp b/src/aliceVision/depthMap/cuda/imageProcessing/deviceColorConversion.hpp index 231cc91d24..bd40d92fb7 100644 --- a/src/aliceVision/depthMap/cuda/imageProcessing/deviceColorConversion.hpp +++ b/src/aliceVision/depthMap/cuda/imageProcessing/deviceColorConversion.hpp @@ -18,6 +18,5 @@ namespace depthMap { */ extern void cuda_rgb2lab(CudaDeviceMemoryPitched& inout_img_dmp, cudaStream_t stream); -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/imageProcessing/deviceGaussianFilter.hpp b/src/aliceVision/depthMap/cuda/imageProcessing/deviceGaussianFilter.hpp index 87fe949162..fedcd6d02b 100644 --- a/src/aliceVision/depthMap/cuda/imageProcessing/deviceGaussianFilter.hpp +++ b/src/aliceVision/depthMap/cuda/imageProcessing/deviceGaussianFilter.hpp @@ -14,20 +14,17 @@ namespace aliceVision { namespace depthMap { -#define MAX_CONSTANT_GAUSS_SCALES 10 +#define MAX_CONSTANT_GAUSS_SCALES 10 #define MAX_CONSTANT_GAUSS_MEM_SIZE 128 /********************************************************************************* -* global / constant data structures -*********************************************************************************/ -extern std::set d_gaussianArrayInitialized; -extern __device__ __constant__ int d_gaussianArrayOffset[MAX_CONSTANT_GAUSS_SCALES]; + * global / constant data structures + *********************************************************************************/ +extern std::set d_gaussianArrayInitialized; +extern __device__ __constant__ int d_gaussianArrayOffset[MAX_CONSTANT_GAUSS_SCALES]; extern __device__ __constant__ float d_gaussianArray[MAX_CONSTANT_GAUSS_MEM_SIZE]; -__device__ inline float getGauss(int scale, int idx) -{ - return d_gaussianArray[d_gaussianArrayOffset[scale] + idx]; -} +__device__ inline float getGauss(int scale, int idx) { return d_gaussianArray[d_gaussianArrayOffset[scale] + idx]; } /** * @brief Create Gaussian array in device constant memory. @@ -56,9 +53,7 @@ extern void cuda_downscaleWithGaussianBlur(CudaDeviceMemoryPitched& * @param[in] gaussRadius the Gaussian radius * @param[in] stream the CUDA stream for gpu execution */ -extern void cuda_gaussianBlurVolumeZ(CudaDeviceMemoryPitched& inout_volume_dmp, - int gaussRadius, - cudaStream_t stream); +extern void cuda_gaussianBlurVolumeZ(CudaDeviceMemoryPitched& inout_volume_dmp, int gaussRadius, cudaStream_t stream); /** * @brief Apply a Gaussion blur to the XYZ axis of the given volume. @@ -66,9 +61,7 @@ extern void cuda_gaussianBlurVolumeZ(CudaDeviceMemoryPitched& inout_vo * @param[in] gaussRadius the Gaussian radius * @param[in] stream the CUDA stream for gpu execution */ -extern void cuda_gaussianBlurVolumeXYZ(CudaDeviceMemoryPitched& inout_volume_dmp, - int gaussRadius, - cudaStream_t stream); +extern void cuda_gaussianBlurVolumeXYZ(CudaDeviceMemoryPitched& inout_volume_dmp, int gaussRadius, cudaStream_t stream); /** * @brief Apply a Median filter to the given image. @@ -76,17 +69,15 @@ extern void cuda_gaussianBlurVolumeXYZ(CudaDeviceMemoryPitched& inout_ */ extern void cuda_medianFilter3(cudaTextureObject_t tex, CudaDeviceMemoryPitched& img); - #ifdef ALICEVISION_TMP_WITH_BILATERALFILTER -//Euclidean Distance (x, y, d) = exp((|x - y| / d)^2 / 2) +// Euclidean Distance (x, y, d) = exp((|x - y| / d)^2 / 2) template __device__ inline float euclideanLen(Type a, Type b, float d); template<> __device__ inline float euclideanLen(float a, float b, float d) { - float mod = (b - a) * (b - a); return __expf(-mod / (2.f * d * d)); @@ -95,10 +86,7 @@ __device__ inline float euclideanLen(float a, float b, float d) template<> __device__ inline float euclideanLen(float4 a, float4 b, float d) { - - float mod = (b.x - a.x) * (b.x - a.x) + - (b.y - a.y) * (b.y - a.y) + - (b.z - a.z) * (b.z - a.z); + float mod = (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y) + (b.z - a.z) * (b.z - a.z); return __expf(-mod / (2.f * d * d)); } @@ -127,7 +115,6 @@ __device__ inline float4 init_type(float v) return make_float4(v, v, v, v); } - /** * Bilateral filter is an edge-preserving nonlinear smoothing filter. There * are three parameters distribute to the filter: gaussian delta, euclidean @@ -145,12 +132,8 @@ __device__ inline float4 init_type(float v) * Filtering for Gray and Color Images". */ template -__global__ void bilateralFilter_kernel( - cudaTextureObject_t rgbaTex, - Type* texLab, int texLab_p, - int width, int height, - float euclideanDelta, - int radius, int scale) +__global__ void +bilateralFilter_kernel(cudaTextureObject_t rgbaTex, Type* texLab, int texLab_p, int width, int height, float euclideanDelta, int radius, int scale) { int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; @@ -161,16 +144,16 @@ __global__ void bilateralFilter_kernel( Type centerPix = tex2D(rgbaTex, x, y); float s = 0.5f; - Type t = init_type(0.0f); // generic make_float4(); + Type t = init_type(0.0f); // generic make_float4(); float sum = 0.0f; for (int i = -radius; i <= radius; i++) { for (int j = -radius; j <= radius; j++) { Type curPix = tex2D(rgbaTex, (float)(x * scale + j) + s, (float)(y * scale + i) + s); - float factor = getGauss(scale - 1, i + radius) // domain factor - * getGauss(scale - 1, j + radius) // domain factor - * euclideanLen(curPix, centerPix, euclideanDelta); //range factor + float factor = getGauss(scale - 1, i + radius) // domain factor + * getGauss(scale - 1, j + radius) // domain factor + * euclideanLen(curPix, centerPix, euclideanDelta); // range factor t = t + curPix * factor; sum += factor; } @@ -181,27 +164,16 @@ __global__ void bilateralFilter_kernel( } template -__host__ void ps_bilateralFilter( - cudaTextureObject_t rgbaTex, - CudaDeviceMemoryPitched& img, - float euclideanDelta, - int radius) +__host__ void ps_bilateralFilter(cudaTextureObject_t rgbaTex, CudaDeviceMemoryPitched& img, float euclideanDelta, int radius) { int scale = 1; const dim3 block(32, 2, 1); const dim3 grid(divUp(img.getSize()[0], block.x), divUp(img.getSize()[1], block.y), 1); - bilateralFilter_kernel - <<>> - (rgbaTex, - img.getBuffer(), img.getPitch(), - img.getSize()[0], img.getSize()[1], - euclideanDelta, - radius, scale - ); + bilateralFilter_kernel<<>>( + rgbaTex, img.getBuffer(), img.getPitch(), img.getSize()[0], img.getSize()[1], euclideanDelta, radius, scale); } #endif -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/imageProcessing/deviceMipmappedArray.hpp b/src/aliceVision/depthMap/cuda/imageProcessing/deviceMipmappedArray.hpp index 8a55b7bd9e..5ee105802e 100644 --- a/src/aliceVision/depthMap/cuda/imageProcessing/deviceMipmappedArray.hpp +++ b/src/aliceVision/depthMap/cuda/imageProcessing/deviceMipmappedArray.hpp @@ -48,6 +48,5 @@ extern void cuda_createMipmappedArrayDebugFlatImage(CudaDeviceMemoryPitched& out_upscal * @param[in] stream the stream for gpu execution */ extern void cuda_depthThicknessSmoothThickness(CudaDeviceMemoryPitched& inout_depthThicknessMap_dmp, - const SgmParams& sgmParams, - const RefineParams& refineParams, - const ROI& roi, - cudaStream_t stream); + const SgmParams& sgmParams, + const RefineParams& refineParams, + const ROI& roi, + cudaStream_t stream); /** * @brief Upscale the given depth/thickness map, filter masked pixels and compute pixSize from thickness. @@ -111,5 +111,5 @@ extern void cuda_depthSimMapOptimizeGradientDescent(CudaDeviceMemoryPitched& inout_ * @param[in] in_volume_dmp the input similarity volume in device memory * @param[in] stream the stream for gpu execution */ -extern void cuda_volumeAdd(CudaDeviceMemoryPitched& inout_volume_dmp, const CudaDeviceMemoryPitched& in_volume_dmp, cudaStream_t stream); +extern void cuda_volumeAdd(CudaDeviceMemoryPitched& inout_volume_dmp, + const CudaDeviceMemoryPitched& in_volume_dmp, + cudaStream_t stream); /** * @brief Update second best similarity volume uninitialized values with first best volume values. @@ -46,7 +48,9 @@ extern void cuda_volumeAdd(CudaDeviceMemoryPitched& inout_volume_ * @param[out] inout_volSecBestSim_dmp the second best similarity volume in device memory * @param[in] stream the stream for gpu execution */ -extern void cuda_volumeUpdateUninitializedSimilarity(const CudaDeviceMemoryPitched& in_volBestSim_dmp, CudaDeviceMemoryPitched& inout_volSecBestSim_dmp, cudaStream_t stream); +extern void cuda_volumeUpdateUninitializedSimilarity(const CudaDeviceMemoryPitched& in_volBestSim_dmp, + CudaDeviceMemoryPitched& inout_volSecBestSim_dmp, + cudaStream_t stream); /** * @brief Compute the best / second best similarity volume for the given RC / TC. @@ -62,14 +66,14 @@ extern void cuda_volumeUpdateUninitializedSimilarity(const CudaDeviceMemoryPitch * @param[in] roi the 2d region of interest * @param[in] stream the stream for gpu execution */ -extern void cuda_volumeComputeSimilarity(CudaDeviceMemoryPitched& out_volBestSim_dmp, +extern void cuda_volumeComputeSimilarity(CudaDeviceMemoryPitched& out_volBestSim_dmp, CudaDeviceMemoryPitched& out_volSecBestSim_dmp, const CudaDeviceMemoryPitched& in_depths_dmp, const int rcDeviceCameraParamsId, const int tcDeviceCameraParamsId, const DeviceMipmapImage& rcDeviceMipmapImage, const DeviceMipmapImage& tcDeviceMipmapImage, - const SgmParams& sgmParams, + const SgmParams& sgmParams, const Range& depthRange, const ROI& roi, cudaStream_t stream); @@ -88,14 +92,14 @@ extern void cuda_volumeComputeSimilarity(CudaDeviceMemoryPitched& out_v * @param[in] roi the 2d region of interest * @param[in] stream the stream for gpu execution */ -extern void cuda_volumeRefineSimilarity(CudaDeviceMemoryPitched& inout_volSim_dmp, +extern void cuda_volumeRefineSimilarity(CudaDeviceMemoryPitched& inout_volSim_dmp, const CudaDeviceMemoryPitched& in_sgmDepthPixSizeMap_dmp, const CudaDeviceMemoryPitched* in_sgmNormalMap_dmpPtr, const int rcDeviceCameraParamsId, const int tcDeviceCameraParamsId, const DeviceMipmapImage& rcDeviceMipmapImage, const DeviceMipmapImage& tcDeviceMipmapImage, - const RefineParams& refineParams, + const RefineParams& refineParams, const Range& depthRange, const ROI& roi, cudaStream_t stream); @@ -117,9 +121,9 @@ extern void cuda_volumeOptimize(CudaDeviceMemoryPitched& out_volSimFilt CudaDeviceMemoryPitched& inout_volSliceAccA_dmp, CudaDeviceMemoryPitched& inout_volSliceAccB_dmp, CudaDeviceMemoryPitched& inout_volAxisAcc_dmp, - const CudaDeviceMemoryPitched& in_volSim_dmp, + const CudaDeviceMemoryPitched& in_volSim_dmp, const DeviceMipmapImage& rcDeviceMipmapImage, - const SgmParams& sgmParams, + const SgmParams& sgmParams, const int lastDepthIndex, const ROI& roi, cudaStream_t stream); @@ -138,12 +142,12 @@ extern void cuda_volumeOptimize(CudaDeviceMemoryPitched& out_volSimFilt */ extern void cuda_volumeRetrieveBestDepth(CudaDeviceMemoryPitched& out_sgmDepthThicknessMap_dmp, CudaDeviceMemoryPitched& out_sgmDepthSimMap_dmp, - const CudaDeviceMemoryPitched& in_depths_dmp, - const CudaDeviceMemoryPitched& in_volSim_dmp, + const CudaDeviceMemoryPitched& in_depths_dmp, + const CudaDeviceMemoryPitched& in_volSim_dmp, const int rcDeviceCameraParamsId, - const SgmParams& sgmParams, + const SgmParams& sgmParams, const Range& depthRange, - const ROI& roi, + const ROI& roi, cudaStream_t stream); /** @@ -158,10 +162,10 @@ extern void cuda_volumeRetrieveBestDepth(CudaDeviceMemoryPitched& out */ extern void cuda_volumeRefineBestDepth(CudaDeviceMemoryPitched& out_refineDepthSimMap_dmp, const CudaDeviceMemoryPitched& in_sgmDepthPixSizeMap_dmp, - const CudaDeviceMemoryPitched& in_volSim_dmp, - const RefineParams& refineParams, - const ROI& roi, + const CudaDeviceMemoryPitched& in_volSim_dmp, + const RefineParams& refineParams, + const ROI& roi, cudaStream_t stream); -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/cuda/planeSweeping/similarity.hpp b/src/aliceVision/depthMap/cuda/planeSweeping/similarity.hpp index 39df1ffa5b..9d21b6cd8c 100644 --- a/src/aliceVision/depthMap/cuda/planeSweeping/similarity.hpp +++ b/src/aliceVision/depthMap/cuda/planeSweeping/similarity.hpp @@ -9,8 +9,8 @@ #define TSIM_REFINE_USE_HALF #ifdef TSIM_REFINE_USE_HALF -#define CUDA_NO_HALF -#include + #define CUDA_NO_HALF + #include #endif namespace aliceVision { @@ -23,19 +23,18 @@ namespace depthMap { */ #ifdef TSIM_USE_FLOAT - using TSim = float; - using TSimAcc = float; +using TSim = float; +using TSimAcc = float; #else - using TSim = unsigned char; - using TSimAcc = unsigned int; // TSimAcc is the similarity accumulation type +using TSim = unsigned char; +using TSimAcc = unsigned int; // TSimAcc is the similarity accumulation type #endif #ifdef TSIM_REFINE_USE_HALF - using TSimRefine = __half; +using TSimRefine = __half; #else - using TSimRefine = float; +using TSimRefine = float; #endif -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/depthMapUtils.cpp b/src/aliceVision/depthMap/depthMapUtils.cpp index 2f79bd396b..029dd17aa1 100644 --- a/src/aliceVision/depthMap/depthMapUtils.cpp +++ b/src/aliceVision/depthMap/depthMapUtils.cpp @@ -20,10 +20,14 @@ namespace aliceVision { namespace depthMap { -void copyFloat2Map(image::Image& out_mapX, image::Image& out_mapY, const CudaHostMemoryHeap& in_map_hmh, const ROI& roi, int downscale) +void copyFloat2Map(image::Image& out_mapX, + image::Image& out_mapY, + const CudaHostMemoryHeap& in_map_hmh, + const ROI& roi, + int downscale) { const ROI downscaledROI = downscaleROI(roi, downscale); - const int width = int(downscaledROI.width()); + const int width = int(downscaledROI.width()); const int height = int(downscaledROI.height()); // resize output images @@ -31,9 +35,9 @@ void copyFloat2Map(image::Image& out_mapX, image::Image& out_mapY, out_mapY.resize(width, height); // copy image from host memory to output images - for(int x = 0; x < width; ++x) + for (int x = 0; x < width; ++x) { - for(int y = 0; y < height; ++y) + for (int y = 0; y < height; ++y) { const float2& value = in_map_hmh(size_t(x), size_t(y)); out_mapX(y, x) = value.x; @@ -42,7 +46,11 @@ void copyFloat2Map(image::Image& out_mapX, image::Image& out_mapY, } } -void copyFloat2Map(image::Image& out_mapX, image::Image& out_mapY, const CudaDeviceMemoryPitched& in_map_dmp, const ROI& roi, int downscale) +void copyFloat2Map(image::Image& out_mapX, + image::Image& out_mapY, + const CudaDeviceMemoryPitched& in_map_dmp, + const ROI& roi, + int downscale) { // copy float2 map from device pitched memory to host memory CudaHostMemoryHeap map_hmh(in_map_dmp.getSize()); @@ -107,47 +115,47 @@ void writeFloat3Map(int rc, int step, const std::string& name) { - const ROI downscaledROI = downscaleROI(roi, scale * step); - const int width = int(downscaledROI.width()); - const int height = int(downscaledROI.height()); - - // copy map from device pitched memory to host memory - CudaHostMemoryHeap map_hmh(in_map_dmp.getSize()); - map_hmh.copyFrom(in_map_dmp); - - // copy map from host memory to an Image - image::Image map(width, height, true, {0.f,0.f,0.f}); - - for(size_t x = 0; x < size_t(width); ++x) - { - for(size_t y = 0; y < size_t(height); ++y) - { - const float3& rgba_hmh = map_hmh(x, y); - image::RGBfColor& rgb = map(int(y), int(x)); - rgb.r() = rgba_hmh.x; - rgb.g() = rgba_hmh.y; - rgb.b() = rgba_hmh.z; - } - } - - // write map from the image buffer - mvsUtils::writeMap(rc, mp, fileType, tileParams, roi, map, scale, step, (name.empty()) ? "" : "_" + name); + const ROI downscaledROI = downscaleROI(roi, scale * step); + const int width = int(downscaledROI.width()); + const int height = int(downscaledROI.height()); + + // copy map from device pitched memory to host memory + CudaHostMemoryHeap map_hmh(in_map_dmp.getSize()); + map_hmh.copyFrom(in_map_dmp); + + // copy map from host memory to an Image + image::Image map(width, height, true, {0.f, 0.f, 0.f}); + + for (size_t x = 0; x < size_t(width); ++x) + { + for (size_t y = 0; y < size_t(height); ++y) + { + const float3& rgba_hmh = map_hmh(x, y); + image::RGBfColor& rgb = map(int(y), int(x)); + rgb.r() = rgba_hmh.x; + rgb.g() = rgba_hmh.y; + rgb.b() = rgba_hmh.z; + } + } + + // write map from the image buffer + mvsUtils::writeMap(rc, mp, fileType, tileParams, roi, map, scale, step, (name.empty()) ? "" : "_" + name); } -void writeDeviceImage(const CudaDeviceMemoryPitched& in_img_dmp, const std::string& path) +void writeDeviceImage(const CudaDeviceMemoryPitched& in_img_dmp, const std::string& path) { const CudaSize<2>& imgSize = in_img_dmp.getSize(); - + // copy image from device pitched memory to host memory CudaHostMemoryHeap img_hmh(imgSize); img_hmh.copyFrom(in_img_dmp); // copy image from host memory to an Image - image::Image img(imgSize.x(), imgSize.y(), true, {0.f,0.f,0.f}); + image::Image img(imgSize.x(), imgSize.y(), true, {0.f, 0.f, 0.f}); - for(size_t x = 0; x < imgSize.x(); ++x) + for (size_t x = 0; x < imgSize.x(); ++x) { - for(size_t y = 0; y < imgSize.y(); ++y) + for (size_t y = 0; y < imgSize.y(); ++y) { const CudaRGBA& rgba_hmh = img_hmh(x, y); image::RGBfColor& rgb = img(int(y), int(x)); @@ -158,7 +166,8 @@ void writeDeviceImage(const CudaDeviceMemoryPitched& in_img_dmp, co } // write the image buffer - image::writeImage(path, img, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); + image::writeImage( + path, img, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); } void writeNormalMap(int rc, @@ -174,26 +183,25 @@ void writeNormalMap(int rc, } void writeNormalMapFiltered(int rc, - const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, - const ROI& roi, - const CudaDeviceMemoryPitched& in_normalMap_dmp, - int scale, - int step, - const std::string& name) + const mvsUtils::MultiViewParams& mp, + const mvsUtils::TileParams& tileParams, + const ROI& roi, + const CudaDeviceMemoryPitched& in_normalMap_dmp, + int scale, + int step, + const std::string& name) { writeFloat3Map(rc, mp, tileParams, roi, in_normalMap_dmp, mvsUtils::EFileType::normalMapFiltered, scale, step, name); } - void writeDepthThicknessMap(int rc, - const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, - const ROI& roi, - const CudaDeviceMemoryPitched& in_depthThicknessMap_dmp, - int scale, - int step, - const std::string& name) + const mvsUtils::MultiViewParams& mp, + const mvsUtils::TileParams& tileParams, + const ROI& roi, + const CudaDeviceMemoryPitched& in_depthThicknessMap_dmp, + int scale, + int step, + const std::string& name) { const mvsUtils::EFileType fileTypeX = mvsUtils::EFileType::depthMap; const mvsUtils::EFileType fileTypeY = mvsUtils::EFileType::thicknessMap; @@ -201,7 +209,6 @@ void writeDepthThicknessMap(int rc, writeFloat2Map(rc, mp, tileParams, roi, in_depthThicknessMap_dmp, fileTypeX, fileTypeY, scale, step, name); } - void writeDepthPixSizeMap(int rc, const mvsUtils::MultiViewParams& mp, const mvsUtils::TileParams& tileParams, @@ -211,25 +218,25 @@ void writeDepthPixSizeMap(int rc, int step, const std::string& name) { - const mvsUtils::EFileType fileTypeX = mvsUtils::EFileType::depthMap; - const mvsUtils::EFileType fileTypeY = mvsUtils::EFileType::pixSizeMap; + const mvsUtils::EFileType fileTypeX = mvsUtils::EFileType::depthMap; + const mvsUtils::EFileType fileTypeY = mvsUtils::EFileType::pixSizeMap; - writeFloat2Map(rc, mp, tileParams, roi, in_depthPixSize_dmp, fileTypeX, fileTypeY, scale, step, name); + writeFloat2Map(rc, mp, tileParams, roi, in_depthPixSize_dmp, fileTypeX, fileTypeY, scale, step, name); } void writeDepthSimMap(int rc, const mvsUtils::MultiViewParams& mp, const mvsUtils::TileParams& tileParams, - const ROI& roi, + const ROI& roi, const CudaDeviceMemoryPitched& in_depthSimMap_dmp, int scale, int step, const std::string& name) { - const mvsUtils::EFileType fileTypeX = mvsUtils::EFileType::depthMap; - const mvsUtils::EFileType fileTypeY = mvsUtils::EFileType::simMap; + const mvsUtils::EFileType fileTypeX = mvsUtils::EFileType::depthMap; + const mvsUtils::EFileType fileTypeY = mvsUtils::EFileType::simMap; - writeFloat2Map(rc, mp, tileParams, roi, in_depthSimMap_dmp, fileTypeX, fileTypeY, scale, step, name); + writeFloat2Map(rc, mp, tileParams, roi, in_depthSimMap_dmp, fileTypeX, fileTypeY, scale, step, name); } void writeDepthSimMapFromTileList(int rc, @@ -241,78 +248,69 @@ void writeDepthSimMapFromTileList(int rc, int step, const std::string& name) { - ALICEVISION_LOG_TRACE("Merge and write depth/similarity map tiles (rc: " << rc << ", view id: " << mp.getViewId(rc) << ")."); + ALICEVISION_LOG_TRACE("Merge and write depth/similarity map tiles (rc: " << rc << ", view id: " << mp.getViewId(rc) << ")."); - const std::string customSuffix = (name.empty()) ? "" : "_" + name; + const std::string customSuffix = (name.empty()) ? "" : "_" + name; - const ROI imageRoi(Range(0, mp.getWidth(rc)), Range(0, mp.getHeight(rc))); - - const int scaleStep = scale * step; - const int width = divideRoundUp(mp.getWidth(rc), scaleStep); - const int height = divideRoundUp(mp.getHeight(rc), scaleStep); + const ROI imageRoi(Range(0, mp.getWidth(rc)), Range(0, mp.getHeight(rc))); - image::Image depthMap(width, height, true, 0.0f); // map should be initialize, additive process - image::Image simMap(width, height, true, 0.0f); // map should be initialize, additive process + const int scaleStep = scale * step; + const int width = divideRoundUp(mp.getWidth(rc), scaleStep); + const int height = divideRoundUp(mp.getHeight(rc), scaleStep); - for(size_t i = 0; i < tileRoiList.size(); ++i) - { - const ROI roi = intersect(tileRoiList.at(i), imageRoi); + image::Image depthMap(width, height, true, 0.0f); // map should be initialize, additive process + image::Image simMap(width, height, true, 0.0f); // map should be initialize, additive process - if(roi.isEmpty()) - continue; + for (size_t i = 0; i < tileRoiList.size(); ++i) + { + const ROI roi = intersect(tileRoiList.at(i), imageRoi); + + if (roi.isEmpty()) + continue; - image::Image tileDepthMap; - image::Image tileSimMap; + image::Image tileDepthMap; + image::Image tileSimMap; - // copy tile depth/sim map from host memory - copyFloat2Map(tileDepthMap, tileSimMap, in_depthSimMapTiles_hmh.at(i), roi, scaleStep); + // copy tile depth/sim map from host memory + copyFloat2Map(tileDepthMap, tileSimMap, in_depthSimMapTiles_hmh.at(i), roi, scaleStep); - // add tile maps to the full-size maps with weighting - mvsUtils::addTileMapWeighted(rc, mp, tileParams, roi, scaleStep, tileDepthMap, depthMap); - mvsUtils::addTileMapWeighted(rc, mp, tileParams, roi, scaleStep, tileSimMap, simMap); - } + // add tile maps to the full-size maps with weighting + mvsUtils::addTileMapWeighted(rc, mp, tileParams, roi, scaleStep, tileDepthMap, depthMap); + mvsUtils::addTileMapWeighted(rc, mp, tileParams, roi, scaleStep, tileSimMap, simMap); + } - // write fullsize maps on disk - mvsUtils::writeMap(rc, mp, mvsUtils::EFileType::depthMap, depthMap, scale, step, customSuffix); // write the merged depth map - mvsUtils::writeMap(rc, mp, mvsUtils::EFileType::simMap, simMap, scale, step, customSuffix); // write the merged similarity map + // write fullsize maps on disk + mvsUtils::writeMap(rc, mp, mvsUtils::EFileType::depthMap, depthMap, scale, step, customSuffix); // write the merged depth map + mvsUtils::writeMap(rc, mp, mvsUtils::EFileType::simMap, simMap, scale, step, customSuffix); // write the merged similarity map } void resetDepthSimMap(CudaHostMemoryHeap& inout_depthSimMap_hmh, float depth, float sim) { - const CudaSize<2>& depthSimMapSize = inout_depthSimMap_hmh.getSize(); - - for(size_t x = 0; x < depthSimMapSize.x(); ++x) - { - for(size_t y = 0; y < depthSimMapSize.y(); ++y) - { - float2& depthSim_hmh = inout_depthSimMap_hmh(x, y); - depthSim_hmh.x = depth; - depthSim_hmh.y = sim; - } - } + const CudaSize<2>& depthSimMapSize = inout_depthSimMap_hmh.getSize(); + + for (size_t x = 0; x < depthSimMapSize.x(); ++x) + { + for (size_t y = 0; y < depthSimMapSize.y(); ++y) + { + float2& depthSim_hmh = inout_depthSimMap_hmh(x, y); + depthSim_hmh.x = depth; + depthSim_hmh.y = sim; + } + } } -void mergeNormalMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name) +void mergeNormalMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name) { const std::string customSuffix = (name.empty()) ? "" : "_" + name; image::Image normalMap; - mvsUtils::readMap(rc, mp, mvsUtils::EFileType::normalMap, normalMap, scale, step, customSuffix); // read and merge normal map tiles - mvsUtils::writeMap(rc, mp, mvsUtils::EFileType::normalMap, normalMap, scale, step, customSuffix); // write the merged normal map - mvsUtils::deleteMapTiles(rc, mp, mvsUtils::EFileType::normalMap, customSuffix); // delete normal map tile files + mvsUtils::readMap(rc, mp, mvsUtils::EFileType::normalMap, normalMap, scale, step, customSuffix); // read and merge normal map tiles + mvsUtils::writeMap(rc, mp, mvsUtils::EFileType::normalMap, normalMap, scale, step, customSuffix); // write the merged normal map + mvsUtils::deleteMapTiles(rc, mp, mvsUtils::EFileType::normalMap, customSuffix); // delete normal map tile files } -void mergeFloatMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - const mvsUtils::EFileType fileType, - int scale, - int step, - const std::string& name) +void mergeFloatMapTiles(int rc, const mvsUtils::MultiViewParams& mp, const mvsUtils::EFileType fileType, int scale, int step, const std::string& name) { const std::string customSuffix = (name.empty()) ? "" : "_" + name; @@ -321,38 +319,24 @@ void mergeFloatMapTiles(int rc, mvsUtils::readMap(rc, mp, fileType, map, scale, step, customSuffix); // read and merge depth map tiles mvsUtils::writeMap(rc, mp, fileType, map, scale, step, customSuffix); // write the merged depth map mvsUtils::deleteMapTiles(rc, mp, fileType, customSuffix); // delete depth map tile files - } -void mergeDepthThicknessMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name) +void mergeDepthThicknessMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name) { - mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::depthMap, scale, step, name); - mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::thicknessMap, scale, step, name); + mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::depthMap, scale, step, name); + mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::thicknessMap, scale, step, name); } -void mergeDepthPixSizeMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name) +void mergeDepthPixSizeMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name) { - mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::depthMap, scale, step, name); - mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::pixSizeMap, scale, step, name); + mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::depthMap, scale, step, name); + mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::pixSizeMap, scale, step, name); } - -void mergeDepthSimMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name) +void mergeDepthSimMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name) { - mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::depthMap, scale, step, name); - mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::simMap, scale, step, name); + mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::depthMap, scale, step, name); + mergeFloatMapTiles(rc, mp, mvsUtils::EFileType::simMap, scale, step, name); } void exportDepthSimMapTilePatternObj(int rc, @@ -360,152 +344,147 @@ void exportDepthSimMapTilePatternObj(int rc, const std::vector& tileRoiList, const std::vector>& tileMinMaxDepthsList) { - const std::string filepath = mvsUtils::getFileNameFromIndex(mp, rc, mvsUtils::EFileType::tilePattern); - - const int nbRoiCornerVertices = 6; // 6 vertices per ROI corner - const int nbRoiCornerFaces = 4; // 4 faces per ROI corner - const int nbRoiVertices = nbRoiCornerVertices * 4; // 24 vertices per ROI - const int nbRoiFaces = nbRoiCornerFaces * 4 + 2; // 18 faces per ROI (16 for corners + 2 for first/last depth) - - std::vector vertices(nbRoiVertices * tileRoiList.size()); - std::vector> faces(nbRoiFaces * tileRoiList.size()); - - const double cornerPixSize = tileRoiList.front().x.size() / 5; // corner bevel size in image pixel - - // 2 points offset from corner (to draw a bevel) - const std::vector> roiCornerOffsets = { - {{ cornerPixSize, 0.0},{0.0, cornerPixSize}}, // corner (roi.x.begin, roi.y.begin) - {{ cornerPixSize, 0.0},{0.0, -cornerPixSize}}, // corner (roi.x.begin, roi.y.end ) - {{-cornerPixSize, 0.0},{0.0, cornerPixSize}}, // corner (roi.x.end, roi.y.begin) - {{-cornerPixSize, 0.0},{0.0, -cornerPixSize}} // corner (roi.x.end, roi.y.end ) - }; - - // vertex color sets - const std::vector roiColors = { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {1, 1, 0, 0}, - {0, 1, 1, 0}, - {1, 0, 1, 0}, - }; - - // build vertices and faces for each ROI - for(std::size_t ri = 0; ri < tileRoiList.size(); ++ri) - { - const ROI& roi = tileRoiList.at(ri); - - const auto& minMaxDepth = tileMinMaxDepthsList.at(ri); - const Point3d planeN = (mp.iRArr[rc] * Point3d(0.0f, 0.0f, 1.0f)).normalize(); // plane normal - const Point3d firstPlaneP = mp.CArr[rc] + planeN * minMaxDepth.first; // first depth plane point - const Point3d lastPlaneP = mp.CArr[rc] + planeN * minMaxDepth.second; // last depth plane point - - const std::vector roiCorners = { - {double(roi.x.begin), double(roi.y.begin)}, - {double(roi.x.begin), double(roi.y.end) }, - {double(roi.x.end), double(roi.y.begin)}, - {double(roi.x.end), double(roi.y.end) } - }; - - // build vertices and faces for each ROI corner - for(std::size_t ci = 0; ci < roiCorners.size(); ++ci) - { - const std::size_t vStartIdx = ri * nbRoiVertices + ci * nbRoiCornerVertices; - const std::size_t fStartIdx = ri * nbRoiFaces + ci * nbRoiCornerFaces; - - const auto& corner = roiCorners.at(ci); // corner 2d point - const auto& cornerOffsets = roiCornerOffsets.at(ci); - - const Point2d cornerX = corner + cornerOffsets.first; // corner 2d point X offsetted - const Point2d cornerY = corner + cornerOffsets.second; // corner 2d point Y offsetted - - vertices[vStartIdx ] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * corner ).normalize(), firstPlaneP, planeN); - vertices[vStartIdx + 1] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * corner ).normalize(), lastPlaneP , planeN); - vertices[vStartIdx + 2] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerX).normalize(), firstPlaneP, planeN); - vertices[vStartIdx + 3] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerX).normalize(), lastPlaneP , planeN); - vertices[vStartIdx + 4] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerY).normalize(), firstPlaneP, planeN); - vertices[vStartIdx + 5] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerY).normalize(), lastPlaneP , planeN); - - faces[fStartIdx ] = {vStartIdx , vStartIdx + 1, vStartIdx + 2}; - faces[fStartIdx + 1] = {vStartIdx + 1, vStartIdx + 2, vStartIdx + 3}; - faces[fStartIdx + 2] = {vStartIdx , vStartIdx + 1, vStartIdx + 4}; - faces[fStartIdx + 3] = {vStartIdx + 1, vStartIdx + 4, vStartIdx + 5}; - } - - // build first/last depth faces - { - const std::size_t vStartIdx = ri * nbRoiVertices; - const std::size_t fStartIdx = ri * nbRoiFaces + roiCorners.size() * nbRoiCornerFaces; - - // first depth - faces[fStartIdx ] = {vStartIdx, - vStartIdx + 1 * nbRoiCornerVertices, - vStartIdx + 2 * nbRoiCornerVertices}; - - // last depth - faces[fStartIdx + 1] = {vStartIdx + 1 * nbRoiCornerVertices + 1, - vStartIdx + 2 * nbRoiCornerVertices + 1, - vStartIdx + 3 * nbRoiCornerVertices + 1}; - } - } - - aiScene scene; - - scene.mRootNode = new aiNode; - - scene.mMeshes = new aiMesh*[1]; - scene.mNumMeshes = 1; - scene.mRootNode->mMeshes = new unsigned int[1]; - scene.mRootNode->mNumMeshes = 1; - - scene.mMaterials = new aiMaterial*[1]; - scene.mNumMaterials = 1; - scene.mMaterials[0] = new aiMaterial; - - scene.mRootNode->mMeshes[0] = 0; - scene.mMeshes[0] = new aiMesh; - aiMesh* aimesh = scene.mMeshes[0]; - aimesh->mMaterialIndex = 0; - - aimesh->mNumVertices = vertices.size(); - aimesh->mVertices = new aiVector3D[vertices.size()]; - - for(std::size_t i = 0; i < vertices.size(); ++i) - { - const auto& vertex = vertices[i]; - aimesh->mVertices[i].x = vertex.x; - aimesh->mVertices[i].y = -vertex.y; // openGL display - aimesh->mVertices[i].z = -vertex.z; // openGL display - } - - aimesh->mColors[0] = new aiColor4D[vertices.size()]; - - for(std::size_t i = 0; i < vertices.size(); ++i) - { - aimesh->mColors[0][i] = roiColors[(i/nbRoiVertices) % roiColors.size()]; - } - - aimesh->mNumFaces = faces.size(); - aimesh->mFaces = new aiFace[faces.size()]; - - for(std::size_t i = 0; i < faces.size(); ++i) - { - const auto& face = faces[i]; - aimesh->mFaces[i].mNumIndices = 3; - aimesh->mFaces[i].mIndices = new unsigned int[3]; - aimesh->mFaces[i].mIndices[0] = std::get<0>(face); - aimesh->mFaces[i].mIndices[1] = std::get<1>(face); - aimesh->mFaces[i].mIndices[2] = std::get<2>(face); - } - - const std::string formatId = "objnomtl"; - const unsigned int pPreprocessing = 0u; - - Assimp::Exporter exporter; - exporter.Export(&scene, formatId, filepath, pPreprocessing); - - ALICEVISION_LOG_INFO("Save debug tiles pattern obj (rc: " << rc << ", view id: " << mp.getViewId(rc) << ") done."); + const std::string filepath = mvsUtils::getFileNameFromIndex(mp, rc, mvsUtils::EFileType::tilePattern); + + const int nbRoiCornerVertices = 6; // 6 vertices per ROI corner + const int nbRoiCornerFaces = 4; // 4 faces per ROI corner + const int nbRoiVertices = nbRoiCornerVertices * 4; // 24 vertices per ROI + const int nbRoiFaces = nbRoiCornerFaces * 4 + 2; // 18 faces per ROI (16 for corners + 2 for first/last depth) + + std::vector vertices(nbRoiVertices * tileRoiList.size()); + std::vector> faces(nbRoiFaces * tileRoiList.size()); + + const double cornerPixSize = tileRoiList.front().x.size() / 5; // corner bevel size in image pixel + + // 2 points offset from corner (to draw a bevel) + const std::vector> roiCornerOffsets = { + {{cornerPixSize, 0.0}, {0.0, cornerPixSize}}, // corner (roi.x.begin, roi.y.begin) + {{cornerPixSize, 0.0}, {0.0, -cornerPixSize}}, // corner (roi.x.begin, roi.y.end ) + {{-cornerPixSize, 0.0}, {0.0, cornerPixSize}}, // corner (roi.x.end, roi.y.begin) + {{-cornerPixSize, 0.0}, {0.0, -cornerPixSize}} // corner (roi.x.end, roi.y.end ) + }; + + // vertex color sets + const std::vector roiColors = { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {1, 1, 0, 0}, + {0, 1, 1, 0}, + {1, 0, 1, 0}, + }; + + // build vertices and faces for each ROI + for (std::size_t ri = 0; ri < tileRoiList.size(); ++ri) + { + const ROI& roi = tileRoiList.at(ri); + + const auto& minMaxDepth = tileMinMaxDepthsList.at(ri); + const Point3d planeN = (mp.iRArr[rc] * Point3d(0.0f, 0.0f, 1.0f)).normalize(); // plane normal + const Point3d firstPlaneP = mp.CArr[rc] + planeN * minMaxDepth.first; // first depth plane point + const Point3d lastPlaneP = mp.CArr[rc] + planeN * minMaxDepth.second; // last depth plane point + + const std::vector roiCorners = {{double(roi.x.begin), double(roi.y.begin)}, + {double(roi.x.begin), double(roi.y.end)}, + {double(roi.x.end), double(roi.y.begin)}, + {double(roi.x.end), double(roi.y.end)}}; + + // build vertices and faces for each ROI corner + for (std::size_t ci = 0; ci < roiCorners.size(); ++ci) + { + const std::size_t vStartIdx = ri * nbRoiVertices + ci * nbRoiCornerVertices; + const std::size_t fStartIdx = ri * nbRoiFaces + ci * nbRoiCornerFaces; + + const auto& corner = roiCorners.at(ci); // corner 2d point + const auto& cornerOffsets = roiCornerOffsets.at(ci); + + const Point2d cornerX = corner + cornerOffsets.first; // corner 2d point X offsetted + const Point2d cornerY = corner + cornerOffsets.second; // corner 2d point Y offsetted + + vertices[vStartIdx] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * corner).normalize(), firstPlaneP, planeN); + vertices[vStartIdx + 1] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * corner).normalize(), lastPlaneP, planeN); + vertices[vStartIdx + 2] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerX).normalize(), firstPlaneP, planeN); + vertices[vStartIdx + 3] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerX).normalize(), lastPlaneP, planeN); + vertices[vStartIdx + 4] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerY).normalize(), firstPlaneP, planeN); + vertices[vStartIdx + 5] = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * cornerY).normalize(), lastPlaneP, planeN); + + faces[fStartIdx] = {vStartIdx, vStartIdx + 1, vStartIdx + 2}; + faces[fStartIdx + 1] = {vStartIdx + 1, vStartIdx + 2, vStartIdx + 3}; + faces[fStartIdx + 2] = {vStartIdx, vStartIdx + 1, vStartIdx + 4}; + faces[fStartIdx + 3] = {vStartIdx + 1, vStartIdx + 4, vStartIdx + 5}; + } + + // build first/last depth faces + { + const std::size_t vStartIdx = ri * nbRoiVertices; + const std::size_t fStartIdx = ri * nbRoiFaces + roiCorners.size() * nbRoiCornerFaces; + + // first depth + faces[fStartIdx] = {vStartIdx, vStartIdx + 1 * nbRoiCornerVertices, vStartIdx + 2 * nbRoiCornerVertices}; + + // last depth + faces[fStartIdx + 1] = { + vStartIdx + 1 * nbRoiCornerVertices + 1, vStartIdx + 2 * nbRoiCornerVertices + 1, vStartIdx + 3 * nbRoiCornerVertices + 1}; + } + } + + aiScene scene; + + scene.mRootNode = new aiNode; + + scene.mMeshes = new aiMesh*[1]; + scene.mNumMeshes = 1; + scene.mRootNode->mMeshes = new unsigned int[1]; + scene.mRootNode->mNumMeshes = 1; + + scene.mMaterials = new aiMaterial*[1]; + scene.mNumMaterials = 1; + scene.mMaterials[0] = new aiMaterial; + + scene.mRootNode->mMeshes[0] = 0; + scene.mMeshes[0] = new aiMesh; + aiMesh* aimesh = scene.mMeshes[0]; + aimesh->mMaterialIndex = 0; + + aimesh->mNumVertices = vertices.size(); + aimesh->mVertices = new aiVector3D[vertices.size()]; + + for (std::size_t i = 0; i < vertices.size(); ++i) + { + const auto& vertex = vertices[i]; + aimesh->mVertices[i].x = vertex.x; + aimesh->mVertices[i].y = -vertex.y; // openGL display + aimesh->mVertices[i].z = -vertex.z; // openGL display + } + + aimesh->mColors[0] = new aiColor4D[vertices.size()]; + + for (std::size_t i = 0; i < vertices.size(); ++i) + { + aimesh->mColors[0][i] = roiColors[(i / nbRoiVertices) % roiColors.size()]; + } + + aimesh->mNumFaces = faces.size(); + aimesh->mFaces = new aiFace[faces.size()]; + + for (std::size_t i = 0; i < faces.size(); ++i) + { + const auto& face = faces[i]; + aimesh->mFaces[i].mNumIndices = 3; + aimesh->mFaces[i].mIndices = new unsigned int[3]; + aimesh->mFaces[i].mIndices[0] = std::get<0>(face); + aimesh->mFaces[i].mIndices[1] = std::get<1>(face); + aimesh->mFaces[i].mIndices[2] = std::get<2>(face); + } + + const std::string formatId = "objnomtl"; + const unsigned int pPreprocessing = 0u; + + Assimp::Exporter exporter; + exporter.Export(&scene, formatId, filepath, pPreprocessing); + + ALICEVISION_LOG_INFO("Save debug tiles pattern obj (rc: " << rc << ", view id: " << mp.getViewId(rc) << ") done."); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/depthMapUtils.hpp b/src/aliceVision/depthMap/depthMapUtils.hpp index 2455c15837..ff49bddb28 100644 --- a/src/aliceVision/depthMap/depthMapUtils.hpp +++ b/src/aliceVision/depthMap/depthMapUtils.hpp @@ -21,7 +21,7 @@ namespace depthMap { /** * @brief Copy an image from device memory to host memory and write on disk. - * @note This function can be useful for code analysis and debugging. + * @note This function can be useful for code analysis and debugging. * @param[in] in_img_dmp the image in device memory * @param[in] path the path of the output image on disk */ @@ -79,13 +79,13 @@ void writeNormalMapFiltered(int rc, * @param[in] name the export filename suffix */ void writeDepthThicknessMap(int rc, - const mvsUtils::MultiViewParams& mp, - const mvsUtils::TileParams& tileParams, - const ROI& roi, - const CudaDeviceMemoryPitched& in_depthThicknessMap_dmp, - int scale, - int step, - const std::string& name = ""); + const mvsUtils::MultiViewParams& mp, + const mvsUtils::TileParams& tileParams, + const ROI& roi, + const CudaDeviceMemoryPitched& in_depthThicknessMap_dmp, + int scale, + int step, + const std::string& name = ""); /** * @brief Write a depth/pixSize map on disk from device memory. @@ -121,7 +121,7 @@ void writeDepthPixSizeMap(int rc, void writeDepthSimMap(int rc, const mvsUtils::MultiViewParams& mp, const mvsUtils::TileParams& tileParams, - const ROI& roi, + const ROI& roi, const CudaDeviceMemoryPitched& in_depthSimMap_dmp, int scale, int step, @@ -163,11 +163,7 @@ void resetDepthSimMap(CudaHostMemoryHeap& inout_depthSimMap_hmh, floa * @param[in] step the normal map step factor * @param[in] name the export filename suffix */ -void mergeNormalMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name = ""); +void mergeNormalMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name = ""); /** * @brief Merge depth/thickness map tiles on disk. @@ -177,11 +173,7 @@ void mergeNormalMapTiles(int rc, * @param[in] step the depth/thickness map step factor * @param[in] name the export filename suffix */ -void mergeDepthThicknessMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name = ""); +void mergeDepthThicknessMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name = ""); /** * @brief Merge depth/pixSize map tiles on disk. @@ -191,11 +183,7 @@ void mergeDepthThicknessMapTiles(int rc, * @param[in] step the depth/pixSize map step factor * @param[in] name the export filename suffix */ -void mergeDepthPixSizeMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name = ""); +void mergeDepthPixSizeMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name = ""); /** * @brief Merge depth/similarity map tiles on disk. @@ -205,11 +193,7 @@ void mergeDepthPixSizeMapTiles(int rc, * @param[in] step the depth/similarity map step factor * @param[in] name the export filename suffix */ -void mergeDepthSimMapTiles(int rc, - const mvsUtils::MultiViewParams& mp, - int scale, - int step, - const std::string& name = ""); +void mergeDepthSimMapTiles(int rc, const mvsUtils::MultiViewParams& mp, int scale, int step, const std::string& name = ""); /** * @brief Build and write a debug OBJ file with all tiles areas @@ -223,6 +207,5 @@ void exportDepthSimMapTilePatternObj(int rc, const std::vector& tileRoiList, const std::vector>& tileMinMaxDepthsList); -} // namespace depthMap -} // namespace aliceVision - +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/volumeIO.cpp b/src/aliceVision/depthMap/volumeIO.cpp index 38a160d113..bb917f55cc 100644 --- a/src/aliceVision/depthMap/volumeIO.cpp +++ b/src/aliceVision/depthMap/volumeIO.cpp @@ -25,9 +25,9 @@ namespace aliceVision { namespace depthMap { -void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - const std::string& name, + const std::string& name, const SgmParams& sgmParams, const std::string& filepath, const ROI& roi) @@ -39,11 +39,11 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_ const int sampleSize = 3; - const int xOffset = std::floor(downscaledRoi.width() / (sampleSize + 1.0f)); + const int xOffset = std::floor(downscaledRoi.width() / (sampleSize + 1.0f)); const int yOffset = std::floor(downscaledRoi.height() / (sampleSize + 1.0f)); - std::vector ptsCoords(sampleSize*sampleSize); - std::vector> ptsDepths(sampleSize*sampleSize); + std::vector ptsCoords(sampleSize * sampleSize); + std::vector> ptsDepths(sampleSize * sampleSize); for (int iy = 0; iy < sampleSize; ++iy) { @@ -58,7 +58,7 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_ pDepths.reserve(in_depths.size()); - for(int iz = 0; iz < in_depths.size(); ++iz) + for (int iz = 0; iz < in_depths.size(); ++iz) { const float simValue = float(*get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, x, y, iz)); pDepths.push_back(simValue); @@ -70,23 +70,23 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_ ss << name << "\n"; - for(int i = 0; i < ptsCoords.size(); ++i) + for (int i = 0; i < ptsCoords.size(); ++i) { const Point2d& coord = ptsCoords.at(i); - ss << "p" << (i + 1) << " (x: " << coord.x << ", y: " << coord.y << ");"; - for(const float depth : ptsDepths.at(i)) + ss << "p" << (i + 1) << " (x: " << coord.x << ", y: " << coord.y << ");"; + for (const float depth : ptsDepths.at(i)) ss << depth << ";"; ss << "\n"; } std::ofstream file; file.open(filepath, std::ios_base::app); - if(file.is_open()) + if (file.is_open()) file << ss.str(); } void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, - const std::string& name, + const std::string& name, const RefineParams& refineParams, const std::string& filepath, const ROI& roi) @@ -99,15 +99,15 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volu const int sampleSize = 3; - const int xOffset = std::floor(downscaledRoi.width() / (sampleSize + 1.0f)); + const int xOffset = std::floor(downscaledRoi.width() / (sampleSize + 1.0f)); const int yOffset = std::floor(downscaledRoi.height() / (sampleSize + 1.0f)); - std::vector ptsCoords(sampleSize*sampleSize); + std::vector ptsCoords(sampleSize * sampleSize); std::vector> ptsDepths(sampleSize * sampleSize); - for(int iy = 0; iy < sampleSize; ++iy) + for (int iy = 0; iy < sampleSize; ++iy) { - for(int ix = 0; ix < sampleSize; ++ix) + for (int ix = 0; ix < sampleSize; ++ix) { const int ptIdx = iy * sampleSize + ix; const int x = (ix + 1) * xOffset; @@ -118,7 +118,7 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volu pDepths.reserve(volDimZ); - for(int iz = 0; iz < volDimZ; ++iz) + for (int iz = 0; iz < volDimZ; ++iz) { const float simValue = float(*get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, x, y, iz)); pDepths.push_back(simValue); @@ -130,27 +130,27 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volu ss << name << "\n"; - for(int i = 0; i < ptsCoords.size(); ++i) + for (int i = 0; i < ptsCoords.size(); ++i) { const Point2d& coord = ptsCoords.at(i); - ss << "p" << (i + 1) << " (x: " << coord.x << ", y: " << coord.y << ");"; - for(const float depth : ptsDepths.at(i)) + ss << "p" << (i + 1) << " (x: " << coord.x << ", y: " << coord.y << ");"; + for (const float depth : ptsDepths.at(i)) ss << depth << ";"; ss << "\n"; } std::ofstream file; file.open(filepath, std::ios_base::app); - if(file.is_open()) + if (file.is_open()) file << ss.str(); } -void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - const mvsUtils::MultiViewParams& mp, - int camIndex, - const SgmParams& sgmParams, - const std::string& filepath, + const mvsUtils::MultiViewParams& mp, + int camIndex, + const SgmParams& sgmParams, + const std::string& filepath, const ROI& roi) { sfmData::SfMData pointCloud; @@ -169,12 +169,12 @@ void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const double x = roi.x.begin + (vx * sgmParams.scale * sgmParams.stepXY); const double y = roi.y.begin + (vy * sgmParams.scale * sgmParams.stepXY); - for(int vz = 0; vz < in_depths.size(); ++vz) + for (int vz = 0; vz < in_depths.size(); ++vz) { const double planeDepth = in_depths[vz]; const Point3d planen = (mp.iRArr[camIndex] * Point3d(0.0f, 0.0f, 1.0f)).normalize(); const Point3d planep = mp.CArr[camIndex] + planen * planeDepth; - const Point3d v = (mp.iCamArr[camIndex] * Point2d(x,y)).normalize(); + const Point3d v = (mp.iCamArr[camIndex] * Point2d(x, y)).normalize(); const Point3d p = linePlaneIntersect(mp.CArr[camIndex], v, planep, planen); const float maxValue = 80.f; @@ -182,7 +182,8 @@ void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, if (simValue > maxValue) continue; const rgb c = getRGBFromJetColorMap(simValue / maxValue); - pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); + pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark( + Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); ++landmarkId; } @@ -192,10 +193,10 @@ void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, sfmDataIO::Save(pointCloud, filepath, sfmDataIO::ESfMData::STRUCTURE); } -void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - const mvsUtils::MultiViewParams& mp, - int camIndex, + const mvsUtils::MultiViewParams& mp, + int camIndex, const SgmParams& sgmParams, const std::string& filepath, const ROI& roi) @@ -208,32 +209,33 @@ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim const size_t spitch = in_volumeSim_hmh.getBytesPaddedUpToDim(1); const size_t pitch = in_volumeSim_hmh.getBytesPaddedUpToDim(0); - for(int vz = 0; vz < in_depths.size(); ++vz) + for (int vz = 0; vz < in_depths.size(); ++vz) { - for(int vy = 0; vy < volDim[1]; ++vy) + for (int vy = 0; vy < volDim[1]; ++vy) { - const bool vyCenter = (vy >= volDim[1]/2) && ((vy-1)< volDim[1]/2); + const bool vyCenter = (vy >= volDim[1] / 2) && ((vy - 1) < volDim[1] / 2); const int xIdxStart = (vyCenter ? 0 : (volDim[0] / 2)); const int xIdxStop = (vyCenter ? volDim[0] : (xIdxStart + 1)); - for(int vx = xIdxStart; vx < xIdxStop; ++vx) + for (int vx = xIdxStart; vx < xIdxStop; ++vx) { const double x = roi.x.begin + (vx * sgmParams.scale * sgmParams.stepXY); const double y = roi.y.begin + (vy * sgmParams.scale * sgmParams.stepXY); const double planeDepth = in_depths[vz]; const Point3d planen = (mp.iRArr[camIndex] * Point3d(0.0f, 0.0f, 1.0f)).normalize(); const Point3d planep = mp.CArr[camIndex] + planen * planeDepth; - const Point3d v = (mp.iCamArr[camIndex] * Point2d(x,y)).normalize(); + const Point3d v = (mp.iCamArr[camIndex] * Point2d(x, y)).normalize(); const Point3d p = linePlaneIntersect(mp.CArr[camIndex], v, planep, planen); const float maxValue = 80.f; float simValue = *get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz); - if(simValue > maxValue) + if (simValue > maxValue) continue; const rgb c = getRGBFromJetColorMap(simValue / maxValue); - pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); + pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark( + Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); ++landmarkId; } @@ -243,12 +245,12 @@ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim sfmDataIO::Save(pointCloud, filepath, sfmDataIO::ESfMData::STRUCTURE); } -void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, const CudaHostMemoryHeap& in_depthSimMapSgmUpscale_hmh, - const mvsUtils::MultiViewParams& mp, - int camIndex, + const mvsUtils::MultiViewParams& mp, + int camIndex, const RefineParams& refineParams, - const std::string& filepath, + const std::string& filepath, const ROI& roi) { sfmData::SfMData pointCloud; @@ -259,13 +261,13 @@ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_vol IndexT landmarkId = 0; - for(int vy = 0; vy < volDim[1]; ++vy) + for (int vy = 0; vy < volDim[1]; ++vy) { - const bool vyCenter = ((vy*2) == volDim[1]); + const bool vyCenter = ((vy * 2) == volDim[1]); const int xIdxStart = (vyCenter ? 0 : (volDim[0] / 2)); const int xIdxStop = (vyCenter ? volDim[0] : (xIdxStart + 1)); - for(int vx = xIdxStart; vx < xIdxStop; ++vx) + for (int vx = xIdxStart; vx < xIdxStop; ++vx) { const int x = roi.x.begin + (double(vx) * refineParams.scale * refineParams.stepXY); const int y = roi.y.begin + (double(vy) * refineParams.scale * refineParams.stepXY); @@ -273,24 +275,25 @@ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_vol const float2 depthPixSizeMap = in_depthSimMapSgmUpscale_hmh(vx, vy); - if(depthPixSizeMap.x < 0.0f) // original depth invalid or masked + if (depthPixSizeMap.x < 0.0f) // original depth invalid or masked continue; - for(int vz = 0; vz < volDim[2]; ++vz) + for (int vz = 0; vz < volDim[2]; ++vz) { const float simValue = float(*get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz)); - const float maxValue = 10.f; // sum of similarity between 0 and 1 - if(simValue > maxValue) + const float maxValue = 10.f; // sum of similarity between 0 and 1 + if (simValue > maxValue) continue; const int relativeDepthIndexOffset = vz - refineParams.halfNbDepths; - const double depth = depthPixSizeMap.x + (relativeDepthIndexOffset * depthPixSizeMap.y); // original depth + z based pixSize offset + const double depth = depthPixSizeMap.x + (relativeDepthIndexOffset * depthPixSizeMap.y); // original depth + z based pixSize offset const Point3d p = mp.CArr[camIndex] + (mp.iCamArr[camIndex] * pix).normalize() * depth; const rgb c = getRGBFromJetColorMap(simValue / maxValue); - pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); + pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark( + Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); ++landmarkId; } @@ -300,7 +303,7 @@ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_vol sfmDataIO::Save(pointCloud, filepath, sfmDataIO::ESfMData::STRUCTURE); } -void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, const mvsUtils::MultiViewParams& mp, int camIndex, @@ -313,7 +316,7 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_v const auto volDim = in_volumeSim_hmh.getSize(); const size_t spitch = in_volumeSim_hmh.getBytesPaddedUpToDim(1); const size_t pitch = in_volumeSim_hmh.getBytesPaddedUpToDim(0); - const size_t vy = size_t(divideRoundUp(int(volDim.y()), 2)); // center only + const size_t vy = size_t(divideRoundUp(int(volDim.y()), 2)); // center only const Point3d planen = (mp.iRArr[camIndex] * Point3d(0.0f, 0.0f, 1.0f)).normalize(); @@ -321,14 +324,14 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_v float minSim = std::numeric_limits::max(); float maxSim = std::numeric_limits::min(); - for(size_t vx = 0; vx < volDim.x(); ++vx) + for (size_t vx = 0; vx < volDim.x(); ++vx) { - for(size_t vz = 0; vz < volDim.z(); ++vz) + for (size_t vz = 0; vz < volDim.z(); ++vz) { const float simValue = float(*get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz)); - if(simValue > 254.f) // invalid similarity - continue; + if (simValue > 254.f) // invalid similarity + continue; maxSim = std::max(maxSim, simValue); minSim = std::min(minSim, simValue); @@ -340,18 +343,18 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_v // compute each point color and position IndexT landmarkId = 0; - for(size_t vx = 0; vx < volDim.x(); ++vx) + for (size_t vx = 0; vx < volDim.x(); ++vx) { const double x = roi.x.begin + (vx * sgmParams.scale * sgmParams.stepXY); const double y = roi.y.begin + (vy * sgmParams.scale * sgmParams.stepXY); const Point2d pix(x, y); - for(size_t vz = 0; vz < in_depths.size(); ++vz) + for (size_t vz = 0; vz < in_depths.size(); ++vz) { const float simValue = float(*get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz)); - if(simValue > 254.f) // invalid similarity - continue; + if (simValue > 254.f) // invalid similarity + continue; const float simValueNorm = (simValue - minSim) * simNorm; @@ -361,7 +364,8 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_v const Point3d p = linePlaneIntersect(mp.CArr[camIndex], v, planep, planen); const rgb c = getRGBFromJetColorMap(simValueNorm); - pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); + pointCloud.getLandmarks()[landmarkId] = + sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); ++landmarkId; } @@ -384,15 +388,15 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap::epsilon(); - for(size_t vx = 0; vx < volDim.x(); ++vx) + for (size_t vx = 0; vx < volDim.x(); ++vx) { - for(size_t vz = 0; vz < volDim.z(); ++vz) + for (size_t vz = 0; vz < volDim.z(); ++vz) { const float simValue = float(*get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz)); maxSim = std::max(maxSim, simValue); @@ -402,7 +406,7 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz)); const float simValueNorm = (simValue - minSim) / (maxSim - minSim); - const float simValueColor = 1 - simValueNorm; // best similarity value is 0, worst value is 1 + const float simValueColor = 1 - simValueNorm; // best similarity value is 0, worst value is 1 const int relativeDepthIndexOffset = vz - refineParams.halfNbDepths; - const double depth = depthPixSizeMap.x + (relativeDepthIndexOffset * depthPixSizeMap.y); // original depth + z based pixSize offset + const double depth = depthPixSizeMap.x + (relativeDepthIndexOffset * depthPixSizeMap.y); // original depth + z based pixSize offset const Point3d p = mp.CArr[camIndex] + (mp.iCamArr[camIndex] * (pix + Point2d(0.0, -simValueNorm * 15.0))).normalize() * depth; const rgb c = getRGBFromJetColorMap(simValueColor); - pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); + pointCloud.getLandmarks()[landmarkId] = + sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); ++landmarkId; } @@ -443,19 +448,16 @@ inline unsigned char float_to_uchar(float v) return out; } -inline rgb float4_to_rgb(const float4& v) -{ - return { float_to_uchar(v.x), float_to_uchar(v.y), float_to_uchar(v.z) }; -} +inline rgb float4_to_rgb(const float4& v) { return {float_to_uchar(v.x), float_to_uchar(v.y), float_to_uchar(v.z)}; } -void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - int startDepth, - int nbDepths, - const mvsUtils::MultiViewParams& mp, - int camIndex, - int scale, - int step, + int startDepth, + int nbDepths, + const mvsUtils::MultiViewParams& mp, + int camIndex, + int scale, + int step, const std::string& filepath, const ROI& roi) { @@ -468,8 +470,8 @@ void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, size_t spitch = in_volumeSim_hmh.getBytesPaddedUpToDim(1); size_t pitch = in_volumeSim_hmh.getBytesPaddedUpToDim(0); - ALICEVISION_LOG_DEBUG("DepthMap exportColorVolume: " << volDim[0] << " x " << volDim[1] << " x " << nbDepths << ", volDim[2]=" << volDim[2] << ", xyStep=" << xyStep << "."); - + ALICEVISION_LOG_DEBUG("DepthMap exportColorVolume: " << volDim[0] << " x " << volDim[1] << " x " << nbDepths << ", volDim[2]=" << volDim[2] + << ", xyStep=" << xyStep << "."); for (int vy = 0; vy < volDim[1]; vy += xyStep) { @@ -478,7 +480,7 @@ void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const double x = roi.x.begin + (vx * scale * step); const double y = roi.y.begin + (vy * scale * step); - for(int vz = 0; vz < nbDepths; ++vz) + for (int vz = 0; vz < nbDepths; ++vz) { const double planeDepth = in_depths[startDepth + vz]; const Point3d planen = (mp.iRArr[camIndex] * Point3d(0.0f, 0.0f, 1.0f)).normalize(); @@ -487,8 +489,9 @@ void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const Point3d p = linePlaneIntersect(mp.CArr[camIndex], v, planep, planen); float4 colorValue = *get3DBufferAt_h(in_volumeSim_hmh.getBuffer(), spitch, pitch, vx, vy, vz); - const rgb c = float4_to_rgb(colorValue); // TODO: convert Lab color into sRGB color - pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark(Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); + const rgb c = float4_to_rgb(colorValue); // TODO: convert Lab color into sRGB color + pointCloud.getLandmarks()[landmarkId] = sfmData::Landmark( + Vec3(p.x, p.y, p.z), feature::EImageDescriberType::UNKNOWN, sfmData::Observations(), image::RGBColor(c.r, c.g, c.b)); ++landmarkId; } @@ -498,5 +501,5 @@ void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, sfmDataIO::Save(pointCloud, filepath, sfmDataIO::ESfMData::STRUCTURE); } -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/depthMap/volumeIO.hpp b/src/aliceVision/depthMap/volumeIO.hpp index 17cd2ca4dd..b024538230 100644 --- a/src/aliceVision/depthMap/volumeIO.hpp +++ b/src/aliceVision/depthMap/volumeIO.hpp @@ -26,9 +26,9 @@ namespace depthMap { * @param[in] filepath the export filepath * @param[in] roi the 2d region of interest */ -void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - const std::string& name, + const std::string& name, const SgmParams& sgmParams, const std::string& filepath, const ROI& roi); @@ -41,8 +41,8 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_ * @param[in] filepath the export filepath * @param[in] roi the 2d region of interest */ -void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, - const std::string& name, +void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volumeSim_hmh, + const std::string& name, const RefineParams& refineParams, const std::string& filepath, const ROI& roi); @@ -57,10 +57,10 @@ void exportSimilaritySamplesCSV(const CudaHostMemoryHeap& in_volu * @param[in] filepath the export filepath * @param[in] roi the 2d region of interest */ -void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - const mvsUtils::MultiViewParams& mp, - int camIndex, + const mvsUtils::MultiViewParams& mp, + int camIndex, const SgmParams& sgmParams, const std::string& filepath, const ROI& roi); @@ -75,12 +75,12 @@ void exportSimilarityVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, * @param[in] filepath the export filepath * @param[in] roi the 2d region of interest */ -void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - const mvsUtils::MultiViewParams& mp, - int camIndex, + const mvsUtils::MultiViewParams& mp, + int camIndex, const SgmParams& sgmParams, - const std::string& filepath, + const std::string& filepath, const ROI& roi); /** @@ -95,10 +95,10 @@ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim */ void exportSimilarityVolumeCross(const CudaHostMemoryHeap& in_volumeSim_hmh, const CudaHostMemoryHeap& in_depthSimMapSgmUpscale_hmh, - const mvsUtils::MultiViewParams& mp, + const mvsUtils::MultiViewParams& mp, int camIndex, - const RefineParams& refineParams, - const std::string& filepath, + const RefineParams& refineParams, + const std::string& filepath, const ROI& roi); /** @@ -140,16 +140,16 @@ void exportSimilarityVolumeTopographicCut(const CudaHostMemoryHeap& in_volumeSim_hmh, +void exportColorVolume(const CudaHostMemoryHeap& in_volumeSim_hmh, const std::vector& in_depths, - int startDepth, - int nbDepths, - const mvsUtils::MultiViewParams& mp, - int camIndex, - int scale, - int step, - const std::string& filepath, + int startDepth, + int nbDepths, + const mvsUtils::MultiViewParams& mp, + int camIndex, + int scale, + int step, + const std::string& filepath, const ROI& roi); -} // namespace depthMap -} // namespace aliceVision +} // namespace depthMap +} // namespace aliceVision diff --git a/src/aliceVision/feature/Descriptor.hpp b/src/aliceVision/feature/Descriptor.hpp index 8acaea01ac..d22db3f95e 100644 --- a/src/aliceVision/feature/Descriptor.hpp +++ b/src/aliceVision/feature/Descriptor.hpp @@ -25,190 +25,184 @@ namespace feature { * Surf 64 => */ -template +template class Descriptor { -public: - typedef Descriptor This; - typedef T value_type; - typedef T bin_type; - typedef std::size_t size_type; - - /// Compile-time length of the descriptor - static const size_type static_size = N; - - /// Constructor - inline Descriptor() {} - - inline Descriptor(T defaultValue) - { - for(size_type i = 0; i < N; ++i) - data[i] = defaultValue; - } - - /// capacity - inline size_type size() const { return N; } - - /// Mutable and non-mutable bin getters - inline bin_type& operator[](std::size_t i) { return data[i]; } - inline bin_type operator[](std::size_t i) const { return data[i]; } - - // Atomic addition between two descriptors - inline This& operator+=(const This other) - { - for(size_type i = 0; i < size(); ++i) - data[i] += other[i]; - return *this; - } - - // Division between two descriptors - inline This operator/(const This other) const - { - This res; - for(size_type i = 0; i < size(); ++i) - res[i] = data[i] / other[i]; - return res; - } - - inline This& operator*=(const value_type scalar) - { - for(size_type i = 0; i < size(); ++i) - data[i] *= scalar; - return *this; - } - - bool operator==(const Descriptor& other) const - { - return std::equal(data, data+N, other.data); - } - - inline bin_type* getData() const {return (bin_type* ) (&data[0]);} - - /// Ostream interface - std::ostream& print(std::ostream& os) const; - /// Istream interface - std::istream& read(std::istream& in); - - template - void save(Archive & archive) const - { - std::vector array(data,data+N); - archive( array ); - } - - template - void load(Archive & archive) - { - std::vector array(N); - archive( array ); - std::memcpy(data, array.data(), sizeof(T)*N); - } - -private: - bin_type data[N]; + public: + typedef Descriptor This; + typedef T value_type; + typedef T bin_type; + typedef std::size_t size_type; + + /// Compile-time length of the descriptor + static const size_type static_size = N; + + /// Constructor + inline Descriptor() {} + + inline Descriptor(T defaultValue) + { + for (size_type i = 0; i < N; ++i) + data[i] = defaultValue; + } + + /// capacity + inline size_type size() const { return N; } + + /// Mutable and non-mutable bin getters + inline bin_type& operator[](std::size_t i) { return data[i]; } + inline bin_type operator[](std::size_t i) const { return data[i]; } + + // Atomic addition between two descriptors + inline This& operator+=(const This other) + { + for (size_type i = 0; i < size(); ++i) + data[i] += other[i]; + return *this; + } + + // Division between two descriptors + inline This operator/(const This other) const + { + This res; + for (size_type i = 0; i < size(); ++i) + res[i] = data[i] / other[i]; + return res; + } + + inline This& operator*=(const value_type scalar) + { + for (size_type i = 0; i < size(); ++i) + data[i] *= scalar; + return *this; + } + + bool operator==(const Descriptor& other) const { return std::equal(data, data + N, other.data); } + + inline bin_type* getData() const { return (bin_type*)(&data[0]); } + + /// Ostream interface + std::ostream& print(std::ostream& os) const; + /// Istream interface + std::istream& read(std::istream& in); + + template + void save(Archive& archive) const + { + std::vector array(data, data + N); + archive(array); + } + + template + void load(Archive& archive) + { + std::vector array(N); + archive(array); + std::memcpy(data, array.data(), sizeof(T) * N); + } + + private: + bin_type data[N]; }; // Output stream definition -template +template inline std::ostream& operator<<(std::ostream& out, const Descriptor& obj) { - return obj.print(out); //simply call the print method. + return obj.print(out); // simply call the print method. } // Input stream definition -template +template inline std::istream& operator>>(std::istream& in, Descriptor& obj) { - return obj.read(in); //simply call the read method. + return obj.read(in); // simply call the read method. } //-- Use specialization to handle unsigned char case. //-- We do not want confuse unsigned char value with the spaces written in the file template -inline std::ostream& printT(std::ostream& os, T *tab, size_t N) +inline std::ostream& printT(std::ostream& os, T* tab, size_t N) { - std::copy( tab, &tab[N], std::ostream_iterator(os," ")); - return os; + std::copy(tab, &tab[N], std::ostream_iterator(os, " ")); + return os; } template<> -inline std::ostream& printT(std::ostream& os, unsigned char *tab, size_t N) +inline std::ostream& printT(std::ostream& os, unsigned char* tab, size_t N) { - for(size_t i=0; i < N; ++i) - os << (int)tab[i] << " "; - return os; + for (size_t i = 0; i < N; ++i) + os << (int)tab[i] << " "; + return os; } template -inline std::istream& readT(std::istream& is, T *tab, size_t N) +inline std::istream& readT(std::istream& is, T* tab, size_t N) { - for(size_t i=0; i> tab[i]; - return is; + for (size_t i = 0; i < N; ++i) + is >> tab[i]; + return is; } template<> -inline std::istream& readT(std::istream& is, unsigned char *tab, size_t N) +inline std::istream& readT(std::istream& is, unsigned char* tab, size_t N) { - int temp = -1; - for(size_t i=0; i < N; ++i){ - is >> temp; tab[i] = (unsigned char)temp; - } - return is; + int temp = -1; + for (size_t i = 0; i < N; ++i) + { + is >> temp; + tab[i] = (unsigned char)temp; + } + return is; } template -std::ostream& Descriptor::print(std::ostream& os) const +std::ostream& Descriptor::print(std::ostream& os) const { - return printT(os, (T*) &data[0], N); + return printT(os, (T*)&data[0], N); } template -std::istream& Descriptor::read(std::istream& in) +std::istream& Descriptor::read(std::istream& in) { - return readT(in, (T*) &data[0], N); + return readT(in, (T*)&data[0], N); } /// Read descriptors from file -template -inline void loadDescsFromFile( - const std::string & sfileNameDescs, - DescriptorsT & vec_desc) +template +inline void loadDescsFromFile(const std::string& sfileNameDescs, DescriptorsT& vec_desc) { - vec_desc.clear(); + vec_desc.clear(); - std::ifstream fileIn(sfileNameDescs); - if(!fileIn.is_open()) - throw std::runtime_error("Can't load descriptor file, can't open '" + sfileNameDescs + "' !"); + std::ifstream fileIn(sfileNameDescs); + if (!fileIn.is_open()) + throw std::runtime_error("Can't load descriptor file, can't open '" + sfileNameDescs + "' !"); - std::copy( - std::istream_iterator(fileIn), - std::istream_iterator(), - std::back_inserter(vec_desc)); + std::copy(std::istream_iterator(fileIn), + std::istream_iterator(), + std::back_inserter(vec_desc)); - if(fileIn.bad()) - throw std::runtime_error("Can't load descriptor file, '" + sfileNameDescs + "' is incorrect !"); + if (fileIn.bad()) + throw std::runtime_error("Can't load descriptor file, '" + sfileNameDescs + "' is incorrect !"); - fileIn.close(); + fileIn.close(); } /// Write descriptors to file -template -inline void saveDescsToFile( - const std::string & sfileNameDescs, - DescriptorsT & vec_desc) +template +inline void saveDescsToFile(const std::string& sfileNameDescs, DescriptorsT& vec_desc) { - std::ofstream file(sfileNameDescs); - if(!file.is_open()) - throw std::runtime_error("Can't save descriptor file, can't open '" + sfileNameDescs + "' !"); + std::ofstream file(sfileNameDescs); + if (!file.is_open()) + throw std::runtime_error("Can't save descriptor file, can't open '" + sfileNameDescs + "' !"); - std::copy(vec_desc.begin(), vec_desc.end(), - std::ostream_iterator(file,"\n")); + std::copy(vec_desc.begin(), vec_desc.end(), std::ostream_iterator(file, "\n")); - if(!file.good()) - throw std::runtime_error("Can't save descriptor file, '" + sfileNameDescs + "' is incorrect !"); + if (!file.good()) + throw std::runtime_error("Can't save descriptor file, '" + sfileNameDescs + "' is incorrect !"); - file.close(); + file.close(); } /** @@ -219,111 +213,98 @@ inline void saveDescsToFile( * @param descTo */ template -void convertDesc( - const DescFrom& descFrom, - DescTo& descTo) +void convertDesc(const DescFrom& descFrom, DescTo& descTo) { + typename DescFrom::bin_type* ptrFrom = descFrom.getData(); + typename DescTo::bin_type* ptrTo = descTo.getData(); - typename DescFrom::bin_type* ptrFrom = descFrom.getData(); - typename DescTo::bin_type* ptrTo = descTo.getData(); - - for (size_t i = 0; i < DescFrom::static_size; ++i) - { - ptrTo[i] = typename DescTo::value_type(ptrFrom[i]); - } + for (size_t i = 0; i < DescFrom::static_size; ++i) + { + ptrTo[i] = typename DescTo::value_type(ptrFrom[i]); + } } - /** - * @brief It load descriptors from a given binary file (.desc). \p DescriptorT is + * @brief It load descriptors from a given binary file (.desc). \p DescriptorT is * the type of descriptor in which to store the data loaded from the file. \p FileDescriptorT is * the type of descriptors that are stored in the file. Usually the two types should * be the same, but it could be the case in which the descriptor stored in the file * has different type representation: for example the file could contain SIFT descriptors * stored as uchar (the default type) and we want to cast these into SIFT descriptors * stored in memory as floats. - * + * * @param[in] sfileNameDescs The file name (usually .desc) * @param[out] vec_desc A vector of descriptors that stores the descriptors to load - * @param[in] append If true, the loaded descriptors will be appended at the end + * @param[in] append If true, the loaded descriptors will be appended at the end * of the vector \p vec_desc * @param[in] Nmax Limit the number of descriptors to load * (default value is 0 which means all descriptors). * @return true if everything went well */ template -inline void loadDescsFromBinFile( - const std::string & sfileNameDescs, - std::vector & vec_desc, - bool append = false, - const int Nmax = 0) +inline void loadDescsFromBinFile(const std::string& sfileNameDescs, std::vector& vec_desc, bool append = false, const int Nmax = 0) { - if( !append ) // for compatibility - vec_desc.clear(); + if (!append) // for compatibility + vec_desc.clear(); - std::ifstream fileIn(sfileNameDescs, std::ios::in | std::ios::binary); + std::ifstream fileIn(sfileNameDescs, std::ios::in | std::ios::binary); - if(!fileIn.is_open()) - throw std::runtime_error("Can't load descriptor binary file, can't open '" + sfileNameDescs + "' !"); + if (!fileIn.is_open()) + throw std::runtime_error("Can't load descriptor binary file, can't open '" + sfileNameDescs + "' !"); - //Read the number of descriptor in the file - std::size_t cardDesc = 0; - fileIn.read((char*) &cardDesc, sizeof(std::size_t)); + // Read the number of descriptor in the file + std::size_t cardDesc = 0; + fileIn.read((char*)&cardDesc, sizeof(std::size_t)); - std::size_t previousSize = vec_desc.size(); + std::size_t previousSize = vec_desc.size(); - if (Nmax != 0) - vec_desc.resize(vec_desc.size() + std::min((int)cardDesc, Nmax)); - else - vec_desc.resize(vec_desc.size() + cardDesc); - typename std::vector::iterator begin = vec_desc.begin(); - std::advance(begin, previousSize); + if (Nmax != 0) + vec_desc.resize(vec_desc.size() + std::min((int)cardDesc, Nmax)); + else + vec_desc.resize(vec_desc.size() + cardDesc); + typename std::vector::iterator begin = vec_desc.begin(); + std::advance(begin, previousSize); - // Compute the memory size of one descriptor - constexpr std::size_t oneDescSize = FileDescriptorT::static_size * sizeof(typename FileDescriptorT::bin_type); + // Compute the memory size of one descriptor + constexpr std::size_t oneDescSize = FileDescriptorT::static_size * sizeof(typename FileDescriptorT::bin_type); - FileDescriptorT fileDescriptor; - for (typename std::vector::iterator iter = begin; - iter != vec_desc.end(); ++iter) - { - fileIn.read((char*)fileDescriptor.getData(), oneDescSize); - convertDesc(fileDescriptor, *iter); - } + FileDescriptorT fileDescriptor; + for (typename std::vector::iterator iter = begin; iter != vec_desc.end(); ++iter) + { + fileIn.read((char*)fileDescriptor.getData(), oneDescSize); + convertDesc(fileDescriptor, *iter); + } - if(fileIn.bad()) - throw std::runtime_error("Can't load descriptor binary file, '" + sfileNameDescs + "' is incorrect !"); + if (fileIn.bad()) + throw std::runtime_error("Can't load descriptor binary file, '" + sfileNameDescs + "' is incorrect !"); - fileIn.close(); + fileIn.close(); } /// Write descriptors to file (in binary mode) -template -inline void saveDescsToBinFile( - const std::string & sfileNameDescs, - DescriptorsT & vec_desc) +template +inline void saveDescsToBinFile(const std::string& sfileNameDescs, DescriptorsT& vec_desc) { - typedef typename DescriptorsT::value_type VALUE; + typedef typename DescriptorsT::value_type VALUE; - std::ofstream file(sfileNameDescs, std::ios::out | std::ios::binary); + std::ofstream file(sfileNameDescs, std::ios::out | std::ios::binary); - if (!file.is_open()) - throw std::runtime_error("Can't save descriptor binary file, can't open '" + sfileNameDescs + "' !"); + if (!file.is_open()) + throw std::runtime_error("Can't save descriptor binary file, can't open '" + sfileNameDescs + "' !"); - //Write the number of descriptor - const std::size_t cardDesc = vec_desc.size(); - file.write((const char*) &cardDesc, sizeof(std::size_t)); - for (typename DescriptorsT::const_iterator iter = vec_desc.begin(); - iter != vec_desc.end(); ++iter) - { - file.write((const char*) (*iter).getData(), - VALUE::static_size*sizeof(typename VALUE::bin_type)); - } + // Write the number of descriptor + const std::size_t cardDesc = vec_desc.size(); + file.write((const char*)&cardDesc, sizeof(std::size_t)); + for (typename DescriptorsT::const_iterator iter = vec_desc.begin(); iter != vec_desc.end(); ++iter) + { + file.write((const char*)(*iter).getData(), VALUE::static_size * sizeof(typename VALUE::bin_type)); + } - if(!file.good()) - throw std::runtime_error("Can't save descriptor binary file, '" + sfileNameDescs + "' is incorrect !"); + if (!file.good()) + throw std::runtime_error("Can't save descriptor binary file, '" + sfileNameDescs + "' is incorrect !"); - file.close(); + file.close(); } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/FeaturesPerView.cpp b/src/aliceVision/feature/FeaturesPerView.cpp index 7458bbb005..9344c52bc1 100644 --- a/src/aliceVision/feature/FeaturesPerView.cpp +++ b/src/aliceVision/feature/FeaturesPerView.cpp @@ -12,41 +12,41 @@ namespace feature { const feature::PointFeatures& FeaturesPerView::getFeatures(IndexT viewId, feature::EImageDescriberType descType) const { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - // Have an empty feature set in order to deal with non existing view_id - static const feature::PointFeatures emptyFeats = feature::PointFeatures(); + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + // Have an empty feature set in order to deal with non existing view_id + static const feature::PointFeatures emptyFeats = feature::PointFeatures(); - MapFeaturesPerView::const_iterator itView = _data.find(viewId); + MapFeaturesPerView::const_iterator itView = _data.find(viewId); - if (itView != _data.end()) - { - MapFeaturesPerDesc::const_iterator itDesc = itView->second.find(descType); - - if(itDesc != itView->second.end()) + if (itView != _data.end()) { - return itDesc->second; + MapFeaturesPerDesc::const_iterator itDesc = itView->second.find(descType); + + if (itDesc != itView->second.end()) + { + return itDesc->second; + } } - } - return emptyFeats; + return emptyFeats; } std::vector FeaturesPerView::getCommonDescTypes(const Pair& pair) const { - std::vector descTypes; + std::vector descTypes; - const auto& featuresA = _data.at(pair.first); - const auto& featuresB = _data.at(pair.second); + const auto& featuresA = _data.at(pair.first); + const auto& featuresB = _data.at(pair.second); - for(const auto& featuresPerDesc : featuresA) - { - const auto desc = featuresPerDesc.first; - if ( featuresB.count(desc) > 0 ) + for (const auto& featuresPerDesc : featuresA) { - descTypes.push_back(desc); + const auto desc = featuresPerDesc.first; + if (featuresB.count(desc) > 0) + { + descTypes.push_back(desc); + } } - } - return descTypes; + return descTypes; } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/FeaturesPerView.hpp b/src/aliceVision/feature/FeaturesPerView.hpp index b524faccf4..3df7f042bf 100644 --- a/src/aliceVision/feature/FeaturesPerView.hpp +++ b/src/aliceVision/feature/FeaturesPerView.hpp @@ -17,7 +17,6 @@ namespace aliceVision { namespace feature { - using MapFeaturesPerDesc = HashMap; using MapFeaturesPerView = HashMap; @@ -26,94 +25,74 @@ using MapFeaturesPerView = HashMap; */ class FeaturesPerView { -public: - - /** - * @brief Get the PointFeatures belonging to the View, - * if the view does not exist it returns an empty PointFeatures. - * - * @param viewId - * @return PointFeatures or an empty PointFeatures. - */ - const feature::PointFeatures& getFeatures(IndexT viewId, feature::EImageDescriberType descType) const; - - MapFeaturesPerDesc& getFeaturesPerDesc(IndexT viewId) - { - return _data.at(viewId); - } - - const MapFeaturesPerDesc& getFeaturesPerDesc(IndexT viewId) const - { - return _data.at(viewId); - } - - std::size_t getNbFeatures(IndexT viewId) const - { - MapFeaturesPerDesc f = getFeaturesPerDesc(viewId); - std::size_t count = 0; - for(const auto& it: f) - { - count += it.second.size(); - } - return count; - } - - const MapFeaturesPerDesc& getDataPerDesc(IndexT viewId) const - { - return _data.at(viewId); - } - - /** - * @brief Get the list of common describerTypes between two views - * @param pair - * @return vector of describerType - */ - std::vector getCommonDescTypes(const Pair& pair) const; - - /** - * @brief Return true if the given viewId is present in the container - * @param viewId - * @return boolean - */ - bool viewExist(IndexT viewId) const - { - return _data.count(viewId) > 0; - } - - /** - * @brief Return true if the container is empty - * @return boolean - */ - bool isEmpty() const - { - return _data.empty(); - } - - /** - * @brief Add a list of features for a given view and describerType - * @param viewId - * @param regionsPtr - */ - void addFeatures(IndexT viewId, feature::EImageDescriberType descType, const feature::PointFeatures& pointFeatures) - { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - _data[viewId][descType] = pointFeatures; - } - - /** - * @brief Get a reference of private container data - * @return MapFeaturesPerView reference - */ - feature::MapFeaturesPerView& getData() - { - return _data; - } - -private: - - /// PointFeature array per ViewId of the considered SfMData container - MapFeaturesPerView _data; + public: + /** + * @brief Get the PointFeatures belonging to the View, + * if the view does not exist it returns an empty PointFeatures. + * + * @param viewId + * @return PointFeatures or an empty PointFeatures. + */ + const feature::PointFeatures& getFeatures(IndexT viewId, feature::EImageDescriberType descType) const; + + MapFeaturesPerDesc& getFeaturesPerDesc(IndexT viewId) { return _data.at(viewId); } + + const MapFeaturesPerDesc& getFeaturesPerDesc(IndexT viewId) const { return _data.at(viewId); } + + std::size_t getNbFeatures(IndexT viewId) const + { + MapFeaturesPerDesc f = getFeaturesPerDesc(viewId); + std::size_t count = 0; + for (const auto& it : f) + { + count += it.second.size(); + } + return count; + } + + const MapFeaturesPerDesc& getDataPerDesc(IndexT viewId) const { return _data.at(viewId); } + + /** + * @brief Get the list of common describerTypes between two views + * @param pair + * @return vector of describerType + */ + std::vector getCommonDescTypes(const Pair& pair) const; + + /** + * @brief Return true if the given viewId is present in the container + * @param viewId + * @return boolean + */ + bool viewExist(IndexT viewId) const { return _data.count(viewId) > 0; } + + /** + * @brief Return true if the container is empty + * @return boolean + */ + bool isEmpty() const { return _data.empty(); } + + /** + * @brief Add a list of features for a given view and describerType + * @param viewId + * @param regionsPtr + */ + void addFeatures(IndexT viewId, feature::EImageDescriberType descType, const feature::PointFeatures& pointFeatures) + { + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + _data[viewId][descType] = pointFeatures; + } + + /** + * @brief Get a reference of private container data + * @return MapFeaturesPerView reference + */ + feature::MapFeaturesPerView& getData() { return _data; } + + private: + /// PointFeature array per ViewId of the considered SfMData container + MapFeaturesPerView _data; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/Hamming.hpp b/src/aliceVision/feature/Hamming.hpp index d1861bcce2..584bb66594 100644 --- a/src/aliceVision/feature/Hamming.hpp +++ b/src/aliceVision/feature/Hamming.hpp @@ -14,13 +14,13 @@ #ifdef _MSC_VER typedef unsigned __int32 uint32_t; typedef unsigned __int64 uint64_t; -#include + #include #else -#include + #include #endif #ifdef __ARM_NEON__ -#include "arm_neon.h" + #include "arm_neon.h" #endif // Brief: @@ -34,9 +34,9 @@ namespace feature { #undef PLATFORM_64_BIT #undef PLATFORM_32_BIT #if __amd64__ || __x86_64__ || _WIN64 || _M_X64 -#define PLATFORM_64_BIT + #define PLATFORM_64_BIT #else -#define PLATFORM_32_BIT + #define PLATFORM_32_BIT #endif /// Hamming distance: @@ -44,26 +44,28 @@ namespace feature { template struct HammingBitSet { - typedef TBitset ElementType; - typedef size_t ResultType; - - // Returns the Hamming Distance between two binary descriptors - template - inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const - { - return (*a ^ *b).count(); - } + typedef TBitset ElementType; + typedef size_t ResultType; + + // Returns the Hamming Distance between two binary descriptors + template + inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const + { + return (*a ^ *b).count(); + } }; // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable // Lookup table to count the number of common 1 bits on unsigned char values -static const unsigned char pop_count_LUT[256] = -{ -#define ALICEVISION_B2(n) n, n+1, n+1, n+2 -#define ALICEVISION_B4(n) ALICEVISION_B2(n), ALICEVISION_B2(n+1), ALICEVISION_B2(n+1), ALICEVISION_B2(n+2) -#define ALICEVISION_B6(n) ALICEVISION_B4(n), ALICEVISION_B4(n+1), ALICEVISION_B4(n+1), ALICEVISION_B4(n+2) - ALICEVISION_B6(0), ALICEVISION_B6(1), ALICEVISION_B6(1), ALICEVISION_B6(2) - +static const unsigned char pop_count_LUT[256] = { +#define ALICEVISION_B2(n) n, n + 1, n + 1, n + 2 +#define ALICEVISION_B4(n) ALICEVISION_B2(n), ALICEVISION_B2(n + 1), ALICEVISION_B2(n + 1), ALICEVISION_B2(n + 2) +#define ALICEVISION_B6(n) ALICEVISION_B4(n), ALICEVISION_B4(n + 1), ALICEVISION_B4(n + 1), ALICEVISION_B4(n + 2) + ALICEVISION_B6(0), + ALICEVISION_B6(1), + ALICEVISION_B6(1), + ALICEVISION_B6(2) + #undef ALICEVISION_B2 #undef ALICEVISION_B4 #undef ALICEVISION_B6 @@ -74,110 +76,112 @@ static const unsigned char pop_count_LUT[256] = template struct Hamming { - typedef T ElementType; - typedef unsigned int ResultType; + typedef T ElementType; + typedef unsigned int ResultType; - /** This is popcount_3() from: - * http://en.wikipedia.org/wiki/Hamming_weight */ - static inline unsigned int popcnt32(uint32_t n) - { + /** This is popcount_3() from: + * http://en.wikipedia.org/wiki/Hamming_weight */ + static inline unsigned int popcnt32(uint32_t n) + { #ifdef _MSC_VER - return __popcnt(n); + return __popcnt(n); #else -#if (defined __GNUC__ || defined __clang__) - return __builtin_popcountl(n); + #if (defined __GNUC__ || defined __clang__) + return __builtin_popcountl(n); + #endif + n -= ((n >> 1) & 0x55555555); + n = (n & 0x33333333) + ((n >> 2) & 0x33333333); + return (((n + (n >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; #endif - n -= ((n >> 1) & 0x55555555); - n = (n & 0x33333333) + ((n >> 2) & 0x33333333); - return (((n + (n >> 4))& 0xF0F0F0F)* 0x1010101) >> 24; -#endif - } + } - static inline unsigned int popcnt64(uint64_t n) - { + static inline unsigned int popcnt64(uint64_t n) + { #if defined _MSC_VER && defined PLATFORM_64_BIT - return __popcnt64(n); + return __popcnt64(n); #else -#if (defined __GNUC__ || defined __clang__) - return __builtin_popcountll(n); + #if (defined __GNUC__ || defined __clang__) + return __builtin_popcountll(n); + #endif + n -= ((n >> 1) & 0x5555555555555555LL); + n = (n & 0x3333333333333333LL) + ((n >> 2) & 0x3333333333333333LL); + return (((n + (n >> 4)) & 0x0f0f0f0f0f0f0f0fLL) * 0x0101010101010101LL) >> 56; #endif - n -= ((n >> 1) & 0x5555555555555555LL); - n = (n & 0x3333333333333333LL) + ((n >> 2) & 0x3333333333333333LL); - return (((n + (n >> 4))& 0x0f0f0f0f0f0f0f0fLL)* 0x0101010101010101LL) >> 56; -#endif - } + } - // Size must be equal to number of ElementType - template - inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const - { - ResultType result = 0; -// Windows & generic platforms: + // Size must be equal to number of ElementType + template + inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const + { + ResultType result = 0; + // Windows & generic platforms: #ifdef PLATFORM_64_BIT - if(size%sizeof(uint64_t) == 0) - { - const uint64_t* pa = reinterpret_cast(a); - const uint64_t* pb = reinterpret_cast(b); - size /= (sizeof(uint64_t)/sizeof(unsigned char)); - for(size_t i = 0; i < size; ++i, ++pa, ++pb ) { - result += popcnt64(*pa ^ *pb); - } - } - else if(size%sizeof(uint32_t) == 0) - { - const uint32_t* pa = reinterpret_cast(a); - const uint32_t* pb = reinterpret_cast(b); - size /= (sizeof(uint32_t)/sizeof(unsigned char)); - for(size_t i = 0; i < size; ++i, ++pa, ++pb ) { - result += popcnt32(*pa ^ *pb); - } - } - else - { - const ElementType * a2 = reinterpret_cast (a); - const ElementType * b2 = reinterpret_cast (b); - for (size_t i = 0; - i < size / (sizeof(unsigned char)); ++i) { - result += pop_count_LUT[a2[i] ^ b2[i]]; - } - } -#else // PLATFORM_64_BIT - if(size%sizeof(uint32_t) == 0) - { - const uint32_t* pa = reinterpret_cast(a); - const uint32_t* pb = reinterpret_cast(b); - size /= (sizeof(uint32_t)/sizeof(unsigned char)); - for(size_t i = 0; i < size; ++i, ++pa, ++pb ) { - result += popcnt32(*pa ^ *pb); - } - } - else - { - const ElementType * a2 = reinterpret_cast (a); - const ElementType * b2 = reinterpret_cast (b); - for (size_t i = 0; - i < size / (sizeof(unsigned char)); ++i) { - result += pop_count_LUT[a2[i] ^ b2[i]]; - } + if (size % sizeof(uint64_t) == 0) + { + const uint64_t* pa = reinterpret_cast(a); + const uint64_t* pb = reinterpret_cast(b); + size /= (sizeof(uint64_t) / sizeof(unsigned char)); + for (size_t i = 0; i < size; ++i, ++pa, ++pb) + { + result += popcnt64(*pa ^ *pb); + } + } + else if (size % sizeof(uint32_t) == 0) + { + const uint32_t* pa = reinterpret_cast(a); + const uint32_t* pb = reinterpret_cast(b); + size /= (sizeof(uint32_t) / sizeof(unsigned char)); + for (size_t i = 0; i < size; ++i, ++pa, ++pb) + { + result += popcnt32(*pa ^ *pb); + } + } + else + { + const ElementType* a2 = reinterpret_cast(a); + const ElementType* b2 = reinterpret_cast(b); + for (size_t i = 0; i < size / (sizeof(unsigned char)); ++i) + { + result += pop_count_LUT[a2[i] ^ b2[i]]; + } + } +#else // PLATFORM_64_BIT + if (size % sizeof(uint32_t) == 0) + { + const uint32_t* pa = reinterpret_cast(a); + const uint32_t* pb = reinterpret_cast(b); + size /= (sizeof(uint32_t) / sizeof(unsigned char)); + for (size_t i = 0; i < size; ++i, ++pa, ++pb) + { + result += popcnt32(*pa ^ *pb); + } + } + else + { + const ElementType* a2 = reinterpret_cast(a); + const ElementType* b2 = reinterpret_cast(b); + for (size_t i = 0; i < size / (sizeof(unsigned char)); ++i) + { + result += pop_count_LUT[a2[i] ^ b2[i]]; + } + } +#endif // PLATFORM_64_BIT + return result; } -#endif // PLATFORM_64_BIT - return result; - } }; - template struct SquaredHamming { - // Size must be equal to number of ElementType - template - inline double operator()(Iterator1 a, Iterator2 b, size_t size) const - { - Hamming metric; - typename Hamming::ResultType h = metric(a, b, size); - return h*h; - } + // Size must be equal to number of ElementType + template + inline double operator()(Iterator1 a, Iterator2 b, size_t size) const + { + Hamming metric; + typename Hamming::ResultType h = metric(a, b, size); + return h * h; + } }; } // namespace feature diff --git a/src/aliceVision/feature/ImageDescriber.cpp b/src/aliceVision/feature/ImageDescriber.cpp index 55e1435211..f080848e2c 100644 --- a/src/aliceVision/feature/ImageDescriber.cpp +++ b/src/aliceVision/feature/ImageDescriber.cpp @@ -14,19 +14,19 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) -#include -#endif //ALICEVISION_HAVE_CCTAG + #include +#endif // ALICEVISION_HAVE_CCTAG #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) -#include -#endif //ALICEVISION_HAVE_APRILTAG + #include +#endif // ALICEVISION_HAVE_APRILTAG #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) -#include -#endif //ALICEVISION_HAVE_OCVSIFT -#include -#endif //ALICEVISION_HAVE_OPENCV + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + #include + #endif // ALICEVISION_HAVE_OCVSIFT + #include +#endif // ALICEVISION_HAVE_OPENCV #include @@ -35,62 +35,68 @@ namespace fs = boost::filesystem; -namespace aliceVision{ -namespace feature{ +namespace aliceVision { +namespace feature { EImageDescriberPreset EImageDescriberPreset_stringToEnum(const std::string& imageDescriberPreset) { - std::string preset = imageDescriberPreset; - std::transform(preset.begin(), preset.end(), preset.begin(), ::tolower); //tolower - - if(preset == "low") return EImageDescriberPreset::LOW; - if(preset == "medium") return EImageDescriberPreset::MEDIUM; - if(preset == "normal") return EImageDescriberPreset::NORMAL; - if(preset == "high") return EImageDescriberPreset::HIGH; - if(preset == "ultra") return EImageDescriberPreset::ULTRA; + std::string preset = imageDescriberPreset; + std::transform(preset.begin(), preset.end(), preset.begin(), ::tolower); // tolower + + if (preset == "low") + return EImageDescriberPreset::LOW; + if (preset == "medium") + return EImageDescriberPreset::MEDIUM; + if (preset == "normal") + return EImageDescriberPreset::NORMAL; + if (preset == "high") + return EImageDescriberPreset::HIGH; + if (preset == "ultra") + return EImageDescriberPreset::ULTRA; - throw std::invalid_argument("Invalid descriptor preset: " + preset); + throw std::invalid_argument("Invalid descriptor preset: " + preset); } std::string EImageDescriberPreset_enumToString(const EImageDescriberPreset imageDescriberPreset) { - if(imageDescriberPreset == EImageDescriberPreset::LOW) return "low"; - if(imageDescriberPreset == EImageDescriberPreset::MEDIUM) return "medium"; - if(imageDescriberPreset == EImageDescriberPreset::NORMAL) return "normal"; - if(imageDescriberPreset == EImageDescriberPreset::HIGH) return "high"; - if(imageDescriberPreset == EImageDescriberPreset::ULTRA) return "ultra"; + if (imageDescriberPreset == EImageDescriberPreset::LOW) + return "low"; + if (imageDescriberPreset == EImageDescriberPreset::MEDIUM) + return "medium"; + if (imageDescriberPreset == EImageDescriberPreset::NORMAL) + return "normal"; + if (imageDescriberPreset == EImageDescriberPreset::HIGH) + return "high"; + if (imageDescriberPreset == EImageDescriberPreset::ULTRA) + return "ultra"; - throw std::invalid_argument("Unrecognized EImageDescriberPreset: " + std::to_string(int(imageDescriberPreset))); + throw std::invalid_argument("Unrecognized EImageDescriberPreset: " + std::to_string(int(imageDescriberPreset))); } -std::ostream& operator<<(std::ostream& os, EImageDescriberPreset p) -{ - return os << EImageDescriberPreset_enumToString(p); -} +std::ostream& operator<<(std::ostream& os, EImageDescriberPreset p) { return os << EImageDescriberPreset_enumToString(p); } std::istream& operator>>(std::istream& in, EImageDescriberPreset& p) { - std::string token; - in >> token; - p = EImageDescriberPreset_stringToEnum(token); - return in; + std::string token; + in >> token; + p = EImageDescriberPreset_stringToEnum(token); + return in; } - EFeatureQuality EFeatureQuality_stringToEnum(const std::string& imageDescriberPreset) { std::string preset = imageDescriberPreset; - std::transform(preset.begin(), preset.end(), preset.begin(), ::tolower); // tolower + std::transform(preset.begin(), preset.end(), preset.begin(), ::tolower); // tolower - if(preset == "low") + if (preset == "low") return EFeatureQuality::LOW; - if(preset == "medium") + if (preset == "medium") return EFeatureQuality::MEDIUM; - if(preset == "normal") + if (preset == "normal") return EFeatureQuality::NORMAL; - if(preset == "high") + if (preset == "high") return EFeatureQuality::HIGH; - if(preset == "ultra") + if (preset == "ultra") return EFeatureQuality::ULTRA; throw std::invalid_argument("Invalid descriptor preset: " + preset); @@ -98,24 +104,21 @@ EFeatureQuality EFeatureQuality_stringToEnum(const std::string& imageDescriberPr std::string EFeatureQuality_enumToString(const EFeatureQuality imageDescriberPreset) { - if(imageDescriberPreset == EFeatureQuality::LOW) + if (imageDescriberPreset == EFeatureQuality::LOW) return "low"; - if(imageDescriberPreset == EFeatureQuality::MEDIUM) + if (imageDescriberPreset == EFeatureQuality::MEDIUM) return "medium"; - if(imageDescriberPreset == EFeatureQuality::NORMAL) + if (imageDescriberPreset == EFeatureQuality::NORMAL) return "normal"; - if(imageDescriberPreset == EFeatureQuality::HIGH) + if (imageDescriberPreset == EFeatureQuality::HIGH) return "high"; - if(imageDescriberPreset == EFeatureQuality::ULTRA) + if (imageDescriberPreset == EFeatureQuality::ULTRA) return "ultra"; throw std::invalid_argument("Unrecognized EFeatureQuality: " + std::to_string(int(imageDescriberPreset))); } -std::ostream& operator<<(std::ostream& os, EFeatureQuality p) -{ - return os << EFeatureQuality_enumToString(p); -} +std::ostream& operator<<(std::ostream& os, EFeatureQuality p) { return os << EFeatureQuality_enumToString(p); } std::istream& operator>>(std::istream& in, EFeatureQuality& p) { @@ -125,34 +128,33 @@ std::istream& operator>>(std::istream& in, EFeatureQuality& p) return in; } - EFeatureConstrastFiltering EFeatureConstrastFiltering_stringToEnum(const std::string& v) { std::string value = v; - std::transform(value.begin(), value.end(), value.begin(), ::tolower); // tolower + std::transform(value.begin(), value.end(), value.begin(), ::tolower); // tolower - if(value == "static") + if (value == "static") return EFeatureConstrastFiltering::Static; - if(value == "adaptivetomedianvariance") + if (value == "adaptivetomedianvariance") return EFeatureConstrastFiltering::AdaptiveToMedianVariance; - if(value == "nofiltering") + if (value == "nofiltering") return EFeatureConstrastFiltering::NoFiltering; - if(value == "gridsortoctaves") + if (value == "gridsortoctaves") return EFeatureConstrastFiltering::GridSortOctaves; - if(value == "gridsort") + if (value == "gridsort") return EFeatureConstrastFiltering::GridSort; - if(value == "gridsortscalesteps") + if (value == "gridsortscalesteps") return EFeatureConstrastFiltering::GridSortScaleSteps; - if(value == "gridsortoctavesteps") + if (value == "gridsortoctavesteps") return EFeatureConstrastFiltering::GridSortOctaveSteps; - if(value == "nonextremafiltering") + if (value == "nonextremafiltering") return EFeatureConstrastFiltering::NonExtremaFiltering; throw std::invalid_argument("Invalid EFeatureConstrastFiltering: " + v); } std::string EFeatureConstrastFiltering_enumToString(const EFeatureConstrastFiltering v) { - switch(v) + switch (v) { case EFeatureConstrastFiltering::Static: return "Static"; @@ -174,10 +176,7 @@ std::string EFeatureConstrastFiltering_enumToString(const EFeatureConstrastFilte throw std::invalid_argument("Unrecognized EFeatureConstrastFiltering: " + std::to_string(int(v))); } -std::ostream& operator<<(std::ostream& os, EFeatureConstrastFiltering p) -{ - return os << EFeatureConstrastFiltering_enumToString(p); -} +std::ostream& operator<<(std::ostream& os, EFeatureConstrastFiltering p) { return os << EFeatureConstrastFiltering_enumToString(p); } std::istream& operator>>(std::istream& in, EFeatureConstrastFiltering& p) { @@ -189,56 +188,83 @@ std::istream& operator>>(std::istream& in, EFeatureConstrastFiltering& p) void ImageDescriber::Save(const Regions* regions, const std::string& sfileNameFeats, const std::string& sfileNameDescs) const { - const fs::path bFeatsPath = fs::path(sfileNameFeats); - const fs::path bDescsPath = fs::path(sfileNameDescs); - const std::string tmpFeatsPath = (bFeatsPath.parent_path() / bFeatsPath.stem()).string() + "." + fs::unique_path().string() + bFeatsPath.extension().string(); - const std::string tmpDescsPath = (bDescsPath.parent_path() / bDescsPath.stem()).string() + "." + fs::unique_path().string() + bDescsPath.extension().string(); - - regions->Save(tmpFeatsPath, tmpDescsPath); - - // rename temporary filenames - fs::rename(tmpFeatsPath, sfileNameFeats); - fs::rename(tmpDescsPath, sfileNameDescs); + const fs::path bFeatsPath = fs::path(sfileNameFeats); + const fs::path bDescsPath = fs::path(sfileNameDescs); + const std::string tmpFeatsPath = + (bFeatsPath.parent_path() / bFeatsPath.stem()).string() + "." + fs::unique_path().string() + bFeatsPath.extension().string(); + const std::string tmpDescsPath = + (bDescsPath.parent_path() / bDescsPath.stem()).string() + "." + fs::unique_path().string() + bDescsPath.extension().string(); + + regions->Save(tmpFeatsPath, tmpDescsPath); + + // rename temporary filenames + fs::rename(tmpFeatsPath, sfileNameFeats); + fs::rename(tmpDescsPath, sfileNameDescs); } std::unique_ptr createImageDescriber(EImageDescriberType imageDescriberType) { - std::unique_ptr describerPtr; - - switch(imageDescriberType) - { - case EImageDescriberType::SIFT: describerPtr.reset(new ImageDescriber_SIFT(SiftParams(), true)); break; - case EImageDescriberType::SIFT_FLOAT: describerPtr.reset(new ImageDescriber_SIFT_vlfeatFloat(SiftParams())); break; - case EImageDescriberType::SIFT_UPRIGHT: describerPtr.reset(new ImageDescriber_SIFT(SiftParams(), false)); break; - - case EImageDescriberType::DSPSIFT: describerPtr.reset(new ImageDescriber_DSPSIFT_vlfeat(DspSiftParams(), true)); break; + std::unique_ptr describerPtr; - case EImageDescriberType::AKAZE: describerPtr.reset(new ImageDescriber_AKAZE(AKAZEParams(AKAZEOptions(), feature::AKAZE_MSURF))); break; - case EImageDescriberType::AKAZE_MLDB: describerPtr.reset(new ImageDescriber_AKAZE(AKAZEParams(AKAZEOptions(), feature::AKAZE_MLDB))); break; - case EImageDescriberType::AKAZE_LIOP: describerPtr.reset(new ImageDescriber_AKAZE(AKAZEParams(AKAZEOptions(), feature::AKAZE_LIOP))); break; + switch (imageDescriberType) + { + case EImageDescriberType::SIFT: + describerPtr.reset(new ImageDescriber_SIFT(SiftParams(), true)); + break; + case EImageDescriberType::SIFT_FLOAT: + describerPtr.reset(new ImageDescriber_SIFT_vlfeatFloat(SiftParams())); + break; + case EImageDescriberType::SIFT_UPRIGHT: + describerPtr.reset(new ImageDescriber_SIFT(SiftParams(), false)); + break; + + case EImageDescriberType::DSPSIFT: + describerPtr.reset(new ImageDescriber_DSPSIFT_vlfeat(DspSiftParams(), true)); + break; + + case EImageDescriberType::AKAZE: + describerPtr.reset(new ImageDescriber_AKAZE(AKAZEParams(AKAZEOptions(), feature::AKAZE_MSURF))); + break; + case EImageDescriberType::AKAZE_MLDB: + describerPtr.reset(new ImageDescriber_AKAZE(AKAZEParams(AKAZEOptions(), feature::AKAZE_MLDB))); + break; + case EImageDescriberType::AKAZE_LIOP: + describerPtr.reset(new ImageDescriber_AKAZE(AKAZEParams(AKAZEOptions(), feature::AKAZE_LIOP))); + break; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case EImageDescriberType::CCTAG3: describerPtr.reset(new ImageDescriber_CCTAG(3)); break; - case EImageDescriberType::CCTAG4: describerPtr.reset(new ImageDescriber_CCTAG(4)); break; -#endif //ALICEVISION_HAVE_CCTAG + case EImageDescriberType::CCTAG3: + describerPtr.reset(new ImageDescriber_CCTAG(3)); + break; + case EImageDescriberType::CCTAG4: + describerPtr.reset(new ImageDescriber_CCTAG(4)); + break; +#endif // ALICEVISION_HAVE_CCTAG #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - case EImageDescriberType::APRILTAG16H5: describerPtr.reset(new ImageDescriber_APRILTAG()); break; -#endif //ALICEVISION_HAVE_APRILTAG + case EImageDescriberType::APRILTAG16H5: + describerPtr.reset(new ImageDescriber_APRILTAG()); + break; +#endif // ALICEVISION_HAVE_APRILTAG #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - case EImageDescriberType::SIFT_OCV: describerPtr.reset(new ImageDescriber_SIFT_openCV()); break; -#endif //ALICEVISION_HAVE_OCVSIFT - case EImageDescriberType::AKAZE_OCV: describerPtr.reset(new ImageDescriber_AKAZE_OCV()); break; -#endif //ALICEVISION_HAVE_OPENCV - - default: throw std::out_of_range("Invalid imageDescriber enum"); - } - assert(describerPtr != nullptr); - - return describerPtr; + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + case EImageDescriberType::SIFT_OCV: + describerPtr.reset(new ImageDescriber_SIFT_openCV()); + break; + #endif // ALICEVISION_HAVE_OCVSIFT + case EImageDescriberType::AKAZE_OCV: + describerPtr.reset(new ImageDescriber_AKAZE_OCV()); + break; +#endif // ALICEVISION_HAVE_OPENCV + + default: + throw std::out_of_range("Invalid imageDescriber enum"); + } + assert(describerPtr != nullptr); + + return describerPtr; } -}//namespace feature -}//namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/ImageDescriber.hpp b/src/aliceVision/feature/ImageDescriber.hpp index 5fde67a32f..2bb84a3f4f 100644 --- a/src/aliceVision/feature/ImageDescriber.hpp +++ b/src/aliceVision/feature/ImageDescriber.hpp @@ -24,11 +24,11 @@ namespace feature { */ enum class EImageDescriberPreset { - LOW = 0 - , MEDIUM - , NORMAL - , HIGH - , ULTRA + LOW = 0, + MEDIUM, + NORMAL, + HIGH, + ULTRA }; inline std::string EImageDescriberPreset_information() @@ -38,7 +38,8 @@ inline std::string EImageDescriberPreset_information() "* MEDIUM: Low density (max 5K points).\n" "* NORMAL: Default feature density (max 10K points).\n" "* HIGH: High density (max 50K points).\n" - "* ULTRA: Very high density (max 100K points). Can use large amount of storage and large amount of computation. Use only on small datasets.\n"; + "* ULTRA: Very high density (max 100K points). Can use large amount of storage and large amount of computation. Use only on small " + "datasets.\n"; } EImageDescriberPreset EImageDescriberPreset_stringToEnum(const std::string& imageDescriberPreset); @@ -73,7 +74,6 @@ std::string EFeatureQuality_enumToString(const EFeatureQuality v); std::ostream& operator<<(std::ostream& os, EFeatureQuality v); std::istream& operator>>(std::istream& in, EFeatureQuality& v); - /** * @brief The method used to filter out features with too low constrast (that can be considered as noise). */ @@ -115,7 +115,6 @@ std::string EFeatureConstrastFiltering_enumToString(const EFeatureConstrastFilte std::ostream& operator<<(std::ostream& os, EFeatureConstrastFiltering v); std::istream& operator>>(std::istream& in, EFeatureConstrastFiltering& v); - struct ConfigurationPreset { EImageDescriberPreset descPreset{EImageDescriberPreset::NORMAL}; @@ -159,116 +158,106 @@ struct ConfigurationPreset */ class ImageDescriber { -public: - ImageDescriber() = default; - - virtual ~ImageDescriber() = default; - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - virtual bool useCuda() const = 0; - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - virtual bool useFloatImage() const = 0; - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - virtual EImageDescriberType getDescriberType() const = 0; - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - virtual std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const = 0; - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - virtual void setUpRight(bool upRight) {} - - /** - * @brief Set if yes or no imageDescriber need to use cuda implementation - * @param[in] useCuda - */ - virtual void setUseCuda(bool useCuda) {} - - /** - * @brief set the CUDA pipe - * @param[in] pipe The CUDA pipe id - */ - virtual void setCudaPipe(int pipe) {} - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - virtual void setConfigurationPreset(ConfigurationPreset preset) = 0; - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - */ - virtual bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) - { - throw std::logic_error("Cannot use " + EImageDescriberType_enumToString(getDescriberType()) + " image describer with an 8-bit image."); - return false; - } - - /** - * @brief Detect regions on the float image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - */ - virtual bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) - { - throw std::logic_error("Cannot use " + EImageDescriberType_enumToString(getDescriberType()) + " image describer with a float image."); - return false; - } - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - virtual void allocate(std::unique_ptr& regions) const = 0; - - // IO - one file for region features, one file for region descriptors - - void Load(Regions* regions, - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) const - { - regions->Load(sfileNameFeats, sfileNameDescs); - } - - void Save(const Regions* regions, - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) const; - - void LoadFeatures(Regions* regions, - const std::string& sfileNameFeats) const - { - regions->LoadFeatures(sfileNameFeats); - } + public: + ImageDescriber() = default; + + virtual ~ImageDescriber() = default; + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + virtual bool useCuda() const = 0; + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + virtual bool useFloatImage() const = 0; + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + virtual EImageDescriberType getDescriberType() const = 0; + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + virtual std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const = 0; + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + virtual void setUpRight(bool upRight) {} + + /** + * @brief Set if yes or no imageDescriber need to use cuda implementation + * @param[in] useCuda + */ + virtual void setUseCuda(bool useCuda) {} + + /** + * @brief set the CUDA pipe + * @param[in] pipe The CUDA pipe id + */ + virtual void setCudaPipe(int pipe) {} + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + virtual void setConfigurationPreset(ConfigurationPreset preset) = 0; + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + */ + virtual bool describe(const image::Image& image, + std::unique_ptr& regions, + const image::Image* mask = nullptr) + { + throw std::logic_error("Cannot use " + EImageDescriberType_enumToString(getDescriberType()) + " image describer with an 8-bit image."); + return false; + } + + /** + * @brief Detect regions on the float image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + */ + virtual bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) + { + throw std::logic_error("Cannot use " + EImageDescriberType_enumToString(getDescriberType()) + " image describer with a float image."); + return false; + } + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + virtual void allocate(std::unique_ptr& regions) const = 0; + + // IO - one file for region features, one file for region descriptors + + void Load(Regions* regions, const std::string& sfileNameFeats, const std::string& sfileNameDescs) const + { + regions->Load(sfileNameFeats, sfileNameDescs); + } + + void Save(const Regions* regions, const std::string& sfileNameFeats, const std::string& sfileNameDescs) const; + + void LoadFeatures(Regions* regions, const std::string& sfileNameFeats) const { regions->LoadFeatures(sfileNameFeats); } }; /** @@ -277,5 +266,5 @@ class ImageDescriber */ std::unique_ptr createImageDescriber(EImageDescriberType imageDescriberType); -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/KeypointSet.hpp b/src/aliceVision/feature/KeypointSet.hpp index 68002a8748..02a13c509e 100644 --- a/src/aliceVision/feature/KeypointSet.hpp +++ b/src/aliceVision/feature/KeypointSet.hpp @@ -22,64 +22,57 @@ namespace feature { /// typedef vector > descsT; /// KeypointSet< featsT, descsT > kpSet; template -class KeypointSet { -public: - // Alias to stored Feature and Descriptor type - typedef typename FeaturesT::value_type FeatureT; - typedef typename DescriptorsT::value_type DescriptorT; +class KeypointSet +{ + public: + // Alias to stored Feature and Descriptor type + typedef typename FeaturesT::value_type FeatureT; + typedef typename DescriptorsT::value_type DescriptorT; - /// Read from files the feats and their corresponding descriptors. - void loadFromFile( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) - { - loadFeatsFromFile(sfileNameFeats, _feats); - loadDescsFromFile(sfileNameDescs, _descs); - } + /// Read from files the feats and their corresponding descriptors. + void loadFromFile(const std::string& sfileNameFeats, const std::string& sfileNameDescs) + { + loadFeatsFromFile(sfileNameFeats, _feats); + loadDescsFromFile(sfileNameDescs, _descs); + } - /// Export in two separate files the feats and their corresponding descriptors. - void saveToFile( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) const - { - saveFeatsToFile(sfileNameFeats, _feats); - saveDescsToFile(sfileNameDescs, _descs); - } + /// Export in two separate files the feats and their corresponding descriptors. + void saveToFile(const std::string& sfileNameFeats, const std::string& sfileNameDescs) const + { + saveFeatsToFile(sfileNameFeats, _feats); + saveDescsToFile(sfileNameDescs, _descs); + } - /// Read from files the feats and their corresponding descriptors - /// descriptor in binary to save place - void loadFromBinFile( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) - { - loadFeatsFromFile(sfileNameFeats, _feats); - loadDescsFromBinFile(sfileNameDescs, _descs); - } + /// Read from files the feats and their corresponding descriptors + /// descriptor in binary to save place + void loadFromBinFile(const std::string& sfileNameFeats, const std::string& sfileNameDescs) + { + loadFeatsFromFile(sfileNameFeats, _feats); + loadDescsFromBinFile(sfileNameDescs, _descs); + } - /// Export in two separate files the feats and their corresponding descriptors - /// descriptor in binary to save place - void saveToBinFile( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) const - { - saveFeatsToFile(sfileNameFeats, _feats); - saveDescsToBinFile(sfileNameDescs, _descs); - } + /// Export in two separate files the feats and their corresponding descriptors + /// descriptor in binary to save place + void saveToBinFile(const std::string& sfileNameFeats, const std::string& sfileNameDescs) const + { + saveFeatsToFile(sfileNameFeats, _feats); + saveDescsToBinFile(sfileNameDescs, _descs); + } - /// Mutable and non-mutable FeatureT getters. - inline FeaturesT & features() { return _feats; } - inline const FeaturesT & features() const { return _feats; } + /// Mutable and non-mutable FeatureT getters. + inline FeaturesT& features() { return _feats; } + inline const FeaturesT& features() const { return _feats; } - /// Mutable and non-mutable DescriptorT getters. - inline DescriptorsT & descriptors() { return _descs; } - inline const DescriptorsT & descriptors() const { return _descs; } + /// Mutable and non-mutable DescriptorT getters. + inline DescriptorsT& descriptors() { return _descs; } + inline const DescriptorsT& descriptors() const { return _descs; } -private: - FeaturesT _feats; - DescriptorsT _descs; + private: + FeaturesT _feats; + DescriptorsT _descs; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision -#endif // ALICEVISION_FEATURES_KEYPOINTSET_HPP +#endif // ALICEVISION_FEATURES_KEYPOINTSET_HPP diff --git a/src/aliceVision/feature/PointFeature.hpp b/src/aliceVision/feature/PointFeature.hpp index 7e91df9b61..d755e5b1b1 100644 --- a/src/aliceVision/feature/PointFeature.hpp +++ b/src/aliceVision/feature/PointFeature.hpp @@ -21,137 +21,120 @@ namespace feature { * Base class for Point features. * Store position of a feature point. */ -class PointFeature { - - friend std::ostream& operator<<(std::ostream& out, const PointFeature& obj); - friend std::istream& operator>>(std::istream& in, PointFeature& obj); - -public: - PointFeature() {} - PointFeature(float x, float y, float scale, float orient) - : _coords(x, y), - _scale(scale), - _orientation(orient) - {} - - float x() const { return _coords(0); } - float y() const { return _coords(1); } - const Vec2f & coords() const { return _coords;} - float scale() const { return _scale; } - float& scale() { return _scale; } - - float& x() { return _coords(0); } - float& y() { return _coords(1); } - Vec2f& coords() { return _coords;} - - float orientation() const { return _orientation; } - float& orientation() { return _orientation; } - - /** - * @brief Return the orientation of the feature as an unit vector. - * @return a unit vector corresponding to the orientation of the feature. - */ - Vec2f getOrientationVector() const - { - return Vec2f(std::cos(orientation()), std::sin(orientation())); - } - - /** - * @brief Return the orientation of the feature as a vector scaled to the the scale of the feature. - * @return a vector corresponding to the orientation of the feature scaled at the scale of the feature. - */ - Vec2f getScaledOrientationVector() const - { - return scale()*getOrientationVector(); - } - - bool operator ==(const PointFeature& b) const { - return (_scale == b.scale()) && - (_orientation == b.orientation()) && - (x() == b.x()) && (y() == b.y()) ; - } - - bool operator !=(const PointFeature& b) const { - return !((*this)==b); - } - -protected: - Vec2f _coords = {0.0f, 0.0f}; // (x, y) - float _scale = 0.0f; // In pixels. - float _orientation = 0.0f; // In radians. +class PointFeature +{ + friend std::ostream& operator<<(std::ostream& out, const PointFeature& obj); + friend std::istream& operator>>(std::istream& in, PointFeature& obj); + + public: + PointFeature() {} + PointFeature(float x, float y, float scale, float orient) + : _coords(x, y), + _scale(scale), + _orientation(orient) + {} + + float x() const { return _coords(0); } + float y() const { return _coords(1); } + const Vec2f& coords() const { return _coords; } + float scale() const { return _scale; } + float& scale() { return _scale; } + + float& x() { return _coords(0); } + float& y() { return _coords(1); } + Vec2f& coords() { return _coords; } + + float orientation() const { return _orientation; } + float& orientation() { return _orientation; } + + /** + * @brief Return the orientation of the feature as an unit vector. + * @return a unit vector corresponding to the orientation of the feature. + */ + Vec2f getOrientationVector() const { return Vec2f(std::cos(orientation()), std::sin(orientation())); } + + /** + * @brief Return the orientation of the feature as a vector scaled to the the scale of the feature. + * @return a vector corresponding to the orientation of the feature scaled at the scale of the feature. + */ + Vec2f getScaledOrientationVector() const { return scale() * getOrientationVector(); } + + bool operator==(const PointFeature& b) const + { + return (_scale == b.scale()) && (_orientation == b.orientation()) && (x() == b.x()) && (y() == b.y()); + } + + bool operator!=(const PointFeature& b) const { return !((*this) == b); } + + protected: + Vec2f _coords = {0.0f, 0.0f}; // (x, y) + float _scale = 0.0f; // In pixels. + float _orientation = 0.0f; // In radians. }; typedef std::vector PointFeatures; -//with overloaded operators: +// with overloaded operators: inline std::ostream& operator<<(std::ostream& out, const PointFeature& obj) { - return out << obj._coords(0) << " " << obj._coords(1) << " " << obj._scale << " " << obj._orientation; + return out << obj._coords(0) << " " << obj._coords(1) << " " << obj._scale << " " << obj._orientation; } inline std::istream& operator>>(std::istream& in, PointFeature& obj) { - return in >> obj._coords(0) >> obj._coords(1) >> obj._scale >> obj._orientation; + return in >> obj._coords(0) >> obj._coords(1) >> obj._scale >> obj._orientation; } /// Read feats from file -template -inline void loadFeatsFromFile( - const std::string & sfileNameFeats, - FeaturesT & vec_feat) +template +inline void loadFeatsFromFile(const std::string& sfileNameFeats, FeaturesT& vec_feat) { - vec_feat.clear(); - - std::ifstream fileIn(sfileNameFeats); - - if(!fileIn.is_open()) - throw std::runtime_error("Can't load features file, can't open '" + sfileNameFeats + "' !"); - - std::copy( - std::istream_iterator(fileIn), - std::istream_iterator(), - std::back_inserter(vec_feat)); - if(fileIn.bad()) - throw std::runtime_error("Can't load features file, '" + sfileNameFeats + "' is incorrect !"); - fileIn.close(); + vec_feat.clear(); + + std::ifstream fileIn(sfileNameFeats); + + if (!fileIn.is_open()) + throw std::runtime_error("Can't load features file, can't open '" + sfileNameFeats + "' !"); + + std::copy(std::istream_iterator(fileIn), + std::istream_iterator(), + std::back_inserter(vec_feat)); + if (fileIn.bad()) + throw std::runtime_error("Can't load features file, '" + sfileNameFeats + "' is incorrect !"); + fileIn.close(); } /// Write feats to file -template -inline void saveFeatsToFile( - const std::string & sfileNameFeats, - FeaturesT & vec_feat) +template +inline void saveFeatsToFile(const std::string& sfileNameFeats, FeaturesT& vec_feat) { - std::ofstream file(sfileNameFeats); + std::ofstream file(sfileNameFeats); - if (!file.is_open()) - throw std::runtime_error("Can't save features file, can't open '" + sfileNameFeats + "' !"); + if (!file.is_open()) + throw std::runtime_error("Can't save features file, can't open '" + sfileNameFeats + "' !"); - std::copy(vec_feat.begin(), vec_feat.end(), std::ostream_iterator(file,"\n")); + std::copy(vec_feat.begin(), vec_feat.end(), std::ostream_iterator(file, "\n")); - if(!file.good()) - throw std::runtime_error("Can't save features file, '" + sfileNameFeats + "' is incorrect !"); + if (!file.good()) + throw std::runtime_error("Can't save features file, '" + sfileNameFeats + "' is incorrect !"); - file.close(); + file.close(); } /// Export point feature based vector to a matrix [(x,y)'T, (x,y)'T] -template< typename FeaturesT, typename MatT > -void PointsToMat( - const FeaturesT & vec_feats, - MatT & m) +template +void PointsToMat(const FeaturesT& vec_feats, MatT& m) { - m.resize(2, vec_feats.size()); - typedef typename FeaturesT::value_type ValueT; // Container type - - size_t i = 0; - for( typename FeaturesT::const_iterator iter = vec_feats.begin(); - iter != vec_feats.end(); ++iter, ++i) - { - const ValueT & feat = *iter; - m.col(i) << feat.x(), feat.y(); - } + m.resize(2, vec_feats.size()); + typedef typename FeaturesT::value_type ValueT; // Container type + + size_t i = 0; + for (typename FeaturesT::const_iterator iter = vec_feats.begin(); iter != vec_feats.end(); ++iter, ++i) + { + const ValueT& feat = *iter; + m.col(i) << feat.x(), feat.y(); + } } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/Regions.hpp b/src/aliceVision/feature/Regions.hpp index 4a38c7e72b..e910326807 100644 --- a/src/aliceVision/feature/Regions.hpp +++ b/src/aliceVision/feature/Regions.hpp @@ -18,7 +18,6 @@ #include #include - namespace aliceVision { namespace feature { @@ -27,18 +26,15 @@ namespace feature { */ struct FeatureInImage { - FeatureInImage(IndexT featureIndex, IndexT point3dId) - : _featureIndex(featureIndex) - , _point3dId(point3dId) - {} - - IndexT _featureIndex; - IndexT _point3dId; - - bool operator<(const FeatureInImage& other) const - { - return _featureIndex < other._featureIndex; - } + FeatureInImage(IndexT featureIndex, IndexT point3dId) + : _featureIndex(featureIndex), + _point3dId(point3dId) + {} + + IndexT _featureIndex; + IndexT _point3dId; + + bool operator<(const FeatureInImage& other) const { return _featureIndex < other._featureIndex; } }; /// Describe an image a set of regions (position, ...) + attributes @@ -49,245 +45,215 @@ struct FeatureInImage */ class Regions { -public: - - virtual ~Regions() = 0; - -protected: - std::vector _vec_feats; // region features + public: + virtual ~Regions() = 0; -public: - void LoadFeatures(const std::string& sfileNameFeats) - { - loadFeatsFromFile(sfileNameFeats, _vec_feats); - } + protected: + std::vector _vec_feats; // region features - PointFeatures GetRegionsPositions() const - { - return PointFeatures(_vec_feats.begin(), _vec_feats.end()); - } + public: + void LoadFeatures(const std::string& sfileNameFeats) { loadFeatsFromFile(sfileNameFeats, _vec_feats); } - Vec2 GetRegionPosition(std::size_t i) const - { - return Vec2f(_vec_feats[i].coords()).cast(); - } + PointFeatures GetRegionsPositions() const { return PointFeatures(_vec_feats.begin(), _vec_feats.end()); } - /// Return the number of defined regions - std::size_t RegionCount() const {return _vec_feats.size();} + Vec2 GetRegionPosition(std::size_t i) const { return Vec2f(_vec_feats[i].coords()).cast(); } - /// Mutable and non-mutable PointFeature getters. - inline std::vector & Features() { return _vec_feats; } - inline const std::vector & Features() const { return _vec_feats; } + /// Return the number of defined regions + std::size_t RegionCount() const { return _vec_feats.size(); } - //-- - // IO - one file for region features, one file for region descriptors - //-- + /// Mutable and non-mutable PointFeature getters. + inline std::vector& Features() { return _vec_feats; } + inline const std::vector& Features() const { return _vec_feats; } - virtual void Load( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) = 0; + //-- + // IO - one file for region features, one file for region descriptors + //-- - virtual void Save( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) const = 0; + virtual void Load(const std::string& sfileNameFeats, const std::string& sfileNameDescs) = 0; - virtual void SaveDesc(const std::string& sfileNameDescs) const = 0; + virtual void Save(const std::string& sfileNameFeats, const std::string& sfileNameDescs) const = 0; - //-- - //- Basic description of a descriptor [Type, Length] - //-- - virtual bool IsScalar() const = 0; - virtual bool IsBinary() const = 0; + virtual void SaveDesc(const std::string& sfileNameDescs) const = 0; - /// basis element used for description - virtual std::string Type_id() const = 0; - virtual std::size_t DescriptorLength() const = 0; + //-- + //- Basic description of a descriptor [Type, Length] + //-- + virtual bool IsScalar() const = 0; + virtual bool IsBinary() const = 0; - /** - * @brief Return a blind pointer to the container of the descriptors array. - * - * @note: Descriptors are always stored as an std::vector. - */ - virtual const void* blindDescriptors() const = 0; + /// basis element used for description + virtual std::string Type_id() const = 0; + virtual std::size_t DescriptorLength() const = 0; - /** - * @brief Return a pointer to the first value of the descriptor array. - * - * @note: Descriptors are always stored as a flat array of descriptors. - */ - virtual const void * DescriptorRawData() const = 0; + /** + * @brief Return a blind pointer to the container of the descriptors array. + * + * @note: Descriptors are always stored as an std::vector. + */ + virtual const void* blindDescriptors() const = 0; - virtual void clearDescriptors() = 0; + /** + * @brief Return a pointer to the first value of the descriptor array. + * + * @note: Descriptors are always stored as a flat array of descriptors. + */ + virtual const void* DescriptorRawData() const = 0; - /// Return the squared distance between two descriptors - // A default metric is used according the descriptor type: - // - Scalar: L2, - // - Binary: Hamming - virtual double SquaredDescriptorDistance(std::size_t i, const Regions *, std::size_t j) const = 0; + virtual void clearDescriptors() = 0; - /// Add the Inth region to another Region container - virtual void CopyRegion(std::size_t i, Regions *) const = 0; + /// Return the squared distance between two descriptors + // A default metric is used according the descriptor type: + // - Scalar: L2, + // - Binary: Hamming + virtual double SquaredDescriptorDistance(std::size_t i, const Regions*, std::size_t j) const = 0; - virtual Regions * EmptyClone() const = 0; + /// Add the Inth region to another Region container + virtual void CopyRegion(std::size_t i, Regions*) const = 0; - virtual std::unique_ptr createFilteredRegions( - const std::vector& featuresInImage, - std::vector& out_associated3dPoint, - std::map& out_mapFullToLocal) const = 0; + virtual Regions* EmptyClone() const = 0; + virtual std::unique_ptr createFilteredRegions(const std::vector& featuresInImage, + std::vector& out_associated3dPoint, + std::map& out_mapFullToLocal) const = 0; }; inline Regions::~Regions() {} -enum class ERegionType: bool +enum class ERegionType : bool { - Binary = 0, - Scalar = 1 + Binary = 0, + Scalar = 1 }; - template struct SquaredMetric; template struct SquaredMetric { - using Metric = L2_Vectorized; + using Metric = L2_Vectorized; }; template struct SquaredMetric { - using Metric = SquaredHamming; + using Metric = SquaredHamming; }; - template class FeatDescRegions : public Regions { -public: - typedef FeatDescRegions This; - /// Region descriptor - typedef Descriptor DescriptorT; - /// Container for multiple regions description - typedef std::vector DescsT; - -protected: - std::vector _vec_descs; // region descriptions - -public: - std::string Type_id() const override {return typeid(T).name();} - std::size_t DescriptorLength() const override {return static_cast(L);} - - bool IsScalar() const override { return regionType == ERegionType::Scalar; } - bool IsBinary() const override { return regionType == ERegionType::Binary; } - - Regions * EmptyClone() const override - { - return new This(); - } - - /// Read from files the regions and their corresponding descriptors. - void Load( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) override - { - loadFeatsFromFile(sfileNameFeats, this->_vec_feats); - loadDescsFromBinFile(sfileNameDescs, _vec_descs); - } - - /// Export in two separate files the regions and their corresponding descriptors. - void Save( - const std::string& sfileNameFeats, - const std::string& sfileNameDescs) const override - { - saveFeatsToFile(sfileNameFeats, this->_vec_feats); - saveDescsToBinFile(sfileNameDescs, _vec_descs); - } - - void SaveDesc(const std::string& sfileNameDescs) const override - { - saveDescsToBinFile(sfileNameDescs, _vec_descs); - } - - /// Mutable and non-mutable DescriptorT getters. - inline std::vector & Descriptors() { return _vec_descs; } - inline const std::vector & Descriptors() const { return _vec_descs; } - - inline const void* blindDescriptors() const override { return &_vec_descs; } - - inline const void* DescriptorRawData() const override { return &_vec_descs[0];} - - inline void clearDescriptors() override { _vec_descs.clear(); } - - inline void swap(This& other) - { - this->_vec_feats.swap(other._vec_feats); - _vec_descs.swap(other._vec_descs); - } - - // Return the distance between two descriptors - double SquaredDescriptorDistance(std::size_t i, const Regions * genericRegions, std::size_t j) const override - { - assert(i < this->_vec_descs.size()); - assert(genericRegions); - assert(j < genericRegions->RegionCount()); - - const This * regionsT = dynamic_cast(genericRegions); - static typename SquaredMetric::Metric metric; - return metric(this->_vec_descs[i].getData(), regionsT->_vec_descs[j].getData(), DescriptorT::static_size); - } - - /** - * @brief Add the Inth region to another Region container - * @param[in] i: index of the region to copy - * @param[out] outRegionContainer: the output region group to add the region - */ - void CopyRegion(std::size_t i, Regions * outRegionContainer) const override - { - assert(i < this->_vec_feats.size() && i < this->_vec_descs.size()); - static_cast(outRegionContainer)->_vec_feats.push_back(this->_vec_feats[i]); - static_cast(outRegionContainer)->_vec_descs.push_back(this->_vec_descs[i]); - } - - /** - * @brief Duplicate only reconstructed regions. - * @param[in] featuresInImage list of features with an associated 3D point Id - * @param[out] out_associated3dPoint - * @param[out] out_mapFullToLocal - */ - std::unique_ptr createFilteredRegions( - const std::vector& featuresInImage, - std::vector& out_associated3dPoint, - std::map& out_mapFullToLocal) const override - { - out_associated3dPoint.clear(); - out_mapFullToLocal.clear(); - - This* regionsPtr = new This; - std::unique_ptr regions(regionsPtr); - regionsPtr->Features().reserve(featuresInImage.size()); - regionsPtr->Descriptors().reserve(featuresInImage.size()); - out_associated3dPoint.reserve(featuresInImage.size()); - for(std::size_t i = 0; i < featuresInImage.size(); ++i) + public: + typedef FeatDescRegions This; + /// Region descriptor + typedef Descriptor DescriptorT; + /// Container for multiple regions description + typedef std::vector DescsT; + + protected: + std::vector _vec_descs; // region descriptions + + public: + std::string Type_id() const override { return typeid(T).name(); } + std::size_t DescriptorLength() const override { return static_cast(L); } + + bool IsScalar() const override { return regionType == ERegionType::Scalar; } + bool IsBinary() const override { return regionType == ERegionType::Binary; } + + Regions* EmptyClone() const override { return new This(); } + + /// Read from files the regions and their corresponding descriptors. + void Load(const std::string& sfileNameFeats, const std::string& sfileNameDescs) override { - const FeatureInImage & feat = featuresInImage[i]; - regionsPtr->Features().push_back(this->_vec_feats[feat._featureIndex]); - regionsPtr->Descriptors().push_back(this->_vec_descs[feat._featureIndex]); - - // This assert should be valid in theory, but in the context of CameraLocalization - // we can have the same 2D feature associated to different 3D points (2 in practice). - // In this particular case, currently it returns the last one... - // - // assert(out_mapFullToLocal.count(feat._featureIndex) == 0); - - out_mapFullToLocal[feat._featureIndex] = i; - out_associated3dPoint.push_back(feat._point3dId); + loadFeatsFromFile(sfileNameFeats, this->_vec_feats); + loadDescsFromBinFile(sfileNameDescs, _vec_descs); } - return regions; - } -}; + /// Export in two separate files the regions and their corresponding descriptors. + void Save(const std::string& sfileNameFeats, const std::string& sfileNameDescs) const override + { + saveFeatsToFile(sfileNameFeats, this->_vec_feats); + saveDescsToBinFile(sfileNameDescs, _vec_descs); + } + + void SaveDesc(const std::string& sfileNameDescs) const override { saveDescsToBinFile(sfileNameDescs, _vec_descs); } + + /// Mutable and non-mutable DescriptorT getters. + inline std::vector& Descriptors() { return _vec_descs; } + inline const std::vector& Descriptors() const { return _vec_descs; } + + inline const void* blindDescriptors() const override { return &_vec_descs; } + + inline const void* DescriptorRawData() const override { return &_vec_descs[0]; } + + inline void clearDescriptors() override { _vec_descs.clear(); } + + inline void swap(This& other) + { + this->_vec_feats.swap(other._vec_feats); + _vec_descs.swap(other._vec_descs); + } + + // Return the distance between two descriptors + double SquaredDescriptorDistance(std::size_t i, const Regions* genericRegions, std::size_t j) const override + { + assert(i < this->_vec_descs.size()); + assert(genericRegions); + assert(j < genericRegions->RegionCount()); + + const This* regionsT = dynamic_cast(genericRegions); + static typename SquaredMetric::Metric metric; + return metric(this->_vec_descs[i].getData(), regionsT->_vec_descs[j].getData(), DescriptorT::static_size); + } + + /** + * @brief Add the Inth region to another Region container + * @param[in] i: index of the region to copy + * @param[out] outRegionContainer: the output region group to add the region + */ + void CopyRegion(std::size_t i, Regions* outRegionContainer) const override + { + assert(i < this->_vec_feats.size() && i < this->_vec_descs.size()); + static_cast(outRegionContainer)->_vec_feats.push_back(this->_vec_feats[i]); + static_cast(outRegionContainer)->_vec_descs.push_back(this->_vec_descs[i]); + } + + /** + * @brief Duplicate only reconstructed regions. + * @param[in] featuresInImage list of features with an associated 3D point Id + * @param[out] out_associated3dPoint + * @param[out] out_mapFullToLocal + */ + std::unique_ptr createFilteredRegions(const std::vector& featuresInImage, + std::vector& out_associated3dPoint, + std::map& out_mapFullToLocal) const override + { + out_associated3dPoint.clear(); + out_mapFullToLocal.clear(); + + This* regionsPtr = new This; + std::unique_ptr regions(regionsPtr); + regionsPtr->Features().reserve(featuresInImage.size()); + regionsPtr->Descriptors().reserve(featuresInImage.size()); + out_associated3dPoint.reserve(featuresInImage.size()); + for (std::size_t i = 0; i < featuresInImage.size(); ++i) + { + const FeatureInImage& feat = featuresInImage[i]; + regionsPtr->Features().push_back(this->_vec_feats[feat._featureIndex]); + regionsPtr->Descriptors().push_back(this->_vec_descs[feat._featureIndex]); + + // This assert should be valid in theory, but in the context of CameraLocalization + // we can have the same 2D feature associated to different 3D points (2 in practice). + // In this particular case, currently it returns the last one... + // + // assert(out_mapFullToLocal.count(feat._featureIndex) == 0); + + out_mapFullToLocal[feat._featureIndex] = i; + out_associated3dPoint.push_back(feat._point3dId); + } + return regions; + } +}; template using ScalarRegions = FeatDescRegions; @@ -295,7 +261,5 @@ using ScalarRegions = FeatDescRegions; template using BinaryRegions = FeatDescRegions; - - -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/RegionsPerView.hpp b/src/aliceVision/feature/RegionsPerView.hpp index 039868e196..616db72b60 100644 --- a/src/aliceVision/feature/RegionsPerView.hpp +++ b/src/aliceVision/feature/RegionsPerView.hpp @@ -20,20 +20,26 @@ namespace feature { /// Regions per ViewId of the considered SfMData container class MapRegionsPerDesc : public std::map> { -public: - std::size_t getNbAllRegions() const - { - std::size_t nb = 0; - for(const auto& it: *this) - nb += it.second->RegionCount(); - return nb; - } - - template - T getRegions(feature::EImageDescriberType descType) { return dynamic_cast(*this->at(descType)); } - - template - const T getRegions(feature::EImageDescriberType descType) const { return dynamic_cast(*this->at(descType)); } + public: + std::size_t getNbAllRegions() const + { + std::size_t nb = 0; + for (const auto& it : *this) + nb += it.second->RegionCount(); + return nb; + } + + template + T getRegions(feature::EImageDescriberType descType) + { + return dynamic_cast(*this->at(descType)); + } + + template + const T getRegions(feature::EImageDescriberType descType) const + { + return dynamic_cast(*this->at(descType)); + } }; using MapRegionsPerView = std::map; @@ -41,16 +47,16 @@ using MapRegionsPerView = std::map; template inline std::vector getCommonDescTypes(const MapFeatOrRegionsPerDesc& regionsA, const MapFeatOrRegionsPerDesc& regionsB) { - std::vector descTypes; - for(const auto& regionsPerDesc : regionsA) - { - const auto desc = regionsPerDesc.first; - if(regionsB.count(desc) > 0) + std::vector descTypes; + for (const auto& regionsPerDesc : regionsA) { - descTypes.push_back(desc); + const auto desc = regionsPerDesc.first; + if (regionsB.count(desc) > 0) + { + descTypes.push_back(desc); + } } - } - return descTypes; + return descTypes; } /** @@ -58,83 +64,60 @@ inline std::vector getCommonDescTypes(const MapFea */ class RegionsPerView { -public: - MapRegionsPerView& getData() - { - return _data; - } - - const MapRegionsPerView& getData() const - { - return _data; - } - - // TODO: to remove - const feature::Regions& getFirstViewRegions(feature::EImageDescriberType descType) const - { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - return *(_data.begin()->second.at(descType).get()); - } - - const feature::MapRegionsPerDesc& getRegionsPerDesc(IndexT viewId) const - { - return _data.at(viewId); - } - const feature::MapRegionsPerDesc& getDataPerDesc(IndexT viewId) const - { - return _data.at(viewId); - } - - const feature::Regions& getRegions(IndexT viewId, feature::EImageDescriberType descType) const - { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - return *(_data.at(viewId).at(descType).get()); - } - - const MapRegionsPerDesc& getAllRegions(IndexT viewId) const - { - return _data.at(viewId); - } - - bool viewExist(IndexT viewId) const - { - return _data.count(viewId) > 0; - } - - bool isEmpty() const - { - return _data.empty(); - } - - void addRegions(IndexT viewId, feature::EImageDescriberType descType, feature::Regions* regionsPtr) - { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - _data[viewId][descType].reset(regionsPtr); - } - - std::vector getCommonDescTypes(const Pair& pair) const - { - const auto& regionsA = getAllRegions(pair.first); - const auto& regionsB = getAllRegions(pair.second); - return aliceVision::feature::getCommonDescTypes(regionsA, regionsB); - } - - void clearDescriptors() - { - for(auto& itA: _data) + public: + MapRegionsPerView& getData() { return _data; } + + const MapRegionsPerView& getData() const { return _data; } + + // TODO: to remove + const feature::Regions& getFirstViewRegions(feature::EImageDescriberType descType) const { - for(auto& itB: itA.second) - { - itB.second->clearDescriptors(); - } + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + return *(_data.begin()->second.at(descType).get()); } - } - -private: - MapRegionsPerView _data; -}; -} // namespace feature -} // namespace aliceVision + const feature::MapRegionsPerDesc& getRegionsPerDesc(IndexT viewId) const { return _data.at(viewId); } + const feature::MapRegionsPerDesc& getDataPerDesc(IndexT viewId) const { return _data.at(viewId); } + + const feature::Regions& getRegions(IndexT viewId, feature::EImageDescriberType descType) const + { + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + return *(_data.at(viewId).at(descType).get()); + } + const MapRegionsPerDesc& getAllRegions(IndexT viewId) const { return _data.at(viewId); } + + bool viewExist(IndexT viewId) const { return _data.count(viewId) > 0; } + + bool isEmpty() const { return _data.empty(); } + + void addRegions(IndexT viewId, feature::EImageDescriberType descType, feature::Regions* regionsPtr) + { + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + _data[viewId][descType].reset(regionsPtr); + } + + std::vector getCommonDescTypes(const Pair& pair) const + { + const auto& regionsA = getAllRegions(pair.first); + const auto& regionsB = getAllRegions(pair.second); + return aliceVision::feature::getCommonDescTypes(regionsA, regionsB); + } + + void clearDescriptors() + { + for (auto& itA : _data) + { + for (auto& itB : itA.second) + { + itB.second->clearDescriptors(); + } + } + } + + private: + MapRegionsPerView _data; +}; +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/AKAZE.cpp b/src/aliceVision/feature/akaze/AKAZE.cpp index a1a229b5cd..d8d21bb202 100644 --- a/src/aliceVision/feature/akaze/AKAZE.cpp +++ b/src/aliceVision/feature/akaze/AKAZE.cpp @@ -14,15 +14,13 @@ namespace aliceVision { namespace feature { /// lookup table for 2d gaussian (sigma = 2.5) where (0,0) is top left and (6,6) is bottom right -const float gauss25[7][7] = { - {0.02546481f, 0.02350698f, 0.01849125f, 0.01239505f, 0.00708017f, 0.00344629f, 0.00142946f}, - {0.02350698f, 0.02169968f, 0.01706957f, 0.01144208f, 0.00653582f, 0.00318132f, 0.00131956f}, - {0.01849125f, 0.01706957f, 0.01342740f, 0.00900066f, 0.00514126f, 0.00250252f, 0.00103800f}, - {0.01239505f, 0.01144208f, 0.00900066f, 0.00603332f, 0.00344629f, 0.00167749f, 0.00069579f}, - {0.00708017f, 0.00653582f, 0.00514126f, 0.00344629f, 0.00196855f, 0.00095820f, 0.00039744f}, - {0.00344629f, 0.00318132f, 0.00250252f, 0.00167749f, 0.00095820f, 0.00046640f, 0.00019346f}, - {0.00142946f, 0.00131956f, 0.00103800f, 0.00069579f, 0.00039744f, 0.00019346f, 0.00008024f} -}; +const float gauss25[7][7] = {{0.02546481f, 0.02350698f, 0.01849125f, 0.01239505f, 0.00708017f, 0.00344629f, 0.00142946f}, + {0.02350698f, 0.02169968f, 0.01706957f, 0.01144208f, 0.00653582f, 0.00318132f, 0.00131956f}, + {0.01849125f, 0.01706957f, 0.01342740f, 0.00900066f, 0.00514126f, 0.00250252f, 0.00103800f}, + {0.01239505f, 0.01144208f, 0.00900066f, 0.00603332f, 0.00344629f, 0.00167749f, 0.00069579f}, + {0.00708017f, 0.00653582f, 0.00514126f, 0.00344629f, 0.00196855f, 0.00095820f, 0.00039744f}, + {0.00344629f, 0.00318132f, 0.00250252f, 0.00167749f, 0.00095820f, 0.00046640f, 0.00019346f}, + {0.00142946f, 0.00131956f, 0.00103800f, 0.00069579f, 0.00039744f, 0.00019346f, 0.00008024f}}; /// factor for the multiscale derivatives const float derivativeFactor = 1.5f; @@ -34,12 +32,12 @@ const float derivativeFactor = 1.5f; * @param[in] q Slice index * @param[in] Q nbSlice */ -inline float sigma(const float sigma0 , const int p , const int q , const int Q) +inline float sigma(const float sigma0, const int p, const int q, const int Q) { - if(p == 0 && q == 0) - return sigma0; - else - return sigma0 * powf(2.f, p + static_cast(q) / static_cast(Q)) ; + if (p == 0 && q == 0) + return sigma0; + else + return sigma0 * powf(2.f, p + static_cast(q) / static_cast(Q)); } /** @@ -66,425 +64,411 @@ void computeAKAZESlice(const image::Image& src, image::Image& Ly, image::Image& Lhess) { - const float sigmaCur = sigma(sigma0, p, q, nbSlice); - const float ratio = 1 << p; //pow(2,p); - const int sigmaScale = MathTrait::round(sigmaCur * derivativeFactor / ratio); - - image::Image smoothed; - - if(p == 0 && q == 0) - { - // compute new image - image::ImageGaussianFilter(src , sigma0 , Li, 0, 0); - } - else - { - // general case - image::Image in; - if( q == 0 ) + const float sigmaCur = sigma(sigma0, p, q, nbSlice); + const float ratio = 1 << p; // pow(2,p); + const int sigmaScale = MathTrait::round(sigmaCur * derivativeFactor / ratio); + + image::Image smoothed; + + if (p == 0 && q == 0) + { + // compute new image + image::ImageGaussianFilter(src, sigma0, Li, 0, 0); + } + else + { + // general case + image::Image in; + if (q == 0) + { + image::ImageHalfSample(src, in); + } + else + { + in = src; + } + + const float sigmaPrev = (q == 0) ? sigma(sigma0, p - 1, nbSlice - 1, nbSlice) : sigma(sigma0, p, q - 1, nbSlice); + + // compute non linear timing between two consecutive slices + const float t_prev = 0.5f * (sigmaPrev * sigmaPrev); + const float t_cur = 0.5f * (sigmaCur * sigmaCur); + const float total_cycle_time = t_cur - t_prev; + + // compute first derivatives (Scharr scale 1, non normalized) for diffusion coef + image::ImageGaussianFilter(in, 1.f, smoothed, 0, 0); + image::ImageScharrXDerivative(smoothed, Lx, false); + image::ImageScharrYDerivative(smoothed, Ly, false); + + // compute diffusion coefficient + image::Image& diff = smoothed; // diffusivity image (reuse existing memory) + image::ImagePeronaMalikG2DiffusionCoef(Lx, Ly, contrastFactor, diff); + + // compute FED cycles + std::vector tau; + image::FEDCycleTimings(total_cycle_time, 0.25f, tau); + image::ImageFEDCycle(in, diff, tau); + Li = in; // evolution image + } + + // compute Hessian response + if (p == 0 && q == 0) { - image::ImageHalfSample(src , in); + smoothed = Li; } else { - in = src; + // add a little smooth to image (for robustness of Scharr derivatives) + image::ImageGaussianFilter(Li, 1.f, smoothed, 0, 0); } - const float sigmaPrev = ( q == 0 ) ? sigma(sigma0, p - 1, nbSlice - 1, nbSlice) : sigma(sigma0, p, q - 1, nbSlice); - - // compute non linear timing between two consecutive slices - const float t_prev = 0.5f * (sigmaPrev * sigmaPrev); - const float t_cur = 0.5f * (sigmaCur * sigmaCur); - const float total_cycle_time = t_cur - t_prev; - - // compute first derivatives (Scharr scale 1, non normalized) for diffusion coef - image::ImageGaussianFilter(in , 1.f , smoothed, 0, 0 ); - image::ImageScharrXDerivative(smoothed, Lx, false); - image::ImageScharrYDerivative(smoothed, Ly, false); - - // compute diffusion coefficient - image::Image & diff = smoothed; // diffusivity image (reuse existing memory) - image::ImagePeronaMalikG2DiffusionCoef(Lx, Ly, contrastFactor, diff) ; - - // compute FED cycles - std::vector tau ; - image::FEDCycleTimings(total_cycle_time, 0.25f, tau); - image::ImageFEDCycle(in, diff, tau); - Li = in ; // evolution image - } - - // compute Hessian response - if(p == 0 && q == 0) - { - smoothed = Li ; - } - else - { - // add a little smooth to image (for robustness of Scharr derivatives) - image::ImageGaussianFilter(Li, 1.f, smoothed, 0, 0); - } - - // compute true first derivatives - image::ImageScaledScharrXDerivative(smoothed, Lx, sigmaScale); - image::ImageScaledScharrYDerivative(smoothed, Ly, sigmaScale); - - // second order spatial derivatives - image::Image Lxx, Lyy, Lxy; - image::ImageScaledScharrXDerivative(Lx, Lxx, sigmaScale); - image::ImageScaledScharrYDerivative(Lx, Lxy, sigmaScale); - image::ImageScaledScharrYDerivative(Ly, Lyy, sigmaScale); - - Lx *= static_cast(sigmaScale); - Ly *= static_cast(sigmaScale); - - // compute Determinant of the Hessian - Lhess.resize(Li.Width(), Li.Height()); - const float sigmaSizeQuad = Square(sigmaScale) * Square(sigmaScale); - Lhess.array() = (Lxx.array() * Lyy.array() - Lxy.array().square()) * sigmaSizeQuad; + // compute true first derivatives + image::ImageScaledScharrXDerivative(smoothed, Lx, sigmaScale); + image::ImageScaledScharrYDerivative(smoothed, Ly, sigmaScale); + + // second order spatial derivatives + image::Image Lxx, Lyy, Lxy; + image::ImageScaledScharrXDerivative(Lx, Lxx, sigmaScale); + image::ImageScaledScharrYDerivative(Lx, Lxy, sigmaScale); + image::ImageScaledScharrYDerivative(Ly, Lyy, sigmaScale); + + Lx *= static_cast(sigmaScale); + Ly *= static_cast(sigmaScale); + + // compute Determinant of the Hessian + Lhess.resize(Li.Width(), Li.Height()); + const float sigmaSizeQuad = Square(sigmaScale) * Square(sigmaScale); + Lhess.array() = (Lxx.array() * Lyy.array() - Lxy.array().square()) * sigmaSizeQuad; } #if DEBUG_OCTAVE -template +template void convertScale(Image& src) { - typename image::Image::Tpixel min_val = src.minCoeff(), max_val = src.maxCoeff(); - src = src.array() - min_val; - src /= max_val; + typename image::Image::Tpixel min_val = src.minCoeff(), max_val = src.maxCoeff(); + src = src.array() - min_val; + src /= max_val; } -#endif // DEBUG_OCTAVE +#endif // DEBUG_OCTAVE -AKAZE::AKAZE(const image::Image& image, const AKAZEOptions& options): - _input(image), +AKAZE::AKAZE(const image::Image& image, const AKAZEOptions& options) + : _input(image), _options(options) { - _options.descFactor = std::max(6.f * sqrtf(2.f), _options.descFactor); + _options.descFactor = std::max(6.f * sqrtf(2.f), _options.descFactor); - // safety check to limit the computable octave count - const int nbOctaveMax = ceil(std::log2( std::min(_input.Width(), _input.Height()))); - _options.nbOctaves = std::min(_options.nbOctaves, nbOctaveMax); + // safety check to limit the computable octave count + const int nbOctaveMax = ceil(std::log2(std::min(_input.Width(), _input.Height()))); + _options.nbOctaves = std::min(_options.nbOctaves, nbOctaveMax); } void AKAZE::computeScaleSpace() { - float contrastFactor = computeAutomaticContrastFactor( _input, 0.7f); - image::Image input = _input; + float contrastFactor = computeAutomaticContrastFactor(_input, 0.7f); + image::Image input = _input; - // octave computation - for(int p = 0; p < _options.nbOctaves; ++p) - { - contrastFactor *= (p == 0) ? 1.f : 0.75f; - - for(int q = 0; q < _options.nbSlicePerOctave; ++q) + // octave computation + for (int p = 0; p < _options.nbOctaves; ++p) { - _evolution.emplace_back(TEvolution()); - TEvolution& evo = _evolution.back(); + contrastFactor *= (p == 0) ? 1.f : 0.75f; + + for (int q = 0; q < _options.nbSlicePerOctave; ++q) + { + _evolution.emplace_back(TEvolution()); + TEvolution& evo = _evolution.back(); - // compute Slice at (p,q) index - computeAKAZESlice(input, p, q, _options.nbSlicePerOctave, _options.sigma0, contrastFactor, - evo.cur, evo.Lx, evo.Ly, evo.Lhess); + // compute Slice at (p,q) index + computeAKAZESlice(input, p, q, _options.nbSlicePerOctave, _options.sigma0, contrastFactor, evo.cur, evo.Lx, evo.Ly, evo.Lhess); - // Prepare inputs for next slice - input = evo.cur; + // Prepare inputs for next slice + input = evo.cur; - // DEBUG octave image + // DEBUG octave image #if DEBUG_OCTAVE - std::stringstream str ; - str << "./" << "_oct_" << p << "_" << q << ".png" ; - image::Image tmp = evo.cur; - convertScale(tmp); - image::Image< unsigned char > tmp2 ((tmp*255).cast()); - image::writeImage(str.str(), tmp2, image::EImageColorSpace::NO_CONVERSION); -#endif // DEBUG_OCTAVE + std::stringstream str; + str << "./" + << "_oct_" << p << "_" << q << ".png"; + image::Image tmp = evo.cur; + convertScale(tmp); + image::Image tmp2((tmp * 255).cast()); + image::writeImage(str.str(), tmp2, image::EImageColorSpace::NO_CONVERSION); +#endif // DEBUG_OCTAVE + } } - } } -void detectDuplicates(std::vector>& previous, - std::vector>& current) +void detectDuplicates(std::vector>& previous, std::vector>& current) { - // mark duplicates, using a full search algorithm - for(std::vector >::iterator p1=previous.begin(); p1 >::iterator p2 = current.begin(); p2>::iterator p1 = previous.begin(); p1 < previous.end(); ++p1) { - if(p2->second == true) - continue; - - // check spatial distance - const float dist = Square(p1->first.x-p2->first.x)+Square(p1->first.y-p2->first.y); - if(dist <= Square(p1->first.size) && dist != 0.f) - { - if (p1->first.response < p2->first.response) - p1->second = true; // mark as duplicate key point - else - p2->second = true; // mark as duplicate key point - break; // no other point can be so close, so skip to the next iteration - } + for (std::vector>::iterator p2 = current.begin(); p2 < current.end(); ++p2) + { + if (p2->second == true) + continue; + + // check spatial distance + const float dist = Square(p1->first.x - p2->first.x) + Square(p1->first.y - p2->first.y); + if (dist <= Square(p1->first.size) && dist != 0.f) + { + if (p1->first.response < p2->first.response) + p1->second = true; // mark as duplicate key point + else + p2->second = true; // mark as duplicate key point + break; // no other point can be so close, so skip to the next iteration + } + } } - } } void AKAZE::featureDetection(std::vector& keypoints) const { - std::vector>> ptsPerSlice(_options.nbOctaves * _options.nbSlicePerOctave); - - #pragma omp parallel for schedule(dynamic) - for(int p = 0 ; p < _options.nbOctaves ; ++p) - { - const float ratio = static_cast(1 << p); + std::vector>> ptsPerSlice(_options.nbOctaves * _options.nbSlicePerOctave); - for(int q = 0 ; q < _options.nbSlicePerOctave ; ++q) +#pragma omp parallel for schedule(dynamic) + for (int p = 0; p < _options.nbOctaves; ++p) { - const float sigma_cur = sigma( _options.sigma0 , p , q , _options.nbSlicePerOctave ); - const image::Image& LDetHess = _evolution[_options.nbOctaves * p + q].Lhess; + const float ratio = static_cast(1 << p); - // check that the point is under the image limits for the descriptor computation - const float borderLimit = - MathTrait::round(_options.descFactor * sigma_cur * derivativeFactor / ratio) + 1; - - for(int jx = borderLimit; jx < LDetHess.Height()-borderLimit; ++jx) - { - for(int ix = borderLimit; ix < LDetHess.Width()-borderLimit; ++ix) + for (int q = 0; q < _options.nbSlicePerOctave; ++q) { - const float value = LDetHess(jx, ix); - - // filter the points with the detector threshold - if(value > _options.threshold && - value > LDetHess(jx-1, ix) && - value > LDetHess(jx-1, ix+1) && - value > LDetHess(jx-1, ix-1) && - value > LDetHess(jx , ix-1) && - value > LDetHess(jx , ix+1) && - value > LDetHess(jx+1, ix-1) && - value > LDetHess(jx+1, ix) && - value > LDetHess(jx+1, ix+1)) - { - AKAZEKeypoint point; - point.size = sigma_cur * derivativeFactor ; - point.octave = p; - point.response = fabs(value); - point.x = ix * ratio + 0.5 * (ratio-1); - point.y = jx * ratio + 0.5 * (ratio-1); - point.angle = 0.0f; - point.class_id = p * _options.nbSlicePerOctave + q; - ptsPerSlice[_options.nbOctaves * p + q].emplace_back(point, false); - } + const float sigma_cur = sigma(_options.sigma0, p, q, _options.nbSlicePerOctave); + const image::Image& LDetHess = _evolution[_options.nbOctaves * p + q].Lhess; + + // check that the point is under the image limits for the descriptor computation + const float borderLimit = MathTrait::round(_options.descFactor * sigma_cur * derivativeFactor / ratio) + 1; + + for (int jx = borderLimit; jx < LDetHess.Height() - borderLimit; ++jx) + { + for (int ix = borderLimit; ix < LDetHess.Width() - borderLimit; ++ix) + { + const float value = LDetHess(jx, ix); + + // filter the points with the detector threshold + if (value > _options.threshold && value > LDetHess(jx - 1, ix) && value > LDetHess(jx - 1, ix + 1) && + value > LDetHess(jx - 1, ix - 1) && value > LDetHess(jx, ix - 1) && value > LDetHess(jx, ix + 1) && + value > LDetHess(jx + 1, ix - 1) && value > LDetHess(jx + 1, ix) && value > LDetHess(jx + 1, ix + 1)) + { + AKAZEKeypoint point; + point.size = sigma_cur * derivativeFactor; + point.octave = p; + point.response = fabs(value); + point.x = ix * ratio + 0.5 * (ratio - 1); + point.y = jx * ratio + 0.5 * (ratio - 1); + point.angle = 0.0f; + point.class_id = p * _options.nbSlicePerOctave + q; + ptsPerSlice[_options.nbOctaves * p + q].emplace_back(point, false); + } + } + } } - } } - } - - // filter duplicates - detectDuplicates(ptsPerSlice[0], ptsPerSlice[0]); - for (int k = 1; k < ptsPerSlice.size(); ++k) - { - detectDuplicates(ptsPerSlice[k], ptsPerSlice[k]); // detect inter scale duplicates - detectDuplicates(ptsPerSlice[k-1], ptsPerSlice[k]); // detect duplicates using previous octave - } - - for(std::size_t k = 0; k < ptsPerSlice.size(); ++k) - { - const std::vector< std::pair >& vec_kp = ptsPerSlice[k]; - for(std::size_t i = 0; i < vec_kp.size(); ++i) + + // filter duplicates + detectDuplicates(ptsPerSlice[0], ptsPerSlice[0]); + for (int k = 1; k < ptsPerSlice.size(); ++k) { - const auto& kp = vec_kp[i]; + detectDuplicates(ptsPerSlice[k], ptsPerSlice[k]); // detect inter scale duplicates + detectDuplicates(ptsPerSlice[k - 1], ptsPerSlice[k]); // detect duplicates using previous octave + } - // keep only the one marked as not duplicated - if(!kp.second) - keypoints.emplace_back(kp.first); + for (std::size_t k = 0; k < ptsPerSlice.size(); ++k) + { + const std::vector>& vec_kp = ptsPerSlice[k]; + for (std::size_t i = 0; i < vec_kp.size(); ++i) + { + const auto& kp = vec_kp[i]; + + // keep only the one marked as not duplicated + if (!kp.second) + keypoints.emplace_back(kp.first); + } } - } } void AKAZE::gridFiltering(std::vector& keypoints) const { - if(_options.maxTotalKeypoints == 0 || keypoints.size() <= _options.maxTotalKeypoints) - return; + if (_options.maxTotalKeypoints == 0 || keypoints.size() <= _options.maxTotalKeypoints) + return; - // sort keypoints by size to guarantee best points are kept - // note: reordering keypoints also helps preventing grid-filtering from creating a - // non-uniform distribution within cells when input keypoints are sorted by spatial coordinates - std::sort(keypoints.begin(), keypoints.end(), [](const AKAZEKeypoint& a, const AKAZEKeypoint& b){ return a.size > b.size; }); + // sort keypoints by size to guarantee best points are kept + // note: reordering keypoints also helps preventing grid-filtering from creating a + // non-uniform distribution within cells when input keypoints are sorted by spatial coordinates + std::sort(keypoints.begin(), keypoints.end(), [](const AKAZEKeypoint& a, const AKAZEKeypoint& b) { return a.size > b.size; }); - std::vector out_keypoints; - out_keypoints.reserve(keypoints.size()); + std::vector out_keypoints; + out_keypoints.reserve(keypoints.size()); - const std::size_t sizeMat = _options.gridSize * _options.gridSize; - const std::size_t keypointsPerCell = _options.maxTotalKeypoints / sizeMat; - const double regionWidth = _input.Width() / static_cast(_options.gridSize); - const double regionHeight = _input.Height() / static_cast(_options.gridSize); + const std::size_t sizeMat = _options.gridSize * _options.gridSize; + const std::size_t keypointsPerCell = _options.maxTotalKeypoints / sizeMat; + const double regionWidth = _input.Width() / static_cast(_options.gridSize); + const double regionHeight = _input.Height() / static_cast(_options.gridSize); - std::vector countFeatPerCell(sizeMat, 0); - std::vector rejectedIndexes; + std::vector countFeatPerCell(sizeMat, 0); + std::vector rejectedIndexes; - for(std::size_t i = 0; i < keypoints.size(); ++i) - { - const AKAZEKeypoint& point = keypoints.at(i); - const std::size_t cellX = std::min(std::size_t(point.x / regionWidth ), _options.gridSize); - const std::size_t cellY = std::min(std::size_t(point.y / regionHeight), _options.gridSize); + for (std::size_t i = 0; i < keypoints.size(); ++i) + { + const AKAZEKeypoint& point = keypoints.at(i); + const std::size_t cellX = std::min(std::size_t(point.x / regionWidth), _options.gridSize); + const std::size_t cellY = std::min(std::size_t(point.y / regionHeight), _options.gridSize); - std::size_t& count = countFeatPerCell.at(cellX * _options.gridSize + cellY); - ++count; + std::size_t& count = countFeatPerCell.at(cellX * _options.gridSize + cellY); + ++count; - if(count > keypointsPerCell) - { - rejectedIndexes.emplace_back(i); - continue; - } + if (count > keypointsPerCell) + { + rejectedIndexes.emplace_back(i); + continue; + } - out_keypoints.emplace_back(point); - } + out_keypoints.emplace_back(point); + } - // if we don't have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in the grid for example). - // we add the best other ones, without repartition constraint. - if(out_keypoints.size() < _options.maxTotalKeypoints) - { - const std::size_t remainingElements = std::min(rejectedIndexes.size(), _options.maxTotalKeypoints - out_keypoints.size()); - ALICEVISION_LOG_TRACE("Grid filtering copy " << remainingElements << " remaining points."); + // if we don't have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in the grid for example). + // we add the best other ones, without repartition constraint. + if (out_keypoints.size() < _options.maxTotalKeypoints) + { + const std::size_t remainingElements = std::min(rejectedIndexes.size(), _options.maxTotalKeypoints - out_keypoints.size()); + ALICEVISION_LOG_TRACE("Grid filtering copy " << remainingElements << " remaining points."); - for(std::size_t i = 0; i < remainingElements; ++i) - out_keypoints.emplace_back(keypoints.at(rejectedIndexes.at(i))); - } + for (std::size_t i = 0; i < remainingElements; ++i) + out_keypoints.emplace_back(keypoints.at(rejectedIndexes.at(i))); + } - out_keypoints.swap(keypoints); + out_keypoints.swap(keypoints); } bool AKAZE::subpixelRefinement(AKAZEKeypoint& keypoint, const image::Image& Ldet) const { - const unsigned int ratio = (1 << keypoint.octave); - const int x = MathTrait::round(keypoint.x / ratio); - const int y = MathTrait::round(keypoint.y / ratio); - - // compute the gradient - const float Dx = 0.5f * (Ldet(y,x+1) - Ldet(y,x-1)); - const float Dy = 0.5f * (Ldet(y+1, x) - Ldet(y-1, x)); - - // compute the Hessian - const float Dxx = Ldet(y, x+1) + Ldet(y, x-1) - 2.0f * Ldet(y, x); - const float Dyy = Ldet(y+1, x) + Ldet(y-1, x) -2.0f * Ldet(y, x); - const float Dxy = 0.25f * (Ldet(y+1, x+1) + Ldet(y-1, x-1)) - 0.25f * (Ldet(y-1, x+1) + Ldet(y+1, x-1)); - - // solve the linear system - Eigen::Matrix A; - Vec2 b; - A << Dxx, Dxy, Dxy, Dyy; - b << -Dx, -Dy; - - const Vec2 dst = A.fullPivLu().solve(b); - - if(fabs(dst(0)) <= 1.0 && fabs(dst(1)) <= 1.0) - { - keypoint.x += dst(0) * ratio + 0.5 * (ratio-1); - keypoint.y += dst(1) * ratio + 0.5 * (ratio-1); - return true; - } - - // delete the point since its not stable - return false; + const unsigned int ratio = (1 << keypoint.octave); + const int x = MathTrait::round(keypoint.x / ratio); + const int y = MathTrait::round(keypoint.y / ratio); + + // compute the gradient + const float Dx = 0.5f * (Ldet(y, x + 1) - Ldet(y, x - 1)); + const float Dy = 0.5f * (Ldet(y + 1, x) - Ldet(y - 1, x)); + + // compute the Hessian + const float Dxx = Ldet(y, x + 1) + Ldet(y, x - 1) - 2.0f * Ldet(y, x); + const float Dyy = Ldet(y + 1, x) + Ldet(y - 1, x) - 2.0f * Ldet(y, x); + const float Dxy = 0.25f * (Ldet(y + 1, x + 1) + Ldet(y - 1, x - 1)) - 0.25f * (Ldet(y - 1, x + 1) + Ldet(y + 1, x - 1)); + + // solve the linear system + Eigen::Matrix A; + Vec2 b; + A << Dxx, Dxy, Dxy, Dyy; + b << -Dx, -Dy; + + const Vec2 dst = A.fullPivLu().solve(b); + + if (fabs(dst(0)) <= 1.0 && fabs(dst(1)) <= 1.0) + { + keypoint.x += dst(0) * ratio + 0.5 * (ratio - 1); + keypoint.y += dst(1) * ratio + 0.5 * (ratio - 1); + return true; + } + + // delete the point since its not stable + return false; } void AKAZE::subpixelRefinement(std::vector& keypoints) const { - std::vector in_keypoints; - in_keypoints.swap(keypoints); - keypoints.reserve(in_keypoints.size()); - - #pragma omp parallel for schedule(dynamic) - for(int i = 0; i < static_cast(in_keypoints.size()); ++i) - { - AKAZEKeypoint& point = in_keypoints[i]; - if(subpixelRefinement(point, this->_evolution[point.class_id].Lhess)) + std::vector in_keypoints; + in_keypoints.swap(keypoints); + keypoints.reserve(in_keypoints.size()); + +#pragma omp parallel for schedule(dynamic) + for (int i = 0; i < static_cast(in_keypoints.size()); ++i) { - #pragma omp critical - keypoints.emplace_back(point); + AKAZEKeypoint& point = in_keypoints[i]; + if (subpixelRefinement(point, this->_evolution[point.class_id].Lhess)) + { +#pragma omp critical + keypoints.emplace_back(point); + } } - } } /// This function computes the angle from the vector given by (X Y). From 0 to 2*Pi inline float getAngle(float x, float y) { - const float angle = atan2(y,x); - // output angle between 0 and 2Pi - return angle > 0.0f ? angle : 2.f * M_PI + angle; + const float angle = atan2(y, x); + // output angle between 0 and 2Pi + return angle > 0.0f ? angle : 2.f * M_PI + angle; } -void AKAZE::computeMainOrientation(AKAZEKeypoint& keypoint, - const image::Image & Lx, - const image::Image & Ly) const +void AKAZE::computeMainOrientation(AKAZEKeypoint& keypoint, const image::Image& Lx, const image::Image& Ly) const { - int ix = 0, iy = 0, idx = 0; - const int TABSIZE = 109; - float resX[TABSIZE], resY[TABSIZE], Ang[TABSIZE]; - const short id[] = {6,5,4,3,2,1,0,1,2,3,4,5,6}; - - // variables for computing the dominant direction - float sumX = 0.0f, sumY = 0.0f, max = 0.0f, ang1 = 0.0f, ang2 = 0.0f; - - // get the information from the keypoint - const unsigned int ratio = (1 << keypoint.octave); - const int s = MathTrait::round(keypoint.size/ratio); - const float xf = keypoint.x/ratio; - const float yf = keypoint.y/ratio; - - // calculate derivatives responses for points within radius of 6*scale - for(int i = -6; i <= 6; ++i) - { - for(int j = -6; j <= 6; ++j) + int ix = 0, iy = 0, idx = 0; + const int TABSIZE = 109; + float resX[TABSIZE], resY[TABSIZE], Ang[TABSIZE]; + const short id[] = {6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6}; + + // variables for computing the dominant direction + float sumX = 0.0f, sumY = 0.0f, max = 0.0f, ang1 = 0.0f, ang2 = 0.0f; + + // get the information from the keypoint + const unsigned int ratio = (1 << keypoint.octave); + const int s = MathTrait::round(keypoint.size / ratio); + const float xf = keypoint.x / ratio; + const float yf = keypoint.y / ratio; + + // calculate derivatives responses for points within radius of 6*scale + for (int i = -6; i <= 6; ++i) { - if(i*i + j*j < 36) - { - iy = MathTrait::round(yf + j * s); - ix = MathTrait::round(xf + i * s); - - const float gweight = gauss25[id[i+6]][id[j+6]]; - resX[idx] = gweight * Lx(iy, ix); - resY[idx] = gweight * Ly(iy, ix); - - Ang[idx] = getAngle(resX[idx],resY[idx]); - ++idx; - } + for (int j = -6; j <= 6; ++j) + { + if (i * i + j * j < 36) + { + iy = MathTrait::round(yf + j * s); + ix = MathTrait::round(xf + i * s); + + const float gweight = gauss25[id[i + 6]][id[j + 6]]; + resX[idx] = gweight * Lx(iy, ix); + resY[idx] = gweight * Ly(iy, ix); + + Ang[idx] = getAngle(resX[idx], resY[idx]); + ++idx; + } + } } - } - - // loop slides pi/3 window around feature point - for (ang1 = 0.f; ang1 < 2.0f * M_PI; ang1+=0.15f) - { - ang2 =(ang1 + M_PI / 3.0f > 2.0f * M_PI ? - ang1 - 5.0f * M_PI / 3.0f : - ang1 + M_PI / 3.0f); - sumX = sumY = 0.f; - for(std::size_t k = 0; k < idx; ++k) + // loop slides pi/3 window around feature point + for (ang1 = 0.f; ang1 < 2.0f * M_PI; ang1 += 0.15f) { - // get angle from the x-axis of the sample point - const float& ang = Ang[k]; - - // determine whether the point is within the window - if (ang1 < ang2 && ang1 < ang && ang < ang2) - { - sumX += resX[k]; - sumY += resY[k]; - } - else if (ang2 < ang1 && - ((ang > 0 && ang < ang2) || (ang > ang1 && ang < 2.0f * M_PI) )) - { - sumX += resX[k]; - sumY += resY[k]; - } - } + ang2 = (ang1 + M_PI / 3.0f > 2.0f * M_PI ? ang1 - 5.0f * M_PI / 3.0f : ang1 + M_PI / 3.0f); + sumX = sumY = 0.f; - // if the vector produced from this window is longer than all - // previous vectors then this forms the new dominant direction - if (sumX * sumX + sumY * sumY > max) - { - // store largest orientation - max = sumX * sumX + sumY * sumY; - keypoint.angle = getAngle(sumX, sumY); + for (std::size_t k = 0; k < idx; ++k) + { + // get angle from the x-axis of the sample point + const float& ang = Ang[k]; + + // determine whether the point is within the window + if (ang1 < ang2 && ang1 < ang && ang < ang2) + { + sumX += resX[k]; + sumY += resY[k]; + } + else if (ang2 < ang1 && ((ang > 0 && ang < ang2) || (ang > ang1 && ang < 2.0f * M_PI))) + { + sumX += resX[k]; + sumY += resY[k]; + } + } + + // if the vector produced from this window is longer than all + // previous vectors then this forms the new dominant direction + if (sumX * sumX + sumY * sumY > max) + { + // store largest orientation + max = sumX * sumX + sumY * sumY; + keypoint.angle = getAngle(sumX, sumY); + } } - } } -} // namespace feature -} // namespace aliceVision - +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/AKAZE.hpp b/src/aliceVision/feature/akaze/AKAZE.hpp index 07388b96db..12fd95eca8 100644 --- a/src/aliceVision/feature/akaze/AKAZE.hpp +++ b/src/aliceVision/feature/akaze/AKAZE.hpp @@ -8,7 +8,7 @@ #pragma once #ifdef _MSC_VER -#pragma warning(once:4244) + #pragma warning(once : 4244) #endif #include @@ -42,40 +42,40 @@ namespace feature { struct AKAZEOptions { - /// octave to process - int nbOctaves = 4; - /// levels per octave - int nbSlicePerOctave = 4; - /// initial sigma offset (used to suppress low level noise) - float sigma0 = 1.6f; - /// hessian determinant threshold - float threshold = 0.0008f; - /// magnifier used to describe an interest point - float descFactor = 1.0f; - /// grid size for filtering - std::size_t gridSize = 4; - /// maximum number of keypoints - std::size_t maxTotalKeypoints = 1000; + /// octave to process + int nbOctaves = 4; + /// levels per octave + int nbSlicePerOctave = 4; + /// initial sigma offset (used to suppress low level noise) + float sigma0 = 1.6f; + /// hessian determinant threshold + float threshold = 0.0008f; + /// magnifier used to describe an interest point + float descFactor = 1.0f; + /// grid size for filtering + std::size_t gridSize = 4; + /// maximum number of keypoints + std::size_t maxTotalKeypoints = 1000; }; struct AKAZEKeypoint { - /// coordinate x of the keypoints - float x = 0.f; - /// coordinate y of the keypoints - float y = 0.f; - /// diameter of the meaningful keypoint neighborhood - float size = 0.f; - /// computed orientation of the keypoint (-1 if not applicable); - /// it's in [0,360) degrees and measured relative to - /// image coordinate system, ie in clockwise. - float angle = 0.f; - /// the response by which the most strong keypoints have been selected. Can be used for the further sorting or subsampling - float response = 0.f; - /// octave (pyramid layer) from which the keypoint has been extracted - unsigned char octave = 0; - /// object class (if the keypoints need to be clustered by an object they belong to) - unsigned char class_id = 0; + /// coordinate x of the keypoints + float x = 0.f; + /// coordinate y of the keypoints + float y = 0.f; + /// diameter of the meaningful keypoint neighborhood + float size = 0.f; + /// computed orientation of the keypoint (-1 if not applicable); + /// it's in [0,360) degrees and measured relative to + /// image coordinate system, ie in clockwise. + float angle = 0.f; + /// the response by which the most strong keypoints have been selected. Can be used for the further sorting or subsampling + float response = 0.f; + /// octave (pyramid layer) from which the keypoint has been extracted + unsigned char octave = 0; + /// object class (if the keypoints need to be clustered by an object they belong to) + unsigned char class_id = 0; }; /** @@ -83,85 +83,79 @@ struct AKAZEKeypoint */ class AKAZE { -public: - - struct TEvolution - { - /// current gaussian image - image::Image cur; - /// current x derivatives - image::Image Lx; - /// current y derivatives - image::Image Ly; - /// current Determinant of Hessian - image::Image Lhess; - }; - - /** - * @brief Constructor - * @param[in] image Input image - * @param[in] options AKAZE configuration options - */ - AKAZE(const image::Image& image, const AKAZEOptions& options); - - /** - * @brief Compute the AKAZE non linear diffusion scale space per slice - */ - void computeScaleSpace(); - - /** - * @brief Detect feature in the AKAZE scale space - * @param[in,out] keypoints AKAZE keypoints - */ - void featureDetection(std::vector& keypoints) const; - - /** - * @brief Grid filtering of the detected keypoints - * @param[in,out] keypoints AKAZE keypoints - */ - void gridFiltering(std::vector& keypoints) const; - - /** - * @brief Sub-pixel refinement of the detected keypoints - * @param[in,out] keypoints AKAZE keypoints - */ - void subpixelRefinement(std::vector& keypoints) const; - - /** - * @brief Sub-pixel refinement of the detected keypoint - * @param[in,out] keypoint AKAZE keypoint - */ - bool subpixelRefinement(AKAZEKeypoint& keypoint, const image::Image& Ldet) const; - - /** - * @brief This method computes the main orientation for a given keypoint - * @param[in,out] keypoint Input keypoint - * @param[in] Lx X derivatives - * @param[in] Ly Y derivatives - * @note The orientation is computed using a similar approach as described in the - * original SURF method. See Bay et al., Speeded Up Robust Features, ECCV 2006 - */ - void computeMainOrientation(AKAZEKeypoint& keypoint, - const image::Image& Lx, - const image::Image& Ly) const; - - /** - * @brief Get scale space slices - * @return slices - */ - inline const std::vector& getSlices() const - { - return _evolution; - } - -private: - /// configuration options for AKAZE - AKAZEOptions _options; - /// vector of nonlinear diffusion evolution (Scale Space) - std::vector _evolution; - /// input image - image::Image _input; + public: + struct TEvolution + { + /// current gaussian image + image::Image cur; + /// current x derivatives + image::Image Lx; + /// current y derivatives + image::Image Ly; + /// current Determinant of Hessian + image::Image Lhess; + }; + + /** + * @brief Constructor + * @param[in] image Input image + * @param[in] options AKAZE configuration options + */ + AKAZE(const image::Image& image, const AKAZEOptions& options); + + /** + * @brief Compute the AKAZE non linear diffusion scale space per slice + */ + void computeScaleSpace(); + + /** + * @brief Detect feature in the AKAZE scale space + * @param[in,out] keypoints AKAZE keypoints + */ + void featureDetection(std::vector& keypoints) const; + + /** + * @brief Grid filtering of the detected keypoints + * @param[in,out] keypoints AKAZE keypoints + */ + void gridFiltering(std::vector& keypoints) const; + + /** + * @brief Sub-pixel refinement of the detected keypoints + * @param[in,out] keypoints AKAZE keypoints + */ + void subpixelRefinement(std::vector& keypoints) const; + + /** + * @brief Sub-pixel refinement of the detected keypoint + * @param[in,out] keypoint AKAZE keypoint + */ + bool subpixelRefinement(AKAZEKeypoint& keypoint, const image::Image& Ldet) const; + + /** + * @brief This method computes the main orientation for a given keypoint + * @param[in,out] keypoint Input keypoint + * @param[in] Lx X derivatives + * @param[in] Ly Y derivatives + * @note The orientation is computed using a similar approach as described in the + * original SURF method. See Bay et al., Speeded Up Robust Features, ECCV 2006 + */ + void computeMainOrientation(AKAZEKeypoint& keypoint, const image::Image& Lx, const image::Image& Ly) const; + + /** + * @brief Get scale space slices + * @return slices + */ + inline const std::vector& getSlices() const { return _evolution; } + + private: + /// configuration options for AKAZE + AKAZEOptions _options; + /// vector of nonlinear diffusion evolution (Scale Space) + std::vector _evolution; + /// input image + image::Image _input; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.cpp b/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.cpp index 958e153b4b..3126679035 100644 --- a/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.cpp +++ b/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.cpp @@ -10,160 +10,151 @@ namespace aliceVision { namespace feature { -bool ImageDescriber_AKAZE::describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask) +bool ImageDescriber_AKAZE::describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask) { - _params.options.descFactor = - (_params.akazeDescriptorType == AKAZE_MSURF || - _params.akazeDescriptorType == AKAZE_LIOP) ? 10.f * sqrtf(2.f) - : 11.f * sqrtf(2.f); // MLDB + _params.options.descFactor = + (_params.akazeDescriptorType == AKAZE_MSURF || _params.akazeDescriptorType == AKAZE_LIOP) ? 10.f * sqrtf(2.f) : 11.f * sqrtf(2.f); // MLDB + std::vector keypoints; + keypoints.reserve(_params.options.maxTotalKeypoints * 2); - std::vector keypoints; - keypoints.reserve(_params.options.maxTotalKeypoints * 2); + AKAZE akaze(image, _params.options); + akaze.computeScaleSpace(); + akaze.featureDetection(keypoints); + akaze.subpixelRefinement(keypoints); + akaze.gridFiltering(keypoints); - AKAZE akaze(image, _params.options); - akaze.computeScaleSpace(); - akaze.featureDetection(keypoints); - akaze.subpixelRefinement(keypoints); - akaze.gridFiltering(keypoints); + allocate(regions); - allocate(regions); - - switch(_params.akazeDescriptorType) - { - case AKAZE_MSURF: + switch (_params.akazeDescriptorType) { - // build alias to cached data - AKAZE_Float_Regions* regionsCasted = dynamic_cast(regions.get()); - regionsCasted->Features().resize(keypoints.size()); - regionsCasted->Descriptors().resize(keypoints.size()); - -#pragma omp parallel for - for(int i = 0; i < static_cast(keypoints.size()); ++i) - { - AKAZEKeypoint point = keypoints.at(i); - - // feature masking - if(mask) + case AKAZE_MSURF: { - const image::Image& maskIma = *mask; - if(maskIma(point.y, point.x) > 0) - continue; - } - - const AKAZE::TEvolution& cur_slice = akaze.getSlices()[point.class_id]; - - if(_isOriented) - akaze.computeMainOrientation(point, cur_slice.Lx, cur_slice.Ly); - else - point.angle = 0.0f; - - regionsCasted->Features()[i] = - PointFeature(point.x, point.y, point.size, point.angle); - - ComputeMSURFDescriptor(cur_slice.Lx, cur_slice.Ly, point.octave, - regionsCasted->Features()[i], - regionsCasted->Descriptors()[i]); - } - } - break; - case AKAZE_LIOP: - { - // build alias to cached data - AKAZE_Liop_Regions* regionsCasted = dynamic_cast(regions.get()); - regionsCasted->Features().resize(keypoints.size()); - regionsCasted->Descriptors().resize(keypoints.size()); - - // init LIOP extractor - DescriptorExtractor_LIOP liop_extractor; + // build alias to cached data + AKAZE_Float_Regions* regionsCasted = dynamic_cast(regions.get()); + regionsCasted->Features().resize(keypoints.size()); + regionsCasted->Descriptors().resize(keypoints.size()); #pragma omp parallel for - for(int i = 0; i < static_cast(keypoints.size()); ++i) - { - AKAZEKeypoint point = keypoints[i]; + for (int i = 0; i < static_cast(keypoints.size()); ++i) + { + AKAZEKeypoint point = keypoints.at(i); - // feature masking - if(mask) - { - const image::Image& maskIma = *mask; - if(maskIma(point.y, point.x) > 0) - continue; - } + // feature masking + if (mask) + { + const image::Image& maskIma = *mask; + if (maskIma(point.y, point.x) > 0) + continue; + } - const AKAZE::TEvolution& cur_slice = akaze.getSlices()[point.class_id]; + const AKAZE::TEvolution& cur_slice = akaze.getSlices()[point.class_id]; - if(_isOriented) - akaze.computeMainOrientation(point, cur_slice.Lx, cur_slice.Ly); - else - point.angle = 0.0f; + if (_isOriented) + akaze.computeMainOrientation(point, cur_slice.Lx, cur_slice.Ly); + else + point.angle = 0.0f; - regionsCasted->Features()[i] = - PointFeature(point.x, point.y, point.size, point.angle); + regionsCasted->Features()[i] = PointFeature(point.x, point.y, point.size, point.angle); - // compute LIOP descriptor (do not need rotation computation, since - // LIOP descriptor is rotation invariant). - // rescale for LIOP patch extraction - const PointFeature fp = PointFeature(point.x, point.y, point.size/2.0, point.angle); + ComputeMSURFDescriptor(cur_slice.Lx, cur_slice.Ly, point.octave, regionsCasted->Features()[i], regionsCasted->Descriptors()[i]); + } + } + break; + case AKAZE_LIOP: + { + // build alias to cached data + AKAZE_Liop_Regions* regionsCasted = dynamic_cast(regions.get()); + regionsCasted->Features().resize(keypoints.size()); + regionsCasted->Descriptors().resize(keypoints.size()); - float desc[144]; - liop_extractor.extract(image, fp, desc); - for(int j=0; j < 144; ++j) - regionsCasted->Descriptors()[i][j] = static_cast(desc[j] * 255.f + .5f); - } - } - break; - case AKAZE_MLDB: - { - // Build alias to cached data - AKAZE_BinaryRegions* regionsCasted = dynamic_cast(regions.get()); - regionsCasted->Features().resize(keypoints.size()); - regionsCasted->Descriptors().resize(keypoints.size()); + // init LIOP extractor + DescriptorExtractor_LIOP liop_extractor; #pragma omp parallel for - for (int i = 0; i < static_cast(keypoints.size()); ++i) - { - AKAZEKeypoint point = keypoints[i]; - - // Feature masking - if(mask) - { - const image::Image & maskIma = *mask; - if (maskIma(point.y, point.x) > 0) - continue; + for (int i = 0; i < static_cast(keypoints.size()); ++i) + { + AKAZEKeypoint point = keypoints[i]; + + // feature masking + if (mask) + { + const image::Image& maskIma = *mask; + if (maskIma(point.y, point.x) > 0) + continue; + } + + const AKAZE::TEvolution& cur_slice = akaze.getSlices()[point.class_id]; + + if (_isOriented) + akaze.computeMainOrientation(point, cur_slice.Lx, cur_slice.Ly); + else + point.angle = 0.0f; + + regionsCasted->Features()[i] = PointFeature(point.x, point.y, point.size, point.angle); + + // compute LIOP descriptor (do not need rotation computation, since + // LIOP descriptor is rotation invariant). + // rescale for LIOP patch extraction + const PointFeature fp = PointFeature(point.x, point.y, point.size / 2.0, point.angle); + + float desc[144]; + liop_extractor.extract(image, fp, desc); + for (int j = 0; j < 144; ++j) + regionsCasted->Descriptors()[i][j] = static_cast(desc[j] * 255.f + .5f); + } } - - const AKAZE::TEvolution& cur_slice = akaze.getSlices()[point.class_id]; - - if(_isOriented) - akaze.computeMainOrientation(point, cur_slice.Lx, cur_slice.Ly); - else - point.angle = 0.0f; - - regionsCasted->Features()[i] = PointFeature(point.x, point.y, point.size, point.angle); - - // compute MLDB descriptor - Descriptor desc; - ComputeMLDBDescriptor(cur_slice.cur, cur_slice.Lx, cur_slice.Ly, point.octave, regionsCasted->Features()[i], desc); - // convert the bool vector to the binary unsigned char array - unsigned char* ptr = reinterpret_cast(®ionsCasted->Descriptors()[i]); - memset(ptr, 0, regionsCasted->DescriptorLength()*sizeof(unsigned char)); - - for(int j = 0; j < std::ceil(486./8.); ++j, ++ptr) // for each byte + break; + case AKAZE_MLDB: { - // set the corresponding 8bits to the good values - for(int iBit = 0; iBit < 8 && j*8+iBit < 486; ++iBit) - { - *ptr |= desc[j*8+iBit] << iBit; - } + // Build alias to cached data + AKAZE_BinaryRegions* regionsCasted = dynamic_cast(regions.get()); + regionsCasted->Features().resize(keypoints.size()); + regionsCasted->Descriptors().resize(keypoints.size()); + +#pragma omp parallel for + for (int i = 0; i < static_cast(keypoints.size()); ++i) + { + AKAZEKeypoint point = keypoints[i]; + + // Feature masking + if (mask) + { + const image::Image& maskIma = *mask; + if (maskIma(point.y, point.x) > 0) + continue; + } + + const AKAZE::TEvolution& cur_slice = akaze.getSlices()[point.class_id]; + + if (_isOriented) + akaze.computeMainOrientation(point, cur_slice.Lx, cur_slice.Ly); + else + point.angle = 0.0f; + + regionsCasted->Features()[i] = PointFeature(point.x, point.y, point.size, point.angle); + + // compute MLDB descriptor + Descriptor desc; + ComputeMLDBDescriptor(cur_slice.cur, cur_slice.Lx, cur_slice.Ly, point.octave, regionsCasted->Features()[i], desc); + // convert the bool vector to the binary unsigned char array + unsigned char* ptr = reinterpret_cast(®ionsCasted->Descriptors()[i]); + memset(ptr, 0, regionsCasted->DescriptorLength() * sizeof(unsigned char)); + + for (int j = 0; j < std::ceil(486. / 8.); ++j, ++ptr) // for each byte + { + // set the corresponding 8bits to the good values + for (int iBit = 0; iBit < 8 && j * 8 + iBit < 486; ++iBit) + { + *ptr |= desc[j * 8 + iBit] << iBit; + } + } + } } - } + break; } - break; - } - return true; + return true; } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.hpp b/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.hpp index b2b34fa857..f43bf36f5c 100644 --- a/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.hpp +++ b/src/aliceVision/feature/akaze/ImageDescriber_AKAZE.hpp @@ -21,172 +21,170 @@ namespace feature { enum EAKAZE_DESCRIPTOR { - AKAZE_MSURF, - AKAZE_LIOP, - AKAZE_MLDB + AKAZE_MSURF, + AKAZE_LIOP, + AKAZE_MLDB }; struct AKAZEParams { - AKAZEParams(AKAZEOptions akazeOptions = AKAZEOptions(), EAKAZE_DESCRIPTOR eAkazeDescriptor = AKAZE_MSURF) - : options(akazeOptions) - , akazeDescriptorType(eAkazeDescriptor) - {} - - // parameters - AKAZEOptions options; - EAKAZE_DESCRIPTOR akazeDescriptorType; + AKAZEParams(AKAZEOptions akazeOptions = AKAZEOptions(), EAKAZE_DESCRIPTOR eAkazeDescriptor = AKAZE_MSURF) + : options(akazeOptions), + akazeDescriptorType(eAkazeDescriptor) + {} + + // parameters + AKAZEOptions options; + EAKAZE_DESCRIPTOR akazeDescriptorType; }; class ImageDescriber_AKAZE : public ImageDescriber { -public: - explicit ImageDescriber_AKAZE(const AKAZEParams& params = AKAZEParams(), bool isOriented = true) - : ImageDescriber() - , _params(params) - , _isOriented(isOriented) - {} - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return true; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - switch(_params.akazeDescriptorType) + public: + explicit ImageDescriber_AKAZE(const AKAZEParams& params = AKAZEParams(), bool isOriented = true) + : ImageDescriber(), + _params(params), + _isOriented(isOriented) + {} + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return true; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override { - case AKAZE_MSURF: return EImageDescriberType::AKAZE; - case AKAZE_LIOP: return EImageDescriberType::AKAZE_LIOP; - case AKAZE_MLDB: return EImageDescriberType::AKAZE_MLDB; + switch (_params.akazeDescriptorType) + { + case AKAZE_MSURF: + return EImageDescriberType::AKAZE; + case AKAZE_LIOP: + return EImageDescriberType::AKAZE_LIOP; + case AKAZE_MLDB: + return EImageDescriberType::AKAZE_MLDB; + } + throw std::logic_error("Unknown AKAZE type."); } - throw std::logic_error("Unknown AKAZE type."); - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - std::size_t fullImgSize = width * height; - std::size_t memoryConsuption = 0; - double downscale = 1.0; - for(int octave = 0; octave < _params.options.nbOctaves; ++octave) - { - memoryConsuption += fullImgSize / (downscale * downscale); - downscale *= 2.0; - } - memoryConsuption *= _params.options.nbSlicePerOctave * sizeof(float); - return 4 * memoryConsuption + (3 * width * height * sizeof(float)) + 1.5 * std::pow(2,30); // add arbitrary 1.5 GB - } - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - void setUpRight(bool upRight) override - { - _isOriented = !upRight; - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - void setConfigurationPreset(ConfigurationPreset preset) override - { - switch(preset.descPreset) + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override { - case EImageDescriberPreset::LOW: - { - _params.options.maxTotalKeypoints = 5000; - break; - } - case EImageDescriberPreset::MEDIUM: - { - _params.options.maxTotalKeypoints = 10000; - break; - } - case EImageDescriberPreset::NORMAL: - { - _params.options.maxTotalKeypoints = 20000; - _params.options.threshold = AKAZEOptions().threshold; - break; - } - case EImageDescriberPreset::HIGH: - { - _params.options.maxTotalKeypoints = 50000; - _params.options.threshold = AKAZEOptions().threshold / 10.f; - break; - } - case EImageDescriberPreset::ULTRA: - { - _params.options.maxTotalKeypoints = 100000; - _params.options.threshold = AKAZEOptions().threshold / 100.f; - break; - } - default: - throw std::out_of_range("Invalid image describer preset enum"); + std::size_t fullImgSize = width * height; + std::size_t memoryConsuption = 0; + double downscale = 1.0; + for (int octave = 0; octave < _params.options.nbOctaves; ++octave) + { + memoryConsuption += fullImgSize / (downscale * downscale); + downscale *= 2.0; + } + memoryConsuption *= _params.options.nbSlicePerOctave * sizeof(float); + return 4 * memoryConsuption + (3 * width * height * sizeof(float)) + 1.5 * std::pow(2, 30); // add arbitrary 1.5 GB } - if(!preset.gridFiltering) + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + void setUpRight(bool upRight) override { _isOriented = !upRight; } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + void setConfigurationPreset(ConfigurationPreset preset) override { - // disable grid filtering - _params.options.maxTotalKeypoints = 0; + switch (preset.descPreset) + { + case EImageDescriberPreset::LOW: + { + _params.options.maxTotalKeypoints = 5000; + break; + } + case EImageDescriberPreset::MEDIUM: + { + _params.options.maxTotalKeypoints = 10000; + break; + } + case EImageDescriberPreset::NORMAL: + { + _params.options.maxTotalKeypoints = 20000; + _params.options.threshold = AKAZEOptions().threshold; + break; + } + case EImageDescriberPreset::HIGH: + { + _params.options.maxTotalKeypoints = 50000; + _params.options.threshold = AKAZEOptions().threshold / 10.f; + break; + } + case EImageDescriberPreset::ULTRA: + { + _params.options.maxTotalKeypoints = 100000; + _params.options.threshold = AKAZEOptions().threshold / 100.f; + break; + } + default: + throw std::out_of_range("Invalid image describer preset enum"); + } + if (!preset.gridFiltering) + { + // disable grid filtering + _params.options.maxTotalKeypoints = 0; + } } - } - - /** - * @brief Detect regions on the float image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image * mask = nullptr) override; - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr& regions) const override - { - switch(_params.akazeDescriptorType) + + /** + * @brief Detect regions on the float image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + */ + bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) override; + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { - case AKAZE_MSURF: regions.reset(new AKAZE_Float_Regions); break; - case AKAZE_LIOP: regions.reset(new AKAZE_Liop_Regions); break; - case AKAZE_MLDB: regions.reset(new AKAZE_BinaryRegions); break; + switch (_params.akazeDescriptorType) + { + case AKAZE_MSURF: + regions.reset(new AKAZE_Float_Regions); + break; + case AKAZE_LIOP: + regions.reset(new AKAZE_Liop_Regions); + break; + case AKAZE_MLDB: + regions.reset(new AKAZE_BinaryRegions); + break; + } } - } - ~ImageDescriber_AKAZE() override = default; + ~ImageDescriber_AKAZE() override = default; -private: - AKAZEParams _params; - bool _isOriented = true; + private: + AKAZEParams _params; + bool _isOriented = true; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/descriptorLIOP.cpp b/src/aliceVision/feature/akaze/descriptorLIOP.cpp index cd0857fb22..92afede4ec 100644 --- a/src/aliceVision/feature/akaze/descriptorLIOP.cpp +++ b/src/aliceVision/feature/akaze/descriptorLIOP.cpp @@ -13,96 +13,82 @@ namespace aliceVision { namespace feature { -DescriptorExtractor_LIOP::DescriptorExtractor_LIOP() -{ - GeneratePatternMap( m_LiopPatternMap, m_LiopPosWeight, _liopNum); -} +DescriptorExtractor_LIOP::DescriptorExtractor_LIOP() { GeneratePatternMap(m_LiopPatternMap, m_LiopPosWeight, _liopNum); } -struct Pixel{ - float x, y; // position - float f_gray; // color value - float weight; - int l_pattern; +struct Pixel +{ + float x, y; // position + float f_gray; // color value + float weight; + int l_pattern; }; -inline bool fGrayComp(const Pixel & p1, const Pixel & p2) -{ - return p1.f_gray < p2.f_gray; -} +inline bool fGrayComp(const Pixel& p1, const Pixel& p2) { return p1.f_gray < p2.f_gray; } -bool BilinearInterpolation_BorderCheck( - float& val, - float x, float y, - const image::Image & image, - const image::Image & flagImage) +bool BilinearInterpolation_BorderCheck(float& val, float x, float y, const image::Image& image, const image::Image& flagImage) { - val = 0.0f; - if(!(x >= 0 && y >= 0 && x<=image.Width()-1 && y<=image.Height()-1)) - return false; - - const int x1 = (int)x; - const int y1 = (int)y; - - int x2, y2; - if( x1 == image.Width()-1) - x2 = x1; - else - x2 = x1+1; - - if(y1 == image.Height()-1 ) - y2 = y1; - else - y2 = y1+1; - - const int step = image.Width(); - const float* data = image.data(); - - const int flag_step = flagImage.Width(); - const unsigned char* flag_data = flagImage.data(); - - if(flag_data[y1*flag_step+x1] == 0 || - flag_data[y1*flag_step+x2] == 0 || - flag_data[y2*flag_step+x1] == 0 || - flag_data[y2*flag_step+x2] == 0) - return false; - - val = - (x2 - x) * (y2 - y) * data[y1*step+x1] + - (x - x1) * (y2 - y) * data[y1*step+x2] + - (x2 - x) * (y - y1) * data[y2*step+x1] + - (x - x1) * (y - y1) * data[y2*step+x2]; - return true; + val = 0.0f; + if (!(x >= 0 && y >= 0 && x <= image.Width() - 1 && y <= image.Height() - 1)) + return false; + + const int x1 = (int)x; + const int y1 = (int)y; + + int x2, y2; + if (x1 == image.Width() - 1) + x2 = x1; + else + x2 = x1 + 1; + + if (y1 == image.Height() - 1) + y2 = y1; + else + y2 = y1 + 1; + + const int step = image.Width(); + const float* data = image.data(); + + const int flag_step = flagImage.Width(); + const unsigned char* flag_data = flagImage.data(); + + if (flag_data[y1 * flag_step + x1] == 0 || flag_data[y1 * flag_step + x2] == 0 || flag_data[y2 * flag_step + x1] == 0 || + flag_data[y2 * flag_step + x2] == 0) + return false; + + val = (x2 - x) * (y2 - y) * data[y1 * step + x1] + (x - x1) * (y2 - y) * data[y1 * step + x2] + (x2 - x) * (y - y1) * data[y2 * step + x1] + + (x - x1) * (y - y1) * data[y2 * step + x2]; + return true; } -//non descend +// non descend void SortGray(float* dst, int* idx, float* src, int len) { - int i, j; - - for (i=0; i illuThresh) - { - des[i] = illuThresh; - } - } - - // Normalize again. + // Normalize the descriptor, and threshold + // value of each element to 'illuThresh'. - norm = 0.f; + float norm = 0.f; + int i; - for (i=0; i illuThresh) + { + des[i] = illuThresh; + } + } + + // Normalize again. + + norm = 0.f; + + for (i = 0; i < dim; ++i) + { + norm += des[i] * des[i]; + } + + norm = sqrt(norm); + assert(norm != 0); + } + + for (i = 0; i < dim; ++i) + { + des[i] /= norm; + } } -void DescriptorExtractor_LIOP::CreateLIOP_GOrder( - const image::Image & outPatch, - const image::Image & flagPatch, - const int inRadius, - float desc[144]) const +void DescriptorExtractor_LIOP::CreateLIOP_GOrder(const image::Image& outPatch, + const image::Image& flagPatch, + const int inRadius, + float desc[144]) const { - const float* out_data = outPatch.data(); - const int out_step = outPatch.Width(); - const unsigned char* flag_data = flagPatch.data(); - const int flag_step = flagPatch.Width(); - - const float inRadius2 = float(inRadius*inRadius); - const int outRadius = outPatch.Width() / 2; - - const int lsRadius = 6; - const float theta = 2.0f*M_PI/(float)_liopNum; - - Pixel pixel[_maxPixelNum]; - int pixelCount = 0; - int idx[_maxSampleNum]; - float dst[_maxSampleNum]; - float src[_maxSampleNum]; - - for (int y=-inRadius; y<=inRadius; ++y) - { - for (int x=-inRadius; x<=inRadius; ++x) + const float* out_data = outPatch.data(); + const int out_step = outPatch.Width(); + const unsigned char* flag_data = flagPatch.data(); + const int flag_step = flagPatch.Width(); + + const float inRadius2 = float(inRadius * inRadius); + const int outRadius = outPatch.Width() / 2; + + const int lsRadius = 6; + const float theta = 2.0f * M_PI / (float)_liopNum; + + Pixel pixel[_maxPixelNum]; + int pixelCount = 0; + int idx[_maxSampleNum]; + float dst[_maxSampleNum]; + float src[_maxSampleNum]; + + for (int y = -inRadius; y <= inRadius; ++y) { - float dis2 = (float)(x*x + y*y); - if(dis2 > inRadius2) - continue; - - const float cur_gray = out_data [(y+outRadius)*out_step +x+outRadius]; - const unsigned char cur_flag = flag_data[(y+outRadius)*flag_step+x+outRadius]; - if (cur_flag == 0) - continue; - - const float nDirX = static_cast(x); - const float nDirY = static_cast(y); - float nOri = atan2(nDirY, nDirX); - if (fabs(nOri - M_PI) < FLT_EPSILON) //[-M_PI, M_PI) - { - nOri = static_cast(-M_PI); - } - - bool isInBound = true; - for (int k=0; k<_liopNum; k++) - { - const float deltaX = lsRadius * cos(nOri+k*theta); - const float deltaY = lsRadius * sin(nOri+k*theta); - - const float sampleX = x+deltaX+outRadius; - const float sampleY = y+deltaY+outRadius; - float gray; - - if (!BilinearInterpolation_BorderCheck(gray, sampleX, sampleY, outPatch, flagPatch)) + for (int x = -inRadius; x <= inRadius; ++x) { - isInBound = false; - break; + float dis2 = (float)(x * x + y * y); + if (dis2 > inRadius2) + continue; + + const float cur_gray = out_data[(y + outRadius) * out_step + x + outRadius]; + const unsigned char cur_flag = flag_data[(y + outRadius) * flag_step + x + outRadius]; + if (cur_flag == 0) + continue; + + const float nDirX = static_cast(x); + const float nDirY = static_cast(y); + float nOri = atan2(nDirY, nDirX); + if (fabs(nOri - M_PI) < FLT_EPSILON) //[-M_PI, M_PI) + { + nOri = static_cast(-M_PI); + } + + bool isInBound = true; + for (int k = 0; k < _liopNum; k++) + { + const float deltaX = lsRadius * cos(nOri + k * theta); + const float deltaY = lsRadius * sin(nOri + k * theta); + + const float sampleX = x + deltaX + outRadius; + const float sampleY = y + deltaY + outRadius; + float gray; + + if (!BilinearInterpolation_BorderCheck(gray, sampleX, sampleY, outPatch, flagPatch)) + { + isInBound = false; + break; + } + src[k] = gray; + } + + if (!isInBound) + { + continue; + } + + int key = 0; + SortGray(dst, idx, src, _liopNum); + for (int k = 0; k < _liopNum; ++k) + { + key += (idx[k] + 1) * m_LiopPosWeight[_liopNum - k - 1]; + } + std::map::const_iterator iter = m_LiopPatternMap.find(key); + if (iter != m_LiopPatternMap.end()) + { + Pixel pix; + pix.x = x; + pix.y = y; + pix.f_gray = cur_gray; + pix.weight = 1; + pix.l_pattern = iter->second; + pixel[pixelCount++] = pix; + } } - src[k] = gray; - } - - if (!isInBound) - { - continue; - } - - int key = 0; - SortGray(dst, idx, src, _liopNum); - for (int k=0; k<_liopNum; ++k) - { - key += (idx[k]+1)* m_LiopPosWeight[_liopNum-k-1]; - } - std::map::const_iterator iter = m_LiopPatternMap.find(key); - if (iter != m_LiopPatternMap.end()) - { - Pixel pix; - pix.x = x; - pix.y = y; - pix.f_gray = cur_gray; - pix.weight = 1; - pix.l_pattern = iter->second; - pixel[pixelCount++] = pix; - } } - } - std::sort(pixel, pixel+pixelCount, fGrayComp); //sort by gray + std::sort(pixel, pixel + pixelCount, fGrayComp); // sort by gray - const int l_patternWidth = _liopNum == 3 ? 6 : 24; - const int dim = l_patternWidth*_regionNum; + const int l_patternWidth = _liopNum == 3 ? 6 : 24; + const int dim = l_patternWidth * _regionNum; - if (pixelCount >= _regionNum) - { - int curId = 0; - int lastId = 0; - for (int i=0; i<_regionNum; ++i) + if (pixelCount >= _regionNum) { - const int fenceId = pixelCount*(i+1)/_regionNum-1; - const float fenceGray = pixel[fenceId].f_gray; - const int regionId = i; - curId = lastId; - - while (true) - { - if (fabs(pixel[curId].f_gray-fenceGray) < FLT_EPSILON) + int curId = 0; + int lastId = 0; + for (int i = 0; i < _regionNum; ++i) { - lastId = curId; - break; + const int fenceId = pixelCount * (i + 1) / _regionNum - 1; + const float fenceGray = pixel[fenceId].f_gray; + const int regionId = i; + curId = lastId; + + while (true) + { + if (fabs(pixel[curId].f_gray - fenceGray) < FLT_EPSILON) + { + lastId = curId; + break; + } + + const int id = regionId * l_patternWidth + pixel[curId].l_pattern; + desc[id] += pixel[curId].weight; + ++curId; + } + + while (true) + { + if (curId == pixelCount || pixel[curId].f_gray > fenceGray) + break; + + const int id = regionId * l_patternWidth + pixel[curId].l_pattern; + desc[id] += pixel[curId].weight; + ++curId; + } } - const int id = regionId*l_patternWidth+pixel[curId].l_pattern; - desc[id] += pixel[curId].weight; - ++curId; - } - - while (true) - { - if(curId==pixelCount || pixel[curId].f_gray>fenceGray) - break; - - const int id = regionId*l_patternWidth+pixel[curId].l_pattern; - desc[id] += pixel[curId].weight; - ++curId; - } + ThreshNorm(desc, dim, 1.f); } - - ThreshNorm(desc,dim,1.f); - } } -void DescriptorExtractor_LIOP::extract( - const image::Image& I, - const PointFeature& feat, - float desc[144]) +void DescriptorExtractor_LIOP::extract(const image::Image& I, const PointFeature& feat, float desc[144]) { - memset(desc, 0, sizeof(float)*144); - - //a. extract the local patch - const int scalePatchWidth = 31; + memset(desc, 0, sizeof(float) * 144); - const int outPatchWidth = scalePatchWidth+6; - const int outRadius = outPatchWidth/2; - const int outRadius2 = outRadius*outRadius; - const float scale = feat.scale(); + // a. extract the local patch + const int scalePatchWidth = 31; - image::Image outPatch(outPatchWidth,outPatchWidth, true, 0); - image::Image flagPatch(outPatchWidth, outPatchWidth, true, 0); + const int outPatchWidth = scalePatchWidth + 6; + const int outRadius = outPatchWidth / 2; + const int outRadius2 = outRadius * outRadius; + const float scale = feat.scale(); - const image::Sampler2d sampler; + image::Image outPatch(outPatchWidth, outPatchWidth, true, 0); + image::Image flagPatch(outPatchWidth, outPatchWidth, true, 0); - // pointer alias - unsigned char * flagPatch_data = flagPatch.data(); - float * outPatch_data = outPatch.data(); + const image::Sampler2d sampler; - for(int y=-outRadius; y<=outRadius; ++y) - { - const float ys = y*scale+feat.y(); - if(ys<0 || ys>I.Height()-1) - continue; + // pointer alias + unsigned char* flagPatch_data = flagPatch.data(); + float* outPatch_data = outPatch.data(); - for(int x=-outRadius; x<=outRadius; ++x) + for (int y = -outRadius; y <= outRadius; ++y) { - const float dis2 = (float)(x*x + y*y); - if(dis2 > outRadius2) - continue; + const float ys = y * scale + feat.y(); + if (ys < 0 || ys > I.Height() - 1) + continue; + + for (int x = -outRadius; x <= outRadius; ++x) + { + const float dis2 = (float)(x * x + y * y); + if (dis2 > outRadius2) + continue; - const float xs = x*scale+feat.x(); - if(xs<0 || xs>I.Width()-1) - continue; + const float xs = x * scale + feat.x(); + if (xs < 0 || xs > I.Width() - 1) + continue; - outPatch_data[(y+outRadius)*outPatchWidth+x+outRadius] = sampler(I, ys, xs); - flagPatch_data[(y+outRadius)*outPatchWidth+x+outRadius] = 1; + outPatch_data[(y + outRadius) * outPatchWidth + x + outRadius] = sampler(I, ys, xs); + flagPatch_data[(y + outRadius) * outPatchWidth + x + outRadius] = 1; + } } - } - image::ImageGaussianFilter(image::Image(outPatch), 1.2, outPatch); + image::ImageGaussianFilter(image::Image(outPatch), 1.2, outPatch); - //b. creation of the LIOP ordering - const int inRadius = scalePatchWidth/2; - CreateLIOP_GOrder(outPatch, flagPatch, inRadius, desc); + // b. creation of the LIOP ordering + const int inRadius = scalePatchWidth / 2; + CreateLIOP_GOrder(outPatch, flagPatch, inRadius, desc); } template -bool NextPermutation(std::vector & p, int n) +bool NextPermutation(std::vector& p, int n) { - int last = n - 1; - int i, j, k; + int last = n - 1; + int i, j, k; - i = last; - while (i > 0 && p[i] < p[i - 1]) - i--; + i = last; + while (i > 0 && p[i] < p[i - 1]) + i--; - if (i == 0) - return false; + if (i == 0) + return false; - k = i; - for (j = last; j >= i; j--) - if (p[j] > p[i - 1] && p[j] < p[k]) - k = j; - std::swap(p[k], p[i - 1]); - for (j = last, k = i; j > k; j--, k++) - std::swap(p[j], p[k]); + k = i; + for (j = last; j >= i; j--) + if (p[j] > p[i - 1] && p[j] < p[k]) + k = j; + std::swap(p[k], p[i - 1]); + for (j = last, k = i; j > k; j--, k++) + std::swap(p[j], p[k]); - return true; + return true; } -void DescriptorExtractor_LIOP::GeneratePatternMap( - std::map & pattern_map, - std::vector & pos_weight, - unsigned char n) +void DescriptorExtractor_LIOP::GeneratePatternMap(std::map& pattern_map, std::vector& pos_weight, unsigned char n) { - pattern_map.clear(); - pos_weight.resize(n); - - //do the job - std::vector p(n); - pos_weight[0] = 1; - for (unsigned char i=1; i p(n); + pos_weight[0] = 1; + for (unsigned char i = 1; i < n; i++) + { + pos_weight[i] = pos_weight[i - 1] * 10; + } + + unsigned char count = 0; + int key = 0; + for (int i = 0; i < n; ++i) { - key += p[i]*pos_weight[n-i-1]; + p[i] = i + 1; + key += p[i] * pos_weight[n - i - 1]; } pattern_map.insert(std::make_pair(key, count)); ++count; - } + + while (NextPermutation(p, n)) + { + key = 0; + for (unsigned char i = 0; i < n; ++i) + { + key += p[i] * pos_weight[n - i - 1]; + } + pattern_map.insert(std::make_pair(key, count)); + ++count; + } } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/descriptorLIOP.hpp b/src/aliceVision/feature/akaze/descriptorLIOP.hpp index 99e7ffd7fd..f655db502a 100644 --- a/src/aliceVision/feature/akaze/descriptorLIOP.hpp +++ b/src/aliceVision/feature/akaze/descriptorLIOP.hpp @@ -15,7 +15,7 @@ #include namespace aliceVision { -namespace feature{ +namespace feature { /** * @brief Local Intensity Order Pattern @@ -30,35 +30,28 @@ namespace feature{ class DescriptorExtractor_LIOP { -private: - std::map m_LiopPatternMap; - std::vector m_LiopPosWeight; + private: + std::map m_LiopPatternMap; + std::vector m_LiopPosWeight; - static const int _maxRegionNum = 10; - static const int _maxPixelNum = 1681; - static const int _maxSampleNum = 10; - static const int _liopNum = 4; - static const int _regionNum = 6; -public: + static const int _maxRegionNum = 10; + static const int _maxPixelNum = 1681; + static const int _maxSampleNum = 10; + static const int _liopNum = 4; + static const int _regionNum = 6; - DescriptorExtractor_LIOP(); + public: + DescriptorExtractor_LIOP(); - void extract( - const image::Image & I, - const PointFeature & feat, - float desc[144]); + void extract(const image::Image& I, const PointFeature& feat, float desc[144]); - void CreateLIOP_GOrder( - const image::Image & outPatch, - const image::Image & flagPatch, - const int inRadius, - float desc[144]) const; + void CreateLIOP_GOrder(const image::Image& outPatch, + const image::Image& flagPatch, + const int inRadius, + float desc[144]) const; - void GeneratePatternMap( - std::map & pattern_map, - std::vector & pos_weight, - unsigned char n); + void GeneratePatternMap(std::map& pattern_map, std::vector& pos_weight, unsigned char n); }; -} // namespace LIO} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/descriptorMLDB.hpp b/src/aliceVision/feature/akaze/descriptorMLDB.hpp index 86161d7417..f3cac849de 100644 --- a/src/aliceVision/feature/akaze/descriptorMLDB.hpp +++ b/src/aliceVision/feature/akaze/descriptorMLDB.hpp @@ -15,208 +15,203 @@ namespace aliceVision { namespace feature { - /** - ** @brief Compute mean values (Li,Lx,Ly) in each subdivisions - ** @param samples_Li input values on Li - ** @param samples_Lx input values on Lx - ** @param samples_Ly input values on Ly - ** @param nb_subdiv number of subdivision (on each 2d axis) - ** @param subdiv_size size of a subdivision (on each 2d axis) - ** @param pattern_size source size (on each 2d axis) - ** @param c cosinus of main orientation (for computing new derivatives values) - ** @param s sinus of main orientation (for computing new derivatives values) - ** @param mean_Li mean of Li in each subdivision - ** @param mean_Lx mean of Lx in each subdivision - ** @param mean_Ly mean of Ly in each subdivision - **/ - template< typename Real> - inline void ComputeMeanValuesInSubdivisions( - const Eigen::Matrix & samples_Li , - const Eigen::Matrix & samples_Lx , - const Eigen::Matrix & samples_Ly , - const int nb_subdiv , - const int subdiv_size , - const int pattern_size , - const Real c , // cos( theta ) - const Real s , // sin( theta ) - Eigen::Matrix & mean_Li , - Eigen::Matrix & mean_Lx , - Eigen::Matrix & mean_Ly ) - { - mean_Li.resize( nb_subdiv , nb_subdiv ) ; - mean_Lx.resize( nb_subdiv , nb_subdiv ) ; - mean_Ly.resize( nb_subdiv , nb_subdiv ) ; - - const int max_w = samples_Li.cols() ; - const int max_h = samples_Li.rows() ; - - for( int i = 0 ; i < nb_subdiv ; ++i ) +/** +** @brief Compute mean values (Li,Lx,Ly) in each subdivisions +** @param samples_Li input values on Li +** @param samples_Lx input values on Lx +** @param samples_Ly input values on Ly +** @param nb_subdiv number of subdivision (on each 2d axis) +** @param subdiv_size size of a subdivision (on each 2d axis) +** @param pattern_size source size (on each 2d axis) +** @param c cosinus of main orientation (for computing new derivatives values) +** @param s sinus of main orientation (for computing new derivatives values) +** @param mean_Li mean of Li in each subdivision +** @param mean_Lx mean of Lx in each subdivision +** @param mean_Ly mean of Ly in each subdivision +**/ +template +inline void ComputeMeanValuesInSubdivisions(const Eigen::Matrix& samples_Li, + const Eigen::Matrix& samples_Lx, + const Eigen::Matrix& samples_Ly, + const int nb_subdiv, + const int subdiv_size, + const int pattern_size, + const Real c, // cos( theta ) + const Real s, // sin( theta ) + Eigen::Matrix& mean_Li, + Eigen::Matrix& mean_Lx, + Eigen::Matrix& mean_Ly) +{ + mean_Li.resize(nb_subdiv, nb_subdiv); + mean_Lx.resize(nb_subdiv, nb_subdiv); + mean_Ly.resize(nb_subdiv, nb_subdiv); + + const int max_w = samples_Li.cols(); + const int max_h = samples_Li.rows(); + + for (int i = 0; i < nb_subdiv; ++i) { - for( int j = 0 ; j < nb_subdiv ; ++j ) - { - // Compute subdivision extend - const int min_x = j * subdiv_size ; - const int min_y = i * subdiv_size ; - const int max_x = std::min( ( j + 1 ) * subdiv_size , max_w ) ; - const int max_y = std::min( ( i + 1 ) * subdiv_size , max_h ) ; - - // Sum every elements of this subdivision - mean_Li( i , j ) = mean_Lx( i , j ) = mean_Ly( i , j ) = 0 ; - - size_t nb_elt = 0 ; - for( int ii = min_y ; ii < max_y ; ++ii ) + for (int j = 0; j < nb_subdiv; ++j) { - for( int jj = min_x ; jj < max_x ; ++jj ) - { - mean_Li( i , j ) += samples_Li( ii , jj ) ; - - const Real dx = samples_Lx( ii , jj ) ; - const Real dy = samples_Ly( ii , jj ) ; - - // Rotate derivatives - // a is original angle, b is keypoint angle - // Cos( a - b ) = cosA cosB + sinA sinB - // = dx * c + dy * s - // Sin( a - b ) = sinA cosB - cosA sinB - // = dy * c - dx * s - mean_Ly( i , j ) += dx * c + dy * s ; - mean_Lx( i , j ) += dy * c - dx * s ; - - ++nb_elt ; - } + // Compute subdivision extend + const int min_x = j * subdiv_size; + const int min_y = i * subdiv_size; + const int max_x = std::min((j + 1) * subdiv_size, max_w); + const int max_y = std::min((i + 1) * subdiv_size, max_h); + + // Sum every elements of this subdivision + mean_Li(i, j) = mean_Lx(i, j) = mean_Ly(i, j) = 0; + + size_t nb_elt = 0; + for (int ii = min_y; ii < max_y; ++ii) + { + for (int jj = min_x; jj < max_x; ++jj) + { + mean_Li(i, j) += samples_Li(ii, jj); + + const Real dx = samples_Lx(ii, jj); + const Real dy = samples_Ly(ii, jj); + + // Rotate derivatives + // a is original angle, b is keypoint angle + // Cos( a - b ) = cosA cosB + sinA sinB + // = dx * c + dy * s + // Sin( a - b ) = sinA cosB - cosA sinB + // = dy * c - dx * s + mean_Ly(i, j) += dx * c + dy * s; + mean_Lx(i, j) += dy * c - dx * s; + + ++nb_elt; + } + } + + mean_Li(i, j) /= static_cast(nb_elt); + mean_Lx(i, j) /= static_cast(nb_elt); + mean_Ly(i, j) /= static_cast(nb_elt); } - - mean_Li( i , j ) /= static_cast( nb_elt ) ; - mean_Lx( i , j ) /= static_cast( nb_elt ) ; - mean_Ly( i , j ) /= static_cast( nb_elt ) ; - } } - } - - /** - ** @brief Compute binary description - ** @param mean_Li input mean values of Li values (mean per subdivision) - ** @param mean_Lx input mean values of Lx values (mean per subdivision) - ** @param mean_Ly input mean values of Ly values (mean per subdivision) - ** @param nb_subdiv Number of subdivision (in 2d so it's a nb_subdivxnb_subdiv pattern) - ** @param outIndex input/ouput index to store description - ** @param desc ouput vector (idealy a std::bitset) containing binary description of theses regions - **/ - template< typename DescriptorType , typename Real> - inline void ComputeBinaryValues( - const Eigen::Matrix & mean_Li , - const Eigen::Matrix & mean_Lx , - const Eigen::Matrix & mean_Ly , - const int nb_subdiv , - size_t & outIndex , - DescriptorType & desc ) - { +} + +/** +** @brief Compute binary description +** @param mean_Li input mean values of Li values (mean per subdivision) +** @param mean_Lx input mean values of Lx values (mean per subdivision) +** @param mean_Ly input mean values of Ly values (mean per subdivision) +** @param nb_subdiv Number of subdivision (in 2d so it's a nb_subdivxnb_subdiv pattern) +** @param outIndex input/ouput index to store description +** @param desc ouput vector (idealy a std::bitset) containing binary description of theses regions +**/ +template +inline void ComputeBinaryValues(const Eigen::Matrix& mean_Li, + const Eigen::Matrix& mean_Lx, + const Eigen::Matrix& mean_Ly, + const int nb_subdiv, + size_t& outIndex, + DescriptorType& desc) +{ // Binary comparisons (ie (0,0) with (0,1), (O,0) with (0,2), ... ) - for( int i = 0 ; i < ( nb_subdiv * nb_subdiv ) ; ++i ) + for (int i = 0; i < (nb_subdiv * nb_subdiv); ++i) { - for( int j = i + 1 ; j < ( nb_subdiv * nb_subdiv ) ; ++j ) - { - const int src_i = i / nb_subdiv ; - const int src_j = i % nb_subdiv ; - - const int dst_i = j / nb_subdiv ; - const int dst_j = j % nb_subdiv ; - - // Compare (src_i,src_j) with (dst_i,dst_j) on the three values - desc[ outIndex++ ] = mean_Li( src_i , src_j ) > mean_Li( dst_i , dst_j ) ; - desc[ outIndex++ ] = mean_Lx( src_i , src_j ) > mean_Lx( dst_i , dst_j ) ; - desc[ outIndex++ ] = mean_Ly( src_i , src_j ) > mean_Ly( dst_i , dst_j ) ; - } + for (int j = i + 1; j < (nb_subdiv * nb_subdiv); ++j) + { + const int src_i = i / nb_subdiv; + const int src_j = i % nb_subdiv; + + const int dst_i = j / nb_subdiv; + const int dst_j = j % nb_subdiv; + + // Compare (src_i,src_j) with (dst_i,dst_j) on the three values + desc[outIndex++] = mean_Li(src_i, src_j) > mean_Li(dst_i, dst_j); + desc[outIndex++] = mean_Lx(src_i, src_j) > mean_Lx(dst_i, dst_j); + desc[outIndex++] = mean_Ly(src_i, src_j) > mean_Ly(dst_i, dst_j); + } } - } - - /** - ** @brief Compute final keypoint (ie interest point + description) for a given interest point - ** @param Li Input Octave slice - ** @param Lx Input X-derivative - ** @param Ly Input Y-derivative - ** @param id_octave Id of current octave - ** @param ipt Input interest point - ** @param desc output descriptor (binary descriptor) - **/ - template< typename Real> - inline void ComputeMLDBDescriptor( - const image::Image & Li, - const image::Image &Lx, - const image::Image &Ly, - const int id_octave , - const PointFeature & ipt , - Descriptor & desc ) - { +} + +/** + ** @brief Compute final keypoint (ie interest point + description) for a given interest point + ** @param Li Input Octave slice + ** @param Lx Input X-derivative + ** @param Ly Input Y-derivative + ** @param id_octave Id of current octave + ** @param ipt Input interest point + ** @param desc output descriptor (binary descriptor) + **/ +template +inline void ComputeMLDBDescriptor(const image::Image& Li, + const image::Image& Lx, + const image::Image& Ly, + const int id_octave, + const PointFeature& ipt, + Descriptor& desc) +{ // // Note : in KAZE description we compute descriptor of previous slice and never the current one // See if it's necessary to change this value (pass it into a param ?) - const int pattern_size = 10 ; + const int pattern_size = 10; // Sampling size according to the scale value - const Real inv_octave_scale = static_cast( 1 ) / static_cast( 1 << id_octave ) ; - const Real sigma_scale = MathTrait::round( ipt.scale() * inv_octave_scale ) ; + const Real inv_octave_scale = static_cast(1) / static_cast(1 << id_octave); + const Real sigma_scale = MathTrait::round(ipt.scale() * inv_octave_scale); // Get every samples inside 2pattern x 2pattern square region // Memory efficient (get samples then work in aligned) - Eigen::Matrix - samples_Li( 2 * pattern_size + 1 , 2 * pattern_size + 1 ), - samples_Lx( 2 * pattern_size + 1 , 2 * pattern_size + 1 ), - samples_Ly( 2 * pattern_size + 1 , 2 * pattern_size + 1 ); + Eigen::Matrix samples_Li(2 * pattern_size + 1, 2 * pattern_size + 1), + samples_Lx(2 * pattern_size + 1, 2 * pattern_size + 1), samples_Ly(2 * pattern_size + 1, 2 * pattern_size + 1); // Compute cos and sin values for this point orientation - const Real c = MathTrait::cos( ipt.orientation() ) ; - const Real s = MathTrait::sin( ipt.orientation() ) ; + const Real c = MathTrait::cos(ipt.orientation()); + const Real s = MathTrait::sin(ipt.orientation()); - const Real cur_x = ipt.x() * inv_octave_scale ; - const Real cur_y = ipt.y() * inv_octave_scale ; + const Real cur_x = ipt.x() * inv_octave_scale; + const Real cur_y = ipt.y() * inv_octave_scale; // Retrieve samples in oriented pattern - for( int i = - pattern_size ; i <= pattern_size ; ++i ) + for (int i = -pattern_size; i <= pattern_size; ++i) { - for( int j = - pattern_size ; j <= pattern_size ; ++j ) - { - // sample_y = yf + (l*scale*co + k*scale*si); - // sample_x = xf + (-l*scale*si + k*scale*co); - - // Need to do a function for that rotate of angle -theta ( minus theta because image frame is Y down) - const Real delta_y = static_cast( j ) * c + static_cast( i ) * s ; - const Real delta_x = -static_cast( j ) * s + static_cast( i ) * c ; - - // Compute new real position - const Real dx = cur_x + sigma_scale * delta_x ; - const Real dy = cur_y + sigma_scale * delta_y ; - - // Compute new discrete position - const int y = MathTrait::round( dy ) ; - const int x = MathTrait::round( dx ) ; - - samples_Li( i + pattern_size , j + pattern_size ) = Li( y , x ) ; - samples_Lx( i + pattern_size , j + pattern_size ) = Lx( y , x ) ; - samples_Ly( i + pattern_size , j + pattern_size ) = Ly( y , x ) ; - } + for (int j = -pattern_size; j <= pattern_size; ++j) + { + // sample_y = yf + (l*scale*co + k*scale*si); + // sample_x = xf + (-l*scale*si + k*scale*co); + + // Need to do a function for that rotate of angle -theta ( minus theta because image frame is Y down) + const Real delta_y = static_cast(j) * c + static_cast(i) * s; + const Real delta_x = -static_cast(j) * s + static_cast(i) * c; + + // Compute new real position + const Real dx = cur_x + sigma_scale * delta_x; + const Real dy = cur_y + sigma_scale * delta_y; + + // Compute new discrete position + const int y = MathTrait::round(dy); + const int x = MathTrait::round(dx); + + samples_Li(i + pattern_size, j + pattern_size) = Li(y, x); + samples_Lx(i + pattern_size, j + pattern_size) = Lx(y, x); + samples_Ly(i + pattern_size, j + pattern_size) = Ly(y, x); + } } - size_t outIndex = 0 ; // Index to store next binary value + size_t outIndex = 0; // Index to store next binary value // Grid 1 : 2x2 subdivision - int subdiv_size = pattern_size ; - Eigen::Matrix sumLi , sumLx , sumLy ; - ComputeMeanValuesInSubdivisions( samples_Li , samples_Lx , samples_Ly , 2 , subdiv_size , pattern_size , c , s , sumLi , sumLx , sumLy ) ; - ComputeBinaryValues( sumLi , sumLx , sumLy , 2 , outIndex , desc ) ; + int subdiv_size = pattern_size; + Eigen::Matrix sumLi, sumLx, sumLy; + ComputeMeanValuesInSubdivisions(samples_Li, samples_Lx, samples_Ly, 2, subdiv_size, pattern_size, c, s, sumLi, sumLx, sumLy); + ComputeBinaryValues(sumLi, sumLx, sumLy, 2, outIndex, desc); // Grid 2 : 3x3 subdivision - subdiv_size = static_cast( MathTrait::ceil( static_cast( 2 * pattern_size ) / static_cast( 3 ) ) ) ; - ComputeMeanValuesInSubdivisions( samples_Li , samples_Lx , samples_Ly , 3 , subdiv_size , pattern_size , c , s , sumLi , sumLx , sumLy ) ; - ComputeBinaryValues( sumLi , sumLx , sumLy , 3 , outIndex , desc ) ; + subdiv_size = static_cast(MathTrait::ceil(static_cast(2 * pattern_size) / static_cast(3))); + ComputeMeanValuesInSubdivisions(samples_Li, samples_Lx, samples_Ly, 3, subdiv_size, pattern_size, c, s, sumLi, sumLx, sumLy); + ComputeBinaryValues(sumLi, sumLx, sumLy, 3, outIndex, desc); // Grid 3 : 4x4 subdivision - subdiv_size = pattern_size / 2 ; - ComputeMeanValuesInSubdivisions( samples_Li , samples_Lx , samples_Ly , 4 , subdiv_size , pattern_size , c , s , sumLi , sumLx , sumLy ) ; - ComputeBinaryValues( sumLi , sumLx , sumLy , 4 , outIndex , desc ) ; + subdiv_size = pattern_size / 2; + ComputeMeanValuesInSubdivisions(samples_Li, samples_Lx, samples_Ly, 4, subdiv_size, pattern_size, c, s, sumLi, sumLx, sumLy); + ComputeBinaryValues(sumLi, sumLx, sumLy, 4, outIndex, desc); - assert( outIndex == 486 ) ; // Just to be sure (and we are sure ! completly sure !) - } + assert(outIndex == 486); // Just to be sure (and we are sure ! completly sure !) +} -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/akaze/descriptorMSURF.hpp b/src/aliceVision/feature/akaze/descriptorMSURF.hpp index e2100d37e1..1193c1e9c9 100644 --- a/src/aliceVision/feature/akaze/descriptorMSURF.hpp +++ b/src/aliceVision/feature/akaze/descriptorMSURF.hpp @@ -14,58 +14,52 @@ namespace aliceVision { namespace feature { - /** - * @brief This function computes the value of a 2D Gaussian function - * @param x X Position - * @param y Y Position - * @param sigma Standard Deviation - */ - template - inline Real gaussian( const Real x, const Real y, const Real sigma ) - { - return MathTrait::exp( - ( ( x * x ) + ( y * y ) ) / ( static_cast( 2 ) * sigma * sigma ) ) ; - } - - /** - * @brief This method computes the descriptor of the provided keypoint given the main orientation of the keypoint - * @param Lx Input X-derivative - * @param Ly Input Y-derivative - * @param id_octave Id of given octave - * @param ipt Input interest point - * @param desc Ouput descriptor - * @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired - * from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching, - * ECCV 2008 - */ - template - inline void ComputeMSURFDescriptor( - const ImageT & Lx , - const ImageT & Ly , - const int id_octave , - const PointFeature & ipt , - Descriptor< Real , 64 > & desc ) - { - +/** + * @brief This function computes the value of a 2D Gaussian function + * @param x X Position + * @param y Y Position + * @param sigma Standard Deviation + */ +template +inline Real gaussian(const Real x, const Real y, const Real sigma) +{ + return MathTrait::exp(-((x * x) + (y * y)) / (static_cast(2) * sigma * sigma)); +} + +/** + * @brief This method computes the descriptor of the provided keypoint given the main orientation of the keypoint + * @param Lx Input X-derivative + * @param Ly Input Y-derivative + * @param id_octave Id of given octave + * @param ipt Input interest point + * @param desc Ouput descriptor + * @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired + * from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching, + * ECCV 2008 + */ +template +inline void ComputeMSURFDescriptor(const ImageT& Lx, const ImageT& Ly, const int id_octave, const PointFeature& ipt, Descriptor& desc) +{ Real dx = 0, dy = 0, mdx = 0, mdy = 0, gauss_s1 = 0, gauss_s2 = 0; Real rx = 0, ry = 0, rrx = 0, rry = 0, ys = 0, xs = 0; Real sample_x = 0, sample_y = 0; int kx = 0, ky = 0, i = 0, j = 0, dcount = 0; // Subregion centers for the 4x4 gaussian weighting - Real cx = - static_cast( 0.5 ) , cy = static_cast( 0.5 ) ; + Real cx = -static_cast(0.5), cy = static_cast(0.5); // Set the descriptor size and the sample and pattern sizes const int sample_step = 5; const int pattern_size = 12; // Get the information from the keypoint - const Real ratio = static_cast( 1 << id_octave ); - const int scale = MathTrait::round( ipt.scale() / ratio ); - const Real angle = ipt.orientation() ; + const Real ratio = static_cast(1 << id_octave); + const int scale = MathTrait::round(ipt.scale() / ratio); + const Real angle = ipt.orientation(); const Real yf = ipt.y() / ratio; const Real xf = ipt.x() / ratio; - const Real co = MathTrait::cos( angle ); - const Real si = MathTrait::sin( angle ); + const Real co = MathTrait::cos(angle); + const Real si = MathTrait::sin(angle); i = -8; @@ -73,88 +67,82 @@ namespace feature { // Calculate descriptor for this interest point // Area of size 24 s x 24 s - while ( i < pattern_size ) + while (i < pattern_size) { - j = -8; - i = i - 4; - - cx += 1.0; - cy = -0.5; - - while ( j < pattern_size ) - { - dx = dy = mdx = mdy = 0.0; - cy += 1.0; - j = j - 4; + j = -8; + i = i - 4; - ky = i + sample_step; - kx = j + sample_step; + cx += 1.0; + cy = -0.5; - xs = xf + ( -kx * scale * si + ky * scale * co ); - ys = yf + ( kx * scale * co + ky * scale * si ); - - for ( int k = i; k < i + 9; ++k ) + while (j < pattern_size) { - for ( int l = j; l < j + 9; ++l ) - { - // Get coords of sample point on the rotated axis - sample_y = yf + ( l * scale * co + k * scale * si ); - sample_x = xf + ( -l * scale * si + k * scale * co ); - - // Get the gaussian weighted x and y responses - gauss_s1 = gaussian( xs - sample_x, ys - sample_y, static_cast( 2.5 ) * static_cast( scale ) ); - - rx = sampler( Lx, sample_y, sample_x ); - ry = sampler( Ly, sample_y, sample_x ); - - // Get the x and y derivatives on the rotated axis - rry = gauss_s1 * ( rx * co + ry * si ); - rrx = gauss_s1 * ( -rx * si + ry * co ); - - // Sum the derivatives to the cumulative descriptor - dx += rrx; - dy += rry; - mdx += MathTrait::abs( rrx ); - mdy += MathTrait::abs( rry ); - } + dx = dy = mdx = mdy = 0.0; + cy += 1.0; + j = j - 4; + + ky = i + sample_step; + kx = j + sample_step; + + xs = xf + (-kx * scale * si + ky * scale * co); + ys = yf + (kx * scale * co + ky * scale * si); + + for (int k = i; k < i + 9; ++k) + { + for (int l = j; l < j + 9; ++l) + { + // Get coords of sample point on the rotated axis + sample_y = yf + (l * scale * co + k * scale * si); + sample_x = xf + (-l * scale * si + k * scale * co); + + // Get the gaussian weighted x and y responses + gauss_s1 = gaussian(xs - sample_x, ys - sample_y, static_cast(2.5) * static_cast(scale)); + + rx = sampler(Lx, sample_y, sample_x); + ry = sampler(Ly, sample_y, sample_x); + + // Get the x and y derivatives on the rotated axis + rry = gauss_s1 * (rx * co + ry * si); + rrx = gauss_s1 * (-rx * si + ry * co); + + // Sum the derivatives to the cumulative descriptor + dx += rrx; + dy += rry; + mdx += MathTrait::abs(rrx); + mdy += MathTrait::abs(rry); + } + } + + // Add the values to the descriptor vector + gauss_s2 = gaussian(cx - static_cast(2.0), cy - static_cast(2.0), static_cast(1.5)); + desc[dcount++] = dx * gauss_s2; + desc[dcount++] = dy * gauss_s2; + desc[dcount++] = mdx * gauss_s2; + desc[dcount++] = mdy * gauss_s2; + + j += 9; } - // Add the values to the descriptor vector - gauss_s2 = gaussian( cx - static_cast( 2.0 ) , cy - static_cast( 2.0 ) , static_cast( 1.5 ) ) ; - desc[dcount++] = dx * gauss_s2; - desc[dcount++] = dy * gauss_s2; - desc[dcount++] = mdx * gauss_s2; - desc[dcount++] = mdy * gauss_s2; - - j += 9; - } - - i += 9; + i += 9; } // convert to unit vector (L2 norm) typedef Eigen::Matrix VecReal; - Eigen::Map< VecReal > dataMap( &desc[0], 64); + Eigen::Map dataMap(&desc[0], 64); dataMap.normalize(); - //ALICEVISION_LOG_DEBUG(dataMap.transpose())); - } - - template - inline void ComputeMSURFDescriptor( - const ImageT & Lx , - const ImageT & Ly , - const int id_octave , - const PointFeature & ipt , - Descriptor< unsigned char , 64 > & desc ) - { - Descriptor< float , 64 > descFloat; - ComputeMSURFDescriptor( - Lx , - Ly , - id_octave , - ipt , - descFloat); - } - -} // namespace feature -} // namespace aliceVision + // ALICEVISION_LOG_DEBUG(dataMap.transpose())); +} + +template +inline void ComputeMSURFDescriptor(const ImageT& Lx, + const ImageT& Ly, + const int id_octave, + const PointFeature& ipt, + Descriptor& desc) +{ + Descriptor descFloat; + ComputeMSURFDescriptor(Lx, Ly, id_octave, ipt, descFloat); +} + +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.cpp b/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.cpp index cf083eca1f..162912e645 100644 --- a/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.cpp +++ b/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.cpp @@ -15,97 +15,85 @@ namespace aliceVision { namespace feature { -void ImageDescriber_APRILTAG::AprilTagParameters::setPreset(EImageDescriberPreset preset) -{ -} +void ImageDescriber_APRILTAG::AprilTagParameters::setPreset(EImageDescriberPreset preset) {} ImageDescriber_APRILTAG::ImageDescriber_APRILTAG() : ImageDescriber() - {} +{} -void ImageDescriber_APRILTAG::allocate(std::unique_ptr ®ions) const -{ - regions.reset( new APRILTAG_Regions ); -} +void ImageDescriber_APRILTAG::allocate(std::unique_ptr& regions) const { regions.reset(new APRILTAG_Regions); } -void ImageDescriber_APRILTAG::setConfigurationPreset(ConfigurationPreset preset) -{ - _params.setPreset(preset.descPreset); -} +void ImageDescriber_APRILTAG::setConfigurationPreset(ConfigurationPreset preset) { _params.setPreset(preset.descPreset); } -EImageDescriberType ImageDescriber_APRILTAG::getDescriberType() const -{ - return EImageDescriberType::APRILTAG16H5; -} +EImageDescriberType ImageDescriber_APRILTAG::getDescriberType() const { return EImageDescriberType::APRILTAG16H5; } bool ImageDescriber_APRILTAG::describe(const image::Image& image, - std::unique_ptr ®ions, - const image::Image * mask) + std::unique_ptr& regions, + const image::Image* mask) { - allocate(regions); - ALICEVISION_LOG_DEBUG(" regions.get() = " << regions.get()); - APRILTAG_Regions * regionsCasted = dynamic_cast(regions.get()); - ALICEVISION_LOG_DEBUG(" regionsCasted = " << regionsCasted); + allocate(regions); + ALICEVISION_LOG_DEBUG(" regions.get() = " << regions.get()); + APRILTAG_Regions* regionsCasted = dynamic_cast(regions.get()); + ALICEVISION_LOG_DEBUG(" regionsCasted = " << regionsCasted); - apriltag_family_t *tf = tag16h5_create(); - apriltag_detector_t *td = apriltag_detector_create(); - apriltag_detector_add_family(td, tf); - td->quad_decimate = 2.0; - td->quad_sigma = 0.0; // blur - td->nthreads = 1; - td->debug = 0; - td->refine_edges = 1; + apriltag_family_t* tf = tag16h5_create(); + apriltag_detector_t* td = apriltag_detector_create(); + apriltag_detector_add_family(td, tf); + td->quad_decimate = 2.0; + td->quad_sigma = 0.0; // blur + td->nthreads = 1; + td->debug = 0; + td->refine_edges = 1; - // Make an image_u8_t header for the image - image_u8_t im = { .width = image.Width(), - .height = image.Height(), - .stride = image.Width(), - .buf = (uint8_t*) image.data() - }; - zarray_t *detections = apriltag_detector_detect(td, &im); - // Draw detection outlines - for (int i = 0; i < zarray_size(detections); i++) { - apriltag_detection_t *det; - zarray_get(detections, i, &det); - if (det->hamming == 0) { - // compute center point as the intersection of the two diagonals - // note: AprilTag corner points are listed counter-clockwise: - Vec2 tl(det->p[0][0], det->p[0][1]); - Vec2 bl(det->p[1][0], det->p[1][1]); - Vec2 br(det->p[2][0], det->p[2][1]); - Vec2 tr(det->p[3][0], det->p[3][1]); - double denominator = ((tl[0]-br[0]) * (bl[1]-tr[1]) - (tl[1]-br[1]) * (bl[0]-tr[0])); - Vec2 center( - ((tl[0]*br[1] - tl[1]*br[0]) * (bl[0]-tr[0]) - (tl[0]-br[0]) * (bl[0]*tr[1] - bl[1]*tr[0])) / denominator, - ((tl[0]*br[1] - tl[1]*br[0]) * (bl[1]-tr[1]) - (tl[1]-br[1]) * (bl[0]*tr[1] - bl[1]*tr[0])) / denominator - ); - Vec2 points[5] = { center, tl, bl, br, tr }; - int indices[5] = { det->id, 30 + det->id, 60 + det->id, 90 + det->id, 120 + det->id}; - // compute scale from max side length and diagonals (divided by sqare root of 2): - const double scale = 0.5 * std::max({(tl-bl).norm(), (bl-br).norm(), (br-tr).norm(), (tr-tl).norm(), 0.707*(tl-br).norm(), 0.707*(tr-bl).norm()}); - ALICEVISION_LOG_DEBUG(" New AprilTag: Id " << det->id << " ; Center location ( " << center[0] << " , " << center[1] << " ) ; Scale " << scale); - // ignore orientation for now: - const float orientation = 0.0f; - for (size_t j = 0; j < 5; ++j) { - // Add its associated descriptor - APRILTAG_Regions::DescriptorT desc; - for(size_t k = 0; k < desc.size(); ++k) + // Make an image_u8_t header for the image + image_u8_t im = {.width = image.Width(), .height = image.Height(), .stride = image.Width(), .buf = (uint8_t*)image.data()}; + zarray_t* detections = apriltag_detector_detect(td, &im); + // Draw detection outlines + for (int i = 0; i < zarray_size(detections); i++) + { + apriltag_detection_t* det; + zarray_get(detections, i, &det); + if (det->hamming == 0) { - desc[k] = (unsigned char) 0; + // compute center point as the intersection of the two diagonals + // note: AprilTag corner points are listed counter-clockwise: + Vec2 tl(det->p[0][0], det->p[0][1]); + Vec2 bl(det->p[1][0], det->p[1][1]); + Vec2 br(det->p[2][0], det->p[2][1]); + Vec2 tr(det->p[3][0], det->p[3][1]); + double denominator = ((tl[0] - br[0]) * (bl[1] - tr[1]) - (tl[1] - br[1]) * (bl[0] - tr[0])); + Vec2 center(((tl[0] * br[1] - tl[1] * br[0]) * (bl[0] - tr[0]) - (tl[0] - br[0]) * (bl[0] * tr[1] - bl[1] * tr[0])) / denominator, + ((tl[0] * br[1] - tl[1] * br[0]) * (bl[1] - tr[1]) - (tl[1] - br[1]) * (bl[0] * tr[1] - bl[1] * tr[0])) / denominator); + Vec2 points[5] = {center, tl, bl, br, tr}; + int indices[5] = {det->id, 30 + det->id, 60 + det->id, 90 + det->id, 120 + det->id}; + // compute scale from max side length and diagonals (divided by sqare root of 2): + const double scale = + 0.5 * + std::max({(tl - bl).norm(), (bl - br).norm(), (br - tr).norm(), (tr - tl).norm(), 0.707 * (tl - br).norm(), 0.707 * (tr - bl).norm()}); + ALICEVISION_LOG_DEBUG(" New AprilTag: Id " << det->id << " ; Center location ( " << center[0] << " , " << center[1] << " ) ; Scale " + << scale); + // ignore orientation for now: + const float orientation = 0.0f; + for (size_t j = 0; j < 5; ++j) + { + // Add its associated descriptor + APRILTAG_Regions::DescriptorT desc; + for (size_t k = 0; k < desc.size(); ++k) + { + desc[k] = (unsigned char)0; + } + desc[indices[j]] = (unsigned char)255; + regionsCasted->Descriptors().push_back(desc); + regionsCasted->Features().push_back(PointFeature(points[j][0], points[j][1], scale, orientation)); + } } - desc[indices[j]] = (unsigned char) 255; - regionsCasted->Descriptors().push_back(desc); - regionsCasted->Features().push_back(PointFeature(points[j][0], points[j][1], scale, orientation)); - } } - } - apriltag_detections_destroy(detections); - apriltag_detector_destroy(td); - tag16h5_destroy(tf); + apriltag_detections_destroy(detections); + apriltag_detector_destroy(td); + tag16h5_destroy(tf); - return true; + return true; } -} // namespace feature -} // namespace aliceVision - +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.hpp b/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.hpp index ea065beefd..93519ca074 100644 --- a/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.hpp +++ b/src/aliceVision/feature/apriltag/ImageDescriber_APRILTAG.hpp @@ -23,85 +23,80 @@ namespace feature { */ class ImageDescriber_APRILTAG : public ImageDescriber { -public: - explicit ImageDescriber_APRILTAG(); - - ~ImageDescriber_APRILTAG() override = default; - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return false; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override; - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - // this should be a reasonably safe upper bound, but to know this exactly, - // much deeper exmination of the AprilTag source code would be needed: - return 3 * width * height * sizeof(unsigned char); - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - * @return True if configuration succeed. (here always false) - */ - void setConfigurationPreset(ConfigurationPreset preset) override; - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr ®ions, - const image::Image * mask = nullptr) override; - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr ®ions) const override; - - struct AprilTagParameters - { - explicit AprilTagParameters() = default; - - ~AprilTagParameters() = default; - - void setPreset(EImageDescriberPreset preset); - }; -private: - //AprilTag parameters - AprilTagParameters _params; + public: + explicit ImageDescriber_APRILTAG(); + + ~ImageDescriber_APRILTAG() override = default; + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return false; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override; + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override + { + // this should be a reasonably safe upper bound, but to know this exactly, + // much deeper exmination of the AprilTag source code would be needed: + return 3 * width * height * sizeof(unsigned char); + } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + * @return True if configuration succeed. (here always false) + */ + void setConfigurationPreset(ConfigurationPreset preset) override; + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, + std::unique_ptr& regions, + const image::Image* mask = nullptr) override; + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override; + + struct AprilTagParameters + { + explicit AprilTagParameters() = default; + + ~AprilTagParameters() = default; + + void setPreset(EImageDescriberPreset preset); + }; + + private: + // AprilTag parameters + AprilTagParameters _params; }; /** @@ -109,27 +104,27 @@ class ImageDescriber_APRILTAG : public ImageDescriber * @param[in] desc descriptor * @return apriltag id or UndefinedIndexT if wrong apriltag descriptor */ -template -IndexT getAprilTagId(const DescriptorT & desc) +template +IndexT getAprilTagId(const DescriptorT& desc) { - std::size_t apriltagId = UndefinedIndexT; - for (std::size_t i = 0; i < desc.size(); ++i) - { - if (desc.getData()[i] == (unsigned char) 255) - { - if (apriltagId != UndefinedIndexT) - { - return UndefinedIndexT; - } - apriltagId = i; - } - else if(desc.getData()[i] != (unsigned char) 0) + std::size_t apriltagId = UndefinedIndexT; + for (std::size_t i = 0; i < desc.size(); ++i) { - return UndefinedIndexT; + if (desc.getData()[i] == (unsigned char)255) + { + if (apriltagId != UndefinedIndexT) + { + return UndefinedIndexT; + } + apriltagId = i; + } + else if (desc.getData()[i] != (unsigned char)0) + { + return UndefinedIndexT; + } } - } - return apriltagId; + return apriltagId; } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.cpp b/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.cpp index c3e72362c0..632a22182b 100644 --- a/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.cpp +++ b/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.cpp @@ -11,9 +11,9 @@ #include #include -//#define CPU_ADAPT_OF_GPU_PART //todo: #ifdef depreciated -#ifdef CPU_ADAPT_OF_GPU_PART - #include "cctag/progBase/MemoryPool.hpp" +// #define CPU_ADAPT_OF_GPU_PART //todo: #ifdef depreciated +#ifdef CPU_ADAPT_OF_GPU_PART + #include "cctag/progBase/MemoryPool.hpp" #endif namespace aliceVision { @@ -22,130 +22,114 @@ namespace feature { ImageDescriber_CCTAG::CCTagParameters::CCTagParameters(size_t nRings) : _internalParams(new cctag::Parameters(nRings)) { -#ifdef CCTAG_WITH_CUDA // CCTAG_WITH_CUDA - _internalParams->_useCuda = gpu::gpuSupportCUDA(3,5); +#ifdef CCTAG_WITH_CUDA // CCTAG_WITH_CUDA + _internalParams->_useCuda = gpu::gpuSupportCUDA(3, 5); #else - _internalParams->_useCuda = false; + _internalParams->_useCuda = false; #endif } bool ImageDescriber_CCTAG::CCTagParameters::setPreset(EImageDescriberPreset preset) { - switch(preset) - { - // Normal lighting conditions: normal contrast - case EImageDescriberPreset::LOW: - case EImageDescriberPreset::MEDIUM: - case EImageDescriberPreset::NORMAL: - _cannyThrLow = 0.01f; - _cannyThrHigh = 0.04f; - break; - // Low lighting conditions: very low contrast - case EImageDescriberPreset::HIGH: - case EImageDescriberPreset::ULTRA: - _cannyThrLow = 0.002f; - _cannyThrHigh = 0.01f; - break; - } - return true; + switch (preset) + { + // Normal lighting conditions: normal contrast + case EImageDescriberPreset::LOW: + case EImageDescriberPreset::MEDIUM: + case EImageDescriberPreset::NORMAL: + _cannyThrLow = 0.01f; + _cannyThrHigh = 0.04f; + break; + // Low lighting conditions: very low contrast + case EImageDescriberPreset::HIGH: + case EImageDescriberPreset::ULTRA: + _cannyThrLow = 0.002f; + _cannyThrHigh = 0.01f; + break; + } + return true; } - ImageDescriber_CCTAG::ImageDescriber_CCTAG(const std::size_t nRings) - : ImageDescriber() - , _params(nRings) - {} + : ImageDescriber(), + _params(nRings) +{} +bool ImageDescriber_CCTAG::useCuda() const { return _params._internalParams->_useCuda; } -bool ImageDescriber_CCTAG::useCuda() const -{ - return _params._internalParams->_useCuda; -} - -void ImageDescriber_CCTAG::allocate(std::unique_ptr ®ions) const -{ - regions.reset( new CCTAG_Regions ); -} +void ImageDescriber_CCTAG::allocate(std::unique_ptr& regions) const { regions.reset(new CCTAG_Regions); } -void ImageDescriber_CCTAG::setConfigurationPreset(ConfigurationPreset preset) -{ - _params.setPreset(preset.descPreset); -} +void ImageDescriber_CCTAG::setConfigurationPreset(ConfigurationPreset preset) { _params.setPreset(preset.descPreset); } EImageDescriberType ImageDescriber_CCTAG::getDescriberType() const { - if(_params._internalParams->_nCrowns == 3) - return EImageDescriberType::CCTAG3; - if(_params._internalParams->_nCrowns == 4) - return EImageDescriberType::CCTAG4; - throw std::out_of_range("Invalid number of rings, can't find CCTag imageDescriber type !"); + if (_params._internalParams->_nCrowns == 3) + return EImageDescriberType::CCTAG3; + if (_params._internalParams->_nCrowns == 4) + return EImageDescriberType::CCTAG4; + throw std::out_of_range("Invalid number of rings, can't find CCTag imageDescriber type !"); } -void ImageDescriber_CCTAG::setUseCuda(bool useCuda) -{ - _params._internalParams->_useCuda = useCuda; -} +void ImageDescriber_CCTAG::setUseCuda(bool useCuda) { _params._internalParams->_useCuda = useCuda; } bool ImageDescriber_CCTAG::describe(const image::Image& image, - std::unique_ptr ®ions, - const image::Image * mask) + std::unique_ptr& regions, + const image::Image* mask) { + if (!_doAppend) + allocate(regions); + // else regions are added to the current vector of features/descriptors - if ( !_doAppend ) - allocate(regions); - // else regions are added to the current vector of features/descriptors - - // Build alias to cached data - CCTAG_Regions * regionsCasted = dynamic_cast(regions.get()); - // reserve some memory for faster keypoint saving + // Build alias to cached data + CCTAG_Regions* regionsCasted = dynamic_cast(regions.get()); + // reserve some memory for faster keypoint saving - regionsCasted->Features().reserve(regionsCasted->Features().size() + 50); - regionsCasted->Descriptors().reserve(regionsCasted->Descriptors().size() + 50); + regionsCasted->Features().reserve(regionsCasted->Features().size() + 50); + regionsCasted->Descriptors().reserve(regionsCasted->Descriptors().size() + 50); - boost::ptr_list cctags; - cctag::logtime::Mgmt* durations = new cctag::logtime::Mgmt( 25 ); - // cctag::CCTagMarkersBank bank(_params._nCrowns); + boost::ptr_list cctags; + cctag::logtime::Mgmt* durations = new cctag::logtime::Mgmt(25); + // cctag::CCTagMarkersBank bank(_params._nCrowns); #ifndef CPU_ADAPT_OF_GPU_PART - const cv::Mat graySrc(cv::Size(image.Width(), image.Height()), CV_8UC1, (unsigned char *) image.data(), cv::Mat::AUTO_STEP); - //// Invert the image - //cv::Mat invertImg; - //cv::bitwise_not(graySrc,invertImg); - cctag::cctagDetection(cctags, _cudaPipe, 1,graySrc, *_params._internalParams, durations); -#else //todo: #ifdef depreciated - cctag::MemoryPool::instance().updateMemoryAuthorizedWithRAM(); - cctag::View cctagView((const unsigned char *) image.data(), image.Width(), image.Height(), image.Depth()*image.Width()); - cctag::cctagDetection(cctags, _cudaPipe, 1 ,cctagView._grayView ,*_params._internalParams, durations ); + const cv::Mat graySrc(cv::Size(image.Width(), image.Height()), CV_8UC1, (unsigned char*)image.data(), cv::Mat::AUTO_STEP); + //// Invert the image + // cv::Mat invertImg; + // cv::bitwise_not(graySrc,invertImg); + cctag::cctagDetection(cctags, _cudaPipe, 1, graySrc, *_params._internalParams, durations); +#else // todo: #ifdef depreciated + cctag::MemoryPool::instance().updateMemoryAuthorizedWithRAM(); + cctag::View cctagView((const unsigned char*)image.data(), image.Width(), image.Height(), image.Depth() * image.Width()); + cctag::cctagDetection(cctags, _cudaPipe, 1, cctagView._grayView, *_params._internalParams, durations); #endif - durations->print( std::cerr ); + durations->print(std::cerr); - // There is no notion of orientation in CCTag - const float orientation = 0.0f; + // There is no notion of orientation in CCTag + const float orientation = 0.0f; - for (const auto & cctag : cctags) - { - if ( cctag.getStatus() > 0 ) + for (const auto& cctag : cctags) { - ALICEVISION_LOG_DEBUG(" New CCTag: Id" << cctag.id() << " ; Location ( " << cctag.x() << " , " << cctag.y() << " ) "); - - // Add its associated descriptor - Descriptor desc; - for(std::size_t i=0; i< desc.size(); ++i) - { - desc[i] = (unsigned char) 0; - } - desc[cctag.id()] = (unsigned char) 255; - regionsCasted->Descriptors().push_back(desc); - const float scale = std::max(cctag.rescaledOuterEllipse().a(), cctag.rescaledOuterEllipse().b()); - regionsCasted->Features().push_back(PointFeature(cctag.x(), cctag.y(), scale, orientation)); + if (cctag.getStatus() > 0) + { + ALICEVISION_LOG_DEBUG(" New CCTag: Id" << cctag.id() << " ; Location ( " << cctag.x() << " , " << cctag.y() << " ) "); + + // Add its associated descriptor + Descriptor desc; + for (std::size_t i = 0; i < desc.size(); ++i) + { + desc[i] = (unsigned char)0; + } + desc[cctag.id()] = (unsigned char)255; + regionsCasted->Descriptors().push_back(desc); + const float scale = std::max(cctag.rescaledOuterEllipse().a(), cctag.rescaledOuterEllipse().b()); + regionsCasted->Features().push_back(PointFeature(cctag.x(), cctag.y(), scale, orientation)); + } } - } - cctags.clear(); + cctags.clear(); - return true; + return true; } -} // namespace feature -} // namespace aliceVision - +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp b/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp index 84eafe39e1..4c0561047e 100644 --- a/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp +++ b/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp @@ -16,7 +16,7 @@ #include namespace cctag { - struct Parameters; // Hidden implementation +struct Parameters; // Hidden implementation } namespace aliceVision { @@ -27,101 +27,93 @@ namespace feature { */ class ImageDescriber_CCTAG : public ImageDescriber { -public: - explicit ImageDescriber_CCTAG(const std::size_t nRings = 3); - - ~ImageDescriber_CCTAG() override = default; - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override; - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return false; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override; - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return 3 * width * height * sizeof(unsigned char); - } - - /** - * @brief Set if yes or no imageDescriber need to use cuda implementation - * @param[in] useCuda - */ - void setUseCuda(bool) override; - - /** - * @brief set the CUDA pipe - * @param[in] pipe The CUDA pipe id - */ - void setCudaPipe(int pipe) override - { - _cudaPipe = pipe; - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - * @return True if configuration succeed. (here always false) - */ - void setConfigurationPreset(ConfigurationPreset preset) override; - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr ®ions, - const image::Image * mask = nullptr) override; - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr ®ions) const override; - - struct CCTagParameters - { - explicit CCTagParameters(size_t nRings); - - ~CCTagParameters() = default; - - bool setPreset(EImageDescriberPreset preset); - - float _cannyThrLow; - float _cannyThrHigh; - std::unique_ptr _internalParams; - }; -private: - //CCTag parameters - CCTagParameters _params; - bool _doAppend = false; - int _cudaPipe = 0; + public: + explicit ImageDescriber_CCTAG(const std::size_t nRings = 3); + + ~ImageDescriber_CCTAG() override = default; + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override; + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return false; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override; + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override { return 3 * width * height * sizeof(unsigned char); } + + /** + * @brief Set if yes or no imageDescriber need to use cuda implementation + * @param[in] useCuda + */ + void setUseCuda(bool) override; + + /** + * @brief set the CUDA pipe + * @param[in] pipe The CUDA pipe id + */ + void setCudaPipe(int pipe) override { _cudaPipe = pipe; } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + * @return True if configuration succeed. (here always false) + */ + void setConfigurationPreset(ConfigurationPreset preset) override; + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, + std::unique_ptr& regions, + const image::Image* mask = nullptr) override; + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override; + + struct CCTagParameters + { + explicit CCTagParameters(size_t nRings); + + ~CCTagParameters() = default; + + bool setPreset(EImageDescriberPreset preset); + + float _cannyThrLow; + float _cannyThrHigh; + std::unique_ptr _internalParams; + }; + + private: + // CCTag parameters + CCTagParameters _params; + bool _doAppend = false; + int _cudaPipe = 0; }; /** @@ -129,27 +121,27 @@ class ImageDescriber_CCTAG : public ImageDescriber * @param[in] desc descriptor * @return cctag id or UndefinedIndexT if wrong cctag descriptor */ -template -IndexT getCCTagId(const DescriptorT & desc) +template +IndexT getCCTagId(const DescriptorT& desc) { - std::size_t cctagId = UndefinedIndexT; - for (std::size_t i = 0; i < desc.size(); ++i) - { - if (desc.getData()[i] == (unsigned char) 255) - { - if (cctagId != UndefinedIndexT) - { - return UndefinedIndexT; - } - cctagId = i; - } - else if(desc.getData()[i] != (unsigned char) 0) + std::size_t cctagId = UndefinedIndexT; + for (std::size_t i = 0; i < desc.size(); ++i) { - return UndefinedIndexT; + if (desc.getData()[i] == (unsigned char)255) + { + if (cctagId != UndefinedIndexT) + { + return UndefinedIndexT; + } + cctagId = i; + } + else if (desc.getData()[i] != (unsigned char)0) + { + return UndefinedIndexT; + } } - } - return cctagId; + return cctagId; } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/feature.hpp b/src/aliceVision/feature/feature.hpp index c7b7eee666..ee74c383ba 100644 --- a/src/aliceVision/feature/feature.hpp +++ b/src/aliceVision/feature/feature.hpp @@ -13,5 +13,3 @@ #include #include #include - - diff --git a/src/aliceVision/feature/features_test.cpp b/src/aliceVision/feature/features_test.cpp index 229449e554..6451af1fda 100644 --- a/src/aliceVision/feature/features_test.cpp +++ b/src/aliceVision/feature/features_test.cpp @@ -35,89 +35,97 @@ typedef std::vector Descs_T; static const int CARD = 12; -BOOST_AUTO_TEST_CASE(featureIO_NON_EXISTING_FILE) { - - // Try to read a non-existing feature file - Feats_T vec_feats; - BOOST_CHECK_THROW(loadFeatsFromFile("x.feat", vec_feats), std::exception); - - // Try to read a non-existing descriptor file - Descs_T vec_descs; - BOOST_CHECK_THROW(loadDescsFromFile("x.desc", vec_descs), std::exception); - BOOST_CHECK_THROW(loadDescsFromBinFile("x.desc", vec_descs), std::exception); +BOOST_AUTO_TEST_CASE(featureIO_NON_EXISTING_FILE) +{ + // Try to read a non-existing feature file + Feats_T vec_feats; + BOOST_CHECK_THROW(loadFeatsFromFile("x.feat", vec_feats), std::exception); + + // Try to read a non-existing descriptor file + Descs_T vec_descs; + BOOST_CHECK_THROW(loadDescsFromFile("x.desc", vec_descs), std::exception); + BOOST_CHECK_THROW(loadDescsFromBinFile("x.desc", vec_descs), std::exception); } -BOOST_AUTO_TEST_CASE(featureIO_ASCII) { - Feats_T vec_feats; - for(int i = 0; i < CARD; ++i) { - vec_feats.push_back(Feature_T(i, i*2, i*3, i*4)); - } - - //Save them to a file - BOOST_CHECK_NO_THROW(saveFeatsToFile("tempFeats.feat", vec_feats)); - - //Read the saved data and compare to input (to check write/read IO) - Feats_T vec_feats_read; - BOOST_CHECK_NO_THROW(loadFeatsFromFile("tempFeats.feat", vec_feats_read)); - BOOST_CHECK_EQUAL(CARD, vec_feats_read.size()); - - for(int i = 0; i < CARD; ++i) { - BOOST_CHECK_EQUAL(vec_feats[i], vec_feats_read[i]); - BOOST_CHECK_EQUAL(vec_feats[i].coords(), vec_feats_read[i].coords()); - BOOST_CHECK_EQUAL(vec_feats[i].scale(), vec_feats_read[i].scale()); - BOOST_CHECK_EQUAL(vec_feats[i].orientation(), vec_feats_read[i].orientation()); - } +BOOST_AUTO_TEST_CASE(featureIO_ASCII) +{ + Feats_T vec_feats; + for (int i = 0; i < CARD; ++i) + { + vec_feats.push_back(Feature_T(i, i * 2, i * 3, i * 4)); + } + + // Save them to a file + BOOST_CHECK_NO_THROW(saveFeatsToFile("tempFeats.feat", vec_feats)); + + // Read the saved data and compare to input (to check write/read IO) + Feats_T vec_feats_read; + BOOST_CHECK_NO_THROW(loadFeatsFromFile("tempFeats.feat", vec_feats_read)); + BOOST_CHECK_EQUAL(CARD, vec_feats_read.size()); + + for (int i = 0; i < CARD; ++i) + { + BOOST_CHECK_EQUAL(vec_feats[i], vec_feats_read[i]); + BOOST_CHECK_EQUAL(vec_feats[i].coords(), vec_feats_read[i].coords()); + BOOST_CHECK_EQUAL(vec_feats[i].scale(), vec_feats_read[i].scale()); + BOOST_CHECK_EQUAL(vec_feats[i].orientation(), vec_feats_read[i].orientation()); + } } //-- //-- Descriptors interface test //-- -BOOST_AUTO_TEST_CASE(descriptorIO_ASCII) { - // Create an input series of descriptor - Descs_T vec_descs; - for(int i = 0; i < CARD; ++i) { - Desc_T desc; - for (int j = 0; j < DESC_LENGTH; ++j) - desc[j] = i*DESC_LENGTH+j; - vec_descs.push_back(desc); - } - - //Save them to a file - BOOST_CHECK_NO_THROW(saveDescsToFile("tempDescs.desc", vec_descs)); - - //Read the saved data and compare to input (to check write/read IO) - Descs_T vec_descs_read; - BOOST_CHECK_NO_THROW(loadDescsFromFile("tempDescs.desc", vec_descs_read)); - BOOST_CHECK_EQUAL(CARD, vec_descs_read.size()); - - for(int i = 0; i < CARD; ++i) { - for (int j = 0; j < DESC_LENGTH; ++j) - BOOST_CHECK_EQUAL(vec_descs[i][j], vec_descs_read[i][j]); - } +BOOST_AUTO_TEST_CASE(descriptorIO_ASCII) +{ + // Create an input series of descriptor + Descs_T vec_descs; + for (int i = 0; i < CARD; ++i) + { + Desc_T desc; + for (int j = 0; j < DESC_LENGTH; ++j) + desc[j] = i * DESC_LENGTH + j; + vec_descs.push_back(desc); + } + + // Save them to a file + BOOST_CHECK_NO_THROW(saveDescsToFile("tempDescs.desc", vec_descs)); + + // Read the saved data and compare to input (to check write/read IO) + Descs_T vec_descs_read; + BOOST_CHECK_NO_THROW(loadDescsFromFile("tempDescs.desc", vec_descs_read)); + BOOST_CHECK_EQUAL(CARD, vec_descs_read.size()); + + for (int i = 0; i < CARD; ++i) + { + for (int j = 0; j < DESC_LENGTH; ++j) + BOOST_CHECK_EQUAL(vec_descs[i][j], vec_descs_read[i][j]); + } } -//Test binary export of descriptor -BOOST_AUTO_TEST_CASE(descriptorIO_BINARY) { - // Create an input series of descriptor - Descs_T vec_descs; - for(int i = 0; i < CARD; ++i) - { - Desc_T desc; - for (int j = 0; j < DESC_LENGTH; ++j) - desc[j] = i*DESC_LENGTH+j; - vec_descs.push_back(desc); - } - - //Save them to a file - BOOST_CHECK_NO_THROW(saveDescsToBinFile("tempDescsBin.desc", vec_descs)); - - //Read the saved data and compare to input (to check write/read IO) - Descs_T vec_descs_read; - BOOST_CHECK_NO_THROW(loadDescsFromBinFile("tempDescsBin.desc", vec_descs_read)); - BOOST_CHECK_EQUAL(CARD, vec_descs_read.size()); - - for(int i = 0; i < CARD; ++i) { - for (int j = 0; j < DESC_LENGTH; ++j) - BOOST_CHECK_EQUAL(vec_descs[i][j], vec_descs_read[i][j]); - } +// Test binary export of descriptor +BOOST_AUTO_TEST_CASE(descriptorIO_BINARY) +{ + // Create an input series of descriptor + Descs_T vec_descs; + for (int i = 0; i < CARD; ++i) + { + Desc_T desc; + for (int j = 0; j < DESC_LENGTH; ++j) + desc[j] = i * DESC_LENGTH + j; + vec_descs.push_back(desc); + } + + // Save them to a file + BOOST_CHECK_NO_THROW(saveDescsToBinFile("tempDescsBin.desc", vec_descs)); + + // Read the saved data and compare to input (to check write/read IO) + Descs_T vec_descs_read; + BOOST_CHECK_NO_THROW(loadDescsFromBinFile("tempDescsBin.desc", vec_descs_read)); + BOOST_CHECK_EQUAL(CARD, vec_descs_read.size()); + + for (int i = 0; i < CARD; ++i) + { + for (int j = 0; j < DESC_LENGTH; ++j) + BOOST_CHECK_EQUAL(vec_descs[i][j], vec_descs_read[i][j]); + } } diff --git a/src/aliceVision/feature/imageDescriberCommon.cpp b/src/aliceVision/feature/imageDescriberCommon.cpp index 78536a8ab8..448db0a231 100644 --- a/src/aliceVision/feature/imageDescriberCommon.cpp +++ b/src/aliceVision/feature/imageDescriberCommon.cpp @@ -13,119 +13,145 @@ #include - namespace aliceVision { namespace feature { std::string EImageDescriberType_informations() { - return "Describer types used to describe an image:\n" - "* sift: Scale-invariant feature transform.\n" - "* sift_float: SIFT stored as float.\n" - "* sift_upright: SIFT with upright feature.\n" - "* akaze: A-KAZE with floating point descriptors.\n" - "* akaze_liop: A-KAZE with Local Intensity Order Pattern descriptors.\n" - "* akaze_mldb: A-KAZE with Modified-Local Difference Binary descriptors.\n" + return "Describer types used to describe an image:\n" + "* sift: Scale-invariant feature transform.\n" + "* sift_float: SIFT stored as float.\n" + "* sift_upright: SIFT with upright feature.\n" + "* akaze: A-KAZE with floating point descriptors.\n" + "* akaze_liop: A-KAZE with Local Intensity Order Pattern descriptors.\n" + "* akaze_mldb: A-KAZE with Modified-Local Difference Binary descriptors.\n" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - "* cctag3: Concentric circles markers with 3 crowns.\n" - "* cctag4: Concentric circles markers with 4 crowns.\n" + "* cctag3: Concentric circles markers with 3 crowns.\n" + "* cctag4: Concentric circles markers with 4 crowns.\n" #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - "* tag16h5: AprilTag family tag16h5.\n" + "* tag16h5: AprilTag family tag16h5.\n" #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - "* sift_ocv: OpenCV implementation of SIFT describer.\n" -#endif - "* akaze_ocv: OpenCV implementation of A-KAZE describer.\n" + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + "* sift_ocv: OpenCV implementation of SIFT describer.\n" + #endif + "* akaze_ocv: OpenCV implementation of A-KAZE describer.\n" #endif - ""; + ""; } std::string EImageDescriberType_enumToString(EImageDescriberType imageDescriberType) { - switch(imageDescriberType) - { - case EImageDescriberType::SIFT: return "sift"; - case EImageDescriberType::SIFT_FLOAT: return "sift_float"; - case EImageDescriberType::SIFT_UPRIGHT: return "sift_upright"; - - case EImageDescriberType::DSPSIFT: return "dspsift"; - - case EImageDescriberType::AKAZE: return "akaze"; - case EImageDescriberType::AKAZE_LIOP: return "akaze_liop"; - case EImageDescriberType::AKAZE_MLDB: return "akaze_mldb"; - + switch (imageDescriberType) + { + case EImageDescriberType::SIFT: + return "sift"; + case EImageDescriberType::SIFT_FLOAT: + return "sift_float"; + case EImageDescriberType::SIFT_UPRIGHT: + return "sift_upright"; + + case EImageDescriberType::DSPSIFT: + return "dspsift"; + + case EImageDescriberType::AKAZE: + return "akaze"; + case EImageDescriberType::AKAZE_LIOP: + return "akaze_liop"; + case EImageDescriberType::AKAZE_MLDB: + return "akaze_mldb"; + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case EImageDescriberType::CCTAG3: return "cctag3"; - case EImageDescriberType::CCTAG4: return "cctag4"; + case EImageDescriberType::CCTAG3: + return "cctag3"; + case EImageDescriberType::CCTAG4: + return "cctag4"; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - case EImageDescriberType::APRILTAG16H5: return "tag16h5"; + case EImageDescriberType::APRILTAG16H5: + return "tag16h5"; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - case EImageDescriberType::SIFT_OCV: return "sift_ocv"; -#endif //ALICEVISION_HAVE_OCVSIFT - case EImageDescriberType::AKAZE_OCV: return "akaze_ocv"; -#endif //ALICEVISION_HAVE_OPENCV - - case EImageDescriberType::UNKNOWN: return "unknown"; - case EImageDescriberType::UNINITIALIZED: break; // Should throw an error. - } - throw std::out_of_range("Invalid imageDescriber enum: " + std::to_string(int(imageDescriberType))); + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + case EImageDescriberType::SIFT_OCV: + return "sift_ocv"; + #endif // ALICEVISION_HAVE_OCVSIFT + case EImageDescriberType::AKAZE_OCV: + return "akaze_ocv"; +#endif // ALICEVISION_HAVE_OPENCV + + case EImageDescriberType::UNKNOWN: + return "unknown"; + case EImageDescriberType::UNINITIALIZED: + break; // Should throw an error. + } + throw std::out_of_range("Invalid imageDescriber enum: " + std::to_string(int(imageDescriberType))); } EImageDescriberType EImageDescriberType_stringToEnum(const std::string& imageDescriberType) { - std::string type = imageDescriberType; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower - - if(type == "sift") return EImageDescriberType::SIFT; - if(type == "sift_float") return EImageDescriberType::SIFT_FLOAT; - if(type == "sift_upright") return EImageDescriberType::SIFT_UPRIGHT; - - if(type == "dspsift") return EImageDescriberType::DSPSIFT; + std::string type = imageDescriberType; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); // tolower + + if (type == "sift") + return EImageDescriberType::SIFT; + if (type == "sift_float") + return EImageDescriberType::SIFT_FLOAT; + if (type == "sift_upright") + return EImageDescriberType::SIFT_UPRIGHT; + + if (type == "dspsift") + return EImageDescriberType::DSPSIFT; + + if (type == "akaze") + return EImageDescriberType::AKAZE; + if (type == "akaze_liop") + return EImageDescriberType::AKAZE_LIOP; + if (type == "akaze_mldb") + return EImageDescriberType::AKAZE_MLDB; - if(type == "akaze") return EImageDescriberType::AKAZE; - if(type == "akaze_liop") return EImageDescriberType::AKAZE_LIOP; - if(type == "akaze_mldb") return EImageDescriberType::AKAZE_MLDB; - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - if(type == "cctag3") return EImageDescriberType::CCTAG3; - if(type == "cctag4") return EImageDescriberType::CCTAG4; + if (type == "cctag3") + return EImageDescriberType::CCTAG3; + if (type == "cctag4") + return EImageDescriberType::CCTAG4; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - if(type == "tag16h5") return EImageDescriberType::APRILTAG16H5; + if (type == "tag16h5") + return EImageDescriberType::APRILTAG16H5; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - if(type == "sift_ocv") return EImageDescriberType::SIFT_OCV; -#endif //ALICEVISION_HAVE_OCVSIFT - if(type == "akaze_ocv") return EImageDescriberType::AKAZE_OCV; -#endif //ALICEVISION_HAVE_OPENCV - - if(type == "unknown") return EImageDescriberType::UNKNOWN; - // UNINITIALIZED should throw an error. - throw std::out_of_range("Invalid imageDescriber : " + imageDescriberType); + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + if (type == "sift_ocv") + return EImageDescriberType::SIFT_OCV; + #endif // ALICEVISION_HAVE_OCVSIFT + if (type == "akaze_ocv") + return EImageDescriberType::AKAZE_OCV; +#endif // ALICEVISION_HAVE_OPENCV + + if (type == "unknown") + return EImageDescriberType::UNKNOWN; + // UNINITIALIZED should throw an error. + throw std::out_of_range("Invalid imageDescriber : " + imageDescriberType); } std::vector EImageDescriberType_stringToEnums(const std::string& describerMethods) { - std::vector out; - std::vector describerMethodsVec; - boost::split(describerMethodsVec, describerMethods, boost::is_any_of(",")); - - for(const auto& describerMethod: describerMethodsVec) - { - out.push_back(EImageDescriberType_stringToEnum(describerMethod)); - } - return out; + std::vector out; + std::vector describerMethodsVec; + boost::split(describerMethodsVec, describerMethods, boost::is_any_of(",")); + + for (const auto& describerMethod : describerMethodsVec) + { + out.push_back(EImageDescriberType_stringToEnum(describerMethod)); + } + return out; } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/imageDescriberCommon.hpp b/src/aliceVision/feature/imageDescriberCommon.hpp index d5c0578cb7..937a3f28a8 100644 --- a/src/aliceVision/feature/imageDescriberCommon.hpp +++ b/src/aliceVision/feature/imageDescriberCommon.hpp @@ -16,33 +16,38 @@ namespace aliceVision { namespace feature { -enum class EImageDescriberType: unsigned char +enum class EImageDescriberType : unsigned char { - UNKNOWN = 0 - , UNINITIALIZED = 1 - , SIFT = 10 - , SIFT_FLOAT = 11 - , SIFT_UPRIGHT = 12 - , DSPSIFT = 13 - - , AKAZE = 20 - , AKAZE_LIOP = 21 - , AKAZE_MLDB = 22 + UNKNOWN = 0, + UNINITIALIZED = 1, + SIFT = 10, + SIFT_FLOAT = 11, + SIFT_UPRIGHT = 12, + DSPSIFT = 13 + + , + AKAZE = 20, + AKAZE_LIOP = 21, + AKAZE_MLDB = 22 #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - , CCTAG3 = 30 - , CCTAG4 = 31 + , + CCTAG3 = 30, + CCTAG4 = 31 #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - , SIFT_OCV = 40 -#endif - , AKAZE_OCV = 41 + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + , + SIFT_OCV = 40 + #endif + , + AKAZE_OCV = 41 #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - , APRILTAG16H5 = 50 + , + APRILTAG16H5 = 50 #endif }; @@ -54,7 +59,7 @@ std::string EImageDescriberType_informations(); /** * @brief convert an enum EImageDescriberType to its corresponding string - * @param EImageDescriberType + * @param EImageDescriberType * @return String */ std::string EImageDescriberType_enumToString(EImageDescriberType imageDescriberType); @@ -66,26 +71,28 @@ std::string EImageDescriberType_enumToString(EImageDescriberType imageDescriberT */ EImageDescriberType EImageDescriberType_stringToEnum(const std::string& imageDescriberType); - /** - * @brief EImageDescriberType_stringToEnums - * @param describerMethods - * @return EImageDescriberType vector - */ +/** + * @brief EImageDescriberType_stringToEnums + * @param describerMethods + * @return EImageDescriberType vector + */ std::vector EImageDescriberType_stringToEnums(const std::string& describerMethods); inline bool isMarker(EImageDescriberType imageDescriberType) { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - if (imageDescriberType == EImageDescriberType::CCTAG3 || imageDescriberType == EImageDescriberType::CCTAG4) { - return true; + if (imageDescriberType == EImageDescriberType::CCTAG3 || imageDescriberType == EImageDescriberType::CCTAG4) + { + return true; } #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - if (imageDescriberType == EImageDescriberType::APRILTAG16H5) { - return true; + if (imageDescriberType == EImageDescriberType::APRILTAG16H5) + { + return true; } #endif - return false; + return false; } /** @@ -95,70 +102,73 @@ inline bool isMarker(EImageDescriberType imageDescriberType) */ inline float getStrongSupportCoeff(EImageDescriberType imageDescriberType) { - switch(imageDescriberType) - { - case EImageDescriberType::SIFT: - case EImageDescriberType::SIFT_FLOAT: - case EImageDescriberType::SIFT_UPRIGHT: - case EImageDescriberType::DSPSIFT: - case EImageDescriberType::AKAZE: - case EImageDescriberType::AKAZE_LIOP: - case EImageDescriberType::AKAZE_MLDB: - return 0.14f; + switch (imageDescriberType) + { + case EImageDescriberType::SIFT: + case EImageDescriberType::SIFT_FLOAT: + case EImageDescriberType::SIFT_UPRIGHT: + case EImageDescriberType::DSPSIFT: + case EImageDescriberType::AKAZE: + case EImageDescriberType::AKAZE_LIOP: + case EImageDescriberType::AKAZE_MLDB: + return 0.14f; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case EImageDescriberType::CCTAG3: - case EImageDescriberType::CCTAG4: - return 1.0f; + case EImageDescriberType::CCTAG3: + case EImageDescriberType::CCTAG4: + return 1.0f; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - case EImageDescriberType::APRILTAG16H5: - return 1.0f; + case EImageDescriberType::APRILTAG16H5: + return 1.0f; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - case EImageDescriberType::SIFT_OCV: return 0.14f; -#endif //ALICEVISION_HAVE_OCVSIFT - case EImageDescriberType::AKAZE_OCV: return 0.14f; -#endif //ALICEVISION_HAVE_OPENCV - - case EImageDescriberType::UNKNOWN: - return 1.0f; - case EImageDescriberType::UNINITIALIZED: break; // Should throw an error. - } - throw std::out_of_range("Invalid imageDescriber enum"); + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + case EImageDescriberType::SIFT_OCV: + return 0.14f; + #endif // ALICEVISION_HAVE_OCVSIFT + case EImageDescriberType::AKAZE_OCV: + return 0.14f; +#endif // ALICEVISION_HAVE_OPENCV + + case EImageDescriberType::UNKNOWN: + return 1.0f; + case EImageDescriberType::UNINITIALIZED: + break; // Should throw an error. + } + throw std::out_of_range("Invalid imageDescriber enum"); } inline std::ostream& operator<<(std::ostream& os, const EImageDescriberType imageDescriberType) { - os << EImageDescriberType_enumToString(imageDescriberType); - return os; + os << EImageDescriberType_enumToString(imageDescriberType); + return os; } -inline std::istream& operator>>(std::istream& in, EImageDescriberType &imageDescriberType) +inline std::istream& operator>>(std::istream& in, EImageDescriberType& imageDescriberType) { - std::string token; - in >> token; - imageDescriberType = EImageDescriberType_stringToEnum(token); - return in; + std::string token; + in >> token; + imageDescriberType = EImageDescriberType_stringToEnum(token); + return in; } -inline std::ostream& operator<<(std::ostream& os, const std::vector &imageDescriberTypes) +inline std::ostream& operator<<(std::ostream& os, const std::vector& imageDescriberTypes) { - for(const EImageDescriberType descType : imageDescriberTypes) - os << descType; - return os; + for (const EImageDescriberType descType : imageDescriberTypes) + os << descType; + return os; } -inline std::istream& operator>>(std::istream &in, std::vector &imageDescriberTypes) +inline std::istream& operator>>(std::istream& in, std::vector& imageDescriberTypes) { - std::string token; - in >> token; - imageDescriberTypes = EImageDescriberType_stringToEnums(token); - return in; + std::string token; + in >> token; + imageDescriberTypes = EImageDescriberType_stringToEnums(token); + return in; } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/imageStats.cpp b/src/aliceVision/feature/imageStats.cpp index ba356af5a0..aac7a0ea5b 100644 --- a/src/aliceVision/feature/imageStats.cpp +++ b/src/aliceVision/feature/imageStats.cpp @@ -39,18 +39,18 @@ float computeAutomaticContrastFactor(const image::Image& image, const flo int nbValues = 0; - for(int i = 1; i < height - 1; ++i) + for (int i = 1; i < height - 1; ++i) { - for(int j = 1; j < width - 1; ++j) + for (int j = 1; j < width - 1; ++j) { const float val = grad(i, j); - if(val > 0) + if (val > 0) { int binId = floor((val / gradMax) * static_cast(nbBins)); // handle overflow (need to do it in a cleaner way) - if(binId == nbBins) + if (binId == nbBins) --binId; // accumulate @@ -65,15 +65,15 @@ float computeAutomaticContrastFactor(const image::Image& image, const flo int binId = 0; std::size_t acc = 0; - while(acc < searchId && binId < nbBins) + while (acc < searchId && binId < nbBins) { acc += histo[binId]; ++binId; } // handle 0 bin search - if(acc < searchId) - return 0.03f; // only empiric value + if (acc < searchId) + return 0.03f; // only empiric value return gradMax * static_cast(binId) / static_cast(nbBins); } diff --git a/src/aliceVision/feature/imageStats.hpp b/src/aliceVision/feature/imageStats.hpp index 2d942d9fca..8610c25135 100644 --- a/src/aliceVision/feature/imageStats.hpp +++ b/src/aliceVision/feature/imageStats.hpp @@ -9,7 +9,6 @@ #include - namespace aliceVision { namespace feature { diff --git a/src/aliceVision/feature/metric.hpp b/src/aliceVision/feature/metric.hpp index b4a60b160f..2a4c0c3173 100644 --- a/src/aliceVision/feature/metric.hpp +++ b/src/aliceVision/feature/metric.hpp @@ -13,13 +13,12 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_SSE) -#include -#include + #include + #include #endif #include - namespace aliceVision { namespace feature { @@ -27,116 +26,119 @@ namespace feature { template struct L2_Simple { - typedef T ElementType; - typedef typename Accumulator::Type ResultType; - - template - inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const - { - ResultType result = ResultType(); - ResultType diff; - for(size_t i = 0; i < size; ++i ) { - diff = *a++ - *b++; - result += diff*diff; + typedef T ElementType; + typedef typename Accumulator::Type ResultType; + + template + inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const + { + ResultType result = ResultType(); + ResultType diff; + for (size_t i = 0; i < size; ++i) + { + diff = *a++ - *b++; + result += diff * diff; + } + return result; } - return result; - } }; /// Squared Euclidean distance functor (vectorized version) template struct L2_Vectorized { - typedef T ElementType; - typedef typename Accumulator::Type ResultType; - - template - inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const - { - ResultType result = ResultType(); - ResultType diff0, diff1, diff2, diff3; - Iterator1 last = a + size; - Iterator1 lastgroup = last - 3; - - // Process 4 items with each loop for efficiency. - while (a < lastgroup) { - diff0 = a[0] - b[0]; - diff1 = a[1] - b[1]; - diff2 = a[2] - b[2]; - diff3 = a[3] - b[3]; - result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; - a += 4; - b += 4; - } - // Process last 0-3 pixels. Not needed for standard vector lengths. - while (a < last) { - diff0 = *a++ - *b++; - result += diff0 * diff0; + typedef T ElementType; + typedef typename Accumulator::Type ResultType; + + template + inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const + { + ResultType result = ResultType(); + ResultType diff0, diff1, diff2, diff3; + Iterator1 last = a + size; + Iterator1 lastgroup = last - 3; + + // Process 4 items with each loop for efficiency. + while (a < lastgroup) + { + diff0 = a[0] - b[0]; + diff1 = a[1] - b[1]; + diff2 = a[2] - b[2]; + diff3 = a[3] - b[3]; + result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + a += 4; + b += 4; + } + // Process last 0-3 pixels. Not needed for standard vector lengths. + while (a < last) + { + diff0 = *a++ - *b++; + result += diff0 * diff0; + } + return result; } - return result; - } }; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_SSE) -namespace optim_ss2{ +namespace optim_ss2 { - /// Union to switch between SSE and float array - union sseRegisterHelper - { - __m128 m; - float f[4]; - }; +/// Union to switch between SSE and float array +union sseRegisterHelper +{ + __m128 m; + float f[4]; +}; - // Euclidean distance (SSE method) (squared result) - inline float l2_sse(const float * b1, const float * b2, int size) - { +// Euclidean distance (SSE method) (squared result) +inline float l2_sse(const float* b1, const float* b2, int size) +{ float* b1Pt = (float*)b1; float* b2Pt = (float*)b2; - if(size%4 == 0) + if (size % 4 == 0) { - __m128 srcA, srcB, temp, cumSum; - float zeros[4] = {0.f,0.f,0.f,0.f}; - cumSum = _mm_load_ps( zeros ); - for(int i = 0 ; i < size; i+=4) - { - srcA = _mm_load_ps(b1Pt+i); - srcB = _mm_load_ps(b2Pt+i); - //-- Subtract - temp = _mm_sub_ps( srcA, srcB ); - //-- Multiply - temp = _mm_mul_ps( temp, temp ); - //-- sum - cumSum = _mm_add_ps( cumSum, temp ); - } - sseRegisterHelper res; - res.m = cumSum; - return (res.f[0]+res.f[1]+res.f[2]+res.f[3]); + __m128 srcA, srcB, temp, cumSum; + float zeros[4] = {0.f, 0.f, 0.f, 0.f}; + cumSum = _mm_load_ps(zeros); + for (int i = 0; i < size; i += 4) + { + srcA = _mm_load_ps(b1Pt + i); + srcB = _mm_load_ps(b2Pt + i); + //-- Subtract + temp = _mm_sub_ps(srcA, srcB); + //-- Multiply + temp = _mm_mul_ps(temp, temp); + //-- sum + cumSum = _mm_add_ps(cumSum, temp); + } + sseRegisterHelper res; + res.m = cumSum; + return (res.f[0] + res.f[1] + res.f[2] + res.f[3]); } else { - ALICEVISION_LOG_WARNING("/!\\ size is not modulus 4, distance cannot be performed in SSE"); - return 0.0f; + ALICEVISION_LOG_WARNING("/!\\ size is not modulus 4, distance cannot be performed in SSE"); + return 0.0f; } - } -} // namespace optim_ss2 +} +} // namespace optim_ss2 // Template specification to run SSE L2 squared distance // on float vector template<> struct L2_Vectorized { - typedef float ElementType; - typedef Accumulator::Type ResultType; - - template - inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const - { - return optim_ss2::l2_sse(a,b,size); - } + typedef float ElementType; + typedef Accumulator::Type ResultType; + + template + inline ResultType operator()(Iterator1 a, Iterator2 b, size_t size) const + { + return optim_ss2::l2_sse(a, b, size); + } }; -#endif // ALICEVISION_HAVE_SSE +#endif // ALICEVISION_HAVE_SSE } // namespace feature } // namespace aliceVision diff --git a/src/aliceVision/feature/metric_test.cpp b/src/aliceVision/feature/metric_test.cpp index 321d12610c..540041c31b 100644 --- a/src/aliceVision/feature/metric_test.cpp +++ b/src/aliceVision/feature/metric_test.cpp @@ -21,116 +21,134 @@ using namespace feature; template typename Metric::ResultType DistanceT() { - typename Metric::ElementType array1[] = {0, 1, 2, 3, 4, 5, 6, 7}; - typename Metric::ElementType array2[] = {7, 6, 5, 4, 3, 2, 1, 0}; - Metric metric; - return metric(array1, array2, 8); + typename Metric::ElementType array1[] = {0, 1, 2, 3, 4, 5, 6, 7}; + typename Metric::ElementType array2[] = {7, 6, 5, 4, 3, 2, 1, 0}; + Metric metric; + return metric(array1, array2, 8); } BOOST_AUTO_TEST_CASE(Metric_L2_Simple) { - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); } BOOST_AUTO_TEST_CASE(Metric_L2_Vectorized) { - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); - BOOST_CHECK_EQUAL(168, DistanceT >()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); + BOOST_CHECK_EQUAL(168, DistanceT>()); } BOOST_AUTO_TEST_CASE(Metric_HAMMING_BITSET) { - std::bitset<8> a(std::string("01010101")); - std::bitset<8> b(std::string("10101010")); - std::bitset<8> c(std::string("11010100")); - - HammingBitSet > metricHamming; - BOOST_CHECK_EQUAL(8, metricHamming(&a,&b,1)); - BOOST_CHECK_EQUAL(0, metricHamming(&a,&a,1)); - BOOST_CHECK_EQUAL(2, metricHamming(&a,&c,1)); - - Hamming< unsigned char > metricHammingUchar; - - BOOST_CHECK_EQUAL(8, metricHammingUchar(reinterpret_cast(&a),reinterpret_cast(&b),1)); - BOOST_CHECK_EQUAL(0, metricHammingUchar(reinterpret_cast(&a),reinterpret_cast(&a),1)); - BOOST_CHECK_EQUAL(2, metricHammingUchar(reinterpret_cast(&a),reinterpret_cast(&c),1)); + std::bitset<8> a(std::string("01010101")); + std::bitset<8> b(std::string("10101010")); + std::bitset<8> c(std::string("11010100")); + + HammingBitSet> metricHamming; + BOOST_CHECK_EQUAL(8, metricHamming(&a, &b, 1)); + BOOST_CHECK_EQUAL(0, metricHamming(&a, &a, 1)); + BOOST_CHECK_EQUAL(2, metricHamming(&a, &c, 1)); + + Hamming metricHammingUchar; + + BOOST_CHECK_EQUAL(8, metricHammingUchar(reinterpret_cast(&a), reinterpret_cast(&b), 1)); + BOOST_CHECK_EQUAL(0, metricHammingUchar(reinterpret_cast(&a), reinterpret_cast(&a), 1)); + BOOST_CHECK_EQUAL(2, metricHammingUchar(reinterpret_cast(&a), reinterpret_cast(&c), 1)); } BOOST_AUTO_TEST_CASE(Metric_HAMMING_BITSET_RAW_MEMORY_64BITS) { - const int COUNT = 4; - std::bitset<64> tab[COUNT]; - // Zeros - for(int i = 0; i < 64; ++i) { tab[0][i] = 0; } - // 0101 ... - for(int i = 0; i < 64; ++i) { tab[1][i] = i%2 == 0; } - // 00110011... - for(int i = 0; i < 64; ++i) { tab[2][i] = (i/2)%2 == 0; } - // 000111000111... - for(int i = 0; i < 64; ++i) { tab[3][i] = (i/3)%2 == 0; } - - // ground truth hamming distances between bit array - const double gtDist[] = - {0, 32, 32, 33, 32, - 0, 32, 21, 32, 32, - 0, 31, 33, 21, 31, 0}; - - HammingBitSet > metricHammingBitSet; - Hamming< unsigned char > metricHamming; - size_t cpt = 0; - for (size_t i = 0; i < COUNT; ++i) - { - for (size_t j = 0; j < COUNT; ++j) + const int COUNT = 4; + std::bitset<64> tab[COUNT]; + // Zeros + for (int i = 0; i < 64; ++i) + { + tab[0][i] = 0; + } + // 0101 ... + for (int i = 0; i < 64; ++i) + { + tab[1][i] = i % 2 == 0; + } + // 00110011... + for (int i = 0; i < 64; ++i) + { + tab[2][i] = (i / 2) % 2 == 0; + } + // 000111000111... + for (int i = 0; i < 64; ++i) { - BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[i],&tab[j], 1)); - BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint64_t*)&tab[i],(uint64_t*)&tab[j], sizeof(uint64_t))); - //Check distance symmetry - BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[j],&tab[i], 1)); - BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint64_t*)&tab[j],(uint64_t*)&tab[i], sizeof(uint64_t))); - ++cpt; + tab[3][i] = (i / 3) % 2 == 0; + } + + // ground truth hamming distances between bit array + const double gtDist[] = {0, 32, 32, 33, 32, 0, 32, 21, 32, 32, 0, 31, 33, 21, 31, 0}; + + HammingBitSet> metricHammingBitSet; + Hamming metricHamming; + size_t cpt = 0; + for (size_t i = 0; i < COUNT; ++i) + { + for (size_t j = 0; j < COUNT; ++j) + { + BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[i], &tab[j], 1)); + BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint64_t*)&tab[i], (uint64_t*)&tab[j], sizeof(uint64_t))); + // Check distance symmetry + BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[j], &tab[i], 1)); + BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint64_t*)&tab[j], (uint64_t*)&tab[i], sizeof(uint64_t))); + ++cpt; + } } - } } BOOST_AUTO_TEST_CASE(Metric_HAMMING_BITSET_RAW_MEMORY_32BITS) { - const int COUNT = 4; - std::bitset<32> tab[COUNT]; - // Zeros - for(int i = 0; i < 32; ++i) { tab[0][i] = 0; } - // 0101 ... - for(int i = 0; i < 32; ++i) { tab[1][i] = i%2 == 0; } - // 00110011... - for(int i = 0; i < 32; ++i) { tab[2][i] = (i/2)%2 == 0; } - // 000111000111... - for(int i = 0; i < 32; ++i) { tab[3][i] = (i/3)%2 == 0; } - - // ground truth hamming distances between bit array - const double gtDist[] = - {0, 16, 16, 17, 16, - 0, 16, 11, 16, 16, - 0, 17, 17, 11, 17, 0}; - - HammingBitSet > metricHammingBitSet; - Hamming< unsigned char > metricHamming; - size_t cpt = 0; - for (size_t i = 0; i < COUNT; ++i) - { - for (size_t j = 0; j < COUNT; ++j) + const int COUNT = 4; + std::bitset<32> tab[COUNT]; + // Zeros + for (int i = 0; i < 32; ++i) + { + tab[0][i] = 0; + } + // 0101 ... + for (int i = 0; i < 32; ++i) + { + tab[1][i] = i % 2 == 0; + } + // 00110011... + for (int i = 0; i < 32; ++i) + { + tab[2][i] = (i / 2) % 2 == 0; + } + // 000111000111... + for (int i = 0; i < 32; ++i) + { + tab[3][i] = (i / 3) % 2 == 0; + } + + // ground truth hamming distances between bit array + const double gtDist[] = {0, 16, 16, 17, 16, 0, 16, 11, 16, 16, 0, 17, 17, 11, 17, 0}; + + HammingBitSet> metricHammingBitSet; + Hamming metricHamming; + size_t cpt = 0; + for (size_t i = 0; i < COUNT; ++i) { - BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[i],&tab[j], 1)); - BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint32_t*)&tab[i],(uint32_t*)&tab[j], sizeof(uint32_t))); - //Check distance symmetry - BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[j],&tab[i], 1)); - BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint32_t*)&tab[j],(uint32_t*)&tab[i], sizeof(uint32_t))); - ++cpt; + for (size_t j = 0; j < COUNT; ++j) + { + BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[i], &tab[j], 1)); + BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint32_t*)&tab[i], (uint32_t*)&tab[j], sizeof(uint32_t))); + // Check distance symmetry + BOOST_CHECK_EQUAL(gtDist[cpt], metricHammingBitSet(&tab[j], &tab[i], 1)); + BOOST_CHECK_EQUAL(gtDist[cpt], metricHamming((uint32_t*)&tab[j], (uint32_t*)&tab[i], sizeof(uint32_t))); + ++cpt; + } } - } } diff --git a/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.cpp b/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.cpp index 7c0f59f918..bf8532e735 100644 --- a/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.cpp +++ b/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.cpp @@ -16,51 +16,49 @@ namespace aliceVision { namespace feature { bool ImageDescriber_AKAZE_OCV::describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask) + std::unique_ptr& regions, + const image::Image* mask) { - cv::Mat img; - cv::eigen2cv(image.GetMat(), img); + cv::Mat img; + cv::eigen2cv(image.GetMat(), img); - std::vector< cv::KeyPoint > vec_keypoints; - cv::Mat m_desc; + std::vector vec_keypoints; + cv::Mat m_desc; - cv::Ptr extractor = cv::AKAZE::create(cv::AKAZE::DESCRIPTOR_KAZE); - extractor->detectAndCompute(img, cv::Mat(), vec_keypoints, m_desc); + cv::Ptr extractor = cv::AKAZE::create(cv::AKAZE::DESCRIPTOR_KAZE); + extractor->detectAndCompute(img, cv::Mat(), vec_keypoints, m_desc); - if (vec_keypoints.empty()) - { - return false; - } + if (vec_keypoints.empty()) + { + return false; + } - allocate(regions); + allocate(regions); - // Build alias to cached data - AKAZE_Float_Regions* regionsCasted = dynamic_cast(regions.get()); + // Build alias to cached data + AKAZE_Float_Regions* regionsCasted = dynamic_cast(regions.get()); - // Reserve some memory for faster keypoint saving - regionsCasted->Features().reserve(vec_keypoints.size()); - regionsCasted->Descriptors().reserve(vec_keypoints.size()); + // Reserve some memory for faster keypoint saving + regionsCasted->Features().reserve(vec_keypoints.size()); + regionsCasted->Descriptors().reserve(vec_keypoints.size()); - typedef Descriptor DescriptorT; - DescriptorT descriptor; - int cpt = 0; + typedef Descriptor DescriptorT; + DescriptorT descriptor; + int cpt = 0; - for(const auto& i_keypoint : vec_keypoints) - { - PointFeature feat(i_keypoint.pt.x, i_keypoint.pt.y, i_keypoint.size, i_keypoint.angle); - regionsCasted->Features().push_back(feat); + for (const auto& i_keypoint : vec_keypoints) + { + PointFeature feat(i_keypoint.pt.x, i_keypoint.pt.y, i_keypoint.size, i_keypoint.angle); + regionsCasted->Features().push_back(feat); - memcpy(descriptor.getData(), - m_desc.ptr(cpt), - DescriptorT::static_size*sizeof(DescriptorT::bin_type)); + memcpy(descriptor.getData(), m_desc.ptr(cpt), DescriptorT::static_size * sizeof(DescriptorT::bin_type)); - regionsCasted->Descriptors().push_back(descriptor); - ++cpt; - } + regionsCasted->Descriptors().push_back(descriptor); + ++cpt; + } - return true; + return true; } -} //namespace feature -} //namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.hpp b/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.hpp index 33093e5b4c..f342e1cb06 100644 --- a/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.hpp +++ b/src/aliceVision/feature/openCV/ImageDescriber_AKAZE_OCV.hpp @@ -17,10 +17,10 @@ namespace aliceVision { namespace image { -template +template class Image; -} //namespace image +} // namespace image namespace feature { @@ -30,84 +30,63 @@ namespace feature { */ class ImageDescriber_AKAZE_OCV : public ImageDescriber { -public: - + public: ImageDescriber_AKAZE_OCV() = default; - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return false; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - return EImageDescriberType::AKAZE_OCV; - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return 3 * width * height * sizeof(unsigned char); - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - * @return True if configuration succeed. (here always false) - */ - void setConfigurationPreset(ConfigurationPreset preset) override - { - ALICEVISION_LOG_DEBUG("Image describer preset ignored for AKAZE_OCV."); - } - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = NULL) override; - - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr ®ions) const override - { - regions.reset( new AKAZE_Float_Regions ); - } - - ~ImageDescriber_AKAZE_OCV() override = default; - + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return false; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override { return EImageDescriberType::AKAZE_OCV; } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override { return 3 * width * height * sizeof(unsigned char); } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + * @return True if configuration succeed. (here always false) + */ + void setConfigurationPreset(ConfigurationPreset preset) override { ALICEVISION_LOG_DEBUG("Image describer preset ignored for AKAZE_OCV."); } + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, + std::unique_ptr& regions, + const image::Image* mask = NULL) override; + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { regions.reset(new AKAZE_Float_Regions); } + + ~ImageDescriber_AKAZE_OCV() override = default; }; -} //namespace feature -} //namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.cpp b/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.cpp index beb7c74428..6d6130af46 100644 --- a/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.cpp +++ b/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.cpp @@ -20,161 +20,157 @@ namespace feature { void SIFT_openCV_Params::setConfigurationPreset(EImageDescriberPreset preset) { - switch(preset) + switch (preset) { - case EImageDescriberPreset::LOW: - contrastThreshold = 0.01; - maxTotalKeypoints = 1000; - break; - case EImageDescriberPreset::MEDIUM: - contrastThreshold = 0.005; - maxTotalKeypoints = 5000; - break; - case EImageDescriberPreset::NORMAL: - contrastThreshold = 0.005; - edgeThreshold = 15; - maxTotalKeypoints = 10000; - break; - case EImageDescriberPreset::HIGH: - contrastThreshold = 0.005; - edgeThreshold = 20; - maxTotalKeypoints = 20000; - break; - case EImageDescriberPreset::ULTRA: - contrastThreshold = 0.005; - edgeThreshold = 20; - maxTotalKeypoints = 40000; - break; - default: - throw std::out_of_range("Invalid image describer preset enum"); + case EImageDescriberPreset::LOW: + contrastThreshold = 0.01; + maxTotalKeypoints = 1000; + break; + case EImageDescriberPreset::MEDIUM: + contrastThreshold = 0.005; + maxTotalKeypoints = 5000; + break; + case EImageDescriberPreset::NORMAL: + contrastThreshold = 0.005; + edgeThreshold = 15; + maxTotalKeypoints = 10000; + break; + case EImageDescriberPreset::HIGH: + contrastThreshold = 0.005; + edgeThreshold = 20; + maxTotalKeypoints = 20000; + break; + case EImageDescriberPreset::ULTRA: + contrastThreshold = 0.005; + edgeThreshold = 20; + maxTotalKeypoints = 40000; + break; + default: + throw std::out_of_range("Invalid image describer preset enum"); } } bool ImageDescriber_SIFT_openCV::describe(const image::Image& image, - std::unique_ptr ®ions, - const image::Image * mask) + std::unique_ptr& regions, + const image::Image* mask) { - // Convert for opencv - cv::Mat img; - cv::eigen2cv(image.GetMat(), img); - - // Create a SIFT detector - std::vector< cv::KeyPoint > v_keypoints; - cv::Mat m_desc; - std::size_t maxDetect = 0; //< No max value by default - if(_params.maxTotalKeypoints) - if(!_params.gridSize) //< If no grid filtering, use opencv to limit the number of features - maxDetect = _params.maxTotalKeypoints; - - cv::Ptr siftdetector = cv::xfeatures2d::SIFT::create(maxDetect, - _params.nOctaveLayers, - _params.contrastThreshold, - _params.edgeThreshold, - _params.sigma); - - // Detect SIFT keypoints - auto detect_start = std::chrono::steady_clock::now(); - siftdetector->detect(img, v_keypoints); - auto detect_end = std::chrono::steady_clock::now(); - auto detect_elapsed = std::chrono::duration_cast(detect_end - detect_start); - - ALICEVISION_LOG_TRACE("SIFT: contrastThreshold: " << _params.contrastThreshold << ", edgeThreshold: " << _params.edgeThreshold << std::endl); - ALICEVISION_LOG_TRACE("Detect SIFT: " << detect_elapsed.count() << " milliseconds." << std::endl); - ALICEVISION_LOG_TRACE("Image size: " << img.cols << " x " << img.rows << std::endl); - ALICEVISION_LOG_TRACE("Grid size: " << _params.gridSize << ", maxTotalKeypoints: " << _params.maxTotalKeypoints << std::endl); - ALICEVISION_LOG_TRACE("Number of detected features: " << v_keypoints.size() << std::endl); - - // cv::KeyPoint::response: the response by which the most strong keypoints have been selected. - // Can be used for the further sorting or subsampling. - std::sort(v_keypoints.begin(), v_keypoints.end(), [](const cv::KeyPoint& a, const cv::KeyPoint& b) { return a.size > b.size; }); - - // Grid filtering of the keypoints to ensure a global repartition - if(_params.gridSize && _params.maxTotalKeypoints) - { - // Only filter features if we have more features than the maxTotalKeypoints - if(v_keypoints.size() > _params.maxTotalKeypoints) + // Convert for opencv + cv::Mat img; + cv::eigen2cv(image.GetMat(), img); + + // Create a SIFT detector + std::vector v_keypoints; + cv::Mat m_desc; + std::size_t maxDetect = 0; //< No max value by default + if (_params.maxTotalKeypoints) + if (!_params.gridSize) //< If no grid filtering, use opencv to limit the number of features + maxDetect = _params.maxTotalKeypoints; + + cv::Ptr siftdetector = + cv::xfeatures2d::SIFT::create(maxDetect, _params.nOctaveLayers, _params.contrastThreshold, _params.edgeThreshold, _params.sigma); + + // Detect SIFT keypoints + auto detect_start = std::chrono::steady_clock::now(); + siftdetector->detect(img, v_keypoints); + auto detect_end = std::chrono::steady_clock::now(); + auto detect_elapsed = std::chrono::duration_cast(detect_end - detect_start); + + ALICEVISION_LOG_TRACE("SIFT: contrastThreshold: " << _params.contrastThreshold << ", edgeThreshold: " << _params.edgeThreshold << std::endl); + ALICEVISION_LOG_TRACE("Detect SIFT: " << detect_elapsed.count() << " milliseconds." << std::endl); + ALICEVISION_LOG_TRACE("Image size: " << img.cols << " x " << img.rows << std::endl); + ALICEVISION_LOG_TRACE("Grid size: " << _params.gridSize << ", maxTotalKeypoints: " << _params.maxTotalKeypoints << std::endl); + ALICEVISION_LOG_TRACE("Number of detected features: " << v_keypoints.size() << std::endl); + + // cv::KeyPoint::response: the response by which the most strong keypoints have been selected. + // Can be used for the further sorting or subsampling. + std::sort(v_keypoints.begin(), v_keypoints.end(), [](const cv::KeyPoint& a, const cv::KeyPoint& b) { return a.size > b.size; }); + + // Grid filtering of the keypoints to ensure a global repartition + if (_params.gridSize && _params.maxTotalKeypoints) { - std::vector< cv::KeyPoint > filtered_keypoints; - std::vector< cv::KeyPoint > rejected_keypoints; - filtered_keypoints.reserve(std::min(v_keypoints.size(), _params.maxTotalKeypoints)); - rejected_keypoints.reserve(v_keypoints.size()); - - cv::Mat countFeatPerCell(_params.gridSize, _params.gridSize, cv::DataType::type, cv::Scalar(0)); - const std::size_t keypointsPerCell = _params.maxTotalKeypoints / countFeatPerCell.total(); - const double regionWidth = image.Width() / double(countFeatPerCell.cols); - const double regionHeight = image.Height() / double(countFeatPerCell.rows); - - ALICEVISION_LOG_TRACE ("Grid filtering -- keypointsPerCell: " << keypointsPerCell - << ", regionWidth: " << regionWidth - << ", regionHeight: " << regionHeight << std::endl); - - for(const cv::KeyPoint& keypoint: v_keypoints) - { - const std::size_t cellX = std::min(std::size_t(keypoint.pt.x / regionWidth), _params.gridSize); - const std::size_t cellY = std::min(std::size_t(keypoint.pt.y / regionHeight), _params.gridSize); - // std::cout << "- keypoint.pt.x: " << keypoint.pt.x << ", keypoint.pt.y: " << keypoint.pt.y << std::endl; - // std::cout << "- cellX: " << cellX << ", cellY: " << cellY << std::endl; - // std::cout << "- countFeatPerCell: " << countFeatPerCell << std::endl; - // std::cout << "- gridSize: " << _params.gridSize << std::endl; - - const int count = countFeatPerCell.at(cellX, cellY); - countFeatPerCell.at(cellX, cellY) = count + 1; - if(count < keypointsPerCell) - filtered_keypoints.push_back(keypoint); - else - rejected_keypoints.push_back(keypoint); - } - // If we don't have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in the grid for example). - // We add the best other ones, without repartition constraint. - if( filtered_keypoints.size() < _params.maxTotalKeypoints ) - { - const std::size_t remainingElements = std::min(rejected_keypoints.size(), _params.maxTotalKeypoints - filtered_keypoints.size()); - ALICEVISION_LOG_TRACE("Grid filtering -- Copy remaining points: " << remainingElements << std::endl); - filtered_keypoints.insert(filtered_keypoints.end(), rejected_keypoints.begin(), rejected_keypoints.begin() + remainingElements); - } - - v_keypoints.swap(filtered_keypoints); + // Only filter features if we have more features than the maxTotalKeypoints + if (v_keypoints.size() > _params.maxTotalKeypoints) + { + std::vector filtered_keypoints; + std::vector rejected_keypoints; + filtered_keypoints.reserve(std::min(v_keypoints.size(), _params.maxTotalKeypoints)); + rejected_keypoints.reserve(v_keypoints.size()); + + cv::Mat countFeatPerCell(_params.gridSize, _params.gridSize, cv::DataType::type, cv::Scalar(0)); + const std::size_t keypointsPerCell = _params.maxTotalKeypoints / countFeatPerCell.total(); + const double regionWidth = image.Width() / double(countFeatPerCell.cols); + const double regionHeight = image.Height() / double(countFeatPerCell.rows); + + ALICEVISION_LOG_TRACE("Grid filtering -- keypointsPerCell: " << keypointsPerCell << ", regionWidth: " << regionWidth + << ", regionHeight: " << regionHeight << std::endl); + + for (const cv::KeyPoint& keypoint : v_keypoints) + { + const std::size_t cellX = std::min(std::size_t(keypoint.pt.x / regionWidth), _params.gridSize); + const std::size_t cellY = std::min(std::size_t(keypoint.pt.y / regionHeight), _params.gridSize); + // std::cout << "- keypoint.pt.x: " << keypoint.pt.x << ", keypoint.pt.y: " << keypoint.pt.y << std::endl; + // std::cout << "- cellX: " << cellX << ", cellY: " << cellY << std::endl; + // std::cout << "- countFeatPerCell: " << countFeatPerCell << std::endl; + // std::cout << "- gridSize: " << _params.gridSize << std::endl; + + const int count = countFeatPerCell.at(cellX, cellY); + countFeatPerCell.at(cellX, cellY) = count + 1; + if (count < keypointsPerCell) + filtered_keypoints.push_back(keypoint); + else + rejected_keypoints.push_back(keypoint); + } + // If we don't have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in the grid for example). + // We add the best other ones, without repartition constraint. + if (filtered_keypoints.size() < _params.maxTotalKeypoints) + { + const std::size_t remainingElements = std::min(rejected_keypoints.size(), _params.maxTotalKeypoints - filtered_keypoints.size()); + ALICEVISION_LOG_TRACE("Grid filtering -- Copy remaining points: " << remainingElements << std::endl); + filtered_keypoints.insert(filtered_keypoints.end(), rejected_keypoints.begin(), rejected_keypoints.begin() + remainingElements); + } + + v_keypoints.swap(filtered_keypoints); + } } - } - ALICEVISION_LOG_TRACE("Number of features: " << v_keypoints.size() << std::endl); - - // Compute SIFT descriptors - auto desc_start = std::chrono::steady_clock::now(); - siftdetector->compute(img, v_keypoints, m_desc); - auto desc_end = std::chrono::steady_clock::now(); - auto desc_elapsed = std::chrono::duration_cast(desc_end - desc_start); - ALICEVISION_LOG_TRACE("Compute descriptors: " << desc_elapsed.count() << " milliseconds." << std::endl); - - allocate(regions); - - // Build alias to cached data - SIFT_Regions * regionsCasted = dynamic_cast(regions.get()); - // Reserve some memory for faster keypoint saving - regionsCasted->Features().reserve(v_keypoints.size()); - regionsCasted->Descriptors().reserve(v_keypoints.size()); - - // Prepare a column vector with the sum of each descriptor - cv::Mat m_siftsum; - cv::reduce(m_desc, m_siftsum, 1, cv::REDUCE_SUM); - - // Copy keypoints and descriptors in the regions - int cpt = 0; - for(const auto& i_kp : v_keypoints) - { - PointFeature feat(i_kp.pt.x, i_kp.pt.y, i_kp.size, i_kp.angle); - regionsCasted->Features().push_back(feat); - - Descriptor desc; - for(std::size_t j = 0; j < 128; ++j) + ALICEVISION_LOG_TRACE("Number of features: " << v_keypoints.size() << std::endl); + + // Compute SIFT descriptors + auto desc_start = std::chrono::steady_clock::now(); + siftdetector->compute(img, v_keypoints, m_desc); + auto desc_end = std::chrono::steady_clock::now(); + auto desc_elapsed = std::chrono::duration_cast(desc_end - desc_start); + ALICEVISION_LOG_TRACE("Compute descriptors: " << desc_elapsed.count() << " milliseconds." << std::endl); + + allocate(regions); + + // Build alias to cached data + SIFT_Regions* regionsCasted = dynamic_cast(regions.get()); + // Reserve some memory for faster keypoint saving + regionsCasted->Features().reserve(v_keypoints.size()); + regionsCasted->Descriptors().reserve(v_keypoints.size()); + + // Prepare a column vector with the sum of each descriptor + cv::Mat m_siftsum; + cv::reduce(m_desc, m_siftsum, 1, cv::REDUCE_SUM); + + // Copy keypoints and descriptors in the regions + int cpt = 0; + for (const auto& i_kp : v_keypoints) { - desc[j] = static_cast(512.0*sqrt(m_desc.at(cpt, j)/m_siftsum.at(cpt, 0))); + PointFeature feat(i_kp.pt.x, i_kp.pt.y, i_kp.size, i_kp.angle); + regionsCasted->Features().push_back(feat); + + Descriptor desc; + for (std::size_t j = 0; j < 128; ++j) + { + desc[j] = static_cast(512.0 * sqrt(m_desc.at(cpt, j) / m_siftsum.at(cpt, 0))); + } + regionsCasted->Descriptors().push_back(desc); + ++cpt; } - regionsCasted->Descriptors().push_back(desc); - ++cpt; - } - return true; + return true; } -} //namespace feature -} //namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.hpp b/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.hpp index 864dfdcd7b..806d1ea34a 100644 --- a/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.hpp +++ b/src/aliceVision/feature/openCV/ImageDescriber_SIFT_OCV.hpp @@ -15,32 +15,31 @@ namespace aliceVision { namespace image { -template +template class Image; -} //namespace image +} // namespace image namespace feature { class SIFT_openCV_Params { -public: - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - * @return True if configuration succeed. - */ - void setConfigurationPreset(EImageDescriberPreset preset); - - /// Parameters - std::size_t gridSize = 4; - std::size_t maxTotalKeypoints = 1000; - int nOctaveLayers = 6; //< default opencv value is 3 - double contrastThreshold = 0.04; //< default opencv value is 0.04 - double edgeThreshold = 10; - double sigma = 1.6; - //bool rootSift = true; + public: + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + * @return True if configuration succeed. + */ + void setConfigurationPreset(EImageDescriberPreset preset); + + /// Parameters + std::size_t gridSize = 4; + std::size_t maxTotalKeypoints = 1000; + int nOctaveLayers = 6; //< default opencv value is 3 + double contrastThreshold = 0.04; //< default opencv value is 0.04 + double edgeThreshold = 10; + double sigma = 1.6; + // bool rootSift = true; }; /** @@ -49,81 +48,65 @@ class SIFT_openCV_Params */ class ImageDescriber_SIFT_openCV : public ImageDescriber { -public: - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return false; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - return EImageDescriberType::SIFT_OCV; - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return 3 * width * height * sizeof(unsigned char) + (_params.maxTotalKeypoints * 128 * sizeof(unsigned char)); - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - * @return True if configuration succeed. - */ - void setConfigurationPreset(EImageDescriberPreset preset) override - { - _params.setConfigurationPreset(preset); - } - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = NULL) override; - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr ®ions) const override - { - regions.reset( new SIFT_Regions ); - } - -private: - SIFT_openCV_Params _params; + public: + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return false; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override { return EImageDescriberType::SIFT_OCV; } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override + { + return 3 * width * height * sizeof(unsigned char) + (_params.maxTotalKeypoints * 128 * sizeof(unsigned char)); + } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + * @return True if configuration succeed. + */ + void setConfigurationPreset(EImageDescriberPreset preset) override { _params.setConfigurationPreset(preset); } + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, + std::unique_ptr& regions, + const image::Image* mask = NULL) override; + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { regions.reset(new SIFT_Regions); } + + private: + SIFT_openCV_Params _params; }; -} //namespace feature -} //namespace openMV +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/regionsFactory.hpp b/src/aliceVision/feature/regionsFactory.hpp index b6818b63af..58e98a392c 100644 --- a/src/aliceVision/feature/regionsFactory.hpp +++ b/src/aliceVision/feature/regionsFactory.hpp @@ -14,20 +14,20 @@ namespace aliceVision { namespace feature { /// Define the classic SIFT Keypoint -using SIFT_Regions = ScalarRegions; +using SIFT_Regions = ScalarRegions; /// Define the classic SIFT features using float representation -using SIFT_Float_Regions = ScalarRegions; +using SIFT_Float_Regions = ScalarRegions; /// Define the classic CCTag Keypoint -using CCTAG_Regions = ScalarRegions; +using CCTAG_Regions = ScalarRegions; /// Define the AprilTag tag16h5 family Keypoint (30 instance with 5 points each) -using APRILTAG_Regions = ScalarRegions; +using APRILTAG_Regions = ScalarRegions; /// Define the AKAZE Keypoint (with a float descriptor) -using AKAZE_Float_Regions = ScalarRegions; +using AKAZE_Float_Regions = ScalarRegions; /// Define the AKAZE Keypoint (with a LIOP descriptor) -using AKAZE_Liop_Regions = ScalarRegions; +using AKAZE_Liop_Regions = ScalarRegions; /// Define the AKAZE Keypoint (with a binary descriptor saved in an uchar array) using AKAZE_BinaryRegions = BinaryRegions<64>; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.cpp b/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.cpp index b4883914c9..e26d5cced2 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.cpp +++ b/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.cpp @@ -11,7 +11,8 @@ #include #include -extern "C" { +extern "C" +{ #include #include } @@ -22,7 +23,6 @@ extern "C" { namespace aliceVision { namespace feature { - void DspSiftParams::setPreset(ConfigurationPreset preset) { SiftParams::setPreset(preset); @@ -36,7 +36,7 @@ void DspSiftParams::setPreset(ConfigurationPreset preset) // Although the more samples the merrier, three size samples are sufficient to outperform ordinary SIFT, // and improvement beyond 10 samples is minimal. Additional samples do not further increase the mean average // precision, but incur more computational cost." - switch(preset.quality) + switch (preset.quality) { case EFeatureQuality::LOW: { @@ -67,9 +67,12 @@ void DspSiftParams::setPreset(ConfigurationPreset preset) } } -template -bool extractDSPSIFT(const image::Image& image, std::unique_ptr& regions, const DspSiftParams& params, - bool orientation, const image::Image* mask) +template +bool extractDSPSIFT(const image::Image& image, + std::unique_ptr& regions, + const DspSiftParams& params, + bool orientation, + const image::Image* mask) { const int w = image.Width(), h = image.Height(); // Setup covariant SIFT detector. @@ -83,12 +86,12 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& vl_covdet_set_edge_threshold(covdet.get(), params._edgeThreshold); // vl_covdet_set_non_extrema_suppression_threshold(covdet.get(), 0.0f); // vl_covdet default is 0.5 - switch(params._contrastFiltering) + switch (params._contrastFiltering) { case EFeatureConstrastFiltering::Static: { ALICEVISION_LOG_TRACE("SIFT constrastTreshold Static: " << params._peakThreshold); - if(params._peakThreshold >= 0) + if (params._peakThreshold >= 0) { vl_covdet_set_peak_threshold(covdet.get(), params._peakThreshold / params._numScales); } @@ -126,11 +129,11 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& const double kBoundaryMargin = 2.0; vl_covdet_drop_features_outside(covdet.get(), kBoundaryMargin); - if(orientation) + if (orientation) { VlCovDetBuffer internalBuffer; vl_covdetbuffer_init(&internalBuffer); - if(params.estimateAffineShape) + if (params.estimateAffineShape) { vl_covdet_extract_affine_shape(covdet.get(), &internalBuffer); } @@ -160,31 +163,31 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& // Sorting the extracted features according to their scale { std::iota(indexSort.begin(), indexSort.end(), 0); - if(params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps) + if (params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps) { std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { - const int scaleA = int(log2(features[a].sigma) * 3.0f); // 3 scale steps per octave + const int scaleA = int(log2(features[a].sigma) * 3.0f); // 3 scale steps per octave const int scaleB = int(log2(features[b].sigma) * 3.0f); - if(scaleA == scaleB) + if (scaleA == scaleB) { return features[a].peakScore > features[b].peakScore; } return scaleA > scaleB; }); } - else if(params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaveSteps) + else if (params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaveSteps) { std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { - const int scaleA = int(log2(features[a].sigma)); // 3 scale steps per octave + const int scaleA = int(log2(features[a].sigma)); // 3 scale steps per octave const int scaleB = int(log2(features[b].sigma)); - if(scaleA == scaleB) + if (scaleA == scaleB) { return features[a].peakScore > features[b].peakScore; } return scaleA > scaleB; }); } - else if(params._contrastFiltering == EFeatureConstrastFiltering::GridSort) + else if (params._contrastFiltering == EFeatureConstrastFiltering::GridSort) { std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { return features[a].sigma * features[a].peakScore > features[b].sigma * features[b].peakScore; @@ -193,52 +196,49 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& else { // sort from largest scales to smallest ones - std::sort(indexSort.begin(), indexSort.end(), - [&](std::size_t a, std::size_t b) { return features[a].sigma > features[b].sigma; }); + std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { return features[a].sigma > features[b].sigma; }); } } - if(params._maxTotalKeypoints && params._contrastFiltering == EFeatureConstrastFiltering::NonExtremaFiltering) + if (params._maxTotalKeypoints && params._contrastFiltering == EFeatureConstrastFiltering::NonExtremaFiltering) { // Only filter features if we have more features than the maxTotalKeypoints - if(indexSort.size() > params._maxTotalKeypoints) + if (indexSort.size() > params._maxTotalKeypoints) { std::vector radiusMaxima(indexSort.size(), std::numeric_limits::max()); - for(IndexT ii = 0; ii < indexSort.size(); ++ii) + for (IndexT ii = 0; ii < indexSort.size(); ++ii) { const IndexT i = indexSort[ii]; const auto& keypointI = features[i]; - for(IndexT jj = 0; jj < indexSort.size(); ++jj) + for (IndexT jj = 0; jj < indexSort.size(); ++jj) { const IndexT j = indexSort[jj]; const auto& keypointJ = features[j]; - if(featuresPeakValue[j] > featuresPeakValue[i]) + if (featuresPeakValue[j] > featuresPeakValue[i]) { const float dx = (keypointJ.frame.x - keypointI.frame.x); const float dy = (keypointJ.frame.y - keypointI.frame.y); const float radius = dx * dx + dy * dy; - if(radius < radiusMaxima[i]) + if (radius < radiusMaxima[i]) radiusMaxima[i] = radius; } } } std::size_t maxNbKeypoints = std::min(params._maxTotalKeypoints, indexSort.size()); - std::partial_sort(indexSort.begin(), indexSort.begin() + maxNbKeypoints, - indexSort.end(), - [&](int a, int b) { return radiusMaxima[a] > radiusMaxima[b]; }); + std::partial_sort(indexSort.begin(), indexSort.begin() + maxNbKeypoints, indexSort.end(), [&](int a, int b) { + return radiusMaxima[a] > radiusMaxima[b]; + }); indexSort.resize(maxNbKeypoints); - ALICEVISION_LOG_TRACE( - "DSPSIFT Features: before: " << nbFeatures - << ", after grid filtering: " << indexSort.size()); + ALICEVISION_LOG_TRACE("DSPSIFT Features: before: " << nbFeatures << ", after grid filtering: " << indexSort.size()); } } // Grid filtering of the keypoints to ensure a global repartition - else if(params._gridSize && params._maxTotalKeypoints && (params._contrastFiltering != EFeatureConstrastFiltering::Static)) + else if (params._gridSize && params._maxTotalKeypoints && (params._contrastFiltering != EFeatureConstrastFiltering::Static)) { // Only filter features if we have more features than the maxTotalKeypoints - if(nbFeatures > params._maxTotalKeypoints) + if (nbFeatures > params._maxTotalKeypoints) { std::vector filteredIndexes; std::vector rejectedIndexes; @@ -251,7 +251,7 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& const double regionWidth = image.Width() / double(params._gridSize); const double regionHeight = image.Height() / double(params._gridSize); - for(IndexT ii = 0; ii < indexSort.size(); ++ii) + for (IndexT ii = 0; ii < indexSort.size(); ++ii) { const IndexT i = indexSort[ii]; const auto& keypoint = features[i]; @@ -262,30 +262,27 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& std::size_t& count = countFeatPerCell[cellX * params._gridSize + cellY]; ++count; - if(count < keypointsPerCell) + if (count < keypointsPerCell) filteredIndexes.push_back(i); else rejectedIndexes.push_back(i); } // If we do not have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in // the grid for example). We add the best other ones, without repartition constraint. - if(filteredIndexes.size() < params._maxTotalKeypoints) + if (filteredIndexes.size() < params._maxTotalKeypoints) { - const std::size_t remainingElements = - std::min(rejectedIndexes.size(), params._maxTotalKeypoints - filteredIndexes.size()); + const std::size_t remainingElements = std::min(rejectedIndexes.size(), params._maxTotalKeypoints - filteredIndexes.size()); ALICEVISION_LOG_TRACE("Grid filtering -- Copy remaining points: " << remainingElements); - filteredIndexes.insert(filteredIndexes.end(), rejectedIndexes.begin(), - rejectedIndexes.begin() + remainingElements); + filteredIndexes.insert(filteredIndexes.end(), rejectedIndexes.begin(), rejectedIndexes.begin() + remainingElements); } indexSort.swap(filteredIndexes); - ALICEVISION_LOG_TRACE("SIFT Features: before: " << nbFeatures - << ", after grid filtering: " << filteredIndexes.size()); + ALICEVISION_LOG_TRACE("SIFT Features: before: " << nbFeatures << ", after grid filtering: " << filteredIndexes.size()); } } - else if(params._maxTotalKeypoints) + else if (params._maxTotalKeypoints) { // Retrieve extracted features - if(indexSort.size() > params._maxTotalKeypoints) + if (indexSort.size() > params._maxTotalKeypoints) indexSort.resize(params._maxTotalKeypoints); } @@ -294,8 +291,7 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& regionsCasted->Features().resize(indexSort.size()); regionsCasted->Descriptors().resize(indexSort.size()); - std::unique_ptr sift(vl_sift_new(16, 16, 1, params._numScales, 0), - &vl_sift_delete); + std::unique_ptr sift(vl_sift_new(16, 16, 1, params._numScales, 0), &vl_sift_delete); // All constant parameters const size_t kPatchResolution = 15; @@ -308,7 +304,7 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& float dspMinScale = 1; float dspScaleStep = 0; int dspNumScales = 1; - if(params.domainSizePooling) + if (params.domainSizePooling) { dspMinScale = params.dspMinScale; dspScaleStep = (params.dspMaxScale - params.dspMinScale) / params.dspNumScales; @@ -324,12 +320,12 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& std::vector patchXY(2 * kPatchSide * kPatchSide); #pragma omp for - for(int oIndex = 0; oIndex < indexSort.size(); ++oIndex) + for (int oIndex = 0; oIndex < indexSort.size(); ++oIndex) { const int iIndex = indexSort[oIndex]; const auto& inFeat = features[iIndex]; - for(int s = 0; s < dspNumScales; ++s) + for (int s = 0; s < dspNumScales; ++s) { const double dspScale = dspMinScale + s * dspScaleStep; @@ -339,18 +335,24 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& frameAtScale.a21 *= dspScale; frameAtScale.a22 *= dspScale; - vl_covdet_extract_patch_for_frame(covdet.get(), patch.data(), kPatchResolution, &internalBuffer, - kPatchRelativeExtent, kPatchRelativeSmoothing, frameAtScale); + vl_covdet_extract_patch_for_frame( + covdet.get(), patch.data(), kPatchResolution, &internalBuffer, kPatchRelativeExtent, kPatchRelativeSmoothing, frameAtScale); - vl_imgradient_polar_f(patchXY.data(), patchXY.data() + 1, 2, 2 * kPatchSide, patch.data(), - kPatchSide, kPatchSide, kPatchSide); + vl_imgradient_polar_f(patchXY.data(), patchXY.data() + 1, 2, 2 * kPatchSide, patch.data(), kPatchSide, kPatchSide, kPatchSide); - vl_sift_calc_raw_descriptor(sift.get(), patchXY.data(), descriptorsOverDspScales.row(s).data(), - kPatchSide, kPatchSide, kPatchResolution, kPatchResolution, kSigma, /*angle0=*/0); + vl_sift_calc_raw_descriptor(sift.get(), + patchXY.data(), + descriptorsOverDspScales.row(s).data(), + kPatchSide, + kPatchSide, + kPatchResolution, + kPatchResolution, + kSigma, + /*angle0=*/0); } Eigen::Matrix descriptor; - if(dspNumScales > 1) + if (dspNumScales > 1) { descriptor = descriptorsOverDspScales.colwise().mean(); } @@ -362,7 +364,7 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& // [a0 a2] [r1 -r2] // [a1 a3] [r2 r1] Vec2 nx; - nx(0) = inFeat.frame.a11; + nx(0) = inFeat.frame.a11; nx(1) = inFeat.frame.a21; nx.normalize(); double angle = std::atan2(nx(1), nx(0)); @@ -379,13 +381,17 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& return true; } -template bool extractDSPSIFT(const image::Image& image, std::unique_ptr& regions, - const DspSiftParams& params, bool orientation, const image::Image* mask); +template bool extractDSPSIFT(const image::Image& image, + std::unique_ptr& regions, + const DspSiftParams& params, + bool orientation, + const image::Image* mask); -template bool extractDSPSIFT(const image::Image& image, std::unique_ptr& regions, - const DspSiftParams& params, bool orientation, +template bool extractDSPSIFT(const image::Image& image, + std::unique_ptr& regions, + const DspSiftParams& params, + bool orientation, const image::Image* mask); - -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.hpp b/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.hpp index 518aca9039..0ef6668f56 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.hpp +++ b/src/aliceVision/feature/sift/ImageDescriber_DSPSIFT_vlfeat.hpp @@ -11,7 +11,8 @@ #include #include -extern "C" { +extern "C" +{ #include } @@ -32,10 +33,12 @@ struct DspSiftParams : public SiftParams void setPreset(ConfigurationPreset preset) override; }; - -template -bool extractDSPSIFT(const image::Image& image, std::unique_ptr& regions, const DspSiftParams& params, - bool orientation, const image::Image* mask); +template +bool extractDSPSIFT(const image::Image& image, + std::unique_ptr& regions, + const DspSiftParams& params, + bool orientation, + const image::Image* mask); /** * @brief Create an ImageDescriber interface for VLFeat SIFT feature extractor @@ -55,107 +58,83 @@ bool extractDSPSIFT(const image::Image& image, std::unique_ptr& */ class ImageDescriber_DSPSIFT_vlfeat : public ImageDescriber { -public: - explicit ImageDescriber_DSPSIFT_vlfeat(const DspSiftParams& params = DspSiftParams(), bool isOriented = true) - : ImageDescriber() - , _params(params) - , _isOriented(isOriented) - { - // Configure VLFeat - VLFeatInstance::initialize(); - } - - ~ImageDescriber_DSPSIFT_vlfeat() override - { - VLFeatInstance::destroy(); - } - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return true; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - return EImageDescriberType::DSPSIFT; - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return getMemoryConsumptionVLFeat(width, height, _params); - } - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - void setUpRight(bool upRight) override - { - _isOriented = !upRight; - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - void setConfigurationPreset(ConfigurationPreset preset) override - { - _params.setPreset(preset); - } - - /** - * @brief Detect regions on the float image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) override - { - return extractDSPSIFT(image, regions, _params, _isOriented, mask); - } - - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr& regions) const override - { - regions.reset(new SIFT_Regions); - } - -private: - DspSiftParams _params; - bool _isOriented; + public: + explicit ImageDescriber_DSPSIFT_vlfeat(const DspSiftParams& params = DspSiftParams(), bool isOriented = true) + : ImageDescriber(), + _params(params), + _isOriented(isOriented) + { + // Configure VLFeat + VLFeatInstance::initialize(); + } + + ~ImageDescriber_DSPSIFT_vlfeat() override { VLFeatInstance::destroy(); } + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return true; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override { return EImageDescriberType::DSPSIFT; } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override + { + return getMemoryConsumptionVLFeat(width, height, _params); + } + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + void setUpRight(bool upRight) override { _isOriented = !upRight; } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + void setConfigurationPreset(ConfigurationPreset preset) override { _params.setPreset(preset); } + + /** + * @brief Detect regions on the float image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) override + { + return extractDSPSIFT(image, regions, _params, _isOriented, mask); + } + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { regions.reset(new SIFT_Regions); } + + private: + DspSiftParams _params; + bool _isOriented; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_SIFT.hpp b/src/aliceVision/feature/sift/ImageDescriber_SIFT.hpp index 3b632f568a..e0a8dc4cd7 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_SIFT.hpp +++ b/src/aliceVision/feature/sift/ImageDescriber_SIFT.hpp @@ -11,8 +11,8 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_POPSIFT) -#include -#include + #include + #include #endif namespace aliceVision { @@ -26,153 +26,133 @@ namespace feature { */ class ImageDescriber_SIFT : public ImageDescriber { -public: - explicit ImageDescriber_SIFT(const SiftParams& params = SiftParams(), bool isOriented = true) - : _params(params) - , _isOriented(isOriented) - { + public: + explicit ImageDescriber_SIFT(const SiftParams& params = SiftParams(), bool isOriented = true) + : _params(params), + _isOriented(isOriented) + { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_POPSIFT) - setUseCuda(gpu::gpuSupportCUDA(3,0)); + setUseCuda(gpu::gpuSupportCUDA(3, 0)); #else - setUseCuda(false); + setUseCuda(false); #endif - } - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return _imageDescriberImpl->useCuda(); - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return _imageDescriberImpl->useFloatImage(); - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - return _imageDescriberImpl->getDescriberType(); - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return _imageDescriberImpl->getMemoryConsumption(width, height); - } - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - void setUpRight(bool upRight) override - { - _isOriented = !upRight; - } - - /** - * @brief Set if yes or no imageDescriber need to use cuda implementation - * @param[in] useCuda - */ - void setUseCuda(bool useCuda) override - { - if(_imageDescriberImpl != nullptr && this->useCuda() == useCuda) - return; + } -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_POPSIFT) - if(useCuda) + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return _imageDescriberImpl->useCuda(); } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return _imageDescriberImpl->useFloatImage(); } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override { return _imageDescriberImpl->getDescriberType(); } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override { - _imageDescriberImpl.release(); // release first to ensure that we don't create the new ImageDescriber before destroying the previous one - _imageDescriberImpl.reset(new ImageDescriber_SIFT_popSIFT(_params, _isOriented)); - return; + return _imageDescriberImpl->getMemoryConsumption(width, height); } + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + void setUpRight(bool upRight) override { _isOriented = !upRight; } + + /** + * @brief Set if yes or no imageDescriber need to use cuda implementation + * @param[in] useCuda + */ + void setUseCuda(bool useCuda) override + { + if (_imageDescriberImpl != nullptr && this->useCuda() == useCuda) + return; + +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_POPSIFT) + if (useCuda) + { + _imageDescriberImpl.release(); // release first to ensure that we don't create the new ImageDescriber before destroying the previous one + _imageDescriberImpl.reset(new ImageDescriber_SIFT_popSIFT(_params, _isOriented)); + return; + } #endif - _imageDescriberImpl.release(); // release first to ensure that we don't create the new ImageDescriber before destroying the previous one - _imageDescriberImpl.reset(new ImageDescriber_SIFT_vlfeat(_params, _isOriented)); - } - - /** - * @brief set the CUDA pipe - * @param[in] pipe The CUDA pipe id - */ - void setCudaPipe(int pipe) override - { - _imageDescriberImpl->setCudaPipe(pipe); - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - void setConfigurationPreset(ConfigurationPreset preset) override - { - _params.setPreset(preset); - _imageDescriberImpl->setConfigurationPreset(preset); - } - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) override - { - return _imageDescriberImpl->describe(image, regions, mask); - } - - /** - * @brief Detect regions on the float image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit gray image for keypoint filtering (optional). - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) override - { - return _imageDescriberImpl->describe(image, regions, mask); - } - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr& regions) const override - { - _imageDescriberImpl->allocate(regions); - } - - ~ImageDescriber_SIFT() override = default; - -private: - SiftParams _params; - std::unique_ptr _imageDescriberImpl = nullptr; - bool _isOriented = true; + _imageDescriberImpl.release(); // release first to ensure that we don't create the new ImageDescriber before destroying the previous one + _imageDescriberImpl.reset(new ImageDescriber_SIFT_vlfeat(_params, _isOriented)); + } + + /** + * @brief set the CUDA pipe + * @param[in] pipe The CUDA pipe id + */ + void setCudaPipe(int pipe) override { _imageDescriberImpl->setCudaPipe(pipe); } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + void setConfigurationPreset(ConfigurationPreset preset) override + { + _params.setPreset(preset); + _imageDescriberImpl->setConfigurationPreset(preset); + } + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, + std::unique_ptr& regions, + const image::Image* mask = nullptr) override + { + return _imageDescriberImpl->describe(image, regions, mask); + } + + /** + * @brief Detect regions on the float image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit gray image for keypoint filtering (optional). + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) override + { + return _imageDescriberImpl->describe(image, regions, mask); + } + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { _imageDescriberImpl->allocate(regions); } + + ~ImageDescriber_SIFT() override = default; + + private: + SiftParams _params; + std::unique_ptr _imageDescriberImpl = nullptr; + bool _isOriented = true; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.cpp b/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.cpp index f18b99c5a2..934fdcad5b 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.cpp +++ b/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.cpp @@ -25,82 +25,79 @@ std::atomic ImageDescriber_SIFT_popSIFT::_instanceCounter{0}; void ImageDescriber_SIFT_popSIFT::setConfigurationPreset(ConfigurationPreset preset) { _params.setPreset(preset); - _popSift.reset(nullptr); // reset by describe method + _popSift.reset(nullptr); // reset by describe method } bool ImageDescriber_SIFT_popSIFT::describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask) + std::unique_ptr& regions, + const image::Image* mask) { - if(_popSift == nullptr) - resetConfiguration(); + if (_popSift == nullptr) + resetConfiguration(); - std::unique_ptr job(_popSift->enqueue(image.Width(), image.Height(), &image(0,0))); - std::unique_ptr popFeatures(job->get()); + std::unique_ptr job(_popSift->enqueue(image.Width(), image.Height(), &image(0, 0))); + std::unique_ptr popFeatures(job->get()); - allocate(regions); + allocate(regions); - // Build alias to cached data - SIFT_Regions * regionsCasted = dynamic_cast(regions.get()); - regionsCasted->Features().reserve(popFeatures->getDescriptorCount()); - regionsCasted->Descriptors().reserve(popFeatures->getDescriptorCount()); + // Build alias to cached data + SIFT_Regions* regionsCasted = dynamic_cast(regions.get()); + regionsCasted->Features().reserve(popFeatures->getDescriptorCount()); + regionsCasted->Descriptors().reserve(popFeatures->getDescriptorCount()); - ALICEVISION_LOG_TRACE("PopSIFT features count: " << popFeatures->getFeatureCount() << ", descriptors count: " << popFeatures->getDescriptorCount() << std::endl); + ALICEVISION_LOG_TRACE("PopSIFT features count: " << popFeatures->getFeatureCount() << ", descriptors count: " << popFeatures->getDescriptorCount() + << std::endl); - for(const auto& popFeat: *popFeatures) - { - for(int orientationIndex = 0; orientationIndex < popFeat.num_ori; ++orientationIndex) + for (const auto& popFeat : *popFeatures) { - const popsift::Descriptor* popDesc = popFeat.desc[orientationIndex]; - Descriptor desc; + for (int orientationIndex = 0; orientationIndex < popFeat.num_ori; ++orientationIndex) + { + const popsift::Descriptor* popDesc = popFeat.desc[orientationIndex]; + Descriptor desc; - //convertSIFT(*popDesc, desc, _params._root_sift); - // root sift is done inside popsift, so we only need to cast the result - for (std::size_t k = 0; k < 128; ++k) - desc[k] = static_cast(popDesc->features[k]); + // convertSIFT(*popDesc, desc, _params._root_sift); + // root sift is done inside popsift, so we only need to cast the result + for (std::size_t k = 0; k < 128; ++k) + desc[k] = static_cast(popDesc->features[k]); - regionsCasted->Features().emplace_back( - popFeat.xpos, - popFeat.ypos, - popFeat.sigma, - popFeat.orientation[orientationIndex]); + regionsCasted->Features().emplace_back(popFeat.xpos, popFeat.ypos, popFeat.sigma, popFeat.orientation[orientationIndex]); - regionsCasted->Descriptors().emplace_back(desc); + regionsCasted->Descriptors().emplace_back(desc); + } } - } - ALICEVISION_LOG_TRACE("aliceVision PopSIFT feature count : " << regionsCasted->RegionCount() << std::endl); + ALICEVISION_LOG_TRACE("aliceVision PopSIFT feature count : " << regionsCasted->RegionCount() << std::endl); - return true; + return true; } void ImageDescriber_SIFT_popSIFT::resetConfiguration() { - // destroy all allocations and reset all state - // on the current device in the current process - cudaDeviceReset(); - - popsift::cuda::device_prop_t deviceInfo; - deviceInfo.set(0, true); // use only the first device & print information - - // reset configuration - popsift::Config config; - config.setLevels(_params._numScales); - config.setDownsampling(_params._firstOctave); - config.setThreshold(_params._peakThreshold); - config.setEdgeLimit(_params._edgeThreshold); - config.setNormalizationMultiplier(9); // 2^9 = 512 - config.setNormMode(_params._rootSift ? popsift::Config::RootSift : popsift::Config::Classic); - config.setFilterMaxExtrema(_params._maxTotalKeypoints); - config.setFilterSorting(popsift::Config::LargestScaleFirst); - - _popSift.reset(new PopSift(config, popsift::Config::ExtractingMode, PopSift::FloatImages)); + // destroy all allocations and reset all state + // on the current device in the current process + cudaDeviceReset(); + + popsift::cuda::device_prop_t deviceInfo; + deviceInfo.set(0, true); // use only the first device & print information + + // reset configuration + popsift::Config config; + config.setLevels(_params._numScales); + config.setDownsampling(_params._firstOctave); + config.setThreshold(_params._peakThreshold); + config.setEdgeLimit(_params._edgeThreshold); + config.setNormalizationMultiplier(9); // 2^9 = 512 + config.setNormMode(_params._rootSift ? popsift::Config::RootSift : popsift::Config::Classic); + config.setFilterMaxExtrema(_params._maxTotalKeypoints); + config.setFilterSorting(popsift::Config::LargestScaleFirst); + + _popSift.reset(new PopSift(config, popsift::Config::ExtractingMode, PopSift::FloatImages)); } ImageDescriber_SIFT_popSIFT::ImageDescriber_SIFT_popSIFT(const SiftParams& params, bool isOriented) - : ImageDescriber() - , _params(params) - , _isOriented(isOriented) + : ImageDescriber(), + _params(params), + _isOriented(isOriented) { _instanceCounter++; } @@ -109,11 +106,11 @@ ImageDescriber_SIFT_popSIFT::~ImageDescriber_SIFT_popSIFT() { _instanceCounter--; - if(_instanceCounter.load() == 0) + if (_instanceCounter.load() == 0) { _popSift.reset(nullptr); } } -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.hpp b/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.hpp index 1b243c7645..cb63103d45 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.hpp +++ b/src/aliceVision/feature/sift/ImageDescriber_SIFT_popSIFT.hpp @@ -25,101 +25,86 @@ namespace feature { */ class ImageDescriber_SIFT_popSIFT : public ImageDescriber { -public: - explicit ImageDescriber_SIFT_popSIFT(const SiftParams& params = SiftParams(), bool isOriented = true); - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return true; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return true; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - if(!_isOriented) - return EImageDescriberType::SIFT_UPRIGHT; - return EImageDescriberType::SIFT; - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - // GPU only - return 4 * width * height * sizeof(float); // only use the input RGBA image - } - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - void setUpRight(bool upRight) override - { - _isOriented = !upRight; - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - void setConfigurationPreset(ConfigurationPreset preset) override; - - /** - * @brief Detect regions on the 8-bit image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) override; - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr& regions) const override - { - regions.reset(new SIFT_Regions); - } - - /** - * @brief Destructor - */ - ~ImageDescriber_SIFT_popSIFT() override; - -private: - - void resetConfiguration(); - - SiftParams _params; - bool _isOriented = true; - static std::unique_ptr _popSift; - static std::atomic _instanceCounter; + public: + explicit ImageDescriber_SIFT_popSIFT(const SiftParams& params = SiftParams(), bool isOriented = true); + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return true; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return true; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override + { + if (!_isOriented) + return EImageDescriberType::SIFT_UPRIGHT; + return EImageDescriberType::SIFT; + } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override + { + // GPU only + return 4 * width * height * sizeof(float); // only use the input RGBA image + } + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + void setUpRight(bool upRight) override { _isOriented = !upRight; } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + void setConfigurationPreset(ConfigurationPreset preset) override; + + /** + * @brief Detect regions on the 8-bit image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) override; + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { regions.reset(new SIFT_Regions); } + + /** + * @brief Destructor + */ + ~ImageDescriber_SIFT_popSIFT() override; + + private: + void resetConfiguration(); + + SiftParams _params; + bool _isOriented = true; + static std::unique_ptr _popSift; + static std::atomic _instanceCounter; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeat.hpp b/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeat.hpp index fa8127ec5f..c8b7d515d1 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeat.hpp +++ b/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeat.hpp @@ -12,7 +12,8 @@ #include #include -extern "C" { +extern "C" +{ #include } @@ -27,109 +28,88 @@ namespace feature { */ class ImageDescriber_SIFT_vlfeat : public ImageDescriber { -public: - explicit ImageDescriber_SIFT_vlfeat(const SiftParams& params = SiftParams(), bool isOriented = true) - : ImageDescriber() - , _params(params) - , _isOriented(isOriented) - { - // Configure VLFeat - VLFeatInstance::initialize(); - } - - ~ImageDescriber_SIFT_vlfeat() override - { - VLFeatInstance::destroy(); - } - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return true; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - if(!_isOriented) - return EImageDescriberType::SIFT_UPRIGHT; - return EImageDescriberType::SIFT; - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return getMemoryConsumptionVLFeat(width, height, _params); - } - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - void setUpRight(bool upRight) override - { - _isOriented = !upRight; - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - void setConfigurationPreset(ConfigurationPreset preset) override - { - _params.setPreset(preset); - } - - /** - * @brief Detect regions on the float image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) override - { - return extractSIFT(image, regions, _params, _isOriented, mask); - } - - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr& regions) const override - { - regions.reset(new SIFT_Regions); - } - -private: - SiftParams _params; - bool _isOriented; + public: + explicit ImageDescriber_SIFT_vlfeat(const SiftParams& params = SiftParams(), bool isOriented = true) + : ImageDescriber(), + _params(params), + _isOriented(isOriented) + { + // Configure VLFeat + VLFeatInstance::initialize(); + } + + ~ImageDescriber_SIFT_vlfeat() override { VLFeatInstance::destroy(); } + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return true; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override + { + if (!_isOriented) + return EImageDescriberType::SIFT_UPRIGHT; + return EImageDescriberType::SIFT; + } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override + { + return getMemoryConsumptionVLFeat(width, height, _params); + } + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + void setUpRight(bool upRight) override { _isOriented = !upRight; } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + void setConfigurationPreset(ConfigurationPreset preset) override { _params.setPreset(preset); } + + /** + * @brief Detect regions on the float image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) override + { + return extractSIFT(image, regions, _params, _isOriented, mask); + } + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { regions.reset(new SIFT_Regions); } + + private: + SiftParams _params; + bool _isOriented; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeatFloat.hpp b/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeatFloat.hpp index 73b5c1097a..8333ef2330 100644 --- a/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeatFloat.hpp +++ b/src/aliceVision/feature/sift/ImageDescriber_SIFT_vlfeatFloat.hpp @@ -12,7 +12,8 @@ #include #include -extern "C" { +extern "C" +{ #include } @@ -27,106 +28,83 @@ namespace feature { */ class ImageDescriber_SIFT_vlfeatFloat : public ImageDescriber { -public: - explicit ImageDescriber_SIFT_vlfeatFloat(const SiftParams& params = SiftParams(), bool isOriented = true) - : ImageDescriber() - , _params(params) - , _isOriented(isOriented) - { - // Configure VLFeat - VLFeatInstance::initialize(); - } - - ~ImageDescriber_SIFT_vlfeatFloat() override - { - VLFeatInstance::destroy(); - } - - /** - * @brief Check if the image describer use CUDA - * @return True if the image describer use CUDA - */ - bool useCuda() const override - { - return false; - } - - /** - * @brief Check if the image describer use float image - * @return True if the image describer use float image - */ - bool useFloatImage() const override - { - return true; - } - - /** - * @brief Get the corresponding EImageDescriberType - * @return EImageDescriberType - */ - EImageDescriberType getDescriberType() const override - { - return EImageDescriberType::SIFT_FLOAT; - } - - /** - * @brief Get the total amount of RAM needed for a - * feature extraction of an image of the given dimension. - * @param[in] width The image width - * @param[in] height The image height - * @return total amount of memory needed - */ - std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override - { - return getMemoryConsumptionVLFeat(width, height, _params); - } - - /** - * @brief Set image describer always upRight - * @param[in] upRight - */ - void setUpRight(bool upRight) override - { - _isOriented = !upRight; - } - - /** - * @brief Use a preset to control the number of detected regions - * @param[in] preset The preset configuration - */ - void setConfigurationPreset(ConfigurationPreset preset) override - { - return _params.setPreset(preset); - } - - /** - * @brief Detect regions on the float image and compute their attributes (description) - * @param[in] image Image. - * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) - * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) - * Non-zero values depict the region of interest. - * @return True if detection succed. - */ - bool describe(const image::Image& image, - std::unique_ptr& regions, - const image::Image* mask = nullptr) override - { - return extractSIFT(image, regions, _params, _isOriented, mask); - } - - /** - * @brief Allocate Regions type depending of the ImageDescriber - * @param[in,out] regions - */ - void allocate(std::unique_ptr& regions) const override - { - regions.reset(new SIFT_Float_Regions); - } - -private: - SiftParams _params; - bool _isOriented; + public: + explicit ImageDescriber_SIFT_vlfeatFloat(const SiftParams& params = SiftParams(), bool isOriented = true) + : ImageDescriber(), + _params(params), + _isOriented(isOriented) + { + // Configure VLFeat + VLFeatInstance::initialize(); + } + + ~ImageDescriber_SIFT_vlfeatFloat() override { VLFeatInstance::destroy(); } + + /** + * @brief Check if the image describer use CUDA + * @return True if the image describer use CUDA + */ + bool useCuda() const override { return false; } + + /** + * @brief Check if the image describer use float image + * @return True if the image describer use float image + */ + bool useFloatImage() const override { return true; } + + /** + * @brief Get the corresponding EImageDescriberType + * @return EImageDescriberType + */ + EImageDescriberType getDescriberType() const override { return EImageDescriberType::SIFT_FLOAT; } + + /** + * @brief Get the total amount of RAM needed for a + * feature extraction of an image of the given dimension. + * @param[in] width The image width + * @param[in] height The image height + * @return total amount of memory needed + */ + std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override + { + return getMemoryConsumptionVLFeat(width, height, _params); + } + + /** + * @brief Set image describer always upRight + * @param[in] upRight + */ + void setUpRight(bool upRight) override { _isOriented = !upRight; } + + /** + * @brief Use a preset to control the number of detected regions + * @param[in] preset The preset configuration + */ + void setConfigurationPreset(ConfigurationPreset preset) override { return _params.setPreset(preset); } + + /** + * @brief Detect regions on the float image and compute their attributes (description) + * @param[in] image Image. + * @param[out] regions The detected regions and attributes (the caller must delete the allocated data) + * @param[in] mask 8-bit grayscale image for keypoint filtering (optional) + * Non-zero values depict the region of interest. + * @return True if detection succed. + */ + bool describe(const image::Image& image, std::unique_ptr& regions, const image::Image* mask = nullptr) override + { + return extractSIFT(image, regions, _params, _isOriented, mask); + } + + /** + * @brief Allocate Regions type depending of the ImageDescriber + * @param[in,out] regions + */ + void allocate(std::unique_ptr& regions) const override { regions.reset(new SIFT_Float_Regions); } + + private: + SiftParams _params; + bool _isOriented; }; -} // namespace feature -} // namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/SIFT.cpp b/src/aliceVision/feature/sift/SIFT.cpp index 657502f80d..7afa41ce4a 100644 --- a/src/aliceVision/feature/sift/SIFT.cpp +++ b/src/aliceVision/feature/sift/SIFT.cpp @@ -13,7 +13,7 @@ int VLFeatInstance::nbInstances = 0; void SiftParams::setPreset(ConfigurationPreset preset) { - switch(preset.descPreset) + switch (preset.descPreset) { case EImageDescriberPreset::LOW: { @@ -48,13 +48,13 @@ void SiftParams::setPreset(ConfigurationPreset preset) default: throw std::out_of_range("Invalid image describer preset enum"); } - if(preset.maxNbFeatures > 0) + if (preset.maxNbFeatures > 0) { _maxTotalKeypoints = preset.maxNbFeatures; } _firstOctave = 0; _numScales = 3; - switch(preset.quality) + switch (preset.quality) { case EFeatureQuality::LOW: { @@ -84,77 +84,79 @@ void SiftParams::setPreset(ConfigurationPreset preset) break; } } - if(!preset.gridFiltering) + if (!preset.gridFiltering) { _gridSize = 0; } _contrastFiltering = preset.contrastFiltering; } - std::size_t getMemoryConsumptionVLFeat(std::size_t width, std::size_t height, const SiftParams& params) { - double scaleFactor = 1.0; - - // if image resolution is low, increase resolution for extraction - const int firstOctave = params.getImageFirstOctave(width, height); - if(firstOctave > 0) - scaleFactor = 1.0 / std::pow(2.0, firstOctave); - else if(firstOctave < 0) - scaleFactor = std::pow(2.0, std::abs(firstOctave)); - const std::size_t fullImgSize = width * height * scaleFactor * scaleFactor; - - const int numOctaves = std::max(int(std::floor(std::log2(std::min(width, height))) - firstOctave - 3), 1); - - std::size_t pyramidMemoryConsuption = 0; - double downscale = 1.0; - for(int octave = 0; octave < numOctaves; ++octave) - { - pyramidMemoryConsuption += fullImgSize / (downscale*downscale); - downscale *= 2.0; - } - pyramidMemoryConsuption *= params._numScales * sizeof(float); - - const int nbTempPyramids = 4; // Gaussian + DOG + Gradiant + orientation (Note: DOG use 1 layer less, but this is ignored here) - return fullImgSize * 4 * sizeof(float) + // input RGBA image - nbTempPyramids * pyramidMemoryConsuption + // pyramids - (params._maxTotalKeypoints * 128 * sizeof(float)); // output keypoints + double scaleFactor = 1.0; + + // if image resolution is low, increase resolution for extraction + const int firstOctave = params.getImageFirstOctave(width, height); + if (firstOctave > 0) + scaleFactor = 1.0 / std::pow(2.0, firstOctave); + else if (firstOctave < 0) + scaleFactor = std::pow(2.0, std::abs(firstOctave)); + const std::size_t fullImgSize = width * height * scaleFactor * scaleFactor; + + const int numOctaves = std::max(int(std::floor(std::log2(std::min(width, height))) - firstOctave - 3), 1); + + std::size_t pyramidMemoryConsuption = 0; + double downscale = 1.0; + for (int octave = 0; octave < numOctaves; ++octave) + { + pyramidMemoryConsuption += fullImgSize / (downscale * downscale); + downscale *= 2.0; + } + pyramidMemoryConsuption *= params._numScales * sizeof(float); + + const int nbTempPyramids = 4; // Gaussian + DOG + Gradiant + orientation (Note: DOG use 1 layer less, but this is ignored here) + return fullImgSize * 4 * sizeof(float) + // input RGBA image + nbTempPyramids * pyramidMemoryConsuption + // pyramids + (params._maxTotalKeypoints * 128 * sizeof(float)); // output keypoints } void VLFeatInstance::initialize() { - assert(nbInstances >= 0); - if(nbInstances <= 0) - vl_constructor(); - ++nbInstances; + assert(nbInstances >= 0); + if (nbInstances <= 0) + vl_constructor(); + ++nbInstances; } void VLFeatInstance::destroy() { - assert(nbInstances > 0); - --nbInstances; - if(nbInstances <= 0) - vl_destructor(); + assert(nbInstances > 0); + --nbInstances; + if (nbInstances <= 0) + vl_destructor(); } -template -bool extractSIFT(const image::Image& image, std::unique_ptr& regions, const SiftParams& params, - bool orientation, const image::Image* mask) +template +bool extractSIFT(const image::Image& image, + std::unique_ptr& regions, + const SiftParams& params, + bool orientation, + const image::Image* mask) { const int w = image.Width(), h = image.Height(); - const int numOctaves = -1; // auto + const int numOctaves = -1; // auto // if image resolution is low, increase resolution for extraction const int firstOctave = params.getImageFirstOctave(w, h); VlSiftFilt* filt = vl_sift_new(w, h, numOctaves, params._numScales, firstOctave); - if(params._edgeThreshold >= 0) + if (params._edgeThreshold >= 0) vl_sift_set_edge_thresh(filt, params._edgeThreshold); - switch(params._contrastFiltering) + switch (params._contrastFiltering) { case EFeatureConstrastFiltering::Static: { ALICEVISION_LOG_TRACE("SIFT constrastTreshold Static: " << params._peakThreshold); - if(params._peakThreshold >= 0) + if (params._peakThreshold >= 0) { vl_sift_set_peak_thresh(filt, params._peakThreshold / params._numScales); } @@ -201,7 +203,7 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg size_t maxOctaveKeypoints = params._maxTotalKeypoints; - while(true) + while (true) { vl_sift_detect(filt); @@ -213,24 +215,24 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg // TODO: should we reduce maxOctaveKeypoints per octave? // grid filtering at the octave level - if(params._gridSize && params._maxTotalKeypoints && - (params._contrastFiltering == EFeatureConstrastFiltering::GridSort || - params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps || - params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaves)) + if (params._gridSize && params._maxTotalKeypoints && + (params._contrastFiltering == EFeatureConstrastFiltering::GridSort || + params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps || + params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaves)) { // Only filter features if we have more features than the maxTotalKeypoints - if(nkeys > maxOctaveKeypoints) + if (nkeys > maxOctaveKeypoints) { // Sorting the extracted features according to their dog value (peak threshold) std::vector keysIndexSort(nkeys); std::iota(keysIndexSort.begin(), keysIndexSort.end(), 0); - if(params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps) + if (params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps) { std::sort(keysIndexSort.begin(), keysIndexSort.end(), [&](std::size_t a, std::size_t b) { - const int scaleA = int(log2(keys[a].sigma) * 3.0f); // 3 scale steps per octave + const int scaleA = int(log2(keys[a].sigma) * 3.0f); // 3 scale steps per octave const int scaleB = int(log2(keys[b].sigma) * 3.0f); - if(scaleA == scaleB) + if (scaleA == scaleB) { // sort by peak value, when we are in the same scale return keys[a].peak_value > keys[b].peak_value; @@ -238,12 +240,12 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg return scaleA > scaleB; }); } - else if(params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaveSteps) + else if (params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaveSteps) { std::sort(keysIndexSort.begin(), keysIndexSort.end(), [&](std::size_t a, std::size_t b) { - const int scaleA = int(log2(keys[a].sigma)); // 3 scale steps per octave + const int scaleA = int(log2(keys[a].sigma)); // 3 scale steps per octave const int scaleB = int(log2(keys[b].sigma)); - if(scaleA == scaleB) + if (scaleA == scaleB) { // sort by peak value, when we are in the same scale return keys[a].peak_value > keys[b].peak_value; @@ -251,17 +253,18 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg return scaleA > scaleB; }); } - else if(params._contrastFiltering == EFeatureConstrastFiltering::GridSort) + else if (params._contrastFiltering == EFeatureConstrastFiltering::GridSort) { std::sort(keysIndexSort.begin(), keysIndexSort.end(), [&](std::size_t a, std::size_t b) { return keys[a].sigma * keys[a].peak_value > keys[b].sigma * keys[b].peak_value; }); } - else // GridSortOctaves + else // GridSortOctaves { // sort from largest peaks to smallest ones - std::sort(keysIndexSort.begin(), keysIndexSort.end(), - [&](std::size_t a, std::size_t b) { return keys[a].peak_value > keys[b].peak_value; }); + std::sort(keysIndexSort.begin(), keysIndexSort.end(), [&](std::size_t a, std::size_t b) { + return keys[a].peak_value > keys[b].peak_value; + }); } std::vector rejected_indexes; @@ -270,7 +273,7 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg const std::size_t sizeMat = params._gridSize * params._gridSize; std::vector countFeatPerCell(sizeMat, 0); - for(int idx = 0; idx < sizeMat; ++idx) + for (int idx = 0; idx < sizeMat; ++idx) { countFeatPerCell[idx] = 0; } @@ -278,9 +281,9 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg const double regionWidth = w / double(params._gridSize); const double regionHeight = h / double(params._gridSize); - for(IndexT ii = 0; ii < nkeys; ++ii) + for (IndexT ii = 0; ii < nkeys; ++ii) { - const IndexT i = keysIndexSort[ii]; // use sorted keypoints + const IndexT i = keysIndexSort[ii]; // use sorted keypoints const auto& keypoint = keys[i]; const std::size_t cellX = std::min(std::size_t(keypoint.x / regionWidth), params._gridSize); @@ -289,20 +292,20 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg std::size_t& count = countFeatPerCell[cellX * params._gridSize + cellY]; ++count; - if(count < keypointsPerCell) + if (count < keypointsPerCell) filteredKeypointsIndex.push_back(i); else rejected_indexes.push_back(i); } // If we don't have enough features (less than maxTotalKeypoints) after the grid filtering (empty // regions in the grid for example). We add the best other ones, without repartition constraint. - if(filteredKeypointsIndex.size() < params._maxTotalKeypoints && !rejected_indexes.empty()) + if (filteredKeypointsIndex.size() < params._maxTotalKeypoints && !rejected_indexes.empty()) { const std::size_t remainingElements = - std::min(rejected_indexes.size(), params._maxTotalKeypoints - filteredKeypointsIndex.size()); + std::min(rejected_indexes.size(), params._maxTotalKeypoints - filteredKeypointsIndex.size()); ALICEVISION_LOG_TRACE("Octave Grid filtering -- Copy remaining points: " << remainingElements); - filteredKeypointsIndex.insert(filteredKeypointsIndex.end(), rejected_indexes.begin(), - rejected_indexes.begin() + remainingElements); + filteredKeypointsIndex.insert( + filteredKeypointsIndex.end(), rejected_indexes.begin(), rejected_indexes.begin() + remainingElements); } ALICEVISION_LOG_TRACE("Octave SIFT keypoints:\n" @@ -311,22 +314,21 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg << " * after grid filtering: " << filteredKeypointsIndex.size()); } } - else if(params._maxTotalKeypoints && - params._contrastFiltering == EFeatureConstrastFiltering::NonExtremaFiltering) + else if (params._maxTotalKeypoints && params._contrastFiltering == EFeatureConstrastFiltering::NonExtremaFiltering) { std::vector radiusMaxima(nkeys, std::numeric_limits::max()); - for(IndexT i = 0; i < nkeys; ++i) + for (IndexT i = 0; i < nkeys; ++i) { const auto& keypointI = keys[i]; - for(IndexT j = 0; j < nkeys; ++j) + for (IndexT j = 0; j < nkeys; ++j) { const auto& keypointJ = keys[j]; - if(keypointJ.peak_value > keypointI.peak_value) + if (keypointJ.peak_value > keypointI.peak_value) { const float dx = (keypointJ.x - keypointI.x); const float dy = (keypointJ.y - keypointI.y); const float radius = dx * dx + dy * dy; - if(radius < radiusMaxima[i]) + if (radius < radiusMaxima[i]) radiusMaxima[i] = radius; } } @@ -336,13 +338,12 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg const std::size_t maxKeypoints = std::min(params._maxTotalKeypoints, std::size_t(nkeys)); std::partial_sort(filteredKeypointsIndex.begin(), filteredKeypointsIndex.begin() + maxKeypoints, - filteredKeypointsIndex.end(), [&](int a, int b) { - return radiusMaxima[a] * keys[a].sigma > radiusMaxima[b] * keys[b].sigma; - }); + filteredKeypointsIndex.end(), + [&](int a, int b) { return radiusMaxima[a] * keys[a].sigma > radiusMaxima[b] * keys[b].sigma; }); filteredKeypointsIndex.resize(maxKeypoints); } - if(filteredKeypointsIndex.empty()) + if (filteredKeypointsIndex.empty()) { ALICEVISION_LOG_TRACE("Octave SIFT nb keypoints:\n" << nkeys << " (no grid filtering)"); filteredKeypointsIndex.resize(nkeys); @@ -353,15 +354,15 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg vl_sift_update_gradient(filt); // Feature masking - if(mask) + if (mask) { std::vector newFilteredKeypointsIndex; const image::Image& maskIma = *mask; - for(int ii = 0; ii < filteredKeypointsIndex.size(); ++ii) + for (int ii = 0; ii < filteredKeypointsIndex.size(); ++ii) { const int i = filteredKeypointsIndex[ii]; - if(maskIma(keys[i].y, keys[i].x) > 0) + if (maskIma(keys[i].y, keys[i].x) > 0) continue; newFilteredKeypointsIndex.push_back(i); } @@ -371,21 +372,21 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg std::vector> anglesPerKeypoint(filteredKeypointsIndex.size()); #pragma omp parallel for - for(int ii = 0; ii < filteredKeypointsIndex.size(); ++ii) + for (int ii = 0; ii < filteredKeypointsIndex.size(); ++ii) { const int i = filteredKeypointsIndex[ii]; double angles[4] = {0.0, 0.0, 0.0, 0.0}; - int nangles = 1; // by default (1 upright feature) - if(orientation) - { // compute from 1 to 4 orientations + int nangles = 1; // by default (1 upright feature) + if (orientation) + { // compute from 1 to 4 orientations nangles = vl_sift_calc_keypoint_orientations(filt, angles, keys + i); } Descriptor vlFeatDescriptor; Descriptor descriptor; - for(int q = 0; q < nangles; ++q) + for (int q = 0; q < nangles; ++q) { const PointFeature fp(keys[i].x, keys[i].y, keys[i].sigma, static_cast(angles[q])); @@ -401,8 +402,8 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg } } - if(vl_sift_process_next_octave(filt)) - break; // Last octave + if (vl_sift_process_next_octave(filt)) + break; // Last octave } vl_sift_delete(filt); @@ -415,31 +416,31 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg std::vector indexSort(features.size()); std::iota(indexSort.begin(), indexSort.end(), 0); - if(params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps) + if (params._contrastFiltering == EFeatureConstrastFiltering::GridSortScaleSteps) { std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { - const int scaleA = int(log2(features[a].scale()) * 3.0f); // 3 scale steps per octave + const int scaleA = int(log2(features[a].scale()) * 3.0f); // 3 scale steps per octave const int scaleB = int(log2(features[b].scale()) * 3.0f); - if(scaleA == scaleB) + if (scaleA == scaleB) { return featuresPeakValue[a] > featuresPeakValue[b]; } return scaleA > scaleB; }); } - else if(params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaveSteps) + else if (params._contrastFiltering == EFeatureConstrastFiltering::GridSortOctaveSteps) { std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { - const int scaleA = int(log2(features[a].scale())); // 1 scale steps per octave + const int scaleA = int(log2(features[a].scale())); // 1 scale steps per octave const int scaleB = int(log2(features[b].scale())); - if(scaleA == scaleB) + if (scaleA == scaleB) { return featuresPeakValue[a] > featuresPeakValue[b]; } return scaleA > scaleB; }); } - else if(params._contrastFiltering == EFeatureConstrastFiltering::GridSort) + else if (params._contrastFiltering == EFeatureConstrastFiltering::GridSort) { std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { return features[a].scale() * featuresPeakValue[a] > features[b].scale() * featuresPeakValue[b]; @@ -448,15 +449,13 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg else { // sort from largest scales to smallest ones - std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { - return features[a].scale() > features[b].scale(); - }); + std::sort(indexSort.begin(), indexSort.end(), [&](std::size_t a, std::size_t b) { return features[a].scale() > features[b].scale(); }); } std::vector sortedFeatures(features.size()); std::vector sortedDescriptors(features.size()); std::vector sortedFeaturesPeakValue(features.size()); - for(std::size_t i : indexSort) + for (std::size_t i : indexSort) { sortedFeatures[i] = features[indexSort[i]]; sortedDescriptors[i] = descriptors[indexSort[i]]; @@ -467,27 +466,27 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg featuresPeakValue.swap(sortedFeaturesPeakValue); } - if(params._maxTotalKeypoints && params._contrastFiltering == EFeatureConstrastFiltering::NonExtremaFiltering) + if (params._maxTotalKeypoints && params._contrastFiltering == EFeatureConstrastFiltering::NonExtremaFiltering) { const auto& features = regionsCasted->Features(); const auto& descriptors = regionsCasted->Descriptors(); // Only filter features if we have more features than the maxTotalKeypoints - if(features.size() > params._maxTotalKeypoints) + if (features.size() > params._maxTotalKeypoints) { std::vector radiusMaxima(features.size(), std::numeric_limits::max()); - for(IndexT i = 0; i < features.size(); ++i) + for (IndexT i = 0; i < features.size(); ++i) { const auto& keypointI = features[i]; - for(IndexT j = 0; j < features.size(); ++j) + for (IndexT j = 0; j < features.size(); ++j) { const auto& keypointJ = features[j]; - if(featuresPeakValue[j] > featuresPeakValue[i]) + if (featuresPeakValue[j] > featuresPeakValue[i]) { const float dx = (keypointJ.x() - keypointI.x()); const float dy = (keypointJ.y() - keypointI.y()); const float radius = dx * dx + dy * dy; - if(radius < radiusMaxima[i]) + if (radius < radiusMaxima[i]) radiusMaxima[i] = radius; } } @@ -495,32 +494,30 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg std::vector indexSort(features.size()); std::iota(indexSort.begin(), indexSort.end(), 0); std::partial_sort(indexSort.begin(), - indexSort.begin() + std::min(params._maxTotalKeypoints, features.size()), indexSort.end(), - [&](int a, int b) { - return radiusMaxima[a] * features[a].scale() > radiusMaxima[b] * features[b].scale(); - }); + indexSort.begin() + std::min(params._maxTotalKeypoints, features.size()), + indexSort.end(), + [&](int a, int b) { return radiusMaxima[a] * features[a].scale() > radiusMaxima[b] * features[b].scale(); }); indexSort.resize(std::min(params._maxTotalKeypoints, features.size())); std::vector filteredFeatures(indexSort.size()); std::vector filteredDescriptors(indexSort.size()); - for(IndexT i = 0; i < indexSort.size(); ++i) + for (IndexT i = 0; i < indexSort.size(); ++i) { filteredFeatures[i] = features[indexSort[i]]; filteredDescriptors[i] = descriptors[indexSort[i]]; } - ALICEVISION_LOG_TRACE("SIFT Features: before: " << features.size() - << ", after grid filtering: " << filteredFeatures.size()); + ALICEVISION_LOG_TRACE("SIFT Features: before: " << features.size() << ", after grid filtering: " << filteredFeatures.size()); regionsCasted->Features().swap(filteredFeatures); regionsCasted->Descriptors().swap(filteredDescriptors); } } // Grid filtering of the keypoints to ensure a global repartition - else if(params._gridSize && params._maxTotalKeypoints) + else if (params._gridSize && params._maxTotalKeypoints) { const auto& features = regionsCasted->Features(); const auto& descriptors = regionsCasted->Descriptors(); // Only filter features if we have more features than the maxTotalKeypoints - if(features.size() > params._maxTotalKeypoints) + if (features.size() > params._maxTotalKeypoints) { std::vector filteredIndexes; std::vector rejectedIndexes; @@ -533,7 +530,7 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg const double regionWidth = w / double(params._gridSize); const double regionHeight = h / double(params._gridSize); - for(IndexT i = 0; i < features.size(); ++i) + for (IndexT i = 0; i < features.size(); ++i) { const auto& keypoint = features.at(i); @@ -543,50 +540,51 @@ bool extractSIFT(const image::Image& image, std::unique_ptr& reg std::size_t& count = countFeatPerCell[cellX * params._gridSize + cellY]; ++count; - if(count < keypointsPerCell) + if (count < keypointsPerCell) filteredIndexes.push_back(i); else rejectedIndexes.push_back(i); } // If we do not have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in // the grid for example). We add the best other ones, without repartition constraint. - if(filteredIndexes.size() < params._maxTotalKeypoints) + if (filteredIndexes.size() < params._maxTotalKeypoints) { - const std::size_t remainingElements = - std::min(rejectedIndexes.size(), params._maxTotalKeypoints - filteredIndexes.size()); + const std::size_t remainingElements = std::min(rejectedIndexes.size(), params._maxTotalKeypoints - filteredIndexes.size()); ALICEVISION_LOG_TRACE("Grid filtering -- Copy remaining points: " << remainingElements); - filteredIndexes.insert(filteredIndexes.end(), rejectedIndexes.begin(), - rejectedIndexes.begin() + remainingElements); + filteredIndexes.insert(filteredIndexes.end(), rejectedIndexes.begin(), rejectedIndexes.begin() + remainingElements); } std::vector filteredFeatures(filteredIndexes.size()); std::vector filteredDescriptors(filteredIndexes.size()); - for(IndexT i = 0; i < filteredIndexes.size(); ++i) + for (IndexT i = 0; i < filteredIndexes.size(); ++i) { filteredFeatures[i] = features[filteredIndexes[i]]; filteredDescriptors[i] = descriptors[filteredIndexes[i]]; } - ALICEVISION_LOG_TRACE("SIFT Features: before: " << features.size() - << ", after grid filtering: " << filteredFeatures.size()); + ALICEVISION_LOG_TRACE("SIFT Features: before: " << features.size() << ", after grid filtering: " << filteredFeatures.size()); regionsCasted->Features().swap(filteredFeatures); regionsCasted->Descriptors().swap(filteredDescriptors); } } - ALICEVISION_LOG_TRACE("SIFT Features: " << regionsCasted->Features().size() - << " (max: " << params._maxTotalKeypoints << ")."); + ALICEVISION_LOG_TRACE("SIFT Features: " << regionsCasted->Features().size() << " (max: " << params._maxTotalKeypoints << ")."); assert(regionsCasted->Features().size() == regionsCasted->Descriptors().size()); return true; } +template bool extractSIFT(const image::Image& image, + std::unique_ptr& regions, + const SiftParams& params, + bool orientation, + const image::Image* mask); -template bool extractSIFT(const image::Image& image, std::unique_ptr& regions, const SiftParams& params, - bool orientation, const image::Image* mask); - -template bool extractSIFT(const image::Image& image, std::unique_ptr& regions, - const SiftParams& params, bool orientation, const image::Image* mask); +template bool extractSIFT(const image::Image& image, + std::unique_ptr& regions, + const SiftParams& params, + bool orientation, + const image::Image* mask); -} //namespace feature -} //namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/feature/sift/SIFT.hpp b/src/aliceVision/feature/sift/SIFT.hpp index e642a9dca2..ffbda7bfad 100644 --- a/src/aliceVision/feature/sift/SIFT.hpp +++ b/src/aliceVision/feature/sift/SIFT.hpp @@ -15,7 +15,8 @@ #include -extern "C" { +extern "C" +{ #include "nonFree/sift/vl/sift.h" } @@ -33,90 +34,79 @@ namespace feature { */ struct SiftParams { - /// Use original image, or perform an upscale if == -1 - int _firstOctave = 0; - /// Scales per octave - int _numScales = 3; - /// Max ratio of Hessian eigenvalues - float _edgeThreshold = 10.0f; - /// Min contrast - float _peakThreshold = 0.005f; - /// Min contrast (relative to variance median) - float _relativePeakThreshold = 0.01f; - EFeatureConstrastFiltering _contrastFiltering = EFeatureConstrastFiltering::GridSort; - - std::size_t _gridSize = 4; - std::size_t _maxTotalKeypoints = 10000; - /// see [1] - bool _rootSift = true; - - virtual void setPreset(ConfigurationPreset preset); - - int getImageFirstOctave(int w, int h) const - { - return _firstOctave - (w * h <= 3000 * 2000 ? 1 : 0); // -1 to upscale for small resolutions - } + /// Use original image, or perform an upscale if == -1 + int _firstOctave = 0; + /// Scales per octave + int _numScales = 3; + /// Max ratio of Hessian eigenvalues + float _edgeThreshold = 10.0f; + /// Min contrast + float _peakThreshold = 0.005f; + /// Min contrast (relative to variance median) + float _relativePeakThreshold = 0.01f; + EFeatureConstrastFiltering _contrastFiltering = EFeatureConstrastFiltering::GridSort; + + std::size_t _gridSize = 4; + std::size_t _maxTotalKeypoints = 10000; + /// see [1] + bool _rootSift = true; + + virtual void setPreset(ConfigurationPreset preset); + + int getImageFirstOctave(int w, int h) const + { + return _firstOctave - (w * h <= 3000 * 2000 ? 1 : 0); // -1 to upscale for small resolutions + } }; // VLFeat Instance management class VLFeatInstance { -public: - - static void initialize(); + public: + static void initialize(); - static void destroy(); + static void destroy(); -private: - static int nbInstances; + private: + static int nbInstances; }; -//convertSIFT +// convertSIFT ////////////////////////////// -template < typename TOut > -inline void convertSIFT( - const vl_sift_pix* descr, - Descriptor & descriptor, - bool rootSift - ); - -template <> -inline void convertSIFT( - const vl_sift_pix* descr, - Descriptor &descriptor, - bool rootSift) +template +inline void convertSIFT(const vl_sift_pix* descr, Descriptor& descriptor, bool rootSift); + +template<> +inline void convertSIFT(const vl_sift_pix* descr, Descriptor& descriptor, bool rootSift) { - if(rootSift) - { - const float sum = std::accumulate(descr, descr + 128, 0.0f); - for(int k = 0; k < 128; ++k) - descriptor[k] = std::floor(512.f*sqrt(descr[k] / sum)); - } - else - { - for(int k = 0; k < 128; ++k) - descriptor[k] = std::floor(512.f*descr[k]); - } + if (rootSift) + { + const float sum = std::accumulate(descr, descr + 128, 0.0f); + for (int k = 0; k < 128; ++k) + descriptor[k] = std::floor(512.f * sqrt(descr[k] / sum)); + } + else + { + for (int k = 0; k < 128; ++k) + descriptor[k] = std::floor(512.f * descr[k]); + } } -template <> -inline void convertSIFT( - const vl_sift_pix* descr, - Descriptor & descriptor, - bool rootSift) +template<> +inline void convertSIFT(const vl_sift_pix* descr, Descriptor& descriptor, bool rootSift) { - if (rootSift) - { - // rootsift = sqrt( sift / sum(sift) ); - const float sum = std::accumulate(descr, descr+128, 0.0f); - for (int k=0;k<128;++k) - descriptor[k] = static_cast(512.f*sqrt(descr[k]/sum)); - } - else - { - for (int k=0;k<128;++k) - descriptor[k] = static_cast(512.f*descr[k]); - } + if (rootSift) + { + // rootsift = sqrt( sift / sum(sift) ); + const float sum = std::accumulate(descr, descr + 128, 0.0f); + for (int k = 0; k < 128; ++k) + descriptor[k] = static_cast(512.f * sqrt(descr[k] / sum)); + } + else + { + for (int k = 0; k < 128; ++k) + descriptor[k] = static_cast(512.f * descr[k]); + } } /** @@ -138,12 +128,12 @@ std::size_t getMemoryConsumptionVLFeat(std::size_t width, std::size_t height, co * @param mask * @return */ -template +template bool extractSIFT(const image::Image& image, - std::unique_ptr& regions, - const SiftParams& params, - bool orientation, - const image::Image* mask); + std::unique_ptr& regions, + const SiftParams& params, + bool orientation, + const image::Image* mask); -} //namespace feature -} //namespace aliceVision +} // namespace feature +} // namespace aliceVision diff --git a/src/aliceVision/featureEngine/FeatureExtractor.cpp b/src/aliceVision/featureEngine/FeatureExtractor.cpp index 17e045613e..032f2ad8cd 100644 --- a/src/aliceVision/featureEngine/FeatureExtractor.cpp +++ b/src/aliceVision/featureEngine/FeatureExtractor.cpp @@ -16,56 +16,51 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace featureEngine { -FeatureExtractorViewJob::FeatureExtractorViewJob(const sfmData::View& view, - const std::string& outputFolder) : - _view(view), +FeatureExtractorViewJob::FeatureExtractorViewJob(const sfmData::View& view, const std::string& outputFolder) + : _view(view), _outputBasename(fs::path(fs::path(outputFolder) / fs::path(std::to_string(view.getViewId()))).string()) {} FeatureExtractorViewJob::~FeatureExtractorViewJob() = default; -void FeatureExtractorViewJob::setImageDescribers( - const std::vector>& imageDescribers) +void FeatureExtractorViewJob::setImageDescribers(const std::vector>& imageDescribers) { for (std::size_t i = 0; i < imageDescribers.size(); ++i) { const std::shared_ptr& imageDescriber = imageDescribers.at(i); feature::EImageDescriberType imageDescriberType = imageDescriber->getDescriberType(); - if (fs::exists(getFeaturesPath(imageDescriberType)) && - fs::exists(getDescriptorPath(imageDescriberType))) + if (fs::exists(getFeaturesPath(imageDescriberType)) && fs::exists(getDescriptorPath(imageDescriberType))) { continue; } - _memoryConsuption += imageDescriber->getMemoryConsumption(_view.getImage().getWidth(), - _view.getImage().getHeight()); + _memoryConsuption += imageDescriber->getMemoryConsumption(_view.getImage().getWidth(), _view.getImage().getHeight()); - if(imageDescriber->useCuda()) + if (imageDescriber->useCuda()) _gpuImageDescriberIndexes.push_back(i); else _cpuImageDescriberIndexes.push_back(i); } } - -FeatureExtractor::FeatureExtractor(const sfmData::SfMData& sfmData) : - _sfmData(sfmData) +FeatureExtractor::FeatureExtractor(const sfmData::SfMData& sfmData) + : _sfmData(sfmData) {} FeatureExtractor::~FeatureExtractor() = default; -void FeatureExtractor::process(const HardwareContext & hContext, const image::EImageColorSpace workingColorSpace) +void FeatureExtractor::process(const HardwareContext& hContext, const image::EImageColorSpace workingColorSpace) { size_t maxAvailableMemory = hContext.getUserMaxMemoryAvailable(); unsigned int maxAvailableCores = hContext.getMaxThreads(); - + // iteration on each view in the range in order // to prepare viewJob stack sfmData::Views::const_iterator itViewBegin = _sfmData.getViews().begin(); sfmData::Views::const_iterator itViewEnd = _sfmData.getViews().end(); - if(_rangeStart != -1) + if (_rangeStart != -1) { std::advance(itViewBegin, _rangeStart); itViewEnd = itViewBegin; @@ -95,14 +90,12 @@ void FeatureExtractor::process(const HardwareContext & hContext, const image::EI if (!cpuJobs.empty()) { system::MemoryInfo memoryInformation = system::getMemoryInfo(); - - //Put an upper bound with user specified memory + // Put an upper bound with user specified memory size_t maxMemory = std::min(memoryInformation.availableRam, maxAvailableMemory); size_t maxTotalMemory = std::min(memoryInformation.totalRam, maxAvailableMemory); - ALICEVISION_LOG_INFO("Job max memory consumption for one image: " - << jobMaxMemoryConsuption / (1024*1024) << " MB"); + ALICEVISION_LOG_INFO("Job max memory consumption for one image: " << jobMaxMemoryConsuption / (1024 * 1024) << " MB"); ALICEVISION_LOG_INFO("Memory information: " << std::endl << memoryInformation); if (jobMaxMemoryConsuption == 0) @@ -110,8 +103,7 @@ void FeatureExtractor::process(const HardwareContext & hContext, const image::EI // How many buffers can fit in 90% of the available RAM? // This is used to estimate how many jobs can be computed in parallel without SWAP. - const std::size_t memoryImageCapacity = - std::size_t((0.9 * maxMemory) / jobMaxMemoryConsuption); + const std::size_t memoryImageCapacity = std::size_t((0.9 * maxMemory) / jobMaxMemoryConsuption); std::size_t nbThreads = std::max(std::size_t(1), memoryImageCapacity); ALICEVISION_LOG_INFO("Max number of threads regarding memory usage: " << nbThreads); @@ -124,9 +116,8 @@ void FeatureExtractor::process(const HardwareContext & hContext, const image::EI ALICEVISION_LOG_WARNING("But the total amount of RAM is enough to extract features, " << "so you should close other running applications."); ALICEVISION_LOG_WARNING(" => " << std::size_t(std::round((double(maxTotalMemory - maxMemory) / oneGB))) - << " GB are used by other applications for a total RAM capacity of " - << std::size_t(std::round(double(maxTotalMemory) / oneGB)) - << " GB."); + << " GB are used by other applications for a total RAM capacity of " + << std::size_t(std::round(double(maxTotalMemory) / oneGB)) << " GB."); } } else @@ -134,19 +125,17 @@ void FeatureExtractor::process(const HardwareContext & hContext, const image::EI if (maxMemory < 0.5 * maxTotalMemory) { ALICEVISION_LOG_WARNING("More than half of the RAM is used by other applications. It would be more efficient to close them."); - ALICEVISION_LOG_WARNING(" => " - << std::size_t(std::round(double(maxTotalMemory - maxMemory) / oneGB)) - << " GB are used by other applications for a total RAM capacity of " - << std::size_t(std::round(double(maxTotalMemory) / oneGB)) - << " GB."); + ALICEVISION_LOG_WARNING(" => " << std::size_t(std::round(double(maxTotalMemory - maxMemory) / oneGB)) + << " GB are used by other applications for a total RAM capacity of " + << std::size_t(std::round(double(maxTotalMemory) / oneGB)) << " GB."); } } - if(maxMemory == 0) + if (maxMemory == 0) { - ALICEVISION_LOG_WARNING("Cannot find available system memory, this can be due to OS limitation.\n" - "Use only one thread for CPU feature extraction."); - nbThreads = 1; + ALICEVISION_LOG_WARNING("Cannot find available system memory, this can be due to OS limitation.\n" + "Use only one thread for CPU feature extraction."); + nbThreads = 1; } // nbThreads should not be higher than the available cores @@ -198,10 +187,8 @@ void FeatureExtractor::computeViewJob(const FeatureExtractorViewJob& job, bool u if (!_masksFolder.empty() && fs::exists(_masksFolder)) { const auto masksFolder = fs::path(_masksFolder); - const auto idMaskPath = masksFolder / - fs::path(std::to_string(job.view().getViewId())).replace_extension(_maskExtension); - const auto nameMaskPath = masksFolder / - fs::path(job.view().getImage().getImagePath()).filename().replace_extension(_maskExtension); + const auto idMaskPath = masksFolder / fs::path(std::to_string(job.view().getViewId())).replace_extension(_maskExtension); + const auto nameMaskPath = masksFolder / fs::path(job.view().getImage().getImagePath()).filename().replace_extension(_maskExtension); if (fs::exists(idMaskPath)) { @@ -213,16 +200,15 @@ void FeatureExtractor::computeViewJob(const FeatureExtractorViewJob& job, bool u } } - for (const auto & imageDescriberIndex : job.imageDescriberIndexes(useGPU)) + for (const auto& imageDescriberIndex : job.imageDescriberIndexes(useGPU)) { const auto& imageDescriber = _imageDescribers.at(imageDescriberIndex); const feature::EImageDescriberType imageDescriberType = imageDescriber->getDescriberType(); - const std::string imageDescriberTypeName = - feature::EImageDescriberType_enumToString(imageDescriberType); + const std::string imageDescriberTypeName = feature::EImageDescriberType_enumToString(imageDescriberType); // Compute features and descriptors and export them to files - ALICEVISION_LOG_INFO("Extracting " << imageDescriberTypeName << " features from view '" - << job.view().getImage().getImagePath() << "' " << (useGPU ? "[gpu]" : "[cpu]")); + ALICEVISION_LOG_INFO("Extracting " << imageDescriberTypeName << " features from view '" << job.view().getImage().getImagePath() << "' " + << (useGPU ? "[gpu]" : "[cpu]")); std::unique_ptr regions; if (imageDescriber->useFloatImage()) @@ -233,7 +219,7 @@ void FeatureExtractor::computeViewJob(const FeatureExtractorViewJob& job, bool u else { // image buffer can't use float image - if (imageGrayUChar.Width() == 0) // the first time, convert the float buffer to uchar + if (imageGrayUChar.Width() == 0) // the first time, convert the float buffer to uchar imageGrayUChar = (imageGrayFloat.GetMat() * 255.f).cast(); imageDescriber->describe(imageGrayUChar, regions); } @@ -241,7 +227,7 @@ void FeatureExtractor::computeViewJob(const FeatureExtractorViewJob& job, bool u if (pixelRatio != 1.0) { // Re-position point features on input image - for (auto & feat : regions->Features()) + for (auto& feat : regions->Features()) { feat.x() /= pixelRatio; } @@ -250,7 +236,7 @@ void FeatureExtractor::computeViewJob(const FeatureExtractorViewJob& job, bool u if (mask.Height() > 0) { std::vector selectedIndices; - for (size_t i=0, n=regions->RegionCount(); i != n; ++i) + for (size_t i = 0, n = regions->RegionCount(); i != n; ++i) { const Vec2 position = regions->GetRegionPosition(i); const int x = int(position.x()); @@ -273,17 +259,14 @@ void FeatureExtractor::computeViewJob(const FeatureExtractorViewJob& job, bool u std::vector out_associated3dPoint; std::map out_mapFullToLocal; - regions = regions->createFilteredRegions(selectedIndices, out_associated3dPoint, - out_mapFullToLocal); + regions = regions->createFilteredRegions(selectedIndices, out_associated3dPoint, out_mapFullToLocal); } - imageDescriber->Save(regions.get(), job.getFeaturesPath(imageDescriberType), - job.getDescriptorPath(imageDescriberType)); - ALICEVISION_LOG_INFO(std::left << std::setw(6) << " " << regions->RegionCount() << " " - << imageDescriberTypeName << " features extracted from view '" - << job.view().getImage().getImagePath() << "'"); + imageDescriber->Save(regions.get(), job.getFeaturesPath(imageDescriberType), job.getDescriptorPath(imageDescriberType)); + ALICEVISION_LOG_INFO(std::left << std::setw(6) << " " << regions->RegionCount() << " " << imageDescriberTypeName + << " features extracted from view '" << job.view().getImage().getImagePath() << "'"); } } -} // namespace featureEngine -} // namespace aliceVision +} // namespace featureEngine +} // namespace aliceVision diff --git a/src/aliceVision/featureEngine/FeatureExtractor.hpp b/src/aliceVision/featureEngine/FeatureExtractor.hpp index 9e7b1362e2..440688f1ec 100644 --- a/src/aliceVision/featureEngine/FeatureExtractor.hpp +++ b/src/aliceVision/featureEngine/FeatureExtractor.hpp @@ -13,21 +13,14 @@ namespace featureEngine { class FeatureExtractorViewJob { -public: - FeatureExtractorViewJob(const sfmData::View& view, - const std::string& outputFolder); + public: + FeatureExtractorViewJob(const sfmData::View& view, const std::string& outputFolder); ~FeatureExtractorViewJob(); - bool useGPU() const - { - return !_gpuImageDescriberIndexes.empty(); - } + bool useGPU() const { return !_gpuImageDescriberIndexes.empty(); } - bool useCPU() const - { - return !_cpuImageDescriberIndexes.empty(); - } + bool useCPU() const { return !_cpuImageDescriberIndexes.empty(); } std::string getFeaturesPath(feature::EImageDescriberType imageDescriberType) const { @@ -39,26 +32,18 @@ class FeatureExtractorViewJob return _outputBasename + "." + EImageDescriberType_enumToString(imageDescriberType) + ".desc"; } - void setImageDescribers( - const std::vector>& imageDescribers); + void setImageDescribers(const std::vector>& imageDescribers); - const sfmData::View& view() const - { - return _view; - } + const sfmData::View& view() const { return _view; } - std::size_t memoryConsuption() const - { - return _memoryConsuption; - } + std::size_t memoryConsuption() const { return _memoryConsuption; } const std::vector& imageDescriberIndexes(bool useGPU) const { - return useGPU ? _gpuImageDescriberIndexes - : _cpuImageDescriberIndexes; + return useGPU ? _gpuImageDescriberIndexes : _cpuImageDescriberIndexes; } -private: + private: const sfmData::View& _view; std::size_t _memoryConsuption = 0; std::string _outputBasename; @@ -68,39 +53,33 @@ class FeatureExtractorViewJob class FeatureExtractor { -public: - + public: explicit FeatureExtractor(const sfmData::SfMData& sfmData); ~FeatureExtractor(); void setRange(int rangeStart, int rangeSize) { - _rangeStart = rangeStart; - _rangeSize = rangeSize; + _rangeStart = rangeStart; + _rangeSize = rangeSize; } void setMasksFolder(const std::string& folder, const std::string& ext, bool invert) { - _masksFolder = folder; - _maskExtension = ext; - _maskInvert = invert; + _masksFolder = folder; + _maskExtension = ext; + _maskInvert = invert; } - void setOutputFolder(const std::string& folder) - { - _outputFolder = folder; - } - - void addImageDescriber(std::shared_ptr& imageDescriber) - { - _imageDescribers.push_back(imageDescriber); - } + void setOutputFolder(const std::string& folder) { _outputFolder = folder; } - void process(const HardwareContext & hcontext, const image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB); + void addImageDescriber(std::shared_ptr& imageDescriber) { _imageDescribers.push_back(imageDescriber); } -private: + void process(const HardwareContext& hcontext, const image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB); - void computeViewJob(const FeatureExtractorViewJob& job, bool useGPU, const image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB); + private: + void computeViewJob(const FeatureExtractorViewJob& job, + bool useGPU, + const image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB); const sfmData::SfMData& _sfmData; std::vector> _imageDescribers; @@ -112,5 +91,5 @@ class FeatureExtractor int _rangeSize = -1; }; -} // namespace featureEngine -} // namespace aliceVision +} // namespace featureEngine +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/DelaunayGraphCut.cpp b/src/aliceVision/fuseCut/DelaunayGraphCut.cpp index 6835180de4..67fac2d19b 100644 --- a/src/aliceVision/fuseCut/DelaunayGraphCut.cpp +++ b/src/aliceVision/fuseCut/DelaunayGraphCut.cpp @@ -55,19 +55,18 @@ static const std::size_t MAX_LEAF_ELEMENTS = 10; struct PointVectorAdaptator { - using Derived = PointVectorAdaptator; //!< In this case the dataset class is myself. + using Derived = PointVectorAdaptator; //!< In this case the dataset class is myself. using T = double; const std::vector& _data; PointVectorAdaptator(const std::vector& data) - : _data(data) + : _data(data) {} - /// CRTP helper method inline const Derived& derived() const { return *static_cast(this); } /// CRTP helper method - inline Derived& derived() { return *static_cast(this); } + inline Derived& derived() { return *static_cast(this); } // Must return the number of data points inline size_t kdtree_get_point_count() const { return _data.size(); } @@ -75,31 +74,29 @@ struct PointVectorAdaptator // Returns the dim'th component of the idx'th point in the class: // Since this is inlined and the "dim" argument is typically an immediate value, the // "if/else's" are actually solved at compile time. - inline T kdtree_get_pt(const size_t idx, int dim) const - { - return _data.at(idx).m[dim]; - } + inline T kdtree_get_pt(const size_t idx, int dim) const { return _data.at(idx).m[dim]; } // Optional bounding-box computation: return false to default to a standard bbox computation loop. // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) - template - bool kdtree_get_bbox(BBOX &bb) const { return false; } + template + bool kdtree_get_bbox(BBOX& bb) const + { + return false; + } }; -typedef nanoflann::KDTreeSingleIndexAdaptor< - nanoflann::L2_Simple_Adaptor, - PointVectorAdaptator, - 3 /* dim */ - > KdTree; +typedef nanoflann::KDTreeSingleIndexAdaptor, PointVectorAdaptator, 3 /* dim */ + > + KdTree; /** * A result-set class used when performing a radius based search. */ -template +template class SmallerPixSizeInRadius { -public: + public: const DistanceType radius; const std::vector& m_pixSizePrepare; @@ -108,14 +105,11 @@ class SmallerPixSizeInRadius const int m_i; bool found = false; - inline SmallerPixSizeInRadius(DistanceType radius_, - const std::vector& pixSizePrepare, - const std::vector& simScorePrepare, - int i) - : radius(radius_) - , m_pixSizePrepare(pixSizePrepare) - , m_simScorePrepare(simScorePrepare) - , m_i(i) + inline SmallerPixSizeInRadius(DistanceType radius_, const std::vector& pixSizePrepare, const std::vector& simScorePrepare, int i) + : radius(radius_), + m_pixSizePrepare(pixSizePrepare), + m_simScorePrepare(simScorePrepare), + m_i(i) { init(); } @@ -133,10 +127,11 @@ class SmallerPixSizeInRadius */ inline bool addPoint(DistanceType dist, IndexType index) { - if(dist < radius) + if (dist < radius) { ++m_result; - if(m_simScorePrepare[index] * m_pixSizePrepare[index] * m_pixSizePrepare[index] < m_simScorePrepare[m_i] * m_pixSizePrepare[m_i] * m_pixSizePrepare[m_i]) + if (m_simScorePrepare[index] * m_pixSizePrepare[index] * m_pixSizePrepare[index] < + m_simScorePrepare[m_i] * m_pixSizePrepare[m_i] * m_pixSizePrepare[m_i]) { found = true; return false; @@ -154,7 +149,7 @@ class Tree std::unique_ptr _tree; std::unique_ptr _pointCloudRef; -public: + public: Tree(const std::vector& verticesCoords) { initKdTree(verticesCoords); } void initKdTree(const std::vector& verticesCoords) @@ -167,8 +162,7 @@ class Tree #else ALICEVISION_LOG_INFO("Build nanoflann KdTree index."); _pointCloudRef = std::make_unique(verticesCoords); - _tree = std::make_unique(3 /*dim*/, *_pointCloudRef.get(), - nanoflann::KDTreeSingleIndexAdaptorParams(MAX_LEAF_ELEMENTS)); + _tree = std::make_unique(3 /*dim*/, *_pointCloudRef.get(), nanoflann::KDTreeSingleIndexAdaptorParams(MAX_LEAF_ELEMENTS)); _tree->buildIndex(); #endif ALICEVISION_LOG_INFO("KdTree created for " << verticesCoords.size() << " points."); @@ -184,7 +178,7 @@ class Tree #else nanoflann::KNNResultSet resultSet(1); resultSet.init(&index, &sq_dist); - if(!_tree->findNeighbors(resultSet, p.m, nanoflann::SearchParameters())) + if (!_tree->findNeighbors(resultSet, p.m, nanoflann::SearchParameters())) { return false; } @@ -194,7 +188,10 @@ class Tree }; /// Filter by pixSize -void filterByPixSize(const std::vector& verticesCoordsPrepare, std::vector& pixSizePrepare, double pixSizeMarginCoef, std::vector& simScorePrepare) +void filterByPixSize(const std::vector& verticesCoordsPrepare, + std::vector& pixSizePrepare, + double pixSizeMarginCoef, + std::vector& simScorePrepare) { #ifdef USE_GEOGRAM_KDTREE ALICEVISION_LOG_INFO("Build geogram KdTree index."); @@ -209,15 +206,15 @@ void filterByPixSize(const std::vector& verticesCoordsPrepare, std::vec #endif ALICEVISION_LOG_INFO("KdTree created for " << verticesCoordsPrepare.size() << " points."); - #pragma omp parallel for - for(int vIndex = 0; vIndex < verticesCoordsPrepare.size(); ++vIndex) +#pragma omp parallel for + for (int vIndex = 0; vIndex < verticesCoordsPrepare.size(); ++vIndex) { - if(pixSizePrepare[vIndex] == -1.0) + if (pixSizePrepare[vIndex] == -1.0) { continue; } const double pixSizeScore = pixSizeMarginCoef * simScorePrepare[vIndex] * pixSizePrepare[vIndex] * pixSizePrepare[vIndex]; - if(pixSizeScore < std::numeric_limits::epsilon()) + if (pixSizeScore < std::numeric_limits::epsilon()) { pixSizePrepare[vIndex] = -1.0; continue; @@ -230,15 +227,14 @@ void filterByPixSize(const std::vector& verticesCoordsPrepare, std::vec // kdTree.get_nearest_neighbors(nbNeighbors, verticesCoordsPrepare[i].m, &nnIndex.front(), &sqDist.front()); kdTree.get_nearest_neighbors(nbNeighbors, vIndex, &nnIndex.front(), &sqDist.front()); - for(std::size_t n = 0; n < nbNeighbors; ++n) + for (std::size_t n = 0; n < nbNeighbors; ++n) { // NOTE: we don't need to test the distance regarding pixSizePrepare[nnIndex[vIndex]] // as we kill ourself only if our pixSize is bigger - if(sqDist[n] < pixSizeScore) + if (sqDist[n] < pixSizeScore) { - if(pixSizePrepare[nnIndex[n]] < pixSizePrepare[vIndex] || - (pixSizePrepare[nnIndex[n]] == pixSizePrepare[vIndex] && nnIndex[n] < vIndex) - ) + if (pixSizePrepare[nnIndex[n]] < pixSizePrepare[vIndex] || + (pixSizePrepare[nnIndex[n]] == pixSizePrepare[vIndex] && nnIndex[n] < vIndex)) { // Kill itself if inside our volume (defined by marginCoef*pixSize) there is another point with a smaller pixSize pixSizePrepare[vIndex] = -1.0; @@ -251,18 +247,17 @@ void filterByPixSize(const std::vector& verticesCoordsPrepare, std::vec // } } #else - - static const nanoflann::SearchParameters searchParams(0.f, false); // false: dont need to sort + + static const nanoflann::SearchParameters searchParams(0.f, false); // false: dont need to sort SmallerPixSizeInRadius resultSet(pixSizeScore, pixSizePrepare, simScorePrepare, vIndex); kdTree.findNeighbors(resultSet, verticesCoordsPrepare[vIndex].m, searchParams); - if(resultSet.found) + if (resultSet.found) pixSizePrepare[vIndex] = -1.0; #endif } ALICEVISION_LOG_INFO("Filtering done."); } - /// Remove invalid points based on invalid pixSize void removeInvalidPoints(std::vector& verticesCoordsPrepare, std::vector& pixSizePrepare, std::vector& simScorePrepare) { @@ -272,9 +267,9 @@ void removeInvalidPoints(std::vector& verticesCoordsPrepare, std::vecto pixSizeTmp.reserve(pixSizePrepare.size()); std::vector simScoreTmp; simScoreTmp.reserve(simScorePrepare.size()); - for(int i = 0; i < verticesCoordsPrepare.size(); ++i) + for (int i = 0; i < verticesCoordsPrepare.size(); ++i) { - if(pixSizePrepare[i] != -1.0) + if (pixSizePrepare[i] != -1.0) { verticesCoordsTmp.push_back(verticesCoordsPrepare[i]); pixSizeTmp.push_back(pixSizePrepare[i]); @@ -287,7 +282,10 @@ void removeInvalidPoints(std::vector& verticesCoordsPrepare, std::vecto simScorePrepare.swap(simScoreTmp); } -void removeInvalidPoints(std::vector& verticesCoordsPrepare, std::vector& pixSizePrepare, std::vector& simScorePrepare, std::vector& verticesAttrPrepare) +void removeInvalidPoints(std::vector& verticesCoordsPrepare, + std::vector& pixSizePrepare, + std::vector& simScorePrepare, + std::vector& verticesAttrPrepare) { std::vector verticesCoordsTmp; verticesCoordsTmp.reserve(verticesCoordsPrepare.size()); @@ -297,9 +295,9 @@ void removeInvalidPoints(std::vector& verticesCoordsPrepare, std::vecto simScoreTmp.reserve(simScorePrepare.size()); std::vector verticesAttrTmp; verticesAttrTmp.reserve(verticesAttrPrepare.size()); - for(int i = 0; i < verticesCoordsPrepare.size(); ++i) + for (int i = 0; i < verticesCoordsPrepare.size(); ++i) { - if(pixSizePrepare[i] != -1.0) + if (pixSizePrepare[i] != -1.0) { verticesCoordsTmp.push_back(verticesCoordsPrepare[i]); pixSizeTmp.push_back(pixSizePrepare[i]); @@ -314,8 +312,16 @@ void removeInvalidPoints(std::vector& verticesCoordsPrepare, std::vecto verticesAttrPrepare.swap(verticesAttrTmp); } -void createVerticesWithVisibilities(const StaticVector& cams, std::vector& verticesCoordsPrepare, std::vector& pixSizePrepare, std::vector& simScorePrepare, - std::vector& verticesAttrPrepare, mvsUtils::MultiViewParams& mp, float simFactor, float voteMarginFactor, float contributeMarginFactor, float simGaussianSize) +void createVerticesWithVisibilities(const StaticVector& cams, + std::vector& verticesCoordsPrepare, + std::vector& pixSizePrepare, + std::vector& simScorePrepare, + std::vector& verticesAttrPrepare, + mvsUtils::MultiViewParams& mp, + float simFactor, + float voteMarginFactor, + float contributeMarginFactor, + float simGaussianSize) { #ifdef USE_GEOGRAM_KDTREE GEO::AdaptiveKdTree kdTree(3); @@ -332,12 +338,12 @@ void createVerticesWithVisibilities(const StaticVector& cams, std::vector

newSimScorePrepare(simScorePrepare.size()); // std::vector newPixSizePrepare(pixSizePrepare.size()); std::vector locks(verticesCoordsPrepare.size()); - for (auto& lock: locks) + for (auto& lock : locks) omp_init_lock(&lock); omp_set_nested(1); - #pragma omp parallel for num_threads(3) - for(int c = 0; c < cams.size(); ++c) +#pragma omp parallel for num_threads(3) + for (int c = 0; c < cams.size(); ++c) { ALICEVISION_LOG_INFO("Create visibilities (" << c << "/" << cams.size() << ")"); image::Image depthMap; @@ -348,7 +354,7 @@ void createVerticesWithVisibilities(const StaticVector& cams, std::vector

& cams, std::vector

& cams, std::vector

::max(); double dist = std::numeric_limits::max(); resultSet.init(&nearestVertexIndex, &dist); - if(!kdTree.findNeighbors(resultSet, p.m, nanoflann::SearchParameters())) + if (!kdTree.findNeighbors(resultSet, p.m, nanoflann::SearchParameters())) { ALICEVISION_LOG_TRACE("Failed to find Neighbors."); continue; } #endif const float pixSizeScoreI = simScorePrepare[nearestVertexIndex] * pixSize * pixSize; - const float pixSizeScoreV = simScorePrepare[nearestVertexIndex] * pixSizePrepare[nearestVertexIndex] * pixSizePrepare[nearestVertexIndex]; + const float pixSizeScoreV = + simScorePrepare[nearestVertexIndex] * pixSizePrepare[nearestVertexIndex] * pixSizePrepare[nearestVertexIndex]; - if(dist < voteMarginFactor * std::max(pixSizeScoreI, pixSizeScoreV)) + if (dist < voteMarginFactor * std::max(pixSizeScoreI, pixSizeScoreV)) { GC_vertexInfo& va = verticesAttrPrepare[nearestVertexIndex]; Point3d& vc = verticesCoordsPrepare[nearestVertexIndex]; @@ -415,12 +422,14 @@ void createVerticesWithVisibilities(const StaticVector& cams, std::vector

& cams, std::vector

set_stores_cicl(true); } -DelaunayGraphCut::~DelaunayGraphCut() -{ -} +DelaunayGraphCut::~DelaunayGraphCut() {} void DelaunayGraphCut::saveDhInfo(const std::string& fileNameInfo) { @@ -484,14 +490,14 @@ void DelaunayGraphCut::saveDhInfo(const std::string& fileNameInfo) int npts = getNbVertices(); fwrite(&npts, sizeof(int), 1, f); - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { v.fwriteinfo(f); } int ncells = _cellsAttr.size(); fwrite(&ncells, sizeof(int), 1, f); - for(const GC_cellInfo& c: _cellsAttr) + for (const GC_cellInfo& c : _cellsAttr) { c.fwriteinfo(f); } @@ -512,19 +518,18 @@ void DelaunayGraphCut::saveDh(const std::string& fileNameDh, const std::string& mvsUtils::printfElapsedTime(t1); } - std::vector DelaunayGraphCut::getNeighboringCellsByGeometry(const GeometryIntersection& g) const { switch (g.type) { - case EGeometryType::Edge: - return getNeighboringCellsByEdge(g.edge); - case EGeometryType::Vertex: - return getNeighboringCellsByVertexIndex(g.vertexIndex); - case EGeometryType::Facet: - return getNeighboringCellsByFacet(g.facet); - case EGeometryType::None: - break; + case EGeometryType::Edge: + return getNeighboringCellsByEdge(g.edge); + case EGeometryType::Vertex: + return getNeighboringCellsByVertexIndex(g.vertexIndex); + case EGeometryType::Facet: + return getNeighboringCellsByFacet(g.facet); + case EGeometryType::None: + break; } throw std::runtime_error("[error] getNeighboringCellsByGeometry: an undefined/None geometry has no neighboring cells."); } @@ -535,7 +540,7 @@ std::vector DelaunayGraphCut::getNeighboringCellsBy neighboringCells.push_back(f.cellIndex); const Facet mFacet = mirrorFacet(f); - if(!isInvalidOrInfiniteCell(mFacet.cellIndex)) + if (!isInvalidOrInfiniteCell(mFacet.cellIndex)) neighboringCells.push_back(mFacet.cellIndex); return neighboringCells; @@ -548,7 +553,7 @@ std::vector DelaunayGraphCut::getNeighboringCellsBy std::vector neighboringCells; std::set_intersection(v0ci.begin(), v0ci.end(), v1ci.begin(), v1ci.end(), std::back_inserter(neighboringCells)); - + return neighboringCells; } @@ -571,10 +576,10 @@ void DelaunayGraphCut::computeDelaunay() void DelaunayGraphCut::initCells() { - _cellsAttr.resize(_tetrahedralization->nb_cells()); // or nb_finite_cells() if keeps_infinite() + _cellsAttr.resize(_tetrahedralization->nb_cells()); // or nb_finite_cells() if keeps_infinite() ALICEVISION_LOG_INFO(_cellsAttr.size() << " cells created by tetrahedralization."); - for(int i = 0; i < _cellsAttr.size(); ++i) + for (int i = 0; i < _cellsAttr.size(); ++i) { GC_cellInfo& c = _cellsAttr[i]; @@ -583,9 +588,9 @@ void DelaunayGraphCut::initCells() c.on = 0.0f; c.fullnessScore = 0.0f; c.emptinessScore = 0.0f; - for(int s = 0; s < 4; ++s) + for (int s = 0; s < 4; ++s) { - c.gEdgeVisWeight[s] = 0.0f; // weights for the 4 faces of the tetrahedron + c.gEdgeVisWeight[s] = 0.0f; // weights for the 4 faces of the tetrahedron } } @@ -598,7 +603,7 @@ void DelaunayGraphCut::displayStatistics() StaticVector* ptsCamsHist = getPtsCamsHist(); ALICEVISION_LOG_TRACE("Histogram of number of cams per point:"); - for(int i = 0; i < ptsCamsHist->size(); ++i) + for (int i = 0; i < ptsCamsHist->size(); ++i) ALICEVISION_LOG_TRACE(" " << i << ": " << mvsUtils::num2str((*ptsCamsHist)[i])); delete ptsCamsHist; /* @@ -618,16 +623,16 @@ StaticVector*>* DelaunayGraphCut::createPtsCams() StaticVector*>* out = new StaticVector*>(); out->reserve(npts); - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { StaticVector* cams = new StaticVector(); cams->reserve(v.getNbCameras()); - for(int c = 0; c < v.getNbCameras(); c++) + for (int c = 0; c < v.getNbCameras(); c++) { cams->push_back(v.cams[c]); } out->push_back(cams); - } // for i + } // for i ALICEVISION_LOG_INFO("Extract visibilities done."); @@ -643,16 +648,16 @@ void DelaunayGraphCut::createPtsCams(StaticVector>& out_ptsCam out_ptsCams.reserve(npts); - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { StaticVector cams; cams.reserve(v.getNbCameras()); - for(int c = 0; c < v.getNbCameras(); c++) + for (int c = 0; c < v.getNbCameras(); c++) { cams.push_back(v.cams[c]); } out_ptsCams.push_back(cams); - } // for i + } // for i ALICEVISION_LOG_INFO("Extract visibilities done."); @@ -662,7 +667,7 @@ void DelaunayGraphCut::createPtsCams(StaticVector>& out_ptsCam StaticVector* DelaunayGraphCut::getPtsCamsHist() { int maxnCams = 0; - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { maxnCams = std::max(maxnCams, (int)v.getNbCameras()); } @@ -673,7 +678,7 @@ StaticVector* DelaunayGraphCut::getPtsCamsHist() ncamsHist->reserve(maxnCams); ncamsHist->resize_with(maxnCams, 0); - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { (*ncamsHist)[v.getNbCameras()] += 1; } @@ -684,7 +689,7 @@ StaticVector* DelaunayGraphCut::getPtsCamsHist() StaticVector* DelaunayGraphCut::getPtsNrcHist() { int maxnnrcs = 0; - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { maxnnrcs = std::max(maxnnrcs, v.nrc); } @@ -697,9 +702,9 @@ StaticVector* DelaunayGraphCut::getPtsNrcHist() nnrcsHist->reserve(maxnnrcs); nnrcsHist->resize_with(maxnnrcs, 0); - for(const GC_vertexInfo& v: _verticesAttr) + for (const GC_vertexInfo& v : _verticesAttr) { - if(v.nrc < nnrcsHist->size()) + if (v.nrc < nnrcsHist->size()) { (*nnrcsHist)[v.nrc] += 1; } @@ -715,11 +720,11 @@ StaticVector DelaunayGraphCut::getIsUsedPerCamera() const StaticVector cams; cams.resize_with(_mp.getNbCameras(), 0); -//#pragma omp parallel for - for(int vi = 0; vi < _verticesAttr.size(); ++vi) + // #pragma omp parallel for + for (int vi = 0; vi < _verticesAttr.size(); ++vi) { const GC_vertexInfo& v = _verticesAttr[vi]; - for(int c = 0; c < v.cams.size(); ++c) + for (int c = 0; c < v.cams.size(); ++c) { const int obsCam = v.cams[c]; // boost::atomic_ref(cams[obsCam]) = 1; @@ -736,9 +741,9 @@ StaticVector DelaunayGraphCut::getSortedUsedCams() const const StaticVector isUsed = getIsUsedPerCamera(); StaticVector out; out.reserve(isUsed.size()); - for(int cameraIndex = 0; cameraIndex < isUsed.size(); ++cameraIndex) + for (int cameraIndex = 0; cameraIndex < isUsed.size(); ++cameraIndex) { - if(isUsed[cameraIndex] != 0) + if (isUsed[cameraIndex] != 0) out.push_back(cameraIndex); } @@ -747,49 +752,49 @@ StaticVector DelaunayGraphCut::getSortedUsedCams() const void DelaunayGraphCut::addPointsFromSfM(const Point3d hexah[8], const StaticVector& cams, const sfmData::SfMData& sfmData) { - const std::size_t nbPoints = sfmData.getLandmarks().size(); - const std::size_t verticesOffset = _verticesCoords.size(); - - _verticesCoords.resize(verticesOffset + nbPoints); - _verticesAttr.resize(verticesOffset + nbPoints); + const std::size_t nbPoints = sfmData.getLandmarks().size(); + const std::size_t verticesOffset = _verticesCoords.size(); - sfmData:: Landmarks::const_iterator landmarkIt = sfmData.getLandmarks().begin(); - std::vector::iterator vCoordsIt = _verticesCoords.begin(); - std::vector::iterator vAttrIt = _verticesAttr.begin(); + _verticesCoords.resize(verticesOffset + nbPoints); + _verticesAttr.resize(verticesOffset + nbPoints); - std::advance(vCoordsIt, verticesOffset); - std::advance(vAttrIt, verticesOffset); + sfmData::Landmarks::const_iterator landmarkIt = sfmData.getLandmarks().begin(); + std::vector::iterator vCoordsIt = _verticesCoords.begin(); + std::vector::iterator vAttrIt = _verticesAttr.begin(); - std::size_t addedPoints = 0; - for(std::size_t i = 0; i < nbPoints; ++i) - { - const sfmData::Landmark& landmark = landmarkIt->second; - const Point3d p(landmark.X(0), landmark.X(1), landmark.X(2)); + std::advance(vCoordsIt, verticesOffset); + std::advance(vAttrIt, verticesOffset); - if(mvsUtils::isPointInHexahedron(p, hexah)) + std::size_t addedPoints = 0; + for (std::size_t i = 0; i < nbPoints; ++i) { - *vCoordsIt = p; + const sfmData::Landmark& landmark = landmarkIt->second; + const Point3d p(landmark.X(0), landmark.X(1), landmark.X(2)); - vAttrIt->nrc = landmark.observations.size(); - vAttrIt->cams.reserve(vAttrIt->nrc); + if (mvsUtils::isPointInHexahedron(p, hexah)) + { + *vCoordsIt = p; + + vAttrIt->nrc = landmark.observations.size(); + vAttrIt->cams.reserve(vAttrIt->nrc); - for(const auto& observationPair : landmark.observations) - vAttrIt->cams.push_back(_mp.getIndexFromViewId(observationPair.first)); + for (const auto& observationPair : landmark.observations) + vAttrIt->cams.push_back(_mp.getIndexFromViewId(observationPair.first)); - vAttrIt->pixSize = _mp.getCamsMinPixelSize(p, vAttrIt->cams); + vAttrIt->pixSize = _mp.getCamsMinPixelSize(p, vAttrIt->cams); - ++vCoordsIt; - ++vAttrIt; - ++addedPoints; + ++vCoordsIt; + ++vAttrIt; + ++addedPoints; + } + ++landmarkIt; } - ++landmarkIt; - } - if(addedPoints != nbPoints) - { - _verticesCoords.resize(verticesOffset + addedPoints); - _verticesAttr.resize(verticesOffset + addedPoints); - } - ALICEVISION_LOG_WARNING("Add " << addedPoints << " new points for the SfM."); + if (addedPoints != nbPoints) + { + _verticesCoords.resize(verticesOffset + addedPoints); + _verticesAttr.resize(verticesOffset + addedPoints); + } + ALICEVISION_LOG_WARNING("Add " << addedPoints << " new points for the SfM."); } void DelaunayGraphCut::addPointsFromCameraCenters(const StaticVector& cams, float minDist) @@ -798,7 +803,7 @@ void DelaunayGraphCut::addPointsFromCameraCenters(const StaticVector& cams, // Note: For now, we skip the check of collision between cameras and data points to avoid useless computation. // const float minDist2 = minDist * minDist; // Tree kdTree(_verticesCoords); - for(int camid = 0; camid < cams.size(); camid++) + for (int camid = 0; camid < cams.size(); camid++) { int rc = cams[camid]; { @@ -835,8 +840,8 @@ void DelaunayGraphCut::addPointsToPreventSingularities(const Point3d voxel[8], f Point3d extrPts[6]; Point3d fcg; const double s = 0.25; - fcg = (voxel[0] + voxel[1] + voxel[2] + voxel[3]) * 0.25; // facet center - extrPts[0] = fcg + (fcg - vcg) * s; // extra point beyond the bbox facet + fcg = (voxel[0] + voxel[1] + voxel[2] + voxel[3]) * 0.25; // facet center + extrPts[0] = fcg + (fcg - vcg) * s; // extra point beyond the bbox facet fcg = (voxel[0] + voxel[4] + voxel[7] + voxel[3]) * 0.25; extrPts[1] = fcg + (fcg - vcg) * s; fcg = (voxel[0] + voxel[1] + voxel[5] + voxel[4]) * 0.25; @@ -849,7 +854,7 @@ void DelaunayGraphCut::addPointsToPreventSingularities(const Point3d voxel[8], f extrPts[5] = fcg + (fcg - vcg) * s; int addedPoints = 0; - for(int i = 0; i < 6; ++i) + for (int i = 0; i < 6; ++i) { const Point3d p(extrPts[i].x, extrPts[i].y, extrPts[i].z); @@ -868,7 +873,7 @@ void DelaunayGraphCut::addPointsToPreventSingularities(const Point3d voxel[8], f void DelaunayGraphCut::densifyWithHelperPoints(int nbFront, int nbBack, double scale) { - if(nbFront <= 0 && nbBack <= 0) + if (nbFront <= 0 && nbBack <= 0) return; const std::size_t nbInputVertices = _verticesCoords.size(); @@ -876,16 +881,16 @@ void DelaunayGraphCut::densifyWithHelperPoints(int nbFront, int nbBack, double s std::vector newHelperPoints; newHelperPoints.reserve((nbFront + nbBack) * nbInputVertices); - for(std::size_t vi = 0; vi < nbInputVertices; ++vi) + for (std::size_t vi = 0; vi < nbInputVertices; ++vi) { const Point3d& v = _verticesCoords[vi]; const GC_vertexInfo& vAttr = _verticesAttr[vi]; - if(vAttr.cams.empty() || vAttr.pixSize <= std::numeric_limits::epsilon()) + if (vAttr.cams.empty() || vAttr.pixSize <= std::numeric_limits::epsilon()) continue; Point3d mainCamDir; - for(int camId: vAttr.cams) + for (int camId : vAttr.cams) { const Point3d& cam = _mp.CArr[camId]; const Point3d d = (cam - v).normalize(); @@ -893,15 +898,15 @@ void DelaunayGraphCut::densifyWithHelperPoints(int nbFront, int nbBack, double s } mainCamDir /= double(vAttr.cams.size()); mainCamDir = mainCamDir.normalize() * vAttr.pixSize; - - for(int iFront = 1; iFront < nbFront + 1; ++iFront) + + for (int iFront = 1; iFront < nbFront + 1; ++iFront) newHelperPoints.push_back(v + mainCamDir * iFront * scale); - for(int iBack = 1; iBack < nbBack + 1; ++iBack) + for (int iBack = 1; iBack < nbBack + 1; ++iBack) newHelperPoints.push_back(v - mainCamDir * iBack * scale); } _verticesCoords.resize(nbInputVertices + newHelperPoints.size()); _verticesAttr.resize(nbInputVertices + newHelperPoints.size()); - for(std::size_t vi = 0; vi < newHelperPoints.size(); ++vi) + for (std::size_t vi = 0; vi < newHelperPoints.size(); ++vi) { _verticesCoords[nbInputVertices + vi] = newHelperPoints[vi]; // GC_vertexInfo& vAttr = _verticesAttr[nbInputVertices + vi]; @@ -909,13 +914,12 @@ void DelaunayGraphCut::densifyWithHelperPoints(int nbFront, int nbBack, double s // vAttr.nrc = 0; } - ALICEVISION_LOG_WARNING("Densify the " << nbInputVertices << " vertices with " << newHelperPoints.size() - << " new helper points."); + ALICEVISION_LOG_WARNING("Densify the " << nbInputVertices << " vertices with " << newHelperPoints.size() << " new helper points."); } void DelaunayGraphCut::addGridHelperPoints(int helperPointsGridSize, const Point3d voxel[8], float minDist) { - if(helperPointsGridSize <= 0) + if (helperPointsGridSize <= 0) return; ALICEVISION_LOG_INFO("Add grid helper points."); @@ -926,13 +930,9 @@ void DelaunayGraphCut::addGridHelperPoints(int helperPointsGridSize, const Point // Add uniform noise on helper points, with 1/8 margin around the vertex. const double md = 1.0 / (helperPointsGridSize * 8.0); - const Point3d maxNoiseSize( - md * (voxel[1] - voxel[0]).size(), - md * (voxel[3] - voxel[0]).size(), - md * (voxel[4] - voxel[0]).size() - ); + const Point3d maxNoiseSize(md * (voxel[1] - voxel[0]).size(), md * (voxel[3] - voxel[0]).size(), md * (voxel[4] - voxel[0]).size()); Point3d center = (voxel[0] + voxel[1] + voxel[2] + voxel[3] + voxel[4] + voxel[5] + voxel[6] + voxel[7]) / 8.0; - + const unsigned int seed = (unsigned int)_mp.userParams.get("delaunaycut.seed", 0); std::mt19937 generator(seed != 0 ? seed : std::random_device{}()); auto rand = std::bind(std::uniform_real_distribution{-1.0, 1.0}, generator); @@ -946,23 +946,22 @@ void DelaunayGraphCut::addGridHelperPoints(int helperPointsGridSize, const Point std::vector valid(gridVerticesCoords.size()); ALICEVISION_LOG_TRACE("Create helper points."); - #pragma omp parallel for - for(int x = 0; x <= ns; ++x) +#pragma omp parallel for + for (int x = 0; x <= ns; ++x) { - for(int y = 0; y <= ns; ++y) + for (int y = 0; y <= ns; ++y) { - for(int z = 0; z <= ns; ++z) + for (int z = 0; z <= ns; ++z) { - int i = x * (ns+1)*(ns+1) + y * (ns+1) + z; - const Point3d pt = voxel[0] + vx * ((double)x / double(ns)) + vy * ((double)y / double(ns)) + - vz * ((double)z / double(ns)); + int i = x * (ns + 1) * (ns + 1) + y * (ns + 1) + z; + const Point3d pt = voxel[0] + vx * ((double)x / double(ns)) + vy * ((double)y / double(ns)) + vz * ((double)z / double(ns)); const Point3d noise(maxNoiseSize.x * rand(), maxNoiseSize.y * rand(), maxNoiseSize.z * rand()); const Point3d p = pt + noise; std::size_t vi{}; double sq_dist{}; // if there is no nearest vertex or the nearest vertex is not too close - if(!kdTree.locateNearestVertex(p, vi, sq_dist) || (sq_dist > minDist2)) + if (!kdTree.locateNearestVertex(p, vi, sq_dist) || (sq_dist > minDist2)) { gridVerticesCoords[i] = p; valid[i] = true; @@ -978,9 +977,9 @@ void DelaunayGraphCut::addGridHelperPoints(int helperPointsGridSize, const Point _verticesCoords.reserve(_verticesCoords.size() + gridVerticesCoords.size()); _verticesAttr.reserve(_verticesAttr.size() + gridVerticesCoords.size()); int addedPoints = 0; - for(int i = 0; i < gridVerticesCoords.size(); ++i) + for (int i = 0; i < gridVerticesCoords.size(); ++i) { - if(!valid[i]) + if (!valid[i]) continue; _verticesCoords.push_back(gridVerticesCoords[i]); GC_vertexInfo newv; @@ -989,13 +988,13 @@ void DelaunayGraphCut::addGridHelperPoints(int helperPointsGridSize, const Point ++addedPoints; } - ALICEVISION_LOG_WARNING("Add " << addedPoints << " new helper points for a 3D grid of " << ns << "x" << ns << "x" - << ns << " (" << std::pow(float(ns + 1), 3.f) << " points)."); + ALICEVISION_LOG_WARNING("Add " << addedPoints << " new helper points for a 3D grid of " << ns << "x" << ns << "x" << ns << " (" + << std::pow(float(ns + 1), 3.f) << " points)."); } void DelaunayGraphCut::addMaskHelperPoints(const Point3d voxel[8], const StaticVector& cams, const FuseParams& params) { - if(params.maskHelperPointsWeight <= 0.0) + if (params.maskHelperPointsWeight <= 0.0) return; ALICEVISION_LOG_INFO("Add Mask Helper Points."); @@ -1004,7 +1003,7 @@ void DelaunayGraphCut::addMaskHelperPoints(const Point3d voxel[8], const StaticV mvsUtils::inflateHexahedron(voxel, inflatedVoxel, 1.01f); std::size_t nbPixels = 0; - for(const auto& imgParams : _mp.getImagesParams()) + for (const auto& imgParams : _mp.getImagesParams()) { nbPixels += imgParams.size; } @@ -1015,12 +1014,12 @@ void DelaunayGraphCut::addMaskHelperPoints(const Point3d voxel[8], const StaticV ALICEVISION_LOG_INFO("Load depth maps and add points."); { - for(int c = 0; c < cams.size(); c++) + for (int c = 0; c < cams.size(); c++) { image::Image depthMap; mvsUtils::readMap(c, _mp, mvsUtils::EFileType::depthMapFiltered, depthMap); - if(depthMap.size() <= 0) + if (depthMap.size() <= 0) { ALICEVISION_LOG_WARNING("Empty depth map (cam id: " << c << ")"); continue; @@ -1031,40 +1030,38 @@ void DelaunayGraphCut::addMaskHelperPoints(const Point3d voxel[8], const StaticV const int syMax = divideRoundUp(height, step); const int sxMax = divideRoundUp(width, step); - for(int sy = 0; sy < syMax; ++sy) + for (int sy = 0; sy < syMax; ++sy) { - for(int sx = 0; sx < sxMax; ++sx) + for (int sx = 0; sx < sxMax; ++sx) { float bestScore = 0; int bestX = 0; int bestY = 0; - for(int y = sy * step, ymax = std::min((sy + 1) * step, height); y < ymax; ++y) + for (int y = sy * step, ymax = std::min((sy + 1) * step, height); y < ymax; ++y) { - for(int x = sx * step, xmax = std::min((sx + 1) * step, width); x < xmax; ++x) + for (int x = sx * step, xmax = std::min((sx + 1) * step, width); x < xmax; ++x) { const std::size_t index = y * width + x; const float depth = depthMap(index); - + // -2 means that the pixels should be masked-out with mask helper points - if(depth > -1.5f) + if (depth > -1.5f) continue; int nbValidDepth = 0; const int kernelSize = params.maskBorderSize; - for(int ly = std::max(y - kernelSize, 0), lyMax = std::min(y + kernelSize, height - 1); - ly < lyMax; ++ly) + for (int ly = std::max(y - kernelSize, 0), lyMax = std::min(y + kernelSize, height - 1); ly < lyMax; ++ly) { - for(int lx = std::max(x - kernelSize, 0), lxMax = std::min(x + kernelSize, width - 1); - lx < lxMax; ++lx) + for (int lx = std::max(x - kernelSize, 0), lxMax = std::min(x + kernelSize, width - 1); lx < lxMax; ++lx) { if (depthMap(ly * width + lx) > 0.0f) ++nbValidDepth; } } - const float score = nbValidDepth; // TODO: best score based on nbValidDepth and kernel size ? - if(score > bestScore) + const float score = nbValidDepth; // TODO: best score based on nbValidDepth and kernel size ? + if (score > bestScore) { bestScore = score; bestX = x; @@ -1072,21 +1069,21 @@ void DelaunayGraphCut::addMaskHelperPoints(const Point3d voxel[8], const StaticV } } } - if(bestScore > 0.0f) + if (bestScore > 0.0f) { const Point3d& cam = _mp.CArr[c]; - Point3d maxP = cam + (_mp.iCamArr[c] * Point2d((float)bestX, (float)bestY)).normalize() * 10000000.0; + Point3d maxP = cam + (_mp.iCamArr[c] * Point2d((float)bestX, (float)bestY)).normalize() * 10000000.0; StaticVector* intersectionsPtr = mvsUtils::lineSegmentHexahedronIntersection(cam, maxP, inflatedVoxel); - if(intersectionsPtr->size() <= 0) + if (intersectionsPtr->size() <= 0) continue; Point3d p; double maxDepth = std::numeric_limits::min(); - for(Point3d& i : *intersectionsPtr) + for (Point3d& i : *intersectionsPtr) { const double depth = dist(cam, i); - if(depth > maxDepth) + if (depth > maxDepth) { p = i; maxDepth = depth; @@ -1121,7 +1118,7 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po const unsigned long nbValidDepths = computeNumberOfAllPoints(_mp, _mp.getProcessDownscale()); ALICEVISION_LOG_INFO("Number of all valid depths in input depth maps: " << nbValidDepths); std::size_t nbPixels = 0; - for(const auto& imgParams: _mp.getImagesParams()) + for (const auto& imgParams : _mp.getImagesParams()) { nbPixels += imgParams.size; } @@ -1130,12 +1127,11 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po step = std::max(step, params.minStep); std::size_t realMaxVertices = 0; std::vector startIndex(_mp.getNbCameras(), 0); - for(int i = 0; i < _mp.getNbCameras(); ++i) + for (int i = 0; i < _mp.getNbCameras(); ++i) { const auto& imgParams = _mp.getImageParams(i); startIndex[i] = realMaxVertices; - realMaxVertices += divideRoundUp(imgParams.width, step) * - divideRoundUp(imgParams.height, step); + realMaxVertices += divideRoundUp(imgParams.width, step) * divideRoundUp(imgParams.height, step); } std::vector verticesCoordsPrepare(realMaxVertices); std::vector pixSizePrepare(realMaxVertices); @@ -1154,8 +1150,8 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po ALICEVISION_LOG_INFO("Load depth maps and add points."); { omp_set_nested(1); - #pragma omp parallel for num_threads(3) - for(int c = 0; c < cams.size(); c++) +#pragma omp parallel for num_threads(3) + for (int c = 0; c < cams.size(); c++) { image::Image depthMap; image::Image simMap; @@ -1168,7 +1164,7 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po // read depth map mvsUtils::readMap(c, _mp, mvsUtils::EFileType::depthMapFiltered, depthMap); - if(depthMap.size() <= 0) + if (depthMap.size() <= 0) { ALICEVISION_LOG_WARNING("Empty depth map (cam id: " << c << ")"); continue; @@ -1179,12 +1175,10 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po { mvsUtils::readMap(c, _mp, mvsUtils::EFileType::simMapFiltered, simMap); image::Image simMapTmp; - imageAlgo::convolveImage(simMap, simMapTmp, "gaussian", - params.simGaussianSizeInit, - params.simGaussianSizeInit); + imageAlgo::convolveImage(simMap, simMapTmp, "gaussian", params.simGaussianSizeInit, params.simGaussianSizeInit); simMap.swap(simMapTmp); } - catch(const std::exception& e) + catch (const std::exception& e) { ALICEVISION_LOG_WARNING("simMap file can't be found."); simMap.resize(width, height, true, -1); @@ -1195,10 +1189,9 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po const std::string nmodMapFilepath = getFileNameFromIndex(_mp, c, mvsUtils::EFileType::nmodMap); // If we have an nModMap in input (from depthmapfilter) use it, // else init with a constant value. - if(boost::filesystem::exists(nmodMapFilepath)) + if (boost::filesystem::exists(nmodMapFilepath)) { - image::readImage(nmodMapFilepath, numOfModalsMap, - image::EImageColorSpace::NO_CONVERSION); + image::readImage(nmodMapFilepath, numOfModalsMap, image::EImageColorSpace::NO_CONVERSION); if (numOfModalsMap.Width() != width || numOfModalsMap.Height() != height) throw std::runtime_error("Wrong nmod map dimensions: " + nmodMapFilepath); } @@ -1211,10 +1204,10 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po const int syMax = divideRoundUp(height, step); const int sxMax = divideRoundUp(width, step); - #pragma omp parallel for - for(int sy = 0; sy < syMax; ++sy) +#pragma omp parallel for + for (int sy = 0; sy < syMax; ++sy) { - for(int sx = 0; sx < sxMax; ++sx) + for (int sx = 0; sx < sxMax; ++sx) { const int index = startIndex[c] + sy * sxMax + sx; float bestDepth = std::numeric_limits::max(); @@ -1222,22 +1215,20 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po float bestSimScore = 0; int bestX = 0; int bestY = 0; - for(int y = sy * step, ymax = std::min((sy+1) * step, height); - y < ymax; ++y) + for (int y = sy * step, ymax = std::min((sy + 1) * step, height); y < ymax; ++y) { - for(int x = sx * step, xmax = std::min((sx+1) * step, width); - x < xmax; ++x) + for (int x = sx * step, xmax = std::min((sx + 1) * step, width); x < xmax; ++x) { const std::size_t index = y * width + x; const float depth = depthMap(index); - if(depth <= 0.0f) + if (depth <= 0.0f) continue; int numOfModals = 0; const int scoreKernelSize = 1; - for(int ly = std::max(y-scoreKernelSize, 0), lyMax = std::min(y+scoreKernelSize, height-1); ly < lyMax; ++ly) + for (int ly = std::max(y - scoreKernelSize, 0), lyMax = std::min(y + scoreKernelSize, height - 1); ly < lyMax; ++ly) { - for(int lx = std::max(x-scoreKernelSize, 0), lxMax = std::min(x+scoreKernelSize, width-1); lx < lxMax; ++lx) + for (int lx = std::max(x - scoreKernelSize, 0), lxMax = std::min(x + scoreKernelSize, width - 1); lx < lxMax; ++lx) { if (depthMap(ly * width + lx) > 0.0f) { @@ -1246,13 +1237,13 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po } } float sim = simMap(index); - sim = sim < 0.0f ? 0.0f : sim; // clamp values < 0 + sim = sim < 0.0f ? 0.0f : sim; // clamp values < 0 // remap similarity values from [-1;+1] to [+1;+simScale] // interpretation is [goodSimilarity;badSimilarity] const float simScore = 1.0f + sim * params.simFactor; const float score = numOfModals + (1.0f / simScore); - if(score > bestScore) + if (score > bestScore) { bestDepth = depth; bestScore = score; @@ -1262,7 +1253,7 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po } } } - if(bestScore < 3*13) + if (bestScore < 3 * 13) { // discard the point pixSizePrepare[index] = -1.0; @@ -1270,9 +1261,9 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po else { const Point3d p = _mp.CArr[c] + (_mp.iCamArr[c] * Point2d((float)bestX, (float)bestY)).normalize() * bestDepth; - + // TODO: isPointInHexahedron: here or in the previous loop per pixel to not loose point? - if(voxel == nullptr || mvsUtils::isPointInHexahedron(p, voxel)) + if (voxel == nullptr || mvsUtils::isPointInHexahedron(p, voxel)) { verticesCoordsPrepare[index] = p; simScorePrepare[index] = bestSimScore; @@ -1304,8 +1295,16 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po // Compute the vertices positions and simScore from all input depthMap/simMap images, // and declare the visibility information (the cameras indexes seeing the vertex). - createVerticesWithVisibilities(cams, verticesCoordsPrepare, pixSizePrepare, simScorePrepare, - verticesAttrPrepare, _mp, params.simFactor, params.voteMarginFactor, params.contributeMarginFactor, params.simGaussianSize); + createVerticesWithVisibilities(cams, + verticesCoordsPrepare, + pixSizePrepare, + simScorePrepare, + verticesAttrPrepare, + _mp, + params.simFactor, + params.voteMarginFactor, + params.contributeMarginFactor, + params.simGaussianSize); ALICEVISION_LOG_INFO("Compute max angle per point"); @@ -1314,47 +1313,47 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po // and weight simScore with angular score #if defined(FUSE_COMPUTE_ANGLE_STATS) && !defined(OMP_HAVE_MIN_MAX_REDUCTION) ALICEVISION_LOG_DEBUG("Disable angle stats computation: OpenMP does not provide required min/max reduction clauses."); -#undef FUSE_COMPUTE_ANGLE_STATS + #undef FUSE_COMPUTE_ANGLE_STATS #endif #ifdef FUSE_COMPUTE_ANGLE_STATS double stat_minAngle = std::numeric_limits::max(), stat_maxAngle = 0.0; double stat_minAngleScore = std::numeric_limits::max(), stat_maxAngleScore = 0.0; - #pragma omp parallel for reduction(max: stat_maxAngle,stat_maxAngleScore) reduction(min: stat_minAngle,stat_minAngleScore) + #pragma omp parallel for reduction(max : stat_maxAngle, stat_maxAngleScore) reduction(min : stat_minAngle, stat_minAngleScore) #else -#pragma omp parallel for + #pragma omp parallel for #endif - for(int vIndex = 0; vIndex < verticesCoordsPrepare.size(); ++vIndex) + for (int vIndex = 0; vIndex < verticesCoordsPrepare.size(); ++vIndex) { - if(pixSizePrepare[vIndex] == -1.0) + if (pixSizePrepare[vIndex] == -1.0) { continue; } const std::vector& visCams = verticesAttrPrepare[vIndex].cams.getData(); - if(visCams.empty()) + if (visCams.empty()) { ALICEVISION_LOG_WARNING("BAD: visCams.empty()"); } double maxAngle = 0.0; - for(int i: visCams) + for (int i : visCams) { - for(int j: visCams) + for (int j : visCams) { - if(i == j) + if (i == j) continue; double angle = angleBetwABandAC(verticesCoordsPrepare[vIndex], _mp.CArr[i], _mp.CArr[j]); maxAngle = std::max(angle, maxAngle); } } // Kill the point if the angle is too small - if(maxAngle < params.minAngleThreshold) + if (maxAngle < params.minAngleThreshold) { pixSizePrepare[vIndex] = -1; minAngleCounter += 1; continue; } // Filter points based on their number of observations - if(visCams.size() < params.minVis) + if (visCams.size() < params.minVis) { pixSizePrepare[vIndex] = -1; minVisCounter += 1; @@ -1377,7 +1376,8 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po ALICEVISION_LOG_INFO("Angle min: " << stat_minAngle << ", max: " << stat_maxAngle << "."); ALICEVISION_LOG_INFO("Angle score min: " << stat_minAngleScore << ", max: " << stat_maxAngleScore << "."); #endif - ALICEVISION_LOG_INFO(minAngleCounter << " filtered points due to low angle of observations (minAngleThreshold=" << params.minAngleThreshold << "). "); + ALICEVISION_LOG_INFO(minAngleCounter << " filtered points due to low angle of observations (minAngleThreshold=" << params.minAngleThreshold + << "). "); ALICEVISION_LOG_INFO(minVisCounter << " filtered points due to low number of observations (minVis=" << params.minVis << "). "); removeInvalidPoints(verticesCoordsPrepare, pixSizePrepare, simScorePrepare, verticesAttrPrepare); @@ -1385,7 +1385,7 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po // while more points than the max points (with a limit to 20 iterations). double pixSizeMarginFinalCoef = params.pixSizeMarginFinalCoef; - for(int filteringIt = 0; filteringIt < 20; ++filteringIt) + for (int filteringIt = 0; filteringIt < 20; ++filteringIt) { ALICEVISION_LOG_INFO("Filter index: " << filteringIt << ", pixSizeMarginFinalCoef: " << pixSizeMarginFinalCoef); // Filter points with new simScore @@ -1394,7 +1394,7 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po ALICEVISION_LOG_INFO("Remove invalid points."); removeInvalidPoints(verticesCoordsPrepare, pixSizePrepare, simScorePrepare, verticesAttrPrepare); - if(verticesCoordsPrepare.size() < params.maxPoints) + if (verticesCoordsPrepare.size() < params.maxPoints) { ALICEVISION_LOG_INFO("The number of points is below the max number of vertices."); break; @@ -1402,26 +1402,36 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po else { pixSizeMarginFinalCoef *= 1.5; - ALICEVISION_LOG_INFO("Increase pixel size margin coef to " << pixSizeMarginFinalCoef << ", nb points: " << verticesCoordsPrepare.size() << ", maxVertices: " << params.maxPoints); + ALICEVISION_LOG_INFO("Increase pixel size margin coef to " << pixSizeMarginFinalCoef << ", nb points: " << verticesCoordsPrepare.size() + << ", maxVertices: " << params.maxPoints); } } - ALICEVISION_LOG_INFO("3D points loaded and filtered to " << verticesCoordsPrepare.size() << " points (maxVertices is " << params.maxPoints << ")."); + ALICEVISION_LOG_INFO("3D points loaded and filtered to " << verticesCoordsPrepare.size() << " points (maxVertices is " << params.maxPoints + << ")."); - if(params.refineFuse) + if (params.refineFuse) { ALICEVISION_LOG_INFO("Create final visibilities"); // Initialize the vertice attributes and declare the visibility information - createVerticesWithVisibilities(cams, verticesCoordsPrepare, pixSizePrepare, simScorePrepare, - verticesAttrPrepare, _mp, params.simFactor, params.voteMarginFactor, params.contributeMarginFactor, params.simGaussianSize); + createVerticesWithVisibilities(cams, + verticesCoordsPrepare, + pixSizePrepare, + simScorePrepare, + verticesAttrPrepare, + _mp, + params.simFactor, + params.voteMarginFactor, + params.contributeMarginFactor, + params.simGaussianSize); } - if(verticesCoordsPrepare.empty()) + if (verticesCoordsPrepare.empty()) throw std::runtime_error("Depth map fusion gives an empty result."); ALICEVISION_LOG_WARNING("fuseFromDepthMaps done: " << verticesCoordsPrepare.size() << " points created."); // Insert the new elements - if(_verticesCoords.empty()) + if (_verticesCoords.empty()) { // replace with the new points if emoty _verticesCoords.swap(verticesCoordsPrepare); @@ -1430,25 +1440,26 @@ void DelaunayGraphCut::fuseFromDepthMaps(const StaticVector& cams, const Po else { // concatenate the new elements with the previous ones - _verticesCoords.insert(_verticesCoords.end(), verticesCoordsPrepare.begin(), verticesCoordsPrepare.end() ); - _verticesAttr.insert(_verticesAttr.end(), verticesAttrPrepare.begin(), verticesAttrPrepare.end() ); + _verticesCoords.insert(_verticesCoords.end(), verticesCoordsPrepare.begin(), verticesCoordsPrepare.end()); + _verticesAttr.insert(_verticesAttr.end(), verticesAttrPrepare.begin(), verticesAttrPrepare.end()); } } -void DelaunayGraphCut::computeVerticesSegSize(std::vector& out_segments, const std::vector& useVertex, float alpha) // allPoints=true, alpha=0 +void DelaunayGraphCut::computeVerticesSegSize(std::vector& out_segments, + const std::vector& useVertex, + float alpha) // allPoints=true, alpha=0 { ALICEVISION_LOG_DEBUG("DelaunayGraphCut::computeVerticesSegSize"); out_segments.clear(); int scalePS = _mp.userParams.get("global.scalePS", 1); int step = _mp.userParams.get("global.step", 1); - float pointToJoinPixSizeDist = (float)_mp.userParams.get("delaunaycut.pointToJoinPixSizeDist", 2.0) * - (float)scalePS * (float)step * 2.0f; + float pointToJoinPixSizeDist = (float)_mp.userParams.get("delaunaycut.pointToJoinPixSizeDist", 2.0) * (float)scalePS * (float)step * 2.0f; std::vector edges; edges.reserve(_verticesAttr.size()); - if(alpha < 1.0f) + if (alpha < 1.0f) { alpha = 2.0f * std::max(2.0f, pointToJoinPixSizeDist); } @@ -1456,11 +1467,11 @@ void DelaunayGraphCut::computeVerticesSegSize(std::vector& out_segments, // long t1 = mvsUtils::initEstimate(); assert(_verticesCoords.size() == _verticesAttr.size()); - for(VertexIndex vi = 0; vi < _verticesAttr.size(); ++vi) + for (VertexIndex vi = 0; vi < _verticesAttr.size(); ++vi) { const GC_vertexInfo& v = _verticesAttr[vi]; const Point3d& p = _verticesCoords[vi]; - if((v.getNbCameras() > 0) && (useVertex.empty() || useVertex[vi])) + if ((v.getNbCameras() > 0) && (useVertex.empty() || useVertex[vi])) { int rc = v.getCamera(0); @@ -1468,16 +1479,16 @@ void DelaunayGraphCut::computeVerticesSegSize(std::vector& out_segments, GEO::vector adjVertices; _tetrahedralization->get_neighbors(vi, adjVertices); - for(VertexIndex nvi: adjVertices) + for (VertexIndex nvi : adjVertices) { const GC_vertexInfo& nv = _verticesAttr[nvi]; const Point3d& np = _verticesCoords[nvi]; - if((vi != nvi) && (useVertex.empty() || useVertex[nvi])) + if ((vi != nvi) && (useVertex.empty() || useVertex[nvi])) { - if((p - np).size() < - alpha * _mp.getCamPixelSize(p, rc)) // TODO FACA: why do we fuse again? And only based on the pixSize of the first camera?? + if ((p - np).size() < + alpha * _mp.getCamPixelSize(p, rc)) // TODO FACA: why do we fuse again? And only based on the pixSize of the first camera?? { - if(vi < nvi) // to remove duplicates + if (vi < nvi) // to remove duplicates { edges.emplace_back(vi, nvi); } @@ -1492,12 +1503,12 @@ void DelaunayGraphCut::computeVerticesSegSize(std::vector& out_segments, Universe u(_verticesAttr.size()); // t1 = mvsUtils::initEstimate(); - int s = (int)edges.size(); // Fuse all edges collected to be merged - for(int i = 0; i < s; i++) + int s = (int)edges.size(); // Fuse all edges collected to be merged + for (int i = 0; i < s; i++) { int a = u.find(edges[i].x); int b = u.find(edges[i].y); - if(a != b) // TODO FACA: Are they not different in all cases? + if (a != b) // TODO FACA: Are they not different in all cases? { u.join(a, b); } @@ -1508,10 +1519,10 @@ void DelaunayGraphCut::computeVerticesSegSize(std::vector& out_segments, out_segments.resize(_verticesAttr.size()); // Last loop over vertices to update segId - for(int vi = 0; vi < _verticesAttr.size(); ++vi) + for (int vi = 0; vi < _verticesAttr.size(); ++vi) { GC_vertexInfo& v = _verticesAttr[vi]; - if(v.isVirtual()) + if (v.isVirtual()) continue; GC_Seg& s = out_segments[vi]; @@ -1529,18 +1540,18 @@ void DelaunayGraphCut::removeSmallSegs(const std::vector& segments, int StaticVector toRemove; toRemove.reserve(getNbVertices()); - for(int i = 0; i < _verticesAttr.size(); ++i) + for (int i = 0; i < _verticesAttr.size(); ++i) { const GC_vertexInfo& v = _verticesAttr[i]; const GC_Seg& s = segments[i]; - if(v.isReal() && s.segSize < minSegSize) + if (v.isReal() && s.segSize < minSegSize) { toRemove.push_back(i); } } ALICEVISION_LOG_WARNING("removeSmallSegs removing " << toRemove.size() << " cells."); - for(int i = 0; i < toRemove.size(); i++) + for (int i = 0; i < toRemove.size(); i++) { int iR = toRemove[i]; GC_vertexInfo& v = _verticesAttr[iR]; @@ -1548,112 +1559,68 @@ void DelaunayGraphCut::removeSmallSegs(const std::vector& segments, int v.cams.clear(); // T.remove(fit); // TODO GEOGRAM } - } -DelaunayGraphCut::GeometryIntersection -DelaunayGraphCut::intersectNextGeom(const DelaunayGraphCut::GeometryIntersection& inGeometry, - const Point3d& originPt, - const Point3d& dirVect, Point3d& intersectPt, - const double epsilonFactor, const Point3d& lastIntersectPt) const +DelaunayGraphCut::GeometryIntersection DelaunayGraphCut::intersectNextGeom(const DelaunayGraphCut::GeometryIntersection& inGeometry, + const Point3d& originPt, + const Point3d& dirVect, + Point3d& intersectPt, + const double epsilonFactor, + const Point3d& lastIntersectPt) const { GeometryIntersection bestMatch; Point3d bestMatchIntersectPt; switch (inGeometry.type) { - case EGeometryType::Facet: - { - const CellIndex tetrahedronIndex = inGeometry.facet.cellIndex; - - // Test all facets of the tetrahedron using i as localVertexIndex to define next intersectionFacet - for (int i = 0; i < 4; ++i) + case EGeometryType::Facet: { - // Because we can't intersect with incoming facet (same localVertexIndex) - if (i == inGeometry.facet.localVertexIndex) - continue; + const CellIndex tetrahedronIndex = inGeometry.facet.cellIndex; - const Facet intersectionFacet(tetrahedronIndex, i); - bool ambiguous = false; - - const GeometryIntersection result = rayIntersectTriangle(originPt, dirVect, intersectionFacet, intersectPt, epsilonFactor, ambiguous, &lastIntersectPt); - if (result.type != EGeometryType::None) + // Test all facets of the tetrahedron using i as localVertexIndex to define next intersectionFacet + for (int i = 0; i < 4; ++i) { - if (!ambiguous) - return result; - - // Retrieve the best intersected point (farthest from origin point) - if (bestMatch.type == EGeometryType::None || (originPt - intersectPt).size() > (originPt - bestMatchIntersectPt).size()) - { - bestMatchIntersectPt = intersectPt; - bestMatch = result; - } - } - } - } - break; - - case EGeometryType::Vertex: - { - for (CellIndex adjCellIndex : getNeighboringCellsByVertexIndex(inGeometry.vertexIndex)) - { - if(isInvalidOrInfiniteCell(adjCellIndex)) - continue; - - // Get local vertex index - const VertexIndex localVertexIndex = _tetrahedralization->index(adjCellIndex, inGeometry.vertexIndex); - - // Define the facet to intersect - const Facet facet(adjCellIndex, localVertexIndex); - bool ambiguous = false; + // Because we can't intersect with incoming facet (same localVertexIndex) + if (i == inGeometry.facet.localVertexIndex) + continue; - const GeometryIntersection result = rayIntersectTriangle(originPt, dirVect, facet, intersectPt, epsilonFactor, ambiguous, &lastIntersectPt); - if (result.type != EGeometryType::None) - { - if (!ambiguous) - return result; + const Facet intersectionFacet(tetrahedronIndex, i); + bool ambiguous = false; - // Retrieve the best intersected point (farthest from origin point) - if (bestMatch.type == EGeometryType::None || (originPt - intersectPt).size() > (originPt - bestMatchIntersectPt).size()) + const GeometryIntersection result = + rayIntersectTriangle(originPt, dirVect, intersectionFacet, intersectPt, epsilonFactor, ambiguous, &lastIntersectPt); + if (result.type != EGeometryType::None) { - bestMatchIntersectPt = intersectPt; - bestMatch = result; + if (!ambiguous) + return result; + + // Retrieve the best intersected point (farthest from origin point) + if (bestMatch.type == EGeometryType::None || (originPt - intersectPt).size() > (originPt - bestMatchIntersectPt).size()) + { + bestMatchIntersectPt = intersectPt; + bestMatch = result; + } } } } - } - break; - - case EGeometryType::Edge: - { - GeometryIntersection result; + break; - for (CellIndex adjCellIndex : getNeighboringCellsByEdge(inGeometry.edge)) + case EGeometryType::Vertex: { - if(isInvalidOrInfiniteCell(adjCellIndex)) - continue; - // Local vertices indices - const VertexIndex lvi0 = _tetrahedralization->index(adjCellIndex, inGeometry.edge.v0); - const VertexIndex lvi1 = _tetrahedralization->index(adjCellIndex, inGeometry.edge.v1); + for (CellIndex adjCellIndex : getNeighboringCellsByVertexIndex(inGeometry.vertexIndex)) + { + if (isInvalidOrInfiniteCell(adjCellIndex)) + continue; - // The two facets that do not touch this edge need to be tested - const std::array opositeFacets{ {{adjCellIndex, lvi0}, {adjCellIndex, lvi1}} }; + // Get local vertex index + const VertexIndex localVertexIndex = _tetrahedralization->index(adjCellIndex, inGeometry.vertexIndex); - for (const Facet& facet : opositeFacets) - { + // Define the facet to intersect + const Facet facet(adjCellIndex, localVertexIndex); bool ambiguous = false; - const GeometryIntersection result = rayIntersectTriangle(originPt, dirVect, facet, intersectPt, epsilonFactor, ambiguous, &lastIntersectPt); - if (result.type == EGeometryType::Edge) - { - if(result.edge.isSameUndirectionalEdge(inGeometry.edge)) - { - ALICEVISION_LOG_ERROR("[intersectNextGeom] intersect the input edge itself, ignore intersection: inGeometry: " << inGeometry << ", intersected geo: " << result); - // do not intersect the input edge itself - continue; - } - // ALICEVISION_LOG_WARNING("[intersectNextGeom] intersect a new edge from an edge: inGeometry: " << inGeometry << ", intersected geo: " << result << ", intersectPt: " << intersectPt << ", lastIntersectPt: " << lastIntersectPt); - } + const GeometryIntersection result = + rayIntersectTriangle(originPt, dirVect, facet, intersectPt, epsilonFactor, ambiguous, &lastIntersectPt); if (result.type != EGeometryType::None) { if (!ambiguous) @@ -1668,13 +1635,62 @@ DelaunayGraphCut::intersectNextGeom(const DelaunayGraphCut::GeometryIntersection } } } - } - break; + break; + + case EGeometryType::Edge: + { + GeometryIntersection result; + + for (CellIndex adjCellIndex : getNeighboringCellsByEdge(inGeometry.edge)) + { + if (isInvalidOrInfiniteCell(adjCellIndex)) + continue; + // Local vertices indices + const VertexIndex lvi0 = _tetrahedralization->index(adjCellIndex, inGeometry.edge.v0); + const VertexIndex lvi1 = _tetrahedralization->index(adjCellIndex, inGeometry.edge.v1); + + // The two facets that do not touch this edge need to be tested + const std::array opositeFacets{{{adjCellIndex, lvi0}, {adjCellIndex, lvi1}}}; + + for (const Facet& facet : opositeFacets) + { + bool ambiguous = false; + const GeometryIntersection result = + rayIntersectTriangle(originPt, dirVect, facet, intersectPt, epsilonFactor, ambiguous, &lastIntersectPt); - case EGeometryType::None: - //throw std::runtime_error("[intersectNextGeom] intersection with input none geometry should not happen."); - ALICEVISION_LOG_WARNING("[intersectNextGeom] intersection with input none geometry should not happen: " << inGeometry); + if (result.type == EGeometryType::Edge) + { + if (result.edge.isSameUndirectionalEdge(inGeometry.edge)) + { + ALICEVISION_LOG_ERROR("[intersectNextGeom] intersect the input edge itself, ignore intersection: inGeometry: " + << inGeometry << ", intersected geo: " << result); + // do not intersect the input edge itself + continue; + } + // ALICEVISION_LOG_WARNING("[intersectNextGeom] intersect a new edge from an edge: inGeometry: " << inGeometry << ", + // intersected geo: " << result << ", intersectPt: " << intersectPt << ", lastIntersectPt: " << lastIntersectPt); + } + if (result.type != EGeometryType::None) + { + if (!ambiguous) + return result; + + // Retrieve the best intersected point (farthest from origin point) + if (bestMatch.type == EGeometryType::None || (originPt - intersectPt).size() > (originPt - bestMatchIntersectPt).size()) + { + bestMatchIntersectPt = intersectPt; + bestMatch = result; + } + } + } + } + } break; + + case EGeometryType::None: + // throw std::runtime_error("[intersectNextGeom] intersection with input none geometry should not happen."); + ALICEVISION_LOG_WARNING("[intersectNextGeom] intersection with input none geometry should not happen: " << inGeometry); + break; } intersectPt = bestMatchIntersectPt; @@ -1682,10 +1698,12 @@ DelaunayGraphCut::intersectNextGeom(const DelaunayGraphCut::GeometryIntersection } DelaunayGraphCut::GeometryIntersection DelaunayGraphCut::rayIntersectTriangle(const Point3d& originPt, - const Point3d& DirVec, - const DelaunayGraphCut::Facet& facet, - Point3d& intersectPt, - const double epsilonFactor, bool &ambiguous, const Point3d* lastIntersectPt) const + const Point3d& DirVec, + const DelaunayGraphCut::Facet& facet, + Point3d& intersectPt, + const double epsilonFactor, + bool& ambiguous, + const Point3d* lastIntersectPt) const { ambiguous = false; @@ -1714,8 +1732,8 @@ DelaunayGraphCut::GeometryIntersection DelaunayGraphCut::rayIntersectTriangle(co return GeometryIntersection(); } - const double u = triangleUv.x; // A to C - const double v = triangleUv.y; // A to B + const double u = triangleUv.x; // A to C + const double v = triangleUv.y; // A to B // If we find invalid uv coordinate if (!std::isfinite(u) || !std::isfinite(v)) @@ -1725,13 +1743,13 @@ DelaunayGraphCut::GeometryIntersection DelaunayGraphCut::rayIntersectTriangle(co if (u < -marginEpsilon || v < -marginEpsilon || (u + v) > (1.0 + marginEpsilon)) return GeometryIntersection(); - // In case intersectPt is provided, check if intersectPt is in front of lastIntersectionPt + // In case intersectPt is provided, check if intersectPt is in front of lastIntersectionPt // in the DirVec direction to ensure that we are moving forward in the right direction if (lastIntersectPt != nullptr) { const Point3d diff = tempIntersectPt - *lastIntersectPt; const double dotValue = dot(DirVec, diff.normalize()); - if(dotValue < marginEpsilon || diff.size() < 100 * std::numeric_limits::min()) + if (dotValue < marginEpsilon || diff.size() < 100 * std::numeric_limits::min()) { return GeometryIntersection(); } @@ -1745,35 +1763,35 @@ DelaunayGraphCut::GeometryIntersection DelaunayGraphCut::rayIntersectTriangle(co // Change intersection point only if tempIntersectionPt is in the right direction (mean we intersect something) intersectPt = tempIntersectPt; - if (v < marginEpsilon) // along A C edge + if (v < marginEpsilon) // along A C edge { if (u < marginEpsilon) { intersectPt = *A; - return GeometryIntersection(AvertexIndex); // vertex A + return GeometryIntersection(AvertexIndex); // vertex A } if (u > 1.0 - marginEpsilon) { intersectPt = *C; - return GeometryIntersection(CvertexIndex); // vertex C + return GeometryIntersection(CvertexIndex); // vertex C } - return GeometryIntersection(Edge(AvertexIndex, CvertexIndex)); // edge AC + return GeometryIntersection(Edge(AvertexIndex, CvertexIndex)); // edge AC } - if (u < marginEpsilon) // along A B edge + if (u < marginEpsilon) // along A B edge { if (v > 1.0 - marginEpsilon) { intersectPt = *B; - return GeometryIntersection(BvertexIndex); // vertex B + return GeometryIntersection(BvertexIndex); // vertex B } - return GeometryIntersection(Edge(AvertexIndex, BvertexIndex)); // edge AB + return GeometryIntersection(Edge(AvertexIndex, BvertexIndex)); // edge AB } if (u + v > 1.0 - marginEpsilon) - return GeometryIntersection(Edge(BvertexIndex, CvertexIndex)); // edge BC + return GeometryIntersection(Edge(BvertexIndex, CvertexIndex)); // edge BC return GeometryIntersection(facet); } @@ -1810,10 +1828,10 @@ double DelaunayGraphCut::maxEdgeLength() const { double dmax = 0.0f; - for(int ci = 0; ci < _cellsAttr.size(); ++ci) + for (int ci = 0; ci < _cellsAttr.size(); ++ci) { // choose finite facet - for(int k = 0; k < 4; ++k) + for (int k = 0; k < 4; ++k) { Facet facet(ci, k); dmax = std::max(dmax, facetMaxEdgeLength(facet)); @@ -1835,36 +1853,36 @@ Point3d DelaunayGraphCut::cellCircumScribedSphereCentre(CellIndex ci) const const Point3d d2 = r2 - r0; const Point3d d3 = r3 - r0; - float x = -(-(d1.x * d2.y * d3.z * conj(d1.x) - d1.x * d2.z * d3.y * conj(d1.x) + d1.y * d2.y * d3.z * conj(d1.y) - - d1.y * d2.z * d3.y * conj(d1.y) + d1.z * d2.y * d3.z * conj(d1.z) - d1.z * d2.z * d3.y * conj(d1.z) - - d1.y * d2.x * d3.z * conj(d2.x) + d1.z * d2.x * d3.y * conj(d2.x) - d1.y * d2.y * d3.z * conj(d2.y) + - d1.z * d2.y * d3.y * conj(d2.y) - d1.y * d2.z * d3.z * conj(d2.z) + d1.z * d2.z * d3.y * conj(d2.z) + - d1.y * d2.z * d3.x * conj(d3.x) - d1.z * d2.y * d3.x * conj(d3.x) + d1.y * d2.z * d3.y * conj(d3.y) - - d1.z * d2.y * d3.y * conj(d3.y) + d1.y * d2.z * d3.z * conj(d3.z) - d1.z * d2.y * d3.z * conj(d3.z)) / - (2 * d1.x * d2.y * d3.z - 2 * d1.x * d2.z * d3.y - 2 * d1.y * d2.x * d3.z + 2 * d1.y * d2.z * d3.x + - 2 * d1.z * d2.x * d3.y - 2 * d1.z * d2.y * d3.x)); - float y = -((d1.x * d2.x * d3.z * conj(d1.x) - d1.x * d2.z * d3.x * conj(d1.x) + d1.y * d2.x * d3.z * conj(d1.y) - - d1.y * d2.z * d3.x * conj(d1.y) + d1.z * d2.x * d3.z * conj(d1.z) - d1.z * d2.z * d3.x * conj(d1.z) - - d1.x * d2.x * d3.z * conj(d2.x) + d1.z * d2.x * d3.x * conj(d2.x) - d1.x * d2.y * d3.z * conj(d2.y) + - d1.z * d2.y * d3.x * conj(d2.y) - d1.x * d2.z * d3.z * conj(d2.z) + d1.z * d2.z * d3.x * conj(d2.z) + - d1.x * d2.z * d3.x * conj(d3.x) - d1.z * d2.x * d3.x * conj(d3.x) + d1.x * d2.z * d3.y * conj(d3.y) - - d1.z * d2.x * d3.y * conj(d3.y) + d1.x * d2.z * d3.z * conj(d3.z) - d1.z * d2.x * d3.z * conj(d3.z)) / - (2 * d1.x * d2.y * d3.z - 2 * d1.x * d2.z * d3.y - 2 * d1.y * d2.x * d3.z + 2 * d1.y * d2.z * d3.x + - 2 * d1.z * d2.x * d3.y - 2 * d1.z * d2.y * d3.x)); - float z = -(-(d1.x * d2.x * d3.y * conj(d1.x) - d1.x * d2.y * d3.x * conj(d1.x) + d1.y * d2.x * d3.y * conj(d1.y) - - d1.y * d2.y * d3.x * conj(d1.y) + d1.z * d2.x * d3.y * conj(d1.z) - d1.z * d2.y * d3.x * conj(d1.z) - - d1.x * d2.x * d3.y * conj(d2.x) + d1.y * d2.x * d3.x * conj(d2.x) - d1.x * d2.y * d3.y * conj(d2.y) + - d1.y * d2.y * d3.x * conj(d2.y) - d1.x * d2.z * d3.y * conj(d2.z) + d1.y * d2.z * d3.x * conj(d2.z) + - d1.x * d2.y * d3.x * conj(d3.x) - d1.y * d2.x * d3.x * conj(d3.x) + d1.x * d2.y * d3.y * conj(d3.y) - - d1.y * d2.x * d3.y * conj(d3.y) + d1.x * d2.y * d3.z * conj(d3.z) - d1.y * d2.x * d3.z * conj(d3.z)) / - (2 * d1.x * d2.y * d3.z - 2 * d1.x * d2.z * d3.y - 2 * d1.y * d2.x * d3.z + 2 * d1.y * d2.z * d3.x + - 2 * d1.z * d2.x * d3.y - 2 * d1.z * d2.y * d3.x)); + float x = + -(-(d1.x * d2.y * d3.z * conj(d1.x) - d1.x * d2.z * d3.y * conj(d1.x) + d1.y * d2.y * d3.z * conj(d1.y) - d1.y * d2.z * d3.y * conj(d1.y) + + d1.z * d2.y * d3.z * conj(d1.z) - d1.z * d2.z * d3.y * conj(d1.z) - d1.y * d2.x * d3.z * conj(d2.x) + d1.z * d2.x * d3.y * conj(d2.x) - + d1.y * d2.y * d3.z * conj(d2.y) + d1.z * d2.y * d3.y * conj(d2.y) - d1.y * d2.z * d3.z * conj(d2.z) + d1.z * d2.z * d3.y * conj(d2.z) + + d1.y * d2.z * d3.x * conj(d3.x) - d1.z * d2.y * d3.x * conj(d3.x) + d1.y * d2.z * d3.y * conj(d3.y) - d1.z * d2.y * d3.y * conj(d3.y) + + d1.y * d2.z * d3.z * conj(d3.z) - d1.z * d2.y * d3.z * conj(d3.z)) / + (2 * d1.x * d2.y * d3.z - 2 * d1.x * d2.z * d3.y - 2 * d1.y * d2.x * d3.z + 2 * d1.y * d2.z * d3.x + 2 * d1.z * d2.x * d3.y - + 2 * d1.z * d2.y * d3.x)); + float y = + -((d1.x * d2.x * d3.z * conj(d1.x) - d1.x * d2.z * d3.x * conj(d1.x) + d1.y * d2.x * d3.z * conj(d1.y) - d1.y * d2.z * d3.x * conj(d1.y) + + d1.z * d2.x * d3.z * conj(d1.z) - d1.z * d2.z * d3.x * conj(d1.z) - d1.x * d2.x * d3.z * conj(d2.x) + d1.z * d2.x * d3.x * conj(d2.x) - + d1.x * d2.y * d3.z * conj(d2.y) + d1.z * d2.y * d3.x * conj(d2.y) - d1.x * d2.z * d3.z * conj(d2.z) + d1.z * d2.z * d3.x * conj(d2.z) + + d1.x * d2.z * d3.x * conj(d3.x) - d1.z * d2.x * d3.x * conj(d3.x) + d1.x * d2.z * d3.y * conj(d3.y) - d1.z * d2.x * d3.y * conj(d3.y) + + d1.x * d2.z * d3.z * conj(d3.z) - d1.z * d2.x * d3.z * conj(d3.z)) / + (2 * d1.x * d2.y * d3.z - 2 * d1.x * d2.z * d3.y - 2 * d1.y * d2.x * d3.z + 2 * d1.y * d2.z * d3.x + 2 * d1.z * d2.x * d3.y - + 2 * d1.z * d2.y * d3.x)); + float z = + -(-(d1.x * d2.x * d3.y * conj(d1.x) - d1.x * d2.y * d3.x * conj(d1.x) + d1.y * d2.x * d3.y * conj(d1.y) - d1.y * d2.y * d3.x * conj(d1.y) + + d1.z * d2.x * d3.y * conj(d1.z) - d1.z * d2.y * d3.x * conj(d1.z) - d1.x * d2.x * d3.y * conj(d2.x) + d1.y * d2.x * d3.x * conj(d2.x) - + d1.x * d2.y * d3.y * conj(d2.y) + d1.y * d2.y * d3.x * conj(d2.y) - d1.x * d2.z * d3.y * conj(d2.z) + d1.y * d2.z * d3.x * conj(d2.z) + + d1.x * d2.y * d3.x * conj(d3.x) - d1.y * d2.x * d3.x * conj(d3.x) + d1.x * d2.y * d3.y * conj(d3.y) - d1.y * d2.x * d3.y * conj(d3.y) + + d1.x * d2.y * d3.z * conj(d3.z) - d1.y * d2.x * d3.z * conj(d3.z)) / + (2 * d1.x * d2.y * d3.z - 2 * d1.x * d2.z * d3.y - 2 * d1.y * d2.x * d3.z + 2 * d1.y * d2.z * d3.x + 2 * d1.z * d2.x * d3.y - + 2 * d1.z * d2.y * d3.x)); return r0 + Point3d(x, y, z); } -// Returns a small score if one of the tetrahedon (from one side of the facet) is strange (the point in front of the current facet is far from the center). -// Returns the best value when the tetrahedons on both side of the facet are equilaterals. +// Returns a small score if one of the tetrahedon (from one side of the facet) is strange (the point in front of the current facet is far from the +// center). Returns the best value when the tetrahedons on both side of the facet are equilaterals. double DelaunayGraphCut::getFaceWeight(const Facet& f1) const { const Facet f2 = mirrorFacet(f1); @@ -1878,13 +1896,13 @@ double DelaunayGraphCut::getFaceWeight(const Facet& f1) const const Point3d n = cross((B - A).normalize(), (C - A).normalize()).normalize(); double a1 = fabs(angleBetwV1andV2(n, (A - s1).normalize())); - if(a1 > 90) + if (a1 > 90) { a1 = 180.0 - a1; } double a2 = fabs(angleBetwV1andV2(n, (A - s2).normalize())); - if(a2 > 90) + if (a2 > 90) { a2 = 180.0 - a2; } @@ -1894,16 +1912,16 @@ double DelaunayGraphCut::getFaceWeight(const Facet& f1) const double wf = 1.0 - std::min(std::cos(a1), std::cos(a2)); - if((std::isnan(wf)) || (wf < 0.0f) || (wf > 1.0f)) + if ((std::isnan(wf)) || (wf < 0.0f) || (wf > 1.0f)) return 1.0; return wf; } -float DelaunayGraphCut::weightFcn(float nrc, bool labatutWeights, int /*ncams*/) +float DelaunayGraphCut::weightFcn(float nrc, bool labatutWeights, int /*ncams*/) { float weight = 0.0f; - if(labatutWeights) + if (labatutWeights) { weight = 32.0f; } @@ -1914,22 +1932,25 @@ float DelaunayGraphCut::weightFcn(float nrc, bool labatutWeights, int /*ncams*/ return weight; } -void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, bool labatutWeights, bool fillOut, float distFcnHeight, - float fullWeight) // nPixelSizeBehind=2*spaceSteps allPoints=1 behind=0 - // labatutWeights=0 fillOut=1 distFcnHeight=0 +void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, + bool labatutWeights, + bool fillOut, + float distFcnHeight, + float fullWeight) // nPixelSizeBehind=2*spaceSteps allPoints=1 behind=0 + // labatutWeights=0 fillOut=1 distFcnHeight=0 { ALICEVISION_LOG_INFO("Computing s-t graph weights."); long t1 = clock(); // loop over all cells ... initialize - for(GC_cellInfo& c: _cellsAttr) + for (GC_cellInfo& c : _cellsAttr) { c.cellSWeight = 0.0f; c.cellTWeight = 0.0f; c.fullnessScore = 0.0f; c.emptinessScore = 0.0f; c.on = 0.0f; - for(int s = 0; s < 4; s++) + for (int s = 0; s < 4; s++) { c.gEdgeVisWeight[s] = 0.0f; } @@ -1948,20 +1969,18 @@ void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, bool labatutWeights, b size_t totalOfVertex = 0; size_t totalIsRealNrc = 0; - + GeometriesCount totalGeometriesIntersectedFrontCount; GeometriesCount totalGeometriesIntersectedBehindCount; - auto progressDisplay = - system::createConsoleProgressDisplay(std::min(size_t(100), verticesRandIds.size()), - std::cout, "fillGraphPartPtRc\n"); + auto progressDisplay = system::createConsoleProgressDisplay(std::min(size_t(100), verticesRandIds.size()), std::cout, "fillGraphPartPtRc\n"); size_t progressStep = verticesRandIds.size() / 100; progressStep = std::max(size_t(1), progressStep); #pragma omp parallel for reduction(+:totalStepsFront,totalRayFront,totalStepsBehind,totalRayBehind,totalCamHaveVisibilityOnVertex,totalOfVertex,totalIsRealNrc) - for(int i = 0; i < verticesRandIds.size(); i++) + for (int i = 0; i < verticesRandIds.size(); i++) { - if(i % progressStep == 0) + if (i % progressStep == 0) { ++progressDisplay; } @@ -1972,13 +1991,13 @@ void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, bool labatutWeights, b GeometriesCount subTotalGeometriesIntersectedFrontCount; GeometriesCount subTotalGeometriesIntersectedBehindCount; - if(v.isReal()) + if (v.isReal()) { ++totalIsRealNrc; // "weight" is called alpha(p) in the paper - const float weight = weightFcn((float)v.nrc, labatutWeights, v.getNbCameras()); // number of cameras + const float weight = weightFcn((float)v.nrc, labatutWeights, v.getNbCameras()); // number of cameras - for(int c = 0; c < v.cams.size(); c++) + for (int c = 0; c < v.cams.size(); c++) { assert(v.cams[c] >= 0); assert(v.cams[c] < _mp.ncams); @@ -1987,10 +2006,17 @@ void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, bool labatutWeights, b int stepsBehind = 0; GeometriesCount geometriesIntersectedFrontCount; GeometriesCount geometriesIntersectedBehindCount; - fillGraphPartPtRc(stepsFront, stepsBehind, geometriesIntersectedFrontCount, - geometriesIntersectedBehindCount, vertexIndex, v.cams[c], weight, fullWeight, + fillGraphPartPtRc(stepsFront, + stepsBehind, + geometriesIntersectedFrontCount, + geometriesIntersectedBehindCount, + vertexIndex, + v.cams[c], + weight, + fullWeight, nPixelSizeBehind, - fillOut, distFcnHeight); + fillOut, + distFcnHeight); totalStepsFront += stepsFront; totalRayFront += 1; @@ -1999,23 +2025,17 @@ void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, bool labatutWeights, b subTotalGeometriesIntersectedFrontCount += geometriesIntersectedFrontCount; subTotalGeometriesIntersectedBehindCount += geometriesIntersectedBehindCount; - } // for c + } // for c totalCamHaveVisibilityOnVertex += v.cams.size(); totalOfVertex += 1; - boost::atomic_ref{totalGeometriesIntersectedFrontCount.facets} += - subTotalGeometriesIntersectedFrontCount.facets; - boost::atomic_ref{totalGeometriesIntersectedFrontCount.vertices} += - subTotalGeometriesIntersectedFrontCount.vertices; - boost::atomic_ref{totalGeometriesIntersectedFrontCount.edges} += - subTotalGeometriesIntersectedFrontCount.edges; - boost::atomic_ref{totalGeometriesIntersectedBehindCount.facets} += - subTotalGeometriesIntersectedBehindCount.facets; - boost::atomic_ref{totalGeometriesIntersectedBehindCount.vertices} += - subTotalGeometriesIntersectedBehindCount.vertices; - boost::atomic_ref{totalGeometriesIntersectedBehindCount.edges} += - subTotalGeometriesIntersectedBehindCount.edges; + boost::atomic_ref{totalGeometriesIntersectedFrontCount.facets} += subTotalGeometriesIntersectedFrontCount.facets; + boost::atomic_ref{totalGeometriesIntersectedFrontCount.vertices} += subTotalGeometriesIntersectedFrontCount.vertices; + boost::atomic_ref{totalGeometriesIntersectedFrontCount.edges} += subTotalGeometriesIntersectedFrontCount.edges; + boost::atomic_ref{totalGeometriesIntersectedBehindCount.facets} += subTotalGeometriesIntersectedBehindCount.facets; + boost::atomic_ref{totalGeometriesIntersectedBehindCount.vertices} += subTotalGeometriesIntersectedBehindCount.vertices; + boost::atomic_ref{totalGeometriesIntersectedBehindCount.edges} += subTotalGeometriesIntersectedBehindCount.edges; } } @@ -2035,10 +2055,17 @@ void DelaunayGraphCut::fillGraph(double nPixelSizeBehind, bool labatutWeights, b mvsUtils::printfElapsedTime(t1, "s-t graph weights computed : "); } -void DelaunayGraphCut::fillGraphPartPtRc( - int& outTotalStepsFront, int& outTotalStepsBehind, GeometriesCount& outFrontCount, GeometriesCount& outBehindCount, - int vertexIndex, int cam, float weight, float fullWeight, double nPixelSizeBehind, - bool fillOut, float distFcnHeight) // nPixelSizeBehind=2*spaceSteps allPoints=1 behind=0 fillOut=1 distFcnHeight=0 +void DelaunayGraphCut::fillGraphPartPtRc(int& outTotalStepsFront, + int& outTotalStepsBehind, + GeometriesCount& outFrontCount, + GeometriesCount& outBehindCount, + int vertexIndex, + int cam, + float weight, + float fullWeight, + double nPixelSizeBehind, + bool fillOut, + float distFcnHeight) // nPixelSizeBehind=2*spaceSteps allPoints=1 behind=0 fillOut=1 distFcnHeight=0 { const int maxint = std::numeric_limits::max(); const double marginEpsilonFactor = 1.0e-4; @@ -2050,10 +2077,10 @@ void DelaunayGraphCut::fillGraphPartPtRc( assert(cam >= 0); assert(cam < _mp.ncams); - if(fillOut) // EMPTY part + if (fillOut) // EMPTY part { // Initialisation - GeometryIntersection geometry(vertexIndex); // Starting on global vertex index + GeometryIntersection geometry(vertexIndex); // Starting on global vertex index Point3d intersectPt = originPt; // toTheCam const double pointCamDistance = (_mp.CArr[cam] - originPt).size(); @@ -2066,7 +2093,7 @@ void DelaunayGraphCut::fillGraphPartPtRc( Facet lastIntersectedFacet; bool lastGeoIsVertex = false; // Break only when we reach our camera vertex (as long as we find a next geometry) - while(geometry.type != EGeometryType::Vertex || (_mp.CArr[cam] - intersectPt).size() >= 1.0e-3) + while (geometry.type != EGeometryType::Vertex || (_mp.CArr[cam] - intersectPt).size() >= 1.0e-3) { lastGeoIsVertex = false; // Keep previous informations @@ -2083,7 +2110,8 @@ void DelaunayGraphCut::fillGraphPartPtRc( if (geometry.type == EGeometryType::None) { #ifdef ALICEVISION_DEBUG_VOTE - // exportBackPropagationMesh("fillGraph_v" + std::to_string(vertexIndex) + "_ToCam_typeNone", history.geometries, originPt, _mp.CArr[cam]); + // exportBackPropagationMesh("fillGraph_v" + std::to_string(vertexIndex) + "_ToCam_typeNone", history.geometries, originPt, + // _mp.CArr[cam]); #endif // ALICEVISION_LOG_DEBUG( // "[Error]: fillGraph(toTheCam) cause: geometry cannot be found." @@ -2093,7 +2121,7 @@ void DelaunayGraphCut::fillGraphPartPtRc( break; } - if((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) + if ((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) { // Inverse direction, stop break; @@ -2130,20 +2158,22 @@ void DelaunayGraphCut::fillGraphPartPtRc( #ifdef ALICEVISION_DEBUG_VOTE // exportBackPropagationMesh("fillGraph_ToCam_invalidMirorFacet", history.geometries, originPt, _mp.CArr[cam]); #endif - //ALICEVISION_LOG_DEBUG("[Error]: fillGraph(toTheCam) cause: invalidOrInfinite miror facet."); + // ALICEVISION_LOG_DEBUG("[Error]: fillGraph(toTheCam) cause: invalidOrInfinite miror facet."); break; } lastIntersectedFacet = mFacet; - if(previousGeometry.type == EGeometryType::Facet && outFrontCount.facets > 10000) + if (previousGeometry.type == EGeometryType::Facet && outFrontCount.facets > 10000) { - ALICEVISION_LOG_WARNING("fillGraphPartPtRc front: loop on facets. Current landmark index: " << vertexIndex << ", camera: " << cam << ", outFrontCount: " << outFrontCount); + ALICEVISION_LOG_WARNING("fillGraphPartPtRc front: loop on facets. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", outFrontCount: " << outFrontCount); break; } } else { // We have just intersected a vertex or edge. - // These geometries do not have a cellIndex, so we use the previousGeometry to retrieve the cell between the previous geometry and the current one. + // These geometries do not have a cellIndex, so we use the previousGeometry to retrieve the cell between the previous geometry and the + // current one. if (previousGeometry.type == EGeometryType::Facet) { boost::atomic_ref{_cellsAttr[previousGeometry.facet.cellIndex].emptinessScore} += weight; @@ -2153,26 +2183,27 @@ void DelaunayGraphCut::fillGraphPartPtRc( { ++outFrontCount.vertices; lastGeoIsVertex = true; - if(previousGeometry.type == EGeometryType::Vertex && outFrontCount.vertices > 1000) + if (previousGeometry.type == EGeometryType::Vertex && outFrontCount.vertices > 1000) { - ALICEVISION_LOG_WARNING("fillGraphPartPtRc front: loop on vertices. Current landmark index: " << vertexIndex << ", camera: " << cam << ", outFrontCount: " << outFrontCount); + ALICEVISION_LOG_WARNING("fillGraphPartPtRc front: loop on vertices. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", outFrontCount: " << outFrontCount); break; } } else if (geometry.type == EGeometryType::Edge) { ++outFrontCount.edges; - if(previousGeometry.type == EGeometryType::Edge && outFrontCount.edges > 1000) + if (previousGeometry.type == EGeometryType::Edge && outFrontCount.edges > 1000) { - ALICEVISION_LOG_WARNING("fillGraphPartPtRc front: loop on edges. Current landmark index: " << vertexIndex << ", camera: " << cam << ", outFrontCount: " << outFrontCount); + ALICEVISION_LOG_WARNING("fillGraphPartPtRc front: loop on edges. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", outFrontCount: " << outFrontCount); break; } } } - + // Declare the last part of the empty path as connected to EMPTY (S node in the graph cut) - if (lastIntersectedFacet.cellIndex != GEO::NO_CELL && - (_mp.CArr[cam] - intersectPt).size() < 0.2 * pointCamDistance) + if (lastIntersectedFacet.cellIndex != GEO::NO_CELL && (_mp.CArr[cam] - intersectPt).size() < 0.2 * pointCamDistance) { boost::atomic_ref{_cellsAttr[lastIntersectedFacet.cellIndex].cellSWeight} = (float)maxint; } @@ -2203,11 +2234,11 @@ void DelaunayGraphCut::fillGraphPartPtRc( // } } - if(pixSize > 0.0f) // fillIn FULL part + if (pixSize > 0.0f) // fillIn FULL part { const float fWeight = fullWeight * weight; // Initialisation - GeometryIntersection geometry(vertexIndex); // Starting on global vertex index + GeometryIntersection geometry(vertexIndex); // Starting on global vertex index Point3d intersectPt = originPt; // behindThePoint const Point3d dirVect = (originPt - _mp.CArr[cam]).normalize(); @@ -2235,7 +2266,8 @@ void DelaunayGraphCut::fillGraphPartPtRc( if (geometry.type == EGeometryType::None) { - // If we come from a facet, the next intersection must exist (even if the mirror facet is invalid, which is verified after taking mirror facet) + // If we come from a facet, the next intersection must exist (even if the mirror facet is invalid, which is verified after taking + // mirror facet) if (previousGeometry.type == EGeometryType::Facet) { #ifdef ALICEVISION_DEBUG_VOTE @@ -2247,7 +2279,7 @@ void DelaunayGraphCut::fillGraphPartPtRc( break; } - if((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) + if ((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) { // Inverse direction, stop break; @@ -2277,12 +2309,12 @@ void DelaunayGraphCut::fillGraphPartPtRc( { const float dist = distFcn(maxDist, (originPt - lastIntersectPt).size(), distFcnHeight); - boost::atomic_ref{_cellsAttr[geometry.facet.cellIndex].gEdgeVisWeight[geometry.facet.localVertexIndex]} += - fWeight * dist; + boost::atomic_ref{_cellsAttr[geometry.facet.cellIndex].gEdgeVisWeight[geometry.facet.localVertexIndex]} += fWeight * dist; } - if(previousGeometry.type == EGeometryType::Facet && outBehindCount.facets > 1000) + if (previousGeometry.type == EGeometryType::Facet && outBehindCount.facets > 1000) { - ALICEVISION_LOG_WARNING("fillGraphPartPtRc behind: loop on facets. Current landmark index: " << vertexIndex << ", camera: " << cam << ", outBehindCount: " << outBehindCount); + ALICEVISION_LOG_WARNING("fillGraphPartPtRc behind: loop on facets. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", outBehindCount: " << outBehindCount); break; } } @@ -2294,15 +2326,21 @@ void DelaunayGraphCut::fillGraphPartPtRc( { if (previousGeometry.type != EGeometryType::Vertex) { - ALICEVISION_LOG_ERROR("[error] The firstIteration vote could only happen during for the first cell when we come from the first vertex."); - //throw std::runtime_error("[error] The firstIteration vote could only happen during for the first cell when we come from the first vertex."); + ALICEVISION_LOG_ERROR( + "[error] The firstIteration vote could only happen during for the first cell when we come from the first vertex."); + // throw std::runtime_error("[error] The firstIteration vote could only happen during for the first cell when we come from the + // first vertex."); } // the information of first intersected cell can only be found by taking intersection of neighbouring cells for both geometries const std::vector previousNeighbouring = getNeighboringCellsByVertexIndex(previousGeometry.vertexIndex); const std::vector currentNeigbouring = getNeighboringCellsByGeometry(geometry); std::vector neighboringCells; - std::set_intersection(previousNeighbouring.begin(), previousNeighbouring.end(), currentNeigbouring.begin(), currentNeigbouring.end(), std::back_inserter(neighboringCells)); + std::set_intersection(previousNeighbouring.begin(), + previousNeighbouring.end(), + currentNeigbouring.begin(), + currentNeigbouring.end(), + std::back_inserter(neighboringCells)); for (const CellIndex& ci : neighboringCells) { @@ -2312,7 +2350,8 @@ void DelaunayGraphCut::fillGraphPartPtRc( } // We have just intersected a vertex or edge. - // These geometries do not have a cellIndex, so we use the previousGeometry to retrieve the cell between the previous geometry and the current one. + // These geometries do not have a cellIndex, so we use the previousGeometry to retrieve the cell between the previous geometry and the + // current one. if (previousGeometry.type == EGeometryType::Facet) { boost::atomic_ref{_cellsAttr[previousGeometry.facet.cellIndex].fullnessScore} += fWeight; @@ -2321,26 +2360,28 @@ void DelaunayGraphCut::fillGraphPartPtRc( if (geometry.type == EGeometryType::Vertex) { ++outBehindCount.vertices; - if(previousGeometry.type == EGeometryType::Vertex && outBehindCount.vertices > 1000) + if (previousGeometry.type == EGeometryType::Vertex && outBehindCount.vertices > 1000) { - ALICEVISION_LOG_WARNING("fillGraphPartPtRc behind: loop on vertices. Current landmark index: " << vertexIndex << ", camera: " << cam << ", outBehindCount: " << outBehindCount); + ALICEVISION_LOG_WARNING("fillGraphPartPtRc behind: loop on vertices. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", outBehindCount: " << outBehindCount); break; } } else if (geometry.type == EGeometryType::Edge) { ++outBehindCount.edges; - if(previousGeometry.type == EGeometryType::Edge && outBehindCount.edges > 1000) + if (previousGeometry.type == EGeometryType::Edge && outBehindCount.edges > 1000) { - ALICEVISION_LOG_WARNING("fillGraphPartPtRc behind: loop on edges. Current landmark index: " << vertexIndex << ", camera: " << cam << ", outBehindCount: " << outBehindCount); + ALICEVISION_LOG_WARNING("fillGraphPartPtRc behind: loop on edges. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", outBehindCount: " << outBehindCount); break; } } } } - // cv: is the tetrahedron in distance 2*sigma behind the point p in the direction of the camera c (called Lcp in the paper) Vote for the last found facet - // Vote for the last intersected facet (farthest from the camera) + // cv: is the tetrahedron in distance 2*sigma behind the point p in the direction of the camera c (called Lcp in the paper) Vote for the last + // found facet Vote for the last intersected facet (farthest from the camera) if (lastIntersectedFacet.cellIndex != GEO::NO_CELL) { boost::atomic_ref{_cellsAttr[lastIntersectedFacet.cellIndex].cellTWeight} += fWeight; @@ -2372,7 +2413,7 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) const float nsigmaBackSilentPart = (float)_mp.userParams.get("delaunaycut.nsigmaBackSilentPart", 2.0f); ALICEVISION_LOG_DEBUG("nsigmaBackSilentPart: " << nsigmaBackSilentPart); - for(GC_cellInfo& c: _cellsAttr) + for (GC_cellInfo& c : _cellsAttr) { c.on = 0.0f; // WARNING out is not the same as the sum because the sum are counted edges behind as well @@ -2398,18 +2439,18 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) GeometriesCount totalGeometriesIntersectedFrontCount; GeometriesCount totalGeometriesIntersectedBehindCount; -#pragma omp parallel for reduction(+:totalStepsFront,totalRayFront,totalStepsBehind,totalRayBehind) - for(int i = 0; i < verticesRandIds.size(); ++i) +#pragma omp parallel for reduction(+ : totalStepsFront, totalRayFront, totalStepsBehind, totalRayBehind) + for (int i = 0; i < verticesRandIds.size(); ++i) { const int vertexIndex = verticesRandIds[i]; const GC_vertexInfo& v = _verticesAttr[vertexIndex]; - if(v.isVirtual()) + if (v.isVirtual()) continue; ++totalVertexIsVirtual; const Point3d& originPt = _verticesCoords[vertexIndex]; // For each camera that has visibility over the vertex v (vertexIndex) - for(const int cam : v.cams) + for (const int cam : v.cams) { GeometriesCount geometriesIntersectedFrontCount; GeometriesCount geometriesIntersectedBehindCount; @@ -2424,7 +2465,7 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) { // Initialisation - GeometryIntersection geometry(vertexIndex); // Starting on global vertex index + GeometryIntersection geometry(vertexIndex); // Starting on global vertex index Point3d intersectPt = originPt; // toTheCam const Point3d dirVect = (_mp.CArr[cam] - originPt).normalize(); @@ -2435,8 +2476,9 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) // As long as we find a next geometry Point3d lastIntersectPt = originPt; // Iterate on geometries in the direction of camera's vertex within margin defined by maxDist (as long as we find a next geometry) - while ((geometry.type != EGeometryType::Vertex || (_mp.CArr[cam] - intersectPt).size() > 1.0e-3) // We reach our camera vertex - && (lastIntersectPt - originPt).size() <= (nsigmaJumpPart + nsigmaFrontSilentPart) * maxDist) // We are to far from the originPt + while ((geometry.type != EGeometryType::Vertex || (_mp.CArr[cam] - intersectPt).size() > 1.0e-3) // We reach our camera vertex + && + (lastIntersectPt - originPt).size() <= (nsigmaJumpPart + nsigmaFrontSilentPart) * maxDist) // We are to far from the originPt { // Keep previous informations const GeometryIntersection previousGeometry = geometry; @@ -2458,7 +2500,7 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) break; } - if((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) + if ((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) { // Inverse direction, stop break; @@ -2480,7 +2522,7 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) { ++geometriesIntersectedFrontCount.facets; const GC_cellInfo& c = _cellsAttr[geometry.facet.cellIndex]; - if ((lastIntersectPt - originPt).size() > nsigmaFrontSilentPart * maxDist) // (p-originPt).size() > 2 * sigma + if ((lastIntersectPt - originPt).size() > nsigmaFrontSilentPart * maxDist) // (p-originPt).size() > 2 * sigma { // minJump = std::min(minJump, c.emptinessScore); maxJump = std::max(maxJump, c.emptinessScore); @@ -2502,38 +2544,41 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) break; } geometry.facet = mFacet; - if(previousGeometry.type == EGeometryType::Facet && geometriesIntersectedFrontCount.facets > 10000) + if (previousGeometry.type == EGeometryType::Facet && geometriesIntersectedFrontCount.facets > 10000) { - ALICEVISION_LOG_WARNING("forceTedgesByGradient front: loop on facets. Current landmark index: " << vertexIndex << ", camera: " << cam << ", intersectPt: " << intersectPt << ", lastIntersectPt: " << lastIntersectPt << ", geometriesIntersectedFrontCount: " << geometriesIntersectedFrontCount); + ALICEVISION_LOG_WARNING("forceTedgesByGradient front: loop on facets. Current landmark index: " + << vertexIndex << ", camera: " << cam << ", intersectPt: " << intersectPt << ", lastIntersectPt: " + << lastIntersectPt << ", geometriesIntersectedFrontCount: " << geometriesIntersectedFrontCount); break; } } else if (geometry.type == EGeometryType::Vertex) { ++geometriesIntersectedFrontCount.vertices; - if(previousGeometry.type == EGeometryType::Vertex && geometriesIntersectedFrontCount.vertices > 1000) + if (previousGeometry.type == EGeometryType::Vertex && geometriesIntersectedFrontCount.vertices > 1000) { - ALICEVISION_LOG_WARNING("forceTedgesByGradient front: loop on edges. Current landmark index: " << vertexIndex << ", camera: " << cam << ", geometriesIntersectedFrontCount: " << geometriesIntersectedFrontCount); + ALICEVISION_LOG_WARNING("forceTedgesByGradient front: loop on edges. Current landmark index: " + << vertexIndex << ", camera: " << cam + << ", geometriesIntersectedFrontCount: " << geometriesIntersectedFrontCount); break; } } else if (geometry.type == EGeometryType::Edge) { ++geometriesIntersectedFrontCount.edges; - if(previousGeometry.type == EGeometryType::Edge && geometriesIntersectedFrontCount.edges > 1000) + if (previousGeometry.type == EGeometryType::Edge && geometriesIntersectedFrontCount.edges > 1000) { - ALICEVISION_LOG_WARNING("forceTedgesByGradient front: loop on edges. Current landmark index: " << vertexIndex << ", camera: " << cam << ", geometriesIntersectedFrontCount: " << geometriesIntersectedFrontCount); + ALICEVISION_LOG_WARNING("forceTedgesByGradient front: loop on edges. Current landmark index: " + << vertexIndex << ", camera: " << cam + << ", geometriesIntersectedFrontCount: " << geometriesIntersectedFrontCount); break; } } } ++totalRayFront; - boost::atomic_ref{totalGeometriesIntersectedFrontCount.facets} += - geometriesIntersectedFrontCount.facets; - boost::atomic_ref{totalGeometriesIntersectedFrontCount.vertices} += - geometriesIntersectedFrontCount.vertices; - boost::atomic_ref{totalGeometriesIntersectedFrontCount.edges} += - geometriesIntersectedFrontCount.edges; + boost::atomic_ref{totalGeometriesIntersectedFrontCount.facets} += geometriesIntersectedFrontCount.facets; + boost::atomic_ref{totalGeometriesIntersectedFrontCount.vertices} += geometriesIntersectedFrontCount.vertices; + boost::atomic_ref{totalGeometriesIntersectedFrontCount.edges} += geometriesIntersectedFrontCount.edges; } { // Initialisation @@ -2548,7 +2593,7 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) Facet lastIntersectedFacet; bool firstIteration = true; - Point3d lastIntersectPt = originPt; + Point3d lastIntersectPt = originPt; // While we are within the surface margin defined by maxDist (as long as we find a next geometry) while ((lastIntersectPt - originPt).size() <= nsigmaBackSilentPart * maxDist) @@ -2564,26 +2609,28 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) geometry = intersectNextGeom(previousGeometry, originPt, dirVect, intersectPt, marginEpsilonFactor, lastIntersectPt); - if(geometry.type == EGeometryType::None) + if (geometry.type == EGeometryType::None) { -// // If we come from a facet, the next intersection must exist (even if the mirror facet is invalid, which is verified later) -// if (previousGeometry.type == EGeometryType::Facet) -// { -// #ifdef ALICEVISION_DEBUG_VOTE -// // exportBackPropagationMesh("forceTedges_behindThePoint_NoneButPreviousIsFacet", history.geometries, originPt, _mp.CArr[cam]); -// #endif -// ALICEVISION_LOG_DEBUG("[Error]: forceTedges(behindThePoint) cause: None geometry but previous is Facet."); -// } + // // If we come from a facet, the next intersection must exist (even if the mirror facet is invalid, + // which is verified later) if (previousGeometry.type == EGeometryType::Facet) + // { + // #ifdef ALICEVISION_DEBUG_VOTE + // // exportBackPropagationMesh("forceTedges_behindThePoint_NoneButPreviousIsFacet", + // history.geometries, originPt, _mp.CArr[cam]); + // #endif + // ALICEVISION_LOG_DEBUG("[Error]: forceTedges(behindThePoint) cause: None geometry but previous + // is Facet."); + // } // Break if we reach the end of the tetrahedralization volume break; } - if((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) + if ((intersectPt - originPt).size() <= (lastIntersectPt - originPt).size()) { // Inverse direction, stop break; } - if(geometry.type == EGeometryType::Facet) + if (geometry.type == EGeometryType::Facet) { ++geometriesIntersectedBehindCount.facets; @@ -2607,9 +2654,11 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) // Break if we reach the end of the tetrahedralization volume (mirror facet cannot be found) break; } - if(previousGeometry.type == EGeometryType::Facet && geometriesIntersectedBehindCount.facets > 1000) + if (previousGeometry.type == EGeometryType::Facet && geometriesIntersectedBehindCount.facets > 1000) { - ALICEVISION_LOG_WARNING("forceTedgesByGradient behind: loop on facets. Current landmark index: " << vertexIndex << ", camera: " << cam << ", geometriesIntersectedBehindCount: " << geometriesIntersectedBehindCount); + ALICEVISION_LOG_WARNING("forceTedgesByGradient behind: loop on facets. Current landmark index: " + << vertexIndex << ", camera: " << cam + << ", geometriesIntersectedBehindCount: " << geometriesIntersectedBehindCount); break; } } @@ -2623,14 +2672,20 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) { ALICEVISION_LOG_ERROR("The firstIteration vote could only happen during for " "the first cell when we come from the first vertex."); - // throw std::runtime_error("[error] The firstIteration vote could only happen during for the first cell when we come from the first vertex."); + // throw std::runtime_error("[error] The firstIteration vote could only happen during for the first cell when we come + // from the first vertex."); } - // the information of first intersected cell can only be found by taking intersection of neighbouring cells for both geometries + // the information of first intersected cell can only be found by taking intersection of neighbouring cells for both + // geometries const std::vector previousNeighbouring = getNeighboringCellsByVertexIndex(previousGeometry.vertexIndex); const std::vector currentNeigbouring = getNeighboringCellsByGeometry(geometry); std::vector neighboringCells; - std::set_intersection(previousNeighbouring.begin(), previousNeighbouring.end(), currentNeigbouring.begin(), currentNeigbouring.end(), std::back_inserter(neighboringCells)); + std::set_intersection(previousNeighbouring.begin(), + previousNeighbouring.end(), + currentNeigbouring.begin(), + currentNeigbouring.end(), + std::back_inserter(neighboringCells)); for (const CellIndex& ci : neighboringCells) { @@ -2642,18 +2697,22 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) if (geometry.type == EGeometryType::Vertex) { ++geometriesIntersectedBehindCount.vertices; - if(previousGeometry.type == EGeometryType::Vertex && geometriesIntersectedBehindCount.vertices > 1000) + if (previousGeometry.type == EGeometryType::Vertex && geometriesIntersectedBehindCount.vertices > 1000) { - ALICEVISION_LOG_WARNING("forceTedgesByGradient behind: loop on vertices. Current landmark index: " << vertexIndex << ", camera: " << cam << ", geometriesIntersectedBehindCount: " << geometriesIntersectedBehindCount); + ALICEVISION_LOG_WARNING("forceTedgesByGradient behind: loop on vertices. Current landmark index: " + << vertexIndex << ", camera: " << cam + << ", geometriesIntersectedBehindCount: " << geometriesIntersectedBehindCount); break; } } else if (geometry.type == EGeometryType::Edge) { ++geometriesIntersectedBehindCount.edges; - if(previousGeometry.type == EGeometryType::Edge && geometriesIntersectedBehindCount.edges > 1000) + if (previousGeometry.type == EGeometryType::Edge && geometriesIntersectedBehindCount.edges > 1000) { - ALICEVISION_LOG_WARNING("forceTedgesByGradient behind: loop on edges. Current landmark index: " << vertexIndex << ", camera: " << cam << ", geometriesIntersectedBehindCount: " << geometriesIntersectedBehindCount); + ALICEVISION_LOG_WARNING("forceTedgesByGradient behind: loop on edges. Current landmark index: " + << vertexIndex << ", camera: " << cam + << ", geometriesIntersectedBehindCount: " << geometriesIntersectedBehindCount); break; } } @@ -2676,10 +2735,10 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) // midSilent: score of the next tetrahedron directly after p (called T1 in the paper) // maxSilent: max score of emptiness for the tetrahedron around the point p (+/- 2*sigma around p) - if((midSilent / maxJump < forceTEdgeDelta) && // (g / B) < k_rel //// k_rel=0.1 - (maxJump - midSilent > minJumpPartRange) && // (B - g) > k_abs //// k_abs=10000 // 1000 in the paper - (maxSilent < maxSilentPartRange)) // g < k_outl //// k_outl=100 // 400 in the paper - //(maxSilent-minSilent minJumpPartRange) && // (B - g) > k_abs //// k_abs=10000 // 1000 in the paper + (maxSilent < maxSilentPartRange)) // g < k_outl //// k_outl=100 // 400 in the paper + //(maxSilent-minSilent{_cellsAttr[lastIntersectedFacet.cellIndex].on} += (maxJump - midSilent); } @@ -2694,7 +2753,7 @@ void DelaunayGraphCut::forceTedgesByGradientIJCV(float nPixelSizeBehind) totalOfVertex += 1; } - for(GC_cellInfo& c: _cellsAttr) + for (GC_cellInfo& c : _cellsAttr) { const float w = std::max(1.0f, c.cellTWeight) * c.on; @@ -2725,21 +2784,21 @@ int DelaunayGraphCut::computeIsOnSurface(std::vector& vertexIsOnSurface) c int nbSurfaceFacets = 0; // loop over all facets - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - for(VertexIndex k = 0; k < 4; ++k) + for (VertexIndex k = 0; k < 4; ++k) { const Facet f1(ci, k); - const bool uo = _cellIsFull[f1.cellIndex]; // get if it is occupied - if(!uo) + const bool uo = _cellIsFull[f1.cellIndex]; // get if it is occupied + if (!uo) continue; const Facet f2 = mirrorFacet(f1); - if(isInvalidOrInfiniteCell(f2.cellIndex)) + if (isInvalidOrInfiniteCell(f2.cellIndex)) continue; - const bool vo = _cellIsFull[f2.cellIndex]; // get if it is occupied + const bool vo = _cellIsFull[f2.cellIndex]; // get if it is occupied - if(vo) + if (vo) continue; VertexIndex v1 = getVertexIndex(f1, 0); @@ -2750,7 +2809,7 @@ int DelaunayGraphCut::computeIsOnSurface(std::vector& vertexIsOnSurface) c vertexIsOnSurface[v2] = true; vertexIsOnSurface[v3] = true; - assert(!(isInfiniteCell(f1.cellIndex) && isInfiniteCell(f2.cellIndex))); // infinite both cells of finite vertex! + assert(!(isInfiniteCell(f1.cellIndex) && isInfiniteCell(f2.cellIndex))); // infinite both cells of finite vertex! } } ALICEVISION_LOG_INFO("computeIsOnSurface nbSurfaceFacets: " << nbSurfaceFacets); @@ -2766,11 +2825,12 @@ void DelaunayGraphCut::graphCutPostProcessing(const Point3d hexah[8], const std: bool doRemoveBubbles = _mp.userParams.get("hallucinationsFiltering.doRemoveBubbles", true); bool doRemoveDust = _mp.userParams.get("hallucinationsFiltering.doRemoveDust", true); bool doLeaveLargestFullSegmentOnly = _mp.userParams.get("hallucinationsFiltering.doLeaveLargestFullSegmentOnly", false); - int invertTetrahedronBasedOnNeighborsNbIterations = _mp.userParams.get("hallucinationsFiltering.invertTetrahedronBasedOnNeighborsNbIterations", 10); + int invertTetrahedronBasedOnNeighborsNbIterations = + _mp.userParams.get("hallucinationsFiltering.invertTetrahedronBasedOnNeighborsNbIterations", 10); double minSolidAngleRatio = _mp.userParams.get("hallucinationsFiltering.minSolidAngleRatio", 0.2); int nbSolidAngleFilteringIterations = _mp.userParams.get("hallucinationsFiltering.nbSolidAngleFilteringIterations", 10); - if(doRemoveBubbles) + if (doRemoveBubbles) { // Remove empty spaces that are not connected to at least one camera removeBubbles(); @@ -2781,50 +2841,51 @@ void DelaunayGraphCut::graphCutPostProcessing(const Point3d hexah[8], const std: ALICEVISION_LOG_INFO("[" << __LINE__ << "] Nb full cells: " << nbFullCells << " / " << _cellIsFull.size() << " cells."); } - if(true) + if (true) { // free all full cell that have a camera vertex int nbModifiedCells = 0; - for(int rc = 0; rc < _mp.ncams; rc++) + for (int rc = 0; rc < _mp.ncams; rc++) { VertexIndex cam_vi = _camsVertexes[rc]; - if(cam_vi == GEO::NO_VERTEX) + if (cam_vi == GEO::NO_VERTEX) continue; - for(CellIndex adjCellIndex : getNeighboringCellsByVertexIndex(cam_vi)) // GEOGRAM: set_stores_cicl(true) required + for (CellIndex adjCellIndex : getNeighboringCellsByVertexIndex(cam_vi)) // GEOGRAM: set_stores_cicl(true) required { - if(isInvalidOrInfiniteCell(adjCellIndex)) + if (isInvalidOrInfiniteCell(adjCellIndex)) continue; - if(_cellIsFull[adjCellIndex]) + if (_cellIsFull[adjCellIndex]) { _cellIsFull[adjCellIndex] = false; ++nbModifiedCells; } } } - ALICEVISION_LOG_WARNING("Declare empty around camera centers: " << nbModifiedCells << " cells changed to empty within " << _cellIsFull.size() << " cells."); + ALICEVISION_LOG_WARNING("Declare empty around camera centers: " << nbModifiedCells << " cells changed to empty within " << _cellIsFull.size() + << " cells."); } - - if(doRemoveDust) + + if (doRemoveDust) { removeDust(minSegmentSize); } - if(doLeaveLargestFullSegmentOnly) + if (doLeaveLargestFullSegmentOnly) { leaveLargestFullSegmentOnly(); } - if(saveTemporaryBinFiles) + if (saveTemporaryBinFiles) { saveDhInfo(folderName + "delaunayTriangulationInfoAfterHallRemoving.bin"); } - if(_mp.userParams.get("LargeScale.saveDelaunayTriangulation", false)) + if (_mp.userParams.get("LargeScale.saveDelaunayTriangulation", false)) { - const std::string fileNameDh = folderName + "delaunayTriangulation.bin"; - const std::string fileNameInfo = folderName + "delaunayTriangulationInfo.bin"; - saveDh(fileNameDh, fileNameInfo); + const std::string fileNameDh = folderName + "delaunayTriangulation.bin"; + const std::string fileNameInfo = folderName + "delaunayTriangulationInfo.bin"; + saveDh(fileNameDh, fileNameInfo); } invertFullStatusForSmallLabels(); @@ -2833,44 +2894,42 @@ void DelaunayGraphCut::graphCutPostProcessing(const Point3d hexah[8], const std: // Changed status of cells to improve coherence with neighboring tetrahedrons // If 3 or 4 facets are connected to cells of the opporite status, // it is better to update the current status. - for(int i = 0; i < invertTetrahedronBasedOnNeighborsNbIterations; ++i) + for (int i = 0; i < invertTetrahedronBasedOnNeighborsNbIterations; ++i) { StaticVector toDoInverse; toDoInverse.reserve(_cellIsFull.size()); - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { int count = 0; - for(int k = 0; k < 4; ++k) + for (int k = 0; k < 4; ++k) { const CellIndex nci = _tetrahedralization->cell_adjacent(ci, k); - if(nci == GEO::NO_CELL) + if (nci == GEO::NO_CELL) continue; count += (_cellIsFull[nci] != _cellIsFull[ci]); } - if(count > 2) + if (count > 2) toDoInverse.push_back(ci); } - if(toDoInverse.empty()) + if (toDoInverse.empty()) break; int movedToEmpty = 0; int movedToFull = 0; - for(std::size_t i = 0; i < toDoInverse.size(); ++i) + for (std::size_t i = 0; i < toDoInverse.size(); ++i) { CellIndex ci = toDoInverse[i]; _cellIsFull[ci] = !_cellIsFull[ci]; - if(_cellIsFull[ci]) + if (_cellIsFull[ci]) ++movedToFull; else ++movedToEmpty; } - ALICEVISION_LOG_WARNING("[" << i << "] Coherence with neighboring tetrahedrons: " - << movedToFull << " cells moved to full, " - << movedToEmpty << " cells moved to empty within " - << _cellIsFull.size() << " cells."); + ALICEVISION_LOG_WARNING("[" << i << "] Coherence with neighboring tetrahedrons: " << movedToFull << " cells moved to full, " + << movedToEmpty << " cells moved to empty within " << _cellIsFull.size() << " cells."); } } - + cellsStatusFilteringBySolidAngleRatio(nbSolidAngleFilteringIterations, minSolidAngleRatio); ALICEVISION_LOG_INFO("Graph cut post-processing done."); @@ -2893,9 +2952,9 @@ void DelaunayGraphCut::invertFullStatusForSmallLabels() StaticVector buff; buff.reserve(nbCells); - for(CellIndex ci = 0; ci < nbCells; ++ci) + for (CellIndex ci = 0; ci < nbCells; ++ci) { - if(colorPerCell[ci] == -1) + if (colorPerCell[ci] == -1) { // backtrack all connected interior cells buff.resize(0); @@ -2904,16 +2963,16 @@ void DelaunayGraphCut::invertFullStatusForSmallLabels() colorPerCell[ci] = lastColorId; nbCellsPerColor[lastColorId] += 1; - while(buff.size() > 0) + while (buff.size() > 0) { CellIndex tmp_ci = buff.pop(); - for(int k = 0; k < 4; ++k) + for (int k = 0; k < 4; ++k) { const CellIndex nci = _tetrahedralization->cell_adjacent(tmp_ci, k); - if(nci == GEO::NO_CELL) + if (nci == GEO::NO_CELL) continue; - if((colorPerCell[nci] == -1) && (_cellIsFull[nci] == _cellIsFull[ci])) + if ((colorPerCell[nci] == -1) && (_cellIsFull[nci] == _cellIsFull[ci])) { colorPerCell[nci] = lastColorId; nbCellsPerColor[lastColorId] += 1; @@ -2921,47 +2980,46 @@ void DelaunayGraphCut::invertFullStatusForSmallLabels() } } } - nbCellsPerColor.push_back(0); // add new color with 0 cell + nbCellsPerColor.push_back(0); // add new color with 0 cell ++lastColorId; assert(lastColorId == nbCellsPerColor.size() - 1); } } - assert(nbCellsPerColor[nbCellsPerColor.size()-1] == 0); - nbCellsPerColor.resize(nbCellsPerColor.size()-1); // remove last empty element + assert(nbCellsPerColor[nbCellsPerColor.size() - 1] == 0); + nbCellsPerColor.resize(nbCellsPerColor.size() - 1); // remove last empty element int movedToEmpty = 0; int movedToFull = 0; - for(CellIndex ci = 0; ci < nbCells; ++ci) + for (CellIndex ci = 0; ci < nbCells; ++ci) { - if(nbCellsPerColor[colorPerCell[ci]] < 100) + if (nbCellsPerColor[colorPerCell[ci]] < 100) { _cellIsFull[ci] = !_cellIsFull[ci]; - if(_cellIsFull[ci]) + if (_cellIsFull[ci]) ++movedToFull; else ++movedToEmpty; } } - ALICEVISION_LOG_WARNING("DelaunayGraphCut::invertFullStatusForSmallLabels: " - << movedToFull << " cells moved to full, " - << movedToEmpty << " cells moved to empty within " - << _cellIsFull.size() << " cells."); + ALICEVISION_LOG_WARNING("DelaunayGraphCut::invertFullStatusForSmallLabels: " << movedToFull << " cells moved to full, " << movedToEmpty + << " cells moved to empty within " << _cellIsFull.size() + << " cells."); - ALICEVISION_LOG_DEBUG("Number of labels: " << nbCellsPerColor.size() << ", Number of cells changed: " << movedToFull + movedToEmpty << ", full number of cells: " << nbCells); + ALICEVISION_LOG_DEBUG("Number of labels: " << nbCellsPerColor.size() << ", Number of cells changed: " << movedToFull + movedToEmpty + << ", full number of cells: " << nbCells); } -void DelaunayGraphCut::cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFilteringIterations, - double minSolidAngleRatio) +void DelaunayGraphCut::cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFilteringIterations, double minSolidAngleRatio) { - if(nbSolidAngleFilteringIterations <= 0 || minSolidAngleRatio <= 0.0) + if (nbSolidAngleFilteringIterations <= 0 || minSolidAngleRatio <= 0.0) return; constexpr double fullSphereSolidAngle = 4.0 * boost::math::constants::pi(); // Change cells status on surface around vertices to improve smoothness // using solid angle ratio between full/empty parts. - for(int i = 0; i < nbSolidAngleFilteringIterations; ++i) + for (int i = 0; i < nbSolidAngleFilteringIterations; ++i) { std::vector cellsInvertStatus(_cellIsFull.size(), false); int toInvertCount = 0; @@ -2970,9 +3028,9 @@ void DelaunayGraphCut::cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFil const int nbSurfaceFacets = computeIsOnSurface(vertexIsOnSurface); #pragma omp parallel for reduction(+ : toInvertCount) - for(int vi = 0; vi < _neighboringCellsPerVertex.size(); ++vi) + for (int vi = 0; vi < _neighboringCellsPerVertex.size(); ++vi) { - if(!vertexIsOnSurface[vi]) + if (!vertexIsOnSurface[vi]) continue; // ALICEVISION_LOG_INFO("vertex is on surface: " << vi); const std::vector& neighboringCells = _neighboringCellsPerVertex[vi]; @@ -2980,23 +3038,23 @@ void DelaunayGraphCut::cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFil neighboringFacets.reserve(neighboringCells.size()); bool borderCase = false; double fullPartSolidAngle = 0.0; - for(CellIndex ci : neighboringCells) + for (CellIndex ci : neighboringCells) { // ALICEVISION_LOG_INFO("full cell: " << ci); std::vector triangle; triangle.reserve(3); GEO::signed_index_t localVertexIndex = 0; - for(int k = 0; k < 4; ++k) + for (int k = 0; k < 4; ++k) { const GEO::signed_index_t currentVertex = _tetrahedralization->cell_vertex(ci, k); - if(currentVertex == GEO::NO_VERTEX) + if (currentVertex == GEO::NO_VERTEX) break; - if(currentVertex != vi) + if (currentVertex != vi) triangle.push_back(currentVertex); else localVertexIndex = k; } - if(triangle.size() != 3) + if (triangle.size() != 3) { borderCase = true; break; @@ -3010,13 +3068,12 @@ void DelaunayGraphCut::cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFil { const Point3d& O = _verticesCoords[vi]; const double s = - tetrahedronSolidAngle(_verticesCoords[triangle[0]] - O, _verticesCoords[triangle[1]] - O, - _verticesCoords[triangle[2]] - O); + tetrahedronSolidAngle(_verticesCoords[triangle[0]] - O, _verticesCoords[triangle[1]] - O, _verticesCoords[triangle[2]] - O); // ALICEVISION_LOG_INFO("tetrahedronSolidAngle: " << s); fullPartSolidAngle += s; } } - if(borderCase) + if (borderCase) { // we cannot compute empty/full ratio if we have undefined cells continue; @@ -3025,146 +3082,150 @@ void DelaunayGraphCut::cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFil bool invertFull = false; // ALICEVISION_LOG_INFO("fullPartSolidAngle: " << fullPartSolidAngle << ", ratio: " // << fullPartSolidAngle / fullSphereSolidAngle); - if(fullPartSolidAngle < minSolidAngleRatio * fullSphereSolidAngle) + if (fullPartSolidAngle < minSolidAngleRatio * fullSphereSolidAngle) { invert = true; - invertFull = true; // we want to invert the FULL cells + invertFull = true; // we want to invert the FULL cells } - else if(fullPartSolidAngle > (1.0 - minSolidAngleRatio) * fullSphereSolidAngle) + else if (fullPartSolidAngle > (1.0 - minSolidAngleRatio) * fullSphereSolidAngle) { invert = true; - invertFull = false; // we want to invert the EMPTY cells + invertFull = false; // we want to invert the EMPTY cells } - if(!invert) + if (!invert) continue; // Ensure that we do not increase inconsitencies (like holes). // Check the status coherency with neighbor cells if we swap the cells status. - for(const Facet& f : neighboringFacets) + for (const Facet& f : neighboringFacets) { - if(_cellIsFull[f.cellIndex] == invertFull) + if (_cellIsFull[f.cellIndex] == invertFull) { const Facet fv = mirrorFacet(f); - if(isInvalidOrInfiniteCell(fv.cellIndex) || _cellIsFull[f.cellIndex] != _cellIsFull[fv.cellIndex]) + if (isInvalidOrInfiniteCell(fv.cellIndex) || _cellIsFull[f.cellIndex] != _cellIsFull[fv.cellIndex]) { borderCase = true; break; } } } - if(borderCase) + if (borderCase) continue; // Invert some cells - for(CellIndex ci : neighboringCells) + for (CellIndex ci : neighboringCells) { - if(_cellIsFull[ci] == invertFull) + if (_cellIsFull[ci] == invertFull) { boost::atomic_ref{cellsInvertStatus[ci]} = true; ++toInvertCount; } } } - if(toInvertCount == 0) + if (toInvertCount == 0) break; int movedToEmpty = 0; int movedToFull = 0; - for(CellIndex ci = 0; ci < cellsInvertStatus.size(); ++ci) + for (CellIndex ci = 0; ci < cellsInvertStatus.size(); ++ci) { - if(!cellsInvertStatus[ci]) + if (!cellsInvertStatus[ci]) continue; _cellIsFull[ci] = !_cellIsFull[ci]; - if(_cellIsFull[ci]) + if (_cellIsFull[ci]) ++movedToFull; else ++movedToEmpty; } - ALICEVISION_LOG_WARNING("[" << i << "] Check solid angle full/empty ratio on surface vertices: " << movedToFull - << " cells moved to full, " << movedToEmpty << " cells moved to empty within " - << _cellIsFull.size() << " cells."); + ALICEVISION_LOG_WARNING("[" << i << "] Check solid angle full/empty ratio on surface vertices: " << movedToFull << " cells moved to full, " + << movedToEmpty << " cells moved to empty within " << _cellIsFull.size() << " cells."); } } -void DelaunayGraphCut::createDensePointCloud(const Point3d hexah[8], const StaticVector& cams, const sfmData::SfMData* sfmData, const FuseParams* depthMapsFuseParams) +void DelaunayGraphCut::createDensePointCloud(const Point3d hexah[8], + const StaticVector& cams, + const sfmData::SfMData* sfmData, + const FuseParams* depthMapsFuseParams) { - assert(sfmData != nullptr || depthMapsFuseParams != nullptr); + assert(sfmData != nullptr || depthMapsFuseParams != nullptr); - ALICEVISION_LOG_INFO("Creating dense point cloud."); + ALICEVISION_LOG_INFO("Creating dense point cloud."); - const int helperPointsGridSize = _mp.userParams.get("LargeScale.helperPointsGridSize", 10); - const int densifyNbFront = _mp.userParams.get("LargeScale.densifyNbFront", 0); - const int densifyNbBack = _mp.userParams.get("LargeScale.densifyNbBack", 0); - const double densifyScale = _mp.userParams.get("LargeScale.densifyScale", 1.0); + const int helperPointsGridSize = _mp.userParams.get("LargeScale.helperPointsGridSize", 10); + const int densifyNbFront = _mp.userParams.get("LargeScale.densifyNbFront", 0); + const int densifyNbBack = _mp.userParams.get("LargeScale.densifyNbBack", 0); + const double densifyScale = _mp.userParams.get("LargeScale.densifyScale", 1.0); - const float minDist = + const float minDist = hexah && (helperPointsGridSize > 0) - ? ((hexah[0] - hexah[1]).size() + (hexah[0] - hexah[3]).size() + (hexah[0] - hexah[4]).size()) / - (3. * helperPointsGridSize) - : 0.00001f; + ? ((hexah[0] - hexah[1]).size() + (hexah[0] - hexah[3]).size() + (hexah[0] - hexah[4]).size()) / (3. * helperPointsGridSize) + : 0.00001f; - // add points from depth maps - if(depthMapsFuseParams != nullptr) - fuseFromDepthMaps(cams, hexah, *depthMapsFuseParams); + // add points from depth maps + if (depthMapsFuseParams != nullptr) + fuseFromDepthMaps(cams, hexah, *depthMapsFuseParams); - // add points from sfm - if(sfmData != nullptr) - addPointsFromSfM(hexah, cams, *sfmData); + // add points from sfm + if (sfmData != nullptr) + addPointsFromSfM(hexah, cams, *sfmData); - // add points for cam centers - addPointsFromCameraCenters(cams, minDist); + // add points for cam centers + addPointsFromCameraCenters(cams, minDist); - densifyWithHelperPoints(densifyNbFront, densifyNbBack, densifyScale); + densifyWithHelperPoints(densifyNbFront, densifyNbBack, densifyScale); - // add volume points to prevent singularities - { - Point3d hexahExt[8]; - mvsUtils::inflateHexahedron(hexah, hexahExt, 1.3); - addGridHelperPoints(helperPointsGridSize, hexahExt, minDist); + // add volume points to prevent singularities + { + Point3d hexahExt[8]; + mvsUtils::inflateHexahedron(hexah, hexahExt, 1.3); + addGridHelperPoints(helperPointsGridSize, hexahExt, minDist); - // add 6 points to prevent singularities (one point beyond each bbox facet) - addPointsToPreventSingularities(hexahExt, minDist); + // add 6 points to prevent singularities (one point beyond each bbox facet) + addPointsToPreventSingularities(hexahExt, minDist); - // add point for shape from silhouette - if(depthMapsFuseParams != nullptr) - addMaskHelperPoints(hexahExt, cams, *depthMapsFuseParams); - } + // add point for shape from silhouette + if (depthMapsFuseParams != nullptr) + addMaskHelperPoints(hexahExt, cams, *depthMapsFuseParams); + } - _verticesCoords.shrink_to_fit(); - _verticesAttr.shrink_to_fit(); + _verticesCoords.shrink_to_fit(); + _verticesAttr.shrink_to_fit(); - ALICEVISION_LOG_WARNING("Final dense point cloud: " << _verticesCoords.size() << " points."); + ALICEVISION_LOG_WARNING("Final dense point cloud: " << _verticesCoords.size() << " points."); } -void DelaunayGraphCut::createGraphCut(const Point3d hexah[8], const StaticVector& cams, - const std::string& folderName, const std::string& tmpCamsPtsFolderName, - bool removeSmallSegments, bool exportDebugTetrahedralization) +void DelaunayGraphCut::createGraphCut(const Point3d hexah[8], + const StaticVector& cams, + const std::string& folderName, + const std::string& tmpCamsPtsFolderName, + bool removeSmallSegments, + bool exportDebugTetrahedralization) { - // Create tetrahedralization - computeDelaunay(); - displayStatistics(); + // Create tetrahedralization + computeDelaunay(); + displayStatistics(); - if(removeSmallSegments) // false - { - std::vector segments; - std::vector useVertex; // keep empty to process all pixels - computeVerticesSegSize(segments, useVertex, 0.0f); - removeSmallSegs(segments, 2500); // TODO FACA: to decide - } + if (removeSmallSegments) // false + { + std::vector segments; + std::vector useVertex; // keep empty to process all pixels + computeVerticesSegSize(segments, useVertex, 0.0f); + removeSmallSegs(segments, 2500); // TODO FACA: to decide + } - voteFullEmptyScore(cams, folderName); + voteFullEmptyScore(cams, folderName); - if(exportDebugTetrahedralization) - exportFullScoreMeshs(folderName); + if (exportDebugTetrahedralization) + exportFullScoreMeshs(folderName); - maxflow(); + maxflow(); } void DelaunayGraphCut::addToInfiniteSw(float sW) { std::size_t nbInfinitCells = 0; - for(CellIndex ci = 0; ci < _cellsAttr.size(); ++ci) + for (CellIndex ci = 0; ci < _cellsAttr.size(); ++ci) { - if(isInfiniteCell(ci)) + if (isInfiniteCell(ci)) { GC_cellInfo& c = _cellsAttr[ci]; c.cellSWeight += sW; @@ -3189,7 +3250,7 @@ void DelaunayGraphCut::maxflow() // fill s-t edges int nbSCells = 0; int nbTCells = 0; - for(CellIndex ci = 0; ci < nbCells; ++ci) + for (CellIndex ci = 0; ci < nbCells; ++ci) { const GC_cellInfo& c = _cellsAttr[ci]; const float ws = c.cellSWeight; @@ -3201,7 +3262,7 @@ void DelaunayGraphCut::maxflow() assert(!std::isnan(wt)); maxFlowGraph.addNode(ci, ws, wt); - if(ws > wt) + if (ws > wt) ++nbSCells; else ++nbTCells; @@ -3213,18 +3274,18 @@ void DelaunayGraphCut::maxflow() const float CONSTalphaPHOTO = 5.0f; // fill u-v directed edges - for(CellIndex ci = 0; ci < nbCells; ++ci) + for (CellIndex ci = 0; ci < nbCells; ++ci) { - for(VertexIndex k = 0; k < 4; ++k) + for (VertexIndex k = 0; k < 4; ++k) { Facet fu(ci, k); Facet fv = mirrorFacet(fu); - if(isInvalidOrInfiniteCell(fv.cellIndex)) + if (isInvalidOrInfiniteCell(fv.cellIndex)) continue; float a1 = 0.0f; float a2 = 0.0f; - if((!isInfiniteCell(fu.cellIndex)) && (!isInfiniteCell(fv.cellIndex))) + if ((!isInfiniteCell(fu.cellIndex)) && (!isInfiniteCell(fv.cellIndex))) { // Score for each facet based on the quality of the topology a1 = getFaceWeight(fu); @@ -3246,7 +3307,7 @@ void DelaunayGraphCut::maxflow() } ALICEVISION_LOG_INFO("Maxflow: clear cells info."); - std::vector().swap(_cellsAttr); // force clear to free some RAM before maxflow + std::vector().swap(_cellsAttr); // force clear to free some RAM before maxflow long t_maxflow_compute = clock(); // Find graph-cut solution @@ -3259,7 +3320,7 @@ void DelaunayGraphCut::maxflow() _cellIsFull.resize(nbCells); // Update FULL/EMPTY status of all cells std::size_t nbFullCells = 0; - for(CellIndex ci = 0; ci < nbCells; ++ci) + for (CellIndex ci = 0; ci < nbCells; ++ci) { _cellIsFull[ci] = maxFlowGraph.isTarget(ci); nbFullCells += _cellIsFull[ci]; @@ -3279,7 +3340,7 @@ void DelaunayGraphCut::voteFullEmptyScore(const StaticVector& cams, const s long t1; // TODO FACA: nPixelSizeBehind 2 or 4 by default? - const double nPixelSizeBehind = _mp.userParams.get("delaunaycut.nPixelSizeBehind", 4.0); // sigma value + const double nPixelSizeBehind = _mp.userParams.get("delaunaycut.nPixelSizeBehind", 4.0); // sigma value const float fullWeight = float(_mp.userParams.get("delaunaycut.fullWeight", 1.0)); ALICEVISION_LOG_INFO("nPixelSizeBehind: " << nPixelSizeBehind); @@ -3288,10 +3349,11 @@ void DelaunayGraphCut::voteFullEmptyScore(const StaticVector& cams, const s const float distFcnHeight = (float)_mp.userParams.get("delaunaycut.distFcnHeight", 0.0); const bool labatutCFG09 = _mp.userParams.get("global.LabatutCFG09", false); - // jancosekIJCV: "Exploiting Visibility Information in Surface Reconstruction to Preserve Weakly Supported Surfaces", Michal Jancosek and Tomas Pajdla, 2014 + // jancosekIJCV: "Exploiting Visibility Information in Surface Reconstruction to Preserve Weakly Supported Surfaces", Michal Jancosek and Tomas + // Pajdla, 2014 const bool jancosekIJCV = _mp.userParams.get("global.JancosekIJCV", true); - if(jancosekIJCV) // true by default + if (jancosekIJCV) // true by default { const bool forceTEdge = _mp.userParams.get("delaunaycut.voteFilteringForWeaklySupportedSurfaces", true); @@ -3306,44 +3368,43 @@ void DelaunayGraphCut::voteFullEmptyScore(const StaticVector& cams, const s displayCellsStats(); - if(saveTemporaryBinFiles) + if (saveTemporaryBinFiles) saveDhInfo(folderName + "delaunayTriangulationInfoInit.bin"); - if(false) + if (false) { std::unique_ptr meshf(createTetrahedralMesh(false, 0.9f, [](const fuseCut::GC_cellInfo& c) { return c.emptinessScore; })); meshf->save(folderName + "tetrahedralMesh_beforeForceTEdge_emptiness"); } - if(forceTEdge) + if (forceTEdge) { forceTedgesByGradientIJCV(nPixelSizeBehind); } displayCellsStats(); - if(saveTemporaryBinFiles) + if (saveTemporaryBinFiles) saveDhInfo(folderName + "delaunayTriangulationInfoAfterForce.bin"); } - if(labatutCFG09) + if (labatutCFG09) { ALICEVISION_LOG_INFO("Labatut CFG 2009 method:"); fillGraph(nPixelSizeBehind, true, true, distFcnHeight, fullWeight); - if(saveTemporaryBinFiles) + if (saveTemporaryBinFiles) saveDhInfo(folderName + "delaunayTriangulationInfoInit.bin"); } } -void DelaunayGraphCut::filterLargeHelperPoints(std::vector& out_reliableVertices, - const std::vector& vertexIsOnSurface, int maxSegSize) +void DelaunayGraphCut::filterLargeHelperPoints(std::vector& out_reliableVertices, const std::vector& vertexIsOnSurface, int maxSegSize) { ALICEVISION_LOG_DEBUG("DelaunayGraphCut::filterLargeHelperPoints"); out_reliableVertices.clear(); // Do not filter helper points if maxSegSize is negative/infinit - if(maxSegSize < 0) + if (maxSegSize < 0) return; out_reliableVertices.resize(_verticesAttr.size(), true); @@ -3352,30 +3413,30 @@ void DelaunayGraphCut::filterLargeHelperPoints(std::vector& out_reliableVe edges.reserve(_verticesAttr.size()); // Create edges for all connected helper points - for(VertexIndex vi = 0; vi < _verticesAttr.size(); ++vi) + for (VertexIndex vi = 0; vi < _verticesAttr.size(); ++vi) { const GC_vertexInfo& v = _verticesAttr[vi]; const Point3d& p = _verticesCoords[vi]; - if(v.nrc <= 0 && vertexIsOnSurface[vi]) + if (v.nrc <= 0 && vertexIsOnSurface[vi]) { // go through all the neighbouring points GEO::vector adjVertices; _tetrahedralization->get_neighbors(vi, adjVertices); - for(VertexIndex nvi : adjVertices) + for (VertexIndex nvi : adjVertices) { // ignore itself - if(vi == nvi) + if (vi == nvi) continue; // avoid duplicates - if(vi < nvi) + if (vi < nvi) continue; // ignore points not on the surface - if(!vertexIsOnSurface[vi]) + if (!vertexIsOnSurface[vi]) continue; // ignore valid vertices const GC_vertexInfo& nv = _verticesAttr[nvi]; - if(nv.nrc > 0) + if (nv.nrc > 0) continue; // Declare a new edge between 2 helper points both on the selected surface edges.emplace_back(vi, nvi); @@ -3389,11 +3450,11 @@ void DelaunayGraphCut::filterLargeHelperPoints(std::vector& out_reliableVe { int s = (int)edges.size(); // Fuse all edges collected to be merged - for(int i = 0; i < s; i++) + for (int i = 0; i < s; i++) { int a = u.find(edges[i].x); int b = u.find(edges[i].y); - if(a != b) + if (a != b) { u.join(a, b); } @@ -3401,23 +3462,23 @@ void DelaunayGraphCut::filterLargeHelperPoints(std::vector& out_reliableVe } // Last loop over vertices to update segId - for(int vi = 0; vi < _verticesAttr.size(); ++vi) + for (int vi = 0; vi < _verticesAttr.size(); ++vi) { - if(!vertexIsOnSurface[vi]) + if (!vertexIsOnSurface[vi]) { // Point is not on the surface out_reliableVertices[vi] = false; continue; } const GC_vertexInfo& v = _verticesAttr[vi]; - if(v.nrc > 0) + if (v.nrc > 0) { // This is not a helper point, so it is reliable. out_reliableVertices[vi] = true; continue; } - if(maxSegSize > 0) + if (maxSegSize > 0) { // It is an helper point, so it is reliable only if it is a small group. const int sigId = u.find(vi); @@ -3451,7 +3512,7 @@ mesh::Mesh* DelaunayGraphCut::createMesh(int maxNbConnectedHelperPoints) me->pts = StaticVector(); me->pts.reserve(_verticesCoords.size()); - for(const Point3d& p: _verticesCoords) + for (const Point3d& p : _verticesCoords) { me->pts.push_back(p); } @@ -3463,24 +3524,24 @@ mesh::Mesh* DelaunayGraphCut::createMesh(int maxNbConnectedHelperPoints) me->tris.reserve(nbSurfaceFacets); // loop over all facets - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - for(VertexIndex k = 0; k < 4; ++k) + for (VertexIndex k = 0; k < 4; ++k) { Facet f1(ci, k); - bool uo = _cellIsFull[f1.cellIndex]; // get if it is occupied - if(!uo) + bool uo = _cellIsFull[f1.cellIndex]; // get if it is occupied + if (!uo) { // "f1" is in an EMPTY cell, skip it continue; } Facet f2 = mirrorFacet(f1); - if(isInvalidOrInfiniteCell(f2.cellIndex)) + if (isInvalidOrInfiniteCell(f2.cellIndex)) continue; - bool vo = _cellIsFull[f2.cellIndex]; // get if it is occupied + bool vo = _cellIsFull[f2.cellIndex]; // get if it is occupied - if(vo) + if (vo) { // "f2" is in a FULL cell, skip it continue; @@ -3493,24 +3554,24 @@ mesh::Mesh* DelaunayGraphCut::createMesh(int maxNbConnectedHelperPoints) vertices[1] = getVertexIndex(f1, 1); vertices[2] = getVertexIndex(f1, 2); - if(!reliableVertices.empty()) + if (!reliableVertices.empty()) { // We skip triangles if it contains one unreliable vertex. bool invalidTriangle = false; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { - if(!reliableVertices[vertices[k]]) + if (!reliableVertices[vertices[k]]) { invalidTriangle = true; break; } } - if(invalidTriangle) + if (invalidTriangle) continue; } Point3d points[3]; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { points[k] = _verticesCoords[vertices[k]]; } @@ -3520,8 +3581,8 @@ mesh::Mesh* DelaunayGraphCut::createMesh(int maxNbConnectedHelperPoints) // printf("WARNINIG infinite vertex\n"); // } - const Point3d D1 = _verticesCoords[getOppositeVertexIndex(f1)]; // in FULL part - const Point3d D2 = _verticesCoords[getOppositeVertexIndex(f2)]; // in EMPTY part + const Point3d D1 = _verticesCoords[getOppositeVertexIndex(f1)]; // in FULL part + const Point3d D2 = _verticesCoords[getOppositeVertexIndex(f2)]; // in EMPTY part const Point3d N = cross((points[1] - points[0]).normalize(), (points[2] - points[0]).normalize()).normalize(); @@ -3530,7 +3591,7 @@ mesh::Mesh* DelaunayGraphCut::createMesh(int maxNbConnectedHelperPoints) const bool clockwise = std::signbit(dd1); - if(clockwise) + if (clockwise) { mesh::Mesh::triangle t; t.alive = true; @@ -3563,13 +3624,13 @@ void DelaunayGraphCut::displayCellsStats() const using Accumulator = accumulator_set>; auto displayAcc = [](const std::string& name, const Accumulator& acc) { ALICEVISION_LOG_INFO(" [" << name << "]" - << " min: " << extract::min(acc) << " max: " << extract::max(acc) - << " mean: " << extract::mean(acc) << " median: " << extract::median(acc)); - }; + << " min: " << extract::min(acc) << " max: " << extract::max(acc) << " mean: " << extract::mean(acc) + << " median: " << extract::median(acc)); + }; { Accumulator acc_nrc; Accumulator acc_camSize; - for(VertexIndex vi = 0; vi < _verticesAttr.size(); ++vi) + for (VertexIndex vi = 0; vi < _verticesAttr.size(); ++vi) { const GC_vertexInfo& vertexAttr = _verticesAttr[vi]; acc_nrc(vertexAttr.nrc); @@ -3588,7 +3649,7 @@ void DelaunayGraphCut::displayCellsStats() const Accumulator acc_on; int64_t countPositiveSWeight = 0; - for(const GC_cellInfo& cellAttr : _cellsAttr) + for (const GC_cellInfo& cellAttr : _cellsAttr) { countPositiveSWeight += (cellAttr.cellSWeight > 0); acc_cellScore(cellAttr.cellSWeight - cellAttr.cellTWeight); @@ -3616,14 +3677,16 @@ void DelaunayGraphCut::displayCellsStats() const } } -mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& downscaleFactor, const std::function getScore) const +mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, + const float& downscaleFactor, + const std::function getScore) const { ALICEVISION_LOG_INFO("Create mesh of the tetrahedralization."); ALICEVISION_LOG_INFO("# vertices: " << _verticesCoords.size()); mesh::Mesh* me = new mesh::Mesh(); - if(_cellsAttr.empty()) + if (_cellsAttr.empty()) { ALICEVISION_LOG_INFO("Empty tetrahedralization."); return me; @@ -3638,14 +3701,14 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do using Accumulator = accumulator_set>; auto displayAcc = [](const std::string& name, const Accumulator& acc) { ALICEVISION_LOG_INFO(" [" << name << "]" - << " min: " << extract::min(acc) << " max: " << extract::max(acc) - << " mean: " << extract::mean(acc) << " median: " << extract::median(acc)); + << " min: " << extract::min(acc) << " max: " << extract::max(acc) << " mean: " << extract::mean(acc) + << " median: " << extract::median(acc)); }; float maxScore = 1.0; { Accumulator acc_selectedScore; - for(const GC_cellInfo& cellAttr : _cellsAttr) + for (const GC_cellInfo& cellAttr : _cellsAttr) { acc_selectedScore(getScore(cellAttr)); } @@ -3660,23 +3723,23 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do maxScore = 1; // loop over all facets - for(CellIndex ci = 0; ci < _cellsAttr.size(); ++ci) + for (CellIndex ci = 0; ci < _cellsAttr.size(); ++ci) { Point3d pointscellCenter(0.0, 0.0, 0.0); { bool invalid = false; int weakVertex = 0; - for(VertexIndex k = 0; k < 4; ++k) + for (VertexIndex k = 0; k < 4; ++k) { const VertexIndex vi = _tetrahedralization->cell_vertex(ci, k); const GC_vertexInfo& vertexAttr = _verticesAttr[vi]; - if(filter && vertexAttr.cams.size() <= 3) + if (filter && vertexAttr.cams.size() <= 3) { ++weakVertex; } const Facet f1(ci, k); const Facet f2 = mirrorFacet(f1); - if(filter && isInvalidOrInfiniteCell(f2.cellIndex)) + if (filter && isInvalidOrInfiniteCell(f2.cellIndex)) { invalid = true; continue; @@ -3684,7 +3747,7 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do pointscellCenter = pointscellCenter + _verticesCoords[vi]; } pointscellCenter = pointscellCenter / 4.0; - if(filter && invalid) + if (filter && invalid) { // If one of the mirror facet is invalid, discard the tetrahedron. continue; @@ -3698,14 +3761,14 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do } const GC_cellInfo& cellAttr = _cellsAttr[ci]; - const float score = getScore(cellAttr); // cellAttr.cellSWeight - cellAttr.cellTWeight; - if(filter && (score < (maxScore / 1000.0f))) + const float score = getScore(cellAttr); // cellAttr.cellSWeight - cellAttr.cellTWeight; + if (filter && (score < (maxScore / 1000.0f))) { // Skip too low score cells continue; } - for(VertexIndex k = 0; k < 4; ++k) + for (VertexIndex k = 0; k < 4; ++k) { const Facet f1(ci, k); @@ -3715,7 +3778,7 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do vertices[2] = getVertexIndex(f1, 2); Point3d points[3]; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { points[k] = _verticesCoords[vertices[k]]; // Downscale cell for visibility @@ -3724,7 +3787,7 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do const Facet f2 = mirrorFacet(f1); bool clockwise = false; - + //// do not need to test again: already filtered before if (!isInvalidOrInfiniteCell(f2.cellIndex)) { @@ -3757,13 +3820,13 @@ mesh::Mesh* DelaunayGraphCut::createTetrahedralMesh(bool filter, const float& do const rgb color = getRGBFromJetColorMap(score / maxScore); const std::size_t vertexBaseIndex = me->pts.size(); - for(const Point3d& p: points) + for (const Point3d& p : points) { me->pts.push_back(p); me->colors().push_back(color); } - if(clockwise) + if (clockwise) { mesh::Mesh::triangle t; t.alive = true; @@ -3839,43 +3902,41 @@ void DelaunayGraphCut::exportFullScoreMeshs(const std::string& outputFolder) con const std::string nameExt = "." + mesh::EFileType_enumToString(mesh::EFileType::OBJ); const std::string outputPathPrefix = outputFolder + "debug_"; { - std::unique_ptr meshEmptiness( - createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.emptinessScore; })); + std::unique_ptr meshEmptiness(createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.emptinessScore; })); meshEmptiness->save(outputPathPrefix + "mesh_emptiness" + nameExt); } { - std::unique_ptr meshFullness( - createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.fullnessScore; })); + std::unique_ptr meshFullness(createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.fullnessScore; })); meshFullness->save(outputPathPrefix + "mesh_fullness" + nameExt); } { - std::unique_ptr meshSWeight( - createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.cellSWeight; })); + std::unique_ptr meshSWeight(createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.cellSWeight; })); meshSWeight->save(outputPathPrefix + "mesh_sWeight" + nameExt); } { - std::unique_ptr meshTWeight( - createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.cellTWeight; })); + std::unique_ptr meshTWeight(createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.cellTWeight; })); meshTWeight->save(outputPathPrefix + "mesh_tWeight" + nameExt); } { - std::unique_ptr meshOn( - createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.on; })); + std::unique_ptr meshOn(createTetrahedralMesh(false, 0.999f, [](const GC_cellInfo& c) { return c.on; })); meshOn->save(outputPathPrefix + "mesh_on" + nameExt); } { - std::unique_ptr mesh(createTetrahedralMesh( - false, 0.99f, [](const fuseCut::GC_cellInfo& c) { return c.fullnessScore - c.emptinessScore; })); + std::unique_ptr mesh( + createTetrahedralMesh(false, 0.99f, [](const fuseCut::GC_cellInfo& c) { return c.fullnessScore - c.emptinessScore; })); mesh->save(outputPathPrefix + "mesh_fullness-emptiness" + nameExt); } { - std::unique_ptr mesh(createTetrahedralMesh( - false, 0.99f, [](const fuseCut::GC_cellInfo& c) { return c.cellSWeight - c.cellTWeight; })); + std::unique_ptr mesh( + createTetrahedralMesh(false, 0.99f, [](const fuseCut::GC_cellInfo& c) { return c.cellSWeight - c.cellTWeight; })); mesh->save(outputPathPrefix + "mesh_s-t" + nameExt); } } -void DelaunayGraphCut::exportBackPropagationMesh(const std::string& filename, std::vector& intersectedGeom, const Point3d& fromPt, const Point3d& toPt) +void DelaunayGraphCut::exportBackPropagationMesh(const std::string& filename, + std::vector& intersectedGeom, + const Point3d& fromPt, + const Point3d& toPt) { // Clean _cellsAttr emptinessScore for (int i = 0; i < _cellsAttr.size(); ++i) @@ -3905,30 +3966,17 @@ void DelaunayGraphCut::writeScoreInCsv(const std::string& filePath, const size_t std::shuffle(idx.begin(), idx.end(), generator); std::ofstream csv(filePath); - const char sep = ','; // separator - csv << "fullnessScore" << sep << - "emptinessScore" << sep << - "cellSWeight" << sep << - "cellTWeight" << sep << - "on" << sep << - "gEdgeVisWeight0" << sep << - "gEdgeVisWeight1" << sep << - "gEdgeVisWeight2" << sep << - "gEdgeVisWeight3" << '\n'; + const char sep = ','; // separator + csv << "fullnessScore" << sep << "emptinessScore" << sep << "cellSWeight" << sep << "cellTWeight" << sep << "on" << sep << "gEdgeVisWeight0" + << sep << "gEdgeVisWeight1" << sep << "gEdgeVisWeight2" << sep << "gEdgeVisWeight3" << '\n'; const size_t size = sizeLimit > 0 ? std::min(sizeLimit, _cellsAttr.size()) : _cellsAttr.size(); for (size_t i = 0; i < size; ++i) { const GC_cellInfo& cellAttr = _cellsAttr[idx.back()]; idx.pop_back(); - csv << cellAttr.fullnessScore << sep << - cellAttr.emptinessScore << sep << - cellAttr.cellSWeight << sep << - cellAttr.cellTWeight << sep << - cellAttr.on << sep << - cellAttr.gEdgeVisWeight[0] << sep << - cellAttr.gEdgeVisWeight[1] << sep << - cellAttr.gEdgeVisWeight[2] << sep << - cellAttr.gEdgeVisWeight[3] << '\n'; + csv << cellAttr.fullnessScore << sep << cellAttr.emptinessScore << sep << cellAttr.cellSWeight << sep << cellAttr.cellTWeight << sep + << cellAttr.on << sep << cellAttr.gEdgeVisWeight[0] << sep << cellAttr.gEdgeVisWeight[1] << sep << cellAttr.gEdgeVisWeight[2] << sep + << cellAttr.gEdgeVisWeight[3] << '\n'; } csv.close(); @@ -3948,27 +3996,26 @@ void DelaunayGraphCut::segmentFullOrFree(bool full, StaticVector& out_fullS int col = 0; // segment connected free space - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - if((!isInfiniteCell(ci)) && (out_fullSegsColor[ci] == -1) && (_cellIsFull[ci] == full)) + if ((!isInfiniteCell(ci)) && (out_fullSegsColor[ci] == -1) && (_cellIsFull[ci] == full)) { // backtrack all connected interior cells buff.resize(0); buff.push_back(ci); - while(buff.size() > 0) + while (buff.size() > 0) { CellIndex tmp_ci = buff.pop(); out_fullSegsColor[tmp_ci] = col; - for(int k = 0; k < 4; ++k) + for (int k = 0; k < 4; ++k) { const CellIndex nci = _tetrahedralization->cell_adjacent(tmp_ci, k); - if(nci == GEO::NO_CELL) + if (nci == GEO::NO_CELL) continue; - if((!isInfiniteCell(nci)) && (out_fullSegsColor[nci] == -1) && - (_cellIsFull[nci] == full)) + if ((!isInfiniteCell(nci)) && (out_fullSegsColor[nci] == -1) && (_cellIsFull[nci] == full)) { buff.push_back(nci); } @@ -3995,16 +4042,16 @@ int DelaunayGraphCut::removeBubbles() colorsToFill.resize_with(nbEmptySegments, true); // all free space segments which contains camera has to remain free - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - if(isInfiniteCell(ci) || emptySegColors[ci] < 0) + if (isInfiniteCell(ci) || emptySegColors[ci] < 0) continue; const GC_vertexInfo& a = _verticesAttr[_tetrahedralization->cell_vertex(ci, 0)]; const GC_vertexInfo& b = _verticesAttr[_tetrahedralization->cell_vertex(ci, 1)]; const GC_vertexInfo& c = _verticesAttr[_tetrahedralization->cell_vertex(ci, 2)]; const GC_vertexInfo& d = _verticesAttr[_tetrahedralization->cell_vertex(ci, 3)]; - if( a.isVirtual() || b.isVirtual() || c.isVirtual() || d.isVirtual()) + if (a.isVirtual() || b.isVirtual() || c.isVirtual() || d.isVirtual()) { // TODO FACA: check helper points are not connected to cameras? colorsToFill[emptySegColors[ci]] = false; @@ -4012,18 +4059,18 @@ int DelaunayGraphCut::removeBubbles() } int nbBubbles = 0; - for(int i = 0; i < nbEmptySegments; ++i) + for (int i = 0; i < nbEmptySegments; ++i) { - if(colorsToFill[i]) + if (colorsToFill[i]) { ++nbBubbles; } } int nbModifiedCells = 0; - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - if((!isInfiniteCell(ci)) && (emptySegColors[ci] >= 0) && (colorsToFill[emptySegColors[ci]])) + if ((!isInfiniteCell(ci)) && (emptySegColors[ci] >= 0) && (colorsToFill[emptySegColors[ci]])) { _cellIsFull[ci] = true; ++nbModifiedCells; @@ -4031,7 +4078,8 @@ int DelaunayGraphCut::removeBubbles() } ALICEVISION_LOG_INFO("DelaunayGraphCut::removeBubbles: nbBubbles: " << nbBubbles << ", all empty segments: " << nbEmptySegments); - ALICEVISION_LOG_WARNING("DelaunayGraphCut::removeBubbles: " << nbModifiedCells << " cells changed to full within " << _cellIsFull.size() << " cells."); + ALICEVISION_LOG_WARNING("DelaunayGraphCut::removeBubbles: " << nbModifiedCells << " cells changed to full within " << _cellIsFull.size() + << " cells."); return nbBubbles; } @@ -4047,19 +4095,19 @@ int DelaunayGraphCut::removeDust(int minSegSize) StaticVector colorsSize(nbFullSegments, 0); // all free space segments which contains camera has to remain free - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - if(fullSegsColor[ci] >= 0) // if we have a valid color: non empty and non infinit cell + if (fullSegsColor[ci] >= 0) // if we have a valid color: non empty and non infinit cell { - colorsSize[fullSegsColor[ci]] += 1; // count the number of cells in the segment + colorsSize[fullSegsColor[ci]] += 1; // count the number of cells in the segment } } int ndust = 0; - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { // if number of cells in the segment is too small, we change the status to "empty" - if((fullSegsColor[ci] >= 0) && (colorsSize[fullSegsColor[ci]] < minSegSize)) + if ((fullSegsColor[ci] >= 0) && (colorsSize[fullSegsColor[ci]] < minSegSize)) { _cellIsFull[ci] = false; ++ndust; @@ -4085,14 +4133,14 @@ void DelaunayGraphCut::leaveLargestFullSegmentOnly() // all free space segments which contains camera has to remain free int largestColor = -1; int maxn = 0; - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { int color = colors[ci]; - if(color >= 0) + if (color >= 0) { colorsSize[color] += 1; int n = colorsSize[color]; - if(n > maxn) + if (n > maxn) { maxn = n; largestColor = color; @@ -4100,9 +4148,9 @@ void DelaunayGraphCut::leaveLargestFullSegmentOnly() } } - for(CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) + for (CellIndex ci = 0; ci < _cellIsFull.size(); ++ci) { - if(colors[ci] != largestColor) + if (colors[ci] != largestColor) { _cellIsFull[ci] = false; } @@ -4115,18 +4163,18 @@ std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::EGeometry { switch (type) { - case DelaunayGraphCut::EGeometryType::Facet: - stream << "Facet"; - break; - case DelaunayGraphCut::EGeometryType::Vertex: - stream << "Vertex"; - break; - case DelaunayGraphCut::EGeometryType::Edge: - stream << "Edge"; - break; - case DelaunayGraphCut::EGeometryType::None: - stream << "None"; - break; + case DelaunayGraphCut::EGeometryType::Facet: + stream << "Facet"; + break; + case DelaunayGraphCut::EGeometryType::Vertex: + stream << "Vertex"; + break; + case DelaunayGraphCut::EGeometryType::Edge: + stream << "Edge"; + break; + case DelaunayGraphCut::EGeometryType::None: + stream << "None"; + break; } return stream; } @@ -4148,18 +4196,18 @@ std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::GeometryI stream << intersection.type << ": "; switch (intersection.type) { - case DelaunayGraphCut::EGeometryType::Facet: - stream << intersection.facet; - break; - case DelaunayGraphCut::EGeometryType::Vertex: - stream << intersection.vertexIndex; - break; - case DelaunayGraphCut::EGeometryType::Edge: - stream << intersection.edge; - break; - case DelaunayGraphCut::EGeometryType::None: - stream << "None"; - break; + case DelaunayGraphCut::EGeometryType::Facet: + stream << intersection.facet; + break; + case DelaunayGraphCut::EGeometryType::Vertex: + stream << intersection.vertexIndex; + break; + case DelaunayGraphCut::EGeometryType::Edge: + stream << intersection.edge; + break; + case DelaunayGraphCut::EGeometryType::None: + stream << "None"; + break; } return stream; } @@ -4170,5 +4218,5 @@ std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::Geometrie return stream; } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/DelaunayGraphCut.hpp b/src/aliceVision/fuseCut/DelaunayGraphCut.hpp index fbfbbb5e01..19c6699354 100644 --- a/src/aliceVision/fuseCut/DelaunayGraphCut.hpp +++ b/src/aliceVision/fuseCut/DelaunayGraphCut.hpp @@ -32,7 +32,6 @@ class SfMData; namespace fuseCut { - struct FuseParams { /// Max input points loaded from images @@ -60,10 +59,9 @@ struct FuseParams int maskBorderSize = 1; }; - class DelaunayGraphCut { -public: + public: using VertexIndex = GEO::index_t; using CellIndex = GEO::index_t; @@ -73,16 +71,13 @@ class DelaunayGraphCut /// local opposite vertex index VertexIndex localVertexIndex = GEO::NO_VERTEX; - Facet(){} + Facet() {} Facet(CellIndex ci, VertexIndex lvi) - : cellIndex(ci) - , localVertexIndex(lvi) + : cellIndex(ci), + localVertexIndex(lvi) {} - bool operator==(const Facet& f) const - { - return cellIndex == f.cellIndex && localVertexIndex == f.localVertexIndex; - } + bool operator==(const Facet& f) const { return cellIndex == f.cellIndex && localVertexIndex == f.localVertexIndex; } }; struct Edge @@ -92,19 +87,12 @@ class DelaunayGraphCut Edge() = default; Edge(VertexIndex v0_, VertexIndex v1_) - : v0{v0_} - , v1{v1_} + : v0{v0_}, + v1{v1_} {} - bool operator==(const Edge& e) const - { - return v0 == e.v0 && v1 == e.v1; - } - bool isSameUndirectionalEdge(const Edge& e) const - { - return (v0 == e.v0 && v1 == e.v1) || - (v0 == e.v1 && v1 == e.v0); - } + bool operator==(const Edge& e) const { return v0 == e.v0 && v1 == e.v1; } + bool isSameUndirectionalEdge(const Edge& e) const { return (v0 == e.v0 && v1 == e.v1) || (v0 == e.v1 && v1 == e.v0); } }; enum class EGeometryType @@ -126,16 +114,16 @@ class DelaunayGraphCut }; GeometryIntersection() {} explicit GeometryIntersection(const Facet& f) - : facet{f} - , type{EGeometryType::Facet} + : facet{f}, + type{EGeometryType::Facet} {} explicit GeometryIntersection(const VertexIndex& v) - : vertexIndex{v} - , type{EGeometryType::Vertex} + : vertexIndex{v}, + type{EGeometryType::Vertex} {} explicit GeometryIntersection(const Edge& e) - : edge{e} - , type{EGeometryType::Edge} + : edge{e}, + type{EGeometryType::Edge} {} bool operator==(const GeometryIntersection& g) const @@ -145,21 +133,18 @@ class DelaunayGraphCut switch (type) { - case EGeometryType::Vertex: - return vertexIndex == g.vertexIndex; - case EGeometryType::Edge: - return edge == g.edge; - case EGeometryType::Facet: - return facet == g.facet; - case EGeometryType::None: - break; + case EGeometryType::Vertex: + return vertexIndex == g.vertexIndex; + case EGeometryType::Edge: + return edge == g.edge; + case EGeometryType::Facet: + return facet == g.facet; + case EGeometryType::None: + break; } return true; } - bool operator!=(const GeometryIntersection& g) const - { - return !(*this == g); - } + bool operator!=(const GeometryIntersection& g) const { return !(*this == g); } }; struct IntersectionHistory @@ -174,8 +159,12 @@ class DelaunayGraphCut std::vector vecToCam; std::vector distToCam; std::vector angleToCam; - IntersectionHistory(const Point3d& c, const Point3d& oPt, const Point3d& diV) : cam{c}, originPt{oPt}, dirVect{diV} {} - + IntersectionHistory(const Point3d& c, const Point3d& oPt, const Point3d& diV) + : cam{c}, + originPt{oPt}, + dirVect{diV} + {} + void append(const GeometryIntersection& geom, const Point3d& intersectPt); }; @@ -228,19 +217,16 @@ class DelaunayGraphCut /** * @brief Retrieve the global vertex index of the localVertexIndex of the facet. - * + * * @param f the facet * @return the global vertex index */ - inline VertexIndex getOppositeVertexIndex(const Facet& f) const - { - return _tetrahedralization->cell_vertex(f.cellIndex, f.localVertexIndex); - } + inline VertexIndex getOppositeVertexIndex(const Facet& f) const { return _tetrahedralization->cell_vertex(f.cellIndex, f.localVertexIndex); } /** - * @brief Retrieve the global vertex index of a vertex from a facet and an relative index + * @brief Retrieve the global vertex index of a vertex from a facet and an relative index * compared to the localVertexIndex of the facet. - * + * * @param f the facet * @param i the relative index (relative to the localVertexIndex of the facet) * @return the global vertex index @@ -252,27 +238,22 @@ class DelaunayGraphCut inline const std::array getFacetsPoints(const Facet& f) const { - return {&(_verticesCoords[getVertexIndex(f, 0)]), - &(_verticesCoords[getVertexIndex(f, 1)]), - &(_verticesCoords[getVertexIndex(f, 2)])}; + return {&(_verticesCoords[getVertexIndex(f, 0)]), &(_verticesCoords[getVertexIndex(f, 1)]), &(_verticesCoords[getVertexIndex(f, 2)])}; } - inline std::size_t getNbVertices() const - { - return _verticesAttr.size(); - } + inline std::size_t getNbVertices() const { return _verticesAttr.size(); } inline GEO::index_t nearestVertexInCell(GEO::index_t cellIndex, const Point3d& p) const { GEO::signed_index_t result = NO_TETRAHEDRON; double d = std::numeric_limits::max(); - for(GEO::index_t i = 0; i < 4; ++i) + for (GEO::index_t i = 0; i < 4; ++i) { GEO::signed_index_t currentVertex = _tetrahedralization->cell_vertex(cellIndex, i); - if(currentVertex < 0) + if (currentVertex < 0) continue; double currentDist = GEO::Geom::distance2(_verticesCoords[currentVertex].m, p.m, 3); - if(currentDist < d) + if (currentDist < d) { d = currentDist; result = currentVertex; @@ -280,7 +261,7 @@ class DelaunayGraphCut } return result; } - + /** * @brief A cell is infinite if one of its vertices is infinite. */ @@ -297,24 +278,20 @@ class DelaunayGraphCut inline Facet mirrorFacet(const Facet& f) const { - const std::array facetVertices = { - getVertexIndex(f, 0), - getVertexIndex(f, 1), - getVertexIndex(f, 2) - }; - + const std::array facetVertices = {getVertexIndex(f, 0), getVertexIndex(f, 1), getVertexIndex(f, 2)}; + Facet out; out.cellIndex = _tetrahedralization->cell_adjacent(f.cellIndex, f.localVertexIndex); - if(out.cellIndex != GEO::NO_CELL) + if (out.cellIndex != GEO::NO_CELL) { // Search for the vertex in adjacent cell which doesn't exist in input facet. - for(int k = 0; k < 4; ++k) + for (int k = 0; k < 4; ++k) { CellIndex out_vi = _tetrahedralization->cell_vertex(out.cellIndex, k); - if(std::find(facetVertices.begin(), facetVertices.end(), out_vi) == facetVertices.end()) + if (std::find(facetVertices.begin(), facetVertices.end(), out_vi) == facetVertices.end()) { - out.localVertexIndex = k; - return out; + out.localVertexIndex = k; + return out; } } } @@ -329,10 +306,10 @@ class DelaunayGraphCut int coutInvalidVertices = 0; for (CellIndex ci = 0; ci < _tetrahedralization->nb_cells(); ++ci) { - for(VertexIndex k = 0; k < 4; ++k) + for (VertexIndex k = 0; k < 4; ++k) { const VertexIndex vi = _tetrahedralization->cell_vertex(ci, k); - if(vi == GEO::NO_VERTEX || vi >= _verticesCoords.size()) + if (vi == GEO::NO_VERTEX || vi >= _verticesCoords.size()) { ++coutInvalidVertices; continue; @@ -344,7 +321,7 @@ class DelaunayGraphCut ALICEVISION_LOG_INFO("neighboringCellsPerVertexTmp: " << neighboringCellsPerVertexTmp.size()); _neighboringCellsPerVertex.resize(_verticesCoords.size()); ALICEVISION_LOG_INFO("verticesCoords: " << _verticesCoords.size()); - for(const auto& it: neighboringCellsPerVertexTmp) + for (const auto& it : neighboringCellsPerVertexTmp) { const std::set& input = it.second; std::vector& output = _neighboringCellsPerVertex[it.first]; @@ -364,12 +341,11 @@ class DelaunayGraphCut inline CellIndex vertexToCells(VertexIndex vi, int lvi) const { const std::vector& localCells = _neighboringCellsPerVertex.at(vi); - if(lvi >= localCells.size()) + if (lvi >= localCells.size()) return GEO::NO_CELL; return localCells[lvi]; } - /** * @brief Retrieves the global indexes of neighboring cells around a geometry. * @@ -388,16 +364,13 @@ class DelaunayGraphCut /** * @brief Retrieves the global indexes of neighboring cells using the global index of a vertex. - * + * * @param vi the global vertexIndex * @return a vector of neighboring cell indices */ - inline const std::vector& getNeighboringCellsByVertexIndex(VertexIndex vi) const - { - return _neighboringCellsPerVertex.at(vi); - } + inline const std::vector& getNeighboringCellsByVertexIndex(VertexIndex vi) const { return _neighboringCellsPerVertex.at(vi); } - /** + /** * @brief Retrieves the global indexes of neighboring cells around one edge. * * @param e the concerned edge @@ -446,7 +419,7 @@ class DelaunayGraphCut /** * @brief Function that returns the next geometry intersected by the ray. * The function handles different cases, whether we come from an edge, a facet or a vertex. - * + * * @param inGeometry the geometry we come from * @param originPt ray origin point * @param dirVect ray direction @@ -454,15 +427,19 @@ class DelaunayGraphCut * @param epsilonFactor a multiplicative factor on the smaller side of the facet used to define the boundary when we * have to consider either a collision with an edge/vertex or a facet. * @param lastIntersectPt constant reference to the last intersection point used to test the direction. - * @return + * @return */ - GeometryIntersection intersectNextGeom(const GeometryIntersection& inGeometry, const Point3d& originPt, - const Point3d& dirVect, Point3d& intersectPt, const double epsilonFactor, const Point3d& lastIntersectPt) const; + GeometryIntersection intersectNextGeom(const GeometryIntersection& inGeometry, + const Point3d& originPt, + const Point3d& dirVect, + Point3d& intersectPt, + const double epsilonFactor, + const Point3d& lastIntersectPt) const; /** * @brief Function that returns the next geometry intersected by the ray on a given facet or None if there are no intersected geometry. * The function distinguishes the intersections cases using epsilon. - * + * * @param originPt ray origin point * @param DirVec ray direction * @param facet the given facet to intersect with @@ -471,10 +448,15 @@ class DelaunayGraphCut * have to consider either a collision with an edge/vertex or a facet. * @param ambiguous boolean used to know if our intersection is ambiguous or not * @param lastIntersectPt pointer to the last intersection point used to test the direction (if not nulllptr) - * @return + * @return */ - GeometryIntersection rayIntersectTriangle(const Point3d& originPt, const Point3d& DirVec, const Facet& facet, - Point3d& intersectPt, const double epsilonFactor, bool& ambiguous, const Point3d* lastIntersectPt = nullptr) const; + GeometryIntersection rayIntersectTriangle(const Point3d& originPt, + const Point3d& DirVec, + const Facet& facet, + Point3d& intersectPt, + const double epsilonFactor, + bool& ambiguous, + const Point3d* lastIntersectPt = nullptr) const; float distFcn(float maxDist, float dist, float distFcnHeight) const; @@ -482,14 +464,22 @@ class DelaunayGraphCut double facetMaxEdgeLength(Facet& f1) const; double maxEdgeLength() const; Point3d cellCircumScribedSphereCentre(CellIndex ci) const; - double getFaceWeight(const Facet &f1) const; + double getFaceWeight(const Facet& f1) const; float weightFcn(float nrc, bool labatutWeights, int ncams); - void fillGraph(double nPixelSizeBehind, bool labatutWeights, bool fillOut, float distFcnHeight, - float fullWeight); - void fillGraphPartPtRc(int& out_nstepsFront, int& out_nstepsBehind, GeometriesCount& outFrontCount, GeometriesCount& outBehindCount, int vertexIndex, int cam, float weight, - float fullWeight, double nPixelSizeBehind, bool fillOut, float distFcnHeight); + void fillGraph(double nPixelSizeBehind, bool labatutWeights, bool fillOut, float distFcnHeight, float fullWeight); + void fillGraphPartPtRc(int& out_nstepsFront, + int& out_nstepsBehind, + GeometriesCount& outFrontCount, + GeometriesCount& outBehindCount, + int vertexIndex, + int cam, + float weight, + float fullWeight, + double nPixelSizeBehind, + bool fillOut, + float distFcnHeight); /** * @brief Estimate the cells property "on" based on the analysis of the visibility of neigbouring cells. @@ -506,10 +496,17 @@ class DelaunayGraphCut void voteFullEmptyScore(const StaticVector& cams, const std::string& folderName); - void createDensePointCloud(const Point3d hexah[8], const StaticVector& cams, const sfmData::SfMData* sfmData, const FuseParams* depthMapsFuseParams); + void createDensePointCloud(const Point3d hexah[8], + const StaticVector& cams, + const sfmData::SfMData* sfmData, + const FuseParams* depthMapsFuseParams); - void createGraphCut(const Point3d hexah[8], const StaticVector& cams, const std::string& folderName, - const std::string& tmpCamsPtsFolderName, bool removeSmallSegments, bool exportDebugTetrahedralization); + void createGraphCut(const Point3d hexah[8], + const StaticVector& cams, + const std::string& folderName, + const std::string& tmpCamsPtsFolderName, + bool removeSmallSegments, + bool exportDebugTetrahedralization); /** * @brief Invert full/empty status of cells if they represent a too small group after labelling. @@ -521,7 +518,8 @@ class DelaunayGraphCut void cellsStatusFilteringBySolidAngleRatio(int nbSolidAngleFilteringIterations, double minSolidAngleRatio); /** - * @brief Combine all post-processing steps results to reduce artefacts from the graph-cut (too large triangles, noisy tetrahedrons, isolated cells, etc). + * @brief Combine all post-processing steps results to reduce artefacts from the graph-cut (too large triangles, noisy tetrahedrons, isolated + * cells, etc). */ void graphCutPostProcessing(const Point3d hexah[8], const std::string& folderName); @@ -542,31 +540,37 @@ class DelaunayGraphCut /** * @brief Create a mesh from the tetrahedral scores - * @param[in] maxNbConnectedHelperPoints: maximum number of connected helper points before we remove the group. 0 means that we remove all helper points. -1 means that we do not filter helper points at all. + * @param[in] maxNbConnectedHelperPoints: maximum number of connected helper points before we remove the group. 0 means that we remove all helper + * points. -1 means that we do not filter helper points at all. */ mesh::Mesh* createMesh(int maxNbConnectedHelperPoints); - mesh::Mesh* createTetrahedralMesh(bool filter = true, const float& downscaleFactor = 0.95f, const std::function getScore = [](const GC_cellInfo& c) { return c.emptinessScore; }) const; + mesh::Mesh* createTetrahedralMesh( + bool filter = true, + const float& downscaleFactor = 0.95f, + const std::function getScore = [](const GC_cellInfo& c) { return c.emptinessScore; }) const; void displayCellsStats() const; void exportDebugMesh(const std::string& filename, const Point3d& fromPt, const Point3d& toPt); /** - * @brief Export debug cells score as tetrahedral mesh. - * WARNING: Could create HUGE meshes, only use on very small datasets. - * @param[in] outputFolder: output directory (ending with path separator) - */ + * @brief Export debug cells score as tetrahedral mesh. + * WARNING: Could create HUGE meshes, only use on very small datasets. + * @param[in] outputFolder: output directory (ending with path separator) + */ void exportFullScoreMeshs(const std::string& outputFolder) const; - void exportBackPropagationMesh(const std::string& filename, std::vector& intersectedGeom, const Point3d& fromPt, const Point3d& toPt); + void exportBackPropagationMesh(const std::string& filename, + std::vector& intersectedGeom, + const Point3d& fromPt, + const Point3d& toPt); void writeScoreInCsv(const std::string& filePath, const size_t& sizeLimit = 1000); }; - std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::EGeometryType type); std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::Facet& facet); std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::Edge& edge); std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::GeometryIntersection& intersection); std::ostream& operator<<(std::ostream& stream, const DelaunayGraphCut::GeometriesCount& count); -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/DelaunayGraphCut_test.cpp b/src/aliceVision/fuseCut/DelaunayGraphCut_test.cpp index ac40baa055..1ef761648c 100644 --- a/src/aliceVision/fuseCut/DelaunayGraphCut_test.cpp +++ b/src/aliceVision/fuseCut/DelaunayGraphCut_test.cpp @@ -19,7 +19,6 @@ #include #include - using namespace aliceVision; using namespace aliceVision::fuseCut; using namespace aliceVision::sfmData; @@ -52,7 +51,9 @@ BOOST_AUTO_TEST_CASE(fuseCut_solidAngle) } } -SfMData generateSfm(const NViewDatasetConfigurator& config, const size_t size = 3, camera::EINTRINSIC eintrinsic = camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3); +SfMData generateSfm(const NViewDatasetConfigurator& config, + const size_t size = 3, + camera::EINTRINSIC eintrinsic = camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3); BOOST_AUTO_TEST_CASE(fuseCut_delaunayGraphCut) { @@ -80,9 +81,9 @@ BOOST_AUTO_TEST_CASE(fuseCut_delaunayGraphCut) cams.resize(mp.getNbCameras()); for (int i = 0; i < cams.size(); ++i) cams[i] = i; - + const std::string tempDirPath = boost::filesystem::temp_directory_path().generic_string(); - + DelaunayGraphCut delaunayGC(mp); ALICEVISION_LOG_TRACE("Creating dense point cloud witout support pts."); @@ -95,7 +96,8 @@ BOOST_AUTO_TEST_CASE(fuseCut_delaunayGraphCut) ALICEVISION_LOG_TRACE("Generated pts:"); for (size_t i = 0; i < delaunayGC._verticesCoords.size(); i++) - ALICEVISION_LOG_TRACE("[" << i << "]: " << delaunayGC._verticesCoords[i].x << ", " << delaunayGC._verticesCoords[i].y << ", " << delaunayGC._verticesCoords[i].z); + ALICEVISION_LOG_TRACE("[" << i << "]: " << delaunayGC._verticesCoords[i].x << ", " << delaunayGC._verticesCoords[i].y << ", " + << delaunayGC._verticesCoords[i].z); delaunayGC.createGraphCut(&hexah[0], cams, tempDirPath + "/", tempDirPath + "/SpaceCamsTracks/", false, false); /* @@ -111,10 +113,10 @@ BOOST_AUTO_TEST_CASE(fuseCut_delaunayGraphCut) /** * @brief Generate syntesize dataset with succesion of n(size) alignaed regular thetraedron and two camera on the last thetrahedron. - * + * * @param size * @param eintrinsic - * @return + * @return */ SfMData generateSfm(const NViewDatasetConfigurator& config, const size_t size, camera::EINTRINSIC eintrinsic) { @@ -127,8 +129,8 @@ SfMData generateSfm(const NViewDatasetConfigurator& config, const size_t size, c // Y axis UP std::vector camsPts; - camsPts.push_back({ static_cast(size) + 10.0, 0.0, 0.0 }); - camsPts.push_back({ static_cast(size) - 0.5 + 10.0, 0.0, heightT - 0.0}); + camsPts.push_back({static_cast(size) + 10.0, 0.0, 0.0}); + camsPts.push_back({static_cast(size) - 0.5 + 10.0, 0.0, heightT - 0.0}); const size_t ptsSize = 5 * size - 3; // Construct our pts (Y axis UP) @@ -137,7 +139,7 @@ SfMData generateSfm(const NViewDatasetConfigurator& config, const size_t size, c // Bottom down for (size_t i = 0; i < size; i++) matPts.col(i) << i, 0.0, 0.0; - + // Bottom up for (size_t i = 0; i < size - 1; i++) matPts.col(size + i) << (i + 0.5), 0.0, heightT; @@ -147,22 +149,21 @@ SfMData generateSfm(const NViewDatasetConfigurator& config, const size_t size, c matPts.col(2 * size - 1 + i) << (i / 2.0 + 0.5), heightT, (i % 2 == 0 ? centerMinT : heightT - centerMinT); // Center - for (size_t i = 0; i < size - 1; i ++) - matPts.col(4 * size - 2 + i) << (i + (1 + (i % 2)) * 0.5), centerMinT + (i % 2) * 0.1, (i % 2 == 0 ? centerMinT : heightT - centerMinT); // center + for (size_t i = 0; i < size - 1; i++) + matPts.col(4 * size - 2 + i) << (i + (1 + (i % 2)) * 0.5), centerMinT + (i % 2) * 0.1, + (i % 2 == 0 ? centerMinT : heightT - centerMinT); // center // jitters all pts matPts += Mat3X::Random(3, ptsSize) * 0.05; // Same internals parameters for all cams Mat3 internalParams; - internalParams << config._fx, 0, config._cx, - 0, config._fy, config._cy, - 0, 0, 1; + internalParams << config._fx, 0, config._cx, 0, config._fy, config._cy, 0, 0, 1; // 1. Views for (int i = 0; i < camsPts.size(); ++i) { - const IndexT id_view = i, id_pose = i, id_intrinsic = 0; // shared intrinsics + const IndexT id_view = i, id_pose = i, id_intrinsic = 0; // shared intrinsics sfm_data.getViews().emplace(i, std::make_shared("", id_view, id_intrinsic, id_pose, config._cx * 2, config._cy * 2)); } diff --git a/src/aliceVision/fuseCut/Fuser.cpp b/src/aliceVision/fuseCut/Fuser.cpp index 5eb94da5a1..26a0d3d9ff 100644 --- a/src/aliceVision/fuseCut/Fuser.cpp +++ b/src/aliceVision/fuseCut/Fuser.cpp @@ -33,8 +33,8 @@ unsigned long computeNumberOfAllPoints(const mvsUtils::MultiViewParams& mp, int { unsigned long npts = 0; -#pragma omp parallel for reduction(+:npts) - for(int rc = 0; rc < mp.ncams; rc++) +#pragma omp parallel for reduction(+ : npts) + for (int rc = 0; rc < mp.ncams; rc++) { const unsigned long nbDepthValues = mvsUtils::getNbDepthValuesFromDepthMap(rc, mp, scale); npts += nbDepthValues; @@ -46,14 +46,11 @@ Fuser::Fuser(const mvsUtils::MultiViewParams& mp) : _mp(mp) {} -Fuser::~Fuser() -{ -} - +Fuser::~Fuser() {} /** - * @brief - * + * @brief + * * @param[in] pixSizeFactor: pixSize tolerance factor * @param[in] * @param[in] p: 3d point back projected from tc camera @@ -64,17 +61,23 @@ Fuser::~Fuser() * @param[in] simMap * @param[in] scale */ -bool Fuser::updateInSurr(float pixToleranceFactor, int pixSizeBall, int pixSizeBallWSP, Point3d& p, int rc, int tc, +bool Fuser::updateInSurr(float pixToleranceFactor, + int pixSizeBall, + int pixSizeBallWSP, + Point3d& p, + int rc, + int tc, StaticVector* numOfPtsMap, - const image::Image& depthMap, const image::Image& simMap, - int scale) + const image::Image& depthMap, + const image::Image& simMap, + int scale) { - int w =_mp.getWidth(rc) / scale; - int h =_mp.getHeight(rc) / scale; + int w = _mp.getWidth(rc) / scale; + int h = _mp.getHeight(rc) / scale; Pixel pix; - _mp.getPixelFor3DPoint(&pix, p, rc); - if(!_mp.isPixelInImage(pix, rc)) + _mp.getPixelFor3DPoint(&pix, p, rc); + if (!_mp.isPixelInImage(pix, rc)) { return false; } @@ -88,25 +91,25 @@ bool Fuser::updateInSurr(float pixToleranceFactor, int pixSizeBall, int pixSizeB int d = pixSizeBall; const float sim = simMap(cell.y, cell.x); - if(sim >= 1.0f) + if (sim >= 1.0f) { d = pixSizeBallWSP; } // float pixSize = 2.0f*(float)std::max(d,1)*_mp.getCamPixelSize(p,cam); - float pixSize = pixToleranceFactor *_mp.getCamPixelSizePlaneSweepAlpha(p, rc, tc, scale, 1); + float pixSize = pixToleranceFactor * _mp.getCamPixelSizePlaneSweepAlpha(p, rc, tc, scale, 1); Pixel ncell; - for(ncell.x = std::max(0, cell.x - d); ncell.x <= std::min(w - 1, cell.x + d); ncell.x++) + for (ncell.x = std::max(0, cell.x - d); ncell.x <= std::min(w - 1, cell.x + d); ncell.x++) { - for(ncell.y = std::max(0, cell.y - d); ncell.y <= std::min(h - 1, cell.y + d); ncell.y++) + for (ncell.y = std::max(0, cell.y - d); ncell.y <= std::min(h - 1, cell.y + d); ncell.y++) { // printf("%i %i %i %i %i %i %i %i\n",ncell.x,ncell.y,w,h,w*h,depthMap->size(),cam,scale); const float depth = depthMap(ncell.y, ncell.x); // Point3d p1 = _mp.CArr[rc] + // (_mp.iCamArr[rc]*Point2d((float)ncell.x*(float)scale,(float)ncell.y*(float)scale)).normalize()*depth; // if ( (p1-p).size() < pixSize ) { - if(fabs(pixDepth - depth) < pixSize) + if (fabs(pixDepth - depth) < pixSize) { (*numOfPtsMap)[ncell.y * w + ncell.x]++; } @@ -122,7 +125,7 @@ void Fuser::filterGroups(const std::vector& cams, float pixToleranceFactor, ALICEVISION_LOG_INFO("Precomputing groups."); long t1 = clock(); #pragma omp parallel for - for(int c = 0; c < cams.size(); c++) + for (int c = 0; c < cams.size(); c++) { int rc = cams[c]; filterGroupsRC(rc, pixToleranceFactor, pixSizeBall, pixSizeBallWSP, nNearestCams); @@ -157,7 +160,7 @@ bool Fuser::filterGroupsRC(int rc, float pixToleranceFactor, int pixSizeBall, in std::stringstream s; s << "filterGroupsRC: bad image dimension for camera: " << _mp.getViewId(rc) << "\n"; s << "depthMap size: " << depthMap.size() << ", simMap size: " << simMap.size() << ", width: " << w << ", height: " << h; - throw std::runtime_error(s.str()); + throw std::runtime_error(s.str()); } StaticVector* numOfPtsMap = new StaticVector(); @@ -166,7 +169,7 @@ bool Fuser::filterGroupsRC(int rc, float pixToleranceFactor, int pixSizeBall, in StaticVector tcams = _mp.findNearestCamsFromLandmarks(rc, nNearestCams); - for(int c = 0; c < tcams.size(); c++) + for (int c = 0; c < tcams.size(); c++) { numOfPtsMap->resize_with(w * h, 0); int tc = tcams[c]; @@ -178,31 +181,31 @@ bool Fuser::filterGroupsRC(int rc, float pixToleranceFactor, int pixSizeBall, in if (tcdepthMap.Height() > 0 && tcdepthMap.Width() > 0) { - for(int y = 0; y < tcdepthMap.Height(); ++y) + for (int y = 0; y < tcdepthMap.Height(); ++y) { - for(int x = 0; x < tcdepthMap.Width(); ++x) + for (int x = 0; x < tcdepthMap.Width(); ++x) { float depth = tcdepthMap(y, x); - if(depth > 0.0f) + if (depth > 0.0f) { - Point3d p = _mp.CArr[tc] + (_mp.iCamArr[tc] * Point2d((float)x, (float)y)).normalize() * depth; - updateInSurr(pixToleranceFactor, pixSizeBall, pixSizeBallWSP, p, rc, tc, numOfPtsMap, depthMap, simMap, 1); + Point3d p = _mp.CArr[tc] + (_mp.iCamArr[tc] * Point2d((float)x, (float)y)).normalize() * depth; + updateInSurr(pixToleranceFactor, pixSizeBall, pixSizeBallWSP, p, rc, tc, numOfPtsMap, depthMap, simMap, 1); } } } - for(int i = 0; i < w * h; i++) + for (int i = 0; i < w * h; i++) { numOfModalsMap(i) += static_cast((*numOfPtsMap)[i] > 0); } } } - image::writeImageWithFloat(getFileNameFromIndex(_mp, rc, mvsUtils::EFileType::nmodMap), - numOfModalsMap, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::LINEAR) - .storageDataType(image::EStorageDataType::Float)); + image::writeImageWithFloat( + getFileNameFromIndex(_mp, rc, mvsUtils::EFileType::nmodMap), + numOfModalsMap, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::LINEAR).storageDataType(image::EStorageDataType::Float)); delete numOfPtsMap; @@ -219,7 +222,7 @@ void Fuser::filterDepthMaps(const std::vector& cams, int minNumOfModals, in long t1 = clock(); #pragma omp parallel for - for(int c = 0; c < cams.size(); c++) + for (int c = 0; c < cams.size(); c++) { int rc = cams[c]; filterDepthMapsRC(rc, minNumOfModals, minNumOfModalsWSP2SSP); @@ -241,17 +244,15 @@ bool Fuser::filterDepthMapsRC(int rc, int minNumOfModals, int minNumOfModalsWSP2 mvsUtils::readMap(rc, _mp, mvsUtils::EFileType::depthMap, depthMap); mvsUtils::readMap(rc, _mp, mvsUtils::EFileType::simMap, simMap); - image::readImage(getFileNameFromIndex(_mp, rc, mvsUtils::EFileType::nmodMap), - numOfModalsMap, - image::EImageColorSpace::NO_CONVERSION); + image::readImage(getFileNameFromIndex(_mp, rc, mvsUtils::EFileType::nmodMap), numOfModalsMap, image::EImageColorSpace::NO_CONVERSION); - if (depthMap.Width() != simMap.Width() || depthMap.Width() != numOfModalsMap.Width() || - depthMap.Height() != simMap.Height() || depthMap.Height() != numOfModalsMap.Height()) + if (depthMap.Width() != simMap.Width() || depthMap.Width() != numOfModalsMap.Width() || depthMap.Height() != simMap.Height() || + depthMap.Height() != numOfModalsMap.Height()) { throw std::invalid_argument("depthMap, simMap and numOfModalsMap must have same size"); } - for(int i = 0; i < depthMap.size(); i++) + for (int i = 0; i < depthMap.size(); i++) { // if the point is part of a mask (alpha) skip if (depthMap(i) <= -2.0f) @@ -273,7 +274,7 @@ bool Fuser::filterDepthMapsRC(int rc, int minNumOfModals, int minNumOfModalsWSP2 } // if it is not conistent in minimal number of cameras and is strongly supported then remove him - if((numOfModalsMap(i) < minNumOfModals - 1) && (simMap(i) < 1.0f)) + if ((numOfModalsMap(i) < minNumOfModals - 1) && (simMap(i) < 1.0f)) { depthMap(i) = -1.0f; simMap(i) = 1.0f; @@ -298,8 +299,8 @@ float Fuser::computeAveragePixelSizeInHexahedron(Point3d* hexah, int step, int s float av = 0.0f; float nav = 0.0f; float minv = std::numeric_limits::max(); - //long t1 = mvsUtils::initEstimate(); - for(int c = 0; c < cams.size(); c++) + // long t1 = mvsUtils::initEstimate(); + for (int c = 0; c < cams.size(); c++) { int rc = cams[c]; int h = _mp.getHeight(rc) / scaleuse; @@ -311,23 +312,21 @@ float Fuser::computeAveragePixelSizeInHexahedron(Point3d* hexah, int step, int s if (rcdepthMap.size() < w * h) throw std::runtime_error("Invalid image size"); - for(int y = 0; y < h; y++) + for (int y = 0; y < h; y++) { - for(int x = 0; x < w; ++x) + for (int x = 0; x < w; ++x) { const float depth = rcdepthMap(y, x); - if(depth > 0.0f) + if (depth > 0.0f) { - if(j % step == 0) + if (j % step == 0) { - Point3d p = _mp.CArr[rc] + - (_mp.iCamArr[rc] * Point2d((float)x * (float)scaleuse, (float)y * (float)scaleuse)) - .normalize() * - depth; - if(mvsUtils::isPointInHexahedron(p, hexah)) + Point3d p = + _mp.CArr[rc] + (_mp.iCamArr[rc] * Point2d((float)x * (float)scaleuse, (float)y * (float)scaleuse)).normalize() * depth; + if (mvsUtils::isPointInHexahedron(p, hexah)) { float v = _mp.getCamPixelSize(p, rc); - av += v; // WARNING: the value may be too big for a float + av += v; // WARNING: the value may be too big for a float nav += 1.0f; minv = std::min(minv, v); } @@ -336,11 +335,11 @@ float Fuser::computeAveragePixelSizeInHexahedron(Point3d* hexah, int step, int s } } } - //mvsUtils::printfEstimate(c, cams.size(), t1); + // mvsUtils::printfEstimate(c, cams.size(), t1); } - //mvsUtils::finishEstimate(); + // mvsUtils::finishEstimate(); - if(nav == 0.0f) + if (nav == 0.0f) { return -1.0f; } @@ -350,37 +349,35 @@ float Fuser::computeAveragePixelSizeInHexahedron(Point3d* hexah, int step, int s // return minv; } - - float Fuser::computeAveragePixelSizeInHexahedron(Point3d* hexah, const sfmData::SfMData& sfmData) { - float av = 0.0f; - float nav = 0.0f; - float minv = std::numeric_limits::max(); - - for(const auto& landmarkPair : sfmData.getLandmarks()) - { - const sfmData::Landmark& landmark = landmarkPair.second; - const Point3d p(landmark.X(0), landmark.X(1), landmark.X(2)); + float av = 0.0f; + float nav = 0.0f; + float minv = std::numeric_limits::max(); - for(const auto& observationPair : landmark.observations) + for (const auto& landmarkPair : sfmData.getLandmarks()) { - const IndexT viewId = observationPair.first; - - if(mvsUtils::isPointInHexahedron(p, hexah)) - { - float v = _mp.getCamPixelSize(p, _mp.getIndexFromViewId(viewId)); - av += v; // WARNING: the value may be too big for a float - nav += 1.0f; - minv = std::min(minv, v); - } + const sfmData::Landmark& landmark = landmarkPair.second; + const Point3d p(landmark.X(0), landmark.X(1), landmark.X(2)); + + for (const auto& observationPair : landmark.observations) + { + const IndexT viewId = observationPair.first; + + if (mvsUtils::isPointInHexahedron(p, hexah)) + { + float v = _mp.getCamPixelSize(p, _mp.getIndexFromViewId(viewId)); + av += v; // WARNING: the value may be too big for a float + nav += 1.0f; + minv = std::min(minv, v); + } + } } - } - if(nav == 0.0f) - return -1.0f; + if (nav == 0.0f) + return -1.0f; - return av / nav; + return av / nav; } /** @@ -396,21 +393,21 @@ void Fuser::divideSpaceFromDepthMaps(Point3d* hexah, float& minPixSize) int stepPts = std::max(1, (int)(npset / (unsigned long)1000000)); minPixSize = std::numeric_limits::max(); - //long t1 = mvsUtils::initEstimate(); + // long t1 = mvsUtils::initEstimate(); Stat3d s3d = Stat3d(); - for(int rc = 0; rc < _mp.ncams; rc++) + for (int rc = 0; rc < _mp.ncams; rc++) { int w = _mp.getWidth(rc); image::Image depthMap; mvsUtils::readMap(rc, _mp, mvsUtils::EFileType::depthMapFiltered, depthMap); - for(int i = 0; i < depthMap.size(); i += stepPts) + for (int i = 0; i < depthMap.size(); i += stepPts) { int x = i % w; int y = i / w; float depth = depthMap(i); - if(depth > 0.0f) + if (depth > 0.0f) { Point3d p = _mp.CArr[rc] + (_mp.iCamArr[rc] * Point2d((float)x, (float)y)).normalize() * depth; float pixSize = _mp.getCamPixelSize(p, rc); @@ -418,64 +415,62 @@ void Fuser::divideSpaceFromDepthMaps(Point3d* hexah, float& minPixSize) s3d.update(&p); } } - //mvsUtils::printfEstimate(rc, _mp.ncams, t1); + // mvsUtils::printfEstimate(rc, _mp.ncams, t1); } - //mvsUtils::finishEstimate(); + // mvsUtils::finishEstimate(); Point3d v1, v2, v3, cg; float d1, d2, d3; s3d.getEigenVectorsDesc(cg, v1, v2, v3, d1, d2, d3); using namespace boost::accumulators; - using Accumulator = accumulator_set - >>; - const std::size_t cacheSize = 10000; - Accumulator accX1( tag::tail::cache_size = cacheSize ); - Accumulator accX2( tag::tail::cache_size = cacheSize ); - Accumulator accY1( tag::tail::cache_size = cacheSize ); - Accumulator accY2( tag::tail::cache_size = cacheSize ); - Accumulator accZ1( tag::tail::cache_size = cacheSize ); - Accumulator accZ2( tag::tail::cache_size = cacheSize ); - - for(int rc = 0; rc < _mp.ncams; ++rc) + using Accumulator = accumulator_set>>; + const std::size_t cacheSize = 10000; + Accumulator accX1(tag::tail::cache_size = cacheSize); + Accumulator accX2(tag::tail::cache_size = cacheSize); + Accumulator accY1(tag::tail::cache_size = cacheSize); + Accumulator accY2(tag::tail::cache_size = cacheSize); + Accumulator accZ1(tag::tail::cache_size = cacheSize); + Accumulator accZ2(tag::tail::cache_size = cacheSize); + + for (int rc = 0; rc < _mp.ncams; ++rc) { int w = _mp.getWidth(rc); image::Image depthMap; mvsUtils::readMap(rc, _mp, mvsUtils::EFileType::depthMapFiltered, depthMap); - for(int i = 0; i < depthMap.size(); i += stepPts) + for (int i = 0; i < depthMap.size(); i += stepPts) { int x = i % w; int y = i / w; float depth = depthMap(i); - if(depth > 0.0f) + if (depth > 0.0f) { Point3d p = _mp.CArr[rc] + (_mp.iCamArr[rc] * Point2d((float)x, (float)y)).normalize() * depth; float d1 = orientedPointPlaneDistance(p, cg, v1); float d2 = orientedPointPlaneDistance(p, cg, v2); float d3 = orientedPointPlaneDistance(p, cg, v3); - if(d1 < 0) + if (d1 < 0) accX1(fabs(d1)); else accX2(fabs(d1)); - if(d2 < 0) + if (d2 < 0) accY1(fabs(d2)); else accY2(fabs(d2)); - if(d3 < 0) + if (d3 < 0) accZ1(fabs(d3)); else accZ2(fabs(d3)); } } - //mvsUtils::printfEstimate(rc, _mp.ncams, t1); + // mvsUtils::printfEstimate(rc, _mp.ncams, t1); } - //mvsUtils::finishEstimate(); + // mvsUtils::finishEstimate(); float perc = (float)_mp.userParams.get("LargeScale.universePercentile", 0.999f); @@ -486,11 +481,11 @@ void Fuser::divideSpaceFromDepthMaps(Point3d* hexah, float& minPixSize) float mind3 = -quantile(accZ1, quantile_probability = perc); float maxd3 = quantile(accZ2, quantile_probability = perc); -// std::cout << "quantile accumulators:" << std::endl; -// std::cout << "X(" << mind1 << ", " << maxd1 << "), " -// << "Y(" << mind2 << ", " << maxd2 << "), " -// << "Z(" << mind3 << ", " << maxd3 << "), " -// << std::endl; + // std::cout << "quantile accumulators:" << std::endl; + // std::cout << "X(" << mind1 << ", " << maxd1 << "), " + // << "Y(" << mind2 << ", " << maxd2 << "), " + // << "Z(" << mind3 << ", " << maxd3 << "), " + // << std::endl; hexah[0] = cg + v1 * maxd1 + v2 * maxd2 + v3 * maxd3; hexah[1] = cg + v1 * mind1 + v2 * maxd2 + v3 * maxd3; @@ -503,122 +498,123 @@ void Fuser::divideSpaceFromDepthMaps(Point3d* hexah, float& minPixSize) const double volume = mvsUtils::computeHexahedronVolume(hexah); - if(std::isnan(volume) || volume < std::numeric_limits::epsilon()) - throw std::runtime_error("Failed to estimate space from depth maps: The space bounding box is too small."); + if (std::isnan(volume) || volume < std::numeric_limits::epsilon()) + throw std::runtime_error("Failed to estimate space from depth maps: The space bounding box is too small."); ALICEVISION_LOG_INFO("Estimate space done."); } bool checkLandmarkMinObservationAngle(const sfmData::SfMData& sfmData, const sfmData::Landmark& landmark, float minObservationAngle) { - for(const auto& observationPairI : landmark.observations) - { - const IndexT I = observationPairI.first; - const sfmData::View& viewI = *(sfmData.getViews().at(I)); - const geometry::Pose3 poseI = sfmData.getPose(viewI).getTransform(); - const camera::IntrinsicBase* intrinsicPtrI = sfmData.getIntrinsicPtr(viewI.getIntrinsicId()); - - for(const auto& observationPairJ : landmark.observations) + for (const auto& observationPairI : landmark.observations) { - const IndexT J = observationPairJ.first; + const IndexT I = observationPairI.first; + const sfmData::View& viewI = *(sfmData.getViews().at(I)); + const geometry::Pose3 poseI = sfmData.getPose(viewI).getTransform(); + const camera::IntrinsicBase* intrinsicPtrI = sfmData.getIntrinsicPtr(viewI.getIntrinsicId()); + + for (const auto& observationPairJ : landmark.observations) + { + const IndexT J = observationPairJ.first; - // cannot compare the current view with itself - if(I == J) - continue; + // cannot compare the current view with itself + if (I == J) + continue; - const sfmData::View& viewJ = *(sfmData.getViews().at(J)); - const geometry::Pose3 poseJ = sfmData.getPose(viewJ).getTransform(); - const camera::IntrinsicBase* intrinsicPtrJ = sfmData.getIntrinsicPtr(viewJ.getIntrinsicId()); + const sfmData::View& viewJ = *(sfmData.getViews().at(J)); + const geometry::Pose3 poseJ = sfmData.getPose(viewJ).getTransform(); + const camera::IntrinsicBase* intrinsicPtrJ = sfmData.getIntrinsicPtr(viewJ.getIntrinsicId()); - const double angle = camera::angleBetweenRays(poseI, intrinsicPtrI, poseJ, intrinsicPtrJ, observationPairI.second.x, observationPairJ.second.x); + const double angle = + camera::angleBetweenRays(poseI, intrinsicPtrI, poseJ, intrinsicPtrJ, observationPairI.second.x, observationPairJ.second.x); - // check angle between two observation - if(angle < minObservationAngle) - continue; + // check angle between two observation + if (angle < minObservationAngle) + continue; - return true; + return true; + } } - } - return false; + return false; } void Fuser::divideSpaceFromSfM(const sfmData::SfMData& sfmData, Point3d* hexah, std::size_t minObservations, float minObservationAngle) const { - ALICEVISION_LOG_INFO("Estimate space from SfM."); - - const std::size_t cacheSize = 10000; - const double percentile = _mp.userParams.get("LargeScale.universePercentile", 0.999); - - using namespace boost::accumulators; - using AccumulatorMin = accumulator_set>>; - using AccumulatorMax = accumulator_set>>; - - AccumulatorMin accMinX(tag::tail::cache_size = cacheSize); - AccumulatorMin accMinY(tag::tail::cache_size = cacheSize); - AccumulatorMin accMinZ(tag::tail::cache_size = cacheSize); - AccumulatorMax accMaxX(tag::tail::cache_size = cacheSize); - AccumulatorMax accMaxY(tag::tail::cache_size = cacheSize); - AccumulatorMax accMaxZ(tag::tail::cache_size = cacheSize); - - for(const auto& landmarkPair : sfmData.getLandmarks()) - { - const sfmData::Landmark& landmark = landmarkPair.second; - - // check number of observations - if(landmark.observations.size() < minObservations) - continue; - - // check angle between observations - if(!checkLandmarkMinObservationAngle(sfmData, landmark, minObservationAngle)) - continue; - - const double x = landmark.X(0); - const double y = landmark.X(1); - const double z = landmark.X(2); - - accMinX(x); - accMinY(y); - accMinZ(z); - accMaxX(x); - accMaxY(y); - accMaxZ(z); - } - - // Remove a percentile of the observations (to remove unstable points) - double xMin = quantile(accMinX, quantile_probability = 1.0 - percentile); - double yMin = quantile(accMinY, quantile_probability = 1.0 - percentile); - double zMin = quantile(accMinZ, quantile_probability = 1.0 - percentile); - double xMax = quantile(accMaxX, quantile_probability = percentile); - double yMax = quantile(accMaxY, quantile_probability = percentile); - double zMax = quantile(accMaxZ, quantile_probability = percentile); - - // Add a margin on the result - const double xMargin = (xMax - xMin) * 0.05; - const double yMargin = (yMax - yMin) * 0.05; - const double zMargin = (zMax - zMin) * 0.05; - xMin -= xMargin; - yMin -= yMargin; - zMin -= zMargin; - xMax += xMargin; - yMax += yMargin; - zMax += zMargin; - - hexah[0] = Point3d(xMax, yMax, zMax); - hexah[1] = Point3d(xMin, yMax, zMax); - hexah[2] = Point3d(xMin, yMin, zMax); - hexah[3] = Point3d(xMax, yMin, zMax); - hexah[4] = Point3d(xMax, yMax, zMin); - hexah[5] = Point3d(xMin, yMax, zMin); - hexah[6] = Point3d(xMin, yMin, zMin); - hexah[7] = Point3d(xMax, yMin, zMin); - - const double volume = mvsUtils::computeHexahedronVolume(hexah); - - if(std::isnan(volume) || volume < std::numeric_limits::epsilon()) - throw std::runtime_error("Failed to estimate space from SfM: The space bounding box is too small."); - - ALICEVISION_LOG_INFO("Estimate space done."); + ALICEVISION_LOG_INFO("Estimate space from SfM."); + + const std::size_t cacheSize = 10000; + const double percentile = _mp.userParams.get("LargeScale.universePercentile", 0.999); + + using namespace boost::accumulators; + using AccumulatorMin = accumulator_set>>; + using AccumulatorMax = accumulator_set>>; + + AccumulatorMin accMinX(tag::tail::cache_size = cacheSize); + AccumulatorMin accMinY(tag::tail::cache_size = cacheSize); + AccumulatorMin accMinZ(tag::tail::cache_size = cacheSize); + AccumulatorMax accMaxX(tag::tail::cache_size = cacheSize); + AccumulatorMax accMaxY(tag::tail::cache_size = cacheSize); + AccumulatorMax accMaxZ(tag::tail::cache_size = cacheSize); + + for (const auto& landmarkPair : sfmData.getLandmarks()) + { + const sfmData::Landmark& landmark = landmarkPair.second; + + // check number of observations + if (landmark.observations.size() < minObservations) + continue; + + // check angle between observations + if (!checkLandmarkMinObservationAngle(sfmData, landmark, minObservationAngle)) + continue; + + const double x = landmark.X(0); + const double y = landmark.X(1); + const double z = landmark.X(2); + + accMinX(x); + accMinY(y); + accMinZ(z); + accMaxX(x); + accMaxY(y); + accMaxZ(z); + } + + // Remove a percentile of the observations (to remove unstable points) + double xMin = quantile(accMinX, quantile_probability = 1.0 - percentile); + double yMin = quantile(accMinY, quantile_probability = 1.0 - percentile); + double zMin = quantile(accMinZ, quantile_probability = 1.0 - percentile); + double xMax = quantile(accMaxX, quantile_probability = percentile); + double yMax = quantile(accMaxY, quantile_probability = percentile); + double zMax = quantile(accMaxZ, quantile_probability = percentile); + + // Add a margin on the result + const double xMargin = (xMax - xMin) * 0.05; + const double yMargin = (yMax - yMin) * 0.05; + const double zMargin = (zMax - zMin) * 0.05; + xMin -= xMargin; + yMin -= yMargin; + zMin -= zMargin; + xMax += xMargin; + yMax += yMargin; + zMax += zMargin; + + hexah[0] = Point3d(xMax, yMax, zMax); + hexah[1] = Point3d(xMin, yMax, zMax); + hexah[2] = Point3d(xMin, yMin, zMax); + hexah[3] = Point3d(xMax, yMin, zMax); + hexah[4] = Point3d(xMax, yMax, zMin); + hexah[5] = Point3d(xMin, yMax, zMin); + hexah[6] = Point3d(xMin, yMin, zMin); + hexah[7] = Point3d(xMax, yMin, zMin); + + const double volume = mvsUtils::computeHexahedronVolume(hexah); + + if (std::isnan(volume) || volume < std::numeric_limits::epsilon()) + throw std::runtime_error("Failed to estimate space from SfM: The space bounding box is too small."); + + ALICEVISION_LOG_INFO("Estimate space done."); } Voxel Fuser::estimateDimensions(Point3d* vox, Point3d* newSpace, int scale, int maxOcTreeDim, const sfmData::SfMData* sfmData) @@ -639,18 +635,18 @@ Voxel Fuser::estimateDimensions(Point3d* vox, Point3d* newSpace, int scale, int float aAvPixelSize; - if(sfmData == nullptr) + if (sfmData == nullptr) { - // WARNING perf: reload all depth maps to compute the minPixelSize (minPixelSize consider only points in the hexahedron) - // Average 3D size for each pixel from all 3D points in the current voxel - const int maxPts = 1000000; - const int nAllPts = computeNumberOfAllPoints(_mp, scale); - const int stepPts = nAllPts / maxPts + 1; - aAvPixelSize = computeAveragePixelSizeInHexahedron(vox, stepPts, scale) * (float)std::max(scale, 1) * pointToJoinPixSizeDist; + // WARNING perf: reload all depth maps to compute the minPixelSize (minPixelSize consider only points in the hexahedron) + // Average 3D size for each pixel from all 3D points in the current voxel + const int maxPts = 1000000; + const int nAllPts = computeNumberOfAllPoints(_mp, scale); + const int stepPts = nAllPts / maxPts + 1; + aAvPixelSize = computeAveragePixelSizeInHexahedron(vox, stepPts, scale) * (float)std::max(scale, 1) * pointToJoinPixSizeDist; } else { - aAvPixelSize = computeAveragePixelSizeInHexahedron(vox, *sfmData) * pointToJoinPixSizeDist; + aAvPixelSize = computeAveragePixelSizeInHexahedron(vox, *sfmData) * pointToJoinPixSizeDist; } Voxel maxDim; @@ -673,16 +669,17 @@ Voxel Fuser::estimateDimensions(Point3d* vox, Point3d* newSpace, int scale, int ALICEVISION_LOG_INFO("Estimated: " << mvsUtils::num2str(maxDim.x) << " " << mvsUtils::num2str(maxDim.y) << " " << mvsUtils::num2str(maxDim.z)); - ALICEVISION_LOG_DEBUG("optimal detail: " - << static_cast((vvx.size() / (float)maxDim.x) / aAvPixelSize) << " " - << static_cast((vvy.size() / (float)maxDim.y) / aAvPixelSize) << " " - << static_cast((vvz.size() / (float)maxDim.z) / aAvPixelSize)); + ALICEVISION_LOG_DEBUG("optimal detail: " << static_cast((vvx.size() / (float)maxDim.x) / aAvPixelSize) << " " + << static_cast((vvy.size() / (float)maxDim.y) / aAvPixelSize) << " " + << static_cast((vvz.size() / (float)maxDim.z) / aAvPixelSize)); return maxDim; } -std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiViewParams& mp, - bool addRandomNoise, float percNoisePts, +std::string generateTempPtsSimsFiles(const std::string& tmpDir, + mvsUtils::MultiViewParams& mp, + bool addRandomNoise, + float percNoisePts, int noisPixSizeDistHalfThr) { ALICEVISION_LOG_INFO("generating temp files."); @@ -699,7 +696,7 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV minMaxDepths->resize_with(mp.ncams, Point2d(-1.0, -1.0)); #pragma omp parallel for - for(int rc = 0; rc < mp.ncams; rc++) + for (int rc = 0; rc < mp.ncams; rc++) { int w = mp.getWidth(rc) / scaleuse; int h = mp.getHeight(rc) / scaleuse; @@ -721,11 +718,11 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV throw std::runtime_error("Invalid image size"); } - if(addRandomNoise) + if (addRandomNoise) { StaticVector* idsAlive = new StaticVector(); idsAlive->reserve(w * h); - for(int i = 0; i < w * h; i++) + for (int i = 0; i < w * h; i++) { if (depthMap(i) > 0.0f) { @@ -739,23 +736,21 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV srand(time(nullptr)); long t1 = clock(); - for(int y = 0; y < h; ++y) + for (int y = 0; y < h; ++y) { - for(int x = 0; x < w; ++x) + for (int x = 0; x < w; ++x) { int id = y * w + x; int i = (*idsAlive)[randIdsAlive[id]]; double depth = depthMap(i); double sim = simMap(i); - if(depth > 0.0f) + if (depth > 0.0f) { Point3d p = mp.CArr[rc] + - (mp.iCamArr[rc] * Point2d((double)x * (double)scaleuse, (double)y * (double)scaleuse)) - .normalize() * - depth; + (mp.iCamArr[rc] * Point2d((double)x * (double)scaleuse, (double)y * (double)scaleuse)).normalize() * depth; - if(id < nnoisePts) + if (id < nnoisePts) { double pixSize = mp.getCamPixelSize(p, rc); int rid = rand() % (2 * noisPixSizeDistHalfThr + 1); @@ -763,14 +758,12 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV double rdepthAdd = pixSize * (double)rid; depth = depth + rdepthAdd; p = mp.CArr[rc] + - (mp.iCamArr[rc] * Point2d((double)x * (double)scaleuse, (double)y * (double)scaleuse)) - .normalize() * - depth; + (mp.iCamArr[rc] * Point2d((double)x * (double)scaleuse, (double)y * (double)scaleuse)).normalize() * depth; } pts->push_back(p); sims->push_back(sim); - if((*minMaxDepths)[rc].x < 0.0f) + if ((*minMaxDepths)[rc].x < 0.0f) { (*minMaxDepths)[rc].x = depth; } @@ -778,7 +771,7 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV { (*minMaxDepths)[rc].x = std::min((*minMaxDepths)[rc].x, depth); } - if((*minMaxDepths)[rc].y < 0.0f) + if ((*minMaxDepths)[rc].y < 0.0f) { (*minMaxDepths)[rc].y = depth; } @@ -795,25 +788,21 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV } else { - long t1 = clock(); - for(int x = 0; x < w; x++) + for (int x = 0; x < w; x++) { - for(int y = 0; y < h; y++) + for (int y = 0; y < h; y++) { int i = x * h + y; double depth = depthMap(i); double sim = simMap(i); - if(depth > 0.0f) + if (depth > 0.0f) { - Point3d p = - mp.CArr[rc] + - (mp.iCamArr[rc] * Point2d((double)x * (double)scaleuse, (double)y * (double)scaleuse)) - .normalize() * - depth; + Point3d p = mp.CArr[rc] + + (mp.iCamArr[rc] * Point2d((double)x * (double)scaleuse, (double)y * (double)scaleuse)).normalize() * depth; pts->push_back(p); sims->push_back(sim); - if((*minMaxDepths)[rc].x < 0.0f) + if ((*minMaxDepths)[rc].x < 0.0f) { (*minMaxDepths)[rc].x = depth; } @@ -821,7 +810,7 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV { (*minMaxDepths)[rc].x = std::min((*minMaxDepths)[rc].x, depth); } - if((*minMaxDepths)[rc].y < 0.0f) + if ((*minMaxDepths)[rc].y < 0.0f) { (*minMaxDepths)[rc].y = depth; } @@ -850,7 +839,7 @@ std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiV void deleteTempPtsSimsFiles(mvsUtils::MultiViewParams& mp, const std::string& depthMapsPtsSimsTmpDir) { - for(int rc = 0; rc < mp.ncams; rc++) + for (int rc = 0; rc < mp.ncams; rc++) { std::string ptsfn = depthMapsPtsSimsTmpDir + std::to_string(mp.getViewId(rc)) + "pts.bin"; std::string simsfn = depthMapsPtsSimsTmpDir + std::to_string(mp.getViewId(rc)) + "sims.bin"; @@ -860,5 +849,5 @@ void deleteTempPtsSimsFiles(mvsUtils::MultiViewParams& mp, const std::string& de bfs::remove_all(depthMapsPtsSimsTmpDir); } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/Fuser.hpp b/src/aliceVision/fuseCut/Fuser.hpp index 7d2450f63e..734917f5d4 100644 --- a/src/aliceVision/fuseCut/Fuser.hpp +++ b/src/aliceVision/fuseCut/Fuser.hpp @@ -20,7 +20,7 @@ namespace fuseCut { class Fuser { -public: + public: const mvsUtils::MultiViewParams& _mp; Fuser(const mvsUtils::MultiViewParams& mp); @@ -42,17 +42,27 @@ class Fuser Voxel estimateDimensions(Point3d* vox, Point3d* newSpace, int scale, int maxOcTreeDim, const sfmData::SfMData* sfmData = nullptr); -private: - bool updateInSurr(float pixToleranceFactor, int pixSizeBall, int pixSizeBallWSP, Point3d& p, int rc, int tc, StaticVector* numOfPtsMap, - const image::Image& depthMap, const image::Image& simMap, int scale); + private: + bool updateInSurr(float pixToleranceFactor, + int pixSizeBall, + int pixSizeBallWSP, + Point3d& p, + int rc, + int tc, + StaticVector* numOfPtsMap, + const image::Image& depthMap, + const image::Image& simMap, + int scale); }; unsigned long computeNumberOfAllPoints(const mvsUtils::MultiViewParams& mp, int scale); -std::string generateTempPtsSimsFiles(const std::string& tmpDir, mvsUtils::MultiViewParams& mp, - bool addRandomNoise = false, float percNoisePts = 0.0, +std::string generateTempPtsSimsFiles(const std::string& tmpDir, + mvsUtils::MultiViewParams& mp, + bool addRandomNoise = false, + float percNoisePts = 0.0, int noisPixSizeDistHalfThr = 0); void deleteTempPtsSimsFiles(mvsUtils::MultiViewParams& mp, const std::string& depthMapsPtsSimsTmpDir); -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/LargeScale.cpp b/src/aliceVision/fuseCut/LargeScale.cpp index 203393dbcc..582cc94130 100644 --- a/src/aliceVision/fuseCut/LargeScale.cpp +++ b/src/aliceVision/fuseCut/LargeScale.cpp @@ -18,10 +18,10 @@ namespace fuseCut { namespace bfs = boost::filesystem; LargeScale::LargeScale(mvsUtils::MultiViewParams* _mp, const std::string& _spaceFolderName) - : mp(_mp) - , spaceFolderName(_spaceFolderName) - , spaceVoxelsFolderName(_spaceFolderName + "_data/") - , spaceFileName(spaceFolderName + "/space.txt") + : mp(_mp), + spaceFolderName(_spaceFolderName), + spaceVoxelsFolderName(_spaceFolderName + "_data/"), + spaceFileName(spaceFolderName + "/space.txt") { bfs::create_directory(spaceFolderName); bfs::create_directory(spaceVoxelsFolderName); @@ -29,26 +29,21 @@ LargeScale::LargeScale(mvsUtils::MultiViewParams* _mp, const std::string& _space doVisualize = mp->userParams.get("LargeScale.doVisualizeOctreeTracks", false); } -LargeScale::~LargeScale() -{ -} +LargeScale::~LargeScale() {} -bool LargeScale::isSpaceSaved() -{ - return bfs::exists(spaceFileName); -} +bool LargeScale::isSpaceSaved() { return bfs::exists(spaceFileName); } void LargeScale::saveSpaceToFile() { std::ofstream out(spaceFileName); - out << space[0].x << " " << space[1].x << " " << space[2].x << " " << space[3].x << " " - << space[4].x << " " << space[5].x << " " << space[6].x << " " << space[7].x << "\n"; + out << space[0].x << " " << space[1].x << " " << space[2].x << " " << space[3].x << " " << space[4].x << " " << space[5].x << " " << space[6].x + << " " << space[7].x << "\n"; - out << space[0].y << " " << space[1].y << " " << space[2].y << " " << space[3].y << " " - << space[4].y << " " << space[5].y << " " << space[6].y << " " << space[7].y << "\n"; + out << space[0].y << " " << space[1].y << " " << space[2].y << " " << space[3].y << " " << space[4].y << " " << space[5].y << " " << space[6].y + << " " << space[7].y << "\n"; - out << space[0].z << " " << space[1].z << " " << space[2].z << " " << space[3].z << " " - << space[4].z << " " << space[5].z << " " << space[6].z << " " << space[7].z << "\n"; + out << space[0].z << " " << space[1].z << " " << space[2].z << " " << space[3].z << " " << space[4].z << " " << space[5].z << " " << space[6].z + << " " << space[7].z << "\n"; out << dimensions.x << " " << dimensions.y << " " << dimensions.z << "\n"; out << maxOcTreeDim << "\n"; @@ -57,14 +52,11 @@ void LargeScale::saveSpaceToFile() void LargeScale::loadSpaceFromFile() { std::ifstream in(spaceFileName); - in >> space[0].x >> space[1].x >> space[2].x >> space[3].x - >> space[4].x >> space[5].x >> space[6].x >> space[7].x; + in >> space[0].x >> space[1].x >> space[2].x >> space[3].x >> space[4].x >> space[5].x >> space[6].x >> space[7].x; - in >> space[0].y >> space[1].y >> space[2].y >> space[3].y - >> space[4].y >> space[5].y >> space[6].y >> space[7].y; + in >> space[0].y >> space[1].y >> space[2].y >> space[3].y >> space[4].y >> space[5].y >> space[6].y >> space[7].y; - in >> space[0].z >> space[1].z >> space[2].z >> space[3].z - >> space[4].z >> space[5].z >> space[6].z >> space[7].z; + in >> space[0].z >> space[1].z >> space[2].z >> space[3].z >> space[4].z >> space[5].z >> space[6].z >> space[7].z; in >> dimensions.x >> dimensions.y >> dimensions.z; in >> maxOcTreeDim; @@ -87,16 +79,15 @@ std::string LargeScale::getSpaceCamsTracksDir() return out; } -LargeScale* LargeScale::cloneSpaceIfDoesNotExists(int newOcTreeDim, - const std::string& newSpaceFolderName) +LargeScale* LargeScale::cloneSpaceIfDoesNotExists(int newOcTreeDim, const std::string& newSpaceFolderName) { - if(isSpaceSaved()) + if (isSpaceSaved()) { loadSpaceFromFile(); - + LargeScale* out = new LargeScale(mp, newSpaceFolderName); - if(out->isSpaceSaved()) + if (out->isSpaceSaved()) { out->loadSpaceFromFile(); return out; @@ -108,7 +99,7 @@ LargeScale* LargeScale::cloneSpaceIfDoesNotExists(int newOcTreeDim, out->maxOcTreeDim = (int)((float)maxOcTreeDim / (1024.0f / (float)newOcTreeDim)); - if(mp->verbose) + if (mp->verbose) { ALICEVISION_LOG_DEBUG("maxOcTreeDim new: " << out->maxOcTreeDim); ALICEVISION_LOG_DEBUG("maxOcTreeDim old: " << maxOcTreeDim); @@ -117,7 +108,7 @@ LargeScale* LargeScale::cloneSpaceIfDoesNotExists(int newOcTreeDim, long t1 = clock(); VoxelsGrid* vgactual = new VoxelsGrid(dimensions, &space[0], mp, spaceVoxelsFolderName, doVisualize); - if(maxOcTreeDim == out->maxOcTreeDim) + if (maxOcTreeDim == out->maxOcTreeDim) { VoxelsGrid* vgnew = vgactual->copySpace(out->spaceVoxelsFolderName); vgnew->generateCamsPtsFromVoxelsTracks(); @@ -133,7 +124,7 @@ LargeScale* LargeScale::cloneSpaceIfDoesNotExists(int newOcTreeDim, out->saveSpaceToFile(); - if(mp->verbose) + if (mp->verbose) mvsUtils::printfElapsedTime(t1, "space cloned in:"); return out; @@ -144,7 +135,7 @@ LargeScale* LargeScale::cloneSpaceIfDoesNotExists(int newOcTreeDim, bool LargeScale::generateSpace(int maxPts, int ocTreeDim, bool generateTracks) { - if(isSpaceSaved()) + if (isSpaceSaved()) { loadSpaceFromFile(); return false; @@ -154,16 +145,14 @@ bool LargeScale::generateSpace(int maxPts, int ocTreeDim, bool generateTracks) initialEstimateSpace(maxOcTreeDim); maxOcTreeDim = ocTreeDim; - if(generateTracks) + if (generateTracks) { bool addRandomNoise = mp->userParams.get("LargeScale.addRandomNoise", false); - float addRandomNoisePercNoisePts = - (float)mp->userParams.get("LargeScale.addRandomNoisePercNoisePts", 10.0); - int addRandomNoiseNoisPixSizeDistHalfThr = - (float)mp->userParams.get("LargeScale.addRandomNoiseNoisPixSizeDistHalfThr", 10); + float addRandomNoisePercNoisePts = (float)mp->userParams.get("LargeScale.addRandomNoisePercNoisePts", 10.0); + int addRandomNoiseNoisPixSizeDistHalfThr = (float)mp->userParams.get("LargeScale.addRandomNoiseNoisPixSizeDistHalfThr", 10); - std::string depthMapsPtsSimsTmpDir = generateTempPtsSimsFiles( - spaceFolderName, *mp, addRandomNoise, addRandomNoisePercNoisePts, addRandomNoiseNoisPixSizeDistHalfThr); + std::string depthMapsPtsSimsTmpDir = + generateTempPtsSimsFiles(spaceFolderName, *mp, addRandomNoise, addRandomNoisePercNoisePts, addRandomNoiseNoisPixSizeDistHalfThr); ALICEVISION_LOG_INFO("Creating tracks: " << dimensions.x << ", " << dimensions.y << ", " << dimensions.z); StaticVector* ReconstructionPlan = new StaticVector(); @@ -175,7 +164,7 @@ bool LargeScale::generateSpace(int maxPts, int ocTreeDim, bool generateTracks) int maxlevel = 0; vg->generateTracksForEachVoxel(ReconstructionPlan, maxOcTreeDim, maxPts, 1, maxlevel, depthMapsPtsSimsTmpDir); ALICEVISION_LOG_DEBUG("max rec level: " << maxlevel); - for(int i = 1; i < maxlevel; i++) + for (int i = 1; i < maxlevel; i++) { dimensions = dimensions * 2; maxOcTreeDim = maxOcTreeDim / 2; @@ -186,7 +175,7 @@ bool LargeScale::generateSpace(int maxPts, int ocTreeDim, bool generateTracks) VoxelsGrid* vgnew = new VoxelsGrid(dimensions, &space[0], mp, spaceVoxelsFolderName, doVisualize); vg->generateSpace(vgnew, Voxel(0, 0, 0), dimensions, depthMapsPtsSimsTmpDir); vgnew->generateCamsPtsFromVoxelsTracks(); - if(doVisualize) + if (doVisualize) vgnew->vizualize(); delete vgnew; @@ -216,5 +205,5 @@ Point3d LargeScale::getSpaceSteps() return sv; } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/LargeScale.hpp b/src/aliceVision/fuseCut/LargeScale.hpp index 1c149eedb6..9a493b36a8 100644 --- a/src/aliceVision/fuseCut/LargeScale.hpp +++ b/src/aliceVision/fuseCut/LargeScale.hpp @@ -19,7 +19,7 @@ namespace fuseCut { class LargeScale { -public: + public: mvsUtils::MultiViewParams* mp; std::string spaceFolderName; std::string spaceVoxelsFolderName; @@ -41,15 +41,12 @@ class LargeScale bool generateSpace(int maxPts, int ocTreeDim, bool generateTracks); Point3d getSpaceSteps(); - std::string getReconstructionVoxelFolder(int i) const - { - return spaceFolderName + "reconstructedVoxel" + mvsUtils::num2strFourDecimal(i) + "/"; - } + std::string getReconstructionVoxelFolder(int i) const { return spaceFolderName + "reconstructedVoxel" + mvsUtils::num2strFourDecimal(i) + "/"; } std::vector getRecsDirs(const StaticVector* voxelsArray) const { std::vector recsDirs; recsDirs.reserve(voxelsArray->size() / 8); - for(int i = 0; i < voxelsArray->size() / 8; i++) + for (int i = 0; i < voxelsArray->size() / 8; i++) { recsDirs.push_back(getReconstructionVoxelFolder(i)); } @@ -57,5 +54,5 @@ class LargeScale } }; -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/LargeScale_test.cpp b/src/aliceVision/fuseCut/LargeScale_test.cpp index 040c648bd3..e882d9dafd 100644 --- a/src/aliceVision/fuseCut/LargeScale_test.cpp +++ b/src/aliceVision/fuseCut/LargeScale_test.cpp @@ -22,14 +22,14 @@ BOOST_AUTO_TEST_CASE(fuseCut_largeScale_io) mvsUtils::MultiViewParams mp{sfmData}; LargeScale ls{&mp, "test_tmp"}; ls.space = {{ - { 1.1, 1.2, 1.3 }, - { 2.1, 2.2, 2.3 }, - { 3.1, 3.2, 3.3 }, - { 4.1, 4.2, 4.3 }, - { 5.1, 5.2, 5.3 }, - { 6.1, 6.2, 6.3 }, - { 7.1, 7.2, 7.3 }, - { 8.1, 8.2, 8.3 }, + {1.1, 1.2, 1.3}, + {2.1, 2.2, 2.3}, + {3.1, 3.2, 3.3}, + {4.1, 4.2, 4.3}, + {5.1, 5.2, 5.3}, + {6.1, 6.2, 6.3}, + {7.1, 7.2, 7.3}, + {8.1, 8.2, 8.3}, }}; ls.dimensions = {1, 2, 3}; ls.maxOcTreeDim = 5; diff --git a/src/aliceVision/fuseCut/MaxFlow_AdjList.cpp b/src/aliceVision/fuseCut/MaxFlow_AdjList.cpp index 299054c212..4130a23417 100644 --- a/src/aliceVision/fuseCut/MaxFlow_AdjList.cpp +++ b/src/aliceVision/fuseCut/MaxFlow_AdjList.cpp @@ -19,54 +19,54 @@ void MaxFlow_AdjList::printStats() const std::map histSize; std::map histCapacity; - for(boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi) + for (boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi) { std::size_t s = _graph.m_vertices[*vi].m_out_edges.size(); std::size_t c = _graph.m_vertices[*vi].m_out_edges.capacity(); - if(histSize.find(s) == histSize.end()) + if (histSize.find(s) == histSize.end()) histSize[s] = 1; else ++histSize[s]; - if(histCapacity.find(c) == histCapacity.end()) + if (histCapacity.find(c) == histCapacity.end()) histCapacity[c] = 1; else ++histCapacity[c]; } // std::cout << "edges: size=" << _graph.m_edges.size() << ", capacity=" << _graph.m_edges.capacity() << std::endl; - for(const auto& it: histSize) + for (const auto& it : histSize) { - ALICEVISION_LOG_INFO("\t- size[" << it.first << "]: " << it.second); + ALICEVISION_LOG_INFO("\t- size[" << it.first << "]: " << it.second); } - for(const auto& it: histCapacity) + for (const auto& it : histCapacity) { - ALICEVISION_LOG_INFO("\t- capacity[" << it.first << "]: " << it.second); + ALICEVISION_LOG_INFO("\t- capacity[" << it.first << "]: " << it.second); } } void MaxFlow_AdjList::printColorStats() const { - std::map histColor; + std::map histColor; - for(const auto& color: _color) - { - const int c = (int)color; - if(histColor.find(c) == histColor.end()) - histColor[c] = 1; - else - ++histColor[c]; - } - ALICEVISION_LOG_INFO("Full (white):" << int(boost::white_color)); - ALICEVISION_LOG_INFO("Empty (black):" << int(boost::black_color)); - ALICEVISION_LOG_INFO("Undefined (gray):" << int(boost::gray_color)); + for (const auto& color : _color) + { + const int c = (int)color; + if (histColor.find(c) == histColor.end()) + histColor[c] = 1; + else + ++histColor[c]; + } + ALICEVISION_LOG_INFO("Full (white):" << int(boost::white_color)); + ALICEVISION_LOG_INFO("Empty (black):" << int(boost::black_color)); + ALICEVISION_LOG_INFO("Undefined (gray):" << int(boost::gray_color)); - for(const auto& it: histColor) - { - ALICEVISION_LOG_INFO("\t- color[" << it.first << "]: " << it.second); - } + for (const auto& it : histColor) + { + ALICEVISION_LOG_INFO("\t- color[" << it.first << "]: " << it.second); + } } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/MaxFlow_AdjList.hpp b/src/aliceVision/fuseCut/MaxFlow_AdjList.hpp index e31556ba01..52b7df639a 100644 --- a/src/aliceVision/fuseCut/MaxFlow_AdjList.hpp +++ b/src/aliceVision/fuseCut/MaxFlow_AdjList.hpp @@ -26,54 +26,54 @@ namespace fuseCut { */ class MaxFlow_AdjList { -public: + public: using NodeType = int; using ValueType = float; - using Traits = boost::adjacency_list_traits< - boost::vecS, // OutEdgeListS - boost::vecS, // VertexListS - boost::directedS, - boost::vecS // EdgeListS - >; + using Traits = boost::adjacency_list_traits; using edge_descriptor = typename Traits::edge_descriptor; using vertex_descriptor = typename Traits::vertex_descriptor; using vertex_size_type = typename Traits::vertices_size_type; - struct Edge { + struct Edge + { ValueType capacity{}; ValueType residual{}; edge_descriptor reverse; }; - using Graph = boost::adjacency_list; using VertexIterator = typename boost::graph_traits::vertex_iterator; -public: + public: explicit MaxFlow_AdjList(size_t numNodes) - : _graph(numNodes+2) - , _S(NodeType(numNodes)) - , _T(NodeType(numNodes+1)) + : _graph(numNodes + 2), + _S(NodeType(numNodes)), + _T(NodeType(numNodes + 1)) { VertexIterator vi, vi_end; - for(boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi) + for (boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi) { _graph.m_vertices[*vi].m_out_edges.reserve(9); } _graph.m_vertices[numNodes].m_out_edges.reserve(numNodes); - _graph.m_vertices[numNodes+1].m_out_edges.reserve(numNodes); + _graph.m_vertices[numNodes + 1].m_out_edges.reserve(numNodes); } inline void addNode(NodeType n, ValueType source, ValueType sink) { assert(source >= 0 && sink >= 0); ValueType score = source - sink; - if(score > 0) + if (score > 0) { edge_descriptor edge(boost::add_edge(_S, n, _graph).first); edge_descriptor reverseEdge(boost::add_edge(n, _S, _graph).first); @@ -83,7 +83,7 @@ class MaxFlow_AdjList _graph[reverseEdge].reverse = edge; _graph[reverseEdge].capacity = score; } - else //if(score <= 0) + else // if(score <= 0) { edge_descriptor edge(boost::add_edge(n, _T, _graph).first); edge_descriptor reverseEdge(boost::add_edge(_T, n, _graph).first); @@ -121,15 +121,15 @@ class MaxFlow_AdjList std::vector dist(nbVertices); ValueType v = boost::boykov_kolmogorov_max_flow(_graph, - boost::get(&Edge::capacity, _graph), - boost::get(&Edge::residual, _graph), - boost::get(&Edge::reverse, _graph), - &pred[0], - &_color[0], - &dist[0], - boost::get(boost::vertex_index, _graph), - _S, _T - ); + boost::get(&Edge::capacity, _graph), + boost::get(&Edge::residual, _graph), + boost::get(&Edge::reverse, _graph), + &pred[0], + &_color[0], + &dist[0], + boost::get(boost::vertex_index, _graph), + _S, + _T); printColorStats(); @@ -137,22 +137,16 @@ class MaxFlow_AdjList } /// is empty - inline bool isSource(NodeType n) const - { - return (_color[n] == boost::black_color); - } + inline bool isSource(NodeType n) const { return (_color[n] == boost::black_color); } /// is full - inline bool isTarget(NodeType n) const - { - return (_color[n] == boost::white_color); - } + inline bool isTarget(NodeType n) const { return (_color[n] == boost::white_color); } -protected: + protected: Graph _graph; std::vector _color; const NodeType _S; //< emptyness const NodeType _T; //< fullness }; -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/MaxFlow_CSR.cpp b/src/aliceVision/fuseCut/MaxFlow_CSR.cpp index 32f25fce08..7408fa17bd 100644 --- a/src/aliceVision/fuseCut/MaxFlow_CSR.cpp +++ b/src/aliceVision/fuseCut/MaxFlow_CSR.cpp @@ -7,8 +7,5 @@ #include "MaxFlow_CSR.hpp" namespace aliceVision { -namespace fuseCut { - -} // namespace fuseCut -} // namespace aliceVision - +namespace fuseCut {} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/MaxFlow_CSR.hpp b/src/aliceVision/fuseCut/MaxFlow_CSR.hpp index 40d53ec13c..a06dd72405 100644 --- a/src/aliceVision/fuseCut/MaxFlow_CSR.hpp +++ b/src/aliceVision/fuseCut/MaxFlow_CSR.hpp @@ -31,55 +31,55 @@ namespace fuseCut { */ class MaxFlow_CSR { -public: + public: using NodeType = unsigned int; using ValueType = float; - using edge_descriptor = typename boost::compressed_sparse_row_graph< - boost::directedS, - boost::no_property, // VertexProperty - boost::no_property, // EdgeProperty - unsigned int, // Vertex - unsigned int // EdgeIndex - >::edge_descriptor; + using edge_descriptor = typename boost::compressed_sparse_row_graph::edge_descriptor; - struct Vertex { + struct Vertex + { boost::default_color_type color{}; ValueType distance{}; edge_descriptor predecessor{}; }; - struct Edge { + struct Edge + { Edge(ValueType pCapacity, ValueType pResidual) - : capacity(pCapacity) - , residual(pResidual) + : capacity(pCapacity), + residual(pResidual) {} Edge(ValueType pCapacity, ValueType pResidual, edge_descriptor pReverse) - : capacity(pCapacity) - , residual(pResidual) - , reverse(pReverse) + : capacity(pCapacity), + residual(pResidual), + reverse(pReverse) {} - Edge(){} + Edge() {} ValueType capacity{}; ValueType residual{}; edge_descriptor reverse{}; }; - using Graph = boost::compressed_sparse_row_graph< - boost::directedS, - Vertex, // VertexProperty - Edge, // EdgeProperty - unsigned int, // Vertex - unsigned int // EdgeIndex - >; + using Graph = boost::compressed_sparse_row_graph; using vertex_descriptor = typename Graph::vertex_descriptor; using vertex_size_type = typename Graph::vertices_size_type; using edges_size_type = typename Graph::edges_size_type; -public: + public: explicit MaxFlow_CSR(size_t numNodes) - : _numNodes(numNodes+2) - , _S(NodeType(numNodes)) - , _T(NodeType(numNodes+1)) + : _numNodes(numNodes + 2), + _S(NodeType(numNodes)), + _T(NodeType(numNodes + 1)) { ALICEVISION_LOG_INFO("MaxFlow constructor."); const std::size_t nbEdgesEstimation = numNodes * 9 + numNodes * 2; @@ -91,11 +91,11 @@ class MaxFlow_CSR { assert(source >= 0 && sink >= 0); ValueType score = source - sink; - if(score > 0) + if (score > 0) { this->addEdge(_S, n, score, score); } - else //if(score <= 0) + else // if(score <= 0) { this->addEdge(n, _T, -score, -score); } @@ -104,16 +104,16 @@ class MaxFlow_CSR inline void addEdge(NodeType n1, NodeType n2, ValueType capacity, ValueType reverseCapacity) { assert(capacity >= 0 && reverseCapacity >= 0); - - //int edgeIndex = _edges.size(); - _edges.push_back(std::make_pair(n1, n2)); // edge - //int reverseEdgeIndex = _edges.size(); - _edges.push_back(std::make_pair(n2, n1)); // reverse edge + // int edgeIndex = _edges.size(); + _edges.push_back(std::make_pair(n1, n2)); // edge + + // int reverseEdgeIndex = _edges.size(); + _edges.push_back(std::make_pair(n2, n1)); // reverse edge ValueType defaultResidual = 0.0; - _edgesData.push_back(Edge(capacity, defaultResidual)); //, reverseEdgeIndex)); - _edgesData.push_back(Edge(reverseCapacity, defaultResidual)); //, edgeIndex)); + _edgesData.push_back(Edge(capacity, defaultResidual)); //, reverseEdgeIndex)); + _edgesData.push_back(Edge(reverseCapacity, defaultResidual)); //, edgeIndex)); } inline ValueType compute() @@ -147,13 +147,13 @@ class MaxFlow_CSR // This step represent the memory peak, would be great to find a way to reduce it. boost::container::flat_map, Graph::edge_descriptor> edgeDescriptors; Graph::edge_iterator ei, ee; - for(boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) + for (boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) { Graph::vertex_descriptor v1 = boost::source(*ei, graph); Graph::vertex_descriptor v2 = boost::target(*ei, graph); edgeDescriptors[std::make_pair(v1, v2)] = *ei; } - for(boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) + for (boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) { Graph::vertex_descriptor v1 = boost::source(*ei, graph); Graph::vertex_descriptor v2 = boost::target(*ei, graph); @@ -161,39 +161,34 @@ class MaxFlow_CSR } } ALICEVISION_LOG_INFO("boykov_kolmogorov_max_flow: start."); - ValueType v = boost::boykov_kolmogorov_max_flow(graph, - boost::get(&Edge::capacity, graph), // edge_capacity: The edge capacity property map. - boost::get(&Edge::residual, graph), // edge_residual_capacity: The edge residual capacity property map. - boost::get(&Edge::reverse, graph), // edge_reverse: An edge property map that maps every edge (u,v) in the graph to the reverse edge (v,u). - boost::get(&Vertex::predecessor, graph), // vertex_predecessor: A vertex property map that stores the edge to the vertex' predecessor. - boost::get(&Vertex::color, graph), // vertex_color - boost::get(&Vertex::distance, graph), // vertex_distance - boost::get(boost::vertex_index, graph), // this is not bundled, get it from graph - _S, _T - ); + ValueType v = boost::boykov_kolmogorov_max_flow( + graph, + boost::get(&Edge::capacity, graph), // edge_capacity: The edge capacity property map. + boost::get(&Edge::residual, graph), // edge_residual_capacity: The edge residual capacity property map. + boost::get(&Edge::reverse, graph), // edge_reverse: An edge property map that maps every edge (u,v) in the graph to the reverse edge (v,u). + boost::get(&Vertex::predecessor, graph), // vertex_predecessor: A vertex property map that stores the edge to the vertex' predecessor. + boost::get(&Vertex::color, graph), // vertex_color + boost::get(&Vertex::distance, graph), // vertex_distance + boost::get(boost::vertex_index, graph), // this is not bundled, get it from graph + _S, + _T); ALICEVISION_LOG_INFO("boykov_kolmogorov_max_flow: done."); _isTarget.resize(nbVertices); - for(std::size_t vi = 0; vi < nbVertices; ++vi) + for (std::size_t vi = 0; vi < nbVertices; ++vi) { - boost::default_color_type color = graph[vi].color; + boost::default_color_type color = graph[vi].color; _isTarget[vi] = (color == boost::white_color); } return v; } /// is empty - inline bool isSource(NodeType n) const - { - return !_isTarget[n]; - } + inline bool isSource(NodeType n) const { return !_isTarget[n]; } /// is full - inline bool isTarget(NodeType n) const - { - return _isTarget[n]; - } + inline bool isTarget(NodeType n) const { return _isTarget[n]; } -protected: + protected: std::size_t _numNodes; std::vector> _edges; std::vector _edgesData; @@ -202,5 +197,5 @@ class MaxFlow_CSR const NodeType _T; //< fullness }; -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/OctreeTracks.cpp b/src/aliceVision/fuseCut/OctreeTracks.cpp index 9e23437876..3c178ed726 100644 --- a/src/aliceVision/fuseCut/OctreeTracks.cpp +++ b/src/aliceVision/fuseCut/OctreeTracks.cpp @@ -18,14 +18,11 @@ namespace aliceVision { namespace fuseCut { -OctreeTracks::Node::Node(NodeType type) -{ - type_ = type; -} +OctreeTracks::Node::Node(NodeType type) { type_ = type; } OctreeTracks::trackStruct* OctreeTracks::getTrack(int x, int y, int z) { - if(!((x >= 0 && x < size_) && (y >= 0 && y < size_) && (z >= 0 && z < size_))) + if (!((x >= 0 && x < size_) && (y >= 0 && y < size_) && (z >= 0 && z < size_))) { return nullptr; } @@ -33,9 +30,9 @@ OctreeTracks::trackStruct* OctreeTracks::getTrack(int x, int y, int z) Node** n = &root_; int size = size_; - while(size != 1) + while (size != 1) { - if(*n == nullptr) + if (*n == nullptr) { return nullptr; } @@ -44,7 +41,7 @@ OctreeTracks::trackStruct* OctreeTracks::getTrack(int x, int y, int z) n = &reinterpret_cast(*n)->children[!((x & size) == 0)][!((y & size) == 0)][!((z & size) == 0)]; } - if(*n == nullptr) + if (*n == nullptr) { return nullptr; } @@ -61,9 +58,9 @@ void OctreeTracks::addPoint(int x, int y, int z, float sim, float pixSize, Point Node** n = &root_; int size = size_; - while(size != 1) + while (size != 1) { - if(*n == nullptr) + if (*n == nullptr) { *n = new Branch(); } @@ -74,7 +71,7 @@ void OctreeTracks::addPoint(int x, int y, int z, float sim, float pixSize, Point } } - if(*n == nullptr) + if (*n == nullptr) { *n = new trackStruct(sim, pixSize, p, rc); leafsNumber_++; @@ -94,9 +91,9 @@ void OctreeTracks::addTrack(int x, int y, int z, OctreeTracks::trackStruct* t) Node** n = &root_; int size = size_; - while(size != 1) + while (size != 1) { - if(*n == nullptr) + if (*n == nullptr) { *n = new Branch(); } @@ -107,7 +104,7 @@ void OctreeTracks::addTrack(int x, int y, int z, OctreeTracks::trackStruct* t) } } - if(*n == nullptr) + if (*n == nullptr) { *n = new trackStruct(t); leafsNumber_++; @@ -122,7 +119,7 @@ StaticVector* OctreeTracks::getAllPoints() { StaticVector* out = new StaticVector(); out->reserve(leafsNumber_); - if(root_ != nullptr) + if (root_ != nullptr) { getAllPointsRecursive(out, root_); } @@ -133,18 +130,18 @@ void OctreeTracks::getAllPointsRecursive(StaticVector* out, Node* { assert(node); - switch(node->type_) + switch (node->type_) { case BranchNode: { Branch* b = reinterpret_cast(node); - for(int i = 0; i < 2; ++i) + for (int i = 0; i < 2; ++i) { - for(int j = 0; j < 2; ++j) + for (int j = 0; j < 2; ++j) { - for(int k = 0; k < 2; ++k) + for (int k = 0; k < 2; ++k) { - if(b->children[i][j][k] != nullptr) + if (b->children[i][j][k] != nullptr) { getAllPointsRecursive(out, b->children[i][j][k]); } @@ -163,18 +160,18 @@ void OctreeTracks::getNPointsByLevelsRecursive(Node* node, int level, StaticVect { assert(node); - switch(node->type_) + switch (node->type_) { case BranchNode: { Branch* b = reinterpret_cast(node); - for(int i = 0; i < 2; ++i) + for (int i = 0; i < 2; ++i) { - for(int j = 0; j < 2; ++j) + for (int j = 0; j < 2; ++j) { - for(int k = 0; k < 2; ++k) + for (int k = 0; k < 2; ++k) { - if(b->children[i][j][k] != nullptr) + if (b->children[i][j][k] != nullptr) { (*nptsAtLevel)[level + 1] += 1; getNPointsByLevelsRecursive(b->children[i][j][k], level + 1, nptsAtLevel); @@ -191,13 +188,13 @@ void OctreeTracks::getNPointsByLevelsRecursive(Node* node, int level, StaticVect } OctreeTracks::Branch::Branch() - : Node(BranchNode) + : Node(BranchNode) { - for(int i = 0; i < 2; ++i) + for (int i = 0; i < 2; ++i) { - for(int j = 0; j < 2; ++j) + for (int j = 0; j < 2; ++j) { - for(int k = 0; k < 2; ++k) + for (int k = 0; k < 2; ++k) { children[i][j][k] = nullptr; } @@ -207,11 +204,11 @@ OctreeTracks::Branch::Branch() OctreeTracks::Branch::~Branch() { - for(int i = 0; i < 2; ++i) + for (int i = 0; i < 2; ++i) { - for(int j = 0; j < 2; ++j) + for (int j = 0; j < 2; ++j) { - for(int k = 0; k < 2; ++k) + for (int k = 0; k < 2; ++k) { assert(children[i][j][k] != this); /* @@ -230,15 +227,15 @@ OctreeTracks::Branch::~Branch() children[i][j][k] = NULL; } */ - if(children[i][j][k] != nullptr) + if (children[i][j][k] != nullptr) { - if(children[i][j][k]->type_ == BranchNode) + if (children[i][j][k]->type_ == BranchNode) { delete reinterpret_cast(children[i][j][k]); } else { - if(children[i][j][k]->type_ == LeafNode) + if (children[i][j][k]->type_ == LeafNode) { delete reinterpret_cast(children[i][j][k]); } @@ -255,7 +252,7 @@ OctreeTracks::Branch::~Branch() } OctreeTracks::trackStruct::trackStruct(float sim, float pixSize, const Point3d& p, int rc) - : Node(LeafNode) + : Node(LeafNode) { npts = 1; point = p; @@ -266,7 +263,7 @@ OctreeTracks::trackStruct::trackStruct(float sim, float pixSize, const Point3d& } OctreeTracks::trackStruct::trackStruct(trackStruct* t) - : Node(LeafNode) + : Node(LeafNode) { npts = t->npts; point = t->point; @@ -275,17 +272,15 @@ OctreeTracks::trackStruct::trackStruct(trackStruct* t) minSim = t->minSim; } -OctreeTracks::trackStruct::~trackStruct() -{ -} +OctreeTracks::trackStruct::~trackStruct() {} void OctreeTracks::trackStruct::addPoint(float sim, float pixSize, const Point3d& p, int rc) { int index = indexOf(rc); - if(index == -1) + if (index == -1) { cams.push_back(Pixel(rc, 1)); - if(cams.size() > 1) + if (cams.size() > 1) { qsort(&cams[0], cams.size(), sizeof(Pixel), qSortComparePixelByXAsc); } @@ -313,14 +308,14 @@ void OctreeTracks::trackStruct::addPoint(float sim, float pixSize, const Point3d //}; // strategy 3: average values with good precision (precision is given by pixel size) - if(pixSize < minPixSize * 0.8f) // if strongly better => replace previous values + if (pixSize < minPixSize * 0.8f) // if strongly better => replace previous values { point = p; minPixSize = pixSize; minSim = sim; npts = 1; } - else if(pixSize < minPixSize * 1.2f) // if close to the previous value => average + else if (pixSize < minPixSize * 1.2f) // if close to the previous value => average { // average with previous values of the same precision point = (point * (float)npts + p) / (float)(npts + 1); @@ -333,16 +328,16 @@ void OctreeTracks::trackStruct::addPoint(float sim, float pixSize, const Point3d void OctreeTracks::trackStruct::addDistinctNonzeroCamsFromTrackAsZeroCams(trackStruct* t) { - for(int i = 0; i < t->cams.size(); i++) + for (int i = 0; i < t->cams.size(); i++) { - if(t->cams[i].y > 0) + if (t->cams[i].y > 0) { int rc = t->cams[i].x; int index = indexOf(rc); - if(index == -1) + if (index == -1) { cams.push_back(Pixel(rc, 0)); - if(cams.size() > 1) + if (cams.size() > 1) { qsort(&cams[0], cams.size(), sizeof(Pixel), qSortComparePixelByXAsc); } @@ -353,14 +348,14 @@ void OctreeTracks::trackStruct::addDistinctNonzeroCamsFromTrackAsZeroCams(trackS void OctreeTracks::trackStruct::addTrack(OctreeTracks::trackStruct* t) { - for(int i = 0; i < t->cams.size(); i++) + for (int i = 0; i < t->cams.size(); i++) { int rc = t->cams[i].x; int index = indexOf(rc); - if(index == -1) + if (index == -1) { cams.push_back(t->cams[i]); - if(cams.size() > 1) + if (cams.size() > 1) { qsort(&cams[0], cams.size(), sizeof(Pixel), qSortComparePixelByXAsc); } @@ -388,14 +383,14 @@ void OctreeTracks::trackStruct::addTrack(OctreeTracks::trackStruct* t) //}; // strategy 3: average values with good precision (precision is given by pixel size) - if(t->minPixSize < minPixSize * 0.8f) // if strongly better => replace previous values + if (t->minPixSize < minPixSize * 0.8f) // if strongly better => replace previous values { point = t->point; minPixSize = t->minPixSize; minSim = t->minSim; npts = t->npts; } - else if(t->minPixSize < minPixSize * 1.2f) // if close to the previous value => average + else if (t->minPixSize < minPixSize * 1.2f) // if close to the previous value => average { // average with previous values of the same precision point = (point * (float)npts + t->point * (float)t->npts) / (float)(npts + t->npts); @@ -408,7 +403,7 @@ void OctreeTracks::trackStruct::addTrack(OctreeTracks::trackStruct* t) int OctreeTracks::trackStruct::indexOf(int val) { - if(cams.empty()) + if (cams.empty()) { return -1; } @@ -416,21 +411,21 @@ int OctreeTracks::trackStruct::indexOf(int val) int lef = 0; int rig = cams.size() - 1; int mid = lef + (rig - lef) / 2; - while((rig - lef) > 1) + while ((rig - lef) > 1) { - if((val >= cams[lef].x) && (val < cams[mid].x)) + if ((val >= cams[lef].x) && (val < cams[mid].x)) { - //lef = lef; + // lef = lef; rig = mid; mid = lef + (rig - lef) / 2; } - if((val >= cams[mid].x) && (val <= cams[rig].x)) + if ((val >= cams[mid].x) && (val <= cams[rig].x)) { lef = mid; - //rig = rig; + // rig = rig; mid = lef + (rig - lef) / 2; } - if((val < cams[lef].x) || (val > cams[rig].x)) + if ((val < cams[lef].x) || (val > cams[rig].x)) { lef = 0; rig = 0; @@ -439,11 +434,11 @@ int OctreeTracks::trackStruct::indexOf(int val) } int id = -1; - if(val == cams[lef].x) + if (val == cams[lef].x) { id = lef; } - if(val == cams[rig].x) + if (val == cams[rig].x) { id = rig; } @@ -457,17 +452,17 @@ void OctreeTracks::trackStruct::doPrintf() { ALICEVISION_LOG_INFO("point: " << point.x << " " << point.y << " " << point.z); ALICEVISION_LOG_INFO("ncams: " << cams.size()); - for(int i = 0; i < cams.size(); i++) + for (int i = 0; i < cams.size(); i++) ALICEVISION_LOG_INFO("\t- cam: " << i << ", rc: " << cams[i].x << ", val: " << cams[i].y); } OctreeTracks::OctreeTracks(const Point3d* voxel_, mvsUtils::MultiViewParams* mp_, Voxel dimensions) - : Fuser(*mp_) + : Fuser(*mp_) { numSubVoxsX = dimensions.x; numSubVoxsY = dimensions.y; numSubVoxsZ = dimensions.z; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { vox[i] = voxel_[i]; } @@ -495,7 +490,7 @@ OctreeTracks::OctreeTracks(const Point3d* voxel_, mvsUtils::MultiViewParams* mp_ int maxNumSubVoxs = std::max({numSubVoxsX, numSubVoxsY, numSubVoxsZ}); size_ = 2; - while(size_ < maxNumSubVoxs) + while (size_ < maxNumSubVoxs) { size_ *= 2; } @@ -507,7 +502,7 @@ OctreeTracks::OctreeTracks(const Point3d* voxel_, mvsUtils::MultiViewParams* mp_ OctreeTracks::~OctreeTracks() { // printf("deleting octree\n"); - if(root_ != nullptr) + if (root_ != nullptr) { delete reinterpret_cast(root_); } @@ -519,8 +514,7 @@ bool OctreeTracks::getVoxelOfOctreeFor3DPoint(Voxel& out, Point3d& tp) out.x = (int)floor(orientedPointPlaneDistance(tp, O, vx) / sx); out.y = (int)floor(orientedPointPlaneDistance(tp, O, vy) / sy); out.z = (int)floor(orientedPointPlaneDistance(tp, O, vz) / sz); - return ((out.x >= 0) && (out.x < numSubVoxsX) && (out.y >= 0) && (out.y < numSubVoxsY) && (out.z >= 0) && - (out.z < numSubVoxsZ)); + return ((out.x >= 0) && (out.x < numSubVoxsX) && (out.y >= 0) && (out.y < numSubVoxsY) && (out.z >= 0) && (out.z < numSubVoxsZ)); } void OctreeTracks::filterMinNumConsistentCams(StaticVector* tracks) @@ -529,38 +523,41 @@ void OctreeTracks::filterMinNumConsistentCams(StaticVector* tracks StaticVector tracksOut; tracksOut.reserve(tracks->size()); - typedef accumulator_set > Accumulator; + typedef accumulator_set> Accumulator; Accumulator accMinPixSize; Accumulator accMinSim; Accumulator accNbCamsA; Accumulator accNbCamsB; // long t1 = initEstimate(); - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { trackStruct* t = (*tracks)[i]; accNbCamsA(t->cams.size()); - if(t->cams.size() >= minNumOfConsistentCams) + if (t->cams.size() >= minNumOfConsistentCams) { tracksOut.push_back((*tracks)[i]); accMinPixSize(t->minPixSize); accMinSim(t->minSim); accNbCamsB(t->cams.size()); - } // ELSE DO NOT DELETE BECAUSE IT IS POINTER TO THE STRUCTURE + } // ELSE DO NOT DELETE BECAUSE IT IS POINTER TO THE STRUCTURE // printfEstimate(i, tracks->size(), t1); } // finishEstimate(); - ALICEVISION_LOG_INFO("filterMinNumConsistentCams: " << std::endl - << "\t- minPixelSize min: " << boost::accumulators::min(accMinPixSize) << ", max: " << boost::accumulators::max(accMinPixSize) << ", mean: " << boost::accumulators::mean(accMinPixSize) << ", median: " << boost::accumulators::median(accMinPixSize) << std::endl - << "\t- minSim min: " << boost::accumulators::min(accMinSim) << ", max: " << boost::accumulators::max(accMinSim) << ", mean: " << boost::accumulators::mean(accMinSim) << ", median: " << boost::accumulators::median(accMinSim) << std::endl - << "\t- accNbCamsA min: " << boost::accumulators::min(accNbCamsA) << ", max: " << boost::accumulators::max(accNbCamsA) << ", mean: " << boost::accumulators::mean(accNbCamsA) << ", median: " << boost::accumulators::median(accNbCamsA) << std::endl - << "\t- accNbCamsB min: " << boost::accumulators::min(accNbCamsB) << ", max: " << boost::accumulators::max(accNbCamsB) << ", mean: " << boost::accumulators::mean(accNbCamsB) << ", median: " << boost::accumulators::median(accNbCamsB) << std::endl); + ALICEVISION_LOG_INFO("filterMinNumConsistentCams: " + << std::endl + << "\t- minPixelSize min: " << boost::accumulators::min(accMinPixSize) + << ", max: " << boost::accumulators::max(accMinPixSize) << ", mean: " << boost::accumulators::mean(accMinPixSize) + << ", median: " << boost::accumulators::median(accMinPixSize) << std::endl + << "\t- minSim min: " << boost::accumulators::min(accMinSim) << ", max: " << boost::accumulators::max(accMinSim) + << ", mean: " << boost::accumulators::mean(accMinSim) << ", median: " << boost::accumulators::median(accMinSim) << std::endl + << "\t- accNbCamsA min: " << boost::accumulators::min(accNbCamsA) << ", max: " << boost::accumulators::max(accNbCamsA) + << ", mean: " << boost::accumulators::mean(accNbCamsA) << ", median: " << boost::accumulators::median(accNbCamsA) + << std::endl + << "\t- accNbCamsB min: " << boost::accumulators::min(accNbCamsB) << ", max: " << boost::accumulators::max(accNbCamsB) + << ", mean: " << boost::accumulators::mean(accNbCamsB) << ", median: " << boost::accumulators::median(accNbCamsB) + << std::endl); tracks->swap(tracksOut); } @@ -574,31 +571,31 @@ void OctreeTracks::updateOctreeTracksCams(StaticVector* tracks) float clusterSizeThr = 1.2f; // long t1 = initEstimate(); - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { int n = (int)ceil(((*tracks)[i]->minPixSize * clusterSizeThr) / sx); Voxel vox; - if((n > 1) && (getVoxelOfOctreeFor3DPoint(vox, (*tracks)[i]->point))) + if ((n > 1) && (getVoxelOfOctreeFor3DPoint(vox, (*tracks)[i]->point))) { // printf("n %i\n",n); - for(int xp = -n; xp <= n; xp++) + for (int xp = -n; xp <= n; xp++) { - for(int yp = -n; yp <= n; yp++) + for (int yp = -n; yp <= n; yp++) { - for(int zp = -n; zp <= n; zp++) + for (int zp = -n; zp <= n; zp++) { - if((xp == 0) && (yp == 0) && (zp == 0)) + if ((xp == 0) && (yp == 0) && (zp == 0)) { assert(getTrack(vox.x + xp, vox.y + yp, vox.z + zp) == (*tracks)[i]); continue; } trackStruct* neighborTrack = getTrack(vox.x + xp, vox.y + yp, vox.z + zp); - if(neighborTrack != nullptr) + if (neighborTrack != nullptr) { trackStruct& currentTrack = *(*tracks)[i]; const float dist = (currentTrack.point - neighborTrack->point).size(); - if((dist < currentTrack.minPixSize * 1.2f) || (dist < neighborTrack->minPixSize * 1.2f)) - // (nt->minPixSize < (*tracks)[i]->minPixSize * 1.2f) && ((*tracks)[i]->minPixSize < nt->minPixSize * 1.2f) + if ((dist < currentTrack.minPixSize * 1.2f) || (dist < neighborTrack->minPixSize * 1.2f)) + // (nt->minPixSize < (*tracks)[i]->minPixSize * 1.2f) && ((*tracks)[i]->minPixSize < nt->minPixSize * 1.2f) { // currentTrack.cams @@ -625,29 +622,29 @@ void OctreeTracks::filterOctreeTracks2(StaticVector* tracks) float clusterSizeThr = _mp.userParams.get("OctreeTracks.clusterSizeThr", 2.0f); // long t1 = initEstimate(); - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { int n = (int)ceil(((*tracks)[i]->minPixSize * clusterSizeThr) / sx); bool ok = true; Voxel vox; - if((n > 1) && (getVoxelOfOctreeFor3DPoint(vox, (*tracks)[i]->point))) + if ((n > 1) && (getVoxelOfOctreeFor3DPoint(vox, (*tracks)[i]->point))) { // printf("n %i\n",n); - for(int xp = -n; xp <= n; xp++) + for (int xp = -n; xp <= n; xp++) { - for(int yp = -n; yp <= n; yp++) + for (int yp = -n; yp <= n; yp++) { - for(int zp = -n; zp <= n; zp++) + for (int zp = -n; zp <= n; zp++) { trackStruct* nt = getTrack(vox.x + xp, vox.y + yp, vox.z + zp); - if((xp == 0) && (yp == 0) && (zp == 0)) + if ((xp == 0) && (yp == 0) && (zp == 0)) { assert(nt == (*tracks)[i]); } else { - if((nt != nullptr) && ((*tracks)[i]->minPixSize > nt->minPixSize * 1.2f)) + if ((nt != nullptr) && ((*tracks)[i]->minPixSize > nt->minPixSize * 1.2f)) { ok = false; } @@ -656,10 +653,10 @@ void OctreeTracks::filterOctreeTracks2(StaticVector* tracks) } } } - if(ok) + if (ok) { tracksOut.push_back((*tracks)[i]); - } // ELSE DO NOT DELETE BECAUSE IT IS POINTER TO THE STRUCTURE + } // ELSE DO NOT DELETE BECAUSE IT IS POINTER TO THE STRUCTURE // printfEstimate(i, tracks->size(), t1); } // finishEstimate(); @@ -667,8 +664,7 @@ void OctreeTracks::filterOctreeTracks2(StaticVector* tracks) tracks->swap(tracksOut); } -StaticVector* - OctreeTracks::fillOctree(int maxPts, const std::string& depthMapsPtsSimsTmpDir) +StaticVector* OctreeTracks::fillOctree(int maxPts, const std::string& depthMapsPtsSimsTmpDir) { long t1 = clock(); StaticVector cams = _mp.findCamsWhichIntersectsHexahedron(vox, depthMapsPtsSimsTmpDir + "minMaxDepths.bin"); @@ -677,28 +673,26 @@ StaticVector* t1 = clock(); - // long t1=initEstimate(); - for(int camid = 0; camid < cams.size(); camid++) + for (int camid = 0; camid < cams.size(); camid++) { int rc = cams[camid]; - StaticVector* pts = - loadArrayFromFile(depthMapsPtsSimsTmpDir + std::to_string(_mp.getViewId(rc)) + "pts.bin"); - StaticVector* sims = - loadArrayFromFile(depthMapsPtsSimsTmpDir + std::to_string(_mp.getViewId(rc)) + "sims.bin"); + StaticVector* pts = loadArrayFromFile(depthMapsPtsSimsTmpDir + std::to_string(_mp.getViewId(rc)) + "pts.bin"); + StaticVector* sims = loadArrayFromFile(depthMapsPtsSimsTmpDir + std::to_string(_mp.getViewId(rc)) + "sims.bin"); // long tpts=initEstimate(); - for(int i = 0; i < pts->size(); i++) + for (int i = 0; i < pts->size(); i++) { float sim = (*sims)[i]; Point3d p = (*pts)[i]; Voxel otVox; - if(((doUseWeaklySupportedPoints) || (sim < simWspThr)) && (getVoxelOfOctreeFor3DPoint(otVox, p))) // doUseWeaklySupportedPoints: false by default + if (((doUseWeaklySupportedPoints) || (sim < simWspThr)) && + (getVoxelOfOctreeFor3DPoint(otVox, p))) // doUseWeaklySupportedPoints: false by default { - if(doUseWeaklySupportedPointCam) + if (doUseWeaklySupportedPointCam) { - if(sim > 1.0f) + if (sim > 1.0f) { sim -= 2.0f; } @@ -706,14 +700,14 @@ StaticVector* float pixSize = _mp.getCamPixelSize(p, rc); addPoint(otVox.x, otVox.y, otVox.z, sim, pixSize, p, rc); } - if(leafsNumber_ > 2 * maxPts) + if (leafsNumber_ > 2 * maxPts) { return nullptr; } // printfEstimate(i, pts->size(), tpts); - } // for i - // finishEstimate(); + } // for i + // finishEstimate(); delete pts; delete sims; @@ -729,7 +723,7 @@ StaticVector* // updateOctreeTracksCams(tracks); // if (_mp.verbose) printfElapsedTime(t1,"updateOctreeTracksCams"); - if(doFilterOctreeTracks) + if (doFilterOctreeTracks) { ALICEVISION_LOG_DEBUG("# tracks before filtering: " << tracks->size()); long t2 = clock(); @@ -749,10 +743,10 @@ StaticVector* ALICEVISION_LOG_DEBUG("# tracks after filtering: " << tracks->size()); } - if(tracks->size() > maxPts) + if (tracks->size() > maxPts) { ALICEVISION_LOG_DEBUG("Too much tracks (" << tracks->size() << "), clear all."); - delete tracks; // DO NOT DELETE POINTER JUST DELETE THE ARRAY!!! + delete tracks; // DO NOT DELETE POINTER JUST DELETE THE ARRAY!!! return nullptr; } @@ -761,20 +755,19 @@ StaticVector* return tracks; } -StaticVector* -OctreeTracks::fillOctreeFromTracks(StaticVector* tracksIn) +StaticVector* OctreeTracks::fillOctreeFromTracks(StaticVector* tracksIn) { long t1 = clock(); - for(int i = 0; i < tracksIn->size(); i++) + for (int i = 0; i < tracksIn->size(); i++) { trackStruct* t = (*tracksIn)[i]; Voxel otVox; - if(getVoxelOfOctreeFor3DPoint(otVox, t->point)) + if (getVoxelOfOctreeFor3DPoint(otVox, t->point)) { addTrack(otVox.x, otVox.y, otVox.z, t); } - } // for i + } // for i StaticVector* tracks = getAllPoints(); @@ -789,9 +782,9 @@ StaticVector* OctreeTracks::getTracksCams(StaticVectorreserve(_mp.ncams); camsb->resize_with(_mp.ncams, false); - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { - for(int c = 0; c < (*tracks)[i]->cams.size(); c++) + for (int c = 0; c < (*tracks)[i]->cams.size(); c++) { (*camsb)[(*tracks)[i]->cams[c].x] = true; } @@ -799,9 +792,9 @@ StaticVector* OctreeTracks::getTracksCams(StaticVector* cams = new StaticVector(); cams->reserve(_mp.ncams); - for(int i = 0; i < _mp.ncams; i++) + for (int i = 0; i < _mp.ncams; i++) { - if((*camsb)[i]) + if ((*camsb)[i]) { cams->push_back(i); } @@ -811,5 +804,5 @@ StaticVector* OctreeTracks::getTracksCams(StaticVector cams; @@ -103,5 +103,5 @@ class OctreeTracks : public Fuser void getNPointsByLevelsRecursive(Node* node, int level, StaticVector* nptsAtLevel); }; -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/ReconstructionPlan.cpp b/src/aliceVision/fuseCut/ReconstructionPlan.cpp index 52907f9c1a..01eef09fb8 100644 --- a/src/aliceVision/fuseCut/ReconstructionPlan.cpp +++ b/src/aliceVision/fuseCut/ReconstructionPlan.cpp @@ -21,24 +21,21 @@ namespace fuseCut { namespace bfs = boost::filesystem; ReconstructionPlan::ReconstructionPlan(Voxel& dimmensions, Point3d* space, mvsUtils::MultiViewParams* _mp, const std::string& _spaceRootDir) - : VoxelsGrid(dimmensions, space, _mp, _spaceRootDir) + : VoxelsGrid(dimmensions, space, _mp, _spaceRootDir) { nVoxelsTracks = getNVoxelsTracks(); } -ReconstructionPlan::~ReconstructionPlan() -{ - delete nVoxelsTracks; -} +ReconstructionPlan::~ReconstructionPlan() { delete nVoxelsTracks; } StaticVector* ReconstructionPlan::voxelsIdsIntersectingHexah(Point3d* hexah) { StaticVector* ids = new StaticVector(); ids->reserve(voxels->size() / 8); - for(int i = 0; i < voxels->size() / 8; i++) + for (int i = 0; i < voxels->size() / 8; i++) { - if(mvsUtils::intersectsHexahedronHexahedron(&(*voxels)[i * 8], hexah)) + if (mvsUtils::intersectsHexahedronHexahedron(&(*voxels)[i * 8], hexah)) { ids->push_back(i); } @@ -51,11 +48,11 @@ unsigned long ReconstructionPlan::getNTracks(const Voxel& LU, const Voxel& RD) { unsigned long n = 0; Voxel v; - for(v.x = LU.x; v.x <= RD.x; v.x++) + for (v.x = LU.x; v.x <= RD.x; v.x++) { - for(v.y = LU.y; v.y <= RD.y; v.y++) + for (v.y = LU.y; v.y <= RD.y; v.y++) { - for(v.z = LU.z; v.z <= RD.z; v.z++) + for (v.z = LU.z; v.z <= RD.z; v.z++) { n += (*nVoxelsTracks)[getIdForVoxel(v)]; } @@ -64,11 +61,10 @@ unsigned long ReconstructionPlan::getNTracks(const Voxel& LU, const Voxel& RD) return n; } -bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& RD2o, const Voxel& LUi, - const Voxel& RDi, unsigned long maxTracks) +bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& RD2o, const Voxel& LUi, const Voxel& RDi, unsigned long maxTracks) { unsigned long n = getNTracks(LUi, RDi); - if(n < maxTracks) + if (n < maxTracks) { return false; } @@ -83,7 +79,7 @@ bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& nz1 = 0; nz2 = 0; - if(RDi.x - LUi.x > 0) + if (RDi.x - LUi.x > 0) { LUt = LUi; RDt = RDi; @@ -95,7 +91,7 @@ bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& nx2 = getNTracks(LUt, RDt); } - if(RDi.y - LUi.y > 0) + if (RDi.y - LUi.y > 0) { LUt = LUi; RDt = RDi; @@ -107,7 +103,7 @@ bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& ny2 = getNTracks(LUt, RDt); } - if(RDi.z - LUi.z > 0) + if (RDi.z - LUi.z > 0) { LUt = LUi; RDt = RDi; @@ -119,9 +115,9 @@ bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& nz2 = getNTracks(LUt, RDt); } - if(RDi.x - LUi.x > 0) + if (RDi.x - LUi.x > 0) { - if((abs(RDi.x - LUi.x) >= abs(RDi.y - LUi.y)) && (abs(RDi.x - LUi.x) >= abs(RDi.z - LUi.z))) + if ((abs(RDi.x - LUi.x) >= abs(RDi.y - LUi.y)) && (abs(RDi.x - LUi.x) >= abs(RDi.z - LUi.z))) { LU1o = LUi; RD1o = RDi; @@ -133,9 +129,9 @@ bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& } } - if(RDi.y - LUi.y > 0) + if (RDi.y - LUi.y > 0) { - if((abs(RDi.y - LUi.y) >= abs(RDi.x - LUi.x)) && (abs(RDi.y - LUi.y) >= abs(RDi.z - LUi.z))) + if ((abs(RDi.y - LUi.y) >= abs(RDi.x - LUi.x)) && (abs(RDi.y - LUi.y) >= abs(RDi.z - LUi.z))) { LU1o = LUi; RD1o = RDi; @@ -147,9 +143,9 @@ bool ReconstructionPlan::divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& } } - if(RDi.z - LUi.z > 0) + if (RDi.z - LUi.z > 0) { - if((abs(RDi.z - LUi.z) >= abs(RDi.x - LUi.x)) && (abs(RDi.z - LUi.z) >= abs(RDi.y - LUi.y))) + if ((abs(RDi.z - LUi.z) >= abs(RDi.x - LUi.x)) && (abs(RDi.z - LUi.z) >= abs(RDi.y - LUi.y))) { LU1o = LUi; RD1o = RDi; @@ -189,7 +185,7 @@ StaticVector* ReconstructionPlan::computeReconstructionPlanBinSearch(un toDivideLU->push_back(actHexahLU); toDivideRD->push_back(actHexahRD); - while(toDivideLU->size() > 0) + while (toDivideLU->size() > 0) { actHexahLU = toDivideLU->pop(); actHexahRD = toDivideRD->pop(); @@ -201,7 +197,7 @@ StaticVector* ReconstructionPlan::computeReconstructionPlanBinSearch(un */ Voxel LU1, RD1, LU2, RD2; - if(divideBox(LU1, RD1, LU2, RD2, actHexahLU, actHexahRD, maxTracks)) + if (divideBox(LU1, RD1, LU2, RD2, actHexahLU, actHexahRD, maxTracks)) { toDivideLU->push_back(LU1); toDivideRD->push_back(RD1); @@ -230,7 +226,7 @@ StaticVector* ReconstructionPlan::computeReconstructionPlanBinSearch(un getHexah(hexah, actHexahLU, actHexahRD); mvsUtils::inflateHexahedron(hexah, hexahinf, 1.05); - for(int k = 0; k < 8; k++) + for (int k = 0; k < 8; k++) { hexahsToReconstruct->push_back(hexahinf[k]); } @@ -243,15 +239,12 @@ StaticVector* ReconstructionPlan::computeReconstructionPlanBinSearch(un return hexahsToReconstruct; } -void ReconstructionPlan::getHexahedronForID(float dist, int id, Point3d* out) -{ - mvsUtils::inflateHexahedron(&(*voxels)[id * 8], out, dist); -} +void ReconstructionPlan::getHexahedronForID(float dist, int id, Point3d* out) { mvsUtils::inflateHexahedron(&(*voxels)[id * 8], out, dist); } StaticVector*>* loadLargeScalePtsCams(const std::vector& recsDirs) { StaticVector*>* ptsCamsFromDct = new StaticVector*>(); - for(int i = 0; i < recsDirs.size(); ++i) + for (int i = 0; i < recsDirs.size(); ++i) { std::string folderName = recsDirs[i]; @@ -264,18 +257,18 @@ StaticVector*>* loadLargeScalePtsCams(const std::vector*>* ptsCamsFromDcti = loadArrayOfArraysFromFile(filePtsCamsFromDCTName); ptsCamsFromDct->reserveAdd(ptsCamsFromDcti->size()); - for(int i = 0; i < ptsCamsFromDcti->size(); i++) + for (int i = 0; i < ptsCamsFromDcti->size(); i++) { ptsCamsFromDct->push_back((*ptsCamsFromDcti)[i]); } - delete ptsCamsFromDcti; //!!!NOT DELETE ARRAYOFARRAYS + delete ptsCamsFromDcti; //!!!NOT DELETE ARRAYOFARRAYS } return ptsCamsFromDct; } void loadLargeScalePtsCams(const std::vector& recsDirs, StaticVector>& out_ptsCams) { - for(int i = 0; i < recsDirs.size(); ++i) + for (int i = 0; i < recsDirs.size(); ++i) { std::string folderName = recsDirs[i]; @@ -293,12 +286,12 @@ StaticVector* getTrisColorsRgb(mesh::Mesh* me, StaticVector* ptsColors { StaticVector* trisColors = new StaticVector(); trisColors->resize(me->tris.size()); - for(int i = 0; i < me->tris.size(); i++) + for (int i = 0; i < me->tris.size(); i++) { float r = 0.0f; float g = 0.0f; float b = 0.0f; - for(int j = 0; j < 3; j++) + for (int j = 0; j < 3; j++) { r += (float)(*ptsColors)[me->tris[i].v[j]].r; g += (float)(*ptsColors)[me->tris[i].v[j]].g; @@ -311,15 +304,14 @@ StaticVector* getTrisColorsRgb(mesh::Mesh* me, StaticVector* ptsColors return trisColors; } -mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVector* voxelsArray, - LargeScale* ls) +mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVector* voxelsArray, LargeScale* ls) { ReconstructionPlan rp(ls->dimensions, &ls->space[0], ls->mp, ls->spaceVoxelsFolderName); ALICEVISION_LOG_DEBUG("Detecting size of merged mesh."); int npts = 0; int ntris = 0; - for(int i = 0; i < recsDirs.size(); i++) + for (int i = 0; i < recsDirs.size(); i++) { std::string folderName = recsDirs[i]; @@ -353,9 +345,9 @@ mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVectorreserve(npts); ALICEVISION_LOG_DEBUG("Merging part to one mesh without connecting them."); - for(int i = 0; i < recsDirs.size(); i++) + for (int i = 0; i < recsDirs.size(); i++) { - if(ls->mp->verbose) + if (ls->mp->verbose) ALICEVISION_LOG_DEBUG("Merging part: " << i); std::string folderName = recsDirs[i]; @@ -371,7 +363,7 @@ mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVectorremoveTrianglesOutsideHexahedron(hexah); - ALICEVISION_LOG_DEBUG("Adding mesh part "<< i << " to mesh"); + ALICEVISION_LOG_DEBUG("Adding mesh part " << i << " to mesh"); me->addMesh(*mei); ALICEVISION_LOG_DEBUG("Merging colors of part: s" << i); @@ -381,11 +373,11 @@ mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVector* ptsColsi = loadArrayFromFile(fileName); StaticVector* trisColsi = getTrisColorsRgb(mei, ptsColsi); - for(int j = 0; j < trisColsi->size(); j++) + for (int j = 0; j < trisColsi->size(); j++) { trisCols->push_back((*trisColsi)[j]); } - for(int j = 0; j < ptsColsi->size(); j++) + for (int j = 0; j < ptsColsi->size(); j++) { ptsCols->push_back((*ptsColsi)[j]); } @@ -402,11 +394,11 @@ mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVectormp->verbose) - // printf("Creating QS\n"); - // createQSfileFromMesh(ls->spaceFolderName + "reconstructedSpaceLevelJoinedMesh"+num2str(gridLevel)+".bin", - // ls->spaceFolderName+"meshAvImgCol.ply.ptsColors", ls->spaceFolderName + - // "reconstructedSpaceLevelJoinedMesh"+num2str(gridLevel)+".qs"); + // if(ls->mp->verbose) + // printf("Creating QS\n"); + // createQSfileFromMesh(ls->spaceFolderName + "reconstructedSpaceLevelJoinedMesh"+num2str(gridLevel)+".bin", + // ls->spaceFolderName+"meshAvImgCol.ply.ptsColors", ls->spaceFolderName + + // "reconstructedSpaceLevelJoinedMesh"+num2str(gridLevel)+".qs"); #ifdef QSPLAT createQSfileFromMesh(spaceBinFileName, spacePtsColsBinFileName, outDir + "mesh.qs"); #endif @@ -416,19 +408,17 @@ mesh::Mesh* joinMeshes(const std::vector& recsDirs, StaticVectordimensions, &ls->space[0], ls->mp, ls->spaceVoxelsFolderName); + ReconstructionPlan* rp = new ReconstructionPlan(ls->dimensions, &ls->space[0], ls->mp, ls->spaceVoxelsFolderName); std::string param = "LargeScale:gridLevel" + mvsUtils::num2str(gl); int gridLevel = ls->mp->userParams.get(param.c_str(), gl * 300); - std::string optimalReconstructionPlanFileName = - ls->spaceFolderName + "optimalReconstructionPlan" + mvsUtils::num2str(gridLevel) + ".bin"; + std::string optimalReconstructionPlanFileName = ls->spaceFolderName + "optimalReconstructionPlan" + mvsUtils::num2str(gridLevel) + ".bin"; StaticVector* optimalReconstructionPlan = loadArrayFromFile(optimalReconstructionPlanFileName); auto subFolderName = ls->mp->userParams.get("LargeScale.subFolderName", ""); - if(subFolderName.empty()) + if (subFolderName.empty()) { - if(ls->mp->userParams.get("global.LabatutCFG09", false)) + if (ls->mp->userParams.get("global.LabatutCFG09", false)) { subFolderName = "LabatutCFG09"; } @@ -439,16 +429,16 @@ mesh::Mesh* joinMeshes(int gl, LargeScale* ls) voxelsArray->reserve(optimalReconstructionPlan->size() * 8); std::vector recsDirs; - for(int i = 0; i < optimalReconstructionPlan->size(); i++) + for (int i = 0; i < optimalReconstructionPlan->size(); i++) { int id = (*optimalReconstructionPlan)[i].id; float inflateFactor = (*optimalReconstructionPlan)[i].value; std::string folderName = ls->spaceFolderName + "reconstructedSpacePart" + mvsUtils::num2strFourDecimal(id) + "/"; - folderName += "GL_" + mvsUtils::num2str(gridLevel) + "_IF_" + mvsUtils::num2str((int)inflateFactor) + "/"; + folderName += "GL_" + mvsUtils::num2str(gridLevel) + "_IF_" + mvsUtils::num2str((int)inflateFactor) + "/"; Point3d hexah[8]; rp->getHexahedronForID(inflateFactor, id, hexah); - for(int k = 0; k < 8; k++) + for (int k = 0; k < 8; k++) { voxelsArray->push_back(hexah[k]); } @@ -475,5 +465,5 @@ mesh::Mesh* joinMeshes(const std::string& voxelsArrayFileName, LargeScale* ls) return me; } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/ReconstructionPlan.hpp b/src/aliceVision/fuseCut/ReconstructionPlan.hpp index 89735ac88e..8993ae1a67 100644 --- a/src/aliceVision/fuseCut/ReconstructionPlan.hpp +++ b/src/aliceVision/fuseCut/ReconstructionPlan.hpp @@ -18,14 +18,13 @@ namespace fuseCut { class ReconstructionPlan : public VoxelsGrid { -public: + public: StaticVector* nVoxelsTracks; ReconstructionPlan(Voxel& dimmensions, Point3d* space, mvsUtils::MultiViewParams* _mp, const std::string& _spaceRootDir); ~ReconstructionPlan(); unsigned long getNTracks(const Voxel& LU, const Voxel& RD); - bool divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& RD2o, const Voxel& LUi, const Voxel& RDi, - unsigned long maxTracks); + bool divideBox(Voxel& LU1o, Voxel& RD1o, Voxel& LU2o, Voxel& RD2o, const Voxel& LUi, const Voxel& RDi, unsigned long maxTracks); StaticVector* computeReconstructionPlanBinSearch(unsigned long maxTracks); StaticVector* voxelsIdsIntersectingHexah(Point3d* hexah); @@ -40,5 +39,5 @@ mesh::Mesh* joinMeshes(const std::string& voxelsArrayFileName, LargeScale* ls); StaticVector*>* loadLargeScalePtsCams(const std::vector& recsDirs); void loadLargeScalePtsCams(const std::vector& recsDirs, StaticVector>& out_ptsCams); -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/VoxelsGrid.cpp b/src/aliceVision/fuseCut/VoxelsGrid.cpp index 9bf92f2363..ede4db3651 100644 --- a/src/aliceVision/fuseCut/VoxelsGrid.cpp +++ b/src/aliceVision/fuseCut/VoxelsGrid.cpp @@ -19,18 +19,16 @@ namespace fuseCut { namespace bfs = boost::filesystem; -VoxelsGrid::VoxelsGrid() -{ -} +VoxelsGrid::VoxelsGrid() {} VoxelsGrid::VoxelsGrid(const Voxel& dimensions, Point3d* _space, mvsUtils::MultiViewParams* _mp, const std::string& _spaceRootDir, bool _doVisualize) { doVisualize = _doVisualize; mp = _mp; voxelDim = dimensions; - for(int k = 0; k < 8; k++) + for (int k = 0; k < 8; k++) { - space[k] = _space[k]; // TODO faca + space[k] = _space[k]; // TODO faca } voxels = mvsUtils::computeVoxels(space, dimensions); spaceRootDir = _spaceRootDir; @@ -49,11 +47,11 @@ VoxelsGrid* VoxelsGrid::clone(const std::string& _spaceRootDir) out->voxelDim = voxelDim; out->voxels = new StaticVector(); out->voxels->resize(voxels->size()); - for(int i = 0; i < voxels->size(); i++) + for (int i = 0; i < voxels->size(); i++) { out->voxels->push_back((*voxels)[i]); } - for(int k = 0; k < 8; k++) + for (int k = 0; k < 8; k++) { out->space[k] = space[k]; } @@ -66,10 +64,7 @@ VoxelsGrid* VoxelsGrid::clone(const std::string& _spaceRootDir) return out; } -VoxelsGrid::~VoxelsGrid() -{ - delete voxels; -} +VoxelsGrid::~VoxelsGrid() { delete voxels; } StaticVector* VoxelsGrid::getNVoxelsTracks() { @@ -77,8 +72,8 @@ StaticVector* VoxelsGrid::getNVoxelsTracks() StaticVector* nVoxelsTracks = new StaticVector(); nVoxelsTracks->reserve(voxels->size() / 8); - //long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < voxels->size() / 8; i++) + // long t1 = mvsUtils::initEstimate(); + for (int i = 0; i < voxels->size() / 8; i++) { std::string folderName = getVoxelFolderName(i); std::string fileNameTracksPts; @@ -86,9 +81,9 @@ StaticVector* VoxelsGrid::getNVoxelsTracks() int n = getArrayLengthFromFile(fileNameTracksPts); // printf("%i %i\n",i,n); nVoxelsTracks->push_back(n); - //mvsUtils::printfEstimate(i, voxels->size() / 8, t1); + // mvsUtils::printfEstimate(i, voxels->size() / 8, t1); } - //mvsUtils::finishEstimate(); + // mvsUtils::finishEstimate(); return nVoxelsTracks; } @@ -98,8 +93,8 @@ unsigned long VoxelsGrid::getNTracks() const ALICEVISION_LOG_DEBUG("computing number of all tracks."); unsigned long ntracks = 0; - //long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < voxels->size() / 8; i++) + // long t1 = mvsUtils::initEstimate(); + for (int i = 0; i < voxels->size() / 8; i++) { const std::string folderName = getVoxelFolderName(i); std::string fileNameTracksPts; @@ -107,23 +102,19 @@ unsigned long VoxelsGrid::getNTracks() const int n = getArrayLengthFromFile(fileNameTracksPts); // printf("%i %i\n",i,n); ntracks += (unsigned long)n; - //mvsUtils::printfEstimate(i, voxels->size() / 8, t1); + // mvsUtils::printfEstimate(i, voxels->size() / 8, t1); } - //mvsUtils::finishEstimate(); + // mvsUtils::finishEstimate(); return ntracks; } -int VoxelsGrid::getIdForVoxel(const Voxel& v) const -{ - return v.x * voxelDim.y * voxelDim.z + v.y * voxelDim.z + v.z; -} +int VoxelsGrid::getIdForVoxel(const Voxel& v) const { return v.x * voxelDim.y * voxelDim.z + v.y * voxelDim.z + v.z; } Voxel VoxelsGrid::getVoxelForId(int id) const { int xp = id / (voxelDim.y * voxelDim.z); - return Voxel(xp, (id - xp * voxelDim.y * voxelDim.z) / voxelDim.z, - (id - xp * voxelDim.y * voxelDim.z) % voxelDim.z); + return Voxel(xp, (id - xp * voxelDim.y * voxelDim.z) / voxelDim.z, (id - xp * voxelDim.y * voxelDim.z) % voxelDim.z); } bool VoxelsGrid::isValidVoxel(const Voxel& v) @@ -165,7 +156,7 @@ StaticVector* VoxelsGrid::loadTracksFromVoxelFiles(S if (!bfs::exists(fileNameTracksPts)) return nullptr; - StaticVector* tracksStat = loadArrayFromFile(fileNameTracksStat); // minPixSize, minSim, npts + StaticVector* tracksStat = loadArrayFromFile(fileNameTracksStat); // minPixSize, minSim, npts StaticVector* tracksPoints = loadArrayFromFile(fileNameTracksPts); StaticVector*>* tracksPointsCams = loadArrayOfArraysFromFile(fileNameTracksPtsCams); *cams = loadArrayFromFile(fileNameTracksCams); @@ -173,7 +164,7 @@ StaticVector* VoxelsGrid::loadTracksFromVoxelFiles(S StaticVector* tracks = new StaticVector(); tracks->reserve(tracksPoints->size()); - for(int i = 0; i < tracksPoints->size(); i++) + for (int i = 0; i < tracksPoints->size(); i++) { StaticVector* tcams = (*tracksPointsCams)[i]; OctreeTracks::trackStruct* t = new OctreeTracks::trackStruct(0.0f, 0.0f, Point3d(), 0); @@ -192,10 +183,9 @@ StaticVector* VoxelsGrid::loadTracksFromVoxelFiles(S return tracks; } -bool VoxelsGrid::saveTracksToVoxelFiles(StaticVector* cams, StaticVector* tracks, - int id) +bool VoxelsGrid::saveTracksToVoxelFiles(StaticVector* cams, StaticVector* tracks, int id) { - if(sizeOfStaticVector(tracks) <= 10) + if (sizeOfStaticVector(tracks) <= 10) { return false; } @@ -215,7 +205,7 @@ bool VoxelsGrid::saveTracksToVoxelFiles(StaticVector* cams, StaticVector* tracksStat = new StaticVector(); tracksStat->reserve(tracks->size()); - for(int j = 0; j < tracks->size(); j++) + for (int j = 0; j < tracks->size(); j++) { OctreeTracks::trackStruct* info = (*tracks)[j]; tracksPoints->push_back(info->point); @@ -223,7 +213,7 @@ bool VoxelsGrid::saveTracksToVoxelFiles(StaticVector* cams, StaticVector* tcams = new StaticVector(); tcams->reserve(info->cams.size()); - for(int k = 0; k < info->cams.size(); k++) + for (int k = 0; k < info->cams.size(); k++) { tcams->push_back(info->cams[k]); } @@ -231,8 +221,7 @@ bool VoxelsGrid::saveTracksToVoxelFiles(StaticVector* cams, StaticVector* cams, StaticVector* ReconstructionPlan, int numSubVoxs, int maxPts, - int level, int& maxlevel, const std::string& depthMapsPtsSimsTmpDir) +void VoxelsGrid::generateTracksForEachVoxel(StaticVector* ReconstructionPlan, + int numSubVoxs, + int maxPts, + int level, + int& maxlevel, + const std::string& depthMapsPtsSimsTmpDir) { ALICEVISION_LOG_DEBUG("generateTracksForEachVoxel recursive " << "\t- numSubVoxs: " << numSubVoxs << std::endl @@ -270,7 +263,7 @@ void VoxelsGrid::generateTracksForEachVoxel(StaticVector* Reconstructio toRecurse->reserve(nvoxs); #pragma omp parallel for - for(int i = 0; i < nvoxs; i++) + for (int i = 0; i < nvoxs; i++) { // printf("GENERATING TRIANGLUATION FOR %i-th VOXEL OF %i\n",i,nvoxs); @@ -279,14 +272,14 @@ void VoxelsGrid::generateTracksForEachVoxel(StaticVector* Reconstructio long t1 = clock(); OctreeTracks* ott = new OctreeTracks(&(*voxels)[i * 8], mp, Voxel(numSubVoxs, numSubVoxs, numSubVoxs)); StaticVector* tracks = ott->fillOctree(maxPts, depthMapsPtsSimsTmpDir); - if(mp->verbose) + if (mp->verbose) mvsUtils::printfElapsedTime(t1, "fillOctree"); - if(tracks == nullptr) + if (tracks == nullptr) { - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("deleting OTT: " << i); delete ott; - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("deleted " << i); #pragma omp critical { @@ -296,15 +289,15 @@ void VoxelsGrid::generateTracksForEachVoxel(StaticVector* Reconstructio else { // printf("SAVING %i-th VOXEL TRACKS FILES\n",i); - if(tracks->size() > 10) + if (tracks->size() > 10) { - if(ReconstructionPlan != nullptr) + if (ReconstructionPlan != nullptr) { #pragma omp critical { - for(int k = 0; k < 8; k++) + for (int k = 0; k < 8; k++) { - if(ReconstructionPlan->size() < ReconstructionPlan->capacity()) + if (ReconstructionPlan->size() < ReconstructionPlan->capacity()) { ReconstructionPlan->push_back((*voxels)[i * 8 + k]); } @@ -320,18 +313,18 @@ void VoxelsGrid::generateTracksForEachVoxel(StaticVector* Reconstructio delete cams; } delete tracks; - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("deleting OTT: " << i); delete ott; - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("deleted " << i); } } - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("toRecurse " << toRecurse->size()); - for(int j = 0; j < toRecurse->size(); j++) + for (int j = 0; j < toRecurse->size(); j++) { int i = (*toRecurse)[j]; // recursion @@ -346,8 +339,7 @@ void VoxelsGrid::generateTracksForEachVoxel(StaticVector* Reconstructio fclose(f); VoxelsGrid* vgnew = new VoxelsGrid(Voxel(2, 2, 2), &(*voxels)[i * 8], mp, subfn, doVisualize); - vgnew->generateTracksForEachVoxel(ReconstructionPlan, numSubVoxs / 2, maxPts, level + 1, maxlevel, - depthMapsPtsSimsTmpDir); + vgnew->generateTracksForEachVoxel(ReconstructionPlan, numSubVoxs / 2, maxPts, level + 1, maxlevel, depthMapsPtsSimsTmpDir); delete vgnew; } @@ -355,20 +347,19 @@ void VoxelsGrid::generateTracksForEachVoxel(StaticVector* Reconstructio mvsUtils::printfElapsedTime(tall); - if(doVisualize) + if (doVisualize) vizualize(); } -void VoxelsGrid::generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& RD, - const std::string& depthMapsPtsSimsTmpDir) +void VoxelsGrid::generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& RD, const std::string& depthMapsPtsSimsTmpDir) { - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("generateSpace recursive: " << std::endl - << "\t- LU: " << LU.x << " " << LU.y << " " << LU.z << std::endl - << "\t- RD: " << RD.x << " " << RD.y << " " << RD.z); + << "\t- LU: " << LU.x << " " << LU.y << " " << LU.z << std::endl + << "\t- RD: " << RD.x << " " << RD.y << " " << RD.z); int nvoxs = voxels->size() / 8; - for(int voxid = 0; voxid < nvoxs; voxid++) + for (int voxid = 0; voxid < nvoxs; voxid++) { std::string folderName = getVoxelFolderName(voxid); // std::string subfn = folderName + "sub/"; @@ -399,15 +390,15 @@ void VoxelsGrid::generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& StaticVector* tracks = loadTracksFromVoxelFiles(&cams, voxid); Voxel vrel; - if((tracks != nullptr) && (tracks->size() > 0)) + if ((tracks != nullptr) && (tracks->size() > 0)) { // estimate nubers // TODO FACA: remove costly estimation of numbers StaticVector* nnewVoxsTracks = new StaticVector(); nnewVoxsTracks->reserve(part.x * part.y * part.z); nnewVoxsTracks->resize_with(part.x * part.y * part.z, 0); - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { - if(ott->getVoxelOfOctreeFor3DPoint(vrel, (*tracks)[i]->point)) + if (ott->getVoxelOfOctreeFor3DPoint(vrel, (*tracks)[i]->point)) { (*nnewVoxsTracks)[vgg->getIdForVoxel(vrel)] += 1; } @@ -415,9 +406,9 @@ void VoxelsGrid::generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& // allocate StaticVector*>* newVoxsTracks = - new StaticVector*>(); + new StaticVector*>(); newVoxsTracks->reserve(part.x * part.y * part.z); - for(int i = 0; i < part.x * part.y * part.z; i++) + for (int i = 0; i < part.x * part.y * part.z; i++) { auto* newVoxTracks = new StaticVector(); newVoxTracks->reserve((*nnewVoxsTracks)[i]); @@ -425,15 +416,15 @@ void VoxelsGrid::generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& } // fill - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { - if(ott->getVoxelOfOctreeFor3DPoint(vrel, (*tracks)[i]->point)) + if (ott->getVoxelOfOctreeFor3DPoint(vrel, (*tracks)[i]->point)) { (*newVoxsTracks)[vgg->getIdForVoxel(vrel)]->push_back((*tracks)[i]); } } - for(int i = 0; i < part.x * part.y * part.z; i++) + for (int i = 0; i < part.x * part.y * part.z; i++) { Voxel vact = vgg->getVoxelForId(i); Voxel vglob = subLU + vact; @@ -454,16 +445,16 @@ void VoxelsGrid::generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& delete nnewVoxsTracks; } - if(cams != nullptr) + if (cams != nullptr) { delete cams; } - if(tracks != nullptr) + if (tracks != nullptr) { - for(int i = 0; i < tracks->size(); i++) + for (int i = 0; i < tracks->size(); i++) { - delete(*tracks)[i]; + delete (*tracks)[i]; } delete tracks; } @@ -481,40 +472,39 @@ void VoxelsGrid::cloneSpaceVoxel(int voxelId, int numSubVoxs, VoxelsGrid* newSpa if (bfs::exists(fileNameTracksPts)) { - OctreeTracks* ott = - new OctreeTracks(&(*voxels)[voxelId * 8], mp, Voxel(numSubVoxs, numSubVoxs, numSubVoxs)); + OctreeTracks* ott = new OctreeTracks(&(*voxels)[voxelId * 8], mp, Voxel(numSubVoxs, numSubVoxs, numSubVoxs)); StaticVector* tcams; StaticVector* tracksOld = loadTracksFromVoxelFiles(&tcams, voxelId); StaticVector* tracksNew = ott->fillOctreeFromTracks(tracksOld); - for(int i = 0; i < tracksOld->size(); i++) + for (int i = 0; i < tracksOld->size(); i++) { - delete(*tracksOld)[i]; + delete (*tracksOld)[i]; } delete tracksOld; newSpace->saveTracksToVoxelFiles(tcams, tracksNew, voxelId); delete tcams; - delete tracksNew; // DO NOT NEEDED TO DELETE PARTICULAR POINTERS BECAUSE THEY POINT TO ott STRUCTURES + delete tracksNew; // DO NOT NEEDED TO DELETE PARTICULAR POINTERS BECAUSE THEY POINT TO ott STRUCTURES delete ott; } } VoxelsGrid* VoxelsGrid::cloneSpace(int numSubVoxs, const std::string& newSpaceRootDir) { - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("cloning space."); VoxelsGrid* out = clone(newSpaceRootDir); long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < voxels->size() / 8; i++) + for (int i = 0; i < voxels->size() / 8; i++) { cloneSpaceVoxel(i, numSubVoxs, out); mvsUtils::printfEstimate(i, voxels->size() / 8, t1); } mvsUtils::finishEstimate(); - if(doVisualize) + if (doVisualize) out->vizualize(); return out; @@ -531,9 +521,9 @@ void VoxelsGrid::copySpaceVoxel(int voxelId, VoxelsGrid* newSpace) StaticVector* tracksOld = loadTracksFromVoxelFiles(&tcams, voxelId); newSpace->saveTracksToVoxelFiles(tcams, tracksOld, voxelId); - for(int i = 0; i < tracksOld->size(); i++) + for (int i = 0; i < tracksOld->size(); i++) { - delete(*tracksOld)[i]; + delete (*tracksOld)[i]; } delete tracksOld; delete tcams; @@ -542,20 +532,20 @@ void VoxelsGrid::copySpaceVoxel(int voxelId, VoxelsGrid* newSpace) VoxelsGrid* VoxelsGrid::copySpace(const std::string& newSpaceRootDir) { - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("Copy space."); VoxelsGrid* out = clone(newSpaceRootDir); long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < voxels->size() / 8; i++) + for (int i = 0; i < voxels->size() / 8; i++) { copySpaceVoxel(i, out); mvsUtils::printfEstimate(i, voxels->size() / 8, t1); } mvsUtils::finishEstimate(); - if(doVisualize) + if (doVisualize) out->vizualize(); return out; @@ -563,16 +553,15 @@ VoxelsGrid* VoxelsGrid::copySpace(const std::string& newSpaceRootDir) void VoxelsGrid::generateCamsPtsFromVoxelsTracks() { - if(mp->verbose) + if (mp->verbose) ALICEVISION_LOG_DEBUG("Distributing pts from voxels tracks to camera files."); long t1 = mvsUtils::initEstimate(); int nvoxs = voxels->size() / 8; - for(int i = 0; i < nvoxs; i++) + for (int i = 0; i < nvoxs; i++) { std::string folderName = getVoxelFolderName(i); - std::string fileNameTracksCams, fileNameTracksPts, - fileNameTracksPtsCams; + std::string fileNameTracksCams, fileNameTracksPts, fileNameTracksPtsCams; fileNameTracksCams = folderName + "tracksGridCams.bin"; fileNameTracksPts = folderName + "tracksGridPts.bin"; fileNameTracksPtsCams = folderName + "tracksGridPtsCams.bin"; @@ -582,16 +571,14 @@ void VoxelsGrid::generateCamsPtsFromVoxelsTracks() if (bfs::exists(fileNameTracksPts)) { StaticVector* tracksPoints = loadArrayFromFile(fileNameTracksPts); - StaticVector*>* tracksPointsCams = - loadArrayOfArraysFromFile(fileNameTracksPtsCams); + StaticVector*>* tracksPointsCams = loadArrayOfArraysFromFile(fileNameTracksPtsCams); StaticVector* cams = loadArrayFromFile(fileNameTracksCams); // printf("distributing %i tracks to %i camspts files \n", tracksPoints->size(), cams->size()); - StaticVector*>* camsTracksPoints = - convertObjectsCamsToCamsObjects(*mp, tracksPointsCams); + StaticVector*>* camsTracksPoints = convertObjectsCamsToCamsObjects(*mp, tracksPointsCams); #pragma omp parallel for - for(int c = 0; c < cams->size(); c++) + for (int c = 0; c < cams->size(); c++) { int rc = (*cams)[c]; @@ -599,19 +586,19 @@ void VoxelsGrid::generateCamsPtsFromVoxelsTracks() std::string camPtsFileName = spaceCamsTracksDir + "camPtsGrid_" + std::to_string(mp->getViewId(rc)) + ".bin"; FILE* fin = fopen(camPtsFileName.c_str(), "ab"); StaticVector* camPtsIds = (*camsTracksPoints)[rc]; - for(int j = 0; j < sizeOfStaticVector(camPtsIds); j++) + for (int j = 0; j < sizeOfStaticVector(camPtsIds); j++) { int ptid = (*camPtsIds)[j].x; int nrc = (*camPtsIds)[j].y; GC_camVertexInfo p; p.point = (*tracksPoints)[ptid]; - p.sim = -0.91; // TODO FACA: why?? + p.sim = -0.91; // TODO FACA: why?? p.nrc = nrc; p.ncams = sizeOfStaticVector((*tracksPointsCams)[ptid]); p.fwriteinfo(fin); } fclose(fin); - } // for cams + } // for cams delete cams; delete tracksPoints; @@ -620,7 +607,7 @@ void VoxelsGrid::generateCamsPtsFromVoxelsTracks() } mvsUtils::printfEstimate(i, nvoxs, t1); - } // for i + } // for i mvsUtils::finishEstimate(); } @@ -631,7 +618,7 @@ void VoxelsGrid::vizualize() fprintf(f, "#VRML V2.0 utf8\n"); fprintf(f, "Background {\n skyColor 1 1 1 \n } \n"); int nvoxs = voxels->size() / 8; - for(int i = 0; i < nvoxs; i++) + for (int i = 0; i < nvoxs; i++) { std::string subFoldeName = getVoxelFolderName(i); std::string fname = subFoldeName + "tracks.wrl"; @@ -691,5 +678,5 @@ void VoxelsGrid::getHexah(Point3d* hexahOut, const Voxel& LUi, const Voxel& RDi) hexahOut[7] = O + vvz + vvy; } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/VoxelsGrid.hpp b/src/aliceVision/fuseCut/VoxelsGrid.hpp index 5594fe1567..229b03b342 100644 --- a/src/aliceVision/fuseCut/VoxelsGrid.hpp +++ b/src/aliceVision/fuseCut/VoxelsGrid.hpp @@ -17,11 +17,11 @@ namespace fuseCut { class VoxelsGrid { -public: + public: mvsUtils::MultiViewParams* mp; Voxel voxelDim; - Point3d space[8]; // TODO FACA: array + Point3d space[8]; // TODO FACA: array StaticVector* voxels; std::string spaceRootDir; std::string spaceCamsTracksDir; @@ -43,8 +43,12 @@ class VoxelsGrid StaticVector* loadTracksFromVoxelFiles(StaticVector** cams, int id); void generateCamsPtsFromVoxelsTracks(); void generateSpace(VoxelsGrid* vgnew, const Voxel& LU, const Voxel& RD, const std::string& depthMapsPtsSimsTmpDir); - void generateTracksForEachVoxel(StaticVector* ReconstructionPlan, int numSubVoxs, int maxPts, int level, - int& maxlevel, const std::string& depthMapsPtsSimsTmpDir); + void generateTracksForEachVoxel(StaticVector* ReconstructionPlan, + int numSubVoxs, + int maxPts, + int level, + int& maxlevel, + const std::string& depthMapsPtsSimsTmpDir); void vizualize(); void cloneSpaceVoxel(int voxelId, int numSubVoxs, VoxelsGrid* newSpace); @@ -56,5 +60,5 @@ class VoxelsGrid void getHexah(Point3d* hexahOut, const Voxel& LUi, const Voxel& RDi); }; -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/fuseCut/delaunayGraphCutTypes.hpp b/src/aliceVision/fuseCut/delaunayGraphCutTypes.hpp index dd6fd1f76a..e56e5340b3 100644 --- a/src/aliceVision/fuseCut/delaunayGraphCutTypes.hpp +++ b/src/aliceVision/fuseCut/delaunayGraphCutTypes.hpp @@ -75,15 +75,9 @@ struct GC_vertexInfo inline bool isVirtual() const { return cams.empty(); } inline bool isReal() const { return !cams.empty(); } - inline std::size_t getNbCameras() const - { - return cams.size(); - } + inline std::size_t getNbCameras() const { return cams.size(); } - inline int getCamera(std::size_t index) const - { - return cams[index]; - } + inline int getCamera(std::size_t index) const { return cams[index]; } void fwriteinfo(FILE* f) const { @@ -91,7 +85,7 @@ struct GC_vertexInfo fwrite(&nrc, sizeof(int), 1, f); int n = cams.size(); fwrite(&n, sizeof(int), 1, f); - if(n > 0) + if (n > 0) { fwrite(&cams[0], sizeof(int), n, f); } @@ -103,7 +97,7 @@ struct GC_vertexInfo fread(&nrc, sizeof(int), 1, f); int n; fread(&n, sizeof(int), 1, f); - if(n > 0) + if (n > 0) { cams.resize(n); fread(&cams[0], sizeof(int), n, f); @@ -113,7 +107,7 @@ struct GC_vertexInfo struct GC_camVertexInfo { - float sim; // TODO FACA: default value? + float sim; // TODO FACA: default value? int nrc = 0; int ncams = 0; Point3d point; @@ -137,18 +131,12 @@ struct GC_camVertexInfo inline std::ostream& operator<<(std::ostream& stream, const GC_cellInfo& cellInfo) { - stream << "cellSWeight:" << cellInfo.cellSWeight - << ",cellTWeight:" << cellInfo.cellTWeight - << ",gEdgeVisWeight[0]:" << cellInfo.gEdgeVisWeight[0] - << ",gEdgeVisWeight[1]:" << cellInfo.gEdgeVisWeight[1] - << ",gEdgeVisWeight[2]:" << cellInfo.gEdgeVisWeight[2] - << ",gEdgeVisWeight[3]:" << cellInfo.gEdgeVisWeight[3] - << ",fullnessScore:" << cellInfo.fullnessScore - << ",emptinessScore:" << cellInfo.emptinessScore - << ",on:" << cellInfo.on - ; + stream << "cellSWeight:" << cellInfo.cellSWeight << ",cellTWeight:" << cellInfo.cellTWeight << ",gEdgeVisWeight[0]:" << cellInfo.gEdgeVisWeight[0] + << ",gEdgeVisWeight[1]:" << cellInfo.gEdgeVisWeight[1] << ",gEdgeVisWeight[2]:" << cellInfo.gEdgeVisWeight[2] + << ",gEdgeVisWeight[3]:" << cellInfo.gEdgeVisWeight[3] << ",fullnessScore:" << cellInfo.fullnessScore + << ",emptinessScore:" << cellInfo.emptinessScore << ",on:" << cellInfo.on; return stream; } -} // namespace fuseCut -} // namespace aliceVision +} // namespace fuseCut +} // namespace aliceVision diff --git a/src/aliceVision/geometry/Frustum.hpp b/src/aliceVision/geometry/Frustum.hpp index 360bf32f9e..e7f575d2a3 100644 --- a/src/aliceVision/geometry/Frustum.hpp +++ b/src/aliceVision/geometry/Frustum.hpp @@ -21,108 +21,96 @@ using namespace aliceVision::geometry::halfPlane; /// - This structure is used for testing frustum intersection (see if two cam can share visual content) struct Frustum { - Vec3 cones[5]; // camera centre and the 4 points that define the image plane - Half_planes planes; // Define infinite frustum planes + 2 optional Near and Far Half Space - double z_near = -1., z_far = -1.; - std::vector points; - - Frustum() {} - - // Build a frustum from the image size, camera intrinsic and pose - Frustum(const int w, const int h, const Mat3 & K, const Mat3 & R, const Vec3 & C, const double zNear = -1.0, const double zFar = -1.0) - : z_near(zNear) - , z_far(zFar) - { - // supporting point are the points defined by the truncated cone - const Mat3 Kinv = K.inverse(); - const Mat3 Rt = R.transpose(); - - // Definition of the frustum with the supporting points - cones[0] = C; - cones[1] = Rt * ((Kinv * Vec3(0,0,1.0))) + C; - cones[2] = Rt * ((Kinv * Vec3(w,0,1.0))) + C; - cones[3] = Rt * ((Kinv * Vec3(w,h,1.0))) + C; - cones[4] = Rt * ((Kinv * Vec3(0,h,1.0))) + C; - - // Definition of the supporting planes - planes.push_back( Half_plane_p(cones[0], cones[4], cones[1]) ); - planes.push_back( Half_plane_p(cones[0], cones[1], cones[2]) ); - planes.push_back( Half_plane_p(cones[0], cones[2], cones[3]) ); - planes.push_back( Half_plane_p(cones[0], cones[3], cones[4]) ); - - // Add Znear and ZFar half plane using the cam looking direction - const Vec3 camLookDirection_n = R.row(2).normalized(); - - if(zNear > 0) - { - const double d_near = - zNear - camLookDirection_n.dot(C); - planes.push_back( Half_plane(camLookDirection_n, d_near) ); + Vec3 cones[5]; // camera centre and the 4 points that define the image plane + Half_planes planes; // Define infinite frustum planes + 2 optional Near and Far Half Space + double z_near = -1., z_far = -1.; + std::vector points; - points.push_back( Rt * (z_near * (Kinv * Vec3(0,0,1.0))) + C); - points.push_back( Rt * (z_near * (Kinv * Vec3(w,0,1.0))) + C); - points.push_back( Rt * (z_near * (Kinv * Vec3(w,h,1.0))) + C); - points.push_back( Rt * (z_near * (Kinv * Vec3(0,h,1.0))) + C); - } - else + Frustum() {} + + // Build a frustum from the image size, camera intrinsic and pose + Frustum(const int w, const int h, const Mat3& K, const Mat3& R, const Vec3& C, const double zNear = -1.0, const double zFar = -1.0) + : z_near(zNear), + z_far(zFar) { - points.push_back(cones[0]); + // supporting point are the points defined by the truncated cone + const Mat3 Kinv = K.inverse(); + const Mat3 Rt = R.transpose(); + + // Definition of the frustum with the supporting points + cones[0] = C; + cones[1] = Rt * ((Kinv * Vec3(0, 0, 1.0))) + C; + cones[2] = Rt * ((Kinv * Vec3(w, 0, 1.0))) + C; + cones[3] = Rt * ((Kinv * Vec3(w, h, 1.0))) + C; + cones[4] = Rt * ((Kinv * Vec3(0, h, 1.0))) + C; + + // Definition of the supporting planes + planes.push_back(Half_plane_p(cones[0], cones[4], cones[1])); + planes.push_back(Half_plane_p(cones[0], cones[1], cones[2])); + planes.push_back(Half_plane_p(cones[0], cones[2], cones[3])); + planes.push_back(Half_plane_p(cones[0], cones[3], cones[4])); + + // Add Znear and ZFar half plane using the cam looking direction + const Vec3 camLookDirection_n = R.row(2).normalized(); + + if (zNear > 0) + { + const double d_near = -zNear - camLookDirection_n.dot(C); + planes.push_back(Half_plane(camLookDirection_n, d_near)); + + points.push_back(Rt * (z_near * (Kinv * Vec3(0, 0, 1.0))) + C); + points.push_back(Rt * (z_near * (Kinv * Vec3(w, 0, 1.0))) + C); + points.push_back(Rt * (z_near * (Kinv * Vec3(w, h, 1.0))) + C); + points.push_back(Rt * (z_near * (Kinv * Vec3(0, h, 1.0))) + C); + } + else + { + points.push_back(cones[0]); + } + if (zFar > 0) + { + const double d_Far = zFar + camLookDirection_n.dot(C); + planes.push_back(Half_plane(-camLookDirection_n, d_Far)); + + points.push_back(Rt * (z_far * (Kinv * Vec3(0, 0, 1.0))) + C); + points.push_back(Rt * (z_far * (Kinv * Vec3(w, 0, 1.0))) + C); + points.push_back(Rt * (z_far * (Kinv * Vec3(w, h, 1.0))) + C); + points.push_back(Rt * (z_far * (Kinv * Vec3(0, h, 1.0))) + C); + } + else + { + points.push_back(cones[1]); + points.push_back(cones[2]); + points.push_back(cones[3]); + points.push_back(cones[4]); + } } - if(zFar > 0) - { - const double d_Far = zFar + camLookDirection_n.dot(C); - planes.push_back( Half_plane(-camLookDirection_n, d_Far) ); - points.push_back( Rt * (z_far * (Kinv * Vec3(0,0,1.0))) + C); - points.push_back( Rt * (z_far * (Kinv * Vec3(w,0,1.0))) + C); - points.push_back( Rt * (z_far * (Kinv * Vec3(w,h,1.0))) + C); - points.push_back( Rt * (z_far * (Kinv * Vec3(0,h,1.0))) + C); - } - else + /// Test if two frustums intersect or not + bool intersect(const Frustum& f) const { - points.push_back(cones[1]); - points.push_back(cones[2]); - points.push_back(cones[3]); - points.push_back(cones[4]); + // Concatenate the Half Planes and see if an intersection exists + std::vector vec_planes(planes.size() + f.planes.size()); + std::copy(&planes[0], &planes[0] + planes.size(), &vec_planes[0]); + std::copy(&f.planes[0], &f.planes[0] + f.planes.size(), &vec_planes[planes.size()]); + + return halfPlane::isNotEmpty(vec_planes); } - } - - /// Test if two frustums intersect or not - bool intersect(const Frustum & f) const - { - // Concatenate the Half Planes and see if an intersection exists - std::vector vec_planes(planes.size() + f.planes.size()); - std::copy(&planes[0], &planes[0]+planes.size(), &vec_planes[0]); - std::copy(&f.planes[0], &f.planes[0]+f.planes.size(), &vec_planes[planes.size()]); - - return halfPlane::isNotEmpty(vec_planes); - } - - /// Return true if the Frustum is an infinite one - bool isInfinite() const - { - return planes.size() == 4; - } - - /// Return true if the Frustum is truncated - bool isTruncated() const - { - return planes.size() == 6; - } - - bool isPartiallyTruncated() const - { - return planes.size() == 5; - } - - // Return the supporting frustum points (5 for the infinite, 8 for the truncated) - const std::vector & frustum_points() const - { - return points; - } - -}; // struct Frustum - -} // namespace geometry -} // namespace aliceVision - -#endif // ALICEVISION_GEOMETRY_FRUSTUM_HPP_ + + /// Return true if the Frustum is an infinite one + bool isInfinite() const { return planes.size() == 4; } + + /// Return true if the Frustum is truncated + bool isTruncated() const { return planes.size() == 6; } + + bool isPartiallyTruncated() const { return planes.size() == 5; } + + // Return the supporting frustum points (5 for the infinite, 8 for the truncated) + const std::vector& frustum_points() const { return points; } + +}; // struct Frustum + +} // namespace geometry +} // namespace aliceVision + +#endif // ALICEVISION_GEOMETRY_FRUSTUM_HPP_ diff --git a/src/aliceVision/geometry/HalfPlane.hpp b/src/aliceVision/geometry/HalfPlane.hpp index e9ad5febc3..f9e50c386b 100644 --- a/src/aliceVision/geometry/HalfPlane.hpp +++ b/src/aliceVision/geometry/HalfPlane.hpp @@ -12,26 +12,26 @@ #include #include -EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Hyperplane) +EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Hyperplane) namespace aliceVision { namespace geometry { namespace halfPlane { /// Define the Half_plane equation (abcd coefficients) -typedef Eigen::Hyperplane Half_plane; +typedef Eigen::Hyperplane Half_plane; /// Define a collection of Half_plane typedef std::vector Half_planes; // Define a plane passing through the points (p, q, r). // The plane is oriented such that p, q and r are oriented in a positive sense (that is counterclockwise). -inline Half_plane Half_plane_p(const Vec3 & p, const Vec3 & q, const Vec3 & r) +inline Half_plane Half_plane_p(const Vec3& p, const Vec3& q, const Vec3& r) { - const Vec3 abc = (p-r).cross(q-r); - const double d = - abc.dot(r); - Half_plane hp; - hp.coeffs() << abc(0),abc(1),abc(2),d; - return hp; + const Vec3 abc = (p - r).cross(q - r); + const double d = -abc.dot(r); + Half_plane hp; + hp.coeffs() << abc(0), abc(1), abc(2), d; + return hp; } // [1] Paper: Finding the intersection of n half-spaces in time O(n log n). @@ -40,52 +40,47 @@ inline Half_plane Half_plane_p(const Vec3 & p, const Vec3 & q, const Vec3 & r) // Year: 1979 // More: ISSN 0304-3975, http://dx.doi.org/10.1016/0304-3975(79)90055-0. - /// Return true if the half_planes define a not empty volume (an intersection exists) -inline bool isNotEmpty(const Half_planes & hplanes) +inline bool isNotEmpty(const Half_planes& hplanes) { - // Check if it exists a point on all positive side of the half plane thanks to a Linear Program formulation [1]. - // => If a point exists: there is a common subspace defined and so intersections. - // The LP formulation consists in set the Half_plane as constraint and check if a point can fit the equations. - - using namespace aliceVision; - using namespace aliceVision::linearProgramming; - - LPConstraints cstraint; - { - cstraint._nbParams = 3; // {X,Y,Z} - cstraint._vec_bounds.resize(cstraint._nbParams); - std::fill(cstraint._vec_bounds.begin(),cstraint._vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); // [X,Y,Z] => -inf, +inf - cstraint._bminimize = true; + // Check if it exists a point on all positive side of the half plane thanks to a Linear Program formulation [1]. + // => If a point exists: there is a common subspace defined and so intersections. + // The LP formulation consists in set the Half_plane as constraint and check if a point can fit the equations. - // Configure constraints - const size_t nbConstraints = hplanes.size(); - cstraint._constraintMat = Mat(nbConstraints,3); - cstraint._vec_sign.resize(nbConstraints); - cstraint._Cst_objective = Vec(nbConstraints); + using namespace aliceVision; + using namespace aliceVision::linearProgramming; - // Fill the constrains (half-space equations) - for (unsigned char i= 0; i < hplanes.size(); ++i) + LPConstraints cstraint; { - const Vec & half_plane_coeff = hplanes[i].coeffs(); - // add the half plane equation to the system - cstraint._constraintMat.row(i) = - Vec3(half_plane_coeff(0), - half_plane_coeff(1), - half_plane_coeff(2)); - cstraint._vec_sign[i] = LPConstraints::LP_GREATER_OR_EQUAL; - cstraint._Cst_objective(i) = - half_plane_coeff(3); + cstraint._nbParams = 3; // {X,Y,Z} + cstraint._vec_bounds.resize(cstraint._nbParams); + std::fill(cstraint._vec_bounds.begin(), cstraint._vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); // [X,Y,Z] => -inf, +inf + cstraint._bminimize = true; + + // Configure constraints + const size_t nbConstraints = hplanes.size(); + cstraint._constraintMat = Mat(nbConstraints, 3); + cstraint._vec_sign.resize(nbConstraints); + cstraint._Cst_objective = Vec(nbConstraints); + + // Fill the constrains (half-space equations) + for (unsigned char i = 0; i < hplanes.size(); ++i) + { + const Vec& half_plane_coeff = hplanes[i].coeffs(); + // add the half plane equation to the system + cstraint._constraintMat.row(i) = Vec3(half_plane_coeff(0), half_plane_coeff(1), half_plane_coeff(2)); + cstraint._vec_sign[i] = LPConstraints::LP_GREATER_OR_EQUAL; + cstraint._Cst_objective(i) = -half_plane_coeff(3); + } } - } - // Solve in order to see if a point exists within the half spaces positive side? - OSI_CISolverWrapper solver(cstraint._nbParams); - solver.setup(cstraint); - const bool bIntersect = solver.solve(); // Status of the solver tell if there is an intersection or not - return bIntersect; + // Solve in order to see if a point exists within the half spaces positive side? + OSI_CISolverWrapper solver(cstraint._nbParams); + solver.setup(cstraint); + const bool bIntersect = solver.solve(); // Status of the solver tell if there is an intersection or not + return bIntersect; } -} // namespace geometry -} // namespace aliceVision -} // namespace halfPlane +} // namespace halfPlane +} // namespace geometry +} // namespace aliceVision diff --git a/src/aliceVision/geometry/Pose3.hpp b/src/aliceVision/geometry/Pose3.hpp index 790a172cd1..46400b68d4 100644 --- a/src/aliceVision/geometry/Pose3.hpp +++ b/src/aliceVision/geometry/Pose3.hpp @@ -11,87 +11,61 @@ #include #include - namespace aliceVision { namespace geometry { // Define a 3D Pose as a SE3 matrix class Pose3 { -protected: + protected: SE3::Matrix _homogeneous{SE3::Matrix::Identity()}; -public: + public: Pose3() = default; Pose3(const Mat3& c_R_w, const Vec3& w_c) { _homogeneous.setIdentity(); _homogeneous.block<3, 3>(0, 0) = c_R_w; - _homogeneous.block<3, 1>(0, 3) = - c_R_w * w_c; + _homogeneous.block<3, 1>(0, 3) = -c_R_w * w_c; } - Pose3(const Mat4& T) : _homogeneous(T) - { - } + Pose3(const Mat4& T) + : _homogeneous(T) + {} // Accessors - Mat3 rotation() const - { - return _homogeneous.block<3, 3>(0, 0); - } + Mat3 rotation() const { return _homogeneous.block<3, 3>(0, 0); } - void setRotation(const Mat3& rotation) - { - _homogeneous.block<3, 3>(0, 0) = rotation; - } + void setRotation(const Mat3& rotation) { _homogeneous.block<3, 3>(0, 0) = rotation; } - Vec3 translation() const - { - return _homogeneous.block<3, 1>(0, 3); - } + Vec3 translation() const { return _homogeneous.block<3, 1>(0, 3); } - Vec3 center() const - { - return - rotation().transpose() * translation(); - } + Vec3 center() const { return -rotation().transpose() * translation(); } - void setCenter(const Vec3& center) - { - _homogeneous.block<3, 1>(0, 3) = - rotation() * center; - } + void setCenter(const Vec3& center) { _homogeneous.block<3, 1>(0, 3) = -rotation() * center; } // Apply pose - inline Mat3X operator () (const Mat3X& p) const - { - return rotation() * (p.colwise() - center()); - } + inline Mat3X operator()(const Mat3X& p) const { return rotation() * (p.colwise() - center()); } // Composition - Pose3 operator * (const Pose3& P) const - { - return Pose3(_homogeneous * P._homogeneous); - } + Pose3 operator*(const Pose3& P) const { return Pose3(_homogeneous * P._homogeneous); } // Operator == bool operator==(const Pose3& other) const { - return AreMatNearEqual(rotation(), other.rotation(), 1e-6) && - AreVecNearEqual(center(), other.center(), 1e-6); + return AreMatNearEqual(rotation(), other.rotation(), 1e-6) && AreVecNearEqual(center(), other.center(), 1e-6); } // Inverse Pose3 inverse() const { // parameter is center ... which will inverse - return Pose3(rotation().transpose(), translation() ); + return Pose3(rotation().transpose(), translation()); } /// Return the depth (distance) of a point respect to the camera center - double depth(const Vec3& X) const - { - return (rotation() * (X - center()))[2]; - } + double depth(const Vec3& X) const { return (rotation() * (X - center()))[2]; } /// Return the pose with the Scale, Rotation and translation applied. Pose3 transformSRt(const double S, const Mat3& R, const Vec3& t) const @@ -103,12 +77,9 @@ class Pose3 return pose; } - const SE3::Matrix & getHomogeneous() const - { - return _homogeneous; - } + const SE3::Matrix& getHomogeneous() const { return _homogeneous; } -public: + public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; @@ -118,17 +89,14 @@ class Pose3 * @param[in] t The 3x1 translation. * @return The pose as [R, -R'*t] */ -inline Pose3 poseFromRT(const Mat3& R, const Vec3& t) -{ - return Pose3(R, -R.transpose() * t); -} +inline Pose3 poseFromRT(const Mat3& R, const Vec3& t) { return Pose3(R, -R.transpose() * t); } inline Pose3 randomPose() { Vec3 vecR = Vec3::Random().normalized() * boost::math::constants::pi(); - + return geometry::Pose3(SO3::expm(vecR), Vec3::Random()); } -} // namespace geometry -} // namespace aliceVision +} // namespace geometry +} // namespace aliceVision diff --git a/src/aliceVision/geometry/Pose3d_test.cpp b/src/aliceVision/geometry/Pose3d_test.cpp index a56b50c18a..72f8fce0dd 100644 --- a/src/aliceVision/geometry/Pose3d_test.cpp +++ b/src/aliceVision/geometry/Pose3d_test.cpp @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(ConstructorTest) EXPECT_MATRIX_NEAR(pose.rotation(), rotation, precision); EXPECT_MATRIX_NEAR(pose.center(), center, precision); - EXPECT_MATRIX_NEAR(pose.translation(), Vec3(-rotation*center), precision); + EXPECT_MATRIX_NEAR(pose.translation(), Vec3(-rotation * center), precision); } BOOST_AUTO_TEST_CASE(RotationAccessorsTest) @@ -138,7 +138,6 @@ BOOST_AUTO_TEST_CASE(InverseTest) EXPECT_MATRIX_NEAR(inverse.rotation(), expectedRotation, precision); EXPECT_MATRIX_NEAR(inverse.center(), expectedCenter, precision); EXPECT_MATRIX_NEAR((inverse * pose).getHomogeneous(), Mat4::Identity(), precision); - } BOOST_AUTO_TEST_CASE(DepthTest) @@ -153,7 +152,7 @@ BOOST_AUTO_TEST_CASE(DepthTest) const Mat3X points = Mat3X::Random(3, 10); - for(Eigen::Index i{0}; i < points.cols(); ++i) + for (Eigen::Index i{0}; i < points.cols(); ++i) { const auto& point = points.col(i); const auto& transformedPoint = (homogeneous * point.homogeneous()).hnormalized(); @@ -164,28 +163,28 @@ BOOST_AUTO_TEST_CASE(DepthTest) } } -//BOOST_AUTO_TEST_CASE(TransformSRtTest) +// BOOST_AUTO_TEST_CASE(TransformSRtTest) //{ -// const double precision{1e-5}; -// Mat3 rotation; -// rotation << 1, 0, 0, 0, 1, 0, 0, 0, 1; -// const Vec3 center(1, 2, 3); +// const double precision{1e-5}; +// Mat3 rotation; +// rotation << 1, 0, 0, 0, 1, 0, 0, 0, 1; +// const Vec3 center(1, 2, 3); // -// const geometry::Pose3 pose(rotation, center); +// const geometry::Pose3 pose(rotation, center); // -// double scale{2.0}; -// Mat3 newRotation; -// newRotation << 0, -1, 0, 1, 0, 0, 0, 0, 1; -// const Vec3 translation(4, 5, 6); +// double scale{2.0}; +// Mat3 newRotation; +// newRotation << 0, -1, 0, 1, 0, 0, 0, 0, 1; +// const Vec3 translation(4, 5, 6); // -// const geometry::Pose3 transformedPose = pose.transformSRt(scale, newRotation, translation); +// const geometry::Pose3 transformedPose = pose.transformSRt(scale, newRotation, translation); // -// const Vec3 expectedCenter = scale * newRotation * center + translation; -// const Mat3 expectedRotation = rotation * newRotation.transpose(); +// const Vec3 expectedCenter = scale * newRotation * center + translation; +// const Mat3 expectedRotation = rotation * newRotation.transpose(); // -// EXPECT_MATRIX_NEAR(transformedPose.center(), expectedCenter, precision); -// EXPECT_MATRIX_NEAR(transformedPose.rotation(), expectedRotation, precision); -//} +// EXPECT_MATRIX_NEAR(transformedPose.center(), expectedCenter, precision); +// EXPECT_MATRIX_NEAR(transformedPose.rotation(), expectedRotation, precision); +// } BOOST_AUTO_TEST_CASE(GetHomogeneousTest) { @@ -202,4 +201,3 @@ BOOST_AUTO_TEST_CASE(GetHomogeneousTest) EXPECT_MATRIX_NEAR(homogeneous, expectedHomogeneous, precision); } - diff --git a/src/aliceVision/geometry/Similarity3.hpp b/src/aliceVision/geometry/Similarity3.hpp index ee395278e3..67c2872f30 100644 --- a/src/aliceVision/geometry/Similarity3.hpp +++ b/src/aliceVision/geometry/Similarity3.hpp @@ -15,23 +15,23 @@ namespace geometry { // Define a 3D Similarity transform encoded as a 3D pose plus a scale struct Similarity3 { - Pose3 _pose; - double _scale; - - Similarity3(): _pose(Pose3()), _scale(1.0) {} - Similarity3(const Pose3 & pose, const double scale) : _pose(pose), _scale(scale) {} - - // Operators - Vec3 operator () (const Vec3 & point) const - { - return _scale * _pose(point); - } - - Pose3 operator () (const Pose3 & pose) const - { - return Pose3( pose.rotation() * _pose.rotation().transpose(), this->operator()(pose.center())); - } + Pose3 _pose; + double _scale; + + Similarity3() + : _pose(Pose3()), + _scale(1.0) + {} + Similarity3(const Pose3& pose, const double scale) + : _pose(pose), + _scale(scale) + {} + + // Operators + Vec3 operator()(const Vec3& point) const { return _scale * _pose(point); } + + Pose3 operator()(const Pose3& pose) const { return Pose3(pose.rotation() * _pose.rotation().transpose(), this->operator()(pose.center())); } }; -} // namespace geometry -} // namespace aliceVision +} // namespace geometry +} // namespace aliceVision diff --git a/src/aliceVision/geometry/frustumIntersection_test.cpp b/src/aliceVision/geometry/frustumIntersection_test.cpp index 2aa941f339..dbec2d005f 100644 --- a/src/aliceVision/geometry/frustumIntersection_test.cpp +++ b/src/aliceVision/geometry/frustumIntersection_test.cpp @@ -22,171 +22,156 @@ using namespace aliceVision; using namespace aliceVision::geometry; using namespace aliceVision::geometry::halfPlane; - //-- // Camera frustum intersection unit test //-- BOOST_AUTO_TEST_CASE(intersection) { - const int focal = 1000; - const int principal_Point = 500; - //-- Setup a circular camera rig or "cardioid". - const int iNviews = 4; - const int iNbPoints = 6; - const NViewDataSet d = - NRealisticCamerasRing( - iNviews, iNbPoints, - NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); - - // Test with infinite Frustum for each camera - { - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) + const int focal = 1000; + const int principal_Point = 500; + //-- Setup a circular camera rig or "cardioid". + const int iNviews = 4; + const int iNbPoints = 6; + const NViewDataSet d = NRealisticCamerasRing(iNviews, iNbPoints, NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); + + // Test with infinite Frustum for each camera { - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, d._K[i], d._R[i], d._C[i])); - BOOST_CHECK(vec_frustum[i].isInfinite()); + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) + { + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i], d._C[i])); + BOOST_CHECK(vec_frustum[i].isInfinite()); + } + + // Check that frustums have an overlap + for (int i = 0; i < iNviews; ++i) + for (int j = 0; j < iNviews; ++j) + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); } - // Check that frustums have an overlap - for (int i = 0; i < iNviews; ++i) - for (int j = 0; j < iNviews; ++j) - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); - } - - // Test with truncated frustum - { - // Build frustum with near and far plane defined by min/max depth per camera - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) + // Test with truncated frustum { - double minDepth = std::numeric_limits::max(); - double maxDepth = std::numeric_limits::min(); - for (int j=0; j < iNbPoints; ++j) - { - const double depth = Depth(d._R[i], d._t[i], d._X.col(j)); - if (depth < minDepth) - minDepth = depth; - if (depth > maxDepth) - maxDepth = depth; - } - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, - d._K[i], d._R[i], d._C[i], minDepth, maxDepth)); - BOOST_CHECK(vec_frustum[i].isTruncated()); + // Build frustum with near and far plane defined by min/max depth per camera + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) + { + double minDepth = std::numeric_limits::max(); + double maxDepth = std::numeric_limits::min(); + for (int j = 0; j < iNbPoints; ++j) + { + const double depth = Depth(d._R[i], d._t[i], d._X.col(j)); + if (depth < minDepth) + minDepth = depth; + if (depth > maxDepth) + maxDepth = depth; + } + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i], d._C[i], minDepth, maxDepth)); + BOOST_CHECK(vec_frustum[i].isTruncated()); + } + + // Check that frustums have an overlap + for (int i = 0; i < iNviews; ++i) + for (int j = 0; j < iNviews; ++j) + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); } - // Check that frustums have an overlap - for (int i = 0; i < iNviews; ++i) - for (int j = 0; j < iNviews; ++j) - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); - } - - //Test with partially truncated frustum - { - //Build frustum with near and far plane defined by a different value - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) + // Test with partially truncated frustum { - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, - d._K[i], d._R[i], d._C[i], 0.1, -1.0)); - BOOST_CHECK(vec_frustum[i].isPartiallyTruncated()); - } + // Build frustum with near and far plane defined by a different value + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) + { + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i], d._C[i], 0.1, -1.0)); + BOOST_CHECK(vec_frustum[i].isPartiallyTruncated()); + } - // Check that frustums have an overlap - for (int i = 0; i < iNviews; ++i) - for (int j = 0; j < iNviews; ++j) - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); - } + // Check that frustums have an overlap + for (int i = 0; i < iNviews; ++i) + for (int j = 0; j < iNviews; ++j) + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); + } } BOOST_AUTO_TEST_CASE(empty_intersection) { - // Create infinite frustum that do not share any space - //-- - // 4 cameras on a circle that look to the same direction - // Apply a 180° rotation to the rotation matrix in order to make the cameras - // don't share any visual hull - //-- - - const int focal = 1000; - const int principal_Point = 500; - const int iNviews = 4; - const int iNbPoints = 6; - const NViewDataSet d = - NRealisticCamerasRing( - iNviews, iNbPoints, - NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); - - // Test with infinite Frustum for each camera - { - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) - { - const Mat3 flipMatrix = RotationAroundY(degreeToRadian(180.0)); - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, d._K[i], d._R[i]*flipMatrix, d._C[i])); - BOOST_CHECK(vec_frustum[i].isInfinite()); - } + // Create infinite frustum that do not share any space + //-- + // 4 cameras on a circle that look to the same direction + // Apply a 180° rotation to the rotation matrix in order to make the cameras + // don't share any visual hull + //-- - // Test if the frustum have an overlap - for (int i=0; i < iNviews; ++i) + const int focal = 1000; + const int principal_Point = 500; + const int iNviews = 4; + const int iNbPoints = 6; + const NViewDataSet d = NRealisticCamerasRing(iNviews, iNbPoints, NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); + + // Test with infinite Frustum for each camera { - for (int j=0; j < iNviews; ++j) - { - if (i == j) // Same frustum (intersection must exist) + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) { - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); + const Mat3 flipMatrix = RotationAroundY(degreeToRadian(180.0)); + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i] * flipMatrix, d._C[i])); + BOOST_CHECK(vec_frustum[i].isInfinite()); } - else // different frustum + + // Test if the frustum have an overlap + for (int i = 0; i < iNviews; ++i) { - BOOST_CHECK(!vec_frustum[i].intersect(vec_frustum[j])); + for (int j = 0; j < iNviews; ++j) + { + if (i == j) // Same frustum (intersection must exist) + { + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); + } + else // different frustum + { + BOOST_CHECK(!vec_frustum[i].intersect(vec_frustum[j])); + } + } } - } } - } - // Test but with truncated frustum - { - // Build frustum with near and far plane defined by min/max depth per camera - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) + // Test but with truncated frustum { - double minDepth = std::numeric_limits::max(); - double maxDepth = std::numeric_limits::min(); - for (int j=0; j < iNbPoints; ++j) - { - const double depth = Depth(d._R[i], d._t[i], d._X.col(j)); - if (depth < minDepth) - minDepth = depth; - if (depth > maxDepth) - maxDepth = depth; - } - const Mat3 flipMatrix = RotationAroundY(degreeToRadian(180.0)); - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, - d._K[i], d._R[i]*flipMatrix, d._C[i], minDepth, maxDepth)); - BOOST_CHECK(vec_frustum[i].isTruncated()); - } - - // Test if the frustum have an overlap - for (int i=0; i < iNviews; ++i) - { - for (int j=0; j < iNviews; ++j) - { - if (i == j) // Same frustum (intersection must exist) + // Build frustum with near and far plane defined by min/max depth per camera + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) { - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); + double minDepth = std::numeric_limits::max(); + double maxDepth = std::numeric_limits::min(); + for (int j = 0; j < iNbPoints; ++j) + { + const double depth = Depth(d._R[i], d._t[i], d._X.col(j)); + if (depth < minDepth) + minDepth = depth; + if (depth > maxDepth) + maxDepth = depth; + } + const Mat3 flipMatrix = RotationAroundY(degreeToRadian(180.0)); + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i] * flipMatrix, d._C[i], minDepth, maxDepth)); + BOOST_CHECK(vec_frustum[i].isTruncated()); } - else // different frustum + + // Test if the frustum have an overlap + for (int i = 0; i < iNviews; ++i) { - BOOST_CHECK(!vec_frustum[i].intersect(vec_frustum[j])); + for (int j = 0; j < iNviews; ++j) + { + if (i == j) // Same frustum (intersection must exist) + { + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); + } + else // different frustum + { + BOOST_CHECK(!vec_frustum[i].intersect(vec_frustum[j])); + } + } } - } } - } } void createPanoramaScene(double coeff, NViewDataSet& d, const int iNviews, const int iNbPoints, const int principal_Point) @@ -198,9 +183,9 @@ void createPanoramaScene(double coeff, NViewDataSet& d, const int iNviews, const // 1 camera looks to iNviews different directions //-- - double field_of_view = (360.0*coeff)/iNviews; - double focalRatio = 0.5/(tan(0.5*degreeToRadian(field_of_view))); - double focalLengthPix=focalRatio*2*principal_Point; + double field_of_view = (360.0 * coeff) / iNviews; + double focalRatio = 0.5 / (tan(0.5 * degreeToRadian(field_of_view))); + double focalLengthPix = focalRatio * 2 * principal_Point; NViewDatasetConfigurator config(focalLengthPix, focalLengthPix, principal_Point, principal_Point, 1, 0); d._n = iNviews; d._K.resize(iNviews); @@ -210,105 +195,97 @@ void createPanoramaScene(double coeff, NViewDataSet& d, const int iNviews, const for (size_t i = 0; i < iNviews; ++i) { - Vec3 camera_center(0., 0., 0.); - const double theta = i * 2 * M_PI / iNviews; - d._C[i] = camera_center; - // Circle - Vec3 lookdir(sin(theta), 0.0, cos(theta)); // Y axis UP - - d._K[i] << config._fx, 0, config._cx, - 0, config._fy, config._cy, - 0, 0, 1; - d._R[i] = LookAt(lookdir); // Y axis UP - d._t[i] = -d._R[i] * camera_center; // [t]=[-RC] Cf HZ. + Vec3 camera_center(0., 0., 0.); + const double theta = i * 2 * M_PI / iNviews; + d._C[i] = camera_center; + // Circle + Vec3 lookdir(sin(theta), 0.0, cos(theta)); // Y axis UP + + d._K[i] << config._fx, 0, config._cx, 0, config._fy, config._cy, 0, 0, 1; + d._R[i] = LookAt(lookdir); // Y axis UP + d._t[i] = -d._R[i] * camera_center; // [t]=[-RC] Cf HZ. } - } BOOST_AUTO_TEST_CASE(panorama_intersection) { - // Create partially truncated frustum - //-- - // 1 camera looks to 4 different directions on a circle - // Create a panorama scene with a field of view - // more than 90° for each view which means no overlap - //-- - - const int principal_Point = 500; - // Setup a panorama camera rig: cameras rotations around a nodal point - const int iNviews = 4; - const int iNbPoints = 6; - NViewDataSet d; - double coeff = 1.2; // overlap coefficient: more than 1 means overlap - // create panorama scene - createPanoramaScene(coeff, d, iNviews, iNbPoints, principal_Point); - - //Test with partially truncated frustum - { - //Build frustum with near and far plane defined by a different value - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) - { - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, - d._K[i], d._R[i], d._t[i], 0.1, -1.0)); - BOOST_CHECK(vec_frustum[i].isPartiallyTruncated()); - } + // Create partially truncated frustum + //-- + // 1 camera looks to 4 different directions on a circle + // Create a panorama scene with a field of view + // more than 90° for each view which means no overlap + //-- + + const int principal_Point = 500; + // Setup a panorama camera rig: cameras rotations around a nodal point + const int iNviews = 4; + const int iNbPoints = 6; + NViewDataSet d; + double coeff = 1.2; // overlap coefficient: more than 1 means overlap + // create panorama scene + createPanoramaScene(coeff, d, iNviews, iNbPoints, principal_Point); - //Check that there is overlap between all frustums - for (int i = 0; i < iNviews; ++i) + // Test with partially truncated frustum { - int j = (i+1) % iNviews; - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); + // Build frustum with near and far plane defined by a different value + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) + { + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i], d._t[i], 0.1, -1.0)); + BOOST_CHECK(vec_frustum[i].isPartiallyTruncated()); + } + + // Check that there is overlap between all frustums + for (int i = 0; i < iNviews; ++i) + { + int j = (i + 1) % iNviews; + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[j])); - int k = (i-1+iNviews) % iNviews; - BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[k])); + int k = (i - 1 + iNviews) % iNviews; + BOOST_CHECK(vec_frustum[i].intersect(vec_frustum[k])); + } } - } } BOOST_AUTO_TEST_CASE(panorama_without_intersection) { - // Create partially truncated frustum - //-- - // 1 camera looks to 4 different directions on a circle - // Create a panorama scene with a field of view - // less than 90° for each view which means no overlap - //-- - - const int principal_Point = 500; - // Setup a panorama camera rig: cameras rotations around a nodal point - const int iNviews = 4; - const int iNbPoints = 6; - NViewDataSet d; - double coeff = 0.8; // overlap coefficient: less than 1 means no overlap - - //create panorama scene - createPanoramaScene(coeff, d, iNviews, iNbPoints, principal_Point); - - //Test with partially truncated frustum - { - //Build frustum with near and far plane defined by a different value - std::vector vec_frustum; - for (int i=0; i < iNviews; ++i) - { - vec_frustum.push_back( - Frustum(principal_Point*2, principal_Point*2, - d._K[i], d._R[i], d._t[i], 0.1, -1.0)); - BOOST_CHECK(vec_frustum[i].isPartiallyTruncated()); - } + // Create partially truncated frustum + //-- + // 1 camera looks to 4 different directions on a circle + // Create a panorama scene with a field of view + // less than 90° for each view which means no overlap + //-- - //Check that there is no overlap between all frustums - for (int i = 0; i < iNviews; ++i) + const int principal_Point = 500; + // Setup a panorama camera rig: cameras rotations around a nodal point + const int iNviews = 4; + const int iNbPoints = 6; + NViewDataSet d; + double coeff = 0.8; // overlap coefficient: less than 1 means no overlap + + // create panorama scene + createPanoramaScene(coeff, d, iNviews, iNbPoints, principal_Point); + + // Test with partially truncated frustum { - for (int j = 0; j < iNviews; ++j) - { - if(i == j) - continue; + // Build frustum with near and far plane defined by a different value + std::vector vec_frustum; + for (int i = 0; i < iNviews; ++i) + { + vec_frustum.push_back(Frustum(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i], d._t[i], 0.1, -1.0)); + BOOST_CHECK(vec_frustum[i].isPartiallyTruncated()); + } + + // Check that there is no overlap between all frustums + for (int i = 0; i < iNviews; ++i) + { + for (int j = 0; j < iNviews; ++j) + { + if (i == j) + continue; - BOOST_CHECK(!vec_frustum[i].intersect(vec_frustum[j])); - } + BOOST_CHECK(!vec_frustum[i].intersect(vec_frustum[j])); + } + } } - } } - diff --git a/src/aliceVision/geometry/halfSpaceIntersection_test.cpp b/src/aliceVision/geometry/halfSpaceIntersection_test.cpp index 4d6442d9e6..885eb018fa 100644 --- a/src/aliceVision/geometry/halfSpaceIntersection_test.cpp +++ b/src/aliceVision/geometry/halfSpaceIntersection_test.cpp @@ -18,49 +18,49 @@ using namespace aliceVision; using namespace aliceVision::geometry::halfPlane; -BOOST_AUTO_TEST_CASE(ExistingSubspace) { +BOOST_AUTO_TEST_CASE(ExistingSubspace) +{ + std::vector vec_hplanes; - std::vector vec_hplanes; + Vec3 a, b, c; + a << 0, 0, 0; + b << 1, 0, 0; + c << 0, 1, 0; - Vec3 a,b,c; - a << 0,0,0; - b << 1,0,0; - c << 0,1,0; + Vec3 offset(0, 0, 2); + vec_hplanes.push_back(Half_plane_p(a, b, c)); + vec_hplanes.push_back(Half_plane_p(a + offset, b + offset, c + offset)); - Vec3 offset(0,0,2); - vec_hplanes.push_back(Half_plane_p(a,b,c)); - vec_hplanes.push_back(Half_plane_p(a+offset,b+offset,c+offset)); - - // /\ + // /\ // ___|____ z = 2 - // - // /\ + // + // /\ // ___|____ z = 0 - BOOST_CHECK(isNotEmpty(vec_hplanes)); + BOOST_CHECK(isNotEmpty(vec_hplanes)); } -BOOST_AUTO_TEST_CASE(EmptyIntersection) { - - std::vector vec_hplanes; +BOOST_AUTO_TEST_CASE(EmptyIntersection) +{ + std::vector vec_hplanes; - Vec3 a,b,c; - a << 0,0,0; - b << 1,0,0; - c << 0,1,0; + Vec3 a, b, c; + a << 0, 0, 0; + b << 1, 0, 0; + c << 0, 1, 0; - Vec3 offset(0,0,2); - vec_hplanes.push_back(Half_plane_p(a,b,c)); - vec_hplanes.push_back(Half_plane_p(a+offset,b+offset,c+offset)); - vec_hplanes[1].normal() *= -1; //invert the side of the half plane + Vec3 offset(0, 0, 2); + vec_hplanes.push_back(Half_plane_p(a, b, c)); + vec_hplanes.push_back(Half_plane_p(a + offset, b + offset, c + offset)); + vec_hplanes[1].normal() *= -1; // invert the side of the half plane - // /\ + // /\ // ___|____ z = 0 - // - // - // _______ z = -2 - // | - // \/ + // + // + // _______ z = -2 + // | + // \/ - BOOST_CHECK(!isNotEmpty(vec_hplanes)); + BOOST_CHECK(!isNotEmpty(vec_hplanes)); } diff --git a/src/aliceVision/geometry/lie.hpp b/src/aliceVision/geometry/lie.hpp index 80f5ef01cf..c901074a7a 100644 --- a/src/aliceVision/geometry/lie.hpp +++ b/src/aliceVision/geometry/lie.hpp @@ -19,7 +19,8 @@ using Matrix = Eigen::Matrix; * @param algebra the 1D vector * @return a 2*2 S0(2) matrix */ -inline Eigen::Matrix2d expm(double algebra) { +inline Eigen::Matrix2d expm(double algebra) +{ Eigen::Matrix2d ret; ret(0, 0) = cos(algebra); @@ -29,7 +30,7 @@ inline Eigen::Matrix2d expm(double algebra) { return ret; } -} //namespace SO2 +} // namespace SO2 namespace SO3 { @@ -40,7 +41,8 @@ using Matrix = Eigen::Matrix; * @param in the 3D vector * @return a skew symmetric matrix */ -inline Eigen::Matrix3d skew(const Eigen::Vector3d& in) { +inline Eigen::Matrix3d skew(const Eigen::Vector3d& in) +{ Eigen::Matrix3d ret; ret.fill(0); @@ -60,10 +62,12 @@ inline Eigen::Matrix3d skew(const Eigen::Vector3d& in) { * @param algebra the 3D vector * @return a 3*3 SO(3) matrix */ -inline Eigen::Matrix3d expm(const Eigen::Vector3d& algebra) { +inline Eigen::Matrix3d expm(const Eigen::Vector3d& algebra) +{ const double angle = algebra.norm(); - if (angle < std::numeric_limits::epsilon()) { + if (angle < std::numeric_limits::epsilon()) + { return Eigen::Matrix3d::Identity(); } @@ -80,7 +84,8 @@ inline Eigen::Matrix3d expm(const Eigen::Vector3d& algebra) { * @param R the input rotation matrix * @return the algebra */ -inline Eigen::Vector3d logm(const Eigen::Matrix3d& R) { +inline Eigen::Vector3d logm(const Eigen::Matrix3d& R) +{ Eigen::Vector3d ret; const double p1 = R(2, 1) - R(1, 2); @@ -88,15 +93,18 @@ inline Eigen::Vector3d logm(const Eigen::Matrix3d& R) { const double p3 = R(1, 0) - R(0, 1); double costheta = (R.trace() - 1.0) / 2.0; - if (costheta < -1.0) { + if (costheta < -1.0) + { costheta = -1.0; } - if (costheta > 1.0) { + if (costheta > 1.0) + { costheta = 1.0; } - if (1.0 - costheta < 1e-24) { + if (1.0 - costheta < 1e-24) + { ret.fill(0); return ret; } @@ -110,8 +118,7 @@ inline Eigen::Vector3d logm(const Eigen::Matrix3d& R) { return ret; } -} //namespace SO3 - +} // namespace SO3 namespace SE3 { @@ -122,7 +129,8 @@ using Matrix = Eigen::Matrix; * @param algebra the 6D vector * @return a 4*4 SE(3) matrix */ -inline Eigen::Matrix4d expm(const Eigen::Matrix& algebra) { +inline Eigen::Matrix4d expm(const Eigen::Matrix& algebra) +{ Eigen::Matrix4d ret; ret.setIdentity(); @@ -130,15 +138,16 @@ inline Eigen::Matrix4d expm(const Eigen::Matrix& algebra) { const Eigen::Vector3d vecT = algebra.block<3, 1>(3, 0); double angle = vecR.norm(); - if (angle < std::numeric_limits::epsilon()) { + if (angle < std::numeric_limits::epsilon()) + { ret.setIdentity(); ret.block<3, 1>(0, 3) = vecT; return ret; } const Eigen::Matrix3d omega = SO3::skew(vecR); - const Eigen::Matrix3d V = Eigen::Matrix3d::Identity() + ((1.0 - cos(angle)) / (angle * angle)) - * omega + ((angle - sin(angle)) / (angle * angle * angle)) * omega * omega; + const Eigen::Matrix3d V = + Eigen::Matrix3d::Identity() + ((1.0 - cos(angle)) / (angle * angle)) * omega + ((angle - sin(angle)) / (angle * angle * angle)) * omega * omega; ret.block<3, 3>(0, 0) = SO3::expm(vecR); ret.block<3, 1>(0, 3) = V * vecT; @@ -146,6 +155,6 @@ inline Eigen::Matrix4d expm(const Eigen::Matrix& algebra) { return ret; } -} //namespace SE3 +} // namespace SE3 -} //namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/geometry/rigidTransformation3D.cpp b/src/aliceVision/geometry/rigidTransformation3D.cpp index feb3b2a9a0..f9a88a0d9d 100644 --- a/src/aliceVision/geometry/rigidTransformation3D.cpp +++ b/src/aliceVision/geometry/rigidTransformation3D.cpp @@ -12,169 +12,161 @@ namespace aliceVision { namespace geometry { -int lm_SRTRefine_functor::operator()(const Vec &x, Vec &fvec) const +int lm_SRTRefine_functor::operator()(const Vec& x, Vec& fvec) const { - // convert x to rotation matrix and a translation vector and a Scale factor - // x = {tx,ty,tz,anglex,angley,anglez,S} - Vec3 transAdd = x.block<3,1>(0,0); - Vec3 rot = x.block<3,1>(3,0); - double Sadd = x(6); - - //Build the rotation matrix - Mat3 Rcor = - (Eigen::AngleAxis(rot(0), Vec3::UnitX()) - * Eigen::AngleAxis(rot(1), Vec3::UnitY()) - * Eigen::AngleAxis(rot(2), Vec3::UnitZ())).toRotationMatrix(); - - const Mat3 nR = _R*Rcor; - const Vec3 nt = _t+transAdd; - const double nS = _S+Sadd; - - // Evaluate re-projection errors - Vec3 proj; - for (Mat::Index i = 0; i < _x1.cols(); ++i) - { - proj = _x2.col(i) - (nS * nR * (_x1.col(i)) + nt); - fvec[i*3] = proj(0); - fvec[i*3+1] = proj(1); - fvec[i*3+2] = proj(2); - } - return 0; + // convert x to rotation matrix and a translation vector and a Scale factor + // x = {tx,ty,tz,anglex,angley,anglez,S} + Vec3 transAdd = x.block<3, 1>(0, 0); + Vec3 rot = x.block<3, 1>(3, 0); + double Sadd = x(6); + + // Build the rotation matrix + Mat3 Rcor = (Eigen::AngleAxis(rot(0), Vec3::UnitX()) * Eigen::AngleAxis(rot(1), Vec3::UnitY()) * + Eigen::AngleAxis(rot(2), Vec3::UnitZ())) + .toRotationMatrix(); + + const Mat3 nR = _R * Rcor; + const Vec3 nt = _t + transAdd; + const double nS = _S + Sadd; + + // Evaluate re-projection errors + Vec3 proj; + for (Mat::Index i = 0; i < _x1.cols(); ++i) + { + proj = _x2.col(i) - (nS * nR * (_x1.col(i)) + nt); + fvec[i * 3] = proj(0); + fvec[i * 3 + 1] = proj(1); + fvec[i * 3 + 2] = proj(2); + } + return 0; } -int lm_RRefine_functor::operator()(const Vec &x, Vec &fvec) const +int lm_RRefine_functor::operator()(const Vec& x, Vec& fvec) const { - // convert x to rotation matrix - // x = {anglex,angley,anglez} - Vec3 rot = x.block<3,1>(0,0); - - //Build the rotation matrix - Mat3 Rcor = - (Eigen::AngleAxis(rot(0), Vec3::UnitX()) - * Eigen::AngleAxis(rot(1), Vec3::UnitY()) - * Eigen::AngleAxis(rot(2), Vec3::UnitZ())).toRotationMatrix(); - - const Mat3 nR = _R*Rcor; - const Vec3 nt = _t; - const double nS = _S; - - // Evaluate re-projection errors - Vec3 proj; - for (Mat::Index i = 0; i < _x1.cols(); ++i) - { - proj = _x2.col(i) - (nS * nR * (_x1.col(i)) + nt); - fvec[i*3] = proj(0); - fvec[i*3+1] = proj(1); - fvec[i*3+2] = proj(2); - } - return 0; + // convert x to rotation matrix + // x = {anglex,angley,anglez} + Vec3 rot = x.block<3, 1>(0, 0); + + // Build the rotation matrix + Mat3 Rcor = (Eigen::AngleAxis(rot(0), Vec3::UnitX()) * Eigen::AngleAxis(rot(1), Vec3::UnitY()) * + Eigen::AngleAxis(rot(2), Vec3::UnitZ())) + .toRotationMatrix(); + + const Mat3 nR = _R * Rcor; + const Vec3 nt = _t; + const double nS = _S; + + // Evaluate re-projection errors + Vec3 proj; + for (Mat::Index i = 0; i < _x1.cols(); ++i) + { + proj = _x2.col(i) - (nS * nR * (_x1.col(i)) + nt); + fvec[i * 3] = proj(0); + fvec[i * 3 + 1] = proj(1); + fvec[i * 3 + 2] = proj(2); + } + return 0; } -void Refine_RTS(const Mat &x1, - const Mat &x2, - double &S, - Vec3 &t, - Mat3 &R) +void Refine_RTS(const Mat& x1, const Mat& x2, double& S, Vec3& t, Mat3& R) { - { - lm_SRTRefine_functor functor(7, 3*x1.cols(), x1, x2, S, R, t); + { + lm_SRTRefine_functor functor(7, 3 * x1.cols(), x1, x2, S, R, t); - Eigen::NumericalDiff numDiff(functor); + Eigen::NumericalDiff numDiff(functor); - Eigen::LevenbergMarquardt > lm(numDiff); - lm.parameters.maxfev = 1000; - Vec xlm = Vec::Zero(7); // The deviation vector {tx,ty,tz,rotX,rotY,rotZ,S} + Eigen::LevenbergMarquardt> lm(numDiff); + lm.parameters.maxfev = 1000; + Vec xlm = Vec::Zero(7); // The deviation vector {tx,ty,tz,rotX,rotY,rotZ,S} - lm.minimize(xlm); + lm.minimize(xlm); - Vec3 transAdd = xlm.block<3,1>(0,0); - Vec3 rot = xlm.block<3,1>(3,0); - double SAdd = xlm(6); + Vec3 transAdd = xlm.block<3, 1>(0, 0); + Vec3 rot = xlm.block<3, 1>(3, 0); + double SAdd = xlm(6); - //Build the rotation matrix - Mat3 Rcor = - (Eigen::AngleAxis(rot(0), Vec3::UnitX()) - * Eigen::AngleAxis(rot(1), Vec3::UnitY()) - * Eigen::AngleAxis(rot(2), Vec3::UnitZ())).toRotationMatrix(); + // Build the rotation matrix + Mat3 Rcor = (Eigen::AngleAxis(rot(0), Vec3::UnitX()) * Eigen::AngleAxis(rot(1), Vec3::UnitY()) * + Eigen::AngleAxis(rot(2), Vec3::UnitZ())) + .toRotationMatrix(); - R = R * Rcor; - t = t + transAdd; - S = S + SAdd; - } + R = R * Rcor; + t = t + transAdd; + S = S + SAdd; + } - // Refine rotation - { - lm_RRefine_functor functor(3, 3*x1.cols(), x1, x2, S, R, t); + // Refine rotation + { + lm_RRefine_functor functor(3, 3 * x1.cols(), x1, x2, S, R, t); - Eigen::NumericalDiff numDiff(functor); + Eigen::NumericalDiff numDiff(functor); - Eigen::LevenbergMarquardt > lm(numDiff); - lm.parameters.maxfev = 1000; - Vec xlm = Vec::Zero(3); // The deviation vector {rotX,rotY,rotZ} + Eigen::LevenbergMarquardt> lm(numDiff); + lm.parameters.maxfev = 1000; + Vec xlm = Vec::Zero(3); // The deviation vector {rotX,rotY,rotZ} - lm.minimize(xlm); + lm.minimize(xlm); - Vec3 rot = xlm.block<3,1>(0,0); + Vec3 rot = xlm.block<3, 1>(0, 0); - //Build the rotation matrix - Mat3 Rcor = - (Eigen::AngleAxis(rot(0), Vec3::UnitX()) - * Eigen::AngleAxis(rot(1), Vec3::UnitY()) - * Eigen::AngleAxis(rot(2), Vec3::UnitZ())).toRotationMatrix(); + // Build the rotation matrix + Mat3 Rcor = (Eigen::AngleAxis(rot(0), Vec3::UnitX()) * Eigen::AngleAxis(rot(1), Vec3::UnitY()) * + Eigen::AngleAxis(rot(2), Vec3::UnitZ())) + .toRotationMatrix(); - R = R * Rcor; - } + R = R * Rcor; + } } bool ACRansac_FindRTS(const Mat& x1, const Mat& x2, - std::mt19937 &randomNumberGenerator, + std::mt19937& randomNumberGenerator, double& S, Vec3& t, Mat3& R, std::vector& vec_inliers, bool refine) { - assert(3 == x1.rows()); - assert(3 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); + assert(3 == x1.rows()); + assert(3 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); - const std::size_t numIterations = 1024; - const double dPrecision = std::numeric_limits::infinity(); + const std::size_t numIterations = 1024; + const double dPrecision = std::numeric_limits::infinity(); - using SolverT = geometry::RTSSolver; - using KernelT = ACKernelAdaptor_PointsRegistrationSRT; + using SolverT = geometry::RTSSolver; + using KernelT = ACKernelAdaptor_PointsRegistrationSRT; - const KernelT kernel = KernelT(x1, x2); + const KernelT kernel = KernelT(x1, x2); - robustEstimation::MatrixModel RTS; + robustEstimation::MatrixModel RTS; - // robust estimation of the Projection matrix and its precision - const std::pair ACRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, vec_inliers, numIterations, &RTS, dPrecision); + // robust estimation of the Projection matrix and its precision + const std::pair ACRansacOut = + robustEstimation::ACRANSAC(kernel, randomNumberGenerator, vec_inliers, numIterations, &RTS, dPrecision); - const bool good = decomposeRTS(RTS.getMatrix(), S, t, R); + const bool good = decomposeRTS(RTS.getMatrix(), S, t, R); - // return if it is not good or refine is not required - if(!good || !refine) - return good; - - const std::size_t nbInliers = vec_inliers.size(); - //only refine the inliers - Mat inliers1 = Mat3X(3, nbInliers); - Mat inliers2 = Mat3X(3, nbInliers); + // return if it is not good or refine is not required + if (!good || !refine) + return good; - for(std::size_t i = 0; i < nbInliers; ++i) - { - inliers1.col(i) = x1.col(vec_inliers[i]); - inliers2.col(i) = x2.col(vec_inliers[i]); - } + const std::size_t nbInliers = vec_inliers.size(); + // only refine the inliers + Mat inliers1 = Mat3X(3, nbInliers); + Mat inliers2 = Mat3X(3, nbInliers); - geometry::Refine_RTS(inliers1, inliers2, S, t, R); + for (std::size_t i = 0; i < nbInliers; ++i) + { + inliers1.col(i) = x1.col(vec_inliers[i]); + inliers2.col(i) = x2.col(vec_inliers[i]); + } - return good; + geometry::Refine_RTS(inliers1, inliers2, S, t, R); + return good; } -} // namespace geometry -} // namespace aliceVision +} // namespace geometry +} // namespace aliceVision diff --git a/src/aliceVision/geometry/rigidTransformation3D.hpp b/src/aliceVision/geometry/rigidTransformation3D.hpp index fe712fc392..5bb4376f4a 100644 --- a/src/aliceVision/geometry/rigidTransformation3D.hpp +++ b/src/aliceVision/geometry/rigidTransformation3D.hpp @@ -12,10 +12,8 @@ #include #include -namespace aliceVision -{ -namespace geometry -{ +namespace aliceVision { +namespace geometry { /** * @brief Compose a similarity matrix given a scale factor, a rotation matrix and @@ -45,11 +43,11 @@ inline bool decomposeRTS(const Mat4& RTS, double& S, Vec3& t, Mat3& R) { // Check critical cases R = RTS.topLeftCorner<3, 3>(); - if(R.determinant() < 0) + if (R.determinant() < 0) return false; S = pow(R.determinant(), 1.0 / 3.0); // Check for degenerate case (if all points have the same value...) - if(S < std::numeric_limits::epsilon()) + if (S < std::numeric_limits::epsilon()) return false; // Extract transformation parameters @@ -78,7 +76,7 @@ inline bool decomposeRTS(const Mat4& RTS, double& S, Vec3& t, Mat3& R) inline bool FindRTS(const Mat& x1, const Mat& x2, double& S, Vec3& t, Mat3& R) { - if(x1.cols() < 3 || x2.cols() < 3) + if (x1.cols() < 3 || x2.cols() < 3) return false; assert(3 == x1.rows()); @@ -99,16 +97,14 @@ inline bool FindRTS(const Mat& x1, const Mat& x2, double& S, Vec3& t, Mat3& R) // Eigen LM functor to refine translation, Rotation and Scale parameter. struct lm_SRTRefine_functor : LMFunctor { - lm_SRTRefine_functor(int inputs, int values, const Mat& x1, const Mat& x2, const double& S, const Mat3& R, - const Vec& t) - : LMFunctor(inputs, values) - , _x1(x1) - , _x2(x2) - , _t(t) - , _R(R) - , _S(S) - { - } + lm_SRTRefine_functor(int inputs, int values, const Mat& x1, const Mat& x2, const double& S, const Mat3& R, const Vec& t) + : LMFunctor(inputs, values), + _x1(x1), + _x2(x2), + _t(t), + _R(R), + _S(S) + {} int operator()(const Vec& x, Vec& fvec) const; @@ -121,16 +117,14 @@ struct lm_SRTRefine_functor : LMFunctor // Eigen LM functor to refine Rotation. struct lm_RRefine_functor : LMFunctor { - lm_RRefine_functor(int inputs, int values, const Mat& x1, const Mat& x2, const double& S, const Mat3& R, - const Vec& t) - : LMFunctor(inputs, values) - , _x1(x1) - , _x2(x2) - , _t(t) - , _R(R) - , _S(S) - { - } + lm_RRefine_functor(int inputs, int values, const Mat& x1, const Mat& x2, const double& S, const Mat3& R, const Vec& t) + : LMFunctor(inputs, values), + _x1(x1), + _x2(x2), + _t(t), + _R(R), + _S(S) + {} int operator()(const Vec& x, Vec& fvec) const; @@ -159,7 +153,7 @@ void Refine_RTS(const Mat& x1, const Mat& x2, double& S, Vec3& t, Mat3& R); */ class RTSSolver : public robustEstimation::ISolver> { -public: + public: /** * @brief Return the minimum number of required samples * @return minimum number of required samples @@ -178,7 +172,9 @@ class RTSSolver : public robustEstimation::ISolver(Eigen::umeyama(pts1, pts2, true))); } - void solve(const Mat& x1, const Mat& x2, std::vector>& models, + void solve(const Mat& x1, + const Mat& x2, + std::vector>& models, const std::vector& weights) const override { throw std::logic_error("RTSSolver does not support problem solving with weights."); @@ -213,18 +209,18 @@ struct RTSSquaredResidualError /** * @brief The kernel to use for ACRansac */ -template > +template> class ACKernelAdaptor_PointsRegistrationSRT { -public: + public: using SolverT = SolverT_; using ModelT = ModelT_; using ErrorT = ErrorT_; ACKernelAdaptor_PointsRegistrationSRT(const Mat& xA, const Mat& xB) - : x1_(xA) - , x2_(xB) - , _logalpha0(log10(M_PI)) //@todo WTF? + : x1_(xA), + x2_(xB), + _logalpha0(log10(M_PI)) //@todo WTF? { assert(3 == x1_.rows()); assert(x1_.rows() == x2_.rows()); @@ -252,21 +248,18 @@ class ACKernelAdaptor_PointsRegistrationSRT _kernelSolver.solve(x1, x2, models); } - double error(std::size_t sample, const ModelT& model) const - { - return Square(_errorEstimator.error(model, x1_.col(sample), x2_.col(sample))); - } + double error(std::size_t sample, const ModelT& model) const { return Square(_errorEstimator.error(model, x1_.col(sample), x2_.col(sample))); } void errors(const ModelT& model, std::vector& vec_errors) const { vec_errors.resize(x1_.cols()); - for(std::size_t sample = 0; sample < x1_.cols(); ++sample) + for (std::size_t sample = 0; sample < x1_.cols(); ++sample) vec_errors[sample] = error(sample, model); } std::size_t nbSamples() const { return static_cast(x1_.cols()); } - void unnormalize(ModelT& model) const {} //-- Do nothing, no normalization + void unnormalize(ModelT& model) const {} //-- Do nothing, no normalization double logalpha0() const { return _logalpha0; } @@ -278,9 +271,9 @@ class ACKernelAdaptor_PointsRegistrationSRT double unormalizeError(double val) const { return sqrt(val); } -private: - Mat x1_, x2_; // normalized input data - double _logalpha0; // alpha0 is used to make the error scale invariant + private: + Mat x1_, x2_; // normalized input data + double _logalpha0; // alpha0 is used to make the error scale invariant SolverT _kernelSolver; ErrorT _errorEstimator; @@ -300,8 +293,14 @@ class ACKernelAdaptor_PointsRegistrationSRT * @return true if the found transformation is a similarity * @see FindRTS() */ -bool ACRansac_FindRTS(const Mat& x1, const Mat& x2, std::mt19937& randomNumberGenerator, double& S, Vec3& t, Mat3& R, - std::vector& vec_inliers, bool refine = false); +bool ACRansac_FindRTS(const Mat& x1, + const Mat& x2, + std::mt19937& randomNumberGenerator, + double& S, + Vec3& t, + Mat3& R, + std::vector& vec_inliers, + bool refine = false); /** * @brief Uses AC ransac to robustly estimate the similarity between two sets of @@ -316,18 +315,22 @@ bool ACRansac_FindRTS(const Mat& x1, const Mat& x2, std::mt19937& randomNumberGe * @see geometry::FindRTS() * @see geometry::ACRansac_FindRTS() */ -inline bool ACRansac_FindRTS(const Mat& x1, const Mat& x2, std::mt19937& randomNumberGenerator, Mat4& RTS, - std::vector& vec_inliers, bool refine = false) +inline bool ACRansac_FindRTS(const Mat& x1, + const Mat& x2, + std::mt19937& randomNumberGenerator, + Mat4& RTS, + std::vector& vec_inliers, + bool refine = false) { double S; Vec3 t; Mat3 R; const bool good = ACRansac_FindRTS(x1, x2, randomNumberGenerator, S, t, R, vec_inliers, refine); - if(good) + if (good) composeRTS(S, t, R, RTS); return good; } -} // namespace geometry -} // namespace aliceVision +} // namespace geometry +} // namespace aliceVision diff --git a/src/aliceVision/geometry/rigidTransformation3D_test.cpp b/src/aliceVision/geometry/rigidTransformation3D_test.cpp index 36fe6d8970..18a7a9bd15 100644 --- a/src/aliceVision/geometry/rigidTransformation3D_test.cpp +++ b/src/aliceVision/geometry/rigidTransformation3D_test.cpp @@ -20,239 +20,243 @@ using namespace aliceVision::geometry; BOOST_AUTO_TEST_CASE(SRT_precision_Experiment_ScaleOnly) { - makeRandomOperationsReproducible(); - - const std::size_t nbPoints = 10; - Mat x1 = Mat::Random(3, nbPoints); - Mat x2 = x1; - - const double scale = 2; - Mat3 rot = Mat3::Identity(); - Vec3 t(0, 0, 0); - - for(std::size_t i = 0; i < nbPoints; ++i) - { - Vec3 pt = x1.col(i); - x2.col(i) = (scale * rot * pt + t); - } - - // Compute the Similarity transform - double Sc = 0; - Mat3 Rc; - Vec3 tc; - FindRTS(x1, x2, Sc, tc, Rc); - Refine_RTS(x1, x2, Sc, tc, Rc); - - ALICEVISION_LOG_DEBUG( - "Scale " << Sc << "\n" << - "Rot \n" << Rc << "\n" << - "t " << tc.transpose()); + makeRandomOperationsReproducible(); + + const std::size_t nbPoints = 10; + Mat x1 = Mat::Random(3, nbPoints); + Mat x2 = x1; + + const double scale = 2; + Mat3 rot = Mat3::Identity(); + Vec3 t(0, 0, 0); + + for (std::size_t i = 0; i < nbPoints; ++i) + { + Vec3 pt = x1.col(i); + x2.col(i) = (scale * rot * pt + t); + } + + // Compute the Similarity transform + double Sc = 0; + Mat3 Rc; + Vec3 tc; + FindRTS(x1, x2, Sc, tc, Rc); + Refine_RTS(x1, x2, Sc, tc, Rc); + + ALICEVISION_LOG_DEBUG("Scale " << Sc << "\n" + << "Rot \n" + << Rc << "\n" + << "t " << tc.transpose()); } BOOST_AUTO_TEST_CASE(SRT_precision_Experiment_ScaleAndRot) { - makeRandomOperationsReproducible(); - - const std::size_t nbPoints = 10; - Mat x1 = Mat::Random(3, nbPoints); - Mat x2 = x1; - - const double scale = 2; - Mat3 rot = (Eigen::AngleAxis(.2, Vec3::UnitX()) - * Eigen::AngleAxis(.3, Vec3::UnitY()) - * Eigen::AngleAxis(.6, Vec3::UnitZ())).toRotationMatrix(); - Vec3 t(0, 0, 0); - - for(std::size_t i = 0; i < nbPoints; ++i) - { - Vec3 pt = x1.col(i); - x2.col(i) = (scale * rot * pt + t); - } - - // Compute the Similarity transform - double Sc = 0; - Mat3 Rc; - Vec3 tc; - FindRTS(x1, x2, Sc, tc, Rc); - Refine_RTS(x1, x2, Sc, tc, Rc); - - ALICEVISION_LOG_DEBUG( - "Scale " << Sc << "\n" << - "Rot \n" << Rc << "\n" << - "t " << tc.transpose()); - - ALICEVISION_LOG_DEBUG("GT\n" << - "Scale " << scale << "\n" << - "Rot \n" << rot << "\n" << - "t " << t.transpose()); + makeRandomOperationsReproducible(); + + const std::size_t nbPoints = 10; + Mat x1 = Mat::Random(3, nbPoints); + Mat x2 = x1; + + const double scale = 2; + Mat3 rot = + (Eigen::AngleAxis(.2, Vec3::UnitX()) * Eigen::AngleAxis(.3, Vec3::UnitY()) * Eigen::AngleAxis(.6, Vec3::UnitZ())) + .toRotationMatrix(); + Vec3 t(0, 0, 0); + + for (std::size_t i = 0; i < nbPoints; ++i) + { + Vec3 pt = x1.col(i); + x2.col(i) = (scale * rot * pt + t); + } + + // Compute the Similarity transform + double Sc = 0; + Mat3 Rc; + Vec3 tc; + FindRTS(x1, x2, Sc, tc, Rc); + Refine_RTS(x1, x2, Sc, tc, Rc); + + ALICEVISION_LOG_DEBUG("Scale " << Sc << "\n" + << "Rot \n" + << Rc << "\n" + << "t " << tc.transpose()); + + ALICEVISION_LOG_DEBUG("GT\n" + << "Scale " << scale << "\n" + << "Rot \n" + << rot << "\n" + << "t " << t.transpose()); } BOOST_AUTO_TEST_CASE(SRT_precision_Experiment_ScaleRotTranslation) { - makeRandomOperationsReproducible(); - - const std::size_t nbPoints = 10; - Mat x1 = Mat::Random(3, nbPoints); - Mat x2 = x1; - - const double scale = 2; - Mat3 rot = (Eigen::AngleAxis(.2, Vec3::UnitX()) - * Eigen::AngleAxis(.3, Vec3::UnitY()) - * Eigen::AngleAxis(.6, Vec3::UnitZ())).toRotationMatrix(); - Vec3 t(0.5, -0.3, .38); - - for(std::size_t i = 0; i < nbPoints; ++i) - { - Vec3 pt = x1.col(i); - x2.col(i) = (scale * rot * pt + t); - } - - // Compute the Similarity transform - double Sc = 0; - Mat3 Rc; - Vec3 tc; - FindRTS(x1, x2, Sc, tc, Rc); - Refine_RTS(x1, x2, Sc, tc, Rc); - - ALICEVISION_LOG_DEBUG( - "Scale " << Sc << "\n" << - "Rot \n" << Rc << "\n" << - "t " << tc.transpose()); - - ALICEVISION_LOG_DEBUG("GT\n" << - "Scale " << scale << "\n" << - "Rot \n" << rot << "\n" << - "t " << t.transpose()); + makeRandomOperationsReproducible(); + + const std::size_t nbPoints = 10; + Mat x1 = Mat::Random(3, nbPoints); + Mat x2 = x1; + + const double scale = 2; + Mat3 rot = + (Eigen::AngleAxis(.2, Vec3::UnitX()) * Eigen::AngleAxis(.3, Vec3::UnitY()) * Eigen::AngleAxis(.6, Vec3::UnitZ())) + .toRotationMatrix(); + Vec3 t(0.5, -0.3, .38); + + for (std::size_t i = 0; i < nbPoints; ++i) + { + Vec3 pt = x1.col(i); + x2.col(i) = (scale * rot * pt + t); + } + + // Compute the Similarity transform + double Sc = 0; + Mat3 Rc; + Vec3 tc; + FindRTS(x1, x2, Sc, tc, Rc); + Refine_RTS(x1, x2, Sc, tc, Rc); + + ALICEVISION_LOG_DEBUG("Scale " << Sc << "\n" + << "Rot \n" + << Rc << "\n" + << "t " << tc.transpose()); + + ALICEVISION_LOG_DEBUG("GT\n" + << "Scale " << scale << "\n" + << "Rot \n" + << rot << "\n" + << "t " << t.transpose()); } BOOST_AUTO_TEST_CASE(SRT_precision_ACRANSAC_noNoise) { - makeRandomOperationsReproducible(); - - std::mt19937 randomNumberGenerator; - const std::size_t nbPoints = 100; - Mat x1 = Mat::Random(3, nbPoints); - Mat x2 = x1; - - const double scale = 2; - Mat3 rot = (Eigen::AngleAxis(.2, Vec3::UnitX()) - * Eigen::AngleAxis(.3, Vec3::UnitY()) - * Eigen::AngleAxis(.6, Vec3::UnitZ())).toRotationMatrix(); - Vec3 t(0.5, -0.3, .38); - - for(std::size_t i = 0; i < nbPoints; ++i) - { - const Vec3 &pt = x1.col(i); - x2.col(i) = (scale * rot * pt + t); - } - - // Compute the Similarity transform - double Sc; - Mat3 Rc; - Vec3 tc; - - std::vector inliers; - const bool result = ACRansac_FindRTS(x1, x2, randomNumberGenerator, Sc, tc, Rc, inliers, true); - - BOOST_CHECK(result); - BOOST_CHECK(inliers.size() == nbPoints); - - ALICEVISION_LOG_DEBUG( - "Scale " << Sc << "\n" << - "Rot \n" << Rc << "\n" << - "t " << tc.transpose()); - - ALICEVISION_LOG_DEBUG("GT\n" << - "Scale " << scale << "\n" << - "Rot \n" << rot << "\n" << - "t " << t.transpose()); - - BOOST_CHECK_SMALL(scale - Sc, 1e-9); - - Mat4 RTS; - composeRTS(Sc, tc, Rc, RTS); - - robustEstimation::MatrixModel modelRTS(RTS); - geometry::RTSSquaredResidualError errorEstimator; - - for(std::size_t i = 0; i < nbPoints; ++i) - { - const double error = errorEstimator.error(modelRTS, x1.col(i), x2.col(i)); - BOOST_CHECK_SMALL(error, 1e-9); - } + makeRandomOperationsReproducible(); + + std::mt19937 randomNumberGenerator; + const std::size_t nbPoints = 100; + Mat x1 = Mat::Random(3, nbPoints); + Mat x2 = x1; + + const double scale = 2; + Mat3 rot = + (Eigen::AngleAxis(.2, Vec3::UnitX()) * Eigen::AngleAxis(.3, Vec3::UnitY()) * Eigen::AngleAxis(.6, Vec3::UnitZ())) + .toRotationMatrix(); + Vec3 t(0.5, -0.3, .38); + + for (std::size_t i = 0; i < nbPoints; ++i) + { + const Vec3& pt = x1.col(i); + x2.col(i) = (scale * rot * pt + t); + } + + // Compute the Similarity transform + double Sc; + Mat3 Rc; + Vec3 tc; + + std::vector inliers; + const bool result = ACRansac_FindRTS(x1, x2, randomNumberGenerator, Sc, tc, Rc, inliers, true); + + BOOST_CHECK(result); + BOOST_CHECK(inliers.size() == nbPoints); + + ALICEVISION_LOG_DEBUG("Scale " << Sc << "\n" + << "Rot \n" + << Rc << "\n" + << "t " << tc.transpose()); + + ALICEVISION_LOG_DEBUG("GT\n" + << "Scale " << scale << "\n" + << "Rot \n" + << rot << "\n" + << "t " << t.transpose()); + + BOOST_CHECK_SMALL(scale - Sc, 1e-9); + + Mat4 RTS; + composeRTS(Sc, tc, Rc, RTS); + + robustEstimation::MatrixModel modelRTS(RTS); + geometry::RTSSquaredResidualError errorEstimator; + + for (std::size_t i = 0; i < nbPoints; ++i) + { + const double error = errorEstimator.error(modelRTS, x1.col(i), x2.col(i)); + BOOST_CHECK_SMALL(error, 1e-9); + } } BOOST_AUTO_TEST_CASE(SRT_precision_ACRANSAC_noiseByShuffling) { - makeRandomOperationsReproducible(); - - std::mt19937 randomNumberGenerator; - // it generates some points x1, it only generates the corresponding - // transformed points x2 for nbPoints-nbShuffles of them while the rest - // are again taken randomly in order to generate outliers - const std::size_t nbPoints = 100; - const std::size_t nbNoisy = 50; - - Mat x1 = Mat::Random(3, nbPoints); - Mat x2 = Mat::Random(3, nbPoints); - - const double scale = 2; - Mat3 rot = (Eigen::AngleAxis(.2, Vec3::UnitX()) - * Eigen::AngleAxis(.3, Vec3::UnitY()) - * Eigen::AngleAxis(.6, Vec3::UnitZ())).toRotationMatrix(); - Vec3 t(0.5, -0.3, .38); - - for(std::size_t i = 0; i < nbPoints - nbNoisy; ++i) - { - const Vec3 &pt = x1.col(i); - x2.col(i) = (scale * rot * pt + t); - } - - // Compute the Similarity transform - double Sc; - Mat3 Rc; - Vec3 tc; - - std::vector inliers; - const bool result = ACRansac_FindRTS(x1, x2, randomNumberGenerator, Sc, tc, Rc, inliers, true); - - ALICEVISION_LOG_DEBUG( - "Scale " << Sc << "\n" << - "Rot \n" << Rc << "\n" << - "t " << tc.transpose()); - - ALICEVISION_LOG_DEBUG("GT\n" << - "Scale " << scale << "\n" << - "Rot \n" << rot << "\n" << - "t " << t.transpose()); - - BOOST_CHECK(result); - // all the points must be inliers (no noise) - const std::size_t nbInliers = inliers.size(); - BOOST_CHECK(nbInliers == nbPoints - nbNoisy); - - Mat inliers1 = Mat3X(3, nbInliers); - Mat inliers2 = Mat3X(3, nbInliers); - - for(std::size_t i = 0; i < nbInliers; ++i) - { - inliers1.col(i) = x1.col(inliers[i]); - inliers2.col(i) = x2.col(inliers[i]); - } - - // check scale - BOOST_CHECK_SMALL(scale - Sc, 1e-9); - - Mat4 RTS; - composeRTS(Sc, tc, Rc, RTS); - - robustEstimation::MatrixModel modelRTS(RTS); - geometry::RTSSquaredResidualError errorEstimator; - - // check the residuals for the inliers - for(std::size_t i = 0; i < nbInliers; ++i) - { - const double error = errorEstimator.error(modelRTS, inliers1.col(i), inliers2.col(i)); - BOOST_CHECK_SMALL(error, 1e-9); - } + makeRandomOperationsReproducible(); + + std::mt19937 randomNumberGenerator; + // it generates some points x1, it only generates the corresponding + // transformed points x2 for nbPoints-nbShuffles of them while the rest + // are again taken randomly in order to generate outliers + const std::size_t nbPoints = 100; + const std::size_t nbNoisy = 50; + + Mat x1 = Mat::Random(3, nbPoints); + Mat x2 = Mat::Random(3, nbPoints); + + const double scale = 2; + Mat3 rot = + (Eigen::AngleAxis(.2, Vec3::UnitX()) * Eigen::AngleAxis(.3, Vec3::UnitY()) * Eigen::AngleAxis(.6, Vec3::UnitZ())) + .toRotationMatrix(); + Vec3 t(0.5, -0.3, .38); + + for (std::size_t i = 0; i < nbPoints - nbNoisy; ++i) + { + const Vec3& pt = x1.col(i); + x2.col(i) = (scale * rot * pt + t); + } + + // Compute the Similarity transform + double Sc; + Mat3 Rc; + Vec3 tc; + + std::vector inliers; + const bool result = ACRansac_FindRTS(x1, x2, randomNumberGenerator, Sc, tc, Rc, inliers, true); + + ALICEVISION_LOG_DEBUG("Scale " << Sc << "\n" + << "Rot \n" + << Rc << "\n" + << "t " << tc.transpose()); + + ALICEVISION_LOG_DEBUG("GT\n" + << "Scale " << scale << "\n" + << "Rot \n" + << rot << "\n" + << "t " << t.transpose()); + + BOOST_CHECK(result); + // all the points must be inliers (no noise) + const std::size_t nbInliers = inliers.size(); + BOOST_CHECK(nbInliers == nbPoints - nbNoisy); + + Mat inliers1 = Mat3X(3, nbInliers); + Mat inliers2 = Mat3X(3, nbInliers); + + for (std::size_t i = 0; i < nbInliers; ++i) + { + inliers1.col(i) = x1.col(inliers[i]); + inliers2.col(i) = x2.col(inliers[i]); + } + + // check scale + BOOST_CHECK_SMALL(scale - Sc, 1e-9); + + Mat4 RTS; + composeRTS(Sc, tc, Rc, RTS); + + robustEstimation::MatrixModel modelRTS(RTS); + geometry::RTSSquaredResidualError errorEstimator; + + // check the residuals for the inliers + for (std::size_t i = 0; i < nbInliers; ++i) + { + const double error = errorEstimator.error(modelRTS, inliers1.col(i), inliers2.col(i)); + BOOST_CHECK_SMALL(error, 1e-9); + } } diff --git a/src/aliceVision/gpu/gpu.cpp b/src/aliceVision/gpu/gpu.cpp index 7a654c57b3..afb6e8756e 100644 --- a/src/aliceVision/gpu/gpu.cpp +++ b/src/aliceVision/gpu/gpu.cpp @@ -7,42 +7,39 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) -#include + #include #endif namespace aliceVision { namespace gpu { -bool gpuSupportCUDA(int minComputeCapabilityMajor, - int minComputeCapabilityMinor, - int minTotalDeviceMemory) +bool gpuSupportCUDA(int minComputeCapabilityMajor, int minComputeCapabilityMinor, int minTotalDeviceMemory) { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) int nbDevices = 0; cudaError_t success; success = cudaGetDeviceCount(&nbDevices); - if( success != cudaSuccess ) + if (success != cudaSuccess) { ALICEVISION_LOG_ERROR("cudaGetDeviceCount failed: " << cudaGetErrorString(success)); nbDevices = 0; } - if(nbDevices > 0) + if (nbDevices > 0) { - for(int i = 0; i < nbDevices; ++i) + for (int i = 0; i < nbDevices; ++i) { cudaDeviceProp deviceProperties; - if(cudaGetDeviceProperties(&deviceProperties, i) != cudaSuccess) + if (cudaGetDeviceProperties(&deviceProperties, i) != cudaSuccess) { ALICEVISION_LOG_ERROR("Cannot get properties for CUDA gpu device " << i); continue; } - if((deviceProperties.major > minComputeCapabilityMajor || - (deviceProperties.major == minComputeCapabilityMajor && - deviceProperties.minor >= minComputeCapabilityMinor)) && - deviceProperties.totalGlobalMem >= (minTotalDeviceMemory*1024*1024)) + if ((deviceProperties.major > minComputeCapabilityMajor || + (deviceProperties.major == minComputeCapabilityMajor && deviceProperties.minor >= minComputeCapabilityMinor)) && + deviceProperties.totalGlobalMem >= (minTotalDeviceMemory * 1024 * 1024)) { ALICEVISION_LOG_INFO("Supported CUDA-Enabled GPU detected."); return true; @@ -50,9 +47,10 @@ bool gpuSupportCUDA(int minComputeCapabilityMajor, else { ALICEVISION_LOG_ERROR("CUDA-Enabled GPU detected, but the compute capabilities is not enough.\n" - << " - Device " << i << ": " << deviceProperties.major << "." << deviceProperties.minor << ", global memory: " << int(deviceProperties.totalGlobalMem / (1024*1024)) << "MB\n" - << " - Requirements: " << minComputeCapabilityMajor << "." << minComputeCapabilityMinor << ", global memory: " << minTotalDeviceMemory << "MB\n" - ); + << " - Device " << i << ": " << deviceProperties.major << "." << deviceProperties.minor + << ", global memory: " << int(deviceProperties.totalGlobalMem / (1024 * 1024)) << "MB\n" + << " - Requirements: " << minComputeCapabilityMajor << "." << minComputeCapabilityMinor + << ", global memory: " << minTotalDeviceMemory << "MB\n"); } } ALICEVISION_LOG_INFO("CUDA-Enabled GPU not supported."); @@ -70,88 +68,83 @@ std::string gpuInformationCUDA() std::string information; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) int nbDevices = 0; - if( cudaGetDeviceCount(&nbDevices) != cudaSuccess ) + if (cudaGetDeviceCount(&nbDevices) != cudaSuccess) { - ALICEVISION_LOG_WARNING( "Could not determine number of CUDA cards in this system" ); + ALICEVISION_LOG_WARNING("Could not determine number of CUDA cards in this system"); nbDevices = 0; } - if(nbDevices > 0) + if (nbDevices > 0) { information = "CUDA-Enabled GPU.\n"; - for(int i = 0; i < nbDevices; ++i) + for (int i = 0; i < nbDevices; ++i) { cudaDeviceProp deviceProperties; - if(cudaGetDeviceProperties( &deviceProperties, i) != cudaSuccess ) + if (cudaGetDeviceProperties(&deviceProperties, i) != cudaSuccess) { ALICEVISION_LOG_ERROR("Cannot get properties for CUDA gpu device " << i); continue; } - if( cudaSetDevice( i ) != cudaSuccess ) + if (cudaSetDevice(i) != cudaSuccess) { - ALICEVISION_LOG_WARNING( "Device with number " << i << " does not exist" ); + ALICEVISION_LOG_WARNING("Device with number " << i << " does not exist"); continue; } std::size_t avail; std::size_t total; cudaError_t memInfoErr = cudaMemGetInfo(&avail, &total); - if(memInfoErr != cudaSuccess) + if (memInfoErr != cudaSuccess) { // if the card does not provide this information. avail = 0; total = 0; ALICEVISION_LOG_WARNING("Cannot get available memory information for CUDA gpu device " << i << ":" << std::endl - << "\t (error code: " << memInfoErr << ") " << cudaGetErrorName(memInfoErr)); - + << "\t (error code: " << memInfoErr << ") " + << cudaGetErrorName(memInfoErr)); + cudaError_t err = cudaGetLastError(); // clear error } std::stringstream deviceSS; deviceSS << "Device information:" << std::endl - << "\t- id: " << i << std::endl - << "\t- name: " << deviceProperties.name << std::endl - << "\t- compute capability: " << deviceProperties.major << "." << deviceProperties.minor << std::endl - << "\t- clock frequency (kHz): " << deviceProperties.clockRate << std::endl - << "\t- total device memory: " << deviceProperties.totalGlobalMem / (1024 * 1024) << " MB " << std::endl - << "\t- device memory available: " << avail / (1024 * 1024) << " MB " << std::endl - << "\t- per-block shared memory: " << deviceProperties.sharedMemPerBlock << std::endl - << "\t- warp size: " << deviceProperties.warpSize << std::endl - << "\t- max threads per block: " << deviceProperties.maxThreadsPerBlock << std::endl - << "\t- max threads per SM(X): " << deviceProperties.maxThreadsPerMultiProcessor << std::endl - << "\t- max block sizes: " - << "{" << deviceProperties.maxThreadsDim[0] - << "," << deviceProperties.maxThreadsDim[1] - << "," << deviceProperties.maxThreadsDim[2] << "}" << std::endl - << "\t- max grid sizes: " - << "{" << deviceProperties.maxGridSize[0] - << "," << deviceProperties.maxGridSize[1] - << "," << deviceProperties.maxGridSize[2] << "}" << std::endl - << "\t- max 2D array texture: " - << "{" << deviceProperties.maxTexture2D[0] - << "," << deviceProperties.maxTexture2D[1] << "}" << std::endl - << "\t- max 3D array texture: " - << "{" << deviceProperties.maxTexture3D[0] - << "," << deviceProperties.maxTexture3D[1] - << "," << deviceProperties.maxTexture3D[2] << "}" << std::endl - << "\t- max 2D linear texture: " - << "{" << deviceProperties.maxTexture2DLinear[0] - << "," << deviceProperties.maxTexture2DLinear[1] - << "," << deviceProperties.maxTexture2DLinear[2] << "}" << std::endl - << "\t- max 2D layered texture: " - << "{" << deviceProperties.maxTexture2DLayered[0] - << "," << deviceProperties.maxTexture2DLayered[1] - << "," << deviceProperties.maxTexture2DLayered[2] << "}" << std::endl - << "\t- number of SM(x)s: " << deviceProperties.multiProcessorCount << std::endl - << "\t- registers per SM(x): " << deviceProperties.regsPerMultiprocessor << std::endl - << "\t- registers per block: " << deviceProperties.regsPerBlock << std::endl - << "\t- concurrent kernels: " << (deviceProperties.concurrentKernels ? "yes":"no") << std::endl - << "\t- mapping host memory: " << (deviceProperties.canMapHostMemory ? "yes":"no") << std::endl - << "\t- unified addressing: " << (deviceProperties.unifiedAddressing ? "yes":"no") << std::endl - << "\t- texture alignment: " << deviceProperties.textureAlignment << " byte" << std::endl - << "\t- pitch alignment: " << deviceProperties.texturePitchAlignment << " byte" << std::endl; + << "\t- id: " << i << std::endl + << "\t- name: " << deviceProperties.name << std::endl + << "\t- compute capability: " << deviceProperties.major << "." << deviceProperties.minor << std::endl + << "\t- clock frequency (kHz): " << deviceProperties.clockRate << std::endl + << "\t- total device memory: " << deviceProperties.totalGlobalMem / (1024 * 1024) << " MB " << std::endl + << "\t- device memory available: " << avail / (1024 * 1024) << " MB " << std::endl + << "\t- per-block shared memory: " << deviceProperties.sharedMemPerBlock << std::endl + << "\t- warp size: " << deviceProperties.warpSize << std::endl + << "\t- max threads per block: " << deviceProperties.maxThreadsPerBlock << std::endl + << "\t- max threads per SM(X): " << deviceProperties.maxThreadsPerMultiProcessor << std::endl + << "\t- max block sizes: " + << "{" << deviceProperties.maxThreadsDim[0] << "," << deviceProperties.maxThreadsDim[1] << "," + << deviceProperties.maxThreadsDim[2] << "}" << std::endl + << "\t- max grid sizes: " + << "{" << deviceProperties.maxGridSize[0] << "," << deviceProperties.maxGridSize[1] << "," << deviceProperties.maxGridSize[2] + << "}" << std::endl + << "\t- max 2D array texture: " + << "{" << deviceProperties.maxTexture2D[0] << "," << deviceProperties.maxTexture2D[1] << "}" << std::endl + << "\t- max 3D array texture: " + << "{" << deviceProperties.maxTexture3D[0] << "," << deviceProperties.maxTexture3D[1] << "," << deviceProperties.maxTexture3D[2] + << "}" << std::endl + << "\t- max 2D linear texture: " + << "{" << deviceProperties.maxTexture2DLinear[0] << "," << deviceProperties.maxTexture2DLinear[1] << "," + << deviceProperties.maxTexture2DLinear[2] << "}" << std::endl + << "\t- max 2D layered texture: " + << "{" << deviceProperties.maxTexture2DLayered[0] << "," << deviceProperties.maxTexture2DLayered[1] << "," + << deviceProperties.maxTexture2DLayered[2] << "}" << std::endl + << "\t- number of SM(x)s: " << deviceProperties.multiProcessorCount << std::endl + << "\t- registers per SM(x): " << deviceProperties.regsPerMultiprocessor << std::endl + << "\t- registers per block: " << deviceProperties.regsPerBlock << std::endl + << "\t- concurrent kernels: " << (deviceProperties.concurrentKernels ? "yes" : "no") << std::endl + << "\t- mapping host memory: " << (deviceProperties.canMapHostMemory ? "yes" : "no") << std::endl + << "\t- unified addressing: " << (deviceProperties.unifiedAddressing ? "yes" : "no") << std::endl + << "\t- texture alignment: " << deviceProperties.textureAlignment << " byte" << std::endl + << "\t- pitch alignment: " << deviceProperties.texturePitchAlignment << " byte" << std::endl; information += deviceSS.str(); } @@ -161,7 +154,7 @@ std::string gpuInformationCUDA() information = "No CUDA-Enabled GPU.\n"; } std::stringstream ss; - ss << "CUDA build version: " << CUDART_VERSION/1000 << "." << CUDART_VERSION/10%100; + ss << "CUDA build version: " << CUDART_VERSION / 1000 << "." << CUDART_VERSION / 10 % 100; information += ss.str(); #else information = "AliceVision built without CUDA support."; @@ -169,5 +162,5 @@ std::string gpuInformationCUDA() return information; } -} // namespace gpu -} // namespace aliceVision +} // namespace gpu +} // namespace aliceVision diff --git a/src/aliceVision/gpu/gpu.hpp b/src/aliceVision/gpu/gpu.hpp index bb2494e6fa..ef048eb98a 100644 --- a/src/aliceVision/gpu/gpu.hpp +++ b/src/aliceVision/gpu/gpu.hpp @@ -18,10 +18,7 @@ namespace gpu { * @param[in] minTotalDeviceMemory The minimum device total memory in MB * @return True if system support CUDA with the given parameters */ -bool gpuSupportCUDA(int minComputeCapabilityMajor, - int minComputeCapabilityMinor, - int minTotalDeviceMemory = 0); - +bool gpuSupportCUDA(int minComputeCapabilityMajor, int minComputeCapabilityMinor, int minTotalDeviceMemory = 0); /** * @brief gpuInformationCUDA @@ -29,5 +26,5 @@ bool gpuSupportCUDA(int minComputeCapabilityMajor, */ std::string gpuInformationCUDA(); -} // namespace gpu -} // namespace aliceVision +} // namespace gpu +} // namespace aliceVision diff --git a/src/aliceVision/graph/IndexedGraph.hpp b/src/aliceVision/graph/IndexedGraph.hpp index 5546253f8f..16e0d7ba43 100644 --- a/src/aliceVision/graph/IndexedGraph.hpp +++ b/src/aliceVision/graph/IndexedGraph.hpp @@ -15,83 +15,73 @@ #include namespace aliceVision { -namespace graph { +namespace graph { // Structure used to keep information of an image graph: // - Build a graph (add nodes and connection between nodes) // struct indexedGraph { - typedef lemon::ListGraph GraphT; - typedef std::map map_Size_t_Node; - typedef GraphT::NodeMap map_NodeMapIndex; + typedef lemon::ListGraph GraphT; + typedef std::map map_Size_t_Node; + typedef GraphT::NodeMap map_NodeMapIndex; - GraphT g; - map_Size_t_Node map_size_t_to_node; // Original image index to graph node - std::unique_ptr map_nodeMapIndex; // Association of data to graph Node + GraphT g; + map_Size_t_Node map_size_t_to_node; // Original image index to graph node + std::unique_ptr map_nodeMapIndex; // Association of data to graph Node - template - indexedGraph(const IterablePairs & pairs) - { - map_nodeMapIndex.reset( new map_NodeMapIndex(g) ); - - //A-- Compute the number of node we need - std::set setNodes; - for (typename IterablePairs::const_iterator iter = pairs.begin(); - iter != pairs.end(); - ++iter) + template + indexedGraph(const IterablePairs& pairs) { - setNodes.insert(iter->first); - setNodes.insert(iter->second); - } - - //B-- Create a node graph for each element of the set - for (std::set::const_iterator iter = setNodes.begin(); - iter != setNodes.end(); - ++iter) - { - map_size_t_to_node[*iter] = g.addNode(); - (*map_nodeMapIndex) [map_size_t_to_node.at(*iter)] = *iter; - } + map_nodeMapIndex.reset(new map_NodeMapIndex(g)); - //C-- Add weighted edges from the pairs object - for (typename IterablePairs::const_iterator iter = pairs.begin(); - iter != pairs.end(); - ++iter) - { - const IndexT i = iter->first; - const IndexT j = iter->second; - g.addEdge(map_size_t_to_node.at(i), map_size_t_to_node.at(j)); - } - } + // A-- Compute the number of node we need + std::set setNodes; + for (typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) + { + setNodes.insert(iter->first); + setNodes.insert(iter->second); + } - /// Create a graph from node index and pairs (edges) - // /!\ pairs must contains valid nodes indexes - template - indexedGraph(const IterableNodes & nodes, const IterablePairs & pairs) - { - map_nodeMapIndex.reset( new map_NodeMapIndex(g) ); + // B-- Create a node graph for each element of the set + for (std::set::const_iterator iter = setNodes.begin(); iter != setNodes.end(); ++iter) + { + map_size_t_to_node[*iter] = g.addNode(); + (*map_nodeMapIndex)[map_size_t_to_node.at(*iter)] = *iter; + } - //A-- Create a node graph for each element of the set - for (typename IterableNodes::const_iterator iter = nodes.begin(); - iter != nodes.end(); - ++iter) - { - map_size_t_to_node[*iter] = g.addNode(); - (*map_nodeMapIndex) [map_size_t_to_node.at(*iter)] = *iter; + // C-- Add weighted edges from the pairs object + for (typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) + { + const IndexT i = iter->first; + const IndexT j = iter->second; + g.addEdge(map_size_t_to_node.at(i), map_size_t_to_node.at(j)); + } } - //B-- Add weighted edges from the pairs object - for (typename IterablePairs::const_iterator iter = pairs.begin(); - iter != pairs.end(); - ++iter) + /// Create a graph from node index and pairs (edges) + // /!\ pairs must contains valid nodes indexes + template + indexedGraph(const IterableNodes& nodes, const IterablePairs& pairs) { - const IndexT i = iter->first; - const IndexT j = iter->second; - g.addEdge(map_size_t_to_node.at(i), map_size_t_to_node.at(j)); + map_nodeMapIndex.reset(new map_NodeMapIndex(g)); + + // A-- Create a node graph for each element of the set + for (typename IterableNodes::const_iterator iter = nodes.begin(); iter != nodes.end(); ++iter) + { + map_size_t_to_node[*iter] = g.addNode(); + (*map_nodeMapIndex)[map_size_t_to_node.at(*iter)] = *iter; + } + + // B-- Add weighted edges from the pairs object + for (typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) + { + const IndexT i = iter->first; + const IndexT j = iter->second; + g.addEdge(map_size_t_to_node.at(i), map_size_t_to_node.at(j)); + } } - } }; -} // namespace graph -} // namespace aliceVision +} // namespace graph +} // namespace aliceVision diff --git a/src/aliceVision/graph/Triplet.hpp b/src/aliceVision/graph/Triplet.hpp index 1cd9bc2aa3..48ef4c307d 100644 --- a/src/aliceVision/graph/Triplet.hpp +++ b/src/aliceVision/graph/Triplet.hpp @@ -24,126 +24,118 @@ using namespace lemon; /// It is used to store the node id of triplets of a graph. struct Triplet { - IndexT i,j,k; - - Triplet(IndexT ii, IndexT jj, IndexT kk) - : i(ii) - , j(jj) - , k(kk) - {} - - bool contain(const std::pair & edge) const - { - const IndexT It = edge.first; - const IndexT Jt = edge.second; - if ( (It == i || It == j || It == k ) && - (Jt == i || Jt == j || Jt == k ) && It != Jt) - return true; - else - return false; - } - - friend bool operator==(const Triplet& m1, const Triplet& m2) { - return m1.contain(std::make_pair(m2.i, m2.j)) - && m1.contain(std::make_pair(m2.i, m2.k)); - } - - friend bool operator!=(const Triplet& m1, const Triplet& m2) { - return !(m1 == m2); - } - - friend std::ostream & operator<<(std::ostream & os, const Triplet & t) - { - os << t.i << " " << t.j << " " << t.k << std::endl; - return os; - } + IndexT i, j, k; + + Triplet(IndexT ii, IndexT jj, IndexT kk) + : i(ii), + j(jj), + k(kk) + {} + + bool contain(const std::pair& edge) const + { + const IndexT It = edge.first; + const IndexT Jt = edge.second; + if ((It == i || It == j || It == k) && (Jt == i || Jt == j || Jt == k) && It != Jt) + return true; + else + return false; + } + + friend bool operator==(const Triplet& m1, const Triplet& m2) + { + return m1.contain(std::make_pair(m2.i, m2.j)) && m1.contain(std::make_pair(m2.i, m2.k)); + } + + friend bool operator!=(const Triplet& m1, const Triplet& m2) { return !(m1 == m2); } + + friend std::ostream& operator<<(std::ostream& os, const Triplet& t) + { + os << t.i << " " << t.j << " " << t.k << std::endl; + return os; + } }; /// Function that return all the triplets found in a graph /// vec_triplets must be empty. template -bool List_Triplets(const GraphT & g, std::vector< Triplet > & vec_triplets) +bool List_Triplets(const GraphT& g, std::vector& vec_triplets) { - // Algorithm - // - //-- For each node - // - list the outgoing not visited edge - // - for each tuple of edge - // - if their end are connected - // Detected cycle of length 3 - // Mark first edge as visited - - typedef typename GraphT::OutArcIt OutArcIt; - typedef typename GraphT::NodeIt NodeIterator; - typedef typename GraphT::template EdgeMap BoolEdgeMap; - - BoolEdgeMap map_edge(g, false); // Visited edge map - - // For each nodes - for (NodeIterator itNode(g); itNode != INVALID; ++itNode) - { - - // For each edges (list the not visited outgoing edges) - std::vector vec_edges; - for (OutArcIt e(g, itNode); e!=INVALID; ++e) + // Algorithm + // + //-- For each node + // - list the outgoing not visited edge + // - for each tuple of edge + // - if their end are connected + // Detected cycle of length 3 + // Mark first edge as visited + + typedef typename GraphT::OutArcIt OutArcIt; + typedef typename GraphT::NodeIt NodeIterator; + typedef typename GraphT::template EdgeMap BoolEdgeMap; + + BoolEdgeMap map_edge(g, false); // Visited edge map + + // For each nodes + for (NodeIterator itNode(g); itNode != INVALID; ++itNode) { - if (!map_edge[e]) // If not visited - vec_edges.push_back(e); - } + // For each edges (list the not visited outgoing edges) + std::vector vec_edges; + for (OutArcIt e(g, itNode); e != INVALID; ++e) + { + if (!map_edge[e]) // If not visited + vec_edges.push_back(e); + } - // For all tuples look of ends of edges are linked - while(vec_edges.size()>1) - { - OutArcIt itPrev = vec_edges[0]; // For all tuple (0,Inth) - for(size_t i=1; i < vec_edges.size(); ++i) - { - // Check if the extremity is linked - typename GraphT::Arc cycleEdge = findArc(g, g.target(itPrev), g.target(vec_edges[i])); - if (cycleEdge!= INVALID && !map_edge[cycleEdge]) + // For all tuples look of ends of edges are linked + while (vec_edges.size() > 1) { - // Elementary cycle found (make value follow a monotonic ascending serie) - int triplet[3] = { - g.id(itNode), - g.id(g.target(itPrev)), - g.id(g.target(vec_edges[i]))}; - std::sort(&triplet[0], &triplet[3]); - vec_triplets.push_back(Triplet(triplet[0],triplet[1],triplet[2])); + OutArcIt itPrev = vec_edges[0]; // For all tuple (0,Inth) + for (size_t i = 1; i < vec_edges.size(); ++i) + { + // Check if the extremity is linked + typename GraphT::Arc cycleEdge = findArc(g, g.target(itPrev), g.target(vec_edges[i])); + if (cycleEdge != INVALID && !map_edge[cycleEdge]) + { + // Elementary cycle found (make value follow a monotonic ascending serie) + int triplet[3] = {g.id(itNode), g.id(g.target(itPrev)), g.id(g.target(vec_edges[i]))}; + std::sort(&triplet[0], &triplet[3]); + vec_triplets.push_back(Triplet(triplet[0], triplet[1], triplet[2])); + } + } + // Mark the current ref edge as visited + map_edge[itPrev] = true; + // remove head to list remaining tuples + vec_edges.erase(vec_edges.begin()); } - } - // Mark the current ref edge as visited - map_edge[itPrev] = true; - // remove head to list remaining tuples - vec_edges.erase(vec_edges.begin()); } - } - return (!vec_triplets.empty()); + return (!vec_triplets.empty()); } /// Return triplets contained in the graph build from IterablePairs -template -inline std::vector< graph::Triplet > tripletListing( - const IterablePairs & pairs) +template +inline std::vector tripletListing(const IterablePairs& pairs) { - std::vector< graph::Triplet > vec_triplets; - - indexedGraph putativeGraph(pairs); - - graph::List_Triplets(putativeGraph.g, vec_triplets); - - //Change triplets to ImageIds - for (size_t i = 0; i < vec_triplets.size(); ++i) - { - graph::Triplet & triplet = vec_triplets[i]; - IndexT I = triplet.i, J = triplet.j , K = triplet.k; - I = (*putativeGraph.map_nodeMapIndex)[putativeGraph.g.nodeFromId(I)]; - J = (*putativeGraph.map_nodeMapIndex)[putativeGraph.g.nodeFromId(J)]; - K = (*putativeGraph.map_nodeMapIndex)[putativeGraph.g.nodeFromId(K)]; - IndexT triplet_[3] = { I, J, K }; - std::sort(&triplet_[0], &triplet_[3]); - triplet = graph::Triplet(triplet_[0],triplet_[1],triplet_[2]); - } - return vec_triplets; + std::vector vec_triplets; + + indexedGraph putativeGraph(pairs); + + graph::List_Triplets(putativeGraph.g, vec_triplets); + + // Change triplets to ImageIds + for (size_t i = 0; i < vec_triplets.size(); ++i) + { + graph::Triplet& triplet = vec_triplets[i]; + IndexT I = triplet.i, J = triplet.j, K = triplet.k; + I = (*putativeGraph.map_nodeMapIndex)[putativeGraph.g.nodeFromId(I)]; + J = (*putativeGraph.map_nodeMapIndex)[putativeGraph.g.nodeFromId(J)]; + K = (*putativeGraph.map_nodeMapIndex)[putativeGraph.g.nodeFromId(K)]; + IndexT triplet_[3] = {I, J, K}; + std::sort(&triplet_[0], &triplet_[3]); + triplet = graph::Triplet(triplet_[0], triplet_[1], triplet_[2]); + } + return vec_triplets; } -} // namespace graph -} // namespace aliceVision +} // namespace graph +} // namespace aliceVision diff --git a/src/aliceVision/graph/connectedComponent.hpp b/src/aliceVision/graph/connectedComponent.hpp index 20c786282f..05a0971195 100644 --- a/src/aliceVision/graph/connectedComponent.hpp +++ b/src/aliceVision/graph/connectedComponent.hpp @@ -18,120 +18,112 @@ namespace aliceVision { namespace graph { /// Export node of each CC (Connected Component) in a map -template -std::map > exportGraphToMapSubgraphs( - const GraphT & g) +template +std::map> exportGraphToMapSubgraphs(const GraphT& g) { - typedef lemon::ListGraph::NodeMap IndexMap; - IndexMap connectedNodeMap(g); - lemon::connectedComponents(g, connectedNodeMap); - - std::map > map_subgraphs; - - // Create subgraphs' map - typedef lemon::ListGraph::NodeIt NodeIterator; - NodeIterator itNode(g); - for (typename IndexMap::MapIt it(connectedNodeMap); - it != lemon::INVALID; ++it, ++itNode) { - map_subgraphs[*it].insert(itNode); - } - return map_subgraphs; + typedef lemon::ListGraph::NodeMap IndexMap; + IndexMap connectedNodeMap(g); + lemon::connectedComponents(g, connectedNodeMap); + + std::map> map_subgraphs; + + // Create subgraphs' map + typedef lemon::ListGraph::NodeIt NodeIterator; + NodeIterator itNode(g); + for (typename IndexMap::MapIt it(connectedNodeMap); it != lemon::INVALID; ++it, ++itNode) + { + map_subgraphs[*it].insert(itNode); + } + return map_subgraphs; } /// Return imageIds that belongs to the largest bi-edge connected component template -std::set CleanGraph_KeepLargestBiEdge_Nodes( - const EdgesInterface_T & edges, - const std::string & _sOutDirectory = "") +std::set CleanGraph_KeepLargestBiEdge_Nodes(const EdgesInterface_T& edges, const std::string& _sOutDirectory = "") { - std::set largestBiEdgeCC; + std::set largestBiEdgeCC; - // Create a graph from pairwise correspondences: - // - remove not biedge connected component, - // - keep the largest connected component. + // Create a graph from pairwise correspondences: + // - remove not biedge connected component, + // - keep the largest connected component. - typedef lemon::ListGraph Graph; - graph::indexedGraph putativeGraph(edges); + typedef lemon::ListGraph Graph; + graph::indexedGraph putativeGraph(edges); - // Remove not bi-edge connected edges - typedef Graph::EdgeMap EdgeMapAlias; - EdgeMapAlias cutMap(putativeGraph.g); + // Remove not bi-edge connected edges + typedef Graph::EdgeMap EdgeMapAlias; + EdgeMapAlias cutMap(putativeGraph.g); - if (lemon::biEdgeConnectedCutEdges(putativeGraph.g, cutMap) > 0) - { - // Some edges must be removed because they don't follow the biEdge condition. - typedef Graph::EdgeIt EdgeIterator; - EdgeIterator itEdge(putativeGraph.g); - for (EdgeMapAlias::MapIt it(cutMap); it!=lemon::INVALID; ++it, ++itEdge) + if (lemon::biEdgeConnectedCutEdges(putativeGraph.g, cutMap) > 0) { - if (*it) - putativeGraph.g.erase(itEdge); // remove the not bi-edge element - } - } - - // Graph is bi-edge connected, but still many connected components can exist - // Keep only the largest one - const int connectedComponentCount = lemon::countConnectedComponents(putativeGraph.g); - ALICEVISION_LOG_DEBUG("CleanGraph_KeepLargestBiEdge_Nodes():: => connected Component: " - << connectedComponentCount); - if (connectedComponentCount >= 1) - { - // Keep only the largest connected component - // - list all CC size - // - if the largest one is meet, keep all the edges that belong to this node - - const std::map > map_subgraphs = exportGraphToMapSubgraphs(putativeGraph.g); - size_t count = std::numeric_limits::min(); - typename std::map >::const_iterator iterLargestCC = map_subgraphs.end(); - for(typename std::map >::const_iterator iter = map_subgraphs.begin(); - iter != map_subgraphs.end(); ++iter) - { - if (iter->second.size() > count) { - count = iter->second.size(); - iterLargestCC = iter; - } - ALICEVISION_LOG_DEBUG("Connected component of size: " << iter->second.size()); + // Some edges must be removed because they don't follow the biEdge condition. + typedef Graph::EdgeIt EdgeIterator; + EdgeIterator itEdge(putativeGraph.g); + for (EdgeMapAlias::MapIt it(cutMap); it != lemon::INVALID; ++it, ++itEdge) + { + if (*it) + putativeGraph.g.erase(itEdge); // remove the not bi-edge element + } } - //-- Keep only the nodes that are in the largest CC - for(typename std::map >::const_iterator iter = map_subgraphs.begin(); - iter != map_subgraphs.end(); ++iter) + // Graph is bi-edge connected, but still many connected components can exist + // Keep only the largest one + const int connectedComponentCount = lemon::countConnectedComponents(putativeGraph.g); + ALICEVISION_LOG_DEBUG("CleanGraph_KeepLargestBiEdge_Nodes():: => connected Component: " << connectedComponentCount); + if (connectedComponentCount >= 1) { - if (iter == iterLargestCC) - { - // list all nodes that belong to the current CC and update the Node Ids list - const std::set & ccSet = iter->second; - for (std::set::const_iterator iter2 = ccSet.begin(); - iter2 != ccSet.end(); ++iter2) + // Keep only the largest connected component + // - list all CC size + // - if the largest one is meet, keep all the edges that belong to this node + + const std::map> map_subgraphs = exportGraphToMapSubgraphs(putativeGraph.g); + size_t count = std::numeric_limits::min(); + typename std::map>::const_iterator iterLargestCC = map_subgraphs.end(); + for (typename std::map>::const_iterator iter = map_subgraphs.begin(); iter != map_subgraphs.end(); ++iter) { - const IndexT Id = (*putativeGraph.map_nodeMapIndex)[*iter2]; - largestBiEdgeCC.insert(Id); + if (iter->second.size() > count) + { + count = iter->second.size(); + iterLargestCC = iter; + } + ALICEVISION_LOG_DEBUG("Connected component of size: " << iter->second.size()); } - } - else - { - // remove the edges from the graph - const std::set & ccSet = iter->second; - for (std::set::const_iterator iter2 = ccSet.begin(); - iter2 != ccSet.end(); ++iter2) + + //-- Keep only the nodes that are in the largest CC + for (typename std::map>::const_iterator iter = map_subgraphs.begin(); iter != map_subgraphs.end(); + ++iter) { - typedef Graph::OutArcIt OutArcIt; - for (OutArcIt e(putativeGraph.g, *iter2); e!=lemon::INVALID; ++e) - { - putativeGraph.g.erase(e); - } + if (iter == iterLargestCC) + { + // list all nodes that belong to the current CC and update the Node Ids list + const std::set& ccSet = iter->second; + for (std::set::const_iterator iter2 = ccSet.begin(); iter2 != ccSet.end(); ++iter2) + { + const IndexT Id = (*putativeGraph.map_nodeMapIndex)[*iter2]; + largestBiEdgeCC.insert(Id); + } + } + else + { + // remove the edges from the graph + const std::set& ccSet = iter->second; + for (std::set::const_iterator iter2 = ccSet.begin(); iter2 != ccSet.end(); ++iter2) + { + typedef Graph::OutArcIt OutArcIt; + for (OutArcIt e(putativeGraph.g, *iter2); e != lemon::INVALID; ++e) + { + putativeGraph.g.erase(e); + } + } + } } - } } - } - ALICEVISION_LOG_DEBUG( - "Cardinal of nodes: " << lemon::countNodes(putativeGraph.g) << "\n" << - "Cardinal of edges: " << lemon::countEdges(putativeGraph.g) - ); + ALICEVISION_LOG_DEBUG("Cardinal of nodes: " << lemon::countNodes(putativeGraph.g) << "\n" + << "Cardinal of edges: " << lemon::countEdges(putativeGraph.g)); - return largestBiEdgeCC; + return largestBiEdgeCC; } -} // namespace graph -} // namespace aliceVision +} // namespace graph +} // namespace aliceVision diff --git a/src/aliceVision/graph/connectedComponent_test.cpp b/src/aliceVision/graph/connectedComponent_test.cpp index c1ba281f1f..b8d6f84b4c 100644 --- a/src/aliceVision/graph/connectedComponent_test.cpp +++ b/src/aliceVision/graph/connectedComponent_test.cpp @@ -17,52 +17,54 @@ using namespace lemon; -BOOST_AUTO_TEST_CASE(Empty) { - lemon::ListGraph graph; +BOOST_AUTO_TEST_CASE(Empty) +{ + lemon::ListGraph graph; - const int connectedComponentCount = lemon::countConnectedComponents(graph); - BOOST_CHECK_EQUAL(0, connectedComponentCount); + const int connectedComponentCount = lemon::countConnectedComponents(graph); + BOOST_CHECK_EQUAL(0, connectedComponentCount); } -BOOST_AUTO_TEST_CASE(OneCC) { - lemon::ListGraph graph; - lemon::ListGraph::Node a = graph.addNode(), b = graph.addNode(); - graph.addEdge(a,b); - const int connectedComponentCount = lemon::countConnectedComponents(graph); - BOOST_CHECK_EQUAL(1, connectedComponentCount); +BOOST_AUTO_TEST_CASE(OneCC) +{ + lemon::ListGraph graph; + lemon::ListGraph::Node a = graph.addNode(), b = graph.addNode(); + graph.addEdge(a, b); + const int connectedComponentCount = lemon::countConnectedComponents(graph); + BOOST_CHECK_EQUAL(1, connectedComponentCount); } -BOOST_AUTO_TEST_CASE(TwoCC) { - lemon::ListGraph graph; +BOOST_AUTO_TEST_CASE(TwoCC) +{ + lemon::ListGraph graph; - lemon::ListGraph::Node a = graph.addNode(), b = graph.addNode(); - graph.addEdge(a,b); - lemon::ListGraph::Node a2 = graph.addNode(), b2 = graph.addNode(); - graph.addEdge(a2,b2); - const int connectedComponentCount = lemon::countConnectedComponents(graph); - BOOST_CHECK_EQUAL(2, connectedComponentCount); + lemon::ListGraph::Node a = graph.addNode(), b = graph.addNode(); + graph.addEdge(a, b); + lemon::ListGraph::Node a2 = graph.addNode(), b2 = graph.addNode(); + graph.addEdge(a2, b2); + const int connectedComponentCount = lemon::countConnectedComponents(graph); + BOOST_CHECK_EQUAL(2, connectedComponentCount); } - -BOOST_AUTO_TEST_CASE(TwoCC_Parsing) { - lemon::ListGraph graph; - - lemon::ListGraph::Node a = graph.addNode(), b = graph.addNode(); - graph.addEdge(a,b); - lemon::ListGraph::Node a2 = graph.addNode(), b2 = graph.addNode(); - graph.addEdge(a2,b2); - - typedef ListGraph::NodeMap IndexMap; - IndexMap connectedNodeMap(graph); - const int connectedComponentCount = lemon::connectedComponents(graph, connectedNodeMap); - BOOST_CHECK_EQUAL(2, connectedComponentCount); - for (IndexMap::MapIt it(connectedNodeMap); it != INVALID; ++it) - { - ALICEVISION_LOG_DEBUG(*it << "\t" << graph.id(it)); - } +BOOST_AUTO_TEST_CASE(TwoCC_Parsing) +{ + lemon::ListGraph graph; + + lemon::ListGraph::Node a = graph.addNode(), b = graph.addNode(); + graph.addEdge(a, b); + lemon::ListGraph::Node a2 = graph.addNode(), b2 = graph.addNode(); + graph.addEdge(a2, b2); + + typedef ListGraph::NodeMap IndexMap; + IndexMap connectedNodeMap(graph); + const int connectedComponentCount = lemon::connectedComponents(graph, connectedNodeMap); + BOOST_CHECK_EQUAL(2, connectedComponentCount); + for (IndexMap::MapIt it(connectedNodeMap); it != INVALID; ++it) + { + ALICEVISION_LOG_DEBUG(*it << "\t" << graph.id(it)); + } } - /// Test to get back node id of each CC // a // @@ -75,39 +77,38 @@ BOOST_AUTO_TEST_CASE(TwoCC_Parsing) { // h-i-j-k // |/ // l -BOOST_AUTO_TEST_CASE(exportGraphToMapSubgraphs_CC_Subgraph) { - lemon::ListGraph graph; - - // single - lemon::ListGraph::Node a = graph.addNode(); - - // two - lemon::ListGraph::Node b = graph.addNode(), c = graph.addNode(); - graph.addEdge(b,c); - - // four - lemon::ListGraph::Node d = graph.addNode(), e = graph.addNode(), - f = graph.addNode(), g = graph.addNode(); - graph.addEdge(d,e); - graph.addEdge(e,f); - graph.addEdge(f,g); - graph.addEdge(g,d); - - // five - lemon::ListGraph::Node h = graph.addNode(), i = graph.addNode(), - j = graph.addNode(), k = graph.addNode(),l = graph.addNode(); - graph.addEdge(h,i); - graph.addEdge(i,j); - graph.addEdge(j,k); - graph.addEdge(i,l); - graph.addEdge(j,l); - - const std::map > map_subgraphs = - aliceVision::graph::exportGraphToMapSubgraphs(graph); - - BOOST_CHECK_EQUAL(4, map_subgraphs.size()); - BOOST_CHECK_EQUAL(5, map_subgraphs.at(0).size()); - BOOST_CHECK_EQUAL(4, map_subgraphs.at(1).size()); - BOOST_CHECK_EQUAL(2, map_subgraphs.at(2).size()); - BOOST_CHECK_EQUAL(1, map_subgraphs.at(3).size()); +BOOST_AUTO_TEST_CASE(exportGraphToMapSubgraphs_CC_Subgraph) +{ + lemon::ListGraph graph; + + // single + lemon::ListGraph::Node a = graph.addNode(); + + // two + lemon::ListGraph::Node b = graph.addNode(), c = graph.addNode(); + graph.addEdge(b, c); + + // four + lemon::ListGraph::Node d = graph.addNode(), e = graph.addNode(), f = graph.addNode(), g = graph.addNode(); + graph.addEdge(d, e); + graph.addEdge(e, f); + graph.addEdge(f, g); + graph.addEdge(g, d); + + // five + lemon::ListGraph::Node h = graph.addNode(), i = graph.addNode(), j = graph.addNode(), k = graph.addNode(), l = graph.addNode(); + graph.addEdge(h, i); + graph.addEdge(i, j); + graph.addEdge(j, k); + graph.addEdge(i, l); + graph.addEdge(j, l); + + const std::map> map_subgraphs = + aliceVision::graph::exportGraphToMapSubgraphs(graph); + + BOOST_CHECK_EQUAL(4, map_subgraphs.size()); + BOOST_CHECK_EQUAL(5, map_subgraphs.at(0).size()); + BOOST_CHECK_EQUAL(4, map_subgraphs.at(1).size()); + BOOST_CHECK_EQUAL(2, map_subgraphs.at(2).size()); + BOOST_CHECK_EQUAL(1, map_subgraphs.at(3).size()); } diff --git a/src/aliceVision/graph/indexedGraphGraphvizExport.hpp b/src/aliceVision/graph/indexedGraphGraphvizExport.hpp index 319f458d18..7381a3affd 100644 --- a/src/aliceVision/graph/indexedGraphGraphvizExport.hpp +++ b/src/aliceVision/graph/indexedGraphGraphvizExport.hpp @@ -26,100 +26,92 @@ namespace graph { // n2 -- n0 // n2 -- n1 // } -template -bool exportToGraphvizFormat_Nodal( - const GraphT & g, - std::ostream & os) +template +bool exportToGraphvizFormat_Nodal(const GraphT& g, std::ostream& os) { - os << "graph 1 {" << std::endl; - os << "node [shape=circle]" << std::endl; - //Export node label - for(typename GraphT::NodeIt n(g); n!= lemon::INVALID; ++n) - { - os << " n" << g.id(n) << std::endl; - } + os << "graph 1 {" << std::endl; + os << "node [shape=circle]" << std::endl; + // Export node label + for (typename GraphT::NodeIt n(g); n != lemon::INVALID; ++n) + { + os << " n" << g.id(n) << std::endl; + } - //-- Export arc (as the graph is bi-directional, export arc only one time) + //-- Export arc (as the graph is bi-directional, export arc only one time) - std::map< std::pair, IndexT > map_arcs; - for(typename GraphT::ArcIt e(g); e!=lemon::INVALID; ++e) { - if( map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.source(e))), IndexT(g.id(g.target(e))))) - && - map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.target(e))), IndexT(g.id(g.source(e)))))) + std::map, IndexT> map_arcs; + for (typename GraphT::ArcIt e(g); e != lemon::INVALID; ++e) + { + if (map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.source(e))), IndexT(g.id(g.target(e))))) && + map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.target(e))), IndexT(g.id(g.source(e)))))) + { + map_arcs[std::make_pair(IndexT(g.id(g.source(e))), IndexT(g.id(g.target(e))))] = 1; + } + } + // os << "edge [style=bold]" << endl; + for (std::map, IndexT>::const_iterator iter = map_arcs.begin(); iter != map_arcs.end(); ++iter) { - map_arcs[std::make_pair(IndexT(g.id(g.source(e))),IndexT(g.id(g.target(e)))) ] = 1; + os << " n" << iter->first.first << " -- " + << " n" << iter->first.second << std::endl; } - } - //os << "edge [style=bold]" << endl; - for (std::map< std::pair, IndexT >::const_iterator iter = map_arcs.begin(); - iter != map_arcs.end(); - ++iter) - { - os << " n" << iter->first.first << " -- " << " n" << iter->first.second << std::endl; - } - os << "}" << std::endl; - return os.good(); + os << "}" << std::endl; + return os.good(); } // Export the Image connection graph // to the graphviz file format. // Add the image name and preview in the graph by using HTML label. -template -bool exportToGraphvizFormat_Image( - const GraphT & g, - const NodeMap & nodeMap, - const EdgeMap & edgeMap, - std::ostream & os, bool bWeightedEdge = false) +template +bool exportToGraphvizFormat_Image(const GraphT& g, const NodeMap& nodeMap, const EdgeMap& edgeMap, std::ostream& os, bool bWeightedEdge = false) { - os << "graph 1 {" << std::endl; - os << "node [shape=none]" << std::endl; - //Export node label - for(typename GraphT::NodeIt n(g); n!=lemon::INVALID; ++n) - { - os << " n" << g.id(n) - << "[ label =" - << "< "<< std::endl - << "" << std::endl - << "" << std::endl - << "" << std::endl - << "
" << "\"" << nodeMap[n] << "\"" << "
" << std::endl - << ">, cluster=1];" << std::endl; - - //os << " n" << g.id(n) - // << " [ " - // << " image=\"" << nodeMap[n] << "\" cluster=1]; " << endl; - } - - //Export arc value - std::map< std::pair, IndexT > map_arcs; - for(typename GraphT::ArcIt e(g); e!=lemon::INVALID; ++e) { - if( map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.source(e))), IndexT(g.id(g.target(e))))) - && - map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.target(e))), IndexT(g.id(g.source(e)))))) + os << "graph 1 {" << std::endl; + os << "node [shape=none]" << std::endl; + // Export node label + for (typename GraphT::NodeIt n(g); n != lemon::INVALID; ++n) { - map_arcs[std::make_pair(IndexT(g.id(g.source(e))), - IndexT(g.id(g.target(e)))) ] = edgeMap[e]; + os << " n" << g.id(n) << "[ label =" + << "< " << std::endl + << "" << std::endl + << "" << std::endl + << "" << std::endl + << "
" + << "\"" << nodeMap[n] << "\"" + << "
" << std::endl + << ">, cluster=1];" << std::endl; + + // os << " n" << g.id(n) + // << " [ " + // << " image=\"" << nodeMap[n] << "\" cluster=1]; " << endl; } - } - os << "edge [style=bold]" << std::endl; - for ( std::map< std::pair, IndexT>::const_iterator iter = map_arcs.begin(); - iter != map_arcs.end(); - ++iter) - { - if (bWeightedEdge) + // Export arc value + std::map, IndexT> map_arcs; + for (typename GraphT::ArcIt e(g); e != lemon::INVALID; ++e) { - os << " n" << iter->first.first << " -- " << " n" << iter->first.second - << " [label=\"" << iter->second << "\"]" << std::endl; + if (map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.source(e))), IndexT(g.id(g.target(e))))) && + map_arcs.end() == map_arcs.find(std::make_pair(IndexT(g.id(g.target(e))), IndexT(g.id(g.source(e)))))) + { + map_arcs[std::make_pair(IndexT(g.id(g.source(e))), IndexT(g.id(g.target(e))))] = edgeMap[e]; + } } - else + + os << "edge [style=bold]" << std::endl; + for (std::map, IndexT>::const_iterator iter = map_arcs.begin(); iter != map_arcs.end(); ++iter) { - os << " n" << iter->first.first << " -- " << " n" << iter->first.second << std::endl; + if (bWeightedEdge) + { + os << " n" << iter->first.first << " -- " + << " n" << iter->first.second << " [label=\"" << iter->second << "\"]" << std::endl; + } + else + { + os << " n" << iter->first.first << " -- " + << " n" << iter->first.second << std::endl; + } } - } - os << "}" << std::endl; - return os.good(); + os << "}" << std::endl; + return os.good(); } /** @@ -128,13 +120,13 @@ bool exportToGraphvizFormat_Image( * For instance, you can convert this export to SVG with the following command: * > neato -Tsvg -O -Goverlap=scale -Gsplines=false /path/to/file.dot */ -template -void exportToGraphvizData(const std::string& sfile, const GraphT & graph) +template +void exportToGraphvizData(const std::string& sfile, const GraphT& graph) { - std::ofstream file(sfile); - aliceVision::graph::exportToGraphvizFormat_Nodal(graph, file); - file.close(); + std::ofstream file(sfile); + aliceVision::graph::exportToGraphvizFormat_Nodal(graph, file); + file.close(); } -} // namespace graph -} // namespace aliceVision +} // namespace graph +} // namespace aliceVision diff --git a/src/aliceVision/graph/triplet_test.cpp b/src/aliceVision/graph/triplet_test.cpp index 4e5be4fe34..449df10b5b 100644 --- a/src/aliceVision/graph/triplet_test.cpp +++ b/src/aliceVision/graph/triplet_test.cpp @@ -17,255 +17,240 @@ using namespace aliceVision::graph; -BOOST_AUTO_TEST_CASE(test_no_triplet) { +BOOST_AUTO_TEST_CASE(test_no_triplet) +{ + typedef lemon::ListGraph Graph; - typedef lemon::ListGraph Graph; - - // a_b_c - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(); - ga.addEdge(a,b); - ga.addEdge(b,c); + // a_b_c + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(); + ga.addEdge(a, b); + ga.addEdge(b, c); - std::vector< Triplet > vec_triplets; + std::vector vec_triplets; - BOOST_CHECK(!List_Triplets(ga, vec_triplets)); - BOOST_CHECK(vec_triplets.empty()); + BOOST_CHECK(!List_Triplets(ga, vec_triplets)); + BOOST_CHECK(vec_triplets.empty()); } -BOOST_AUTO_TEST_CASE(test_one_triplet) { +BOOST_AUTO_TEST_CASE(test_one_triplet) +{ + typedef lemon::ListGraph Graph; - typedef lemon::ListGraph Graph; + { + // + // a_b + // |/ + // c + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(); + ga.addEdge(a, b); + ga.addEdge(a, c); + ga.addEdge(b, c); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(1, vec_triplets.size()); + // Check the cycle values + BOOST_CHECK_EQUAL(0, vec_triplets[0].i); + BOOST_CHECK_EQUAL(1, vec_triplets[0].j); + BOOST_CHECK_EQUAL(2, vec_triplets[0].k); + } - { - // - // a_b - // |/ - // c - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(); - ga.addEdge(a,b); - ga.addEdge(a,c); - ga.addEdge(b,c); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(1, vec_triplets.size()); - //Check the cycle values - BOOST_CHECK_EQUAL(0,vec_triplets[0].i); - BOOST_CHECK_EQUAL(1,vec_triplets[0].j); - BOOST_CHECK_EQUAL(2,vec_triplets[0].k); - } - - { - // - // a_b__c - // |/ - // d - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(); - ga.addEdge(a,b); - ga.addEdge(b,c); - ga.addEdge(b,d); - ga.addEdge(c,d); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(1, vec_triplets.size()); - //Check the cycle values - BOOST_CHECK_EQUAL(1,vec_triplets[0].i); - BOOST_CHECK_EQUAL(2,vec_triplets[0].j); - BOOST_CHECK_EQUAL(3,vec_triplets[0].k); - } + { + // + // a_b__c + // |/ + // d + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(); + ga.addEdge(a, b); + ga.addEdge(b, c); + ga.addEdge(b, d); + ga.addEdge(c, d); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(1, vec_triplets.size()); + // Check the cycle values + BOOST_CHECK_EQUAL(1, vec_triplets[0].i); + BOOST_CHECK_EQUAL(2, vec_triplets[0].j); + BOOST_CHECK_EQUAL(3, vec_triplets[0].k); + } } -BOOST_AUTO_TEST_CASE(test_two_triplet) { +BOOST_AUTO_TEST_CASE(test_two_triplet) +{ + typedef lemon::ListGraph Graph; - typedef lemon::ListGraph Graph; + { + // + // a__b + // |\ | + // | \| + // c--d + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(); + + ga.addEdge(a, b); + ga.addEdge(a, c); + ga.addEdge(a, d); + ga.addEdge(c, d); + ga.addEdge(b, d); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(2, vec_triplets.size()); + } - { - // - // a__b - // |\ | - // | \| - // c--d - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(); - - ga.addEdge(a,b); - ga.addEdge(a,c); - ga.addEdge(a,d); - ga.addEdge(c,d); - ga.addEdge(b,d); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(2, vec_triplets.size()); - } - - { - // - // a c - // |\ /| - // | b | - // |/ \| - // d e - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(), - e = ga.addNode(); - - ga.addEdge(a,b); - ga.addEdge(b,c); - ga.addEdge(c,e); - ga.addEdge(e,b); - ga.addEdge(b,d); - ga.addEdge(d,a); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(2, vec_triplets.size()); - } + { + // + // a c + // |\ /| + // | b | + // |/ \| + // d e + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(), e = ga.addNode(); + + ga.addEdge(a, b); + ga.addEdge(b, c); + ga.addEdge(c, e); + ga.addEdge(e, b); + ga.addEdge(b, d); + ga.addEdge(d, a); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(2, vec_triplets.size()); + } { - // - // a c - // |\ /| - // | b--f | - // |/ \| - // d e - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(), - e = ga.addNode(), f = ga.addNode(); - - ga.addEdge(a,b); - ga.addEdge(b,f); - ga.addEdge(f,c); - ga.addEdge(c,e); - ga.addEdge(e,f); - ga.addEdge(f,b); - ga.addEdge(b,d); - ga.addEdge(d,a); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(2, vec_triplets.size()); - } + // + // a c + // |\ /| + // | b--f | + // |/ \| + // d e + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(), e = ga.addNode(), f = ga.addNode(); + + ga.addEdge(a, b); + ga.addEdge(b, f); + ga.addEdge(f, c); + ga.addEdge(c, e); + ga.addEdge(e, f); + ga.addEdge(f, b); + ga.addEdge(b, d); + ga.addEdge(d, a); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(2, vec_triplets.size()); + } } +BOOST_AUTO_TEST_CASE(test_three_triplet) +{ + typedef lemon::ListGraph Graph; -BOOST_AUTO_TEST_CASE(test_three_triplet) { - - typedef lemon::ListGraph Graph; + { + // + // a b + // |\ /| + // c-d-e + // |/ + // f + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(), e = ga.addNode(), f = ga.addNode(); + + ga.addEdge(a, c); + ga.addEdge(a, d); + ga.addEdge(c, d); + ga.addEdge(c, f); + ga.addEdge(f, d); + ga.addEdge(d, b); + ga.addEdge(b, e); + ga.addEdge(e, d); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(3, vec_triplets.size()); + } - { - // - // a b - // |\ /| - // c-d-e - // |/ - // f - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(), - e = ga.addNode(), f = ga.addNode(); - - ga.addEdge(a,c); - ga.addEdge(a,d); - ga.addEdge(c,d); - ga.addEdge(c,f); - ga.addEdge(f,d); - ga.addEdge(d,b); - ga.addEdge(b,e); - ga.addEdge(e,d); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(3, vec_triplets.size()); - } - - { - // - // a b--g--h - // | \ / | \/ - // | d--e | i - // | / \ | - // c f - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(), - e = ga.addNode(), f= ga.addNode(), - g = ga.addNode(), h = ga.addNode(), - i = ga.addNode(); - - ga.addEdge(a,c); - ga.addEdge(a,d); - ga.addEdge(d,c); - ga.addEdge(d,e); - ga.addEdge(e,b); - ga.addEdge(e,f); - ga.addEdge(b,f); - ga.addEdge(b,g); - ga.addEdge(g,h); - ga.addEdge(h,i); - ga.addEdge(i,g); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(3, vec_triplets.size()); - } + { + // + // a b--g--h + // | \ / | \/ + // | d--e | i + // | / \ | + // c f + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(), e = ga.addNode(), f = ga.addNode(), g = ga.addNode(), + h = ga.addNode(), i = ga.addNode(); + + ga.addEdge(a, c); + ga.addEdge(a, d); + ga.addEdge(d, c); + ga.addEdge(d, e); + ga.addEdge(e, b); + ga.addEdge(e, f); + ga.addEdge(b, f); + ga.addEdge(b, g); + ga.addEdge(g, h); + ga.addEdge(h, i); + ga.addEdge(i, g); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(3, vec_triplets.size()); + } { - // - // a---b - // |\ |\ + // + // a---b + // |\ |\ // | \ | \ // | \| \ // c---d---e - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(), - e = ga.addNode(); - - ga.addEdge(a,b); - ga.addEdge(b,d); - ga.addEdge(d,c); - ga.addEdge(c,a); - ga.addEdge(a,d); - ga.addEdge(b,e); - ga.addEdge(d,e); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(3, vec_triplets.size()); - } + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(), e = ga.addNode(); + + ga.addEdge(a, b); + ga.addEdge(b, d); + ga.addEdge(d, c); + ga.addEdge(c, a); + ga.addEdge(a, d); + ga.addEdge(b, e); + ga.addEdge(d, e); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(3, vec_triplets.size()); + } } -BOOST_AUTO_TEST_CASE(test_for_triplet) { - - typedef lemon::ListGraph Graph; - { - // - // a__b - // |\/| - // |/\| - // c--d - Graph ga; - Graph::Node a = ga.addNode(), b = ga.addNode(), - c = ga.addNode(), d = ga.addNode(); - - ga.addEdge(a,b); - ga.addEdge(a,c); - ga.addEdge(a,d); - ga.addEdge(c,d); - ga.addEdge(b,d); - ga.addEdge(c,b); - - std::vector< Triplet > vec_triplets; - BOOST_CHECK(List_Triplets(ga, vec_triplets)); - BOOST_CHECK_EQUAL(4, vec_triplets.size()); - } +BOOST_AUTO_TEST_CASE(test_for_triplet) +{ + typedef lemon::ListGraph Graph; + { + // + // a__b + // |\/| + // |/\| + // c--d + Graph ga; + Graph::Node a = ga.addNode(), b = ga.addNode(), c = ga.addNode(), d = ga.addNode(); + + ga.addEdge(a, b); + ga.addEdge(a, c); + ga.addEdge(a, d); + ga.addEdge(c, d); + ga.addEdge(b, d); + ga.addEdge(c, b); + + std::vector vec_triplets; + BOOST_CHECK(List_Triplets(ga, vec_triplets)); + BOOST_CHECK_EQUAL(4, vec_triplets.size()); + } } diff --git a/src/aliceVision/half.hpp b/src/aliceVision/half.hpp index 7b13584441..6708af056e 100644 --- a/src/aliceVision/half.hpp +++ b/src/aliceVision/half.hpp @@ -8,14 +8,12 @@ #pragma once #if __has_include() - // Try to use the modern header first: OpenEXR >= 3.0 - #include + // Try to use the modern header first: OpenEXR >= 3.0 + #include #elif __has_include() - // Try fallback for compatibility with OpenEXR < 3.0 - #include + // Try fallback for compatibility with OpenEXR < 3.0 + #include #else - // Generate an error using the modern header - #include + // Generate an error using the modern header + #include #endif - - diff --git a/src/aliceVision/hdr/DebevecCalibrate.cpp b/src/aliceVision/hdr/DebevecCalibrate.cpp index 00c099da4f..2d01834f98 100644 --- a/src/aliceVision/hdr/DebevecCalibrate.cpp +++ b/src/aliceVision/hdr/DebevecCalibrate.cpp @@ -18,25 +18,25 @@ #include #include - namespace aliceVision { namespace hdr { - bool DebevecCalibrate::process(const std::vector>& ldrSamples, - const std::vector>& times, const std::size_t channelQuantization, - const rgbCurve& weight, float lambda, rgbCurve& response) + const std::vector>& times, + const std::size_t channelQuantization, + const rgbCurve& weight, + float lambda, + rgbCurve& response) { // Always 3 channels for the input images static const std::size_t channelsCount = 3; - // Count really extracted amount of points (observed in multiple brackets) std::vector countPointPerGroup; size_t totalPoints = 0; - for(size_t groupId = 0; groupId < ldrSamples.size(); groupId++) + for (size_t groupId = 0; groupId < ldrSamples.size(); groupId++) { - const std::vector & group = ldrSamples[groupId]; + const std::vector& group = ldrSamples[groupId]; totalPoints += group.size(); } @@ -48,7 +48,7 @@ bool DebevecCalibrate::process(const std::vector>& ldrS // Store intermediate data for all three channels // Initialize intermediate buffers - for(unsigned int channel = 0; channel < channelsCount; ++channel) + for (unsigned int channel = 0; channel < channelsCount; ++channel) { Eigen::MatrixXd A(channelQuantization, channelQuantization); Eigen::MatrixXd B(channelQuantization, totalPoints); @@ -64,18 +64,18 @@ bool DebevecCalibrate::process(const std::vector>& ldrS Dinv.setZero(); size_t countPoints = 0; - for(size_t groupId = 0; groupId < ldrSamples.size(); groupId++) + for (size_t groupId = 0; groupId < ldrSamples.size(); groupId++) { /*Process a group of brackets*/ const std::vector& group = ldrSamples[groupId]; - const std::vector & local_times = times[groupId]; - - for (size_t sampleId = 0; sampleId < group.size(); sampleId++) { - - const ImageSample & sample = group[sampleId]; - - for (size_t bracketPos = 0; bracketPos < sample.descriptions.size(); bracketPos++) { - + const std::vector& local_times = times[groupId]; + + for (size_t sampleId = 0; sampleId < group.size(); sampleId++) + { + const ImageSample& sample = group[sampleId]; + + for (size_t bracketPos = 0; bracketPos < sample.descriptions.size(); bracketPos++) + { const float time = std::log(sample.descriptions[bracketPos].exposure); const float value = clamp(sample.descriptions[bracketPos].mean(channel), 0.0f, 1.0f); @@ -83,7 +83,7 @@ bool DebevecCalibrate::process(const std::vector>& ldrS const std::size_t index = quantizedValue; const float w_ij = std::max(1e-6f, weight(value, channel)); - + const std::size_t pospoint = countPoints + sampleId; const double w_ij_2 = w_ij * w_ij; @@ -98,11 +98,10 @@ bool DebevecCalibrate::process(const std::vector>& ldrS } countPoints += group.size(); - } // Make sure the discrete response curve has a minimal second derivative - for(std::size_t k = 0; k < channelQuantization - 2; k++) + for (std::size_t k = 0; k < channelQuantization - 2; k++) { // Simple derivatives of second derivative wrt to the k+1 element // f''(x) = f(x + 1) - 2 * f(x) + f(x - 1) @@ -146,7 +145,7 @@ bool DebevecCalibrate::process(const std::vector>& ldrS // [h2] [Atr^T bh + Abr^T bb] [Abr^T bb] const Eigen::MatrixXd C = B.transpose(); - for(int i = 0; i < Dinv.rows(); i++) + for (int i = 0; i < Dinv.rows(); i++) { Dinv.diagonal()[i] = 1.0 / Dinv.diagonal()[i]; } @@ -158,7 +157,7 @@ bool DebevecCalibrate::process(const std::vector>& ldrS const Eigen::VectorXd x = left.lu().solve(right); // Copy the result to the response curve - for(std::size_t k = 0; k < channelQuantization; ++k) + for (std::size_t k = 0; k < channelQuantization; ++k) { response.setValue(k, channel, x(k)); } @@ -167,5 +166,5 @@ bool DebevecCalibrate::process(const std::vector>& ldrS return true; } -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/DebevecCalibrate.hpp b/src/aliceVision/hdr/DebevecCalibrate.hpp index 5f7c859ed2..781cb57c51 100644 --- a/src/aliceVision/hdr/DebevecCalibrate.hpp +++ b/src/aliceVision/hdr/DebevecCalibrate.hpp @@ -12,7 +12,6 @@ #include #include - namespace aliceVision { namespace hdr { @@ -28,25 +27,23 @@ namespace hdr { */ class DebevecCalibrate { -public: - - /** - * @brief - * @param[in] LDR images groups - * @param[in] exposure times - * @param[in] channel quantization - * @param[in] calibration weight function - * @param[in] lambda (parameter of smoothness) - * @param[out] camera response function - */ - bool process(const std::vector> & ldrSamples, - const std::vector> ×, - std::size_t channelQuantization, - const rgbCurve &weight, - float lambda, - rgbCurve &response); - + public: + /** + * @brief + * @param[in] LDR images groups + * @param[in] exposure times + * @param[in] channel quantization + * @param[in] calibration weight function + * @param[in] lambda (parameter of smoothness) + * @param[out] camera response function + */ + bool process(const std::vector>& ldrSamples, + const std::vector>& times, + std::size_t channelQuantization, + const rgbCurve& weight, + float lambda, + rgbCurve& response); }; -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/GrossbergCalibrate.cpp b/src/aliceVision/hdr/GrossbergCalibrate.cpp index 5097d807d8..afe480bc60 100644 --- a/src/aliceVision/hdr/GrossbergCalibrate.cpp +++ b/src/aliceVision/hdr/GrossbergCalibrate.cpp @@ -16,12 +16,12 @@ namespace aliceVision { namespace hdr { -GrossbergCalibrate::GrossbergCalibrate(unsigned int dimension) -{ - _dimension = dimension; -} +GrossbergCalibrate::GrossbergCalibrate(unsigned int dimension) { _dimension = dimension; } -void GrossbergCalibrate::process(const std::vector>& ldrSamples, const std::vector>& times, std::size_t channelQuantization, rgbCurve& response) +void GrossbergCalibrate::process(const std::vector>& ldrSamples, + const std::vector>& times, + std::size_t channelQuantization, + rgbCurve& response) { const double step = 1.0 / double(channelQuantization - 1); @@ -33,7 +33,7 @@ void GrossbergCalibrate::process(const std::vector>& ld response.setEmorInv(0); const std::size_t emorSize = std::pow(2, 10); - if(channelQuantization != emorSize) + if (channelQuantization != emorSize) { ALICEVISION_LOG_ERROR("Incompatible channel quantization"); return; @@ -45,16 +45,17 @@ void GrossbergCalibrate::process(const std::vector>& ld // sum(c_i * f_i(Ba)) - k*sum(c_i * f_i(Bb)) = k*f0(Bb) - f0(Ba) size_t count_measures = 0; - for(size_t group = 0; group < ldrSamples.size(); group++) + for (size_t group = 0; group < ldrSamples.size(); group++) { - const std::vector & groupSamples = ldrSamples[group]; + const std::vector& groupSamples = ldrSamples[group]; - for (size_t sampleId = 0; sampleId < groupSamples.size(); sampleId++) { + for (size_t sampleId = 0; sampleId < groupSamples.size(); sampleId++) + { count_measures += groupSamples[sampleId].descriptions.size() - 1; } } - for(int channel = 0; channel < 3; channel++) + for (int channel = 0; channel < 3; channel++) { Eigen::MatrixXd E(count_measures, _dimension); Eigen::MatrixXd v(count_measures, 1); @@ -62,21 +63,21 @@ void GrossbergCalibrate::process(const std::vector>& ld rgbCurve f0(channelQuantization); f0.setEmorInv(0); - for(size_t dim = 0; dim < _dimension; dim++) + for (size_t dim = 0; dim < _dimension; dim++) { rgbCurve fdim(channelQuantization); fdim.setEmorInv(dim + 1); size_t rowId = 0; - for(size_t groupId = 0; groupId < ldrSamples.size(); groupId++) + for (size_t groupId = 0; groupId < ldrSamples.size(); groupId++) { const std::vector& groupSamples = ldrSamples[groupId]; - for(size_t sampleId = 0; sampleId < groupSamples.size(); sampleId++) + for (size_t sampleId = 0; sampleId < groupSamples.size(); sampleId++) { - const ImageSample & sample = groupSamples[sampleId]; + const ImageSample& sample = groupSamples[sampleId]; - for(size_t bracketPos = 0; bracketPos < sample.descriptions.size() - 1; bracketPos++) + for (size_t bracketPos = 0; bracketPos < sample.descriptions.size() - 1; bracketPos++) { image::Rgb Ba = sample.descriptions[bracketPos].mean; image::Rgb Bb = sample.descriptions[bracketPos + 1].mean; @@ -99,7 +100,6 @@ void GrossbergCalibrate::process(const std::vector>& ld Eigen::MatrixXd H = E.transpose() * E; Eigen::VectorXd d = (E.transpose() * v).col(0); - // d (f0(val) + sum_i(c_i * f_i(val))) d_val > 0 // d (f0(val)) + sum_i(d(c_i * f_i(val))) > 0 // d (f0(val)) + sum_i(c_i * d_f_i(val)) > 0 @@ -109,7 +109,7 @@ void GrossbergCalibrate::process(const std::vector>& ld Eigen::MatrixXd dF0(channelQuantization - 1, 1); dF0.setZero(); - for(int i = 0; i < channelQuantization - 1; i++) + for (int i = 0; i < channelQuantization - 1; i++) { double eval_cur = double(i) * step; double eval_next = double(i + 1) * step; @@ -120,12 +120,12 @@ void GrossbergCalibrate::process(const std::vector>& ld Eigen::MatrixXd D(channelQuantization - 1, _dimension); D.setZero(); - for(int dim = 0; dim < _dimension; dim++) + for (int dim = 0; dim < _dimension; dim++) { rgbCurve fdim(channelQuantization); fdim.setEmorInv(dim + 1); - for(int i = 0; i < channelQuantization - 1; i++) + for (int i = 0; i < channelQuantization - 1; i++) { double eval_cur = double(i) * step; double eval_next = double(i + 1) * step; @@ -134,16 +134,16 @@ void GrossbergCalibrate::process(const std::vector>& ld } Eigen::MatrixXd CE(_dimension, 1); - for(int i = 0; i < 1; i++) + for (int i = 0; i < 1; i++) { - for(int j = 0; j < _dimension; j++) + for (int j = 0; j < _dimension; j++) { CE(j, i) = 0.0; } } Eigen::VectorXd ce0(1); - for(int i = 0; i < 1; i++) + for (int i = 0; i < 1; i++) { ce0[i] = 0.0; } @@ -152,16 +152,15 @@ void GrossbergCalibrate::process(const std::vector>& ld // Create final curve std::vector& curve = response.getCurve(channel); - for(unsigned int i = 0; i < curve.size(); ++i) + for (unsigned int i = 0; i < curve.size(); ++i) { rgbCurve f0(channelQuantization); f0.setEmorInv(0); const double val = double(i) * step; double curve_val = f0(val, 0); - for(int d = 0; d < _dimension; d++) + for (int d = 0; d < _dimension; d++) { - rgbCurve fdim(channelQuantization); fdim.setEmorInv(d + 1); curve_val += c(d) * fdim(val, 0); @@ -172,5 +171,5 @@ void GrossbergCalibrate::process(const std::vector>& ld } } -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/GrossbergCalibrate.hpp b/src/aliceVision/hdr/GrossbergCalibrate.hpp index 33c6026797..4978b812af 100644 --- a/src/aliceVision/hdr/GrossbergCalibrate.hpp +++ b/src/aliceVision/hdr/GrossbergCalibrate.hpp @@ -10,7 +10,6 @@ #include "emorCurve.hpp" #include "rgbCurve.hpp" - namespace aliceVision { namespace hdr { @@ -29,26 +28,25 @@ namespace hdr { */ class GrossbergCalibrate { -public: - explicit GrossbergCalibrate(unsigned int dimension); - - - /** - * @brief - * @param[in] LDR images groups - * @param[in] exposure times - * @param[in] channel quantization - * @param[out] camera response function - */ - void process(const std::vector>& ldrSamples, - const std::vector< std::vector > ×, - std::size_t channelQuantization, - rgbCurve &response); - -private: - /// Dimension of the response ie number of basis vectors to calculate the response function - unsigned int _dimension; + public: + explicit GrossbergCalibrate(unsigned int dimension); + + /** + * @brief + * @param[in] LDR images groups + * @param[in] exposure times + * @param[in] channel quantization + * @param[out] camera response function + */ + void process(const std::vector>& ldrSamples, + const std::vector>& times, + std::size_t channelQuantization, + rgbCurve& response); + + private: + /// Dimension of the response ie number of basis vectors to calculate the response function + unsigned int _dimension; }; -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/LaguerreBACalibration.cpp b/src/aliceVision/hdr/LaguerreBACalibration.cpp index 751a001280..1f88629b2b 100644 --- a/src/aliceVision/hdr/LaguerreBACalibration.cpp +++ b/src/aliceVision/hdr/LaguerreBACalibration.cpp @@ -72,12 +72,11 @@ double d_laguerreFunction_d_x(double a, double x) class HdrResidualAnalytic : public ceres::SizedCostFunction<2, 1, 1> { -public: + public: HdrResidualAnalytic(double a, double b) - : _colorA(a) - , _colorB(b) - { - } + : _colorA(a), + _colorB(b) + {} bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override { @@ -92,26 +91,26 @@ class HdrResidualAnalytic : public ceres::SizedCostFunction<2, 1, 1> residuals[0] = errorCost_1; residuals[1] = errorCost_2; - - if(jacobians == nullptr) + + if (jacobians == nullptr) { return true; } - if(jacobians[0] != nullptr) + if (jacobians[0] != nullptr) { - double d_errorCost_1_d_laguerre_param = d_laguerreFunction_d_param(laguerre_param, a) + - d_laguerreFunction_d_x(laguerre_param, a) * ratio_expB_over_expA * - -d_laguerreFunction_d_param(-laguerre_param, _colorA); - double d_errorCost_2_d_laguerre_param = d_laguerreFunction_d_param(laguerre_param, b) + - d_laguerreFunction_d_x(laguerre_param, b) / ratio_expB_over_expA * - -d_laguerreFunction_d_param(-laguerre_param, _colorB); + double d_errorCost_1_d_laguerre_param = + d_laguerreFunction_d_param(laguerre_param, a) + + d_laguerreFunction_d_x(laguerre_param, a) * ratio_expB_over_expA * -d_laguerreFunction_d_param(-laguerre_param, _colorA); + double d_errorCost_2_d_laguerre_param = + d_laguerreFunction_d_param(laguerre_param, b) + + d_laguerreFunction_d_x(laguerre_param, b) / ratio_expB_over_expA * -d_laguerreFunction_d_param(-laguerre_param, _colorB); jacobians[0][0] = d_errorCost_1_d_laguerre_param; jacobians[0][1] = d_errorCost_2_d_laguerre_param; } - if(jacobians[1] != nullptr) + if (jacobians[1] != nullptr) { jacobians[1][0] = d_laguerreFunction_d_x(laguerre_param, a) * laguerreFunctionInv(laguerre_param, _colorA); jacobians[1][1] = d_laguerreFunction_d_x(laguerre_param, b) * laguerreFunctionInv(laguerre_param, _colorB) * @@ -121,18 +120,17 @@ class HdrResidualAnalytic : public ceres::SizedCostFunction<2, 1, 1> return true; } -private: + private: double _colorA; double _colorB; }; class ExposureConstraint : public ceres::SizedCostFunction<1, 1> { -public: + public: explicit ExposureConstraint(double ratio) - : _ratio(ratio) - { - } + : _ratio(ratio) + {} bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override { @@ -141,12 +139,12 @@ class ExposureConstraint : public ceres::SizedCostFunction<1, 1> const double w = 1.0; residuals[0] = w * (ratio_cur - _ratio); - if(jacobians == nullptr) + if (jacobians == nullptr) { return true; } - if(jacobians[0] != nullptr) + if (jacobians[0] != nullptr) { jacobians[0][0] = w; } @@ -154,19 +152,20 @@ class ExposureConstraint : public ceres::SizedCostFunction<1, 1> return true; } -private: + private: double _ratio; }; void LaguerreBACalibration::process(const std::vector>& ldrSamples, - std::vector> &cameraExposures, + std::vector>& cameraExposures, const std::size_t channelQuantization, - bool refineExposures, rgbCurve& response) + bool refineExposures, + rgbCurve& response) { std::map, double> exposureParameters; - for(std::vector& group : cameraExposures) + for (std::vector& group : cameraExposures) { - for(int index = 0; index < group.size() - 1; index++) + for (int index = 0; index < group.size() - 1; index++) { std::pair exposurePair; exposurePair.first = group[index]; @@ -180,38 +179,41 @@ void LaguerreBACalibration::process(const std::vector>& ceres::Problem problem; ceres::LossFunction* lossFunction = nullptr; - for(auto& param : exposureParameters) { + for (auto& param : exposureParameters) + { problem.AddParameterBlock(¶m.second, 1); } // Convert selected samples into residual blocks - for(int groupId = 0; groupId < ldrSamples.size(); ++groupId) + for (int groupId = 0; groupId < ldrSamples.size(); ++groupId) { - const std::vector & group = ldrSamples[groupId]; + const std::vector& group = ldrSamples[groupId]; - for (int sampleId = 0; sampleId < group.size(); sampleId++) { - - const ImageSample & sample = group[sampleId]; + for (int sampleId = 0; sampleId < group.size(); sampleId++) + { + const ImageSample& sample = group[sampleId]; - for (int bracketPos = 0; bracketPos < sample.descriptions.size() - 1; bracketPos++) { - + for (int bracketPos = 0; bracketPos < sample.descriptions.size() - 1; bracketPos++) + { std::pair exposurePair; exposurePair.first = sample.descriptions[bracketPos].exposure; exposurePair.second = sample.descriptions[bracketPos + 1].exposure; - double * expParam = &exposureParameters[exposurePair]; + double* expParam = &exposureParameters[exposurePair]; - for (int channel = 0; channel < 3; channel++) { - ceres::CostFunction * cost = new HdrResidualAnalytic(sample.descriptions[bracketPos].mean(channel), sample.descriptions[bracketPos + 1].mean(channel)); + for (int channel = 0; channel < 3; channel++) + { + ceres::CostFunction* cost = + new HdrResidualAnalytic(sample.descriptions[bracketPos].mean(channel), sample.descriptions[bracketPos + 1].mean(channel)); problem.AddResidualBlock(cost, lossFunction, &(laguerreParam.data()[channel]), expParam); } } } } - if(!refineExposures) + if (!refineExposures) { - for(auto& param : exposureParameters) + for (auto& param : exposureParameters) { problem.AddParameterBlock(¶m.second, 1); problem.SetParameterBlockConstant(¶m.second); @@ -219,7 +221,7 @@ void LaguerreBACalibration::process(const std::vector>& } else { - for(auto& param : exposureParameters) + for (auto& param : exposureParameters) { problem.AddResidualBlock(new ExposureConstraint(param.second), nullptr, ¶m.second); } @@ -248,22 +250,22 @@ void LaguerreBACalibration::process(const std::vector>& const double step = 1.0 / double(curve.size()); std::cout << laguerreParam[channel] << std::endl; - for(unsigned int i = 0; i < curve.size(); ++i) + for (unsigned int i = 0; i < curve.size(); ++i) { curve[i] = laguerreFunctionInv(laguerreParam[channel], i * step); } } - if(refineExposures) + if (refineExposures) { - for(size_t idGroup = 0; idGroup < cameraExposures.size(); idGroup++) - { - std::vector & group = cameraExposures[idGroup]; - - //Copy ! + for (size_t idGroup = 0; idGroup < cameraExposures.size(); idGroup++) + { + std::vector& group = cameraExposures[idGroup]; + + // Copy ! std::vector res = cameraExposures[idGroup]; - - for(int index = 0; index < group.size() - 1; index++) + + for (int index = 0; index < group.size() - 1; index++) { std::pair exposurePair; exposurePair.first = group[index]; @@ -277,5 +279,5 @@ void LaguerreBACalibration::process(const std::vector>& } } -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/LaguerreBACalibration.hpp b/src/aliceVision/hdr/LaguerreBACalibration.hpp index 33290c294f..9b27f6c75e 100644 --- a/src/aliceVision/hdr/LaguerreBACalibration.hpp +++ b/src/aliceVision/hdr/LaguerreBACalibration.hpp @@ -12,7 +12,6 @@ #include - namespace aliceVision { namespace hdr { @@ -33,26 +32,25 @@ namespace hdr { */ class LaguerreBACalibration { -public: - explicit LaguerreBACalibration() = default; + public: + explicit LaguerreBACalibration() = default; - /** - * @brief - * @param[in] LDR images groups - * @param[in] channel quantization - * @param[in] exposure times - * @param[in] calibration weight function - * @param[out] camera response function - */ - void process( - const std::vector>& ldrSamples, - std::vector>& cameraExposures, - const std::size_t channelQuantization, - bool refineExposures, - rgbCurve &response); + /** + * @brief + * @param[in] LDR images groups + * @param[in] channel quantization + * @param[in] exposure times + * @param[in] calibration weight function + * @param[out] camera response function + */ + void process(const std::vector>& ldrSamples, + std::vector>& cameraExposures, + const std::size_t channelQuantization, + bool refineExposures, + rgbCurve& response); }; -template +template T laguerreFunction(const T& a, const T& x) { // https://www.desmos.com/calculator/ib1y06t4pe @@ -61,12 +59,11 @@ T laguerreFunction(const T& a, const T& x) return x + c * atan((a * sin(pi() * x)) / (1.0 - a * cos(pi() * x))); } -template +template T laguerreFunctionInv(const T& a, const T& x) { return laguerreFunction(-a, x); } - -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/QuadProg++.cpp b/src/aliceVision/hdr/QuadProg++.cpp index bc2c98a16c..4168d23ce1 100644 --- a/src/aliceVision/hdr/QuadProg++.cpp +++ b/src/aliceVision/hdr/QuadProg++.cpp @@ -65,34 +65,34 @@ double solve_quadprog(Eigen::MatrixXd& G, { std::ostringstream msg; unsigned int n = G.cols(), p = CE.cols(), m = CI.cols(); - if(G.rows() != n) + if (G.rows() != n) { msg << "The matrix G is not a squared matrix (" << G.rows() << " x " << G.cols() << ")"; throw std::logic_error(msg.str()); } - if(CE.rows() != n) + if (CE.rows() != n) { msg << "The matrix CE is incompatible (incorrect number of rows " << CE.rows() << " , expecting " << n << ")"; throw std::logic_error(msg.str()); } - if(ce0.size() != p) + if (ce0.size() != p) { msg << "The vector ce0 is incompatible (incorrect dimension " << ce0.size() << ", expecting " << p << ")"; throw std::logic_error(msg.str()); } - if(CI.rows() != n) + if (CI.rows() != n) { msg << "The matrix CI is incompatible (incorrect number of rows " << CI.rows() << " , expecting " << n << ")"; throw std::logic_error(msg.str()); } - if(ci0.size() != m) + if (ci0.size() != m) { msg << "The vector ci0 is incompatible (incorrect dimension " << ci0.size() << ", expecting " << m << ")"; throw std::logic_error(msg.str()); } x.resize(n); - int ip; // this is the index of the constraint to be added to the active set + int ip; // this is the index of the constraint to be added to the active set Eigen::Matrix R(n, n), J(n, n); Eigen::VectorXd s(m + p), z(n), r(m + p), d(n), np(n), u(m + p), x_old(n), u_old(m + p); double f_value, psi, c1, c2, sum, ss, R_norm; @@ -111,7 +111,7 @@ double solve_quadprog(Eigen::MatrixXd& G, /* compute the trace of the original matrix G */ c1 = 0.0; - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { c1 += G(i, i); } @@ -119,10 +119,10 @@ double solve_quadprog(Eigen::MatrixXd& G, cholesky_decomposition(G); /* initialize the matrix R */ - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { d[i] = 0.0; - for(Eigen::Index j = 0; j < n; ++j) + for (Eigen::Index j = 0; j < n; ++j) { R(i, j) = 0.0; } @@ -131,11 +131,11 @@ double solve_quadprog(Eigen::MatrixXd& G, /* compute the inverse of the factorized matrix G^-1, this is the initial value for H */ c2 = 0.0; - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { d[i] = 1.0; forward_elimination(G, z, d); - for(Eigen::Index j = 0; j < n; ++j) + for (Eigen::Index j = 0; j < n; ++j) { J(i, j) = z[j]; } @@ -151,7 +151,7 @@ double solve_quadprog(Eigen::MatrixXd& G, * x = G^-1 * g0 */ cholesky_solve(G, x, g0); - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { x[i] = -x[i]; } @@ -161,9 +161,9 @@ double solve_quadprog(Eigen::MatrixXd& G, /* Add equality constraints to the working set A */ iq = 0; std::cout << p << std::endl; - for(Eigen::Index i = 0; i < p; ++i) + for (Eigen::Index i = 0; i < p; ++i) { - for(Eigen::Index j = 0; j < n; ++j) + for (Eigen::Index j = 0; j < n; ++j) { np[j] = CE(j, i); } @@ -174,18 +174,18 @@ double solve_quadprog(Eigen::MatrixXd& G, /* compute full step length t2: i.e., the minimum step in primal space s.t. the contraint becomes feasible */ t2 = 0.0; - if(fabs(scalar_product(z, z)) > std::numeric_limits::epsilon()) // i.e. z != 0 + if (fabs(scalar_product(z, z)) > std::numeric_limits::epsilon()) // i.e. z != 0 t2 = (-scalar_product(np, x) - ce0[i]) / scalar_product(z, np); /* set x = x + t2 * z */ - for(Eigen::Index k = 0; k < n; ++k) + for (Eigen::Index k = 0; k < n; ++k) { x[k] += t2 * z[k]; } /* set u = u+ */ u[iq] = t2; - for(Eigen::Index k = 0; k < iq; ++k) + for (Eigen::Index k = 0; k < iq; ++k) { u[k] -= t2 * r[k]; } @@ -203,7 +203,7 @@ double solve_quadprog(Eigen::MatrixXd& G, } /* set iai = K \ A */ - for(Eigen::Index i = 0; i < m; ++i) + for (Eigen::Index i = 0; i < m; ++i) { iai[i] = i; } @@ -211,7 +211,7 @@ double solve_quadprog(Eigen::MatrixXd& G, l1: iter++; /* step 1: choose a violated constraint */ - for(Eigen::Index i = p; i < iq; ++i) + for (Eigen::Index i = p; i < iq; ++i) { ip = A[i]; iai[ip] = -1; @@ -221,11 +221,11 @@ double solve_quadprog(Eigen::MatrixXd& G, ss = 0.0; psi = 0.0; /* this value will contain the sum of all infeasibilities */ ip = 0; /* ip will be the index of the chosen violated constraint */ - for(Eigen::Index i = 0; i < m; ++i) + for (Eigen::Index i = 0; i < m; ++i) { iaexcl[i] = true; sum = 0.0; - for(Eigen::Index j = 0; j < n; ++j) + for (Eigen::Index j = 0; j < n; ++j) { sum += CI(j, i) * x[j]; } @@ -234,40 +234,40 @@ double solve_quadprog(Eigen::MatrixXd& G, psi += std::min(0.0, sum); } - if(fabs(psi) <= m * std::numeric_limits::epsilon() * c1 * c2 * 100.0) + if (fabs(psi) <= m * std::numeric_limits::epsilon() * c1 * c2 * 100.0) { /* numerically there are not infeasibilities anymore */ return f_value; } /* save old values for u and A */ - for(Eigen::Index i = 0; i < iq; ++i) + for (Eigen::Index i = 0; i < iq; ++i) { u_old[i] = u[i]; A_old[i] = A[i]; } /* and for x */ - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { x_old[i] = x[i]; } l2: /* Step 2: check for feasibility and determine a new S-pair */ - for(Eigen::Index i = 0; i < m; ++i) + for (Eigen::Index i = 0; i < m; ++i) { - if(s[i] < ss && iai[i] != -1 && iaexcl[i]) + if (s[i] < ss && iai[i] != -1 && iaexcl[i]) { ss = s[i]; ip = i; } } - if(ss >= 0.0) + if (ss >= 0.0) { return f_value; } /* set np = n[ip] */ - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) np[i] = CI(i, ip); /* set u = [u 0]^T */ u[iq] = 0.0; @@ -286,11 +286,11 @@ double solve_quadprog(Eigen::MatrixXd& G, /* Compute t1: partial step length (maximum step in dual space without violating dual feasibility */ t1 = inf; /* +inf */ /* find the index l s.t. it reaches the minimum of u+[x] / r */ - for(Eigen::Index k = p; k < iq; ++k) + for (Eigen::Index k = p; k < iq; ++k) { - if(r[k] > 0.0) + if (r[k] > 0.0) { - if(u[k] / r[k] < t1) + if (u[k] / r[k] < t1) { t1 = u[k] / r[k]; l = A[k]; @@ -298,10 +298,10 @@ double solve_quadprog(Eigen::MatrixXd& G, } } /* Compute t2: full step length (minimum step in primal space such that the constraint ip becomes feasible */ - if(fabs(scalar_product(z, z)) > std::numeric_limits::epsilon()) // i.e. z != 0 + if (fabs(scalar_product(z, z)) > std::numeric_limits::epsilon()) // i.e. z != 0 { t2 = -s[ip] / scalar_product(z, np); - if(t2 < 0) // patch suggested by Takano Akio for handling numerical inconsistencies + if (t2 < 0) // patch suggested by Takano Akio for handling numerical inconsistencies t2 = inf; } else @@ -313,17 +313,17 @@ double solve_quadprog(Eigen::MatrixXd& G, /* Step 2c: determine new S-pair and take step: */ /* case (i): no step in primal or dual space */ - if(t >= inf) + if (t >= inf) { /* QPP is infeasible */ // FIXME: unbounded to raise return inf; } /* case (ii): step in dual space */ - if(t2 >= inf) + if (t2 >= inf) { /* set u = u + t * [-r 1] and drop constraint l from the active set A */ - for(Eigen::Index k = 0; k < iq; ++k) + for (Eigen::Index k = 0; k < iq; ++k) u[k] -= t * r[k]; u[iq] += t; iai[l] = l; @@ -335,33 +335,33 @@ double solve_quadprog(Eigen::MatrixXd& G, /* case (iii): step in primal and dual space */ /* set x = x + t * z */ - for(Eigen::Index k = 0; k < n; ++k) + for (Eigen::Index k = 0; k < n; ++k) x[k] += t * z[k]; /* update the solution value */ f_value += t * scalar_product(z, np) * (0.5 * t + u[iq]); /* u = u + t * [-r 1] */ - for(Eigen::Index k = 0; k < iq; ++k) + for (Eigen::Index k = 0; k < iq; ++k) u[k] -= t * r[k]; u[iq] += t; - if(fabs(t - t2) < std::numeric_limits::epsilon()) + if (fabs(t - t2) < std::numeric_limits::epsilon()) { /* full step has taken */ /* add constraint ip to the active set*/ - if(!add_constraint(R, J, d, iq, R_norm)) + if (!add_constraint(R, J, d, iq, R_norm)) { iaexcl[ip] = false; delete_constraint(R, J, A, u, n, p, iq, ip); - for(Eigen::Index i = 0; i < m; ++i) + for (Eigen::Index i = 0; i < m; ++i) iai[i] = i; - for(Eigen::Index i = p; i < iq; ++i) + for (Eigen::Index i = p; i < iq; ++i) { A[i] = A_old[i]; u[i] = u_old[i]; iai[A[i]] = -1; } - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) x[i] = x_old[i]; goto l2; /* go to step 2 */ } @@ -378,7 +378,7 @@ double solve_quadprog(Eigen::MatrixXd& G, /* update s[ip] = CI * x + ci0 */ sum = 0.0; - for(Eigen::Index k = 0; k < n; ++k) + for (Eigen::Index k = 0; k < n; ++k) sum += CI(k, ip) * x[k]; s[ip] = sum + ci0[ip]; @@ -390,10 +390,10 @@ inline void compute_d(Eigen::VectorXd& d, const Eigen::MatrixXd& J, const Eigen: const auto n = d.size(); /* compute d = H^T * np */ - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { double sum{0.0}; - for(Eigen::Index j = 0; j < n; ++j) + for (Eigen::Index j = 0; j < n; ++j) sum += J(j, i) * np[j]; d[i] = sum; } @@ -404,10 +404,10 @@ inline void update_z(Eigen::VectorXd& z, const Eigen::MatrixXd& J, const Eigen:: const auto n = z.size(); /* setting of z = H * d */ - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { z[i] = 0.0; - for(Eigen::Index j = iq; j < n; ++j) + for (Eigen::Index j = iq; j < n; ++j) z[i] += J(i, j) * d[j]; } } @@ -415,10 +415,10 @@ inline void update_z(Eigen::VectorXd& z, const Eigen::MatrixXd& J, const Eigen:: inline void update_r(const Eigen::MatrixXd& R, Eigen::VectorXd& r, const Eigen::VectorXd& d, int iq) { /* setting of r = R^-1 d */ - for(Eigen::Index i = iq - 1; i >= 0; --i) + for (Eigen::Index i = iq - 1; i >= 0; --i) { double sum{0.0}; - for(Eigen::Index j = i + 1; j < iq; ++j) + for (Eigen::Index j = i + 1; j < iq; ++j) sum += R(i, j) * r[j]; r[i] = (d[i] - sum) / R(i, i); } @@ -432,7 +432,7 @@ bool add_constraint(Eigen::MatrixXd& R, Eigen::MatrixXd& J, Eigen::VectorXd& d, d[j] to zero. if it is already zero we don't have to do anything, except of decreasing j */ - for(Eigen::Index j = n - 1; j >= iq + 1; --j) + for (Eigen::Index j = n - 1; j >= iq + 1; --j) { /* The Givens rotation is done with the matrix (cc cs, cs -cc). If cc is one, then element (j) of d is zero compared with element @@ -445,12 +445,12 @@ bool add_constraint(Eigen::MatrixXd& R, Eigen::MatrixXd& J, Eigen::VectorXd& d, auto cc = d[j - 1]; auto ss = d[j]; const auto h = distance(cc, ss); - if(fabs(h) < std::numeric_limits::epsilon()) // h == 0 + if (fabs(h) < std::numeric_limits::epsilon()) // h == 0 continue; d[j] = 0.0; ss = ss / h; cc = cc / h; - if(cc < 0.0) + if (cc < 0.0) { cc = -cc; ss = -ss; @@ -459,7 +459,7 @@ bool add_constraint(Eigen::MatrixXd& R, Eigen::MatrixXd& J, Eigen::VectorXd& d, else d[j - 1] = h; const auto xny = ss / (1.0 + cc); - for(Eigen::Index k = 0; k < n; ++k) + for (Eigen::Index k = 0; k < n; ++k) { const auto t1 = J(k, j - 1); const auto t2 = J(k, j); @@ -472,10 +472,10 @@ bool add_constraint(Eigen::MatrixXd& R, Eigen::MatrixXd& J, Eigen::VectorXd& d, /* To update R we have to put the iq components of the d vector into column iq - 1 of R */ - for(Eigen::Index i = 0; i < iq; ++i) + for (Eigen::Index i = 0; i < iq; ++i) R(i, iq - 1) = d[i]; - if(fabs(d[iq - 1]) <= std::numeric_limits::epsilon() * R_norm) + if (fabs(d[iq - 1]) <= std::numeric_limits::epsilon() * R_norm) { // problem degenerate return false; @@ -484,14 +484,7 @@ bool add_constraint(Eigen::MatrixXd& R, Eigen::MatrixXd& J, Eigen::VectorXd& d, return true; } -void delete_constraint(Eigen::MatrixXd& R, - Eigen::MatrixXd& J, - Eigen::VectorXi& A, - Eigen::VectorXd& u, - unsigned int n, - int p, - unsigned int& iq, - int l) +void delete_constraint(Eigen::MatrixXd& R, Eigen::MatrixXd& J, Eigen::VectorXi& A, Eigen::VectorXd& u, unsigned int n, int p, unsigned int& iq, int l) { #ifdef TRACE_SOLVER std::cout << "Delete constraint " << l << ' ' << iq; @@ -500,27 +493,27 @@ void delete_constraint(Eigen::MatrixXd& R, bool found = false; /* Find the index qq for active constraint l to be removed */ - for(Eigen::Index i = p; i < iq; ++i) + for (Eigen::Index i = p; i < iq; ++i) { - if(A[i] == l) + if (A[i] == l) { qq = i; found = true; break; } } - if(!found) + if (!found) { std::ostringstream os; os << "Attempt to delete non existing constraint, constraint: " << l; throw std::invalid_argument(os.str()); } /* remove the constraint from the active set and the duals */ - for(Eigen::Index i = qq; i < iq - 1; ++i) + for (Eigen::Index i = qq; i < iq - 1; ++i) { A[i] = A[i + 1]; u[i] = u[i + 1]; - for(Eigen::Index j = 0; j < n; ++j) + for (Eigen::Index j = 0; j < n; ++j) { R(j, i) = R(j, i + 1); } @@ -531,7 +524,7 @@ void delete_constraint(Eigen::MatrixXd& R, A[iq] = 0; u[iq] = 0.0; - for(Eigen::Index j = 0; j < iq; ++j) + for (Eigen::Index j = 0; j < iq; ++j) { R(j, iq - 1) = 0.0; } @@ -541,22 +534,22 @@ void delete_constraint(Eigen::MatrixXd& R, std::cout << '/' << iq << std::endl; #endif - if(iq == 0) + if (iq == 0) return; - for(Eigen::Index j = qq; j < iq; ++j) + for (Eigen::Index j = qq; j < iq; ++j) { auto cc = R(j, j); auto ss = R(j + 1, j); const auto h = distance(cc, ss); - if(fabs(h) < std::numeric_limits::epsilon()) // h == 0 + if (fabs(h) < std::numeric_limits::epsilon()) // h == 0 continue; cc = cc / h; ss = ss / h; R(j + 1, j) = 0.0; - if(cc < 0.0) + if (cc < 0.0) { R(j, j) = -h; cc = -cc; @@ -566,14 +559,14 @@ void delete_constraint(Eigen::MatrixXd& R, R(j, j) = h; const auto xny = ss / (1.0 + cc); - for(Eigen::Index k = j + 1; k < iq; ++k) + for (Eigen::Index k = j + 1; k < iq; ++k) { const auto t1 = R(j, k); const auto t2 = R(j + 1, k); R(j, k) = t1 * cc + t2 * ss; R(j + 1, k) = xny * (t1 + R(j, k)) - t2; } - for(Eigen::Index k = 0; k < n; ++k) + for (Eigen::Index k = 0; k < n; ++k) { const auto t1 = J(k, j); const auto t2 = J(k, j + 1); @@ -587,12 +580,12 @@ inline double distance(double a, double b) { const auto a1 = fabs(a); const auto b1 = fabs(b); - if(a1 > b1) + if (a1 > b1) { const auto t = (b1 / a1); return a1 * sqrt(1.0 + t * t); } - else if(b1 > a1) + else if (b1 > a1) { const auto t = (a1 / b1); return b1 * sqrt(1.0 + t * t); @@ -600,25 +593,22 @@ inline double distance(double a, double b) return a1 * sqrt(2.0); } -inline double scalar_product(const Eigen::VectorXd& x, const Eigen::VectorXd& y) -{ - return x.dot(y); -} +inline double scalar_product(const Eigen::VectorXd& x, const Eigen::VectorXd& y) { return x.dot(y); } void cholesky_decomposition(Eigen::MatrixXd& A) { const auto n = A.rows(); - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { - for(Eigen::Index j = i; j < n; ++j) + for (Eigen::Index j = i; j < n; ++j) { auto sum = A(i, j); - for(Eigen::Index k = i - 1; k >= 0; --k) + for (Eigen::Index k = i - 1; k >= 0; --k) sum -= A(i, k) * A(j, k); - if(i == j) + if (i == j) { - if(sum <= 0.0) + if (sum <= 0.0) { std::ostringstream os; // raise error @@ -631,7 +621,7 @@ void cholesky_decomposition(Eigen::MatrixXd& A) else A(j, i) = sum / A(i, i); } - for(Eigen::Index k = i + 1; k < n; ++k) + for (Eigen::Index k = i + 1; k < n; ++k) { A(i, k) = A(k, i); } @@ -654,10 +644,10 @@ inline void forward_elimination(const Eigen::MatrixXd& L, Eigen::VectorXd& y, co const auto n = L.rows(); y[0] = b[0] / L(0, 0); - for(Eigen::Index i = 1; i < n; ++i) + for (Eigen::Index i = 1; i < n; ++i) { y[i] = b[i]; - for(Eigen::Index j = 0; j < i; ++j) + for (Eigen::Index j = 0; j < i; ++j) y[i] -= L(i, j) * y[j]; y[i] = y[i] / L(i, i); } @@ -668,10 +658,10 @@ inline void backward_elimination(const Eigen::MatrixXd& U, Eigen::VectorXd& x, c const auto n = U.rows(); x[n - 1] = y[n - 1] / U(n - 1, n - 1); - for(Eigen::Index i = n - 2; i >= 0; --i) + for (Eigen::Index i = n - 2; i >= 0; --i) { x[i] = y[i]; - for(Eigen::Index j = i + 1; j < n; ++j) + for (Eigen::Index j = i + 1; j < n; ++j) x[i] -= U(i, j) * x[j]; x[i] = x[i] / U(i, i); } @@ -681,21 +671,21 @@ void print_matrix(const char* name, const Eigen::MatrixXd& A, int n, int m) { std::ostringstream s; std::string t; - if(n == -1) + if (n == -1) n = A.rows(); - if(m == -1) + if (m == -1) m = A.cols(); s << name << ": " << std::endl; - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { s << " "; - for(Eigen::Index j = 0; j < m; ++j) + for (Eigen::Index j = 0; j < m; ++j) s << A(i, j) << ", "; s << std::endl; } t = s.str(); - t = t.substr(0, t.size() - 3); // To remove the trailing space, comma and newline + t = t.substr(0, t.size() - 3); // To remove the trailing space, comma and newline std::cout << t << std::endl; } @@ -705,18 +695,18 @@ void print_vector(const char* name, const Eigen::Matrix& v { std::ostringstream s; std::string t; - if(n == -1) + if (n == -1) n = v.size(); s << name << ": " << std::endl << " "; - for(Eigen::Index i = 0; i < n; ++i) + for (Eigen::Index i = 0; i < n; ++i) { s << v[i] << ", "; } t = s.str(); - t = t.substr(0, t.size() - 2); // To remove the trailing space and comma + t = t.substr(0, t.size() - 2); // To remove the trailing space and comma std::cout << t << std::endl; } -} // namespace quadprogpp +} // namespace quadprogpp diff --git a/src/aliceVision/hdr/QuadProg++.hpp b/src/aliceVision/hdr/QuadProg++.hpp index 202c8e066a..84820cc05b 100644 --- a/src/aliceVision/hdr/QuadProg++.hpp +++ b/src/aliceVision/hdr/QuadProg++.hpp @@ -1,59 +1,59 @@ /* - File $Id: QuadProg++.hh 232 2007-06-21 12:29:00Z digasper $ - - The quadprog_solve() function implements the algorithm of Goldfarb and Idnani + File $Id: QuadProg++.hh 232 2007-06-21 12:29:00Z digasper $ + + The quadprog_solve() function implements the algorithm of Goldfarb and Idnani for the solution of a (convex) Quadratic Programming problem by means of an active-set dual method. - + The problem is in the form: min 0.5 * x G x + g0 x s.t. CE^T x + ce0 = 0 CI^T x + ci0 >= 0 - + The matrix and vectors dimensions are as follows: G: n * n - g0: n - - CE: n * p - ce0: p - - CI: n * m + g0: n + + CE: n * p + ce0: p + + CI: n * m ci0: m x: n - + The function will return the cost of the solution written in the x vector or std::numeric_limits::infinity() if the problem is infeasible. In the latter case the value of the x vector is not correct. - + References: D. Goldfarb, A. Idnani. A numerically stable dual method for solving strictly convex quadratic programs. Mathematical Programming 27 (1983) pp. 1-33. Notes: - 1. pay attention in setting up the vectors ce0 and ci0. - If the constraints of your problem are specified in the form - A^T x = b and C^T x >= d, then you should set ce0 = -b and ci0 = -d. - 2. The matrices have column dimension equal to MATRIX_DIM, - a constant set to 20 in this file (by means of a #define macro). + 1. pay attention in setting up the vectors ce0 and ci0. + If the constraints of your problem are specified in the form + A^T x = b and C^T x >= d, then you should set ce0 = -b and ci0 = -d. + 2. The matrices have column dimension equal to MATRIX_DIM, + a constant set to 20 in this file (by means of a #define macro). If the matrices are bigger than 20 x 20 the limit could be - increased by means of a -DMATRIX_DIM=n on the compiler command line. + increased by means of a -DMATRIX_DIM=n on the compiler command line. 3. The matrix G is modified within the function since it is used to compute - the G = L^T L cholesky factorization for further computations inside the function. + the G = L^T L cholesky factorization for further computations inside the function. If you need the original matrix G you should make a copy of it and pass the copy to the function. - + Author: Luca Di Gaspero - DIEGM - University of Udine, Italy - luca.digaspero@uniud.it - http://www.diegm.uniud.it/digaspero/ - + DIEGM - University of Udine, Italy + luca.digaspero@uniud.it + http://www.diegm.uniud.it/digaspero/ + The author will be grateful if the researchers using this software will acknowledge the contribution of this function in their research papers. - + Copyright (c) 2007-2016 Luca Di Gaspero - + This software may be modified and distributed under the terms of the MIT license. See the LICENSE file for details. */ @@ -64,9 +64,12 @@ s.t. namespace quadprogpp { -double solve_quadprog(Eigen::MatrixXd & G, Eigen::VectorXd& g0, - const Eigen::MatrixXd& CE, const Eigen::VectorXd& ce0, - const Eigen::MatrixXd& CI, const Eigen::VectorXd& ci0, - Eigen::VectorXd & x); +double solve_quadprog(Eigen::MatrixXd& G, + Eigen::VectorXd& g0, + const Eigen::MatrixXd& CE, + const Eigen::VectorXd& ce0, + const Eigen::MatrixXd& CI, + const Eigen::VectorXd& ci0, + Eigen::VectorXd& x); } // namespace quadprogpp diff --git a/src/aliceVision/hdr/brackets.cpp b/src/aliceVision/hdr/brackets.cpp index 2a1bc8914f..f344d3213f 100644 --- a/src/aliceVision/hdr/brackets.cpp +++ b/src/aliceVision/hdr/brackets.cpp @@ -21,7 +21,7 @@ bool estimateBracketsFromSfmData(std::vector> viewsOrderedByName; @@ -30,18 +30,17 @@ bool estimateBracketsFromSfmData(std::vector& a, const std::shared_ptr& b) -> bool - { - if (a == nullptr || b == nullptr) - return true; + std::sort(viewsOrderedByName.begin(), + viewsOrderedByName.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (a == nullptr || b == nullptr) + return true; - boost::filesystem::path path_a(a->getImage().getImagePath()); - boost::filesystem::path path_b(b->getImage().getImagePath()); + boost::filesystem::path path_a(a->getImage().getImagePath()); + boost::filesystem::path path_b(b->getImage().getImagePath()); - return (path_a.stem().string() < path_b.stem().string()); - } - ); + return (path_a.stem().string() < path_b.stem().string()); + }); // Print a warning if the aperture changes. std::set fnumbers; @@ -49,7 +48,7 @@ bool estimateBracketsFromSfmData(std::vectorgetImage().getMetadataFNumber()); } - + if (fnumbers.size() != 1) { ALICEVISION_LOG_WARNING("Different apertures amongst the dataset. For correct HDR, you should only change " @@ -60,7 +59,7 @@ bool estimateBracketsFromSfmData(std::vector> group; double lastExposure = std::numeric_limits::min(); for (auto& view : viewsOrderedByName) @@ -88,16 +87,15 @@ bool estimateBracketsFromSfmData(std::vector counters; - for (const auto & group : groups) + for (const auto& group : groups) { size_t bracketCount = group.size(); if (counters.find(bracketCount) != counters.end()) @@ -112,7 +110,7 @@ bool estimateBracketsFromSfmData(std::vector maxSize) { @@ -141,18 +139,15 @@ bool estimateBracketsFromSfmData(std::vector> v_exposuresSetting; - for (auto & group : groups) + std::vector> v_exposuresSetting; + for (auto& group : groups) { // Sort all images by exposure time - std::sort(group.begin(), group.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { - if (a == nullptr || b == nullptr) - return true; - return (a->getImage().getCameraExposureSetting().getExposure() < b->getImage().getCameraExposureSetting().getExposure()); - } - ); + std::sort(group.begin(), group.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (a == nullptr || b == nullptr) + return true; + return (a->getImage().getCameraExposureSetting().getExposure() < b->getImage().getCameraExposureSetting().getExposure()); + }); std::vector exposuresSetting; for (auto& v : group) @@ -165,13 +160,14 @@ bool estimateBracketsFromSfmData(std::vector 1) { - for (int g = 1 ; g < v_exposuresSetting.size(); ++g) + for (int g = 1; g < v_exposuresSetting.size(); ++g) { for (int e = 0; e < v_exposuresSetting[g].size(); ++e) { if (!(v_exposuresSetting[g][e] == v_exposuresSetting[g - 1][e])) { - ALICEVISION_LOG_WARNING("Non consistant exposures between poses have been detected. Most likely the dataset has been captured with an automatic exposure mode enabled. Final result can be impacted."); + ALICEVISION_LOG_WARNING("Non consistant exposures between poses have been detected. Most likely the dataset has been captured " + "with an automatic exposure mode enabled. Final result can be impacted."); g = v_exposuresSetting.size(); break; } @@ -201,10 +197,10 @@ int selectTargetViews(std::vector>& out_targetVie { ALICEVISION_LOG_INFO("Use offsetRefBracketIndex parameter"); } - else // try to use the luminance statistics of the LDR images stored in the file + else // try to use the luminance statistics of the LDR images stored in the file { - ALICEVISION_LOG_INFO("offsetRefBracketIndex parameter out of range, " << - "read file containing luminance statistics to compute an estimation"); + ALICEVISION_LOG_INFO("offsetRefBracketIndex parameter out of range, " + << "read file containing luminance statistics to compute an estimation"); std::ifstream file(lumaStatFilepath); if (!file) { @@ -254,8 +250,7 @@ int selectTargetViews(std::vector>& out_targetVie // Adjust last index to avoid non increasing luminance curve due to saturation in highlights int lastIdx = v_lumaMeanMean.size() - 1; - while ((lastIdx > 1) && ((v_lumaMeanMean[lastIdx] < v_lumaMeanMean[lastIdx - 1]) || - (v_lumaMeanMean[lastIdx] < v_lumaMeanMean[lastIdx - 2]))) + while ((lastIdx > 1) && ((v_lumaMeanMean[lastIdx] < v_lumaMeanMean[lastIdx - 1]) || (v_lumaMeanMean[lastIdx] < v_lumaMeanMean[lastIdx - 2]))) { lastIdx--; } @@ -265,9 +260,8 @@ int selectTargetViews(std::vector>& out_targetVie for (int k = 0; k < lastIdx; ++k) { - const double diffWithLumaTarget = (v_lumaMeanMean[k] > meanTargetedLuma) ? - (v_lumaMeanMean[k] - meanTargetedLuma) : - (meanTargetedLuma - v_lumaMeanMean[k]); + const double diffWithLumaTarget = + (v_lumaMeanMean[k] > meanTargetedLuma) ? (v_lumaMeanMean[k] - meanTargetedLuma) : (meanTargetedLuma - v_lumaMeanMean[k]); if (diffWithLumaTarget < minDiffWithLumaTarget) { minDiffWithLumaTarget = diffWithLumaTarget; @@ -279,7 +273,7 @@ int selectTargetViews(std::vector>& out_targetVie for (auto& group : groups) { - //Set the ldr ancestors id + // Set the ldr ancestors id for (auto v : group) { group[targetIndex]->addAncestor(v->getViewId()); @@ -290,5 +284,5 @@ int selectTargetViews(std::vector>& out_targetVie return targetIndex; } -} -} +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/brackets.hpp b/src/aliceVision/hdr/brackets.hpp index 5a2a2e5515..73d2a28ade 100644 --- a/src/aliceVision/hdr/brackets.hpp +++ b/src/aliceVision/hdr/brackets.hpp @@ -29,16 +29,16 @@ inline std::string ECalibrationMethod_enumToString(const ECalibrationMethod cali { switch (calibrationMethod) { - case ECalibrationMethod::AUTO: - return "auto"; - case ECalibrationMethod::LINEAR: - return "linear"; - case ECalibrationMethod::DEBEVEC: - return "debevec"; - case ECalibrationMethod::GROSSBERG: - return "grossberg"; - case ECalibrationMethod::LAGUERRE: - return "laguerre"; + case ECalibrationMethod::AUTO: + return "auto"; + case ECalibrationMethod::LINEAR: + return "linear"; + case ECalibrationMethod::DEBEVEC: + return "debevec"; + case ECalibrationMethod::GROSSBERG: + return "grossberg"; + case ECalibrationMethod::LAGUERRE: + return "laguerre"; } throw std::out_of_range("Invalid method name enum"); } @@ -88,7 +88,7 @@ inline std::istream& operator>>(std::istream& in, ECalibrationMethod& calibratio * @param[in] countBrackets the number of brackets * @return false if an error occurs (e.g. an invalid SfMData file has been provided), true otherwise */ -bool estimateBracketsFromSfmData(std::vector>> & groups, +bool estimateBracketsFromSfmData(std::vector>>& groups, const sfmData::SfMData& sfmData, size_t countBrackets); @@ -110,5 +110,5 @@ int selectTargetViews(std::vector>& out_targetVie const std::string& targetIndexesFilename = "", const double meanTargetedLuma = 0.4); -} -} +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/emorCurve.cpp b/src/aliceVision/hdr/emorCurve.cpp index 6673b5d71b..e37d526c1d 100644 --- a/src/aliceVision/hdr/emorCurve.cpp +++ b/src/aliceVision/hdr/emorCurve.cpp @@ -14,6720 +14,2972 @@ namespace aliceVision { namespace hdr { static const double kEmor[kEmorMDimension][kEmorQuantization] = { -{ -0.000000e+000, 8.833750e-003, 1.497601e-002, 2.023710e-002, -2.498590e-002, 2.937536e-002, 3.348347e-002, 3.740247e-002, -4.116645e-002, 4.481240e-002, 4.835121e-002, 5.178991e-002, -5.514774e-002, 5.843527e-002, 6.167138e-002, 6.485830e-002, -6.799864e-002, 7.109369e-002, 7.414113e-002, 7.714684e-002, -8.011208e-002, 8.303313e-002, 8.591695e-002, 8.876004e-002, -9.156913e-002, 9.434381e-002, 9.708718e-002, 9.980128e-002, -1.024881e-001, 1.051487e-001, 1.077847e-001, 1.103984e-001, -1.129923e-001, 1.155692e-001, 1.181306e-001, 1.206751e-001, -1.232042e-001, 1.257223e-001, 1.282268e-001, 1.307177e-001, -1.331949e-001, 1.356606e-001, 1.381127e-001, 1.405497e-001, -1.429754e-001, 1.453869e-001, 1.477816e-001, 1.501633e-001, -1.525314e-001, 1.548870e-001, 1.572343e-001, 1.595739e-001, -1.619041e-001, 1.642287e-001, 1.665419e-001, 1.688466e-001, -1.711441e-001, 1.734281e-001, 1.757041e-001, 1.779716e-001, -1.802266e-001, 1.824685e-001, 1.847028e-001, 1.869270e-001, -1.891430e-001, 1.913527e-001, 1.935541e-001, 1.957470e-001, -1.979323e-001, 2.001116e-001, 2.022840e-001, 2.044483e-001, -2.066046e-001, 2.087546e-001, 2.108964e-001, 2.130336e-001, -2.151646e-001, 2.172897e-001, 2.194090e-001, 2.215230e-001, -2.236334e-001, 2.257399e-001, 2.278428e-001, 2.299395e-001, -2.320352e-001, 2.341277e-001, 2.362148e-001, 2.383003e-001, -2.403819e-001, 2.424598e-001, 2.445341e-001, 2.466031e-001, -2.486695e-001, 2.507313e-001, 2.527875e-001, 2.548405e-001, -2.568878e-001, 2.589288e-001, 2.609657e-001, 2.629976e-001, -2.650239e-001, 2.670452e-001, 2.690614e-001, 2.710740e-001, -2.730820e-001, 2.750845e-001, 2.770829e-001, 2.790768e-001, -2.810665e-001, 2.830520e-001, 2.850325e-001, 2.870062e-001, -2.889752e-001, 2.909403e-001, 2.929002e-001, 2.948523e-001, -2.967999e-001, 2.987424e-001, 3.006780e-001, 3.026081e-001, -3.045304e-001, 3.064477e-001, 3.083587e-001, 3.102641e-001, -3.121645e-001, 3.140588e-001, 3.159478e-001, 3.178321e-001, -3.197115e-001, 3.215854e-001, 3.234539e-001, 3.253173e-001, -3.271750e-001, 3.290263e-001, 3.308733e-001, 3.327147e-001, -3.345507e-001, 3.363827e-001, 3.382077e-001, 3.400275e-001, -3.418430e-001, 3.436523e-001, 3.454572e-001, 3.472560e-001, -3.490492e-001, 3.508357e-001, 3.526168e-001, 3.543927e-001, -3.561617e-001, 3.579243e-001, 3.596821e-001, 3.614338e-001, -3.631794e-001, 3.649187e-001, 3.666514e-001, 3.683797e-001, -3.701042e-001, 3.718237e-001, 3.735390e-001, 3.752482e-001, -3.769527e-001, 3.786515e-001, 3.803469e-001, 3.820369e-001, -3.837210e-001, 3.853998e-001, 3.870728e-001, 3.887416e-001, -3.904061e-001, 3.920668e-001, 3.937225e-001, 3.953728e-001, -3.970202e-001, 3.986625e-001, 4.003001e-001, 4.019333e-001, -4.035619e-001, 4.051867e-001, 4.068054e-001, 4.084197e-001, -4.100310e-001, 4.116374e-001, 4.132398e-001, 4.148394e-001, -4.164354e-001, 4.180263e-001, 4.196143e-001, 4.211982e-001, -4.227779e-001, 4.243544e-001, 4.259255e-001, 4.274937e-001, -4.290582e-001, 4.306178e-001, 4.321742e-001, 4.337241e-001, -4.352709e-001, 4.368133e-001, 4.383513e-001, 4.398857e-001, -4.414162e-001, 4.429419e-001, 4.444639e-001, 4.459815e-001, -4.474938e-001, 4.490017e-001, 4.505049e-001, 4.520032e-001, -4.534963e-001, 4.549859e-001, 4.564702e-001, 4.579507e-001, -4.594252e-001, 4.608951e-001, 4.623603e-001, 4.638208e-001, -4.652769e-001, 4.667274e-001, 4.681745e-001, 4.696162e-001, -4.710546e-001, 4.724894e-001, 4.739204e-001, 4.753461e-001, -4.767679e-001, 4.781860e-001, 4.795997e-001, 4.810095e-001, -4.824153e-001, 4.838171e-001, 4.852141e-001, 4.866072e-001, -4.879966e-001, 4.893811e-001, 4.907610e-001, 4.921370e-001, -4.935079e-001, 4.948743e-001, 4.962369e-001, 4.975940e-001, -4.989463e-001, 5.002944e-001, 5.016375e-001, 5.029762e-001, -5.043101e-001, 5.056411e-001, 5.069678e-001, 5.082890e-001, -5.096068e-001, 5.109207e-001, 5.122312e-001, 5.135380e-001, -5.148395e-001, 5.161376e-001, 5.174326e-001, 5.187239e-001, -5.200108e-001, 5.212956e-001, 5.225765e-001, 5.238539e-001, -5.251270e-001, 5.263976e-001, 5.276647e-001, 5.289288e-001, -5.301895e-001, 5.314460e-001, 5.326988e-001, 5.339488e-001, -5.351959e-001, 5.364394e-001, 5.376781e-001, 5.389128e-001, -5.401439e-001, 5.413711e-001, 5.425948e-001, 5.438155e-001, -5.450334e-001, 5.462481e-001, 5.474585e-001, 5.486653e-001, -5.498682e-001, 5.510679e-001, 5.522646e-001, 5.534584e-001, -5.546488e-001, 5.558350e-001, 5.570184e-001, 5.581970e-001, -5.593729e-001, 5.605464e-001, 5.617169e-001, 5.628855e-001, -5.640514e-001, 5.652144e-001, 5.663742e-001, 5.675307e-001, -5.686837e-001, 5.698335e-001, 5.709814e-001, 5.721262e-001, -5.732678e-001, 5.744062e-001, 5.755408e-001, 5.766719e-001, -5.777991e-001, 5.789228e-001, 5.800434e-001, 5.811608e-001, -5.822757e-001, 5.833864e-001, 5.844935e-001, 5.855962e-001, -5.866946e-001, 5.877900e-001, 5.888820e-001, 5.899714e-001, -5.910584e-001, 5.921409e-001, 5.932197e-001, 5.942954e-001, -5.953670e-001, 5.964353e-001, 5.975001e-001, 5.985620e-001, -5.996207e-001, 6.006759e-001, 6.017287e-001, 6.027787e-001, -6.038259e-001, 6.048711e-001, 6.059128e-001, 6.069513e-001, -6.079871e-001, 6.090199e-001, 6.100498e-001, 6.110774e-001, -6.121015e-001, 6.131222e-001, 6.141404e-001, 6.151544e-001, -6.161652e-001, 6.171720e-001, 6.181760e-001, 6.191776e-001, -6.201763e-001, 6.211720e-001, 6.221657e-001, 6.231560e-001, -6.241430e-001, 6.251274e-001, 6.261093e-001, 6.270888e-001, -6.280658e-001, 6.290399e-001, 6.300118e-001, 6.309817e-001, -6.319492e-001, 6.329147e-001, 6.338775e-001, 6.348380e-001, -6.357973e-001, 6.367547e-001, 6.377090e-001, 6.386612e-001, -6.396111e-001, 6.405584e-001, 6.415040e-001, 6.424475e-001, -6.433883e-001, 6.443268e-001, 6.452632e-001, 6.461974e-001, -6.471300e-001, 6.480595e-001, 6.489846e-001, 6.499076e-001, -6.508284e-001, 6.517463e-001, 6.526614e-001, 6.535751e-001, -6.544869e-001, 6.553963e-001, 6.563038e-001, 6.572084e-001, -6.581105e-001, 6.590103e-001, 6.599075e-001, 6.608029e-001, -6.616960e-001, 6.625868e-001, 6.634748e-001, 6.643607e-001, -6.652449e-001, 6.661269e-001, 6.670068e-001, 6.678847e-001, -6.687603e-001, 6.696338e-001, 6.705051e-001, 6.713738e-001, -6.722403e-001, 6.731052e-001, 6.739680e-001, 6.748279e-001, -6.756849e-001, 6.765397e-001, 6.773922e-001, 6.782424e-001, -6.790905e-001, 6.799357e-001, 6.807790e-001, 6.816211e-001, -6.824613e-001, 6.832992e-001, 6.841354e-001, 6.849693e-001, -6.858009e-001, 6.866306e-001, 6.874586e-001, 6.882845e-001, -6.891088e-001, 6.899312e-001, 6.907522e-001, 6.915713e-001, -6.923889e-001, 6.932055e-001, 6.940204e-001, 6.948331e-001, -6.956444e-001, 6.964545e-001, 6.972630e-001, 6.980709e-001, -6.988765e-001, 6.996802e-001, 7.004820e-001, 7.012829e-001, -7.020820e-001, 7.028784e-001, 7.036729e-001, 7.044662e-001, -7.052567e-001, 7.060448e-001, 7.068312e-001, 7.076163e-001, -7.084000e-001, 7.091818e-001, 7.099615e-001, 7.107392e-001, -7.115153e-001, 7.122902e-001, 7.130639e-001, 7.138360e-001, -7.146063e-001, 7.153750e-001, 7.161427e-001, 7.169086e-001, -7.176714e-001, 7.184334e-001, 7.191948e-001, 7.199551e-001, -7.207136e-001, 7.214710e-001, 7.222273e-001, 7.229821e-001, -7.237351e-001, 7.244873e-001, 7.252385e-001, 7.259878e-001, -7.267357e-001, 7.274823e-001, 7.282280e-001, 7.289722e-001, -7.297146e-001, 7.304559e-001, 7.311956e-001, 7.319334e-001, -7.326702e-001, 7.334058e-001, 7.341401e-001, 7.348725e-001, -7.356036e-001, 7.363326e-001, 7.370595e-001, 7.377838e-001, -7.385062e-001, 7.392276e-001, 7.399476e-001, 7.406657e-001, -7.413815e-001, 7.420955e-001, 7.428083e-001, 7.435197e-001, -7.442295e-001, 7.449381e-001, 7.456450e-001, 7.463493e-001, -7.470524e-001, 7.477545e-001, 7.484556e-001, 7.491544e-001, -7.498516e-001, 7.505475e-001, 7.512428e-001, 7.519365e-001, -7.526290e-001, 7.533205e-001, 7.540111e-001, 7.547005e-001, -7.553885e-001, 7.560757e-001, 7.567612e-001, 7.574450e-001, -7.581269e-001, 7.588078e-001, 7.594872e-001, 7.601647e-001, -7.608397e-001, 7.615131e-001, 7.621848e-001, 7.628556e-001, -7.635244e-001, 7.641918e-001, 7.648576e-001, 7.655221e-001, -7.661856e-001, 7.668478e-001, 7.675096e-001, 7.681704e-001, -7.688298e-001, 7.694874e-001, 7.701442e-001, 7.708006e-001, -7.714566e-001, 7.721111e-001, 7.727647e-001, 7.734175e-001, -7.740696e-001, 7.747209e-001, 7.753715e-001, 7.760213e-001, -7.766698e-001, 7.773165e-001, 7.779616e-001, 7.786055e-001, -7.792483e-001, 7.798897e-001, 7.805297e-001, 7.811681e-001, -7.818059e-001, 7.824431e-001, 7.830795e-001, 7.837143e-001, -7.843478e-001, 7.849804e-001, 7.856122e-001, 7.862434e-001, -7.868737e-001, 7.875034e-001, 7.881315e-001, 7.887587e-001, -7.893850e-001, 7.900109e-001, 7.906364e-001, 7.912619e-001, -7.918865e-001, 7.925099e-001, 7.931321e-001, 7.937540e-001, -7.943754e-001, 7.949960e-001, 7.956153e-001, 7.962338e-001, -7.968516e-001, 7.974686e-001, 7.980840e-001, 7.986981e-001, -7.993113e-001, 7.999234e-001, 8.005342e-001, 8.011433e-001, -8.017515e-001, 8.023587e-001, 8.029649e-001, 8.035695e-001, -8.041722e-001, 8.047736e-001, 8.053747e-001, 8.059753e-001, -8.065749e-001, 8.071730e-001, 8.077706e-001, 8.083676e-001, -8.089641e-001, 8.095600e-001, 8.101552e-001, 8.107499e-001, -8.113440e-001, 8.119368e-001, 8.125279e-001, 8.131183e-001, -8.137079e-001, 8.142971e-001, 8.148864e-001, 8.154749e-001, -8.160624e-001, 8.166492e-001, 8.172353e-001, 8.178209e-001, -8.184058e-001, 8.189893e-001, 8.195708e-001, 8.201507e-001, -8.207299e-001, 8.213078e-001, 8.218841e-001, 8.224596e-001, -8.230342e-001, 8.236078e-001, 8.241803e-001, 8.247514e-001, -8.253219e-001, 8.258914e-001, 8.264596e-001, 8.270267e-001, -8.275923e-001, 8.281567e-001, 8.287201e-001, 8.292832e-001, -8.298456e-001, 8.304071e-001, 8.309677e-001, 8.315275e-001, -8.320871e-001, 8.326465e-001, 8.332050e-001, 8.337630e-001, -8.343207e-001, 8.348772e-001, 8.354332e-001, 8.359882e-001, -8.365424e-001, 8.370960e-001, 8.376492e-001, 8.382020e-001, -8.387537e-001, 8.393034e-001, 8.398514e-001, 8.403982e-001, -8.409439e-001, 8.414888e-001, 8.420314e-001, 8.425724e-001, -8.431122e-001, 8.436513e-001, 8.441898e-001, 8.447275e-001, -8.452641e-001, 8.457995e-001, 8.463338e-001, 8.468673e-001, -8.474001e-001, 8.479313e-001, 8.484619e-001, 8.489916e-001, -8.495206e-001, 8.500489e-001, 8.505759e-001, 8.511019e-001, -8.516276e-001, 8.521532e-001, 8.526788e-001, 8.532041e-001, -8.537274e-001, 8.542500e-001, 8.547719e-001, 8.552934e-001, -8.558142e-001, 8.563345e-001, 8.568541e-001, 8.573727e-001, -8.578908e-001, 8.584080e-001, 8.589247e-001, 8.594402e-001, -8.599549e-001, 8.604689e-001, 8.609809e-001, 8.614913e-001, -8.620006e-001, 8.625081e-001, 8.630143e-001, 8.635199e-001, -8.640247e-001, 8.645286e-001, 8.650318e-001, 8.655342e-001, -8.660358e-001, 8.665362e-001, 8.670353e-001, 8.675341e-001, -8.680320e-001, 8.685293e-001, 8.690256e-001, 8.695210e-001, -8.700148e-001, 8.705079e-001, 8.710004e-001, 8.714923e-001, -8.719839e-001, 8.724751e-001, 8.729660e-001, 8.734568e-001, -8.739459e-001, 8.744347e-001, 8.749232e-001, 8.754115e-001, -8.758991e-001, 8.763859e-001, 8.768713e-001, 8.773560e-001, -8.778394e-001, 8.783218e-001, 8.788030e-001, 8.792834e-001, -8.797630e-001, 8.802419e-001, 8.807204e-001, 8.811979e-001, -8.816744e-001, 8.821505e-001, 8.826258e-001, 8.831009e-001, -8.835756e-001, 8.840499e-001, 8.845239e-001, 8.849976e-001, -8.854704e-001, 8.859428e-001, 8.864150e-001, 8.868866e-001, -8.873580e-001, 8.878287e-001, 8.882986e-001, 8.887683e-001, -8.892373e-001, 8.897059e-001, 8.901739e-001, 8.906407e-001, -8.911065e-001, 8.915716e-001, 8.920358e-001, 8.924988e-001, -8.929612e-001, 8.934233e-001, 8.938844e-001, 8.943450e-001, -8.948051e-001, 8.952646e-001, 8.957233e-001, 8.961818e-001, -8.966400e-001, 8.970972e-001, 8.975541e-001, 8.980107e-001, -8.984668e-001, 8.989224e-001, 8.993777e-001, 8.998324e-001, -9.002873e-001, 9.007422e-001, 9.011971e-001, 9.016515e-001, -9.021055e-001, 9.025588e-001, 9.030118e-001, 9.034647e-001, -9.039173e-001, 9.043691e-001, 9.048198e-001, 9.052697e-001, -9.057189e-001, 9.061676e-001, 9.066155e-001, 9.070628e-001, -9.075091e-001, 9.079547e-001, 9.083993e-001, 9.088430e-001, -9.092861e-001, 9.097289e-001, 9.101710e-001, 9.106125e-001, -9.110534e-001, 9.114935e-001, 9.119334e-001, 9.123729e-001, -9.128117e-001, 9.132502e-001, 9.136887e-001, 9.141261e-001, -9.145633e-001, 9.150005e-001, 9.154379e-001, 9.158750e-001, -9.163116e-001, 9.167482e-001, 9.171846e-001, 9.176209e-001, -9.180570e-001, 9.184931e-001, 9.189291e-001, 9.193650e-001, -9.198002e-001, 9.202344e-001, 9.206675e-001, 9.211001e-001, -9.215318e-001, 9.219623e-001, 9.223921e-001, 9.228206e-001, -9.232483e-001, 9.236752e-001, 9.241015e-001, 9.245273e-001, -9.249527e-001, 9.253776e-001, 9.258018e-001, 9.262260e-001, -9.266500e-001, 9.270736e-001, 9.274966e-001, 9.279189e-001, -9.283404e-001, 9.287611e-001, 9.291817e-001, 9.296025e-001, -9.300234e-001, 9.304442e-001, 9.308649e-001, 9.312853e-001, -9.317051e-001, 9.321247e-001, 9.325440e-001, 9.329630e-001, -9.333813e-001, 9.337989e-001, 9.342158e-001, 9.346318e-001, -9.350470e-001, 9.354616e-001, 9.358757e-001, 9.362891e-001, -9.367019e-001, 9.371142e-001, 9.375252e-001, 9.379359e-001, -9.383464e-001, 9.387562e-001, 9.391652e-001, 9.395733e-001, -9.399811e-001, 9.403880e-001, 9.407948e-001, 9.412017e-001, -9.416088e-001, 9.420160e-001, 9.424232e-001, 9.428301e-001, -9.432357e-001, 9.436407e-001, 9.440456e-001, 9.444499e-001, -9.448543e-001, 9.452587e-001, 9.456630e-001, 9.460668e-001, -9.464702e-001, 9.468731e-001, 9.472752e-001, 9.476769e-001, -9.480781e-001, 9.484789e-001, 9.488786e-001, 9.492763e-001, -9.496733e-001, 9.500697e-001, 9.504652e-001, 9.508601e-001, -9.512542e-001, 9.516477e-001, 9.520401e-001, 9.524325e-001, -9.528247e-001, 9.532167e-001, 9.536083e-001, 9.539985e-001, -9.543878e-001, 9.547762e-001, 9.551640e-001, 9.555517e-001, -9.559390e-001, 9.563265e-001, 9.567139e-001, 9.571011e-001, -9.574878e-001, 9.578741e-001, 9.582601e-001, 9.586452e-001, -9.590301e-001, 9.594146e-001, 9.597986e-001, 9.601827e-001, -9.605663e-001, 9.609499e-001, 9.613334e-001, 9.617168e-001, -9.621000e-001, 9.624829e-001, 9.628657e-001, 9.632470e-001, -9.636274e-001, 9.640080e-001, 9.643889e-001, 9.647693e-001, -9.651495e-001, 9.655296e-001, 9.659094e-001, 9.662882e-001, -9.666668e-001, 9.670450e-001, 9.674231e-001, 9.678013e-001, -9.681793e-001, 9.685573e-001, 9.689350e-001, 9.693117e-001, -9.696878e-001, 9.700628e-001, 9.704373e-001, 9.708112e-001, -9.711849e-001, 9.715584e-001, 9.719319e-001, 9.723043e-001, -9.726759e-001, 9.730465e-001, 9.734168e-001, 9.737866e-001, -9.741559e-001, 9.745245e-001, 9.748921e-001, 9.752583e-001, -9.756247e-001, 9.759911e-001, 9.763572e-001, 9.767229e-001, -9.770882e-001, 9.774521e-001, 9.778142e-001, 9.781752e-001, -9.785354e-001, 9.788955e-001, 9.792559e-001, 9.796167e-001, -9.799774e-001, 9.803373e-001, 9.806962e-001, 9.810546e-001, -9.814132e-001, 9.817717e-001, 9.821283e-001, 9.824845e-001, -9.828408e-001, 9.831978e-001, 9.835534e-001, 9.839081e-001, -9.842627e-001, 9.846173e-001, 9.849717e-001, 9.853253e-001, -9.856776e-001, 9.860290e-001, 9.863792e-001, 9.867276e-001, -9.870745e-001, 9.874211e-001, 9.877674e-001, 9.881133e-001, -9.884589e-001, 9.888046e-001, 9.891491e-001, 9.894908e-001, -9.898319e-001, 9.901736e-001, 9.905145e-001, 9.908552e-001, -9.911950e-001, 9.915323e-001, 9.918691e-001, 9.922037e-001, -9.925383e-001, 9.928728e-001, 9.932051e-001, 9.935379e-001, -9.938715e-001, 9.942054e-001, 9.945395e-001, 9.948709e-001, -9.952013e-001, 9.955306e-001, 9.958570e-001, 9.961821e-001, -9.965066e-001, 9.968276e-001, 9.971488e-001, 9.974682e-001, -9.977856e-001, 9.981028e-001, 9.984198e-001, 9.987365e-001, -9.990528e-001, 9.993689e-001, 9.996846e-001, 1.000000e+000 -}, -{ -0.000000e+000, -1.452326e-003, -2.133227e-003, -2.674255e-003, --3.149903e-003, -3.582399e-003, -3.980914e-003, -4.359803e-003, --4.725236e-003, -5.080627e-003, -5.427092e-003, -5.765801e-003, --6.098779e-003, -6.426347e-003, -6.750412e-003, -7.071325e-003, --7.389887e-003, -7.705940e-003, -8.018821e-003, -8.329350e-003, --8.636635e-003, -8.939656e-003, -9.239358e-003, -9.535422e-003, --9.828782e-003, -1.011918e-002, -1.040701e-002, -1.069241e-002, --1.097627e-002, -1.125855e-002, -1.153943e-002, -1.181888e-002, --1.209722e-002, -1.237451e-002, -1.265082e-002, -1.292588e-002, --1.319994e-002, -1.347363e-002, -1.374673e-002, -1.401901e-002, --1.429037e-002, -1.456117e-002, -1.483101e-002, -1.509930e-002, --1.536673e-002, -1.563288e-002, -1.589735e-002, -1.616107e-002, --1.642365e-002, -1.668546e-002, -1.694699e-002, -1.720795e-002, --1.746800e-002, -1.772795e-002, -1.798663e-002, -1.824441e-002, --1.850145e-002, -1.875670e-002, -1.901103e-002, -1.926427e-002, --1.951601e-002, -1.976624e-002, -2.001590e-002, -2.026443e-002, --2.051216e-002, -2.075924e-002, -2.100532e-002, -2.125028e-002, --2.149434e-002, -2.173771e-002, -2.198008e-002, -2.222141e-002, --2.246182e-002, -2.270150e-002, -2.293991e-002, -2.317745e-002, --2.341402e-002, -2.364972e-002, -2.388422e-002, -2.411771e-002, --2.435039e-002, -2.458214e-002, -2.481298e-002, -2.504251e-002, --2.527139e-002, -2.549926e-002, -2.572567e-002, -2.595116e-002, --2.617529e-002, -2.639808e-002, -2.661950e-002, -2.683932e-002, --2.705793e-002, -2.727509e-002, -2.749054e-002, -2.770478e-002, --2.791765e-002, -2.812906e-002, -2.833907e-002, -2.854777e-002, --2.875515e-002, -2.896148e-002, -2.916651e-002, -2.937037e-002, --2.957319e-002, -2.977480e-002, -2.997510e-002, -3.017387e-002, --3.037131e-002, -3.056735e-002, -3.076189e-002, -3.095487e-002, --3.114650e-002, -3.133707e-002, -3.152595e-002, -3.171299e-002, --3.189852e-002, -3.208257e-002, -3.226478e-002, -3.244534e-002, --3.262393e-002, -3.280084e-002, -3.297597e-002, -3.314928e-002, --3.332076e-002, -3.349039e-002, -3.365804e-002, -3.382414e-002, --3.398861e-002, -3.415137e-002, -3.431267e-002, -3.447234e-002, --3.463056e-002, -3.478694e-002, -3.494207e-002, -3.509573e-002, --3.524784e-002, -3.539876e-002, -3.554815e-002, -3.569617e-002, --3.584304e-002, -3.598837e-002, -3.613225e-002, -3.627470e-002, --3.641544e-002, -3.655459e-002, -3.669223e-002, -3.682852e-002, --3.696315e-002, -3.709616e-002, -3.722770e-002, -3.735784e-002, --3.748648e-002, -3.761366e-002, -3.773904e-002, -3.786323e-002, --3.798581e-002, -3.810706e-002, -3.822713e-002, -3.834571e-002, --3.846347e-002, -3.857978e-002, -3.869504e-002, -3.880919e-002, --3.892227e-002, -3.903446e-002, -3.914561e-002, -3.925559e-002, --3.936454e-002, -3.947248e-002, -3.957931e-002, -3.968514e-002, --3.978970e-002, -3.989321e-002, -3.999547e-002, -4.009679e-002, --4.019671e-002, -4.029567e-002, -4.039327e-002, -4.048966e-002, --4.058472e-002, -4.067842e-002, -4.077072e-002, -4.086172e-002, --4.095115e-002, -4.103928e-002, -4.112599e-002, -4.121130e-002, --4.129514e-002, -4.137745e-002, -4.145831e-002, -4.153784e-002, --4.161594e-002, -4.169268e-002, -4.176806e-002, -4.184201e-002, --4.191495e-002, -4.198670e-002, -4.205730e-002, -4.212682e-002, --4.219527e-002, -4.226271e-002, -4.232926e-002, -4.239516e-002, --4.246016e-002, -4.252416e-002, -4.258751e-002, -4.264987e-002, --4.271121e-002, -4.277182e-002, -4.283147e-002, -4.289001e-002, --4.294758e-002, -4.300416e-002, -4.305989e-002, -4.311482e-002, --4.316872e-002, -4.322183e-002, -4.327415e-002, -4.332560e-002, --4.337624e-002, -4.342598e-002, -4.347473e-002, -4.352182e-002, --4.356825e-002, -4.361366e-002, -4.365823e-002, -4.370212e-002, --4.374504e-002, -4.378715e-002, -4.382844e-002, -4.386884e-002, --4.390862e-002, -4.394763e-002, -4.398574e-002, -4.402313e-002, --4.405966e-002, -4.409571e-002, -4.413102e-002, -4.416548e-002, --4.419924e-002, -4.423225e-002, -4.426458e-002, -4.429625e-002, --4.432721e-002, -4.435773e-002, -4.438731e-002, -4.441590e-002, --4.444384e-002, -4.447094e-002, -4.449737e-002, -4.452283e-002, --4.454739e-002, -4.457123e-002, -4.459430e-002, -4.461653e-002, --4.463811e-002, -4.465908e-002, -4.467913e-002, -4.469845e-002, --4.471662e-002, -4.473380e-002, -4.475009e-002, -4.476545e-002, --4.477980e-002, -4.479333e-002, -4.480625e-002, -4.481868e-002, --4.483077e-002, -4.484231e-002, -4.485350e-002, -4.486444e-002, --4.487481e-002, -4.488466e-002, -4.489428e-002, -4.490342e-002, --4.491209e-002, -4.492045e-002, -4.492805e-002, -4.493506e-002, --4.494158e-002, -4.494787e-002, -4.495373e-002, -4.495914e-002, --4.496418e-002, -4.496864e-002, -4.497266e-002, -4.497585e-002, --4.497837e-002, -4.498044e-002, -4.498202e-002, -4.498301e-002, --4.498330e-002, -4.498296e-002, -4.498195e-002, -4.498020e-002, --4.497782e-002, -4.497470e-002, -4.497100e-002, -4.496672e-002, --4.496213e-002, -4.495708e-002, -4.495139e-002, -4.494517e-002, --4.493846e-002, -4.493136e-002, -4.492396e-002, -4.491610e-002, --4.490805e-002, -4.489969e-002, -4.489106e-002, -4.488211e-002, --4.487255e-002, -4.486277e-002, -4.485249e-002, -4.484181e-002, --4.483081e-002, -4.481932e-002, -4.480724e-002, -4.479465e-002, --4.478147e-002, -4.476786e-002, -4.475389e-002, -4.473952e-002, --4.472469e-002, -4.470938e-002, -4.469373e-002, -4.467754e-002, --4.466059e-002, -4.464314e-002, -4.462507e-002, -4.460638e-002, --4.458708e-002, -4.456743e-002, -4.454741e-002, -4.452720e-002, --4.450653e-002, -4.448564e-002, -4.446457e-002, -4.444315e-002, --4.442160e-002, -4.439973e-002, -4.437771e-002, -4.435557e-002, --4.433333e-002, -4.431076e-002, -4.428809e-002, -4.426507e-002, --4.424180e-002, -4.421827e-002, -4.419435e-002, -4.416995e-002, --4.414517e-002, -4.411995e-002, -4.409431e-002, -4.406830e-002, --4.404176e-002, -4.401464e-002, -4.398713e-002, -4.395903e-002, --4.393053e-002, -4.390148e-002, -4.387187e-002, -4.384186e-002, --4.381149e-002, -4.378073e-002, -4.374977e-002, -4.371862e-002, --4.368725e-002, -4.365557e-002, -4.362372e-002, -4.359164e-002, --4.355944e-002, -4.352701e-002, -4.349412e-002, -4.346092e-002, --4.342764e-002, -4.339421e-002, -4.336059e-002, -4.332684e-002, --4.329287e-002, -4.325861e-002, -4.322421e-002, -4.318963e-002, --4.315485e-002, -4.311988e-002, -4.308463e-002, -4.304913e-002, --4.301337e-002, -4.297723e-002, -4.294067e-002, -4.290364e-002, --4.286630e-002, -4.282879e-002, -4.279104e-002, -4.275296e-002, --4.271456e-002, -4.267588e-002, -4.263693e-002, -4.259777e-002, --4.255827e-002, -4.251860e-002, -4.247873e-002, -4.243858e-002, --4.239825e-002, -4.235783e-002, -4.231732e-002, -4.227674e-002, --4.223604e-002, -4.219519e-002, -4.215421e-002, -4.211319e-002, --4.207202e-002, -4.203058e-002, -4.198890e-002, -4.194710e-002, --4.190514e-002, -4.186294e-002, -4.182052e-002, -4.177779e-002, --4.173476e-002, -4.169156e-002, -4.164812e-002, -4.160442e-002, --4.156042e-002, -4.151617e-002, -4.147183e-002, -4.142717e-002, --4.138201e-002, -4.133632e-002, -4.129025e-002, -4.124396e-002, --4.119736e-002, -4.115062e-002, -4.110383e-002, -4.105702e-002, --4.101012e-002, -4.096321e-002, -4.091652e-002, -4.086999e-002, --4.082333e-002, -4.077658e-002, -4.072966e-002, -4.068264e-002, --4.063561e-002, -4.058844e-002, -4.054119e-002, -4.049385e-002, --4.044649e-002, -4.039923e-002, -4.035198e-002, -4.030469e-002, --4.025731e-002, -4.020983e-002, -4.016225e-002, -4.011440e-002, --4.006619e-002, -4.001770e-002, -3.996890e-002, -3.991991e-002, --3.987055e-002, -3.982097e-002, -3.977121e-002, -3.972128e-002, --3.967103e-002, -3.962057e-002, -3.956995e-002, -3.951911e-002, --3.946820e-002, -3.941725e-002, -3.936621e-002, -3.931499e-002, --3.926351e-002, -3.921183e-002, -3.915993e-002, -3.910795e-002, --3.905590e-002, -3.900372e-002, -3.895139e-002, -3.889896e-002, --3.884661e-002, -3.879430e-002, -3.874202e-002, -3.868974e-002, --3.863756e-002, -3.858543e-002, -3.853330e-002, -3.848104e-002, --3.842885e-002, -3.837659e-002, -3.832437e-002, -3.827206e-002, --3.821967e-002, -3.816718e-002, -3.811467e-002, -3.806202e-002, --3.800927e-002, -3.795642e-002, -3.790338e-002, -3.785004e-002, --3.779642e-002, -3.774283e-002, -3.768910e-002, -3.763511e-002, --3.758087e-002, -3.752642e-002, -3.747180e-002, -3.741704e-002, --3.736217e-002, -3.730733e-002, -3.725246e-002, -3.719750e-002, --3.714247e-002, -3.708753e-002, -3.703262e-002, -3.697800e-002, --3.692337e-002, -3.686882e-002, -3.681430e-002, -3.675979e-002, --3.670506e-002, -3.665026e-002, -3.659535e-002, -3.654034e-002, --3.648529e-002, -3.643007e-002, -3.637474e-002, -3.631929e-002, --3.626369e-002, -3.620801e-002, -3.615226e-002, -3.609632e-002, --3.604017e-002, -3.598370e-002, -3.592686e-002, -3.586971e-002, --3.581237e-002, -3.575475e-002, -3.569686e-002, -3.563889e-002, --3.558077e-002, -3.552268e-002, -3.546456e-002, -3.540657e-002, --3.534881e-002, -3.529128e-002, -3.523379e-002, -3.517636e-002, --3.511898e-002, -3.506159e-002, -3.500414e-002, -3.494666e-002, --3.488908e-002, -3.483150e-002, -3.477383e-002, -3.471608e-002, --3.465814e-002, -3.460010e-002, -3.454205e-002, -3.448395e-002, --3.442572e-002, -3.436726e-002, -3.430844e-002, -3.424929e-002, --3.418989e-002, -3.413021e-002, -3.407043e-002, -3.401052e-002, --3.395045e-002, -3.389023e-002, -3.382983e-002, -3.376928e-002, --3.370871e-002, -3.364813e-002, -3.358757e-002, -3.352692e-002, --3.346630e-002, -3.340576e-002, -3.334525e-002, -3.328481e-002, --3.322440e-002, -3.316406e-002, -3.310386e-002, -3.304379e-002, --3.298377e-002, -3.292377e-002, -3.286372e-002, -3.280355e-002, --3.274319e-002, -3.268266e-002, -3.262198e-002, -3.256111e-002, --3.250009e-002, -3.243891e-002, -3.237754e-002, -3.231607e-002, --3.225447e-002, -3.219283e-002, -3.213101e-002, -3.206899e-002, --3.200680e-002, -3.194430e-002, -3.188145e-002, -3.181831e-002, --3.175498e-002, -3.169153e-002, -3.162793e-002, -3.156425e-002, --3.150052e-002, -3.143672e-002, -3.137295e-002, -3.130926e-002, --3.124574e-002, -3.118237e-002, -3.111912e-002, -3.105597e-002, --3.099290e-002, -3.092988e-002, -3.086698e-002, -3.080408e-002, --3.074124e-002, -3.067840e-002, -3.061561e-002, -3.055281e-002, --3.048987e-002, -3.042686e-002, -3.036359e-002, -3.030020e-002, --3.023671e-002, -3.017308e-002, -3.010935e-002, -3.004546e-002, --2.998135e-002, -2.991703e-002, -2.985247e-002, -2.978771e-002, --2.972264e-002, -2.965720e-002, -2.959146e-002, -2.952556e-002, --2.945951e-002, -2.939339e-002, -2.932716e-002, -2.926086e-002, --2.919452e-002, -2.912804e-002, -2.906153e-002, -2.899507e-002, --2.892880e-002, -2.886262e-002, -2.879658e-002, -2.873073e-002, --2.866496e-002, -2.859924e-002, -2.853353e-002, -2.846785e-002, --2.840220e-002, -2.833658e-002, -2.827092e-002, -2.820524e-002, --2.813952e-002, -2.807372e-002, -2.800790e-002, -2.794213e-002, --2.787632e-002, -2.781050e-002, -2.774459e-002, -2.767845e-002, --2.761209e-002, -2.754545e-002, -2.747860e-002, -2.741149e-002, --2.734402e-002, -2.727633e-002, -2.720847e-002, -2.714041e-002, --2.707219e-002, -2.700379e-002, -2.693523e-002, -2.686658e-002, --2.679786e-002, -2.672913e-002, -2.666035e-002, -2.659160e-002, --2.652289e-002, -2.645426e-002, -2.638563e-002, -2.631707e-002, --2.624859e-002, -2.618019e-002, -2.611194e-002, -2.604383e-002, --2.597575e-002, -2.590775e-002, -2.583979e-002, -2.577181e-002, --2.570380e-002, -2.563576e-002, -2.556754e-002, -2.549921e-002, --2.543084e-002, -2.536250e-002, -2.529422e-002, -2.522590e-002, --2.515742e-002, -2.508879e-002, -2.501993e-002, -2.495078e-002, --2.488133e-002, -2.481160e-002, -2.474170e-002, -2.467165e-002, --2.460146e-002, -2.453125e-002, -2.446102e-002, -2.439082e-002, --2.432065e-002, -2.425063e-002, -2.418066e-002, -2.411074e-002, --2.404082e-002, -2.397092e-002, -2.390089e-002, -2.383077e-002, --2.376058e-002, -2.369044e-002, -2.362035e-002, -2.355029e-002, --2.348017e-002, -2.340993e-002, -2.333951e-002, -2.326897e-002, --2.319835e-002, -2.312768e-002, -2.305693e-002, -2.298606e-002, --2.291496e-002, -2.284367e-002, -2.277219e-002, -2.270046e-002, --2.262853e-002, -2.255652e-002, -2.248440e-002, -2.241222e-002, --2.234001e-002, -2.226780e-002, -2.219553e-002, -2.212314e-002, --2.205074e-002, -2.197841e-002, -2.190626e-002, -2.183424e-002, --2.176229e-002, -2.169028e-002, -2.161810e-002, -2.154568e-002, --2.147305e-002, -2.140035e-002, -2.132762e-002, -2.125478e-002, --2.118175e-002, -2.110860e-002, -2.103531e-002, -2.096183e-002, --2.088818e-002, -2.081438e-002, -2.074048e-002, -2.066639e-002, --2.059209e-002, -2.051752e-002, -2.044265e-002, -2.036751e-002, --2.029207e-002, -2.021638e-002, -2.014058e-002, -2.006472e-002, --1.998890e-002, -1.991314e-002, -1.983744e-002, -1.976183e-002, --1.968627e-002, -1.961074e-002, -1.953539e-002, -1.946019e-002, --1.938501e-002, -1.930989e-002, -1.923479e-002, -1.915966e-002, --1.908452e-002, -1.900944e-002, -1.893439e-002, -1.885926e-002, --1.878408e-002, -1.870884e-002, -1.863348e-002, -1.855796e-002, --1.848231e-002, -1.840652e-002, -1.833057e-002, -1.825440e-002, --1.817798e-002, -1.810133e-002, -1.802449e-002, -1.794747e-002, --1.787021e-002, -1.779264e-002, -1.771478e-002, -1.763670e-002, --1.755842e-002, -1.747999e-002, -1.740148e-002, -1.732291e-002, --1.724431e-002, -1.716563e-002, -1.708696e-002, -1.700842e-002, --1.693010e-002, -1.685210e-002, -1.677433e-002, -1.669677e-002, --1.661930e-002, -1.654190e-002, -1.646447e-002, -1.638702e-002, --1.630954e-002, -1.623204e-002, -1.615443e-002, -1.607676e-002, --1.599907e-002, -1.592144e-002, -1.584389e-002, -1.576632e-002, --1.568863e-002, -1.561081e-002, -1.553288e-002, -1.545472e-002, --1.537633e-002, -1.529772e-002, -1.521894e-002, -1.513999e-002, --1.506083e-002, -1.498161e-002, -1.490233e-002, -1.482310e-002, --1.474395e-002, -1.466488e-002, -1.458589e-002, -1.450702e-002, --1.442836e-002, -1.434983e-002, -1.427133e-002, -1.419289e-002, --1.411455e-002, -1.403623e-002, -1.395785e-002, -1.387951e-002, --1.380115e-002, -1.372280e-002, -1.364444e-002, -1.356610e-002, --1.348754e-002, -1.340874e-002, -1.332979e-002, -1.325070e-002, --1.317143e-002, -1.309201e-002, -1.301245e-002, -1.293275e-002, --1.285271e-002, -1.277242e-002, -1.269182e-002, -1.261105e-002, --1.253009e-002, -1.244900e-002, -1.236778e-002, -1.228641e-002, --1.220500e-002, -1.212366e-002, -1.204226e-002, -1.196090e-002, --1.187970e-002, -1.179848e-002, -1.171736e-002, -1.163649e-002, --1.155585e-002, -1.147541e-002, -1.139521e-002, -1.131516e-002, --1.123521e-002, -1.115536e-002, -1.107554e-002, -1.099559e-002, --1.091568e-002, -1.083583e-002, -1.075604e-002, -1.067644e-002, --1.059687e-002, -1.051720e-002, -1.043748e-002, -1.035779e-002, --1.027803e-002, -1.019800e-002, -1.011781e-002, -1.003758e-002, --9.957239e-003, -9.876816e-003, -9.796439e-003, -9.716210e-003, --9.636026e-003, -9.555834e-003, -9.475533e-003, -9.395003e-003, --9.314153e-003, -9.233184e-003, -9.152170e-003, -9.071143e-003, --8.990137e-003, -8.909167e-003, -8.828271e-003, -8.747499e-003, --8.666663e-003, -8.585664e-003, -8.504367e-003, -8.422923e-003, --8.341348e-003, -8.259648e-003, -8.177829e-003, -8.095776e-003, --8.013583e-003, -7.931298e-003, -7.848872e-003, -7.766315e-003, --7.683466e-003, -7.600526e-003, -7.517544e-003, -7.434385e-003, --7.351137e-003, -7.267938e-003, -7.184759e-003, -7.101645e-003, --7.018527e-003, -6.935418e-003, -6.851983e-003, -6.768426e-003, --6.685034e-003, -6.601836e-003, -6.518750e-003, -6.435825e-003, --6.353018e-003, -6.270365e-003, -6.187727e-003, -6.105019e-003, --6.022069e-003, -5.938993e-003, -5.855980e-003, -5.773054e-003, --5.690185e-003, -5.607578e-003, -5.525072e-003, -5.442573e-003, --5.360150e-003, -5.277665e-003, -5.194958e-003, -5.111897e-003, --5.028772e-003, -4.945543e-003, -4.862157e-003, -4.778586e-003, --4.694894e-003, -4.611060e-003, -4.527402e-003, -4.443631e-003, --4.359563e-003, -4.274865e-003, -4.189983e-003, -4.104942e-003, --4.019846e-003, -3.934731e-003, -3.849588e-003, -3.764451e-003, --3.679434e-003, -3.594517e-003, -3.509579e-003, -3.424345e-003, --3.339334e-003, -3.254477e-003, -3.169773e-003, -3.085151e-003, --3.000545e-003, -2.915934e-003, -2.831347e-003, -2.746836e-003, --2.662332e-003, -2.577334e-003, -2.492046e-003, -2.406713e-003, --2.321474e-003, -2.236671e-003, -2.151859e-003, -2.066829e-003, --1.981710e-003, -1.896570e-003, -1.811610e-003, -1.726102e-003, --1.639910e-003, -1.553454e-003, -1.466765e-003, -1.379805e-003, --1.292726e-003, -1.205572e-003, -1.118955e-003, -1.032592e-003, --9.464431e-004, -8.607769e-004, -7.746067e-004, -6.883249e-004, --6.019335e-004, -5.156037e-004, -4.293733e-004, -3.432325e-004, --2.572296e-004, -1.713395e-004, -8.560801e-005, -1.734677e-018 -}, -{ -0.000000e+000, -6.834736e-003, -1.030655e-002, -1.309653e-002, --1.550273e-002, -1.765338e-002, -1.961569e-002, -2.144469e-002, --2.316949e-002, -2.480979e-002, -2.637446e-002, -2.787000e-002, --2.930205e-002, -3.067509e-002, -3.199528e-002, -3.326566e-002, --3.448766e-002, -3.566654e-002, -3.680414e-002, -3.790353e-002, --3.896870e-002, -3.999961e-002, -4.099430e-002, -4.195820e-002, --4.289104e-002, -4.379444e-002, -4.467011e-002, -4.551842e-002, --4.634050e-002, -4.713816e-002, -4.791264e-002, -4.866400e-002, --4.939394e-002, -5.010301e-002, -5.079321e-002, -5.146556e-002, --5.212060e-002, -5.275808e-002, -5.338037e-002, -5.398612e-002, --5.457620e-002, -5.515033e-002, -5.570914e-002, -5.625304e-002, --5.678294e-002, -5.729777e-002, -5.779807e-002, -5.828404e-002, --5.875630e-002, -5.921497e-002, -5.966021e-002, -6.009192e-002, --6.051124e-002, -6.091733e-002, -6.131049e-002, -6.169107e-002, --6.205854e-002, -6.241281e-002, -6.275280e-002, -6.307833e-002, --6.339045e-002, -6.368931e-002, -6.397532e-002, -6.424917e-002, --6.450968e-002, -6.475804e-002, -6.499499e-002, -6.522048e-002, --6.543458e-002, -6.563702e-002, -6.582738e-002, -6.600698e-002, --6.617651e-002, -6.633596e-002, -6.648488e-002, -6.662203e-002, --6.674955e-002, -6.686704e-002, -6.697447e-002, -6.707293e-002, --6.716189e-002, -6.724087e-002, -6.731006e-002, -6.737121e-002, --6.742258e-002, -6.746481e-002, -6.749928e-002, -6.752386e-002, --6.753829e-002, -6.754365e-002, -6.754009e-002, -6.752833e-002, --6.750745e-002, -6.747733e-002, -6.743835e-002, -6.738998e-002, --6.733389e-002, -6.726971e-002, -6.719592e-002, -6.711396e-002, --6.702469e-002, -6.692758e-002, -6.682206e-002, -6.670795e-002, --6.658773e-002, -6.646093e-002, -6.632665e-002, -6.618518e-002, --6.603761e-002, -6.588456e-002, -6.572434e-002, -6.555916e-002, --6.538862e-002, -6.521215e-002, -6.502931e-002, -6.484248e-002, --6.464931e-002, -6.445091e-002, -6.424875e-002, -6.404104e-002, --6.382964e-002, -6.361353e-002, -6.339300e-002, -6.316857e-002, --6.293894e-002, -6.270501e-002, -6.246678e-002, -6.222511e-002, --6.198001e-002, -6.173081e-002, -6.147881e-002, -6.122315e-002, --6.096412e-002, -6.070220e-002, -6.043667e-002, -6.016932e-002, --5.989955e-002, -5.962626e-002, -5.935165e-002, -5.907452e-002, --5.879482e-002, -5.851286e-002, -5.822720e-002, -5.793945e-002, --5.764931e-002, -5.735722e-002, -5.706336e-002, -5.676615e-002, --5.646789e-002, -5.616717e-002, -5.586355e-002, -5.555858e-002, --5.525142e-002, -5.494337e-002, -5.463375e-002, -5.432268e-002, --5.400878e-002, -5.369359e-002, -5.337638e-002, -5.305805e-002, --5.273846e-002, -5.241742e-002, -5.209304e-002, -5.176789e-002, --5.144062e-002, -5.111253e-002, -5.078344e-002, -5.045272e-002, --5.011944e-002, -4.978481e-002, -4.944995e-002, -4.911371e-002, --4.877491e-002, -4.843408e-002, -4.809225e-002, -4.774920e-002, --4.740392e-002, -4.705772e-002, -4.671063e-002, -4.636272e-002, --4.601150e-002, -4.565978e-002, -4.530795e-002, -4.495361e-002, --4.459696e-002, -4.424074e-002, -4.388182e-002, -4.352130e-002, --4.316080e-002, -4.279696e-002, -4.243225e-002, -4.206572e-002, --4.169662e-002, -4.132640e-002, -4.095428e-002, -4.058141e-002, --4.020654e-002, -3.983143e-002, -3.945481e-002, -3.907752e-002, --3.869863e-002, -3.831916e-002, -3.793909e-002, -3.755856e-002, --3.717837e-002, -3.679812e-002, -3.641870e-002, -3.603933e-002, --3.566199e-002, -3.528460e-002, -3.490798e-002, -3.453196e-002, --3.415788e-002, -3.378426e-002, -3.341166e-002, -3.303988e-002, --3.266905e-002, -3.229894e-002, -3.192930e-002, -3.156158e-002, --3.119414e-002, -3.082719e-002, -3.046210e-002, -3.009774e-002, --2.973468e-002, -2.937301e-002, -2.901148e-002, -2.865136e-002, --2.829204e-002, -2.793274e-002, -2.757493e-002, -2.721877e-002, --2.686271e-002, -2.650782e-002, -2.615378e-002, -2.580023e-002, --2.544731e-002, -2.509595e-002, -2.474575e-002, -2.439674e-002, --2.404838e-002, -2.370154e-002, -2.335531e-002, -2.301049e-002, --2.266754e-002, -2.232499e-002, -2.198264e-002, -2.164178e-002, --2.130193e-002, -2.096273e-002, -2.062418e-002, -2.028711e-002, --1.995205e-002, -1.961796e-002, -1.928439e-002, -1.895179e-002, --1.862081e-002, -1.829111e-002, -1.796200e-002, -1.763426e-002, --1.730856e-002, -1.698441e-002, -1.666101e-002, -1.633836e-002, --1.601732e-002, -1.569876e-002, -1.538064e-002, -1.506325e-002, --1.474730e-002, -1.443237e-002, -1.411902e-002, -1.380695e-002, --1.349567e-002, -1.318542e-002, -1.287660e-002, -1.256888e-002, --1.226211e-002, -1.195664e-002, -1.165172e-002, -1.134866e-002, --1.104682e-002, -1.074621e-002, -1.044633e-002, -1.014755e-002, --9.850819e-003, -9.554834e-003, -9.260157e-003, -8.967085e-003, --8.674770e-003, -8.383864e-003, -8.094663e-003, -7.806046e-003, --7.518072e-003, -7.231338e-003, -6.945811e-003, -6.661126e-003, --6.377697e-003, -6.095236e-003, -5.814124e-003, -5.534324e-003, --5.255688e-003, -4.978303e-003, -4.701866e-003, -4.426695e-003, --4.152214e-003, -3.878992e-003, -3.607353e-003, -3.336666e-003, --3.067033e-003, -2.797994e-003, -2.529929e-003, -2.263697e-003, --1.998356e-003, -1.733835e-003, -1.470706e-003, -1.208125e-003, --9.470783e-004, -6.878352e-004, -4.302868e-004, -1.744681e-004, -8.006053e-005, 3.337304e-004, 5.862463e-004, 8.373778e-004, -1.086435e-003, 1.334537e-003, 1.581397e-003, 1.826863e-003, -2.071398e-003, 2.314104e-003, 2.555159e-003, 2.794924e-003, -3.033519e-003, 3.270476e-003, 3.505720e-003, 3.739996e-003, -3.972842e-003, 4.204140e-003, 4.434708e-003, 4.664032e-003, -4.890714e-003, 5.116126e-003, 5.340849e-003, 5.564195e-003, -5.786559e-003, 6.007642e-003, 6.226949e-003, 6.444732e-003, -6.661478e-003, 6.876964e-003, 7.091547e-003, 7.304253e-003, -7.515924e-003, 7.726824e-003, 7.935763e-003, 8.142976e-003, -8.349168e-003, 8.553944e-003, 8.757340e-003, 8.959792e-003, -9.161106e-003, 9.361287e-003, 9.559349e-003, 9.756497e-003, -9.952685e-003, 1.014762e-002, 1.034074e-002, 1.053309e-002, -1.072541e-002, 1.091594e-002, 1.110504e-002, 1.129373e-002, -1.148156e-002, 1.166782e-002, 1.185313e-002, 1.203736e-002, -1.222075e-002, 1.240279e-002, 1.258331e-002, 1.276309e-002, -1.294256e-002, 1.312040e-002, 1.329677e-002, 1.347249e-002, -1.364706e-002, 1.382051e-002, 1.399266e-002, 1.416408e-002, -1.433441e-002, 1.450364e-002, 1.467111e-002, 1.483672e-002, -1.500138e-002, 1.516571e-002, 1.532860e-002, 1.549030e-002, -1.565125e-002, 1.581128e-002, 1.596967e-002, 1.612637e-002, -1.628214e-002, 1.643701e-002, 1.659115e-002, 1.674467e-002, -1.689665e-002, 1.704747e-002, 1.719766e-002, 1.734634e-002, -1.749310e-002, 1.763928e-002, 1.778497e-002, 1.792967e-002, -1.807319e-002, 1.821614e-002, 1.835842e-002, 1.849997e-002, -1.864049e-002, 1.878001e-002, 1.891777e-002, 1.905389e-002, -1.918975e-002, 1.932466e-002, 1.945806e-002, 1.959045e-002, -1.972178e-002, 1.985215e-002, 1.998132e-002, 2.010955e-002, -2.023659e-002, 2.036272e-002, 2.048797e-002, 2.061253e-002, -2.073551e-002, 2.085671e-002, 2.097730e-002, 2.109728e-002, -2.121611e-002, 2.133362e-002, 2.145057e-002, 2.156713e-002, -2.168222e-002, 2.179582e-002, 2.190811e-002, 2.201902e-002, -2.212963e-002, 2.223941e-002, 2.234774e-002, 2.245507e-002, -2.256168e-002, 2.266780e-002, 2.277300e-002, 2.287686e-002, -2.298009e-002, 2.308251e-002, 2.318447e-002, 2.328479e-002, -2.338382e-002, 2.348174e-002, 2.357885e-002, 2.367505e-002, -2.377069e-002, 2.386548e-002, 2.395961e-002, 2.405276e-002, -2.414502e-002, 2.423653e-002, 2.432725e-002, 2.441701e-002, -2.450582e-002, 2.459374e-002, 2.468095e-002, 2.476778e-002, -2.485295e-002, 2.493731e-002, 2.502082e-002, 2.510419e-002, -2.518703e-002, 2.526896e-002, 2.534992e-002, 2.543015e-002, -2.550961e-002, 2.558855e-002, 2.566690e-002, 2.574410e-002, -2.581993e-002, 2.589510e-002, 2.596982e-002, 2.604419e-002, -2.611726e-002, 2.618891e-002, 2.625982e-002, 2.633075e-002, -2.640090e-002, 2.646978e-002, 2.653755e-002, 2.660499e-002, -2.667202e-002, 2.673802e-002, 2.680315e-002, 2.686678e-002, -2.692976e-002, 2.699183e-002, 2.705374e-002, 2.711568e-002, -2.717667e-002, 2.723653e-002, 2.729552e-002, 2.735416e-002, -2.741264e-002, 2.747052e-002, 2.752731e-002, 2.758320e-002, -2.763833e-002, 2.769307e-002, 2.774696e-002, 2.780023e-002, -2.785280e-002, 2.790422e-002, 2.795501e-002, 2.800557e-002, -2.805546e-002, 2.810432e-002, 2.815198e-002, 2.819804e-002, -2.824345e-002, 2.828880e-002, 2.833374e-002, 2.837770e-002, -2.842043e-002, 2.846258e-002, 2.850428e-002, 2.854542e-002, -2.858598e-002, 2.862594e-002, 2.866495e-002, 2.870252e-002, -2.873966e-002, 2.877646e-002, 2.881295e-002, 2.884832e-002, -2.888255e-002, 2.891574e-002, 2.894903e-002, 2.898223e-002, -2.901508e-002, 2.904705e-002, 2.907791e-002, 2.910819e-002, -2.913826e-002, 2.916796e-002, 2.919735e-002, 2.922622e-002, -2.925391e-002, 2.928040e-002, 2.930629e-002, 2.933194e-002, -2.935745e-002, 2.938241e-002, 2.940608e-002, 2.942892e-002, -2.945141e-002, 2.947362e-002, 2.949535e-002, 2.951647e-002, -2.953636e-002, 2.955591e-002, 2.957522e-002, 2.959431e-002, -2.961273e-002, 2.963049e-002, 2.964725e-002, 2.966316e-002, -2.967857e-002, 2.969379e-002, 2.970889e-002, 2.972363e-002, -2.973772e-002, 2.975090e-002, 2.976332e-002, 2.977532e-002, -2.978702e-002, 2.979864e-002, 2.980987e-002, 2.982054e-002, -2.983048e-002, 2.983975e-002, 2.984855e-002, 2.985685e-002, -2.986464e-002, 2.987176e-002, 2.987795e-002, 2.988374e-002, -2.988937e-002, 2.989469e-002, 2.989998e-002, 2.990470e-002, -2.990875e-002, 2.991192e-002, 2.991424e-002, 2.991637e-002, -2.991777e-002, 2.991885e-002, 2.991928e-002, 2.991874e-002, -2.991738e-002, 2.991572e-002, 2.991373e-002, 2.991128e-002, -2.990860e-002, 2.990584e-002, 2.990235e-002, 2.989797e-002, -2.989336e-002, 2.988847e-002, 2.988322e-002, 2.987778e-002, -2.987201e-002, 2.986577e-002, 2.985911e-002, 2.985255e-002, -2.984558e-002, 2.983821e-002, 2.983035e-002, 2.982172e-002, -2.981263e-002, 2.980268e-002, 2.979215e-002, 2.978133e-002, -2.977045e-002, 2.975935e-002, 2.974743e-002, 2.973463e-002, -2.972134e-002, 2.970785e-002, 2.969386e-002, 2.967948e-002, -2.966457e-002, 2.964874e-002, 2.963212e-002, 2.961527e-002, -2.959817e-002, 2.958032e-002, 2.956216e-002, 2.954350e-002, -2.952412e-002, 2.950376e-002, 2.948252e-002, 2.946105e-002, -2.943933e-002, 2.941770e-002, 2.939591e-002, 2.937378e-002, -2.935096e-002, 2.932764e-002, 2.930405e-002, 2.928041e-002, -2.925683e-002, 2.923300e-002, 2.920860e-002, 2.918345e-002, -2.915766e-002, 2.913147e-002, 2.910496e-002, 2.907784e-002, -2.905027e-002, 2.902246e-002, 2.899426e-002, 2.896544e-002, -2.893613e-002, 2.890619e-002, 2.887590e-002, 2.884529e-002, -2.881411e-002, 2.878199e-002, 2.874931e-002, 2.871610e-002, -2.868268e-002, 2.864918e-002, 2.861521e-002, 2.858077e-002, -2.854600e-002, 2.851046e-002, 2.847440e-002, 2.843823e-002, -2.840170e-002, 2.836473e-002, 2.832715e-002, 2.828919e-002, -2.825080e-002, 2.821174e-002, 2.817164e-002, 2.813137e-002, -2.809064e-002, 2.804988e-002, 2.800906e-002, 2.796814e-002, -2.792655e-002, 2.788389e-002, 2.784049e-002, 2.779686e-002, -2.775313e-002, 2.770916e-002, 2.766488e-002, 2.762008e-002, -2.757387e-002, 2.752694e-002, 2.748006e-002, 2.743309e-002, -2.738562e-002, 2.733757e-002, 2.728918e-002, 2.724059e-002, -2.719126e-002, 2.714126e-002, 2.709101e-002, 2.704059e-002, -2.699002e-002, 2.693964e-002, 2.688949e-002, 2.683898e-002, -2.678728e-002, 2.673486e-002, 2.668206e-002, 2.662915e-002, -2.657626e-002, 2.652307e-002, 2.646954e-002, 2.641526e-002, -2.636039e-002, 2.630526e-002, 2.625021e-002, 2.619506e-002, -2.613945e-002, 2.608340e-002, 2.602684e-002, 2.596993e-002, -2.591262e-002, 2.585493e-002, 2.579680e-002, 2.573835e-002, -2.567970e-002, 2.562103e-002, 2.556225e-002, 2.550317e-002, -2.544336e-002, 2.538317e-002, 2.532253e-002, 2.526149e-002, -2.520069e-002, 2.513982e-002, 2.507873e-002, 2.501757e-002, -2.495623e-002, 2.489478e-002, 2.483325e-002, 2.477154e-002, -2.470957e-002, 2.464714e-002, 2.458375e-002, 2.451995e-002, -2.445596e-002, 2.439214e-002, 2.432815e-002, 2.426368e-002, -2.419895e-002, 2.413401e-002, 2.406879e-002, 2.400333e-002, -2.393762e-002, 2.387124e-002, 2.380421e-002, 2.373677e-002, -2.366924e-002, 2.360201e-002, 2.353462e-002, 2.346708e-002, -2.339930e-002, 2.333108e-002, 2.326216e-002, 2.319319e-002, -2.312445e-002, 2.305565e-002, 2.298672e-002, 2.291752e-002, -2.284820e-002, 2.277878e-002, 2.270904e-002, 2.263863e-002, -2.256796e-002, 2.249704e-002, 2.242582e-002, 2.235437e-002, -2.228287e-002, 2.221145e-002, 2.213982e-002, 2.206778e-002, -2.199519e-002, 2.192199e-002, 2.184854e-002, 2.177484e-002, -2.170073e-002, 2.162616e-002, 2.155125e-002, 2.147579e-002, -2.139931e-002, 2.132241e-002, 2.124553e-002, 2.116845e-002, -2.109140e-002, 2.101413e-002, 2.093653e-002, 2.085859e-002, -2.078051e-002, 2.070170e-002, 2.062251e-002, 2.054327e-002, -2.046379e-002, 2.038417e-002, 2.030452e-002, 2.022499e-002, -2.014550e-002, 2.006557e-002, 1.998500e-002, 1.990404e-002, -1.982261e-002, 1.974109e-002, 1.965972e-002, 1.957832e-002, -1.949669e-002, 1.941454e-002, 1.933192e-002, 1.924860e-002, -1.916515e-002, 1.908171e-002, 1.899820e-002, 1.891452e-002, -1.883073e-002, 1.874656e-002, 1.866178e-002, 1.857668e-002, -1.849097e-002, 1.840473e-002, 1.831818e-002, 1.823158e-002, -1.814483e-002, 1.805806e-002, 1.797132e-002, 1.788475e-002, -1.779823e-002, 1.771130e-002, 1.762377e-002, 1.753588e-002, -1.744771e-002, 1.735949e-002, 1.727150e-002, 1.718335e-002, -1.709513e-002, 1.700680e-002, 1.691792e-002, 1.682779e-002, -1.673734e-002, 1.664724e-002, 1.655709e-002, 1.646661e-002, -1.637596e-002, 1.628514e-002, 1.619373e-002, 1.610176e-002, -1.600926e-002, 1.591590e-002, 1.582272e-002, 1.572961e-002, -1.563630e-002, 1.554271e-002, 1.544886e-002, 1.535515e-002, -1.526130e-002, 1.516701e-002, 1.507180e-002, 1.497624e-002, -1.488052e-002, 1.478434e-002, 1.468826e-002, 1.459273e-002, -1.449716e-002, 1.440158e-002, 1.430571e-002, 1.420940e-002, -1.411214e-002, 1.401480e-002, 1.391760e-002, 1.382018e-002, -1.372267e-002, 1.362512e-002, 1.352760e-002, 1.343008e-002, -1.333228e-002, 1.323431e-002, 1.313591e-002, 1.303727e-002, -1.293852e-002, 1.283950e-002, 1.274032e-002, 1.264080e-002, -1.254123e-002, 1.244169e-002, 1.234195e-002, 1.224205e-002, -1.214193e-002, 1.204189e-002, 1.194184e-002, 1.184131e-002, -1.174076e-002, 1.164014e-002, 1.153936e-002, 1.143851e-002, -1.133759e-002, 1.123653e-002, 1.113548e-002, 1.103454e-002, -1.093387e-002, 1.083302e-002, 1.073188e-002, 1.063088e-002, -1.052970e-002, 1.042837e-002, 1.032659e-002, 1.022455e-002, -1.012248e-002, 1.002035e-002, 9.918116e-003, 9.815804e-003, -9.713331e-003, 9.610621e-003, 9.507541e-003, 9.404811e-003, -9.301925e-003, 9.198768e-003, 9.094977e-003, 8.991043e-003, -8.887070e-003, 8.782970e-003, 8.678326e-003, 8.573843e-003, -8.469633e-003, 8.365521e-003, 8.261370e-003, 8.157106e-003, -8.052351e-003, 7.947350e-003, 7.842282e-003, 7.737671e-003, -7.632770e-003, 7.527568e-003, 7.422291e-003, 7.316904e-003, -7.211409e-003, 7.106033e-003, 7.000307e-003, 6.894502e-003, -6.788705e-003, 6.682880e-003, 6.576743e-003, 6.470157e-003, -6.363435e-003, 6.256815e-003, 6.150156e-003, 6.043626e-003, -5.936934e-003, 5.829892e-003, 5.723172e-003, 5.616471e-003, -5.509550e-003, 5.401966e-003, 5.294037e-003, 5.186386e-003, -5.078716e-003, 4.971001e-003, 4.863286e-003, 4.755597e-003, -4.647307e-003, 4.538980e-003, 4.430595e-003, 4.322479e-003, -4.214607e-003, 4.106767e-003, 3.998947e-003, 3.891184e-003, -3.783453e-003, 3.675667e-003, 3.567486e-003, 3.459373e-003, -3.351252e-003, 3.242571e-003, 3.134150e-003, 3.025716e-003, -2.917545e-003, 2.810184e-003, 2.702678e-003, 2.595736e-003, -2.488716e-003, 2.381184e-003, 2.272856e-003, 2.164048e-003, -2.054665e-003, 1.945212e-003, 1.835736e-003, 1.726726e-003, -1.618062e-003, 1.509930e-003, 1.402651e-003, 1.295712e-003, -1.188421e-003, 1.080173e-003, 9.714036e-004, 8.630221e-004, -7.551179e-004, 6.472019e-004, 5.393068e-004, 4.314098e-004, -3.235321e-004, 2.156618e-004, 1.078233e-004, 1.817274e-018 -}, -{ -0.000000e+000, -1.492129e-002, -2.127088e-002, -2.618806e-002, --3.030776e-002, -3.390256e-002, -3.710799e-002, -4.003321e-002, --4.274783e-002, -4.528460e-002, -4.765963e-002, -4.988519e-002, --5.197049e-002, -5.392217e-002, -5.575409e-002, -5.747167e-002, --5.908103e-002, -6.059303e-002, -6.201298e-002, -6.335102e-002, --6.461079e-002, -6.579581e-002, -6.690524e-002, -6.794801e-002, --6.892519e-002, -6.984189e-002, -7.069960e-002, -7.150041e-002, --7.224501e-002, -7.293687e-002, -7.357614e-002, -7.416297e-002, --7.469762e-002, -7.517955e-002, -7.561567e-002, -7.600640e-002, --7.634987e-002, -7.664419e-002, -7.689663e-002, -7.710327e-002, --7.726759e-002, -7.738455e-002, -7.745873e-002, -7.749457e-002, --7.748945e-002, -7.744165e-002, -7.735865e-002, -7.723364e-002, --7.707189e-002, -7.687827e-002, -7.664647e-002, -7.637822e-002, --7.608302e-002, -7.575068e-002, -7.538839e-002, -7.499824e-002, --7.457771e-002, -7.412979e-002, -7.364984e-002, -7.314172e-002, --7.260533e-002, -7.204270e-002, -7.145579e-002, -7.084881e-002, --7.021362e-002, -6.955401e-002, -6.887651e-002, -6.817895e-002, --6.746294e-002, -6.672660e-002, -6.597115e-002, -6.519937e-002, --6.441377e-002, -6.361464e-002, -6.280368e-002, -6.197680e-002, --6.113930e-002, -6.029036e-002, -5.943020e-002, -5.856084e-002, --5.768074e-002, -5.678807e-002, -5.588325e-002, -5.497403e-002, --5.405088e-002, -5.311932e-002, -5.218269e-002, -5.123219e-002, --5.027069e-002, -4.930252e-002, -4.832386e-002, -4.733829e-002, --4.634025e-002, -4.533376e-002, -4.432209e-002, -4.329936e-002, --4.226919e-002, -4.123282e-002, -4.018882e-002, -3.913876e-002, --3.808507e-002, -3.702615e-002, -3.596504e-002, -3.490341e-002, --3.384366e-002, -3.278206e-002, -3.171784e-002, -3.065539e-002, --2.959573e-002, -2.854068e-002, -2.748243e-002, -2.643014e-002, --2.538179e-002, -2.433511e-002, -2.329353e-002, -2.225788e-002, --2.122554e-002, -2.019842e-002, -1.918127e-002, -1.816785e-002, --1.716183e-002, -1.616060e-002, -1.516746e-002, -1.418151e-002, --1.320141e-002, -1.222747e-002, -1.126370e-002, -1.030776e-002, --9.360055e-003, -8.420152e-003, -7.488138e-003, -6.566507e-003, --5.655479e-003, -4.756311e-003, -3.866571e-003, -2.990216e-003, --2.126094e-003, -1.267782e-003, -4.210361e-004, 4.168841e-004, -1.246749e-003, 2.066966e-003, 2.877267e-003, 3.679979e-003, -4.472107e-003, 5.252754e-003, 6.023304e-003, 6.788588e-003, -7.544078e-003, 8.288665e-003, 9.025675e-003, 9.753873e-003, -1.047475e-002, 1.118661e-002, 1.188755e-002, 1.258288e-002, -1.326729e-002, 1.394282e-002, 1.461229e-002, 1.527265e-002, -1.592762e-002, 1.657146e-002, 1.720636e-002, 1.782967e-002, -1.844406e-002, 1.904969e-002, 1.964527e-002, 2.023076e-002, -2.081047e-002, 2.138261e-002, 2.194487e-002, 2.250196e-002, -2.304905e-002, 2.358819e-002, 2.411839e-002, 2.463960e-002, -2.514865e-002, 2.565030e-002, 2.614183e-002, 2.662619e-002, -2.710025e-002, 2.756598e-002, 2.802123e-002, 2.846987e-002, -2.891075e-002, 2.934243e-002, 2.976562e-002, 3.018131e-002, -3.058678e-002, 3.098458e-002, 3.137367e-002, 3.175329e-002, -3.212513e-002, 3.248639e-002, 3.283770e-002, 3.318035e-002, -3.351670e-002, 3.384299e-002, 3.416177e-002, 3.447175e-002, -3.477535e-002, 3.507138e-002, 3.536013e-002, 3.564247e-002, -3.591487e-002, 3.617964e-002, 3.643723e-002, 3.668991e-002, -3.693380e-002, 3.717302e-002, 3.740944e-002, 3.763660e-002, -3.785714e-002, 3.806982e-002, 3.827548e-002, 3.847419e-002, -3.866396e-002, 3.885067e-002, 3.903115e-002, 3.920685e-002, -3.937780e-002, 3.954491e-002, 3.970796e-002, 3.986178e-002, -4.001373e-002, 4.015855e-002, 4.029890e-002, 4.043529e-002, -4.056528e-002, 4.069092e-002, 4.081260e-002, 4.092676e-002, -4.103873e-002, 4.114570e-002, 4.124747e-002, 4.134345e-002, -4.143188e-002, 4.151645e-002, 4.159395e-002, 4.166355e-002, -4.173055e-002, 4.179073e-002, 4.184467e-002, 4.189187e-002, -4.193239e-002, 4.196968e-002, 4.200237e-002, 4.202886e-002, -4.205290e-002, 4.207137e-002, 4.208656e-002, 4.209566e-002, -4.209963e-002, 4.209968e-002, 4.209733e-002, 4.208945e-002, -4.207928e-002, 4.206520e-002, 4.204744e-002, 4.202663e-002, -4.199872e-002, 4.196584e-002, 4.193164e-002, 4.189285e-002, -4.184639e-002, 4.179631e-002, 4.174293e-002, 4.168768e-002, -4.162983e-002, 4.156668e-002, 4.149975e-002, 4.142993e-002, -4.135414e-002, 4.127279e-002, 4.119022e-002, 4.110327e-002, -4.101329e-002, 4.092093e-002, 4.082437e-002, 4.072230e-002, -4.061815e-002, 4.051180e-002, 4.040312e-002, 4.029203e-002, -4.017848e-002, 4.006180e-002, 3.994356e-002, 3.982271e-002, -3.969911e-002, 3.957502e-002, 3.945107e-002, 3.932636e-002, -3.919872e-002, 3.906977e-002, 3.893930e-002, 3.880742e-002, -3.867382e-002, 3.853734e-002, 3.839708e-002, 3.825433e-002, -3.811106e-002, 3.796525e-002, 3.781681e-002, 3.766634e-002, -3.751375e-002, 3.735865e-002, 3.720277e-002, 3.704359e-002, -3.688229e-002, 3.671925e-002, 3.655369e-002, 3.638574e-002, -3.621416e-002, 3.604147e-002, 3.586678e-002, 3.569232e-002, -3.551728e-002, 3.534102e-002, 3.516244e-002, 3.498119e-002, -3.479838e-002, 3.461231e-002, 3.442526e-002, 3.423686e-002, -3.404634e-002, 3.385433e-002, 3.366179e-002, 3.346944e-002, -3.327630e-002, 3.308278e-002, 3.288709e-002, 3.268883e-002, -3.248765e-002, 3.228683e-002, 3.208361e-002, 3.187810e-002, -3.166924e-002, 3.146022e-002, 3.124957e-002, 3.103519e-002, -3.081974e-002, 3.060306e-002, 3.038578e-002, 3.016596e-002, -2.994620e-002, 2.972404e-002, 2.949999e-002, 2.927360e-002, -2.904702e-002, 2.882051e-002, 2.859334e-002, 2.836347e-002, -2.813350e-002, 2.790291e-002, 2.767057e-002, 2.743701e-002, -2.720281e-002, 2.696625e-002, 2.673157e-002, 2.649659e-002, -2.626168e-002, 2.602569e-002, 2.578910e-002, 2.555118e-002, -2.531368e-002, 2.507698e-002, 2.484014e-002, 2.460235e-002, -2.436583e-002, 2.412815e-002, 2.388920e-002, 2.365033e-002, -2.341059e-002, 2.316970e-002, 2.292831e-002, 2.268420e-002, -2.244054e-002, 2.219756e-002, 2.195360e-002, 2.170899e-002, -2.146600e-002, 2.122240e-002, 2.097791e-002, 2.073426e-002, -2.049191e-002, 2.025015e-002, 2.000811e-002, 1.976600e-002, -1.952313e-002, 1.928183e-002, 1.904155e-002, 1.880019e-002, -1.855812e-002, 1.831728e-002, 1.807667e-002, 1.783590e-002, -1.759459e-002, 1.735397e-002, 1.711338e-002, 1.687298e-002, -1.663316e-002, 1.639232e-002, 1.615232e-002, 1.591339e-002, -1.567317e-002, 1.543240e-002, 1.519348e-002, 1.495407e-002, -1.471271e-002, 1.447120e-002, 1.423002e-002, 1.398939e-002, -1.374940e-002, 1.350901e-002, 1.326724e-002, 1.302598e-002, -1.278601e-002, 1.254618e-002, 1.230531e-002, 1.206388e-002, -1.182352e-002, 1.158297e-002, 1.134161e-002, 1.110085e-002, -1.085994e-002, 1.061897e-002, 1.037977e-002, 1.014137e-002, -9.901052e-003, 9.659893e-003, 9.419405e-003, 9.179110e-003, -8.937359e-003, 8.695699e-003, 8.456020e-003, 8.215845e-003, -7.975956e-003, 7.736163e-003, 7.496472e-003, 7.256680e-003, -7.015320e-003, 6.775422e-003, 6.534686e-003, 6.293493e-003, -6.053075e-003, 5.813559e-003, 5.574828e-003, 5.336566e-003, -5.099228e-003, 4.863776e-003, 4.628290e-003, 4.393131e-003, -4.158830e-003, 3.925326e-003, 3.692849e-003, 3.459805e-003, -3.227877e-003, 2.996216e-003, 2.765209e-003, 2.535830e-003, -2.308013e-003, 2.081011e-003, 1.854741e-003, 1.629909e-003, -1.406858e-003, 1.184176e-003, 9.618576e-004, 7.402950e-004, -5.197663e-004, 2.995273e-004, 8.047108e-005, -1.378609e-004, --3.561288e-004, -5.742468e-004, -7.912948e-004, -1.006561e-003, --1.221347e-003, -1.436459e-003, -1.652513e-003, -1.867854e-003, --2.082229e-003, -2.296697e-003, -2.511206e-003, -2.725272e-003, --2.938945e-003, -3.151963e-003, -3.364068e-003, -3.575356e-003, --3.785537e-003, -3.996126e-003, -4.205662e-003, -4.413863e-003, --4.620833e-003, -4.828086e-003, -5.034579e-003, -5.239381e-003, --5.442322e-003, -5.644325e-003, -5.846386e-003, -6.048032e-003, --6.249134e-003, -6.448037e-003, -6.646043e-003, -6.841923e-003, --7.036523e-003, -7.230505e-003, -7.423379e-003, -7.614369e-003, --7.804266e-003, -7.992173e-003, -8.178170e-003, -8.364124e-003, --8.549087e-003, -8.733346e-003, -8.918920e-003, -9.103168e-003, --9.287606e-003, -9.472283e-003, -9.655203e-003, -9.837420e-003, --1.001970e-002, -1.020096e-002, -1.038267e-002, -1.056340e-002, --1.074229e-002, -1.091976e-002, -1.109704e-002, -1.127340e-002, --1.144945e-002, -1.162365e-002, -1.179651e-002, -1.196920e-002, --1.214148e-002, -1.231243e-002, -1.248266e-002, -1.265180e-002, --1.281936e-002, -1.298695e-002, -1.315444e-002, -1.332086e-002, --1.348694e-002, -1.365192e-002, -1.381604e-002, -1.398117e-002, --1.414626e-002, -1.431091e-002, -1.447517e-002, -1.463846e-002, --1.480160e-002, -1.496485e-002, -1.512775e-002, -1.528922e-002, --1.544980e-002, -1.560926e-002, -1.576820e-002, -1.592641e-002, --1.608491e-002, -1.624321e-002, -1.640017e-002, -1.655687e-002, --1.671230e-002, -1.686651e-002, -1.702076e-002, -1.717454e-002, --1.732689e-002, -1.747800e-002, -1.762782e-002, -1.777712e-002, --1.792688e-002, -1.807691e-002, -1.822576e-002, -1.837435e-002, --1.852222e-002, -1.866919e-002, -1.881573e-002, -1.896344e-002, --1.911117e-002, -1.925852e-002, -1.940528e-002, -1.955111e-002, --1.969716e-002, -1.984384e-002, -1.999016e-002, -2.013557e-002, --2.028047e-002, -2.042520e-002, -2.057056e-002, -2.071559e-002, --2.085892e-002, -2.100088e-002, -2.114227e-002, -2.128283e-002, --2.142158e-002, -2.155918e-002, -2.169606e-002, -2.183246e-002, --2.196871e-002, -2.210460e-002, -2.223865e-002, -2.237185e-002, --2.250417e-002, -2.263552e-002, -2.276588e-002, -2.289492e-002, --2.302223e-002, -2.314923e-002, -2.327584e-002, -2.340239e-002, --2.352801e-002, -2.365306e-002, -2.377671e-002, -2.389804e-002, --2.401916e-002, -2.413960e-002, -2.425943e-002, -2.437922e-002, --2.449893e-002, -2.461832e-002, -2.473598e-002, -2.485238e-002, --2.496896e-002, -2.508484e-002, -2.520058e-002, -2.531536e-002, --2.542905e-002, -2.554121e-002, -2.565210e-002, -2.576196e-002, --2.586960e-002, -2.597567e-002, -2.608072e-002, -2.618498e-002, --2.628824e-002, -2.639022e-002, -2.648999e-002, -2.658812e-002, --2.668522e-002, -2.678123e-002, -2.687642e-002, -2.696982e-002, --2.706123e-002, -2.715124e-002, -2.724066e-002, -2.732941e-002, --2.741711e-002, -2.750363e-002, -2.758979e-002, -2.767590e-002, --2.776181e-002, -2.784758e-002, -2.793144e-002, -2.801439e-002, --2.809756e-002, -2.818080e-002, -2.826359e-002, -2.834509e-002, --2.842487e-002, -2.850355e-002, -2.858192e-002, -2.866000e-002, --2.873745e-002, -2.881340e-002, -2.888830e-002, -2.896095e-002, --2.903270e-002, -2.910387e-002, -2.917398e-002, -2.924238e-002, --2.930963e-002, -2.937588e-002, -2.944073e-002, -2.950452e-002, --2.956450e-002, -2.962310e-002, -2.968106e-002, -2.973823e-002, --2.979351e-002, -2.984713e-002, -2.989988e-002, -2.995041e-002, --3.000066e-002, -3.005056e-002, -3.010012e-002, -3.014840e-002, --3.019567e-002, -3.024223e-002, -3.028617e-002, -3.032814e-002, --3.036954e-002, -3.041102e-002, -3.045222e-002, -3.049327e-002, --3.053466e-002, -3.057574e-002, -3.061549e-002, -3.065404e-002, --3.069146e-002, -3.072746e-002, -3.076229e-002, -3.079727e-002, --3.083157e-002, -3.086484e-002, -3.089665e-002, -3.092718e-002, --3.095631e-002, -3.098415e-002, -3.101104e-002, -3.103658e-002, --3.106086e-002, -3.108326e-002, -3.110436e-002, -3.112487e-002, --3.114376e-002, -3.116162e-002, -3.117918e-002, -3.119660e-002, --3.121399e-002, -3.123114e-002, -3.124711e-002, -3.126241e-002, --3.127668e-002, -3.129050e-002, -3.130314e-002, -3.131486e-002, --3.132576e-002, -3.133567e-002, -3.134454e-002, -3.135222e-002, --3.135904e-002, -3.136564e-002, -3.137218e-002, -3.137862e-002, --3.138390e-002, -3.138817e-002, -3.139153e-002, -3.139400e-002, --3.139580e-002, -3.139690e-002, -3.139783e-002, -3.139858e-002, --3.139891e-002, -3.139831e-002, -3.139585e-002, -3.139237e-002, --3.138806e-002, -3.138327e-002, -3.137886e-002, -3.137495e-002, --3.137116e-002, -3.136708e-002, -3.136174e-002, -3.135583e-002, --3.135000e-002, -3.134365e-002, -3.133670e-002, -3.133094e-002, --3.132526e-002, -3.131825e-002, -3.130912e-002, -3.129914e-002, --3.128862e-002, -3.127698e-002, -3.126509e-002, -3.125428e-002, --3.124434e-002, -3.123373e-002, -3.122201e-002, -3.120911e-002, --3.119552e-002, -3.118113e-002, -3.116580e-002, -3.114961e-002, --3.113421e-002, -3.111750e-002, -3.110020e-002, -3.108284e-002, --3.106499e-002, -3.104645e-002, -3.102719e-002, -3.100783e-002, --3.098805e-002, -3.096867e-002, -3.094856e-002, -3.092767e-002, --3.090604e-002, -3.088390e-002, -3.086107e-002, -3.083835e-002, --3.081606e-002, -3.079334e-002, -3.076981e-002, -3.074538e-002, --3.071995e-002, -3.069313e-002, -3.066548e-002, -3.063684e-002, --3.060647e-002, -3.057544e-002, -3.054392e-002, -3.051034e-002, --3.047645e-002, -3.044176e-002, -3.040569e-002, -3.036833e-002, --3.032999e-002, -3.029101e-002, -3.025124e-002, -3.021057e-002, --3.016863e-002, -3.012580e-002, -3.008211e-002, -3.003763e-002, --2.999231e-002, -2.994693e-002, -2.990135e-002, -2.985585e-002, --2.980960e-002, -2.976303e-002, -2.971655e-002, -2.966943e-002, --2.962093e-002, -2.957121e-002, -2.952121e-002, -2.947046e-002, --2.941903e-002, -2.936703e-002, -2.931414e-002, -2.926076e-002, --2.920690e-002, -2.915168e-002, -2.909498e-002, -2.903775e-002, --2.897961e-002, -2.892032e-002, -2.886049e-002, -2.879999e-002, --2.873869e-002, -2.867589e-002, -2.861158e-002, -2.854613e-002, --2.847926e-002, -2.841161e-002, -2.834415e-002, -2.827651e-002, --2.820831e-002, -2.813997e-002, -2.807145e-002, -2.800227e-002, --2.793237e-002, -2.786218e-002, -2.779120e-002, -2.771971e-002, --2.764775e-002, -2.757554e-002, -2.750195e-002, -2.742777e-002, --2.735320e-002, -2.727702e-002, -2.719959e-002, -2.712069e-002, --2.704102e-002, -2.695988e-002, -2.687810e-002, -2.679571e-002, --2.671273e-002, -2.662901e-002, -2.654375e-002, -2.645708e-002, --2.636810e-002, -2.627746e-002, -2.618469e-002, -2.609053e-002, --2.599556e-002, -2.589976e-002, -2.580349e-002, -2.570639e-002, --2.560821e-002, -2.550845e-002, -2.540844e-002, -2.530785e-002, --2.520559e-002, -2.510217e-002, -2.499837e-002, -2.489339e-002, --2.478844e-002, -2.468330e-002, -2.457710e-002, -2.446992e-002, --2.436192e-002, -2.425277e-002, -2.414230e-002, -2.403049e-002, --2.391828e-002, -2.380561e-002, -2.369251e-002, -2.357840e-002, --2.346281e-002, -2.334630e-002, -2.322889e-002, -2.310974e-002, --2.298976e-002, -2.286840e-002, -2.274575e-002, -2.262220e-002, --2.249813e-002, -2.237310e-002, -2.224729e-002, -2.212094e-002, --2.199337e-002, -2.186401e-002, -2.173349e-002, -2.160165e-002, --2.146829e-002, -2.133448e-002, -2.119999e-002, -2.106507e-002, --2.092976e-002, -2.079391e-002, -2.065639e-002, -2.051733e-002, --2.037726e-002, -2.023626e-002, -2.009377e-002, -1.995007e-002, --1.980568e-002, -1.966048e-002, -1.951412e-002, -1.936651e-002, --1.921762e-002, -1.906792e-002, -1.891756e-002, -1.876641e-002, --1.861296e-002, -1.845867e-002, -1.830355e-002, -1.814728e-002, --1.799036e-002, -1.783077e-002, -1.767085e-002, -1.751058e-002, --1.734999e-002, -1.718899e-002, -1.702579e-002, -1.686109e-002, --1.669571e-002, -1.652990e-002, -1.636388e-002, -1.619736e-002, --1.603026e-002, -1.586195e-002, -1.569253e-002, -1.552149e-002, --1.534850e-002, -1.517396e-002, -1.499892e-002, -1.482333e-002, --1.464706e-002, -1.446959e-002, -1.429032e-002, -1.410958e-002, --1.392775e-002, -1.374517e-002, -1.356104e-002, -1.337471e-002, --1.318766e-002, -1.299876e-002, -1.280845e-002, -1.261703e-002, --1.242486e-002, -1.223155e-002, -1.203656e-002, -1.184078e-002, --1.164411e-002, -1.144457e-002, -1.124374e-002, -1.104176e-002, --1.083946e-002, -1.063695e-002, -1.043423e-002, -1.023041e-002, --1.002511e-002, -9.819270e-003, -9.612281e-003, -9.401320e-003, --9.189639e-003, -8.978009e-003, -8.766439e-003, -8.554695e-003, --8.342656e-003, -8.130345e-003, -7.916270e-003, -7.699817e-003, --7.482708e-003, -7.262805e-003, -7.040255e-003, -6.817261e-003, --6.593612e-003, -6.368892e-003, -6.143592e-003, -5.916395e-003, --5.688754e-003, -5.459543e-003, -5.228324e-003, -4.994517e-003, --4.757789e-003, -4.520199e-003, -4.281770e-003, -4.040579e-003, --3.798196e-003, -3.554220e-003, -3.309345e-003, -3.064056e-003, --2.817498e-003, -2.566793e-003, -2.312722e-003, -2.057098e-003, --1.799981e-003, -1.542788e-003, -1.285585e-003, -1.028354e-003, --7.711852e-004, -5.140255e-004, -2.569877e-004, -7.472155e-018 -}, -{ -0.000000e+000, -3.686752e-002, -4.707916e-002, -5.424298e-002, --5.974686e-002, -6.421704e-002, -6.809730e-002, -7.150207e-002, --7.456737e-002, -7.735639e-002, -7.991997e-002, -8.227440e-002, --8.439895e-002, -8.625641e-002, -8.779916e-002, -8.904610e-002, --9.001451e-002, -9.072532e-002, -9.124275e-002, -9.158673e-002, --9.182539e-002, -9.200225e-002, -9.208038e-002, -9.210195e-002, --9.201929e-002, -9.184150e-002, -9.158301e-002, -9.123810e-002, --9.083478e-002, -9.035514e-002, -8.977864e-002, -8.910458e-002, --8.833068e-002, -8.745202e-002, -8.646511e-002, -8.537988e-002, --8.419200e-002, -8.287240e-002, -8.145336e-002, -7.994302e-002, --7.834295e-002, -7.665127e-002, -7.490048e-002, -7.308915e-002, --7.122782e-002, -6.932175e-002, -6.738629e-002, -6.542944e-002, --6.343609e-002, -6.141668e-002, -5.936762e-002, -5.726929e-002, --5.513005e-002, -5.294581e-002, -5.074574e-002, -4.853045e-002, --4.630711e-002, -4.409634e-002, -4.189540e-002, -3.972582e-002, --3.758626e-002, -3.549378e-002, -3.341818e-002, -3.135686e-002, --2.930771e-002, -2.727640e-002, -2.526127e-002, -2.327324e-002, --2.131527e-002, -1.937966e-002, -1.746311e-002, -1.557883e-002, --1.372572e-002, -1.189704e-002, -1.009320e-002, -8.330456e-003, --6.600937e-003, -4.892731e-003, -3.204983e-003, -1.539323e-003, -1.087786e-004, 1.741523e-003, 3.357796e-003, 4.946539e-003, -6.526105e-003, 8.087222e-003, 9.623635e-003, 1.114311e-002, -1.263078e-002, 1.409044e-002, 1.553051e-002, 1.693758e-002, -1.832100e-002, 1.967297e-002, 2.099259e-002, 2.228373e-002, -2.354167e-002, 2.476117e-002, 2.593983e-002, 2.709688e-002, -2.822413e-002, 2.932345e-002, 3.039570e-002, 3.144278e-002, -3.247026e-002, 3.347581e-002, 3.445661e-002, 3.541730e-002, -3.635977e-002, 3.728128e-002, 3.817157e-002, 3.903504e-002, -3.988187e-002, 4.070002e-002, 4.147968e-002, 4.222756e-002, -4.294408e-002, 4.363506e-002, 4.429347e-002, 4.491849e-002, -4.550622e-002, 4.606525e-002, 4.659057e-002, 4.708795e-002, -4.755407e-002, 4.798880e-002, 4.839570e-002, 4.878390e-002, -4.914796e-002, 4.949170e-002, 4.981731e-002, 5.012803e-002, -5.041794e-002, 5.068709e-002, 5.093673e-002, 5.117082e-002, -5.139053e-002, 5.160546e-002, 5.180635e-002, 5.198778e-002, -5.216079e-002, 5.232146e-002, 5.245521e-002, 5.258058e-002, -5.268417e-002, 5.277219e-002, 5.284493e-002, 5.289987e-002, -5.294295e-002, 5.295232e-002, 5.293886e-002, 5.290820e-002, -5.285789e-002, 5.278694e-002, 5.269092e-002, 5.258416e-002, -5.245384e-002, 5.230755e-002, 5.215269e-002, 5.197809e-002, -5.179503e-002, 5.160154e-002, 5.138270e-002, 5.115835e-002, -5.092048e-002, 5.066528e-002, 5.040347e-002, 5.012365e-002, -4.983004e-002, 4.953082e-002, 4.922354e-002, 4.890520e-002, -4.855925e-002, 4.820714e-002, 4.784969e-002, 4.748517e-002, -4.708991e-002, 4.668237e-002, 4.626548e-002, 4.584670e-002, -4.539089e-002, 4.493146e-002, 4.446371e-002, 4.396844e-002, -4.345779e-002, 4.294188e-002, 4.240077e-002, 4.184042e-002, -4.127357e-002, 4.066929e-002, 4.005822e-002, 3.942501e-002, -3.876983e-002, 3.809920e-002, 3.740036e-002, 3.669153e-002, -3.595514e-002, 3.521205e-002, 3.445416e-002, 3.368468e-002, -3.290001e-002, 3.210693e-002, 3.130610e-002, 3.049788e-002, -2.969452e-002, 2.888758e-002, 2.808967e-002, 2.729010e-002, -2.650343e-002, 2.571092e-002, 2.492943e-002, 2.414864e-002, -2.338439e-002, 2.262252e-002, 2.186761e-002, 2.111868e-002, -2.037299e-002, 1.963561e-002, 1.889743e-002, 1.817550e-002, -1.745468e-002, 1.673810e-002, 1.603273e-002, 1.532968e-002, -1.463230e-002, 1.393855e-002, 1.324819e-002, 1.256748e-002, -1.189409e-002, 1.121913e-002, 1.055384e-002, 9.891197e-003, -9.226005e-003, 8.570603e-003, 7.924821e-003, 7.278813e-003, -6.636009e-003, 6.003245e-003, 5.370034e-003, 4.736873e-003, -4.114991e-003, 3.497752e-003, 2.884234e-003, 2.272889e-003, -1.669924e-003, 1.068295e-003, 4.689074e-004, -1.273354e-004, --7.135699e-004, -1.299002e-003, -1.884465e-003, -2.465799e-003, --3.035150e-003, -3.602649e-003, -4.165791e-003, -4.726458e-003, --5.272796e-003, -5.815489e-003, -6.352506e-003, -6.881806e-003, --7.399020e-003, -7.909706e-003, -8.412922e-003, -8.907683e-003, --9.398501e-003, -9.875158e-003, -1.034606e-002, -1.081212e-002, --1.127346e-002, -1.172512e-002, -1.216630e-002, -1.260043e-002, --1.302984e-002, -1.345338e-002, -1.386666e-002, -1.427369e-002, --1.467342e-002, -1.506746e-002, -1.545470e-002, -1.583157e-002, --1.620119e-002, -1.656596e-002, -1.692563e-002, -1.728027e-002, --1.762665e-002, -1.796578e-002, -1.829915e-002, -1.862659e-002, --1.894976e-002, -1.926897e-002, -1.958175e-002, -1.989009e-002, --2.019472e-002, -2.049394e-002, -2.078876e-002, -2.107657e-002, --2.136162e-002, -2.164384e-002, -2.192293e-002, -2.219727e-002, --2.246562e-002, -2.273129e-002, -2.299395e-002, -2.325271e-002, --2.351119e-002, -2.376432e-002, -2.401186e-002, -2.425936e-002, --2.450587e-002, -2.475234e-002, -2.500292e-002, -2.524975e-002, --2.549221e-002, -2.573377e-002, -2.597303e-002, -2.620966e-002, --2.644458e-002, -2.667915e-002, -2.691132e-002, -2.714220e-002, --2.736910e-002, -2.759550e-002, -2.782144e-002, -2.804273e-002, --2.826174e-002, -2.848028e-002, -2.869335e-002, -2.890189e-002, --2.910718e-002, -2.930464e-002, -2.949511e-002, -2.968108e-002, --2.986668e-002, -3.004672e-002, -3.022181e-002, -3.039373e-002, --3.056167e-002, -3.072471e-002, -3.088472e-002, -3.104097e-002, --3.119396e-002, -3.134184e-002, -3.148208e-002, -3.161625e-002, --3.174617e-002, -3.187219e-002, -3.199329e-002, -3.210708e-002, --3.221758e-002, -3.232474e-002, -3.242694e-002, -3.252236e-002, --3.261333e-002, -3.269782e-002, -3.277689e-002, -3.284772e-002, --3.291825e-002, -3.298548e-002, -3.304762e-002, -3.310467e-002, --3.315795e-002, -3.320481e-002, -3.323905e-002, -3.327168e-002, --3.330400e-002, -3.333563e-002, -3.336245e-002, -3.338772e-002, --3.341355e-002, -3.343043e-002, -3.344169e-002, -3.344942e-002, --3.345532e-002, -3.345876e-002, -3.346031e-002, -3.346090e-002, --3.345749e-002, -3.344829e-002, -3.343847e-002, -3.342726e-002, --3.341415e-002, -3.340012e-002, -3.338378e-002, -3.336691e-002, --3.334871e-002, -3.333131e-002, -3.330978e-002, -3.328701e-002, --3.326246e-002, -3.323938e-002, -3.321396e-002, -3.318061e-002, --3.314734e-002, -3.311349e-002, -3.308041e-002, -3.304688e-002, --3.301147e-002, -3.297355e-002, -3.293685e-002, -3.289329e-002, --3.284876e-002, -3.280400e-002, -3.276137e-002, -3.271631e-002, --3.266975e-002, -3.262341e-002, -3.257818e-002, -3.253365e-002, --3.248434e-002, -3.243188e-002, -3.237650e-002, -3.232121e-002, --3.226633e-002, -3.220718e-002, -3.214571e-002, -3.208198e-002, --3.201791e-002, -3.195090e-002, -3.187977e-002, -3.180300e-002, --3.172280e-002, -3.164116e-002, -3.155572e-002, -3.146735e-002, --3.137357e-002, -3.127847e-002, -3.118422e-002, -3.108368e-002, --3.097873e-002, -3.087175e-002, -3.076270e-002, -3.065118e-002, --3.053370e-002, -3.041047e-002, -3.028623e-002, -3.016156e-002, --3.003651e-002, -2.990797e-002, -2.977988e-002, -2.965398e-002, --2.952611e-002, -2.939309e-002, -2.925492e-002, -2.911300e-002, --2.897271e-002, -2.883175e-002, -2.868896e-002, -2.854478e-002, --2.839884e-002, -2.825371e-002, -2.810742e-002, -2.796095e-002, --2.781175e-002, -2.765748e-002, -2.750248e-002, -2.734364e-002, --2.718256e-002, -2.701869e-002, -2.685742e-002, -2.669690e-002, --2.653339e-002, -2.637057e-002, -2.620674e-002, -2.603931e-002, --2.586909e-002, -2.569720e-002, -2.552496e-002, -2.534904e-002, --2.517170e-002, -2.499340e-002, -2.481836e-002, -2.464383e-002, --2.446611e-002, -2.428726e-002, -2.410204e-002, -2.391778e-002, --2.373298e-002, -2.354938e-002, -2.336368e-002, -2.317681e-002, --2.298983e-002, -2.280452e-002, -2.262086e-002, -2.243574e-002, --2.224932e-002, -2.206124e-002, -2.187361e-002, -2.168629e-002, --2.149628e-002, -2.130444e-002, -2.111423e-002, -2.092539e-002, --2.073661e-002, -2.054704e-002, -2.035571e-002, -2.016589e-002, --1.997657e-002, -1.978525e-002, -1.959239e-002, -1.939691e-002, --1.920034e-002, -1.900579e-002, -1.881134e-002, -1.861693e-002, --1.842240e-002, -1.822638e-002, -1.803051e-002, -1.783656e-002, --1.764623e-002, -1.745772e-002, -1.726663e-002, -1.707309e-002, --1.687929e-002, -1.668741e-002, -1.649485e-002, -1.630447e-002, --1.611337e-002, -1.592193e-002, -1.572956e-002, -1.553841e-002, --1.534620e-002, -1.515653e-002, -1.496662e-002, -1.477267e-002, --1.457842e-002, -1.438401e-002, -1.418770e-002, -1.399272e-002, --1.379496e-002, -1.359669e-002, -1.340046e-002, -1.320370e-002, --1.300539e-002, -1.280573e-002, -1.260308e-002, -1.239963e-002, --1.219630e-002, -1.198908e-002, -1.177731e-002, -1.156688e-002, --1.135609e-002, -1.114344e-002, -1.092908e-002, -1.071463e-002, --1.049985e-002, -1.028499e-002, -1.006678e-002, -9.845976e-003, --9.624370e-003, -9.401041e-003, -9.175400e-003, -8.950493e-003, --8.722643e-003, -8.491283e-003, -8.259034e-003, -8.025257e-003, --7.789180e-003, -7.551850e-003, -7.311113e-003, -7.066043e-003, --6.818615e-003, -6.570490e-003, -6.322606e-003, -6.072834e-003, --5.820865e-003, -5.565984e-003, -5.309028e-003, -5.051837e-003, --4.791712e-003, -4.528116e-003, -4.262771e-003, -3.996271e-003, --3.728189e-003, -3.461617e-003, -3.195312e-003, -2.928175e-003, --2.661061e-003, -2.393332e-003, -2.125493e-003, -1.858081e-003, --1.590996e-003, -1.324668e-003, -1.058363e-003, -7.913172e-004, --5.221920e-004, -2.510189e-004, 2.140818e-005, 2.928121e-004, -5.630597e-004, 8.352630e-004, 1.108931e-003, 1.383521e-003, -1.657981e-003, 1.932709e-003, 2.207605e-003, 2.482357e-003, -2.758673e-003, 3.037314e-003, 3.311569e-003, 3.585463e-003, -3.857855e-003, 4.131148e-003, 4.404711e-003, 4.682437e-003, -4.962140e-003, 5.241502e-003, 5.520677e-003, 5.798921e-003, -6.076529e-003, 6.355233e-003, 6.630229e-003, 6.896276e-003, -7.160813e-003, 7.423036e-003, 7.683619e-003, 7.942773e-003, -8.200177e-003, 8.456512e-003, 8.710720e-003, 8.964936e-003, -9.218669e-003, 9.469419e-003, 9.718448e-003, 9.967114e-003, -1.021657e-002, 1.046084e-002, 1.069793e-002, 1.093269e-002, -1.116588e-002, 1.139857e-002, 1.163240e-002, 1.186718e-002, -1.210047e-002, 1.233178e-002, 1.256112e-002, 1.278869e-002, -1.301435e-002, 1.324011e-002, 1.346778e-002, 1.369427e-002, -1.391720e-002, 1.413625e-002, 1.435342e-002, 1.457140e-002, -1.478901e-002, 1.500581e-002, 1.521804e-002, 1.542812e-002, -1.563485e-002, 1.583790e-002, 1.603386e-002, 1.622727e-002, -1.642084e-002, 1.661401e-002, 1.680573e-002, 1.699682e-002, -1.718835e-002, 1.737690e-002, 1.756313e-002, 1.774984e-002, -1.793515e-002, 1.811611e-002, 1.829717e-002, 1.847575e-002, -1.865394e-002, 1.883276e-002, 1.901329e-002, 1.919362e-002, -1.937381e-002, 1.955299e-002, 1.973262e-002, 1.991024e-002, -2.007569e-002, 2.023895e-002, 2.040218e-002, 2.056510e-002, -2.072457e-002, 2.088419e-002, 2.104356e-002, 2.119746e-002, -2.135005e-002, 2.150264e-002, 2.165576e-002, 2.180790e-002, -2.195855e-002, 2.210787e-002, 2.224657e-002, 2.237759e-002, -2.250815e-002, 2.263985e-002, 2.277238e-002, 2.290370e-002, -2.303282e-002, 2.315917e-002, 2.328280e-002, 2.340484e-002, -2.352691e-002, 2.364822e-002, 2.376566e-002, 2.388233e-002, -2.399686e-002, 2.410843e-002, 2.421724e-002, 2.432245e-002, -2.442169e-002, 2.451867e-002, 2.461635e-002, 2.471437e-002, -2.480897e-002, 2.490082e-002, 2.499110e-002, 2.508133e-002, -2.517542e-002, 2.527088e-002, 2.536616e-002, 2.546041e-002, -2.555259e-002, 2.564398e-002, 2.573504e-002, 2.582530e-002, -2.591610e-002, 2.600670e-002, 2.609049e-002, 2.616960e-002, -2.624690e-002, 2.632224e-002, 2.639638e-002, 2.646646e-002, -2.653611e-002, 2.660697e-002, 2.667836e-002, 2.674905e-002, -2.681822e-002, 2.688619e-002, 2.695308e-002, 2.701991e-002, -2.708692e-002, 2.715228e-002, 2.721881e-002, 2.728731e-002, -2.735443e-002, 2.741990e-002, 2.748351e-002, 2.754559e-002, -2.760754e-002, 2.766992e-002, 2.773132e-002, 2.778697e-002, -2.784087e-002, 2.789418e-002, 2.794661e-002, 2.799862e-002, -2.805053e-002, 2.810131e-002, 2.815085e-002, 2.820139e-002, -2.824903e-002, 2.829519e-002, 2.833650e-002, 2.837695e-002, -2.841817e-002, 2.846024e-002, 2.850330e-002, 2.854653e-002, -2.858834e-002, 2.862776e-002, 2.866714e-002, 2.870822e-002, -2.874992e-002, 2.879280e-002, 2.883658e-002, 2.887837e-002, -2.891985e-002, 2.896213e-002, 2.900452e-002, 2.904645e-002, -2.908751e-002, 2.912664e-002, 2.916125e-002, 2.919489e-002, -2.922734e-002, 2.925837e-002, 2.928688e-002, 2.931367e-002, -2.933987e-002, 2.936466e-002, 2.938917e-002, 2.941494e-002, -2.943962e-002, 2.946136e-002, 2.948006e-002, 2.949563e-002, -2.950960e-002, 2.952191e-002, 2.953362e-002, 2.954413e-002, -2.955414e-002, 2.956491e-002, 2.957439e-002, 2.957989e-002, -2.958568e-002, 2.959031e-002, 2.959346e-002, 2.959619e-002, -2.960041e-002, 2.960541e-002, 2.961041e-002, 2.961507e-002, -2.962047e-002, 2.962507e-002, 2.962887e-002, 2.963091e-002, -2.963218e-002, 2.963440e-002, 2.963310e-002, 2.963052e-002, -2.962576e-002, 2.961695e-002, 2.960482e-002, 2.959002e-002, -2.957304e-002, 2.955240e-002, 2.953086e-002, 2.950836e-002, -2.948497e-002, 2.946018e-002, 2.943453e-002, 2.940795e-002, -2.938030e-002, 2.935177e-002, 2.932053e-002, 2.928996e-002, -2.926032e-002, 2.923058e-002, 2.920033e-002, 2.916999e-002, -2.913908e-002, 2.910708e-002, 2.907456e-002, 2.904135e-002, -2.900909e-002, 2.897787e-002, 2.894670e-002, 2.891464e-002, -2.888131e-002, 2.884690e-002, 2.881171e-002, 2.877515e-002, -2.873658e-002, 2.869810e-002, 2.865913e-002, 2.861727e-002, -2.857394e-002, 2.852956e-002, 2.848188e-002, 2.843185e-002, -2.838120e-002, 2.832874e-002, 2.827399e-002, 2.821993e-002, -2.816489e-002, 2.810958e-002, 2.805411e-002, 2.800055e-002, -2.794712e-002, 2.789330e-002, 2.783867e-002, 2.778228e-002, -2.772642e-002, 2.767076e-002, 2.761538e-002, 2.755842e-002, -2.750172e-002, 2.744443e-002, 2.738648e-002, 2.732786e-002, -2.726752e-002, 2.720471e-002, 2.714402e-002, 2.708276e-002, -2.701978e-002, 2.695388e-002, 2.688552e-002, 2.681497e-002, -2.674168e-002, 2.666553e-002, 2.658529e-002, 2.650240e-002, -2.641919e-002, 2.633420e-002, 2.624774e-002, 2.615760e-002, -2.606572e-002, 2.597198e-002, 2.587634e-002, 2.577769e-002, -2.567703e-002, 2.557585e-002, 2.547455e-002, 2.537420e-002, -2.527359e-002, 2.517095e-002, 2.506483e-002, 2.495609e-002, -2.484646e-002, 2.473641e-002, 2.462477e-002, 2.451235e-002, -2.439961e-002, 2.428628e-002, 2.417314e-002, 2.405808e-002, -2.394161e-002, 2.382471e-002, 2.370691e-002, 2.358810e-002, -2.346806e-002, 2.334681e-002, 2.322358e-002, 2.309854e-002, -2.297210e-002, 2.284372e-002, 2.271237e-002, 2.258113e-002, -2.244906e-002, 2.231511e-002, 2.218007e-002, 2.204387e-002, -2.190615e-002, 2.176667e-002, 2.162626e-002, 2.148471e-002, -2.133846e-002, 2.119118e-002, 2.104237e-002, 2.089317e-002, -2.074246e-002, 2.058801e-002, 2.043430e-002, 2.027965e-002, -2.012410e-002, 1.996752e-002, 1.980653e-002, 1.964417e-002, -1.947994e-002, 1.931414e-002, 1.914714e-002, 1.897797e-002, -1.880728e-002, 1.863435e-002, 1.845874e-002, 1.828167e-002, -1.809997e-002, 1.791451e-002, 1.772691e-002, 1.753726e-002, -1.734495e-002, 1.714788e-002, 1.694687e-002, 1.674338e-002, -1.653714e-002, 1.632927e-002, 1.611793e-002, 1.590216e-002, -1.568510e-002, 1.546762e-002, 1.524848e-002, 1.502823e-002, -1.480667e-002, 1.458427e-002, 1.435927e-002, 1.413336e-002, -1.390664e-002, 1.367515e-002, 1.344395e-002, 1.321234e-002, -1.298018e-002, 1.274746e-002, 1.251421e-002, 1.228035e-002, -1.204704e-002, 1.181207e-002, 1.157563e-002, 1.133152e-002, -1.108539e-002, 1.083813e-002, 1.058951e-002, 1.033983e-002, -1.008932e-002, 9.837875e-003, 9.583791e-003, 9.327003e-003, -9.068603e-003, 8.805135e-003, 8.541302e-003, 8.276973e-003, -8.011151e-003, 7.741779e-003, 7.471056e-003, 7.199198e-003, -6.926701e-003, 6.652117e-003, 6.372945e-003, 6.090283e-003, -5.804499e-003, 5.518835e-003, 5.233289e-003, 4.946827e-003, -4.659788e-003, 4.373713e-003, 4.083638e-003, 3.791420e-003, -3.495903e-003, 3.187555e-003, 2.873031e-003, 2.557847e-003, -2.241773e-003, 1.924788e-003, 1.606810e-003, 1.287795e-003, -9.675759e-004, 6.462152e-004, 3.236570e-004, 7.435108e-018 -}, -{ -0.000000e+000, -6.469713e-002, -7.828328e-002, -8.686220e-002, --9.211825e-002, -9.558424e-002, -9.846409e-002, -1.007057e-001, --1.024699e-001, -1.037916e-001, -1.047760e-001, -1.054649e-001, --1.057462e-001, -1.056053e-001, -1.049179e-001, -1.037458e-001, --1.021393e-001, -1.002687e-001, -9.822223e-002, -9.594575e-002, --9.350405e-002, -9.096367e-002, -8.826829e-002, -8.553726e-002, --8.273321e-002, -7.986001e-002, -7.690865e-002, -7.387185e-002, --7.074680e-002, -6.755361e-002, -6.429751e-002, -6.099197e-002, --5.764445e-002, -5.425388e-002, -5.085381e-002, -4.744986e-002, --4.403984e-002, -4.058302e-002, -3.713389e-002, -3.370350e-002, --3.029566e-002, -2.686463e-002, -2.344809e-002, -2.008649e-002, --1.671963e-002, -1.337802e-002, -1.010702e-002, -6.864855e-003, --3.672138e-003, -5.307672e-004, 2.589418e-003, 5.674849e-003, -8.695555e-003, 1.171779e-002, 1.464808e-002, 1.752246e-002, -2.036470e-002, 2.309187e-002, 2.575660e-002, 2.832696e-002, -3.079189e-002, 3.314843e-002, 3.544774e-002, 3.764760e-002, -3.976008e-002, 4.179727e-002, 4.374745e-002, 4.559640e-002, -4.735952e-002, 4.903644e-002, 5.060058e-002, 5.208287e-002, -5.348482e-002, 5.480818e-002, 5.602395e-002, 5.716275e-002, -5.821523e-002, 5.920252e-002, 6.010275e-002, 6.094844e-002, -6.173664e-002, 6.243826e-002, 6.308983e-002, 6.368032e-002, -6.421476e-002, 6.469794e-002, 6.511787e-002, 6.548597e-002, -6.576099e-002, 6.598615e-002, 6.614675e-002, 6.624009e-002, -6.624674e-002, 6.618259e-002, 6.602643e-002, 6.579915e-002, -6.550360e-002, 6.512106e-002, 6.463925e-002, 6.409128e-002, -6.346120e-002, 6.275858e-002, 6.201425e-002, 6.120558e-002, -6.035688e-002, 5.946349e-002, 5.851048e-002, 5.750876e-002, -5.646295e-002, 5.539168e-002, 5.424992e-002, 5.308774e-002, -5.189343e-002, 5.066878e-002, 4.939702e-002, 4.810207e-002, -4.677344e-002, 4.542653e-002, 4.406433e-002, 4.265098e-002, -4.122814e-002, 3.978345e-002, 3.831821e-002, 3.683513e-002, -3.531468e-002, 3.378772e-002, 3.224818e-002, 3.070666e-002, -2.914897e-002, 2.758195e-002, 2.601353e-002, 2.445871e-002, -2.290973e-002, 2.136340e-002, 1.982237e-002, 1.829095e-002, -1.677156e-002, 1.525633e-002, 1.376226e-002, 1.226910e-002, -1.078235e-002, 9.318496e-003, 7.867929e-003, 6.432017e-003, -5.003915e-003, 3.604318e-003, 2.223420e-003, 8.547227e-004, --4.877055e-004, -1.807694e-003, -3.107970e-003, -4.385532e-003, --5.638788e-003, -6.866865e-003, -8.074734e-003, -9.267347e-003, --1.042628e-002, -1.155523e-002, -1.266292e-002, -1.374568e-002, --1.480350e-002, -1.582305e-002, -1.681563e-002, -1.776001e-002, --1.868603e-002, -1.958011e-002, -2.043513e-002, -2.125504e-002, --2.205138e-002, -2.282605e-002, -2.356869e-002, -2.428369e-002, --2.496682e-002, -2.561731e-002, -2.624393e-002, -2.684944e-002, --2.742411e-002, -2.797223e-002, -2.849773e-002, -2.900693e-002, --2.948752e-002, -2.994816e-002, -3.038768e-002, -3.081011e-002, --3.121545e-002, -3.160541e-002, -3.197178e-002, -3.232573e-002, --3.266658e-002, -3.298497e-002, -3.328772e-002, -3.357785e-002, --3.385232e-002, -3.411056e-002, -3.434806e-002, -3.457410e-002, --3.478713e-002, -3.498274e-002, -3.515363e-002, -3.531586e-002, --3.546732e-002, -3.561505e-002, -3.574698e-002, -3.587096e-002, --3.596673e-002, -3.606402e-002, -3.614986e-002, -3.623441e-002, --3.630137e-002, -3.636224e-002, -3.642192e-002, -3.646557e-002, --3.650864e-002, -3.652658e-002, -3.654059e-002, -3.653371e-002, --3.651568e-002, -3.650123e-002, -3.647878e-002, -3.645327e-002, --3.641473e-002, -3.637458e-002, -3.633287e-002, -3.628509e-002, --3.623413e-002, -3.617405e-002, -3.609516e-002, -3.601382e-002, --3.592722e-002, -3.584515e-002, -3.576005e-002, -3.566187e-002, --3.555786e-002, -3.543635e-002, -3.532140e-002, -3.520260e-002, --3.506985e-002, -3.493287e-002, -3.477655e-002, -3.460915e-002, --3.445059e-002, -3.428397e-002, -3.410934e-002, -3.392135e-002, --3.371533e-002, -3.350538e-002, -3.330485e-002, -3.308700e-002, --3.286704e-002, -3.263976e-002, -3.240353e-002, -3.215252e-002, --3.190454e-002, -3.165636e-002, -3.140870e-002, -3.114935e-002, --3.088873e-002, -3.062491e-002, -3.035330e-002, -3.008189e-002, --2.980032e-002, -2.950477e-002, -2.920876e-002, -2.890041e-002, --2.857632e-002, -2.825969e-002, -2.793609e-002, -2.761030e-002, --2.728419e-002, -2.694444e-002, -2.659894e-002, -2.625316e-002, --2.590819e-002, -2.555708e-002, -2.519867e-002, -2.482573e-002, --2.444520e-002, -2.405853e-002, -2.366800e-002, -2.327730e-002, --2.289013e-002, -2.249635e-002, -2.209150e-002, -2.167495e-002, --2.125765e-002, -2.083612e-002, -2.041710e-002, -2.000520e-002, --1.959938e-002, -1.919129e-002, -1.877985e-002, -1.835896e-002, --1.793262e-002, -1.751796e-002, -1.711021e-002, -1.671708e-002, --1.633344e-002, -1.594612e-002, -1.554789e-002, -1.514211e-002, --1.473306e-002, -1.433118e-002, -1.393662e-002, -1.355774e-002, --1.317637e-002, -1.279617e-002, -1.241989e-002, -1.203777e-002, --1.164872e-002, -1.125977e-002, -1.087416e-002, -1.050511e-002, --1.014183e-002, -9.779162e-003, -9.419219e-003, -9.068369e-003, --8.724870e-003, -8.378705e-003, -8.025508e-003, -7.678557e-003, --7.342223e-003, -7.004579e-003, -6.672211e-003, -6.340469e-003, --6.006833e-003, -5.667340e-003, -5.336685e-003, -5.008936e-003, --4.681159e-003, -4.362073e-003, -4.048104e-003, -3.733661e-003, --3.409438e-003, -3.093370e-003, -2.775665e-003, -2.451900e-003, --2.122110e-003, -1.808526e-003, -1.494753e-003, -1.172514e-003, --8.560255e-004, -5.530390e-004, -2.482669e-004, 6.327031e-005, -3.705023e-004, 6.780779e-004, 9.896990e-004, 1.303300e-003, -1.608933e-003, 1.903439e-003, 2.192033e-003, 2.478485e-003, -2.758963e-003, 3.041980e-003, 3.327492e-003, 3.612473e-003, -3.893617e-003, 4.170845e-003, 4.443882e-003, 4.714857e-003, -4.977444e-003, 5.232276e-003, 5.473099e-003, 5.719741e-003, -5.960384e-003, 6.197108e-003, 6.425966e-003, 6.652598e-003, -6.876645e-003, 7.096168e-003, 7.316715e-003, 7.532865e-003, -7.745077e-003, 7.954218e-003, 8.159821e-003, 8.371581e-003, -8.576753e-003, 8.777826e-003, 8.972151e-003, 9.174652e-003, -9.376912e-003, 9.576662e-003, 9.775730e-003, 9.973411e-003, -1.016606e-002, 1.035288e-002, 1.053215e-002, 1.071271e-002, -1.089768e-002, 1.107173e-002, 1.123799e-002, 1.141147e-002, -1.159198e-002, 1.177428e-002, 1.195221e-002, 1.212903e-002, -1.231008e-002, 1.249142e-002, 1.266480e-002, 1.283348e-002, -1.299771e-002, 1.316520e-002, 1.332222e-002, 1.347624e-002, -1.363001e-002, 1.378710e-002, 1.393893e-002, 1.409555e-002, -1.425783e-002, 1.442263e-002, 1.458512e-002, 1.474156e-002, -1.489157e-002, 1.503672e-002, 1.517820e-002, 1.531295e-002, -1.544401e-002, 1.557124e-002, 1.570071e-002, 1.582456e-002, -1.594279e-002, 1.606681e-002, 1.619073e-002, 1.631032e-002, -1.642245e-002, 1.652711e-002, 1.663151e-002, 1.673808e-002, -1.684839e-002, 1.694630e-002, 1.704264e-002, 1.714222e-002, -1.724219e-002, 1.734485e-002, 1.744845e-002, 1.755396e-002, -1.765652e-002, 1.775528e-002, 1.785925e-002, 1.796461e-002, -1.807811e-002, 1.819162e-002, 1.830988e-002, 1.843336e-002, -1.855691e-002, 1.868156e-002, 1.879863e-002, 1.891709e-002, -1.903567e-002, 1.915606e-002, 1.927826e-002, 1.940671e-002, -1.953632e-002, 1.966803e-002, 1.980208e-002, 1.993816e-002, -2.007503e-002, 2.021599e-002, 2.036183e-002, 2.050835e-002, -2.064794e-002, 2.079284e-002, 2.093873e-002, 2.108389e-002, -2.121629e-002, 2.134323e-002, 2.147300e-002, 2.160443e-002, -2.173438e-002, 2.186495e-002, 2.199561e-002, 2.212724e-002, -2.225363e-002, 2.237569e-002, 2.249110e-002, 2.260387e-002, -2.271555e-002, 2.282435e-002, 2.293389e-002, 2.303817e-002, -2.313783e-002, 2.323866e-002, 2.334169e-002, 2.344180e-002, -2.353482e-002, 2.362114e-002, 2.370497e-002, 2.378883e-002, -2.387323e-002, 2.395422e-002, 2.403218e-002, 2.410758e-002, -2.417310e-002, 2.423806e-002, 2.429789e-002, 2.435348e-002, -2.440469e-002, 2.445107e-002, 2.449678e-002, 2.454035e-002, -2.457773e-002, 2.460996e-002, 2.463956e-002, 2.466377e-002, -2.468411e-002, 2.469747e-002, 2.470441e-002, 2.470438e-002, -2.470047e-002, 2.468993e-002, 2.467398e-002, 2.466152e-002, -2.464754e-002, 2.463338e-002, 2.462103e-002, 2.461048e-002, -2.460505e-002, 2.460307e-002, 2.460096e-002, 2.460043e-002, -2.460160e-002, 2.460665e-002, 2.461550e-002, 2.462380e-002, -2.463099e-002, 2.463669e-002, 2.464766e-002, 2.465821e-002, -2.466327e-002, 2.466966e-002, 2.467323e-002, 2.467568e-002, -2.467941e-002, 2.468133e-002, 2.468430e-002, 2.468566e-002, -2.468287e-002, 2.468217e-002, 2.468345e-002, 2.468323e-002, -2.467766e-002, 2.466956e-002, 2.466029e-002, 2.465382e-002, -2.464682e-002, 2.463666e-002, 2.462295e-002, 2.460483e-002, -2.458508e-002, 2.456382e-002, 2.454258e-002, 2.452160e-002, -2.449445e-002, 2.446215e-002, 2.442702e-002, 2.438685e-002, -2.434432e-002, 2.429725e-002, 2.424789e-002, 2.419664e-002, -2.414239e-002, 2.408240e-002, 2.402071e-002, 2.395808e-002, -2.388684e-002, 2.381085e-002, 2.373556e-002, 2.365988e-002, -2.358518e-002, 2.351202e-002, 2.343433e-002, 2.335242e-002, -2.326923e-002, 2.318460e-002, 2.310367e-002, 2.302928e-002, -2.295453e-002, 2.287819e-002, 2.279826e-002, 2.271765e-002, -2.263280e-002, 2.254679e-002, 2.245907e-002, 2.236747e-002, -2.227408e-002, 2.217659e-002, 2.207895e-002, 2.197686e-002, -2.186393e-002, 2.174706e-002, 2.162682e-002, 2.150253e-002, -2.137813e-002, 2.124852e-002, 2.111178e-002, 2.097042e-002, -2.082634e-002, 2.067989e-002, 2.052427e-002, 2.036544e-002, -2.020594e-002, 2.004695e-002, 1.988641e-002, 1.972133e-002, -1.955308e-002, 1.938313e-002, 1.920888e-002, 1.903272e-002, -1.885401e-002, 1.867309e-002, 1.848854e-002, 1.829809e-002, -1.810801e-002, 1.791995e-002, 1.773515e-002, 1.754897e-002, -1.736490e-002, 1.718336e-002, 1.700188e-002, 1.681903e-002, -1.663972e-002, 1.646311e-002, 1.629235e-002, 1.612415e-002, -1.595575e-002, 1.578185e-002, 1.560191e-002, 1.542351e-002, -1.524853e-002, 1.507342e-002, 1.490000e-002, 1.472524e-002, -1.454808e-002, 1.436873e-002, 1.418834e-002, 1.400464e-002, -1.382014e-002, 1.363393e-002, 1.344717e-002, 1.325962e-002, -1.306770e-002, 1.287641e-002, 1.268371e-002, 1.248988e-002, -1.229512e-002, 1.209297e-002, 1.188538e-002, 1.168147e-002, -1.147991e-002, 1.127749e-002, 1.107289e-002, 1.086519e-002, -1.065663e-002, 1.044708e-002, 1.023466e-002, 1.001842e-002, -9.797728e-003, 9.575952e-003, 9.353547e-003, 9.130139e-003, -8.906888e-003, 8.680285e-003, 8.448413e-003, 8.210929e-003, -7.970192e-003, 7.728476e-003, 7.485796e-003, 7.241040e-003, -6.994088e-003, 6.743012e-003, 6.486717e-003, 6.228414e-003, -5.965036e-003, 5.703174e-003, 5.440872e-003, 5.178946e-003, -4.915254e-003, 4.649898e-003, 4.385245e-003, 4.119458e-003, -3.855736e-003, 3.593426e-003, 3.331036e-003, 3.070594e-003, -2.810871e-003, 2.551814e-003, 2.294389e-003, 2.038227e-003, -1.784816e-003, 1.536631e-003, 1.290172e-003, 1.044451e-003, -8.014830e-004, 5.612823e-004, 3.223942e-004, 8.606447e-005, --1.492419e-004, -3.836474e-004, -6.161727e-004, -8.459682e-004, --1.072967e-003, -1.297765e-003, -1.521848e-003, -1.744353e-003, --1.967006e-003, -2.189597e-003, -2.412024e-003, -2.634081e-003, --2.860468e-003, -3.088981e-003, -3.318789e-003, -3.549015e-003, --3.781653e-003, -4.016864e-003, -4.253058e-003, -4.490186e-003, --4.729270e-003, -4.970168e-003, -5.211515e-003, -5.453856e-003, --5.702097e-003, -5.950879e-003, -6.198840e-003, -6.446401e-003, --6.696393e-003, -6.948213e-003, -7.203690e-003, -7.461122e-003, --7.718444e-003, -7.974375e-003, -8.228753e-003, -8.480625e-003, --8.735986e-003, -8.991849e-003, -9.247726e-003, -9.505388e-003, --9.764156e-003, -1.002380e-002, -1.028405e-002, -1.054084e-002, --1.079839e-002, -1.105724e-002, -1.131689e-002, -1.157669e-002, --1.183838e-002, -1.210115e-002, -1.236222e-002, -1.262037e-002, --1.288148e-002, -1.314344e-002, -1.340458e-002, -1.366718e-002, --1.393092e-002, -1.419661e-002, -1.446416e-002, -1.472882e-002, --1.499741e-002, -1.527085e-002, -1.554702e-002, -1.582706e-002, --1.610954e-002, -1.639179e-002, -1.667363e-002, -1.695181e-002, --1.722961e-002, -1.751060e-002, -1.779443e-002, -1.807916e-002, --1.836610e-002, -1.865573e-002, -1.895005e-002, -1.924631e-002, --1.953794e-002, -1.982801e-002, -2.011786e-002, -2.040675e-002, --2.069546e-002, -2.098438e-002, -2.127256e-002, -2.155927e-002, --2.184541e-002, -2.213186e-002, -2.241507e-002, -2.269731e-002, --2.297683e-002, -2.325343e-002, -2.352645e-002, -2.379552e-002, --2.406015e-002, -2.432153e-002, -2.458049e-002, -2.483710e-002, --2.509053e-002, -2.534055e-002, -2.558961e-002, -2.583631e-002, --2.608061e-002, -2.632558e-002, -2.656768e-002, -2.680694e-002, --2.704260e-002, -2.727655e-002, -2.750937e-002, -2.774004e-002, --2.797067e-002, -2.820135e-002, -2.843305e-002, -2.866541e-002, --2.889548e-002, -2.912432e-002, -2.935349e-002, -2.958233e-002, --2.981045e-002, -3.003581e-002, -3.025792e-002, -3.047710e-002, --3.069430e-002, -3.090894e-002, -3.112030e-002, -3.132802e-002, --3.152953e-002, -3.172998e-002, -3.192716e-002, -3.212286e-002, --3.231720e-002, -3.250905e-002, -3.269861e-002, -3.288566e-002, --3.307003e-002, -3.325347e-002, -3.343313e-002, -3.361087e-002, --3.378396e-002, -3.395387e-002, -3.412284e-002, -3.429113e-002, --3.445767e-002, -3.462374e-002, -3.479154e-002, -3.495918e-002, --3.512264e-002, -3.528609e-002, -3.544544e-002, -3.560309e-002, --3.575832e-002, -3.591354e-002, -3.606808e-002, -3.622087e-002, --3.637071e-002, -3.651832e-002, -3.665980e-002, -3.679747e-002, --3.693382e-002, -3.706887e-002, -3.720216e-002, -3.733594e-002, --3.747021e-002, -3.760209e-002, -3.773188e-002, -3.786154e-002, --3.798599e-002, -3.810754e-002, -3.822909e-002, -3.835180e-002, --3.847460e-002, -3.859646e-002, -3.871935e-002, -3.884211e-002, --3.896610e-002, -3.909227e-002, -3.921192e-002, -3.932457e-002, --3.943722e-002, -3.955001e-002, -3.966218e-002, -3.977233e-002, --3.988096e-002, -3.998759e-002, -4.009208e-002, -4.019456e-002, --4.029081e-002, -4.037704e-002, -4.045696e-002, -4.052301e-002, --4.058132e-002, -4.063269e-002, -4.067976e-002, -4.072108e-002, --4.075773e-002, -4.079054e-002, -4.081888e-002, -4.083108e-002, --4.083847e-002, -4.084045e-002, -4.083695e-002, -4.082632e-002, --4.080907e-002, -4.078653e-002, -4.075970e-002, -4.073380e-002, --4.070558e-002, -4.066605e-002, -4.062168e-002, -4.057635e-002, --4.053042e-002, -4.048439e-002, -4.043798e-002, -4.038752e-002, --4.033951e-002, -4.029268e-002, -4.024423e-002, -4.018799e-002, --4.012350e-002, -4.005710e-002, -3.998893e-002, -3.991869e-002, --3.984632e-002, -3.977418e-002, -3.970271e-002, -3.962869e-002, --3.955072e-002, -3.946492e-002, -3.936503e-002, -3.926086e-002, --3.915602e-002, -3.904906e-002, -3.894186e-002, -3.883076e-002, --3.871626e-002, -3.860040e-002, -3.848227e-002, -3.835984e-002, --3.821386e-002, -3.806576e-002, -3.791635e-002, -3.776568e-002, --3.761430e-002, -3.745988e-002, -3.730235e-002, -3.714267e-002, --3.698171e-002, -3.681943e-002, -3.663804e-002, -3.645160e-002, --3.626004e-002, -3.606420e-002, -3.586515e-002, -3.566253e-002, --3.545444e-002, -3.524196e-002, -3.502434e-002, -3.479720e-002, --3.454854e-002, -3.428425e-002, -3.401370e-002, -3.373672e-002, --3.345306e-002, -3.315869e-002, -3.285711e-002, -3.254918e-002, --3.223500e-002, -3.191521e-002, -3.158003e-002, -3.122345e-002, --3.086254e-002, -3.049347e-002, -3.012136e-002, -2.974533e-002, --2.936611e-002, -2.898553e-002, -2.859750e-002, -2.820706e-002, --2.781376e-002, -2.739560e-002, -2.697754e-002, -2.656104e-002, --2.614437e-002, -2.572719e-002, -2.530948e-002, -2.488978e-002, --2.446722e-002, -2.404288e-002, -2.361898e-002, -2.315664e-002, --2.268684e-002, -2.221551e-002, -2.174211e-002, -2.126660e-002, --2.078944e-002, -2.031042e-002, -1.983102e-002, -1.934786e-002, --1.886225e-002, -1.834936e-002, -1.782041e-002, -1.728978e-002, --1.675420e-002, -1.620606e-002, -1.565660e-002, -1.510645e-002, --1.455489e-002, -1.400285e-002, -1.344636e-002, -1.287052e-002, --1.227259e-002, -1.167410e-002, -1.107524e-002, -1.047727e-002, --9.877835e-003, -9.273231e-003, -8.655532e-003, -8.031998e-003, --7.408098e-003, -6.768808e-003, -6.094122e-003, -5.420056e-003, --4.746569e-003, -4.072105e-003, -3.396666e-003, -2.720035e-003, --2.041980e-003, -1.362527e-003, -6.818454e-004, -8.441329e-018 -}, -{ -0.000000e+000, -9.303034e-002, -1.043270e-001, -1.089305e-001, --1.112771e-001, -1.122107e-001, -1.115387e-001, -1.098958e-001, --1.077653e-001, -1.051520e-001, -1.020536e-001, -9.863098e-002, --9.495198e-002, -9.115456e-002, -8.731258e-002, -8.346059e-002, --7.950756e-002, -7.544519e-002, -7.124775e-002, -6.703300e-002, --6.278051e-002, -5.854203e-002, -5.428164e-002, -5.000572e-002, --4.573679e-002, -4.148641e-002, -3.723481e-002, -3.300679e-002, --2.877581e-002, -2.457002e-002, -2.041787e-002, -1.635034e-002, --1.237288e-002, -8.480525e-003, -4.738316e-003, -1.119081e-003, -2.362968e-003, 5.698824e-003, 8.898447e-003, 1.196393e-002, -1.487221e-002, 1.762636e-002, 2.027874e-002, 2.275033e-002, -2.510044e-002, 2.731678e-002, 2.936266e-002, 3.128832e-002, -3.306548e-002, 3.472033e-002, 3.624400e-002, 3.759164e-002, -3.879899e-002, 3.989232e-002, 4.084855e-002, 4.171747e-002, -4.254545e-002, 4.328453e-002, 4.397328e-002, 4.464648e-002, -4.525405e-002, 4.586957e-002, 4.648785e-002, 4.703341e-002, -4.750380e-002, 4.792682e-002, 4.829667e-002, 4.859971e-002, -4.886785e-002, 4.908528e-002, 4.920737e-002, 4.928087e-002, -4.932687e-002, 4.932539e-002, 4.923916e-002, 4.911107e-002, -4.893566e-002, 4.870492e-002, 4.839777e-002, 4.805972e-002, -4.765583e-002, 4.716266e-002, 4.660307e-002, 4.600542e-002, -4.529323e-002, 4.451696e-002, 4.369199e-002, 4.277729e-002, -4.177616e-002, 4.073652e-002, 3.962325e-002, 3.847811e-002, -3.722188e-002, 3.592511e-002, 3.456954e-002, 3.314710e-002, -3.167329e-002, 3.015168e-002, 2.855270e-002, 2.692613e-002, -2.527488e-002, 2.356581e-002, 2.184884e-002, 2.009119e-002, -1.831962e-002, 1.652453e-002, 1.469347e-002, 1.285258e-002, -1.099660e-002, 9.148225e-003, 7.268883e-003, 5.408934e-003, -3.540035e-003, 1.693736e-003, -1.236256e-004, -1.905663e-003, --3.687461e-003, -5.447510e-003, -7.154115e-003, -8.863895e-003, --1.051590e-002, -1.215173e-002, -1.375109e-002, -1.530053e-002, --1.681718e-002, -1.828086e-002, -1.970373e-002, -2.109478e-002, --2.244971e-002, -2.377175e-002, -2.506424e-002, -2.632043e-002, --2.752982e-002, -2.868364e-002, -2.978081e-002, -3.081732e-002, --3.181841e-002, -3.279818e-002, -3.371280e-002, -3.459856e-002, --3.546493e-002, -3.628787e-002, -3.703949e-002, -3.776670e-002, --3.846436e-002, -3.912116e-002, -3.975772e-002, -4.038634e-002, --4.098569e-002, -4.153772e-002, -4.205998e-002, -4.257041e-002, --4.306705e-002, -4.355096e-002, -4.401686e-002, -4.447502e-002, --4.487894e-002, -4.525036e-002, -4.562979e-002, -4.598490e-002, --4.634284e-002, -4.668134e-002, -4.696885e-002, -4.723291e-002, --4.749072e-002, -4.774066e-002, -4.796676e-002, -4.814433e-002, --4.829204e-002, -4.844392e-002, -4.857639e-002, -4.868928e-002, --4.873343e-002, -4.876303e-002, -4.878049e-002, -4.878612e-002, --4.870593e-002, -4.858596e-002, -4.845367e-002, -4.831608e-002, --4.807066e-002, -4.780161e-002, -4.750407e-002, -4.713799e-002, --4.671949e-002, -4.627722e-002, -4.575552e-002, -4.518642e-002, --4.459805e-002, -4.389375e-002, -4.316171e-002, -4.236791e-002, --4.150750e-002, -4.061161e-002, -3.961851e-002, -3.860121e-002, --3.749940e-002, -3.638053e-002, -3.518935e-002, -3.397700e-002, --3.272644e-002, -3.146315e-002, -3.017875e-002, -2.888053e-002, --2.757329e-002, -2.625708e-002, -2.496456e-002, -2.366403e-002, --2.238617e-002, -2.110061e-002, -1.985026e-002, -1.859525e-002, --1.739433e-002, -1.618445e-002, -1.500915e-002, -1.384491e-002, --1.269358e-002, -1.158436e-002, -1.048297e-002, -9.424129e-003, --8.367092e-003, -7.325523e-003, -6.320015e-003, -5.322653e-003, --4.352684e-003, -3.392985e-003, -2.433504e-003, -1.502217e-003, --5.995891e-004, 2.893460e-004, 1.154213e-003, 2.014878e-003, -2.881001e-003, 3.732345e-003, 4.538165e-003, 5.336501e-003, -6.127824e-003, 6.885954e-003, 7.658883e-003, 8.429773e-003, -9.159087e-003, 9.879434e-003, 1.058440e-002, 1.129046e-002, -1.198059e-002, 1.265760e-002, 1.330774e-002, 1.395927e-002, -1.457827e-002, 1.518631e-002, 1.579176e-002, 1.640320e-002, -1.697146e-002, 1.753342e-002, 1.808937e-002, 1.864566e-002, -1.916627e-002, 1.968177e-002, 2.019282e-002, 2.068944e-002, -2.116874e-002, 2.164423e-002, 2.210696e-002, 2.255649e-002, -2.300955e-002, 2.343174e-002, 2.384105e-002, 2.424369e-002, -2.463825e-002, 2.501927e-002, 2.537309e-002, 2.571329e-002, -2.603680e-002, 2.634311e-002, 2.663341e-002, 2.691613e-002, -2.718778e-002, 2.744777e-002, 2.768991e-002, 2.789870e-002, -2.807986e-002, 2.825501e-002, 2.842605e-002, 2.860068e-002, -2.875888e-002, 2.889413e-002, 2.901452e-002, 2.912205e-002, -2.921247e-002, 2.929385e-002, 2.937240e-002, 2.945516e-002, -2.953591e-002, 2.959643e-002, 2.964768e-002, 2.967629e-002, -2.969223e-002, 2.971112e-002, 2.973641e-002, 2.976402e-002, -2.978845e-002, 2.980206e-002, 2.981243e-002, 2.980830e-002, -2.980673e-002, 2.979774e-002, 2.977557e-002, 2.976568e-002, -2.975920e-002, 2.975318e-002, 2.975866e-002, 2.974631e-002, -2.972254e-002, 2.970313e-002, 2.968283e-002, 2.965906e-002, -2.963541e-002, 2.961749e-002, 2.961489e-002, 2.961390e-002, -2.959442e-002, 2.956762e-002, 2.954296e-002, 2.951549e-002, -2.948730e-002, 2.946392e-002, 2.943189e-002, 2.940184e-002, -2.936614e-002, 2.931923e-002, 2.926116e-002, 2.919633e-002, -2.914288e-002, 2.909262e-002, 2.904035e-002, 2.898404e-002, -2.892859e-002, 2.886306e-002, 2.879542e-002, 2.872363e-002, -2.865928e-002, 2.859319e-002, 2.852168e-002, 2.844157e-002, -2.836134e-002, 2.828691e-002, 2.821710e-002, 2.814341e-002, -2.805664e-002, 2.795905e-002, 2.785542e-002, 2.774758e-002, -2.763816e-002, 2.753532e-002, 2.744040e-002, 2.733492e-002, -2.724073e-002, 2.714756e-002, 2.705893e-002, 2.696322e-002, -2.686550e-002, 2.675897e-002, 2.663641e-002, 2.651716e-002, -2.640664e-002, 2.630038e-002, 2.619243e-002, 2.607988e-002, -2.597174e-002, 2.585216e-002, 2.573069e-002, 2.559872e-002, -2.546299e-002, 2.532555e-002, 2.518761e-002, 2.504809e-002, -2.489045e-002, 2.472366e-002, 2.455824e-002, 2.439427e-002, -2.422145e-002, 2.405232e-002, 2.388095e-002, 2.369445e-002, -2.349408e-002, 2.329504e-002, 2.308756e-002, 2.287485e-002, -2.265870e-002, 2.243203e-002, 2.219888e-002, 2.196137e-002, -2.171924e-002, 2.147157e-002, 2.122252e-002, 2.097307e-002, -2.072168e-002, 2.046219e-002, 2.020029e-002, 1.992333e-002, -1.963633e-002, 1.934764e-002, 1.905203e-002, 1.875611e-002, -1.845532e-002, 1.815623e-002, 1.785154e-002, 1.754728e-002, -1.725372e-002, 1.696278e-002, 1.666208e-002, 1.636052e-002, -1.605885e-002, 1.575126e-002, 1.543698e-002, 1.511962e-002, -1.479998e-002, 1.448403e-002, 1.417573e-002, 1.387004e-002, -1.355709e-002, 1.324904e-002, 1.295061e-002, 1.265536e-002, -1.235077e-002, 1.204275e-002, 1.173761e-002, 1.143932e-002, -1.114628e-002, 1.084266e-002, 1.053770e-002, 1.023308e-002, -9.930177e-003, 9.626567e-003, 9.322103e-003, 9.014777e-003, -8.704896e-003, 8.396409e-003, 8.091276e-003, 7.785713e-003, -7.486422e-003, 7.185851e-003, 6.881343e-003, 6.565880e-003, -6.247711e-003, 5.930411e-003, 5.602225e-003, 5.270708e-003, -4.935523e-003, 4.597738e-003, 4.252543e-003, 3.908867e-003, -3.560984e-003, 3.202739e-003, 2.842120e-003, 2.473756e-003, -2.107328e-003, 1.740755e-003, 1.375019e-003, 1.013677e-003, -6.468577e-004, 2.801418e-004, -9.032403e-005, -4.715020e-004, --8.635331e-004, -1.265031e-003, -1.663909e-003, -2.054582e-003, --2.436957e-003, -2.817296e-003, -3.193600e-003, -3.567967e-003, --3.939406e-003, -4.309288e-003, -4.694035e-003, -5.079178e-003, --5.461835e-003, -5.836651e-003, -6.207453e-003, -6.577243e-003, --6.946944e-003, -7.310163e-003, -7.671361e-003, -8.024875e-003, --8.373197e-003, -8.732091e-003, -9.092484e-003, -9.449519e-003, --9.793405e-003, -1.012953e-002, -1.046076e-002, -1.078905e-002, --1.111630e-002, -1.143436e-002, -1.175081e-002, -1.206400e-002, --1.237265e-002, -1.268433e-002, -1.299383e-002, -1.329012e-002, --1.358470e-002, -1.387121e-002, -1.415897e-002, -1.444438e-002, --1.471914e-002, -1.498634e-002, -1.525164e-002, -1.551813e-002, --1.578291e-002, -1.603966e-002, -1.629817e-002, -1.655223e-002, --1.681210e-002, -1.707526e-002, -1.734172e-002, -1.760486e-002, --1.786187e-002, -1.811735e-002, -1.837155e-002, -1.862751e-002, --1.888750e-002, -1.913764e-002, -1.937964e-002, -1.962844e-002, --1.988251e-002, -2.014016e-002, -2.039957e-002, -2.064958e-002, --2.090213e-002, -2.115651e-002, -2.141170e-002, -2.166483e-002, --2.191784e-002, -2.216633e-002, -2.240626e-002, -2.263723e-002, --2.286913e-002, -2.310855e-002, -2.335650e-002, -2.359114e-002, --2.381785e-002, -2.404510e-002, -2.427410e-002, -2.450184e-002, --2.473072e-002, -2.495013e-002, -2.516223e-002, -2.537511e-002, --2.558991e-002, -2.580201e-002, -2.601053e-002, -2.621077e-002, --2.640861e-002, -2.660771e-002, -2.679730e-002, -2.698717e-002, --2.717737e-002, -2.736315e-002, -2.753237e-002, -2.769801e-002, --2.786297e-002, -2.802723e-002, -2.818645e-002, -2.834224e-002, --2.849099e-002, -2.864086e-002, -2.879140e-002, -2.893860e-002, --2.908796e-002, -2.924236e-002, -2.939318e-002, -2.954162e-002, --2.969246e-002, -2.983721e-002, -2.997453e-002, -3.010417e-002, --3.023105e-002, -3.035269e-002, -3.046869e-002, -3.058286e-002, --3.069480e-002, -3.080807e-002, -3.092121e-002, -3.102877e-002, --3.112530e-002, -3.122324e-002, -3.131739e-002, -3.140554e-002, --3.148672e-002, -3.156605e-002, -3.163701e-002, -3.170708e-002, --3.176903e-002, -3.182474e-002, -3.187429e-002, -3.192084e-002, --3.196329e-002, -3.199648e-002, -3.201448e-002, -3.202499e-002, --3.202130e-002, -3.201297e-002, -3.200232e-002, -3.199325e-002, --3.198028e-002, -3.196750e-002, -3.195689e-002, -3.194324e-002, --3.192755e-002, -3.191414e-002, -3.189178e-002, -3.185126e-002, --3.181082e-002, -3.176886e-002, -3.172515e-002, -3.168396e-002, --3.164452e-002, -3.160541e-002, -3.156385e-002, -3.152518e-002, --3.148691e-002, -3.144838e-002, -3.141128e-002, -3.137727e-002, --3.134623e-002, -3.130302e-002, -3.125093e-002, -3.119797e-002, --3.114230e-002, -3.108608e-002, -3.102881e-002, -3.097350e-002, --3.091716e-002, -3.086050e-002, -3.079957e-002, -3.073195e-002, --3.066171e-002, -3.058957e-002, -3.051443e-002, -3.043771e-002, --3.035529e-002, -3.026525e-002, -3.017571e-002, -3.009037e-002, --3.000084e-002, -2.989992e-002, -2.978251e-002, -2.965803e-002, --2.952848e-002, -2.939706e-002, -2.926392e-002, -2.912811e-002, --2.899045e-002, -2.885192e-002, -2.870888e-002, -2.856516e-002, --2.841528e-002, -2.825869e-002, -2.809769e-002, -2.792968e-002, --2.775836e-002, -2.758246e-002, -2.740393e-002, -2.722410e-002, --2.704723e-002, -2.687288e-002, -2.668815e-002, -2.649755e-002, --2.630461e-002, -2.610554e-002, -2.590675e-002, -2.570347e-002, --2.548978e-002, -2.526934e-002, -2.504801e-002, -2.482454e-002, --2.458784e-002, -2.435185e-002, -2.411539e-002, -2.387697e-002, --2.364078e-002, -2.340161e-002, -2.316548e-002, -2.293494e-002, --2.270383e-002, -2.247407e-002, -2.223858e-002, -2.200098e-002, --2.177069e-002, -2.154047e-002, -2.131269e-002, -2.108827e-002, --2.086330e-002, -2.064155e-002, -2.041804e-002, -2.019518e-002, --1.996611e-002, -1.972738e-002, -1.948897e-002, -1.925106e-002, --1.901025e-002, -1.876611e-002, -1.852013e-002, -1.827487e-002, --1.802381e-002, -1.777121e-002, -1.752035e-002, -1.726326e-002, --1.699705e-002, -1.672726e-002, -1.645387e-002, -1.618160e-002, --1.591490e-002, -1.565007e-002, -1.538311e-002, -1.511497e-002, --1.484433e-002, -1.456734e-002, -1.428777e-002, -1.400771e-002, --1.372098e-002, -1.343347e-002, -1.314654e-002, -1.285959e-002, --1.256813e-002, -1.227279e-002, -1.197082e-002, -1.166110e-002, --1.134470e-002, -1.102884e-002, -1.071234e-002, -1.039400e-002, --1.006862e-002, -9.738795e-003, -9.403636e-003, -9.069431e-003, --8.730534e-003, -8.388891e-003, -8.043898e-003, -7.701344e-003, --7.356372e-003, -7.013856e-003, -6.672303e-003, -6.330828e-003, --5.985169e-003, -5.638881e-003, -5.292245e-003, -4.938486e-003, --4.579355e-003, -4.217080e-003, -3.849978e-003, -3.483144e-003, --3.117564e-003, -2.752181e-003, -2.388346e-003, -2.022805e-003, --1.650467e-003, -1.269718e-003, -8.767821e-004, -4.776649e-004, --7.623562e-005, 3.258055e-004, 7.280977e-004, 1.135383e-003, -1.547792e-003, 1.960457e-003, 2.375741e-003, 2.790709e-003, -3.212097e-003, 3.639058e-003, 4.070175e-003, 4.503332e-003, -4.939710e-003, 5.376668e-003, 5.813535e-003, 6.250691e-003, -6.688689e-003, 7.126667e-003, 7.570752e-003, 8.013479e-003, -8.453302e-003, 8.890125e-003, 9.323666e-003, 9.755337e-003, -1.018191e-002, 1.060611e-002, 1.102831e-002, 1.144729e-002, -1.186491e-002, 1.228139e-002, 1.269406e-002, 1.309953e-002, -1.350055e-002, 1.389704e-002, 1.428958e-002, 1.467723e-002, -1.506192e-002, 1.543798e-002, 1.581033e-002, 1.617856e-002, -1.654549e-002, 1.691711e-002, 1.729144e-002, 1.766769e-002, -1.804705e-002, 1.842600e-002, 1.880228e-002, 1.917798e-002, -1.955398e-002, 1.992866e-002, 2.030387e-002, 2.068121e-002, -2.105847e-002, 2.143718e-002, 2.181898e-002, 2.219875e-002, -2.257561e-002, 2.294921e-002, 2.331925e-002, 2.368520e-002, -2.405139e-002, 2.441641e-002, 2.478315e-002, 2.514632e-002, -2.550537e-002, 2.585931e-002, 2.620706e-002, 2.655227e-002, -2.689664e-002, 2.723573e-002, 2.757378e-002, 2.791125e-002, -2.824600e-002, 2.857588e-002, 2.890035e-002, 2.922019e-002, -2.953738e-002, 2.985432e-002, 3.017367e-002, 3.049323e-002, -3.080744e-002, 3.112068e-002, 3.143420e-002, 3.174726e-002, -3.206202e-002, 3.237597e-002, 3.268740e-002, 3.299781e-002, -3.330954e-002, 3.362071e-002, 3.392253e-002, 3.422280e-002, -3.452093e-002, 3.481696e-002, 3.511222e-002, 3.541007e-002, -3.570758e-002, 3.601007e-002, 3.631795e-002, 3.662938e-002, -3.693097e-002, 3.722511e-002, 3.751877e-002, 3.780648e-002, -3.809330e-002, 3.837956e-002, 3.866794e-002, 3.895995e-002, -3.925160e-002, 3.954178e-002, 3.982581e-002, 4.009569e-002, -4.036354e-002, 4.062970e-002, 4.089329e-002, 4.115284e-002, -4.141196e-002, 4.167269e-002, 4.192832e-002, 4.217854e-002, -4.241650e-002, 4.263374e-002, 4.284403e-002, 4.304530e-002, -4.324117e-002, 4.343132e-002, 4.361932e-002, 4.380622e-002, -4.399112e-002, 4.416706e-002, 4.433391e-002, 4.447581e-002, -4.461209e-002, 4.474297e-002, 4.486815e-002, 4.498548e-002, -4.510444e-002, 4.521904e-002, 4.532946e-002, 4.543084e-002, -4.552715e-002, 4.560322e-002, 4.567231e-002, 4.574352e-002, -4.581284e-002, 4.588116e-002, 4.595273e-002, 4.602118e-002, -4.608709e-002, 4.615217e-002, 4.621767e-002, 4.626827e-002, -4.630312e-002, 4.633656e-002, 4.636755e-002, 4.639629e-002, -4.642355e-002, 4.644679e-002, 4.646587e-002, 4.647318e-002, -4.647298e-002, 4.645929e-002, 4.642001e-002, 4.637724e-002, -4.632851e-002, 4.627617e-002, 4.621972e-002, 4.616037e-002, -4.609914e-002, 4.603108e-002, 4.595954e-002, 4.588228e-002, -4.576596e-002, 4.564738e-002, 4.552689e-002, 4.540092e-002, -4.527560e-002, 4.515920e-002, 4.503993e-002, 4.491476e-002, -4.478820e-002, 4.466109e-002, 4.449835e-002, 4.432483e-002, -4.414532e-002, 4.395967e-002, 4.377069e-002, 4.358043e-002, -4.338869e-002, 4.319033e-002, 4.298494e-002, 4.277253e-002, -4.252574e-002, 4.225473e-002, 4.197675e-002, 4.169117e-002, -4.139794e-002, 4.109385e-002, 4.078462e-002, 4.046842e-002, -4.014334e-002, 3.981247e-002, 3.945714e-002, 3.906555e-002, -3.866814e-002, 3.826758e-002, 3.786155e-002, 3.744989e-002, -3.703492e-002, 3.661620e-002, 3.618247e-002, 3.574338e-002, -3.529116e-002, 3.478797e-002, 3.428280e-002, 3.377420e-002, -3.326453e-002, 3.275462e-002, 3.224466e-002, 3.173934e-002, -3.123526e-002, 3.072762e-002, 3.021816e-002, 2.964021e-002, -2.905079e-002, 2.846012e-002, 2.786797e-002, 2.727414e-002, -2.667884e-002, 2.608199e-002, 2.548819e-002, 2.488706e-002, -2.428204e-002, 2.362702e-002, 2.295218e-002, 2.227662e-002, -2.159595e-002, 2.090188e-002, 2.020631e-002, 1.950435e-002, -1.880100e-002, 1.810477e-002, 1.740962e-002, 1.667748e-002, -1.590205e-002, 1.512578e-002, 1.434887e-002, 1.356570e-002, -1.277875e-002, 1.199463e-002, 1.119623e-002, 1.039175e-002, -9.595671e-003, 8.791325e-003, 7.919921e-003, 7.045715e-003, -6.167517e-003, 5.288726e-003, 4.409267e-003, 3.529053e-003, -2.647947e-003, 1.765928e-003, 8.832766e-004, 4.011034e-018 -}, -{ -0.000000e+000, -4.942774e-002, -5.620194e-002, -6.033508e-002, --5.956883e-002, -5.694401e-002, -5.641818e-002, -5.612780e-002, --5.565626e-002, -5.512035e-002, -5.483838e-002, -5.472761e-002, --5.432260e-002, -5.318601e-002, -5.067231e-002, -4.704666e-002, --4.284764e-002, -3.838921e-002, -3.397954e-002, -2.923316e-002, --2.429448e-002, -1.937010e-002, -1.465969e-002, -1.029320e-002, --6.031593e-003, -1.927065e-003, 1.963398e-003, 5.518040e-003, -8.619470e-003, 1.143255e-002, 1.404786e-002, 1.645029e-002, -1.860436e-002, 2.056524e-002, 2.248132e-002, 2.437502e-002, -2.627858e-002, 2.807497e-002, 2.982601e-002, 3.151945e-002, -3.316860e-002, 3.468624e-002, 3.622281e-002, 3.772091e-002, -3.912694e-002, 4.043210e-002, 4.170604e-002, 4.286968e-002, -4.383504e-002, 4.481109e-002, 4.576937e-002, 4.675407e-002, -4.790732e-002, 4.898121e-002, 5.000870e-002, 5.093195e-002, -5.167297e-002, 5.217184e-002, 5.227566e-002, 5.205385e-002, -5.131330e-002, 5.008573e-002, 4.855362e-002, 4.685183e-002, -4.493919e-002, 4.285717e-002, 4.068231e-002, 3.840300e-002, -3.606937e-002, 3.367056e-002, 3.120329e-002, 2.870178e-002, -2.623596e-002, 2.376192e-002, 2.130460e-002, 1.887164e-002, -1.651063e-002, 1.418260e-002, 1.188893e-002, 9.681037e-003, -7.517575e-003, 5.432591e-003, 3.391561e-003, 1.538631e-003, --2.844528e-004, -2.015985e-003, -3.620587e-003, -5.208189e-003, --6.690715e-003, -8.090300e-003, -9.482404e-003, -1.076560e-002, --1.206911e-002, -1.334254e-002, -1.455664e-002, -1.580701e-002, --1.704343e-002, -1.825500e-002, -1.951005e-002, -2.078845e-002, --2.204449e-002, -2.325520e-002, -2.438905e-002, -2.545315e-002, --2.642558e-002, -2.733950e-002, -2.818972e-002, -2.892898e-002, --2.958445e-002, -3.017323e-002, -3.072878e-002, -3.121372e-002, --3.166043e-002, -3.205790e-002, -3.240439e-002, -3.272266e-002, --3.304446e-002, -3.336613e-002, -3.367364e-002, -3.402521e-002, --3.438424e-002, -3.481048e-002, -3.524815e-002, -3.569441e-002, --3.614043e-002, -3.658523e-002, -3.702793e-002, -3.746327e-002, --3.790564e-002, -3.835358e-002, -3.879603e-002, -3.924533e-002, --3.966620e-002, -4.009375e-002, -4.051143e-002, -4.088426e-002, --4.125696e-002, -4.163597e-002, -4.201228e-002, -4.239476e-002, --4.277260e-002, -4.312579e-002, -4.338952e-002, -4.366793e-002, --4.396312e-002, -4.420515e-002, -4.442900e-002, -4.464002e-002, --4.482948e-002, -4.495943e-002, -4.503289e-002, -4.506701e-002, --4.506025e-002, -4.501793e-002, -4.494139e-002, -4.483739e-002, --4.463975e-002, -4.437333e-002, -4.408866e-002, -4.375725e-002, --4.338542e-002, -4.296531e-002, -4.241919e-002, -4.182143e-002, --4.117225e-002, -4.050738e-002, -3.977809e-002, -3.895029e-002, --3.804492e-002, -3.709414e-002, -3.609185e-002, -3.504401e-002, --3.386098e-002, -3.263345e-002, -3.139147e-002, -3.011820e-002, --2.872601e-002, -2.728554e-002, -2.583946e-002, -2.438591e-002, --2.279293e-002, -2.118350e-002, -1.958839e-002, -1.792443e-002, --1.623116e-002, -1.453449e-002, -1.277259e-002, -1.098264e-002, --9.226227e-003, -7.382607e-003, -5.560485e-003, -3.718502e-003, --1.842124e-003, -5.982263e-006, 1.884596e-003, 3.715761e-003, -5.609214e-003, 7.455636e-003, 9.339029e-003, 1.119537e-002, -1.304122e-002, 1.484337e-002, 1.661710e-002, 1.836591e-002, -2.006511e-002, 2.174788e-002, 2.335466e-002, 2.494083e-002, -2.643510e-002, 2.791140e-002, 2.929740e-002, 3.065722e-002, -3.191392e-002, 3.314456e-002, 3.430343e-002, 3.540971e-002, -3.646118e-002, 3.743421e-002, 3.839122e-002, 3.926222e-002, -4.011405e-002, 4.093749e-002, 4.168461e-002, 4.242333e-002, -4.308857e-002, 4.372123e-002, 4.433603e-002, 4.488468e-002, -4.539105e-002, 4.588555e-002, 4.632587e-002, 4.674220e-002, -4.714128e-002, 4.748918e-002, 4.778586e-002, 4.806970e-002, -4.833884e-002, 4.855142e-002, 4.875623e-002, 4.894397e-002, -4.907882e-002, 4.918094e-002, 4.926698e-002, 4.934852e-002, -4.937562e-002, 4.939142e-002, 4.939379e-002, 4.937365e-002, -4.930145e-002, 4.922329e-002, 4.913717e-002, 4.905406e-002, -4.891887e-002, 4.877922e-002, 4.864691e-002, 4.850716e-002, -4.832685e-002, 4.816567e-002, 4.799680e-002, 4.783320e-002, -4.765780e-002, 4.745879e-002, 4.725898e-002, 4.705580e-002, -4.684707e-002, 4.661899e-002, 4.639545e-002, 4.617675e-002, -4.595969e-002, 4.572458e-002, 4.547079e-002, 4.523614e-002, -4.501944e-002, 4.481294e-002, 4.460572e-002, 4.441004e-002, -4.421824e-002, 4.403754e-002, 4.386717e-002, 4.368081e-002, -4.349568e-002, 4.332300e-002, 4.315284e-002, 4.298348e-002, -4.280567e-002, 4.261786e-002, 4.241911e-002, 4.223286e-002, -4.205297e-002, 4.185197e-002, 4.164386e-002, 4.143250e-002, -4.122115e-002, 4.099980e-002, 4.077129e-002, 4.053252e-002, -4.028345e-002, 4.003343e-002, 3.976313e-002, 3.947468e-002, -3.916118e-002, 3.882683e-002, 3.847438e-002, 3.810547e-002, -3.772306e-002, 3.730540e-002, 3.685602e-002, 3.638464e-002, -3.589005e-002, 3.536914e-002, 3.482636e-002, 3.425109e-002, -3.364158e-002, 3.301847e-002, 3.234993e-002, 3.164629e-002, -3.092118e-002, 3.014186e-002, 2.933030e-002, 2.851492e-002, -2.767072e-002, 2.679345e-002, 2.590840e-002, 2.500243e-002, -2.404951e-002, 2.306575e-002, 2.209714e-002, 2.112626e-002, -2.015158e-002, 1.917142e-002, 1.818766e-002, 1.719707e-002, -1.618570e-002, 1.519512e-002, 1.419316e-002, 1.317726e-002, -1.215139e-002, 1.114372e-002, 1.012906e-002, 9.111430e-003, -8.106138e-003, 7.095371e-003, 6.057204e-003, 5.009886e-003, -3.978694e-003, 2.942915e-003, 1.905639e-003, 8.895909e-004, --1.320180e-004, -1.177145e-003, -2.230970e-003, -3.260533e-003, --4.289462e-003, -5.321955e-003, -6.345175e-003, -7.371445e-003, --8.383436e-003, -9.392233e-003, -1.039225e-002, -1.138916e-002, --1.238843e-002, -1.339274e-002, -1.437815e-002, -1.536229e-002, --1.633839e-002, -1.730141e-002, -1.823734e-002, -1.917128e-002, --2.011388e-002, -2.105184e-002, -2.196640e-002, -2.288619e-002, --2.380939e-002, -2.471618e-002, -2.559052e-002, -2.646637e-002, --2.735472e-002, -2.822763e-002, -2.907988e-002, -2.992485e-002, --3.076427e-002, -3.158522e-002, -3.238191e-002, -3.318139e-002, --3.397746e-002, -3.474015e-002, -3.547172e-002, -3.619741e-002, --3.691719e-002, -3.762212e-002, -3.828962e-002, -3.896476e-002, --3.963787e-002, -4.028500e-002, -4.090150e-002, -4.150252e-002, --4.209054e-002, -4.266695e-002, -4.320766e-002, -4.374175e-002, --4.427590e-002, -4.479413e-002, -4.526666e-002, -4.573208e-002, --4.618809e-002, -4.663705e-002, -4.705281e-002, -4.747072e-002, --4.787753e-002, -4.827656e-002, -4.865137e-002, -4.900735e-002, --4.935996e-002, -4.970447e-002, -5.003527e-002, -5.033950e-002, --5.064212e-002, -5.093999e-002, -5.122976e-002, -5.149338e-002, --5.174827e-002, -5.199781e-002, -5.223649e-002, -5.245069e-002, --5.267392e-002, -5.289036e-002, -5.308399e-002, -5.323626e-002, --5.335505e-002, -5.344816e-002, -5.351148e-002, -5.354967e-002, --5.354666e-002, -5.353814e-002, -5.351493e-002, -5.347344e-002, --5.342013e-002, -5.334387e-002, -5.325380e-002, -5.315332e-002, --5.302734e-002, -5.286562e-002, -5.268859e-002, -5.251739e-002, --5.232002e-002, -5.209273e-002, -5.184579e-002, -5.159129e-002, --5.132721e-002, -5.104986e-002, -5.074819e-002, -5.043375e-002, --5.010742e-002, -4.977912e-002, -4.944048e-002, -4.907736e-002, --4.869887e-002, -4.830468e-002, -4.790432e-002, -4.748158e-002, --4.702665e-002, -4.656120e-002, -4.608877e-002, -4.562646e-002, --4.514820e-002, -4.466099e-002, -4.417314e-002, -4.367368e-002, --4.316069e-002, -4.263195e-002, -4.210386e-002, -4.157405e-002, --4.103127e-002, -4.047939e-002, -3.993559e-002, -3.939004e-002, --3.883940e-002, -3.828306e-002, -3.773155e-002, -3.717403e-002, --3.661549e-002, -3.605951e-002, -3.551314e-002, -3.496430e-002, --3.440647e-002, -3.386595e-002, -3.333983e-002, -3.282090e-002, --3.229771e-002, -3.175423e-002, -3.122185e-002, -3.069928e-002, --3.018090e-002, -2.967027e-002, -2.916218e-002, -2.866019e-002, --2.816653e-002, -2.768821e-002, -2.722911e-002, -2.676853e-002, --2.631747e-002, -2.587422e-002, -2.543550e-002, -2.500765e-002, --2.458189e-002, -2.415671e-002, -2.374382e-002, -2.334658e-002, --2.295395e-002, -2.256492e-002, -2.220331e-002, -2.185116e-002, --2.150136e-002, -2.114806e-002, -2.077344e-002, -2.039209e-002, --2.001069e-002, -1.962643e-002, -1.924048e-002, -1.885211e-002, --1.846616e-002, -1.808152e-002, -1.768378e-002, -1.729128e-002, --1.690856e-002, -1.651797e-002, -1.611085e-002, -1.569417e-002, --1.526706e-002, -1.484520e-002, -1.442802e-002, -1.399961e-002, --1.356751e-002, -1.313351e-002, -1.269229e-002, -1.223855e-002, --1.177302e-002, -1.130890e-002, -1.084769e-002, -1.038641e-002, --9.920370e-003, -9.459815e-003, -8.997027e-003, -8.539077e-003, --8.080995e-003, -7.620812e-003, -7.154236e-003, -6.685203e-003, --6.201714e-003, -5.700616e-003, -5.192633e-003, -4.689251e-003, --4.188658e-003, -3.691479e-003, -3.183011e-003, -2.668322e-003, --2.151657e-003, -1.628537e-003, -1.086054e-003, -5.227901e-004, -4.565609e-005, 6.164596e-004, 1.178995e-003, 1.747960e-003, -2.329319e-003, 2.914850e-003, 3.498697e-003, 4.084484e-003, -4.669223e-003, 5.253026e-003, 5.845171e-003, 6.442419e-003, -7.038177e-003, 7.633871e-003, 8.226937e-003, 8.821689e-003, -9.420432e-003, 1.002058e-002, 1.061902e-002, 1.121747e-002, -1.181805e-002, 1.241627e-002, 1.301694e-002, 1.362216e-002, -1.421938e-002, 1.478960e-002, 1.535054e-002, 1.590619e-002, -1.645529e-002, 1.700278e-002, 1.754731e-002, 1.808484e-002, -1.861915e-002, 1.915513e-002, 1.970014e-002, 2.024049e-002, -2.077330e-002, 2.130200e-002, 2.180283e-002, 2.229387e-002, -2.279252e-002, 2.328979e-002, 2.378641e-002, 2.427753e-002, -2.475956e-002, 2.523740e-002, 2.572076e-002, 2.620044e-002, -2.668112e-002, 2.715737e-002, 2.760834e-002, 2.801831e-002, -2.842659e-002, 2.883425e-002, 2.923489e-002, 2.962492e-002, -3.000631e-002, 3.038162e-002, 3.075594e-002, 3.113703e-002, -3.151418e-002, 3.188319e-002, 3.224468e-002, 3.259722e-002, -3.294979e-002, 3.327500e-002, 3.356624e-002, 3.385194e-002, -3.413390e-002, 3.441647e-002, 3.469609e-002, 3.497764e-002, -3.525620e-002, 3.553041e-002, 3.580024e-002, 3.606073e-002, -3.631222e-002, 3.655632e-002, 3.679405e-002, 3.702437e-002, -3.724145e-002, 3.744400e-002, 3.763503e-002, 3.780758e-002, -3.797359e-002, 3.811815e-002, 3.824159e-002, 3.835526e-002, -3.845868e-002, 3.854784e-002, 3.861961e-002, 3.868151e-002, -3.872866e-002, 3.875724e-002, 3.877605e-002, 3.878833e-002, -3.879805e-002, 3.880025e-002, 3.879599e-002, 3.878468e-002, -3.876084e-002, 3.873342e-002, 3.870528e-002, 3.867295e-002, -3.863528e-002, 3.858880e-002, 3.853180e-002, 3.846744e-002, -3.839832e-002, 3.832864e-002, 3.826046e-002, 3.819760e-002, -3.810429e-002, 3.800899e-002, 3.791874e-002, 3.782892e-002, -3.772060e-002, 3.761236e-002, 3.750538e-002, 3.739747e-002, -3.728814e-002, 3.717441e-002, 3.705464e-002, 3.693168e-002, -3.681214e-002, 3.669692e-002, 3.656380e-002, 3.640993e-002, -3.624536e-002, 3.607313e-002, 3.589502e-002, 3.571964e-002, -3.554758e-002, 3.537479e-002, 3.520112e-002, 3.502661e-002, -3.484793e-002, 3.464983e-002, 3.444898e-002, 3.424778e-002, -3.404516e-002, 3.384146e-002, 3.364158e-002, 3.343987e-002, -3.323213e-002, 3.302314e-002, 3.281397e-002, 3.259222e-002, -3.236374e-002, 3.212910e-002, 3.189317e-002, 3.165666e-002, -3.141257e-002, 3.116749e-002, 3.092317e-002, 3.067598e-002, -3.042475e-002, 3.016504e-002, 2.989867e-002, 2.962749e-002, -2.935065e-002, 2.906138e-002, 2.876405e-002, 2.846271e-002, -2.816342e-002, 2.786495e-002, 2.757206e-002, 2.727151e-002, -2.696350e-002, 2.664963e-002, 2.633198e-002, 2.601260e-002, -2.569912e-002, 2.538470e-002, 2.507121e-002, 2.476143e-002, -2.445594e-002, 2.414133e-002, 2.382350e-002, 2.349838e-002, -2.317493e-002, 2.285167e-002, 2.253187e-002, 2.221263e-002, -2.189212e-002, 2.157099e-002, 2.124493e-002, 2.090486e-002, -2.055575e-002, 2.020317e-002, 1.984898e-002, 1.949519e-002, -1.913724e-002, 1.877502e-002, 1.840690e-002, 1.803390e-002, -1.765454e-002, 1.728018e-002, 1.689616e-002, 1.651421e-002, -1.613230e-002, 1.574839e-002, 1.536175e-002, 1.497380e-002, -1.458848e-002, 1.420760e-002, 1.382438e-002, 1.343259e-002, -1.303983e-002, 1.265022e-002, 1.227147e-002, 1.188726e-002, -1.150222e-002, 1.112043e-002, 1.073746e-002, 1.035444e-002, -9.974270e-003, 9.594954e-003, 9.200553e-003, 8.800720e-003, -8.400698e-003, 8.004350e-003, 7.608551e-003, 7.216144e-003, -6.821407e-003, 6.426434e-003, 6.029861e-003, 5.628536e-003, -5.233361e-003, 4.845306e-003, 4.463979e-003, 4.083287e-003, -3.705020e-003, 3.327617e-003, 2.951562e-003, 2.576235e-003, -2.205662e-003, 1.838341e-003, 1.474079e-003, 1.108195e-003, -7.421916e-004, 3.808521e-004, 2.287793e-005, -3.265042e-004, --6.716933e-004, -1.008671e-003, -1.340826e-003, -1.668361e-003, --1.995238e-003, -2.317955e-003, -2.635013e-003, -2.951551e-003, --3.266539e-003, -3.587078e-003, -3.920640e-003, -4.254221e-003, --4.585536e-003, -4.916072e-003, -5.249222e-003, -5.584866e-003, --5.923222e-003, -6.261374e-003, -6.602499e-003, -6.942125e-003, --7.277854e-003, -7.612200e-003, -7.946726e-003, -8.279381e-003, --8.611686e-003, -8.946173e-003, -9.271993e-003, -9.597305e-003, --9.926970e-003, -1.025736e-002, -1.058713e-002, -1.091496e-002, --1.123693e-002, -1.154777e-002, -1.185163e-002, -1.215326e-002, --1.244934e-002, -1.274713e-002, -1.304510e-002, -1.334134e-002, --1.363490e-002, -1.392559e-002, -1.421481e-002, -1.450545e-002, --1.479699e-002, -1.509105e-002, -1.538179e-002, -1.567328e-002, --1.596203e-002, -1.625159e-002, -1.654580e-002, -1.684388e-002, --1.714350e-002, -1.744390e-002, -1.774144e-002, -1.803897e-002, --1.832986e-002, -1.861655e-002, -1.890207e-002, -1.919060e-002, --1.947551e-002, -1.975853e-002, -2.003435e-002, -2.030911e-002, --2.058764e-002, -2.087049e-002, -2.114509e-002, -2.140793e-002, --2.166401e-002, -2.191575e-002, -2.216553e-002, -2.241441e-002, --2.266377e-002, -2.291306e-002, -2.316472e-002, -2.341229e-002, --2.365436e-002, -2.387535e-002, -2.409555e-002, -2.431321e-002, --2.452835e-002, -2.474030e-002, -2.494991e-002, -2.515961e-002, --2.536702e-002, -2.557086e-002, -2.576876e-002, -2.594413e-002, --2.611726e-002, -2.628871e-002, -2.645848e-002, -2.662294e-002, --2.678332e-002, -2.694499e-002, -2.710695e-002, -2.726485e-002, --2.742393e-002, -2.756317e-002, -2.769724e-002, -2.783294e-002, --2.796886e-002, -2.810566e-002, -2.824310e-002, -2.838025e-002, --2.851599e-002, -2.865608e-002, -2.879542e-002, -2.891674e-002, --2.902496e-002, -2.913312e-002, -2.924063e-002, -2.934805e-002, --2.945552e-002, -2.955898e-002, -2.965715e-002, -2.975098e-002, --2.984358e-002, -2.992523e-002, -2.998339e-002, -3.004271e-002, --3.009951e-002, -3.015406e-002, -3.019989e-002, -3.024388e-002, --3.028488e-002, -3.031982e-002, -3.035221e-002, -3.037962e-002, --3.036888e-002, -3.035598e-002, -3.033809e-002, -3.031826e-002, --3.029319e-002, -3.027401e-002, -3.026247e-002, -3.025223e-002, --3.024180e-002, -3.023034e-002, -3.018051e-002, -3.012447e-002, --3.006679e-002, -3.000334e-002, -2.993978e-002, -2.987663e-002, --2.981411e-002, -2.974799e-002, -2.967617e-002, -2.960237e-002, --2.949654e-002, -2.936942e-002, -2.923805e-002, -2.910164e-002, --2.896236e-002, -2.881608e-002, -2.866542e-002, -2.851460e-002, --2.835908e-002, -2.820026e-002, -2.801855e-002, -2.780135e-002, --2.757821e-002, -2.735127e-002, -2.711877e-002, -2.688338e-002, --2.664550e-002, -2.640613e-002, -2.616013e-002, -2.591142e-002, --2.565332e-002, -2.534148e-002, -2.501931e-002, -2.469332e-002, --2.436732e-002, -2.404210e-002, -2.371715e-002, -2.339278e-002, --2.307412e-002, -2.275437e-002, -2.243056e-002, -2.204322e-002, --2.164620e-002, -2.125142e-002, -2.085845e-002, -2.046642e-002, --2.007482e-002, -1.968302e-002, -1.928721e-002, -1.888776e-002, --1.848611e-002, -1.803246e-002, -1.755346e-002, -1.707420e-002, --1.659252e-002, -1.610423e-002, -1.561354e-002, -1.511842e-002, --1.462232e-002, -1.412584e-002, -1.362301e-002, -1.307952e-002, --1.248736e-002, -1.189278e-002, -1.129640e-002, -1.069338e-002, --1.008949e-002, -9.484904e-003, -8.873625e-003, -8.260552e-003, --7.650085e-003, -7.016932e-003, -6.315541e-003, -5.610504e-003, --4.902638e-003, -4.196229e-003, -3.491864e-003, -2.789194e-003, --2.088697e-003, -1.390051e-003, -6.939679e-004, 7.302615e-018 -}, -{ -0.000000e+000, 1.648656e-001, 1.665361e-001, 1.587156e-001, -1.482945e-001, 1.367121e-001, 1.248286e-001, 1.123901e-001, -1.003328e-001, 8.868965e-002, 7.747023e-002, 6.684091e-002, -5.697325e-002, 4.802379e-002, 3.948657e-002, 3.107607e-002, -2.273036e-002, 1.463456e-002, 7.107590e-003, 3.194369e-004, --5.674128e-003, -1.068986e-002, -1.490075e-002, -1.856333e-002, --2.140829e-002, -2.369741e-002, -2.582885e-002, -2.797068e-002, --3.036549e-002, -3.301093e-002, -3.583747e-002, -3.874029e-002, --4.151691e-002, -4.409961e-002, -4.654450e-002, -4.871264e-002, --5.055134e-002, -5.199464e-002, -5.342826e-002, -5.483972e-002, --5.610964e-002, -5.707460e-002, -5.788090e-002, -5.844096e-002, --5.862796e-002, -5.841032e-002, -5.776606e-002, -5.671255e-002, --5.530392e-002, -5.367291e-002, -5.185020e-002, -4.960074e-002, --4.698220e-002, -4.393180e-002, -4.064743e-002, -3.711994e-002, --3.339834e-002, -2.964691e-002, -2.581521e-002, -2.207445e-002, --1.846601e-002, -1.502998e-002, -1.155335e-002, -8.126644e-003, --4.756059e-003, -1.400728e-003, 1.962585e-003, 5.239039e-003, -8.424385e-003, 1.154155e-002, 1.452822e-002, 1.738345e-002, -2.007916e-002, 2.267727e-002, 2.507508e-002, 2.724398e-002, -2.918630e-002, 3.097799e-002, 3.251732e-002, 3.378478e-002, -3.479866e-002, 3.553151e-002, 3.607182e-002, 3.634344e-002, -3.642145e-002, 3.636087e-002, 3.614136e-002, 3.580433e-002, -3.526922e-002, 3.470799e-002, 3.413901e-002, 3.350797e-002, -3.284612e-002, 3.219442e-002, 3.155684e-002, 3.097272e-002, -3.048014e-002, 3.007404e-002, 2.974699e-002, 2.953383e-002, -2.933729e-002, 2.911774e-002, 2.896211e-002, 2.881185e-002, -2.870212e-002, 2.861064e-002, 2.853348e-002, 2.851805e-002, -2.856296e-002, 2.866926e-002, 2.866844e-002, 2.870975e-002, -2.878630e-002, 2.887089e-002, 2.890231e-002, 2.888916e-002, -2.880110e-002, 2.868867e-002, 2.852794e-002, 2.823901e-002, -2.785424e-002, 2.738615e-002, 2.681202e-002, 2.619428e-002, -2.553211e-002, 2.482031e-002, 2.403611e-002, 2.320999e-002, -2.231290e-002, 2.137543e-002, 2.035766e-002, 1.928418e-002, -1.815463e-002, 1.690966e-002, 1.558195e-002, 1.415801e-002, -1.263356e-002, 1.102963e-002, 9.399969e-003, 7.696888e-003, -5.979689e-003, 4.227131e-003, 2.499382e-003, 8.100498e-004, --9.187090e-004, -2.584860e-003, -4.211633e-003, -5.814482e-003, --7.312234e-003, -8.750723e-003, -1.008900e-002, -1.132133e-002, --1.245027e-002, -1.345079e-002, -1.439867e-002, -1.531712e-002, --1.616317e-002, -1.693821e-002, -1.768208e-002, -1.840368e-002, --1.909185e-002, -1.975477e-002, -2.033744e-002, -2.081399e-002, --2.126617e-002, -2.172850e-002, -2.213712e-002, -2.249120e-002, --2.279416e-002, -2.307447e-002, -2.332246e-002, -2.356600e-002, --2.376032e-002, -2.391474e-002, -2.406796e-002, -2.420042e-002, --2.427748e-002, -2.432112e-002, -2.436340e-002, -2.440945e-002, --2.438278e-002, -2.435546e-002, -2.431735e-002, -2.425829e-002, --2.414404e-002, -2.403202e-002, -2.386826e-002, -2.370082e-002, --2.356622e-002, -2.336001e-002, -2.315214e-002, -2.292051e-002, --2.268686e-002, -2.246260e-002, -2.220474e-002, -2.197391e-002, --2.173555e-002, -2.152616e-002, -2.130609e-002, -2.114136e-002, --2.099700e-002, -2.089786e-002, -2.081976e-002, -2.075867e-002, --2.072316e-002, -2.070624e-002, -2.071699e-002, -2.072785e-002, --2.076722e-002, -2.082479e-002, -2.093703e-002, -2.105672e-002, --2.120003e-002, -2.133434e-002, -2.150244e-002, -2.168458e-002, --2.189217e-002, -2.212635e-002, -2.237097e-002, -2.263425e-002, --2.289521e-002, -2.315157e-002, -2.341235e-002, -2.368894e-002, --2.397271e-002, -2.425522e-002, -2.452157e-002, -2.478456e-002, --2.505032e-002, -2.530129e-002, -2.555797e-002, -2.581819e-002, --2.603833e-002, -2.625279e-002, -2.647385e-002, -2.667508e-002, --2.686011e-002, -2.704261e-002, -2.719508e-002, -2.733054e-002, --2.745848e-002, -2.756610e-002, -2.763009e-002, -2.767220e-002, --2.769987e-002, -2.770107e-002, -2.767007e-002, -2.760205e-002, --2.750579e-002, -2.735505e-002, -2.716490e-002, -2.693854e-002, --2.668225e-002, -2.638386e-002, -2.607343e-002, -2.573004e-002, --2.535863e-002, -2.498160e-002, -2.456642e-002, -2.412605e-002, --2.367505e-002, -2.319377e-002, -2.268316e-002, -2.213112e-002, --2.155380e-002, -2.097018e-002, -2.036167e-002, -1.974150e-002, --1.909899e-002, -1.842746e-002, -1.772967e-002, -1.698337e-002, --1.619778e-002, -1.538115e-002, -1.454251e-002, -1.365695e-002, --1.273126e-002, -1.175674e-002, -1.074279e-002, -9.707857e-003, --8.649348e-003, -7.556388e-003, -6.432324e-003, -5.281657e-003, --4.109176e-003, -2.902809e-003, -1.669971e-003, -4.117346e-004, -8.621738e-004, 2.148278e-003, 3.458079e-003, 4.785930e-003, -6.133727e-003, 7.489635e-003, 8.865912e-003, 1.023105e-002, -1.158935e-002, 1.296441e-002, 1.434801e-002, 1.574195e-002, -1.713846e-002, 1.852877e-002, 1.991088e-002, 2.126416e-002, -2.261031e-002, 2.397137e-002, 2.531392e-002, 2.665901e-002, -2.798396e-002, 2.928033e-002, 3.054636e-002, 3.178414e-002, -3.300126e-002, 3.417977e-002, 3.534837e-002, 3.650872e-002, -3.762551e-002, 3.870045e-002, 3.976638e-002, 4.077133e-002, -4.173600e-002, 4.266960e-002, 4.355760e-002, 4.438432e-002, -4.514259e-002, 4.589849e-002, 4.662923e-002, 4.733018e-002, -4.801000e-002, 4.867431e-002, 4.931472e-002, 4.993573e-002, -5.052049e-002, 5.106986e-002, 5.160527e-002, 5.211652e-002, -5.260214e-002, 5.304062e-002, 5.346881e-002, 5.387821e-002, -5.420717e-002, 5.449126e-002, 5.474501e-002, 5.494970e-002, -5.511051e-002, 5.524536e-002, 5.535545e-002, 5.541000e-002, -5.541096e-002, 5.536532e-002, 5.530058e-002, 5.517370e-002, -5.502575e-002, 5.487469e-002, 5.469684e-002, 5.447889e-002, -5.422841e-002, 5.394265e-002, 5.359705e-002, 5.323359e-002, -5.286278e-002, 5.249040e-002, 5.208851e-002, 5.166377e-002, -5.124845e-002, 5.078865e-002, 5.028231e-002, 4.975251e-002, -4.920314e-002, 4.862747e-002, 4.804810e-002, 4.746467e-002, -4.687119e-002, 4.622861e-002, 4.555613e-002, 4.486221e-002, -4.416155e-002, 4.344962e-002, 4.270468e-002, 4.197747e-002, -4.125476e-002, 4.051170e-002, 3.976138e-002, 3.900479e-002, -3.822912e-002, 3.743441e-002, 3.663669e-002, 3.585483e-002, -3.509600e-002, 3.431744e-002, 3.353552e-002, 3.276933e-002, -3.200522e-002, 3.124753e-002, 3.048262e-002, 2.971426e-002, -2.897447e-002, 2.824838e-002, 2.750587e-002, 2.677799e-002, -2.606182e-002, 2.536254e-002, 2.464162e-002, 2.395793e-002, -2.330333e-002, 2.267578e-002, 2.203233e-002, 2.138238e-002, -2.074968e-002, 2.013979e-002, 1.952581e-002, 1.889132e-002, -1.823326e-002, 1.758423e-002, 1.692307e-002, 1.623585e-002, -1.551742e-002, 1.480768e-002, 1.409884e-002, 1.340131e-002, -1.271451e-002, 1.202882e-002, 1.135416e-002, 1.067201e-002, -9.968630e-003, 9.247445e-003, 8.509544e-003, 7.775959e-003, -7.038999e-003, 6.280407e-003, 5.523698e-003, 4.777245e-003, -4.030875e-003, 3.290009e-003, 2.543304e-003, 1.789783e-003, -1.011804e-003, 2.362518e-004, -5.346216e-004, -1.316201e-003, --2.105008e-003, -2.886694e-003, -3.658983e-003, -4.446426e-003, --5.240270e-003, -6.035550e-003, -6.841524e-003, -7.655121e-003, --8.459005e-003, -9.266256e-003, -1.009471e-002, -1.091878e-002, --1.173407e-002, -1.254942e-002, -1.335987e-002, -1.418019e-002, --1.497996e-002, -1.576961e-002, -1.657450e-002, -1.737380e-002, --1.818221e-002, -1.897833e-002, -1.976453e-002, -2.054819e-002, --2.133598e-002, -2.212494e-002, -2.290213e-002, -2.365877e-002, --2.439701e-002, -2.513541e-002, -2.589567e-002, -2.664362e-002, --2.738065e-002, -2.811278e-002, -2.886028e-002, -2.959162e-002, --3.031390e-002, -3.103428e-002, -3.175000e-002, -3.245801e-002, --3.316228e-002, -3.384967e-002, -3.453199e-002, -3.519807e-002, --3.586001e-002, -3.652391e-002, -3.718030e-002, -3.782117e-002, --3.843542e-002, -3.904701e-002, -3.965390e-002, -4.024772e-002, --4.082225e-002, -4.137917e-002, -4.192935e-002, -4.246910e-002, --4.300001e-002, -4.352846e-002, -4.404700e-002, -4.453667e-002, --4.500289e-002, -4.544391e-002, -4.587197e-002, -4.627895e-002, --4.666678e-002, -4.703594e-002, -4.739371e-002, -4.774283e-002, --4.808899e-002, -4.841911e-002, -4.874365e-002, -4.905338e-002, --4.935911e-002, -4.966440e-002, -4.996350e-002, -5.024957e-002, --5.050456e-002, -5.074863e-002, -5.098292e-002, -5.120698e-002, --5.142354e-002, -5.161209e-002, -5.178359e-002, -5.195647e-002, --5.212073e-002, -5.227669e-002, -5.241440e-002, -5.252967e-002, --5.264015e-002, -5.273358e-002, -5.281561e-002, -5.289321e-002, --5.296201e-002, -5.300858e-002, -5.303278e-002, -5.303784e-002, --5.302831e-002, -5.300271e-002, -5.296007e-002, -5.289239e-002, --5.280832e-002, -5.271210e-002, -5.260884e-002, -5.249797e-002, --5.238561e-002, -5.224753e-002, -5.208700e-002, -5.190544e-002, --5.170276e-002, -5.148356e-002, -5.125040e-002, -5.098638e-002, --5.070225e-002, -5.040520e-002, -5.007878e-002, -4.973669e-002, --4.937999e-002, -4.901314e-002, -4.860741e-002, -4.817192e-002, --4.771127e-002, -4.723127e-002, -4.673206e-002, -4.621924e-002, --4.568841e-002, -4.514000e-002, -4.457640e-002, -4.400028e-002, --4.341116e-002, -4.280731e-002, -4.219416e-002, -4.157232e-002, --4.092678e-002, -4.025705e-002, -3.956513e-002, -3.885764e-002, --3.815207e-002, -3.743736e-002, -3.671728e-002, -3.598237e-002, --3.524156e-002, -3.449909e-002, -3.376225e-002, -3.302115e-002, --3.225729e-002, -3.147250e-002, -3.067132e-002, -2.987300e-002, --2.909771e-002, -2.831407e-002, -2.751718e-002, -2.670992e-002, --2.589341e-002, -2.505947e-002, -2.422223e-002, -2.339326e-002, --2.255378e-002, -2.168102e-002, -2.083297e-002, -1.998217e-002, --1.911538e-002, -1.824876e-002, -1.738020e-002, -1.650432e-002, --1.561513e-002, -1.473073e-002, -1.385448e-002, -1.296939e-002, --1.208226e-002, -1.117867e-002, -1.029849e-002, -9.443680e-003, --8.587966e-003, -7.731475e-003, -6.874128e-003, -6.026714e-003, --5.182423e-003, -4.338335e-003, -3.494409e-003, -2.646398e-003, --1.786433e-003, -9.332632e-004, -8.711178e-005, 7.550747e-004, -1.603341e-003, 2.435620e-003, 3.211347e-003, 3.983251e-003, -4.751629e-003, 5.523783e-003, 6.307042e-003, 7.080800e-003, -7.847702e-003, 8.614441e-003, 9.387861e-003, 1.015561e-002, -1.090676e-002, 1.164601e-002, 1.238417e-002, 1.312293e-002, -1.386531e-002, 1.460412e-002, 1.533420e-002, 1.605149e-002, -1.676201e-002, 1.744608e-002, 1.809224e-002, 1.872866e-002, -1.935607e-002, 1.995618e-002, 2.049669e-002, 2.100994e-002, -2.151096e-002, 2.199572e-002, 2.247969e-002, 2.296158e-002, -2.344297e-002, 2.392009e-002, 2.438694e-002, 2.485870e-002, -2.531045e-002, 2.573474e-002, 2.616205e-002, 2.655987e-002, -2.693471e-002, 2.731010e-002, 2.769128e-002, 2.805704e-002, -2.841248e-002, 2.877477e-002, 2.913777e-002, 2.950030e-002, -2.977982e-002, 3.004592e-002, 3.031101e-002, 3.057765e-002, -3.084844e-002, 3.112298e-002, 3.139820e-002, 3.162949e-002, -3.185257e-002, 3.208182e-002, 3.230989e-002, 3.253178e-002, -3.275634e-002, 3.297829e-002, 3.313283e-002, 3.323217e-002, -3.332072e-002, 3.341181e-002, 3.349416e-002, 3.356566e-002, -3.363237e-002, 3.370015e-002, 3.377932e-002, 3.386042e-002, -3.394486e-002, 3.403215e-002, 3.406932e-002, 3.409846e-002, -3.413776e-002, 3.418040e-002, 3.422501e-002, 3.424525e-002, -3.422114e-002, 3.417634e-002, 3.413455e-002, 3.408325e-002, -3.401298e-002, 3.392511e-002, 3.383990e-002, 3.375620e-002, -3.367675e-002, 3.358883e-002, 3.349950e-002, 3.340714e-002, -3.331938e-002, 3.323020e-002, 3.314692e-002, 3.305986e-002, -3.297870e-002, 3.289542e-002, 3.274754e-002, 3.256955e-002, -3.239759e-002, 3.222514e-002, 3.206015e-002, 3.185798e-002, -3.163931e-002, 3.142029e-002, 3.120606e-002, 3.099147e-002, -3.078312e-002, 3.058375e-002, 3.039738e-002, 3.020910e-002, -3.002362e-002, 2.981420e-002, 2.960401e-002, 2.938910e-002, -2.917766e-002, 2.897555e-002, 2.877144e-002, 2.856065e-002, -2.835637e-002, 2.815078e-002, 2.793368e-002, 2.769597e-002, -2.746005e-002, 2.722942e-002, 2.699053e-002, 2.675156e-002, -2.650981e-002, 2.625312e-002, 2.597252e-002, 2.569708e-002, -2.540580e-002, 2.511809e-002, 2.479944e-002, 2.448592e-002, -2.417578e-002, 2.387068e-002, 2.356427e-002, 2.325824e-002, -2.295043e-002, 2.263333e-002, 2.232220e-002, 2.202341e-002, -2.173364e-002, 2.144986e-002, 2.116938e-002, 2.086964e-002, -2.057378e-002, 2.028044e-002, 1.998937e-002, 1.969730e-002, -1.940195e-002, 1.909499e-002, 1.877959e-002, 1.846487e-002, -1.815038e-002, 1.783068e-002, 1.750917e-002, 1.718671e-002, -1.686427e-002, 1.654390e-002, 1.623152e-002, 1.592384e-002, -1.561504e-002, 1.530114e-002, 1.498188e-002, 1.465636e-002, -1.433815e-002, 1.403352e-002, 1.373639e-002, 1.342216e-002, -1.309391e-002, 1.275781e-002, 1.242559e-002, 1.207810e-002, -1.173428e-002, 1.140391e-002, 1.108416e-002, 1.077798e-002, -1.048570e-002, 1.020012e-002, 9.921276e-003, 9.644519e-003, -9.369857e-003, 9.098682e-003, 8.832246e-003, 8.574583e-003, -8.325964e-003, 8.081838e-003, 7.824745e-003, 7.566522e-003, -7.304365e-003, 7.025837e-003, 6.736105e-003, 6.452535e-003, -6.173803e-003, 5.874006e-003, 5.565829e-003, 5.253627e-003, -4.936111e-003, 4.619931e-003, 4.308389e-003, 3.997063e-003, -3.688517e-003, 3.384718e-003, 3.085927e-003, 2.790003e-003, -2.490607e-003, 2.193412e-003, 1.900456e-003, 1.610615e-003, -1.322726e-003, 1.037343e-003, 7.594792e-004, 4.910713e-004, -2.356175e-004, -1.957448e-005, -2.714582e-004, -5.211445e-004, --7.739405e-004, -1.036034e-003, -1.301894e-003, -1.563699e-003, --1.819830e-003, -2.077159e-003, -2.332951e-003, -2.612326e-003, --2.895497e-003, -3.177904e-003, -3.472516e-003, -3.769982e-003, --4.063370e-003, -4.352682e-003, -4.642165e-003, -4.926231e-003, --5.204860e-003, -5.470016e-003, -5.730434e-003, -5.995383e-003, --6.259866e-003, -6.523060e-003, -6.781593e-003, -7.027385e-003, --7.271532e-003, -7.527287e-003, -7.778510e-003, -8.016796e-003, --8.255041e-003, -8.493239e-003, -8.731262e-003, -8.963758e-003, --9.185029e-003, -9.395365e-003, -9.600169e-003, -9.808688e-003, --1.001340e-002, -1.021733e-002, -1.041848e-002, -1.061300e-002, --1.080893e-002, -1.100560e-002, -1.119920e-002, -1.138785e-002, --1.157160e-002, -1.175489e-002, -1.193350e-002, -1.210295e-002, --1.227393e-002, -1.244484e-002, -1.261508e-002, -1.278251e-002, --1.294720e-002, -1.310460e-002, -1.325741e-002, -1.341275e-002, --1.356422e-002, -1.370696e-002, -1.385329e-002, -1.399826e-002, --1.414139e-002, -1.428106e-002, -1.441340e-002, -1.454424e-002, --1.468369e-002, -1.482951e-002, -1.497436e-002, -1.511291e-002, --1.523863e-002, -1.536186e-002, -1.548562e-002, -1.560976e-002, --1.573294e-002, -1.586050e-002, -1.599093e-002, -1.611575e-002, --1.623399e-002, -1.634727e-002, -1.644925e-002, -1.655056e-002, --1.665475e-002, -1.676844e-002, -1.689201e-002, -1.701580e-002, --1.713525e-002, -1.725052e-002, -1.736236e-002, -1.746980e-002, --1.755745e-002, -1.764517e-002, -1.773411e-002, -1.781477e-002, --1.789968e-002, -1.798563e-002, -1.806995e-002, -1.815155e-002, --1.823102e-002, -1.831059e-002, -1.837418e-002, -1.842858e-002, --1.847832e-002, -1.852947e-002, -1.858231e-002, -1.863194e-002, --1.867564e-002, -1.871700e-002, -1.875366e-002, -1.878402e-002, --1.880018e-002, -1.880857e-002, -1.881712e-002, -1.882766e-002, --1.884144e-002, -1.885651e-002, -1.885886e-002, -1.884915e-002, --1.883217e-002, -1.881215e-002, -1.878209e-002, -1.873678e-002, --1.869086e-002, -1.864542e-002, -1.859411e-002, -1.853495e-002, --1.847190e-002, -1.840220e-002, -1.831816e-002, -1.822767e-002, --1.813060e-002, -1.801172e-002, -1.788443e-002, -1.775050e-002, --1.761483e-002, -1.747844e-002, -1.734003e-002, -1.719173e-002, --1.703209e-002, -1.686579e-002, -1.669635e-002, -1.649078e-002, --1.628269e-002, -1.607659e-002, -1.587214e-002, -1.566752e-002, --1.546166e-002, -1.525455e-002, -1.503455e-002, -1.478548e-002, --1.452899e-002, -1.425048e-002, -1.396035e-002, -1.366830e-002, --1.337109e-002, -1.306368e-002, -1.275516e-002, -1.243027e-002, --1.210254e-002, -1.175701e-002, -1.137430e-002, -1.096938e-002, --1.054174e-002, -1.010800e-002, -9.668676e-003, -9.208381e-003, --8.736989e-003, -8.262726e-003, -7.778388e-003, -7.290102e-003, --6.785662e-003, -6.208459e-003, -5.604584e-003, -4.990339e-003, --4.363681e-003, -3.737213e-003, -3.111566e-003, -2.486590e-003, --1.863048e-003, -1.240486e-003, -6.196939e-004, -1.779836e-017 -}, -{ -0.000000e+000, 1.502618e-001, 1.494584e-001, 1.430128e-001, -1.382886e-001, 1.342618e-001, 1.274038e-001, 1.198531e-001, -1.116745e-001, 1.029250e-001, 9.320480e-002, 8.278434e-002, -7.194458e-002, 6.109329e-002, 5.131315e-002, 4.285204e-002, -3.544078e-002, 2.829305e-002, 2.092176e-002, 1.369301e-002, -6.502255e-003, -8.276016e-004, -7.964621e-003, -1.509358e-002, --2.246249e-002, -2.971735e-002, -3.654769e-002, -4.260653e-002, --4.772251e-002, -5.194169e-002, -5.541601e-002, -5.829437e-002, --6.056519e-002, -6.236312e-002, -6.384026e-002, -6.505423e-002, --6.609224e-002, -6.669607e-002, -6.691516e-002, -6.685879e-002, --6.652929e-002, -6.568700e-002, -6.463312e-002, -6.343984e-002, --6.202305e-002, -6.046732e-002, -5.893315e-002, -5.740302e-002, --5.566495e-002, -5.387634e-002, -5.186860e-002, -4.975779e-002, --4.775799e-002, -4.562893e-002, -4.352805e-002, -4.137753e-002, --3.912613e-002, -3.682632e-002, -3.439250e-002, -3.194815e-002, --2.930056e-002, -2.656167e-002, -2.379128e-002, -2.098688e-002, --1.814850e-002, -1.536356e-002, -1.261164e-002, -9.868152e-003, --7.173675e-003, -4.525448e-003, -1.965084e-003, 6.168254e-004, -3.162284e-003, 5.707233e-003, 8.195772e-003, 1.068451e-002, -1.314364e-002, 1.564131e-002, 1.818456e-002, 2.084022e-002, -2.356551e-002, 2.624975e-002, 2.904896e-002, 3.188372e-002, -3.478474e-002, 3.770818e-002, 4.064820e-002, 4.359833e-002, -4.634738e-002, 4.909670e-002, 5.182554e-002, 5.439479e-002, -5.680792e-002, 5.902445e-002, 6.099067e-002, 6.269302e-002, -6.421239e-002, 6.544923e-002, 6.624927e-002, 6.681713e-002, -6.712631e-002, 6.726487e-002, 6.728269e-002, 6.704739e-002, -6.663485e-002, 6.611592e-002, 6.533447e-002, 6.431141e-002, -6.308573e-002, 6.175877e-002, 6.017576e-002, 5.847610e-002, -5.666525e-002, 5.469120e-002, 5.264451e-002, 5.057616e-002, -4.835811e-002, 4.606366e-002, 4.371613e-002, 4.122858e-002, -3.877405e-002, 3.628604e-002, 3.372210e-002, 3.113418e-002, -2.848793e-002, 2.586313e-002, 2.314775e-002, 2.041119e-002, -1.765324e-002, 1.487305e-002, 1.213176e-002, 9.380706e-003, -6.632160e-003, 3.951821e-003, 1.410248e-003, -1.076965e-003, --3.498997e-003, -5.916821e-003, -8.197745e-003, -1.046101e-002, --1.269171e-002, -1.485863e-002, -1.688913e-002, -1.884015e-002, --2.078778e-002, -2.267046e-002, -2.456608e-002, -2.648875e-002, --2.832836e-002, -3.014256e-002, -3.188720e-002, -3.363371e-002, --3.542073e-002, -3.720799e-002, -3.900214e-002, -4.077968e-002, --4.242680e-002, -4.399767e-002, -4.555023e-002, -4.707265e-002, --4.851820e-002, -4.998965e-002, -5.133481e-002, -5.257725e-002, --5.383985e-002, -5.509882e-002, -5.631151e-002, -5.737942e-002, --5.833593e-002, -5.927504e-002, -6.015817e-002, -6.097732e-002, --6.158503e-002, -6.209689e-002, -6.255621e-002, -6.299116e-002, --6.325338e-002, -6.337238e-002, -6.346345e-002, -6.351750e-002, --6.329521e-002, -6.299696e-002, -6.269109e-002, -6.218936e-002, --6.156519e-002, -6.086646e-002, -5.996721e-002, -5.895799e-002, --5.793210e-002, -5.661305e-002, -5.521785e-002, -5.371145e-002, --5.203058e-002, -5.033116e-002, -4.837816e-002, -4.639728e-002, --4.416284e-002, -4.188589e-002, -3.941294e-002, -3.689078e-002, --3.423520e-002, -3.152978e-002, -2.875980e-002, -2.596427e-002, --2.318996e-002, -2.043350e-002, -1.773289e-002, -1.501457e-002, --1.237982e-002, -9.745071e-003, -7.229239e-003, -4.708208e-003, --2.320967e-003, 8.070091e-005, 2.348366e-003, 4.591811e-003, -6.764018e-003, 8.850274e-003, 1.093949e-002, 1.288259e-002, -1.482072e-002, 1.670620e-002, 1.849338e-002, 2.026930e-002, -2.195358e-002, 2.358069e-002, 2.521621e-002, 2.674060e-002, -2.821928e-002, 2.967623e-002, 3.102724e-002, 3.234031e-002, -3.364976e-002, 3.489012e-002, 3.605705e-002, 3.720929e-002, -3.832424e-002, 3.933046e-002, 4.032283e-002, 4.129232e-002, -4.218216e-002, 4.300282e-002, 4.382003e-002, 4.460349e-002, -4.526021e-002, 4.588554e-002, 4.650034e-002, 4.707632e-002, -4.756497e-002, 4.802367e-002, 4.843365e-002, 4.881548e-002, -4.910217e-002, 4.937479e-002, 4.963733e-002, 4.988234e-002, -5.005874e-002, 5.020310e-002, 5.034665e-002, 5.049171e-002, -5.058967e-002, 5.064064e-002, 5.068562e-002, 5.073603e-002, -5.075845e-002, 5.072401e-002, 5.067237e-002, 5.058246e-002, -5.046650e-002, 5.031505e-002, 5.009900e-002, 4.984151e-002, -4.953146e-002, 4.916712e-002, 4.871084e-002, 4.821376e-002, -4.768106e-002, 4.711512e-002, 4.652647e-002, 4.585785e-002, -4.514133e-002, 4.439439e-002, 4.361535e-002, 4.280429e-002, -4.192881e-002, 4.102300e-002, 4.009239e-002, 3.913833e-002, -3.815946e-002, 3.713070e-002, 3.606890e-002, 3.499174e-002, -3.389942e-002, 3.279640e-002, 3.168237e-002, 3.051263e-002, -2.933234e-002, 2.815132e-002, 2.695674e-002, 2.575655e-002, -2.450979e-002, 2.324600e-002, 2.198786e-002, 2.073296e-002, -1.945922e-002, 1.819424e-002, 1.690579e-002, 1.560894e-002, -1.430015e-002, 1.299360e-002, 1.169695e-002, 1.037968e-002, -9.068149e-003, 7.741817e-003, 6.415321e-003, 5.105502e-003, -3.819000e-003, 2.531463e-003, 1.269096e-003, 2.384484e-005, --1.193432e-003, -2.386539e-003, -3.560392e-003, -4.718147e-003, --5.815072e-003, -6.866752e-003, -7.887419e-003, -8.907708e-003, --9.933379e-003, -1.095651e-002, -1.197075e-002, -1.296265e-002, --1.390421e-002, -1.481505e-002, -1.571800e-002, -1.662590e-002, --1.752483e-002, -1.842051e-002, -1.930908e-002, -2.015003e-002, --2.094065e-002, -2.171573e-002, -2.250890e-002, -2.327995e-002, --2.402365e-002, -2.473761e-002, -2.542798e-002, -2.608625e-002, --2.671729e-002, -2.732652e-002, -2.791842e-002, -2.848333e-002, --2.903645e-002, -2.955853e-002, -3.003906e-002, -3.051089e-002, --3.095459e-002, -3.137805e-002, -3.176411e-002, -3.212434e-002, --3.247152e-002, -3.278630e-002, -3.309353e-002, -3.339251e-002, --3.366102e-002, -3.388245e-002, -3.407782e-002, -3.427637e-002, --3.445911e-002, -3.466297e-002, -3.481982e-002, -3.496659e-002, --3.511681e-002, -3.523699e-002, -3.530970e-002, -3.536507e-002, --3.544618e-002, -3.551390e-002, -3.557507e-002, -3.561826e-002, --3.565932e-002, -3.567753e-002, -3.568320e-002, -3.567384e-002, --3.567145e-002, -3.566303e-002, -3.565306e-002, -3.564454e-002, --3.561138e-002, -3.555141e-002, -3.548285e-002, -3.544837e-002, --3.542393e-002, -3.537955e-002, -3.532114e-002, -3.525028e-002, --3.517465e-002, -3.508291e-002, -3.497593e-002, -3.490447e-002, --3.483494e-002, -3.477417e-002, -3.471751e-002, -3.466583e-002, --3.460672e-002, -3.456024e-002, -3.448910e-002, -3.443390e-002, --3.435854e-002, -3.427583e-002, -3.417394e-002, -3.407909e-002, --3.397054e-002, -3.384070e-002, -3.369647e-002, -3.355718e-002, --3.340723e-002, -3.323516e-002, -3.303598e-002, -3.279781e-002, --3.254387e-002, -3.228604e-002, -3.201505e-002, -3.171318e-002, --3.142293e-002, -3.114209e-002, -3.082772e-002, -3.045841e-002, --3.005733e-002, -2.964362e-002, -2.917462e-002, -2.867504e-002, --2.813418e-002, -2.760626e-002, -2.707090e-002, -2.652276e-002, --2.593222e-002, -2.531618e-002, -2.469594e-002, -2.407103e-002, --2.343334e-002, -2.277295e-002, -2.207018e-002, -2.138198e-002, --2.069594e-002, -1.998571e-002, -1.925349e-002, -1.849806e-002, --1.773696e-002, -1.696485e-002, -1.618180e-002, -1.539289e-002, --1.459834e-002, -1.381856e-002, -1.303101e-002, -1.221188e-002, --1.137027e-002, -1.050989e-002, -9.626323e-003, -8.732401e-003, --7.834357e-003, -6.930994e-003, -6.020929e-003, -5.141985e-003, --4.256497e-003, -3.361107e-003, -2.465532e-003, -1.556973e-003, --6.467956e-004, 2.871382e-004, 1.237592e-003, 2.190082e-003, -3.133055e-003, 4.074881e-003, 4.983417e-003, 5.889459e-003, -6.791265e-003, 7.673202e-003, 8.542332e-003, 9.408963e-003, -1.026319e-002, 1.110473e-002, 1.194347e-002, 1.279316e-002, -1.365171e-002, 1.447525e-002, 1.527256e-002, 1.605954e-002, -1.683167e-002, 1.758684e-002, 1.831307e-002, 1.902876e-002, -1.972684e-002, 2.041050e-002, 2.108935e-002, 2.173923e-002, -2.237265e-002, 2.297090e-002, 2.354096e-002, 2.412886e-002, -2.471794e-002, 2.527551e-002, 2.581197e-002, 2.633118e-002, -2.683890e-002, 2.733438e-002, 2.780126e-002, 2.824413e-002, -2.865447e-002, 2.904084e-002, 2.937105e-002, 2.968539e-002, -2.998415e-002, 3.027428e-002, 3.054532e-002, 3.079764e-002, -3.106455e-002, 3.132506e-002, 3.156960e-002, 3.180857e-002, -3.205375e-002, 3.228896e-002, 3.252091e-002, 3.271934e-002, -3.289472e-002, 3.307206e-002, 3.324106e-002, 3.339448e-002, -3.353456e-002, 3.366045e-002, 3.377804e-002, 3.389761e-002, -3.401727e-002, 3.413144e-002, 3.425254e-002, 3.437081e-002, -3.448903e-002, 3.459243e-002, 3.467257e-002, 3.474069e-002, -3.480717e-002, 3.486153e-002, 3.491283e-002, 3.494985e-002, -3.497361e-002, 3.497508e-002, 3.495995e-002, 3.492369e-002, -3.485748e-002, 3.476868e-002, 3.467043e-002, 3.455774e-002, -3.441120e-002, 3.423531e-002, 3.406846e-002, 3.389805e-002, -3.372093e-002, 3.352094e-002, 3.330048e-002, 3.306500e-002, -3.281435e-002, 3.255679e-002, 3.229502e-002, 3.203285e-002, -3.176566e-002, 3.149615e-002, 3.120929e-002, 3.091477e-002, -3.059739e-002, 3.026246e-002, 2.991732e-002, 2.955209e-002, -2.916415e-002, 2.875910e-002, 2.835779e-002, 2.796257e-002, -2.755974e-002, 2.714536e-002, 2.672353e-002, 2.629519e-002, -2.584748e-002, 2.538733e-002, 2.492165e-002, 2.444579e-002, -2.396350e-002, 2.343459e-002, 2.287924e-002, 2.232696e-002, -2.178998e-002, 2.125035e-002, 2.070484e-002, 2.015387e-002, -1.959821e-002, 1.904552e-002, 1.848591e-002, 1.791866e-002, -1.735488e-002, 1.678414e-002, 1.623217e-002, 1.568497e-002, -1.513833e-002, 1.459029e-002, 1.404801e-002, 1.349753e-002, -1.293772e-002, 1.237014e-002, 1.180083e-002, 1.122481e-002, -1.064271e-002, 1.005560e-002, 9.476598e-003, 8.896534e-003, -8.304817e-003, 7.710918e-003, 7.115831e-003, 6.518919e-003, -5.918190e-003, 5.315373e-003, 4.709568e-003, 4.090631e-003, -3.465813e-003, 2.846576e-003, 2.226961e-003, 1.601896e-003, -9.867563e-004, 3.745772e-004, -2.105205e-004, -7.933030e-004, --1.371328e-003, -1.940325e-003, -2.511783e-003, -3.076740e-003, --3.647312e-003, -4.213629e-003, -4.768049e-003, -5.306119e-003, --5.828920e-003, -6.336394e-003, -6.832906e-003, -7.320921e-003, --7.797977e-003, -8.274190e-003, -8.748236e-003, -9.233121e-003, --9.707003e-003, -1.015312e-002, -1.057526e-002, -1.099189e-002, --1.140492e-002, -1.180347e-002, -1.215806e-002, -1.249829e-002, --1.283284e-002, -1.316834e-002, -1.350417e-002, -1.383645e-002, --1.416119e-002, -1.448808e-002, -1.481028e-002, -1.513343e-002, --1.544453e-002, -1.573912e-002, -1.603154e-002, -1.630950e-002, --1.658602e-002, -1.687403e-002, -1.715366e-002, -1.742182e-002, --1.768665e-002, -1.795419e-002, -1.821771e-002, -1.846527e-002, --1.864034e-002, -1.877560e-002, -1.891428e-002, -1.905052e-002, --1.918990e-002, -1.932156e-002, -1.944473e-002, -1.953402e-002, --1.962136e-002, -1.971149e-002, -1.980488e-002, -1.989959e-002, --1.999073e-002, -2.008237e-002, -2.012979e-002, -2.014827e-002, --2.018234e-002, -2.021430e-002, -2.025566e-002, -2.030232e-002, --2.035800e-002, -2.041849e-002, -2.047975e-002, -2.053755e-002, --2.059503e-002, -2.066297e-002, -2.070026e-002, -2.073074e-002, --2.076274e-002, -2.079700e-002, -2.081766e-002, -2.081751e-002, --2.078707e-002, -2.074217e-002, -2.068854e-002, -2.061840e-002, --2.053394e-002, -2.044232e-002, -2.034929e-002, -2.025436e-002, --2.016599e-002, -2.008360e-002, -2.000049e-002, -1.992195e-002, --1.984999e-002, -1.977996e-002, -1.970968e-002, -1.963963e-002, --1.957092e-002, -1.951273e-002, -1.941598e-002, -1.930148e-002, --1.919344e-002, -1.909173e-002, -1.898761e-002, -1.885601e-002, --1.871118e-002, -1.856783e-002, -1.841840e-002, -1.826673e-002, --1.811925e-002, -1.797291e-002, -1.782092e-002, -1.766494e-002, --1.750164e-002, -1.733041e-002, -1.715757e-002, -1.699718e-002, --1.684047e-002, -1.668598e-002, -1.652583e-002, -1.636523e-002, --1.620326e-002, -1.604395e-002, -1.588348e-002, -1.571252e-002, --1.554306e-002, -1.537691e-002, -1.521382e-002, -1.505256e-002, --1.489495e-002, -1.472986e-002, -1.454640e-002, -1.435792e-002, --1.415967e-002, -1.395707e-002, -1.372492e-002, -1.349921e-002, --1.327217e-002, -1.304305e-002, -1.281467e-002, -1.258340e-002, --1.235459e-002, -1.212523e-002, -1.190113e-002, -1.168102e-002, --1.145324e-002, -1.121639e-002, -1.097750e-002, -1.073424e-002, --1.049076e-002, -1.024075e-002, -9.991022e-003, -9.742739e-003, --9.494931e-003, -9.245145e-003, -8.995961e-003, -8.752816e-003, --8.512593e-003, -8.277541e-003, -8.049587e-003, -7.826348e-003, --7.608062e-003, -7.396156e-003, -7.185370e-003, -6.980983e-003, --6.777299e-003, -6.577432e-003, -6.382538e-003, -6.191508e-003, --6.003896e-003, -5.816081e-003, -5.627869e-003, -5.427195e-003, --5.222663e-003, -5.037527e-003, -4.855783e-003, -4.649402e-003, --4.440233e-003, -4.232805e-003, -4.024880e-003, -3.807483e-003, --3.588480e-003, -3.361602e-003, -3.132096e-003, -2.905517e-003, --2.679254e-003, -2.452608e-003, -2.227449e-003, -2.008791e-003, --1.790952e-003, -1.573746e-003, -1.361542e-003, -1.157087e-003, --9.609823e-004, -7.674150e-004, -5.797771e-004, -3.990046e-004, --2.239958e-004, -4.439211e-005, 1.396298e-004, 3.222211e-004, -5.008715e-004, 6.779647e-004, 8.601898e-004, 1.042781e-003, -1.220758e-003, 1.381251e-003, 1.538930e-003, 1.693212e-003, -1.843456e-003, 1.997282e-003, 2.155009e-003, 2.317536e-003, -2.481689e-003, 2.649820e-003, 2.819629e-003, 2.990812e-003, -3.162751e-003, 3.331905e-003, 3.507244e-003, 3.683785e-003, -3.861910e-003, 4.030485e-003, 4.198306e-003, 4.364774e-003, -4.524752e-003, 4.680467e-003, 4.821825e-003, 4.974119e-003, -5.126121e-003, 5.275984e-003, 5.445741e-003, 5.614988e-003, -5.783175e-003, 5.947920e-003, 6.113995e-003, 6.273493e-003, -6.426155e-003, 6.586598e-003, 6.749856e-003, 6.902632e-003, -7.059211e-003, 7.218763e-003, 7.379529e-003, 7.536365e-003, -7.705722e-003, 7.882556e-003, 8.061374e-003, 8.239117e-003, -8.422710e-003, 8.610220e-003, 8.796941e-003, 8.987158e-003, -9.176041e-003, 9.359229e-003, 9.540617e-003, 9.712234e-003, -9.883348e-003, 1.004536e-002, 1.020352e-002, 1.036267e-002, -1.051669e-002, 1.066336e-002, 1.079316e-002, 1.091487e-002, -1.102950e-002, 1.114212e-002, 1.125885e-002, 1.136107e-002, -1.145830e-002, 1.155206e-002, 1.164214e-002, 1.172806e-002, -1.180849e-002, 1.188952e-002, 1.196905e-002, 1.204584e-002, -1.211635e-002, 1.217674e-002, 1.223541e-002, 1.229263e-002, -1.234995e-002, 1.240863e-002, 1.246192e-002, 1.251184e-002, -1.256242e-002, 1.261874e-002, 1.267709e-002, 1.272766e-002, -1.277669e-002, 1.282692e-002, 1.287511e-002, 1.292109e-002, -1.296405e-002, 1.300595e-002, 1.304837e-002, 1.309052e-002, -1.313521e-002, 1.317410e-002, 1.320113e-002, 1.322924e-002, -1.325429e-002, 1.328400e-002, 1.332435e-002, 1.338605e-002, -1.345960e-002, 1.352992e-002, 1.360079e-002, 1.367062e-002, -1.372020e-002, 1.376942e-002, 1.381755e-002, 1.387327e-002, -1.393805e-002, 1.400128e-002, 1.405127e-002, 1.409417e-002, -1.413538e-002, 1.417545e-002, 1.419578e-002, 1.421460e-002, -1.422852e-002, 1.423914e-002, 1.424496e-002, 1.424318e-002, -1.423454e-002, 1.422075e-002, 1.420547e-002, 1.419162e-002, -1.415933e-002, 1.411267e-002, 1.406270e-002, 1.400903e-002, -1.395095e-002, 1.388625e-002, 1.381661e-002, 1.374747e-002, -1.367476e-002, 1.360097e-002, 1.351428e-002, 1.340822e-002, -1.330277e-002, 1.319812e-002, 1.309727e-002, 1.300067e-002, -1.290355e-002, 1.280535e-002, 1.270370e-002, 1.260333e-002, -1.250062e-002, 1.237286e-002, 1.226199e-002, 1.215275e-002, -1.204196e-002, 1.192944e-002, 1.181639e-002, 1.169824e-002, -1.156265e-002, 1.142394e-002, 1.128725e-002, 1.111601e-002, -1.093656e-002, 1.075112e-002, 1.055972e-002, 1.036437e-002, -1.016662e-002, 9.966574e-003, 9.764344e-003, 9.564577e-003, -9.363137e-003, 9.134555e-003, 8.895172e-003, 8.654333e-003, -8.410100e-003, 8.156153e-003, 7.901157e-003, 7.657088e-003, -7.412666e-003, 7.162834e-003, 6.905823e-003, 6.633215e-003, -6.343749e-003, 6.058495e-003, 5.776984e-003, 5.509354e-003, -5.244486e-003, 4.976913e-003, 4.696457e-003, 4.408548e-003, -4.108398e-003, 3.771421e-003, 3.395763e-003, 3.026870e-003, -2.665420e-003, 2.300077e-003, 1.929931e-003, 1.555189e-003, -1.174700e-003, 7.890409e-004, 3.971999e-004, -4.543580e-018 -}, -{ -0.000000e+000, 2.582338e-002, 2.788695e-002, 3.015120e-002, -1.992367e-002, 6.122007e-003, 1.873491e-003, 1.551893e-003, -1.851705e-003, 2.831383e-003, 5.649215e-003, 9.446452e-003, -1.257734e-002, 1.404221e-002, 1.265673e-002, 9.201761e-003, -5.724363e-003, 3.914090e-003, 3.643757e-003, 2.951421e-003, -1.069848e-003, -2.419836e-003, -6.426817e-003, -1.023838e-002, --1.385503e-002, -1.724476e-002, -2.030696e-002, -2.291385e-002, --2.487775e-002, -2.614447e-002, -2.663786e-002, -2.650462e-002, --2.580013e-002, -2.461288e-002, -2.313307e-002, -2.146232e-002, --1.965955e-002, -1.744552e-002, -1.463732e-002, -1.140667e-002, --7.972878e-003, -4.575456e-003, -1.313533e-003, 1.588777e-003, -3.999687e-003, 5.835754e-003, 6.927453e-003, 7.243991e-003, -7.317584e-003, 7.073351e-003, 6.257333e-003, 4.874401e-003, -3.121776e-003, 1.038513e-003, -1.218678e-003, -3.454468e-003, --5.575850e-003, -7.444446e-003, -8.954404e-003, -9.935053e-003, --1.031070e-002, -9.867237e-003, -8.816129e-003, -7.373982e-003, --5.672227e-003, -3.845725e-003, -1.813834e-003, 3.513706e-004, -2.673189e-003, 5.083045e-003, 7.527808e-003, 1.002128e-002, -1.260762e-002, 1.524348e-002, 1.786674e-002, 2.050779e-002, -2.313621e-002, 2.573596e-002, 2.833679e-002, 3.108054e-002, -3.380981e-002, 3.642909e-002, 3.899303e-002, 4.142137e-002, -4.348831e-002, 4.530173e-002, 4.694105e-002, 4.820910e-002, -4.915036e-002, 4.982978e-002, 5.018315e-002, 5.023589e-002, -4.980763e-002, 4.905758e-002, 4.796739e-002, 4.640574e-002, -4.444484e-002, 4.207434e-002, 3.928124e-002, 3.635617e-002, -3.336321e-002, 3.010738e-002, 2.671548e-002, 2.312237e-002, -1.937084e-002, 1.547951e-002, 1.141165e-002, 7.274858e-003, -3.033589e-003, -1.220267e-003, -5.534770e-003, -9.847984e-003, --1.415600e-002, -1.834604e-002, -2.239824e-002, -2.637304e-002, --3.025736e-002, -3.398421e-002, -3.748769e-002, -4.081280e-002, --4.393477e-002, -4.686695e-002, -4.953000e-002, -5.187861e-002, --5.394374e-002, -5.574413e-002, -5.728003e-002, -5.858608e-002, --5.961233e-002, -6.040203e-002, -6.099119e-002, -6.134030e-002, --6.140432e-002, -6.106806e-002, -6.028717e-002, -5.908612e-002, --5.758131e-002, -5.579794e-002, -5.366737e-002, -5.135445e-002, --4.887707e-002, -4.625764e-002, -4.342870e-002, -4.054161e-002, --3.765350e-002, -3.472359e-002, -3.186216e-002, -2.911793e-002, --2.650080e-002, -2.398324e-002, -2.159563e-002, -1.946005e-002, --1.759561e-002, -1.603332e-002, -1.476296e-002, -1.354104e-002, --1.230068e-002, -1.108787e-002, -9.978465e-003, -8.980276e-003, --8.103006e-003, -7.298285e-003, -6.463383e-003, -5.695816e-003, --5.031625e-003, -4.555449e-003, -4.164841e-003, -3.788624e-003, --3.434260e-003, -3.225064e-003, -3.142920e-003, -3.208623e-003, --3.257603e-003, -3.399448e-003, -3.636708e-003, -3.937849e-003, --4.162655e-003, -4.428913e-003, -4.778235e-003, -5.185898e-003, --5.409569e-003, -5.634282e-003, -5.892079e-003, -6.062179e-003, --6.160839e-003, -6.230034e-003, -6.141727e-003, -5.946886e-003, --5.718027e-003, -5.266735e-003, -4.730247e-003, -4.026396e-003, --3.133571e-003, -2.145805e-003, -8.841697e-004, 4.460122e-004, -1.962817e-003, 3.519369e-003, 5.228009e-003, 6.988262e-003, -8.859223e-003, 1.078435e-002, 1.276813e-002, 1.476935e-002, -1.679453e-002, 1.885455e-002, 2.088238e-002, 2.293859e-002, -2.493055e-002, 2.693966e-002, 2.883856e-002, 3.074620e-002, -3.254727e-002, 3.437391e-002, 3.610964e-002, 3.782007e-002, -3.946395e-002, 4.101541e-002, 4.256785e-002, 4.401362e-002, -4.546050e-002, 4.685832e-002, 4.814837e-002, 4.942355e-002, -5.059405e-002, 5.171846e-002, 5.282062e-002, 5.381627e-002, -5.475877e-002, 5.566143e-002, 5.646076e-002, 5.720148e-002, -5.790035e-002, 5.850898e-002, 5.901851e-002, 5.948157e-002, -5.988680e-002, 6.016002e-002, 6.038337e-002, 6.053420e-002, -6.057080e-002, 6.051355e-002, 6.037638e-002, 6.015988e-002, -5.980726e-002, 5.936905e-002, 5.885350e-002, 5.823953e-002, -5.747670e-002, 5.662954e-002, 5.568238e-002, 5.464153e-002, -5.345658e-002, 5.220852e-002, 5.092240e-002, 4.958150e-002, -4.812957e-002, 4.663464e-002, 4.510249e-002, 4.353549e-002, -4.190036e-002, 4.019099e-002, 3.843812e-002, 3.664865e-002, -3.481497e-002, 3.292826e-002, 3.101100e-002, 2.906868e-002, -2.709568e-002, 2.507404e-002, 2.299438e-002, 2.090159e-002, -1.879844e-002, 1.667227e-002, 1.449982e-002, 1.228980e-002, -1.006114e-002, 7.834112e-003, 5.600179e-003, 3.342738e-003, -1.067792e-003, -1.203849e-003, -3.480048e-003, -5.753192e-003, --8.040342e-003, -1.033072e-002, -1.260687e-002, -1.485605e-002, --1.708884e-002, -1.932198e-002, -2.153992e-002, -2.374419e-002, --2.592434e-002, -2.807872e-002, -3.020181e-002, -3.231914e-002, --3.438478e-002, -3.640979e-002, -3.841246e-002, -4.038254e-002, --4.233059e-002, -4.424713e-002, -4.610782e-002, -4.789066e-002, --4.962434e-002, -5.132563e-002, -5.299403e-002, -5.459660e-002, --5.613912e-002, -5.761421e-002, -5.899103e-002, -6.030601e-002, --6.157000e-002, -6.275918e-002, -6.387614e-002, -6.492458e-002, --6.588903e-002, -6.676407e-002, -6.755520e-002, -6.828829e-002, --6.896337e-002, -6.957697e-002, -7.012728e-002, -7.063573e-002, --7.107170e-002, -7.145046e-002, -7.176842e-002, -7.202866e-002, --7.224724e-002, -7.241784e-002, -7.254152e-002, -7.262095e-002, --7.263374e-002, -7.256884e-002, -7.246964e-002, -7.232437e-002, --7.213061e-002, -7.187811e-002, -7.157505e-002, -7.116766e-002, --7.063421e-002, -6.999795e-002, -6.930333e-002, -6.851589e-002, --6.761833e-002, -6.664138e-002, -6.560128e-002, -6.445090e-002, --6.324399e-002, -6.195046e-002, -6.058348e-002, -5.913489e-002, --5.762982e-002, -5.607133e-002, -5.444229e-002, -5.276641e-002, --5.103873e-002, -4.928213e-002, -4.745380e-002, -4.556179e-002, --4.362721e-002, -4.166393e-002, -3.966141e-002, -3.762823e-002, --3.554553e-002, -3.345927e-002, -3.134624e-002, -2.920581e-002, --2.701512e-002, -2.481717e-002, -2.259149e-002, -2.034948e-002, --1.809695e-002, -1.585847e-002, -1.360849e-002, -1.136313e-002, --9.135985e-003, -6.906143e-003, -4.648397e-003, -2.403261e-003, --1.620247e-004, 2.051146e-003, 4.246357e-003, 6.409294e-003, -8.536767e-003, 1.065861e-002, 1.276302e-002, 1.484251e-002, -1.690199e-002, 1.893074e-002, 2.093150e-002, 2.286372e-002, -2.473345e-002, 2.657574e-002, 2.838702e-002, 3.013479e-002, -3.184725e-002, 3.350079e-002, 3.507956e-002, 3.657114e-002, -3.802480e-002, 3.942060e-002, 4.074637e-002, 4.200214e-002, -4.318976e-002, 4.429811e-002, 4.531892e-002, 4.624901e-002, -4.715347e-002, 4.803189e-002, 4.890701e-002, 4.976844e-002, -5.060156e-002, 5.140311e-002, 5.218886e-002, 5.295747e-002, -5.365133e-002, 5.430922e-002, 5.492732e-002, 5.554098e-002, -5.613254e-002, 5.669940e-002, 5.723183e-002, 5.772675e-002, -5.819009e-002, 5.860019e-002, 5.891647e-002, 5.917831e-002, -5.939426e-002, 5.955203e-002, 5.964731e-002, 5.968833e-002, -5.969862e-002, 5.963703e-002, 5.948914e-002, 5.928945e-002, -5.901539e-002, 5.869905e-002, 5.832790e-002, 5.790845e-002, -5.745916e-002, 5.698327e-002, 5.645336e-002, 5.585263e-002, -5.522002e-002, 5.456399e-002, 5.386684e-002, 5.310711e-002, -5.231428e-002, 5.148238e-002, 5.063449e-002, 4.976438e-002, -4.886258e-002, 4.791742e-002, 4.695486e-002, 4.597573e-002, -4.496933e-002, 4.393220e-002, 4.284279e-002, 4.175212e-002, -4.065589e-002, 3.953443e-002, 3.840069e-002, 3.723112e-002, -3.605475e-002, 3.486881e-002, 3.367353e-002, 3.247582e-002, -3.128344e-002, 3.008618e-002, 2.885326e-002, 2.761403e-002, -2.637146e-002, 2.513492e-002, 2.388366e-002, 2.263708e-002, -2.139103e-002, 2.014758e-002, 1.890976e-002, 1.768721e-002, -1.647237e-002, 1.526169e-002, 1.405306e-002, 1.286517e-002, -1.169275e-002, 1.051736e-002, 9.343780e-003, 8.187537e-003, -7.059036e-003, 5.959319e-003, 4.881003e-003, 3.827071e-003, -2.803146e-003, 1.803574e-003, 8.336306e-004, -1.112889e-004, --1.029277e-003, -1.942344e-003, -2.836194e-003, -3.677700e-003, --4.485395e-003, -5.262823e-003, -6.004349e-003, -6.702482e-003, --7.349067e-003, -7.943895e-003, -8.494786e-003, -9.000597e-003, --9.459769e-003, -9.866974e-003, -1.025754e-002, -1.063083e-002, --1.099423e-002, -1.135477e-002, -1.172834e-002, -1.209433e-002, --1.244451e-002, -1.278519e-002, -1.312057e-002, -1.345353e-002, --1.378238e-002, -1.410562e-002, -1.440929e-002, -1.472319e-002, --1.504577e-002, -1.535873e-002, -1.568911e-002, -1.601979e-002, --1.634087e-002, -1.665018e-002, -1.695646e-002, -1.725807e-002, --1.756278e-002, -1.785997e-002, -1.814962e-002, -1.842431e-002, --1.869064e-002, -1.897816e-002, -1.928970e-002, -1.959482e-002, --1.989242e-002, -2.018911e-002, -2.047715e-002, -2.076220e-002, --2.104346e-002, -2.132549e-002, -2.160666e-002, -2.187249e-002, --2.215183e-002, -2.244255e-002, -2.273536e-002, -2.302519e-002, --2.332048e-002, -2.362422e-002, -2.392241e-002, -2.421397e-002, --2.450256e-002, -2.478739e-002, -2.506222e-002, -2.534028e-002, --2.560466e-002, -2.586774e-002, -2.613478e-002, -2.640197e-002, --2.666248e-002, -2.691659e-002, -2.716008e-002, -2.740416e-002, --2.767136e-002, -2.795510e-002, -2.822781e-002, -2.849630e-002, --2.874778e-002, -2.898433e-002, -2.922457e-002, -2.945505e-002, --2.967060e-002, -2.988182e-002, -3.007252e-002, -3.025605e-002, --3.043270e-002, -3.060505e-002, -3.077101e-002, -3.093089e-002, --3.109494e-002, -3.125094e-002, -3.140677e-002, -3.155636e-002, --3.167006e-002, -3.177725e-002, -3.188191e-002, -3.198533e-002, --3.209000e-002, -3.219402e-002, -3.228849e-002, -3.237911e-002, --3.246861e-002, -3.253237e-002, -3.253422e-002, -3.250458e-002, --3.246226e-002, -3.241514e-002, -3.236453e-002, -3.232251e-002, --3.228404e-002, -3.223931e-002, -3.218492e-002, -3.211922e-002, --3.203205e-002, -3.192054e-002, -3.176521e-002, -3.156059e-002, --3.134152e-002, -3.111350e-002, -3.087894e-002, -3.063793e-002, --3.038738e-002, -3.012938e-002, -2.986057e-002, -2.956141e-002, --2.923532e-002, -2.890091e-002, -2.855211e-002, -2.819847e-002, --2.783067e-002, -2.743875e-002, -2.698010e-002, -2.651385e-002, --2.603993e-002, -2.554995e-002, -2.503188e-002, -2.449559e-002, --2.394813e-002, -2.338839e-002, -2.281920e-002, -2.225099e-002, --2.167806e-002, -2.109607e-002, -2.050371e-002, -1.990144e-002, --1.929286e-002, -1.867887e-002, -1.806404e-002, -1.745847e-002, --1.683969e-002, -1.619093e-002, -1.549554e-002, -1.478858e-002, --1.406755e-002, -1.335079e-002, -1.267159e-002, -1.198672e-002, --1.129643e-002, -1.059907e-002, -9.898953e-003, -9.196647e-003, --8.498074e-003, -7.794314e-003, -7.095248e-003, -6.396417e-003, --5.727504e-003, -5.095640e-003, -4.460635e-003, -3.838829e-003, --3.227981e-003, -2.620730e-003, -2.017433e-003, -1.420021e-003, --8.220225e-004, -2.187297e-004, 3.900817e-004, 1.000093e-003, -1.582605e-003, 2.126356e-003, 2.676725e-003, 3.241377e-003, -3.804041e-003, 4.371819e-003, 4.942579e-003, 5.484599e-003, -6.031348e-003, 6.583482e-003, 7.143376e-003, 7.714608e-003, -8.292779e-003, 8.879410e-003, 9.377144e-003, 9.798808e-003, -1.020809e-002, 1.062327e-002, 1.104663e-002, 1.147767e-002, -1.191842e-002, 1.236953e-002, 1.282603e-002, 1.328764e-002, -1.375019e-002, 1.421328e-002, 1.466225e-002, 1.511258e-002, -1.556932e-002, 1.602989e-002, 1.648278e-002, 1.690894e-002, -1.727076e-002, 1.760933e-002, 1.795406e-002, 1.829887e-002, -1.867353e-002, 1.903998e-002, 1.940619e-002, 1.977776e-002, -2.014896e-002, 2.051914e-002, 2.088825e-002, 2.125882e-002, -2.162986e-002, 2.199536e-002, 2.235992e-002, 2.272515e-002, -2.309407e-002, 2.346409e-002, 2.377533e-002, 2.405118e-002, -2.432890e-002, 2.460762e-002, 2.489001e-002, 2.512178e-002, -2.532171e-002, 2.551999e-002, 2.571579e-002, 2.590933e-002, -2.610171e-002, 2.629283e-002, 2.648123e-002, 2.667615e-002, -2.687626e-002, 2.706187e-002, 2.723278e-002, 2.739052e-002, -2.754781e-002, 2.770775e-002, 2.786157e-002, 2.800788e-002, -2.815196e-002, 2.829371e-002, 2.841866e-002, 2.850806e-002, -2.859112e-002, 2.867208e-002, 2.875144e-002, 2.882869e-002, -2.890365e-002, 2.896650e-002, 2.900974e-002, 2.904810e-002, -2.910987e-002, 2.917881e-002, 2.917776e-002, 2.916975e-002, -2.915829e-002, 2.914576e-002, 2.912872e-002, 2.910170e-002, -2.906646e-002, 2.901714e-002, 2.896276e-002, 2.889193e-002, -2.881246e-002, 2.873334e-002, 2.865144e-002, 2.855451e-002, -2.845170e-002, 2.834675e-002, 2.823901e-002, 2.812772e-002, -2.801081e-002, 2.787629e-002, 2.771456e-002, 2.754716e-002, -2.737634e-002, 2.720424e-002, 2.703163e-002, 2.685442e-002, -2.667217e-002, 2.648514e-002, 2.629500e-002, 2.610084e-002, -2.591180e-002, 2.571996e-002, 2.551914e-002, 2.530547e-002, -2.509024e-002, 2.487238e-002, 2.464957e-002, 2.440947e-002, -2.415352e-002, 2.388243e-002, 2.360648e-002, 2.328907e-002, -2.296099e-002, 2.262688e-002, 2.228336e-002, 2.193198e-002, -2.157621e-002, 2.122087e-002, 2.085911e-002, 2.049128e-002, -2.011490e-002, 1.973083e-002, 1.933753e-002, 1.893406e-002, -1.852277e-002, 1.810294e-002, 1.766812e-002, 1.722695e-002, -1.678013e-002, 1.630866e-002, 1.582072e-002, 1.533606e-002, -1.485178e-002, 1.436312e-002, 1.389237e-002, 1.342056e-002, -1.295249e-002, 1.248761e-002, 1.202538e-002, 1.156448e-002, -1.110221e-002, 1.063403e-002, 1.016925e-002, 9.705857e-003, -9.241361e-003, 8.780186e-003, 8.320196e-003, 7.861386e-003, -7.404690e-003, 6.948702e-003, 6.492689e-003, 6.035016e-003, -5.588828e-003, 5.144262e-003, 4.700165e-003, 4.260471e-003, -3.820413e-003, 3.378009e-003, 2.936965e-003, 2.496685e-003, -2.056279e-003, 1.616401e-003, 1.191256e-003, 7.536394e-004, -3.129552e-004, -1.289709e-004, -5.984666e-004, -1.068986e-003, --1.540534e-003, -2.018044e-003, -2.500498e-003, -2.979633e-003, --3.446692e-003, -3.905171e-003, -4.363551e-003, -4.831924e-003, --5.299494e-003, -5.768363e-003, -6.238863e-003, -6.713220e-003, --7.188559e-003, -7.681969e-003, -8.167602e-003, -8.632872e-003, --9.094300e-003, -9.556350e-003, -1.001708e-002, -1.047489e-002, --1.093133e-002, -1.138558e-002, -1.183474e-002, -1.227822e-002, --1.271034e-002, -1.311342e-002, -1.351145e-002, -1.389745e-002, --1.427333e-002, -1.464059e-002, -1.500170e-002, -1.535901e-002, --1.571338e-002, -1.606026e-002, -1.639657e-002, -1.669678e-002, --1.699257e-002, -1.728501e-002, -1.757322e-002, -1.785175e-002, --1.812491e-002, -1.839617e-002, -1.866475e-002, -1.893234e-002, --1.920176e-002, -1.944210e-002, -1.968713e-002, -1.993595e-002, --2.018648e-002, -2.043727e-002, -2.069106e-002, -2.094287e-002, --2.118790e-002, -2.142222e-002, -2.165966e-002, -2.187858e-002, --2.207911e-002, -2.228202e-002, -2.248486e-002, -2.268916e-002, --2.289575e-002, -2.310060e-002, -2.330430e-002, -2.349982e-002, --2.369265e-002, -2.387126e-002, -2.401661e-002, -2.416483e-002, --2.431245e-002, -2.446701e-002, -2.462648e-002, -2.480247e-002, --2.498029e-002, -2.515447e-002, -2.532875e-002, -2.549896e-002, --2.561131e-002, -2.572436e-002, -2.583953e-002, -2.595272e-002, --2.606886e-002, -2.620108e-002, -2.634142e-002, -2.648246e-002, --2.662419e-002, -2.676823e-002, -2.686031e-002, -2.693937e-002, --2.701250e-002, -2.707716e-002, -2.713994e-002, -2.720194e-002, --2.726222e-002, -2.731722e-002, -2.736718e-002, -2.741234e-002, --2.740787e-002, -2.736974e-002, -2.732665e-002, -2.727741e-002, --2.722798e-002, -2.717501e-002, -2.711777e-002, -2.705342e-002, --2.698006e-002, -2.690410e-002, -2.679515e-002, -2.663376e-002, --2.646899e-002, -2.630447e-002, -2.613991e-002, -2.597426e-002, --2.580972e-002, -2.564703e-002, -2.546739e-002, -2.528640e-002, --2.509463e-002, -2.482749e-002, -2.455954e-002, -2.429094e-002, --2.402580e-002, -2.376498e-002, -2.350641e-002, -2.325066e-002, --2.300422e-002, -2.275431e-002, -2.250732e-002, -2.215493e-002, --2.178699e-002, -2.142203e-002, -2.105955e-002, -2.069899e-002, --2.034095e-002, -1.998511e-002, -1.963402e-002, -1.927448e-002, --1.891150e-002, -1.847149e-002, -1.799834e-002, -1.752812e-002, --1.705416e-002, -1.656324e-002, -1.607551e-002, -1.558619e-002, --1.509975e-002, -1.461906e-002, -1.412681e-002, -1.357825e-002, --1.296390e-002, -1.235324e-002, -1.174679e-002, -1.114098e-002, --1.053527e-002, -9.935998e-003, -9.318518e-003, -8.696637e-003, --8.082559e-003, -7.425515e-003, -6.664508e-003, -5.906687e-003, --5.151437e-003, -4.401025e-003, -3.655789e-003, -2.915332e-003, --2.179478e-003, -1.448199e-003, -7.217395e-004, -2.567240e-018 -}, -{ -0.000000e+000, 1.994115e-001, 1.800203e-001, 1.545653e-001, -1.299366e-001, 1.062012e-001, 8.457450e-002, 6.488222e-002, -4.582496e-002, 2.772342e-002, 1.092794e-002, -4.723545e-003, --1.888470e-002, -3.061054e-002, -4.022590e-002, -4.805896e-002, --5.397992e-002, -5.710701e-002, -5.818212e-002, -5.794322e-002, --5.703980e-002, -5.625454e-002, -5.561314e-002, -5.507816e-002, --5.383257e-002, -5.228108e-002, -5.080053e-002, -4.990720e-002, --5.017243e-002, -5.112138e-002, -5.221414e-002, -5.336463e-002, --5.449491e-002, -5.540424e-002, -5.577496e-002, -5.544910e-002, --5.427255e-002, -5.230961e-002, -4.962822e-002, -4.633362e-002, --4.256385e-002, -3.847006e-002, -3.396507e-002, -2.927351e-002, --2.439569e-002, -1.943869e-002, -1.452581e-002, -9.778460e-003, --5.048164e-003, -1.867567e-004, 4.743900e-003, 1.024199e-002, -1.640414e-002, 2.289655e-002, 2.956801e-002, 3.630103e-002, -4.300259e-002, 4.937217e-002, 5.518242e-002, 6.034856e-002, -6.452966e-002, 6.779443e-002, 7.070444e-002, 7.335252e-002, -7.567860e-002, 7.753827e-002, 7.907946e-002, 8.048497e-002, -8.177406e-002, 8.263777e-002, 8.302860e-002, 8.322929e-002, -8.325761e-002, 8.290563e-002, 8.207983e-002, 8.113907e-002, -7.997070e-002, 7.841454e-002, 7.645719e-002, 7.443928e-002, -7.219326e-002, 6.950285e-002, 6.676136e-002, 6.401585e-002, -6.073379e-002, 5.721529e-002, 5.361167e-002, 4.957851e-002, -4.550726e-002, 4.119757e-002, 3.645610e-002, 3.165577e-002, -2.635704e-002, 2.113759e-002, 1.574249e-002, 1.002620e-002, -4.174248e-003, -1.853029e-003, -7.871257e-003, -1.396751e-002, --2.000963e-002, -2.602361e-002, -3.180849e-002, -3.738520e-002, --4.274972e-002, -4.784664e-002, -5.247224e-002, -5.661126e-002, --6.049407e-002, -6.402261e-002, -6.701596e-002, -6.961977e-002, --7.210251e-002, -7.405991e-002, -7.591641e-002, -7.747944e-002, --7.872568e-002, -7.978812e-002, -8.066890e-002, -8.138986e-002, --8.189995e-002, -8.244012e-002, -8.279679e-002, -8.292167e-002, --8.292392e-002, -8.268926e-002, -8.217472e-002, -8.144679e-002, --8.054291e-002, -7.942496e-002, -7.812349e-002, -7.659656e-002, --7.481911e-002, -7.290770e-002, -7.088981e-002, -6.854076e-002, --6.606341e-002, -6.348505e-002, -6.069982e-002, -5.779689e-002, --5.491708e-002, -5.183715e-002, -4.871784e-002, -4.574789e-002, --4.267877e-002, -3.950965e-002, -3.634840e-002, -3.313373e-002, --3.010130e-002, -2.696456e-002, -2.397643e-002, -2.114233e-002, --1.828296e-002, -1.541789e-002, -1.265875e-002, -9.858510e-003, --7.186767e-003, -4.550681e-003, -1.879534e-003, 7.951079e-004, -3.408193e-003, 6.183411e-003, 8.915607e-003, 1.160468e-002, -1.434871e-002, 1.722379e-002, 2.016723e-002, 2.305516e-002, -2.589494e-002, 2.867486e-002, 3.144102e-002, 3.430633e-002, -3.705611e-002, 3.971557e-002, 4.232671e-002, 4.498104e-002, -4.750010e-002, 4.985911e-002, 5.215897e-002, 5.443794e-002, -5.650808e-002, 5.849790e-002, 6.038815e-002, 6.207193e-002, -6.355134e-002, 6.496431e-002, 6.615325e-002, 6.713388e-002, -6.805462e-002, 6.856693e-002, 6.898120e-002, 6.919332e-002, -6.918685e-002, 6.901046e-002, 6.851246e-002, 6.787536e-002, -6.697997e-002, 6.590996e-002, 6.453573e-002, 6.300738e-002, -6.128590e-002, 5.939574e-002, 5.747478e-002, 5.545459e-002, -5.347508e-002, 5.145667e-002, 4.943969e-002, 4.738949e-002, -4.538466e-002, 4.335512e-002, 4.143163e-002, 3.944634e-002, -3.750252e-002, 3.558318e-002, 3.374665e-002, 3.195876e-002, -3.016734e-002, 2.841343e-002, 2.659634e-002, 2.495524e-002, -2.329459e-002, 2.163580e-002, 1.999798e-002, 1.832130e-002, -1.668069e-002, 1.511285e-002, 1.352915e-002, 1.196980e-002, -1.042156e-002, 8.812893e-003, 7.276352e-003, 5.816718e-003, -4.300734e-003, 2.831803e-003, 1.382030e-003, -8.584407e-005, --1.522037e-003, -2.899761e-003, -4.235273e-003, -5.575000e-003, --6.914166e-003, -8.216285e-003, -9.569998e-003, -1.091799e-002, --1.213511e-002, -1.335990e-002, -1.462280e-002, -1.587424e-002, --1.707880e-002, -1.830529e-002, -1.950721e-002, -2.066613e-002, --2.179987e-002, -2.294531e-002, -2.406942e-002, -2.516915e-002, --2.621419e-002, -2.721940e-002, -2.819383e-002, -2.917306e-002, --3.005866e-002, -3.087541e-002, -3.166449e-002, -3.245383e-002, --3.318760e-002, -3.383592e-002, -3.446329e-002, -3.503471e-002, --3.557826e-002, -3.604683e-002, -3.643831e-002, -3.671976e-002, --3.689451e-002, -3.695002e-002, -3.685698e-002, -3.668621e-002, --3.641659e-002, -3.607950e-002, -3.566249e-002, -3.512920e-002, --3.454340e-002, -3.392062e-002, -3.322512e-002, -3.244180e-002, --3.156596e-002, -3.064623e-002, -2.970363e-002, -2.873052e-002, --2.772481e-002, -2.666664e-002, -2.554997e-002, -2.440148e-002, --2.322167e-002, -2.208665e-002, -2.089837e-002, -1.968317e-002, --1.846749e-002, -1.722797e-002, -1.599427e-002, -1.479363e-002, --1.357495e-002, -1.239061e-002, -1.117971e-002, -9.989264e-003, --8.805567e-003, -7.694404e-003, -6.612748e-003, -5.549787e-003, --4.500399e-003, -3.486171e-003, -2.464558e-003, -1.498954e-003, --5.846051e-004, 2.768073e-004, 1.064311e-003, 1.770394e-003, -2.392970e-003, 2.977479e-003, 3.465503e-003, 3.890226e-003, -4.211557e-003, 4.426568e-003, 4.558136e-003, 4.627256e-003, -4.601091e-003, 4.490713e-003, 4.358354e-003, 4.239949e-003, -4.078649e-003, 3.891331e-003, 3.703991e-003, 3.485885e-003, -3.256289e-003, 3.030036e-003, 2.778938e-003, 2.542953e-003, -2.361275e-003, 2.156343e-003, 1.893464e-003, 1.614705e-003, -1.372374e-003, 1.136471e-003, 9.298647e-004, 7.181386e-004, -5.421042e-004, 4.016982e-004, 2.055644e-004, 4.693864e-005, --8.816619e-005, -2.384021e-004, -3.908703e-004, -5.145917e-004, --6.438117e-004, -7.884539e-004, -9.246930e-004, -1.011938e-003, --1.033255e-003, -1.048758e-003, -1.041810e-003, -1.053986e-003, --1.106793e-003, -1.210031e-003, -1.325548e-003, -1.437410e-003, --1.497819e-003, -1.491852e-003, -1.480585e-003, -1.470560e-003, --1.434580e-003, -1.377257e-003, -1.381436e-003, -1.394461e-003, --1.423218e-003, -1.418667e-003, -1.316771e-003, -1.202684e-003, --1.075200e-003, -9.376969e-004, -8.280971e-004, -7.796047e-004, --7.302366e-004, -6.624544e-004, -6.092372e-004, -5.252831e-004, --4.008807e-004, -2.414486e-004, -9.781290e-005, 3.044945e-005, -8.284647e-005, 1.305904e-004, 1.623638e-004, 1.853695e-004, -2.317645e-004, 3.045913e-004, 3.990136e-004, 4.987041e-004, -6.220249e-004, 7.118130e-004, 7.676574e-004, 8.099047e-004, -8.294054e-004, 8.377904e-004, 8.822233e-004, 9.177472e-004, -9.379131e-004, 9.440616e-004, 9.810649e-004, 1.103409e-003, -1.176540e-003, 1.223098e-003, 1.259795e-003, 1.292701e-003, -1.335389e-003, 1.338652e-003, 1.339449e-003, 1.381085e-003, -1.488194e-003, 1.577164e-003, 1.630007e-003, 1.662634e-003, -1.693413e-003, 1.728487e-003, 1.728010e-003, 1.739054e-003, -1.765114e-003, 1.808860e-003, 1.860458e-003, 1.908194e-003, -1.959702e-003, 1.989123e-003, 1.976282e-003, 1.948353e-003, -1.938278e-003, 1.933186e-003, 1.959555e-003, 1.984207e-003, -2.007310e-003, 2.026893e-003, 2.054498e-003, 2.087436e-003, -2.153807e-003, 2.193039e-003, 2.173026e-003, 2.168770e-003, -2.171189e-003, 2.227901e-003, 2.244870e-003, 2.249290e-003, -2.244648e-003, 2.240134e-003, 2.243374e-003, 2.223341e-003, -2.195735e-003, 2.194777e-003, 2.192997e-003, 2.156069e-003, -2.079016e-003, 1.971312e-003, 1.890285e-003, 1.849775e-003, -1.811996e-003, 1.747163e-003, 1.654081e-003, 1.586662e-003, -1.509017e-003, 1.407003e-003, 1.287489e-003, 1.156615e-003, -1.029739e-003, 8.725774e-004, 7.514670e-004, 6.310717e-004, -4.668026e-004, 3.296253e-004, 2.472284e-004, 1.780564e-004, -9.813202e-005, 7.024856e-006, -8.364791e-005, -1.638346e-004, --2.404041e-004, -3.261619e-004, -4.043435e-004, -4.712044e-004, --5.911881e-004, -6.915854e-004, -7.744435e-004, -8.497977e-004, --9.347223e-004, -1.002663e-003, -1.062698e-003, -1.103743e-003, --1.166452e-003, -1.255231e-003, -1.361254e-003, -1.447649e-003, --1.520915e-003, -1.574976e-003, -1.624774e-003, -1.682619e-003, --1.773881e-003, -1.863953e-003, -1.949136e-003, -2.033017e-003, --2.107201e-003, -2.189812e-003, -2.275609e-003, -2.340071e-003, --2.386362e-003, -2.436182e-003, -2.468430e-003, -2.505986e-003, --2.522184e-003, -2.528802e-003, -2.534709e-003, -2.542867e-003, --2.564423e-003, -2.597663e-003, -2.625108e-003, -2.651275e-003, --2.675466e-003, -2.695150e-003, -2.700661e-003, -2.680944e-003, --2.636909e-003, -2.583672e-003, -2.532316e-003, -2.494897e-003, --2.484584e-003, -2.458162e-003, -2.437826e-003, -2.417717e-003, --2.403159e-003, -2.387185e-003, -2.392382e-003, -2.406272e-003, --2.431643e-003, -2.437993e-003, -2.413190e-003, -2.381726e-003, --2.339674e-003, -2.310178e-003, -2.261447e-003, -2.199916e-003, --2.118498e-003, -2.026408e-003, -1.914556e-003, -1.771726e-003, --1.595517e-003, -1.386857e-003, -1.158363e-003, -9.254445e-004, --6.746435e-004, -4.075848e-004, -1.382223e-004, 1.566729e-004, -4.775807e-004, 8.081464e-004, 1.152529e-003, 1.524407e-003, -1.907760e-003, 2.300949e-003, 2.688409e-003, 3.080793e-003, -3.476256e-003, 3.890289e-003, 4.314994e-003, 4.753486e-003, -5.211753e-003, 5.687781e-003, 6.179677e-003, 6.675471e-003, -7.169366e-003, 7.652750e-003, 8.130249e-003, 8.621879e-003, -9.142344e-003, 9.666380e-003, 1.020179e-002, 1.075643e-002, -1.131664e-002, 1.188455e-002, 1.247185e-002, 1.306803e-002, -1.366827e-002, 1.425815e-002, 1.484815e-002, 1.543804e-002, -1.603555e-002, 1.663695e-002, 1.721990e-002, 1.779312e-002, -1.836562e-002, 1.892609e-002, 1.949438e-002, 2.007326e-002, -2.062172e-002, 2.111005e-002, 2.160011e-002, 2.209030e-002, -2.259632e-002, 2.309821e-002, 2.358284e-002, 2.405531e-002, -2.451988e-002, 2.497966e-002, 2.542588e-002, 2.583990e-002, -2.624814e-002, 2.663117e-002, 2.699270e-002, 2.732745e-002, -2.764841e-002, 2.795764e-002, 2.826967e-002, 2.857269e-002, -2.885510e-002, 2.911152e-002, 2.935808e-002, 2.958524e-002, -2.975768e-002, 2.991022e-002, 3.009176e-002, 3.026225e-002, -3.040200e-002, 3.050378e-002, 3.059241e-002, 3.066170e-002, -3.070899e-002, 3.072240e-002, 3.067765e-002, 3.059507e-002, -3.048192e-002, 3.033001e-002, 3.015391e-002, 2.994818e-002, -2.971085e-002, 2.942712e-002, 2.909253e-002, 2.872605e-002, -2.830716e-002, 2.784749e-002, 2.735425e-002, 2.683947e-002, -2.628566e-002, 2.569121e-002, 2.508597e-002, 2.446298e-002, -2.381813e-002, 2.315801e-002, 2.252099e-002, 2.185049e-002, -2.113087e-002, 2.037404e-002, 1.959958e-002, 1.883099e-002, -1.807093e-002, 1.729770e-002, 1.652498e-002, 1.573159e-002, -1.495137e-002, 1.421379e-002, 1.346815e-002, 1.273341e-002, -1.201557e-002, 1.129502e-002, 1.057152e-002, 9.848899e-003, -9.129448e-003, 8.397180e-003, 7.642661e-003, 6.912411e-003, -6.261204e-003, 5.593916e-003, 4.907036e-003, 4.229196e-003, -3.540967e-003, 2.843428e-003, 2.145019e-003, 1.479969e-003, -8.144344e-004, 1.216311e-004, -5.833074e-004, -1.273385e-003, --1.970200e-003, -2.678760e-003, -3.305254e-003, -3.870133e-003, --4.422037e-003, -4.977046e-003, -5.525795e-003, -6.075219e-003, --6.630161e-003, -7.192632e-003, -7.760078e-003, -8.326019e-003, --8.906654e-003, -9.505481e-003, -1.003924e-002, -1.057216e-002, --1.110722e-002, -1.164026e-002, -1.218242e-002, -1.270181e-002, --1.319003e-002, -1.365166e-002, -1.410855e-002, -1.455369e-002, --1.500198e-002, -1.545054e-002, -1.590635e-002, -1.637174e-002, --1.683438e-002, -1.729068e-002, -1.775106e-002, -1.821105e-002, --1.867706e-002, -1.912882e-002, -1.956989e-002, -2.000570e-002, --2.047037e-002, -2.094902e-002, -2.135494e-002, -2.171667e-002, --2.206925e-002, -2.241727e-002, -2.275526e-002, -2.304135e-002, --2.331169e-002, -2.358582e-002, -2.385427e-002, -2.411570e-002, --2.437681e-002, -2.465152e-002, -2.494976e-002, -2.524366e-002, --2.553070e-002, -2.580825e-002, -2.607810e-002, -2.632767e-002, --2.657636e-002, -2.684301e-002, -2.709067e-002, -2.732745e-002, --2.757891e-002, -2.783068e-002, -2.806875e-002, -2.827531e-002, --2.846376e-002, -2.864872e-002, -2.881077e-002, -2.896882e-002, --2.912747e-002, -2.927044e-002, -2.938489e-002, -2.949837e-002, --2.958264e-002, -2.966053e-002, -2.969128e-002, -2.972274e-002, --2.975694e-002, -2.978394e-002, -2.980791e-002, -2.982642e-002, --2.982843e-002, -2.981198e-002, -2.978656e-002, -2.975477e-002, --2.973172e-002, -2.970673e-002, -2.968467e-002, -2.965510e-002, --2.961405e-002, -2.955663e-002, -2.948887e-002, -2.941371e-002, --2.932945e-002, -2.922619e-002, -2.911933e-002, -2.900568e-002, --2.887813e-002, -2.874517e-002, -2.859753e-002, -2.844296e-002, --2.828284e-002, -2.812081e-002, -2.796222e-002, -2.781061e-002, --2.763165e-002, -2.743285e-002, -2.721977e-002, -2.699281e-002, --2.676430e-002, -2.653488e-002, -2.631279e-002, -2.607819e-002, --2.582505e-002, -2.555764e-002, -2.529125e-002, -2.498855e-002, --2.467981e-002, -2.437210e-002, -2.406614e-002, -2.378183e-002, --2.352124e-002, -2.325633e-002, -2.298571e-002, -2.270701e-002, --2.241807e-002, -2.212651e-002, -2.183469e-002, -2.154408e-002, --2.125230e-002, -2.096461e-002, -2.066675e-002, -2.035571e-002, --2.002522e-002, -1.965941e-002, -1.926862e-002, -1.886961e-002, --1.846221e-002, -1.803678e-002, -1.759340e-002, -1.714963e-002, --1.669965e-002, -1.624895e-002, -1.579296e-002, -1.533473e-002, --1.487257e-002, -1.440648e-002, -1.394547e-002, -1.349306e-002, --1.303329e-002, -1.256285e-002, -1.209812e-002, -1.163973e-002, --1.118525e-002, -1.073190e-002, -1.028767e-002, -9.853180e-003, --9.444167e-003, -9.038903e-003, -8.646198e-003, -8.249178e-003, --7.837462e-003, -7.416346e-003, -6.988440e-003, -6.551874e-003, --6.112431e-003, -5.675586e-003, -5.254285e-003, -4.810506e-003, --4.366722e-003, -3.922109e-003, -3.447888e-003, -2.980728e-003, --2.515324e-003, -2.035227e-003, -1.534855e-003, -1.046957e-003, --5.773481e-004, -1.078402e-004, 3.594199e-004, 8.382999e-004, -1.313792e-003, 1.788263e-003, 2.258657e-003, 2.716756e-003, -3.174055e-003, 3.641204e-003, 4.096444e-003, 4.524308e-003, -4.949731e-003, 5.373405e-003, 5.795411e-003, 6.223141e-003, -6.644972e-003, 7.065756e-003, 7.464499e-003, 7.858962e-003, -8.237450e-003, 8.589952e-003, 8.945863e-003, 9.300365e-003, -9.666188e-003, 1.004092e-002, 1.042301e-002, 1.080405e-002, -1.117096e-002, 1.153361e-002, 1.190375e-002, 1.223310e-002, -1.256484e-002, 1.289871e-002, 1.323478e-002, 1.358060e-002, -1.394978e-002, 1.431581e-002, 1.467376e-002, 1.502842e-002, -1.538847e-002, 1.571083e-002, 1.603173e-002, 1.634546e-002, -1.666260e-002, 1.699210e-002, 1.731944e-002, 1.763995e-002, -1.796294e-002, 1.828430e-002, 1.861413e-002, 1.891148e-002, -1.918801e-002, 1.946620e-002, 1.974920e-002, 2.003498e-002, -2.032269e-002, 2.060757e-002, 2.089902e-002, 2.118249e-002, -2.147054e-002, 2.173628e-002, 2.195179e-002, 2.217109e-002, -2.239403e-002, 2.262547e-002, 2.286194e-002, 2.309923e-002, -2.334493e-002, 2.358237e-002, 2.381620e-002, 2.404114e-002, -2.418968e-002, 2.434190e-002, 2.449639e-002, 2.466359e-002, -2.486284e-002, 2.507680e-002, 2.528531e-002, 2.549299e-002, -2.570084e-002, 2.591056e-002, 2.604882e-002, 2.618800e-002, -2.632532e-002, 2.646491e-002, 2.661001e-002, 2.675594e-002, -2.688870e-002, 2.701337e-002, 2.714179e-002, 2.728166e-002, -2.736228e-002, 2.740784e-002, 2.745358e-002, 2.750195e-002, -2.755637e-002, 2.761526e-002, 2.766599e-002, 2.769690e-002, -2.771925e-002, 2.774145e-002, 2.772148e-002, 2.763421e-002, -2.754580e-002, 2.746702e-002, 2.739504e-002, 2.732600e-002, -2.725186e-002, 2.716142e-002, 2.704823e-002, 2.692614e-002, -2.677919e-002, 2.652559e-002, 2.627190e-002, 2.602732e-002, -2.578227e-002, 2.553829e-002, 2.530127e-002, 2.508215e-002, -2.485323e-002, 2.461848e-002, 2.439795e-002, 2.406492e-002, -2.372369e-002, 2.338799e-002, 2.305694e-002, 2.273056e-002, -2.240680e-002, 2.208303e-002, 2.175864e-002, 2.141452e-002, -2.106455e-002, 2.061398e-002, 2.012728e-002, 1.964071e-002, -1.915317e-002, 1.865905e-002, 1.816798e-002, 1.769839e-002, -1.722646e-002, 1.673997e-002, 1.620857e-002, 1.559720e-002, -1.489314e-002, 1.418567e-002, 1.347678e-002, 1.278654e-002, -1.209855e-002, 1.142005e-002, 1.073145e-002, 1.004071e-002, -9.344574e-003, 8.571980e-003, 7.670828e-003, 6.788875e-003, -5.927109e-003, 5.068001e-003, 4.212475e-003, 3.360459e-003, -2.513387e-003, 1.670471e-003, 8.330329e-004, -3.150190e-017 -}, -{ -0.000000e+000, 2.616309e-001, 2.187042e-001, 1.748157e-001, -1.417391e-001, 1.150747e-001, 8.688658e-002, 6.075912e-002, -3.646030e-002, 1.351882e-002, -8.209922e-003, -2.886362e-002, --4.766997e-002, -6.452617e-002, -7.749161e-002, -8.697334e-002, --9.373771e-002, -9.940247e-002, -1.046593e-001, -1.083829e-001, --1.105832e-001, -1.117065e-001, -1.115141e-001, -1.105643e-001, --1.090158e-001, -1.067233e-001, -1.030868e-001, -9.781883e-002, --9.039628e-002, -8.085284e-002, -6.991689e-002, -5.785499e-002, --4.543213e-002, -3.286415e-002, -2.012794e-002, -7.606798e-003, -4.347646e-003, 1.524646e-002, 2.574387e-002, 3.608154e-002, -4.608898e-002, 5.483372e-002, 6.345248e-002, 7.156473e-002, -7.881243e-002, 8.527163e-002, 9.114060e-002, 9.636036e-002, -1.003114e-001, 1.040243e-001, 1.070431e-001, 1.086666e-001, -1.093005e-001, 1.084687e-001, 1.065810e-001, 1.037056e-001, -1.000408e-001, 9.550326e-002, 9.029829e-002, 8.517134e-002, -7.922808e-002, 7.320034e-002, 6.681099e-002, 5.971452e-002, -5.216704e-002, 4.456903e-002, 3.666625e-002, 2.850437e-002, -2.035880e-002, 1.231888e-002, 4.545512e-003, -3.166212e-003, --1.053504e-002, -1.769757e-002, -2.434263e-002, -3.059878e-002, --3.629133e-002, -4.149259e-002, -4.613754e-002, -5.029473e-002, --5.400154e-002, -5.679247e-002, -5.925371e-002, -6.098967e-002, --6.227302e-002, -6.300515e-002, -6.324049e-002, -6.323603e-002, --6.252300e-002, -6.155722e-002, -6.043528e-002, -5.889734e-002, --5.721450e-002, -5.529738e-002, -5.307543e-002, -5.065763e-002, --4.849789e-002, -4.620116e-002, -4.371069e-002, -4.139970e-002, --3.915118e-002, -3.688204e-002, -3.472676e-002, -3.240213e-002, --3.007064e-002, -2.801528e-002, -2.602072e-002, -2.412263e-002, --2.228238e-002, -2.071468e-002, -1.904888e-002, -1.755147e-002, --1.614635e-002, -1.473501e-002, -1.328342e-002, -1.202087e-002, --1.066777e-002, -9.281785e-003, -7.742223e-003, -5.960808e-003, --4.257935e-003, -2.422493e-003, -3.195274e-004, 1.796373e-003, -3.828922e-003, 5.601601e-003, 7.425616e-003, 9.215458e-003, -1.106508e-002, 1.283224e-002, 1.448997e-002, 1.617500e-002, -1.783639e-002, 1.944480e-002, 2.086413e-002, 2.222284e-002, -2.349670e-002, 2.486960e-002, 2.604883e-002, 2.717056e-002, -2.830369e-002, 2.933500e-002, 3.015436e-002, 3.085847e-002, -3.161770e-002, 3.220586e-002, 3.279920e-002, 3.342561e-002, -3.381156e-002, 3.412947e-002, 3.437861e-002, 3.457182e-002, -3.473040e-002, 3.475502e-002, 3.469529e-002, 3.466792e-002, -3.451209e-002, 3.423636e-002, 3.394094e-002, 3.356073e-002, -3.308630e-002, 3.266903e-002, 3.204367e-002, 3.120684e-002, -3.039136e-002, 2.953746e-002, 2.864049e-002, 2.758818e-002, -2.647184e-002, 2.538506e-002, 2.434684e-002, 2.332702e-002, -2.209125e-002, 2.085895e-002, 1.967308e-002, 1.858240e-002, -1.731540e-002, 1.599940e-002, 1.474620e-002, 1.357118e-002, -1.207677e-002, 1.065390e-002, 9.352745e-003, 7.894652e-003, -6.400186e-003, 4.870625e-003, 3.202870e-003, 1.529805e-003, -3.449278e-006, -1.866329e-003, -3.732043e-003, -5.676562e-003, --7.703822e-003, -9.642500e-003, -1.182382e-002, -1.392094e-002, --1.620490e-002, -1.833710e-002, -2.043938e-002, -2.239789e-002, --2.430849e-002, -2.611203e-002, -2.794738e-002, -2.976166e-002, --3.143381e-002, -3.301074e-002, -3.445158e-002, -3.590805e-002, --3.713095e-002, -3.831127e-002, -3.922851e-002, -4.008871e-002, --4.073530e-002, -4.131773e-002, -4.168150e-002, -4.192563e-002, --4.203134e-002, -4.201026e-002, -4.200734e-002, -4.172996e-002, --4.140786e-002, -4.095624e-002, -4.035322e-002, -3.973137e-002, --3.894405e-002, -3.810126e-002, -3.720283e-002, -3.614239e-002, --3.504046e-002, -3.396816e-002, -3.271543e-002, -3.142777e-002, --3.012944e-002, -2.872402e-002, -2.730413e-002, -2.590909e-002, --2.447171e-002, -2.291314e-002, -2.133224e-002, -1.975593e-002, --1.819254e-002, -1.656962e-002, -1.503513e-002, -1.345689e-002, --1.174644e-002, -1.005234e-002, -8.510246e-003, -6.953671e-003, --5.323991e-003, -3.747955e-003, -2.126349e-003, -4.840920e-004, -1.115175e-003, 2.650654e-003, 4.186460e-003, 5.705632e-003, -7.272811e-003, 8.887157e-003, 1.040112e-002, 1.180232e-002, -1.329916e-002, 1.484239e-002, 1.635634e-002, 1.782388e-002, -1.928053e-002, 2.064900e-002, 2.197776e-002, 2.329312e-002, -2.455140e-002, 2.580663e-002, 2.695870e-002, 2.789678e-002, -2.865565e-002, 2.929528e-002, 2.991832e-002, 3.044219e-002, -3.087617e-002, 3.118649e-002, 3.134317e-002, 3.137674e-002, -3.132466e-002, 3.118910e-002, 3.101798e-002, 3.079929e-002, -3.053619e-002, 3.018039e-002, 2.966197e-002, 2.905223e-002, -2.836924e-002, 2.769439e-002, 2.701125e-002, 2.629913e-002, -2.558599e-002, 2.471208e-002, 2.374464e-002, 2.282130e-002, -2.183831e-002, 2.084821e-002, 1.984954e-002, 1.884930e-002, -1.792678e-002, 1.688622e-002, 1.582119e-002, 1.470698e-002, -1.365960e-002, 1.254829e-002, 1.146662e-002, 1.043918e-002, -9.432422e-003, 8.408720e-003, 7.361661e-003, 6.296192e-003, -5.237162e-003, 4.297232e-003, 3.340338e-003, 2.401789e-003, -1.485533e-003, 6.973520e-004, -9.765721e-005, -7.986929e-004, --1.457459e-003, -2.051026e-003, -2.508999e-003, -2.823115e-003, --3.085576e-003, -3.301244e-003, -3.532309e-003, -3.759468e-003, --3.939279e-003, -4.105624e-003, -4.257060e-003, -4.402302e-003, --4.524387e-003, -4.614262e-003, -4.709395e-003, -4.766498e-003, --4.789093e-003, -4.838223e-003, -4.927303e-003, -5.075132e-003, --5.237850e-003, -5.343672e-003, -5.369815e-003, -5.385244e-003, --5.385448e-003, -5.421267e-003, -5.451886e-003, -5.448310e-003, --5.548790e-003, -5.637334e-003, -5.718407e-003, -5.754008e-003, --5.770676e-003, -5.760104e-003, -5.781192e-003, -5.772122e-003, --5.747847e-003, -5.744699e-003, -5.664803e-003, -5.564198e-003, --5.508380e-003, -5.504042e-003, -5.430905e-003, -5.305196e-003, --5.246689e-003, -5.252686e-003, -5.248574e-003, -5.177898e-003, --5.032330e-003, -4.853154e-003, -4.721768e-003, -4.628169e-003, --4.584288e-003, -4.565798e-003, -4.615826e-003, -4.702669e-003, --4.776684e-003, -4.843721e-003, -4.803575e-003, -4.784674e-003, --4.752776e-003, -4.745525e-003, -4.743236e-003, -4.811438e-003, --4.950630e-003, -5.125605e-003, -5.270802e-003, -5.411600e-003, --5.591552e-003, -5.763395e-003, -5.915234e-003, -6.008405e-003, --6.098120e-003, -6.184349e-003, -6.332087e-003, -6.521344e-003, --6.718278e-003, -6.965978e-003, -7.265330e-003, -7.524359e-003, --7.761833e-003, -7.987428e-003, -8.184914e-003, -8.384649e-003, --8.693130e-003, -9.019203e-003, -9.372612e-003, -9.725989e-003, --1.009062e-002, -1.043568e-002, -1.076088e-002, -1.102721e-002, --1.128950e-002, -1.158322e-002, -1.188324e-002, -1.213739e-002, --1.237289e-002, -1.259527e-002, -1.282652e-002, -1.311049e-002, --1.338502e-002, -1.361028e-002, -1.380472e-002, -1.397776e-002, --1.408034e-002, -1.412290e-002, -1.413199e-002, -1.408977e-002, --1.400033e-002, -1.391448e-002, -1.383656e-002, -1.370991e-002, --1.354660e-002, -1.330013e-002, -1.299053e-002, -1.265488e-002, --1.235031e-002, -1.202417e-002, -1.166323e-002, -1.129266e-002, --1.087387e-002, -1.039377e-002, -9.934663e-003, -9.424126e-003, --8.834887e-003, -8.230009e-003, -7.702270e-003, -7.204794e-003, --6.682111e-003, -6.144585e-003, -5.587094e-003, -5.002825e-003, --4.375693e-003, -3.722229e-003, -3.089703e-003, -2.465468e-003, --1.800777e-003, -1.116030e-003, -4.498091e-004, 2.248057e-004, -9.706666e-004, 1.733746e-003, 2.505235e-003, 3.348760e-003, -4.171017e-003, 4.942018e-003, 5.728039e-003, 6.524442e-003, -7.318125e-003, 8.073822e-003, 8.783564e-003, 9.509737e-003, -1.026816e-002, 1.098915e-002, 1.173011e-002, 1.247034e-002, -1.322201e-002, 1.401679e-002, 1.480660e-002, 1.558687e-002, -1.634232e-002, 1.709538e-002, 1.781741e-002, 1.846771e-002, -1.902315e-002, 1.955720e-002, 2.007976e-002, 2.060608e-002, -2.113101e-002, 2.166041e-002, 2.218780e-002, 2.270317e-002, -2.316922e-002, 2.362090e-002, 2.403324e-002, 2.447493e-002, -2.488595e-002, 2.530160e-002, 2.572022e-002, 2.605959e-002, -2.633706e-002, 2.658410e-002, 2.679599e-002, 2.698466e-002, -2.715546e-002, 2.726182e-002, 2.731446e-002, 2.730675e-002, -2.731138e-002, 2.728518e-002, 2.726644e-002, 2.723452e-002, -2.717142e-002, 2.706998e-002, 2.695426e-002, 2.684809e-002, -2.668433e-002, 2.647758e-002, 2.625914e-002, 2.602748e-002, -2.578017e-002, 2.552078e-002, 2.526091e-002, 2.502719e-002, -2.479559e-002, 2.456060e-002, 2.428876e-002, 2.403608e-002, -2.376839e-002, 2.350502e-002, 2.320176e-002, 2.289762e-002, -2.259429e-002, 2.229809e-002, 2.198899e-002, 2.168862e-002, -2.138450e-002, 2.108281e-002, 2.078204e-002, 2.046311e-002, -2.010149e-002, 1.971926e-002, 1.933504e-002, 1.894218e-002, -1.851907e-002, 1.805759e-002, 1.757056e-002, 1.705722e-002, -1.650379e-002, 1.592092e-002, 1.531940e-002, 1.471146e-002, -1.408378e-002, 1.343434e-002, 1.271643e-002, 1.194604e-002, -1.115917e-002, 1.039148e-002, 9.613897e-003, 8.793899e-003, -7.978010e-003, 7.123317e-003, 6.245877e-003, 5.370102e-003, -4.476524e-003, 3.576845e-003, 2.676305e-003, 1.758402e-003, -8.446902e-004, -7.110065e-005, -9.733178e-004, -1.869164e-003, --2.762325e-003, -3.658872e-003, -4.569332e-003, -5.511076e-003, --6.478237e-003, -7.459028e-003, -8.443705e-003, -9.467735e-003, --1.049145e-002, -1.153028e-002, -1.258350e-002, -1.364316e-002, --1.469400e-002, -1.571618e-002, -1.671194e-002, -1.770642e-002, --1.864553e-002, -1.957009e-002, -2.048951e-002, -2.141449e-002, --2.232687e-002, -2.323075e-002, -2.407870e-002, -2.492980e-002, --2.579019e-002, -2.664221e-002, -2.742733e-002, -2.814326e-002, --2.885675e-002, -2.955288e-002, -3.022473e-002, -3.083768e-002, --3.140972e-002, -3.195704e-002, -3.249520e-002, -3.304792e-002, --3.357974e-002, -3.409454e-002, -3.455706e-002, -3.494506e-002, --3.532302e-002, -3.568962e-002, -3.604982e-002, -3.643438e-002, --3.682183e-002, -3.719656e-002, -3.755569e-002, -3.785514e-002, --3.814662e-002, -3.845371e-002, -3.875486e-002, -3.902183e-002, --3.926361e-002, -3.948225e-002, -3.958732e-002, -3.967712e-002, --3.973963e-002, -3.975492e-002, -3.971670e-002, -3.965357e-002, --3.953955e-002, -3.939289e-002, -3.920699e-002, -3.899348e-002, --3.875126e-002, -3.850175e-002, -3.820469e-002, -3.785769e-002, --3.749264e-002, -3.710484e-002, -3.669338e-002, -3.621590e-002, --3.569464e-002, -3.508582e-002, -3.439865e-002, -3.368438e-002, --3.295247e-002, -3.221960e-002, -3.151954e-002, -3.079015e-002, --3.003636e-002, -2.925127e-002, -2.845931e-002, -2.767171e-002, --2.687716e-002, -2.608908e-002, -2.531272e-002, -2.452898e-002, --2.376143e-002, -2.298945e-002, -2.221931e-002, -2.149787e-002, --2.078149e-002, -2.002688e-002, -1.924975e-002, -1.846294e-002, --1.766731e-002, -1.688026e-002, -1.609423e-002, -1.532322e-002, --1.454264e-002, -1.379617e-002, -1.300885e-002, -1.222838e-002, --1.145498e-002, -1.067497e-002, -9.891558e-003, -9.175706e-003, --8.465063e-003, -7.750555e-003, -7.045032e-003, -6.335894e-003, --5.622342e-003, -4.913743e-003, -4.320842e-003, -3.798131e-003, --3.269022e-003, -2.734525e-003, -2.208693e-003, -1.682748e-003, --1.143876e-003, -6.081016e-004, -6.630030e-005, 4.881984e-004, -1.053882e-003, 1.605480e-003, 2.076789e-003, 2.544047e-003, -3.025428e-003, 3.513402e-003, 3.978367e-003, 4.386814e-003, -4.716371e-003, 5.014728e-003, 5.334329e-003, 5.662562e-003, -6.058405e-003, 6.446699e-003, 6.840161e-003, 7.241860e-003, -7.674519e-003, 8.123500e-003, 8.575565e-003, 9.019672e-003, -9.450103e-003, 9.876752e-003, 1.030647e-002, 1.072961e-002, -1.114144e-002, 1.154630e-002, 1.184170e-002, 1.207532e-002, -1.233833e-002, 1.261466e-002, 1.287894e-002, 1.308786e-002, -1.327828e-002, 1.348041e-002, 1.369956e-002, 1.392951e-002, -1.416685e-002, 1.440413e-002, 1.462540e-002, 1.484389e-002, -1.508447e-002, 1.532451e-002, 1.558096e-002, 1.586768e-002, -1.615642e-002, 1.643420e-002, 1.669900e-002, 1.694894e-002, -1.719108e-002, 1.743280e-002, 1.765179e-002, 1.781542e-002, -1.796699e-002, 1.811802e-002, 1.826513e-002, 1.841026e-002, -1.854011e-002, 1.864458e-002, 1.871480e-002, 1.879340e-002, -1.889738e-002, 1.901934e-002, 1.908576e-002, 1.916819e-002, -1.927523e-002, 1.940246e-002, 1.953514e-002, 1.967653e-002, -1.981623e-002, 1.992489e-002, 2.004373e-002, 2.018901e-002, -2.033955e-002, 2.048860e-002, 2.066218e-002, 2.083185e-002, -2.101261e-002, 2.120715e-002, 2.140490e-002, 2.160185e-002, -2.179537e-002, 2.195982e-002, 2.207417e-002, 2.217943e-002, -2.227547e-002, 2.234468e-002, 2.240482e-002, 2.244652e-002, -2.246348e-002, 2.246817e-002, 2.247667e-002, 2.247706e-002, -2.246136e-002, 2.243421e-002, 2.238140e-002, 2.228066e-002, -2.217668e-002, 2.207545e-002, 2.196340e-002, 2.182217e-002, -2.167603e-002, 2.155638e-002, 2.143114e-002, 2.127399e-002, -2.111776e-002, 2.097319e-002, 2.083404e-002, 2.069418e-002, -2.056094e-002, 2.042159e-002, 2.030101e-002, 2.019684e-002, -2.009938e-002, 2.000351e-002, 1.990383e-002, 1.979381e-002, -1.968590e-002, 1.957534e-002, 1.944928e-002, 1.931353e-002, -1.916016e-002, 1.895429e-002, 1.870555e-002, 1.844438e-002, -1.818109e-002, 1.789508e-002, 1.761845e-002, 1.732106e-002, -1.700858e-002, 1.669065e-002, 1.636746e-002, 1.604298e-002, -1.572119e-002, 1.540721e-002, 1.509434e-002, 1.477797e-002, -1.445579e-002, 1.413619e-002, 1.380992e-002, 1.347754e-002, -1.316513e-002, 1.287765e-002, 1.259116e-002, 1.230700e-002, -1.205244e-002, 1.179915e-002, 1.153887e-002, 1.127258e-002, -1.099731e-002, 1.072762e-002, 1.045186e-002, 1.017873e-002, -9.911024e-003, 9.643432e-003, 9.385897e-003, 9.097465e-003, -8.796735e-003, 8.491115e-003, 8.169308e-003, 7.835788e-003, -7.498905e-003, 7.156931e-003, 6.798763e-003, 6.448502e-003, -6.112574e-003, 5.807285e-003, 5.512039e-003, 5.244423e-003, -4.983049e-003, 4.727585e-003, 4.475615e-003, 4.223985e-003, -3.986483e-003, 3.722121e-003, 3.478817e-003, 3.264948e-003, -3.058838e-003, 2.844829e-003, 2.621190e-003, 2.401502e-003, -2.173198e-003, 1.935061e-003, 1.684484e-003, 1.443250e-003, -1.192445e-003, 9.358708e-004, 6.640067e-004, 3.911635e-004, -8.753651e-005, -2.324088e-004, -5.587866e-004, -8.957589e-004, --1.240984e-003, -1.605898e-003, -1.969859e-003, -2.313585e-003, --2.669489e-003, -3.036341e-003, -3.412040e-003, -3.791887e-003, --4.186591e-003, -4.576267e-003, -4.961596e-003, -5.339523e-003, --5.699528e-003, -6.032103e-003, -6.384200e-003, -6.746542e-003, --7.111394e-003, -7.470422e-003, -7.830704e-003, -8.179860e-003, --8.525526e-003, -8.843227e-003, -9.150797e-003, -9.440520e-003, --9.699417e-003, -9.955033e-003, -1.021250e-002, -1.047285e-002, --1.073737e-002, -1.102376e-002, -1.132571e-002, -1.162909e-002, --1.192733e-002, -1.221872e-002, -1.248308e-002, -1.275180e-002, --1.302408e-002, -1.330674e-002, -1.361168e-002, -1.393081e-002, --1.425966e-002, -1.456352e-002, -1.486341e-002, -1.515424e-002, --1.538027e-002, -1.560887e-002, -1.584675e-002, -1.607153e-002, --1.631649e-002, -1.656848e-002, -1.680136e-002, -1.702400e-002, --1.724729e-002, -1.747449e-002, -1.765796e-002, -1.782648e-002, --1.798849e-002, -1.814933e-002, -1.832042e-002, -1.850273e-002, --1.868757e-002, -1.887861e-002, -1.907686e-002, -1.926864e-002, --1.942867e-002, -1.957442e-002, -1.973200e-002, -1.990341e-002, --2.009741e-002, -2.030249e-002, -2.051201e-002, -2.071748e-002, --2.091934e-002, -2.112629e-002, -2.131107e-002, -2.145359e-002, --2.159981e-002, -2.173789e-002, -2.187444e-002, -2.200218e-002, --2.213378e-002, -2.227790e-002, -2.239264e-002, -2.250166e-002, --2.260443e-002, -2.263148e-002, -2.266883e-002, -2.268151e-002, --2.268970e-002, -2.269930e-002, -2.271236e-002, -2.271743e-002, --2.268455e-002, -2.264106e-002, -2.259842e-002, -2.243410e-002, --2.224910e-002, -2.207414e-002, -2.191017e-002, -2.175289e-002, --2.159886e-002, -2.144884e-002, -2.129710e-002, -2.110407e-002, --2.090125e-002, -2.062630e-002, -2.029199e-002, -1.995701e-002, --1.960875e-002, -1.921918e-002, -1.883493e-002, -1.842686e-002, --1.801856e-002, -1.760148e-002, -1.713699e-002, -1.660590e-002, --1.599780e-002, -1.537926e-002, -1.475072e-002, -1.408667e-002, --1.340152e-002, -1.269229e-002, -1.193805e-002, -1.116873e-002, --1.040116e-002, -9.555318e-003, -8.611026e-003, -7.648784e-003, --6.666498e-003, -5.689759e-003, -4.719994e-003, -3.757357e-003, --2.804373e-003, -1.859909e-003, -9.256437e-004, -2.013541e-017 -}, -{ -0.000000e+000, 1.507455e-001, 1.207007e-001, 9.171221e-002, -6.179052e-002, 3.365747e-002, 1.332257e-002, -2.375174e-003, --1.649751e-002, -2.877970e-002, -3.811668e-002, -4.527494e-002, --5.011530e-002, -5.315954e-002, -5.572485e-002, -5.786769e-002, --5.867673e-002, -5.705429e-002, -5.326458e-002, -4.860399e-002, --4.401021e-002, -3.995093e-002, -3.576162e-002, -3.153032e-002, --2.650787e-002, -2.115455e-002, -1.580126e-002, -1.088052e-002, --6.794520e-003, -3.031228e-003, 7.699568e-004, 4.962568e-003, -8.936494e-003, 1.270241e-002, 1.686004e-002, 2.118074e-002, -2.557811e-002, 2.970777e-002, 3.409884e-002, 3.867944e-002, -4.342115e-002, 4.733632e-002, 5.110933e-002, 5.475782e-002, -5.758125e-002, 5.941473e-002, 6.072315e-002, 6.154284e-002, -6.129292e-002, 6.065663e-002, 5.912191e-002, 5.703610e-002, -5.489733e-002, 5.218830e-002, 4.934198e-002, 4.593269e-002, -4.193563e-002, 3.756416e-002, 3.264069e-002, 2.791803e-002, -2.265526e-002, 1.703786e-002, 1.112656e-002, 5.029451e-003, --8.870066e-004, -6.429659e-003, -1.209488e-002, -1.795477e-002, --2.389911e-002, -2.949066e-002, -3.445773e-002, -3.937190e-002, --4.413430e-002, -4.874777e-002, -5.267152e-002, -5.633061e-002, --5.965567e-002, -6.268070e-002, -6.514167e-002, -6.739588e-002, --6.926296e-002, -7.003671e-002, -7.108702e-002, -7.180639e-002, --7.177871e-002, -7.139445e-002, -7.100258e-002, -7.006411e-002, --6.861398e-002, -6.684054e-002, -6.465894e-002, -6.219882e-002, --5.890686e-002, -5.571682e-002, -5.216535e-002, -4.807539e-002, --4.392300e-002, -3.944633e-002, -3.483985e-002, -3.003443e-002, --2.491446e-002, -1.948053e-002, -1.414013e-002, -8.453057e-003, --2.650175e-003, 2.943184e-003, 8.558212e-003, 1.401360e-002, -1.961674e-002, 2.476884e-002, 2.968361e-002, 3.432279e-002, -3.893293e-002, 4.288325e-002, 4.688597e-002, 5.040807e-002, -5.363671e-002, 5.646457e-002, 5.877778e-002, 6.074455e-002, -6.207494e-002, 6.318869e-002, 6.393809e-002, 6.410510e-002, -6.427001e-002, 6.421797e-002, 6.396191e-002, 6.349514e-002, -6.263445e-002, 6.143830e-002, 6.012655e-002, 5.865263e-002, -5.692138e-002, 5.520538e-002, 5.372955e-002, 5.203802e-002, -5.041504e-002, 4.895197e-002, 4.693858e-002, 4.490112e-002, -4.297358e-002, 4.090379e-002, 3.889292e-002, 3.695421e-002, -3.483684e-002, 3.238920e-002, 2.995791e-002, 2.748953e-002, -2.486904e-002, 2.199338e-002, 1.923505e-002, 1.644573e-002, -1.343826e-002, 1.006541e-002, 6.671872e-003, 3.071788e-003, --1.568400e-004, -3.428808e-003, -6.719075e-003, -1.009985e-002, --1.350290e-002, -1.699157e-002, -2.015547e-002, -2.321278e-002, --2.628419e-002, -2.925985e-002, -3.237629e-002, -3.517825e-002, --3.779963e-002, -4.038895e-002, -4.299644e-002, -4.581103e-002, --4.802005e-002, -5.008550e-002, -5.221627e-002, -5.448365e-002, --5.639040e-002, -5.801093e-002, -5.961495e-002, -6.118521e-002, --6.217152e-002, -6.314675e-002, -6.408047e-002, -6.443810e-002, --6.448580e-002, -6.461142e-002, -6.419266e-002, -6.336891e-002, --6.252112e-002, -6.082518e-002, -5.911583e-002, -5.709120e-002, --5.450672e-002, -5.171306e-002, -4.822821e-002, -4.474604e-002, --4.082495e-002, -3.690032e-002, -3.247575e-002, -2.803771e-002, --2.339857e-002, -1.884295e-002, -1.422596e-002, -9.614114e-003, --5.041266e-003, -4.446784e-004, 3.949733e-003, 8.251256e-003, -1.222173e-002, 1.612896e-002, 1.961673e-002, 2.321542e-002, -2.630637e-002, 2.935242e-002, 3.198098e-002, 3.446069e-002, -3.681257e-002, 3.883299e-002, 4.089755e-002, 4.232988e-002, -4.373358e-002, 4.497128e-002, 4.589218e-002, 4.695297e-002, -4.769053e-002, 4.810847e-002, 4.864462e-002, 4.883958e-002, -4.899831e-002, 4.918537e-002, 4.913879e-002, 4.890685e-002, -4.866293e-002, 4.829285e-002, 4.774044e-002, 4.718648e-002, -4.667514e-002, 4.581629e-002, 4.492544e-002, 4.411025e-002, -4.306915e-002, 4.196890e-002, 4.090352e-002, 3.991363e-002, -3.866343e-002, 3.735754e-002, 3.601406e-002, 3.474194e-002, -3.316444e-002, 3.158613e-002, 3.010587e-002, 2.853235e-002, -2.672790e-002, 2.493368e-002, 2.317910e-002, 2.145095e-002, -1.956620e-002, 1.767439e-002, 1.581577e-002, 1.394612e-002, -1.201088e-002, 1.008587e-002, 8.221889e-003, 6.389277e-003, -4.629087e-003, 2.666268e-003, 7.295213e-004, -1.079513e-003, --2.819868e-003, -4.586865e-003, -6.284481e-003, -7.956978e-003, --9.595485e-003, -1.106582e-002, -1.253188e-002, -1.394419e-002, --1.520342e-002, -1.642875e-002, -1.756485e-002, -1.876650e-002, --1.982803e-002, -2.077306e-002, -2.160304e-002, -2.234521e-002, --2.313108e-002, -2.382931e-002, -2.448592e-002, -2.504228e-002, --2.553018e-002, -2.606546e-002, -2.660545e-002, -2.706670e-002, --2.741439e-002, -2.767432e-002, -2.801954e-002, -2.831347e-002, --2.861195e-002, -2.887123e-002, -2.905667e-002, -2.911488e-002, --2.920747e-002, -2.926428e-002, -2.934518e-002, -2.945847e-002, --2.944722e-002, -2.939849e-002, -2.937279e-002, -2.928941e-002, --2.914309e-002, -2.899382e-002, -2.901849e-002, -2.898158e-002, --2.890917e-002, -2.873517e-002, -2.853805e-002, -2.831494e-002, --2.809450e-002, -2.802744e-002, -2.796440e-002, -2.783785e-002, --2.766449e-002, -2.748631e-002, -2.720883e-002, -2.697823e-002, --2.691683e-002, -2.689137e-002, -2.686864e-002, -2.682734e-002, --2.667790e-002, -2.654989e-002, -2.640144e-002, -2.616097e-002, --2.600905e-002, -2.580411e-002, -2.551551e-002, -2.526653e-002, --2.508277e-002, -2.477066e-002, -2.440104e-002, -2.401076e-002, --2.361477e-002, -2.314140e-002, -2.265344e-002, -2.208807e-002, --2.146436e-002, -2.084392e-002, -2.004937e-002, -1.918571e-002, --1.835898e-002, -1.768489e-002, -1.696431e-002, -1.613480e-002, --1.519525e-002, -1.418729e-002, -1.319371e-002, -1.223807e-002, --1.129099e-002, -1.027270e-002, -9.288805e-003, -8.308808e-003, --7.309813e-003, -6.267727e-003, -5.206320e-003, -4.083539e-003, --3.084312e-003, -2.086245e-003, -1.050033e-003, 4.998547e-005, -1.066313e-003, 2.021410e-003, 3.017337e-003, 4.026281e-003, -5.044716e-003, 6.124675e-003, 7.125214e-003, 8.115053e-003, -9.037940e-003, 1.002129e-002, 1.095755e-002, 1.192147e-002, -1.291961e-002, 1.395327e-002, 1.501197e-002, 1.597175e-002, -1.683475e-002, 1.765669e-002, 1.855457e-002, 1.946130e-002, -2.038072e-002, 2.122765e-002, 2.213247e-002, 2.296523e-002, -2.381279e-002, 2.462380e-002, 2.540353e-002, 2.615253e-002, -2.682719e-002, 2.750333e-002, 2.822515e-002, 2.887570e-002, -2.940916e-002, 2.993575e-002, 3.051099e-002, 3.111243e-002, -3.161910e-002, 3.209820e-002, 3.253353e-002, 3.279163e-002, -3.307110e-002, 3.333781e-002, 3.358343e-002, 3.384312e-002, -3.408320e-002, 3.431763e-002, 3.453719e-002, 3.471092e-002, -3.476957e-002, 3.482974e-002, 3.488886e-002, 3.487440e-002, -3.482113e-002, 3.477919e-002, 3.474281e-002, 3.470721e-002, -3.455085e-002, 3.435650e-002, 3.414889e-002, 3.386373e-002, -3.353237e-002, 3.318449e-002, 3.281683e-002, 3.238839e-002, -3.186102e-002, 3.127100e-002, 3.061074e-002, 2.993174e-002, -2.919960e-002, 2.844869e-002, 2.771817e-002, 2.695492e-002, -2.618520e-002, 2.542574e-002, 2.466143e-002, 2.379740e-002, -2.295379e-002, 2.204366e-002, 2.113896e-002, 2.022909e-002, -1.933885e-002, 1.845589e-002, 1.759167e-002, 1.672857e-002, -1.583019e-002, 1.481065e-002, 1.376404e-002, 1.279384e-002, -1.186478e-002, 1.094294e-002, 1.001004e-002, 9.012174e-003, -8.082517e-003, 7.169932e-003, 6.256297e-003, 5.244143e-003, -4.210156e-003, 3.173984e-003, 2.151424e-003, 1.129065e-003, -7.185728e-005, -9.537177e-004, -1.978641e-003, -2.996118e-003, --3.948044e-003, -4.944983e-003, -6.060654e-003, -7.173239e-003, --8.263116e-003, -9.291362e-003, -1.028351e-002, -1.125762e-002, --1.220515e-002, -1.312572e-002, -1.404869e-002, -1.498295e-002, --1.589178e-002, -1.687253e-002, -1.787364e-002, -1.883711e-002, --1.978343e-002, -2.068520e-002, -2.153163e-002, -2.236686e-002, --2.320237e-002, -2.401354e-002, -2.479700e-002, -2.548873e-002, --2.615126e-002, -2.685416e-002, -2.757484e-002, -2.829151e-002, --2.896108e-002, -2.956827e-002, -3.011979e-002, -3.064041e-002, --3.115683e-002, -3.168951e-002, -3.216816e-002, -3.260952e-002, --3.298530e-002, -3.333476e-002, -3.373130e-002, -3.407092e-002, --3.435486e-002, -3.461992e-002, -3.475103e-002, -3.483662e-002, --3.491736e-002, -3.496680e-002, -3.497289e-002, -3.496535e-002, --3.493678e-002, -3.491557e-002, -3.492174e-002, -3.493524e-002, --3.496301e-002, -3.497345e-002, -3.487417e-002, -3.473014e-002, --3.451913e-002, -3.426984e-002, -3.402763e-002, -3.377191e-002, --3.348873e-002, -3.317374e-002, -3.282796e-002, -3.245477e-002, --3.208893e-002, -3.170444e-002, -3.129554e-002, -3.091144e-002, --3.055035e-002, -3.014052e-002, -2.970653e-002, -2.924689e-002, --2.877350e-002, -2.827706e-002, -2.772609e-002, -2.714500e-002, --2.645422e-002, -2.567297e-002, -2.487842e-002, -2.406899e-002, --2.328278e-002, -2.252632e-002, -2.179114e-002, -2.107730e-002, --2.035702e-002, -1.959177e-002, -1.879094e-002, -1.792391e-002, --1.702358e-002, -1.612899e-002, -1.524853e-002, -1.435823e-002, --1.344119e-002, -1.249419e-002, -1.152394e-002, -1.054476e-002, --9.584596e-003, -8.620266e-003, -7.604014e-003, -6.535475e-003, --5.434275e-003, -4.338105e-003, -3.207219e-003, -2.091769e-003, --9.785595e-004, 1.698964e-004, 1.262579e-003, 2.340592e-003, -3.446993e-003, 4.561649e-003, 5.696582e-003, 6.855829e-003, -8.053227e-003, 9.199333e-003, 1.032752e-002, 1.140867e-002, -1.234371e-002, 1.330707e-002, 1.428309e-002, 1.526401e-002, -1.625672e-002, 1.723995e-002, 1.825994e-002, 1.929007e-002, -2.033209e-002, 2.140126e-002, 2.221302e-002, 2.293528e-002, -2.359944e-002, 2.426900e-002, 2.493809e-002, 2.555341e-002, -2.617221e-002, 2.682864e-002, 2.750845e-002, 2.817458e-002, -2.884649e-002, 2.954125e-002, 3.009369e-002, 3.042899e-002, -3.079001e-002, 3.116041e-002, 3.155298e-002, 3.195887e-002, -3.237526e-002, 3.281738e-002, 3.328424e-002, 3.380567e-002, -3.435142e-002, 3.490482e-002, 3.530946e-002, 3.568174e-002, -3.608347e-002, 3.641704e-002, 3.647525e-002, 3.654460e-002, -3.662770e-002, 3.670958e-002, 3.682143e-002, 3.695622e-002, -3.711807e-002, 3.727668e-002, 3.743557e-002, 3.758555e-002, -3.771415e-002, 3.785510e-002, 3.801110e-002, 3.816554e-002, -3.827483e-002, 3.837108e-002, 3.843883e-002, 3.841973e-002, -3.841019e-002, 3.830109e-002, 3.801662e-002, 3.771771e-002, -3.738390e-002, 3.702411e-002, 3.661568e-002, 3.618336e-002, -3.575402e-002, 3.532903e-002, 3.489354e-002, 3.446403e-002, -3.404808e-002, 3.361245e-002, 3.317832e-002, 3.275886e-002, -3.234730e-002, 3.196426e-002, 3.157168e-002, 3.114090e-002, -3.064398e-002, 3.008919e-002, 2.957976e-002, 2.907225e-002, -2.856804e-002, 2.805755e-002, 2.752631e-002, 2.694661e-002, -2.612992e-002, 2.530734e-002, 2.452570e-002, 2.372374e-002, -2.292920e-002, 2.213835e-002, 2.135455e-002, 2.052935e-002, -1.970821e-002, 1.890817e-002, 1.813941e-002, 1.741154e-002, -1.669089e-002, 1.597296e-002, 1.528024e-002, 1.456072e-002, -1.383897e-002, 1.317138e-002, 1.255615e-002, 1.196954e-002, -1.140584e-002, 1.083495e-002, 1.026730e-002, 9.715342e-003, -9.196408e-003, 8.719287e-003, 8.180861e-003, 7.649272e-003, -7.120772e-003, 6.596352e-003, 6.082540e-003, 5.603839e-003, -5.140709e-003, 4.708265e-003, 4.294533e-003, 3.880151e-003, -3.321137e-003, 2.743674e-003, 2.157028e-003, 1.568430e-003, -9.792462e-004, 3.859348e-004, -2.069354e-004, -8.029176e-004, --1.412156e-003, -1.998078e-003, -2.569707e-003, -3.126044e-003, --3.671261e-003, -4.197133e-003, -4.703609e-003, -5.169262e-003, --5.612812e-003, -6.041460e-003, -6.525097e-003, -7.024805e-003, --7.508268e-003, -7.966510e-003, -8.410419e-003, -8.841791e-003, --9.272304e-003, -9.702981e-003, -1.012522e-002, -1.054875e-002, --1.096610e-002, -1.136526e-002, -1.176295e-002, -1.218526e-002, --1.261010e-002, -1.304709e-002, -1.343999e-002, -1.380190e-002, --1.415720e-002, -1.450148e-002, -1.481715e-002, -1.511271e-002, --1.539437e-002, -1.567916e-002, -1.594710e-002, -1.621766e-002, --1.649069e-002, -1.677384e-002, -1.706291e-002, -1.735078e-002, --1.778201e-002, -1.827519e-002, -1.877452e-002, -1.926988e-002, --1.975993e-002, -2.023905e-002, -2.070333e-002, -2.115199e-002, --2.159680e-002, -2.201455e-002, -2.245062e-002, -2.293145e-002, --2.341226e-002, -2.390530e-002, -2.439455e-002, -2.489573e-002, --2.538210e-002, -2.585069e-002, -2.631210e-002, -2.676656e-002, --2.721564e-002, -2.764699e-002, -2.804987e-002, -2.843253e-002, --2.879446e-002, -2.914842e-002, -2.948872e-002, -2.981655e-002, --3.011373e-002, -3.039260e-002, -3.063766e-002, -3.084019e-002, --3.105178e-002, -3.124592e-002, -3.140965e-002, -3.152888e-002, --3.162988e-002, -3.171277e-002, -3.178741e-002, -3.185242e-002, --3.188986e-002, -3.193901e-002, -3.197612e-002, -3.201158e-002, --3.203229e-002, -3.204133e-002, -3.204453e-002, -3.203612e-002, --3.199997e-002, -3.197920e-002, -3.193995e-002, -3.188434e-002, --3.180956e-002, -3.173601e-002, -3.166591e-002, -3.159756e-002, --3.152534e-002, -3.142901e-002, -3.133299e-002, -3.122326e-002, --3.109537e-002, -3.094049e-002, -3.076455e-002, -3.057683e-002, --3.036474e-002, -3.013159e-002, -2.995462e-002, -2.975585e-002, --2.954206e-002, -2.931729e-002, -2.907734e-002, -2.882566e-002, --2.856925e-002, -2.836073e-002, -2.815740e-002, -2.792169e-002, --2.764472e-002, -2.734216e-002, -2.703747e-002, -2.672691e-002, --2.640106e-002, -2.605751e-002, -2.571478e-002, -2.537412e-002, --2.502843e-002, -2.465196e-002, -2.426497e-002, -2.389776e-002, --2.352990e-002, -2.313097e-002, -2.272339e-002, -2.230516e-002, --2.188208e-002, -2.143910e-002, -2.100070e-002, -2.057200e-002, --2.014454e-002, -1.970938e-002, -1.927460e-002, -1.884924e-002, --1.841703e-002, -1.796012e-002, -1.749917e-002, -1.699678e-002, --1.652079e-002, -1.605484e-002, -1.558911e-002, -1.516947e-002, --1.474583e-002, -1.431388e-002, -1.387555e-002, -1.343679e-002, --1.297288e-002, -1.245838e-002, -1.196012e-002, -1.151270e-002, --1.106075e-002, -1.060709e-002, -1.015104e-002, -9.682566e-003, --9.212216e-003, -8.731728e-003, -8.208278e-003, -7.643232e-003, --7.087355e-003, -6.578601e-003, -6.048960e-003, -5.477234e-003, --4.897702e-003, -4.299889e-003, -3.662780e-003, -3.001082e-003, --2.297310e-003, -1.584170e-003, -8.516245e-004, -1.952714e-004, -4.833759e-004, 1.183689e-003, 1.901444e-003, 2.626945e-003, -3.356069e-003, 4.121274e-003, 4.924593e-003, 5.701597e-003, -6.462709e-003, 7.150691e-003, 7.854372e-003, 8.555725e-003, -9.263497e-003, 9.983510e-003, 1.070692e-002, 1.144402e-002, -1.217113e-002, 1.284441e-002, 1.353203e-002, 1.415597e-002, -1.473274e-002, 1.531914e-002, 1.592020e-002, 1.653393e-002, -1.716095e-002, 1.779465e-002, 1.842422e-002, 1.903823e-002, -1.967016e-002, 2.027178e-002, 2.078086e-002, 2.131783e-002, -2.188602e-002, 2.246889e-002, 2.306096e-002, 2.366982e-002, -2.425589e-002, 2.485314e-002, 2.546142e-002, 2.606522e-002, -2.651181e-002, 2.697275e-002, 2.744145e-002, 2.792537e-002, -2.838825e-002, 2.886726e-002, 2.933813e-002, 2.978398e-002, -3.023047e-002, 3.068084e-002, 3.097320e-002, 3.124851e-002, -3.151979e-002, 3.179491e-002, 3.208364e-002, 3.238522e-002, -3.270960e-002, 3.304227e-002, 3.338576e-002, 3.376693e-002, -3.403786e-002, 3.424990e-002, 3.448659e-002, 3.475116e-002, -3.505739e-002, 3.539451e-002, 3.575027e-002, 3.611933e-002, -3.649073e-002, 3.688173e-002, 3.719951e-002, 3.738622e-002, -3.759411e-002, 3.784242e-002, 3.810564e-002, 3.838660e-002, -3.868005e-002, 3.898292e-002, 3.924536e-002, 3.950280e-002, -3.972558e-002, 3.973594e-002, 3.975274e-002, 3.978129e-002, -3.981270e-002, 3.984607e-002, 3.987135e-002, 3.986742e-002, -3.983345e-002, 3.977924e-002, 3.972687e-002, 3.939447e-002, -3.903504e-002, 3.868324e-002, 3.833726e-002, 3.799708e-002, -3.766243e-002, 3.733177e-002, 3.699997e-002, 3.663235e-002, -3.624944e-002, 3.564720e-002, 3.496800e-002, 3.429306e-002, -3.361142e-002, 3.290150e-002, 3.219412e-002, 3.149192e-002, -3.079306e-002, 3.008957e-002, 2.930615e-002, 2.835259e-002, -2.721108e-002, 2.606995e-002, 2.493019e-002, 2.379139e-002, -2.265073e-002, 2.153695e-002, 2.038014e-002, 1.920723e-002, -1.802808e-002, 1.663728e-002, 1.495627e-002, 1.327881e-002, -1.160589e-002, 9.935733e-003, 8.270407e-003, 6.608634e-003, -4.951101e-003, 3.297583e-003, 1.647433e-003, -6.357235e-017 -}, -{ -0.000000e+000, 4.681131e-002, 3.448546e-002, 2.787317e-002, -9.077404e-003, -1.252344e-002, -1.981374e-002, -2.022446e-002, --1.977085e-002, -1.690704e-002, -1.085543e-002, -3.675414e-003, -3.000490e-003, 6.717629e-003, 5.669027e-003, 2.072485e-003, --5.012936e-004, -1.170948e-003, -2.397314e-004, -7.352893e-005, --1.721719e-003, -5.648954e-003, -8.984094e-003, -1.196890e-002, --1.556017e-002, -1.883280e-002, -2.117636e-002, -2.178414e-002, --2.045620e-002, -1.774205e-002, -1.351744e-002, -8.063788e-003, --1.939619e-003, 4.290677e-003, 1.058171e-002, 1.655834e-002, -2.145392e-002, 2.635716e-002, 3.162410e-002, 3.651302e-002, -4.110853e-002, 4.530762e-002, 4.825638e-002, 5.006986e-002, -5.095183e-002, 5.020408e-002, 4.785951e-002, 4.428271e-002, -3.991452e-002, 3.477011e-002, 2.873794e-002, 2.134703e-002, -1.273631e-002, 3.663851e-003, -6.029854e-003, -1.575807e-002, --2.491389e-002, -3.361578e-002, -4.141220e-002, -4.798057e-002, --5.285234e-002, -5.599161e-002, -5.772764e-002, -5.855767e-002, --5.854105e-002, -5.801915e-002, -5.695607e-002, -5.533240e-002, --5.323196e-002, -5.069518e-002, -4.795854e-002, -4.473753e-002, --4.117124e-002, -3.724427e-002, -3.321496e-002, -2.912680e-002, --2.482910e-002, -2.033936e-002, -1.569094e-002, -1.062176e-002, --5.170073e-003, 3.437857e-004, 5.848103e-003, 1.125088e-002, -1.656631e-002, 2.169067e-002, 2.633630e-002, 3.086059e-002, -3.502068e-002, 3.873037e-002, 4.183588e-002, 4.451947e-002, -4.678120e-002, 4.859437e-002, 4.970678e-002, 5.023995e-002, -5.038301e-002, 4.983822e-002, 4.855151e-002, 4.718407e-002, -4.576427e-002, 4.390175e-002, 4.157071e-002, 3.880250e-002, -3.576013e-002, 3.229183e-002, 2.847238e-002, 2.440965e-002, -2.012665e-002, 1.559214e-002, 1.074161e-002, 5.951684e-003, -1.151384e-003, -3.585619e-003, -8.887757e-003, -1.384535e-002, --1.852657e-002, -2.288487e-002, -2.716061e-002, -3.125988e-002, --3.473129e-002, -3.773333e-002, -4.020690e-002, -4.265230e-002, --4.493803e-002, -4.666470e-002, -4.805004e-002, -4.896643e-002, --4.989895e-002, -5.036629e-002, -5.021192e-002, -4.963145e-002, --4.856864e-002, -4.738171e-002, -4.572717e-002, -4.331828e-002, --4.020556e-002, -3.658248e-002, -3.273865e-002, -2.844455e-002, --2.379064e-002, -1.869489e-002, -1.358453e-002, -8.443050e-003, --3.364645e-003, 1.860701e-003, 7.123102e-003, 1.238238e-002, -1.757942e-002, 2.234447e-002, 2.642148e-002, 3.020768e-002, -3.373309e-002, 3.683327e-002, 3.981301e-002, 4.254222e-002, -4.469727e-002, 4.665348e-002, 4.855023e-002, 5.049459e-002, -5.250306e-002, 5.419849e-002, 5.525308e-002, 5.609343e-002, -5.680514e-002, 5.758628e-002, 5.837509e-002, 5.863108e-002, -5.836131e-002, 5.798997e-002, 5.741974e-002, 5.695278e-002, -5.583392e-002, 5.458059e-002, 5.316203e-002, 5.172504e-002, -4.972813e-002, 4.758693e-002, 4.553395e-002, 4.351386e-002, -4.071082e-002, 3.784024e-002, 3.500919e-002, 3.172657e-002, -2.824462e-002, 2.487347e-002, 2.106127e-002, 1.698121e-002, -1.299358e-002, 8.379497e-003, 3.935987e-003, -6.608164e-004, --5.529345e-003, -1.019073e-002, -1.535437e-002, -2.023319e-002, --2.546930e-002, -3.047127e-002, -3.571817e-002, -4.071906e-002, --4.573477e-002, -5.051431e-002, -5.516725e-002, -5.948015e-002, --6.348838e-002, -6.729707e-002, -7.065566e-002, -7.376872e-002, --7.635738e-002, -7.874548e-002, -8.043464e-002, -8.203416e-002, --8.298526e-002, -8.381424e-002, -8.403609e-002, -8.399522e-002, --8.362292e-002, -8.280955e-002, -8.193264e-002, -8.045636e-002, --7.892246e-002, -7.713694e-002, -7.494625e-002, -7.286040e-002, --7.045504e-002, -6.778598e-002, -6.515759e-002, -6.216961e-002, --5.912094e-002, -5.610186e-002, -5.278094e-002, -4.936363e-002, --4.598159e-002, -4.242170e-002, -3.876846e-002, -3.519103e-002, --3.159487e-002, -2.771813e-002, -2.388344e-002, -2.015988e-002, --1.638076e-002, -1.255269e-002, -8.791596e-003, -5.133212e-003, --1.274546e-003, 2.479637e-003, 6.032230e-003, 9.420152e-003, -1.288947e-002, 1.619042e-002, 1.927861e-002, 2.216957e-002, -2.501808e-002, 2.770244e-002, 3.024260e-002, 3.260544e-002, -3.508378e-002, 3.740506e-002, 3.952824e-002, 4.145633e-002, -4.331550e-002, 4.507710e-002, 4.664530e-002, 4.804002e-002, -4.922756e-002, 5.037657e-002, 5.138086e-002, 5.223361e-002, -5.295540e-002, 5.367978e-002, 5.436264e-002, 5.489276e-002, -5.520533e-002, 5.531303e-002, 5.539436e-002, 5.543604e-002, -5.532854e-002, 5.507301e-002, 5.464520e-002, 5.415568e-002, -5.354191e-002, 5.279835e-002, 5.193196e-002, 5.097215e-002, -5.002294e-002, 4.900709e-002, 4.786388e-002, 4.653563e-002, -4.507678e-002, 4.355459e-002, 4.199924e-002, 4.037431e-002, -3.864483e-002, 3.679183e-002, 3.490835e-002, 3.300271e-002, -3.090309e-002, 2.876163e-002, 2.660743e-002, 2.444052e-002, -2.240324e-002, 2.038992e-002, 1.831627e-002, 1.609441e-002, -1.387756e-002, 1.174006e-002, 9.751399e-003, 7.766189e-003, -5.844567e-003, 3.982798e-003, 2.117273e-003, 3.151913e-004, --1.360628e-003, -2.939121e-003, -4.451436e-003, -5.858844e-003, --7.168514e-003, -8.499262e-003, -9.705243e-003, -1.081210e-002, --1.189051e-002, -1.291298e-002, -1.387140e-002, -1.465477e-002, --1.550300e-002, -1.637613e-002, -1.719256e-002, -1.803177e-002, --1.888324e-002, -1.968082e-002, -2.042611e-002, -2.116963e-002, --2.207949e-002, -2.299855e-002, -2.388119e-002, -2.467255e-002, --2.531417e-002, -2.592913e-002, -2.644008e-002, -2.682552e-002, --2.718591e-002, -2.754750e-002, -2.765186e-002, -2.765523e-002, --2.752406e-002, -2.726710e-002, -2.688072e-002, -2.640137e-002, --2.570285e-002, -2.515455e-002, -2.455274e-002, -2.390108e-002, --2.319054e-002, -2.239094e-002, -2.154277e-002, -2.055979e-002, --1.945161e-002, -1.830144e-002, -1.730625e-002, -1.635173e-002, --1.538551e-002, -1.442422e-002, -1.337190e-002, -1.235285e-002, --1.128799e-002, -1.006423e-002, -8.741049e-003, -7.472599e-003, --6.401690e-003, -5.221186e-003, -3.989571e-003, -2.756025e-003, --1.520108e-003, -2.012723e-004, 1.237720e-003, 2.688091e-003, -4.202303e-003, 5.734698e-003, 7.020855e-003, 8.318617e-003, -9.573227e-003, 1.089393e-002, 1.223390e-002, 1.362433e-002, -1.507834e-002, 1.652139e-002, 1.792093e-002, 1.924950e-002, -2.042536e-002, 2.143528e-002, 2.239416e-002, 2.339400e-002, -2.438456e-002, 2.532135e-002, 2.616545e-002, 2.698230e-002, -2.770054e-002, 2.833202e-002, 2.894373e-002, 2.958028e-002, -2.991290e-002, 3.012259e-002, 3.028503e-002, 3.033722e-002, -3.030585e-002, 3.019283e-002, 3.007496e-002, 3.003720e-002, -2.999292e-002, 2.996758e-002, 2.993461e-002, 2.980423e-002, -2.955674e-002, 2.930377e-002, 2.897844e-002, 2.865083e-002, -2.848003e-002, 2.830830e-002, 2.813526e-002, 2.792500e-002, -2.766062e-002, 2.730710e-002, 2.693337e-002, 2.659270e-002, -2.614234e-002, 2.564160e-002, 2.515083e-002, 2.461619e-002, -2.395083e-002, 2.308640e-002, 2.211561e-002, 2.107071e-002, -1.996197e-002, 1.886544e-002, 1.785440e-002, 1.676589e-002, -1.567677e-002, 1.454009e-002, 1.342230e-002, 1.226866e-002, -1.104708e-002, 9.824566e-003, 8.595248e-003, 7.422231e-003, -6.177077e-003, 4.908154e-003, 3.637750e-003, 2.406062e-003, -1.150715e-003, -1.269682e-004, -1.485081e-003, -2.869399e-003, --4.230416e-003, -5.489638e-003, -6.794520e-003, -8.186402e-003, --9.585763e-003, -1.098258e-002, -1.231384e-002, -1.370129e-002, --1.516476e-002, -1.662410e-002, -1.813693e-002, -1.958581e-002, --2.108534e-002, -2.263624e-002, -2.422710e-002, -2.581051e-002, --2.737335e-002, -2.892406e-002, -3.039598e-002, -3.186074e-002, --3.332416e-002, -3.478130e-002, -3.614510e-002, -3.751609e-002, --3.885534e-002, -4.018989e-002, -4.152692e-002, -4.283544e-002, --4.410973e-002, -4.532101e-002, -4.642662e-002, -4.745378e-002, --4.839035e-002, -4.923504e-002, -4.999617e-002, -5.073088e-002, --5.137246e-002, -5.192545e-002, -5.248106e-002, -5.302770e-002, --5.353326e-002, -5.397520e-002, -5.435785e-002, -5.470494e-002, --5.502279e-002, -5.518973e-002, -5.527213e-002, -5.528006e-002, --5.526554e-002, -5.516498e-002, -5.497562e-002, -5.476407e-002, --5.450516e-002, -5.418434e-002, -5.384248e-002, -5.348794e-002, --5.309449e-002, -5.261004e-002, -5.191361e-002, -5.118071e-002, --5.048657e-002, -4.977398e-002, -4.888613e-002, -4.794007e-002, --4.695792e-002, -4.597193e-002, -4.497811e-002, -4.396739e-002, --4.297801e-002, -4.197763e-002, -4.098473e-002, -3.996192e-002, --3.891484e-002, -3.788558e-002, -3.667123e-002, -3.541216e-002, --3.419372e-002, -3.303450e-002, -3.191623e-002, -3.079176e-002, --2.967039e-002, -2.858740e-002, -2.754537e-002, -2.657461e-002, --2.561912e-002, -2.452975e-002, -2.326314e-002, -2.199472e-002, --2.072116e-002, -1.944397e-002, -1.814401e-002, -1.681423e-002, --1.546728e-002, -1.406499e-002, -1.265142e-002, -1.128272e-002, --9.781957e-003, -8.171467e-003, -6.557077e-003, -4.975358e-003, --3.376001e-003, -1.754926e-003, -1.140562e-004, 1.509670e-003, -3.113203e-003, 4.681309e-003, 6.329369e-003, 8.145204e-003, -9.908321e-003, 1.162172e-002, 1.324871e-002, 1.482348e-002, -1.635826e-002, 1.785242e-002, 1.928703e-002, 2.069971e-002, -2.223961e-002, 2.387469e-002, 2.546514e-002, 2.701919e-002, -2.850560e-002, 2.993761e-002, 3.138086e-002, 3.283292e-002, -3.426880e-002, 3.571753e-002, 3.709773e-002, 3.850995e-002, -3.991266e-002, 4.131552e-002, 4.272713e-002, 4.417610e-002, -4.567907e-002, 4.713312e-002, 4.858780e-002, 4.994958e-002, -5.099983e-002, 5.202061e-002, 5.303529e-002, 5.402985e-002, -5.501470e-002, 5.601541e-002, 5.697689e-002, 5.788245e-002, -5.878455e-002, 5.960431e-002, 5.993314e-002, 6.004867e-002, -6.017560e-002, 6.025680e-002, 6.028332e-002, 6.036600e-002, -6.047204e-002, 6.054307e-002, 6.061440e-002, 6.066773e-002, -6.067408e-002, 6.064750e-002, 6.029579e-002, 5.948691e-002, -5.871027e-002, 5.798872e-002, 5.730511e-002, 5.662379e-002, -5.596847e-002, 5.535131e-002, 5.478482e-002, 5.418309e-002, -5.354282e-002, 5.290137e-002, 5.234137e-002, 5.183970e-002, -5.136903e-002, 5.066515e-002, 4.934083e-002, 4.801558e-002, -4.669566e-002, 4.537542e-002, 4.400899e-002, 4.263368e-002, -4.124278e-002, 3.983301e-002, 3.841035e-002, 3.697916e-002, -3.549100e-002, 3.397292e-002, 3.245419e-002, 3.094846e-002, -2.942530e-002, 2.787906e-002, 2.636620e-002, 2.494316e-002, -2.354671e-002, 2.191724e-002, 1.997041e-002, 1.806422e-002, -1.621102e-002, 1.446106e-002, 1.291231e-002, 1.143140e-002, -1.001174e-002, 8.622862e-003, 7.266216e-003, 5.909518e-003, -4.587831e-003, 3.242925e-003, 1.913073e-003, 6.163586e-004, --5.650576e-004, -1.612659e-003, -2.639918e-003, -3.609561e-003, --4.506571e-003, -5.356361e-003, -6.214368e-003, -7.096796e-003, --8.025984e-003, -9.000385e-003, -1.000258e-002, -1.097974e-002, --1.221216e-002, -1.331860e-002, -1.444947e-002, -1.561844e-002, --1.687950e-002, -1.814989e-002, -1.941037e-002, -2.055378e-002, --2.170066e-002, -2.283110e-002, -2.395691e-002, -2.509341e-002, --2.618455e-002, -2.723229e-002, -2.801128e-002, -2.853404e-002, --2.895595e-002, -2.932786e-002, -2.968468e-002, -2.999819e-002, --3.028305e-002, -3.054678e-002, -3.077979e-002, -3.100213e-002, --3.122261e-002, -3.151470e-002, -3.172709e-002, -3.190430e-002, --3.204822e-002, -3.217858e-002, -3.222945e-002, -3.218066e-002, --3.191587e-002, -3.156741e-002, -3.123162e-002, -3.102675e-002, --3.111331e-002, -3.125889e-002, -3.142315e-002, -3.160778e-002, --3.177897e-002, -3.192405e-002, -3.203485e-002, -3.212855e-002, --3.218406e-002, -3.220427e-002, -3.218557e-002, -3.216532e-002, --3.215987e-002, -3.217860e-002, -3.200768e-002, -3.171497e-002, --3.139800e-002, -3.107310e-002, -3.069901e-002, -3.017591e-002, --2.955571e-002, -2.893570e-002, -2.830517e-002, -2.767579e-002, --2.702912e-002, -2.638762e-002, -2.575170e-002, -2.514445e-002, --2.455085e-002, -2.401040e-002, -2.345075e-002, -2.286864e-002, --2.230433e-002, -2.174911e-002, -2.118840e-002, -2.062891e-002, --2.006041e-002, -1.948396e-002, -1.885741e-002, -1.815325e-002, --1.744793e-002, -1.671256e-002, -1.596254e-002, -1.518492e-002, --1.440409e-002, -1.360366e-002, -1.276341e-002, -1.192953e-002, --1.131855e-002, -1.075737e-002, -1.002369e-002, -9.305102e-003, --8.616256e-003, -7.950955e-003, -7.311456e-003, -6.678224e-003, --6.041242e-003, -5.385214e-003, -4.747592e-003, -4.080719e-003, --3.454269e-003, -2.879744e-003, -2.343424e-003, -1.879314e-003, --1.446905e-003, -1.032713e-003, -6.463152e-004, -2.763472e-004, -1.004613e-004, 5.282436e-004, 9.870480e-004, 1.455067e-003, -1.935803e-003, 2.427716e-003, 2.917809e-003, 3.426500e-003, -3.931207e-003, 4.440017e-003, 4.950393e-003, 5.456166e-003, -5.947855e-003, 6.439354e-003, 6.939765e-003, 7.429828e-003, -7.900502e-003, 8.348521e-003, 8.763991e-003, 9.170761e-003, -9.576973e-003, 9.994175e-003, 1.037298e-002, 1.083292e-002, -1.126701e-002, 1.165610e-002, 1.200309e-002, 1.231259e-002, -1.256936e-002, 1.277863e-002, 1.294129e-002, 1.304473e-002, -1.310577e-002, 1.313875e-002, 1.315785e-002, 1.315361e-002, -1.313038e-002, 1.307920e-002, 1.295946e-002, 1.283788e-002, -1.271356e-002, 1.268945e-002, 1.272269e-002, 1.280713e-002, -1.290529e-002, 1.295629e-002, 1.288772e-002, 1.280596e-002, -1.269083e-002, 1.255395e-002, 1.239690e-002, 1.222712e-002, -1.205917e-002, 1.194490e-002, 1.185235e-002, 1.175429e-002, -1.162861e-002, 1.147165e-002, 1.129526e-002, 1.107945e-002, -1.081822e-002, 1.051647e-002, 1.019372e-002, 9.856525e-003, -9.509824e-003, 9.129752e-003, 8.744128e-003, 8.371728e-003, -8.023015e-003, 7.691966e-003, 7.391482e-003, 7.127639e-003, -6.903494e-003, 6.689683e-003, 6.493887e-003, 6.357181e-003, -6.257253e-003, 6.180738e-003, 6.219192e-003, 6.258491e-003, -6.317628e-003, 6.409330e-003, 6.524055e-003, 6.616680e-003, -6.709712e-003, 6.805812e-003, 6.894513e-003, 7.026513e-003, -7.145610e-003, 7.248054e-003, 7.341368e-003, 7.414935e-003, -7.410269e-003, 7.413855e-003, 7.402326e-003, 7.374186e-003, -7.320299e-003, 7.248887e-003, 7.150171e-003, 7.035966e-003, -6.912878e-003, 6.768290e-003, 6.593352e-003, 6.397631e-003, -6.208808e-003, 6.046527e-003, 5.895229e-003, 5.753246e-003, -5.636468e-003, 5.535089e-003, 5.430815e-003, 5.316514e-003, -5.203072e-003, 5.076031e-003, 4.947930e-003, 4.858131e-003, -4.762396e-003, 4.663724e-003, 4.563925e-003, 4.459467e-003, -4.345069e-003, 4.217650e-003, 4.068917e-003, 3.934200e-003, -3.804770e-003, 3.696061e-003, 3.609597e-003, 3.501340e-003, -3.383188e-003, 3.251549e-003, 3.140899e-003, 3.032227e-003, -2.881046e-003, 2.619986e-003, 2.350280e-003, 2.104944e-003, -1.873185e-003, 1.630876e-003, 1.369334e-003, 1.105577e-003, -8.510127e-004, 5.952370e-004, 3.385831e-004, 7.613545e-005, --1.933397e-004, -4.570994e-004, -6.873165e-004, -9.421861e-004, --1.240337e-003, -1.545867e-003, -1.854179e-003, -2.132665e-003, --2.397875e-003, -2.687182e-003, -3.000602e-003, -3.331590e-003, --3.607402e-003, -3.912631e-003, -4.228442e-003, -4.552572e-003, --4.847655e-003, -5.128624e-003, -5.387860e-003, -5.629705e-003, --5.868755e-003, -6.102562e-003, -6.256287e-003, -6.404354e-003, --6.538640e-003, -6.654996e-003, -6.759347e-003, -6.844263e-003, --6.931231e-003, -7.024069e-003, -7.124276e-003, -7.238232e-003, --7.307320e-003, -7.359717e-003, -7.417052e-003, -7.477403e-003, --7.537770e-003, -7.577184e-003, -7.619149e-003, -7.685883e-003, --7.766367e-003, -7.867068e-003, -7.951303e-003, -7.998447e-003, --8.066885e-003, -8.163488e-003, -8.280622e-003, -8.423789e-003, --8.594658e-003, -8.810135e-003, -9.059259e-003, -9.338173e-003, --9.639212e-003, -9.877399e-003, -1.011859e-002, -1.036583e-002, --1.061711e-002, -1.086555e-002, -1.110966e-002, -1.132911e-002, --1.150975e-002, -1.168918e-002, -1.185982e-002, -1.189004e-002, --1.188348e-002, -1.184896e-002, -1.178765e-002, -1.170955e-002, --1.162512e-002, -1.154200e-002, -1.144819e-002, -1.134496e-002, --1.125208e-002, -1.107638e-002, -1.085459e-002, -1.063878e-002, --1.042280e-002, -1.018981e-002, -9.957078e-003, -9.724109e-003, --9.506621e-003, -9.299984e-003, -9.105711e-003, -8.882068e-003, --8.624317e-003, -8.406313e-003, -8.219835e-003, -8.026353e-003, --7.835124e-003, -7.643890e-003, -7.422261e-003, -7.180429e-003, --6.900958e-003, -6.493511e-003, -5.934865e-003, -5.357188e-003, --4.757886e-003, -4.144439e-003, -3.509118e-003, -2.855865e-003, --2.177809e-003, -1.479250e-003, -7.512174e-004, -1.016566e-017 -}, -{ -0.000000e+000, 9.337579e-002, 8.054700e-002, 6.256344e-002, -3.666294e-002, 8.144030e-003, -1.059384e-002, -2.663954e-002, --3.935114e-002, -4.824101e-002, -5.237458e-002, -5.184092e-002, --4.726596e-002, -4.032197e-002, -3.534554e-002, -3.300175e-002, --3.222506e-002, -3.109026e-002, -2.817204e-002, -2.516272e-002, --2.076238e-002, -1.440327e-002, -6.912883e-003, 1.941417e-003, -1.157612e-002, 2.126827e-002, 3.039692e-002, 3.774032e-002, -4.245606e-002, 4.444934e-002, 4.460454e-002, 4.343607e-002, -4.107697e-002, 3.760178e-002, 3.386266e-002, 2.996247e-002, -2.615551e-002, 2.180640e-002, 1.697367e-002, 1.177458e-002, -6.244900e-003, 5.070997e-004, -5.046233e-003, -1.037382e-002, --1.509954e-002, -1.909733e-002, -2.221596e-002, -2.381300e-002, --2.486913e-002, -2.565004e-002, -2.611091e-002, -2.613241e-002, --2.550826e-002, -2.404751e-002, -2.226260e-002, -2.009056e-002, --1.753187e-002, -1.486596e-002, -1.213645e-002, -9.042711e-003, --5.815066e-003, -2.922695e-003, 6.328107e-005, 3.130231e-003, -6.150614e-003, 9.359206e-003, 1.243885e-002, 1.544246e-002, -1.827674e-002, 2.096147e-002, 2.357433e-002, 2.594095e-002, -2.801248e-002, 2.966496e-002, 3.101287e-002, 3.224910e-002, -3.311050e-002, 3.338042e-002, 3.301061e-002, 3.168125e-002, -2.988833e-002, 2.758315e-002, 2.447003e-002, 2.050972e-002, -1.628483e-002, 1.147938e-002, 6.069612e-003, 4.319296e-004, --5.188078e-003, -1.082200e-002, -1.641430e-002, -2.201648e-002, --2.710552e-002, -3.195749e-002, -3.649277e-002, -4.012370e-002, --4.320136e-002, -4.551116e-002, -4.667907e-002, -4.724751e-002, --4.716803e-002, -4.660996e-002, -4.558051e-002, -4.405821e-002, --4.220763e-002, -3.997135e-002, -3.723699e-002, -3.432818e-002, --3.108256e-002, -2.769270e-002, -2.381317e-002, -1.993181e-002, --1.601529e-002, -1.215511e-002, -7.974437e-003, -3.711301e-003, -5.040333e-004, 4.524147e-003, 8.259167e-003, 1.191973e-002, -1.532632e-002, 1.848794e-002, 2.125973e-002, 2.375283e-002, -2.629281e-002, 2.892457e-002, 3.134179e-002, 3.340889e-002, -3.515550e-002, 3.656751e-002, 3.787512e-002, 3.893071e-002, -3.961539e-002, 3.981786e-002, 3.990924e-002, 3.939848e-002, -3.859373e-002, 3.751452e-002, 3.588884e-002, 3.405013e-002, -3.197804e-002, 2.972880e-002, 2.759941e-002, 2.546564e-002, -2.339918e-002, 2.093479e-002, 1.839426e-002, 1.594785e-002, -1.330018e-002, 1.090515e-002, 8.845472e-003, 6.767977e-003, -4.753374e-003, 2.727377e-003, 9.251994e-004, -1.073709e-003, --2.956624e-003, -4.900178e-003, -7.063213e-003, -9.318317e-003, --1.150778e-002, -1.378903e-002, -1.568296e-002, -1.766367e-002, --1.971595e-002, -2.159365e-002, -2.352753e-002, -2.530709e-002, --2.683944e-002, -2.855413e-002, -3.043345e-002, -3.230536e-002, --3.363735e-002, -3.500978e-002, -3.638806e-002, -3.793768e-002, --3.901289e-002, -3.971353e-002, -4.039272e-002, -4.122470e-002, --4.127548e-002, -4.140999e-002, -4.167933e-002, -4.128496e-002, --4.069180e-002, -4.036119e-002, -3.946187e-002, -3.827728e-002, --3.708067e-002, -3.509397e-002, -3.299071e-002, -3.067334e-002, --2.770009e-002, -2.467655e-002, -2.094021e-002, -1.728392e-002, --1.298017e-002, -8.677546e-003, -4.119176e-003, 3.914347e-004, -4.987787e-003, 9.541484e-003, 1.406785e-002, 1.869771e-002, -2.319567e-002, 2.750408e-002, 3.160078e-002, 3.547548e-002, -3.906471e-002, 4.249579e-002, 4.547527e-002, 4.836086e-002, -5.055178e-002, 5.268234e-002, 5.420818e-002, 5.567213e-002, -5.675608e-002, 5.751439e-002, 5.827158e-002, 5.826996e-002, -5.820207e-002, 5.781884e-002, 5.704479e-002, 5.616921e-002, -5.498818e-002, 5.350800e-002, 5.202346e-002, 5.009597e-002, -4.806783e-002, 4.599423e-002, 4.364423e-002, 4.113364e-002, -3.866013e-002, 3.609612e-002, 3.335584e-002, 3.066471e-002, -2.806307e-002, 2.520214e-002, 2.247422e-002, 1.991643e-002, -1.722360e-002, 1.451583e-002, 1.193606e-002, 9.520540e-003, -6.953139e-003, 4.558911e-003, 2.287653e-003, 2.052085e-004, --2.048454e-003, -4.205553e-003, -6.180249e-003, -8.077998e-003, --9.991269e-003, -1.182419e-002, -1.354970e-002, -1.522469e-002, --1.701507e-002, -1.876009e-002, -2.043974e-002, -2.204278e-002, --2.365799e-002, -2.528322e-002, -2.681548e-002, -2.829378e-002, --2.978555e-002, -3.129256e-002, -3.265782e-002, -3.381694e-002, --3.482830e-002, -3.577425e-002, -3.655835e-002, -3.712273e-002, --3.751840e-002, -3.772809e-002, -3.777965e-002, -3.771771e-002, --3.748561e-002, -3.714185e-002, -3.671973e-002, -3.638967e-002, --3.592087e-002, -3.524906e-002, -3.453363e-002, -3.367208e-002, --3.282128e-002, -3.192228e-002, -3.094828e-002, -2.996387e-002, --2.895276e-002, -2.798544e-002, -2.699783e-002, -2.598904e-002, --2.505665e-002, -2.406740e-002, -2.317733e-002, -2.238234e-002, --2.157464e-002, -2.079896e-002, -1.995379e-002, -1.901615e-002, --1.809261e-002, -1.710185e-002, -1.611102e-002, -1.509913e-002, --1.404581e-002, -1.302115e-002, -1.200123e-002, -1.104464e-002, --9.955152e-003, -8.730752e-003, -7.477647e-003, -6.247350e-003, --5.104490e-003, -3.869194e-003, -2.607954e-003, -1.385911e-003, --1.641177e-004, 9.854357e-004, 2.060707e-003, 3.154591e-003, -4.187122e-003, 5.179847e-003, 6.077607e-003, 6.777136e-003, -7.424068e-003, 8.014570e-003, 8.583143e-003, 9.165609e-003, -9.725707e-003, 1.012095e-002, 1.055190e-002, 1.093842e-002, -1.132863e-002, 1.166970e-002, 1.200443e-002, 1.232981e-002, -1.260928e-002, 1.288811e-002, 1.319934e-002, 1.342834e-002, -1.364720e-002, 1.389613e-002, 1.403256e-002, 1.419949e-002, -1.436808e-002, 1.450918e-002, 1.462757e-002, 1.474620e-002, -1.484067e-002, 1.488036e-002, 1.488585e-002, 1.485840e-002, -1.482058e-002, 1.478127e-002, 1.469202e-002, 1.440663e-002, -1.410424e-002, 1.374990e-002, 1.343710e-002, 1.311401e-002, -1.275624e-002, 1.228563e-002, 1.161193e-002, 1.090090e-002, -1.007068e-002, 9.343667e-003, 8.678377e-003, 8.033764e-003, -7.440297e-003, 6.761212e-003, 6.026261e-003, 5.338306e-003, -4.729615e-003, 4.308365e-003, 3.823137e-003, 3.389233e-003, -2.861451e-003, 2.335036e-003, 1.829381e-003, 1.309549e-003, -7.877082e-004, 3.416839e-004, -1.170449e-004, -6.148117e-004, --1.155524e-003, -1.688575e-003, -2.237354e-003, -2.823631e-003, --3.452555e-003, -4.052090e-003, -4.615889e-003, -5.217007e-003, --5.784518e-003, -6.373255e-003, -7.041528e-003, -7.614589e-003, --8.177274e-003, -8.712735e-003, -9.077423e-003, -9.461381e-003, --9.785105e-003, -1.004130e-002, -1.020022e-002, -1.029365e-002, --1.033404e-002, -1.023723e-002, -9.933239e-003, -9.599172e-003, --9.315980e-003, -9.005085e-003, -8.640219e-003, -8.236887e-003, --7.734047e-003, -7.192223e-003, -6.675314e-003, -6.214613e-003, --5.635462e-003, -4.983051e-003, -4.247794e-003, -3.557568e-003, --2.866000e-003, -2.102296e-003, -1.298655e-003, -4.638119e-004, -3.587447e-004, 1.159503e-003, 2.088161e-003, 2.984260e-003, -3.770795e-003, 4.504367e-003, 5.192513e-003, 5.833605e-003, -6.431160e-003, 6.987493e-003, 7.534671e-003, 8.059066e-003, -8.699260e-003, 9.342952e-003, 9.986501e-003, 1.066711e-002, -1.132237e-002, 1.199838e-002, 1.260538e-002, 1.306040e-002, -1.348763e-002, 1.382594e-002, 1.414093e-002, 1.451043e-002, -1.484075e-002, 1.509472e-002, 1.529071e-002, 1.548732e-002, -1.561509e-002, 1.552972e-002, 1.534550e-002, 1.506998e-002, -1.477325e-002, 1.440358e-002, 1.408849e-002, 1.366535e-002, -1.314723e-002, 1.253122e-002, 1.186016e-002, 1.103214e-002, -1.015124e-002, 9.193284e-003, 8.199210e-003, 7.176804e-003, -6.115569e-003, 5.071359e-003, 4.173115e-003, 3.299768e-003, -2.476470e-003, 1.603802e-003, 5.976111e-004, -4.051276e-004, --1.385052e-003, -2.347748e-003, -3.232117e-003, -4.088827e-003, --4.867963e-003, -5.591498e-003, -6.131005e-003, -6.627589e-003, --7.027709e-003, -7.474431e-003, -7.923041e-003, -8.315020e-003, --8.613571e-003, -8.908797e-003, -9.122805e-003, -9.299557e-003, --9.409668e-003, -9.468532e-003, -9.445102e-003, -9.323435e-003, --9.165683e-003, -9.024795e-003, -8.870623e-003, -8.762402e-003, --8.682179e-003, -8.592505e-003, -8.453258e-003, -8.263731e-003, --8.087003e-003, -7.912042e-003, -7.736209e-003, -7.521169e-003, --7.249127e-003, -6.965512e-003, -6.762702e-003, -6.515550e-003, --6.258677e-003, -6.013257e-003, -5.770505e-003, -5.493118e-003, --5.255936e-003, -5.034171e-003, -4.816254e-003, -4.629203e-003, --4.449963e-003, -4.336181e-003, -4.268466e-003, -4.292624e-003, --4.391840e-003, -4.533672e-003, -4.736679e-003, -4.994259e-003, --5.228097e-003, -5.456070e-003, -5.734247e-003, -6.046107e-003, --6.378313e-003, -6.736148e-003, -7.135370e-003, -7.632638e-003, --8.178881e-003, -8.785749e-003, -9.447093e-003, -1.012371e-002, --1.083444e-002, -1.148722e-002, -1.206124e-002, -1.252398e-002, --1.285561e-002, -1.306116e-002, -1.313299e-002, -1.311074e-002, --1.299841e-002, -1.280540e-002, -1.251108e-002, -1.210224e-002, --1.164463e-002, -1.119139e-002, -1.073385e-002, -1.022790e-002, --9.616787e-003, -8.907662e-003, -8.118590e-003, -7.282146e-003, --6.398673e-003, -5.506171e-003, -4.597237e-003, -3.703928e-003, --2.829107e-003, -1.932029e-003, -9.461520e-004, 3.753457e-005, -9.556617e-004, 1.818693e-003, 2.702970e-003, 3.613503e-003, -4.494044e-003, 5.315143e-003, 6.157584e-003, 7.025176e-003, -7.949765e-003, 8.926645e-003, 9.954034e-003, 1.104090e-002, -1.217455e-002, 1.336638e-002, 1.460108e-002, 1.591158e-002, -1.728392e-002, 1.855519e-002, 1.975071e-002, 2.093432e-002, -2.211945e-002, 2.334418e-002, 2.458248e-002, 2.581480e-002, -2.701267e-002, 2.815575e-002, 2.922650e-002, 3.031731e-002, -3.140689e-002, 3.236835e-002, 3.330991e-002, 3.419446e-002, -3.499449e-002, 3.576448e-002, 3.644604e-002, 3.699905e-002, -3.747390e-002, 3.793745e-002, 3.840063e-002, 3.881495e-002, -3.915204e-002, 3.937664e-002, 3.961970e-002, 3.988549e-002, -4.010040e-002, 4.030473e-002, 4.051671e-002, 4.069752e-002, -4.085415e-002, 4.096553e-002, 4.102323e-002, 4.093267e-002, -4.072444e-002, 4.047253e-002, 4.008965e-002, 3.958233e-002, -3.899244e-002, 3.831920e-002, 3.756706e-002, 3.672373e-002, -3.578440e-002, 3.472734e-002, 3.354933e-002, 3.231533e-002, -3.099715e-002, 2.955171e-002, 2.798917e-002, 2.632923e-002, -2.458800e-002, 2.278689e-002, 2.090699e-002, 1.898109e-002, -1.697175e-002, 1.492934e-002, 1.280709e-002, 1.057119e-002, -8.312012e-003, 6.028641e-003, 3.704349e-003, 1.368374e-003, --9.636291e-004, -3.221179e-003, -5.194699e-003, -7.076719e-003, --8.911461e-003, -1.071179e-002, -1.249202e-002, -1.425596e-002, --1.601797e-002, -1.774000e-002, -1.939483e-002, -2.099249e-002, --2.239130e-002, -2.357917e-002, -2.473676e-002, -2.570602e-002, --2.661983e-002, -2.753098e-002, -2.845196e-002, -2.930966e-002, --3.015494e-002, -3.104343e-002, -3.194993e-002, -3.290752e-002, --3.361877e-002, -3.420379e-002, -3.480905e-002, -3.549334e-002, --3.625962e-002, -3.708951e-002, -3.796340e-002, -3.869558e-002, --3.943532e-002, -4.019785e-002, -4.099971e-002, -4.181653e-002, --4.268911e-002, -4.358275e-002, -4.417339e-002, -4.450766e-002, --4.493635e-002, -4.532211e-002, -4.566009e-002, -4.599084e-002, --4.632297e-002, -4.665601e-002, -4.703642e-002, -4.745356e-002, --4.792047e-002, -4.839403e-002, -4.870564e-002, -4.899779e-002, --4.928525e-002, -4.959662e-002, -4.987861e-002, -5.007376e-002, --4.998058e-002, -4.982173e-002, -4.968543e-002, -4.964423e-002, --4.963048e-002, -4.957751e-002, -4.953594e-002, -4.950427e-002, --4.951134e-002, -4.955110e-002, -4.957089e-002, -4.955461e-002, --4.950445e-002, -4.943094e-002, -4.931222e-002, -4.917423e-002, --4.901950e-002, -4.879597e-002, -4.833046e-002, -4.779963e-002, --4.726231e-002, -4.670867e-002, -4.615839e-002, -4.538542e-002, --4.447395e-002, -4.353573e-002, -4.258400e-002, -4.164180e-002, --4.072973e-002, -3.983688e-002, -3.899708e-002, -3.819590e-002, --3.741894e-002, -3.659510e-002, -3.583894e-002, -3.516010e-002, --3.451904e-002, -3.388106e-002, -3.331450e-002, -3.278897e-002, --3.228370e-002, -3.175822e-002, -3.114787e-002, -3.043007e-002, --2.970818e-002, -2.892343e-002, -2.808010e-002, -2.716863e-002, --2.620993e-002, -2.517996e-002, -2.401188e-002, -2.280922e-002, --2.159224e-002, -2.035964e-002, -1.885166e-002, -1.732302e-002, --1.577494e-002, -1.418543e-002, -1.255596e-002, -1.090489e-002, --9.229455e-003, -7.559030e-003, -5.879610e-003, -4.270280e-003, --2.700607e-003, -1.172344e-003, 3.035896e-004, 1.853425e-003, -3.386585e-003, 4.889981e-003, 6.408420e-003, 7.933404e-003, -9.464477e-003, 1.105332e-002, 1.270783e-002, 1.437190e-002, -1.603634e-002, 1.770604e-002, 1.940208e-002, 2.107708e-002, -2.273079e-002, 2.435622e-002, 2.595365e-002, 2.754913e-002, -2.907069e-002, 3.053007e-002, 3.192599e-002, 3.322939e-002, -3.446752e-002, 3.562821e-002, 3.670819e-002, 3.778943e-002, -3.881524e-002, 3.972464e-002, 4.057232e-002, 4.154276e-002, -4.249255e-002, 4.333301e-002, 4.407401e-002, 4.471403e-002, -4.528109e-002, 4.575084e-002, 4.615970e-002, 4.653299e-002, -4.687983e-002, 4.719266e-002, 4.746699e-002, 4.770137e-002, -4.790248e-002, 4.812787e-002, 4.840149e-002, 4.868858e-002, -4.899610e-002, 4.939016e-002, 4.982599e-002, 5.028005e-002, -5.069078e-002, 5.100873e-002, 5.125548e-002, 5.143315e-002, -5.156982e-002, 5.165233e-002, 5.166331e-002, 5.161557e-002, -5.149329e-002, 5.126921e-002, 5.100872e-002, 5.071450e-002, -5.038752e-002, 5.001719e-002, 4.958009e-002, 4.906007e-002, -4.846992e-002, 4.778778e-002, 4.703981e-002, 4.624614e-002, -4.547481e-002, 4.470176e-002, 4.394577e-002, 4.321158e-002, -4.248757e-002, 4.180063e-002, 4.113622e-002, 4.051122e-002, -3.993146e-002, 3.939281e-002, 3.898187e-002, 3.868661e-002, -3.841846e-002, 3.816450e-002, 3.805861e-002, 3.797960e-002, -3.790789e-002, 3.782917e-002, 3.770065e-002, 3.755250e-002, -3.744995e-002, 3.735331e-002, 3.723614e-002, 3.703699e-002, -3.679838e-002, 3.650905e-002, 3.614840e-002, 3.571775e-002, -3.526075e-002, 3.483020e-002, 3.442124e-002, 3.409020e-002, -3.371572e-002, 3.329526e-002, 3.284643e-002, 3.236931e-002, -3.185238e-002, 3.129653e-002, 3.073311e-002, 3.016913e-002, -2.962126e-002, 2.916572e-002, 2.869517e-002, 2.825708e-002, -2.780402e-002, 2.732901e-002, 2.682298e-002, 2.626712e-002, -2.566305e-002, 2.499945e-002, 2.428835e-002, 2.372180e-002, -2.311835e-002, 2.246110e-002, 2.176425e-002, 2.104785e-002, -2.026311e-002, 1.941361e-002, 1.850893e-002, 1.753306e-002, -1.650973e-002, 1.561526e-002, 1.469558e-002, 1.372472e-002, -1.272100e-002, 1.170618e-002, 1.068380e-002, 9.679732e-003, -8.657886e-003, 7.651527e-003, 6.622926e-003, 5.681231e-003, -4.801839e-003, 3.873979e-003, 2.905915e-003, 1.896289e-003, -8.439484e-004, -2.630574e-004, -1.457973e-003, -2.676914e-003, --3.964507e-003, -5.248744e-003, -6.417704e-003, -7.674556e-003, --9.026760e-003, -1.043564e-002, -1.191928e-002, -1.341661e-002, --1.496836e-002, -1.657154e-002, -1.824421e-002, -1.995019e-002, --2.136379e-002, -2.283756e-002, -2.437045e-002, -2.595351e-002, --2.755731e-002, -2.913923e-002, -3.074726e-002, -3.233955e-002, --3.391494e-002, -3.547784e-002, -3.672283e-002, -3.788117e-002, --3.896046e-002, -3.997611e-002, -4.095671e-002, -4.191678e-002, --4.288731e-002, -4.386789e-002, -4.488039e-002, -4.593007e-002, --4.677068e-002, -4.748846e-002, -4.825510e-002, -4.907650e-002, --4.996382e-002, -5.085780e-002, -5.178688e-002, -5.273962e-002, --5.370904e-002, -5.475475e-002, -5.569017e-002, -5.640125e-002, --5.719480e-002, -5.804965e-002, -5.898991e-002, -6.001694e-002, --6.113002e-002, -6.234433e-002, -6.349029e-002, -6.466185e-002, --6.581352e-002, -6.656992e-002, -6.738337e-002, -6.820166e-002, --6.901374e-002, -6.981737e-002, -7.061313e-002, -7.141248e-002, --7.219523e-002, -7.291148e-002, -7.362366e-002, -7.366409e-002, --7.354246e-002, -7.336383e-002, -7.313253e-002, -7.286657e-002, --7.258232e-002, -7.228563e-002, -7.201053e-002, -7.162843e-002, --7.120132e-002, -7.033058e-002, -6.921598e-002, -6.808678e-002, --6.689582e-002, -6.550464e-002, -6.411324e-002, -6.274257e-002, --6.137885e-002, -6.004870e-002, -5.860752e-002, -5.687383e-002, --5.481994e-002, -5.282281e-002, -5.087249e-002, -4.893320e-002, --4.696397e-002, -4.496778e-002, -4.270812e-002, -4.032353e-002, --3.792581e-002, -3.517678e-002, -3.177874e-002, -2.838066e-002, --2.497265e-002, -2.153301e-002, -1.805253e-002, -1.453713e-002, --1.097515e-002, -7.373098e-003, -3.712301e-003, 1.043064e-016 -}, -{ -0.000000e+000, -2.936768e-001, -2.015949e-001, -1.220699e-001, --5.804498e-002, -5.519080e-003, 3.533058e-002, 6.731481e-002, -9.339430e-002, 1.135458e-001, 1.279245e-001, 1.361439e-001, -1.374618e-001, 1.339212e-001, 1.257410e-001, 1.147397e-001, -1.013346e-001, 8.624868e-002, 6.842650e-002, 5.014312e-002, -3.088195e-002, 1.082044e-002, -8.111322e-003, -2.675255e-002, --4.481194e-002, -6.168148e-002, -7.697756e-002, -8.950938e-002, --9.937547e-002, -1.063143e-001, -1.108610e-001, -1.130846e-001, --1.137385e-001, -1.131079e-001, -1.103790e-001, -1.063397e-001, --1.017369e-001, -9.633422e-002, -8.980022e-002, -8.223918e-002, --7.366070e-002, -6.456267e-002, -5.520378e-002, -4.477592e-002, --3.455095e-002, -2.477141e-002, -1.444846e-002, -5.134203e-003, -3.986291e-003, 1.358703e-002, 2.267270e-002, 3.193805e-002, -4.036000e-002, 4.722075e-002, 5.376914e-002, 5.927965e-002, -6.378966e-002, 6.772552e-002, 7.117900e-002, 7.475348e-002, -7.751042e-002, 7.978749e-002, 8.096580e-002, 8.104600e-002, -8.078002e-002, 7.946478e-002, 7.704405e-002, 7.472491e-002, -7.213703e-002, 6.846022e-002, 6.468923e-002, 6.072799e-002, -5.627498e-002, 5.105749e-002, 4.570861e-002, 4.156578e-002, -3.735507e-002, 3.269376e-002, 2.791665e-002, 2.311310e-002, -1.853042e-002, 1.394646e-002, 9.929325e-003, 6.043247e-003, -2.054935e-003, -1.841048e-003, -5.745632e-003, -1.016518e-002, --1.330462e-002, -1.639998e-002, -1.984540e-002, -2.336333e-002, --2.704136e-002, -3.027176e-002, -3.354240e-002, -3.677033e-002, --4.058446e-002, -4.484417e-002, -4.759682e-002, -5.084941e-002, --5.429818e-002, -5.774795e-002, -6.062470e-002, -6.250695e-002, --6.459745e-002, -6.704703e-002, -6.857499e-002, -6.965070e-002, --7.074597e-002, -7.215960e-002, -7.211470e-002, -7.192852e-002, --7.185874e-002, -7.066754e-002, -6.899936e-002, -6.736284e-002, --6.466165e-002, -6.164115e-002, -5.857296e-002, -5.436931e-002, --5.010039e-002, -4.535452e-002, -3.988804e-002, -3.449618e-002, --2.836020e-002, -2.212858e-002, -1.547472e-002, -8.954541e-003, --2.535012e-003, 4.128329e-003, 1.053920e-002, 1.711540e-002, -2.338534e-002, 2.924300e-002, 3.552279e-002, 4.168130e-002, -4.740185e-002, 5.301939e-002, 5.782601e-002, 6.240031e-002, -6.657105e-002, 7.024735e-002, 7.399709e-002, 7.689109e-002, -7.967738e-002, 8.158660e-002, 8.314143e-002, 8.457268e-002, -8.460744e-002, 8.478982e-002, 8.495112e-002, 8.412648e-002, -8.291248e-002, 8.144217e-002, 7.923192e-002, 7.676066e-002, -7.417096e-002, 7.119955e-002, 6.825478e-002, 6.457495e-002, -6.023473e-002, 5.575412e-002, 5.169566e-002, 4.693907e-002, -4.188323e-002, 3.685779e-002, 3.128173e-002, 2.569002e-002, -2.038308e-002, 1.483897e-002, 8.877003e-003, 2.688030e-003, --2.741197e-003, -8.290472e-003, -1.422623e-002, -2.001177e-002, --2.543532e-002, -3.080157e-002, -3.633894e-002, -4.167820e-002, --4.606155e-002, -5.075269e-002, -5.540333e-002, -5.924835e-002, --6.245726e-002, -6.600031e-002, -6.868650e-002, -7.084773e-002, --7.273671e-002, -7.389852e-002, -7.487878e-002, -7.524029e-002, --7.474255e-002, -7.407999e-002, -7.259613e-002, -7.104394e-002, --6.841792e-002, -6.564814e-002, -6.238021e-002, -5.898563e-002, --5.491038e-002, -5.065509e-002, -4.624402e-002, -4.203314e-002, --3.789913e-002, -3.363404e-002, -2.949475e-002, -2.549986e-002, --2.178322e-002, -1.815861e-002, -1.504353e-002, -1.160480e-002, --8.864023e-003, -5.922494e-003, -3.420531e-003, -9.987396e-004, -1.230515e-003, 3.087195e-003, 5.056426e-003, 6.588084e-003, -8.046189e-003, 9.373976e-003, 1.040771e-002, 1.174158e-002, -1.282667e-002, 1.373572e-002, 1.468166e-002, 1.522524e-002, -1.584725e-002, 1.671026e-002, 1.724098e-002, 1.770623e-002, -1.801950e-002, 1.816463e-002, 1.827345e-002, 1.859983e-002, -1.898470e-002, 1.888698e-002, 1.894233e-002, 1.909027e-002, -1.901929e-002, 1.891470e-002, 1.885301e-002, 1.888202e-002, -1.860902e-002, 1.837130e-002, 1.826879e-002, 1.811034e-002, -1.765121e-002, 1.735133e-002, 1.716652e-002, 1.700131e-002, -1.656082e-002, 1.617117e-002, 1.570243e-002, 1.545613e-002, -1.510207e-002, 1.480608e-002, 1.441838e-002, 1.410778e-002, -1.377689e-002, 1.353286e-002, 1.343980e-002, 1.346744e-002, -1.345521e-002, 1.320992e-002, 1.306366e-002, 1.284477e-002, -1.271885e-002, 1.238879e-002, 1.200176e-002, 1.150655e-002, -1.109537e-002, 1.083720e-002, 1.031358e-002, 9.565111e-003, -8.801930e-003, 8.079452e-003, 7.271431e-003, 6.548226e-003, -6.026242e-003, 5.280815e-003, 4.592209e-003, 3.819072e-003, -2.880257e-003, 1.983226e-003, 1.140822e-003, 3.996909e-004, --8.522758e-005, -6.494040e-004, -1.385765e-003, -2.153876e-003, --2.883218e-003, -3.819219e-003, -4.556017e-003, -5.188247e-003, --5.653660e-003, -6.017518e-003, -6.380325e-003, -6.886456e-003, --7.514929e-003, -8.228115e-003, -8.785033e-003, -9.261756e-003, --9.600018e-003, -9.878417e-003, -1.013148e-002, -1.031248e-002, --1.052252e-002, -1.085787e-002, -1.130320e-002, -1.173121e-002, --1.204879e-002, -1.233928e-002, -1.245130e-002, -1.236927e-002, --1.221533e-002, -1.231641e-002, -1.271262e-002, -1.301432e-002, --1.307163e-002, -1.291921e-002, -1.258418e-002, -1.223215e-002, --1.199725e-002, -1.178477e-002, -1.139687e-002, -1.105983e-002, --1.071305e-002, -1.027994e-002, -9.744510e-003, -9.218520e-003, --8.857367e-003, -8.497478e-003, -7.943536e-003, -7.376532e-003, --6.777619e-003, -6.115300e-003, -5.627660e-003, -5.233202e-003, --4.874954e-003, -4.407527e-003, -4.022906e-003, -3.526863e-003, --3.070908e-003, -2.408599e-003, -1.908698e-003, -1.540239e-003, --1.271974e-003, -9.739660e-004, -5.589403e-004, -8.258355e-005, -5.415968e-004, 1.118237e-003, 1.569301e-003, 2.017566e-003, -2.445089e-003, 3.092905e-003, 3.609528e-003, 4.107092e-003, -4.654387e-003, 5.327793e-003, 5.773269e-003, 6.292694e-003, -6.977780e-003, 7.589159e-003, 8.230404e-003, 9.010027e-003, -9.713868e-003, 1.035827e-002, 1.075201e-002, 1.111499e-002, -1.140439e-002, 1.166118e-002, 1.198110e-002, 1.240441e-002, -1.278734e-002, 1.318441e-002, 1.349859e-002, 1.357861e-002, -1.365388e-002, 1.380309e-002, 1.403954e-002, 1.421868e-002, -1.418233e-002, 1.411611e-002, 1.416000e-002, 1.424730e-002, -1.409272e-002, 1.409251e-002, 1.419539e-002, 1.418713e-002, -1.421271e-002, 1.413665e-002, 1.403632e-002, 1.399163e-002, -1.399067e-002, 1.396313e-002, 1.371654e-002, 1.333010e-002, -1.293880e-002, 1.256885e-002, 1.224894e-002, 1.199725e-002, -1.175304e-002, 1.148493e-002, 1.119659e-002, 1.079604e-002, -1.015031e-002, 9.565293e-003, 9.019779e-003, 8.496733e-003, -8.051583e-003, 7.593983e-003, 7.200224e-003, 6.621290e-003, -5.890945e-003, 5.285071e-003, 4.630048e-003, 3.924453e-003, -3.315777e-003, 2.738697e-003, 2.238671e-003, 1.766340e-003, -1.187857e-003, 6.924309e-004, 2.707819e-004, -2.683250e-004, --8.025090e-004, -1.230826e-003, -1.817614e-003, -2.325287e-003, --2.726808e-003, -3.156073e-003, -3.595694e-003, -3.975455e-003, --4.255906e-003, -4.455909e-003, -4.601438e-003, -4.721331e-003, --4.926394e-003, -5.114726e-003, -5.453687e-003, -5.946068e-003, --6.374388e-003, -6.705998e-003, -6.979633e-003, -7.202080e-003, --7.363996e-003, -7.512350e-003, -7.673691e-003, -7.799986e-003, --7.958815e-003, -8.296506e-003, -8.630880e-003, -9.057584e-003, --9.380145e-003, -9.623917e-003, -9.764959e-003, -9.842687e-003, --9.841396e-003, -9.732894e-003, -9.586204e-003, -9.512504e-003, --9.428227e-003, -9.238353e-003, -8.990154e-003, -8.803221e-003, --8.675061e-003, -8.784048e-003, -8.886083e-003, -8.957777e-003, --8.930264e-003, -8.835069e-003, -8.864927e-003, -8.827950e-003, --8.760685e-003, -8.613337e-003, -8.493304e-003, -8.308700e-003, --8.095132e-003, -7.842690e-003, -7.608056e-003, -7.521895e-003, --7.685235e-003, -7.866583e-003, -8.072438e-003, -8.285467e-003, --8.510732e-003, -8.618677e-003, -8.687487e-003, -8.776203e-003, --8.794228e-003, -8.749712e-003, -8.656180e-003, -8.498556e-003, --8.311921e-003, -8.176089e-003, -8.062720e-003, -8.131976e-003, --8.257884e-003, -8.278275e-003, -8.287908e-003, -8.303266e-003, --8.259429e-003, -8.199778e-003, -8.113108e-003, -7.985273e-003, --7.836651e-003, -7.664828e-003, -7.661636e-003, -7.691789e-003, --7.730387e-003, -7.757622e-003, -7.661235e-003, -7.541190e-003, --7.612368e-003, -7.805112e-003, -7.983787e-003, -8.132910e-003, --8.288107e-003, -8.331002e-003, -8.281455e-003, -8.271275e-003, --8.264693e-003, -8.275415e-003, -8.236136e-003, -8.088917e-003, --7.965373e-003, -7.827753e-003, -7.625517e-003, -7.388793e-003, --7.107577e-003, -6.794117e-003, -6.612974e-003, -6.477065e-003, --6.242760e-003, -5.981966e-003, -5.764776e-003, -5.448761e-003, --5.044742e-003, -4.580647e-003, -4.150102e-003, -3.751930e-003, --3.391930e-003, -3.000885e-003, -2.617643e-003, -2.231810e-003, --1.779358e-003, -1.298472e-003, -8.489956e-004, -4.351513e-004, --1.041706e-004, 9.963910e-005, 1.274792e-004, 1.508024e-004, -1.850784e-004, 2.374298e-004, 3.362977e-004, 5.360913e-004, -7.358193e-004, 9.807954e-004, 1.232764e-003, 1.550029e-003, -1.955373e-003, 2.402896e-003, 2.840806e-003, 3.311111e-003, -3.769763e-003, 4.249881e-003, 4.768687e-003, 5.326996e-003, -5.924390e-003, 6.497732e-003, 6.930072e-003, 7.176890e-003, -7.361552e-003, 7.547584e-003, 7.707608e-003, 7.854872e-003, -8.028688e-003, 8.244274e-003, 8.449570e-003, 8.607686e-003, -8.791022e-003, 8.857488e-003, 8.906268e-003, 8.998656e-003, -9.000183e-003, 9.024599e-003, 9.095320e-003, 9.233406e-003, -9.386764e-003, 9.648181e-003, 9.944573e-003, 1.023837e-002, -1.053789e-002, 1.072921e-002, 1.068953e-002, 1.061984e-002, -1.061898e-002, 1.060712e-002, 1.067886e-002, 1.074872e-002, -1.085869e-002, 1.100744e-002, 1.115670e-002, 1.128521e-002, -1.140377e-002, 1.158208e-002, 1.163734e-002, 1.148630e-002, -1.132360e-002, 1.115935e-002, 1.098737e-002, 1.082004e-002, -1.068577e-002, 1.057331e-002, 1.046538e-002, 1.037540e-002, -1.021240e-002, 9.990249e-003, 9.671927e-003, 9.360159e-003, -9.098900e-003, 8.709533e-003, 8.166034e-003, 7.653474e-003, -7.140204e-003, 6.670485e-003, 6.258721e-003, 5.855775e-003, -5.526621e-003, 5.285678e-003, 5.136632e-003, 5.061678e-003, -5.032726e-003, 5.007274e-003, 5.036058e-003, 5.136255e-003, -5.269003e-003, 5.416654e-003, 5.604207e-003, 5.737814e-003, -5.906916e-003, 6.010574e-003, 5.934993e-003, 5.821985e-003, -5.680258e-003, 5.548788e-003, 5.412331e-003, 5.255356e-003, -5.126781e-003, 5.021458e-003, 4.969456e-003, 4.924835e-003, -4.848574e-003, 4.676046e-003, 4.482570e-003, 4.306927e-003, -4.143328e-003, 4.031528e-003, 3.957084e-003, 3.870657e-003, -3.743736e-003, 3.578103e-003, 3.399313e-003, 3.195686e-003, -3.003193e-003, 2.831148e-003, 2.651942e-003, 2.570384e-003, -2.414776e-003, 2.207626e-003, 2.028747e-003, 1.804843e-003, -1.437045e-003, 1.085409e-003, 7.768265e-004, 5.257356e-004, -2.832211e-004, 7.939926e-005, -8.185038e-005, -2.642934e-004, --3.893426e-004, -4.803039e-004, -5.583388e-004, -6.912243e-004, --8.922830e-004, -1.103698e-003, -1.313241e-003, -1.539198e-003, --1.744262e-003, -1.898197e-003, -2.000999e-003, -2.089315e-003, --2.188149e-003, -2.387017e-003, -2.549236e-003, -2.688044e-003, --2.787079e-003, -2.841859e-003, -2.930464e-003, -3.068594e-003, --3.248214e-003, -3.437049e-003, -3.606165e-003, -3.747210e-003, --3.915627e-003, -4.083446e-003, -4.223562e-003, -4.342259e-003, --4.500291e-003, -4.674399e-003, -4.828653e-003, -4.987135e-003, --5.080731e-003, -5.196160e-003, -5.313727e-003, -5.406999e-003, --5.497271e-003, -5.611920e-003, -5.699726e-003, -5.774775e-003, --5.827751e-003, -5.860136e-003, -5.835875e-003, -5.812617e-003, --5.794548e-003, -5.754962e-003, -5.696424e-003, -5.627999e-003, --5.525310e-003, -5.407427e-003, -5.340450e-003, -5.273184e-003, --5.228324e-003, -5.194937e-003, -5.141431e-003, -5.166114e-003, --5.156070e-003, -5.140736e-003, -5.125548e-003, -5.113386e-003, --5.027214e-003, -4.929130e-003, -4.829162e-003, -4.780176e-003, --4.767687e-003, -4.764598e-003, -4.805533e-003, -4.842525e-003, --4.877159e-003, -4.903936e-003, -4.925239e-003, -4.910348e-003, --4.901364e-003, -4.865290e-003, -4.795979e-003, -4.682319e-003, --4.548763e-003, -4.441000e-003, -4.335957e-003, -4.195996e-003, --4.026592e-003, -3.855323e-003, -3.679350e-003, -3.615158e-003, --3.563790e-003, -3.507993e-003, -3.400825e-003, -3.313285e-003, --3.210967e-003, -3.132612e-003, -3.060857e-003, -2.992745e-003, --2.928459e-003, -2.873708e-003, -2.890812e-003, -2.932178e-003, --3.007318e-003, -3.126197e-003, -3.259832e-003, -3.387900e-003, --3.523348e-003, -3.674451e-003, -3.827247e-003, -3.969389e-003, --4.095850e-003, -4.199474e-003, -4.295658e-003, -4.376171e-003, --4.451044e-003, -4.484057e-003, -4.466344e-003, -4.433656e-003, --4.410333e-003, -4.413169e-003, -4.379511e-003, -4.319999e-003, --4.251073e-003, -4.117601e-003, -3.926462e-003, -3.756393e-003, --3.564936e-003, -3.376022e-003, -3.148865e-003, -2.893821e-003, --2.669323e-003, -2.422560e-003, -2.149523e-003, -1.850924e-003, --1.550038e-003, -1.295857e-003, -1.137224e-003, -1.010534e-003, --9.196924e-004, -8.734266e-004, -8.723178e-004, -8.866582e-004, --9.112268e-004, -9.921129e-004, -1.099246e-003, -1.222787e-003, --1.362810e-003, -1.491410e-003, -1.596758e-003, -1.705382e-003, --1.814625e-003, -1.943649e-003, -2.043062e-003, -2.134246e-003, --2.252688e-003, -2.374122e-003, -2.431223e-003, -2.439711e-003, --2.415068e-003, -2.330210e-003, -2.182512e-003, -1.979074e-003, --1.746538e-003, -1.541191e-003, -1.402165e-003, -1.279310e-003, --1.192885e-003, -1.118719e-003, -1.065302e-003, -1.025728e-003, --9.944830e-004, -9.799140e-004, -9.764293e-004, -9.627254e-004, --9.227947e-004, -8.766555e-004, -8.536206e-004, -8.899056e-004, --9.019655e-004, -9.178436e-004, -9.004027e-004, -8.456344e-004, --7.583629e-004, -6.377012e-004, -4.864260e-004, -3.816787e-004, --2.541675e-004, -9.779120e-005, 1.130500e-004, 3.961810e-004, -6.761234e-004, 9.783680e-004, 1.278447e-003, 1.638380e-003, -2.012629e-003, 2.421799e-003, 2.842232e-003, 3.264205e-003, -3.729394e-003, 4.231021e-003, 4.765229e-003, 5.282722e-003, -5.788358e-003, 6.289456e-003, 6.749198e-003, 7.024100e-003, -7.207942e-003, 7.334569e-003, 7.455426e-003, 7.560493e-003, -7.665876e-003, 7.734223e-003, 7.781479e-003, 7.816486e-003, -7.813068e-003, 7.785428e-003, 7.735542e-003, 7.681516e-003, -7.602725e-003, 7.524671e-003, 7.445705e-003, 7.304258e-003, -7.137539e-003, 6.980915e-003, 6.814667e-003, 6.628111e-003, -6.451876e-003, 6.272940e-003, 6.108483e-003, 5.931899e-003, -5.721106e-003, 5.449523e-003, 5.169551e-003, 4.928307e-003, -4.714848e-003, 4.510251e-003, 4.303939e-003, 4.094503e-003, -3.880572e-003, 3.621729e-003, 3.359829e-003, 3.141626e-003, -2.931855e-003, 2.740337e-003, 2.588937e-003, 2.431991e-003, -2.313353e-003, 2.210978e-003, 2.126756e-003, 1.990951e-003, -1.822069e-003, 1.731355e-003, 1.663912e-003, 1.608721e-003, -1.581127e-003, 1.560562e-003, 1.561507e-003, 1.570561e-003, -1.602764e-003, 1.614179e-003, 1.595199e-003, 1.595560e-003, -1.595194e-003, 1.592725e-003, 1.629488e-003, 1.653593e-003, -1.649492e-003, 1.604501e-003, 1.520757e-003, 1.413421e-003, -1.295971e-003, 1.182100e-003, 1.073205e-003, 9.057418e-004, -7.393787e-004, 5.609143e-004, 3.536729e-004, 1.109452e-004, --1.645280e-004, -4.681189e-004, -7.657539e-004, -1.086370e-003, --1.387928e-003, -1.696252e-003, -1.997915e-003, -2.281441e-003, --2.574554e-003, -2.926901e-003, -3.256042e-003, -3.573890e-003, --3.882270e-003, -4.177003e-003, -4.394570e-003, -4.577888e-003, --4.678746e-003, -4.708638e-003, -4.794129e-003, -4.880412e-003, --4.958695e-003, -5.042706e-003, -5.125962e-003, -5.227551e-003, --5.354214e-003, -5.447163e-003, -5.527750e-003, -5.604583e-003, --5.724183e-003, -5.882734e-003, -6.076476e-003, -6.297282e-003, --6.535573e-003, -6.785053e-003, -6.984168e-003, -7.067299e-003, --7.118797e-003, -7.146493e-003, -7.278332e-003, -7.427251e-003, --7.589382e-003, -7.779550e-003, -7.965826e-003, -8.132341e-003, --8.298978e-003, -8.408957e-003, -8.277868e-003, -8.085832e-003, --7.844389e-003, -7.576261e-003, -7.286521e-003, -6.935731e-003, --6.579726e-003, -6.345295e-003, -6.136374e-003, -5.949192e-003, --5.728288e-003, -5.154815e-003, -4.592246e-003, -4.013779e-003, --3.421577e-003, -2.848376e-003, -2.301668e-003, -1.778515e-003, --1.287604e-003, -8.249034e-004, -3.974324e-004, 6.021932e-018 -}, -{ -0.000000e+000, -1.419539e-001, -1.036124e-001, -6.792405e-002, --2.708800e-002, 1.517038e-002, 4.019696e-002, 5.969302e-002, -7.318021e-002, 7.955336e-002, 7.740017e-002, 6.786770e-002, -5.264640e-002, 3.606222e-002, 2.435019e-002, 1.902869e-002, -1.784040e-002, 2.015166e-002, 2.176854e-002, 2.380658e-002, -2.387411e-002, 1.935135e-002, 1.038963e-002, -1.001266e-003, --1.433260e-002, -2.778570e-002, -4.081063e-002, -5.304675e-002, --6.476809e-002, -7.332503e-002, -7.868723e-002, -8.142049e-002, --8.262070e-002, -8.287768e-002, -8.119369e-002, -7.735994e-002, --7.207675e-002, -6.624884e-002, -5.734553e-002, -4.611067e-002, --3.305934e-002, -2.016471e-002, -6.374361e-003, 8.127395e-003, -2.130722e-002, 3.278650e-002, 4.353354e-002, 5.205411e-002, -5.887513e-002, 6.586277e-002, 7.168401e-002, 7.693746e-002, -8.264459e-002, 8.641797e-002, 9.037891e-002, 9.304619e-002, -9.399092e-002, 9.404765e-002, 9.111092e-002, 8.638032e-002, -7.929395e-002, 7.036889e-002, 5.953161e-002, 4.825266e-002, -3.633480e-002, 2.367534e-002, 1.052038e-002, -2.657346e-003, --1.589899e-002, -2.896741e-002, -4.152623e-002, -5.338227e-002, --6.409219e-002, -7.406542e-002, -8.205233e-002, -9.017282e-002, --9.689866e-002, -1.023482e-001, -1.055274e-001, -1.067142e-001, --1.067523e-001, -1.041731e-001, -1.007724e-001, -9.483050e-002, --8.787849e-002, -7.957985e-002, -6.943558e-002, -5.877117e-002, --4.777642e-002, -3.635834e-002, -2.434076e-002, -1.169129e-002, -4.245005e-004, 1.149325e-002, 2.258445e-002, 3.241976e-002, -4.132175e-002, 4.970854e-002, 5.553423e-002, 6.049711e-002, -6.509272e-002, 6.927640e-002, 7.257623e-002, 7.518055e-002, -7.756234e-002, 7.945192e-002, 8.039453e-002, 8.097967e-002, -8.128767e-002, 8.108285e-002, 7.982047e-002, 7.824626e-002, -7.656209e-002, 7.350573e-002, 7.002981e-002, 6.663025e-002, -6.219666e-002, 5.728286e-002, 5.233078e-002, 4.657957e-002, -4.063100e-002, 3.430746e-002, 2.768315e-002, 2.085859e-002, -1.364530e-002, 6.248875e-003, -1.250127e-003, -8.652431e-003, --1.588737e-002, -2.317202e-002, -2.976850e-002, -3.625468e-002, --4.235110e-002, -4.798755e-002, -5.290627e-002, -5.679774e-002, --5.985921e-002, -6.230819e-002, -6.392358e-002, -6.487407e-002, --6.509388e-002, -6.480806e-002, -6.428655e-002, -6.324443e-002, --6.185623e-002, -6.007233e-002, -5.821870e-002, -5.653940e-002, --5.441352e-002, -5.258841e-002, -5.119751e-002, -4.981180e-002, --4.884794e-002, -4.856944e-002, -4.807488e-002, -4.738218e-002, --4.656297e-002, -4.545571e-002, -4.451550e-002, -4.302666e-002, --4.098589e-002, -3.906619e-002, -3.745225e-002, -3.521159e-002, --3.305556e-002, -3.075919e-002, -2.816611e-002, -2.553889e-002, --2.286192e-002, -2.012358e-002, -1.677399e-002, -1.321086e-002, --9.671028e-003, -6.194379e-003, -2.438305e-003, 1.305949e-003, -4.998369e-003, 9.002757e-003, 1.307251e-002, 1.685137e-002, -2.035346e-002, 2.406746e-002, 2.767851e-002, 3.103426e-002, -3.420898e-002, 3.733744e-002, 4.021906e-002, 4.268473e-002, -4.501986e-002, 4.693454e-002, 4.883143e-002, 5.010330e-002, -5.092922e-002, 5.144524e-002, 5.156375e-002, 5.134105e-002, -5.069609e-002, 4.997770e-002, 4.896712e-002, 4.802644e-002, -4.688289e-002, 4.557125e-002, 4.409246e-002, 4.265972e-002, -4.112532e-002, 3.964203e-002, 3.808217e-002, 3.666312e-002, -3.534755e-002, 3.392347e-002, 3.280277e-002, 3.155641e-002, -3.068470e-002, 2.954000e-002, 2.834969e-002, 2.719150e-002, -2.587153e-002, 2.505802e-002, 2.408689e-002, 2.314924e-002, -2.221252e-002, 2.126470e-002, 2.086830e-002, 2.040811e-002, -2.010343e-002, 1.988996e-002, 1.935163e-002, 1.899015e-002, -1.861569e-002, 1.821986e-002, 1.796585e-002, 1.752384e-002, -1.709545e-002, 1.650732e-002, 1.589593e-002, 1.520503e-002, -1.440425e-002, 1.365198e-002, 1.264166e-002, 1.127104e-002, -9.841914e-003, 8.339769e-003, 6.712574e-003, 4.933912e-003, -2.884468e-003, 5.985493e-004, -1.708059e-003, -3.991786e-003, --6.257966e-003, -8.585060e-003, -1.100853e-002, -1.385632e-002, --1.659416e-002, -1.910374e-002, -2.157096e-002, -2.398674e-002, --2.617890e-002, -2.825907e-002, -3.018520e-002, -3.190477e-002, --3.336141e-002, -3.466525e-002, -3.578901e-002, -3.680746e-002, --3.771924e-002, -3.827121e-002, -3.877290e-002, -3.913692e-002, --3.950250e-002, -3.981678e-002, -4.004068e-002, -4.023469e-002, --4.030622e-002, -4.033762e-002, -4.025896e-002, -4.013123e-002, --3.998137e-002, -3.978413e-002, -3.967819e-002, -3.939395e-002, --3.883163e-002, -3.810040e-002, -3.757120e-002, -3.705130e-002, --3.640822e-002, -3.584473e-002, -3.522015e-002, -3.416148e-002, --3.278193e-002, -3.134782e-002, -2.977984e-002, -2.829860e-002, --2.696610e-002, -2.551638e-002, -2.404139e-002, -2.224460e-002, --2.022519e-002, -1.829419e-002, -1.649663e-002, -1.473209e-002, --1.310277e-002, -1.167724e-002, -1.034436e-002, -8.846850e-003, --7.353908e-003, -5.725595e-003, -4.293107e-003, -3.148903e-003, --2.236444e-003, -1.645302e-003, -1.233031e-003, -4.919654e-004, -1.413101e-004, 5.636048e-004, 1.077949e-003, 1.506724e-003, -1.655457e-003, 1.694085e-003, 1.580385e-003, 1.407083e-003, -1.290979e-003, 1.241103e-003, 1.164737e-003, 7.979683e-004, -6.047147e-004, 5.998700e-004, 4.217476e-004, 2.288406e-004, -1.315521e-004, 1.019066e-004, 1.381810e-004, 4.759979e-004, -1.007209e-003, 1.238664e-003, 1.383367e-003, 1.443117e-003, -1.410411e-003, 1.365550e-003, 1.273418e-003, 1.424101e-003, -1.407576e-003, 1.630785e-003, 1.940799e-003, 2.176384e-003, -2.280105e-003, 2.394124e-003, 2.835923e-003, 3.174333e-003, -3.526331e-003, 3.968612e-003, 4.578331e-003, 5.055281e-003, -5.761884e-003, 6.541719e-003, 7.434230e-003, 8.211070e-003, -9.345373e-003, 1.054248e-002, 1.162063e-002, 1.261968e-002, -1.374627e-002, 1.506815e-002, 1.660291e-002, 1.806392e-002, -1.970821e-002, 2.139701e-002, 2.289452e-002, 2.448306e-002, -2.601973e-002, 2.743160e-002, 2.882312e-002, 3.021573e-002, -3.158087e-002, 3.285834e-002, 3.407062e-002, 3.516235e-002, -3.628794e-002, 3.720631e-002, 3.800676e-002, 3.885607e-002, -3.964268e-002, 4.028641e-002, 4.062831e-002, 4.108059e-002, -4.148136e-002, 4.168244e-002, 4.176866e-002, 4.181152e-002, -4.198007e-002, 4.218633e-002, 4.237012e-002, 4.253384e-002, -4.259278e-002, 4.243740e-002, 4.206305e-002, 4.150209e-002, -4.086748e-002, 4.016169e-002, 3.960730e-002, 3.887417e-002, -3.806509e-002, 3.697609e-002, 3.571246e-002, 3.438925e-002, -3.301974e-002, 3.150530e-002, 2.962946e-002, 2.769375e-002, -2.577277e-002, 2.379602e-002, 2.163412e-002, 1.946542e-002, -1.734635e-002, 1.509087e-002, 1.262924e-002, 1.015724e-002, -7.902637e-003, 5.665930e-003, 3.406363e-003, 1.172860e-003, --9.998845e-004, -3.263325e-003, -5.638867e-003, -8.192518e-003, --1.071512e-002, -1.299464e-002, -1.505132e-002, -1.706454e-002, --1.902563e-002, -2.067841e-002, -2.190756e-002, -2.302112e-002, --2.424621e-002, -2.530534e-002, -2.628679e-002, -2.722248e-002, --2.806121e-002, -2.894963e-002, -2.989156e-002, -3.077421e-002, --3.140256e-002, -3.212894e-002, -3.264985e-002, -3.302154e-002, --3.324528e-002, -3.348531e-002, -3.370481e-002, -3.382639e-002, --3.389419e-002, -3.400893e-002, -3.426127e-002, -3.452348e-002, --3.477738e-002, -3.491843e-002, -3.492807e-002, -3.476538e-002, --3.436834e-002, -3.393744e-002, -3.331573e-002, -3.268536e-002, --3.194480e-002, -3.123312e-002, -3.047864e-002, -2.961140e-002, --2.868011e-002, -2.774287e-002, -2.679371e-002, -2.586089e-002, --2.482176e-002, -2.366655e-002, -2.243803e-002, -2.125467e-002, --1.989082e-002, -1.853484e-002, -1.720170e-002, -1.585174e-002, --1.459108e-002, -1.324508e-002, -1.193631e-002, -1.071926e-002, --9.540640e-003, -8.517981e-003, -7.488759e-003, -6.416223e-003, --5.386322e-003, -4.353601e-003, -3.370936e-003, -2.506711e-003, --1.788548e-003, -1.242683e-003, -6.646805e-004, -1.758541e-004, -3.037850e-004, 7.090468e-004, 9.946048e-004, 1.409651e-003, -1.777146e-003, 2.075085e-003, 2.318047e-003, 2.646397e-003, -3.001634e-003, 3.335471e-003, 3.609256e-003, 3.852900e-003, -3.941584e-003, 3.848084e-003, 3.729051e-003, 3.693554e-003, -3.659795e-003, 3.448675e-003, 3.068618e-003, 2.594031e-003, -2.213261e-003, 1.758666e-003, 1.230598e-003, 5.974798e-004, -1.002171e-004, -3.511939e-004, -7.997501e-004, -1.291263e-003, --1.827102e-003, -2.317758e-003, -2.901357e-003, -3.432364e-003, --3.863615e-003, -4.343495e-003, -4.930273e-003, -5.421391e-003, --5.859212e-003, -6.179578e-003, -6.463739e-003, -6.845580e-003, --7.266627e-003, -7.589516e-003, -7.702766e-003, -7.427188e-003, --7.142174e-003, -6.800878e-003, -6.367102e-003, -5.864443e-003, --5.382831e-003, -4.881120e-003, -4.481691e-003, -4.207930e-003, --4.041625e-003, -3.913481e-003, -3.798549e-003, -3.659885e-003, --3.447355e-003, -3.227707e-003, -3.052418e-003, -2.849870e-003, --2.540188e-003, -2.169183e-003, -1.623437e-003, -9.806347e-004, --2.884090e-004, 4.951020e-004, 1.268905e-003, 2.020584e-003, -2.804607e-003, 3.686111e-003, 4.623393e-003, 5.558912e-003, -6.590802e-003, 7.683101e-003, 8.814030e-003, 9.926143e-003, -1.098814e-002, 1.202529e-002, 1.312637e-002, 1.433848e-002, -1.557870e-002, 1.672425e-002, 1.785887e-002, 1.900206e-002, -2.008627e-002, 2.110877e-002, 2.206490e-002, 2.294132e-002, -2.383328e-002, 2.463579e-002, 2.523893e-002, 2.572642e-002, -2.616126e-002, 2.653914e-002, 2.681667e-002, 2.698384e-002, -2.701180e-002, 2.705757e-002, 2.710002e-002, 2.717537e-002, -2.721729e-002, 2.722810e-002, 2.723821e-002, 2.725637e-002, -2.729546e-002, 2.723936e-002, 2.713780e-002, 2.694573e-002, -2.668998e-002, 2.644269e-002, 2.620186e-002, 2.597265e-002, -2.581422e-002, 2.569973e-002, 2.557705e-002, 2.544458e-002, -2.522740e-002, 2.494625e-002, 2.455333e-002, 2.410170e-002, -2.351828e-002, 2.282917e-002, 2.211129e-002, 2.135670e-002, -2.053562e-002, 1.963763e-002, 1.866019e-002, 1.760652e-002, -1.658837e-002, 1.564097e-002, 1.469566e-002, 1.369593e-002, -1.267910e-002, 1.164322e-002, 1.050212e-002, 9.382651e-003, -8.228941e-003, 7.003910e-003, 5.715816e-003, 4.385959e-003, -3.187868e-003, 2.077364e-003, 1.056469e-003, 1.056810e-004, --8.166558e-004, -1.749681e-003, -2.671533e-003, -3.560762e-003, --4.396105e-003, -5.183609e-003, -5.961650e-003, -6.726190e-003, --7.532381e-003, -8.388815e-003, -9.148416e-003, -1.001064e-002, --1.090672e-002, -1.186608e-002, -1.281656e-002, -1.391314e-002, --1.509008e-002, -1.630194e-002, -1.748983e-002, -1.866812e-002, --1.990342e-002, -2.124323e-002, -2.266669e-002, -2.409413e-002, --2.540345e-002, -2.651204e-002, -2.759958e-002, -2.860726e-002, --2.963686e-002, -3.066199e-002, -3.170440e-002, -3.271547e-002, --3.366741e-002, -3.451744e-002, -3.526645e-002, -3.597432e-002, --3.654182e-002, -3.686749e-002, -3.708588e-002, -3.716189e-002, --3.717350e-002, -3.712003e-002, -3.705128e-002, -3.693900e-002, --3.687041e-002, -3.681770e-002, -3.672809e-002, -3.663553e-002, --3.660483e-002, -3.660009e-002, -3.643445e-002, -3.608549e-002, --3.582227e-002, -3.563342e-002, -3.544855e-002, -3.533970e-002, --3.521676e-002, -3.507009e-002, -3.489187e-002, -3.474534e-002, --3.462618e-002, -3.442417e-002, -3.418774e-002, -3.397048e-002, --3.368356e-002, -3.338937e-002, -3.305459e-002, -3.269441e-002, --3.198629e-002, -3.111630e-002, -3.022737e-002, -2.931267e-002, --2.834872e-002, -2.726518e-002, -2.609620e-002, -2.487626e-002, --2.359541e-002, -2.236799e-002, -2.118645e-002, -2.003864e-002, --1.887809e-002, -1.777604e-002, -1.671133e-002, -1.569087e-002, --1.470441e-002, -1.374682e-002, -1.284282e-002, -1.201802e-002, --1.114242e-002, -1.026903e-002, -9.444259e-003, -8.502198e-003, --7.545191e-003, -6.625737e-003, -5.724872e-003, -4.875653e-003, --4.011697e-003, -3.114425e-003, -2.133028e-003, -1.132353e-003, --7.581203e-006, 1.138889e-003, 2.263788e-003, 3.370658e-003, -4.518324e-003, 5.673439e-003, 6.774203e-003, 7.822776e-003, -8.848177e-003, 9.819092e-003, 1.071289e-002, 1.154698e-002, -1.233961e-002, 1.308728e-002, 1.368194e-002, 1.416295e-002, -1.455475e-002, 1.490606e-002, 1.526442e-002, 1.561591e-002, -1.594705e-002, 1.624214e-002, 1.677711e-002, 1.738704e-002, -1.801871e-002, 1.862352e-002, 1.921178e-002, 1.969546e-002, -2.012506e-002, 2.044887e-002, 2.084089e-002, 2.135367e-002, -2.194371e-002, 2.262269e-002, 2.341510e-002, 2.427020e-002, -2.509585e-002, 2.587514e-002, 2.668172e-002, 2.745540e-002, -2.812808e-002, 2.872863e-002, 2.924824e-002, 2.970274e-002, -3.004808e-002, 3.029362e-002, 3.046823e-002, 3.055698e-002, -3.058566e-002, 3.056395e-002, 3.047943e-002, 3.038018e-002, -3.015302e-002, 2.981492e-002, 2.940199e-002, 2.894101e-002, -2.849902e-002, 2.808541e-002, 2.770490e-002, 2.731710e-002, -2.688582e-002, 2.647155e-002, 2.608558e-002, 2.591296e-002, -2.581973e-002, 2.575380e-002, 2.571715e-002, 2.572992e-002, -2.577940e-002, 2.589100e-002, 2.607847e-002, 2.634906e-002, -2.659737e-002, 2.681429e-002, 2.698701e-002, 2.714503e-002, -2.725628e-002, 2.729188e-002, 2.723950e-002, 2.706785e-002, -2.679017e-002, 2.632206e-002, 2.570716e-002, 2.502466e-002, -2.428319e-002, 2.334337e-002, 2.231898e-002, 2.122946e-002, -2.013992e-002, 1.905499e-002, 1.799630e-002, 1.693870e-002, -1.588512e-002, 1.485546e-002, 1.376223e-002, 1.264511e-002, -1.151455e-002, 1.043219e-002, 9.414249e-003, 8.489298e-003, -7.670184e-003, 6.923129e-003, 6.231918e-003, 5.602638e-003, -5.008505e-003, 4.501276e-003, 4.072697e-003, 3.668189e-003, -3.220896e-003, 2.748418e-003, 2.191690e-003, 1.597310e-003, -9.583032e-004, 2.915937e-004, -3.414514e-004, -1.006103e-003, --1.716042e-003, -2.454044e-003, -3.131360e-003, -3.851934e-003, --4.556372e-003, -5.288167e-003, -6.098473e-003, -6.879084e-003, --7.646168e-003, -8.386765e-003, -9.097550e-003, -9.790325e-003, --1.046676e-002, -1.111886e-002, -1.174480e-002, -1.229662e-002, --1.275620e-002, -1.311655e-002, -1.344013e-002, -1.370861e-002, --1.395867e-002, -1.419176e-002, -1.438439e-002, -1.452059e-002, --1.456022e-002, -1.447935e-002, -1.432253e-002, -1.405163e-002, --1.376406e-002, -1.351821e-002, -1.326792e-002, -1.302730e-002, --1.276939e-002, -1.248687e-002, -1.210445e-002, -1.169238e-002, --1.128910e-002, -1.087348e-002, -1.046596e-002, -1.003700e-002, --9.591587e-003, -9.137892e-003, -8.684494e-003, -8.265109e-003, --7.879819e-003, -7.487533e-003, -7.078767e-003, -6.663434e-003, --6.218614e-003, -5.754517e-003, -5.350267e-003, -4.991624e-003, --4.649861e-003, -4.300304e-003, -3.951166e-003, -3.712821e-003, --3.546430e-003, -3.414193e-003, -3.331016e-003, -3.299512e-003, --3.278189e-003, -3.264178e-003, -3.251670e-003, -3.279345e-003, --3.358939e-003, -3.549348e-003, -3.799394e-003, -4.082149e-003, --4.391320e-003, -4.710511e-003, -5.044619e-003, -5.410087e-003, --5.711682e-003, -5.996753e-003, -6.340770e-003, -6.584797e-003, --6.727178e-003, -6.768959e-003, -6.777004e-003, -6.771874e-003, --6.786380e-003, -6.778971e-003, -6.805732e-003, -6.859159e-003, --6.962634e-003, -7.052824e-003, -7.162723e-003, -7.256644e-003, --7.347714e-003, -7.450831e-003, -7.590599e-003, -7.678282e-003, --7.770598e-003, -7.932179e-003, -8.099055e-003, -8.254360e-003, --8.401964e-003, -8.529252e-003, -8.638737e-003, -8.706753e-003, --8.708989e-003, -8.640035e-003, -8.552731e-003, -8.461409e-003, --8.355552e-003, -8.320266e-003, -8.273267e-003, -8.183507e-003, --8.035477e-003, -7.841541e-003, -7.603160e-003, -7.317098e-003, --7.023905e-003, -6.754152e-003, -6.494216e-003, -6.191246e-003, --5.855130e-003, -5.490166e-003, -5.107889e-003, -4.692356e-003, --4.183558e-003, -3.597480e-003, -3.021860e-003, -2.368777e-003, --1.732934e-003, -1.128342e-003, -5.544300e-004, -2.789279e-005, -4.621089e-004, 9.479503e-004, 1.355681e-003, 1.661723e-003, -1.846546e-003, 1.966050e-003, 2.029690e-003, 2.058399e-003, -2.069139e-003, 2.085198e-003, 2.086770e-003, 2.118438e-003, -2.191080e-003, 2.283026e-003, 2.280907e-003, 2.282985e-003, -2.254391e-003, 2.112161e-003, 1.946159e-003, 1.757909e-003, -1.599493e-003, 1.512843e-003, 1.589365e-003, 1.736455e-003, -1.947146e-003, 2.212924e-003, 2.511569e-003, 2.772376e-003, -3.034046e-003, 3.178168e-003, 3.165527e-003, 3.055279e-003, -2.938614e-003, 3.061092e-003, 3.094326e-003, 3.032048e-003, -2.882782e-003, 2.681154e-003, 2.409797e-003, 2.075953e-003, -1.665721e-003, 1.188743e-003, 6.284173e-004, 2.064357e-017 -}, -{ -0.000000e+000, -5.884035e-003, 1.206050e-002, 3.049206e-002, -3.003751e-002, 1.966938e-002, 1.709589e-002, 1.097572e-002, -5.710203e-003, 3.384460e-003, 5.629450e-003, 1.143536e-002, -1.917816e-002, 2.587902e-002, 2.589293e-002, 1.871079e-002, -5.688994e-003, -1.257538e-002, -3.206193e-002, -5.227155e-002, --6.826732e-002, -7.666923e-002, -7.778825e-002, -7.387267e-002, --6.796031e-002, -5.995261e-002, -4.956054e-002, -3.672650e-002, --2.252167e-002, -8.694235e-003, 4.488508e-003, 1.696310e-002, -2.825635e-002, 3.791796e-002, 4.659577e-002, 5.390258e-002, -5.931234e-002, 6.192430e-002, 6.156818e-002, 5.863152e-002, -5.357603e-002, 4.669202e-002, 3.916060e-002, 3.117202e-002, -2.359120e-002, 1.704662e-002, 1.212091e-002, 8.710634e-003, -5.831097e-003, 3.360740e-003, 5.391114e-004, -3.764149e-003, --9.071557e-003, -1.531453e-002, -2.255599e-002, -2.966411e-002, --3.601688e-002, -4.185548e-002, -4.654736e-002, -4.908090e-002, --4.971211e-002, -4.805107e-002, -4.496298e-002, -4.147839e-002, --3.795349e-002, -3.409726e-002, -2.930175e-002, -2.347795e-002, --1.686408e-002, -1.021798e-002, -3.376739e-003, 3.754763e-003, -1.130456e-002, 1.885608e-002, 2.589634e-002, 3.323095e-002, -4.051716e-002, 4.714532e-002, 5.248908e-002, 5.655314e-002, -5.943179e-002, 6.044005e-002, 6.046374e-002, 5.946990e-002, -5.665442e-002, 5.263206e-002, 4.804912e-002, 4.197308e-002, -3.557421e-002, 2.879640e-002, 2.130866e-002, 1.412141e-002, -6.402630e-003, -3.954837e-004, -6.820323e-003, -1.268624e-002, --1.766589e-002, -2.165018e-002, -2.393816e-002, -2.563379e-002, --2.677766e-002, -2.823566e-002, -2.937754e-002, -3.041117e-002, --3.146949e-002, -3.267914e-002, -3.384882e-002, -3.490533e-002, --3.612919e-002, -3.728481e-002, -3.788315e-002, -3.844097e-002, --3.912957e-002, -3.894780e-002, -3.844641e-002, -3.738447e-002, --3.592443e-002, -3.402646e-002, -3.156060e-002, -2.857174e-002, --2.479943e-002, -2.073766e-002, -1.614759e-002, -1.124309e-002, --6.154171e-003, -8.895209e-004, 4.301080e-003, 9.167609e-003, -1.394155e-002, 1.858114e-002, 2.283719e-002, 2.696660e-002, -3.068767e-002, 3.329175e-002, 3.510834e-002, 3.621983e-002, -3.628220e-002, 3.552816e-002, 3.418535e-002, 3.208951e-002, -2.929572e-002, 2.604667e-002, 2.294167e-002, 1.937953e-002, -1.562590e-002, 1.189731e-002, 8.045938e-003, 4.237038e-003, -4.310596e-004, -2.706980e-003, -5.062185e-003, -7.028978e-003, --8.336149e-003, -8.766212e-003, -8.931890e-003, -8.995374e-003, --9.001025e-003, -8.741414e-003, -8.518932e-003, -8.407627e-003, --8.284246e-003, -8.117318e-003, -7.625531e-003, -7.526594e-003, --7.456860e-003, -7.662614e-003, -7.723305e-003, -7.689266e-003, --7.542797e-003, -7.756374e-003, -8.293640e-003, -8.805057e-003, --9.057601e-003, -9.720454e-003, -1.066413e-002, -1.172893e-002, --1.242470e-002, -1.312386e-002, -1.394897e-002, -1.488869e-002, --1.518750e-002, -1.563231e-002, -1.633437e-002, -1.684247e-002, --1.722660e-002, -1.777575e-002, -1.780741e-002, -1.778761e-002, --1.763925e-002, -1.714208e-002, -1.649148e-002, -1.562869e-002, --1.416601e-002, -1.295431e-002, -1.101993e-002, -8.948106e-003, --6.121230e-003, -3.173768e-003, 1.660451e-004, 3.487262e-003, -6.887342e-003, 1.031193e-002, 1.346142e-002, 1.656899e-002, -1.942181e-002, 2.240880e-002, 2.498714e-002, 2.751651e-002, -2.986261e-002, 3.191622e-002, 3.363757e-002, 3.495018e-002, -3.587321e-002, 3.667872e-002, 3.692653e-002, 3.719468e-002, -3.700629e-002, 3.650387e-002, 3.592412e-002, 3.520577e-002, -3.447157e-002, 3.337643e-002, 3.201951e-002, 3.043860e-002, -2.849360e-002, 2.664718e-002, 2.451794e-002, 2.237383e-002, -2.002122e-002, 1.757373e-002, 1.514208e-002, 1.270289e-002, -1.043545e-002, 8.049355e-003, 5.636358e-003, 3.536537e-003, -1.348231e-003, -8.218181e-004, -2.493908e-003, -3.951792e-003, --5.248754e-003, -6.500497e-003, -7.593310e-003, -8.091750e-003, --8.625289e-003, -8.914666e-003, -8.996373e-003, -8.989998e-003, --8.992182e-003, -8.942410e-003, -8.802284e-003, -8.096253e-003, --7.462768e-003, -6.823762e-003, -6.019022e-003, -5.316966e-003, --4.808466e-003, -4.183201e-003, -3.691915e-003, -3.272723e-003, --2.804278e-003, -2.752393e-003, -2.858813e-003, -3.139648e-003, --3.631077e-003, -4.178289e-003, -4.837927e-003, -5.415356e-003, --5.852108e-003, -6.244190e-003, -6.752684e-003, -7.179716e-003, --7.599783e-003, -8.096081e-003, -8.604389e-003, -9.210337e-003, --9.832264e-003, -1.044150e-002, -1.105628e-002, -1.175909e-002, --1.250079e-002, -1.318013e-002, -1.376539e-002, -1.421848e-002, --1.470018e-002, -1.535426e-002, -1.593455e-002, -1.644070e-002, --1.696452e-002, -1.762929e-002, -1.828440e-002, -1.903388e-002, --1.977325e-002, -2.060567e-002, -2.139396e-002, -2.226622e-002, --2.327479e-002, -2.422805e-002, -2.524178e-002, -2.625225e-002, --2.732794e-002, -2.848347e-002, -2.933124e-002, -3.004910e-002, --3.077191e-002, -3.159298e-002, -3.236835e-002, -3.290230e-002, --3.331127e-002, -3.345762e-002, -3.320700e-002, -3.271422e-002, --3.203304e-002, -3.113345e-002, -2.991892e-002, -2.850035e-002, --2.686171e-002, -2.504731e-002, -2.311087e-002, -2.094423e-002, --1.855454e-002, -1.603842e-002, -1.345968e-002, -1.066311e-002, --7.677695e-003, -4.693214e-003, -1.804628e-003, 1.198819e-003, -4.050774e-003, 6.727898e-003, 9.258785e-003, 1.175012e-002, -1.410771e-002, 1.640370e-002, 1.872032e-002, 2.106224e-002, -2.347714e-002, 2.588787e-002, 2.832950e-002, 3.066889e-002, -3.292749e-002, 3.505272e-002, 3.707771e-002, 3.897498e-002, -4.074446e-002, 4.245702e-002, 4.395962e-002, 4.521019e-002, -4.619311e-002, 4.688558e-002, 4.750285e-002, 4.794766e-002, -4.816383e-002, 4.810482e-002, 4.792793e-002, 4.729618e-002, -4.652057e-002, 4.558017e-002, 4.452366e-002, 4.319393e-002, -4.170817e-002, 3.992725e-002, 3.788809e-002, 3.552905e-002, -3.306365e-002, 3.054822e-002, 2.812698e-002, 2.558530e-002, -2.297020e-002, 2.032699e-002, 1.759461e-002, 1.491980e-002, -1.230803e-002, 9.845191e-003, 7.546819e-003, 5.449565e-003, -3.372990e-003, 1.356474e-003, -6.244383e-004, -2.610921e-003, --4.586890e-003, -6.278519e-003, -7.890025e-003, -9.517659e-003, --1.103433e-002, -1.232521e-002, -1.350277e-002, -1.452207e-002, --1.563505e-002, -1.662070e-002, -1.747172e-002, -1.809808e-002, --1.868008e-002, -1.924296e-002, -1.972334e-002, -1.995173e-002, --1.992581e-002, -1.988442e-002, -1.969942e-002, -1.948009e-002, --1.907908e-002, -1.854615e-002, -1.787303e-002, -1.699768e-002, --1.594111e-002, -1.459593e-002, -1.299512e-002, -1.109364e-002, --9.132048e-003, -7.098413e-003, -5.133031e-003, -3.156067e-003, --1.176582e-003, 8.107661e-004, 2.753242e-003, 4.567266e-003, -6.461167e-003, 8.365149e-003, 1.023503e-002, 1.199766e-002, -1.367235e-002, 1.514784e-002, 1.654619e-002, 1.774623e-002, -1.876584e-002, 1.959939e-002, 2.027723e-002, 2.075191e-002, -2.098424e-002, 2.090633e-002, 2.048742e-002, 1.985573e-002, -1.914052e-002, 1.828560e-002, 1.722887e-002, 1.606533e-002, -1.494413e-002, 1.375048e-002, 1.252149e-002, 1.132415e-002, -1.021822e-002, 9.142808e-003, 7.972039e-003, 6.717654e-003, -5.346377e-003, 4.066137e-003, 2.773927e-003, 1.623577e-003, -5.727074e-004, -4.577180e-004, -1.420504e-003, -2.300119e-003, --3.070242e-003, -3.835211e-003, -4.607136e-003, -5.486896e-003, --6.424022e-003, -7.375626e-003, -8.289346e-003, -9.244838e-003, --1.026491e-002, -1.143799e-002, -1.272087e-002, -1.413782e-002, --1.564241e-002, -1.723947e-002, -1.889180e-002, -2.051288e-002, --2.204456e-002, -2.367888e-002, -2.526027e-002, -2.691839e-002, --2.851257e-002, -3.000154e-002, -3.161105e-002, -3.331999e-002, --3.503159e-002, -3.662259e-002, -3.805864e-002, -3.942737e-002, --4.068278e-002, -4.189856e-002, -4.286019e-002, -4.363595e-002, --4.429410e-002, -4.496614e-002, -4.568508e-002, -4.638920e-002, --4.690511e-002, -4.718796e-002, -4.740586e-002, -4.759310e-002, --4.774237e-002, -4.767961e-002, -4.749608e-002, -4.723799e-002, --4.696139e-002, -4.672770e-002, -4.646786e-002, -4.610451e-002, --4.578019e-002, -4.551681e-002, -4.523970e-002, -4.497662e-002, --4.461293e-002, -4.408986e-002, -4.344115e-002, -4.269466e-002, --4.190986e-002, -4.101344e-002, -4.001374e-002, -3.877195e-002, --3.730987e-002, -3.576281e-002, -3.404682e-002, -3.226266e-002, --3.029614e-002, -2.812401e-002, -2.579066e-002, -2.340318e-002, --2.092865e-002, -1.838213e-002, -1.568175e-002, -1.289884e-002, --1.017833e-002, -7.495431e-003, -4.834545e-003, -2.142456e-003, -5.920163e-004, 3.337825e-003, 5.959578e-003, 8.519402e-003, -1.102971e-002, 1.344931e-002, 1.576932e-002, 1.796100e-002, -1.991082e-002, 2.168644e-002, 2.337365e-002, 2.504023e-002, -2.678373e-002, 2.862147e-002, 3.049878e-002, 3.241890e-002, -3.436431e-002, 3.641611e-002, 3.856386e-002, 4.073100e-002, -4.283885e-002, 4.490802e-002, 4.689472e-002, 4.871002e-002, -5.046645e-002, 5.204726e-002, 5.343029e-002, 5.472832e-002, -5.599236e-002, 5.721597e-002, 5.839899e-002, 5.957367e-002, -6.056500e-002, 6.136720e-002, 6.186965e-002, 6.211838e-002, -6.226175e-002, 6.229212e-002, 6.214178e-002, 6.190056e-002, -6.152783e-002, 6.101349e-002, 6.050825e-002, 6.002261e-002, -5.945987e-002, 5.870049e-002, 5.780476e-002, 5.684328e-002, -5.594430e-002, 5.514625e-002, 5.451345e-002, 5.392397e-002, -5.337463e-002, 5.275612e-002, 5.213341e-002, 5.162743e-002, -5.126525e-002, 5.069816e-002, 5.000647e-002, 4.919533e-002, -4.829488e-002, 4.734442e-002, 4.636469e-002, 4.530876e-002, -4.408834e-002, 4.273445e-002, 4.124301e-002, 3.971368e-002, -3.816382e-002, 3.635555e-002, 3.442473e-002, 3.242237e-002, -3.032685e-002, 2.810918e-002, 2.570025e-002, 2.315001e-002, -2.051721e-002, 1.781774e-002, 1.504993e-002, 1.223533e-002, -9.403792e-003, 6.614334e-003, 3.961789e-003, 1.352513e-003, --1.142669e-003, -3.556329e-003, -5.808669e-003, -7.896957e-003, --9.784636e-003, -1.150217e-002, -1.308122e-002, -1.463586e-002, --1.613689e-002, -1.753596e-002, -1.880683e-002, -1.998000e-002, --2.100789e-002, -2.209715e-002, -2.315729e-002, -2.421098e-002, --2.529663e-002, -2.641581e-002, -2.758166e-002, -2.869584e-002, --2.979604e-002, -3.095794e-002, -3.225884e-002, -3.361836e-002, --3.503545e-002, -3.645276e-002, -3.783637e-002, -3.929751e-002, --4.089781e-002, -4.259918e-002, -4.432874e-002, -4.607143e-002, --4.773009e-002, -4.920081e-002, -5.047505e-002, -5.164006e-002, --5.269007e-002, -5.353123e-002, -5.403724e-002, -5.434575e-002, --5.443941e-002, -5.436548e-002, -5.425290e-002, -5.409159e-002, --5.387804e-002, -5.379968e-002, -5.374968e-002, -5.360891e-002, --5.324331e-002, -5.274358e-002, -5.222948e-002, -5.160853e-002, --5.102097e-002, -5.055632e-002, -5.009110e-002, -4.961392e-002, --4.919782e-002, -4.890147e-002, -4.865878e-002, -4.853132e-002, --4.840425e-002, -4.825781e-002, -4.817701e-002, -4.814303e-002, --4.827690e-002, -4.839454e-002, -4.851642e-002, -4.853947e-002, --4.851070e-002, -4.835915e-002, -4.806847e-002, -4.772811e-002, --4.735464e-002, -4.686800e-002, -4.616908e-002, -4.515623e-002, --4.409057e-002, -4.273307e-002, -4.112326e-002, -3.938006e-002, --3.749485e-002, -3.557317e-002, -3.367075e-002, -3.179954e-002, --2.987817e-002, -2.790936e-002, -2.572261e-002, -2.346290e-002, --2.115555e-002, -1.891577e-002, -1.679818e-002, -1.477311e-002, --1.243367e-002, -1.002242e-002, -7.706430e-003, -5.560307e-003, --3.565738e-003, -1.700999e-003, 4.548754e-005, 1.703930e-003, -3.307118e-003, 4.857230e-003, 6.397235e-003, 7.923882e-003, -9.460259e-003, 1.106621e-002, 1.270158e-002, 1.435002e-002, -1.601426e-002, 1.764552e-002, 1.926211e-002, 2.078512e-002, -2.229888e-002, 2.374488e-002, 2.518172e-002, 2.686031e-002, -2.867680e-002, 3.047771e-002, 3.223375e-002, 3.387937e-002, -3.545273e-002, 3.691159e-002, 3.824734e-002, 3.954426e-002, -4.082855e-002, 4.204487e-002, 4.314479e-002, 4.410489e-002, -4.499020e-002, 4.571630e-002, 4.630931e-002, 4.683214e-002, -4.735618e-002, 4.790854e-002, 4.847886e-002, 4.893745e-002, -4.941052e-002, 4.991920e-002, 5.043605e-002, 5.094296e-002, -5.148773e-002, 5.203289e-002, 5.260084e-002, 5.310336e-002, -5.352573e-002, 5.382221e-002, 5.425348e-002, 5.457538e-002, -5.479892e-002, 5.487953e-002, 5.487374e-002, 5.468993e-002, -5.432971e-002, 5.377583e-002, 5.311632e-002, 5.224699e-002, -5.121794e-002, 5.003129e-002, 4.869618e-002, 4.720427e-002, -4.546396e-002, 4.364570e-002, 4.182177e-002, 4.002437e-002, -3.824361e-002, 3.652127e-002, 3.481240e-002, 3.322805e-002, -3.166959e-002, 3.018601e-002, 2.874048e-002, 2.740325e-002, -2.617446e-002, 2.505664e-002, 2.406591e-002, 2.328137e-002, -2.266331e-002, 2.205704e-002, 2.146733e-002, 2.085548e-002, -2.027054e-002, 1.963182e-002, 1.899520e-002, 1.840720e-002, -1.782242e-002, 1.715968e-002, 1.646351e-002, 1.588462e-002, -1.533953e-002, 1.475195e-002, 1.402474e-002, 1.309852e-002, -1.207905e-002, 1.095860e-002, 9.732415e-003, 8.430559e-003, -7.069104e-003, 5.627828e-003, 4.144554e-003, 2.612547e-003, -1.078197e-003, -3.333381e-004, -1.660112e-003, -2.819464e-003, --3.856188e-003, -4.675506e-003, -5.290251e-003, -5.731264e-003, --6.131214e-003, -6.553499e-003, -6.806265e-003, -7.035203e-003, --7.211436e-003, -7.395562e-003, -7.599733e-003, -7.776983e-003, --7.917681e-003, -8.115935e-003, -8.351167e-003, -8.462478e-003, --8.520384e-003, -8.543514e-003, -8.582004e-003, -8.686430e-003, --8.921784e-003, -9.378241e-003, -9.984562e-003, -1.070819e-002, --1.144842e-002, -1.216734e-002, -1.286840e-002, -1.355399e-002, --1.423771e-002, -1.485368e-002, -1.539165e-002, -1.587404e-002, --1.635753e-002, -1.671768e-002, -1.694643e-002, -1.704845e-002, --1.708001e-002, -1.707063e-002, -1.693722e-002, -1.683292e-002, --1.669635e-002, -1.663055e-002, -1.664120e-002, -1.659552e-002, --1.662302e-002, -1.674169e-002, -1.693562e-002, -1.723151e-002, --1.759452e-002, -1.803317e-002, -1.857834e-002, -1.926417e-002, --2.000791e-002, -2.079699e-002, -2.178555e-002, -2.305588e-002, --2.442474e-002, -2.587617e-002, -2.736611e-002, -2.886701e-002, --3.039988e-002, -3.194743e-002, -3.332878e-002, -3.456006e-002, --3.567533e-002, -3.674389e-002, -3.767837e-002, -3.860935e-002, --3.928897e-002, -3.975846e-002, -4.005642e-002, -4.022738e-002, --4.019999e-002, -4.001636e-002, -3.973286e-002, -3.941271e-002, --3.895531e-002, -3.836477e-002, -3.766485e-002, -3.697775e-002, --3.620546e-002, -3.534119e-002, -3.439193e-002, -3.339706e-002, --3.244995e-002, -3.156241e-002, -3.081037e-002, -3.013177e-002, --2.946770e-002, -2.880825e-002, -2.812245e-002, -2.744323e-002, --2.669691e-002, -2.591785e-002, -2.518238e-002, -2.454735e-002, --2.400416e-002, -2.345074e-002, -2.286687e-002, -2.222466e-002, --2.150362e-002, -2.072564e-002, -1.985872e-002, -1.896279e-002, --1.805334e-002, -1.713478e-002, -1.627721e-002, -1.540866e-002, --1.449563e-002, -1.353659e-002, -1.258583e-002, -1.161945e-002, --1.064567e-002, -9.697183e-003, -8.766315e-003, -7.884624e-003, --7.248590e-003, -6.613830e-003, -5.981632e-003, -5.358558e-003, --4.684666e-003, -4.061358e-003, -3.394395e-003, -2.704937e-003, --1.992494e-003, -1.265195e-003, -6.845468e-004, -1.080782e-004, -4.257485e-004, 9.852376e-004, 1.683515e-003, 2.508954e-003, -3.467462e-003, 4.520353e-003, 5.608052e-003, 6.758289e-003, -7.913474e-003, 9.121853e-003, 1.047055e-002, 1.199733e-002, -1.366311e-002, 1.525929e-002, 1.684295e-002, 1.845380e-002, -2.008954e-002, 2.181070e-002, 2.352426e-002, 2.516126e-002, -2.685818e-002, 2.855091e-002, 3.021117e-002, 3.187322e-002, -3.354269e-002, 3.517659e-002, 3.652914e-002, 3.774941e-002, -3.887767e-002, 3.973597e-002, 4.048107e-002, 4.117310e-002, -4.185933e-002, 4.257679e-002, 4.334223e-002, 4.412559e-002, -4.484661e-002, 4.553661e-002, 4.620972e-002, 4.647402e-002, -4.666042e-002, 4.698923e-002, 4.745676e-002, 4.803383e-002, -4.868036e-002, 4.937314e-002, 4.998720e-002, 5.037414e-002, -5.070796e-002, 5.085181e-002, 5.089037e-002, 5.094595e-002, -5.090955e-002, 5.051478e-002, 5.013270e-002, 4.969428e-002, -4.925393e-002, 4.869329e-002, 4.782868e-002, 4.668914e-002, -4.525280e-002, 4.372563e-002, 4.212665e-002, 4.033387e-002, -3.845674e-002, 3.655031e-002, 3.419669e-002, 3.170815e-002, -2.924282e-002, 2.647364e-002, 2.354786e-002, 2.060480e-002, -1.765068e-002, 1.477969e-002, 1.201827e-002, 9.359265e-003, -6.832206e-003, 4.422553e-003, 2.152366e-003, 1.115139e-016 -}, -{ -0.000000e+000, -2.579398e-001, -1.209567e-001, -1.932490e-002, -2.504666e-002, 3.760578e-002, 5.783333e-002, 6.761293e-002, -7.434871e-002, 7.908871e-002, 8.784226e-002, 9.907153e-002, -1.101218e-001, 1.189280e-001, 1.136878e-001, 9.264423e-002, -6.011690e-002, 2.173496e-002, -1.755617e-002, -5.624576e-002, --8.941955e-002, -1.129872e-001, -1.263236e-001, -1.315514e-001, --1.303526e-001, -1.240840e-001, -1.136003e-001, -9.992641e-002, --8.610108e-002, -7.163968e-002, -5.759440e-002, -4.251288e-002, --2.767609e-002, -1.359326e-002, 7.703830e-004, 1.501648e-002, -2.836959e-002, 3.884818e-002, 4.631836e-002, 5.056561e-002, -5.256275e-002, 5.108808e-002, 4.837986e-002, 4.531974e-002, -4.190391e-002, 3.886251e-002, 3.834583e-002, 3.955288e-002, -4.053713e-002, 4.172433e-002, 3.956807e-002, 3.614354e-002, -3.136865e-002, 2.491181e-002, 1.852397e-002, 1.143958e-002, -4.409165e-003, -2.266289e-003, -8.557574e-003, -1.352364e-002, --1.756921e-002, -1.901168e-002, -1.933776e-002, -2.004519e-002, --2.022225e-002, -1.937490e-002, -1.828170e-002, -1.674618e-002, --1.469506e-002, -1.204811e-002, -8.579812e-003, -4.427528e-003, -3.616970e-004, 5.099766e-003, 1.097479e-002, 1.545632e-002, -1.975683e-002, 2.381555e-002, 2.770366e-002, 3.035031e-002, -3.069347e-002, 3.000374e-002, 2.646521e-002, 2.213796e-002, -1.632744e-002, 9.611939e-003, 2.464927e-003, -5.452643e-003, --1.477599e-002, -2.404947e-002, -3.276840e-002, -4.067040e-002, --4.763239e-002, -5.479383e-002, -5.999768e-002, -6.344228e-002, --6.477467e-002, -6.281421e-002, -5.982008e-002, -5.487841e-002, --4.827628e-002, -4.055054e-002, -3.342795e-002, -2.628211e-002, --1.818398e-002, -9.700948e-003, -2.041464e-003, 5.061294e-003, -1.255927e-002, 2.012797e-002, 2.589389e-002, 3.187245e-002, -3.843609e-002, 4.340302e-002, 4.789767e-002, 5.259461e-002, -5.620754e-002, 5.940958e-002, 6.281688e-002, 6.470254e-002, -6.624533e-002, 6.730981e-002, 6.758890e-002, 6.759588e-002, -6.630336e-002, 6.442698e-002, 6.216506e-002, 5.918142e-002, -5.575290e-002, 5.118315e-002, 4.674005e-002, 4.178763e-002, -3.653629e-002, 3.068895e-002, 2.349925e-002, 1.581754e-002, -7.878387e-003, -6.886181e-004, -9.249308e-003, -1.821434e-002, --2.673689e-002, -3.492459e-002, -4.339498e-002, -5.120203e-002, --5.873327e-002, -6.544557e-002, -7.133148e-002, -7.676154e-002, --8.055690e-002, -8.398265e-002, -8.641409e-002, -8.696943e-002, --8.643045e-002, -8.427497e-002, -8.095234e-002, -7.756008e-002, --7.407493e-002, -7.043029e-002, -6.678125e-002, -6.298851e-002, --5.833634e-002, -5.378697e-002, -4.955872e-002, -4.419111e-002, --3.872515e-002, -3.292736e-002, -2.664201e-002, -2.033412e-002, --1.446422e-002, -8.907711e-003, -2.955067e-003, 2.399817e-003, -7.114083e-003, 1.193007e-002, 1.690367e-002, 2.174539e-002, -2.561761e-002, 2.978104e-002, 3.374028e-002, 3.771790e-002, -4.051783e-002, 4.374519e-002, 4.664450e-002, 4.870739e-002, -4.993152e-002, 5.142378e-002, 5.211863e-002, 5.224159e-002, -5.236009e-002, 5.158623e-002, 5.080987e-002, 4.946461e-002, -4.774334e-002, 4.624232e-002, 4.478164e-002, 4.325651e-002, -4.093693e-002, 3.838341e-002, 3.583514e-002, 3.326730e-002, -3.064711e-002, 2.774813e-002, 2.511689e-002, 2.258792e-002, -2.041250e-002, 1.831328e-002, 1.624659e-002, 1.420284e-002, -1.216297e-002, 9.958626e-003, 7.791126e-003, 5.516001e-003, -3.696278e-003, 1.620315e-003, -8.438702e-005, -1.743089e-003, --3.159685e-003, -4.207882e-003, -5.405470e-003, -6.339237e-003, --7.585506e-003, -8.845937e-003, -1.026351e-002, -1.217476e-002, --1.430957e-002, -1.664366e-002, -1.902981e-002, -2.118980e-002, --2.320684e-002, -2.539996e-002, -2.752148e-002, -2.937368e-002, --3.133944e-002, -3.315962e-002, -3.472621e-002, -3.607433e-002, --3.697657e-002, -3.724698e-002, -3.727162e-002, -3.702979e-002, --3.667382e-002, -3.569018e-002, -3.416013e-002, -3.221873e-002, --2.982019e-002, -2.719112e-002, -2.453182e-002, -2.166123e-002, --1.833190e-002, -1.485849e-002, -1.131786e-002, -7.878930e-003, --4.391772e-003, -6.966317e-004, 2.750646e-003, 5.956787e-003, -9.095940e-003, 1.203525e-002, 1.447489e-002, 1.655321e-002, -1.843273e-002, 1.982740e-002, 2.076265e-002, 2.118521e-002, -2.141936e-002, 2.128287e-002, 2.087018e-002, 2.035876e-002, -1.991192e-002, 1.983346e-002, 2.005756e-002, 2.033808e-002, -2.060518e-002, 2.108593e-002, 2.154459e-002, 2.217383e-002, -2.276094e-002, 2.327822e-002, 2.352310e-002, 2.370586e-002, -2.398790e-002, 2.436696e-002, 2.459088e-002, 2.467673e-002, -2.485049e-002, 2.487367e-002, 2.447821e-002, 2.417875e-002, -2.377879e-002, 2.299162e-002, 2.185145e-002, 2.032041e-002, -1.863735e-002, 1.653366e-002, 1.419286e-002, 1.180981e-002, -9.317537e-003, 6.749421e-003, 4.055280e-003, 1.357940e-003, --1.281496e-003, -4.161932e-003, -6.926867e-003, -9.448191e-003, --1.177807e-002, -1.358487e-002, -1.520972e-002, -1.654234e-002, --1.753675e-002, -1.846182e-002, -1.911281e-002, -1.888710e-002, --1.808285e-002, -1.712806e-002, -1.613268e-002, -1.494508e-002, --1.364783e-002, -1.235170e-002, -1.107139e-002, -9.711666e-003, --8.429320e-003, -7.124912e-003, -5.589019e-003, -3.905409e-003, --2.196126e-003, -3.586341e-004, 1.586919e-003, 2.722414e-003, -3.098131e-003, 3.316094e-003, 3.332841e-003, 3.213552e-003, -3.178998e-003, 3.364780e-003, 3.439043e-003, 3.404151e-003, -3.610437e-003, 4.061402e-003, 4.446161e-003, 5.030620e-003, -5.955267e-003, 6.882735e-003, 7.661038e-003, 8.398200e-003, -9.215394e-003, 9.814869e-003, 1.068802e-002, 1.164980e-002, -1.234742e-002, 1.293027e-002, 1.321345e-002, 1.358017e-002, -1.360308e-002, 1.332827e-002, 1.292256e-002, 1.253662e-002, -1.193453e-002, 1.097354e-002, 9.608262e-003, 7.727396e-003, -5.181497e-003, 2.498065e-003, 8.392901e-005, -2.753614e-003, --5.874013e-003, -9.066884e-003, -1.221922e-002, -1.530662e-002, --1.841245e-002, -2.134858e-002, -2.361042e-002, -2.593328e-002, --2.841748e-002, -3.068935e-002, -3.268556e-002, -3.460273e-002, --3.637718e-002, -3.784616e-002, -3.897207e-002, -3.989913e-002, --4.084184e-002, -4.161193e-002, -4.224743e-002, -4.272912e-002, --4.332241e-002, -4.402182e-002, -4.452376e-002, -4.506558e-002, --4.528480e-002, -4.546102e-002, -4.559239e-002, -4.611191e-002, --4.668735e-002, -4.709852e-002, -4.723655e-002, -4.745577e-002, --4.775760e-002, -4.783295e-002, -4.757291e-002, -4.731658e-002, --4.693088e-002, -4.636473e-002, -4.527026e-002, -4.392313e-002, --4.232921e-002, -4.058036e-002, -3.830428e-002, -3.563550e-002, --3.252757e-002, -2.947177e-002, -2.609116e-002, -2.252321e-002, --1.882878e-002, -1.536624e-002, -1.178465e-002, -7.949011e-003, --4.070393e-003, -2.169144e-004, 3.653688e-003, 7.723708e-003, -1.168033e-002, 1.546843e-002, 1.906044e-002, 2.267620e-002, -2.635240e-002, 2.999622e-002, 3.326988e-002, 3.609206e-002, -3.854707e-002, 4.039571e-002, 4.218781e-002, 4.365848e-002, -4.492490e-002, 4.587969e-002, 4.670635e-002, 4.746898e-002, -4.822103e-002, 4.922597e-002, 5.035996e-002, 5.168701e-002, -5.302462e-002, 5.431828e-002, 5.574555e-002, 5.716833e-002, -5.851182e-002, 5.970190e-002, 6.062621e-002, 6.131199e-002, -6.187983e-002, 6.242391e-002, 6.307774e-002, 6.358222e-002, -6.392120e-002, 6.404110e-002, 6.397023e-002, 6.414935e-002, -6.390093e-002, 6.311954e-002, 6.182869e-002, 6.025462e-002, -5.846745e-002, 5.627545e-002, 5.370097e-002, 5.086212e-002, -4.779427e-002, 4.450851e-002, 4.093421e-002, 3.710409e-002, -3.311468e-002, 2.955969e-002, 2.605301e-002, 2.247117e-002, -1.871255e-002, 1.477813e-002, 1.093143e-002, 7.142533e-003, -3.338119e-003, -4.218725e-004, -4.152341e-003, -7.620255e-003, --1.079695e-002, -1.377148e-002, -1.649072e-002, -1.882488e-002, --2.069452e-002, -2.226946e-002, -2.359193e-002, -2.482650e-002, --2.621025e-002, -2.742695e-002, -2.847715e-002, -2.931658e-002, --3.001557e-002, -3.069451e-002, -3.125401e-002, -3.177524e-002, --3.219790e-002, -3.247020e-002, -3.272664e-002, -3.279779e-002, --3.263522e-002, -3.244683e-002, -3.222611e-002, -3.213730e-002, --3.209843e-002, -3.227508e-002, -3.256853e-002, -3.275860e-002, --3.273513e-002, -3.249075e-002, -3.201190e-002, -3.153708e-002, --3.095856e-002, -3.013429e-002, -2.898258e-002, -2.755313e-002, --2.572948e-002, -2.360818e-002, -2.141612e-002, -1.911493e-002, --1.679410e-002, -1.461674e-002, -1.256759e-002, -1.060445e-002, --8.690035e-003, -6.837316e-003, -5.127986e-003, -3.560941e-003, --2.282790e-003, -1.246488e-003, -2.973902e-004, 6.036451e-004, -1.307093e-003, 1.665978e-003, 1.747005e-003, 1.506567e-003, -9.812409e-004, 2.203542e-004, -6.531253e-004, -1.681214e-003, --2.734614e-003, -3.757117e-003, -4.544129e-003, -4.963993e-003, --4.966266e-003, -4.609048e-003, -4.097634e-003, -3.432080e-003, --2.618152e-003, -1.660368e-003, -7.237833e-004, 6.128496e-005, -7.160381e-004, 1.312671e-003, 2.144561e-003, 3.002123e-003, -3.878402e-003, 4.715605e-003, 5.403221e-003, 5.927875e-003, -6.323630e-003, 6.462751e-003, 6.248945e-003, 5.646920e-003, -4.782494e-003, 3.694970e-003, 2.381798e-003, 1.009759e-003, --3.573277e-004, -1.749128e-003, -3.192651e-003, -4.741605e-003, --6.371721e-003, -8.005634e-003, -9.490331e-003, -1.071508e-002, --1.170148e-002, -1.247470e-002, -1.319494e-002, -1.387452e-002, --1.429349e-002, -1.441293e-002, -1.413480e-002, -1.355052e-002, --1.277048e-002, -1.187251e-002, -1.086824e-002, -9.790924e-003, --8.634118e-003, -7.568894e-003, -6.702131e-003, -6.024374e-003, --5.571644e-003, -5.145867e-003, -4.660619e-003, -4.168417e-003, --3.612648e-003, -2.891409e-003, -2.164632e-003, -1.474701e-003, --1.017929e-003, -8.793184e-004, -1.119031e-003, -1.625717e-003, --2.389485e-003, -3.264484e-003, -4.157508e-003, -5.140339e-003, --5.965895e-003, -6.638305e-003, -7.134637e-003, -7.518238e-003, --7.531199e-003, -7.281284e-003, -6.756853e-003, -6.034889e-003, --5.124576e-003, -4.092030e-003, -2.867205e-003, -1.451115e-003, -2.239897e-004, 2.110914e-003, 4.097981e-003, 6.129396e-003, -8.113317e-003, 9.938029e-003, 1.170526e-002, 1.338156e-002, -1.488051e-002, 1.625691e-002, 1.735182e-002, 1.821789e-002, -1.884886e-002, 1.927077e-002, 1.943597e-002, 1.930072e-002, -1.876736e-002, 1.770444e-002, 1.630472e-002, 1.469489e-002, -1.287807e-002, 1.086195e-002, 8.783380e-003, 6.777411e-003, -4.842621e-003, 2.984875e-003, 1.441480e-003, 9.822667e-005, --9.469432e-004, -1.556145e-003, -1.622938e-003, -1.253699e-003, --6.320784e-004, 1.547057e-004, 1.161875e-003, 2.363016e-003, -3.635548e-003, 4.995336e-003, 6.441811e-003, 7.925154e-003, -9.416030e-003, 1.112582e-002, 1.294499e-002, 1.477188e-002, -1.673515e-002, 1.875369e-002, 2.061192e-002, 2.226624e-002, -2.370853e-002, 2.478138e-002, 2.551645e-002, 2.603810e-002, -2.642191e-002, 2.662482e-002, 2.660716e-002, 2.629400e-002, -2.560136e-002, 2.468079e-002, 2.366704e-002, 2.259868e-002, -2.147261e-002, 2.027612e-002, 1.906563e-002, 1.796267e-002, -1.695637e-002, 1.601241e-002, 1.507960e-002, 1.423999e-002, -1.361573e-002, 1.327799e-002, 1.318320e-002, 1.335855e-002, -1.365530e-002, 1.403293e-002, 1.446425e-002, 1.491372e-002, -1.518441e-002, 1.508684e-002, 1.482889e-002, 1.460668e-002, -1.439235e-002, 1.421736e-002, 1.418394e-002, 1.423821e-002, -1.396676e-002, 1.354445e-002, 1.298255e-002, 1.212716e-002, -1.097619e-002, 9.402749e-003, 7.573798e-003, 5.595428e-003, -3.515146e-003, 1.485177e-003, -4.882044e-004, -2.362990e-003, --4.052034e-003, -5.482022e-003, -6.651650e-003, -7.596364e-003, --8.507816e-003, -9.470378e-003, -1.028909e-002, -1.093851e-002, --1.143958e-002, -1.179755e-002, -1.208581e-002, -1.236813e-002, --1.272414e-002, -1.309716e-002, -1.345903e-002, -1.385009e-002, --1.428607e-002, -1.479398e-002, -1.527407e-002, -1.575459e-002, --1.633419e-002, -1.703189e-002, -1.787018e-002, -1.887978e-002, --2.003423e-002, -2.136734e-002, -2.269959e-002, -2.400660e-002, --2.528676e-002, -2.652036e-002, -2.762633e-002, -2.855865e-002, --2.922374e-002, -2.966257e-002, -2.971810e-002, -2.947755e-002, --2.904298e-002, -2.847870e-002, -2.780494e-002, -2.710601e-002, --2.637022e-002, -2.562690e-002, -2.505907e-002, -2.454080e-002, --2.409288e-002, -2.375043e-002, -2.345757e-002, -2.311544e-002, --2.272731e-002, -2.229195e-002, -2.188252e-002, -2.141581e-002, --2.111549e-002, -2.106495e-002, -2.132958e-002, -2.186872e-002, --2.253665e-002, -2.326625e-002, -2.399170e-002, -2.463249e-002, --2.508957e-002, -2.535475e-002, -2.552324e-002, -2.543599e-002, --2.506226e-002, -2.446855e-002, -2.356986e-002, -2.241903e-002, --2.114630e-002, -1.971796e-002, -1.818763e-002, -1.663143e-002, --1.495635e-002, -1.309890e-002, -1.118190e-002, -9.284025e-003, --7.421051e-003, -5.629939e-003, -3.948830e-003, -2.429027e-003, --1.061970e-003, 2.193347e-004, 1.338235e-003, 2.240275e-003, -2.909313e-003, 3.341588e-003, 3.507632e-003, 3.431855e-003, -2.972452e-003, 2.230958e-003, 1.300598e-003, 1.916446e-004, --1.096381e-003, -2.459304e-003, -3.894535e-003, -5.393157e-003, --6.819771e-003, -8.182386e-003, -9.446314e-003, -1.037507e-002, --1.090151e-002, -1.082440e-002, -1.025120e-002, -9.172517e-003, --7.938720e-003, -6.504267e-003, -5.105796e-003, -3.747840e-003, --2.432724e-003, -1.143524e-003, 1.246597e-004, 1.377356e-003, -2.640967e-003, 4.002917e-003, 5.459114e-003, 6.838093e-003, -8.104481e-003, 9.337010e-003, 1.050151e-002, 1.142605e-002, -1.202659e-002, 1.226805e-002, 1.219793e-002, 1.191685e-002, -1.143601e-002, 1.088010e-002, 1.042566e-002, 1.008632e-002, -9.938196e-003, 1.003155e-002, 1.038871e-002, 1.101200e-002, -1.182630e-002, 1.280232e-002, 1.389360e-002, 1.515404e-002, -1.652723e-002, 1.800978e-002, 1.957076e-002, 2.120760e-002, -2.293185e-002, 2.460919e-002, 2.622679e-002, 2.760060e-002, -2.880274e-002, 2.991452e-002, 3.087578e-002, 3.175920e-002, -3.247300e-002, 3.299391e-002, 3.326776e-002, 3.321213e-002, -3.278646e-002, 3.195643e-002, 3.073559e-002, 2.922576e-002, -2.752823e-002, 2.569974e-002, 2.371693e-002, 2.170333e-002, -1.964248e-002, 1.757875e-002, 1.543505e-002, 1.332448e-002, -1.135122e-002, 9.568384e-003, 8.017653e-003, 6.721457e-003, -5.781147e-003, 5.137044e-003, 4.789883e-003, 4.564974e-003, -4.333416e-003, 4.154308e-003, 4.117791e-003, 4.174322e-003, -4.336080e-003, 4.586243e-003, 4.934014e-003, 5.360635e-003, -5.687154e-003, 5.939883e-003, 6.078859e-003, 6.193818e-003, -6.233641e-003, 6.230615e-003, 6.171828e-003, 6.037512e-003, -5.889675e-003, 5.794554e-003, 5.789711e-003, 5.835376e-003, -5.987347e-003, 6.108669e-003, 6.164964e-003, 6.139106e-003, -6.042786e-003, 5.886521e-003, 5.720929e-003, 5.610427e-003, -5.596352e-003, 5.689164e-003, 5.873301e-003, 6.044754e-003, -6.199628e-003, 6.303955e-003, 6.357360e-003, 6.267259e-003, -6.137201e-003, 5.919732e-003, 5.632784e-003, 5.270683e-003, -4.763314e-003, 4.151010e-003, 3.381284e-003, 2.459789e-003, -1.415645e-003, 2.846581e-004, -9.016040e-004, -2.072679e-003, --3.195387e-003, -4.372159e-003, -5.493731e-003, -6.489536e-003, --7.410036e-003, -8.249778e-003, -9.020135e-003, -9.679026e-003, --1.024993e-002, -1.059533e-002, -1.069940e-002, -1.057291e-002, --1.037040e-002, -1.008951e-002, -9.803875e-003, -9.588151e-003, --9.352192e-003, -9.079449e-003, -8.737069e-003, -8.270414e-003, --7.721555e-003, -7.106008e-003, -6.552974e-003, -6.071447e-003, --5.627334e-003, -5.260632e-003, -4.940564e-003, -4.680510e-003, --4.493115e-003, -4.518905e-003, -4.674544e-003, -4.982171e-003, --5.456682e-003, -6.227990e-003, -7.194331e-003, -8.369432e-003, --9.673612e-003, -1.104694e-002, -1.238453e-002, -1.370461e-002, --1.498773e-002, -1.617976e-002, -1.726968e-002, -1.835257e-002, --1.944410e-002, -2.039484e-002, -2.120457e-002, -2.177195e-002, --2.207613e-002, -2.203223e-002, -2.166245e-002, -2.106259e-002, --2.032756e-002, -1.951773e-002, -1.870273e-002, -1.789780e-002, --1.709755e-002, -1.626338e-002, -1.553065e-002, -1.478031e-002, --1.399874e-002, -1.316922e-002, -1.227253e-002, -1.133702e-002, --1.044539e-002, -9.706953e-003, -9.170190e-003, -8.882478e-003, --8.908699e-003, -9.208948e-003, -9.718379e-003, -1.033312e-002, --1.101671e-002, -1.187972e-002, -1.258727e-002, -1.310278e-002, --1.339329e-002, -1.341135e-002, -1.308742e-002, -1.263665e-002, --1.201478e-002, -1.118051e-002, -1.006663e-002, -8.698361e-003, --7.000645e-003, -5.014099e-003, -2.661863e-003, -9.504265e-017 -}, -{ -0.000000e+000, -2.469714e-002, -2.473741e-002, -2.405222e-002, --3.741649e-003, 2.191523e-002, 3.004823e-002, 3.490736e-002, -3.573678e-002, 3.198222e-002, 2.153496e-002, 5.702981e-003, --1.289214e-002, -2.994308e-002, -3.871065e-002, -3.894194e-002, --3.340419e-002, -2.426176e-002, -1.466523e-002, -3.085266e-003, -7.732097e-003, 1.503688e-002, 1.859290e-002, 1.864447e-002, -1.710718e-002, 1.421894e-002, 1.018549e-002, 5.587017e-003, -1.192989e-003, -1.994035e-003, -4.262802e-003, -6.295929e-003, --7.636204e-003, -8.170831e-003, -8.616133e-003, -8.658765e-003, --8.173853e-003, -6.305262e-003, -3.508473e-003, 4.313680e-005, -4.071673e-003, 8.678984e-003, 1.303724e-002, 1.668072e-002, -1.909616e-002, 1.994462e-002, 1.870585e-002, 1.551111e-002, -1.160449e-002, 7.490275e-003, 4.442047e-003, 2.945680e-003, -2.356617e-003, 2.250466e-003, 2.092284e-003, 1.829290e-003, -1.088032e-003, -1.959431e-004, -1.711074e-003, -4.201147e-003, --7.821432e-003, -1.258203e-002, -1.716226e-002, -2.097909e-002, --2.367560e-002, -2.557019e-002, -2.671241e-002, -2.731694e-002, --2.703611e-002, -2.607524e-002, -2.432872e-002, -2.218602e-002, --1.956096e-002, -1.642157e-002, -1.306824e-002, -9.128684e-003, --5.022810e-003, -9.088909e-004, 3.112213e-003, 7.408376e-003, -1.207519e-002, 1.677878e-002, 2.180218e-002, 2.659783e-002, -3.138106e-002, 3.598792e-002, 3.984319e-002, 4.272506e-002, -4.433416e-002, 4.461072e-002, 4.327752e-002, 4.044258e-002, -3.630138e-002, 3.116601e-002, 2.491316e-002, 1.780005e-002, -9.515282e-003, 2.064248e-004, -9.068661e-003, -1.837233e-002, --2.730946e-002, -3.490568e-002, -4.063500e-002, -4.392272e-002, --4.557725e-002, -4.587873e-002, -4.485252e-002, -4.275832e-002, --4.008398e-002, -3.694630e-002, -3.304749e-002, -2.867330e-002, --2.397167e-002, -1.852278e-002, -1.271055e-002, -6.908028e-003, --9.235115e-004, 4.936529e-003, 1.046311e-002, 1.555157e-002, -1.956183e-002, 2.223251e-002, 2.328826e-002, 2.283432e-002, -2.121724e-002, 1.820797e-002, 1.445283e-002, 1.005728e-002, -5.757214e-003, 1.708169e-003, -2.368862e-003, -5.969397e-003, --8.953819e-003, -1.091631e-002, -1.153879e-002, -1.093272e-002, --9.583846e-003, -7.227005e-003, -4.264415e-003, -6.332222e-004, -3.563772e-003, 8.025719e-003, 1.287193e-002, 1.782613e-002, -2.253753e-002, 2.677626e-002, 3.036717e-002, 3.329387e-002, -3.537673e-002, 3.672864e-002, 3.757941e-002, 3.725171e-002, -3.591777e-002, 3.347496e-002, 2.979634e-002, 2.495512e-002, -1.884777e-002, 1.194969e-002, 4.639894e-003, -2.832655e-003, --1.030999e-002, -1.717243e-002, -2.310889e-002, -2.816440e-002, --3.175951e-002, -3.384981e-002, -3.451982e-002, -3.438689e-002, --3.356070e-002, -3.223152e-002, -3.060941e-002, -2.874502e-002, --2.639277e-002, -2.348729e-002, -1.991079e-002, -1.568495e-002, --1.065097e-002, -5.328739e-003, 3.436562e-004, 6.088442e-003, -1.190536e-002, 1.717460e-002, 2.168178e-002, 2.516354e-002, -2.763609e-002, 2.888219e-002, 2.930595e-002, 2.878452e-002, -2.710643e-002, 2.461488e-002, 2.113023e-002, 1.695251e-002, -1.212703e-002, 6.948012e-003, 1.508608e-003, -3.814585e-003, --8.648795e-003, -1.315810e-002, -1.760640e-002, -2.184816e-002, --2.575361e-002, -2.903948e-002, -3.116364e-002, -3.165045e-002, --3.033117e-002, -2.780772e-002, -2.431087e-002, -2.053059e-002, --1.683747e-002, -1.331233e-002, -1.008198e-002, -7.237729e-003, --4.673095e-003, -1.874590e-003, 1.184268e-003, 4.574012e-003, -8.403873e-003, 1.233318e-002, 1.643578e-002, 2.011707e-002, -2.310985e-002, 2.494469e-002, 2.490363e-002, 2.320294e-002, -2.020001e-002, 1.665536e-002, 1.270204e-002, 8.612392e-003, -4.478917e-003, 1.571016e-004, -4.365922e-003, -9.130007e-003, --1.384645e-002, -1.828565e-002, -2.235556e-002, -2.575146e-002, --2.834808e-002, -3.020620e-002, -3.106271e-002, -3.114853e-002, --3.020945e-002, -2.813767e-002, -2.501593e-002, -2.099232e-002, --1.602425e-002, -1.064933e-002, -5.045817e-003, 1.254503e-004, -4.967998e-003, 9.552591e-003, 1.366735e-002, 1.710611e-002, -1.998054e-002, 2.231760e-002, 2.432907e-002, 2.600744e-002, -2.726502e-002, 2.819859e-002, 2.863906e-002, 2.822755e-002, -2.653581e-002, 2.294667e-002, 1.778420e-002, 1.143211e-002, -4.104702e-003, -4.014319e-003, -1.202931e-002, -1.958348e-002, --2.631378e-002, -3.174873e-002, -3.564200e-002, -3.743810e-002, --3.769528e-002, -3.679272e-002, -3.530662e-002, -3.303707e-002, --3.029167e-002, -2.702925e-002, -2.327617e-002, -1.888226e-002, --1.401580e-002, -8.596156e-003, -2.685436e-003, 3.539384e-003, -1.005315e-002, 1.680412e-002, 2.360350e-002, 3.004834e-002, -3.575538e-002, 4.032734e-002, 4.376997e-002, 4.607003e-002, -4.731562e-002, 4.740315e-002, 4.663758e-002, 4.480173e-002, -4.201254e-002, 3.841397e-002, 3.402428e-002, 2.922936e-002, -2.436055e-002, 1.965072e-002, 1.519454e-002, 1.091344e-002, -6.779922e-003, 2.651729e-003, -1.142556e-003, -4.355308e-003, --6.874424e-003, -8.187367e-003, -8.004693e-003, -6.653978e-003, --4.675131e-003, -2.412549e-003, -1.801794e-004, 1.960451e-003, -3.827486e-003, 5.336422e-003, 6.455668e-003, 7.130686e-003, -7.704794e-003, 8.426043e-003, 9.196559e-003, 9.846271e-003, -1.034433e-002, 1.058532e-002, 1.013297e-002, 8.621587e-003, -5.706139e-003, 1.455167e-003, -4.188250e-003, -1.052945e-002, --1.721039e-002, -2.394485e-002, -3.058344e-002, -3.662466e-002, --4.184156e-002, -4.620814e-002, -4.926624e-002, -5.120101e-002, --5.182468e-002, -5.133663e-002, -4.946614e-002, -4.645581e-002, --4.243800e-002, -3.769361e-002, -3.224662e-002, -2.612433e-002, --1.953594e-002, -1.264116e-002, -5.611731e-003, 1.309327e-003, -7.759884e-003, 1.360613e-002, 1.873400e-002, 2.334811e-002, -2.703138e-002, 2.983114e-002, 3.161283e-002, 3.257043e-002, -3.243703e-002, 3.129269e-002, 2.910848e-002, 2.596110e-002, -2.211607e-002, 1.783682e-002, 1.363941e-002, 9.775343e-003, -6.289424e-003, 3.168244e-003, 5.442142e-004, -1.432615e-003, --2.882379e-003, -3.757345e-003, -4.019791e-003, -3.602201e-003, --2.572118e-003, -9.670831e-004, 9.029681e-004, 2.872666e-003, -4.880329e-003, 7.085902e-003, 9.357564e-003, 1.165989e-002, -1.398067e-002, 1.630992e-002, 1.856286e-002, 2.061765e-002, -2.214967e-002, 2.288716e-002, 2.274893e-002, 2.155210e-002, -1.984837e-002, 1.773392e-002, 1.526252e-002, 1.250028e-002, -9.520906e-003, 6.080876e-003, 2.117947e-003, -2.154828e-003, --6.470547e-003, -1.082293e-002, -1.511478e-002, -1.901925e-002, --2.252049e-002, -2.537071e-002, -2.770976e-002, -2.932445e-002, --3.033750e-002, -3.050300e-002, -3.011630e-002, -2.915869e-002, --2.780598e-002, -2.603283e-002, -2.395820e-002, -2.171752e-002, --1.941299e-002, -1.700557e-002, -1.478619e-002, -1.284328e-002, --1.111396e-002, -9.706651e-003, -8.524213e-003, -7.608755e-003, --6.815194e-003, -6.397150e-003, -6.352473e-003, -7.017941e-003, --8.643011e-003, -1.161232e-002, -1.560416e-002, -2.014740e-002, --2.488373e-002, -2.963286e-002, -3.410267e-002, -3.790953e-002, --4.083287e-002, -4.222920e-002, -4.184246e-002, -3.988442e-002, --3.704735e-002, -3.362409e-002, -3.007321e-002, -2.650582e-002, --2.295975e-002, -1.922940e-002, -1.514926e-002, -1.058975e-002, --5.348284e-003, 5.125951e-004, 6.877922e-003, 1.373155e-002, -2.097317e-002, 2.830371e-002, 3.544873e-002, 4.198773e-002, -4.748402e-002, 5.180024e-002, 5.471164e-002, 5.658613e-002, -5.737312e-002, 5.727197e-002, 5.622846e-002, 5.430131e-002, -5.140708e-002, 4.792081e-002, 4.392795e-002, 3.975572e-002, -3.543148e-002, 3.099325e-002, 2.642971e-002, 2.175544e-002, -1.688887e-002, 1.171612e-002, 6.227816e-003, 5.519335e-004, --5.072114e-003, -1.046543e-002, -1.551374e-002, -1.999188e-002, --2.362500e-002, -2.643011e-002, -2.782810e-002, -2.796351e-002, --2.697521e-002, -2.543845e-002, -2.346073e-002, -2.116862e-002, --1.848509e-002, -1.557067e-002, -1.227826e-002, -8.675187e-003, --4.761031e-003, -4.820634e-004, 3.906117e-003, 8.156725e-003, -1.215343e-002, 1.572166e-002, 1.891351e-002, 2.172695e-002, -2.402011e-002, 2.574990e-002, 2.674857e-002, 2.698458e-002, -2.647294e-002, 2.525541e-002, 2.335007e-002, 2.088851e-002, -1.799333e-002, 1.499020e-002, 1.216037e-002, 9.681682e-003, -7.817044e-003, 6.748233e-003, 6.583826e-003, 7.596197e-003, -9.844509e-003, 1.315159e-002, 1.710790e-002, 2.134541e-002, -2.553117e-002, 2.967176e-002, 3.352215e-002, 3.695839e-002, -4.001679e-002, 4.261101e-002, 4.475191e-002, 4.648808e-002, -4.778138e-002, 4.860870e-002, 4.909802e-002, 4.895594e-002, -4.811304e-002, 4.619319e-002, 4.286583e-002, 3.757626e-002, -3.078569e-002, 2.270371e-002, 1.387770e-002, 4.779164e-003, --4.092503e-003, -1.223514e-002, -1.943952e-002, -2.501403e-002, --2.890377e-002, -3.074296e-002, -3.146610e-002, -3.118263e-002, --3.031050e-002, -2.902008e-002, -2.750036e-002, -2.575038e-002, --2.389904e-002, -2.210426e-002, -2.036294e-002, -1.863248e-002, --1.669441e-002, -1.452353e-002, -1.226031e-002, -1.015735e-002, --8.429232e-003, -7.551904e-003, -7.805462e-003, -9.591092e-003, --1.251954e-002, -1.624616e-002, -2.042288e-002, -2.489127e-002, --2.961188e-002, -3.457365e-002, -3.974123e-002, -4.515161e-002, --5.066904e-002, -5.605736e-002, -6.114674e-002, -6.562307e-002, --6.922415e-002, -7.189946e-002, -7.361066e-002, -7.433874e-002, --7.405536e-002, -7.270721e-002, -7.012071e-002, -6.611841e-002, --6.100698e-002, -5.502489e-002, -4.868240e-002, -4.232679e-002, --3.612787e-002, -3.028428e-002, -2.487482e-002, -1.998584e-002, --1.574838e-002, -1.197147e-002, -8.556320e-003, -5.313683e-003, --2.056393e-003, 1.300921e-003, 4.759251e-003, 7.977133e-003, -1.041292e-002, 1.159322e-002, 1.095794e-002, 8.868775e-003, -5.720589e-003, 1.987927e-003, -2.142447e-003, -6.294556e-003, --1.019883e-002, -1.354720e-002, -1.568827e-002, -1.662010e-002, --1.626665e-002, -1.495183e-002, -1.263052e-002, -9.424037e-003, --5.214581e-003, -3.637457e-004, 5.184059e-003, 1.123331e-002, -1.783394e-002, 2.477580e-002, 3.196704e-002, 3.925451e-002, -4.636777e-002, 5.324160e-002, 5.970919e-002, 6.560096e-002, -7.076938e-002, 7.526375e-002, 7.894620e-002, 8.173836e-002, -8.338935e-002, 8.389413e-002, 8.295499e-002, 8.052404e-002, -7.633860e-002, 7.064882e-002, 6.368782e-002, 5.574961e-002, -4.712387e-002, 3.798304e-002, 2.847083e-002, 1.876894e-002, -9.199882e-003, 8.134982e-005, -8.292884e-003, -1.578281e-002, --2.208553e-002, -2.686247e-002, -2.986153e-002, -3.109736e-002, --3.103209e-002, -3.000446e-002, -2.835409e-002, -2.610663e-002, --2.350317e-002, -2.038541e-002, -1.674303e-002, -1.252272e-002, --7.866186e-003, -2.851779e-003, 2.492958e-003, 8.033557e-003, -1.367719e-002, 1.931524e-002, 2.477433e-002, 2.961346e-002, -3.353311e-002, 3.612810e-002, 3.755714e-002, 3.776986e-002, -3.696566e-002, 3.494299e-002, 3.198361e-002, 2.795162e-002, -2.309764e-002, 1.740483e-002, 1.108238e-002, 4.356670e-003, --2.321363e-003, -8.611773e-003, -1.452709e-002, -2.007548e-002, --2.534522e-002, -3.017915e-002, -3.470007e-002, -3.849996e-002, --4.131858e-002, -4.254531e-002, -4.249640e-002, -4.128506e-002, --3.936655e-002, -3.702202e-002, -3.448186e-002, -3.190818e-002, --2.934765e-002, -2.684640e-002, -2.460685e-002, -2.221496e-002, --1.964941e-002, -1.697355e-002, -1.424889e-002, -1.160120e-002, --9.247429e-003, -7.441464e-003, -6.361837e-003, -6.528129e-003, --8.050804e-003, -1.102172e-002, -1.492772e-002, -1.948966e-002, --2.436264e-002, -2.923087e-002, -3.383541e-002, -3.788422e-002, --4.107220e-002, -4.299821e-002, -4.374500e-002, -4.370206e-002, --4.299503e-002, -4.176135e-002, -4.030828e-002, -3.855888e-002, --3.641545e-002, -3.388490e-002, -3.092344e-002, -2.770441e-002, --2.423872e-002, -2.068526e-002, -1.718202e-002, -1.392786e-002, --1.103860e-002, -8.474431e-003, -6.318499e-003, -4.599155e-003, --3.422108e-003, -2.864479e-003, -3.108889e-003, -4.030147e-003, --5.601586e-003, -7.665182e-003, -1.009093e-002, -1.265057e-002, --1.499711e-002, -1.699155e-002, -1.836148e-002, -1.901478e-002, --1.855544e-002, -1.709955e-002, -1.429007e-002, -1.041143e-002, --5.460287e-003, -1.261665e-004, 5.386747e-003, 1.089976e-002, -1.626002e-002, 2.136863e-002, 2.598781e-002, 3.025222e-002, -3.411378e-002, 3.765520e-002, 4.092193e-002, 4.407766e-002, -4.711565e-002, 5.002545e-002, 5.253883e-002, 5.441827e-002, -5.516846e-002, 5.452273e-002, 5.231446e-002, 4.903327e-002, -4.494703e-002, 4.041311e-002, 3.570318e-002, 3.118171e-002, -2.717054e-002, 2.386149e-002, 2.160893e-002, 2.049939e-002, -2.038334e-002, 2.116251e-002, 2.286725e-002, 2.550860e-002, -2.894125e-002, 3.308738e-002, 3.792023e-002, 4.331846e-002, -4.918008e-002, 5.532373e-002, 6.159011e-002, 6.779338e-002, -7.374011e-002, 7.924353e-002, 8.423250e-002, 8.856909e-002, -9.221454e-002, 9.504767e-002, 9.707952e-002, 9.807293e-002, -9.799350e-002, 9.672070e-002, 9.415504e-002, 9.002348e-002, -8.450535e-002, 7.751431e-002, 6.940370e-002, 6.022578e-002, -5.033211e-002, 3.981972e-002, 2.890956e-002, 1.780620e-002, -6.837274e-003, -3.649866e-003, -1.346158e-002, -2.202409e-002, --2.927461e-002, -3.418701e-002, -3.712054e-002, -3.801048e-002, --3.799177e-002, -3.726162e-002, -3.619610e-002, -3.506033e-002, --3.400738e-002, -3.308499e-002, -3.234542e-002, -3.170849e-002, --3.109412e-002, -3.025806e-002, -2.916112e-002, -2.775150e-002, --2.622987e-002, -2.484602e-002, -2.393411e-002, -2.409971e-002, --2.573158e-002, -2.916639e-002, -3.379603e-002, -3.916467e-002, --4.486303e-002, -5.070799e-002, -5.639171e-002, -6.160724e-002, --6.602924e-002, -6.921359e-002, -7.122981e-002, -7.199898e-002, --7.168501e-002, -7.024902e-002, -6.776982e-002, -6.444134e-002, --6.037314e-002, -5.562864e-002, -5.029028e-002, -4.437816e-002, --3.802252e-002, -3.145502e-002, -2.485232e-002, -1.840639e-002, --1.235996e-002, -6.777109e-003, -1.772432e-003, 2.620537e-003, -6.263084e-003, 9.094347e-003, 1.090423e-002, 1.170086e-002, -1.140007e-002, 1.000376e-002, 7.240458e-003, 3.183850e-003, --1.888138e-003, -7.616923e-003, -1.385779e-002, -2.034156e-002, --2.700623e-002, -3.368848e-002, -4.017702e-002, -4.643075e-002, --5.220429e-002, -5.740459e-002, -6.166947e-002, -6.508270e-002, --6.702545e-002, -6.779592e-002, -6.742814e-002, -6.647366e-002, --6.500989e-002, -6.315287e-002, -6.095275e-002, -5.846067e-002, --5.555226e-002, -5.228052e-002, -4.860444e-002, -4.471134e-002, --4.066064e-002, -3.656729e-002, -3.265045e-002, -2.903471e-002, --2.584128e-002, -2.297362e-002, -2.040460e-002, -1.796517e-002, --1.557589e-002, -1.311999e-002, -1.026399e-002, -6.838540e-003, --3.021240e-003, 8.577324e-004, 4.517220e-003, 7.792295e-003, -1.059834e-002, 1.309582e-002, 1.548577e-002, 1.807547e-002, -2.106829e-002, 2.450881e-002, 2.821024e-002, 3.197372e-002, -3.565061e-002, 3.905510e-002, 4.210720e-002, 4.478590e-002, -4.702872e-002, 4.874047e-002, 4.982489e-002, 5.008851e-002, -4.962144e-002, 4.843749e-002, 4.653002e-002, 4.392504e-002, -4.065179e-002, 3.687196e-002, 3.284988e-002, 2.874829e-002, -2.468267e-002, 2.080937e-002, 1.727332e-002, 1.404233e-002, -1.108063e-002, 8.401815e-003, 5.919254e-003, 3.778594e-003, -2.106801e-003, 1.245779e-003, 1.378594e-003, 2.542308e-003, -4.343873e-003, 6.437735e-003, 8.610165e-003, 1.076727e-002, -1.291231e-002, 1.512545e-002, 1.763357e-002, 2.059015e-002, -2.385508e-002, 2.733745e-002, 3.091171e-002, 3.446772e-002, -3.781009e-002, 4.082006e-002, 4.347937e-002, 4.566470e-002, -4.742892e-002, 4.858360e-002, 4.916511e-002, 4.900863e-002, -4.814129e-002, 4.638987e-002, 4.362059e-002, 3.988572e-002, -3.532695e-002, 2.990024e-002, 2.397584e-002, 1.788353e-002, -1.186827e-002, 6.180271e-003, 8.880984e-004, -3.978949e-003, --8.481564e-003, -1.262243e-002, -1.627360e-002, -1.929416e-002, --2.138942e-002, -2.203095e-002, -2.130047e-002, -1.960012e-002, --1.735257e-002, -1.482877e-002, -1.222444e-002, -9.705679e-003, --7.346903e-003, -5.179196e-003, -2.998641e-003, -7.377860e-004, -1.586075e-003, 3.984716e-003, 6.592354e-003, 9.220608e-003, -1.159025e-002, 1.343546e-002, 1.426936e-002, 1.371354e-002, -1.144513e-002, 7.906687e-003, 3.383170e-003, -1.721122e-003, --7.078401e-003, -1.232085e-002, -1.725658e-002, -2.157435e-002, --2.485614e-002, -2.723712e-002, -2.853628e-002, -2.936226e-002, --2.956426e-002, -2.892067e-002, -2.713533e-002, -2.432830e-002, --2.018329e-002, -1.487482e-002, -8.065705e-003, -4.364645e-016 -}, -{ -0.000000e+000, 2.809420e-003, -1.018166e-002, -1.607233e-002, --6.531790e-003, 9.806124e-003, 1.547079e-002, 1.789322e-002, -2.090920e-002, 2.277750e-002, 2.272582e-002, 2.096861e-002, -1.861513e-002, 1.398930e-002, 9.723537e-003, 6.832688e-003, -4.442118e-003, -3.286288e-003, -1.543934e-002, -2.795253e-002, --3.955612e-002, -4.810746e-002, -5.207969e-002, -5.463485e-002, --5.850764e-002, -6.197131e-002, -6.348723e-002, -6.040675e-002, --5.143065e-002, -3.913079e-002, -2.436117e-002, -8.321060e-003, -7.847207e-003, 2.324741e-002, 3.768372e-002, 4.978536e-002, -5.901839e-002, 6.590013e-002, 7.029410e-002, 7.237472e-002, -7.293774e-002, 7.211752e-002, 6.999381e-002, 6.665861e-002, -6.280686e-002, 5.910306e-002, 5.514026e-002, 5.083006e-002, -4.657457e-002, 4.226260e-002, 3.811827e-002, 3.144685e-002, -2.234341e-002, 1.130665e-002, -1.228664e-003, -1.412547e-002, --2.665243e-002, -3.848116e-002, -4.958071e-002, -5.857460e-002, --6.477188e-002, -6.806169e-002, -6.988016e-002, -7.073277e-002, --7.151384e-002, -7.239182e-002, -7.174895e-002, -7.010280e-002, --6.744340e-002, -6.444957e-002, -6.164063e-002, -5.812576e-002, --5.367930e-002, -4.848620e-002, -4.297960e-002, -3.657945e-002, --2.995950e-002, -2.296909e-002, -1.597946e-002, -8.585166e-003, --1.697770e-003, 4.594097e-003, 1.148932e-002, 1.864880e-002, -2.454625e-002, 2.995974e-002, 3.540992e-002, 4.048432e-002, -4.511239e-002, 4.926768e-002, 5.268009e-002, 5.575321e-002, -5.734974e-002, 5.920056e-002, 6.032806e-002, 5.995693e-002, -5.969690e-002, 5.902042e-002, 5.781207e-002, 5.622053e-002, -5.381067e-002, 5.049819e-002, 4.761916e-002, 4.397071e-002, -3.998229e-002, 3.513161e-002, 2.942715e-002, 2.346468e-002, -1.659425e-002, 9.489434e-003, 2.247064e-003, -4.686881e-003, --1.174621e-002, -1.771552e-002, -2.386280e-002, -2.966788e-002, --3.437926e-002, -3.819551e-002, -4.163036e-002, -4.412448e-002, --4.555780e-002, -4.623978e-002, -4.544021e-002, -4.476229e-002, --4.358747e-002, -4.207403e-002, -4.058685e-002, -3.819245e-002, --3.615695e-002, -3.405509e-002, -3.179835e-002, -2.949527e-002, --2.648948e-002, -2.369028e-002, -2.114936e-002, -1.836444e-002, --1.591602e-002, -1.363393e-002, -1.136803e-002, -9.137404e-003, --7.535997e-003, -5.620719e-003, -3.213874e-003, -1.470036e-003, --1.923592e-004, 1.624043e-003, 3.547500e-003, 5.339353e-003, -6.709763e-003, 8.519441e-003, 1.007765e-002, 1.163250e-002, -1.367996e-002, 1.581156e-002, 1.724416e-002, 1.839022e-002, -1.950540e-002, 2.023805e-002, 2.076328e-002, 2.068973e-002, -1.967446e-002, 1.825632e-002, 1.712688e-002, 1.629514e-002, -1.496719e-002, 1.384224e-002, 1.222421e-002, 1.104391e-002, -9.688910e-003, 8.311743e-003, 6.099691e-003, 4.040970e-003, -2.728207e-003, 1.296239e-003, -3.271050e-004, -1.922908e-003, --3.056780e-003, -4.447362e-003, -6.021394e-003, -7.704061e-003, --8.476206e-003, -9.592973e-003, -1.139000e-002, -1.291937e-002, --1.483325e-002, -1.662574e-002, -1.839277e-002, -1.977263e-002, --2.159780e-002, -2.298761e-002, -2.444793e-002, -2.541764e-002, --2.607849e-002, -2.653594e-002, -2.625631e-002, -2.607451e-002, --2.522690e-002, -2.381982e-002, -2.209074e-002, -1.965890e-002, --1.650331e-002, -1.267991e-002, -8.316619e-003, -4.122197e-003, -6.139747e-004, 5.426511e-003, 1.008538e-002, 1.478529e-002, -1.908357e-002, 2.327826e-002, 2.681402e-002, 3.041813e-002, -3.346004e-002, 3.662196e-002, 3.956993e-002, 4.235200e-002, -4.478214e-002, 4.653939e-002, 4.830894e-002, 4.948398e-002, -5.054973e-002, 5.105857e-002, 5.050251e-002, 4.961175e-002, -4.782370e-002, 4.582569e-002, 4.386202e-002, 4.110515e-002, -3.822829e-002, 3.546672e-002, 3.212723e-002, 2.876827e-002, -2.521422e-002, 2.131439e-002, 1.761771e-002, 1.397709e-002, -1.067392e-002, 7.543316e-003, 4.366291e-003, 1.737139e-003, --7.447393e-004, -3.013053e-003, -4.591211e-003, -5.725243e-003, --6.626321e-003, -7.209248e-003, -7.683898e-003, -7.636667e-003, --7.853054e-003, -7.986021e-003, -7.726953e-003, -7.367519e-003, --6.967053e-003, -6.343483e-003, -5.740733e-003, -5.276809e-003, --4.778452e-003, -4.480262e-003, -4.075902e-003, -3.580844e-003, --3.453706e-003, -3.529391e-003, -3.686823e-003, -4.354754e-003, --5.070101e-003, -5.457422e-003, -6.091960e-003, -6.374289e-003, --6.448705e-003, -6.388631e-003, -5.845405e-003, -4.843681e-003, --3.979188e-003, -3.074580e-003, -2.277702e-003, -1.925746e-003, --1.628717e-003, -1.406584e-003, -1.278687e-003, -1.262424e-003, --1.415470e-003, -1.777537e-003, -2.472035e-003, -3.214778e-003, --3.956296e-003, -4.650500e-003, -5.418870e-003, -6.527017e-003, --7.932233e-003, -9.894023e-003, -1.201590e-002, -1.445854e-002, --1.729280e-002, -2.017117e-002, -2.323385e-002, -2.645051e-002, --2.976934e-002, -3.354606e-002, -3.716312e-002, -4.084034e-002, --4.469100e-002, -4.802205e-002, -5.104382e-002, -5.378772e-002, --5.643616e-002, -5.879358e-002, -6.113698e-002, -6.347768e-002, --6.499656e-002, -6.606218e-002, -6.666706e-002, -6.644076e-002, --6.592303e-002, -6.526618e-002, -6.405506e-002, -6.249396e-002, --6.097593e-002, -5.937146e-002, -5.764781e-002, -5.538720e-002, --5.260856e-002, -4.966647e-002, -4.619167e-002, -4.256092e-002, --3.869885e-002, -3.498655e-002, -3.147448e-002, -2.776321e-002, --2.440123e-002, -2.141686e-002, -1.823270e-002, -1.508519e-002, --1.260855e-002, -1.029625e-002, -7.651348e-003, -5.079844e-003, --2.508632e-003, 3.713189e-004, 3.481791e-003, 6.670783e-003, -9.664226e-003, 1.295243e-002, 1.639668e-002, 1.963827e-002, -2.294923e-002, 2.659309e-002, 3.039730e-002, 3.433333e-002, -3.817145e-002, 4.179942e-002, 4.539036e-002, 4.862560e-002, -5.183228e-002, 5.502648e-002, 5.808067e-002, 6.084900e-002, -6.341277e-002, 6.574665e-002, 6.741887e-002, 6.867368e-002, -6.959052e-002, 7.031543e-002, 7.042482e-002, 7.021797e-002, -7.007120e-002, 7.006946e-002, 6.961916e-002, 6.908332e-002, -6.829339e-002, 6.748685e-002, 6.637344e-002, 6.538728e-002, -6.473878e-002, 6.422098e-002, 6.387686e-002, 6.329067e-002, -6.286465e-002, 6.251631e-002, 6.188805e-002, 6.105184e-002, -6.027457e-002, 5.917069e-002, 5.799086e-002, 5.699766e-002, -5.628580e-002, 5.530932e-002, 5.426259e-002, 5.306346e-002, -5.151547e-002, 4.933541e-002, 4.663960e-002, 4.381481e-002, -4.088084e-002, 3.784104e-002, 3.480345e-002, 3.164293e-002, -2.852055e-002, 2.516690e-002, 2.142936e-002, 1.767626e-002, -1.381035e-002, 9.869841e-003, 5.840078e-003, 1.961443e-003, --1.358005e-003, -4.485878e-003, -7.844382e-003, -1.106633e-002, --1.413394e-002, -1.704586e-002, -1.989014e-002, -2.259185e-002, --2.510935e-002, -2.738712e-002, -2.965424e-002, -3.162825e-002, --3.340499e-002, -3.516393e-002, -3.685782e-002, -3.853843e-002, --3.995720e-002, -4.124214e-002, -4.253524e-002, -4.369096e-002, --4.475482e-002, -4.585656e-002, -4.689625e-002, -4.779712e-002, --4.873259e-002, -4.978599e-002, -5.109208e-002, -5.227883e-002, --5.339013e-002, -5.436726e-002, -5.517795e-002, -5.562611e-002, --5.606504e-002, -5.596047e-002, -5.523544e-002, -5.410832e-002, --5.231778e-002, -5.051189e-002, -4.865704e-002, -4.707922e-002, --4.551303e-002, -4.370392e-002, -4.186493e-002, -3.984977e-002, --3.772692e-002, -3.534138e-002, -3.281257e-002, -3.043038e-002, --2.817283e-002, -2.586624e-002, -2.348127e-002, -2.129979e-002, --1.967071e-002, -1.839842e-002, -1.758337e-002, -1.692628e-002, --1.655944e-002, -1.635380e-002, -1.630859e-002, -1.657208e-002, --1.692339e-002, -1.738999e-002, -1.809193e-002, -1.884559e-002, --1.934509e-002, -1.997869e-002, -2.068653e-002, -2.147315e-002, --2.232529e-002, -2.288271e-002, -2.354313e-002, -2.417639e-002, --2.481200e-002, -2.556814e-002, -2.596303e-002, -2.634337e-002, --2.627289e-002, -2.605863e-002, -2.562108e-002, -2.464726e-002, --2.362012e-002, -2.237576e-002, -2.099902e-002, -1.956252e-002, --1.798387e-002, -1.623161e-002, -1.425377e-002, -1.219557e-002, --1.007178e-002, -8.158232e-003, -6.325662e-003, -4.402081e-003, --2.443807e-003, -5.249522e-004, 1.116050e-003, 2.573652e-003, -3.830355e-003, 5.231720e-003, 6.668176e-003, 7.899824e-003, -8.566820e-003, 9.086328e-003, 9.565302e-003, 1.009236e-002, -1.041448e-002, 1.049655e-002, 1.023852e-002, 1.014630e-002, -1.049090e-002, 1.106715e-002, 1.201162e-002, 1.303328e-002, -1.423878e-002, 1.573134e-002, 1.754041e-002, 1.944909e-002, -2.130418e-002, 2.279024e-002, 2.414751e-002, 2.561326e-002, -2.707436e-002, 2.834315e-002, 2.955795e-002, 3.041365e-002, -3.138294e-002, 3.250166e-002, 3.341287e-002, 3.403989e-002, -3.457351e-002, 3.486614e-002, 3.486823e-002, 3.454881e-002, -3.386356e-002, 3.300952e-002, 3.216468e-002, 3.105147e-002, -2.995258e-002, 2.907563e-002, 2.836722e-002, 2.807916e-002, -2.819382e-002, 2.838264e-002, 2.874392e-002, 2.930124e-002, -3.000012e-002, 3.082115e-002, 3.160027e-002, 3.202467e-002, -3.221299e-002, 3.213385e-002, 3.201174e-002, 3.195233e-002, -3.182716e-002, 3.149019e-002, 3.104207e-002, 3.067949e-002, -3.034484e-002, 2.983977e-002, 2.880255e-002, 2.726677e-002, -2.528284e-002, 2.307693e-002, 2.086115e-002, 1.852106e-002, -1.601943e-002, 1.343327e-002, 1.065968e-002, 7.784080e-003, -5.057599e-003, 2.257712e-003, -5.166272e-004, -3.099734e-003, --5.503263e-003, -7.692548e-003, -9.775971e-003, -1.143168e-002, --1.273145e-002, -1.379694e-002, -1.453645e-002, -1.485248e-002, --1.478715e-002, -1.456329e-002, -1.416304e-002, -1.378568e-002, --1.367538e-002, -1.346707e-002, -1.338091e-002, -1.337350e-002, --1.344266e-002, -1.353764e-002, -1.371793e-002, -1.369050e-002, --1.370878e-002, -1.354285e-002, -1.367127e-002, -1.380686e-002, --1.389728e-002, -1.409517e-002, -1.467250e-002, -1.566821e-002, --1.688552e-002, -1.812636e-002, -1.934493e-002, -2.040972e-002, --2.125185e-002, -2.191478e-002, -2.265382e-002, -2.379584e-002, --2.447061e-002, -2.483882e-002, -2.466953e-002, -2.419094e-002, --2.354120e-002, -2.269191e-002, -2.169821e-002, -2.041436e-002, --1.892845e-002, -1.727855e-002, -1.540800e-002, -1.364184e-002, --1.184800e-002, -1.046199e-002, -9.719390e-003, -8.879728e-003, --8.060939e-003, -7.387932e-003, -6.826069e-003, -6.333804e-003, --6.051961e-003, -5.931181e-003, -5.879742e-003, -6.004457e-003, --6.466059e-003, -7.343483e-003, -8.425080e-003, -9.639188e-003, --1.103471e-002, -1.231647e-002, -1.353618e-002, -1.470765e-002, --1.584574e-002, -1.708902e-002, -1.832268e-002, -1.910493e-002, --1.925496e-002, -1.898867e-002, -1.843157e-002, -1.736933e-002, --1.577318e-002, -1.370763e-002, -1.132518e-002, -8.694927e-003, --5.833429e-003, -2.880202e-003, 1.200538e-004, 3.182671e-003, -6.302583e-003, 9.494026e-003, 1.294633e-002, 1.644557e-002, -1.981930e-002, 2.316515e-002, 2.634589e-002, 2.937961e-002, -3.235725e-002, 3.509975e-002, 3.762362e-002, 4.001504e-002, -4.158814e-002, 4.258871e-002, 4.334019e-002, 4.405809e-002, -4.443160e-002, 4.448508e-002, 4.444019e-002, 4.393559e-002, -4.338478e-002, 4.269724e-002, 4.207625e-002, 4.169790e-002, -4.121821e-002, 4.074979e-002, 4.020284e-002, 3.948899e-002, -3.893418e-002, 3.868117e-002, 3.893758e-002, 3.943516e-002, -3.980624e-002, 4.027181e-002, 4.080484e-002, 4.131376e-002, -4.164916e-002, 4.183542e-002, 4.159049e-002, 4.116916e-002, -4.056963e-002, 3.999757e-002, 3.944882e-002, 3.891403e-002, -3.807400e-002, 3.720542e-002, 3.616425e-002, 3.469979e-002, -3.260476e-002, 3.017073e-002, 2.754672e-002, 2.477863e-002, -2.195307e-002, 1.907490e-002, 1.629220e-002, 1.371438e-002, -1.123741e-002, 9.049839e-003, 7.219714e-003, 5.585377e-003, -3.969487e-003, 2.484008e-003, 8.596376e-004, -5.845881e-004, --1.981340e-003, -3.207072e-003, -4.217189e-003, -5.311963e-003, --6.446577e-003, -7.501763e-003, -8.535742e-003, -9.563127e-003, --1.065290e-002, -1.175375e-002, -1.291877e-002, -1.412201e-002, --1.544301e-002, -1.693868e-002, -1.861178e-002, -2.036392e-002, --2.227662e-002, -2.422983e-002, -2.606564e-002, -2.775785e-002, --2.954101e-002, -3.116040e-002, -3.252434e-002, -3.383050e-002, --3.498702e-002, -3.587027e-002, -3.633093e-002, -3.639564e-002, --3.618091e-002, -3.586866e-002, -3.559997e-002, -3.533912e-002, --3.522113e-002, -3.522200e-002, -3.540567e-002, -3.554025e-002, --3.575823e-002, -3.593865e-002, -3.603758e-002, -3.603350e-002, --3.588265e-002, -3.557097e-002, -3.522465e-002, -3.501552e-002, --3.505161e-002, -3.529570e-002, -3.580193e-002, -3.648309e-002, --3.721433e-002, -3.805832e-002, -3.878846e-002, -3.944967e-002, --3.998897e-002, -4.028615e-002, -4.051445e-002, -4.050024e-002, --4.014576e-002, -3.959262e-002, -3.869401e-002, -3.755213e-002, --3.617108e-002, -3.462522e-002, -3.303161e-002, -3.128883e-002, --2.941250e-002, -2.744797e-002, -2.531693e-002, -2.305536e-002, --2.085937e-002, -1.874498e-002, -1.677514e-002, -1.508296e-002, --1.364214e-002, -1.237144e-002, -1.124997e-002, -1.046911e-002, --9.808205e-003, -9.511396e-003, -9.490375e-003, -9.878623e-003, --1.065723e-002, -1.170485e-002, -1.295802e-002, -1.434085e-002, --1.576909e-002, -1.729681e-002, -1.886865e-002, -2.043805e-002, --2.197814e-002, -2.342369e-002, -2.479661e-002, -2.582199e-002, --2.637706e-002, -2.631817e-002, -2.577374e-002, -2.476469e-002, --2.347682e-002, -2.193465e-002, -2.036816e-002, -1.871834e-002, --1.691508e-002, -1.507779e-002, -1.329426e-002, -1.154331e-002, --9.832614e-003, -8.087838e-003, -6.224715e-003, -4.325074e-003, --2.329493e-003, -3.341823e-004, 1.549680e-003, 3.178575e-003, -4.457852e-003, 5.302413e-003, 5.862484e-003, 6.293863e-003, -6.533263e-003, 6.768137e-003, 6.924227e-003, 7.212002e-003, -7.725657e-003, 8.505379e-003, 9.444463e-003, 1.057038e-002, -1.193028e-002, 1.346425e-002, 1.525876e-002, 1.691790e-002, -1.866684e-002, 2.049635e-002, 2.218015e-002, 2.373882e-002, -2.531557e-002, 2.699776e-002, 2.875195e-002, 3.036702e-002, -3.189079e-002, 3.327291e-002, 3.447763e-002, 3.540290e-002, -3.607898e-002, 3.651880e-002, 3.672107e-002, 3.670173e-002, -3.634252e-002, 3.574405e-002, 3.487347e-002, 3.377866e-002, -3.239732e-002, 3.085258e-002, 2.919181e-002, 2.744419e-002, -2.560025e-002, 2.383301e-002, 2.204382e-002, 2.027120e-002, -1.866113e-002, 1.723227e-002, 1.596429e-002, 1.488236e-002, -1.413082e-002, 1.364276e-002, 1.345665e-002, 1.339082e-002, -1.339199e-002, 1.336845e-002, 1.334505e-002, 1.345700e-002, -1.366851e-002, 1.391576e-002, 1.421393e-002, 1.463397e-002, -1.518057e-002, 1.562639e-002, 1.594114e-002, 1.608331e-002, -1.614689e-002, 1.625631e-002, 1.642853e-002, 1.646063e-002, -1.647893e-002, 1.658519e-002, 1.673895e-002, 1.697354e-002, -1.719142e-002, 1.732659e-002, 1.746007e-002, 1.763408e-002, -1.779985e-002, 1.791608e-002, 1.807997e-002, 1.831716e-002, -1.863347e-002, 1.902103e-002, 1.942895e-002, 1.983889e-002, -2.020219e-002, 2.053772e-002, 2.089104e-002, 2.118801e-002, -2.146233e-002, 2.153422e-002, 2.140011e-002, 2.093561e-002, -2.026548e-002, 1.942257e-002, 1.840138e-002, 1.722718e-002, -1.609799e-002, 1.489345e-002, 1.364427e-002, 1.231308e-002, -1.101310e-002, 9.770681e-003, 8.442610e-003, 7.183730e-003, -5.965439e-003, 4.779826e-003, 3.802337e-003, 2.959676e-003, -2.220901e-003, 1.621127e-003, 1.216553e-003, 9.882911e-004, -7.562584e-004, 5.403305e-004, 2.496082e-004, -1.623190e-004, --5.324524e-004, -8.451632e-004, -1.211276e-003, -1.583089e-003, --1.895811e-003, -2.109040e-003, -2.431405e-003, -2.975188e-003, --3.585077e-003, -4.352275e-003, -5.128317e-003, -5.850866e-003, --6.757719e-003, -7.814969e-003, -9.050739e-003, -1.051684e-002, --1.216976e-002, -1.401496e-002, -1.592177e-002, -1.802597e-002, --2.026878e-002, -2.242096e-002, -2.465691e-002, -2.685876e-002, --2.900694e-002, -3.107607e-002, -3.304047e-002, -3.487575e-002, --3.665964e-002, -3.828642e-002, -3.978452e-002, -4.072626e-002, --4.138006e-002, -4.170846e-002, -4.175056e-002, -4.160614e-002, --4.136515e-002, -4.109265e-002, -4.082368e-002, -4.048039e-002, --4.011434e-002, -3.952376e-002, -3.882572e-002, -3.812580e-002, --3.738514e-002, -3.647261e-002, -3.553116e-002, -3.463221e-002, --3.381664e-002, -3.312101e-002, -3.244309e-002, -3.185788e-002, --3.142738e-002, -3.129164e-002, -3.138592e-002, -3.153924e-002, --3.167297e-002, -3.174434e-002, -3.150999e-002, -3.102688e-002, --3.028653e-002, -2.890663e-002, -2.690131e-002, -2.480032e-002, --2.257537e-002, -2.016211e-002, -1.749404e-002, -1.459472e-002, --1.138792e-002, -7.915766e-003, -4.100576e-003, -9.188738e-017 -}, -{ -0.000000e+000, 1.775357e-001, 7.979680e-002, 1.352792e-002, --4.181026e-002, -8.132769e-002, -9.404215e-002, -9.455916e-002, --8.989608e-002, -7.985256e-002, -6.352006e-002, -4.249475e-002, --2.158452e-002, -2.144340e-003, 1.387942e-002, 2.614182e-002, -3.623704e-002, 4.952434e-002, 6.440980e-002, 7.656527e-002, -8.532643e-002, 9.006238e-002, 8.925514e-002, 8.553569e-002, -8.001661e-002, 7.234274e-002, 6.292727e-002, 5.081386e-002, -3.455084e-002, 1.628341e-002, -9.577941e-004, -1.710321e-002, --3.364821e-002, -4.937845e-002, -6.160547e-002, -7.215006e-002, --7.944840e-002, -8.551187e-002, -8.850542e-002, -8.786581e-002, --8.574996e-002, -8.164471e-002, -7.543580e-002, -6.745867e-002, --5.972414e-002, -5.113554e-002, -4.158569e-002, -3.217542e-002, --2.235508e-002, -1.259558e-002, -4.125795e-003, 4.980666e-003, -1.542817e-002, 2.425986e-002, 3.323126e-002, 4.138104e-002, -4.786503e-002, 5.384034e-002, 5.833674e-002, 5.985009e-002, -6.008450e-002, 5.856717e-002, 5.535562e-002, 5.217350e-002, -4.939453e-002, 4.647304e-002, 4.302402e-002, 3.869665e-002, -3.376300e-002, 2.895096e-002, 2.508852e-002, 2.138382e-002, -1.790209e-002, 1.397248e-002, 1.085247e-002, 7.112188e-003, -4.141784e-003, 1.008189e-003, -1.734751e-003, -4.170502e-003, --5.749999e-003, -6.565894e-003, -8.863328e-003, -1.047774e-002, --1.134416e-002, -1.237537e-002, -1.359732e-002, -1.499437e-002, --1.656193e-002, -1.875878e-002, -2.116694e-002, -2.377183e-002, --2.583156e-002, -2.972528e-002, -3.352681e-002, -3.652694e-002, --3.924507e-002, -4.137043e-002, -4.400875e-002, -4.597247e-002, --4.615330e-002, -4.443518e-002, -4.269496e-002, -3.992261e-002, --3.606324e-002, -3.078620e-002, -2.500238e-002, -1.934453e-002, --1.294978e-002, -5.985306e-003, 6.514435e-004, 7.523569e-003, -1.469514e-002, 2.019589e-002, 2.646191e-002, 3.300599e-002, -3.863131e-002, 4.337335e-002, 4.811716e-002, 5.140081e-002, -5.356248e-002, 5.459523e-002, 5.348807e-002, 5.220604e-002, -4.915858e-002, 4.542092e-002, 4.126595e-002, 3.610679e-002, -3.093757e-002, 2.501628e-002, 1.937139e-002, 1.391436e-002, -8.183900e-003, 3.182525e-003, -1.561228e-003, -6.152567e-003, --1.003398e-002, -1.312406e-002, -1.580312e-002, -1.801383e-002, --1.940371e-002, -2.016203e-002, -2.105435e-002, -2.114119e-002, --2.037169e-002, -1.994137e-002, -1.934457e-002, -1.869300e-002, --1.806662e-002, -1.770362e-002, -1.741813e-002, -1.694187e-002, --1.680878e-002, -1.744478e-002, -1.743596e-002, -1.789348e-002, --1.848119e-002, -1.931411e-002, -2.069512e-002, -2.212333e-002, --2.299142e-002, -2.329873e-002, -2.367479e-002, -2.315248e-002, --2.254663e-002, -2.116020e-002, -1.878980e-002, -1.636173e-002, --1.359265e-002, -1.084110e-002, -7.631074e-003, -4.656106e-003, --2.122915e-003, 8.894334e-004, 4.645573e-003, 8.285496e-003, -1.132095e-002, 1.480997e-002, 1.825488e-002, 2.156204e-002, -2.443520e-002, 2.778102e-002, 3.067893e-002, 3.261212e-002, -3.396389e-002, 3.433366e-002, 3.416079e-002, 3.297483e-002, -3.162678e-002, 2.971160e-002, 2.783266e-002, 2.509069e-002, -2.207689e-002, 1.883806e-002, 1.577284e-002, 1.242304e-002, -8.554981e-003, 4.585555e-003, 6.295037e-004, -3.454476e-003, --7.768312e-003, -1.168489e-002, -1.528311e-002, -1.806253e-002, --1.981329e-002, -2.133462e-002, -2.190818e-002, -2.226976e-002, --2.224323e-002, -2.247921e-002, -2.243741e-002, -2.190444e-002, --2.096512e-002, -1.985596e-002, -1.875623e-002, -1.726679e-002, --1.558051e-002, -1.292094e-002, -1.037565e-002, -8.147544e-003, --6.437165e-003, -5.227895e-003, -4.158536e-003, -3.732459e-003, --3.722019e-003, -4.418284e-003, -5.350611e-003, -6.384840e-003, --7.369892e-003, -8.086721e-003, -8.829875e-003, -9.976483e-003, --1.128610e-002, -1.227019e-002, -1.288059e-002, -1.316039e-002, --1.299378e-002, -1.232223e-002, -1.129450e-002, -1.033996e-002, --8.544031e-003, -6.416954e-003, -3.781462e-003, -5.711857e-004, -3.006480e-003, 6.724098e-003, 1.076401e-002, 1.491517e-002, -1.895882e-002, 2.256829e-002, 2.590205e-002, 2.861946e-002, -3.150810e-002, 3.415531e-002, 3.629499e-002, 3.811590e-002, -3.945154e-002, 4.027185e-002, 4.107981e-002, 4.160502e-002, -4.122586e-002, 3.995920e-002, 3.781511e-002, 3.466041e-002, -3.106434e-002, 2.731750e-002, 2.336091e-002, 1.926572e-002, -1.498141e-002, 1.066941e-002, 6.827950e-003, 3.929504e-003, -1.759070e-003, 5.049203e-006, -1.236753e-003, -2.031242e-003, --3.023990e-003, -3.997621e-003, -4.846515e-003, -5.539956e-003, --5.614635e-003, -5.390870e-003, -5.293738e-003, -4.959130e-003, --4.652576e-003, -4.340632e-003, -4.029976e-003, -3.551811e-003, --2.873659e-003, -2.737576e-003, -3.096771e-003, -3.675966e-003, --4.912411e-003, -6.339363e-003, -8.262740e-003, -1.008501e-002, --1.186835e-002, -1.416567e-002, -1.651681e-002, -1.885461e-002, --2.101977e-002, -2.302081e-002, -2.520444e-002, -2.701756e-002, --2.872377e-002, -2.963996e-002, -3.049702e-002, -3.142430e-002, --3.186568e-002, -3.136799e-002, -3.013262e-002, -2.838334e-002, --2.608881e-002, -2.367548e-002, -2.104875e-002, -1.850133e-002, --1.626662e-002, -1.424306e-002, -1.216910e-002, -1.007748e-002, --8.601265e-003, -7.327093e-003, -6.156899e-003, -5.263699e-003, --4.500261e-003, -3.868689e-003, -3.471726e-003, -3.621955e-003, --4.361121e-003, -5.825650e-003, -7.523705e-003, -9.524831e-003, --1.113932e-002, -1.300857e-002, -1.443190e-002, -1.598567e-002, --1.755453e-002, -1.858104e-002, -1.876336e-002, -1.890419e-002, --1.853937e-002, -1.717003e-002, -1.518982e-002, -1.340745e-002, --1.166089e-002, -9.369656e-003, -6.375983e-003, -3.794227e-003, --9.063803e-004, 2.630407e-003, 5.814687e-003, 8.457891e-003, -1.113809e-002, 1.383462e-002, 1.630567e-002, 1.795249e-002, -1.923565e-002, 2.063875e-002, 2.170862e-002, 2.202251e-002, -2.190855e-002, 2.131468e-002, 2.012651e-002, 1.837314e-002, -1.611885e-002, 1.369061e-002, 1.112773e-002, 8.527285e-003, -6.354191e-003, 4.498634e-003, 3.205042e-003, 2.104224e-003, -1.452466e-003, 1.030718e-003, 8.000544e-004, 8.816518e-004, -1.198639e-003, 1.529135e-003, 2.624371e-003, 4.219281e-003, -5.570242e-003, 6.871363e-003, 8.345820e-003, 9.974319e-003, -1.168317e-002, 1.370895e-002, 1.566419e-002, 1.728765e-002, -1.910706e-002, 2.073761e-002, 2.189938e-002, 2.300193e-002, -2.397654e-002, 2.428721e-002, 2.408840e-002, 2.403179e-002, -2.375233e-002, 2.336199e-002, 2.326847e-002, 2.302351e-002, -2.278487e-002, 2.246375e-002, 2.221760e-002, 2.205809e-002, -2.194189e-002, 2.205544e-002, 2.226019e-002, 2.240831e-002, -2.316443e-002, 2.446934e-002, 2.578075e-002, 2.708407e-002, -2.885995e-002, 3.097414e-002, 3.285330e-002, 3.454499e-002, -3.600295e-002, 3.718985e-002, 3.845224e-002, 3.961443e-002, -4.031147e-002, 4.055667e-002, 4.062118e-002, 4.058071e-002, -4.039952e-002, 3.970058e-002, 3.854700e-002, 3.684086e-002, -3.427955e-002, 3.125401e-002, 2.787866e-002, 2.395604e-002, -1.942683e-002, 1.482693e-002, 1.048785e-002, 6.045669e-003, -1.982673e-003, -1.302004e-003, -3.899580e-003, -5.886104e-003, --7.842257e-003, -9.674443e-003, -1.105018e-002, -1.237017e-002, --1.361624e-002, -1.502766e-002, -1.603838e-002, -1.661329e-002, --1.706196e-002, -1.710428e-002, -1.716246e-002, -1.704844e-002, --1.664906e-002, -1.602535e-002, -1.540476e-002, -1.505375e-002, --1.475886e-002, -1.478372e-002, -1.529264e-002, -1.635865e-002, --1.777904e-002, -1.943414e-002, -2.142450e-002, -2.368372e-002, --2.625104e-002, -2.898016e-002, -3.201301e-002, -3.513431e-002, --3.830305e-002, -4.121431e-002, -4.401926e-002, -4.682618e-002, --4.941242e-002, -5.210793e-002, -5.493625e-002, -5.790831e-002, --6.089795e-002, -6.379123e-002, -6.650859e-002, -6.910548e-002, --7.126689e-002, -7.298534e-002, -7.419012e-002, -7.499471e-002, --7.476631e-002, -7.414241e-002, -7.328070e-002, -7.232296e-002, --7.106393e-002, -6.968162e-002, -6.801872e-002, -6.629371e-002, --6.421620e-002, -6.203182e-002, -5.979665e-002, -5.726520e-002, --5.468427e-002, -5.205914e-002, -4.954892e-002, -4.721034e-002, --4.493305e-002, -4.271330e-002, -4.077976e-002, -3.902642e-002, --3.774334e-002, -3.660856e-002, -3.561699e-002, -3.464784e-002, --3.360078e-002, -3.245776e-002, -3.106918e-002, -2.941113e-002, --2.756369e-002, -2.518323e-002, -2.236100e-002, -1.906614e-002, --1.513502e-002, -1.066250e-002, -5.681722e-003, -3.627199e-004, -4.849522e-003, 9.939384e-003, 1.493057e-002, 1.974156e-002, -2.450762e-002, 2.918955e-002, 3.348365e-002, 3.766386e-002, -4.191393e-002, 4.577118e-002, 4.961030e-002, 5.326948e-002, -5.665471e-002, 5.960551e-002, 6.212528e-002, 6.362037e-002, -6.463237e-002, 6.510611e-002, 6.501099e-002, 6.478102e-002, -6.437586e-002, 6.401700e-002, 6.395799e-002, 6.448419e-002, -6.546915e-002, 6.707784e-002, 6.892822e-002, 7.064042e-002, -7.225311e-002, 7.377380e-002, 7.524948e-002, 7.637023e-002, -7.737744e-002, 7.797798e-002, 7.851166e-002, 7.888344e-002, -7.909658e-002, 7.911198e-002, 7.878248e-002, 7.778447e-002, -7.639693e-002, 7.467471e-002, 7.215210e-002, 6.866437e-002, -6.455535e-002, 5.989722e-002, 5.492741e-002, 4.986163e-002, -4.466924e-002, 3.919746e-002, 3.343930e-002, 2.759372e-002, -2.154575e-002, 1.548573e-002, 9.867207e-003, 4.594771e-003, --4.861828e-004, -5.317351e-003, -9.733447e-003, -1.373598e-002, --1.712451e-002, -1.994701e-002, -2.240120e-002, -2.426787e-002, --2.553491e-002, -2.681574e-002, -2.795392e-002, -2.874376e-002, --2.909890e-002, -2.966294e-002, -3.030858e-002, -3.103860e-002, --3.196441e-002, -3.287562e-002, -3.385058e-002, -3.484905e-002, --3.569129e-002, -3.637912e-002, -3.649371e-002, -3.642645e-002, --3.634688e-002, -3.652791e-002, -3.765951e-002, -3.941217e-002, --4.160522e-002, -4.403155e-002, -4.650886e-002, -4.882421e-002, --5.100350e-002, -5.296732e-002, -5.371197e-002, -5.339855e-002, --5.269341e-002, -5.153556e-002, -4.975909e-002, -4.759429e-002, --4.500084e-002, -4.201360e-002, -3.873806e-002, -3.510419e-002, --3.104240e-002, -2.663765e-002, -2.225724e-002, -1.802804e-002, --1.384412e-002, -9.820509e-003, -5.050622e-003, -4.289706e-004, -4.052347e-003, 8.205976e-003, 1.192988e-002, 1.524583e-002, -1.807433e-002, 2.038908e-002, 2.202368e-002, 2.313006e-002, -2.338327e-002, 2.299727e-002, 2.193849e-002, 2.026913e-002, -1.817884e-002, 1.570489e-002, 1.309728e-002, 1.032596e-002, -7.360666e-003, 4.936064e-003, 3.377962e-003, 2.120746e-003, -1.173231e-003, 8.454707e-004, 1.201537e-003, 2.048667e-003, -3.265170e-003, 4.649957e-003, 6.221985e-003, 7.788325e-003, -9.483841e-003, 1.107598e-002, 1.252047e-002, 1.402649e-002, -1.551110e-002, 1.708970e-002, 1.877187e-002, 2.067124e-002, -2.263600e-002, 2.437845e-002, 2.573648e-002, 2.668888e-002, -2.715850e-002, 2.706352e-002, 2.640040e-002, 2.515413e-002, -2.428459e-002, 2.305134e-002, 2.144984e-002, 1.924720e-002, -1.642339e-002, 1.321792e-002, 9.807667e-003, 6.797553e-003, -3.735818e-003, 7.514496e-004, -2.166591e-003, -4.979942e-003, --7.600587e-003, -9.896762e-003, -1.157031e-002, -1.263526e-002, --1.341388e-002, -1.373675e-002, -1.361667e-002, -1.293288e-002, --1.186383e-002, -1.051878e-002, -8.980802e-003, -7.485443e-003, --6.153049e-003, -5.251272e-003, -4.228142e-003, -3.160194e-003, --2.085850e-003, -1.061451e-003, 1.950122e-004, 1.459818e-003, -2.644450e-003, 3.392209e-003, 3.663072e-003, 3.417374e-003, -3.087652e-003, 2.184496e-003, 9.372766e-004, -7.760320e-004, --2.820221e-003, -5.122270e-003, -7.301273e-003, -9.263047e-003, --1.080978e-002, -1.194177e-002, -1.257250e-002, -1.309406e-002, --1.360562e-002, -1.414431e-002, -1.414208e-002, -1.386393e-002, --1.342487e-002, -1.288935e-002, -1.209866e-002, -1.123930e-002, --1.027309e-002, -9.341628e-003, -8.439419e-003, -7.611592e-003, --6.857919e-003, -6.202253e-003, -5.447803e-003, -4.974656e-003, --4.786738e-003, -4.817972e-003, -5.098632e-003, -5.534286e-003, --6.200894e-003, -6.997894e-003, -7.957233e-003, -8.975942e-003, --9.901044e-003, -1.069107e-002, -1.110689e-002, -1.127027e-002, --1.109272e-002, -1.050726e-002, -9.507492e-003, -8.115713e-003, --6.430399e-003, -4.421938e-003, -2.272362e-003, -1.816417e-004, -2.030257e-003, 4.134755e-003, 6.023023e-003, 7.810538e-003, -9.156440e-003, 1.024259e-002, 1.111831e-002, 1.189350e-002, -1.259942e-002, 1.310139e-002, 1.327879e-002, 1.315171e-002, -1.252567e-002, 1.112800e-002, 8.994592e-003, 6.421979e-003, -3.538716e-003, 3.993954e-004, -2.835571e-003, -6.087859e-003, --9.221628e-003, -1.197625e-002, -1.434723e-002, -1.632804e-002, --1.784148e-002, -1.888035e-002, -1.948038e-002, -1.954604e-002, --1.911668e-002, -1.829814e-002, -1.730183e-002, -1.598150e-002, --1.446889e-002, -1.274503e-002, -1.078627e-002, -8.696629e-003, --6.663931e-003, -4.758186e-003, -2.985306e-003, -1.346903e-003, -1.714629e-004, 1.503688e-003, 2.620473e-003, 3.419879e-003, -3.913133e-003, 3.983803e-003, 3.627704e-003, 2.761205e-003, -1.373365e-003, -7.614991e-004, -3.359978e-003, -6.327332e-003, --9.489271e-003, -1.285245e-002, -1.628861e-002, -1.966313e-002, --2.305150e-002, -2.635355e-002, -2.939505e-002, -3.182279e-002, --3.366256e-002, -3.428710e-002, -3.390725e-002, -3.269257e-002, --3.094919e-002, -2.875207e-002, -2.632205e-002, -2.374175e-002, --2.108559e-002, -1.842597e-002, -1.582152e-002, -1.321845e-002, --1.054523e-002, -7.588319e-003, -4.343790e-003, -1.007493e-003, -2.452652e-003, 5.753544e-003, 8.825611e-003, 1.144666e-002, -1.342857e-002, 1.460600e-002, 1.527906e-002, 1.564168e-002, -1.556156e-002, 1.525283e-002, 1.497526e-002, 1.477464e-002, -1.477853e-002, 1.529520e-002, 1.616800e-002, 1.737829e-002, -1.898786e-002, 2.082708e-002, 2.292831e-002, 2.539158e-002, -2.815409e-002, 3.113407e-002, 3.422539e-002, 3.734651e-002, -4.054805e-002, 4.381825e-002, 4.694746e-002, 4.979756e-002, -5.248548e-002, 5.485682e-002, 5.697983e-002, 5.879039e-002, -6.025996e-002, 6.138572e-002, 6.213849e-002, 6.254298e-002, -6.213726e-002, 6.111987e-002, 5.961144e-002, 5.760319e-002, -5.506664e-002, 5.217947e-002, 4.898028e-002, 4.545073e-002, -4.171353e-002, 3.784315e-002, 3.368698e-002, 2.941330e-002, -2.514378e-002, 2.112067e-002, 1.730015e-002, 1.380694e-002, -1.074233e-002, 8.111265e-003, 5.932291e-003, 3.967943e-003, -2.124484e-003, 2.590918e-004, -1.614750e-003, -3.221389e-003, --4.685661e-003, -5.996490e-003, -7.168788e-003, -8.353833e-003, --9.407996e-003, -1.060870e-002, -1.197320e-002, -1.347213e-002, --1.502766e-002, -1.650786e-002, -1.807159e-002, -1.972657e-002, --2.140553e-002, -2.310271e-002, -2.455948e-002, -2.595821e-002, --2.726262e-002, -2.829948e-002, -2.929560e-002, -3.016580e-002, --3.104933e-002, -3.194615e-002, -3.275305e-002, -3.329386e-002, --3.348462e-002, -3.323642e-002, -3.268994e-002, -3.197261e-002, --3.115347e-002, -3.014446e-002, -2.882002e-002, -2.734648e-002, --2.583023e-002, -2.425473e-002, -2.271960e-002, -2.135692e-002, --2.021249e-002, -1.921181e-002, -1.840541e-002, -1.780193e-002, --1.708260e-002, -1.653625e-002, -1.605120e-002, -1.573433e-002, --1.542857e-002, -1.488292e-002, -1.426872e-002, -1.356561e-002, --1.284821e-002, -1.216225e-002, -1.119633e-002, -1.033367e-002, --9.469171e-003, -8.475329e-003, -7.252268e-003, -5.751224e-003, --4.212961e-003, -2.722542e-003, -1.440589e-003, -4.467704e-004, -6.579989e-004, 1.813995e-003, 2.956321e-003, 4.202859e-003, -5.523915e-003, 6.832496e-003, 7.916275e-003, 8.690821e-003, -9.389211e-003, 9.914250e-003, 1.047504e-002, 1.115510e-002, -1.163301e-002, 1.186959e-002, 1.178835e-002, 1.136925e-002, -1.073839e-002, 9.806369e-003, 8.580976e-003, 7.023311e-003, -5.227929e-003, 3.581448e-003, 1.674001e-003, -2.985087e-004, --2.187038e-003, -3.948673e-003, -5.603546e-003, -7.083235e-003, --8.440955e-003, -9.694266e-003, -1.092461e-002, -1.151291e-002, --1.176717e-002, -1.150360e-002, -1.078362e-002, -9.779732e-003, --8.650834e-003, -7.540768e-003, -6.564140e-003, -5.746833e-003, --5.044707e-003, -4.079054e-003, -2.880693e-003, -1.766820e-003, --7.531407e-004, 1.536002e-004, 9.972695e-004, 1.514170e-003, -1.794759e-003, 1.766574e-003, 1.399637e-003, 7.233265e-004, --3.432990e-004, -1.971344e-003, -4.029041e-003, -6.459820e-003, --8.946882e-003, -1.117553e-002, -1.336150e-002, -1.533071e-002, --1.677262e-002, -1.736740e-002, -1.696338e-002, -1.647855e-002, --1.588990e-002, -1.499175e-002, -1.364992e-002, -1.191554e-002, --9.664514e-003, -6.972047e-003, -3.712297e-003, 1.604006e-016 -}, -{ -0.000000e+000, 2.038977e-001, 5.986865e-002, -2.943833e-002, --7.799666e-002, -9.455111e-002, -9.181085e-002, -8.392553e-002, --7.199876e-002, -5.644654e-002, -3.877417e-002, -1.894875e-002, -1.009213e-003, 1.790310e-002, 3.261412e-002, 4.651401e-002, -5.801635e-002, 6.683222e-002, 7.214600e-002, 7.485705e-002, -7.358793e-002, 6.958921e-002, 6.187436e-002, 5.197944e-002, -3.971716e-002, 2.704245e-002, 1.407361e-002, 1.397047e-003, --9.703171e-003, -1.932254e-002, -2.683198e-002, -3.274516e-002, --3.789992e-002, -4.282698e-002, -4.668334e-002, -4.931934e-002, --5.059847e-002, -5.211500e-002, -5.158548e-002, -4.862963e-002, --4.493928e-002, -4.019584e-002, -3.521807e-002, -2.887773e-002, --2.261393e-002, -1.728430e-002, -1.101569e-002, -4.683203e-003, -1.118357e-003, 7.670594e-003, 1.411585e-002, 1.895433e-002, -2.304213e-002, 2.609218e-002, 2.919360e-002, 3.087543e-002, -3.124964e-002, 3.164525e-002, 3.109008e-002, 3.038019e-002, -3.022243e-002, 3.008269e-002, 2.843726e-002, 2.660720e-002, -2.479568e-002, 2.230414e-002, 1.947827e-002, 1.670303e-002, -1.323369e-002, 9.039395e-003, 5.287494e-003, 1.356397e-003, --2.354074e-003, -6.225131e-003, -9.184846e-003, -1.151499e-002, --1.356011e-002, -1.546715e-002, -1.719785e-002, -1.903340e-002, --2.042553e-002, -2.092884e-002, -2.167044e-002, -2.157935e-002, --2.112037e-002, -2.044625e-002, -1.954117e-002, -1.821105e-002, --1.605512e-002, -1.398371e-002, -1.200372e-002, -9.451352e-003, --6.988076e-003, -4.615986e-003, -2.342345e-003, 1.613535e-004, -2.904732e-003, 4.932203e-003, 6.368238e-003, 7.562709e-003, -9.143085e-003, 1.008219e-002, 1.080866e-002, 1.151120e-002, -1.251888e-002, 1.319438e-002, 1.336476e-002, 1.308933e-002, -1.267152e-002, 1.174073e-002, 1.118541e-002, 1.032100e-002, -9.654384e-003, 8.508705e-003, 7.499380e-003, 6.289018e-003, -4.965812e-003, 3.897420e-003, 2.870485e-003, 1.839803e-003, -1.197311e-003, 3.646383e-004, -1.088043e-004, -1.641654e-003, --2.939876e-003, -4.559207e-003, -6.083721e-003, -7.883860e-003, --1.042554e-002, -1.267706e-002, -1.501106e-002, -1.577742e-002, --1.642432e-002, -1.637608e-002, -1.584167e-002, -1.460733e-002, --1.253995e-002, -1.029386e-002, -7.819679e-003, -5.157811e-003, --2.415107e-003, 1.666464e-004, 2.251748e-003, 4.320920e-003, -6.234691e-003, 7.793974e-003, 9.442236e-003, 1.080505e-002, -1.236911e-002, 1.337776e-002, 1.377380e-002, 1.412727e-002, -1.386806e-002, 1.331523e-002, 1.270956e-002, 1.149312e-002, -1.025597e-002, 8.388563e-003, 6.375204e-003, 3.815845e-003, -1.012968e-003, -1.399721e-003, -3.834535e-003, -6.341051e-003, --7.555108e-003, -8.631437e-003, -9.460954e-003, -1.000364e-002, --1.074642e-002, -1.168537e-002, -1.193079e-002, -1.182945e-002, --1.195279e-002, -1.132751e-002, -1.085958e-002, -9.892540e-003, --8.961343e-003, -7.922163e-003, -6.631306e-003, -4.882750e-003, --3.767355e-003, -2.535538e-003, -1.054977e-003, -2.860438e-005, -5.330343e-004, 1.292331e-003, 1.433929e-003, 1.731070e-003, -2.259687e-003, 2.852055e-003, 2.403100e-003, 1.752836e-003, -4.696530e-004, -1.098445e-003, -2.427891e-003, -2.971284e-003, --3.481177e-003, -4.161724e-003, -3.073227e-003, -2.428219e-003, --1.484795e-003, -8.435546e-004, -3.580265e-004, 1.018132e-005, -1.240260e-003, 3.431439e-003, 5.212790e-003, 7.105515e-003, -9.362173e-003, 1.160303e-002, 1.368285e-002, 1.625282e-002, -1.966779e-002, 2.221942e-002, 2.447969e-002, 2.651007e-002, -2.855703e-002, 3.070740e-002, 3.202880e-002, 3.379481e-002, -3.485243e-002, 3.496797e-002, 3.460821e-002, 3.433300e-002, -3.361378e-002, 3.179794e-002, 2.918193e-002, 2.663118e-002, -2.328188e-002, 1.964777e-002, 1.595553e-002, 1.204813e-002, -7.612108e-003, 2.669550e-003, -2.293426e-003, -6.832927e-003, --1.186429e-002, -1.712440e-002, -2.201673e-002, -2.599345e-002, --2.957282e-002, -3.361600e-002, -3.744508e-002, -3.962718e-002, --4.222042e-002, -4.502838e-002, -4.789559e-002, -4.996224e-002, --5.196838e-002, -5.421404e-002, -5.678315e-002, -5.764661e-002, --5.799532e-002, -5.869838e-002, -5.931909e-002, -5.932131e-002, --5.875619e-002, -5.790336e-002, -5.684888e-002, -5.537023e-002, --5.258649e-002, -5.004176e-002, -4.763360e-002, -4.517555e-002, --4.185483e-002, -3.785347e-002, -3.337568e-002, -2.899931e-002, --2.452926e-002, -1.914155e-002, -1.346341e-002, -7.615584e-003, --1.541612e-003, 5.020995e-003, 1.176112e-002, 1.802937e-002, -2.344018e-002, 2.861669e-002, 3.380264e-002, 3.889844e-002, -4.339233e-002, 4.731591e-002, 5.146970e-002, 5.537960e-002, -5.839466e-002, 6.065892e-002, 6.231486e-002, 6.378404e-002, -6.523854e-002, 6.607171e-002, 6.599560e-002, 6.527617e-002, -6.461802e-002, 6.320286e-002, 6.117599e-002, 5.881066e-002, -5.590146e-002, 5.301861e-002, 5.013788e-002, 4.702900e-002, -4.370030e-002, 3.995248e-002, 3.586635e-002, 3.153618e-002, -2.725323e-002, 2.291460e-002, 1.838014e-002, 1.460601e-002, -1.102648e-002, 8.120367e-003, 5.065249e-003, 1.737932e-003, --1.011730e-003, -3.147316e-003, -4.481536e-003, -6.074214e-003, --7.514559e-003, -9.156324e-003, -1.037794e-002, -1.107405e-002, --1.172200e-002, -1.252735e-002, -1.315486e-002, -1.272022e-002, --1.234652e-002, -1.223645e-002, -1.227328e-002, -1.235086e-002, --1.277333e-002, -1.371944e-002, -1.478117e-002, -1.577827e-002, --1.730560e-002, -1.900038e-002, -2.038587e-002, -2.174282e-002, --2.266705e-002, -2.333158e-002, -2.362447e-002, -2.329662e-002, --2.261270e-002, -2.224872e-002, -2.129169e-002, -2.035654e-002, --1.924514e-002, -1.809091e-002, -1.676887e-002, -1.579749e-002, --1.455195e-002, -1.367036e-002, -1.293713e-002, -1.217810e-002, --1.140223e-002, -1.048087e-002, -9.495843e-003, -8.751255e-003, --8.063505e-003, -7.037342e-003, -6.107426e-003, -5.328495e-003, --4.449697e-003, -3.911455e-003, -3.501307e-003, -2.831702e-003, --2.412742e-003, -2.426512e-003, -2.280264e-003, -2.254412e-003, --2.310929e-003, -1.822689e-003, -1.055015e-003, -3.095796e-004, -1.004677e-003, 2.104851e-003, 3.309984e-003, 4.953510e-003, -6.465559e-003, 7.755958e-003, 8.856977e-003, 1.001807e-002, -1.103338e-002, 1.215749e-002, 1.314452e-002, 1.380485e-002, -1.416623e-002, 1.468435e-002, 1.466699e-002, 1.437880e-002, -1.387350e-002, 1.273483e-002, 1.128784e-002, 1.034932e-002, -8.903706e-003, 7.120053e-003, 5.308982e-003, 3.312845e-003, -7.750683e-004, -1.671693e-003, -3.925227e-003, -6.748509e-003, --1.007054e-002, -1.328866e-002, -1.663213e-002, -2.007830e-002, --2.329442e-002, -2.628339e-002, -2.895602e-002, -3.120966e-002, --3.309309e-002, -3.483582e-002, -3.656914e-002, -3.812007e-002, --3.989632e-002, -4.157040e-002, -4.268643e-002, -4.308429e-002, --4.314905e-002, -4.322757e-002, -4.281862e-002, -4.203354e-002, --4.116743e-002, -4.007474e-002, -3.883531e-002, -3.774353e-002, --3.678234e-002, -3.533250e-002, -3.360346e-002, -3.176519e-002, --2.928535e-002, -2.624132e-002, -2.304734e-002, -2.001894e-002, --1.712506e-002, -1.383000e-002, -1.047400e-002, -6.833856e-003, --2.938309e-003, 8.382916e-004, 4.974584e-003, 9.608949e-003, -1.516981e-002, 2.059556e-002, 2.563920e-002, 3.080621e-002, -3.584224e-002, 4.029793e-002, 4.446684e-002, 4.853835e-002, -5.248158e-002, 5.603093e-002, 5.952553e-002, 6.279237e-002, -6.560385e-002, 6.823341e-002, 7.069727e-002, 7.317271e-002, -7.509902e-002, 7.611028e-002, 7.630491e-002, 7.590466e-002, -7.521284e-002, 7.388243e-002, 7.185528e-002, 6.955907e-002, -6.667665e-002, 6.357474e-002, 6.020980e-002, 5.652693e-002, -5.274491e-002, 4.893944e-002, 4.515162e-002, 4.131544e-002, -3.726452e-002, 3.299605e-002, 2.885540e-002, 2.475995e-002, -2.068679e-002, 1.675184e-002, 1.320004e-002, 9.920054e-003, -6.617535e-003, 3.704446e-003, 9.802665e-004, -1.769586e-003, --3.985101e-003, -6.020592e-003, -7.922071e-003, -9.781234e-003, --1.151929e-002, -1.278941e-002, -1.399082e-002, -1.505156e-002, --1.600855e-002, -1.702415e-002, -1.779437e-002, -1.822644e-002, --1.871108e-002, -1.949713e-002, -2.045065e-002, -2.174309e-002, --2.328937e-002, -2.504176e-002, -2.692291e-002, -2.895548e-002, --3.104563e-002, -3.331170e-002, -3.556487e-002, -3.782073e-002, --3.994800e-002, -4.193592e-002, -4.369154e-002, -4.495409e-002, --4.600990e-002, -4.683610e-002, -4.704017e-002, -4.684475e-002, --4.634755e-002, -4.547854e-002, -4.442972e-002, -4.334853e-002, --4.186357e-002, -4.037194e-002, -3.857011e-002, -3.668145e-002, --3.504295e-002, -3.350181e-002, -3.171778e-002, -2.997645e-002, --2.839207e-002, -2.668402e-002, -2.491234e-002, -2.330417e-002, --2.193242e-002, -2.077107e-002, -1.994255e-002, -1.935393e-002, --1.910959e-002, -1.895475e-002, -1.879883e-002, -1.872891e-002, --1.875136e-002, -1.871446e-002, -1.857140e-002, -1.825883e-002, --1.767523e-002, -1.681002e-002, -1.596656e-002, -1.487790e-002, --1.344102e-002, -1.162909e-002, -9.852538e-003, -8.113489e-003, --6.445951e-003, -4.641957e-003, -2.885425e-003, -1.083789e-003, -7.257122e-004, 2.550678e-003, 4.469009e-003, 6.470269e-003, -8.153632e-003, 9.612265e-003, 1.071153e-002, 1.152931e-002, -1.198704e-002, 1.222847e-002, 1.250427e-002, 1.286550e-002, -1.323518e-002, 1.352264e-002, 1.366316e-002, 1.368216e-002, -1.356630e-002, 1.348990e-002, 1.352584e-002, 1.366536e-002, -1.389662e-002, 1.424056e-002, 1.468941e-002, 1.525777e-002, -1.599095e-002, 1.712325e-002, 1.853401e-002, 2.015504e-002, -2.234412e-002, 2.463301e-002, 2.708933e-002, 2.953201e-002, -3.158713e-002, 3.339768e-002, 3.509685e-002, 3.687388e-002, -3.882640e-002, 4.059849e-002, 4.202608e-002, 4.339748e-002, -4.472859e-002, 4.602700e-002, 4.676366e-002, 4.696798e-002, -4.685429e-002, 4.655567e-002, 4.590091e-002, 4.486815e-002, -4.356470e-002, 4.214784e-002, 4.049342e-002, 3.877558e-002, -3.702745e-002, 3.532785e-002, 3.350575e-002, 3.150888e-002, -2.967715e-002, 2.799735e-002, 2.661135e-002, 2.535264e-002, -2.432964e-002, 2.326283e-002, 2.237897e-002, 2.161819e-002, -2.090879e-002, 2.010783e-002, 1.930238e-002, 1.835758e-002, -1.725111e-002, 1.584139e-002, 1.379933e-002, 1.157148e-002, -9.268491e-003, 6.705655e-003, 3.715205e-003, 4.107081e-004, --3.087601e-003, -6.798565e-003, -1.095160e-002, -1.540491e-002, --2.002156e-002, -2.449577e-002, -2.919329e-002, -3.395445e-002, --3.868531e-002, -4.353909e-002, -4.844245e-002, -5.329503e-002, --5.809130e-002, -6.290737e-002, -6.772933e-002, -7.189172e-002, --7.551542e-002, -7.850489e-002, -8.063716e-002, -8.220160e-002, --8.336836e-002, -8.406753e-002, -8.441841e-002, -8.444045e-002, --8.391728e-002, -8.295204e-002, -8.152658e-002, -7.973977e-002, --7.751409e-002, -7.500005e-002, -7.228445e-002, -6.906917e-002, --6.537511e-002, -6.141198e-002, -5.724726e-002, -5.284759e-002, --4.848487e-002, -4.401123e-002, -3.970338e-002, -3.573580e-002, --3.260237e-002, -2.964431e-002, -2.677199e-002, -2.404109e-002, --2.135362e-002, -1.888864e-002, -1.624491e-002, -1.332417e-002, --1.031546e-002, -7.261665e-003, -4.268121e-003, -1.508795e-003, -1.134295e-003, 3.619571e-003, 5.828011e-003, 8.110666e-003, -1.040704e-002, 1.319549e-002, 1.620710e-002, 1.916553e-002, -2.209625e-002, 2.500756e-002, 2.775402e-002, 3.036326e-002, -3.288114e-002, 3.527847e-002, 3.786179e-002, 4.037397e-002, -4.285528e-002, 4.534341e-002, 4.758996e-002, 4.938518e-002, -5.116656e-002, 5.275974e-002, 5.416225e-002, 5.535033e-002, -5.590618e-002, 5.616684e-002, 5.619416e-002, 5.602111e-002, -5.555202e-002, 5.494568e-002, 5.425079e-002, 5.352648e-002, -5.294317e-002, 5.251581e-002, 5.223673e-002, 5.199132e-002, -5.166691e-002, 5.134220e-002, 5.096302e-002, 5.033949e-002, -4.969348e-002, 4.905626e-002, 4.825902e-002, 4.749799e-002, -4.673475e-002, 4.582434e-002, 4.472824e-002, 4.351704e-002, -4.223126e-002, 4.085643e-002, 3.937696e-002, 3.769328e-002, -3.580202e-002, 3.366185e-002, 3.121069e-002, 2.864057e-002, -2.601440e-002, 2.338889e-002, 2.062021e-002, 1.769810e-002, -1.490952e-002, 1.217546e-002, 9.358863e-003, 6.301049e-003, -3.555297e-003, 1.238527e-003, -6.116535e-004, -2.176547e-003, --3.355215e-003, -4.286351e-003, -4.988579e-003, -5.747776e-003, --6.681510e-003, -7.600147e-003, -8.416422e-003, -9.151412e-003, --9.772220e-003, -1.020583e-002, -1.056118e-002, -1.089222e-002, --1.116069e-002, -1.135084e-002, -1.147240e-002, -1.158118e-002, --1.179887e-002, -1.230994e-002, -1.298814e-002, -1.377298e-002, --1.466114e-002, -1.544403e-002, -1.615037e-002, -1.670681e-002, --1.709712e-002, -1.754141e-002, -1.800747e-002, -1.828585e-002, --1.838731e-002, -1.832312e-002, -1.813977e-002, -1.784323e-002, --1.741100e-002, -1.691073e-002, -1.632508e-002, -1.554783e-002, --1.460601e-002, -1.365363e-002, -1.288288e-002, -1.231089e-002, --1.190100e-002, -1.169113e-002, -1.170780e-002, -1.183701e-002, --1.194267e-002, -1.225889e-002, -1.279041e-002, -1.359520e-002, --1.433554e-002, -1.504622e-002, -1.605093e-002, -1.738929e-002, --1.904751e-002, -2.101755e-002, -2.325450e-002, -2.573629e-002, --2.829256e-002, -3.092450e-002, -3.360584e-002, -3.626469e-002, --3.879736e-002, -4.076347e-002, -4.205671e-002, -4.287240e-002, --4.339240e-002, -4.352638e-002, -4.320936e-002, -4.234417e-002, --4.109221e-002, -3.982037e-002, -3.851914e-002, -3.699785e-002, --3.534256e-002, -3.359361e-002, -3.176071e-002, -2.988578e-002, --2.780529e-002, -2.552479e-002, -2.306791e-002, -2.042006e-002, --1.752280e-002, -1.463915e-002, -1.188046e-002, -9.355941e-003, --7.073806e-003, -5.058920e-003, -3.291251e-003, -1.717509e-003, --2.524424e-004, 1.023075e-003, 2.260394e-003, 3.567657e-003, -5.079620e-003, 6.856197e-003, 8.699813e-003, 1.063617e-002, -1.267506e-002, 1.475928e-002, 1.687259e-002, 1.910209e-002, -2.126923e-002, 2.329358e-002, 2.521197e-002, 2.701610e-002, -2.863762e-002, 3.010457e-002, 3.123741e-002, 3.206594e-002, -3.262429e-002, 3.282690e-002, 3.267768e-002, 3.219683e-002, -3.132763e-002, 3.004464e-002, 2.832705e-002, 2.626040e-002, -2.379265e-002, 2.081880e-002, 1.762662e-002, 1.424133e-002, -1.065133e-002, 6.965864e-003, 3.232117e-003, -5.094708e-004, --4.106098e-003, -7.623158e-003, -1.100570e-002, -1.415960e-002, --1.697216e-002, -1.938143e-002, -2.133751e-002, -2.265492e-002, --2.348320e-002, -2.383396e-002, -2.366856e-002, -2.325705e-002, --2.271105e-002, -2.193964e-002, -2.098082e-002, -1.980062e-002, --1.843948e-002, -1.692524e-002, -1.523926e-002, -1.334551e-002, --1.118717e-002, -8.944406e-003, -6.764265e-003, -4.646752e-003, --2.614082e-003, -6.296331e-004, 1.150117e-003, 2.828635e-003, -4.412365e-003, 5.928408e-003, 7.507944e-003, 9.290022e-003, -1.114943e-002, 1.287923e-002, 1.454201e-002, 1.611053e-002, -1.758332e-002, 1.898921e-002, 2.024133e-002, 2.141627e-002, -2.256373e-002, 2.373198e-002, 2.496664e-002, 2.614804e-002, -2.716888e-002, 2.804019e-002, 2.875792e-002, 2.945554e-002, -2.996165e-002, 3.036138e-002, 3.063906e-002, 3.067688e-002, -3.028947e-002, 2.953769e-002, 2.852818e-002, 2.737068e-002, -2.620999e-002, 2.488068e-002, 2.340507e-002, 2.180249e-002, -2.007300e-002, 1.846771e-002, 1.687297e-002, 1.529707e-002, -1.374633e-002, 1.223441e-002, 1.080189e-002, 9.363292e-003, -8.066254e-003, 7.057066e-003, 6.279500e-003, 5.748732e-003, -5.397326e-003, 5.210849e-003, 5.058444e-003, 5.004529e-003, -4.988791e-003, 4.955283e-003, 4.933273e-003, 4.963275e-003, -4.974368e-003, 5.099264e-003, 5.254558e-003, 5.312861e-003, -5.220178e-003, 5.047695e-003, 4.838553e-003, 4.549059e-003, -4.117503e-003, 3.671899e-003, 3.046092e-003, 2.163210e-003, -1.101814e-003, -7.744071e-005, -1.194064e-003, -2.514380e-003, --4.079641e-003, -5.848599e-003, -7.838580e-003, -1.010623e-002, --1.239045e-002, -1.463796e-002, -1.681831e-002, -1.882169e-002, --2.062006e-002, -2.234526e-002, -2.399241e-002, -2.525671e-002, --2.616381e-002, -2.677689e-002, -2.711283e-002, -2.724403e-002, --2.725471e-002, -2.720445e-002, -2.713185e-002, -2.731374e-002, --2.753442e-002, -2.771595e-002, -2.758083e-002, -2.738584e-002, --2.701981e-002, -2.619481e-002, -2.531688e-002, -2.447496e-002, --2.366043e-002, -2.289788e-002, -2.225163e-002, -2.187533e-002, --2.181494e-002, -2.199627e-002, -2.237662e-002, -2.311109e-002, --2.399615e-002, -2.451233e-002, -2.442773e-002, -2.398563e-002, --2.323590e-002, -2.194075e-002, -2.036509e-002, -1.876638e-002, --1.714303e-002, -1.538013e-002, -1.342394e-002, -1.128224e-002, --8.859936e-003, -6.198258e-003, -3.229861e-003, -1.215553e-016 -}, -{ -0.000000e+000, -4.426256e-001, -9.788617e-002, 1.103052e-001, -2.074472e-001, 2.236316e-001, 1.905781e-001, 1.553585e-001, -1.162719e-001, 7.326630e-002, 2.565355e-002, -2.661771e-002, --7.898507e-002, -1.232298e-001, -1.535677e-001, -1.707928e-001, --1.718427e-001, -1.543418e-001, -1.242447e-001, -8.760311e-002, --4.904209e-002, -1.852978e-002, 5.422101e-003, 2.645649e-002, -4.626530e-002, 6.276776e-002, 7.470737e-002, 7.947473e-002, -7.874632e-002, 7.476442e-002, 6.710252e-002, 5.668877e-002, -4.690894e-002, 3.583451e-002, 2.376012e-002, 1.339382e-002, -3.719670e-003, -2.079770e-003, -3.446964e-003, -2.730284e-003, -1.204935e-003, 4.354620e-003, 7.186059e-003, 8.658222e-003, -9.326182e-003, 7.479452e-003, 9.300147e-004, -7.350316e-003, --1.560447e-002, -2.437501e-002, -3.139798e-002, -3.580118e-002, --3.681373e-002, -3.280146e-002, -2.755347e-002, -2.027133e-002, --1.204846e-002, -4.985635e-003, 1.326156e-003, 7.075843e-003, -1.094889e-002, 9.658565e-003, 8.698411e-003, 8.313470e-003, -7.450266e-003, 6.850333e-003, 6.251132e-003, 4.996391e-003, -2.673786e-003, 6.534366e-004, -1.342255e-003, -3.754922e-003, --7.308775e-003, -9.656508e-003, -1.154943e-002, -1.340400e-002, --1.549289e-002, -1.689364e-002, -1.784053e-002, -1.864461e-002, --1.790010e-002, -1.531855e-002, -1.206865e-002, -9.881805e-003, --5.823708e-003, -1.604496e-003, 1.683076e-003, 5.385228e-003, -8.287201e-003, 1.130908e-002, 1.484023e-002, 1.658365e-002, -1.917724e-002, 2.101180e-002, 2.081750e-002, 2.011015e-002, -1.746059e-002, 1.414899e-002, 1.046083e-002, 5.986478e-003, -1.928149e-003, -8.264124e-004, -3.367271e-003, -5.848097e-003, --7.483155e-003, -8.553182e-003, -8.197007e-003, -8.442523e-003, --7.854757e-003, -6.368900e-003, -3.748691e-003, -1.691001e-003, -7.225895e-004, 2.886092e-003, 5.741766e-003, 8.288244e-003, -1.017717e-002, 1.150823e-002, 1.195427e-002, 1.274806e-002, -1.227997e-002, 1.115023e-002, 9.023154e-003, 6.439063e-003, -4.560128e-003, 2.083562e-003, -1.255635e-003, -5.060332e-003, --8.679987e-003, -1.290611e-002, -1.681560e-002, -2.084523e-002, --2.492939e-002, -2.789864e-002, -2.882802e-002, -2.912818e-002, --2.756511e-002, -2.559286e-002, -2.262439e-002, -1.854269e-002, --1.365014e-002, -9.728028e-003, -5.351973e-003, 1.151219e-005, -6.367849e-003, 1.130888e-002, 1.637912e-002, 2.062565e-002, -2.460765e-002, 2.775190e-002, 3.047390e-002, 3.243981e-002, -3.265326e-002, 3.152360e-002, 2.963271e-002, 2.767579e-002, -2.551175e-002, 2.312655e-002, 2.055888e-002, 1.765303e-002, -1.522919e-002, 1.197632e-002, 9.233995e-003, 6.499312e-003, -4.264982e-003, 2.984319e-003, 1.050249e-003, -1.987282e-004, --1.507741e-003, -2.796039e-003, -4.056193e-003, -4.976315e-003, --5.481622e-003, -6.175870e-003, -6.526531e-003, -6.919749e-003, --7.450196e-003, -7.121866e-003, -6.505571e-003, -6.143038e-003, --5.438646e-003, -5.219527e-003, -4.999457e-003, -4.246250e-003, --3.941065e-003, -3.994740e-003, -4.182282e-003, -3.993894e-003, --3.789955e-003, -3.646234e-003, -4.460992e-003, -5.310140e-003, --6.485153e-003, -7.686435e-003, -8.934210e-003, -1.057511e-002, --1.211719e-002, -1.315324e-002, -1.447711e-002, -1.581320e-002, --1.730214e-002, -1.873890e-002, -1.965352e-002, -1.958190e-002, --1.957804e-002, -1.890122e-002, -1.840664e-002, -1.790402e-002, --1.621263e-002, -1.418810e-002, -1.228494e-002, -1.019742e-002, --7.162075e-003, -4.457602e-003, -1.529338e-003, 1.791121e-003, -4.794344e-003, 7.650136e-003, 1.038940e-002, 1.372243e-002, -1.673587e-002, 1.964875e-002, 2.229075e-002, 2.369957e-002, -2.534590e-002, 2.645219e-002, 2.719062e-002, 2.762791e-002, -2.759310e-002, 2.718226e-002, 2.641654e-002, 2.535439e-002, -2.468030e-002, 2.270722e-002, 2.095719e-002, 1.912157e-002, -1.641603e-002, 1.370782e-002, 1.107192e-002, 9.226676e-003, -7.001006e-003, 4.527941e-003, 2.405401e-003, 1.095899e-003, --7.735699e-004, -2.655811e-003, -4.735180e-003, -6.672915e-003, --8.651625e-003, -1.120396e-002, -1.365487e-002, -1.591794e-002, --1.860750e-002, -2.098193e-002, -2.306572e-002, -2.525036e-002, --2.654935e-002, -2.787005e-002, -2.960896e-002, -3.188759e-002, --3.304582e-002, -3.362509e-002, -3.421006e-002, -3.493158e-002, --3.527951e-002, -3.541151e-002, -3.616275e-002, -3.667030e-002, --3.645722e-002, -3.534547e-002, -3.384610e-002, -3.192936e-002, --3.007744e-002, -2.816464e-002, -2.599383e-002, -2.364519e-002, --2.056853e-002, -1.721039e-002, -1.379104e-002, -1.101966e-002, --8.279911e-003, -5.622427e-003, -3.033445e-003, -3.456622e-005, -3.156631e-003, 6.854350e-003, 1.036193e-002, 1.319639e-002, -1.547752e-002, 1.801207e-002, 2.066166e-002, 2.227310e-002, -2.414273e-002, 2.664079e-002, 2.869163e-002, 3.030688e-002, -3.128613e-002, 3.210737e-002, 3.323065e-002, 3.430051e-002, -3.496578e-002, 3.573864e-002, 3.690582e-002, 3.775529e-002, -3.815364e-002, 3.812283e-002, 3.809212e-002, 3.835298e-002, -3.836190e-002, 3.849059e-002, 3.875257e-002, 3.914174e-002, -3.934953e-002, 3.915474e-002, 3.886012e-002, 3.830582e-002, -3.761627e-002, 3.683023e-002, 3.518517e-002, 3.367588e-002, -3.253379e-002, 3.106042e-002, 2.882998e-002, 2.622605e-002, -2.310408e-002, 2.041273e-002, 1.741670e-002, 1.413832e-002, -1.057358e-002, 6.703073e-003, 3.278225e-003, 1.649775e-004, --3.372965e-003, -7.129515e-003, -1.079104e-002, -1.454826e-002, --1.792318e-002, -2.190900e-002, -2.550286e-002, -2.851237e-002, --3.107104e-002, -3.305261e-002, -3.461994e-002, -3.612647e-002, --3.740351e-002, -3.872915e-002, -3.959441e-002, -4.057913e-002, --4.139544e-002, -4.191170e-002, -4.162979e-002, -4.134529e-002, --4.105334e-002, -4.031408e-002, -3.923497e-002, -3.834592e-002, --3.734537e-002, -3.683082e-002, -3.657909e-002, -3.589161e-002, --3.498336e-002, -3.395033e-002, -3.273263e-002, -3.183399e-002, --3.094509e-002, -2.983059e-002, -2.840917e-002, -2.697139e-002, --2.530493e-002, -2.424594e-002, -2.312261e-002, -2.148955e-002, --1.930896e-002, -1.706080e-002, -1.525190e-002, -1.337980e-002, --1.124707e-002, -8.980724e-003, -6.313428e-003, -3.738550e-003, --1.155300e-003, 1.462800e-003, 4.117725e-003, 6.576173e-003, -9.156960e-003, 1.127338e-002, 1.330129e-002, 1.552762e-002, -1.760814e-002, 1.996352e-002, 2.202453e-002, 2.414522e-002, -2.611037e-002, 2.795979e-002, 2.932229e-002, 3.054205e-002, -3.140910e-002, 3.166134e-002, 3.185657e-002, 3.184578e-002, -3.196037e-002, 3.198783e-002, 3.158186e-002, 3.074272e-002, -2.957877e-002, 2.857800e-002, 2.762290e-002, 2.656386e-002, -2.538639e-002, 2.389762e-002, 2.246380e-002, 2.096857e-002, -1.937127e-002, 1.802197e-002, 1.668617e-002, 1.476751e-002, -1.326889e-002, 1.232738e-002, 1.173407e-002, 1.098063e-002, -1.018137e-002, 8.966329e-003, 7.971853e-003, 7.195106e-003, -6.592168e-003, 5.924248e-003, 5.587663e-003, 4.914722e-003, -3.926392e-003, 2.833351e-003, 1.739622e-003, 7.716546e-004, --1.380842e-004, -7.569115e-004, -1.178506e-003, -1.528822e-003, --1.645077e-003, -1.569529e-003, -1.275292e-003, -7.566247e-004, --2.034698e-004, 5.606138e-004, 1.390843e-003, 2.289548e-003, -3.109334e-003, 3.698633e-003, 4.000512e-003, 4.394167e-003, -5.186160e-003, 6.097267e-003, 6.857379e-003, 7.680091e-003, -8.616599e-003, 8.980693e-003, 8.861837e-003, 8.835621e-003, -9.021308e-003, 8.928020e-003, 8.789753e-003, 8.395309e-003, -7.397421e-003, 6.203323e-003, 4.886902e-003, 3.676173e-003, -2.702861e-003, 1.350104e-003, -2.499577e-005, -1.655689e-003, --3.220519e-003, -4.921209e-003, -6.453772e-003, -7.723878e-003, --9.124404e-003, -1.084593e-002, -1.325368e-002, -1.553902e-002, --1.763123e-002, -1.977015e-002, -2.151874e-002, -2.297458e-002, --2.406587e-002, -2.473961e-002, -2.485753e-002, -2.495961e-002, --2.472564e-002, -2.456694e-002, -2.428995e-002, -2.371211e-002, --2.352511e-002, -2.351498e-002, -2.332999e-002, -2.282696e-002, --2.226596e-002, -2.177532e-002, -2.128669e-002, -2.076872e-002, --2.050420e-002, -2.019321e-002, -1.987545e-002, -1.972984e-002, --1.932872e-002, -1.885399e-002, -1.850600e-002, -1.837970e-002, --1.833540e-002, -1.839107e-002, -1.843416e-002, -1.862415e-002, --1.869155e-002, -1.873371e-002, -1.912060e-002, -1.936999e-002, --1.925480e-002, -1.895933e-002, -1.855780e-002, -1.807826e-002, --1.735073e-002, -1.647183e-002, -1.582349e-002, -1.481334e-002, --1.282715e-002, -1.087725e-002, -8.901899e-003, -6.841317e-003, --4.661811e-003, -2.376884e-003, -3.888844e-004, 1.512233e-003, -3.396649e-003, 5.589986e-003, 7.739855e-003, 9.897991e-003, -1.213807e-002, 1.422586e-002, 1.585109e-002, 1.718941e-002, -1.834364e-002, 1.934096e-002, 2.030101e-002, 2.100210e-002, -2.160543e-002, 2.235764e-002, 2.316455e-002, 2.402791e-002, -2.489666e-002, 2.581245e-002, 2.680838e-002, 2.806215e-002, -2.921748e-002, 3.009971e-002, 3.071540e-002, 3.143973e-002, -3.207420e-002, 3.255228e-002, 3.285824e-002, 3.299576e-002, -3.321511e-002, 3.318542e-002, 3.234970e-002, 3.111225e-002, -2.985794e-002, 2.849297e-002, 2.731962e-002, 2.595160e-002, -2.429608e-002, 2.257101e-002, 2.040358e-002, 1.816682e-002, -1.598018e-002, 1.377310e-002, 1.156882e-002, 9.352989e-003, -7.217448e-003, 5.121836e-003, 3.106883e-003, 1.026752e-003, --1.154152e-003, -3.090965e-003, -4.711256e-003, -6.128800e-003, --7.401729e-003, -8.748151e-003, -1.005783e-002, -1.134497e-002, --1.191050e-002, -1.211458e-002, -1.228022e-002, -1.230333e-002, --1.198417e-002, -1.150072e-002, -1.105589e-002, -1.059785e-002, --1.065774e-002, -1.056733e-002, -1.061557e-002, -1.064500e-002, --1.053697e-002, -1.033711e-002, -9.531796e-003, -8.518715e-003, --7.761465e-003, -7.395734e-003, -7.076393e-003, -6.666961e-003, --6.179050e-003, -5.830931e-003, -5.689979e-003, -5.596322e-003, --5.533289e-003, -5.495375e-003, -5.351921e-003, -4.527135e-003, --3.682631e-003, -2.747405e-003, -1.492281e-003, -2.266926e-004, -1.077606e-003, 2.516098e-003, 3.965081e-003, 5.034290e-003, -5.899161e-003, 6.721525e-003, 7.365934e-003, 7.930988e-003, -8.618543e-003, 9.532525e-003, 1.080112e-002, 1.184946e-002, -1.232808e-002, 1.268872e-002, 1.273024e-002, 1.253847e-002, -1.210405e-002, 1.142066e-002, 1.060681e-002, 9.827226e-003, -8.979523e-003, 7.749134e-003, 6.172073e-003, 4.464178e-003, -2.671899e-003, 6.769367e-004, -1.444927e-003, -3.662987e-003, --5.961754e-003, -7.891737e-003, -9.788798e-003, -1.196441e-002, --1.426424e-002, -1.650583e-002, -1.813978e-002, -1.952051e-002, --2.079222e-002, -2.199140e-002, -2.318612e-002, -2.428860e-002, --2.536385e-002, -2.632518e-002, -2.708004e-002, -2.772888e-002, --2.793252e-002, -2.763354e-002, -2.729784e-002, -2.680502e-002, --2.625252e-002, -2.562801e-002, -2.520969e-002, -2.459134e-002, --2.402998e-002, -2.367944e-002, -2.349122e-002, -2.347004e-002, --2.268655e-002, -2.177420e-002, -2.102067e-002, -2.046612e-002, --2.017435e-002, -2.010539e-002, -1.984907e-002, -1.941804e-002, --1.894860e-002, -1.847332e-002, -1.803340e-002, -1.760023e-002, --1.714239e-002, -1.670953e-002, -1.597351e-002, -1.467756e-002, --1.334437e-002, -1.143254e-002, -9.190748e-003, -6.932587e-003, --4.763536e-003, -2.551227e-003, -2.080688e-004, 2.219243e-003, -4.409672e-003, 6.443073e-003, 8.377831e-003, 1.021087e-002, -1.201156e-002, 1.374064e-002, 1.545830e-002, 1.702197e-002, -1.888390e-002, 2.071518e-002, 2.245935e-002, 2.344217e-002, -2.440418e-002, 2.522417e-002, 2.594909e-002, 2.645563e-002, -2.669631e-002, 2.675554e-002, 2.674782e-002, 2.667195e-002, -2.652849e-002, 2.627962e-002, 2.596760e-002, 2.567768e-002, -2.546515e-002, 2.536571e-002, 2.524167e-002, 2.506378e-002, -2.497145e-002, 2.491217e-002, 2.476054e-002, 2.489279e-002, -2.497366e-002, 2.503084e-002, 2.515165e-002, 2.516790e-002, -2.510283e-002, 2.498922e-002, 2.482976e-002, 2.454682e-002, -2.416579e-002, 2.381887e-002, 2.336490e-002, 2.242567e-002, -2.132110e-002, 2.024481e-002, 1.909589e-002, 1.787123e-002, -1.656772e-002, 1.532286e-002, 1.415350e-002, 1.311266e-002, -1.220944e-002, 1.149946e-002, 1.055560e-002, 9.755882e-003, -8.992160e-003, 8.292029e-003, 7.805815e-003, 7.355886e-003, -6.959233e-003, 6.586886e-003, 6.569916e-003, 6.496576e-003, -6.406097e-003, 6.001400e-003, 5.458051e-003, 4.926243e-003, -4.315321e-003, 3.649063e-003, 2.913955e-003, 2.172937e-003, -1.323520e-003, 2.494032e-004, -1.016842e-003, -2.265649e-003, --3.811695e-003, -5.729069e-003, -7.528427e-003, -9.359915e-003, --1.115366e-002, -1.289618e-002, -1.436931e-002, -1.565365e-002, --1.676667e-002, -1.776497e-002, -1.858983e-002, -1.937746e-002, --2.036896e-002, -2.135186e-002, -2.212974e-002, -2.264182e-002, --2.316925e-002, -2.365002e-002, -2.414610e-002, -2.478922e-002, --2.543580e-002, -2.605312e-002, -2.671318e-002, -2.741154e-002, --2.847393e-002, -2.961953e-002, -3.076283e-002, -3.149091e-002, --3.195223e-002, -3.249157e-002, -3.313850e-002, -3.396393e-002, --3.494448e-002, -3.608686e-002, -3.721108e-002, -3.841949e-002, --3.989676e-002, -4.134524e-002, -4.278263e-002, -4.418183e-002, --4.539317e-002, -4.600373e-002, -4.607777e-002, -4.588179e-002, --4.533499e-002, -4.441912e-002, -4.314347e-002, -4.137390e-002, --3.963328e-002, -3.768302e-002, -3.546730e-002, -3.298884e-002, --3.033928e-002, -2.740187e-002, -2.440793e-002, -2.134315e-002, --1.816116e-002, -1.496300e-002, -1.160043e-002, -8.079983e-003, --4.961348e-003, -1.850219e-003, 1.395687e-003, 4.545362e-003, -7.506101e-003, 1.021898e-002, 1.274419e-002, 1.520360e-002, -1.771136e-002, 2.009179e-002, 2.236379e-002, 2.460752e-002, -2.660510e-002, 2.826751e-002, 2.992299e-002, 3.158282e-002, -3.312083e-002, 3.459771e-002, 3.604084e-002, 3.741493e-002, -3.872687e-002, 3.999709e-002, 4.147162e-002, 4.292926e-002, -4.437781e-002, 4.533007e-002, 4.592974e-002, 4.629982e-002, -4.658148e-002, 4.703863e-002, 4.741979e-002, 4.756681e-002, -4.750426e-002, 4.725191e-002, 4.678453e-002, 4.614883e-002, -4.559104e-002, 4.489925e-002, 4.352546e-002, 4.195567e-002, -4.020170e-002, 3.827532e-002, 3.620106e-002, 3.414337e-002, -3.196287e-002, 2.964226e-002, 2.729380e-002, 2.493518e-002, -2.286074e-002, 2.093077e-002, 1.913063e-002, 1.697084e-002, -1.489076e-002, 1.292853e-002, 1.105977e-002, 9.244886e-003, -7.475782e-003, 5.788072e-003, 4.388515e-003, 3.096280e-003, -1.877727e-003, 7.323806e-004, -3.526722e-004, -1.412134e-003, --3.003547e-003, -4.489163e-003, -5.870796e-003, -7.201572e-003, --8.424293e-003, -9.596610e-003, -1.082831e-002, -1.199337e-002, --1.312588e-002, -1.417553e-002, -1.518676e-002, -1.613656e-002, --1.694176e-002, -1.763842e-002, -1.849378e-002, -1.924874e-002, --1.972425e-002, -2.008452e-002, -2.044026e-002, -2.070091e-002, --2.083397e-002, -2.080791e-002, -2.064436e-002, -2.015454e-002, --1.948716e-002, -1.874811e-002, -1.789859e-002, -1.745872e-002, --1.705482e-002, -1.657329e-002, -1.605583e-002, -1.526581e-002, --1.441425e-002, -1.330928e-002, -1.221088e-002, -1.114925e-002, --1.007136e-002, -9.052310e-003, -8.101342e-003, -6.970330e-003, --5.879696e-003, -5.170864e-003, -4.381743e-003, -3.250538e-003, --2.109429e-003, -1.069344e-003, 2.367350e-005, 1.139974e-003, -2.068386e-003, 2.867085e-003, 3.617170e-003, 4.322660e-003, -5.017536e-003, 5.853732e-003, 6.643603e-003, 6.956689e-003, -7.182838e-003, 7.277807e-003, 7.253118e-003, 7.160764e-003, -6.984247e-003, 6.586128e-003, 6.184843e-003, 6.037641e-003, -6.031915e-003, 5.939830e-003, 5.841023e-003, 5.748898e-003, -5.558739e-003, 4.700314e-003, 3.831358e-003, 2.986117e-003, -2.095344e-003, 1.179259e-003, 5.411149e-004, -2.201710e-004, --1.136316e-003, -2.083543e-003, -3.000939e-003, -3.863737e-003, --4.718609e-003, -5.564055e-003, -6.398729e-003, -7.477716e-003, --8.498700e-003, -9.038157e-003, -9.474478e-003, -9.704287e-003, --9.998273e-003, -1.022132e-002, -1.041375e-002, -1.061439e-002, --1.084289e-002, -1.115954e-002, -1.152177e-002, -1.116880e-002, --1.066547e-002, -1.016949e-002, -1.040908e-002, -1.081580e-002, --1.135060e-002, -1.212178e-002, -1.296112e-002, -1.353674e-002, --1.423231e-002, -1.510847e-002, -1.505159e-002, -1.500618e-002, --1.508254e-002, -1.530489e-002, -1.563732e-002, -1.563577e-002, --1.564875e-002, -1.650280e-002, -1.750175e-002, -1.851541e-002, --1.941360e-002, -1.841245e-002, -1.718508e-002, -1.572792e-002, --1.396834e-002, -1.218440e-002, -1.033563e-002, -8.433616e-003, --6.438788e-003, -4.373265e-003, -2.216235e-003, -2.579775e-017 -}, -{ -0.000000e+000, -6.037378e-002, -6.469045e-003, 2.566552e-002, -4.210873e-002, 4.124150e-002, 3.096052e-002, 1.524907e-002, -2.785362e-003, -7.728595e-003, -1.698578e-002, -2.445510e-002, --2.919319e-002, -3.148743e-002, -3.142251e-002, -2.962631e-002, --2.550305e-002, -1.973155e-002, -1.427010e-002, -7.931907e-003, --6.112233e-004, 7.491481e-003, 1.559396e-002, 2.351770e-002, -2.877784e-002, 3.255009e-002, 3.492912e-002, 3.425700e-002, -3.255751e-002, 2.866576e-002, 2.492890e-002, 2.033932e-002, -1.534457e-002, 7.894215e-003, 5.786402e-004, -5.714463e-003, --9.626747e-003, -1.367878e-002, -1.634066e-002, -1.844778e-002, --2.053967e-002, -2.310193e-002, -2.553322e-002, -2.710448e-002, --2.760002e-002, -2.805446e-002, -2.734367e-002, -2.496195e-002, --2.176249e-002, -1.742829e-002, -1.233449e-002, -8.869814e-003, --4.536597e-003, 2.414270e-004, 3.938743e-003, 7.684524e-003, -1.100897e-002, 1.401072e-002, 1.595778e-002, 1.702782e-002, -1.884968e-002, 1.967238e-002, 1.974188e-002, 2.062971e-002, -2.013866e-002, 1.842569e-002, 1.771824e-002, 1.658602e-002, -1.430307e-002, 1.201499e-002, 9.553737e-003, 6.269042e-003, -3.360829e-003, 1.254667e-003, -5.182312e-004, -2.372817e-003, --3.097430e-003, -3.573376e-003, -4.153988e-003, -4.488018e-003, --4.492118e-003, -5.161001e-003, -6.639471e-003, -7.166803e-003, --7.256751e-003, -7.773312e-003, -7.994558e-003, -7.889688e-003, --8.911424e-003, -9.977403e-003, -1.122079e-002, -1.155498e-002, --1.100076e-002, -1.071660e-002, -1.011592e-002, -9.500469e-003, --7.222929e-003, -4.232981e-003, -2.761877e-003, -1.355558e-003, -5.808436e-004, 3.069494e-003, 4.022636e-003, 3.910751e-003, -4.202777e-003, 5.278721e-003, 4.696964e-003, 3.762970e-003, -3.120345e-003, 3.765287e-003, 3.939764e-003, 4.252810e-003, -5.437575e-003, 5.450772e-003, 6.156525e-003, 6.802959e-003, -6.699919e-003, 6.798808e-003, 7.977533e-003, 8.510721e-003, -9.513612e-003, 1.051562e-002, 1.177616e-002, 1.312116e-002, -1.386606e-002, 1.373182e-002, 1.313098e-002, 1.166804e-002, -9.809053e-003, 7.025868e-003, 3.968250e-003, 1.770472e-003, --5.016523e-004, -1.935521e-003, -3.686553e-003, -4.829481e-003, --4.830921e-003, -6.160095e-003, -6.258897e-003, -6.919228e-003, --7.621123e-003, -8.593602e-003, -1.016672e-002, -1.202727e-002, --1.408548e-002, -1.511033e-002, -1.554094e-002, -1.610314e-002, --1.532400e-002, -1.542983e-002, -1.570783e-002, -1.507096e-002, --1.528697e-002, -1.455619e-002, -1.406848e-002, -1.362672e-002, --1.223586e-002, -1.119136e-002, -1.028329e-002, -1.046815e-002, --9.989788e-003, -9.566241e-003, -8.986772e-003, -8.049935e-003, --5.965631e-003, -4.368594e-003, -3.173520e-003, -1.001296e-003, --3.109018e-004, 2.629551e-004, 1.745318e-003, 3.204123e-003, -4.035660e-003, 6.218978e-003, 7.753124e-003, 9.885569e-003, -1.116178e-002, 1.245254e-002, 1.320182e-002, 1.478459e-002, -1.509932e-002, 1.588767e-002, 1.658916e-002, 1.657746e-002, -1.548080e-002, 1.605646e-002, 1.608772e-002, 1.586954e-002, -1.626351e-002, 1.626150e-002, 1.550502e-002, 1.512801e-002, -1.410334e-002, 1.271030e-002, 1.192894e-002, 1.140073e-002, -1.063790e-002, 9.100201e-003, 9.333021e-003, 9.095271e-003, -8.481116e-003, 7.338499e-003, 5.751654e-003, 3.801295e-003, -1.168824e-003, -6.809504e-004, -3.116799e-003, -5.788835e-003, --7.881047e-003, -9.965807e-003, -1.219276e-002, -1.443369e-002, --1.563531e-002, -1.753826e-002, -1.941432e-002, -2.113058e-002, --2.274960e-002, -2.438502e-002, -2.659924e-002, -2.772699e-002, --2.903151e-002, -3.029340e-002, -3.139278e-002, -3.169041e-002, --3.153138e-002, -3.158971e-002, -3.159664e-002, -3.035610e-002, --2.937392e-002, -2.840589e-002, -2.711180e-002, -2.504244e-002, --2.289625e-002, -2.159539e-002, -1.963929e-002, -1.684629e-002, --1.452578e-002, -1.154706e-002, -8.112968e-003, -3.753865e-003, -1.071476e-003, 5.515752e-003, 1.012575e-002, 1.547505e-002, -2.051150e-002, 2.510707e-002, 2.964321e-002, 3.356479e-002, -3.731168e-002, 4.017798e-002, 4.232159e-002, 4.555931e-002, -4.874874e-002, 5.124080e-002, 5.340434e-002, 5.536813e-002, -5.637646e-002, 5.676013e-002, 5.718229e-002, 5.732404e-002, -5.762667e-002, 5.706885e-002, 5.536933e-002, 5.288875e-002, -5.123134e-002, 4.912973e-002, 4.607073e-002, 4.270710e-002, -3.889235e-002, 3.498278e-002, 3.045412e-002, 2.560045e-002, -2.085320e-002, 1.701732e-002, 1.295545e-002, 7.964438e-003, -3.261862e-003, -1.359544e-003, -5.152683e-003, -8.195529e-003, --1.217268e-002, -1.566566e-002, -1.773716e-002, -1.973003e-002, --2.213997e-002, -2.455584e-002, -2.695953e-002, -2.935791e-002, --3.138782e-002, -3.306528e-002, -3.517096e-002, -3.749783e-002, --3.829531e-002, -3.880917e-002, -3.934775e-002, -3.965057e-002, --4.061743e-002, -4.112536e-002, -4.111388e-002, -4.129607e-002, --4.209697e-002, -4.240947e-002, -4.211714e-002, -4.203469e-002, --4.247345e-002, -4.244573e-002, -4.312946e-002, -4.308403e-002, --4.291717e-002, -4.212285e-002, -4.151760e-002, -4.083205e-002, --3.895152e-002, -3.679633e-002, -3.512734e-002, -3.359432e-002, --3.169090e-002, -3.006724e-002, -2.781366e-002, -2.505097e-002, --2.203950e-002, -1.970662e-002, -1.742626e-002, -1.380310e-002, --1.021928e-002, -7.155949e-003, -4.269678e-003, -7.184659e-004, -2.214367e-003, 4.618178e-003, 7.212744e-003, 1.011789e-002, -1.197997e-002, 1.395555e-002, 1.624820e-002, 1.828034e-002, -1.945293e-002, 2.061147e-002, 2.232654e-002, 2.488742e-002, -2.696525e-002, 2.888020e-002, 3.086175e-002, 3.208284e-002, -3.300377e-002, 3.399652e-002, 3.501373e-002, 3.538998e-002, -3.593130e-002, 3.624569e-002, 3.671883e-002, 3.694650e-002, -3.611745e-002, 3.491173e-002, 3.379608e-002, 3.215676e-002, -3.050474e-002, 2.942390e-002, 2.851606e-002, 2.729075e-002, -2.556305e-002, 2.351257e-002, 2.206939e-002, 2.063788e-002, -1.874339e-002, 1.674897e-002, 1.499513e-002, 1.282930e-002, -1.031069e-002, 8.318007e-003, 6.849225e-003, 4.868687e-003, -2.799463e-003, 6.554596e-004, -1.657586e-003, -3.815220e-003, --6.019962e-003, -8.374727e-003, -1.060240e-002, -1.246368e-002, --1.438541e-002, -1.599249e-002, -1.711392e-002, -1.829963e-002, --1.957395e-002, -2.076187e-002, -2.192923e-002, -2.255913e-002, --2.259167e-002, -2.263124e-002, -2.274266e-002, -2.276364e-002, --2.277775e-002, -2.255446e-002, -2.185118e-002, -2.075609e-002, --1.997064e-002, -1.953343e-002, -1.885195e-002, -1.806234e-002, --1.703671e-002, -1.563231e-002, -1.427013e-002, -1.333103e-002, --1.232643e-002, -1.134038e-002, -9.914061e-003, -7.802220e-003, --4.849087e-003, -2.247817e-003, 7.093138e-005, 2.131760e-003, -3.507166e-003, 5.108711e-003, 7.054914e-003, 9.141091e-003, -1.116481e-002, 1.284738e-002, 1.467287e-002, 1.625545e-002, -1.738957e-002, 1.827707e-002, 1.889924e-002, 1.908773e-002, -1.882092e-002, 1.811302e-002, 1.646014e-002, 1.450542e-002, -1.299139e-002, 1.174056e-002, 1.085030e-002, 9.895594e-003, -8.697252e-003, 7.162899e-003, 5.196303e-003, 3.473491e-003, -1.543164e-003, -4.213685e-004, -2.074557e-003, -3.537647e-003, --4.668432e-003, -6.406290e-003, -8.097026e-003, -9.015186e-003, --9.838171e-003, -1.069196e-002, -1.162989e-002, -1.209421e-002, --1.231395e-002, -1.258722e-002, -1.212346e-002, -1.175342e-002, --1.128135e-002, -1.041603e-002, -9.455872e-003, -7.964977e-003, --6.990851e-003, -6.215775e-003, -5.414600e-003, -4.704862e-003, --4.048329e-003, -3.610792e-003, -3.213320e-003, -2.062114e-003, --1.004225e-003, 2.307415e-004, 1.273663e-003, 2.001758e-003, -3.443290e-003, 5.198106e-003, 6.909056e-003, 8.372257e-003, -8.707803e-003, 9.031754e-003, 9.231475e-003, 9.137415e-003, -8.805090e-003, 8.294868e-003, 8.180670e-003, 8.338760e-003, -7.864747e-003, 7.392176e-003, 6.555473e-003, 5.747565e-003, -6.255572e-003, 6.865768e-003, 7.337572e-003, 7.390398e-003, -6.762009e-003, 6.973934e-003, 7.537761e-003, 7.971484e-003, -8.123720e-003, 7.604849e-003, 7.216810e-003, 7.532116e-003, -7.600449e-003, 7.221970e-003, 6.604117e-003, 5.589618e-003, -4.770874e-003, 4.229896e-003, 3.640737e-003, 2.992700e-003, -2.006548e-003, 8.023127e-004, -1.177813e-004, -9.945589e-004, --2.032928e-003, -3.132484e-003, -4.516936e-003, -5.695809e-003, --6.470323e-003, -7.195920e-003, -7.928012e-003, -8.794739e-003, --9.580493e-003, -9.773680e-003, -9.881701e-003, -1.006301e-002, --1.045548e-002, -1.122410e-002, -1.164230e-002, -1.163747e-002, --1.138447e-002, -1.123806e-002, -1.134106e-002, -1.185723e-002, --1.204192e-002, -1.168398e-002, -1.126568e-002, -1.089795e-002, --1.090845e-002, -1.105166e-002, -1.103055e-002, -1.023981e-002, --9.361922e-003, -8.371049e-003, -7.578562e-003, -7.349685e-003, --7.310679e-003, -7.294808e-003, -7.335154e-003, -7.301579e-003, --7.330223e-003, -7.763455e-003, -8.227565e-003, -8.066161e-003, --7.718895e-003, -7.156124e-003, -6.877906e-003, -6.921739e-003, --6.834300e-003, -6.052919e-003, -5.057393e-003, -3.905368e-003, --2.622174e-003, -1.349292e-003, -5.034098e-004, 5.799790e-004, -1.529785e-003, 2.692727e-003, 3.428851e-003, 4.137996e-003, -4.574666e-003, 5.135323e-003, 5.730637e-003, 6.553256e-003, -7.186724e-003, 7.605896e-003, 7.715709e-003, 7.881939e-003, -8.185649e-003, 8.335573e-003, 8.606283e-003, 8.836317e-003, -8.999634e-003, 9.223964e-003, 9.686810e-003, 9.935024e-003, -1.040059e-002, 1.090493e-002, 1.146881e-002, 1.189723e-002, -1.261770e-002, 1.321377e-002, 1.409098e-002, 1.510879e-002, -1.597020e-002, 1.640195e-002, 1.658232e-002, 1.679451e-002, -1.700724e-002, 1.730688e-002, 1.765152e-002, 1.793573e-002, -1.790739e-002, 1.797367e-002, 1.782853e-002, 1.753643e-002, -1.766969e-002, 1.759952e-002, 1.742829e-002, 1.705576e-002, -1.673999e-002, 1.628532e-002, 1.573928e-002, 1.533888e-002, -1.467525e-002, 1.365310e-002, 1.227108e-002, 1.074951e-002, -9.222079e-003, 7.718998e-003, 6.239504e-003, 4.895915e-003, -3.669358e-003, 2.454426e-003, 1.345865e-003, 2.091807e-004, --7.286221e-004, -1.465081e-003, -2.030929e-003, -2.784536e-003, --3.528809e-003, -4.517121e-003, -5.207065e-003, -5.788640e-003, --6.267730e-003, -6.845133e-003, -7.835692e-003, -8.727780e-003, --9.531163e-003, -1.022097e-002, -1.088633e-002, -1.122360e-002, --1.120520e-002, -1.090742e-002, -1.051147e-002, -9.996182e-003, --9.743216e-003, -9.607908e-003, -9.309272e-003, -8.930210e-003, --8.891010e-003, -8.848051e-003, -8.519383e-003, -7.971881e-003, --7.183360e-003, -6.413700e-003, -5.484757e-003, -4.647386e-003, --4.008340e-003, -3.364449e-003, -2.953996e-003, -2.620614e-003, --2.334315e-003, -2.267890e-003, -2.359967e-003, -2.927530e-003, --3.405608e-003, -4.080607e-003, -4.935338e-003, -5.752372e-003, --6.416990e-003, -7.381614e-003, -8.781892e-003, -1.029007e-002, --1.184245e-002, -1.303781e-002, -1.434610e-002, -1.560290e-002, --1.713361e-002, -1.882600e-002, -2.064117e-002, -2.229700e-002, --2.428934e-002, -2.626077e-002, -2.812560e-002, -2.924710e-002, --2.997286e-002, -3.080780e-002, -3.126990e-002, -3.152929e-002, --3.173127e-002, -3.176193e-002, -3.189371e-002, -3.190178e-002, --3.184276e-002, -3.153658e-002, -3.104103e-002, -3.051133e-002, --2.995644e-002, -2.890267e-002, -2.733478e-002, -2.565742e-002, --2.418164e-002, -2.291387e-002, -2.122296e-002, -1.944181e-002, --1.748546e-002, -1.522782e-002, -1.309264e-002, -1.141865e-002, --9.949129e-003, -8.412726e-003, -6.820412e-003, -5.404121e-003, --4.063398e-003, -2.967430e-003, -1.739144e-003, -5.983088e-004, -4.915583e-004, 1.805720e-003, 3.122572e-003, 4.539031e-003, -6.164998e-003, 8.052443e-003, 1.022771e-002, 1.228164e-002, -1.396105e-002, 1.559995e-002, 1.727900e-002, 1.876976e-002, -2.034776e-002, 2.214442e-002, 2.390706e-002, 2.563993e-002, -2.734110e-002, 2.902073e-002, 3.079046e-002, 3.247927e-002, -3.409687e-002, 3.581839e-002, 3.786579e-002, 3.975921e-002, -4.158360e-002, 4.319656e-002, 4.466196e-002, 4.597251e-002, -4.729922e-002, 4.886675e-002, 5.051901e-002, 5.214561e-002, -5.351464e-002, 5.482532e-002, 5.588239e-002, 5.625881e-002, -5.646446e-002, 5.666459e-002, 5.687511e-002, 5.705516e-002, -5.734115e-002, 5.764511e-002, 5.804020e-002, 5.810874e-002, -5.800663e-002, 5.769519e-002, 5.719326e-002, 5.666811e-002, -5.596670e-002, 5.500465e-002, 5.389550e-002, 5.246805e-002, -5.085580e-002, 4.908175e-002, 4.716307e-002, 4.540169e-002, -4.352360e-002, 4.129263e-002, 3.899745e-002, 3.640574e-002, -3.340002e-002, 3.029288e-002, 2.713674e-002, 2.419922e-002, -2.126105e-002, 1.814286e-002, 1.467007e-002, 1.126856e-002, -8.035846e-003, 4.936749e-003, 2.034712e-003, -8.402643e-004, --3.798186e-003, -6.713516e-003, -9.620852e-003, -1.236111e-002, --1.469108e-002, -1.673423e-002, -1.874035e-002, -2.076505e-002, --2.275763e-002, -2.457012e-002, -2.626111e-002, -2.762188e-002, --2.881963e-002, -3.008063e-002, -3.138960e-002, -3.280146e-002, --3.417407e-002, -3.500368e-002, -3.558858e-002, -3.613100e-002, --3.685923e-002, -3.757275e-002, -3.824011e-002, -3.881196e-002, --3.945278e-002, -4.014865e-002, -4.089369e-002, -4.152280e-002, --4.222498e-002, -4.300391e-002, -4.357753e-002, -4.389153e-002, --4.417572e-002, -4.468131e-002, -4.536676e-002, -4.616546e-002, --4.687856e-002, -4.775342e-002, -4.867290e-002, -4.967323e-002, --5.078950e-002, -5.206538e-002, -5.347670e-002, -5.497112e-002, --5.641970e-002, -5.769714e-002, -5.885459e-002, -6.002815e-002, --6.126985e-002, -6.245799e-002, -6.357684e-002, -6.478398e-002, --6.606717e-002, -6.743520e-002, -6.884572e-002, -7.012717e-002, --7.142284e-002, -7.255595e-002, -7.339904e-002, -7.408170e-002, --7.470702e-002, -7.521750e-002, -7.548688e-002, -7.547660e-002, --7.509164e-002, -7.452128e-002, -7.366689e-002, -7.234040e-002, --7.051609e-002, -6.851947e-002, -6.624192e-002, -6.388047e-002, --6.132734e-002, -5.849585e-002, -5.552270e-002, -5.250218e-002, --4.927400e-002, -4.567800e-002, -4.185546e-002, -3.780921e-002, --3.356174e-002, -2.912511e-002, -2.457000e-002, -1.988600e-002, --1.517933e-002, -1.038374e-002, -5.597219e-003, -7.254290e-004, -4.143014e-003, 8.949597e-003, 1.368268e-002, 1.831937e-002, -2.285228e-002, 2.744269e-002, 3.192047e-002, 3.617557e-002, -4.037269e-002, 4.418612e-002, 4.761960e-002, 5.086220e-002, -5.387639e-002, 5.665861e-002, 5.920675e-002, 6.136500e-002, -6.316524e-002, 6.463713e-002, 6.592216e-002, 6.690185e-002, -6.769238e-002, 6.832333e-002, 6.879984e-002, 6.902306e-002, -6.894227e-002, 6.865506e-002, 6.807717e-002, 6.729998e-002, -6.639905e-002, 6.538164e-002, 6.426421e-002, 6.306813e-002, -6.194848e-002, 6.087209e-002, 5.986571e-002, 5.899887e-002, -5.816417e-002, 5.746282e-002, 5.678570e-002, 5.594664e-002, -5.501553e-002, 5.413006e-002, 5.330730e-002, 5.257485e-002, -5.192842e-002, 5.132706e-002, 5.074689e-002, 5.050003e-002, -5.035575e-002, 5.021581e-002, 5.003469e-002, 4.979947e-002, -4.952558e-002, 4.932980e-002, 4.909361e-002, 4.856641e-002, -4.766466e-002, 4.700044e-002, 4.636619e-002, 4.567631e-002, -4.477307e-002, 4.382717e-002, 4.287739e-002, 4.202948e-002, -4.108119e-002, 4.025089e-002, 3.912707e-002, 3.823687e-002, -3.738083e-002, 3.653945e-002, 3.545934e-002, 3.423688e-002, -3.288536e-002, 3.152637e-002, 3.012073e-002, 2.871450e-002, -2.720307e-002, 2.576423e-002, 2.421299e-002, 2.252979e-002, -2.060456e-002, 1.848958e-002, 1.630198e-002, 1.402818e-002, -1.163621e-002, 9.157455e-003, 6.467600e-003, 3.681196e-003, -1.076935e-003, -1.611882e-003, -4.520276e-003, -7.677941e-003, --1.089190e-002, -1.424759e-002, -1.762310e-002, -2.098455e-002, --2.436754e-002, -2.750442e-002, -2.988875e-002, -3.221942e-002, --3.458169e-002, -3.712299e-002, -3.970633e-002, -4.240767e-002, --4.501945e-002, -4.757800e-002, -5.010187e-002, -5.255989e-002, --5.471572e-002, -5.653811e-002, -5.833453e-002, -6.018337e-002, --6.191623e-002, -6.360020e-002, -6.524241e-002, -6.684681e-002, --6.843125e-002, -7.002673e-002, -7.152629e-002, -7.240049e-002, --7.300993e-002, -7.388027e-002, -7.498386e-002, -7.612198e-002, --7.717995e-002, -7.794049e-002, -7.872595e-002, -7.944172e-002, --8.020577e-002, -8.077332e-002, -7.938030e-002, -7.806317e-002, --7.696373e-002, -7.589552e-002, -7.481753e-002, -7.383193e-002, --7.278924e-002, -7.177350e-002, -7.024781e-002, -6.849253e-002, --6.630172e-002, -6.048931e-002, -5.491701e-002, -4.916730e-002, --4.324916e-002, -3.726084e-002, -3.118685e-002, -2.504838e-002, --1.886408e-002, -1.263625e-002, -6.343596e-003, -4.415809e-016} -}; + {0.000000e+000, 8.833750e-003, 1.497601e-002, 2.023710e-002, 2.498590e-002, 2.937536e-002, 3.348347e-002, 3.740247e-002, 4.116645e-002, + 4.481240e-002, 4.835121e-002, 5.178991e-002, 5.514774e-002, 5.843527e-002, 6.167138e-002, 6.485830e-002, 6.799864e-002, 7.109369e-002, + 7.414113e-002, 7.714684e-002, 8.011208e-002, 8.303313e-002, 8.591695e-002, 8.876004e-002, 9.156913e-002, 9.434381e-002, 9.708718e-002, + 9.980128e-002, 1.024881e-001, 1.051487e-001, 1.077847e-001, 1.103984e-001, 1.129923e-001, 1.155692e-001, 1.181306e-001, 1.206751e-001, + 1.232042e-001, 1.257223e-001, 1.282268e-001, 1.307177e-001, 1.331949e-001, 1.356606e-001, 1.381127e-001, 1.405497e-001, 1.429754e-001, + 1.453869e-001, 1.477816e-001, 1.501633e-001, 1.525314e-001, 1.548870e-001, 1.572343e-001, 1.595739e-001, 1.619041e-001, 1.642287e-001, + 1.665419e-001, 1.688466e-001, 1.711441e-001, 1.734281e-001, 1.757041e-001, 1.779716e-001, 1.802266e-001, 1.824685e-001, 1.847028e-001, + 1.869270e-001, 1.891430e-001, 1.913527e-001, 1.935541e-001, 1.957470e-001, 1.979323e-001, 2.001116e-001, 2.022840e-001, 2.044483e-001, + 2.066046e-001, 2.087546e-001, 2.108964e-001, 2.130336e-001, 2.151646e-001, 2.172897e-001, 2.194090e-001, 2.215230e-001, 2.236334e-001, + 2.257399e-001, 2.278428e-001, 2.299395e-001, 2.320352e-001, 2.341277e-001, 2.362148e-001, 2.383003e-001, 2.403819e-001, 2.424598e-001, + 2.445341e-001, 2.466031e-001, 2.486695e-001, 2.507313e-001, 2.527875e-001, 2.548405e-001, 2.568878e-001, 2.589288e-001, 2.609657e-001, + 2.629976e-001, 2.650239e-001, 2.670452e-001, 2.690614e-001, 2.710740e-001, 2.730820e-001, 2.750845e-001, 2.770829e-001, 2.790768e-001, + 2.810665e-001, 2.830520e-001, 2.850325e-001, 2.870062e-001, 2.889752e-001, 2.909403e-001, 2.929002e-001, 2.948523e-001, 2.967999e-001, + 2.987424e-001, 3.006780e-001, 3.026081e-001, 3.045304e-001, 3.064477e-001, 3.083587e-001, 3.102641e-001, 3.121645e-001, 3.140588e-001, + 3.159478e-001, 3.178321e-001, 3.197115e-001, 3.215854e-001, 3.234539e-001, 3.253173e-001, 3.271750e-001, 3.290263e-001, 3.308733e-001, + 3.327147e-001, 3.345507e-001, 3.363827e-001, 3.382077e-001, 3.400275e-001, 3.418430e-001, 3.436523e-001, 3.454572e-001, 3.472560e-001, + 3.490492e-001, 3.508357e-001, 3.526168e-001, 3.543927e-001, 3.561617e-001, 3.579243e-001, 3.596821e-001, 3.614338e-001, 3.631794e-001, + 3.649187e-001, 3.666514e-001, 3.683797e-001, 3.701042e-001, 3.718237e-001, 3.735390e-001, 3.752482e-001, 3.769527e-001, 3.786515e-001, + 3.803469e-001, 3.820369e-001, 3.837210e-001, 3.853998e-001, 3.870728e-001, 3.887416e-001, 3.904061e-001, 3.920668e-001, 3.937225e-001, + 3.953728e-001, 3.970202e-001, 3.986625e-001, 4.003001e-001, 4.019333e-001, 4.035619e-001, 4.051867e-001, 4.068054e-001, 4.084197e-001, + 4.100310e-001, 4.116374e-001, 4.132398e-001, 4.148394e-001, 4.164354e-001, 4.180263e-001, 4.196143e-001, 4.211982e-001, 4.227779e-001, + 4.243544e-001, 4.259255e-001, 4.274937e-001, 4.290582e-001, 4.306178e-001, 4.321742e-001, 4.337241e-001, 4.352709e-001, 4.368133e-001, + 4.383513e-001, 4.398857e-001, 4.414162e-001, 4.429419e-001, 4.444639e-001, 4.459815e-001, 4.474938e-001, 4.490017e-001, 4.505049e-001, + 4.520032e-001, 4.534963e-001, 4.549859e-001, 4.564702e-001, 4.579507e-001, 4.594252e-001, 4.608951e-001, 4.623603e-001, 4.638208e-001, + 4.652769e-001, 4.667274e-001, 4.681745e-001, 4.696162e-001, 4.710546e-001, 4.724894e-001, 4.739204e-001, 4.753461e-001, 4.767679e-001, + 4.781860e-001, 4.795997e-001, 4.810095e-001, 4.824153e-001, 4.838171e-001, 4.852141e-001, 4.866072e-001, 4.879966e-001, 4.893811e-001, + 4.907610e-001, 4.921370e-001, 4.935079e-001, 4.948743e-001, 4.962369e-001, 4.975940e-001, 4.989463e-001, 5.002944e-001, 5.016375e-001, + 5.029762e-001, 5.043101e-001, 5.056411e-001, 5.069678e-001, 5.082890e-001, 5.096068e-001, 5.109207e-001, 5.122312e-001, 5.135380e-001, + 5.148395e-001, 5.161376e-001, 5.174326e-001, 5.187239e-001, 5.200108e-001, 5.212956e-001, 5.225765e-001, 5.238539e-001, 5.251270e-001, + 5.263976e-001, 5.276647e-001, 5.289288e-001, 5.301895e-001, 5.314460e-001, 5.326988e-001, 5.339488e-001, 5.351959e-001, 5.364394e-001, + 5.376781e-001, 5.389128e-001, 5.401439e-001, 5.413711e-001, 5.425948e-001, 5.438155e-001, 5.450334e-001, 5.462481e-001, 5.474585e-001, + 5.486653e-001, 5.498682e-001, 5.510679e-001, 5.522646e-001, 5.534584e-001, 5.546488e-001, 5.558350e-001, 5.570184e-001, 5.581970e-001, + 5.593729e-001, 5.605464e-001, 5.617169e-001, 5.628855e-001, 5.640514e-001, 5.652144e-001, 5.663742e-001, 5.675307e-001, 5.686837e-001, + 5.698335e-001, 5.709814e-001, 5.721262e-001, 5.732678e-001, 5.744062e-001, 5.755408e-001, 5.766719e-001, 5.777991e-001, 5.789228e-001, + 5.800434e-001, 5.811608e-001, 5.822757e-001, 5.833864e-001, 5.844935e-001, 5.855962e-001, 5.866946e-001, 5.877900e-001, 5.888820e-001, + 5.899714e-001, 5.910584e-001, 5.921409e-001, 5.932197e-001, 5.942954e-001, 5.953670e-001, 5.964353e-001, 5.975001e-001, 5.985620e-001, + 5.996207e-001, 6.006759e-001, 6.017287e-001, 6.027787e-001, 6.038259e-001, 6.048711e-001, 6.059128e-001, 6.069513e-001, 6.079871e-001, + 6.090199e-001, 6.100498e-001, 6.110774e-001, 6.121015e-001, 6.131222e-001, 6.141404e-001, 6.151544e-001, 6.161652e-001, 6.171720e-001, + 6.181760e-001, 6.191776e-001, 6.201763e-001, 6.211720e-001, 6.221657e-001, 6.231560e-001, 6.241430e-001, 6.251274e-001, 6.261093e-001, + 6.270888e-001, 6.280658e-001, 6.290399e-001, 6.300118e-001, 6.309817e-001, 6.319492e-001, 6.329147e-001, 6.338775e-001, 6.348380e-001, + 6.357973e-001, 6.367547e-001, 6.377090e-001, 6.386612e-001, 6.396111e-001, 6.405584e-001, 6.415040e-001, 6.424475e-001, 6.433883e-001, + 6.443268e-001, 6.452632e-001, 6.461974e-001, 6.471300e-001, 6.480595e-001, 6.489846e-001, 6.499076e-001, 6.508284e-001, 6.517463e-001, + 6.526614e-001, 6.535751e-001, 6.544869e-001, 6.553963e-001, 6.563038e-001, 6.572084e-001, 6.581105e-001, 6.590103e-001, 6.599075e-001, + 6.608029e-001, 6.616960e-001, 6.625868e-001, 6.634748e-001, 6.643607e-001, 6.652449e-001, 6.661269e-001, 6.670068e-001, 6.678847e-001, + 6.687603e-001, 6.696338e-001, 6.705051e-001, 6.713738e-001, 6.722403e-001, 6.731052e-001, 6.739680e-001, 6.748279e-001, 6.756849e-001, + 6.765397e-001, 6.773922e-001, 6.782424e-001, 6.790905e-001, 6.799357e-001, 6.807790e-001, 6.816211e-001, 6.824613e-001, 6.832992e-001, + 6.841354e-001, 6.849693e-001, 6.858009e-001, 6.866306e-001, 6.874586e-001, 6.882845e-001, 6.891088e-001, 6.899312e-001, 6.907522e-001, + 6.915713e-001, 6.923889e-001, 6.932055e-001, 6.940204e-001, 6.948331e-001, 6.956444e-001, 6.964545e-001, 6.972630e-001, 6.980709e-001, + 6.988765e-001, 6.996802e-001, 7.004820e-001, 7.012829e-001, 7.020820e-001, 7.028784e-001, 7.036729e-001, 7.044662e-001, 7.052567e-001, + 7.060448e-001, 7.068312e-001, 7.076163e-001, 7.084000e-001, 7.091818e-001, 7.099615e-001, 7.107392e-001, 7.115153e-001, 7.122902e-001, + 7.130639e-001, 7.138360e-001, 7.146063e-001, 7.153750e-001, 7.161427e-001, 7.169086e-001, 7.176714e-001, 7.184334e-001, 7.191948e-001, + 7.199551e-001, 7.207136e-001, 7.214710e-001, 7.222273e-001, 7.229821e-001, 7.237351e-001, 7.244873e-001, 7.252385e-001, 7.259878e-001, + 7.267357e-001, 7.274823e-001, 7.282280e-001, 7.289722e-001, 7.297146e-001, 7.304559e-001, 7.311956e-001, 7.319334e-001, 7.326702e-001, + 7.334058e-001, 7.341401e-001, 7.348725e-001, 7.356036e-001, 7.363326e-001, 7.370595e-001, 7.377838e-001, 7.385062e-001, 7.392276e-001, + 7.399476e-001, 7.406657e-001, 7.413815e-001, 7.420955e-001, 7.428083e-001, 7.435197e-001, 7.442295e-001, 7.449381e-001, 7.456450e-001, + 7.463493e-001, 7.470524e-001, 7.477545e-001, 7.484556e-001, 7.491544e-001, 7.498516e-001, 7.505475e-001, 7.512428e-001, 7.519365e-001, + 7.526290e-001, 7.533205e-001, 7.540111e-001, 7.547005e-001, 7.553885e-001, 7.560757e-001, 7.567612e-001, 7.574450e-001, 7.581269e-001, + 7.588078e-001, 7.594872e-001, 7.601647e-001, 7.608397e-001, 7.615131e-001, 7.621848e-001, 7.628556e-001, 7.635244e-001, 7.641918e-001, + 7.648576e-001, 7.655221e-001, 7.661856e-001, 7.668478e-001, 7.675096e-001, 7.681704e-001, 7.688298e-001, 7.694874e-001, 7.701442e-001, + 7.708006e-001, 7.714566e-001, 7.721111e-001, 7.727647e-001, 7.734175e-001, 7.740696e-001, 7.747209e-001, 7.753715e-001, 7.760213e-001, + 7.766698e-001, 7.773165e-001, 7.779616e-001, 7.786055e-001, 7.792483e-001, 7.798897e-001, 7.805297e-001, 7.811681e-001, 7.818059e-001, + 7.824431e-001, 7.830795e-001, 7.837143e-001, 7.843478e-001, 7.849804e-001, 7.856122e-001, 7.862434e-001, 7.868737e-001, 7.875034e-001, + 7.881315e-001, 7.887587e-001, 7.893850e-001, 7.900109e-001, 7.906364e-001, 7.912619e-001, 7.918865e-001, 7.925099e-001, 7.931321e-001, + 7.937540e-001, 7.943754e-001, 7.949960e-001, 7.956153e-001, 7.962338e-001, 7.968516e-001, 7.974686e-001, 7.980840e-001, 7.986981e-001, + 7.993113e-001, 7.999234e-001, 8.005342e-001, 8.011433e-001, 8.017515e-001, 8.023587e-001, 8.029649e-001, 8.035695e-001, 8.041722e-001, + 8.047736e-001, 8.053747e-001, 8.059753e-001, 8.065749e-001, 8.071730e-001, 8.077706e-001, 8.083676e-001, 8.089641e-001, 8.095600e-001, + 8.101552e-001, 8.107499e-001, 8.113440e-001, 8.119368e-001, 8.125279e-001, 8.131183e-001, 8.137079e-001, 8.142971e-001, 8.148864e-001, + 8.154749e-001, 8.160624e-001, 8.166492e-001, 8.172353e-001, 8.178209e-001, 8.184058e-001, 8.189893e-001, 8.195708e-001, 8.201507e-001, + 8.207299e-001, 8.213078e-001, 8.218841e-001, 8.224596e-001, 8.230342e-001, 8.236078e-001, 8.241803e-001, 8.247514e-001, 8.253219e-001, + 8.258914e-001, 8.264596e-001, 8.270267e-001, 8.275923e-001, 8.281567e-001, 8.287201e-001, 8.292832e-001, 8.298456e-001, 8.304071e-001, + 8.309677e-001, 8.315275e-001, 8.320871e-001, 8.326465e-001, 8.332050e-001, 8.337630e-001, 8.343207e-001, 8.348772e-001, 8.354332e-001, + 8.359882e-001, 8.365424e-001, 8.370960e-001, 8.376492e-001, 8.382020e-001, 8.387537e-001, 8.393034e-001, 8.398514e-001, 8.403982e-001, + 8.409439e-001, 8.414888e-001, 8.420314e-001, 8.425724e-001, 8.431122e-001, 8.436513e-001, 8.441898e-001, 8.447275e-001, 8.452641e-001, + 8.457995e-001, 8.463338e-001, 8.468673e-001, 8.474001e-001, 8.479313e-001, 8.484619e-001, 8.489916e-001, 8.495206e-001, 8.500489e-001, + 8.505759e-001, 8.511019e-001, 8.516276e-001, 8.521532e-001, 8.526788e-001, 8.532041e-001, 8.537274e-001, 8.542500e-001, 8.547719e-001, + 8.552934e-001, 8.558142e-001, 8.563345e-001, 8.568541e-001, 8.573727e-001, 8.578908e-001, 8.584080e-001, 8.589247e-001, 8.594402e-001, + 8.599549e-001, 8.604689e-001, 8.609809e-001, 8.614913e-001, 8.620006e-001, 8.625081e-001, 8.630143e-001, 8.635199e-001, 8.640247e-001, + 8.645286e-001, 8.650318e-001, 8.655342e-001, 8.660358e-001, 8.665362e-001, 8.670353e-001, 8.675341e-001, 8.680320e-001, 8.685293e-001, + 8.690256e-001, 8.695210e-001, 8.700148e-001, 8.705079e-001, 8.710004e-001, 8.714923e-001, 8.719839e-001, 8.724751e-001, 8.729660e-001, + 8.734568e-001, 8.739459e-001, 8.744347e-001, 8.749232e-001, 8.754115e-001, 8.758991e-001, 8.763859e-001, 8.768713e-001, 8.773560e-001, + 8.778394e-001, 8.783218e-001, 8.788030e-001, 8.792834e-001, 8.797630e-001, 8.802419e-001, 8.807204e-001, 8.811979e-001, 8.816744e-001, + 8.821505e-001, 8.826258e-001, 8.831009e-001, 8.835756e-001, 8.840499e-001, 8.845239e-001, 8.849976e-001, 8.854704e-001, 8.859428e-001, + 8.864150e-001, 8.868866e-001, 8.873580e-001, 8.878287e-001, 8.882986e-001, 8.887683e-001, 8.892373e-001, 8.897059e-001, 8.901739e-001, + 8.906407e-001, 8.911065e-001, 8.915716e-001, 8.920358e-001, 8.924988e-001, 8.929612e-001, 8.934233e-001, 8.938844e-001, 8.943450e-001, + 8.948051e-001, 8.952646e-001, 8.957233e-001, 8.961818e-001, 8.966400e-001, 8.970972e-001, 8.975541e-001, 8.980107e-001, 8.984668e-001, + 8.989224e-001, 8.993777e-001, 8.998324e-001, 9.002873e-001, 9.007422e-001, 9.011971e-001, 9.016515e-001, 9.021055e-001, 9.025588e-001, + 9.030118e-001, 9.034647e-001, 9.039173e-001, 9.043691e-001, 9.048198e-001, 9.052697e-001, 9.057189e-001, 9.061676e-001, 9.066155e-001, + 9.070628e-001, 9.075091e-001, 9.079547e-001, 9.083993e-001, 9.088430e-001, 9.092861e-001, 9.097289e-001, 9.101710e-001, 9.106125e-001, + 9.110534e-001, 9.114935e-001, 9.119334e-001, 9.123729e-001, 9.128117e-001, 9.132502e-001, 9.136887e-001, 9.141261e-001, 9.145633e-001, + 9.150005e-001, 9.154379e-001, 9.158750e-001, 9.163116e-001, 9.167482e-001, 9.171846e-001, 9.176209e-001, 9.180570e-001, 9.184931e-001, + 9.189291e-001, 9.193650e-001, 9.198002e-001, 9.202344e-001, 9.206675e-001, 9.211001e-001, 9.215318e-001, 9.219623e-001, 9.223921e-001, + 9.228206e-001, 9.232483e-001, 9.236752e-001, 9.241015e-001, 9.245273e-001, 9.249527e-001, 9.253776e-001, 9.258018e-001, 9.262260e-001, + 9.266500e-001, 9.270736e-001, 9.274966e-001, 9.279189e-001, 9.283404e-001, 9.287611e-001, 9.291817e-001, 9.296025e-001, 9.300234e-001, + 9.304442e-001, 9.308649e-001, 9.312853e-001, 9.317051e-001, 9.321247e-001, 9.325440e-001, 9.329630e-001, 9.333813e-001, 9.337989e-001, + 9.342158e-001, 9.346318e-001, 9.350470e-001, 9.354616e-001, 9.358757e-001, 9.362891e-001, 9.367019e-001, 9.371142e-001, 9.375252e-001, + 9.379359e-001, 9.383464e-001, 9.387562e-001, 9.391652e-001, 9.395733e-001, 9.399811e-001, 9.403880e-001, 9.407948e-001, 9.412017e-001, + 9.416088e-001, 9.420160e-001, 9.424232e-001, 9.428301e-001, 9.432357e-001, 9.436407e-001, 9.440456e-001, 9.444499e-001, 9.448543e-001, + 9.452587e-001, 9.456630e-001, 9.460668e-001, 9.464702e-001, 9.468731e-001, 9.472752e-001, 9.476769e-001, 9.480781e-001, 9.484789e-001, + 9.488786e-001, 9.492763e-001, 9.496733e-001, 9.500697e-001, 9.504652e-001, 9.508601e-001, 9.512542e-001, 9.516477e-001, 9.520401e-001, + 9.524325e-001, 9.528247e-001, 9.532167e-001, 9.536083e-001, 9.539985e-001, 9.543878e-001, 9.547762e-001, 9.551640e-001, 9.555517e-001, + 9.559390e-001, 9.563265e-001, 9.567139e-001, 9.571011e-001, 9.574878e-001, 9.578741e-001, 9.582601e-001, 9.586452e-001, 9.590301e-001, + 9.594146e-001, 9.597986e-001, 9.601827e-001, 9.605663e-001, 9.609499e-001, 9.613334e-001, 9.617168e-001, 9.621000e-001, 9.624829e-001, + 9.628657e-001, 9.632470e-001, 9.636274e-001, 9.640080e-001, 9.643889e-001, 9.647693e-001, 9.651495e-001, 9.655296e-001, 9.659094e-001, + 9.662882e-001, 9.666668e-001, 9.670450e-001, 9.674231e-001, 9.678013e-001, 9.681793e-001, 9.685573e-001, 9.689350e-001, 9.693117e-001, + 9.696878e-001, 9.700628e-001, 9.704373e-001, 9.708112e-001, 9.711849e-001, 9.715584e-001, 9.719319e-001, 9.723043e-001, 9.726759e-001, + 9.730465e-001, 9.734168e-001, 9.737866e-001, 9.741559e-001, 9.745245e-001, 9.748921e-001, 9.752583e-001, 9.756247e-001, 9.759911e-001, + 9.763572e-001, 9.767229e-001, 9.770882e-001, 9.774521e-001, 9.778142e-001, 9.781752e-001, 9.785354e-001, 9.788955e-001, 9.792559e-001, + 9.796167e-001, 9.799774e-001, 9.803373e-001, 9.806962e-001, 9.810546e-001, 9.814132e-001, 9.817717e-001, 9.821283e-001, 9.824845e-001, + 9.828408e-001, 9.831978e-001, 9.835534e-001, 9.839081e-001, 9.842627e-001, 9.846173e-001, 9.849717e-001, 9.853253e-001, 9.856776e-001, + 9.860290e-001, 9.863792e-001, 9.867276e-001, 9.870745e-001, 9.874211e-001, 9.877674e-001, 9.881133e-001, 9.884589e-001, 9.888046e-001, + 9.891491e-001, 9.894908e-001, 9.898319e-001, 9.901736e-001, 9.905145e-001, 9.908552e-001, 9.911950e-001, 9.915323e-001, 9.918691e-001, + 9.922037e-001, 9.925383e-001, 9.928728e-001, 9.932051e-001, 9.935379e-001, 9.938715e-001, 9.942054e-001, 9.945395e-001, 9.948709e-001, + 9.952013e-001, 9.955306e-001, 9.958570e-001, 9.961821e-001, 9.965066e-001, 9.968276e-001, 9.971488e-001, 9.974682e-001, 9.977856e-001, + 9.981028e-001, 9.984198e-001, 9.987365e-001, 9.990528e-001, 9.993689e-001, 9.996846e-001, 1.000000e+000}, + {0.000000e+000, -1.452326e-003, -2.133227e-003, -2.674255e-003, -3.149903e-003, -3.582399e-003, -3.980914e-003, -4.359803e-003, -4.725236e-003, + -5.080627e-003, -5.427092e-003, -5.765801e-003, -6.098779e-003, -6.426347e-003, -6.750412e-003, -7.071325e-003, -7.389887e-003, -7.705940e-003, + -8.018821e-003, -8.329350e-003, -8.636635e-003, -8.939656e-003, -9.239358e-003, -9.535422e-003, -9.828782e-003, -1.011918e-002, -1.040701e-002, + -1.069241e-002, -1.097627e-002, -1.125855e-002, -1.153943e-002, -1.181888e-002, -1.209722e-002, -1.237451e-002, -1.265082e-002, -1.292588e-002, + -1.319994e-002, -1.347363e-002, -1.374673e-002, -1.401901e-002, -1.429037e-002, -1.456117e-002, -1.483101e-002, -1.509930e-002, -1.536673e-002, + -1.563288e-002, -1.589735e-002, -1.616107e-002, -1.642365e-002, -1.668546e-002, -1.694699e-002, -1.720795e-002, -1.746800e-002, -1.772795e-002, + -1.798663e-002, -1.824441e-002, -1.850145e-002, -1.875670e-002, -1.901103e-002, -1.926427e-002, -1.951601e-002, -1.976624e-002, -2.001590e-002, + -2.026443e-002, -2.051216e-002, -2.075924e-002, -2.100532e-002, -2.125028e-002, -2.149434e-002, -2.173771e-002, -2.198008e-002, -2.222141e-002, + -2.246182e-002, -2.270150e-002, -2.293991e-002, -2.317745e-002, -2.341402e-002, -2.364972e-002, -2.388422e-002, -2.411771e-002, -2.435039e-002, + -2.458214e-002, -2.481298e-002, -2.504251e-002, -2.527139e-002, -2.549926e-002, -2.572567e-002, -2.595116e-002, -2.617529e-002, -2.639808e-002, + -2.661950e-002, -2.683932e-002, -2.705793e-002, -2.727509e-002, -2.749054e-002, -2.770478e-002, -2.791765e-002, -2.812906e-002, -2.833907e-002, + -2.854777e-002, -2.875515e-002, -2.896148e-002, -2.916651e-002, -2.937037e-002, -2.957319e-002, -2.977480e-002, -2.997510e-002, -3.017387e-002, + -3.037131e-002, -3.056735e-002, -3.076189e-002, -3.095487e-002, -3.114650e-002, -3.133707e-002, -3.152595e-002, -3.171299e-002, -3.189852e-002, + -3.208257e-002, -3.226478e-002, -3.244534e-002, -3.262393e-002, -3.280084e-002, -3.297597e-002, -3.314928e-002, -3.332076e-002, -3.349039e-002, + -3.365804e-002, -3.382414e-002, -3.398861e-002, -3.415137e-002, -3.431267e-002, -3.447234e-002, -3.463056e-002, -3.478694e-002, -3.494207e-002, + -3.509573e-002, -3.524784e-002, -3.539876e-002, -3.554815e-002, -3.569617e-002, -3.584304e-002, -3.598837e-002, -3.613225e-002, -3.627470e-002, + -3.641544e-002, -3.655459e-002, -3.669223e-002, -3.682852e-002, -3.696315e-002, -3.709616e-002, -3.722770e-002, -3.735784e-002, -3.748648e-002, + -3.761366e-002, -3.773904e-002, -3.786323e-002, -3.798581e-002, -3.810706e-002, -3.822713e-002, -3.834571e-002, -3.846347e-002, -3.857978e-002, + -3.869504e-002, -3.880919e-002, -3.892227e-002, -3.903446e-002, -3.914561e-002, -3.925559e-002, -3.936454e-002, -3.947248e-002, -3.957931e-002, + -3.968514e-002, -3.978970e-002, -3.989321e-002, -3.999547e-002, -4.009679e-002, -4.019671e-002, -4.029567e-002, -4.039327e-002, -4.048966e-002, + -4.058472e-002, -4.067842e-002, -4.077072e-002, -4.086172e-002, -4.095115e-002, -4.103928e-002, -4.112599e-002, -4.121130e-002, -4.129514e-002, + -4.137745e-002, -4.145831e-002, -4.153784e-002, -4.161594e-002, -4.169268e-002, -4.176806e-002, -4.184201e-002, -4.191495e-002, -4.198670e-002, + -4.205730e-002, -4.212682e-002, -4.219527e-002, -4.226271e-002, -4.232926e-002, -4.239516e-002, -4.246016e-002, -4.252416e-002, -4.258751e-002, + -4.264987e-002, -4.271121e-002, -4.277182e-002, -4.283147e-002, -4.289001e-002, -4.294758e-002, -4.300416e-002, -4.305989e-002, -4.311482e-002, + -4.316872e-002, -4.322183e-002, -4.327415e-002, -4.332560e-002, -4.337624e-002, -4.342598e-002, -4.347473e-002, -4.352182e-002, -4.356825e-002, + -4.361366e-002, -4.365823e-002, -4.370212e-002, -4.374504e-002, -4.378715e-002, -4.382844e-002, -4.386884e-002, -4.390862e-002, -4.394763e-002, + -4.398574e-002, -4.402313e-002, -4.405966e-002, -4.409571e-002, -4.413102e-002, -4.416548e-002, -4.419924e-002, -4.423225e-002, -4.426458e-002, + -4.429625e-002, -4.432721e-002, -4.435773e-002, -4.438731e-002, -4.441590e-002, -4.444384e-002, -4.447094e-002, -4.449737e-002, -4.452283e-002, + -4.454739e-002, -4.457123e-002, -4.459430e-002, -4.461653e-002, -4.463811e-002, -4.465908e-002, -4.467913e-002, -4.469845e-002, -4.471662e-002, + -4.473380e-002, -4.475009e-002, -4.476545e-002, -4.477980e-002, -4.479333e-002, -4.480625e-002, -4.481868e-002, -4.483077e-002, -4.484231e-002, + -4.485350e-002, -4.486444e-002, -4.487481e-002, -4.488466e-002, -4.489428e-002, -4.490342e-002, -4.491209e-002, -4.492045e-002, -4.492805e-002, + -4.493506e-002, -4.494158e-002, -4.494787e-002, -4.495373e-002, -4.495914e-002, -4.496418e-002, -4.496864e-002, -4.497266e-002, -4.497585e-002, + -4.497837e-002, -4.498044e-002, -4.498202e-002, -4.498301e-002, -4.498330e-002, -4.498296e-002, -4.498195e-002, -4.498020e-002, -4.497782e-002, + -4.497470e-002, -4.497100e-002, -4.496672e-002, -4.496213e-002, -4.495708e-002, -4.495139e-002, -4.494517e-002, -4.493846e-002, -4.493136e-002, + -4.492396e-002, -4.491610e-002, -4.490805e-002, -4.489969e-002, -4.489106e-002, -4.488211e-002, -4.487255e-002, -4.486277e-002, -4.485249e-002, + -4.484181e-002, -4.483081e-002, -4.481932e-002, -4.480724e-002, -4.479465e-002, -4.478147e-002, -4.476786e-002, -4.475389e-002, -4.473952e-002, + -4.472469e-002, -4.470938e-002, -4.469373e-002, -4.467754e-002, -4.466059e-002, -4.464314e-002, -4.462507e-002, -4.460638e-002, -4.458708e-002, + -4.456743e-002, -4.454741e-002, -4.452720e-002, -4.450653e-002, -4.448564e-002, -4.446457e-002, -4.444315e-002, -4.442160e-002, -4.439973e-002, + -4.437771e-002, -4.435557e-002, -4.433333e-002, -4.431076e-002, -4.428809e-002, -4.426507e-002, -4.424180e-002, -4.421827e-002, -4.419435e-002, + -4.416995e-002, -4.414517e-002, -4.411995e-002, -4.409431e-002, -4.406830e-002, -4.404176e-002, -4.401464e-002, -4.398713e-002, -4.395903e-002, + -4.393053e-002, -4.390148e-002, -4.387187e-002, -4.384186e-002, -4.381149e-002, -4.378073e-002, -4.374977e-002, -4.371862e-002, -4.368725e-002, + -4.365557e-002, -4.362372e-002, -4.359164e-002, -4.355944e-002, -4.352701e-002, -4.349412e-002, -4.346092e-002, -4.342764e-002, -4.339421e-002, + -4.336059e-002, -4.332684e-002, -4.329287e-002, -4.325861e-002, -4.322421e-002, -4.318963e-002, -4.315485e-002, -4.311988e-002, -4.308463e-002, + -4.304913e-002, -4.301337e-002, -4.297723e-002, -4.294067e-002, -4.290364e-002, -4.286630e-002, -4.282879e-002, -4.279104e-002, -4.275296e-002, + -4.271456e-002, -4.267588e-002, -4.263693e-002, -4.259777e-002, -4.255827e-002, -4.251860e-002, -4.247873e-002, -4.243858e-002, -4.239825e-002, + -4.235783e-002, -4.231732e-002, -4.227674e-002, -4.223604e-002, -4.219519e-002, -4.215421e-002, -4.211319e-002, -4.207202e-002, -4.203058e-002, + -4.198890e-002, -4.194710e-002, -4.190514e-002, -4.186294e-002, -4.182052e-002, -4.177779e-002, -4.173476e-002, -4.169156e-002, -4.164812e-002, + -4.160442e-002, -4.156042e-002, -4.151617e-002, -4.147183e-002, -4.142717e-002, -4.138201e-002, -4.133632e-002, -4.129025e-002, -4.124396e-002, + -4.119736e-002, -4.115062e-002, -4.110383e-002, -4.105702e-002, -4.101012e-002, -4.096321e-002, -4.091652e-002, -4.086999e-002, -4.082333e-002, + -4.077658e-002, -4.072966e-002, -4.068264e-002, -4.063561e-002, -4.058844e-002, -4.054119e-002, -4.049385e-002, -4.044649e-002, -4.039923e-002, + -4.035198e-002, -4.030469e-002, -4.025731e-002, -4.020983e-002, -4.016225e-002, -4.011440e-002, -4.006619e-002, -4.001770e-002, -3.996890e-002, + -3.991991e-002, -3.987055e-002, -3.982097e-002, -3.977121e-002, -3.972128e-002, -3.967103e-002, -3.962057e-002, -3.956995e-002, -3.951911e-002, + -3.946820e-002, -3.941725e-002, -3.936621e-002, -3.931499e-002, -3.926351e-002, -3.921183e-002, -3.915993e-002, -3.910795e-002, -3.905590e-002, + -3.900372e-002, -3.895139e-002, -3.889896e-002, -3.884661e-002, -3.879430e-002, -3.874202e-002, -3.868974e-002, -3.863756e-002, -3.858543e-002, + -3.853330e-002, -3.848104e-002, -3.842885e-002, -3.837659e-002, -3.832437e-002, -3.827206e-002, -3.821967e-002, -3.816718e-002, -3.811467e-002, + -3.806202e-002, -3.800927e-002, -3.795642e-002, -3.790338e-002, -3.785004e-002, -3.779642e-002, -3.774283e-002, -3.768910e-002, -3.763511e-002, + -3.758087e-002, -3.752642e-002, -3.747180e-002, -3.741704e-002, -3.736217e-002, -3.730733e-002, -3.725246e-002, -3.719750e-002, -3.714247e-002, + -3.708753e-002, -3.703262e-002, -3.697800e-002, -3.692337e-002, -3.686882e-002, -3.681430e-002, -3.675979e-002, -3.670506e-002, -3.665026e-002, + -3.659535e-002, -3.654034e-002, -3.648529e-002, -3.643007e-002, -3.637474e-002, -3.631929e-002, -3.626369e-002, -3.620801e-002, -3.615226e-002, + -3.609632e-002, -3.604017e-002, -3.598370e-002, -3.592686e-002, -3.586971e-002, -3.581237e-002, -3.575475e-002, -3.569686e-002, -3.563889e-002, + -3.558077e-002, -3.552268e-002, -3.546456e-002, -3.540657e-002, -3.534881e-002, -3.529128e-002, -3.523379e-002, -3.517636e-002, -3.511898e-002, + -3.506159e-002, -3.500414e-002, -3.494666e-002, -3.488908e-002, -3.483150e-002, -3.477383e-002, -3.471608e-002, -3.465814e-002, -3.460010e-002, + -3.454205e-002, -3.448395e-002, -3.442572e-002, -3.436726e-002, -3.430844e-002, -3.424929e-002, -3.418989e-002, -3.413021e-002, -3.407043e-002, + -3.401052e-002, -3.395045e-002, -3.389023e-002, -3.382983e-002, -3.376928e-002, -3.370871e-002, -3.364813e-002, -3.358757e-002, -3.352692e-002, + -3.346630e-002, -3.340576e-002, -3.334525e-002, -3.328481e-002, -3.322440e-002, -3.316406e-002, -3.310386e-002, -3.304379e-002, -3.298377e-002, + -3.292377e-002, -3.286372e-002, -3.280355e-002, -3.274319e-002, -3.268266e-002, -3.262198e-002, -3.256111e-002, -3.250009e-002, -3.243891e-002, + -3.237754e-002, -3.231607e-002, -3.225447e-002, -3.219283e-002, -3.213101e-002, -3.206899e-002, -3.200680e-002, -3.194430e-002, -3.188145e-002, + -3.181831e-002, -3.175498e-002, -3.169153e-002, -3.162793e-002, -3.156425e-002, -3.150052e-002, -3.143672e-002, -3.137295e-002, -3.130926e-002, + -3.124574e-002, -3.118237e-002, -3.111912e-002, -3.105597e-002, -3.099290e-002, -3.092988e-002, -3.086698e-002, -3.080408e-002, -3.074124e-002, + -3.067840e-002, -3.061561e-002, -3.055281e-002, -3.048987e-002, -3.042686e-002, -3.036359e-002, -3.030020e-002, -3.023671e-002, -3.017308e-002, + -3.010935e-002, -3.004546e-002, -2.998135e-002, -2.991703e-002, -2.985247e-002, -2.978771e-002, -2.972264e-002, -2.965720e-002, -2.959146e-002, + -2.952556e-002, -2.945951e-002, -2.939339e-002, -2.932716e-002, -2.926086e-002, -2.919452e-002, -2.912804e-002, -2.906153e-002, -2.899507e-002, + -2.892880e-002, -2.886262e-002, -2.879658e-002, -2.873073e-002, -2.866496e-002, -2.859924e-002, -2.853353e-002, -2.846785e-002, -2.840220e-002, + -2.833658e-002, -2.827092e-002, -2.820524e-002, -2.813952e-002, -2.807372e-002, -2.800790e-002, -2.794213e-002, -2.787632e-002, -2.781050e-002, + -2.774459e-002, -2.767845e-002, -2.761209e-002, -2.754545e-002, -2.747860e-002, -2.741149e-002, -2.734402e-002, -2.727633e-002, -2.720847e-002, + -2.714041e-002, -2.707219e-002, -2.700379e-002, -2.693523e-002, -2.686658e-002, -2.679786e-002, -2.672913e-002, -2.666035e-002, -2.659160e-002, + -2.652289e-002, -2.645426e-002, -2.638563e-002, -2.631707e-002, -2.624859e-002, -2.618019e-002, -2.611194e-002, -2.604383e-002, -2.597575e-002, + -2.590775e-002, -2.583979e-002, -2.577181e-002, -2.570380e-002, -2.563576e-002, -2.556754e-002, -2.549921e-002, -2.543084e-002, -2.536250e-002, + -2.529422e-002, -2.522590e-002, -2.515742e-002, -2.508879e-002, -2.501993e-002, -2.495078e-002, -2.488133e-002, -2.481160e-002, -2.474170e-002, + -2.467165e-002, -2.460146e-002, -2.453125e-002, -2.446102e-002, -2.439082e-002, -2.432065e-002, -2.425063e-002, -2.418066e-002, -2.411074e-002, + -2.404082e-002, -2.397092e-002, -2.390089e-002, -2.383077e-002, -2.376058e-002, -2.369044e-002, -2.362035e-002, -2.355029e-002, -2.348017e-002, + -2.340993e-002, -2.333951e-002, -2.326897e-002, -2.319835e-002, -2.312768e-002, -2.305693e-002, -2.298606e-002, -2.291496e-002, -2.284367e-002, + -2.277219e-002, -2.270046e-002, -2.262853e-002, -2.255652e-002, -2.248440e-002, -2.241222e-002, -2.234001e-002, -2.226780e-002, -2.219553e-002, + -2.212314e-002, -2.205074e-002, -2.197841e-002, -2.190626e-002, -2.183424e-002, -2.176229e-002, -2.169028e-002, -2.161810e-002, -2.154568e-002, + -2.147305e-002, -2.140035e-002, -2.132762e-002, -2.125478e-002, -2.118175e-002, -2.110860e-002, -2.103531e-002, -2.096183e-002, -2.088818e-002, + -2.081438e-002, -2.074048e-002, -2.066639e-002, -2.059209e-002, -2.051752e-002, -2.044265e-002, -2.036751e-002, -2.029207e-002, -2.021638e-002, + -2.014058e-002, -2.006472e-002, -1.998890e-002, -1.991314e-002, -1.983744e-002, -1.976183e-002, -1.968627e-002, -1.961074e-002, -1.953539e-002, + -1.946019e-002, -1.938501e-002, -1.930989e-002, -1.923479e-002, -1.915966e-002, -1.908452e-002, -1.900944e-002, -1.893439e-002, -1.885926e-002, + -1.878408e-002, -1.870884e-002, -1.863348e-002, -1.855796e-002, -1.848231e-002, -1.840652e-002, -1.833057e-002, -1.825440e-002, -1.817798e-002, + -1.810133e-002, -1.802449e-002, -1.794747e-002, -1.787021e-002, -1.779264e-002, -1.771478e-002, -1.763670e-002, -1.755842e-002, -1.747999e-002, + -1.740148e-002, -1.732291e-002, -1.724431e-002, -1.716563e-002, -1.708696e-002, -1.700842e-002, -1.693010e-002, -1.685210e-002, -1.677433e-002, + -1.669677e-002, -1.661930e-002, -1.654190e-002, -1.646447e-002, -1.638702e-002, -1.630954e-002, -1.623204e-002, -1.615443e-002, -1.607676e-002, + -1.599907e-002, -1.592144e-002, -1.584389e-002, -1.576632e-002, -1.568863e-002, -1.561081e-002, -1.553288e-002, -1.545472e-002, -1.537633e-002, + -1.529772e-002, -1.521894e-002, -1.513999e-002, -1.506083e-002, -1.498161e-002, -1.490233e-002, -1.482310e-002, -1.474395e-002, -1.466488e-002, + -1.458589e-002, -1.450702e-002, -1.442836e-002, -1.434983e-002, -1.427133e-002, -1.419289e-002, -1.411455e-002, -1.403623e-002, -1.395785e-002, + -1.387951e-002, -1.380115e-002, -1.372280e-002, -1.364444e-002, -1.356610e-002, -1.348754e-002, -1.340874e-002, -1.332979e-002, -1.325070e-002, + -1.317143e-002, -1.309201e-002, -1.301245e-002, -1.293275e-002, -1.285271e-002, -1.277242e-002, -1.269182e-002, -1.261105e-002, -1.253009e-002, + -1.244900e-002, -1.236778e-002, -1.228641e-002, -1.220500e-002, -1.212366e-002, -1.204226e-002, -1.196090e-002, -1.187970e-002, -1.179848e-002, + -1.171736e-002, -1.163649e-002, -1.155585e-002, -1.147541e-002, -1.139521e-002, -1.131516e-002, -1.123521e-002, -1.115536e-002, -1.107554e-002, + -1.099559e-002, -1.091568e-002, -1.083583e-002, -1.075604e-002, -1.067644e-002, -1.059687e-002, -1.051720e-002, -1.043748e-002, -1.035779e-002, + -1.027803e-002, -1.019800e-002, -1.011781e-002, -1.003758e-002, -9.957239e-003, -9.876816e-003, -9.796439e-003, -9.716210e-003, -9.636026e-003, + -9.555834e-003, -9.475533e-003, -9.395003e-003, -9.314153e-003, -9.233184e-003, -9.152170e-003, -9.071143e-003, -8.990137e-003, -8.909167e-003, + -8.828271e-003, -8.747499e-003, -8.666663e-003, -8.585664e-003, -8.504367e-003, -8.422923e-003, -8.341348e-003, -8.259648e-003, -8.177829e-003, + -8.095776e-003, -8.013583e-003, -7.931298e-003, -7.848872e-003, -7.766315e-003, -7.683466e-003, -7.600526e-003, -7.517544e-003, -7.434385e-003, + -7.351137e-003, -7.267938e-003, -7.184759e-003, -7.101645e-003, -7.018527e-003, -6.935418e-003, -6.851983e-003, -6.768426e-003, -6.685034e-003, + -6.601836e-003, -6.518750e-003, -6.435825e-003, -6.353018e-003, -6.270365e-003, -6.187727e-003, -6.105019e-003, -6.022069e-003, -5.938993e-003, + -5.855980e-003, -5.773054e-003, -5.690185e-003, -5.607578e-003, -5.525072e-003, -5.442573e-003, -5.360150e-003, -5.277665e-003, -5.194958e-003, + -5.111897e-003, -5.028772e-003, -4.945543e-003, -4.862157e-003, -4.778586e-003, -4.694894e-003, -4.611060e-003, -4.527402e-003, -4.443631e-003, + -4.359563e-003, -4.274865e-003, -4.189983e-003, -4.104942e-003, -4.019846e-003, -3.934731e-003, -3.849588e-003, -3.764451e-003, -3.679434e-003, + -3.594517e-003, -3.509579e-003, -3.424345e-003, -3.339334e-003, -3.254477e-003, -3.169773e-003, -3.085151e-003, -3.000545e-003, -2.915934e-003, + -2.831347e-003, -2.746836e-003, -2.662332e-003, -2.577334e-003, -2.492046e-003, -2.406713e-003, -2.321474e-003, -2.236671e-003, -2.151859e-003, + -2.066829e-003, -1.981710e-003, -1.896570e-003, -1.811610e-003, -1.726102e-003, -1.639910e-003, -1.553454e-003, -1.466765e-003, -1.379805e-003, + -1.292726e-003, -1.205572e-003, -1.118955e-003, -1.032592e-003, -9.464431e-004, -8.607769e-004, -7.746067e-004, -6.883249e-004, -6.019335e-004, + -5.156037e-004, -4.293733e-004, -3.432325e-004, -2.572296e-004, -1.713395e-004, -8.560801e-005, -1.734677e-018}, + {0.000000e+000, -6.834736e-003, -1.030655e-002, -1.309653e-002, -1.550273e-002, -1.765338e-002, -1.961569e-002, -2.144469e-002, -2.316949e-002, + -2.480979e-002, -2.637446e-002, -2.787000e-002, -2.930205e-002, -3.067509e-002, -3.199528e-002, -3.326566e-002, -3.448766e-002, -3.566654e-002, + -3.680414e-002, -3.790353e-002, -3.896870e-002, -3.999961e-002, -4.099430e-002, -4.195820e-002, -4.289104e-002, -4.379444e-002, -4.467011e-002, + -4.551842e-002, -4.634050e-002, -4.713816e-002, -4.791264e-002, -4.866400e-002, -4.939394e-002, -5.010301e-002, -5.079321e-002, -5.146556e-002, + -5.212060e-002, -5.275808e-002, -5.338037e-002, -5.398612e-002, -5.457620e-002, -5.515033e-002, -5.570914e-002, -5.625304e-002, -5.678294e-002, + -5.729777e-002, -5.779807e-002, -5.828404e-002, -5.875630e-002, -5.921497e-002, -5.966021e-002, -6.009192e-002, -6.051124e-002, -6.091733e-002, + -6.131049e-002, -6.169107e-002, -6.205854e-002, -6.241281e-002, -6.275280e-002, -6.307833e-002, -6.339045e-002, -6.368931e-002, -6.397532e-002, + -6.424917e-002, -6.450968e-002, -6.475804e-002, -6.499499e-002, -6.522048e-002, -6.543458e-002, -6.563702e-002, -6.582738e-002, -6.600698e-002, + -6.617651e-002, -6.633596e-002, -6.648488e-002, -6.662203e-002, -6.674955e-002, -6.686704e-002, -6.697447e-002, -6.707293e-002, -6.716189e-002, + -6.724087e-002, -6.731006e-002, -6.737121e-002, -6.742258e-002, -6.746481e-002, -6.749928e-002, -6.752386e-002, -6.753829e-002, -6.754365e-002, + -6.754009e-002, -6.752833e-002, -6.750745e-002, -6.747733e-002, -6.743835e-002, -6.738998e-002, -6.733389e-002, -6.726971e-002, -6.719592e-002, + -6.711396e-002, -6.702469e-002, -6.692758e-002, -6.682206e-002, -6.670795e-002, -6.658773e-002, -6.646093e-002, -6.632665e-002, -6.618518e-002, + -6.603761e-002, -6.588456e-002, -6.572434e-002, -6.555916e-002, -6.538862e-002, -6.521215e-002, -6.502931e-002, -6.484248e-002, -6.464931e-002, + -6.445091e-002, -6.424875e-002, -6.404104e-002, -6.382964e-002, -6.361353e-002, -6.339300e-002, -6.316857e-002, -6.293894e-002, -6.270501e-002, + -6.246678e-002, -6.222511e-002, -6.198001e-002, -6.173081e-002, -6.147881e-002, -6.122315e-002, -6.096412e-002, -6.070220e-002, -6.043667e-002, + -6.016932e-002, -5.989955e-002, -5.962626e-002, -5.935165e-002, -5.907452e-002, -5.879482e-002, -5.851286e-002, -5.822720e-002, -5.793945e-002, + -5.764931e-002, -5.735722e-002, -5.706336e-002, -5.676615e-002, -5.646789e-002, -5.616717e-002, -5.586355e-002, -5.555858e-002, -5.525142e-002, + -5.494337e-002, -5.463375e-002, -5.432268e-002, -5.400878e-002, -5.369359e-002, -5.337638e-002, -5.305805e-002, -5.273846e-002, -5.241742e-002, + -5.209304e-002, -5.176789e-002, -5.144062e-002, -5.111253e-002, -5.078344e-002, -5.045272e-002, -5.011944e-002, -4.978481e-002, -4.944995e-002, + -4.911371e-002, -4.877491e-002, -4.843408e-002, -4.809225e-002, -4.774920e-002, -4.740392e-002, -4.705772e-002, -4.671063e-002, -4.636272e-002, + -4.601150e-002, -4.565978e-002, -4.530795e-002, -4.495361e-002, -4.459696e-002, -4.424074e-002, -4.388182e-002, -4.352130e-002, -4.316080e-002, + -4.279696e-002, -4.243225e-002, -4.206572e-002, -4.169662e-002, -4.132640e-002, -4.095428e-002, -4.058141e-002, -4.020654e-002, -3.983143e-002, + -3.945481e-002, -3.907752e-002, -3.869863e-002, -3.831916e-002, -3.793909e-002, -3.755856e-002, -3.717837e-002, -3.679812e-002, -3.641870e-002, + -3.603933e-002, -3.566199e-002, -3.528460e-002, -3.490798e-002, -3.453196e-002, -3.415788e-002, -3.378426e-002, -3.341166e-002, -3.303988e-002, + -3.266905e-002, -3.229894e-002, -3.192930e-002, -3.156158e-002, -3.119414e-002, -3.082719e-002, -3.046210e-002, -3.009774e-002, -2.973468e-002, + -2.937301e-002, -2.901148e-002, -2.865136e-002, -2.829204e-002, -2.793274e-002, -2.757493e-002, -2.721877e-002, -2.686271e-002, -2.650782e-002, + -2.615378e-002, -2.580023e-002, -2.544731e-002, -2.509595e-002, -2.474575e-002, -2.439674e-002, -2.404838e-002, -2.370154e-002, -2.335531e-002, + -2.301049e-002, -2.266754e-002, -2.232499e-002, -2.198264e-002, -2.164178e-002, -2.130193e-002, -2.096273e-002, -2.062418e-002, -2.028711e-002, + -1.995205e-002, -1.961796e-002, -1.928439e-002, -1.895179e-002, -1.862081e-002, -1.829111e-002, -1.796200e-002, -1.763426e-002, -1.730856e-002, + -1.698441e-002, -1.666101e-002, -1.633836e-002, -1.601732e-002, -1.569876e-002, -1.538064e-002, -1.506325e-002, -1.474730e-002, -1.443237e-002, + -1.411902e-002, -1.380695e-002, -1.349567e-002, -1.318542e-002, -1.287660e-002, -1.256888e-002, -1.226211e-002, -1.195664e-002, -1.165172e-002, + -1.134866e-002, -1.104682e-002, -1.074621e-002, -1.044633e-002, -1.014755e-002, -9.850819e-003, -9.554834e-003, -9.260157e-003, -8.967085e-003, + -8.674770e-003, -8.383864e-003, -8.094663e-003, -7.806046e-003, -7.518072e-003, -7.231338e-003, -6.945811e-003, -6.661126e-003, -6.377697e-003, + -6.095236e-003, -5.814124e-003, -5.534324e-003, -5.255688e-003, -4.978303e-003, -4.701866e-003, -4.426695e-003, -4.152214e-003, -3.878992e-003, + -3.607353e-003, -3.336666e-003, -3.067033e-003, -2.797994e-003, -2.529929e-003, -2.263697e-003, -1.998356e-003, -1.733835e-003, -1.470706e-003, + -1.208125e-003, -9.470783e-004, -6.878352e-004, -4.302868e-004, -1.744681e-004, 8.006053e-005, 3.337304e-004, 5.862463e-004, 8.373778e-004, + 1.086435e-003, 1.334537e-003, 1.581397e-003, 1.826863e-003, 2.071398e-003, 2.314104e-003, 2.555159e-003, 2.794924e-003, 3.033519e-003, + 3.270476e-003, 3.505720e-003, 3.739996e-003, 3.972842e-003, 4.204140e-003, 4.434708e-003, 4.664032e-003, 4.890714e-003, 5.116126e-003, + 5.340849e-003, 5.564195e-003, 5.786559e-003, 6.007642e-003, 6.226949e-003, 6.444732e-003, 6.661478e-003, 6.876964e-003, 7.091547e-003, + 7.304253e-003, 7.515924e-003, 7.726824e-003, 7.935763e-003, 8.142976e-003, 8.349168e-003, 8.553944e-003, 8.757340e-003, 8.959792e-003, + 9.161106e-003, 9.361287e-003, 9.559349e-003, 9.756497e-003, 9.952685e-003, 1.014762e-002, 1.034074e-002, 1.053309e-002, 1.072541e-002, + 1.091594e-002, 1.110504e-002, 1.129373e-002, 1.148156e-002, 1.166782e-002, 1.185313e-002, 1.203736e-002, 1.222075e-002, 1.240279e-002, + 1.258331e-002, 1.276309e-002, 1.294256e-002, 1.312040e-002, 1.329677e-002, 1.347249e-002, 1.364706e-002, 1.382051e-002, 1.399266e-002, + 1.416408e-002, 1.433441e-002, 1.450364e-002, 1.467111e-002, 1.483672e-002, 1.500138e-002, 1.516571e-002, 1.532860e-002, 1.549030e-002, + 1.565125e-002, 1.581128e-002, 1.596967e-002, 1.612637e-002, 1.628214e-002, 1.643701e-002, 1.659115e-002, 1.674467e-002, 1.689665e-002, + 1.704747e-002, 1.719766e-002, 1.734634e-002, 1.749310e-002, 1.763928e-002, 1.778497e-002, 1.792967e-002, 1.807319e-002, 1.821614e-002, + 1.835842e-002, 1.849997e-002, 1.864049e-002, 1.878001e-002, 1.891777e-002, 1.905389e-002, 1.918975e-002, 1.932466e-002, 1.945806e-002, + 1.959045e-002, 1.972178e-002, 1.985215e-002, 1.998132e-002, 2.010955e-002, 2.023659e-002, 2.036272e-002, 2.048797e-002, 2.061253e-002, + 2.073551e-002, 2.085671e-002, 2.097730e-002, 2.109728e-002, 2.121611e-002, 2.133362e-002, 2.145057e-002, 2.156713e-002, 2.168222e-002, + 2.179582e-002, 2.190811e-002, 2.201902e-002, 2.212963e-002, 2.223941e-002, 2.234774e-002, 2.245507e-002, 2.256168e-002, 2.266780e-002, + 2.277300e-002, 2.287686e-002, 2.298009e-002, 2.308251e-002, 2.318447e-002, 2.328479e-002, 2.338382e-002, 2.348174e-002, 2.357885e-002, + 2.367505e-002, 2.377069e-002, 2.386548e-002, 2.395961e-002, 2.405276e-002, 2.414502e-002, 2.423653e-002, 2.432725e-002, 2.441701e-002, + 2.450582e-002, 2.459374e-002, 2.468095e-002, 2.476778e-002, 2.485295e-002, 2.493731e-002, 2.502082e-002, 2.510419e-002, 2.518703e-002, + 2.526896e-002, 2.534992e-002, 2.543015e-002, 2.550961e-002, 2.558855e-002, 2.566690e-002, 2.574410e-002, 2.581993e-002, 2.589510e-002, + 2.596982e-002, 2.604419e-002, 2.611726e-002, 2.618891e-002, 2.625982e-002, 2.633075e-002, 2.640090e-002, 2.646978e-002, 2.653755e-002, + 2.660499e-002, 2.667202e-002, 2.673802e-002, 2.680315e-002, 2.686678e-002, 2.692976e-002, 2.699183e-002, 2.705374e-002, 2.711568e-002, + 2.717667e-002, 2.723653e-002, 2.729552e-002, 2.735416e-002, 2.741264e-002, 2.747052e-002, 2.752731e-002, 2.758320e-002, 2.763833e-002, + 2.769307e-002, 2.774696e-002, 2.780023e-002, 2.785280e-002, 2.790422e-002, 2.795501e-002, 2.800557e-002, 2.805546e-002, 2.810432e-002, + 2.815198e-002, 2.819804e-002, 2.824345e-002, 2.828880e-002, 2.833374e-002, 2.837770e-002, 2.842043e-002, 2.846258e-002, 2.850428e-002, + 2.854542e-002, 2.858598e-002, 2.862594e-002, 2.866495e-002, 2.870252e-002, 2.873966e-002, 2.877646e-002, 2.881295e-002, 2.884832e-002, + 2.888255e-002, 2.891574e-002, 2.894903e-002, 2.898223e-002, 2.901508e-002, 2.904705e-002, 2.907791e-002, 2.910819e-002, 2.913826e-002, + 2.916796e-002, 2.919735e-002, 2.922622e-002, 2.925391e-002, 2.928040e-002, 2.930629e-002, 2.933194e-002, 2.935745e-002, 2.938241e-002, + 2.940608e-002, 2.942892e-002, 2.945141e-002, 2.947362e-002, 2.949535e-002, 2.951647e-002, 2.953636e-002, 2.955591e-002, 2.957522e-002, + 2.959431e-002, 2.961273e-002, 2.963049e-002, 2.964725e-002, 2.966316e-002, 2.967857e-002, 2.969379e-002, 2.970889e-002, 2.972363e-002, + 2.973772e-002, 2.975090e-002, 2.976332e-002, 2.977532e-002, 2.978702e-002, 2.979864e-002, 2.980987e-002, 2.982054e-002, 2.983048e-002, + 2.983975e-002, 2.984855e-002, 2.985685e-002, 2.986464e-002, 2.987176e-002, 2.987795e-002, 2.988374e-002, 2.988937e-002, 2.989469e-002, + 2.989998e-002, 2.990470e-002, 2.990875e-002, 2.991192e-002, 2.991424e-002, 2.991637e-002, 2.991777e-002, 2.991885e-002, 2.991928e-002, + 2.991874e-002, 2.991738e-002, 2.991572e-002, 2.991373e-002, 2.991128e-002, 2.990860e-002, 2.990584e-002, 2.990235e-002, 2.989797e-002, + 2.989336e-002, 2.988847e-002, 2.988322e-002, 2.987778e-002, 2.987201e-002, 2.986577e-002, 2.985911e-002, 2.985255e-002, 2.984558e-002, + 2.983821e-002, 2.983035e-002, 2.982172e-002, 2.981263e-002, 2.980268e-002, 2.979215e-002, 2.978133e-002, 2.977045e-002, 2.975935e-002, + 2.974743e-002, 2.973463e-002, 2.972134e-002, 2.970785e-002, 2.969386e-002, 2.967948e-002, 2.966457e-002, 2.964874e-002, 2.963212e-002, + 2.961527e-002, 2.959817e-002, 2.958032e-002, 2.956216e-002, 2.954350e-002, 2.952412e-002, 2.950376e-002, 2.948252e-002, 2.946105e-002, + 2.943933e-002, 2.941770e-002, 2.939591e-002, 2.937378e-002, 2.935096e-002, 2.932764e-002, 2.930405e-002, 2.928041e-002, 2.925683e-002, + 2.923300e-002, 2.920860e-002, 2.918345e-002, 2.915766e-002, 2.913147e-002, 2.910496e-002, 2.907784e-002, 2.905027e-002, 2.902246e-002, + 2.899426e-002, 2.896544e-002, 2.893613e-002, 2.890619e-002, 2.887590e-002, 2.884529e-002, 2.881411e-002, 2.878199e-002, 2.874931e-002, + 2.871610e-002, 2.868268e-002, 2.864918e-002, 2.861521e-002, 2.858077e-002, 2.854600e-002, 2.851046e-002, 2.847440e-002, 2.843823e-002, + 2.840170e-002, 2.836473e-002, 2.832715e-002, 2.828919e-002, 2.825080e-002, 2.821174e-002, 2.817164e-002, 2.813137e-002, 2.809064e-002, + 2.804988e-002, 2.800906e-002, 2.796814e-002, 2.792655e-002, 2.788389e-002, 2.784049e-002, 2.779686e-002, 2.775313e-002, 2.770916e-002, + 2.766488e-002, 2.762008e-002, 2.757387e-002, 2.752694e-002, 2.748006e-002, 2.743309e-002, 2.738562e-002, 2.733757e-002, 2.728918e-002, + 2.724059e-002, 2.719126e-002, 2.714126e-002, 2.709101e-002, 2.704059e-002, 2.699002e-002, 2.693964e-002, 2.688949e-002, 2.683898e-002, + 2.678728e-002, 2.673486e-002, 2.668206e-002, 2.662915e-002, 2.657626e-002, 2.652307e-002, 2.646954e-002, 2.641526e-002, 2.636039e-002, + 2.630526e-002, 2.625021e-002, 2.619506e-002, 2.613945e-002, 2.608340e-002, 2.602684e-002, 2.596993e-002, 2.591262e-002, 2.585493e-002, + 2.579680e-002, 2.573835e-002, 2.567970e-002, 2.562103e-002, 2.556225e-002, 2.550317e-002, 2.544336e-002, 2.538317e-002, 2.532253e-002, + 2.526149e-002, 2.520069e-002, 2.513982e-002, 2.507873e-002, 2.501757e-002, 2.495623e-002, 2.489478e-002, 2.483325e-002, 2.477154e-002, + 2.470957e-002, 2.464714e-002, 2.458375e-002, 2.451995e-002, 2.445596e-002, 2.439214e-002, 2.432815e-002, 2.426368e-002, 2.419895e-002, + 2.413401e-002, 2.406879e-002, 2.400333e-002, 2.393762e-002, 2.387124e-002, 2.380421e-002, 2.373677e-002, 2.366924e-002, 2.360201e-002, + 2.353462e-002, 2.346708e-002, 2.339930e-002, 2.333108e-002, 2.326216e-002, 2.319319e-002, 2.312445e-002, 2.305565e-002, 2.298672e-002, + 2.291752e-002, 2.284820e-002, 2.277878e-002, 2.270904e-002, 2.263863e-002, 2.256796e-002, 2.249704e-002, 2.242582e-002, 2.235437e-002, + 2.228287e-002, 2.221145e-002, 2.213982e-002, 2.206778e-002, 2.199519e-002, 2.192199e-002, 2.184854e-002, 2.177484e-002, 2.170073e-002, + 2.162616e-002, 2.155125e-002, 2.147579e-002, 2.139931e-002, 2.132241e-002, 2.124553e-002, 2.116845e-002, 2.109140e-002, 2.101413e-002, + 2.093653e-002, 2.085859e-002, 2.078051e-002, 2.070170e-002, 2.062251e-002, 2.054327e-002, 2.046379e-002, 2.038417e-002, 2.030452e-002, + 2.022499e-002, 2.014550e-002, 2.006557e-002, 1.998500e-002, 1.990404e-002, 1.982261e-002, 1.974109e-002, 1.965972e-002, 1.957832e-002, + 1.949669e-002, 1.941454e-002, 1.933192e-002, 1.924860e-002, 1.916515e-002, 1.908171e-002, 1.899820e-002, 1.891452e-002, 1.883073e-002, + 1.874656e-002, 1.866178e-002, 1.857668e-002, 1.849097e-002, 1.840473e-002, 1.831818e-002, 1.823158e-002, 1.814483e-002, 1.805806e-002, + 1.797132e-002, 1.788475e-002, 1.779823e-002, 1.771130e-002, 1.762377e-002, 1.753588e-002, 1.744771e-002, 1.735949e-002, 1.727150e-002, + 1.718335e-002, 1.709513e-002, 1.700680e-002, 1.691792e-002, 1.682779e-002, 1.673734e-002, 1.664724e-002, 1.655709e-002, 1.646661e-002, + 1.637596e-002, 1.628514e-002, 1.619373e-002, 1.610176e-002, 1.600926e-002, 1.591590e-002, 1.582272e-002, 1.572961e-002, 1.563630e-002, + 1.554271e-002, 1.544886e-002, 1.535515e-002, 1.526130e-002, 1.516701e-002, 1.507180e-002, 1.497624e-002, 1.488052e-002, 1.478434e-002, + 1.468826e-002, 1.459273e-002, 1.449716e-002, 1.440158e-002, 1.430571e-002, 1.420940e-002, 1.411214e-002, 1.401480e-002, 1.391760e-002, + 1.382018e-002, 1.372267e-002, 1.362512e-002, 1.352760e-002, 1.343008e-002, 1.333228e-002, 1.323431e-002, 1.313591e-002, 1.303727e-002, + 1.293852e-002, 1.283950e-002, 1.274032e-002, 1.264080e-002, 1.254123e-002, 1.244169e-002, 1.234195e-002, 1.224205e-002, 1.214193e-002, + 1.204189e-002, 1.194184e-002, 1.184131e-002, 1.174076e-002, 1.164014e-002, 1.153936e-002, 1.143851e-002, 1.133759e-002, 1.123653e-002, + 1.113548e-002, 1.103454e-002, 1.093387e-002, 1.083302e-002, 1.073188e-002, 1.063088e-002, 1.052970e-002, 1.042837e-002, 1.032659e-002, + 1.022455e-002, 1.012248e-002, 1.002035e-002, 9.918116e-003, 9.815804e-003, 9.713331e-003, 9.610621e-003, 9.507541e-003, 9.404811e-003, + 9.301925e-003, 9.198768e-003, 9.094977e-003, 8.991043e-003, 8.887070e-003, 8.782970e-003, 8.678326e-003, 8.573843e-003, 8.469633e-003, + 8.365521e-003, 8.261370e-003, 8.157106e-003, 8.052351e-003, 7.947350e-003, 7.842282e-003, 7.737671e-003, 7.632770e-003, 7.527568e-003, + 7.422291e-003, 7.316904e-003, 7.211409e-003, 7.106033e-003, 7.000307e-003, 6.894502e-003, 6.788705e-003, 6.682880e-003, 6.576743e-003, + 6.470157e-003, 6.363435e-003, 6.256815e-003, 6.150156e-003, 6.043626e-003, 5.936934e-003, 5.829892e-003, 5.723172e-003, 5.616471e-003, + 5.509550e-003, 5.401966e-003, 5.294037e-003, 5.186386e-003, 5.078716e-003, 4.971001e-003, 4.863286e-003, 4.755597e-003, 4.647307e-003, + 4.538980e-003, 4.430595e-003, 4.322479e-003, 4.214607e-003, 4.106767e-003, 3.998947e-003, 3.891184e-003, 3.783453e-003, 3.675667e-003, + 3.567486e-003, 3.459373e-003, 3.351252e-003, 3.242571e-003, 3.134150e-003, 3.025716e-003, 2.917545e-003, 2.810184e-003, 2.702678e-003, + 2.595736e-003, 2.488716e-003, 2.381184e-003, 2.272856e-003, 2.164048e-003, 2.054665e-003, 1.945212e-003, 1.835736e-003, 1.726726e-003, + 1.618062e-003, 1.509930e-003, 1.402651e-003, 1.295712e-003, 1.188421e-003, 1.080173e-003, 9.714036e-004, 8.630221e-004, 7.551179e-004, + 6.472019e-004, 5.393068e-004, 4.314098e-004, 3.235321e-004, 2.156618e-004, 1.078233e-004, 1.817274e-018}, + {0.000000e+000, -1.492129e-002, -2.127088e-002, -2.618806e-002, -3.030776e-002, -3.390256e-002, -3.710799e-002, -4.003321e-002, -4.274783e-002, + -4.528460e-002, -4.765963e-002, -4.988519e-002, -5.197049e-002, -5.392217e-002, -5.575409e-002, -5.747167e-002, -5.908103e-002, -6.059303e-002, + -6.201298e-002, -6.335102e-002, -6.461079e-002, -6.579581e-002, -6.690524e-002, -6.794801e-002, -6.892519e-002, -6.984189e-002, -7.069960e-002, + -7.150041e-002, -7.224501e-002, -7.293687e-002, -7.357614e-002, -7.416297e-002, -7.469762e-002, -7.517955e-002, -7.561567e-002, -7.600640e-002, + -7.634987e-002, -7.664419e-002, -7.689663e-002, -7.710327e-002, -7.726759e-002, -7.738455e-002, -7.745873e-002, -7.749457e-002, -7.748945e-002, + -7.744165e-002, -7.735865e-002, -7.723364e-002, -7.707189e-002, -7.687827e-002, -7.664647e-002, -7.637822e-002, -7.608302e-002, -7.575068e-002, + -7.538839e-002, -7.499824e-002, -7.457771e-002, -7.412979e-002, -7.364984e-002, -7.314172e-002, -7.260533e-002, -7.204270e-002, -7.145579e-002, + -7.084881e-002, -7.021362e-002, -6.955401e-002, -6.887651e-002, -6.817895e-002, -6.746294e-002, -6.672660e-002, -6.597115e-002, -6.519937e-002, + -6.441377e-002, -6.361464e-002, -6.280368e-002, -6.197680e-002, -6.113930e-002, -6.029036e-002, -5.943020e-002, -5.856084e-002, -5.768074e-002, + -5.678807e-002, -5.588325e-002, -5.497403e-002, -5.405088e-002, -5.311932e-002, -5.218269e-002, -5.123219e-002, -5.027069e-002, -4.930252e-002, + -4.832386e-002, -4.733829e-002, -4.634025e-002, -4.533376e-002, -4.432209e-002, -4.329936e-002, -4.226919e-002, -4.123282e-002, -4.018882e-002, + -3.913876e-002, -3.808507e-002, -3.702615e-002, -3.596504e-002, -3.490341e-002, -3.384366e-002, -3.278206e-002, -3.171784e-002, -3.065539e-002, + -2.959573e-002, -2.854068e-002, -2.748243e-002, -2.643014e-002, -2.538179e-002, -2.433511e-002, -2.329353e-002, -2.225788e-002, -2.122554e-002, + -2.019842e-002, -1.918127e-002, -1.816785e-002, -1.716183e-002, -1.616060e-002, -1.516746e-002, -1.418151e-002, -1.320141e-002, -1.222747e-002, + -1.126370e-002, -1.030776e-002, -9.360055e-003, -8.420152e-003, -7.488138e-003, -6.566507e-003, -5.655479e-003, -4.756311e-003, -3.866571e-003, + -2.990216e-003, -2.126094e-003, -1.267782e-003, -4.210361e-004, 4.168841e-004, 1.246749e-003, 2.066966e-003, 2.877267e-003, 3.679979e-003, + 4.472107e-003, 5.252754e-003, 6.023304e-003, 6.788588e-003, 7.544078e-003, 8.288665e-003, 9.025675e-003, 9.753873e-003, 1.047475e-002, + 1.118661e-002, 1.188755e-002, 1.258288e-002, 1.326729e-002, 1.394282e-002, 1.461229e-002, 1.527265e-002, 1.592762e-002, 1.657146e-002, + 1.720636e-002, 1.782967e-002, 1.844406e-002, 1.904969e-002, 1.964527e-002, 2.023076e-002, 2.081047e-002, 2.138261e-002, 2.194487e-002, + 2.250196e-002, 2.304905e-002, 2.358819e-002, 2.411839e-002, 2.463960e-002, 2.514865e-002, 2.565030e-002, 2.614183e-002, 2.662619e-002, + 2.710025e-002, 2.756598e-002, 2.802123e-002, 2.846987e-002, 2.891075e-002, 2.934243e-002, 2.976562e-002, 3.018131e-002, 3.058678e-002, + 3.098458e-002, 3.137367e-002, 3.175329e-002, 3.212513e-002, 3.248639e-002, 3.283770e-002, 3.318035e-002, 3.351670e-002, 3.384299e-002, + 3.416177e-002, 3.447175e-002, 3.477535e-002, 3.507138e-002, 3.536013e-002, 3.564247e-002, 3.591487e-002, 3.617964e-002, 3.643723e-002, + 3.668991e-002, 3.693380e-002, 3.717302e-002, 3.740944e-002, 3.763660e-002, 3.785714e-002, 3.806982e-002, 3.827548e-002, 3.847419e-002, + 3.866396e-002, 3.885067e-002, 3.903115e-002, 3.920685e-002, 3.937780e-002, 3.954491e-002, 3.970796e-002, 3.986178e-002, 4.001373e-002, + 4.015855e-002, 4.029890e-002, 4.043529e-002, 4.056528e-002, 4.069092e-002, 4.081260e-002, 4.092676e-002, 4.103873e-002, 4.114570e-002, + 4.124747e-002, 4.134345e-002, 4.143188e-002, 4.151645e-002, 4.159395e-002, 4.166355e-002, 4.173055e-002, 4.179073e-002, 4.184467e-002, + 4.189187e-002, 4.193239e-002, 4.196968e-002, 4.200237e-002, 4.202886e-002, 4.205290e-002, 4.207137e-002, 4.208656e-002, 4.209566e-002, + 4.209963e-002, 4.209968e-002, 4.209733e-002, 4.208945e-002, 4.207928e-002, 4.206520e-002, 4.204744e-002, 4.202663e-002, 4.199872e-002, + 4.196584e-002, 4.193164e-002, 4.189285e-002, 4.184639e-002, 4.179631e-002, 4.174293e-002, 4.168768e-002, 4.162983e-002, 4.156668e-002, + 4.149975e-002, 4.142993e-002, 4.135414e-002, 4.127279e-002, 4.119022e-002, 4.110327e-002, 4.101329e-002, 4.092093e-002, 4.082437e-002, + 4.072230e-002, 4.061815e-002, 4.051180e-002, 4.040312e-002, 4.029203e-002, 4.017848e-002, 4.006180e-002, 3.994356e-002, 3.982271e-002, + 3.969911e-002, 3.957502e-002, 3.945107e-002, 3.932636e-002, 3.919872e-002, 3.906977e-002, 3.893930e-002, 3.880742e-002, 3.867382e-002, + 3.853734e-002, 3.839708e-002, 3.825433e-002, 3.811106e-002, 3.796525e-002, 3.781681e-002, 3.766634e-002, 3.751375e-002, 3.735865e-002, + 3.720277e-002, 3.704359e-002, 3.688229e-002, 3.671925e-002, 3.655369e-002, 3.638574e-002, 3.621416e-002, 3.604147e-002, 3.586678e-002, + 3.569232e-002, 3.551728e-002, 3.534102e-002, 3.516244e-002, 3.498119e-002, 3.479838e-002, 3.461231e-002, 3.442526e-002, 3.423686e-002, + 3.404634e-002, 3.385433e-002, 3.366179e-002, 3.346944e-002, 3.327630e-002, 3.308278e-002, 3.288709e-002, 3.268883e-002, 3.248765e-002, + 3.228683e-002, 3.208361e-002, 3.187810e-002, 3.166924e-002, 3.146022e-002, 3.124957e-002, 3.103519e-002, 3.081974e-002, 3.060306e-002, + 3.038578e-002, 3.016596e-002, 2.994620e-002, 2.972404e-002, 2.949999e-002, 2.927360e-002, 2.904702e-002, 2.882051e-002, 2.859334e-002, + 2.836347e-002, 2.813350e-002, 2.790291e-002, 2.767057e-002, 2.743701e-002, 2.720281e-002, 2.696625e-002, 2.673157e-002, 2.649659e-002, + 2.626168e-002, 2.602569e-002, 2.578910e-002, 2.555118e-002, 2.531368e-002, 2.507698e-002, 2.484014e-002, 2.460235e-002, 2.436583e-002, + 2.412815e-002, 2.388920e-002, 2.365033e-002, 2.341059e-002, 2.316970e-002, 2.292831e-002, 2.268420e-002, 2.244054e-002, 2.219756e-002, + 2.195360e-002, 2.170899e-002, 2.146600e-002, 2.122240e-002, 2.097791e-002, 2.073426e-002, 2.049191e-002, 2.025015e-002, 2.000811e-002, + 1.976600e-002, 1.952313e-002, 1.928183e-002, 1.904155e-002, 1.880019e-002, 1.855812e-002, 1.831728e-002, 1.807667e-002, 1.783590e-002, + 1.759459e-002, 1.735397e-002, 1.711338e-002, 1.687298e-002, 1.663316e-002, 1.639232e-002, 1.615232e-002, 1.591339e-002, 1.567317e-002, + 1.543240e-002, 1.519348e-002, 1.495407e-002, 1.471271e-002, 1.447120e-002, 1.423002e-002, 1.398939e-002, 1.374940e-002, 1.350901e-002, + 1.326724e-002, 1.302598e-002, 1.278601e-002, 1.254618e-002, 1.230531e-002, 1.206388e-002, 1.182352e-002, 1.158297e-002, 1.134161e-002, + 1.110085e-002, 1.085994e-002, 1.061897e-002, 1.037977e-002, 1.014137e-002, 9.901052e-003, 9.659893e-003, 9.419405e-003, 9.179110e-003, + 8.937359e-003, 8.695699e-003, 8.456020e-003, 8.215845e-003, 7.975956e-003, 7.736163e-003, 7.496472e-003, 7.256680e-003, 7.015320e-003, + 6.775422e-003, 6.534686e-003, 6.293493e-003, 6.053075e-003, 5.813559e-003, 5.574828e-003, 5.336566e-003, 5.099228e-003, 4.863776e-003, + 4.628290e-003, 4.393131e-003, 4.158830e-003, 3.925326e-003, 3.692849e-003, 3.459805e-003, 3.227877e-003, 2.996216e-003, 2.765209e-003, + 2.535830e-003, 2.308013e-003, 2.081011e-003, 1.854741e-003, 1.629909e-003, 1.406858e-003, 1.184176e-003, 9.618576e-004, 7.402950e-004, + 5.197663e-004, 2.995273e-004, 8.047108e-005, -1.378609e-004, -3.561288e-004, -5.742468e-004, -7.912948e-004, -1.006561e-003, -1.221347e-003, + -1.436459e-003, -1.652513e-003, -1.867854e-003, -2.082229e-003, -2.296697e-003, -2.511206e-003, -2.725272e-003, -2.938945e-003, -3.151963e-003, + -3.364068e-003, -3.575356e-003, -3.785537e-003, -3.996126e-003, -4.205662e-003, -4.413863e-003, -4.620833e-003, -4.828086e-003, -5.034579e-003, + -5.239381e-003, -5.442322e-003, -5.644325e-003, -5.846386e-003, -6.048032e-003, -6.249134e-003, -6.448037e-003, -6.646043e-003, -6.841923e-003, + -7.036523e-003, -7.230505e-003, -7.423379e-003, -7.614369e-003, -7.804266e-003, -7.992173e-003, -8.178170e-003, -8.364124e-003, -8.549087e-003, + -8.733346e-003, -8.918920e-003, -9.103168e-003, -9.287606e-003, -9.472283e-003, -9.655203e-003, -9.837420e-003, -1.001970e-002, -1.020096e-002, + -1.038267e-002, -1.056340e-002, -1.074229e-002, -1.091976e-002, -1.109704e-002, -1.127340e-002, -1.144945e-002, -1.162365e-002, -1.179651e-002, + -1.196920e-002, -1.214148e-002, -1.231243e-002, -1.248266e-002, -1.265180e-002, -1.281936e-002, -1.298695e-002, -1.315444e-002, -1.332086e-002, + -1.348694e-002, -1.365192e-002, -1.381604e-002, -1.398117e-002, -1.414626e-002, -1.431091e-002, -1.447517e-002, -1.463846e-002, -1.480160e-002, + -1.496485e-002, -1.512775e-002, -1.528922e-002, -1.544980e-002, -1.560926e-002, -1.576820e-002, -1.592641e-002, -1.608491e-002, -1.624321e-002, + -1.640017e-002, -1.655687e-002, -1.671230e-002, -1.686651e-002, -1.702076e-002, -1.717454e-002, -1.732689e-002, -1.747800e-002, -1.762782e-002, + -1.777712e-002, -1.792688e-002, -1.807691e-002, -1.822576e-002, -1.837435e-002, -1.852222e-002, -1.866919e-002, -1.881573e-002, -1.896344e-002, + -1.911117e-002, -1.925852e-002, -1.940528e-002, -1.955111e-002, -1.969716e-002, -1.984384e-002, -1.999016e-002, -2.013557e-002, -2.028047e-002, + -2.042520e-002, -2.057056e-002, -2.071559e-002, -2.085892e-002, -2.100088e-002, -2.114227e-002, -2.128283e-002, -2.142158e-002, -2.155918e-002, + -2.169606e-002, -2.183246e-002, -2.196871e-002, -2.210460e-002, -2.223865e-002, -2.237185e-002, -2.250417e-002, -2.263552e-002, -2.276588e-002, + -2.289492e-002, -2.302223e-002, -2.314923e-002, -2.327584e-002, -2.340239e-002, -2.352801e-002, -2.365306e-002, -2.377671e-002, -2.389804e-002, + -2.401916e-002, -2.413960e-002, -2.425943e-002, -2.437922e-002, -2.449893e-002, -2.461832e-002, -2.473598e-002, -2.485238e-002, -2.496896e-002, + -2.508484e-002, -2.520058e-002, -2.531536e-002, -2.542905e-002, -2.554121e-002, -2.565210e-002, -2.576196e-002, -2.586960e-002, -2.597567e-002, + -2.608072e-002, -2.618498e-002, -2.628824e-002, -2.639022e-002, -2.648999e-002, -2.658812e-002, -2.668522e-002, -2.678123e-002, -2.687642e-002, + -2.696982e-002, -2.706123e-002, -2.715124e-002, -2.724066e-002, -2.732941e-002, -2.741711e-002, -2.750363e-002, -2.758979e-002, -2.767590e-002, + -2.776181e-002, -2.784758e-002, -2.793144e-002, -2.801439e-002, -2.809756e-002, -2.818080e-002, -2.826359e-002, -2.834509e-002, -2.842487e-002, + -2.850355e-002, -2.858192e-002, -2.866000e-002, -2.873745e-002, -2.881340e-002, -2.888830e-002, -2.896095e-002, -2.903270e-002, -2.910387e-002, + -2.917398e-002, -2.924238e-002, -2.930963e-002, -2.937588e-002, -2.944073e-002, -2.950452e-002, -2.956450e-002, -2.962310e-002, -2.968106e-002, + -2.973823e-002, -2.979351e-002, -2.984713e-002, -2.989988e-002, -2.995041e-002, -3.000066e-002, -3.005056e-002, -3.010012e-002, -3.014840e-002, + -3.019567e-002, -3.024223e-002, -3.028617e-002, -3.032814e-002, -3.036954e-002, -3.041102e-002, -3.045222e-002, -3.049327e-002, -3.053466e-002, + -3.057574e-002, -3.061549e-002, -3.065404e-002, -3.069146e-002, -3.072746e-002, -3.076229e-002, -3.079727e-002, -3.083157e-002, -3.086484e-002, + -3.089665e-002, -3.092718e-002, -3.095631e-002, -3.098415e-002, -3.101104e-002, -3.103658e-002, -3.106086e-002, -3.108326e-002, -3.110436e-002, + -3.112487e-002, -3.114376e-002, -3.116162e-002, -3.117918e-002, -3.119660e-002, -3.121399e-002, -3.123114e-002, -3.124711e-002, -3.126241e-002, + -3.127668e-002, -3.129050e-002, -3.130314e-002, -3.131486e-002, -3.132576e-002, -3.133567e-002, -3.134454e-002, -3.135222e-002, -3.135904e-002, + -3.136564e-002, -3.137218e-002, -3.137862e-002, -3.138390e-002, -3.138817e-002, -3.139153e-002, -3.139400e-002, -3.139580e-002, -3.139690e-002, + -3.139783e-002, -3.139858e-002, -3.139891e-002, -3.139831e-002, -3.139585e-002, -3.139237e-002, -3.138806e-002, -3.138327e-002, -3.137886e-002, + -3.137495e-002, -3.137116e-002, -3.136708e-002, -3.136174e-002, -3.135583e-002, -3.135000e-002, -3.134365e-002, -3.133670e-002, -3.133094e-002, + -3.132526e-002, -3.131825e-002, -3.130912e-002, -3.129914e-002, -3.128862e-002, -3.127698e-002, -3.126509e-002, -3.125428e-002, -3.124434e-002, + -3.123373e-002, -3.122201e-002, -3.120911e-002, -3.119552e-002, -3.118113e-002, -3.116580e-002, -3.114961e-002, -3.113421e-002, -3.111750e-002, + -3.110020e-002, -3.108284e-002, -3.106499e-002, -3.104645e-002, -3.102719e-002, -3.100783e-002, -3.098805e-002, -3.096867e-002, -3.094856e-002, + -3.092767e-002, -3.090604e-002, -3.088390e-002, -3.086107e-002, -3.083835e-002, -3.081606e-002, -3.079334e-002, -3.076981e-002, -3.074538e-002, + -3.071995e-002, -3.069313e-002, -3.066548e-002, -3.063684e-002, -3.060647e-002, -3.057544e-002, -3.054392e-002, -3.051034e-002, -3.047645e-002, + -3.044176e-002, -3.040569e-002, -3.036833e-002, -3.032999e-002, -3.029101e-002, -3.025124e-002, -3.021057e-002, -3.016863e-002, -3.012580e-002, + -3.008211e-002, -3.003763e-002, -2.999231e-002, -2.994693e-002, -2.990135e-002, -2.985585e-002, -2.980960e-002, -2.976303e-002, -2.971655e-002, + -2.966943e-002, -2.962093e-002, -2.957121e-002, -2.952121e-002, -2.947046e-002, -2.941903e-002, -2.936703e-002, -2.931414e-002, -2.926076e-002, + -2.920690e-002, -2.915168e-002, -2.909498e-002, -2.903775e-002, -2.897961e-002, -2.892032e-002, -2.886049e-002, -2.879999e-002, -2.873869e-002, + -2.867589e-002, -2.861158e-002, -2.854613e-002, -2.847926e-002, -2.841161e-002, -2.834415e-002, -2.827651e-002, -2.820831e-002, -2.813997e-002, + -2.807145e-002, -2.800227e-002, -2.793237e-002, -2.786218e-002, -2.779120e-002, -2.771971e-002, -2.764775e-002, -2.757554e-002, -2.750195e-002, + -2.742777e-002, -2.735320e-002, -2.727702e-002, -2.719959e-002, -2.712069e-002, -2.704102e-002, -2.695988e-002, -2.687810e-002, -2.679571e-002, + -2.671273e-002, -2.662901e-002, -2.654375e-002, -2.645708e-002, -2.636810e-002, -2.627746e-002, -2.618469e-002, -2.609053e-002, -2.599556e-002, + -2.589976e-002, -2.580349e-002, -2.570639e-002, -2.560821e-002, -2.550845e-002, -2.540844e-002, -2.530785e-002, -2.520559e-002, -2.510217e-002, + -2.499837e-002, -2.489339e-002, -2.478844e-002, -2.468330e-002, -2.457710e-002, -2.446992e-002, -2.436192e-002, -2.425277e-002, -2.414230e-002, + -2.403049e-002, -2.391828e-002, -2.380561e-002, -2.369251e-002, -2.357840e-002, -2.346281e-002, -2.334630e-002, -2.322889e-002, -2.310974e-002, + -2.298976e-002, -2.286840e-002, -2.274575e-002, -2.262220e-002, -2.249813e-002, -2.237310e-002, -2.224729e-002, -2.212094e-002, -2.199337e-002, + -2.186401e-002, -2.173349e-002, -2.160165e-002, -2.146829e-002, -2.133448e-002, -2.119999e-002, -2.106507e-002, -2.092976e-002, -2.079391e-002, + -2.065639e-002, -2.051733e-002, -2.037726e-002, -2.023626e-002, -2.009377e-002, -1.995007e-002, -1.980568e-002, -1.966048e-002, -1.951412e-002, + -1.936651e-002, -1.921762e-002, -1.906792e-002, -1.891756e-002, -1.876641e-002, -1.861296e-002, -1.845867e-002, -1.830355e-002, -1.814728e-002, + -1.799036e-002, -1.783077e-002, -1.767085e-002, -1.751058e-002, -1.734999e-002, -1.718899e-002, -1.702579e-002, -1.686109e-002, -1.669571e-002, + -1.652990e-002, -1.636388e-002, -1.619736e-002, -1.603026e-002, -1.586195e-002, -1.569253e-002, -1.552149e-002, -1.534850e-002, -1.517396e-002, + -1.499892e-002, -1.482333e-002, -1.464706e-002, -1.446959e-002, -1.429032e-002, -1.410958e-002, -1.392775e-002, -1.374517e-002, -1.356104e-002, + -1.337471e-002, -1.318766e-002, -1.299876e-002, -1.280845e-002, -1.261703e-002, -1.242486e-002, -1.223155e-002, -1.203656e-002, -1.184078e-002, + -1.164411e-002, -1.144457e-002, -1.124374e-002, -1.104176e-002, -1.083946e-002, -1.063695e-002, -1.043423e-002, -1.023041e-002, -1.002511e-002, + -9.819270e-003, -9.612281e-003, -9.401320e-003, -9.189639e-003, -8.978009e-003, -8.766439e-003, -8.554695e-003, -8.342656e-003, -8.130345e-003, + -7.916270e-003, -7.699817e-003, -7.482708e-003, -7.262805e-003, -7.040255e-003, -6.817261e-003, -6.593612e-003, -6.368892e-003, -6.143592e-003, + -5.916395e-003, -5.688754e-003, -5.459543e-003, -5.228324e-003, -4.994517e-003, -4.757789e-003, -4.520199e-003, -4.281770e-003, -4.040579e-003, + -3.798196e-003, -3.554220e-003, -3.309345e-003, -3.064056e-003, -2.817498e-003, -2.566793e-003, -2.312722e-003, -2.057098e-003, -1.799981e-003, + -1.542788e-003, -1.285585e-003, -1.028354e-003, -7.711852e-004, -5.140255e-004, -2.569877e-004, -7.472155e-018}, + {0.000000e+000, -3.686752e-002, -4.707916e-002, -5.424298e-002, -5.974686e-002, -6.421704e-002, -6.809730e-002, -7.150207e-002, -7.456737e-002, + -7.735639e-002, -7.991997e-002, -8.227440e-002, -8.439895e-002, -8.625641e-002, -8.779916e-002, -8.904610e-002, -9.001451e-002, -9.072532e-002, + -9.124275e-002, -9.158673e-002, -9.182539e-002, -9.200225e-002, -9.208038e-002, -9.210195e-002, -9.201929e-002, -9.184150e-002, -9.158301e-002, + -9.123810e-002, -9.083478e-002, -9.035514e-002, -8.977864e-002, -8.910458e-002, -8.833068e-002, -8.745202e-002, -8.646511e-002, -8.537988e-002, + -8.419200e-002, -8.287240e-002, -8.145336e-002, -7.994302e-002, -7.834295e-002, -7.665127e-002, -7.490048e-002, -7.308915e-002, -7.122782e-002, + -6.932175e-002, -6.738629e-002, -6.542944e-002, -6.343609e-002, -6.141668e-002, -5.936762e-002, -5.726929e-002, -5.513005e-002, -5.294581e-002, + -5.074574e-002, -4.853045e-002, -4.630711e-002, -4.409634e-002, -4.189540e-002, -3.972582e-002, -3.758626e-002, -3.549378e-002, -3.341818e-002, + -3.135686e-002, -2.930771e-002, -2.727640e-002, -2.526127e-002, -2.327324e-002, -2.131527e-002, -1.937966e-002, -1.746311e-002, -1.557883e-002, + -1.372572e-002, -1.189704e-002, -1.009320e-002, -8.330456e-003, -6.600937e-003, -4.892731e-003, -3.204983e-003, -1.539323e-003, 1.087786e-004, + 1.741523e-003, 3.357796e-003, 4.946539e-003, 6.526105e-003, 8.087222e-003, 9.623635e-003, 1.114311e-002, 1.263078e-002, 1.409044e-002, + 1.553051e-002, 1.693758e-002, 1.832100e-002, 1.967297e-002, 2.099259e-002, 2.228373e-002, 2.354167e-002, 2.476117e-002, 2.593983e-002, + 2.709688e-002, 2.822413e-002, 2.932345e-002, 3.039570e-002, 3.144278e-002, 3.247026e-002, 3.347581e-002, 3.445661e-002, 3.541730e-002, + 3.635977e-002, 3.728128e-002, 3.817157e-002, 3.903504e-002, 3.988187e-002, 4.070002e-002, 4.147968e-002, 4.222756e-002, 4.294408e-002, + 4.363506e-002, 4.429347e-002, 4.491849e-002, 4.550622e-002, 4.606525e-002, 4.659057e-002, 4.708795e-002, 4.755407e-002, 4.798880e-002, + 4.839570e-002, 4.878390e-002, 4.914796e-002, 4.949170e-002, 4.981731e-002, 5.012803e-002, 5.041794e-002, 5.068709e-002, 5.093673e-002, + 5.117082e-002, 5.139053e-002, 5.160546e-002, 5.180635e-002, 5.198778e-002, 5.216079e-002, 5.232146e-002, 5.245521e-002, 5.258058e-002, + 5.268417e-002, 5.277219e-002, 5.284493e-002, 5.289987e-002, 5.294295e-002, 5.295232e-002, 5.293886e-002, 5.290820e-002, 5.285789e-002, + 5.278694e-002, 5.269092e-002, 5.258416e-002, 5.245384e-002, 5.230755e-002, 5.215269e-002, 5.197809e-002, 5.179503e-002, 5.160154e-002, + 5.138270e-002, 5.115835e-002, 5.092048e-002, 5.066528e-002, 5.040347e-002, 5.012365e-002, 4.983004e-002, 4.953082e-002, 4.922354e-002, + 4.890520e-002, 4.855925e-002, 4.820714e-002, 4.784969e-002, 4.748517e-002, 4.708991e-002, 4.668237e-002, 4.626548e-002, 4.584670e-002, + 4.539089e-002, 4.493146e-002, 4.446371e-002, 4.396844e-002, 4.345779e-002, 4.294188e-002, 4.240077e-002, 4.184042e-002, 4.127357e-002, + 4.066929e-002, 4.005822e-002, 3.942501e-002, 3.876983e-002, 3.809920e-002, 3.740036e-002, 3.669153e-002, 3.595514e-002, 3.521205e-002, + 3.445416e-002, 3.368468e-002, 3.290001e-002, 3.210693e-002, 3.130610e-002, 3.049788e-002, 2.969452e-002, 2.888758e-002, 2.808967e-002, + 2.729010e-002, 2.650343e-002, 2.571092e-002, 2.492943e-002, 2.414864e-002, 2.338439e-002, 2.262252e-002, 2.186761e-002, 2.111868e-002, + 2.037299e-002, 1.963561e-002, 1.889743e-002, 1.817550e-002, 1.745468e-002, 1.673810e-002, 1.603273e-002, 1.532968e-002, 1.463230e-002, + 1.393855e-002, 1.324819e-002, 1.256748e-002, 1.189409e-002, 1.121913e-002, 1.055384e-002, 9.891197e-003, 9.226005e-003, 8.570603e-003, + 7.924821e-003, 7.278813e-003, 6.636009e-003, 6.003245e-003, 5.370034e-003, 4.736873e-003, 4.114991e-003, 3.497752e-003, 2.884234e-003, + 2.272889e-003, 1.669924e-003, 1.068295e-003, 4.689074e-004, -1.273354e-004, -7.135699e-004, -1.299002e-003, -1.884465e-003, -2.465799e-003, + -3.035150e-003, -3.602649e-003, -4.165791e-003, -4.726458e-003, -5.272796e-003, -5.815489e-003, -6.352506e-003, -6.881806e-003, -7.399020e-003, + -7.909706e-003, -8.412922e-003, -8.907683e-003, -9.398501e-003, -9.875158e-003, -1.034606e-002, -1.081212e-002, -1.127346e-002, -1.172512e-002, + -1.216630e-002, -1.260043e-002, -1.302984e-002, -1.345338e-002, -1.386666e-002, -1.427369e-002, -1.467342e-002, -1.506746e-002, -1.545470e-002, + -1.583157e-002, -1.620119e-002, -1.656596e-002, -1.692563e-002, -1.728027e-002, -1.762665e-002, -1.796578e-002, -1.829915e-002, -1.862659e-002, + -1.894976e-002, -1.926897e-002, -1.958175e-002, -1.989009e-002, -2.019472e-002, -2.049394e-002, -2.078876e-002, -2.107657e-002, -2.136162e-002, + -2.164384e-002, -2.192293e-002, -2.219727e-002, -2.246562e-002, -2.273129e-002, -2.299395e-002, -2.325271e-002, -2.351119e-002, -2.376432e-002, + -2.401186e-002, -2.425936e-002, -2.450587e-002, -2.475234e-002, -2.500292e-002, -2.524975e-002, -2.549221e-002, -2.573377e-002, -2.597303e-002, + -2.620966e-002, -2.644458e-002, -2.667915e-002, -2.691132e-002, -2.714220e-002, -2.736910e-002, -2.759550e-002, -2.782144e-002, -2.804273e-002, + -2.826174e-002, -2.848028e-002, -2.869335e-002, -2.890189e-002, -2.910718e-002, -2.930464e-002, -2.949511e-002, -2.968108e-002, -2.986668e-002, + -3.004672e-002, -3.022181e-002, -3.039373e-002, -3.056167e-002, -3.072471e-002, -3.088472e-002, -3.104097e-002, -3.119396e-002, -3.134184e-002, + -3.148208e-002, -3.161625e-002, -3.174617e-002, -3.187219e-002, -3.199329e-002, -3.210708e-002, -3.221758e-002, -3.232474e-002, -3.242694e-002, + -3.252236e-002, -3.261333e-002, -3.269782e-002, -3.277689e-002, -3.284772e-002, -3.291825e-002, -3.298548e-002, -3.304762e-002, -3.310467e-002, + -3.315795e-002, -3.320481e-002, -3.323905e-002, -3.327168e-002, -3.330400e-002, -3.333563e-002, -3.336245e-002, -3.338772e-002, -3.341355e-002, + -3.343043e-002, -3.344169e-002, -3.344942e-002, -3.345532e-002, -3.345876e-002, -3.346031e-002, -3.346090e-002, -3.345749e-002, -3.344829e-002, + -3.343847e-002, -3.342726e-002, -3.341415e-002, -3.340012e-002, -3.338378e-002, -3.336691e-002, -3.334871e-002, -3.333131e-002, -3.330978e-002, + -3.328701e-002, -3.326246e-002, -3.323938e-002, -3.321396e-002, -3.318061e-002, -3.314734e-002, -3.311349e-002, -3.308041e-002, -3.304688e-002, + -3.301147e-002, -3.297355e-002, -3.293685e-002, -3.289329e-002, -3.284876e-002, -3.280400e-002, -3.276137e-002, -3.271631e-002, -3.266975e-002, + -3.262341e-002, -3.257818e-002, -3.253365e-002, -3.248434e-002, -3.243188e-002, -3.237650e-002, -3.232121e-002, -3.226633e-002, -3.220718e-002, + -3.214571e-002, -3.208198e-002, -3.201791e-002, -3.195090e-002, -3.187977e-002, -3.180300e-002, -3.172280e-002, -3.164116e-002, -3.155572e-002, + -3.146735e-002, -3.137357e-002, -3.127847e-002, -3.118422e-002, -3.108368e-002, -3.097873e-002, -3.087175e-002, -3.076270e-002, -3.065118e-002, + -3.053370e-002, -3.041047e-002, -3.028623e-002, -3.016156e-002, -3.003651e-002, -2.990797e-002, -2.977988e-002, -2.965398e-002, -2.952611e-002, + -2.939309e-002, -2.925492e-002, -2.911300e-002, -2.897271e-002, -2.883175e-002, -2.868896e-002, -2.854478e-002, -2.839884e-002, -2.825371e-002, + -2.810742e-002, -2.796095e-002, -2.781175e-002, -2.765748e-002, -2.750248e-002, -2.734364e-002, -2.718256e-002, -2.701869e-002, -2.685742e-002, + -2.669690e-002, -2.653339e-002, -2.637057e-002, -2.620674e-002, -2.603931e-002, -2.586909e-002, -2.569720e-002, -2.552496e-002, -2.534904e-002, + -2.517170e-002, -2.499340e-002, -2.481836e-002, -2.464383e-002, -2.446611e-002, -2.428726e-002, -2.410204e-002, -2.391778e-002, -2.373298e-002, + -2.354938e-002, -2.336368e-002, -2.317681e-002, -2.298983e-002, -2.280452e-002, -2.262086e-002, -2.243574e-002, -2.224932e-002, -2.206124e-002, + -2.187361e-002, -2.168629e-002, -2.149628e-002, -2.130444e-002, -2.111423e-002, -2.092539e-002, -2.073661e-002, -2.054704e-002, -2.035571e-002, + -2.016589e-002, -1.997657e-002, -1.978525e-002, -1.959239e-002, -1.939691e-002, -1.920034e-002, -1.900579e-002, -1.881134e-002, -1.861693e-002, + -1.842240e-002, -1.822638e-002, -1.803051e-002, -1.783656e-002, -1.764623e-002, -1.745772e-002, -1.726663e-002, -1.707309e-002, -1.687929e-002, + -1.668741e-002, -1.649485e-002, -1.630447e-002, -1.611337e-002, -1.592193e-002, -1.572956e-002, -1.553841e-002, -1.534620e-002, -1.515653e-002, + -1.496662e-002, -1.477267e-002, -1.457842e-002, -1.438401e-002, -1.418770e-002, -1.399272e-002, -1.379496e-002, -1.359669e-002, -1.340046e-002, + -1.320370e-002, -1.300539e-002, -1.280573e-002, -1.260308e-002, -1.239963e-002, -1.219630e-002, -1.198908e-002, -1.177731e-002, -1.156688e-002, + -1.135609e-002, -1.114344e-002, -1.092908e-002, -1.071463e-002, -1.049985e-002, -1.028499e-002, -1.006678e-002, -9.845976e-003, -9.624370e-003, + -9.401041e-003, -9.175400e-003, -8.950493e-003, -8.722643e-003, -8.491283e-003, -8.259034e-003, -8.025257e-003, -7.789180e-003, -7.551850e-003, + -7.311113e-003, -7.066043e-003, -6.818615e-003, -6.570490e-003, -6.322606e-003, -6.072834e-003, -5.820865e-003, -5.565984e-003, -5.309028e-003, + -5.051837e-003, -4.791712e-003, -4.528116e-003, -4.262771e-003, -3.996271e-003, -3.728189e-003, -3.461617e-003, -3.195312e-003, -2.928175e-003, + -2.661061e-003, -2.393332e-003, -2.125493e-003, -1.858081e-003, -1.590996e-003, -1.324668e-003, -1.058363e-003, -7.913172e-004, -5.221920e-004, + -2.510189e-004, 2.140818e-005, 2.928121e-004, 5.630597e-004, 8.352630e-004, 1.108931e-003, 1.383521e-003, 1.657981e-003, 1.932709e-003, + 2.207605e-003, 2.482357e-003, 2.758673e-003, 3.037314e-003, 3.311569e-003, 3.585463e-003, 3.857855e-003, 4.131148e-003, 4.404711e-003, + 4.682437e-003, 4.962140e-003, 5.241502e-003, 5.520677e-003, 5.798921e-003, 6.076529e-003, 6.355233e-003, 6.630229e-003, 6.896276e-003, + 7.160813e-003, 7.423036e-003, 7.683619e-003, 7.942773e-003, 8.200177e-003, 8.456512e-003, 8.710720e-003, 8.964936e-003, 9.218669e-003, + 9.469419e-003, 9.718448e-003, 9.967114e-003, 1.021657e-002, 1.046084e-002, 1.069793e-002, 1.093269e-002, 1.116588e-002, 1.139857e-002, + 1.163240e-002, 1.186718e-002, 1.210047e-002, 1.233178e-002, 1.256112e-002, 1.278869e-002, 1.301435e-002, 1.324011e-002, 1.346778e-002, + 1.369427e-002, 1.391720e-002, 1.413625e-002, 1.435342e-002, 1.457140e-002, 1.478901e-002, 1.500581e-002, 1.521804e-002, 1.542812e-002, + 1.563485e-002, 1.583790e-002, 1.603386e-002, 1.622727e-002, 1.642084e-002, 1.661401e-002, 1.680573e-002, 1.699682e-002, 1.718835e-002, + 1.737690e-002, 1.756313e-002, 1.774984e-002, 1.793515e-002, 1.811611e-002, 1.829717e-002, 1.847575e-002, 1.865394e-002, 1.883276e-002, + 1.901329e-002, 1.919362e-002, 1.937381e-002, 1.955299e-002, 1.973262e-002, 1.991024e-002, 2.007569e-002, 2.023895e-002, 2.040218e-002, + 2.056510e-002, 2.072457e-002, 2.088419e-002, 2.104356e-002, 2.119746e-002, 2.135005e-002, 2.150264e-002, 2.165576e-002, 2.180790e-002, + 2.195855e-002, 2.210787e-002, 2.224657e-002, 2.237759e-002, 2.250815e-002, 2.263985e-002, 2.277238e-002, 2.290370e-002, 2.303282e-002, + 2.315917e-002, 2.328280e-002, 2.340484e-002, 2.352691e-002, 2.364822e-002, 2.376566e-002, 2.388233e-002, 2.399686e-002, 2.410843e-002, + 2.421724e-002, 2.432245e-002, 2.442169e-002, 2.451867e-002, 2.461635e-002, 2.471437e-002, 2.480897e-002, 2.490082e-002, 2.499110e-002, + 2.508133e-002, 2.517542e-002, 2.527088e-002, 2.536616e-002, 2.546041e-002, 2.555259e-002, 2.564398e-002, 2.573504e-002, 2.582530e-002, + 2.591610e-002, 2.600670e-002, 2.609049e-002, 2.616960e-002, 2.624690e-002, 2.632224e-002, 2.639638e-002, 2.646646e-002, 2.653611e-002, + 2.660697e-002, 2.667836e-002, 2.674905e-002, 2.681822e-002, 2.688619e-002, 2.695308e-002, 2.701991e-002, 2.708692e-002, 2.715228e-002, + 2.721881e-002, 2.728731e-002, 2.735443e-002, 2.741990e-002, 2.748351e-002, 2.754559e-002, 2.760754e-002, 2.766992e-002, 2.773132e-002, + 2.778697e-002, 2.784087e-002, 2.789418e-002, 2.794661e-002, 2.799862e-002, 2.805053e-002, 2.810131e-002, 2.815085e-002, 2.820139e-002, + 2.824903e-002, 2.829519e-002, 2.833650e-002, 2.837695e-002, 2.841817e-002, 2.846024e-002, 2.850330e-002, 2.854653e-002, 2.858834e-002, + 2.862776e-002, 2.866714e-002, 2.870822e-002, 2.874992e-002, 2.879280e-002, 2.883658e-002, 2.887837e-002, 2.891985e-002, 2.896213e-002, + 2.900452e-002, 2.904645e-002, 2.908751e-002, 2.912664e-002, 2.916125e-002, 2.919489e-002, 2.922734e-002, 2.925837e-002, 2.928688e-002, + 2.931367e-002, 2.933987e-002, 2.936466e-002, 2.938917e-002, 2.941494e-002, 2.943962e-002, 2.946136e-002, 2.948006e-002, 2.949563e-002, + 2.950960e-002, 2.952191e-002, 2.953362e-002, 2.954413e-002, 2.955414e-002, 2.956491e-002, 2.957439e-002, 2.957989e-002, 2.958568e-002, + 2.959031e-002, 2.959346e-002, 2.959619e-002, 2.960041e-002, 2.960541e-002, 2.961041e-002, 2.961507e-002, 2.962047e-002, 2.962507e-002, + 2.962887e-002, 2.963091e-002, 2.963218e-002, 2.963440e-002, 2.963310e-002, 2.963052e-002, 2.962576e-002, 2.961695e-002, 2.960482e-002, + 2.959002e-002, 2.957304e-002, 2.955240e-002, 2.953086e-002, 2.950836e-002, 2.948497e-002, 2.946018e-002, 2.943453e-002, 2.940795e-002, + 2.938030e-002, 2.935177e-002, 2.932053e-002, 2.928996e-002, 2.926032e-002, 2.923058e-002, 2.920033e-002, 2.916999e-002, 2.913908e-002, + 2.910708e-002, 2.907456e-002, 2.904135e-002, 2.900909e-002, 2.897787e-002, 2.894670e-002, 2.891464e-002, 2.888131e-002, 2.884690e-002, + 2.881171e-002, 2.877515e-002, 2.873658e-002, 2.869810e-002, 2.865913e-002, 2.861727e-002, 2.857394e-002, 2.852956e-002, 2.848188e-002, + 2.843185e-002, 2.838120e-002, 2.832874e-002, 2.827399e-002, 2.821993e-002, 2.816489e-002, 2.810958e-002, 2.805411e-002, 2.800055e-002, + 2.794712e-002, 2.789330e-002, 2.783867e-002, 2.778228e-002, 2.772642e-002, 2.767076e-002, 2.761538e-002, 2.755842e-002, 2.750172e-002, + 2.744443e-002, 2.738648e-002, 2.732786e-002, 2.726752e-002, 2.720471e-002, 2.714402e-002, 2.708276e-002, 2.701978e-002, 2.695388e-002, + 2.688552e-002, 2.681497e-002, 2.674168e-002, 2.666553e-002, 2.658529e-002, 2.650240e-002, 2.641919e-002, 2.633420e-002, 2.624774e-002, + 2.615760e-002, 2.606572e-002, 2.597198e-002, 2.587634e-002, 2.577769e-002, 2.567703e-002, 2.557585e-002, 2.547455e-002, 2.537420e-002, + 2.527359e-002, 2.517095e-002, 2.506483e-002, 2.495609e-002, 2.484646e-002, 2.473641e-002, 2.462477e-002, 2.451235e-002, 2.439961e-002, + 2.428628e-002, 2.417314e-002, 2.405808e-002, 2.394161e-002, 2.382471e-002, 2.370691e-002, 2.358810e-002, 2.346806e-002, 2.334681e-002, + 2.322358e-002, 2.309854e-002, 2.297210e-002, 2.284372e-002, 2.271237e-002, 2.258113e-002, 2.244906e-002, 2.231511e-002, 2.218007e-002, + 2.204387e-002, 2.190615e-002, 2.176667e-002, 2.162626e-002, 2.148471e-002, 2.133846e-002, 2.119118e-002, 2.104237e-002, 2.089317e-002, + 2.074246e-002, 2.058801e-002, 2.043430e-002, 2.027965e-002, 2.012410e-002, 1.996752e-002, 1.980653e-002, 1.964417e-002, 1.947994e-002, + 1.931414e-002, 1.914714e-002, 1.897797e-002, 1.880728e-002, 1.863435e-002, 1.845874e-002, 1.828167e-002, 1.809997e-002, 1.791451e-002, + 1.772691e-002, 1.753726e-002, 1.734495e-002, 1.714788e-002, 1.694687e-002, 1.674338e-002, 1.653714e-002, 1.632927e-002, 1.611793e-002, + 1.590216e-002, 1.568510e-002, 1.546762e-002, 1.524848e-002, 1.502823e-002, 1.480667e-002, 1.458427e-002, 1.435927e-002, 1.413336e-002, + 1.390664e-002, 1.367515e-002, 1.344395e-002, 1.321234e-002, 1.298018e-002, 1.274746e-002, 1.251421e-002, 1.228035e-002, 1.204704e-002, + 1.181207e-002, 1.157563e-002, 1.133152e-002, 1.108539e-002, 1.083813e-002, 1.058951e-002, 1.033983e-002, 1.008932e-002, 9.837875e-003, + 9.583791e-003, 9.327003e-003, 9.068603e-003, 8.805135e-003, 8.541302e-003, 8.276973e-003, 8.011151e-003, 7.741779e-003, 7.471056e-003, + 7.199198e-003, 6.926701e-003, 6.652117e-003, 6.372945e-003, 6.090283e-003, 5.804499e-003, 5.518835e-003, 5.233289e-003, 4.946827e-003, + 4.659788e-003, 4.373713e-003, 4.083638e-003, 3.791420e-003, 3.495903e-003, 3.187555e-003, 2.873031e-003, 2.557847e-003, 2.241773e-003, + 1.924788e-003, 1.606810e-003, 1.287795e-003, 9.675759e-004, 6.462152e-004, 3.236570e-004, 7.435108e-018}, + {0.000000e+000, -6.469713e-002, -7.828328e-002, -8.686220e-002, -9.211825e-002, -9.558424e-002, -9.846409e-002, -1.007057e-001, -1.024699e-001, + -1.037916e-001, -1.047760e-001, -1.054649e-001, -1.057462e-001, -1.056053e-001, -1.049179e-001, -1.037458e-001, -1.021393e-001, -1.002687e-001, + -9.822223e-002, -9.594575e-002, -9.350405e-002, -9.096367e-002, -8.826829e-002, -8.553726e-002, -8.273321e-002, -7.986001e-002, -7.690865e-002, + -7.387185e-002, -7.074680e-002, -6.755361e-002, -6.429751e-002, -6.099197e-002, -5.764445e-002, -5.425388e-002, -5.085381e-002, -4.744986e-002, + -4.403984e-002, -4.058302e-002, -3.713389e-002, -3.370350e-002, -3.029566e-002, -2.686463e-002, -2.344809e-002, -2.008649e-002, -1.671963e-002, + -1.337802e-002, -1.010702e-002, -6.864855e-003, -3.672138e-003, -5.307672e-004, 2.589418e-003, 5.674849e-003, 8.695555e-003, 1.171779e-002, + 1.464808e-002, 1.752246e-002, 2.036470e-002, 2.309187e-002, 2.575660e-002, 2.832696e-002, 3.079189e-002, 3.314843e-002, 3.544774e-002, + 3.764760e-002, 3.976008e-002, 4.179727e-002, 4.374745e-002, 4.559640e-002, 4.735952e-002, 4.903644e-002, 5.060058e-002, 5.208287e-002, + 5.348482e-002, 5.480818e-002, 5.602395e-002, 5.716275e-002, 5.821523e-002, 5.920252e-002, 6.010275e-002, 6.094844e-002, 6.173664e-002, + 6.243826e-002, 6.308983e-002, 6.368032e-002, 6.421476e-002, 6.469794e-002, 6.511787e-002, 6.548597e-002, 6.576099e-002, 6.598615e-002, + 6.614675e-002, 6.624009e-002, 6.624674e-002, 6.618259e-002, 6.602643e-002, 6.579915e-002, 6.550360e-002, 6.512106e-002, 6.463925e-002, + 6.409128e-002, 6.346120e-002, 6.275858e-002, 6.201425e-002, 6.120558e-002, 6.035688e-002, 5.946349e-002, 5.851048e-002, 5.750876e-002, + 5.646295e-002, 5.539168e-002, 5.424992e-002, 5.308774e-002, 5.189343e-002, 5.066878e-002, 4.939702e-002, 4.810207e-002, 4.677344e-002, + 4.542653e-002, 4.406433e-002, 4.265098e-002, 4.122814e-002, 3.978345e-002, 3.831821e-002, 3.683513e-002, 3.531468e-002, 3.378772e-002, + 3.224818e-002, 3.070666e-002, 2.914897e-002, 2.758195e-002, 2.601353e-002, 2.445871e-002, 2.290973e-002, 2.136340e-002, 1.982237e-002, + 1.829095e-002, 1.677156e-002, 1.525633e-002, 1.376226e-002, 1.226910e-002, 1.078235e-002, 9.318496e-003, 7.867929e-003, 6.432017e-003, + 5.003915e-003, 3.604318e-003, 2.223420e-003, 8.547227e-004, -4.877055e-004, -1.807694e-003, -3.107970e-003, -4.385532e-003, -5.638788e-003, + -6.866865e-003, -8.074734e-003, -9.267347e-003, -1.042628e-002, -1.155523e-002, -1.266292e-002, -1.374568e-002, -1.480350e-002, -1.582305e-002, + -1.681563e-002, -1.776001e-002, -1.868603e-002, -1.958011e-002, -2.043513e-002, -2.125504e-002, -2.205138e-002, -2.282605e-002, -2.356869e-002, + -2.428369e-002, -2.496682e-002, -2.561731e-002, -2.624393e-002, -2.684944e-002, -2.742411e-002, -2.797223e-002, -2.849773e-002, -2.900693e-002, + -2.948752e-002, -2.994816e-002, -3.038768e-002, -3.081011e-002, -3.121545e-002, -3.160541e-002, -3.197178e-002, -3.232573e-002, -3.266658e-002, + -3.298497e-002, -3.328772e-002, -3.357785e-002, -3.385232e-002, -3.411056e-002, -3.434806e-002, -3.457410e-002, -3.478713e-002, -3.498274e-002, + -3.515363e-002, -3.531586e-002, -3.546732e-002, -3.561505e-002, -3.574698e-002, -3.587096e-002, -3.596673e-002, -3.606402e-002, -3.614986e-002, + -3.623441e-002, -3.630137e-002, -3.636224e-002, -3.642192e-002, -3.646557e-002, -3.650864e-002, -3.652658e-002, -3.654059e-002, -3.653371e-002, + -3.651568e-002, -3.650123e-002, -3.647878e-002, -3.645327e-002, -3.641473e-002, -3.637458e-002, -3.633287e-002, -3.628509e-002, -3.623413e-002, + -3.617405e-002, -3.609516e-002, -3.601382e-002, -3.592722e-002, -3.584515e-002, -3.576005e-002, -3.566187e-002, -3.555786e-002, -3.543635e-002, + -3.532140e-002, -3.520260e-002, -3.506985e-002, -3.493287e-002, -3.477655e-002, -3.460915e-002, -3.445059e-002, -3.428397e-002, -3.410934e-002, + -3.392135e-002, -3.371533e-002, -3.350538e-002, -3.330485e-002, -3.308700e-002, -3.286704e-002, -3.263976e-002, -3.240353e-002, -3.215252e-002, + -3.190454e-002, -3.165636e-002, -3.140870e-002, -3.114935e-002, -3.088873e-002, -3.062491e-002, -3.035330e-002, -3.008189e-002, -2.980032e-002, + -2.950477e-002, -2.920876e-002, -2.890041e-002, -2.857632e-002, -2.825969e-002, -2.793609e-002, -2.761030e-002, -2.728419e-002, -2.694444e-002, + -2.659894e-002, -2.625316e-002, -2.590819e-002, -2.555708e-002, -2.519867e-002, -2.482573e-002, -2.444520e-002, -2.405853e-002, -2.366800e-002, + -2.327730e-002, -2.289013e-002, -2.249635e-002, -2.209150e-002, -2.167495e-002, -2.125765e-002, -2.083612e-002, -2.041710e-002, -2.000520e-002, + -1.959938e-002, -1.919129e-002, -1.877985e-002, -1.835896e-002, -1.793262e-002, -1.751796e-002, -1.711021e-002, -1.671708e-002, -1.633344e-002, + -1.594612e-002, -1.554789e-002, -1.514211e-002, -1.473306e-002, -1.433118e-002, -1.393662e-002, -1.355774e-002, -1.317637e-002, -1.279617e-002, + -1.241989e-002, -1.203777e-002, -1.164872e-002, -1.125977e-002, -1.087416e-002, -1.050511e-002, -1.014183e-002, -9.779162e-003, -9.419219e-003, + -9.068369e-003, -8.724870e-003, -8.378705e-003, -8.025508e-003, -7.678557e-003, -7.342223e-003, -7.004579e-003, -6.672211e-003, -6.340469e-003, + -6.006833e-003, -5.667340e-003, -5.336685e-003, -5.008936e-003, -4.681159e-003, -4.362073e-003, -4.048104e-003, -3.733661e-003, -3.409438e-003, + -3.093370e-003, -2.775665e-003, -2.451900e-003, -2.122110e-003, -1.808526e-003, -1.494753e-003, -1.172514e-003, -8.560255e-004, -5.530390e-004, + -2.482669e-004, 6.327031e-005, 3.705023e-004, 6.780779e-004, 9.896990e-004, 1.303300e-003, 1.608933e-003, 1.903439e-003, 2.192033e-003, + 2.478485e-003, 2.758963e-003, 3.041980e-003, 3.327492e-003, 3.612473e-003, 3.893617e-003, 4.170845e-003, 4.443882e-003, 4.714857e-003, + 4.977444e-003, 5.232276e-003, 5.473099e-003, 5.719741e-003, 5.960384e-003, 6.197108e-003, 6.425966e-003, 6.652598e-003, 6.876645e-003, + 7.096168e-003, 7.316715e-003, 7.532865e-003, 7.745077e-003, 7.954218e-003, 8.159821e-003, 8.371581e-003, 8.576753e-003, 8.777826e-003, + 8.972151e-003, 9.174652e-003, 9.376912e-003, 9.576662e-003, 9.775730e-003, 9.973411e-003, 1.016606e-002, 1.035288e-002, 1.053215e-002, + 1.071271e-002, 1.089768e-002, 1.107173e-002, 1.123799e-002, 1.141147e-002, 1.159198e-002, 1.177428e-002, 1.195221e-002, 1.212903e-002, + 1.231008e-002, 1.249142e-002, 1.266480e-002, 1.283348e-002, 1.299771e-002, 1.316520e-002, 1.332222e-002, 1.347624e-002, 1.363001e-002, + 1.378710e-002, 1.393893e-002, 1.409555e-002, 1.425783e-002, 1.442263e-002, 1.458512e-002, 1.474156e-002, 1.489157e-002, 1.503672e-002, + 1.517820e-002, 1.531295e-002, 1.544401e-002, 1.557124e-002, 1.570071e-002, 1.582456e-002, 1.594279e-002, 1.606681e-002, 1.619073e-002, + 1.631032e-002, 1.642245e-002, 1.652711e-002, 1.663151e-002, 1.673808e-002, 1.684839e-002, 1.694630e-002, 1.704264e-002, 1.714222e-002, + 1.724219e-002, 1.734485e-002, 1.744845e-002, 1.755396e-002, 1.765652e-002, 1.775528e-002, 1.785925e-002, 1.796461e-002, 1.807811e-002, + 1.819162e-002, 1.830988e-002, 1.843336e-002, 1.855691e-002, 1.868156e-002, 1.879863e-002, 1.891709e-002, 1.903567e-002, 1.915606e-002, + 1.927826e-002, 1.940671e-002, 1.953632e-002, 1.966803e-002, 1.980208e-002, 1.993816e-002, 2.007503e-002, 2.021599e-002, 2.036183e-002, + 2.050835e-002, 2.064794e-002, 2.079284e-002, 2.093873e-002, 2.108389e-002, 2.121629e-002, 2.134323e-002, 2.147300e-002, 2.160443e-002, + 2.173438e-002, 2.186495e-002, 2.199561e-002, 2.212724e-002, 2.225363e-002, 2.237569e-002, 2.249110e-002, 2.260387e-002, 2.271555e-002, + 2.282435e-002, 2.293389e-002, 2.303817e-002, 2.313783e-002, 2.323866e-002, 2.334169e-002, 2.344180e-002, 2.353482e-002, 2.362114e-002, + 2.370497e-002, 2.378883e-002, 2.387323e-002, 2.395422e-002, 2.403218e-002, 2.410758e-002, 2.417310e-002, 2.423806e-002, 2.429789e-002, + 2.435348e-002, 2.440469e-002, 2.445107e-002, 2.449678e-002, 2.454035e-002, 2.457773e-002, 2.460996e-002, 2.463956e-002, 2.466377e-002, + 2.468411e-002, 2.469747e-002, 2.470441e-002, 2.470438e-002, 2.470047e-002, 2.468993e-002, 2.467398e-002, 2.466152e-002, 2.464754e-002, + 2.463338e-002, 2.462103e-002, 2.461048e-002, 2.460505e-002, 2.460307e-002, 2.460096e-002, 2.460043e-002, 2.460160e-002, 2.460665e-002, + 2.461550e-002, 2.462380e-002, 2.463099e-002, 2.463669e-002, 2.464766e-002, 2.465821e-002, 2.466327e-002, 2.466966e-002, 2.467323e-002, + 2.467568e-002, 2.467941e-002, 2.468133e-002, 2.468430e-002, 2.468566e-002, 2.468287e-002, 2.468217e-002, 2.468345e-002, 2.468323e-002, + 2.467766e-002, 2.466956e-002, 2.466029e-002, 2.465382e-002, 2.464682e-002, 2.463666e-002, 2.462295e-002, 2.460483e-002, 2.458508e-002, + 2.456382e-002, 2.454258e-002, 2.452160e-002, 2.449445e-002, 2.446215e-002, 2.442702e-002, 2.438685e-002, 2.434432e-002, 2.429725e-002, + 2.424789e-002, 2.419664e-002, 2.414239e-002, 2.408240e-002, 2.402071e-002, 2.395808e-002, 2.388684e-002, 2.381085e-002, 2.373556e-002, + 2.365988e-002, 2.358518e-002, 2.351202e-002, 2.343433e-002, 2.335242e-002, 2.326923e-002, 2.318460e-002, 2.310367e-002, 2.302928e-002, + 2.295453e-002, 2.287819e-002, 2.279826e-002, 2.271765e-002, 2.263280e-002, 2.254679e-002, 2.245907e-002, 2.236747e-002, 2.227408e-002, + 2.217659e-002, 2.207895e-002, 2.197686e-002, 2.186393e-002, 2.174706e-002, 2.162682e-002, 2.150253e-002, 2.137813e-002, 2.124852e-002, + 2.111178e-002, 2.097042e-002, 2.082634e-002, 2.067989e-002, 2.052427e-002, 2.036544e-002, 2.020594e-002, 2.004695e-002, 1.988641e-002, + 1.972133e-002, 1.955308e-002, 1.938313e-002, 1.920888e-002, 1.903272e-002, 1.885401e-002, 1.867309e-002, 1.848854e-002, 1.829809e-002, + 1.810801e-002, 1.791995e-002, 1.773515e-002, 1.754897e-002, 1.736490e-002, 1.718336e-002, 1.700188e-002, 1.681903e-002, 1.663972e-002, + 1.646311e-002, 1.629235e-002, 1.612415e-002, 1.595575e-002, 1.578185e-002, 1.560191e-002, 1.542351e-002, 1.524853e-002, 1.507342e-002, + 1.490000e-002, 1.472524e-002, 1.454808e-002, 1.436873e-002, 1.418834e-002, 1.400464e-002, 1.382014e-002, 1.363393e-002, 1.344717e-002, + 1.325962e-002, 1.306770e-002, 1.287641e-002, 1.268371e-002, 1.248988e-002, 1.229512e-002, 1.209297e-002, 1.188538e-002, 1.168147e-002, + 1.147991e-002, 1.127749e-002, 1.107289e-002, 1.086519e-002, 1.065663e-002, 1.044708e-002, 1.023466e-002, 1.001842e-002, 9.797728e-003, + 9.575952e-003, 9.353547e-003, 9.130139e-003, 8.906888e-003, 8.680285e-003, 8.448413e-003, 8.210929e-003, 7.970192e-003, 7.728476e-003, + 7.485796e-003, 7.241040e-003, 6.994088e-003, 6.743012e-003, 6.486717e-003, 6.228414e-003, 5.965036e-003, 5.703174e-003, 5.440872e-003, + 5.178946e-003, 4.915254e-003, 4.649898e-003, 4.385245e-003, 4.119458e-003, 3.855736e-003, 3.593426e-003, 3.331036e-003, 3.070594e-003, + 2.810871e-003, 2.551814e-003, 2.294389e-003, 2.038227e-003, 1.784816e-003, 1.536631e-003, 1.290172e-003, 1.044451e-003, 8.014830e-004, + 5.612823e-004, 3.223942e-004, 8.606447e-005, -1.492419e-004, -3.836474e-004, -6.161727e-004, -8.459682e-004, -1.072967e-003, -1.297765e-003, + -1.521848e-003, -1.744353e-003, -1.967006e-003, -2.189597e-003, -2.412024e-003, -2.634081e-003, -2.860468e-003, -3.088981e-003, -3.318789e-003, + -3.549015e-003, -3.781653e-003, -4.016864e-003, -4.253058e-003, -4.490186e-003, -4.729270e-003, -4.970168e-003, -5.211515e-003, -5.453856e-003, + -5.702097e-003, -5.950879e-003, -6.198840e-003, -6.446401e-003, -6.696393e-003, -6.948213e-003, -7.203690e-003, -7.461122e-003, -7.718444e-003, + -7.974375e-003, -8.228753e-003, -8.480625e-003, -8.735986e-003, -8.991849e-003, -9.247726e-003, -9.505388e-003, -9.764156e-003, -1.002380e-002, + -1.028405e-002, -1.054084e-002, -1.079839e-002, -1.105724e-002, -1.131689e-002, -1.157669e-002, -1.183838e-002, -1.210115e-002, -1.236222e-002, + -1.262037e-002, -1.288148e-002, -1.314344e-002, -1.340458e-002, -1.366718e-002, -1.393092e-002, -1.419661e-002, -1.446416e-002, -1.472882e-002, + -1.499741e-002, -1.527085e-002, -1.554702e-002, -1.582706e-002, -1.610954e-002, -1.639179e-002, -1.667363e-002, -1.695181e-002, -1.722961e-002, + -1.751060e-002, -1.779443e-002, -1.807916e-002, -1.836610e-002, -1.865573e-002, -1.895005e-002, -1.924631e-002, -1.953794e-002, -1.982801e-002, + -2.011786e-002, -2.040675e-002, -2.069546e-002, -2.098438e-002, -2.127256e-002, -2.155927e-002, -2.184541e-002, -2.213186e-002, -2.241507e-002, + -2.269731e-002, -2.297683e-002, -2.325343e-002, -2.352645e-002, -2.379552e-002, -2.406015e-002, -2.432153e-002, -2.458049e-002, -2.483710e-002, + -2.509053e-002, -2.534055e-002, -2.558961e-002, -2.583631e-002, -2.608061e-002, -2.632558e-002, -2.656768e-002, -2.680694e-002, -2.704260e-002, + -2.727655e-002, -2.750937e-002, -2.774004e-002, -2.797067e-002, -2.820135e-002, -2.843305e-002, -2.866541e-002, -2.889548e-002, -2.912432e-002, + -2.935349e-002, -2.958233e-002, -2.981045e-002, -3.003581e-002, -3.025792e-002, -3.047710e-002, -3.069430e-002, -3.090894e-002, -3.112030e-002, + -3.132802e-002, -3.152953e-002, -3.172998e-002, -3.192716e-002, -3.212286e-002, -3.231720e-002, -3.250905e-002, -3.269861e-002, -3.288566e-002, + -3.307003e-002, -3.325347e-002, -3.343313e-002, -3.361087e-002, -3.378396e-002, -3.395387e-002, -3.412284e-002, -3.429113e-002, -3.445767e-002, + -3.462374e-002, -3.479154e-002, -3.495918e-002, -3.512264e-002, -3.528609e-002, -3.544544e-002, -3.560309e-002, -3.575832e-002, -3.591354e-002, + -3.606808e-002, -3.622087e-002, -3.637071e-002, -3.651832e-002, -3.665980e-002, -3.679747e-002, -3.693382e-002, -3.706887e-002, -3.720216e-002, + -3.733594e-002, -3.747021e-002, -3.760209e-002, -3.773188e-002, -3.786154e-002, -3.798599e-002, -3.810754e-002, -3.822909e-002, -3.835180e-002, + -3.847460e-002, -3.859646e-002, -3.871935e-002, -3.884211e-002, -3.896610e-002, -3.909227e-002, -3.921192e-002, -3.932457e-002, -3.943722e-002, + -3.955001e-002, -3.966218e-002, -3.977233e-002, -3.988096e-002, -3.998759e-002, -4.009208e-002, -4.019456e-002, -4.029081e-002, -4.037704e-002, + -4.045696e-002, -4.052301e-002, -4.058132e-002, -4.063269e-002, -4.067976e-002, -4.072108e-002, -4.075773e-002, -4.079054e-002, -4.081888e-002, + -4.083108e-002, -4.083847e-002, -4.084045e-002, -4.083695e-002, -4.082632e-002, -4.080907e-002, -4.078653e-002, -4.075970e-002, -4.073380e-002, + -4.070558e-002, -4.066605e-002, -4.062168e-002, -4.057635e-002, -4.053042e-002, -4.048439e-002, -4.043798e-002, -4.038752e-002, -4.033951e-002, + -4.029268e-002, -4.024423e-002, -4.018799e-002, -4.012350e-002, -4.005710e-002, -3.998893e-002, -3.991869e-002, -3.984632e-002, -3.977418e-002, + -3.970271e-002, -3.962869e-002, -3.955072e-002, -3.946492e-002, -3.936503e-002, -3.926086e-002, -3.915602e-002, -3.904906e-002, -3.894186e-002, + -3.883076e-002, -3.871626e-002, -3.860040e-002, -3.848227e-002, -3.835984e-002, -3.821386e-002, -3.806576e-002, -3.791635e-002, -3.776568e-002, + -3.761430e-002, -3.745988e-002, -3.730235e-002, -3.714267e-002, -3.698171e-002, -3.681943e-002, -3.663804e-002, -3.645160e-002, -3.626004e-002, + -3.606420e-002, -3.586515e-002, -3.566253e-002, -3.545444e-002, -3.524196e-002, -3.502434e-002, -3.479720e-002, -3.454854e-002, -3.428425e-002, + -3.401370e-002, -3.373672e-002, -3.345306e-002, -3.315869e-002, -3.285711e-002, -3.254918e-002, -3.223500e-002, -3.191521e-002, -3.158003e-002, + -3.122345e-002, -3.086254e-002, -3.049347e-002, -3.012136e-002, -2.974533e-002, -2.936611e-002, -2.898553e-002, -2.859750e-002, -2.820706e-002, + -2.781376e-002, -2.739560e-002, -2.697754e-002, -2.656104e-002, -2.614437e-002, -2.572719e-002, -2.530948e-002, -2.488978e-002, -2.446722e-002, + -2.404288e-002, -2.361898e-002, -2.315664e-002, -2.268684e-002, -2.221551e-002, -2.174211e-002, -2.126660e-002, -2.078944e-002, -2.031042e-002, + -1.983102e-002, -1.934786e-002, -1.886225e-002, -1.834936e-002, -1.782041e-002, -1.728978e-002, -1.675420e-002, -1.620606e-002, -1.565660e-002, + -1.510645e-002, -1.455489e-002, -1.400285e-002, -1.344636e-002, -1.287052e-002, -1.227259e-002, -1.167410e-002, -1.107524e-002, -1.047727e-002, + -9.877835e-003, -9.273231e-003, -8.655532e-003, -8.031998e-003, -7.408098e-003, -6.768808e-003, -6.094122e-003, -5.420056e-003, -4.746569e-003, + -4.072105e-003, -3.396666e-003, -2.720035e-003, -2.041980e-003, -1.362527e-003, -6.818454e-004, -8.441329e-018}, + {0.000000e+000, -9.303034e-002, -1.043270e-001, -1.089305e-001, -1.112771e-001, -1.122107e-001, -1.115387e-001, -1.098958e-001, -1.077653e-001, + -1.051520e-001, -1.020536e-001, -9.863098e-002, -9.495198e-002, -9.115456e-002, -8.731258e-002, -8.346059e-002, -7.950756e-002, -7.544519e-002, + -7.124775e-002, -6.703300e-002, -6.278051e-002, -5.854203e-002, -5.428164e-002, -5.000572e-002, -4.573679e-002, -4.148641e-002, -3.723481e-002, + -3.300679e-002, -2.877581e-002, -2.457002e-002, -2.041787e-002, -1.635034e-002, -1.237288e-002, -8.480525e-003, -4.738316e-003, -1.119081e-003, + 2.362968e-003, 5.698824e-003, 8.898447e-003, 1.196393e-002, 1.487221e-002, 1.762636e-002, 2.027874e-002, 2.275033e-002, 2.510044e-002, + 2.731678e-002, 2.936266e-002, 3.128832e-002, 3.306548e-002, 3.472033e-002, 3.624400e-002, 3.759164e-002, 3.879899e-002, 3.989232e-002, + 4.084855e-002, 4.171747e-002, 4.254545e-002, 4.328453e-002, 4.397328e-002, 4.464648e-002, 4.525405e-002, 4.586957e-002, 4.648785e-002, + 4.703341e-002, 4.750380e-002, 4.792682e-002, 4.829667e-002, 4.859971e-002, 4.886785e-002, 4.908528e-002, 4.920737e-002, 4.928087e-002, + 4.932687e-002, 4.932539e-002, 4.923916e-002, 4.911107e-002, 4.893566e-002, 4.870492e-002, 4.839777e-002, 4.805972e-002, 4.765583e-002, + 4.716266e-002, 4.660307e-002, 4.600542e-002, 4.529323e-002, 4.451696e-002, 4.369199e-002, 4.277729e-002, 4.177616e-002, 4.073652e-002, + 3.962325e-002, 3.847811e-002, 3.722188e-002, 3.592511e-002, 3.456954e-002, 3.314710e-002, 3.167329e-002, 3.015168e-002, 2.855270e-002, + 2.692613e-002, 2.527488e-002, 2.356581e-002, 2.184884e-002, 2.009119e-002, 1.831962e-002, 1.652453e-002, 1.469347e-002, 1.285258e-002, + 1.099660e-002, 9.148225e-003, 7.268883e-003, 5.408934e-003, 3.540035e-003, 1.693736e-003, -1.236256e-004, -1.905663e-003, -3.687461e-003, + -5.447510e-003, -7.154115e-003, -8.863895e-003, -1.051590e-002, -1.215173e-002, -1.375109e-002, -1.530053e-002, -1.681718e-002, -1.828086e-002, + -1.970373e-002, -2.109478e-002, -2.244971e-002, -2.377175e-002, -2.506424e-002, -2.632043e-002, -2.752982e-002, -2.868364e-002, -2.978081e-002, + -3.081732e-002, -3.181841e-002, -3.279818e-002, -3.371280e-002, -3.459856e-002, -3.546493e-002, -3.628787e-002, -3.703949e-002, -3.776670e-002, + -3.846436e-002, -3.912116e-002, -3.975772e-002, -4.038634e-002, -4.098569e-002, -4.153772e-002, -4.205998e-002, -4.257041e-002, -4.306705e-002, + -4.355096e-002, -4.401686e-002, -4.447502e-002, -4.487894e-002, -4.525036e-002, -4.562979e-002, -4.598490e-002, -4.634284e-002, -4.668134e-002, + -4.696885e-002, -4.723291e-002, -4.749072e-002, -4.774066e-002, -4.796676e-002, -4.814433e-002, -4.829204e-002, -4.844392e-002, -4.857639e-002, + -4.868928e-002, -4.873343e-002, -4.876303e-002, -4.878049e-002, -4.878612e-002, -4.870593e-002, -4.858596e-002, -4.845367e-002, -4.831608e-002, + -4.807066e-002, -4.780161e-002, -4.750407e-002, -4.713799e-002, -4.671949e-002, -4.627722e-002, -4.575552e-002, -4.518642e-002, -4.459805e-002, + -4.389375e-002, -4.316171e-002, -4.236791e-002, -4.150750e-002, -4.061161e-002, -3.961851e-002, -3.860121e-002, -3.749940e-002, -3.638053e-002, + -3.518935e-002, -3.397700e-002, -3.272644e-002, -3.146315e-002, -3.017875e-002, -2.888053e-002, -2.757329e-002, -2.625708e-002, -2.496456e-002, + -2.366403e-002, -2.238617e-002, -2.110061e-002, -1.985026e-002, -1.859525e-002, -1.739433e-002, -1.618445e-002, -1.500915e-002, -1.384491e-002, + -1.269358e-002, -1.158436e-002, -1.048297e-002, -9.424129e-003, -8.367092e-003, -7.325523e-003, -6.320015e-003, -5.322653e-003, -4.352684e-003, + -3.392985e-003, -2.433504e-003, -1.502217e-003, -5.995891e-004, 2.893460e-004, 1.154213e-003, 2.014878e-003, 2.881001e-003, 3.732345e-003, + 4.538165e-003, 5.336501e-003, 6.127824e-003, 6.885954e-003, 7.658883e-003, 8.429773e-003, 9.159087e-003, 9.879434e-003, 1.058440e-002, + 1.129046e-002, 1.198059e-002, 1.265760e-002, 1.330774e-002, 1.395927e-002, 1.457827e-002, 1.518631e-002, 1.579176e-002, 1.640320e-002, + 1.697146e-002, 1.753342e-002, 1.808937e-002, 1.864566e-002, 1.916627e-002, 1.968177e-002, 2.019282e-002, 2.068944e-002, 2.116874e-002, + 2.164423e-002, 2.210696e-002, 2.255649e-002, 2.300955e-002, 2.343174e-002, 2.384105e-002, 2.424369e-002, 2.463825e-002, 2.501927e-002, + 2.537309e-002, 2.571329e-002, 2.603680e-002, 2.634311e-002, 2.663341e-002, 2.691613e-002, 2.718778e-002, 2.744777e-002, 2.768991e-002, + 2.789870e-002, 2.807986e-002, 2.825501e-002, 2.842605e-002, 2.860068e-002, 2.875888e-002, 2.889413e-002, 2.901452e-002, 2.912205e-002, + 2.921247e-002, 2.929385e-002, 2.937240e-002, 2.945516e-002, 2.953591e-002, 2.959643e-002, 2.964768e-002, 2.967629e-002, 2.969223e-002, + 2.971112e-002, 2.973641e-002, 2.976402e-002, 2.978845e-002, 2.980206e-002, 2.981243e-002, 2.980830e-002, 2.980673e-002, 2.979774e-002, + 2.977557e-002, 2.976568e-002, 2.975920e-002, 2.975318e-002, 2.975866e-002, 2.974631e-002, 2.972254e-002, 2.970313e-002, 2.968283e-002, + 2.965906e-002, 2.963541e-002, 2.961749e-002, 2.961489e-002, 2.961390e-002, 2.959442e-002, 2.956762e-002, 2.954296e-002, 2.951549e-002, + 2.948730e-002, 2.946392e-002, 2.943189e-002, 2.940184e-002, 2.936614e-002, 2.931923e-002, 2.926116e-002, 2.919633e-002, 2.914288e-002, + 2.909262e-002, 2.904035e-002, 2.898404e-002, 2.892859e-002, 2.886306e-002, 2.879542e-002, 2.872363e-002, 2.865928e-002, 2.859319e-002, + 2.852168e-002, 2.844157e-002, 2.836134e-002, 2.828691e-002, 2.821710e-002, 2.814341e-002, 2.805664e-002, 2.795905e-002, 2.785542e-002, + 2.774758e-002, 2.763816e-002, 2.753532e-002, 2.744040e-002, 2.733492e-002, 2.724073e-002, 2.714756e-002, 2.705893e-002, 2.696322e-002, + 2.686550e-002, 2.675897e-002, 2.663641e-002, 2.651716e-002, 2.640664e-002, 2.630038e-002, 2.619243e-002, 2.607988e-002, 2.597174e-002, + 2.585216e-002, 2.573069e-002, 2.559872e-002, 2.546299e-002, 2.532555e-002, 2.518761e-002, 2.504809e-002, 2.489045e-002, 2.472366e-002, + 2.455824e-002, 2.439427e-002, 2.422145e-002, 2.405232e-002, 2.388095e-002, 2.369445e-002, 2.349408e-002, 2.329504e-002, 2.308756e-002, + 2.287485e-002, 2.265870e-002, 2.243203e-002, 2.219888e-002, 2.196137e-002, 2.171924e-002, 2.147157e-002, 2.122252e-002, 2.097307e-002, + 2.072168e-002, 2.046219e-002, 2.020029e-002, 1.992333e-002, 1.963633e-002, 1.934764e-002, 1.905203e-002, 1.875611e-002, 1.845532e-002, + 1.815623e-002, 1.785154e-002, 1.754728e-002, 1.725372e-002, 1.696278e-002, 1.666208e-002, 1.636052e-002, 1.605885e-002, 1.575126e-002, + 1.543698e-002, 1.511962e-002, 1.479998e-002, 1.448403e-002, 1.417573e-002, 1.387004e-002, 1.355709e-002, 1.324904e-002, 1.295061e-002, + 1.265536e-002, 1.235077e-002, 1.204275e-002, 1.173761e-002, 1.143932e-002, 1.114628e-002, 1.084266e-002, 1.053770e-002, 1.023308e-002, + 9.930177e-003, 9.626567e-003, 9.322103e-003, 9.014777e-003, 8.704896e-003, 8.396409e-003, 8.091276e-003, 7.785713e-003, 7.486422e-003, + 7.185851e-003, 6.881343e-003, 6.565880e-003, 6.247711e-003, 5.930411e-003, 5.602225e-003, 5.270708e-003, 4.935523e-003, 4.597738e-003, + 4.252543e-003, 3.908867e-003, 3.560984e-003, 3.202739e-003, 2.842120e-003, 2.473756e-003, 2.107328e-003, 1.740755e-003, 1.375019e-003, + 1.013677e-003, 6.468577e-004, 2.801418e-004, -9.032403e-005, -4.715020e-004, -8.635331e-004, -1.265031e-003, -1.663909e-003, -2.054582e-003, + -2.436957e-003, -2.817296e-003, -3.193600e-003, -3.567967e-003, -3.939406e-003, -4.309288e-003, -4.694035e-003, -5.079178e-003, -5.461835e-003, + -5.836651e-003, -6.207453e-003, -6.577243e-003, -6.946944e-003, -7.310163e-003, -7.671361e-003, -8.024875e-003, -8.373197e-003, -8.732091e-003, + -9.092484e-003, -9.449519e-003, -9.793405e-003, -1.012953e-002, -1.046076e-002, -1.078905e-002, -1.111630e-002, -1.143436e-002, -1.175081e-002, + -1.206400e-002, -1.237265e-002, -1.268433e-002, -1.299383e-002, -1.329012e-002, -1.358470e-002, -1.387121e-002, -1.415897e-002, -1.444438e-002, + -1.471914e-002, -1.498634e-002, -1.525164e-002, -1.551813e-002, -1.578291e-002, -1.603966e-002, -1.629817e-002, -1.655223e-002, -1.681210e-002, + -1.707526e-002, -1.734172e-002, -1.760486e-002, -1.786187e-002, -1.811735e-002, -1.837155e-002, -1.862751e-002, -1.888750e-002, -1.913764e-002, + -1.937964e-002, -1.962844e-002, -1.988251e-002, -2.014016e-002, -2.039957e-002, -2.064958e-002, -2.090213e-002, -2.115651e-002, -2.141170e-002, + -2.166483e-002, -2.191784e-002, -2.216633e-002, -2.240626e-002, -2.263723e-002, -2.286913e-002, -2.310855e-002, -2.335650e-002, -2.359114e-002, + -2.381785e-002, -2.404510e-002, -2.427410e-002, -2.450184e-002, -2.473072e-002, -2.495013e-002, -2.516223e-002, -2.537511e-002, -2.558991e-002, + -2.580201e-002, -2.601053e-002, -2.621077e-002, -2.640861e-002, -2.660771e-002, -2.679730e-002, -2.698717e-002, -2.717737e-002, -2.736315e-002, + -2.753237e-002, -2.769801e-002, -2.786297e-002, -2.802723e-002, -2.818645e-002, -2.834224e-002, -2.849099e-002, -2.864086e-002, -2.879140e-002, + -2.893860e-002, -2.908796e-002, -2.924236e-002, -2.939318e-002, -2.954162e-002, -2.969246e-002, -2.983721e-002, -2.997453e-002, -3.010417e-002, + -3.023105e-002, -3.035269e-002, -3.046869e-002, -3.058286e-002, -3.069480e-002, -3.080807e-002, -3.092121e-002, -3.102877e-002, -3.112530e-002, + -3.122324e-002, -3.131739e-002, -3.140554e-002, -3.148672e-002, -3.156605e-002, -3.163701e-002, -3.170708e-002, -3.176903e-002, -3.182474e-002, + -3.187429e-002, -3.192084e-002, -3.196329e-002, -3.199648e-002, -3.201448e-002, -3.202499e-002, -3.202130e-002, -3.201297e-002, -3.200232e-002, + -3.199325e-002, -3.198028e-002, -3.196750e-002, -3.195689e-002, -3.194324e-002, -3.192755e-002, -3.191414e-002, -3.189178e-002, -3.185126e-002, + -3.181082e-002, -3.176886e-002, -3.172515e-002, -3.168396e-002, -3.164452e-002, -3.160541e-002, -3.156385e-002, -3.152518e-002, -3.148691e-002, + -3.144838e-002, -3.141128e-002, -3.137727e-002, -3.134623e-002, -3.130302e-002, -3.125093e-002, -3.119797e-002, -3.114230e-002, -3.108608e-002, + -3.102881e-002, -3.097350e-002, -3.091716e-002, -3.086050e-002, -3.079957e-002, -3.073195e-002, -3.066171e-002, -3.058957e-002, -3.051443e-002, + -3.043771e-002, -3.035529e-002, -3.026525e-002, -3.017571e-002, -3.009037e-002, -3.000084e-002, -2.989992e-002, -2.978251e-002, -2.965803e-002, + -2.952848e-002, -2.939706e-002, -2.926392e-002, -2.912811e-002, -2.899045e-002, -2.885192e-002, -2.870888e-002, -2.856516e-002, -2.841528e-002, + -2.825869e-002, -2.809769e-002, -2.792968e-002, -2.775836e-002, -2.758246e-002, -2.740393e-002, -2.722410e-002, -2.704723e-002, -2.687288e-002, + -2.668815e-002, -2.649755e-002, -2.630461e-002, -2.610554e-002, -2.590675e-002, -2.570347e-002, -2.548978e-002, -2.526934e-002, -2.504801e-002, + -2.482454e-002, -2.458784e-002, -2.435185e-002, -2.411539e-002, -2.387697e-002, -2.364078e-002, -2.340161e-002, -2.316548e-002, -2.293494e-002, + -2.270383e-002, -2.247407e-002, -2.223858e-002, -2.200098e-002, -2.177069e-002, -2.154047e-002, -2.131269e-002, -2.108827e-002, -2.086330e-002, + -2.064155e-002, -2.041804e-002, -2.019518e-002, -1.996611e-002, -1.972738e-002, -1.948897e-002, -1.925106e-002, -1.901025e-002, -1.876611e-002, + -1.852013e-002, -1.827487e-002, -1.802381e-002, -1.777121e-002, -1.752035e-002, -1.726326e-002, -1.699705e-002, -1.672726e-002, -1.645387e-002, + -1.618160e-002, -1.591490e-002, -1.565007e-002, -1.538311e-002, -1.511497e-002, -1.484433e-002, -1.456734e-002, -1.428777e-002, -1.400771e-002, + -1.372098e-002, -1.343347e-002, -1.314654e-002, -1.285959e-002, -1.256813e-002, -1.227279e-002, -1.197082e-002, -1.166110e-002, -1.134470e-002, + -1.102884e-002, -1.071234e-002, -1.039400e-002, -1.006862e-002, -9.738795e-003, -9.403636e-003, -9.069431e-003, -8.730534e-003, -8.388891e-003, + -8.043898e-003, -7.701344e-003, -7.356372e-003, -7.013856e-003, -6.672303e-003, -6.330828e-003, -5.985169e-003, -5.638881e-003, -5.292245e-003, + -4.938486e-003, -4.579355e-003, -4.217080e-003, -3.849978e-003, -3.483144e-003, -3.117564e-003, -2.752181e-003, -2.388346e-003, -2.022805e-003, + -1.650467e-003, -1.269718e-003, -8.767821e-004, -4.776649e-004, -7.623562e-005, 3.258055e-004, 7.280977e-004, 1.135383e-003, 1.547792e-003, + 1.960457e-003, 2.375741e-003, 2.790709e-003, 3.212097e-003, 3.639058e-003, 4.070175e-003, 4.503332e-003, 4.939710e-003, 5.376668e-003, + 5.813535e-003, 6.250691e-003, 6.688689e-003, 7.126667e-003, 7.570752e-003, 8.013479e-003, 8.453302e-003, 8.890125e-003, 9.323666e-003, + 9.755337e-003, 1.018191e-002, 1.060611e-002, 1.102831e-002, 1.144729e-002, 1.186491e-002, 1.228139e-002, 1.269406e-002, 1.309953e-002, + 1.350055e-002, 1.389704e-002, 1.428958e-002, 1.467723e-002, 1.506192e-002, 1.543798e-002, 1.581033e-002, 1.617856e-002, 1.654549e-002, + 1.691711e-002, 1.729144e-002, 1.766769e-002, 1.804705e-002, 1.842600e-002, 1.880228e-002, 1.917798e-002, 1.955398e-002, 1.992866e-002, + 2.030387e-002, 2.068121e-002, 2.105847e-002, 2.143718e-002, 2.181898e-002, 2.219875e-002, 2.257561e-002, 2.294921e-002, 2.331925e-002, + 2.368520e-002, 2.405139e-002, 2.441641e-002, 2.478315e-002, 2.514632e-002, 2.550537e-002, 2.585931e-002, 2.620706e-002, 2.655227e-002, + 2.689664e-002, 2.723573e-002, 2.757378e-002, 2.791125e-002, 2.824600e-002, 2.857588e-002, 2.890035e-002, 2.922019e-002, 2.953738e-002, + 2.985432e-002, 3.017367e-002, 3.049323e-002, 3.080744e-002, 3.112068e-002, 3.143420e-002, 3.174726e-002, 3.206202e-002, 3.237597e-002, + 3.268740e-002, 3.299781e-002, 3.330954e-002, 3.362071e-002, 3.392253e-002, 3.422280e-002, 3.452093e-002, 3.481696e-002, 3.511222e-002, + 3.541007e-002, 3.570758e-002, 3.601007e-002, 3.631795e-002, 3.662938e-002, 3.693097e-002, 3.722511e-002, 3.751877e-002, 3.780648e-002, + 3.809330e-002, 3.837956e-002, 3.866794e-002, 3.895995e-002, 3.925160e-002, 3.954178e-002, 3.982581e-002, 4.009569e-002, 4.036354e-002, + 4.062970e-002, 4.089329e-002, 4.115284e-002, 4.141196e-002, 4.167269e-002, 4.192832e-002, 4.217854e-002, 4.241650e-002, 4.263374e-002, + 4.284403e-002, 4.304530e-002, 4.324117e-002, 4.343132e-002, 4.361932e-002, 4.380622e-002, 4.399112e-002, 4.416706e-002, 4.433391e-002, + 4.447581e-002, 4.461209e-002, 4.474297e-002, 4.486815e-002, 4.498548e-002, 4.510444e-002, 4.521904e-002, 4.532946e-002, 4.543084e-002, + 4.552715e-002, 4.560322e-002, 4.567231e-002, 4.574352e-002, 4.581284e-002, 4.588116e-002, 4.595273e-002, 4.602118e-002, 4.608709e-002, + 4.615217e-002, 4.621767e-002, 4.626827e-002, 4.630312e-002, 4.633656e-002, 4.636755e-002, 4.639629e-002, 4.642355e-002, 4.644679e-002, + 4.646587e-002, 4.647318e-002, 4.647298e-002, 4.645929e-002, 4.642001e-002, 4.637724e-002, 4.632851e-002, 4.627617e-002, 4.621972e-002, + 4.616037e-002, 4.609914e-002, 4.603108e-002, 4.595954e-002, 4.588228e-002, 4.576596e-002, 4.564738e-002, 4.552689e-002, 4.540092e-002, + 4.527560e-002, 4.515920e-002, 4.503993e-002, 4.491476e-002, 4.478820e-002, 4.466109e-002, 4.449835e-002, 4.432483e-002, 4.414532e-002, + 4.395967e-002, 4.377069e-002, 4.358043e-002, 4.338869e-002, 4.319033e-002, 4.298494e-002, 4.277253e-002, 4.252574e-002, 4.225473e-002, + 4.197675e-002, 4.169117e-002, 4.139794e-002, 4.109385e-002, 4.078462e-002, 4.046842e-002, 4.014334e-002, 3.981247e-002, 3.945714e-002, + 3.906555e-002, 3.866814e-002, 3.826758e-002, 3.786155e-002, 3.744989e-002, 3.703492e-002, 3.661620e-002, 3.618247e-002, 3.574338e-002, + 3.529116e-002, 3.478797e-002, 3.428280e-002, 3.377420e-002, 3.326453e-002, 3.275462e-002, 3.224466e-002, 3.173934e-002, 3.123526e-002, + 3.072762e-002, 3.021816e-002, 2.964021e-002, 2.905079e-002, 2.846012e-002, 2.786797e-002, 2.727414e-002, 2.667884e-002, 2.608199e-002, + 2.548819e-002, 2.488706e-002, 2.428204e-002, 2.362702e-002, 2.295218e-002, 2.227662e-002, 2.159595e-002, 2.090188e-002, 2.020631e-002, + 1.950435e-002, 1.880100e-002, 1.810477e-002, 1.740962e-002, 1.667748e-002, 1.590205e-002, 1.512578e-002, 1.434887e-002, 1.356570e-002, + 1.277875e-002, 1.199463e-002, 1.119623e-002, 1.039175e-002, 9.595671e-003, 8.791325e-003, 7.919921e-003, 7.045715e-003, 6.167517e-003, + 5.288726e-003, 4.409267e-003, 3.529053e-003, 2.647947e-003, 1.765928e-003, 8.832766e-004, 4.011034e-018}, + {0.000000e+000, -4.942774e-002, -5.620194e-002, -6.033508e-002, -5.956883e-002, -5.694401e-002, -5.641818e-002, -5.612780e-002, -5.565626e-002, + -5.512035e-002, -5.483838e-002, -5.472761e-002, -5.432260e-002, -5.318601e-002, -5.067231e-002, -4.704666e-002, -4.284764e-002, -3.838921e-002, + -3.397954e-002, -2.923316e-002, -2.429448e-002, -1.937010e-002, -1.465969e-002, -1.029320e-002, -6.031593e-003, -1.927065e-003, 1.963398e-003, + 5.518040e-003, 8.619470e-003, 1.143255e-002, 1.404786e-002, 1.645029e-002, 1.860436e-002, 2.056524e-002, 2.248132e-002, 2.437502e-002, + 2.627858e-002, 2.807497e-002, 2.982601e-002, 3.151945e-002, 3.316860e-002, 3.468624e-002, 3.622281e-002, 3.772091e-002, 3.912694e-002, + 4.043210e-002, 4.170604e-002, 4.286968e-002, 4.383504e-002, 4.481109e-002, 4.576937e-002, 4.675407e-002, 4.790732e-002, 4.898121e-002, + 5.000870e-002, 5.093195e-002, 5.167297e-002, 5.217184e-002, 5.227566e-002, 5.205385e-002, 5.131330e-002, 5.008573e-002, 4.855362e-002, + 4.685183e-002, 4.493919e-002, 4.285717e-002, 4.068231e-002, 3.840300e-002, 3.606937e-002, 3.367056e-002, 3.120329e-002, 2.870178e-002, + 2.623596e-002, 2.376192e-002, 2.130460e-002, 1.887164e-002, 1.651063e-002, 1.418260e-002, 1.188893e-002, 9.681037e-003, 7.517575e-003, + 5.432591e-003, 3.391561e-003, 1.538631e-003, -2.844528e-004, -2.015985e-003, -3.620587e-003, -5.208189e-003, -6.690715e-003, -8.090300e-003, + -9.482404e-003, -1.076560e-002, -1.206911e-002, -1.334254e-002, -1.455664e-002, -1.580701e-002, -1.704343e-002, -1.825500e-002, -1.951005e-002, + -2.078845e-002, -2.204449e-002, -2.325520e-002, -2.438905e-002, -2.545315e-002, -2.642558e-002, -2.733950e-002, -2.818972e-002, -2.892898e-002, + -2.958445e-002, -3.017323e-002, -3.072878e-002, -3.121372e-002, -3.166043e-002, -3.205790e-002, -3.240439e-002, -3.272266e-002, -3.304446e-002, + -3.336613e-002, -3.367364e-002, -3.402521e-002, -3.438424e-002, -3.481048e-002, -3.524815e-002, -3.569441e-002, -3.614043e-002, -3.658523e-002, + -3.702793e-002, -3.746327e-002, -3.790564e-002, -3.835358e-002, -3.879603e-002, -3.924533e-002, -3.966620e-002, -4.009375e-002, -4.051143e-002, + -4.088426e-002, -4.125696e-002, -4.163597e-002, -4.201228e-002, -4.239476e-002, -4.277260e-002, -4.312579e-002, -4.338952e-002, -4.366793e-002, + -4.396312e-002, -4.420515e-002, -4.442900e-002, -4.464002e-002, -4.482948e-002, -4.495943e-002, -4.503289e-002, -4.506701e-002, -4.506025e-002, + -4.501793e-002, -4.494139e-002, -4.483739e-002, -4.463975e-002, -4.437333e-002, -4.408866e-002, -4.375725e-002, -4.338542e-002, -4.296531e-002, + -4.241919e-002, -4.182143e-002, -4.117225e-002, -4.050738e-002, -3.977809e-002, -3.895029e-002, -3.804492e-002, -3.709414e-002, -3.609185e-002, + -3.504401e-002, -3.386098e-002, -3.263345e-002, -3.139147e-002, -3.011820e-002, -2.872601e-002, -2.728554e-002, -2.583946e-002, -2.438591e-002, + -2.279293e-002, -2.118350e-002, -1.958839e-002, -1.792443e-002, -1.623116e-002, -1.453449e-002, -1.277259e-002, -1.098264e-002, -9.226227e-003, + -7.382607e-003, -5.560485e-003, -3.718502e-003, -1.842124e-003, -5.982263e-006, 1.884596e-003, 3.715761e-003, 5.609214e-003, 7.455636e-003, + 9.339029e-003, 1.119537e-002, 1.304122e-002, 1.484337e-002, 1.661710e-002, 1.836591e-002, 2.006511e-002, 2.174788e-002, 2.335466e-002, + 2.494083e-002, 2.643510e-002, 2.791140e-002, 2.929740e-002, 3.065722e-002, 3.191392e-002, 3.314456e-002, 3.430343e-002, 3.540971e-002, + 3.646118e-002, 3.743421e-002, 3.839122e-002, 3.926222e-002, 4.011405e-002, 4.093749e-002, 4.168461e-002, 4.242333e-002, 4.308857e-002, + 4.372123e-002, 4.433603e-002, 4.488468e-002, 4.539105e-002, 4.588555e-002, 4.632587e-002, 4.674220e-002, 4.714128e-002, 4.748918e-002, + 4.778586e-002, 4.806970e-002, 4.833884e-002, 4.855142e-002, 4.875623e-002, 4.894397e-002, 4.907882e-002, 4.918094e-002, 4.926698e-002, + 4.934852e-002, 4.937562e-002, 4.939142e-002, 4.939379e-002, 4.937365e-002, 4.930145e-002, 4.922329e-002, 4.913717e-002, 4.905406e-002, + 4.891887e-002, 4.877922e-002, 4.864691e-002, 4.850716e-002, 4.832685e-002, 4.816567e-002, 4.799680e-002, 4.783320e-002, 4.765780e-002, + 4.745879e-002, 4.725898e-002, 4.705580e-002, 4.684707e-002, 4.661899e-002, 4.639545e-002, 4.617675e-002, 4.595969e-002, 4.572458e-002, + 4.547079e-002, 4.523614e-002, 4.501944e-002, 4.481294e-002, 4.460572e-002, 4.441004e-002, 4.421824e-002, 4.403754e-002, 4.386717e-002, + 4.368081e-002, 4.349568e-002, 4.332300e-002, 4.315284e-002, 4.298348e-002, 4.280567e-002, 4.261786e-002, 4.241911e-002, 4.223286e-002, + 4.205297e-002, 4.185197e-002, 4.164386e-002, 4.143250e-002, 4.122115e-002, 4.099980e-002, 4.077129e-002, 4.053252e-002, 4.028345e-002, + 4.003343e-002, 3.976313e-002, 3.947468e-002, 3.916118e-002, 3.882683e-002, 3.847438e-002, 3.810547e-002, 3.772306e-002, 3.730540e-002, + 3.685602e-002, 3.638464e-002, 3.589005e-002, 3.536914e-002, 3.482636e-002, 3.425109e-002, 3.364158e-002, 3.301847e-002, 3.234993e-002, + 3.164629e-002, 3.092118e-002, 3.014186e-002, 2.933030e-002, 2.851492e-002, 2.767072e-002, 2.679345e-002, 2.590840e-002, 2.500243e-002, + 2.404951e-002, 2.306575e-002, 2.209714e-002, 2.112626e-002, 2.015158e-002, 1.917142e-002, 1.818766e-002, 1.719707e-002, 1.618570e-002, + 1.519512e-002, 1.419316e-002, 1.317726e-002, 1.215139e-002, 1.114372e-002, 1.012906e-002, 9.111430e-003, 8.106138e-003, 7.095371e-003, + 6.057204e-003, 5.009886e-003, 3.978694e-003, 2.942915e-003, 1.905639e-003, 8.895909e-004, -1.320180e-004, -1.177145e-003, -2.230970e-003, + -3.260533e-003, -4.289462e-003, -5.321955e-003, -6.345175e-003, -7.371445e-003, -8.383436e-003, -9.392233e-003, -1.039225e-002, -1.138916e-002, + -1.238843e-002, -1.339274e-002, -1.437815e-002, -1.536229e-002, -1.633839e-002, -1.730141e-002, -1.823734e-002, -1.917128e-002, -2.011388e-002, + -2.105184e-002, -2.196640e-002, -2.288619e-002, -2.380939e-002, -2.471618e-002, -2.559052e-002, -2.646637e-002, -2.735472e-002, -2.822763e-002, + -2.907988e-002, -2.992485e-002, -3.076427e-002, -3.158522e-002, -3.238191e-002, -3.318139e-002, -3.397746e-002, -3.474015e-002, -3.547172e-002, + -3.619741e-002, -3.691719e-002, -3.762212e-002, -3.828962e-002, -3.896476e-002, -3.963787e-002, -4.028500e-002, -4.090150e-002, -4.150252e-002, + -4.209054e-002, -4.266695e-002, -4.320766e-002, -4.374175e-002, -4.427590e-002, -4.479413e-002, -4.526666e-002, -4.573208e-002, -4.618809e-002, + -4.663705e-002, -4.705281e-002, -4.747072e-002, -4.787753e-002, -4.827656e-002, -4.865137e-002, -4.900735e-002, -4.935996e-002, -4.970447e-002, + -5.003527e-002, -5.033950e-002, -5.064212e-002, -5.093999e-002, -5.122976e-002, -5.149338e-002, -5.174827e-002, -5.199781e-002, -5.223649e-002, + -5.245069e-002, -5.267392e-002, -5.289036e-002, -5.308399e-002, -5.323626e-002, -5.335505e-002, -5.344816e-002, -5.351148e-002, -5.354967e-002, + -5.354666e-002, -5.353814e-002, -5.351493e-002, -5.347344e-002, -5.342013e-002, -5.334387e-002, -5.325380e-002, -5.315332e-002, -5.302734e-002, + -5.286562e-002, -5.268859e-002, -5.251739e-002, -5.232002e-002, -5.209273e-002, -5.184579e-002, -5.159129e-002, -5.132721e-002, -5.104986e-002, + -5.074819e-002, -5.043375e-002, -5.010742e-002, -4.977912e-002, -4.944048e-002, -4.907736e-002, -4.869887e-002, -4.830468e-002, -4.790432e-002, + -4.748158e-002, -4.702665e-002, -4.656120e-002, -4.608877e-002, -4.562646e-002, -4.514820e-002, -4.466099e-002, -4.417314e-002, -4.367368e-002, + -4.316069e-002, -4.263195e-002, -4.210386e-002, -4.157405e-002, -4.103127e-002, -4.047939e-002, -3.993559e-002, -3.939004e-002, -3.883940e-002, + -3.828306e-002, -3.773155e-002, -3.717403e-002, -3.661549e-002, -3.605951e-002, -3.551314e-002, -3.496430e-002, -3.440647e-002, -3.386595e-002, + -3.333983e-002, -3.282090e-002, -3.229771e-002, -3.175423e-002, -3.122185e-002, -3.069928e-002, -3.018090e-002, -2.967027e-002, -2.916218e-002, + -2.866019e-002, -2.816653e-002, -2.768821e-002, -2.722911e-002, -2.676853e-002, -2.631747e-002, -2.587422e-002, -2.543550e-002, -2.500765e-002, + -2.458189e-002, -2.415671e-002, -2.374382e-002, -2.334658e-002, -2.295395e-002, -2.256492e-002, -2.220331e-002, -2.185116e-002, -2.150136e-002, + -2.114806e-002, -2.077344e-002, -2.039209e-002, -2.001069e-002, -1.962643e-002, -1.924048e-002, -1.885211e-002, -1.846616e-002, -1.808152e-002, + -1.768378e-002, -1.729128e-002, -1.690856e-002, -1.651797e-002, -1.611085e-002, -1.569417e-002, -1.526706e-002, -1.484520e-002, -1.442802e-002, + -1.399961e-002, -1.356751e-002, -1.313351e-002, -1.269229e-002, -1.223855e-002, -1.177302e-002, -1.130890e-002, -1.084769e-002, -1.038641e-002, + -9.920370e-003, -9.459815e-003, -8.997027e-003, -8.539077e-003, -8.080995e-003, -7.620812e-003, -7.154236e-003, -6.685203e-003, -6.201714e-003, + -5.700616e-003, -5.192633e-003, -4.689251e-003, -4.188658e-003, -3.691479e-003, -3.183011e-003, -2.668322e-003, -2.151657e-003, -1.628537e-003, + -1.086054e-003, -5.227901e-004, 4.565609e-005, 6.164596e-004, 1.178995e-003, 1.747960e-003, 2.329319e-003, 2.914850e-003, 3.498697e-003, + 4.084484e-003, 4.669223e-003, 5.253026e-003, 5.845171e-003, 6.442419e-003, 7.038177e-003, 7.633871e-003, 8.226937e-003, 8.821689e-003, + 9.420432e-003, 1.002058e-002, 1.061902e-002, 1.121747e-002, 1.181805e-002, 1.241627e-002, 1.301694e-002, 1.362216e-002, 1.421938e-002, + 1.478960e-002, 1.535054e-002, 1.590619e-002, 1.645529e-002, 1.700278e-002, 1.754731e-002, 1.808484e-002, 1.861915e-002, 1.915513e-002, + 1.970014e-002, 2.024049e-002, 2.077330e-002, 2.130200e-002, 2.180283e-002, 2.229387e-002, 2.279252e-002, 2.328979e-002, 2.378641e-002, + 2.427753e-002, 2.475956e-002, 2.523740e-002, 2.572076e-002, 2.620044e-002, 2.668112e-002, 2.715737e-002, 2.760834e-002, 2.801831e-002, + 2.842659e-002, 2.883425e-002, 2.923489e-002, 2.962492e-002, 3.000631e-002, 3.038162e-002, 3.075594e-002, 3.113703e-002, 3.151418e-002, + 3.188319e-002, 3.224468e-002, 3.259722e-002, 3.294979e-002, 3.327500e-002, 3.356624e-002, 3.385194e-002, 3.413390e-002, 3.441647e-002, + 3.469609e-002, 3.497764e-002, 3.525620e-002, 3.553041e-002, 3.580024e-002, 3.606073e-002, 3.631222e-002, 3.655632e-002, 3.679405e-002, + 3.702437e-002, 3.724145e-002, 3.744400e-002, 3.763503e-002, 3.780758e-002, 3.797359e-002, 3.811815e-002, 3.824159e-002, 3.835526e-002, + 3.845868e-002, 3.854784e-002, 3.861961e-002, 3.868151e-002, 3.872866e-002, 3.875724e-002, 3.877605e-002, 3.878833e-002, 3.879805e-002, + 3.880025e-002, 3.879599e-002, 3.878468e-002, 3.876084e-002, 3.873342e-002, 3.870528e-002, 3.867295e-002, 3.863528e-002, 3.858880e-002, + 3.853180e-002, 3.846744e-002, 3.839832e-002, 3.832864e-002, 3.826046e-002, 3.819760e-002, 3.810429e-002, 3.800899e-002, 3.791874e-002, + 3.782892e-002, 3.772060e-002, 3.761236e-002, 3.750538e-002, 3.739747e-002, 3.728814e-002, 3.717441e-002, 3.705464e-002, 3.693168e-002, + 3.681214e-002, 3.669692e-002, 3.656380e-002, 3.640993e-002, 3.624536e-002, 3.607313e-002, 3.589502e-002, 3.571964e-002, 3.554758e-002, + 3.537479e-002, 3.520112e-002, 3.502661e-002, 3.484793e-002, 3.464983e-002, 3.444898e-002, 3.424778e-002, 3.404516e-002, 3.384146e-002, + 3.364158e-002, 3.343987e-002, 3.323213e-002, 3.302314e-002, 3.281397e-002, 3.259222e-002, 3.236374e-002, 3.212910e-002, 3.189317e-002, + 3.165666e-002, 3.141257e-002, 3.116749e-002, 3.092317e-002, 3.067598e-002, 3.042475e-002, 3.016504e-002, 2.989867e-002, 2.962749e-002, + 2.935065e-002, 2.906138e-002, 2.876405e-002, 2.846271e-002, 2.816342e-002, 2.786495e-002, 2.757206e-002, 2.727151e-002, 2.696350e-002, + 2.664963e-002, 2.633198e-002, 2.601260e-002, 2.569912e-002, 2.538470e-002, 2.507121e-002, 2.476143e-002, 2.445594e-002, 2.414133e-002, + 2.382350e-002, 2.349838e-002, 2.317493e-002, 2.285167e-002, 2.253187e-002, 2.221263e-002, 2.189212e-002, 2.157099e-002, 2.124493e-002, + 2.090486e-002, 2.055575e-002, 2.020317e-002, 1.984898e-002, 1.949519e-002, 1.913724e-002, 1.877502e-002, 1.840690e-002, 1.803390e-002, + 1.765454e-002, 1.728018e-002, 1.689616e-002, 1.651421e-002, 1.613230e-002, 1.574839e-002, 1.536175e-002, 1.497380e-002, 1.458848e-002, + 1.420760e-002, 1.382438e-002, 1.343259e-002, 1.303983e-002, 1.265022e-002, 1.227147e-002, 1.188726e-002, 1.150222e-002, 1.112043e-002, + 1.073746e-002, 1.035444e-002, 9.974270e-003, 9.594954e-003, 9.200553e-003, 8.800720e-003, 8.400698e-003, 8.004350e-003, 7.608551e-003, + 7.216144e-003, 6.821407e-003, 6.426434e-003, 6.029861e-003, 5.628536e-003, 5.233361e-003, 4.845306e-003, 4.463979e-003, 4.083287e-003, + 3.705020e-003, 3.327617e-003, 2.951562e-003, 2.576235e-003, 2.205662e-003, 1.838341e-003, 1.474079e-003, 1.108195e-003, 7.421916e-004, + 3.808521e-004, 2.287793e-005, -3.265042e-004, -6.716933e-004, -1.008671e-003, -1.340826e-003, -1.668361e-003, -1.995238e-003, -2.317955e-003, + -2.635013e-003, -2.951551e-003, -3.266539e-003, -3.587078e-003, -3.920640e-003, -4.254221e-003, -4.585536e-003, -4.916072e-003, -5.249222e-003, + -5.584866e-003, -5.923222e-003, -6.261374e-003, -6.602499e-003, -6.942125e-003, -7.277854e-003, -7.612200e-003, -7.946726e-003, -8.279381e-003, + -8.611686e-003, -8.946173e-003, -9.271993e-003, -9.597305e-003, -9.926970e-003, -1.025736e-002, -1.058713e-002, -1.091496e-002, -1.123693e-002, + -1.154777e-002, -1.185163e-002, -1.215326e-002, -1.244934e-002, -1.274713e-002, -1.304510e-002, -1.334134e-002, -1.363490e-002, -1.392559e-002, + -1.421481e-002, -1.450545e-002, -1.479699e-002, -1.509105e-002, -1.538179e-002, -1.567328e-002, -1.596203e-002, -1.625159e-002, -1.654580e-002, + -1.684388e-002, -1.714350e-002, -1.744390e-002, -1.774144e-002, -1.803897e-002, -1.832986e-002, -1.861655e-002, -1.890207e-002, -1.919060e-002, + -1.947551e-002, -1.975853e-002, -2.003435e-002, -2.030911e-002, -2.058764e-002, -2.087049e-002, -2.114509e-002, -2.140793e-002, -2.166401e-002, + -2.191575e-002, -2.216553e-002, -2.241441e-002, -2.266377e-002, -2.291306e-002, -2.316472e-002, -2.341229e-002, -2.365436e-002, -2.387535e-002, + -2.409555e-002, -2.431321e-002, -2.452835e-002, -2.474030e-002, -2.494991e-002, -2.515961e-002, -2.536702e-002, -2.557086e-002, -2.576876e-002, + -2.594413e-002, -2.611726e-002, -2.628871e-002, -2.645848e-002, -2.662294e-002, -2.678332e-002, -2.694499e-002, -2.710695e-002, -2.726485e-002, + -2.742393e-002, -2.756317e-002, -2.769724e-002, -2.783294e-002, -2.796886e-002, -2.810566e-002, -2.824310e-002, -2.838025e-002, -2.851599e-002, + -2.865608e-002, -2.879542e-002, -2.891674e-002, -2.902496e-002, -2.913312e-002, -2.924063e-002, -2.934805e-002, -2.945552e-002, -2.955898e-002, + -2.965715e-002, -2.975098e-002, -2.984358e-002, -2.992523e-002, -2.998339e-002, -3.004271e-002, -3.009951e-002, -3.015406e-002, -3.019989e-002, + -3.024388e-002, -3.028488e-002, -3.031982e-002, -3.035221e-002, -3.037962e-002, -3.036888e-002, -3.035598e-002, -3.033809e-002, -3.031826e-002, + -3.029319e-002, -3.027401e-002, -3.026247e-002, -3.025223e-002, -3.024180e-002, -3.023034e-002, -3.018051e-002, -3.012447e-002, -3.006679e-002, + -3.000334e-002, -2.993978e-002, -2.987663e-002, -2.981411e-002, -2.974799e-002, -2.967617e-002, -2.960237e-002, -2.949654e-002, -2.936942e-002, + -2.923805e-002, -2.910164e-002, -2.896236e-002, -2.881608e-002, -2.866542e-002, -2.851460e-002, -2.835908e-002, -2.820026e-002, -2.801855e-002, + -2.780135e-002, -2.757821e-002, -2.735127e-002, -2.711877e-002, -2.688338e-002, -2.664550e-002, -2.640613e-002, -2.616013e-002, -2.591142e-002, + -2.565332e-002, -2.534148e-002, -2.501931e-002, -2.469332e-002, -2.436732e-002, -2.404210e-002, -2.371715e-002, -2.339278e-002, -2.307412e-002, + -2.275437e-002, -2.243056e-002, -2.204322e-002, -2.164620e-002, -2.125142e-002, -2.085845e-002, -2.046642e-002, -2.007482e-002, -1.968302e-002, + -1.928721e-002, -1.888776e-002, -1.848611e-002, -1.803246e-002, -1.755346e-002, -1.707420e-002, -1.659252e-002, -1.610423e-002, -1.561354e-002, + -1.511842e-002, -1.462232e-002, -1.412584e-002, -1.362301e-002, -1.307952e-002, -1.248736e-002, -1.189278e-002, -1.129640e-002, -1.069338e-002, + -1.008949e-002, -9.484904e-003, -8.873625e-003, -8.260552e-003, -7.650085e-003, -7.016932e-003, -6.315541e-003, -5.610504e-003, -4.902638e-003, + -4.196229e-003, -3.491864e-003, -2.789194e-003, -2.088697e-003, -1.390051e-003, -6.939679e-004, 7.302615e-018}, + {0.000000e+000, 1.648656e-001, 1.665361e-001, 1.587156e-001, 1.482945e-001, 1.367121e-001, 1.248286e-001, 1.123901e-001, 1.003328e-001, + 8.868965e-002, 7.747023e-002, 6.684091e-002, 5.697325e-002, 4.802379e-002, 3.948657e-002, 3.107607e-002, 2.273036e-002, 1.463456e-002, + 7.107590e-003, 3.194369e-004, -5.674128e-003, -1.068986e-002, -1.490075e-002, -1.856333e-002, -2.140829e-002, -2.369741e-002, -2.582885e-002, + -2.797068e-002, -3.036549e-002, -3.301093e-002, -3.583747e-002, -3.874029e-002, -4.151691e-002, -4.409961e-002, -4.654450e-002, -4.871264e-002, + -5.055134e-002, -5.199464e-002, -5.342826e-002, -5.483972e-002, -5.610964e-002, -5.707460e-002, -5.788090e-002, -5.844096e-002, -5.862796e-002, + -5.841032e-002, -5.776606e-002, -5.671255e-002, -5.530392e-002, -5.367291e-002, -5.185020e-002, -4.960074e-002, -4.698220e-002, -4.393180e-002, + -4.064743e-002, -3.711994e-002, -3.339834e-002, -2.964691e-002, -2.581521e-002, -2.207445e-002, -1.846601e-002, -1.502998e-002, -1.155335e-002, + -8.126644e-003, -4.756059e-003, -1.400728e-003, 1.962585e-003, 5.239039e-003, 8.424385e-003, 1.154155e-002, 1.452822e-002, 1.738345e-002, + 2.007916e-002, 2.267727e-002, 2.507508e-002, 2.724398e-002, 2.918630e-002, 3.097799e-002, 3.251732e-002, 3.378478e-002, 3.479866e-002, + 3.553151e-002, 3.607182e-002, 3.634344e-002, 3.642145e-002, 3.636087e-002, 3.614136e-002, 3.580433e-002, 3.526922e-002, 3.470799e-002, + 3.413901e-002, 3.350797e-002, 3.284612e-002, 3.219442e-002, 3.155684e-002, 3.097272e-002, 3.048014e-002, 3.007404e-002, 2.974699e-002, + 2.953383e-002, 2.933729e-002, 2.911774e-002, 2.896211e-002, 2.881185e-002, 2.870212e-002, 2.861064e-002, 2.853348e-002, 2.851805e-002, + 2.856296e-002, 2.866926e-002, 2.866844e-002, 2.870975e-002, 2.878630e-002, 2.887089e-002, 2.890231e-002, 2.888916e-002, 2.880110e-002, + 2.868867e-002, 2.852794e-002, 2.823901e-002, 2.785424e-002, 2.738615e-002, 2.681202e-002, 2.619428e-002, 2.553211e-002, 2.482031e-002, + 2.403611e-002, 2.320999e-002, 2.231290e-002, 2.137543e-002, 2.035766e-002, 1.928418e-002, 1.815463e-002, 1.690966e-002, 1.558195e-002, + 1.415801e-002, 1.263356e-002, 1.102963e-002, 9.399969e-003, 7.696888e-003, 5.979689e-003, 4.227131e-003, 2.499382e-003, 8.100498e-004, + -9.187090e-004, -2.584860e-003, -4.211633e-003, -5.814482e-003, -7.312234e-003, -8.750723e-003, -1.008900e-002, -1.132133e-002, -1.245027e-002, + -1.345079e-002, -1.439867e-002, -1.531712e-002, -1.616317e-002, -1.693821e-002, -1.768208e-002, -1.840368e-002, -1.909185e-002, -1.975477e-002, + -2.033744e-002, -2.081399e-002, -2.126617e-002, -2.172850e-002, -2.213712e-002, -2.249120e-002, -2.279416e-002, -2.307447e-002, -2.332246e-002, + -2.356600e-002, -2.376032e-002, -2.391474e-002, -2.406796e-002, -2.420042e-002, -2.427748e-002, -2.432112e-002, -2.436340e-002, -2.440945e-002, + -2.438278e-002, -2.435546e-002, -2.431735e-002, -2.425829e-002, -2.414404e-002, -2.403202e-002, -2.386826e-002, -2.370082e-002, -2.356622e-002, + -2.336001e-002, -2.315214e-002, -2.292051e-002, -2.268686e-002, -2.246260e-002, -2.220474e-002, -2.197391e-002, -2.173555e-002, -2.152616e-002, + -2.130609e-002, -2.114136e-002, -2.099700e-002, -2.089786e-002, -2.081976e-002, -2.075867e-002, -2.072316e-002, -2.070624e-002, -2.071699e-002, + -2.072785e-002, -2.076722e-002, -2.082479e-002, -2.093703e-002, -2.105672e-002, -2.120003e-002, -2.133434e-002, -2.150244e-002, -2.168458e-002, + -2.189217e-002, -2.212635e-002, -2.237097e-002, -2.263425e-002, -2.289521e-002, -2.315157e-002, -2.341235e-002, -2.368894e-002, -2.397271e-002, + -2.425522e-002, -2.452157e-002, -2.478456e-002, -2.505032e-002, -2.530129e-002, -2.555797e-002, -2.581819e-002, -2.603833e-002, -2.625279e-002, + -2.647385e-002, -2.667508e-002, -2.686011e-002, -2.704261e-002, -2.719508e-002, -2.733054e-002, -2.745848e-002, -2.756610e-002, -2.763009e-002, + -2.767220e-002, -2.769987e-002, -2.770107e-002, -2.767007e-002, -2.760205e-002, -2.750579e-002, -2.735505e-002, -2.716490e-002, -2.693854e-002, + -2.668225e-002, -2.638386e-002, -2.607343e-002, -2.573004e-002, -2.535863e-002, -2.498160e-002, -2.456642e-002, -2.412605e-002, -2.367505e-002, + -2.319377e-002, -2.268316e-002, -2.213112e-002, -2.155380e-002, -2.097018e-002, -2.036167e-002, -1.974150e-002, -1.909899e-002, -1.842746e-002, + -1.772967e-002, -1.698337e-002, -1.619778e-002, -1.538115e-002, -1.454251e-002, -1.365695e-002, -1.273126e-002, -1.175674e-002, -1.074279e-002, + -9.707857e-003, -8.649348e-003, -7.556388e-003, -6.432324e-003, -5.281657e-003, -4.109176e-003, -2.902809e-003, -1.669971e-003, -4.117346e-004, + 8.621738e-004, 2.148278e-003, 3.458079e-003, 4.785930e-003, 6.133727e-003, 7.489635e-003, 8.865912e-003, 1.023105e-002, 1.158935e-002, + 1.296441e-002, 1.434801e-002, 1.574195e-002, 1.713846e-002, 1.852877e-002, 1.991088e-002, 2.126416e-002, 2.261031e-002, 2.397137e-002, + 2.531392e-002, 2.665901e-002, 2.798396e-002, 2.928033e-002, 3.054636e-002, 3.178414e-002, 3.300126e-002, 3.417977e-002, 3.534837e-002, + 3.650872e-002, 3.762551e-002, 3.870045e-002, 3.976638e-002, 4.077133e-002, 4.173600e-002, 4.266960e-002, 4.355760e-002, 4.438432e-002, + 4.514259e-002, 4.589849e-002, 4.662923e-002, 4.733018e-002, 4.801000e-002, 4.867431e-002, 4.931472e-002, 4.993573e-002, 5.052049e-002, + 5.106986e-002, 5.160527e-002, 5.211652e-002, 5.260214e-002, 5.304062e-002, 5.346881e-002, 5.387821e-002, 5.420717e-002, 5.449126e-002, + 5.474501e-002, 5.494970e-002, 5.511051e-002, 5.524536e-002, 5.535545e-002, 5.541000e-002, 5.541096e-002, 5.536532e-002, 5.530058e-002, + 5.517370e-002, 5.502575e-002, 5.487469e-002, 5.469684e-002, 5.447889e-002, 5.422841e-002, 5.394265e-002, 5.359705e-002, 5.323359e-002, + 5.286278e-002, 5.249040e-002, 5.208851e-002, 5.166377e-002, 5.124845e-002, 5.078865e-002, 5.028231e-002, 4.975251e-002, 4.920314e-002, + 4.862747e-002, 4.804810e-002, 4.746467e-002, 4.687119e-002, 4.622861e-002, 4.555613e-002, 4.486221e-002, 4.416155e-002, 4.344962e-002, + 4.270468e-002, 4.197747e-002, 4.125476e-002, 4.051170e-002, 3.976138e-002, 3.900479e-002, 3.822912e-002, 3.743441e-002, 3.663669e-002, + 3.585483e-002, 3.509600e-002, 3.431744e-002, 3.353552e-002, 3.276933e-002, 3.200522e-002, 3.124753e-002, 3.048262e-002, 2.971426e-002, + 2.897447e-002, 2.824838e-002, 2.750587e-002, 2.677799e-002, 2.606182e-002, 2.536254e-002, 2.464162e-002, 2.395793e-002, 2.330333e-002, + 2.267578e-002, 2.203233e-002, 2.138238e-002, 2.074968e-002, 2.013979e-002, 1.952581e-002, 1.889132e-002, 1.823326e-002, 1.758423e-002, + 1.692307e-002, 1.623585e-002, 1.551742e-002, 1.480768e-002, 1.409884e-002, 1.340131e-002, 1.271451e-002, 1.202882e-002, 1.135416e-002, + 1.067201e-002, 9.968630e-003, 9.247445e-003, 8.509544e-003, 7.775959e-003, 7.038999e-003, 6.280407e-003, 5.523698e-003, 4.777245e-003, + 4.030875e-003, 3.290009e-003, 2.543304e-003, 1.789783e-003, 1.011804e-003, 2.362518e-004, -5.346216e-004, -1.316201e-003, -2.105008e-003, + -2.886694e-003, -3.658983e-003, -4.446426e-003, -5.240270e-003, -6.035550e-003, -6.841524e-003, -7.655121e-003, -8.459005e-003, -9.266256e-003, + -1.009471e-002, -1.091878e-002, -1.173407e-002, -1.254942e-002, -1.335987e-002, -1.418019e-002, -1.497996e-002, -1.576961e-002, -1.657450e-002, + -1.737380e-002, -1.818221e-002, -1.897833e-002, -1.976453e-002, -2.054819e-002, -2.133598e-002, -2.212494e-002, -2.290213e-002, -2.365877e-002, + -2.439701e-002, -2.513541e-002, -2.589567e-002, -2.664362e-002, -2.738065e-002, -2.811278e-002, -2.886028e-002, -2.959162e-002, -3.031390e-002, + -3.103428e-002, -3.175000e-002, -3.245801e-002, -3.316228e-002, -3.384967e-002, -3.453199e-002, -3.519807e-002, -3.586001e-002, -3.652391e-002, + -3.718030e-002, -3.782117e-002, -3.843542e-002, -3.904701e-002, -3.965390e-002, -4.024772e-002, -4.082225e-002, -4.137917e-002, -4.192935e-002, + -4.246910e-002, -4.300001e-002, -4.352846e-002, -4.404700e-002, -4.453667e-002, -4.500289e-002, -4.544391e-002, -4.587197e-002, -4.627895e-002, + -4.666678e-002, -4.703594e-002, -4.739371e-002, -4.774283e-002, -4.808899e-002, -4.841911e-002, -4.874365e-002, -4.905338e-002, -4.935911e-002, + -4.966440e-002, -4.996350e-002, -5.024957e-002, -5.050456e-002, -5.074863e-002, -5.098292e-002, -5.120698e-002, -5.142354e-002, -5.161209e-002, + -5.178359e-002, -5.195647e-002, -5.212073e-002, -5.227669e-002, -5.241440e-002, -5.252967e-002, -5.264015e-002, -5.273358e-002, -5.281561e-002, + -5.289321e-002, -5.296201e-002, -5.300858e-002, -5.303278e-002, -5.303784e-002, -5.302831e-002, -5.300271e-002, -5.296007e-002, -5.289239e-002, + -5.280832e-002, -5.271210e-002, -5.260884e-002, -5.249797e-002, -5.238561e-002, -5.224753e-002, -5.208700e-002, -5.190544e-002, -5.170276e-002, + -5.148356e-002, -5.125040e-002, -5.098638e-002, -5.070225e-002, -5.040520e-002, -5.007878e-002, -4.973669e-002, -4.937999e-002, -4.901314e-002, + -4.860741e-002, -4.817192e-002, -4.771127e-002, -4.723127e-002, -4.673206e-002, -4.621924e-002, -4.568841e-002, -4.514000e-002, -4.457640e-002, + -4.400028e-002, -4.341116e-002, -4.280731e-002, -4.219416e-002, -4.157232e-002, -4.092678e-002, -4.025705e-002, -3.956513e-002, -3.885764e-002, + -3.815207e-002, -3.743736e-002, -3.671728e-002, -3.598237e-002, -3.524156e-002, -3.449909e-002, -3.376225e-002, -3.302115e-002, -3.225729e-002, + -3.147250e-002, -3.067132e-002, -2.987300e-002, -2.909771e-002, -2.831407e-002, -2.751718e-002, -2.670992e-002, -2.589341e-002, -2.505947e-002, + -2.422223e-002, -2.339326e-002, -2.255378e-002, -2.168102e-002, -2.083297e-002, -1.998217e-002, -1.911538e-002, -1.824876e-002, -1.738020e-002, + -1.650432e-002, -1.561513e-002, -1.473073e-002, -1.385448e-002, -1.296939e-002, -1.208226e-002, -1.117867e-002, -1.029849e-002, -9.443680e-003, + -8.587966e-003, -7.731475e-003, -6.874128e-003, -6.026714e-003, -5.182423e-003, -4.338335e-003, -3.494409e-003, -2.646398e-003, -1.786433e-003, + -9.332632e-004, -8.711178e-005, 7.550747e-004, 1.603341e-003, 2.435620e-003, 3.211347e-003, 3.983251e-003, 4.751629e-003, 5.523783e-003, + 6.307042e-003, 7.080800e-003, 7.847702e-003, 8.614441e-003, 9.387861e-003, 1.015561e-002, 1.090676e-002, 1.164601e-002, 1.238417e-002, + 1.312293e-002, 1.386531e-002, 1.460412e-002, 1.533420e-002, 1.605149e-002, 1.676201e-002, 1.744608e-002, 1.809224e-002, 1.872866e-002, + 1.935607e-002, 1.995618e-002, 2.049669e-002, 2.100994e-002, 2.151096e-002, 2.199572e-002, 2.247969e-002, 2.296158e-002, 2.344297e-002, + 2.392009e-002, 2.438694e-002, 2.485870e-002, 2.531045e-002, 2.573474e-002, 2.616205e-002, 2.655987e-002, 2.693471e-002, 2.731010e-002, + 2.769128e-002, 2.805704e-002, 2.841248e-002, 2.877477e-002, 2.913777e-002, 2.950030e-002, 2.977982e-002, 3.004592e-002, 3.031101e-002, + 3.057765e-002, 3.084844e-002, 3.112298e-002, 3.139820e-002, 3.162949e-002, 3.185257e-002, 3.208182e-002, 3.230989e-002, 3.253178e-002, + 3.275634e-002, 3.297829e-002, 3.313283e-002, 3.323217e-002, 3.332072e-002, 3.341181e-002, 3.349416e-002, 3.356566e-002, 3.363237e-002, + 3.370015e-002, 3.377932e-002, 3.386042e-002, 3.394486e-002, 3.403215e-002, 3.406932e-002, 3.409846e-002, 3.413776e-002, 3.418040e-002, + 3.422501e-002, 3.424525e-002, 3.422114e-002, 3.417634e-002, 3.413455e-002, 3.408325e-002, 3.401298e-002, 3.392511e-002, 3.383990e-002, + 3.375620e-002, 3.367675e-002, 3.358883e-002, 3.349950e-002, 3.340714e-002, 3.331938e-002, 3.323020e-002, 3.314692e-002, 3.305986e-002, + 3.297870e-002, 3.289542e-002, 3.274754e-002, 3.256955e-002, 3.239759e-002, 3.222514e-002, 3.206015e-002, 3.185798e-002, 3.163931e-002, + 3.142029e-002, 3.120606e-002, 3.099147e-002, 3.078312e-002, 3.058375e-002, 3.039738e-002, 3.020910e-002, 3.002362e-002, 2.981420e-002, + 2.960401e-002, 2.938910e-002, 2.917766e-002, 2.897555e-002, 2.877144e-002, 2.856065e-002, 2.835637e-002, 2.815078e-002, 2.793368e-002, + 2.769597e-002, 2.746005e-002, 2.722942e-002, 2.699053e-002, 2.675156e-002, 2.650981e-002, 2.625312e-002, 2.597252e-002, 2.569708e-002, + 2.540580e-002, 2.511809e-002, 2.479944e-002, 2.448592e-002, 2.417578e-002, 2.387068e-002, 2.356427e-002, 2.325824e-002, 2.295043e-002, + 2.263333e-002, 2.232220e-002, 2.202341e-002, 2.173364e-002, 2.144986e-002, 2.116938e-002, 2.086964e-002, 2.057378e-002, 2.028044e-002, + 1.998937e-002, 1.969730e-002, 1.940195e-002, 1.909499e-002, 1.877959e-002, 1.846487e-002, 1.815038e-002, 1.783068e-002, 1.750917e-002, + 1.718671e-002, 1.686427e-002, 1.654390e-002, 1.623152e-002, 1.592384e-002, 1.561504e-002, 1.530114e-002, 1.498188e-002, 1.465636e-002, + 1.433815e-002, 1.403352e-002, 1.373639e-002, 1.342216e-002, 1.309391e-002, 1.275781e-002, 1.242559e-002, 1.207810e-002, 1.173428e-002, + 1.140391e-002, 1.108416e-002, 1.077798e-002, 1.048570e-002, 1.020012e-002, 9.921276e-003, 9.644519e-003, 9.369857e-003, 9.098682e-003, + 8.832246e-003, 8.574583e-003, 8.325964e-003, 8.081838e-003, 7.824745e-003, 7.566522e-003, 7.304365e-003, 7.025837e-003, 6.736105e-003, + 6.452535e-003, 6.173803e-003, 5.874006e-003, 5.565829e-003, 5.253627e-003, 4.936111e-003, 4.619931e-003, 4.308389e-003, 3.997063e-003, + 3.688517e-003, 3.384718e-003, 3.085927e-003, 2.790003e-003, 2.490607e-003, 2.193412e-003, 1.900456e-003, 1.610615e-003, 1.322726e-003, + 1.037343e-003, 7.594792e-004, 4.910713e-004, 2.356175e-004, -1.957448e-005, -2.714582e-004, -5.211445e-004, -7.739405e-004, -1.036034e-003, + -1.301894e-003, -1.563699e-003, -1.819830e-003, -2.077159e-003, -2.332951e-003, -2.612326e-003, -2.895497e-003, -3.177904e-003, -3.472516e-003, + -3.769982e-003, -4.063370e-003, -4.352682e-003, -4.642165e-003, -4.926231e-003, -5.204860e-003, -5.470016e-003, -5.730434e-003, -5.995383e-003, + -6.259866e-003, -6.523060e-003, -6.781593e-003, -7.027385e-003, -7.271532e-003, -7.527287e-003, -7.778510e-003, -8.016796e-003, -8.255041e-003, + -8.493239e-003, -8.731262e-003, -8.963758e-003, -9.185029e-003, -9.395365e-003, -9.600169e-003, -9.808688e-003, -1.001340e-002, -1.021733e-002, + -1.041848e-002, -1.061300e-002, -1.080893e-002, -1.100560e-002, -1.119920e-002, -1.138785e-002, -1.157160e-002, -1.175489e-002, -1.193350e-002, + -1.210295e-002, -1.227393e-002, -1.244484e-002, -1.261508e-002, -1.278251e-002, -1.294720e-002, -1.310460e-002, -1.325741e-002, -1.341275e-002, + -1.356422e-002, -1.370696e-002, -1.385329e-002, -1.399826e-002, -1.414139e-002, -1.428106e-002, -1.441340e-002, -1.454424e-002, -1.468369e-002, + -1.482951e-002, -1.497436e-002, -1.511291e-002, -1.523863e-002, -1.536186e-002, -1.548562e-002, -1.560976e-002, -1.573294e-002, -1.586050e-002, + -1.599093e-002, -1.611575e-002, -1.623399e-002, -1.634727e-002, -1.644925e-002, -1.655056e-002, -1.665475e-002, -1.676844e-002, -1.689201e-002, + -1.701580e-002, -1.713525e-002, -1.725052e-002, -1.736236e-002, -1.746980e-002, -1.755745e-002, -1.764517e-002, -1.773411e-002, -1.781477e-002, + -1.789968e-002, -1.798563e-002, -1.806995e-002, -1.815155e-002, -1.823102e-002, -1.831059e-002, -1.837418e-002, -1.842858e-002, -1.847832e-002, + -1.852947e-002, -1.858231e-002, -1.863194e-002, -1.867564e-002, -1.871700e-002, -1.875366e-002, -1.878402e-002, -1.880018e-002, -1.880857e-002, + -1.881712e-002, -1.882766e-002, -1.884144e-002, -1.885651e-002, -1.885886e-002, -1.884915e-002, -1.883217e-002, -1.881215e-002, -1.878209e-002, + -1.873678e-002, -1.869086e-002, -1.864542e-002, -1.859411e-002, -1.853495e-002, -1.847190e-002, -1.840220e-002, -1.831816e-002, -1.822767e-002, + -1.813060e-002, -1.801172e-002, -1.788443e-002, -1.775050e-002, -1.761483e-002, -1.747844e-002, -1.734003e-002, -1.719173e-002, -1.703209e-002, + -1.686579e-002, -1.669635e-002, -1.649078e-002, -1.628269e-002, -1.607659e-002, -1.587214e-002, -1.566752e-002, -1.546166e-002, -1.525455e-002, + -1.503455e-002, -1.478548e-002, -1.452899e-002, -1.425048e-002, -1.396035e-002, -1.366830e-002, -1.337109e-002, -1.306368e-002, -1.275516e-002, + -1.243027e-002, -1.210254e-002, -1.175701e-002, -1.137430e-002, -1.096938e-002, -1.054174e-002, -1.010800e-002, -9.668676e-003, -9.208381e-003, + -8.736989e-003, -8.262726e-003, -7.778388e-003, -7.290102e-003, -6.785662e-003, -6.208459e-003, -5.604584e-003, -4.990339e-003, -4.363681e-003, + -3.737213e-003, -3.111566e-003, -2.486590e-003, -1.863048e-003, -1.240486e-003, -6.196939e-004, -1.779836e-017}, + {0.000000e+000, 1.502618e-001, 1.494584e-001, 1.430128e-001, 1.382886e-001, 1.342618e-001, 1.274038e-001, 1.198531e-001, 1.116745e-001, + 1.029250e-001, 9.320480e-002, 8.278434e-002, 7.194458e-002, 6.109329e-002, 5.131315e-002, 4.285204e-002, 3.544078e-002, 2.829305e-002, + 2.092176e-002, 1.369301e-002, 6.502255e-003, -8.276016e-004, -7.964621e-003, -1.509358e-002, -2.246249e-002, -2.971735e-002, -3.654769e-002, + -4.260653e-002, -4.772251e-002, -5.194169e-002, -5.541601e-002, -5.829437e-002, -6.056519e-002, -6.236312e-002, -6.384026e-002, -6.505423e-002, + -6.609224e-002, -6.669607e-002, -6.691516e-002, -6.685879e-002, -6.652929e-002, -6.568700e-002, -6.463312e-002, -6.343984e-002, -6.202305e-002, + -6.046732e-002, -5.893315e-002, -5.740302e-002, -5.566495e-002, -5.387634e-002, -5.186860e-002, -4.975779e-002, -4.775799e-002, -4.562893e-002, + -4.352805e-002, -4.137753e-002, -3.912613e-002, -3.682632e-002, -3.439250e-002, -3.194815e-002, -2.930056e-002, -2.656167e-002, -2.379128e-002, + -2.098688e-002, -1.814850e-002, -1.536356e-002, -1.261164e-002, -9.868152e-003, -7.173675e-003, -4.525448e-003, -1.965084e-003, 6.168254e-004, + 3.162284e-003, 5.707233e-003, 8.195772e-003, 1.068451e-002, 1.314364e-002, 1.564131e-002, 1.818456e-002, 2.084022e-002, 2.356551e-002, + 2.624975e-002, 2.904896e-002, 3.188372e-002, 3.478474e-002, 3.770818e-002, 4.064820e-002, 4.359833e-002, 4.634738e-002, 4.909670e-002, + 5.182554e-002, 5.439479e-002, 5.680792e-002, 5.902445e-002, 6.099067e-002, 6.269302e-002, 6.421239e-002, 6.544923e-002, 6.624927e-002, + 6.681713e-002, 6.712631e-002, 6.726487e-002, 6.728269e-002, 6.704739e-002, 6.663485e-002, 6.611592e-002, 6.533447e-002, 6.431141e-002, + 6.308573e-002, 6.175877e-002, 6.017576e-002, 5.847610e-002, 5.666525e-002, 5.469120e-002, 5.264451e-002, 5.057616e-002, 4.835811e-002, + 4.606366e-002, 4.371613e-002, 4.122858e-002, 3.877405e-002, 3.628604e-002, 3.372210e-002, 3.113418e-002, 2.848793e-002, 2.586313e-002, + 2.314775e-002, 2.041119e-002, 1.765324e-002, 1.487305e-002, 1.213176e-002, 9.380706e-003, 6.632160e-003, 3.951821e-003, 1.410248e-003, + -1.076965e-003, -3.498997e-003, -5.916821e-003, -8.197745e-003, -1.046101e-002, -1.269171e-002, -1.485863e-002, -1.688913e-002, -1.884015e-002, + -2.078778e-002, -2.267046e-002, -2.456608e-002, -2.648875e-002, -2.832836e-002, -3.014256e-002, -3.188720e-002, -3.363371e-002, -3.542073e-002, + -3.720799e-002, -3.900214e-002, -4.077968e-002, -4.242680e-002, -4.399767e-002, -4.555023e-002, -4.707265e-002, -4.851820e-002, -4.998965e-002, + -5.133481e-002, -5.257725e-002, -5.383985e-002, -5.509882e-002, -5.631151e-002, -5.737942e-002, -5.833593e-002, -5.927504e-002, -6.015817e-002, + -6.097732e-002, -6.158503e-002, -6.209689e-002, -6.255621e-002, -6.299116e-002, -6.325338e-002, -6.337238e-002, -6.346345e-002, -6.351750e-002, + -6.329521e-002, -6.299696e-002, -6.269109e-002, -6.218936e-002, -6.156519e-002, -6.086646e-002, -5.996721e-002, -5.895799e-002, -5.793210e-002, + -5.661305e-002, -5.521785e-002, -5.371145e-002, -5.203058e-002, -5.033116e-002, -4.837816e-002, -4.639728e-002, -4.416284e-002, -4.188589e-002, + -3.941294e-002, -3.689078e-002, -3.423520e-002, -3.152978e-002, -2.875980e-002, -2.596427e-002, -2.318996e-002, -2.043350e-002, -1.773289e-002, + -1.501457e-002, -1.237982e-002, -9.745071e-003, -7.229239e-003, -4.708208e-003, -2.320967e-003, 8.070091e-005, 2.348366e-003, 4.591811e-003, + 6.764018e-003, 8.850274e-003, 1.093949e-002, 1.288259e-002, 1.482072e-002, 1.670620e-002, 1.849338e-002, 2.026930e-002, 2.195358e-002, + 2.358069e-002, 2.521621e-002, 2.674060e-002, 2.821928e-002, 2.967623e-002, 3.102724e-002, 3.234031e-002, 3.364976e-002, 3.489012e-002, + 3.605705e-002, 3.720929e-002, 3.832424e-002, 3.933046e-002, 4.032283e-002, 4.129232e-002, 4.218216e-002, 4.300282e-002, 4.382003e-002, + 4.460349e-002, 4.526021e-002, 4.588554e-002, 4.650034e-002, 4.707632e-002, 4.756497e-002, 4.802367e-002, 4.843365e-002, 4.881548e-002, + 4.910217e-002, 4.937479e-002, 4.963733e-002, 4.988234e-002, 5.005874e-002, 5.020310e-002, 5.034665e-002, 5.049171e-002, 5.058967e-002, + 5.064064e-002, 5.068562e-002, 5.073603e-002, 5.075845e-002, 5.072401e-002, 5.067237e-002, 5.058246e-002, 5.046650e-002, 5.031505e-002, + 5.009900e-002, 4.984151e-002, 4.953146e-002, 4.916712e-002, 4.871084e-002, 4.821376e-002, 4.768106e-002, 4.711512e-002, 4.652647e-002, + 4.585785e-002, 4.514133e-002, 4.439439e-002, 4.361535e-002, 4.280429e-002, 4.192881e-002, 4.102300e-002, 4.009239e-002, 3.913833e-002, + 3.815946e-002, 3.713070e-002, 3.606890e-002, 3.499174e-002, 3.389942e-002, 3.279640e-002, 3.168237e-002, 3.051263e-002, 2.933234e-002, + 2.815132e-002, 2.695674e-002, 2.575655e-002, 2.450979e-002, 2.324600e-002, 2.198786e-002, 2.073296e-002, 1.945922e-002, 1.819424e-002, + 1.690579e-002, 1.560894e-002, 1.430015e-002, 1.299360e-002, 1.169695e-002, 1.037968e-002, 9.068149e-003, 7.741817e-003, 6.415321e-003, + 5.105502e-003, 3.819000e-003, 2.531463e-003, 1.269096e-003, 2.384484e-005, -1.193432e-003, -2.386539e-003, -3.560392e-003, -4.718147e-003, + -5.815072e-003, -6.866752e-003, -7.887419e-003, -8.907708e-003, -9.933379e-003, -1.095651e-002, -1.197075e-002, -1.296265e-002, -1.390421e-002, + -1.481505e-002, -1.571800e-002, -1.662590e-002, -1.752483e-002, -1.842051e-002, -1.930908e-002, -2.015003e-002, -2.094065e-002, -2.171573e-002, + -2.250890e-002, -2.327995e-002, -2.402365e-002, -2.473761e-002, -2.542798e-002, -2.608625e-002, -2.671729e-002, -2.732652e-002, -2.791842e-002, + -2.848333e-002, -2.903645e-002, -2.955853e-002, -3.003906e-002, -3.051089e-002, -3.095459e-002, -3.137805e-002, -3.176411e-002, -3.212434e-002, + -3.247152e-002, -3.278630e-002, -3.309353e-002, -3.339251e-002, -3.366102e-002, -3.388245e-002, -3.407782e-002, -3.427637e-002, -3.445911e-002, + -3.466297e-002, -3.481982e-002, -3.496659e-002, -3.511681e-002, -3.523699e-002, -3.530970e-002, -3.536507e-002, -3.544618e-002, -3.551390e-002, + -3.557507e-002, -3.561826e-002, -3.565932e-002, -3.567753e-002, -3.568320e-002, -3.567384e-002, -3.567145e-002, -3.566303e-002, -3.565306e-002, + -3.564454e-002, -3.561138e-002, -3.555141e-002, -3.548285e-002, -3.544837e-002, -3.542393e-002, -3.537955e-002, -3.532114e-002, -3.525028e-002, + -3.517465e-002, -3.508291e-002, -3.497593e-002, -3.490447e-002, -3.483494e-002, -3.477417e-002, -3.471751e-002, -3.466583e-002, -3.460672e-002, + -3.456024e-002, -3.448910e-002, -3.443390e-002, -3.435854e-002, -3.427583e-002, -3.417394e-002, -3.407909e-002, -3.397054e-002, -3.384070e-002, + -3.369647e-002, -3.355718e-002, -3.340723e-002, -3.323516e-002, -3.303598e-002, -3.279781e-002, -3.254387e-002, -3.228604e-002, -3.201505e-002, + -3.171318e-002, -3.142293e-002, -3.114209e-002, -3.082772e-002, -3.045841e-002, -3.005733e-002, -2.964362e-002, -2.917462e-002, -2.867504e-002, + -2.813418e-002, -2.760626e-002, -2.707090e-002, -2.652276e-002, -2.593222e-002, -2.531618e-002, -2.469594e-002, -2.407103e-002, -2.343334e-002, + -2.277295e-002, -2.207018e-002, -2.138198e-002, -2.069594e-002, -1.998571e-002, -1.925349e-002, -1.849806e-002, -1.773696e-002, -1.696485e-002, + -1.618180e-002, -1.539289e-002, -1.459834e-002, -1.381856e-002, -1.303101e-002, -1.221188e-002, -1.137027e-002, -1.050989e-002, -9.626323e-003, + -8.732401e-003, -7.834357e-003, -6.930994e-003, -6.020929e-003, -5.141985e-003, -4.256497e-003, -3.361107e-003, -2.465532e-003, -1.556973e-003, + -6.467956e-004, 2.871382e-004, 1.237592e-003, 2.190082e-003, 3.133055e-003, 4.074881e-003, 4.983417e-003, 5.889459e-003, 6.791265e-003, + 7.673202e-003, 8.542332e-003, 9.408963e-003, 1.026319e-002, 1.110473e-002, 1.194347e-002, 1.279316e-002, 1.365171e-002, 1.447525e-002, + 1.527256e-002, 1.605954e-002, 1.683167e-002, 1.758684e-002, 1.831307e-002, 1.902876e-002, 1.972684e-002, 2.041050e-002, 2.108935e-002, + 2.173923e-002, 2.237265e-002, 2.297090e-002, 2.354096e-002, 2.412886e-002, 2.471794e-002, 2.527551e-002, 2.581197e-002, 2.633118e-002, + 2.683890e-002, 2.733438e-002, 2.780126e-002, 2.824413e-002, 2.865447e-002, 2.904084e-002, 2.937105e-002, 2.968539e-002, 2.998415e-002, + 3.027428e-002, 3.054532e-002, 3.079764e-002, 3.106455e-002, 3.132506e-002, 3.156960e-002, 3.180857e-002, 3.205375e-002, 3.228896e-002, + 3.252091e-002, 3.271934e-002, 3.289472e-002, 3.307206e-002, 3.324106e-002, 3.339448e-002, 3.353456e-002, 3.366045e-002, 3.377804e-002, + 3.389761e-002, 3.401727e-002, 3.413144e-002, 3.425254e-002, 3.437081e-002, 3.448903e-002, 3.459243e-002, 3.467257e-002, 3.474069e-002, + 3.480717e-002, 3.486153e-002, 3.491283e-002, 3.494985e-002, 3.497361e-002, 3.497508e-002, 3.495995e-002, 3.492369e-002, 3.485748e-002, + 3.476868e-002, 3.467043e-002, 3.455774e-002, 3.441120e-002, 3.423531e-002, 3.406846e-002, 3.389805e-002, 3.372093e-002, 3.352094e-002, + 3.330048e-002, 3.306500e-002, 3.281435e-002, 3.255679e-002, 3.229502e-002, 3.203285e-002, 3.176566e-002, 3.149615e-002, 3.120929e-002, + 3.091477e-002, 3.059739e-002, 3.026246e-002, 2.991732e-002, 2.955209e-002, 2.916415e-002, 2.875910e-002, 2.835779e-002, 2.796257e-002, + 2.755974e-002, 2.714536e-002, 2.672353e-002, 2.629519e-002, 2.584748e-002, 2.538733e-002, 2.492165e-002, 2.444579e-002, 2.396350e-002, + 2.343459e-002, 2.287924e-002, 2.232696e-002, 2.178998e-002, 2.125035e-002, 2.070484e-002, 2.015387e-002, 1.959821e-002, 1.904552e-002, + 1.848591e-002, 1.791866e-002, 1.735488e-002, 1.678414e-002, 1.623217e-002, 1.568497e-002, 1.513833e-002, 1.459029e-002, 1.404801e-002, + 1.349753e-002, 1.293772e-002, 1.237014e-002, 1.180083e-002, 1.122481e-002, 1.064271e-002, 1.005560e-002, 9.476598e-003, 8.896534e-003, + 8.304817e-003, 7.710918e-003, 7.115831e-003, 6.518919e-003, 5.918190e-003, 5.315373e-003, 4.709568e-003, 4.090631e-003, 3.465813e-003, + 2.846576e-003, 2.226961e-003, 1.601896e-003, 9.867563e-004, 3.745772e-004, -2.105205e-004, -7.933030e-004, -1.371328e-003, -1.940325e-003, + -2.511783e-003, -3.076740e-003, -3.647312e-003, -4.213629e-003, -4.768049e-003, -5.306119e-003, -5.828920e-003, -6.336394e-003, -6.832906e-003, + -7.320921e-003, -7.797977e-003, -8.274190e-003, -8.748236e-003, -9.233121e-003, -9.707003e-003, -1.015312e-002, -1.057526e-002, -1.099189e-002, + -1.140492e-002, -1.180347e-002, -1.215806e-002, -1.249829e-002, -1.283284e-002, -1.316834e-002, -1.350417e-002, -1.383645e-002, -1.416119e-002, + -1.448808e-002, -1.481028e-002, -1.513343e-002, -1.544453e-002, -1.573912e-002, -1.603154e-002, -1.630950e-002, -1.658602e-002, -1.687403e-002, + -1.715366e-002, -1.742182e-002, -1.768665e-002, -1.795419e-002, -1.821771e-002, -1.846527e-002, -1.864034e-002, -1.877560e-002, -1.891428e-002, + -1.905052e-002, -1.918990e-002, -1.932156e-002, -1.944473e-002, -1.953402e-002, -1.962136e-002, -1.971149e-002, -1.980488e-002, -1.989959e-002, + -1.999073e-002, -2.008237e-002, -2.012979e-002, -2.014827e-002, -2.018234e-002, -2.021430e-002, -2.025566e-002, -2.030232e-002, -2.035800e-002, + -2.041849e-002, -2.047975e-002, -2.053755e-002, -2.059503e-002, -2.066297e-002, -2.070026e-002, -2.073074e-002, -2.076274e-002, -2.079700e-002, + -2.081766e-002, -2.081751e-002, -2.078707e-002, -2.074217e-002, -2.068854e-002, -2.061840e-002, -2.053394e-002, -2.044232e-002, -2.034929e-002, + -2.025436e-002, -2.016599e-002, -2.008360e-002, -2.000049e-002, -1.992195e-002, -1.984999e-002, -1.977996e-002, -1.970968e-002, -1.963963e-002, + -1.957092e-002, -1.951273e-002, -1.941598e-002, -1.930148e-002, -1.919344e-002, -1.909173e-002, -1.898761e-002, -1.885601e-002, -1.871118e-002, + -1.856783e-002, -1.841840e-002, -1.826673e-002, -1.811925e-002, -1.797291e-002, -1.782092e-002, -1.766494e-002, -1.750164e-002, -1.733041e-002, + -1.715757e-002, -1.699718e-002, -1.684047e-002, -1.668598e-002, -1.652583e-002, -1.636523e-002, -1.620326e-002, -1.604395e-002, -1.588348e-002, + -1.571252e-002, -1.554306e-002, -1.537691e-002, -1.521382e-002, -1.505256e-002, -1.489495e-002, -1.472986e-002, -1.454640e-002, -1.435792e-002, + -1.415967e-002, -1.395707e-002, -1.372492e-002, -1.349921e-002, -1.327217e-002, -1.304305e-002, -1.281467e-002, -1.258340e-002, -1.235459e-002, + -1.212523e-002, -1.190113e-002, -1.168102e-002, -1.145324e-002, -1.121639e-002, -1.097750e-002, -1.073424e-002, -1.049076e-002, -1.024075e-002, + -9.991022e-003, -9.742739e-003, -9.494931e-003, -9.245145e-003, -8.995961e-003, -8.752816e-003, -8.512593e-003, -8.277541e-003, -8.049587e-003, + -7.826348e-003, -7.608062e-003, -7.396156e-003, -7.185370e-003, -6.980983e-003, -6.777299e-003, -6.577432e-003, -6.382538e-003, -6.191508e-003, + -6.003896e-003, -5.816081e-003, -5.627869e-003, -5.427195e-003, -5.222663e-003, -5.037527e-003, -4.855783e-003, -4.649402e-003, -4.440233e-003, + -4.232805e-003, -4.024880e-003, -3.807483e-003, -3.588480e-003, -3.361602e-003, -3.132096e-003, -2.905517e-003, -2.679254e-003, -2.452608e-003, + -2.227449e-003, -2.008791e-003, -1.790952e-003, -1.573746e-003, -1.361542e-003, -1.157087e-003, -9.609823e-004, -7.674150e-004, -5.797771e-004, + -3.990046e-004, -2.239958e-004, -4.439211e-005, 1.396298e-004, 3.222211e-004, 5.008715e-004, 6.779647e-004, 8.601898e-004, 1.042781e-003, + 1.220758e-003, 1.381251e-003, 1.538930e-003, 1.693212e-003, 1.843456e-003, 1.997282e-003, 2.155009e-003, 2.317536e-003, 2.481689e-003, + 2.649820e-003, 2.819629e-003, 2.990812e-003, 3.162751e-003, 3.331905e-003, 3.507244e-003, 3.683785e-003, 3.861910e-003, 4.030485e-003, + 4.198306e-003, 4.364774e-003, 4.524752e-003, 4.680467e-003, 4.821825e-003, 4.974119e-003, 5.126121e-003, 5.275984e-003, 5.445741e-003, + 5.614988e-003, 5.783175e-003, 5.947920e-003, 6.113995e-003, 6.273493e-003, 6.426155e-003, 6.586598e-003, 6.749856e-003, 6.902632e-003, + 7.059211e-003, 7.218763e-003, 7.379529e-003, 7.536365e-003, 7.705722e-003, 7.882556e-003, 8.061374e-003, 8.239117e-003, 8.422710e-003, + 8.610220e-003, 8.796941e-003, 8.987158e-003, 9.176041e-003, 9.359229e-003, 9.540617e-003, 9.712234e-003, 9.883348e-003, 1.004536e-002, + 1.020352e-002, 1.036267e-002, 1.051669e-002, 1.066336e-002, 1.079316e-002, 1.091487e-002, 1.102950e-002, 1.114212e-002, 1.125885e-002, + 1.136107e-002, 1.145830e-002, 1.155206e-002, 1.164214e-002, 1.172806e-002, 1.180849e-002, 1.188952e-002, 1.196905e-002, 1.204584e-002, + 1.211635e-002, 1.217674e-002, 1.223541e-002, 1.229263e-002, 1.234995e-002, 1.240863e-002, 1.246192e-002, 1.251184e-002, 1.256242e-002, + 1.261874e-002, 1.267709e-002, 1.272766e-002, 1.277669e-002, 1.282692e-002, 1.287511e-002, 1.292109e-002, 1.296405e-002, 1.300595e-002, + 1.304837e-002, 1.309052e-002, 1.313521e-002, 1.317410e-002, 1.320113e-002, 1.322924e-002, 1.325429e-002, 1.328400e-002, 1.332435e-002, + 1.338605e-002, 1.345960e-002, 1.352992e-002, 1.360079e-002, 1.367062e-002, 1.372020e-002, 1.376942e-002, 1.381755e-002, 1.387327e-002, + 1.393805e-002, 1.400128e-002, 1.405127e-002, 1.409417e-002, 1.413538e-002, 1.417545e-002, 1.419578e-002, 1.421460e-002, 1.422852e-002, + 1.423914e-002, 1.424496e-002, 1.424318e-002, 1.423454e-002, 1.422075e-002, 1.420547e-002, 1.419162e-002, 1.415933e-002, 1.411267e-002, + 1.406270e-002, 1.400903e-002, 1.395095e-002, 1.388625e-002, 1.381661e-002, 1.374747e-002, 1.367476e-002, 1.360097e-002, 1.351428e-002, + 1.340822e-002, 1.330277e-002, 1.319812e-002, 1.309727e-002, 1.300067e-002, 1.290355e-002, 1.280535e-002, 1.270370e-002, 1.260333e-002, + 1.250062e-002, 1.237286e-002, 1.226199e-002, 1.215275e-002, 1.204196e-002, 1.192944e-002, 1.181639e-002, 1.169824e-002, 1.156265e-002, + 1.142394e-002, 1.128725e-002, 1.111601e-002, 1.093656e-002, 1.075112e-002, 1.055972e-002, 1.036437e-002, 1.016662e-002, 9.966574e-003, + 9.764344e-003, 9.564577e-003, 9.363137e-003, 9.134555e-003, 8.895172e-003, 8.654333e-003, 8.410100e-003, 8.156153e-003, 7.901157e-003, + 7.657088e-003, 7.412666e-003, 7.162834e-003, 6.905823e-003, 6.633215e-003, 6.343749e-003, 6.058495e-003, 5.776984e-003, 5.509354e-003, + 5.244486e-003, 4.976913e-003, 4.696457e-003, 4.408548e-003, 4.108398e-003, 3.771421e-003, 3.395763e-003, 3.026870e-003, 2.665420e-003, + 2.300077e-003, 1.929931e-003, 1.555189e-003, 1.174700e-003, 7.890409e-004, 3.971999e-004, -4.543580e-018}, + {0.000000e+000, 2.582338e-002, 2.788695e-002, 3.015120e-002, 1.992367e-002, 6.122007e-003, 1.873491e-003, 1.551893e-003, 1.851705e-003, + 2.831383e-003, 5.649215e-003, 9.446452e-003, 1.257734e-002, 1.404221e-002, 1.265673e-002, 9.201761e-003, 5.724363e-003, 3.914090e-003, + 3.643757e-003, 2.951421e-003, 1.069848e-003, -2.419836e-003, -6.426817e-003, -1.023838e-002, -1.385503e-002, -1.724476e-002, -2.030696e-002, + -2.291385e-002, -2.487775e-002, -2.614447e-002, -2.663786e-002, -2.650462e-002, -2.580013e-002, -2.461288e-002, -2.313307e-002, -2.146232e-002, + -1.965955e-002, -1.744552e-002, -1.463732e-002, -1.140667e-002, -7.972878e-003, -4.575456e-003, -1.313533e-003, 1.588777e-003, 3.999687e-003, + 5.835754e-003, 6.927453e-003, 7.243991e-003, 7.317584e-003, 7.073351e-003, 6.257333e-003, 4.874401e-003, 3.121776e-003, 1.038513e-003, + -1.218678e-003, -3.454468e-003, -5.575850e-003, -7.444446e-003, -8.954404e-003, -9.935053e-003, -1.031070e-002, -9.867237e-003, -8.816129e-003, + -7.373982e-003, -5.672227e-003, -3.845725e-003, -1.813834e-003, 3.513706e-004, 2.673189e-003, 5.083045e-003, 7.527808e-003, 1.002128e-002, + 1.260762e-002, 1.524348e-002, 1.786674e-002, 2.050779e-002, 2.313621e-002, 2.573596e-002, 2.833679e-002, 3.108054e-002, 3.380981e-002, + 3.642909e-002, 3.899303e-002, 4.142137e-002, 4.348831e-002, 4.530173e-002, 4.694105e-002, 4.820910e-002, 4.915036e-002, 4.982978e-002, + 5.018315e-002, 5.023589e-002, 4.980763e-002, 4.905758e-002, 4.796739e-002, 4.640574e-002, 4.444484e-002, 4.207434e-002, 3.928124e-002, + 3.635617e-002, 3.336321e-002, 3.010738e-002, 2.671548e-002, 2.312237e-002, 1.937084e-002, 1.547951e-002, 1.141165e-002, 7.274858e-003, + 3.033589e-003, -1.220267e-003, -5.534770e-003, -9.847984e-003, -1.415600e-002, -1.834604e-002, -2.239824e-002, -2.637304e-002, -3.025736e-002, + -3.398421e-002, -3.748769e-002, -4.081280e-002, -4.393477e-002, -4.686695e-002, -4.953000e-002, -5.187861e-002, -5.394374e-002, -5.574413e-002, + -5.728003e-002, -5.858608e-002, -5.961233e-002, -6.040203e-002, -6.099119e-002, -6.134030e-002, -6.140432e-002, -6.106806e-002, -6.028717e-002, + -5.908612e-002, -5.758131e-002, -5.579794e-002, -5.366737e-002, -5.135445e-002, -4.887707e-002, -4.625764e-002, -4.342870e-002, -4.054161e-002, + -3.765350e-002, -3.472359e-002, -3.186216e-002, -2.911793e-002, -2.650080e-002, -2.398324e-002, -2.159563e-002, -1.946005e-002, -1.759561e-002, + -1.603332e-002, -1.476296e-002, -1.354104e-002, -1.230068e-002, -1.108787e-002, -9.978465e-003, -8.980276e-003, -8.103006e-003, -7.298285e-003, + -6.463383e-003, -5.695816e-003, -5.031625e-003, -4.555449e-003, -4.164841e-003, -3.788624e-003, -3.434260e-003, -3.225064e-003, -3.142920e-003, + -3.208623e-003, -3.257603e-003, -3.399448e-003, -3.636708e-003, -3.937849e-003, -4.162655e-003, -4.428913e-003, -4.778235e-003, -5.185898e-003, + -5.409569e-003, -5.634282e-003, -5.892079e-003, -6.062179e-003, -6.160839e-003, -6.230034e-003, -6.141727e-003, -5.946886e-003, -5.718027e-003, + -5.266735e-003, -4.730247e-003, -4.026396e-003, -3.133571e-003, -2.145805e-003, -8.841697e-004, 4.460122e-004, 1.962817e-003, 3.519369e-003, + 5.228009e-003, 6.988262e-003, 8.859223e-003, 1.078435e-002, 1.276813e-002, 1.476935e-002, 1.679453e-002, 1.885455e-002, 2.088238e-002, + 2.293859e-002, 2.493055e-002, 2.693966e-002, 2.883856e-002, 3.074620e-002, 3.254727e-002, 3.437391e-002, 3.610964e-002, 3.782007e-002, + 3.946395e-002, 4.101541e-002, 4.256785e-002, 4.401362e-002, 4.546050e-002, 4.685832e-002, 4.814837e-002, 4.942355e-002, 5.059405e-002, + 5.171846e-002, 5.282062e-002, 5.381627e-002, 5.475877e-002, 5.566143e-002, 5.646076e-002, 5.720148e-002, 5.790035e-002, 5.850898e-002, + 5.901851e-002, 5.948157e-002, 5.988680e-002, 6.016002e-002, 6.038337e-002, 6.053420e-002, 6.057080e-002, 6.051355e-002, 6.037638e-002, + 6.015988e-002, 5.980726e-002, 5.936905e-002, 5.885350e-002, 5.823953e-002, 5.747670e-002, 5.662954e-002, 5.568238e-002, 5.464153e-002, + 5.345658e-002, 5.220852e-002, 5.092240e-002, 4.958150e-002, 4.812957e-002, 4.663464e-002, 4.510249e-002, 4.353549e-002, 4.190036e-002, + 4.019099e-002, 3.843812e-002, 3.664865e-002, 3.481497e-002, 3.292826e-002, 3.101100e-002, 2.906868e-002, 2.709568e-002, 2.507404e-002, + 2.299438e-002, 2.090159e-002, 1.879844e-002, 1.667227e-002, 1.449982e-002, 1.228980e-002, 1.006114e-002, 7.834112e-003, 5.600179e-003, + 3.342738e-003, 1.067792e-003, -1.203849e-003, -3.480048e-003, -5.753192e-003, -8.040342e-003, -1.033072e-002, -1.260687e-002, -1.485605e-002, + -1.708884e-002, -1.932198e-002, -2.153992e-002, -2.374419e-002, -2.592434e-002, -2.807872e-002, -3.020181e-002, -3.231914e-002, -3.438478e-002, + -3.640979e-002, -3.841246e-002, -4.038254e-002, -4.233059e-002, -4.424713e-002, -4.610782e-002, -4.789066e-002, -4.962434e-002, -5.132563e-002, + -5.299403e-002, -5.459660e-002, -5.613912e-002, -5.761421e-002, -5.899103e-002, -6.030601e-002, -6.157000e-002, -6.275918e-002, -6.387614e-002, + -6.492458e-002, -6.588903e-002, -6.676407e-002, -6.755520e-002, -6.828829e-002, -6.896337e-002, -6.957697e-002, -7.012728e-002, -7.063573e-002, + -7.107170e-002, -7.145046e-002, -7.176842e-002, -7.202866e-002, -7.224724e-002, -7.241784e-002, -7.254152e-002, -7.262095e-002, -7.263374e-002, + -7.256884e-002, -7.246964e-002, -7.232437e-002, -7.213061e-002, -7.187811e-002, -7.157505e-002, -7.116766e-002, -7.063421e-002, -6.999795e-002, + -6.930333e-002, -6.851589e-002, -6.761833e-002, -6.664138e-002, -6.560128e-002, -6.445090e-002, -6.324399e-002, -6.195046e-002, -6.058348e-002, + -5.913489e-002, -5.762982e-002, -5.607133e-002, -5.444229e-002, -5.276641e-002, -5.103873e-002, -4.928213e-002, -4.745380e-002, -4.556179e-002, + -4.362721e-002, -4.166393e-002, -3.966141e-002, -3.762823e-002, -3.554553e-002, -3.345927e-002, -3.134624e-002, -2.920581e-002, -2.701512e-002, + -2.481717e-002, -2.259149e-002, -2.034948e-002, -1.809695e-002, -1.585847e-002, -1.360849e-002, -1.136313e-002, -9.135985e-003, -6.906143e-003, + -4.648397e-003, -2.403261e-003, -1.620247e-004, 2.051146e-003, 4.246357e-003, 6.409294e-003, 8.536767e-003, 1.065861e-002, 1.276302e-002, + 1.484251e-002, 1.690199e-002, 1.893074e-002, 2.093150e-002, 2.286372e-002, 2.473345e-002, 2.657574e-002, 2.838702e-002, 3.013479e-002, + 3.184725e-002, 3.350079e-002, 3.507956e-002, 3.657114e-002, 3.802480e-002, 3.942060e-002, 4.074637e-002, 4.200214e-002, 4.318976e-002, + 4.429811e-002, 4.531892e-002, 4.624901e-002, 4.715347e-002, 4.803189e-002, 4.890701e-002, 4.976844e-002, 5.060156e-002, 5.140311e-002, + 5.218886e-002, 5.295747e-002, 5.365133e-002, 5.430922e-002, 5.492732e-002, 5.554098e-002, 5.613254e-002, 5.669940e-002, 5.723183e-002, + 5.772675e-002, 5.819009e-002, 5.860019e-002, 5.891647e-002, 5.917831e-002, 5.939426e-002, 5.955203e-002, 5.964731e-002, 5.968833e-002, + 5.969862e-002, 5.963703e-002, 5.948914e-002, 5.928945e-002, 5.901539e-002, 5.869905e-002, 5.832790e-002, 5.790845e-002, 5.745916e-002, + 5.698327e-002, 5.645336e-002, 5.585263e-002, 5.522002e-002, 5.456399e-002, 5.386684e-002, 5.310711e-002, 5.231428e-002, 5.148238e-002, + 5.063449e-002, 4.976438e-002, 4.886258e-002, 4.791742e-002, 4.695486e-002, 4.597573e-002, 4.496933e-002, 4.393220e-002, 4.284279e-002, + 4.175212e-002, 4.065589e-002, 3.953443e-002, 3.840069e-002, 3.723112e-002, 3.605475e-002, 3.486881e-002, 3.367353e-002, 3.247582e-002, + 3.128344e-002, 3.008618e-002, 2.885326e-002, 2.761403e-002, 2.637146e-002, 2.513492e-002, 2.388366e-002, 2.263708e-002, 2.139103e-002, + 2.014758e-002, 1.890976e-002, 1.768721e-002, 1.647237e-002, 1.526169e-002, 1.405306e-002, 1.286517e-002, 1.169275e-002, 1.051736e-002, + 9.343780e-003, 8.187537e-003, 7.059036e-003, 5.959319e-003, 4.881003e-003, 3.827071e-003, 2.803146e-003, 1.803574e-003, 8.336306e-004, + -1.112889e-004, -1.029277e-003, -1.942344e-003, -2.836194e-003, -3.677700e-003, -4.485395e-003, -5.262823e-003, -6.004349e-003, -6.702482e-003, + -7.349067e-003, -7.943895e-003, -8.494786e-003, -9.000597e-003, -9.459769e-003, -9.866974e-003, -1.025754e-002, -1.063083e-002, -1.099423e-002, + -1.135477e-002, -1.172834e-002, -1.209433e-002, -1.244451e-002, -1.278519e-002, -1.312057e-002, -1.345353e-002, -1.378238e-002, -1.410562e-002, + -1.440929e-002, -1.472319e-002, -1.504577e-002, -1.535873e-002, -1.568911e-002, -1.601979e-002, -1.634087e-002, -1.665018e-002, -1.695646e-002, + -1.725807e-002, -1.756278e-002, -1.785997e-002, -1.814962e-002, -1.842431e-002, -1.869064e-002, -1.897816e-002, -1.928970e-002, -1.959482e-002, + -1.989242e-002, -2.018911e-002, -2.047715e-002, -2.076220e-002, -2.104346e-002, -2.132549e-002, -2.160666e-002, -2.187249e-002, -2.215183e-002, + -2.244255e-002, -2.273536e-002, -2.302519e-002, -2.332048e-002, -2.362422e-002, -2.392241e-002, -2.421397e-002, -2.450256e-002, -2.478739e-002, + -2.506222e-002, -2.534028e-002, -2.560466e-002, -2.586774e-002, -2.613478e-002, -2.640197e-002, -2.666248e-002, -2.691659e-002, -2.716008e-002, + -2.740416e-002, -2.767136e-002, -2.795510e-002, -2.822781e-002, -2.849630e-002, -2.874778e-002, -2.898433e-002, -2.922457e-002, -2.945505e-002, + -2.967060e-002, -2.988182e-002, -3.007252e-002, -3.025605e-002, -3.043270e-002, -3.060505e-002, -3.077101e-002, -3.093089e-002, -3.109494e-002, + -3.125094e-002, -3.140677e-002, -3.155636e-002, -3.167006e-002, -3.177725e-002, -3.188191e-002, -3.198533e-002, -3.209000e-002, -3.219402e-002, + -3.228849e-002, -3.237911e-002, -3.246861e-002, -3.253237e-002, -3.253422e-002, -3.250458e-002, -3.246226e-002, -3.241514e-002, -3.236453e-002, + -3.232251e-002, -3.228404e-002, -3.223931e-002, -3.218492e-002, -3.211922e-002, -3.203205e-002, -3.192054e-002, -3.176521e-002, -3.156059e-002, + -3.134152e-002, -3.111350e-002, -3.087894e-002, -3.063793e-002, -3.038738e-002, -3.012938e-002, -2.986057e-002, -2.956141e-002, -2.923532e-002, + -2.890091e-002, -2.855211e-002, -2.819847e-002, -2.783067e-002, -2.743875e-002, -2.698010e-002, -2.651385e-002, -2.603993e-002, -2.554995e-002, + -2.503188e-002, -2.449559e-002, -2.394813e-002, -2.338839e-002, -2.281920e-002, -2.225099e-002, -2.167806e-002, -2.109607e-002, -2.050371e-002, + -1.990144e-002, -1.929286e-002, -1.867887e-002, -1.806404e-002, -1.745847e-002, -1.683969e-002, -1.619093e-002, -1.549554e-002, -1.478858e-002, + -1.406755e-002, -1.335079e-002, -1.267159e-002, -1.198672e-002, -1.129643e-002, -1.059907e-002, -9.898953e-003, -9.196647e-003, -8.498074e-003, + -7.794314e-003, -7.095248e-003, -6.396417e-003, -5.727504e-003, -5.095640e-003, -4.460635e-003, -3.838829e-003, -3.227981e-003, -2.620730e-003, + -2.017433e-003, -1.420021e-003, -8.220225e-004, -2.187297e-004, 3.900817e-004, 1.000093e-003, 1.582605e-003, 2.126356e-003, 2.676725e-003, + 3.241377e-003, 3.804041e-003, 4.371819e-003, 4.942579e-003, 5.484599e-003, 6.031348e-003, 6.583482e-003, 7.143376e-003, 7.714608e-003, + 8.292779e-003, 8.879410e-003, 9.377144e-003, 9.798808e-003, 1.020809e-002, 1.062327e-002, 1.104663e-002, 1.147767e-002, 1.191842e-002, + 1.236953e-002, 1.282603e-002, 1.328764e-002, 1.375019e-002, 1.421328e-002, 1.466225e-002, 1.511258e-002, 1.556932e-002, 1.602989e-002, + 1.648278e-002, 1.690894e-002, 1.727076e-002, 1.760933e-002, 1.795406e-002, 1.829887e-002, 1.867353e-002, 1.903998e-002, 1.940619e-002, + 1.977776e-002, 2.014896e-002, 2.051914e-002, 2.088825e-002, 2.125882e-002, 2.162986e-002, 2.199536e-002, 2.235992e-002, 2.272515e-002, + 2.309407e-002, 2.346409e-002, 2.377533e-002, 2.405118e-002, 2.432890e-002, 2.460762e-002, 2.489001e-002, 2.512178e-002, 2.532171e-002, + 2.551999e-002, 2.571579e-002, 2.590933e-002, 2.610171e-002, 2.629283e-002, 2.648123e-002, 2.667615e-002, 2.687626e-002, 2.706187e-002, + 2.723278e-002, 2.739052e-002, 2.754781e-002, 2.770775e-002, 2.786157e-002, 2.800788e-002, 2.815196e-002, 2.829371e-002, 2.841866e-002, + 2.850806e-002, 2.859112e-002, 2.867208e-002, 2.875144e-002, 2.882869e-002, 2.890365e-002, 2.896650e-002, 2.900974e-002, 2.904810e-002, + 2.910987e-002, 2.917881e-002, 2.917776e-002, 2.916975e-002, 2.915829e-002, 2.914576e-002, 2.912872e-002, 2.910170e-002, 2.906646e-002, + 2.901714e-002, 2.896276e-002, 2.889193e-002, 2.881246e-002, 2.873334e-002, 2.865144e-002, 2.855451e-002, 2.845170e-002, 2.834675e-002, + 2.823901e-002, 2.812772e-002, 2.801081e-002, 2.787629e-002, 2.771456e-002, 2.754716e-002, 2.737634e-002, 2.720424e-002, 2.703163e-002, + 2.685442e-002, 2.667217e-002, 2.648514e-002, 2.629500e-002, 2.610084e-002, 2.591180e-002, 2.571996e-002, 2.551914e-002, 2.530547e-002, + 2.509024e-002, 2.487238e-002, 2.464957e-002, 2.440947e-002, 2.415352e-002, 2.388243e-002, 2.360648e-002, 2.328907e-002, 2.296099e-002, + 2.262688e-002, 2.228336e-002, 2.193198e-002, 2.157621e-002, 2.122087e-002, 2.085911e-002, 2.049128e-002, 2.011490e-002, 1.973083e-002, + 1.933753e-002, 1.893406e-002, 1.852277e-002, 1.810294e-002, 1.766812e-002, 1.722695e-002, 1.678013e-002, 1.630866e-002, 1.582072e-002, + 1.533606e-002, 1.485178e-002, 1.436312e-002, 1.389237e-002, 1.342056e-002, 1.295249e-002, 1.248761e-002, 1.202538e-002, 1.156448e-002, + 1.110221e-002, 1.063403e-002, 1.016925e-002, 9.705857e-003, 9.241361e-003, 8.780186e-003, 8.320196e-003, 7.861386e-003, 7.404690e-003, + 6.948702e-003, 6.492689e-003, 6.035016e-003, 5.588828e-003, 5.144262e-003, 4.700165e-003, 4.260471e-003, 3.820413e-003, 3.378009e-003, + 2.936965e-003, 2.496685e-003, 2.056279e-003, 1.616401e-003, 1.191256e-003, 7.536394e-004, 3.129552e-004, -1.289709e-004, -5.984666e-004, + -1.068986e-003, -1.540534e-003, -2.018044e-003, -2.500498e-003, -2.979633e-003, -3.446692e-003, -3.905171e-003, -4.363551e-003, -4.831924e-003, + -5.299494e-003, -5.768363e-003, -6.238863e-003, -6.713220e-003, -7.188559e-003, -7.681969e-003, -8.167602e-003, -8.632872e-003, -9.094300e-003, + -9.556350e-003, -1.001708e-002, -1.047489e-002, -1.093133e-002, -1.138558e-002, -1.183474e-002, -1.227822e-002, -1.271034e-002, -1.311342e-002, + -1.351145e-002, -1.389745e-002, -1.427333e-002, -1.464059e-002, -1.500170e-002, -1.535901e-002, -1.571338e-002, -1.606026e-002, -1.639657e-002, + -1.669678e-002, -1.699257e-002, -1.728501e-002, -1.757322e-002, -1.785175e-002, -1.812491e-002, -1.839617e-002, -1.866475e-002, -1.893234e-002, + -1.920176e-002, -1.944210e-002, -1.968713e-002, -1.993595e-002, -2.018648e-002, -2.043727e-002, -2.069106e-002, -2.094287e-002, -2.118790e-002, + -2.142222e-002, -2.165966e-002, -2.187858e-002, -2.207911e-002, -2.228202e-002, -2.248486e-002, -2.268916e-002, -2.289575e-002, -2.310060e-002, + -2.330430e-002, -2.349982e-002, -2.369265e-002, -2.387126e-002, -2.401661e-002, -2.416483e-002, -2.431245e-002, -2.446701e-002, -2.462648e-002, + -2.480247e-002, -2.498029e-002, -2.515447e-002, -2.532875e-002, -2.549896e-002, -2.561131e-002, -2.572436e-002, -2.583953e-002, -2.595272e-002, + -2.606886e-002, -2.620108e-002, -2.634142e-002, -2.648246e-002, -2.662419e-002, -2.676823e-002, -2.686031e-002, -2.693937e-002, -2.701250e-002, + -2.707716e-002, -2.713994e-002, -2.720194e-002, -2.726222e-002, -2.731722e-002, -2.736718e-002, -2.741234e-002, -2.740787e-002, -2.736974e-002, + -2.732665e-002, -2.727741e-002, -2.722798e-002, -2.717501e-002, -2.711777e-002, -2.705342e-002, -2.698006e-002, -2.690410e-002, -2.679515e-002, + -2.663376e-002, -2.646899e-002, -2.630447e-002, -2.613991e-002, -2.597426e-002, -2.580972e-002, -2.564703e-002, -2.546739e-002, -2.528640e-002, + -2.509463e-002, -2.482749e-002, -2.455954e-002, -2.429094e-002, -2.402580e-002, -2.376498e-002, -2.350641e-002, -2.325066e-002, -2.300422e-002, + -2.275431e-002, -2.250732e-002, -2.215493e-002, -2.178699e-002, -2.142203e-002, -2.105955e-002, -2.069899e-002, -2.034095e-002, -1.998511e-002, + -1.963402e-002, -1.927448e-002, -1.891150e-002, -1.847149e-002, -1.799834e-002, -1.752812e-002, -1.705416e-002, -1.656324e-002, -1.607551e-002, + -1.558619e-002, -1.509975e-002, -1.461906e-002, -1.412681e-002, -1.357825e-002, -1.296390e-002, -1.235324e-002, -1.174679e-002, -1.114098e-002, + -1.053527e-002, -9.935998e-003, -9.318518e-003, -8.696637e-003, -8.082559e-003, -7.425515e-003, -6.664508e-003, -5.906687e-003, -5.151437e-003, + -4.401025e-003, -3.655789e-003, -2.915332e-003, -2.179478e-003, -1.448199e-003, -7.217395e-004, -2.567240e-018}, + {0.000000e+000, 1.994115e-001, 1.800203e-001, 1.545653e-001, 1.299366e-001, 1.062012e-001, 8.457450e-002, 6.488222e-002, 4.582496e-002, + 2.772342e-002, 1.092794e-002, -4.723545e-003, -1.888470e-002, -3.061054e-002, -4.022590e-002, -4.805896e-002, -5.397992e-002, -5.710701e-002, + -5.818212e-002, -5.794322e-002, -5.703980e-002, -5.625454e-002, -5.561314e-002, -5.507816e-002, -5.383257e-002, -5.228108e-002, -5.080053e-002, + -4.990720e-002, -5.017243e-002, -5.112138e-002, -5.221414e-002, -5.336463e-002, -5.449491e-002, -5.540424e-002, -5.577496e-002, -5.544910e-002, + -5.427255e-002, -5.230961e-002, -4.962822e-002, -4.633362e-002, -4.256385e-002, -3.847006e-002, -3.396507e-002, -2.927351e-002, -2.439569e-002, + -1.943869e-002, -1.452581e-002, -9.778460e-003, -5.048164e-003, -1.867567e-004, 4.743900e-003, 1.024199e-002, 1.640414e-002, 2.289655e-002, + 2.956801e-002, 3.630103e-002, 4.300259e-002, 4.937217e-002, 5.518242e-002, 6.034856e-002, 6.452966e-002, 6.779443e-002, 7.070444e-002, + 7.335252e-002, 7.567860e-002, 7.753827e-002, 7.907946e-002, 8.048497e-002, 8.177406e-002, 8.263777e-002, 8.302860e-002, 8.322929e-002, + 8.325761e-002, 8.290563e-002, 8.207983e-002, 8.113907e-002, 7.997070e-002, 7.841454e-002, 7.645719e-002, 7.443928e-002, 7.219326e-002, + 6.950285e-002, 6.676136e-002, 6.401585e-002, 6.073379e-002, 5.721529e-002, 5.361167e-002, 4.957851e-002, 4.550726e-002, 4.119757e-002, + 3.645610e-002, 3.165577e-002, 2.635704e-002, 2.113759e-002, 1.574249e-002, 1.002620e-002, 4.174248e-003, -1.853029e-003, -7.871257e-003, + -1.396751e-002, -2.000963e-002, -2.602361e-002, -3.180849e-002, -3.738520e-002, -4.274972e-002, -4.784664e-002, -5.247224e-002, -5.661126e-002, + -6.049407e-002, -6.402261e-002, -6.701596e-002, -6.961977e-002, -7.210251e-002, -7.405991e-002, -7.591641e-002, -7.747944e-002, -7.872568e-002, + -7.978812e-002, -8.066890e-002, -8.138986e-002, -8.189995e-002, -8.244012e-002, -8.279679e-002, -8.292167e-002, -8.292392e-002, -8.268926e-002, + -8.217472e-002, -8.144679e-002, -8.054291e-002, -7.942496e-002, -7.812349e-002, -7.659656e-002, -7.481911e-002, -7.290770e-002, -7.088981e-002, + -6.854076e-002, -6.606341e-002, -6.348505e-002, -6.069982e-002, -5.779689e-002, -5.491708e-002, -5.183715e-002, -4.871784e-002, -4.574789e-002, + -4.267877e-002, -3.950965e-002, -3.634840e-002, -3.313373e-002, -3.010130e-002, -2.696456e-002, -2.397643e-002, -2.114233e-002, -1.828296e-002, + -1.541789e-002, -1.265875e-002, -9.858510e-003, -7.186767e-003, -4.550681e-003, -1.879534e-003, 7.951079e-004, 3.408193e-003, 6.183411e-003, + 8.915607e-003, 1.160468e-002, 1.434871e-002, 1.722379e-002, 2.016723e-002, 2.305516e-002, 2.589494e-002, 2.867486e-002, 3.144102e-002, + 3.430633e-002, 3.705611e-002, 3.971557e-002, 4.232671e-002, 4.498104e-002, 4.750010e-002, 4.985911e-002, 5.215897e-002, 5.443794e-002, + 5.650808e-002, 5.849790e-002, 6.038815e-002, 6.207193e-002, 6.355134e-002, 6.496431e-002, 6.615325e-002, 6.713388e-002, 6.805462e-002, + 6.856693e-002, 6.898120e-002, 6.919332e-002, 6.918685e-002, 6.901046e-002, 6.851246e-002, 6.787536e-002, 6.697997e-002, 6.590996e-002, + 6.453573e-002, 6.300738e-002, 6.128590e-002, 5.939574e-002, 5.747478e-002, 5.545459e-002, 5.347508e-002, 5.145667e-002, 4.943969e-002, + 4.738949e-002, 4.538466e-002, 4.335512e-002, 4.143163e-002, 3.944634e-002, 3.750252e-002, 3.558318e-002, 3.374665e-002, 3.195876e-002, + 3.016734e-002, 2.841343e-002, 2.659634e-002, 2.495524e-002, 2.329459e-002, 2.163580e-002, 1.999798e-002, 1.832130e-002, 1.668069e-002, + 1.511285e-002, 1.352915e-002, 1.196980e-002, 1.042156e-002, 8.812893e-003, 7.276352e-003, 5.816718e-003, 4.300734e-003, 2.831803e-003, + 1.382030e-003, -8.584407e-005, -1.522037e-003, -2.899761e-003, -4.235273e-003, -5.575000e-003, -6.914166e-003, -8.216285e-003, -9.569998e-003, + -1.091799e-002, -1.213511e-002, -1.335990e-002, -1.462280e-002, -1.587424e-002, -1.707880e-002, -1.830529e-002, -1.950721e-002, -2.066613e-002, + -2.179987e-002, -2.294531e-002, -2.406942e-002, -2.516915e-002, -2.621419e-002, -2.721940e-002, -2.819383e-002, -2.917306e-002, -3.005866e-002, + -3.087541e-002, -3.166449e-002, -3.245383e-002, -3.318760e-002, -3.383592e-002, -3.446329e-002, -3.503471e-002, -3.557826e-002, -3.604683e-002, + -3.643831e-002, -3.671976e-002, -3.689451e-002, -3.695002e-002, -3.685698e-002, -3.668621e-002, -3.641659e-002, -3.607950e-002, -3.566249e-002, + -3.512920e-002, -3.454340e-002, -3.392062e-002, -3.322512e-002, -3.244180e-002, -3.156596e-002, -3.064623e-002, -2.970363e-002, -2.873052e-002, + -2.772481e-002, -2.666664e-002, -2.554997e-002, -2.440148e-002, -2.322167e-002, -2.208665e-002, -2.089837e-002, -1.968317e-002, -1.846749e-002, + -1.722797e-002, -1.599427e-002, -1.479363e-002, -1.357495e-002, -1.239061e-002, -1.117971e-002, -9.989264e-003, -8.805567e-003, -7.694404e-003, + -6.612748e-003, -5.549787e-003, -4.500399e-003, -3.486171e-003, -2.464558e-003, -1.498954e-003, -5.846051e-004, 2.768073e-004, 1.064311e-003, + 1.770394e-003, 2.392970e-003, 2.977479e-003, 3.465503e-003, 3.890226e-003, 4.211557e-003, 4.426568e-003, 4.558136e-003, 4.627256e-003, + 4.601091e-003, 4.490713e-003, 4.358354e-003, 4.239949e-003, 4.078649e-003, 3.891331e-003, 3.703991e-003, 3.485885e-003, 3.256289e-003, + 3.030036e-003, 2.778938e-003, 2.542953e-003, 2.361275e-003, 2.156343e-003, 1.893464e-003, 1.614705e-003, 1.372374e-003, 1.136471e-003, + 9.298647e-004, 7.181386e-004, 5.421042e-004, 4.016982e-004, 2.055644e-004, 4.693864e-005, -8.816619e-005, -2.384021e-004, -3.908703e-004, + -5.145917e-004, -6.438117e-004, -7.884539e-004, -9.246930e-004, -1.011938e-003, -1.033255e-003, -1.048758e-003, -1.041810e-003, -1.053986e-003, + -1.106793e-003, -1.210031e-003, -1.325548e-003, -1.437410e-003, -1.497819e-003, -1.491852e-003, -1.480585e-003, -1.470560e-003, -1.434580e-003, + -1.377257e-003, -1.381436e-003, -1.394461e-003, -1.423218e-003, -1.418667e-003, -1.316771e-003, -1.202684e-003, -1.075200e-003, -9.376969e-004, + -8.280971e-004, -7.796047e-004, -7.302366e-004, -6.624544e-004, -6.092372e-004, -5.252831e-004, -4.008807e-004, -2.414486e-004, -9.781290e-005, + 3.044945e-005, 8.284647e-005, 1.305904e-004, 1.623638e-004, 1.853695e-004, 2.317645e-004, 3.045913e-004, 3.990136e-004, 4.987041e-004, + 6.220249e-004, 7.118130e-004, 7.676574e-004, 8.099047e-004, 8.294054e-004, 8.377904e-004, 8.822233e-004, 9.177472e-004, 9.379131e-004, + 9.440616e-004, 9.810649e-004, 1.103409e-003, 1.176540e-003, 1.223098e-003, 1.259795e-003, 1.292701e-003, 1.335389e-003, 1.338652e-003, + 1.339449e-003, 1.381085e-003, 1.488194e-003, 1.577164e-003, 1.630007e-003, 1.662634e-003, 1.693413e-003, 1.728487e-003, 1.728010e-003, + 1.739054e-003, 1.765114e-003, 1.808860e-003, 1.860458e-003, 1.908194e-003, 1.959702e-003, 1.989123e-003, 1.976282e-003, 1.948353e-003, + 1.938278e-003, 1.933186e-003, 1.959555e-003, 1.984207e-003, 2.007310e-003, 2.026893e-003, 2.054498e-003, 2.087436e-003, 2.153807e-003, + 2.193039e-003, 2.173026e-003, 2.168770e-003, 2.171189e-003, 2.227901e-003, 2.244870e-003, 2.249290e-003, 2.244648e-003, 2.240134e-003, + 2.243374e-003, 2.223341e-003, 2.195735e-003, 2.194777e-003, 2.192997e-003, 2.156069e-003, 2.079016e-003, 1.971312e-003, 1.890285e-003, + 1.849775e-003, 1.811996e-003, 1.747163e-003, 1.654081e-003, 1.586662e-003, 1.509017e-003, 1.407003e-003, 1.287489e-003, 1.156615e-003, + 1.029739e-003, 8.725774e-004, 7.514670e-004, 6.310717e-004, 4.668026e-004, 3.296253e-004, 2.472284e-004, 1.780564e-004, 9.813202e-005, + 7.024856e-006, -8.364791e-005, -1.638346e-004, -2.404041e-004, -3.261619e-004, -4.043435e-004, -4.712044e-004, -5.911881e-004, -6.915854e-004, + -7.744435e-004, -8.497977e-004, -9.347223e-004, -1.002663e-003, -1.062698e-003, -1.103743e-003, -1.166452e-003, -1.255231e-003, -1.361254e-003, + -1.447649e-003, -1.520915e-003, -1.574976e-003, -1.624774e-003, -1.682619e-003, -1.773881e-003, -1.863953e-003, -1.949136e-003, -2.033017e-003, + -2.107201e-003, -2.189812e-003, -2.275609e-003, -2.340071e-003, -2.386362e-003, -2.436182e-003, -2.468430e-003, -2.505986e-003, -2.522184e-003, + -2.528802e-003, -2.534709e-003, -2.542867e-003, -2.564423e-003, -2.597663e-003, -2.625108e-003, -2.651275e-003, -2.675466e-003, -2.695150e-003, + -2.700661e-003, -2.680944e-003, -2.636909e-003, -2.583672e-003, -2.532316e-003, -2.494897e-003, -2.484584e-003, -2.458162e-003, -2.437826e-003, + -2.417717e-003, -2.403159e-003, -2.387185e-003, -2.392382e-003, -2.406272e-003, -2.431643e-003, -2.437993e-003, -2.413190e-003, -2.381726e-003, + -2.339674e-003, -2.310178e-003, -2.261447e-003, -2.199916e-003, -2.118498e-003, -2.026408e-003, -1.914556e-003, -1.771726e-003, -1.595517e-003, + -1.386857e-003, -1.158363e-003, -9.254445e-004, -6.746435e-004, -4.075848e-004, -1.382223e-004, 1.566729e-004, 4.775807e-004, 8.081464e-004, + 1.152529e-003, 1.524407e-003, 1.907760e-003, 2.300949e-003, 2.688409e-003, 3.080793e-003, 3.476256e-003, 3.890289e-003, 4.314994e-003, + 4.753486e-003, 5.211753e-003, 5.687781e-003, 6.179677e-003, 6.675471e-003, 7.169366e-003, 7.652750e-003, 8.130249e-003, 8.621879e-003, + 9.142344e-003, 9.666380e-003, 1.020179e-002, 1.075643e-002, 1.131664e-002, 1.188455e-002, 1.247185e-002, 1.306803e-002, 1.366827e-002, + 1.425815e-002, 1.484815e-002, 1.543804e-002, 1.603555e-002, 1.663695e-002, 1.721990e-002, 1.779312e-002, 1.836562e-002, 1.892609e-002, + 1.949438e-002, 2.007326e-002, 2.062172e-002, 2.111005e-002, 2.160011e-002, 2.209030e-002, 2.259632e-002, 2.309821e-002, 2.358284e-002, + 2.405531e-002, 2.451988e-002, 2.497966e-002, 2.542588e-002, 2.583990e-002, 2.624814e-002, 2.663117e-002, 2.699270e-002, 2.732745e-002, + 2.764841e-002, 2.795764e-002, 2.826967e-002, 2.857269e-002, 2.885510e-002, 2.911152e-002, 2.935808e-002, 2.958524e-002, 2.975768e-002, + 2.991022e-002, 3.009176e-002, 3.026225e-002, 3.040200e-002, 3.050378e-002, 3.059241e-002, 3.066170e-002, 3.070899e-002, 3.072240e-002, + 3.067765e-002, 3.059507e-002, 3.048192e-002, 3.033001e-002, 3.015391e-002, 2.994818e-002, 2.971085e-002, 2.942712e-002, 2.909253e-002, + 2.872605e-002, 2.830716e-002, 2.784749e-002, 2.735425e-002, 2.683947e-002, 2.628566e-002, 2.569121e-002, 2.508597e-002, 2.446298e-002, + 2.381813e-002, 2.315801e-002, 2.252099e-002, 2.185049e-002, 2.113087e-002, 2.037404e-002, 1.959958e-002, 1.883099e-002, 1.807093e-002, + 1.729770e-002, 1.652498e-002, 1.573159e-002, 1.495137e-002, 1.421379e-002, 1.346815e-002, 1.273341e-002, 1.201557e-002, 1.129502e-002, + 1.057152e-002, 9.848899e-003, 9.129448e-003, 8.397180e-003, 7.642661e-003, 6.912411e-003, 6.261204e-003, 5.593916e-003, 4.907036e-003, + 4.229196e-003, 3.540967e-003, 2.843428e-003, 2.145019e-003, 1.479969e-003, 8.144344e-004, 1.216311e-004, -5.833074e-004, -1.273385e-003, + -1.970200e-003, -2.678760e-003, -3.305254e-003, -3.870133e-003, -4.422037e-003, -4.977046e-003, -5.525795e-003, -6.075219e-003, -6.630161e-003, + -7.192632e-003, -7.760078e-003, -8.326019e-003, -8.906654e-003, -9.505481e-003, -1.003924e-002, -1.057216e-002, -1.110722e-002, -1.164026e-002, + -1.218242e-002, -1.270181e-002, -1.319003e-002, -1.365166e-002, -1.410855e-002, -1.455369e-002, -1.500198e-002, -1.545054e-002, -1.590635e-002, + -1.637174e-002, -1.683438e-002, -1.729068e-002, -1.775106e-002, -1.821105e-002, -1.867706e-002, -1.912882e-002, -1.956989e-002, -2.000570e-002, + -2.047037e-002, -2.094902e-002, -2.135494e-002, -2.171667e-002, -2.206925e-002, -2.241727e-002, -2.275526e-002, -2.304135e-002, -2.331169e-002, + -2.358582e-002, -2.385427e-002, -2.411570e-002, -2.437681e-002, -2.465152e-002, -2.494976e-002, -2.524366e-002, -2.553070e-002, -2.580825e-002, + -2.607810e-002, -2.632767e-002, -2.657636e-002, -2.684301e-002, -2.709067e-002, -2.732745e-002, -2.757891e-002, -2.783068e-002, -2.806875e-002, + -2.827531e-002, -2.846376e-002, -2.864872e-002, -2.881077e-002, -2.896882e-002, -2.912747e-002, -2.927044e-002, -2.938489e-002, -2.949837e-002, + -2.958264e-002, -2.966053e-002, -2.969128e-002, -2.972274e-002, -2.975694e-002, -2.978394e-002, -2.980791e-002, -2.982642e-002, -2.982843e-002, + -2.981198e-002, -2.978656e-002, -2.975477e-002, -2.973172e-002, -2.970673e-002, -2.968467e-002, -2.965510e-002, -2.961405e-002, -2.955663e-002, + -2.948887e-002, -2.941371e-002, -2.932945e-002, -2.922619e-002, -2.911933e-002, -2.900568e-002, -2.887813e-002, -2.874517e-002, -2.859753e-002, + -2.844296e-002, -2.828284e-002, -2.812081e-002, -2.796222e-002, -2.781061e-002, -2.763165e-002, -2.743285e-002, -2.721977e-002, -2.699281e-002, + -2.676430e-002, -2.653488e-002, -2.631279e-002, -2.607819e-002, -2.582505e-002, -2.555764e-002, -2.529125e-002, -2.498855e-002, -2.467981e-002, + -2.437210e-002, -2.406614e-002, -2.378183e-002, -2.352124e-002, -2.325633e-002, -2.298571e-002, -2.270701e-002, -2.241807e-002, -2.212651e-002, + -2.183469e-002, -2.154408e-002, -2.125230e-002, -2.096461e-002, -2.066675e-002, -2.035571e-002, -2.002522e-002, -1.965941e-002, -1.926862e-002, + -1.886961e-002, -1.846221e-002, -1.803678e-002, -1.759340e-002, -1.714963e-002, -1.669965e-002, -1.624895e-002, -1.579296e-002, -1.533473e-002, + -1.487257e-002, -1.440648e-002, -1.394547e-002, -1.349306e-002, -1.303329e-002, -1.256285e-002, -1.209812e-002, -1.163973e-002, -1.118525e-002, + -1.073190e-002, -1.028767e-002, -9.853180e-003, -9.444167e-003, -9.038903e-003, -8.646198e-003, -8.249178e-003, -7.837462e-003, -7.416346e-003, + -6.988440e-003, -6.551874e-003, -6.112431e-003, -5.675586e-003, -5.254285e-003, -4.810506e-003, -4.366722e-003, -3.922109e-003, -3.447888e-003, + -2.980728e-003, -2.515324e-003, -2.035227e-003, -1.534855e-003, -1.046957e-003, -5.773481e-004, -1.078402e-004, 3.594199e-004, 8.382999e-004, + 1.313792e-003, 1.788263e-003, 2.258657e-003, 2.716756e-003, 3.174055e-003, 3.641204e-003, 4.096444e-003, 4.524308e-003, 4.949731e-003, + 5.373405e-003, 5.795411e-003, 6.223141e-003, 6.644972e-003, 7.065756e-003, 7.464499e-003, 7.858962e-003, 8.237450e-003, 8.589952e-003, + 8.945863e-003, 9.300365e-003, 9.666188e-003, 1.004092e-002, 1.042301e-002, 1.080405e-002, 1.117096e-002, 1.153361e-002, 1.190375e-002, + 1.223310e-002, 1.256484e-002, 1.289871e-002, 1.323478e-002, 1.358060e-002, 1.394978e-002, 1.431581e-002, 1.467376e-002, 1.502842e-002, + 1.538847e-002, 1.571083e-002, 1.603173e-002, 1.634546e-002, 1.666260e-002, 1.699210e-002, 1.731944e-002, 1.763995e-002, 1.796294e-002, + 1.828430e-002, 1.861413e-002, 1.891148e-002, 1.918801e-002, 1.946620e-002, 1.974920e-002, 2.003498e-002, 2.032269e-002, 2.060757e-002, + 2.089902e-002, 2.118249e-002, 2.147054e-002, 2.173628e-002, 2.195179e-002, 2.217109e-002, 2.239403e-002, 2.262547e-002, 2.286194e-002, + 2.309923e-002, 2.334493e-002, 2.358237e-002, 2.381620e-002, 2.404114e-002, 2.418968e-002, 2.434190e-002, 2.449639e-002, 2.466359e-002, + 2.486284e-002, 2.507680e-002, 2.528531e-002, 2.549299e-002, 2.570084e-002, 2.591056e-002, 2.604882e-002, 2.618800e-002, 2.632532e-002, + 2.646491e-002, 2.661001e-002, 2.675594e-002, 2.688870e-002, 2.701337e-002, 2.714179e-002, 2.728166e-002, 2.736228e-002, 2.740784e-002, + 2.745358e-002, 2.750195e-002, 2.755637e-002, 2.761526e-002, 2.766599e-002, 2.769690e-002, 2.771925e-002, 2.774145e-002, 2.772148e-002, + 2.763421e-002, 2.754580e-002, 2.746702e-002, 2.739504e-002, 2.732600e-002, 2.725186e-002, 2.716142e-002, 2.704823e-002, 2.692614e-002, + 2.677919e-002, 2.652559e-002, 2.627190e-002, 2.602732e-002, 2.578227e-002, 2.553829e-002, 2.530127e-002, 2.508215e-002, 2.485323e-002, + 2.461848e-002, 2.439795e-002, 2.406492e-002, 2.372369e-002, 2.338799e-002, 2.305694e-002, 2.273056e-002, 2.240680e-002, 2.208303e-002, + 2.175864e-002, 2.141452e-002, 2.106455e-002, 2.061398e-002, 2.012728e-002, 1.964071e-002, 1.915317e-002, 1.865905e-002, 1.816798e-002, + 1.769839e-002, 1.722646e-002, 1.673997e-002, 1.620857e-002, 1.559720e-002, 1.489314e-002, 1.418567e-002, 1.347678e-002, 1.278654e-002, + 1.209855e-002, 1.142005e-002, 1.073145e-002, 1.004071e-002, 9.344574e-003, 8.571980e-003, 7.670828e-003, 6.788875e-003, 5.927109e-003, + 5.068001e-003, 4.212475e-003, 3.360459e-003, 2.513387e-003, 1.670471e-003, 8.330329e-004, -3.150190e-017}, + {0.000000e+000, 2.616309e-001, 2.187042e-001, 1.748157e-001, 1.417391e-001, 1.150747e-001, 8.688658e-002, 6.075912e-002, 3.646030e-002, + 1.351882e-002, -8.209922e-003, -2.886362e-002, -4.766997e-002, -6.452617e-002, -7.749161e-002, -8.697334e-002, -9.373771e-002, -9.940247e-002, + -1.046593e-001, -1.083829e-001, -1.105832e-001, -1.117065e-001, -1.115141e-001, -1.105643e-001, -1.090158e-001, -1.067233e-001, -1.030868e-001, + -9.781883e-002, -9.039628e-002, -8.085284e-002, -6.991689e-002, -5.785499e-002, -4.543213e-002, -3.286415e-002, -2.012794e-002, -7.606798e-003, + 4.347646e-003, 1.524646e-002, 2.574387e-002, 3.608154e-002, 4.608898e-002, 5.483372e-002, 6.345248e-002, 7.156473e-002, 7.881243e-002, + 8.527163e-002, 9.114060e-002, 9.636036e-002, 1.003114e-001, 1.040243e-001, 1.070431e-001, 1.086666e-001, 1.093005e-001, 1.084687e-001, + 1.065810e-001, 1.037056e-001, 1.000408e-001, 9.550326e-002, 9.029829e-002, 8.517134e-002, 7.922808e-002, 7.320034e-002, 6.681099e-002, + 5.971452e-002, 5.216704e-002, 4.456903e-002, 3.666625e-002, 2.850437e-002, 2.035880e-002, 1.231888e-002, 4.545512e-003, -3.166212e-003, + -1.053504e-002, -1.769757e-002, -2.434263e-002, -3.059878e-002, -3.629133e-002, -4.149259e-002, -4.613754e-002, -5.029473e-002, -5.400154e-002, + -5.679247e-002, -5.925371e-002, -6.098967e-002, -6.227302e-002, -6.300515e-002, -6.324049e-002, -6.323603e-002, -6.252300e-002, -6.155722e-002, + -6.043528e-002, -5.889734e-002, -5.721450e-002, -5.529738e-002, -5.307543e-002, -5.065763e-002, -4.849789e-002, -4.620116e-002, -4.371069e-002, + -4.139970e-002, -3.915118e-002, -3.688204e-002, -3.472676e-002, -3.240213e-002, -3.007064e-002, -2.801528e-002, -2.602072e-002, -2.412263e-002, + -2.228238e-002, -2.071468e-002, -1.904888e-002, -1.755147e-002, -1.614635e-002, -1.473501e-002, -1.328342e-002, -1.202087e-002, -1.066777e-002, + -9.281785e-003, -7.742223e-003, -5.960808e-003, -4.257935e-003, -2.422493e-003, -3.195274e-004, 1.796373e-003, 3.828922e-003, 5.601601e-003, + 7.425616e-003, 9.215458e-003, 1.106508e-002, 1.283224e-002, 1.448997e-002, 1.617500e-002, 1.783639e-002, 1.944480e-002, 2.086413e-002, + 2.222284e-002, 2.349670e-002, 2.486960e-002, 2.604883e-002, 2.717056e-002, 2.830369e-002, 2.933500e-002, 3.015436e-002, 3.085847e-002, + 3.161770e-002, 3.220586e-002, 3.279920e-002, 3.342561e-002, 3.381156e-002, 3.412947e-002, 3.437861e-002, 3.457182e-002, 3.473040e-002, + 3.475502e-002, 3.469529e-002, 3.466792e-002, 3.451209e-002, 3.423636e-002, 3.394094e-002, 3.356073e-002, 3.308630e-002, 3.266903e-002, + 3.204367e-002, 3.120684e-002, 3.039136e-002, 2.953746e-002, 2.864049e-002, 2.758818e-002, 2.647184e-002, 2.538506e-002, 2.434684e-002, + 2.332702e-002, 2.209125e-002, 2.085895e-002, 1.967308e-002, 1.858240e-002, 1.731540e-002, 1.599940e-002, 1.474620e-002, 1.357118e-002, + 1.207677e-002, 1.065390e-002, 9.352745e-003, 7.894652e-003, 6.400186e-003, 4.870625e-003, 3.202870e-003, 1.529805e-003, 3.449278e-006, + -1.866329e-003, -3.732043e-003, -5.676562e-003, -7.703822e-003, -9.642500e-003, -1.182382e-002, -1.392094e-002, -1.620490e-002, -1.833710e-002, + -2.043938e-002, -2.239789e-002, -2.430849e-002, -2.611203e-002, -2.794738e-002, -2.976166e-002, -3.143381e-002, -3.301074e-002, -3.445158e-002, + -3.590805e-002, -3.713095e-002, -3.831127e-002, -3.922851e-002, -4.008871e-002, -4.073530e-002, -4.131773e-002, -4.168150e-002, -4.192563e-002, + -4.203134e-002, -4.201026e-002, -4.200734e-002, -4.172996e-002, -4.140786e-002, -4.095624e-002, -4.035322e-002, -3.973137e-002, -3.894405e-002, + -3.810126e-002, -3.720283e-002, -3.614239e-002, -3.504046e-002, -3.396816e-002, -3.271543e-002, -3.142777e-002, -3.012944e-002, -2.872402e-002, + -2.730413e-002, -2.590909e-002, -2.447171e-002, -2.291314e-002, -2.133224e-002, -1.975593e-002, -1.819254e-002, -1.656962e-002, -1.503513e-002, + -1.345689e-002, -1.174644e-002, -1.005234e-002, -8.510246e-003, -6.953671e-003, -5.323991e-003, -3.747955e-003, -2.126349e-003, -4.840920e-004, + 1.115175e-003, 2.650654e-003, 4.186460e-003, 5.705632e-003, 7.272811e-003, 8.887157e-003, 1.040112e-002, 1.180232e-002, 1.329916e-002, + 1.484239e-002, 1.635634e-002, 1.782388e-002, 1.928053e-002, 2.064900e-002, 2.197776e-002, 2.329312e-002, 2.455140e-002, 2.580663e-002, + 2.695870e-002, 2.789678e-002, 2.865565e-002, 2.929528e-002, 2.991832e-002, 3.044219e-002, 3.087617e-002, 3.118649e-002, 3.134317e-002, + 3.137674e-002, 3.132466e-002, 3.118910e-002, 3.101798e-002, 3.079929e-002, 3.053619e-002, 3.018039e-002, 2.966197e-002, 2.905223e-002, + 2.836924e-002, 2.769439e-002, 2.701125e-002, 2.629913e-002, 2.558599e-002, 2.471208e-002, 2.374464e-002, 2.282130e-002, 2.183831e-002, + 2.084821e-002, 1.984954e-002, 1.884930e-002, 1.792678e-002, 1.688622e-002, 1.582119e-002, 1.470698e-002, 1.365960e-002, 1.254829e-002, + 1.146662e-002, 1.043918e-002, 9.432422e-003, 8.408720e-003, 7.361661e-003, 6.296192e-003, 5.237162e-003, 4.297232e-003, 3.340338e-003, + 2.401789e-003, 1.485533e-003, 6.973520e-004, -9.765721e-005, -7.986929e-004, -1.457459e-003, -2.051026e-003, -2.508999e-003, -2.823115e-003, + -3.085576e-003, -3.301244e-003, -3.532309e-003, -3.759468e-003, -3.939279e-003, -4.105624e-003, -4.257060e-003, -4.402302e-003, -4.524387e-003, + -4.614262e-003, -4.709395e-003, -4.766498e-003, -4.789093e-003, -4.838223e-003, -4.927303e-003, -5.075132e-003, -5.237850e-003, -5.343672e-003, + -5.369815e-003, -5.385244e-003, -5.385448e-003, -5.421267e-003, -5.451886e-003, -5.448310e-003, -5.548790e-003, -5.637334e-003, -5.718407e-003, + -5.754008e-003, -5.770676e-003, -5.760104e-003, -5.781192e-003, -5.772122e-003, -5.747847e-003, -5.744699e-003, -5.664803e-003, -5.564198e-003, + -5.508380e-003, -5.504042e-003, -5.430905e-003, -5.305196e-003, -5.246689e-003, -5.252686e-003, -5.248574e-003, -5.177898e-003, -5.032330e-003, + -4.853154e-003, -4.721768e-003, -4.628169e-003, -4.584288e-003, -4.565798e-003, -4.615826e-003, -4.702669e-003, -4.776684e-003, -4.843721e-003, + -4.803575e-003, -4.784674e-003, -4.752776e-003, -4.745525e-003, -4.743236e-003, -4.811438e-003, -4.950630e-003, -5.125605e-003, -5.270802e-003, + -5.411600e-003, -5.591552e-003, -5.763395e-003, -5.915234e-003, -6.008405e-003, -6.098120e-003, -6.184349e-003, -6.332087e-003, -6.521344e-003, + -6.718278e-003, -6.965978e-003, -7.265330e-003, -7.524359e-003, -7.761833e-003, -7.987428e-003, -8.184914e-003, -8.384649e-003, -8.693130e-003, + -9.019203e-003, -9.372612e-003, -9.725989e-003, -1.009062e-002, -1.043568e-002, -1.076088e-002, -1.102721e-002, -1.128950e-002, -1.158322e-002, + -1.188324e-002, -1.213739e-002, -1.237289e-002, -1.259527e-002, -1.282652e-002, -1.311049e-002, -1.338502e-002, -1.361028e-002, -1.380472e-002, + -1.397776e-002, -1.408034e-002, -1.412290e-002, -1.413199e-002, -1.408977e-002, -1.400033e-002, -1.391448e-002, -1.383656e-002, -1.370991e-002, + -1.354660e-002, -1.330013e-002, -1.299053e-002, -1.265488e-002, -1.235031e-002, -1.202417e-002, -1.166323e-002, -1.129266e-002, -1.087387e-002, + -1.039377e-002, -9.934663e-003, -9.424126e-003, -8.834887e-003, -8.230009e-003, -7.702270e-003, -7.204794e-003, -6.682111e-003, -6.144585e-003, + -5.587094e-003, -5.002825e-003, -4.375693e-003, -3.722229e-003, -3.089703e-003, -2.465468e-003, -1.800777e-003, -1.116030e-003, -4.498091e-004, + 2.248057e-004, 9.706666e-004, 1.733746e-003, 2.505235e-003, 3.348760e-003, 4.171017e-003, 4.942018e-003, 5.728039e-003, 6.524442e-003, + 7.318125e-003, 8.073822e-003, 8.783564e-003, 9.509737e-003, 1.026816e-002, 1.098915e-002, 1.173011e-002, 1.247034e-002, 1.322201e-002, + 1.401679e-002, 1.480660e-002, 1.558687e-002, 1.634232e-002, 1.709538e-002, 1.781741e-002, 1.846771e-002, 1.902315e-002, 1.955720e-002, + 2.007976e-002, 2.060608e-002, 2.113101e-002, 2.166041e-002, 2.218780e-002, 2.270317e-002, 2.316922e-002, 2.362090e-002, 2.403324e-002, + 2.447493e-002, 2.488595e-002, 2.530160e-002, 2.572022e-002, 2.605959e-002, 2.633706e-002, 2.658410e-002, 2.679599e-002, 2.698466e-002, + 2.715546e-002, 2.726182e-002, 2.731446e-002, 2.730675e-002, 2.731138e-002, 2.728518e-002, 2.726644e-002, 2.723452e-002, 2.717142e-002, + 2.706998e-002, 2.695426e-002, 2.684809e-002, 2.668433e-002, 2.647758e-002, 2.625914e-002, 2.602748e-002, 2.578017e-002, 2.552078e-002, + 2.526091e-002, 2.502719e-002, 2.479559e-002, 2.456060e-002, 2.428876e-002, 2.403608e-002, 2.376839e-002, 2.350502e-002, 2.320176e-002, + 2.289762e-002, 2.259429e-002, 2.229809e-002, 2.198899e-002, 2.168862e-002, 2.138450e-002, 2.108281e-002, 2.078204e-002, 2.046311e-002, + 2.010149e-002, 1.971926e-002, 1.933504e-002, 1.894218e-002, 1.851907e-002, 1.805759e-002, 1.757056e-002, 1.705722e-002, 1.650379e-002, + 1.592092e-002, 1.531940e-002, 1.471146e-002, 1.408378e-002, 1.343434e-002, 1.271643e-002, 1.194604e-002, 1.115917e-002, 1.039148e-002, + 9.613897e-003, 8.793899e-003, 7.978010e-003, 7.123317e-003, 6.245877e-003, 5.370102e-003, 4.476524e-003, 3.576845e-003, 2.676305e-003, + 1.758402e-003, 8.446902e-004, -7.110065e-005, -9.733178e-004, -1.869164e-003, -2.762325e-003, -3.658872e-003, -4.569332e-003, -5.511076e-003, + -6.478237e-003, -7.459028e-003, -8.443705e-003, -9.467735e-003, -1.049145e-002, -1.153028e-002, -1.258350e-002, -1.364316e-002, -1.469400e-002, + -1.571618e-002, -1.671194e-002, -1.770642e-002, -1.864553e-002, -1.957009e-002, -2.048951e-002, -2.141449e-002, -2.232687e-002, -2.323075e-002, + -2.407870e-002, -2.492980e-002, -2.579019e-002, -2.664221e-002, -2.742733e-002, -2.814326e-002, -2.885675e-002, -2.955288e-002, -3.022473e-002, + -3.083768e-002, -3.140972e-002, -3.195704e-002, -3.249520e-002, -3.304792e-002, -3.357974e-002, -3.409454e-002, -3.455706e-002, -3.494506e-002, + -3.532302e-002, -3.568962e-002, -3.604982e-002, -3.643438e-002, -3.682183e-002, -3.719656e-002, -3.755569e-002, -3.785514e-002, -3.814662e-002, + -3.845371e-002, -3.875486e-002, -3.902183e-002, -3.926361e-002, -3.948225e-002, -3.958732e-002, -3.967712e-002, -3.973963e-002, -3.975492e-002, + -3.971670e-002, -3.965357e-002, -3.953955e-002, -3.939289e-002, -3.920699e-002, -3.899348e-002, -3.875126e-002, -3.850175e-002, -3.820469e-002, + -3.785769e-002, -3.749264e-002, -3.710484e-002, -3.669338e-002, -3.621590e-002, -3.569464e-002, -3.508582e-002, -3.439865e-002, -3.368438e-002, + -3.295247e-002, -3.221960e-002, -3.151954e-002, -3.079015e-002, -3.003636e-002, -2.925127e-002, -2.845931e-002, -2.767171e-002, -2.687716e-002, + -2.608908e-002, -2.531272e-002, -2.452898e-002, -2.376143e-002, -2.298945e-002, -2.221931e-002, -2.149787e-002, -2.078149e-002, -2.002688e-002, + -1.924975e-002, -1.846294e-002, -1.766731e-002, -1.688026e-002, -1.609423e-002, -1.532322e-002, -1.454264e-002, -1.379617e-002, -1.300885e-002, + -1.222838e-002, -1.145498e-002, -1.067497e-002, -9.891558e-003, -9.175706e-003, -8.465063e-003, -7.750555e-003, -7.045032e-003, -6.335894e-003, + -5.622342e-003, -4.913743e-003, -4.320842e-003, -3.798131e-003, -3.269022e-003, -2.734525e-003, -2.208693e-003, -1.682748e-003, -1.143876e-003, + -6.081016e-004, -6.630030e-005, 4.881984e-004, 1.053882e-003, 1.605480e-003, 2.076789e-003, 2.544047e-003, 3.025428e-003, 3.513402e-003, + 3.978367e-003, 4.386814e-003, 4.716371e-003, 5.014728e-003, 5.334329e-003, 5.662562e-003, 6.058405e-003, 6.446699e-003, 6.840161e-003, + 7.241860e-003, 7.674519e-003, 8.123500e-003, 8.575565e-003, 9.019672e-003, 9.450103e-003, 9.876752e-003, 1.030647e-002, 1.072961e-002, + 1.114144e-002, 1.154630e-002, 1.184170e-002, 1.207532e-002, 1.233833e-002, 1.261466e-002, 1.287894e-002, 1.308786e-002, 1.327828e-002, + 1.348041e-002, 1.369956e-002, 1.392951e-002, 1.416685e-002, 1.440413e-002, 1.462540e-002, 1.484389e-002, 1.508447e-002, 1.532451e-002, + 1.558096e-002, 1.586768e-002, 1.615642e-002, 1.643420e-002, 1.669900e-002, 1.694894e-002, 1.719108e-002, 1.743280e-002, 1.765179e-002, + 1.781542e-002, 1.796699e-002, 1.811802e-002, 1.826513e-002, 1.841026e-002, 1.854011e-002, 1.864458e-002, 1.871480e-002, 1.879340e-002, + 1.889738e-002, 1.901934e-002, 1.908576e-002, 1.916819e-002, 1.927523e-002, 1.940246e-002, 1.953514e-002, 1.967653e-002, 1.981623e-002, + 1.992489e-002, 2.004373e-002, 2.018901e-002, 2.033955e-002, 2.048860e-002, 2.066218e-002, 2.083185e-002, 2.101261e-002, 2.120715e-002, + 2.140490e-002, 2.160185e-002, 2.179537e-002, 2.195982e-002, 2.207417e-002, 2.217943e-002, 2.227547e-002, 2.234468e-002, 2.240482e-002, + 2.244652e-002, 2.246348e-002, 2.246817e-002, 2.247667e-002, 2.247706e-002, 2.246136e-002, 2.243421e-002, 2.238140e-002, 2.228066e-002, + 2.217668e-002, 2.207545e-002, 2.196340e-002, 2.182217e-002, 2.167603e-002, 2.155638e-002, 2.143114e-002, 2.127399e-002, 2.111776e-002, + 2.097319e-002, 2.083404e-002, 2.069418e-002, 2.056094e-002, 2.042159e-002, 2.030101e-002, 2.019684e-002, 2.009938e-002, 2.000351e-002, + 1.990383e-002, 1.979381e-002, 1.968590e-002, 1.957534e-002, 1.944928e-002, 1.931353e-002, 1.916016e-002, 1.895429e-002, 1.870555e-002, + 1.844438e-002, 1.818109e-002, 1.789508e-002, 1.761845e-002, 1.732106e-002, 1.700858e-002, 1.669065e-002, 1.636746e-002, 1.604298e-002, + 1.572119e-002, 1.540721e-002, 1.509434e-002, 1.477797e-002, 1.445579e-002, 1.413619e-002, 1.380992e-002, 1.347754e-002, 1.316513e-002, + 1.287765e-002, 1.259116e-002, 1.230700e-002, 1.205244e-002, 1.179915e-002, 1.153887e-002, 1.127258e-002, 1.099731e-002, 1.072762e-002, + 1.045186e-002, 1.017873e-002, 9.911024e-003, 9.643432e-003, 9.385897e-003, 9.097465e-003, 8.796735e-003, 8.491115e-003, 8.169308e-003, + 7.835788e-003, 7.498905e-003, 7.156931e-003, 6.798763e-003, 6.448502e-003, 6.112574e-003, 5.807285e-003, 5.512039e-003, 5.244423e-003, + 4.983049e-003, 4.727585e-003, 4.475615e-003, 4.223985e-003, 3.986483e-003, 3.722121e-003, 3.478817e-003, 3.264948e-003, 3.058838e-003, + 2.844829e-003, 2.621190e-003, 2.401502e-003, 2.173198e-003, 1.935061e-003, 1.684484e-003, 1.443250e-003, 1.192445e-003, 9.358708e-004, + 6.640067e-004, 3.911635e-004, 8.753651e-005, -2.324088e-004, -5.587866e-004, -8.957589e-004, -1.240984e-003, -1.605898e-003, -1.969859e-003, + -2.313585e-003, -2.669489e-003, -3.036341e-003, -3.412040e-003, -3.791887e-003, -4.186591e-003, -4.576267e-003, -4.961596e-003, -5.339523e-003, + -5.699528e-003, -6.032103e-003, -6.384200e-003, -6.746542e-003, -7.111394e-003, -7.470422e-003, -7.830704e-003, -8.179860e-003, -8.525526e-003, + -8.843227e-003, -9.150797e-003, -9.440520e-003, -9.699417e-003, -9.955033e-003, -1.021250e-002, -1.047285e-002, -1.073737e-002, -1.102376e-002, + -1.132571e-002, -1.162909e-002, -1.192733e-002, -1.221872e-002, -1.248308e-002, -1.275180e-002, -1.302408e-002, -1.330674e-002, -1.361168e-002, + -1.393081e-002, -1.425966e-002, -1.456352e-002, -1.486341e-002, -1.515424e-002, -1.538027e-002, -1.560887e-002, -1.584675e-002, -1.607153e-002, + -1.631649e-002, -1.656848e-002, -1.680136e-002, -1.702400e-002, -1.724729e-002, -1.747449e-002, -1.765796e-002, -1.782648e-002, -1.798849e-002, + -1.814933e-002, -1.832042e-002, -1.850273e-002, -1.868757e-002, -1.887861e-002, -1.907686e-002, -1.926864e-002, -1.942867e-002, -1.957442e-002, + -1.973200e-002, -1.990341e-002, -2.009741e-002, -2.030249e-002, -2.051201e-002, -2.071748e-002, -2.091934e-002, -2.112629e-002, -2.131107e-002, + -2.145359e-002, -2.159981e-002, -2.173789e-002, -2.187444e-002, -2.200218e-002, -2.213378e-002, -2.227790e-002, -2.239264e-002, -2.250166e-002, + -2.260443e-002, -2.263148e-002, -2.266883e-002, -2.268151e-002, -2.268970e-002, -2.269930e-002, -2.271236e-002, -2.271743e-002, -2.268455e-002, + -2.264106e-002, -2.259842e-002, -2.243410e-002, -2.224910e-002, -2.207414e-002, -2.191017e-002, -2.175289e-002, -2.159886e-002, -2.144884e-002, + -2.129710e-002, -2.110407e-002, -2.090125e-002, -2.062630e-002, -2.029199e-002, -1.995701e-002, -1.960875e-002, -1.921918e-002, -1.883493e-002, + -1.842686e-002, -1.801856e-002, -1.760148e-002, -1.713699e-002, -1.660590e-002, -1.599780e-002, -1.537926e-002, -1.475072e-002, -1.408667e-002, + -1.340152e-002, -1.269229e-002, -1.193805e-002, -1.116873e-002, -1.040116e-002, -9.555318e-003, -8.611026e-003, -7.648784e-003, -6.666498e-003, + -5.689759e-003, -4.719994e-003, -3.757357e-003, -2.804373e-003, -1.859909e-003, -9.256437e-004, -2.013541e-017}, + {0.000000e+000, 1.507455e-001, 1.207007e-001, 9.171221e-002, 6.179052e-002, 3.365747e-002, 1.332257e-002, -2.375174e-003, -1.649751e-002, + -2.877970e-002, -3.811668e-002, -4.527494e-002, -5.011530e-002, -5.315954e-002, -5.572485e-002, -5.786769e-002, -5.867673e-002, -5.705429e-002, + -5.326458e-002, -4.860399e-002, -4.401021e-002, -3.995093e-002, -3.576162e-002, -3.153032e-002, -2.650787e-002, -2.115455e-002, -1.580126e-002, + -1.088052e-002, -6.794520e-003, -3.031228e-003, 7.699568e-004, 4.962568e-003, 8.936494e-003, 1.270241e-002, 1.686004e-002, 2.118074e-002, + 2.557811e-002, 2.970777e-002, 3.409884e-002, 3.867944e-002, 4.342115e-002, 4.733632e-002, 5.110933e-002, 5.475782e-002, 5.758125e-002, + 5.941473e-002, 6.072315e-002, 6.154284e-002, 6.129292e-002, 6.065663e-002, 5.912191e-002, 5.703610e-002, 5.489733e-002, 5.218830e-002, + 4.934198e-002, 4.593269e-002, 4.193563e-002, 3.756416e-002, 3.264069e-002, 2.791803e-002, 2.265526e-002, 1.703786e-002, 1.112656e-002, + 5.029451e-003, -8.870066e-004, -6.429659e-003, -1.209488e-002, -1.795477e-002, -2.389911e-002, -2.949066e-002, -3.445773e-002, -3.937190e-002, + -4.413430e-002, -4.874777e-002, -5.267152e-002, -5.633061e-002, -5.965567e-002, -6.268070e-002, -6.514167e-002, -6.739588e-002, -6.926296e-002, + -7.003671e-002, -7.108702e-002, -7.180639e-002, -7.177871e-002, -7.139445e-002, -7.100258e-002, -7.006411e-002, -6.861398e-002, -6.684054e-002, + -6.465894e-002, -6.219882e-002, -5.890686e-002, -5.571682e-002, -5.216535e-002, -4.807539e-002, -4.392300e-002, -3.944633e-002, -3.483985e-002, + -3.003443e-002, -2.491446e-002, -1.948053e-002, -1.414013e-002, -8.453057e-003, -2.650175e-003, 2.943184e-003, 8.558212e-003, 1.401360e-002, + 1.961674e-002, 2.476884e-002, 2.968361e-002, 3.432279e-002, 3.893293e-002, 4.288325e-002, 4.688597e-002, 5.040807e-002, 5.363671e-002, + 5.646457e-002, 5.877778e-002, 6.074455e-002, 6.207494e-002, 6.318869e-002, 6.393809e-002, 6.410510e-002, 6.427001e-002, 6.421797e-002, + 6.396191e-002, 6.349514e-002, 6.263445e-002, 6.143830e-002, 6.012655e-002, 5.865263e-002, 5.692138e-002, 5.520538e-002, 5.372955e-002, + 5.203802e-002, 5.041504e-002, 4.895197e-002, 4.693858e-002, 4.490112e-002, 4.297358e-002, 4.090379e-002, 3.889292e-002, 3.695421e-002, + 3.483684e-002, 3.238920e-002, 2.995791e-002, 2.748953e-002, 2.486904e-002, 2.199338e-002, 1.923505e-002, 1.644573e-002, 1.343826e-002, + 1.006541e-002, 6.671872e-003, 3.071788e-003, -1.568400e-004, -3.428808e-003, -6.719075e-003, -1.009985e-002, -1.350290e-002, -1.699157e-002, + -2.015547e-002, -2.321278e-002, -2.628419e-002, -2.925985e-002, -3.237629e-002, -3.517825e-002, -3.779963e-002, -4.038895e-002, -4.299644e-002, + -4.581103e-002, -4.802005e-002, -5.008550e-002, -5.221627e-002, -5.448365e-002, -5.639040e-002, -5.801093e-002, -5.961495e-002, -6.118521e-002, + -6.217152e-002, -6.314675e-002, -6.408047e-002, -6.443810e-002, -6.448580e-002, -6.461142e-002, -6.419266e-002, -6.336891e-002, -6.252112e-002, + -6.082518e-002, -5.911583e-002, -5.709120e-002, -5.450672e-002, -5.171306e-002, -4.822821e-002, -4.474604e-002, -4.082495e-002, -3.690032e-002, + -3.247575e-002, -2.803771e-002, -2.339857e-002, -1.884295e-002, -1.422596e-002, -9.614114e-003, -5.041266e-003, -4.446784e-004, 3.949733e-003, + 8.251256e-003, 1.222173e-002, 1.612896e-002, 1.961673e-002, 2.321542e-002, 2.630637e-002, 2.935242e-002, 3.198098e-002, 3.446069e-002, + 3.681257e-002, 3.883299e-002, 4.089755e-002, 4.232988e-002, 4.373358e-002, 4.497128e-002, 4.589218e-002, 4.695297e-002, 4.769053e-002, + 4.810847e-002, 4.864462e-002, 4.883958e-002, 4.899831e-002, 4.918537e-002, 4.913879e-002, 4.890685e-002, 4.866293e-002, 4.829285e-002, + 4.774044e-002, 4.718648e-002, 4.667514e-002, 4.581629e-002, 4.492544e-002, 4.411025e-002, 4.306915e-002, 4.196890e-002, 4.090352e-002, + 3.991363e-002, 3.866343e-002, 3.735754e-002, 3.601406e-002, 3.474194e-002, 3.316444e-002, 3.158613e-002, 3.010587e-002, 2.853235e-002, + 2.672790e-002, 2.493368e-002, 2.317910e-002, 2.145095e-002, 1.956620e-002, 1.767439e-002, 1.581577e-002, 1.394612e-002, 1.201088e-002, + 1.008587e-002, 8.221889e-003, 6.389277e-003, 4.629087e-003, 2.666268e-003, 7.295213e-004, -1.079513e-003, -2.819868e-003, -4.586865e-003, + -6.284481e-003, -7.956978e-003, -9.595485e-003, -1.106582e-002, -1.253188e-002, -1.394419e-002, -1.520342e-002, -1.642875e-002, -1.756485e-002, + -1.876650e-002, -1.982803e-002, -2.077306e-002, -2.160304e-002, -2.234521e-002, -2.313108e-002, -2.382931e-002, -2.448592e-002, -2.504228e-002, + -2.553018e-002, -2.606546e-002, -2.660545e-002, -2.706670e-002, -2.741439e-002, -2.767432e-002, -2.801954e-002, -2.831347e-002, -2.861195e-002, + -2.887123e-002, -2.905667e-002, -2.911488e-002, -2.920747e-002, -2.926428e-002, -2.934518e-002, -2.945847e-002, -2.944722e-002, -2.939849e-002, + -2.937279e-002, -2.928941e-002, -2.914309e-002, -2.899382e-002, -2.901849e-002, -2.898158e-002, -2.890917e-002, -2.873517e-002, -2.853805e-002, + -2.831494e-002, -2.809450e-002, -2.802744e-002, -2.796440e-002, -2.783785e-002, -2.766449e-002, -2.748631e-002, -2.720883e-002, -2.697823e-002, + -2.691683e-002, -2.689137e-002, -2.686864e-002, -2.682734e-002, -2.667790e-002, -2.654989e-002, -2.640144e-002, -2.616097e-002, -2.600905e-002, + -2.580411e-002, -2.551551e-002, -2.526653e-002, -2.508277e-002, -2.477066e-002, -2.440104e-002, -2.401076e-002, -2.361477e-002, -2.314140e-002, + -2.265344e-002, -2.208807e-002, -2.146436e-002, -2.084392e-002, -2.004937e-002, -1.918571e-002, -1.835898e-002, -1.768489e-002, -1.696431e-002, + -1.613480e-002, -1.519525e-002, -1.418729e-002, -1.319371e-002, -1.223807e-002, -1.129099e-002, -1.027270e-002, -9.288805e-003, -8.308808e-003, + -7.309813e-003, -6.267727e-003, -5.206320e-003, -4.083539e-003, -3.084312e-003, -2.086245e-003, -1.050033e-003, 4.998547e-005, 1.066313e-003, + 2.021410e-003, 3.017337e-003, 4.026281e-003, 5.044716e-003, 6.124675e-003, 7.125214e-003, 8.115053e-003, 9.037940e-003, 1.002129e-002, + 1.095755e-002, 1.192147e-002, 1.291961e-002, 1.395327e-002, 1.501197e-002, 1.597175e-002, 1.683475e-002, 1.765669e-002, 1.855457e-002, + 1.946130e-002, 2.038072e-002, 2.122765e-002, 2.213247e-002, 2.296523e-002, 2.381279e-002, 2.462380e-002, 2.540353e-002, 2.615253e-002, + 2.682719e-002, 2.750333e-002, 2.822515e-002, 2.887570e-002, 2.940916e-002, 2.993575e-002, 3.051099e-002, 3.111243e-002, 3.161910e-002, + 3.209820e-002, 3.253353e-002, 3.279163e-002, 3.307110e-002, 3.333781e-002, 3.358343e-002, 3.384312e-002, 3.408320e-002, 3.431763e-002, + 3.453719e-002, 3.471092e-002, 3.476957e-002, 3.482974e-002, 3.488886e-002, 3.487440e-002, 3.482113e-002, 3.477919e-002, 3.474281e-002, + 3.470721e-002, 3.455085e-002, 3.435650e-002, 3.414889e-002, 3.386373e-002, 3.353237e-002, 3.318449e-002, 3.281683e-002, 3.238839e-002, + 3.186102e-002, 3.127100e-002, 3.061074e-002, 2.993174e-002, 2.919960e-002, 2.844869e-002, 2.771817e-002, 2.695492e-002, 2.618520e-002, + 2.542574e-002, 2.466143e-002, 2.379740e-002, 2.295379e-002, 2.204366e-002, 2.113896e-002, 2.022909e-002, 1.933885e-002, 1.845589e-002, + 1.759167e-002, 1.672857e-002, 1.583019e-002, 1.481065e-002, 1.376404e-002, 1.279384e-002, 1.186478e-002, 1.094294e-002, 1.001004e-002, + 9.012174e-003, 8.082517e-003, 7.169932e-003, 6.256297e-003, 5.244143e-003, 4.210156e-003, 3.173984e-003, 2.151424e-003, 1.129065e-003, + 7.185728e-005, -9.537177e-004, -1.978641e-003, -2.996118e-003, -3.948044e-003, -4.944983e-003, -6.060654e-003, -7.173239e-003, -8.263116e-003, + -9.291362e-003, -1.028351e-002, -1.125762e-002, -1.220515e-002, -1.312572e-002, -1.404869e-002, -1.498295e-002, -1.589178e-002, -1.687253e-002, + -1.787364e-002, -1.883711e-002, -1.978343e-002, -2.068520e-002, -2.153163e-002, -2.236686e-002, -2.320237e-002, -2.401354e-002, -2.479700e-002, + -2.548873e-002, -2.615126e-002, -2.685416e-002, -2.757484e-002, -2.829151e-002, -2.896108e-002, -2.956827e-002, -3.011979e-002, -3.064041e-002, + -3.115683e-002, -3.168951e-002, -3.216816e-002, -3.260952e-002, -3.298530e-002, -3.333476e-002, -3.373130e-002, -3.407092e-002, -3.435486e-002, + -3.461992e-002, -3.475103e-002, -3.483662e-002, -3.491736e-002, -3.496680e-002, -3.497289e-002, -3.496535e-002, -3.493678e-002, -3.491557e-002, + -3.492174e-002, -3.493524e-002, -3.496301e-002, -3.497345e-002, -3.487417e-002, -3.473014e-002, -3.451913e-002, -3.426984e-002, -3.402763e-002, + -3.377191e-002, -3.348873e-002, -3.317374e-002, -3.282796e-002, -3.245477e-002, -3.208893e-002, -3.170444e-002, -3.129554e-002, -3.091144e-002, + -3.055035e-002, -3.014052e-002, -2.970653e-002, -2.924689e-002, -2.877350e-002, -2.827706e-002, -2.772609e-002, -2.714500e-002, -2.645422e-002, + -2.567297e-002, -2.487842e-002, -2.406899e-002, -2.328278e-002, -2.252632e-002, -2.179114e-002, -2.107730e-002, -2.035702e-002, -1.959177e-002, + -1.879094e-002, -1.792391e-002, -1.702358e-002, -1.612899e-002, -1.524853e-002, -1.435823e-002, -1.344119e-002, -1.249419e-002, -1.152394e-002, + -1.054476e-002, -9.584596e-003, -8.620266e-003, -7.604014e-003, -6.535475e-003, -5.434275e-003, -4.338105e-003, -3.207219e-003, -2.091769e-003, + -9.785595e-004, 1.698964e-004, 1.262579e-003, 2.340592e-003, 3.446993e-003, 4.561649e-003, 5.696582e-003, 6.855829e-003, 8.053227e-003, + 9.199333e-003, 1.032752e-002, 1.140867e-002, 1.234371e-002, 1.330707e-002, 1.428309e-002, 1.526401e-002, 1.625672e-002, 1.723995e-002, + 1.825994e-002, 1.929007e-002, 2.033209e-002, 2.140126e-002, 2.221302e-002, 2.293528e-002, 2.359944e-002, 2.426900e-002, 2.493809e-002, + 2.555341e-002, 2.617221e-002, 2.682864e-002, 2.750845e-002, 2.817458e-002, 2.884649e-002, 2.954125e-002, 3.009369e-002, 3.042899e-002, + 3.079001e-002, 3.116041e-002, 3.155298e-002, 3.195887e-002, 3.237526e-002, 3.281738e-002, 3.328424e-002, 3.380567e-002, 3.435142e-002, + 3.490482e-002, 3.530946e-002, 3.568174e-002, 3.608347e-002, 3.641704e-002, 3.647525e-002, 3.654460e-002, 3.662770e-002, 3.670958e-002, + 3.682143e-002, 3.695622e-002, 3.711807e-002, 3.727668e-002, 3.743557e-002, 3.758555e-002, 3.771415e-002, 3.785510e-002, 3.801110e-002, + 3.816554e-002, 3.827483e-002, 3.837108e-002, 3.843883e-002, 3.841973e-002, 3.841019e-002, 3.830109e-002, 3.801662e-002, 3.771771e-002, + 3.738390e-002, 3.702411e-002, 3.661568e-002, 3.618336e-002, 3.575402e-002, 3.532903e-002, 3.489354e-002, 3.446403e-002, 3.404808e-002, + 3.361245e-002, 3.317832e-002, 3.275886e-002, 3.234730e-002, 3.196426e-002, 3.157168e-002, 3.114090e-002, 3.064398e-002, 3.008919e-002, + 2.957976e-002, 2.907225e-002, 2.856804e-002, 2.805755e-002, 2.752631e-002, 2.694661e-002, 2.612992e-002, 2.530734e-002, 2.452570e-002, + 2.372374e-002, 2.292920e-002, 2.213835e-002, 2.135455e-002, 2.052935e-002, 1.970821e-002, 1.890817e-002, 1.813941e-002, 1.741154e-002, + 1.669089e-002, 1.597296e-002, 1.528024e-002, 1.456072e-002, 1.383897e-002, 1.317138e-002, 1.255615e-002, 1.196954e-002, 1.140584e-002, + 1.083495e-002, 1.026730e-002, 9.715342e-003, 9.196408e-003, 8.719287e-003, 8.180861e-003, 7.649272e-003, 7.120772e-003, 6.596352e-003, + 6.082540e-003, 5.603839e-003, 5.140709e-003, 4.708265e-003, 4.294533e-003, 3.880151e-003, 3.321137e-003, 2.743674e-003, 2.157028e-003, + 1.568430e-003, 9.792462e-004, 3.859348e-004, -2.069354e-004, -8.029176e-004, -1.412156e-003, -1.998078e-003, -2.569707e-003, -3.126044e-003, + -3.671261e-003, -4.197133e-003, -4.703609e-003, -5.169262e-003, -5.612812e-003, -6.041460e-003, -6.525097e-003, -7.024805e-003, -7.508268e-003, + -7.966510e-003, -8.410419e-003, -8.841791e-003, -9.272304e-003, -9.702981e-003, -1.012522e-002, -1.054875e-002, -1.096610e-002, -1.136526e-002, + -1.176295e-002, -1.218526e-002, -1.261010e-002, -1.304709e-002, -1.343999e-002, -1.380190e-002, -1.415720e-002, -1.450148e-002, -1.481715e-002, + -1.511271e-002, -1.539437e-002, -1.567916e-002, -1.594710e-002, -1.621766e-002, -1.649069e-002, -1.677384e-002, -1.706291e-002, -1.735078e-002, + -1.778201e-002, -1.827519e-002, -1.877452e-002, -1.926988e-002, -1.975993e-002, -2.023905e-002, -2.070333e-002, -2.115199e-002, -2.159680e-002, + -2.201455e-002, -2.245062e-002, -2.293145e-002, -2.341226e-002, -2.390530e-002, -2.439455e-002, -2.489573e-002, -2.538210e-002, -2.585069e-002, + -2.631210e-002, -2.676656e-002, -2.721564e-002, -2.764699e-002, -2.804987e-002, -2.843253e-002, -2.879446e-002, -2.914842e-002, -2.948872e-002, + -2.981655e-002, -3.011373e-002, -3.039260e-002, -3.063766e-002, -3.084019e-002, -3.105178e-002, -3.124592e-002, -3.140965e-002, -3.152888e-002, + -3.162988e-002, -3.171277e-002, -3.178741e-002, -3.185242e-002, -3.188986e-002, -3.193901e-002, -3.197612e-002, -3.201158e-002, -3.203229e-002, + -3.204133e-002, -3.204453e-002, -3.203612e-002, -3.199997e-002, -3.197920e-002, -3.193995e-002, -3.188434e-002, -3.180956e-002, -3.173601e-002, + -3.166591e-002, -3.159756e-002, -3.152534e-002, -3.142901e-002, -3.133299e-002, -3.122326e-002, -3.109537e-002, -3.094049e-002, -3.076455e-002, + -3.057683e-002, -3.036474e-002, -3.013159e-002, -2.995462e-002, -2.975585e-002, -2.954206e-002, -2.931729e-002, -2.907734e-002, -2.882566e-002, + -2.856925e-002, -2.836073e-002, -2.815740e-002, -2.792169e-002, -2.764472e-002, -2.734216e-002, -2.703747e-002, -2.672691e-002, -2.640106e-002, + -2.605751e-002, -2.571478e-002, -2.537412e-002, -2.502843e-002, -2.465196e-002, -2.426497e-002, -2.389776e-002, -2.352990e-002, -2.313097e-002, + -2.272339e-002, -2.230516e-002, -2.188208e-002, -2.143910e-002, -2.100070e-002, -2.057200e-002, -2.014454e-002, -1.970938e-002, -1.927460e-002, + -1.884924e-002, -1.841703e-002, -1.796012e-002, -1.749917e-002, -1.699678e-002, -1.652079e-002, -1.605484e-002, -1.558911e-002, -1.516947e-002, + -1.474583e-002, -1.431388e-002, -1.387555e-002, -1.343679e-002, -1.297288e-002, -1.245838e-002, -1.196012e-002, -1.151270e-002, -1.106075e-002, + -1.060709e-002, -1.015104e-002, -9.682566e-003, -9.212216e-003, -8.731728e-003, -8.208278e-003, -7.643232e-003, -7.087355e-003, -6.578601e-003, + -6.048960e-003, -5.477234e-003, -4.897702e-003, -4.299889e-003, -3.662780e-003, -3.001082e-003, -2.297310e-003, -1.584170e-003, -8.516245e-004, + -1.952714e-004, 4.833759e-004, 1.183689e-003, 1.901444e-003, 2.626945e-003, 3.356069e-003, 4.121274e-003, 4.924593e-003, 5.701597e-003, + 6.462709e-003, 7.150691e-003, 7.854372e-003, 8.555725e-003, 9.263497e-003, 9.983510e-003, 1.070692e-002, 1.144402e-002, 1.217113e-002, + 1.284441e-002, 1.353203e-002, 1.415597e-002, 1.473274e-002, 1.531914e-002, 1.592020e-002, 1.653393e-002, 1.716095e-002, 1.779465e-002, + 1.842422e-002, 1.903823e-002, 1.967016e-002, 2.027178e-002, 2.078086e-002, 2.131783e-002, 2.188602e-002, 2.246889e-002, 2.306096e-002, + 2.366982e-002, 2.425589e-002, 2.485314e-002, 2.546142e-002, 2.606522e-002, 2.651181e-002, 2.697275e-002, 2.744145e-002, 2.792537e-002, + 2.838825e-002, 2.886726e-002, 2.933813e-002, 2.978398e-002, 3.023047e-002, 3.068084e-002, 3.097320e-002, 3.124851e-002, 3.151979e-002, + 3.179491e-002, 3.208364e-002, 3.238522e-002, 3.270960e-002, 3.304227e-002, 3.338576e-002, 3.376693e-002, 3.403786e-002, 3.424990e-002, + 3.448659e-002, 3.475116e-002, 3.505739e-002, 3.539451e-002, 3.575027e-002, 3.611933e-002, 3.649073e-002, 3.688173e-002, 3.719951e-002, + 3.738622e-002, 3.759411e-002, 3.784242e-002, 3.810564e-002, 3.838660e-002, 3.868005e-002, 3.898292e-002, 3.924536e-002, 3.950280e-002, + 3.972558e-002, 3.973594e-002, 3.975274e-002, 3.978129e-002, 3.981270e-002, 3.984607e-002, 3.987135e-002, 3.986742e-002, 3.983345e-002, + 3.977924e-002, 3.972687e-002, 3.939447e-002, 3.903504e-002, 3.868324e-002, 3.833726e-002, 3.799708e-002, 3.766243e-002, 3.733177e-002, + 3.699997e-002, 3.663235e-002, 3.624944e-002, 3.564720e-002, 3.496800e-002, 3.429306e-002, 3.361142e-002, 3.290150e-002, 3.219412e-002, + 3.149192e-002, 3.079306e-002, 3.008957e-002, 2.930615e-002, 2.835259e-002, 2.721108e-002, 2.606995e-002, 2.493019e-002, 2.379139e-002, + 2.265073e-002, 2.153695e-002, 2.038014e-002, 1.920723e-002, 1.802808e-002, 1.663728e-002, 1.495627e-002, 1.327881e-002, 1.160589e-002, + 9.935733e-003, 8.270407e-003, 6.608634e-003, 4.951101e-003, 3.297583e-003, 1.647433e-003, -6.357235e-017}, + {0.000000e+000, 4.681131e-002, 3.448546e-002, 2.787317e-002, 9.077404e-003, -1.252344e-002, -1.981374e-002, -2.022446e-002, -1.977085e-002, + -1.690704e-002, -1.085543e-002, -3.675414e-003, 3.000490e-003, 6.717629e-003, 5.669027e-003, 2.072485e-003, -5.012936e-004, -1.170948e-003, + -2.397314e-004, -7.352893e-005, -1.721719e-003, -5.648954e-003, -8.984094e-003, -1.196890e-002, -1.556017e-002, -1.883280e-002, -2.117636e-002, + -2.178414e-002, -2.045620e-002, -1.774205e-002, -1.351744e-002, -8.063788e-003, -1.939619e-003, 4.290677e-003, 1.058171e-002, 1.655834e-002, + 2.145392e-002, 2.635716e-002, 3.162410e-002, 3.651302e-002, 4.110853e-002, 4.530762e-002, 4.825638e-002, 5.006986e-002, 5.095183e-002, + 5.020408e-002, 4.785951e-002, 4.428271e-002, 3.991452e-002, 3.477011e-002, 2.873794e-002, 2.134703e-002, 1.273631e-002, 3.663851e-003, + -6.029854e-003, -1.575807e-002, -2.491389e-002, -3.361578e-002, -4.141220e-002, -4.798057e-002, -5.285234e-002, -5.599161e-002, -5.772764e-002, + -5.855767e-002, -5.854105e-002, -5.801915e-002, -5.695607e-002, -5.533240e-002, -5.323196e-002, -5.069518e-002, -4.795854e-002, -4.473753e-002, + -4.117124e-002, -3.724427e-002, -3.321496e-002, -2.912680e-002, -2.482910e-002, -2.033936e-002, -1.569094e-002, -1.062176e-002, -5.170073e-003, + 3.437857e-004, 5.848103e-003, 1.125088e-002, 1.656631e-002, 2.169067e-002, 2.633630e-002, 3.086059e-002, 3.502068e-002, 3.873037e-002, + 4.183588e-002, 4.451947e-002, 4.678120e-002, 4.859437e-002, 4.970678e-002, 5.023995e-002, 5.038301e-002, 4.983822e-002, 4.855151e-002, + 4.718407e-002, 4.576427e-002, 4.390175e-002, 4.157071e-002, 3.880250e-002, 3.576013e-002, 3.229183e-002, 2.847238e-002, 2.440965e-002, + 2.012665e-002, 1.559214e-002, 1.074161e-002, 5.951684e-003, 1.151384e-003, -3.585619e-003, -8.887757e-003, -1.384535e-002, -1.852657e-002, + -2.288487e-002, -2.716061e-002, -3.125988e-002, -3.473129e-002, -3.773333e-002, -4.020690e-002, -4.265230e-002, -4.493803e-002, -4.666470e-002, + -4.805004e-002, -4.896643e-002, -4.989895e-002, -5.036629e-002, -5.021192e-002, -4.963145e-002, -4.856864e-002, -4.738171e-002, -4.572717e-002, + -4.331828e-002, -4.020556e-002, -3.658248e-002, -3.273865e-002, -2.844455e-002, -2.379064e-002, -1.869489e-002, -1.358453e-002, -8.443050e-003, + -3.364645e-003, 1.860701e-003, 7.123102e-003, 1.238238e-002, 1.757942e-002, 2.234447e-002, 2.642148e-002, 3.020768e-002, 3.373309e-002, + 3.683327e-002, 3.981301e-002, 4.254222e-002, 4.469727e-002, 4.665348e-002, 4.855023e-002, 5.049459e-002, 5.250306e-002, 5.419849e-002, + 5.525308e-002, 5.609343e-002, 5.680514e-002, 5.758628e-002, 5.837509e-002, 5.863108e-002, 5.836131e-002, 5.798997e-002, 5.741974e-002, + 5.695278e-002, 5.583392e-002, 5.458059e-002, 5.316203e-002, 5.172504e-002, 4.972813e-002, 4.758693e-002, 4.553395e-002, 4.351386e-002, + 4.071082e-002, 3.784024e-002, 3.500919e-002, 3.172657e-002, 2.824462e-002, 2.487347e-002, 2.106127e-002, 1.698121e-002, 1.299358e-002, + 8.379497e-003, 3.935987e-003, -6.608164e-004, -5.529345e-003, -1.019073e-002, -1.535437e-002, -2.023319e-002, -2.546930e-002, -3.047127e-002, + -3.571817e-002, -4.071906e-002, -4.573477e-002, -5.051431e-002, -5.516725e-002, -5.948015e-002, -6.348838e-002, -6.729707e-002, -7.065566e-002, + -7.376872e-002, -7.635738e-002, -7.874548e-002, -8.043464e-002, -8.203416e-002, -8.298526e-002, -8.381424e-002, -8.403609e-002, -8.399522e-002, + -8.362292e-002, -8.280955e-002, -8.193264e-002, -8.045636e-002, -7.892246e-002, -7.713694e-002, -7.494625e-002, -7.286040e-002, -7.045504e-002, + -6.778598e-002, -6.515759e-002, -6.216961e-002, -5.912094e-002, -5.610186e-002, -5.278094e-002, -4.936363e-002, -4.598159e-002, -4.242170e-002, + -3.876846e-002, -3.519103e-002, -3.159487e-002, -2.771813e-002, -2.388344e-002, -2.015988e-002, -1.638076e-002, -1.255269e-002, -8.791596e-003, + -5.133212e-003, -1.274546e-003, 2.479637e-003, 6.032230e-003, 9.420152e-003, 1.288947e-002, 1.619042e-002, 1.927861e-002, 2.216957e-002, + 2.501808e-002, 2.770244e-002, 3.024260e-002, 3.260544e-002, 3.508378e-002, 3.740506e-002, 3.952824e-002, 4.145633e-002, 4.331550e-002, + 4.507710e-002, 4.664530e-002, 4.804002e-002, 4.922756e-002, 5.037657e-002, 5.138086e-002, 5.223361e-002, 5.295540e-002, 5.367978e-002, + 5.436264e-002, 5.489276e-002, 5.520533e-002, 5.531303e-002, 5.539436e-002, 5.543604e-002, 5.532854e-002, 5.507301e-002, 5.464520e-002, + 5.415568e-002, 5.354191e-002, 5.279835e-002, 5.193196e-002, 5.097215e-002, 5.002294e-002, 4.900709e-002, 4.786388e-002, 4.653563e-002, + 4.507678e-002, 4.355459e-002, 4.199924e-002, 4.037431e-002, 3.864483e-002, 3.679183e-002, 3.490835e-002, 3.300271e-002, 3.090309e-002, + 2.876163e-002, 2.660743e-002, 2.444052e-002, 2.240324e-002, 2.038992e-002, 1.831627e-002, 1.609441e-002, 1.387756e-002, 1.174006e-002, + 9.751399e-003, 7.766189e-003, 5.844567e-003, 3.982798e-003, 2.117273e-003, 3.151913e-004, -1.360628e-003, -2.939121e-003, -4.451436e-003, + -5.858844e-003, -7.168514e-003, -8.499262e-003, -9.705243e-003, -1.081210e-002, -1.189051e-002, -1.291298e-002, -1.387140e-002, -1.465477e-002, + -1.550300e-002, -1.637613e-002, -1.719256e-002, -1.803177e-002, -1.888324e-002, -1.968082e-002, -2.042611e-002, -2.116963e-002, -2.207949e-002, + -2.299855e-002, -2.388119e-002, -2.467255e-002, -2.531417e-002, -2.592913e-002, -2.644008e-002, -2.682552e-002, -2.718591e-002, -2.754750e-002, + -2.765186e-002, -2.765523e-002, -2.752406e-002, -2.726710e-002, -2.688072e-002, -2.640137e-002, -2.570285e-002, -2.515455e-002, -2.455274e-002, + -2.390108e-002, -2.319054e-002, -2.239094e-002, -2.154277e-002, -2.055979e-002, -1.945161e-002, -1.830144e-002, -1.730625e-002, -1.635173e-002, + -1.538551e-002, -1.442422e-002, -1.337190e-002, -1.235285e-002, -1.128799e-002, -1.006423e-002, -8.741049e-003, -7.472599e-003, -6.401690e-003, + -5.221186e-003, -3.989571e-003, -2.756025e-003, -1.520108e-003, -2.012723e-004, 1.237720e-003, 2.688091e-003, 4.202303e-003, 5.734698e-003, + 7.020855e-003, 8.318617e-003, 9.573227e-003, 1.089393e-002, 1.223390e-002, 1.362433e-002, 1.507834e-002, 1.652139e-002, 1.792093e-002, + 1.924950e-002, 2.042536e-002, 2.143528e-002, 2.239416e-002, 2.339400e-002, 2.438456e-002, 2.532135e-002, 2.616545e-002, 2.698230e-002, + 2.770054e-002, 2.833202e-002, 2.894373e-002, 2.958028e-002, 2.991290e-002, 3.012259e-002, 3.028503e-002, 3.033722e-002, 3.030585e-002, + 3.019283e-002, 3.007496e-002, 3.003720e-002, 2.999292e-002, 2.996758e-002, 2.993461e-002, 2.980423e-002, 2.955674e-002, 2.930377e-002, + 2.897844e-002, 2.865083e-002, 2.848003e-002, 2.830830e-002, 2.813526e-002, 2.792500e-002, 2.766062e-002, 2.730710e-002, 2.693337e-002, + 2.659270e-002, 2.614234e-002, 2.564160e-002, 2.515083e-002, 2.461619e-002, 2.395083e-002, 2.308640e-002, 2.211561e-002, 2.107071e-002, + 1.996197e-002, 1.886544e-002, 1.785440e-002, 1.676589e-002, 1.567677e-002, 1.454009e-002, 1.342230e-002, 1.226866e-002, 1.104708e-002, + 9.824566e-003, 8.595248e-003, 7.422231e-003, 6.177077e-003, 4.908154e-003, 3.637750e-003, 2.406062e-003, 1.150715e-003, -1.269682e-004, + -1.485081e-003, -2.869399e-003, -4.230416e-003, -5.489638e-003, -6.794520e-003, -8.186402e-003, -9.585763e-003, -1.098258e-002, -1.231384e-002, + -1.370129e-002, -1.516476e-002, -1.662410e-002, -1.813693e-002, -1.958581e-002, -2.108534e-002, -2.263624e-002, -2.422710e-002, -2.581051e-002, + -2.737335e-002, -2.892406e-002, -3.039598e-002, -3.186074e-002, -3.332416e-002, -3.478130e-002, -3.614510e-002, -3.751609e-002, -3.885534e-002, + -4.018989e-002, -4.152692e-002, -4.283544e-002, -4.410973e-002, -4.532101e-002, -4.642662e-002, -4.745378e-002, -4.839035e-002, -4.923504e-002, + -4.999617e-002, -5.073088e-002, -5.137246e-002, -5.192545e-002, -5.248106e-002, -5.302770e-002, -5.353326e-002, -5.397520e-002, -5.435785e-002, + -5.470494e-002, -5.502279e-002, -5.518973e-002, -5.527213e-002, -5.528006e-002, -5.526554e-002, -5.516498e-002, -5.497562e-002, -5.476407e-002, + -5.450516e-002, -5.418434e-002, -5.384248e-002, -5.348794e-002, -5.309449e-002, -5.261004e-002, -5.191361e-002, -5.118071e-002, -5.048657e-002, + -4.977398e-002, -4.888613e-002, -4.794007e-002, -4.695792e-002, -4.597193e-002, -4.497811e-002, -4.396739e-002, -4.297801e-002, -4.197763e-002, + -4.098473e-002, -3.996192e-002, -3.891484e-002, -3.788558e-002, -3.667123e-002, -3.541216e-002, -3.419372e-002, -3.303450e-002, -3.191623e-002, + -3.079176e-002, -2.967039e-002, -2.858740e-002, -2.754537e-002, -2.657461e-002, -2.561912e-002, -2.452975e-002, -2.326314e-002, -2.199472e-002, + -2.072116e-002, -1.944397e-002, -1.814401e-002, -1.681423e-002, -1.546728e-002, -1.406499e-002, -1.265142e-002, -1.128272e-002, -9.781957e-003, + -8.171467e-003, -6.557077e-003, -4.975358e-003, -3.376001e-003, -1.754926e-003, -1.140562e-004, 1.509670e-003, 3.113203e-003, 4.681309e-003, + 6.329369e-003, 8.145204e-003, 9.908321e-003, 1.162172e-002, 1.324871e-002, 1.482348e-002, 1.635826e-002, 1.785242e-002, 1.928703e-002, + 2.069971e-002, 2.223961e-002, 2.387469e-002, 2.546514e-002, 2.701919e-002, 2.850560e-002, 2.993761e-002, 3.138086e-002, 3.283292e-002, + 3.426880e-002, 3.571753e-002, 3.709773e-002, 3.850995e-002, 3.991266e-002, 4.131552e-002, 4.272713e-002, 4.417610e-002, 4.567907e-002, + 4.713312e-002, 4.858780e-002, 4.994958e-002, 5.099983e-002, 5.202061e-002, 5.303529e-002, 5.402985e-002, 5.501470e-002, 5.601541e-002, + 5.697689e-002, 5.788245e-002, 5.878455e-002, 5.960431e-002, 5.993314e-002, 6.004867e-002, 6.017560e-002, 6.025680e-002, 6.028332e-002, + 6.036600e-002, 6.047204e-002, 6.054307e-002, 6.061440e-002, 6.066773e-002, 6.067408e-002, 6.064750e-002, 6.029579e-002, 5.948691e-002, + 5.871027e-002, 5.798872e-002, 5.730511e-002, 5.662379e-002, 5.596847e-002, 5.535131e-002, 5.478482e-002, 5.418309e-002, 5.354282e-002, + 5.290137e-002, 5.234137e-002, 5.183970e-002, 5.136903e-002, 5.066515e-002, 4.934083e-002, 4.801558e-002, 4.669566e-002, 4.537542e-002, + 4.400899e-002, 4.263368e-002, 4.124278e-002, 3.983301e-002, 3.841035e-002, 3.697916e-002, 3.549100e-002, 3.397292e-002, 3.245419e-002, + 3.094846e-002, 2.942530e-002, 2.787906e-002, 2.636620e-002, 2.494316e-002, 2.354671e-002, 2.191724e-002, 1.997041e-002, 1.806422e-002, + 1.621102e-002, 1.446106e-002, 1.291231e-002, 1.143140e-002, 1.001174e-002, 8.622862e-003, 7.266216e-003, 5.909518e-003, 4.587831e-003, + 3.242925e-003, 1.913073e-003, 6.163586e-004, -5.650576e-004, -1.612659e-003, -2.639918e-003, -3.609561e-003, -4.506571e-003, -5.356361e-003, + -6.214368e-003, -7.096796e-003, -8.025984e-003, -9.000385e-003, -1.000258e-002, -1.097974e-002, -1.221216e-002, -1.331860e-002, -1.444947e-002, + -1.561844e-002, -1.687950e-002, -1.814989e-002, -1.941037e-002, -2.055378e-002, -2.170066e-002, -2.283110e-002, -2.395691e-002, -2.509341e-002, + -2.618455e-002, -2.723229e-002, -2.801128e-002, -2.853404e-002, -2.895595e-002, -2.932786e-002, -2.968468e-002, -2.999819e-002, -3.028305e-002, + -3.054678e-002, -3.077979e-002, -3.100213e-002, -3.122261e-002, -3.151470e-002, -3.172709e-002, -3.190430e-002, -3.204822e-002, -3.217858e-002, + -3.222945e-002, -3.218066e-002, -3.191587e-002, -3.156741e-002, -3.123162e-002, -3.102675e-002, -3.111331e-002, -3.125889e-002, -3.142315e-002, + -3.160778e-002, -3.177897e-002, -3.192405e-002, -3.203485e-002, -3.212855e-002, -3.218406e-002, -3.220427e-002, -3.218557e-002, -3.216532e-002, + -3.215987e-002, -3.217860e-002, -3.200768e-002, -3.171497e-002, -3.139800e-002, -3.107310e-002, -3.069901e-002, -3.017591e-002, -2.955571e-002, + -2.893570e-002, -2.830517e-002, -2.767579e-002, -2.702912e-002, -2.638762e-002, -2.575170e-002, -2.514445e-002, -2.455085e-002, -2.401040e-002, + -2.345075e-002, -2.286864e-002, -2.230433e-002, -2.174911e-002, -2.118840e-002, -2.062891e-002, -2.006041e-002, -1.948396e-002, -1.885741e-002, + -1.815325e-002, -1.744793e-002, -1.671256e-002, -1.596254e-002, -1.518492e-002, -1.440409e-002, -1.360366e-002, -1.276341e-002, -1.192953e-002, + -1.131855e-002, -1.075737e-002, -1.002369e-002, -9.305102e-003, -8.616256e-003, -7.950955e-003, -7.311456e-003, -6.678224e-003, -6.041242e-003, + -5.385214e-003, -4.747592e-003, -4.080719e-003, -3.454269e-003, -2.879744e-003, -2.343424e-003, -1.879314e-003, -1.446905e-003, -1.032713e-003, + -6.463152e-004, -2.763472e-004, 1.004613e-004, 5.282436e-004, 9.870480e-004, 1.455067e-003, 1.935803e-003, 2.427716e-003, 2.917809e-003, + 3.426500e-003, 3.931207e-003, 4.440017e-003, 4.950393e-003, 5.456166e-003, 5.947855e-003, 6.439354e-003, 6.939765e-003, 7.429828e-003, + 7.900502e-003, 8.348521e-003, 8.763991e-003, 9.170761e-003, 9.576973e-003, 9.994175e-003, 1.037298e-002, 1.083292e-002, 1.126701e-002, + 1.165610e-002, 1.200309e-002, 1.231259e-002, 1.256936e-002, 1.277863e-002, 1.294129e-002, 1.304473e-002, 1.310577e-002, 1.313875e-002, + 1.315785e-002, 1.315361e-002, 1.313038e-002, 1.307920e-002, 1.295946e-002, 1.283788e-002, 1.271356e-002, 1.268945e-002, 1.272269e-002, + 1.280713e-002, 1.290529e-002, 1.295629e-002, 1.288772e-002, 1.280596e-002, 1.269083e-002, 1.255395e-002, 1.239690e-002, 1.222712e-002, + 1.205917e-002, 1.194490e-002, 1.185235e-002, 1.175429e-002, 1.162861e-002, 1.147165e-002, 1.129526e-002, 1.107945e-002, 1.081822e-002, + 1.051647e-002, 1.019372e-002, 9.856525e-003, 9.509824e-003, 9.129752e-003, 8.744128e-003, 8.371728e-003, 8.023015e-003, 7.691966e-003, + 7.391482e-003, 7.127639e-003, 6.903494e-003, 6.689683e-003, 6.493887e-003, 6.357181e-003, 6.257253e-003, 6.180738e-003, 6.219192e-003, + 6.258491e-003, 6.317628e-003, 6.409330e-003, 6.524055e-003, 6.616680e-003, 6.709712e-003, 6.805812e-003, 6.894513e-003, 7.026513e-003, + 7.145610e-003, 7.248054e-003, 7.341368e-003, 7.414935e-003, 7.410269e-003, 7.413855e-003, 7.402326e-003, 7.374186e-003, 7.320299e-003, + 7.248887e-003, 7.150171e-003, 7.035966e-003, 6.912878e-003, 6.768290e-003, 6.593352e-003, 6.397631e-003, 6.208808e-003, 6.046527e-003, + 5.895229e-003, 5.753246e-003, 5.636468e-003, 5.535089e-003, 5.430815e-003, 5.316514e-003, 5.203072e-003, 5.076031e-003, 4.947930e-003, + 4.858131e-003, 4.762396e-003, 4.663724e-003, 4.563925e-003, 4.459467e-003, 4.345069e-003, 4.217650e-003, 4.068917e-003, 3.934200e-003, + 3.804770e-003, 3.696061e-003, 3.609597e-003, 3.501340e-003, 3.383188e-003, 3.251549e-003, 3.140899e-003, 3.032227e-003, 2.881046e-003, + 2.619986e-003, 2.350280e-003, 2.104944e-003, 1.873185e-003, 1.630876e-003, 1.369334e-003, 1.105577e-003, 8.510127e-004, 5.952370e-004, + 3.385831e-004, 7.613545e-005, -1.933397e-004, -4.570994e-004, -6.873165e-004, -9.421861e-004, -1.240337e-003, -1.545867e-003, -1.854179e-003, + -2.132665e-003, -2.397875e-003, -2.687182e-003, -3.000602e-003, -3.331590e-003, -3.607402e-003, -3.912631e-003, -4.228442e-003, -4.552572e-003, + -4.847655e-003, -5.128624e-003, -5.387860e-003, -5.629705e-003, -5.868755e-003, -6.102562e-003, -6.256287e-003, -6.404354e-003, -6.538640e-003, + -6.654996e-003, -6.759347e-003, -6.844263e-003, -6.931231e-003, -7.024069e-003, -7.124276e-003, -7.238232e-003, -7.307320e-003, -7.359717e-003, + -7.417052e-003, -7.477403e-003, -7.537770e-003, -7.577184e-003, -7.619149e-003, -7.685883e-003, -7.766367e-003, -7.867068e-003, -7.951303e-003, + -7.998447e-003, -8.066885e-003, -8.163488e-003, -8.280622e-003, -8.423789e-003, -8.594658e-003, -8.810135e-003, -9.059259e-003, -9.338173e-003, + -9.639212e-003, -9.877399e-003, -1.011859e-002, -1.036583e-002, -1.061711e-002, -1.086555e-002, -1.110966e-002, -1.132911e-002, -1.150975e-002, + -1.168918e-002, -1.185982e-002, -1.189004e-002, -1.188348e-002, -1.184896e-002, -1.178765e-002, -1.170955e-002, -1.162512e-002, -1.154200e-002, + -1.144819e-002, -1.134496e-002, -1.125208e-002, -1.107638e-002, -1.085459e-002, -1.063878e-002, -1.042280e-002, -1.018981e-002, -9.957078e-003, + -9.724109e-003, -9.506621e-003, -9.299984e-003, -9.105711e-003, -8.882068e-003, -8.624317e-003, -8.406313e-003, -8.219835e-003, -8.026353e-003, + -7.835124e-003, -7.643890e-003, -7.422261e-003, -7.180429e-003, -6.900958e-003, -6.493511e-003, -5.934865e-003, -5.357188e-003, -4.757886e-003, + -4.144439e-003, -3.509118e-003, -2.855865e-003, -2.177809e-003, -1.479250e-003, -7.512174e-004, -1.016566e-017}, + {0.000000e+000, 9.337579e-002, 8.054700e-002, 6.256344e-002, 3.666294e-002, 8.144030e-003, -1.059384e-002, -2.663954e-002, -3.935114e-002, + -4.824101e-002, -5.237458e-002, -5.184092e-002, -4.726596e-002, -4.032197e-002, -3.534554e-002, -3.300175e-002, -3.222506e-002, -3.109026e-002, + -2.817204e-002, -2.516272e-002, -2.076238e-002, -1.440327e-002, -6.912883e-003, 1.941417e-003, 1.157612e-002, 2.126827e-002, 3.039692e-002, + 3.774032e-002, 4.245606e-002, 4.444934e-002, 4.460454e-002, 4.343607e-002, 4.107697e-002, 3.760178e-002, 3.386266e-002, 2.996247e-002, + 2.615551e-002, 2.180640e-002, 1.697367e-002, 1.177458e-002, 6.244900e-003, 5.070997e-004, -5.046233e-003, -1.037382e-002, -1.509954e-002, + -1.909733e-002, -2.221596e-002, -2.381300e-002, -2.486913e-002, -2.565004e-002, -2.611091e-002, -2.613241e-002, -2.550826e-002, -2.404751e-002, + -2.226260e-002, -2.009056e-002, -1.753187e-002, -1.486596e-002, -1.213645e-002, -9.042711e-003, -5.815066e-003, -2.922695e-003, 6.328107e-005, + 3.130231e-003, 6.150614e-003, 9.359206e-003, 1.243885e-002, 1.544246e-002, 1.827674e-002, 2.096147e-002, 2.357433e-002, 2.594095e-002, + 2.801248e-002, 2.966496e-002, 3.101287e-002, 3.224910e-002, 3.311050e-002, 3.338042e-002, 3.301061e-002, 3.168125e-002, 2.988833e-002, + 2.758315e-002, 2.447003e-002, 2.050972e-002, 1.628483e-002, 1.147938e-002, 6.069612e-003, 4.319296e-004, -5.188078e-003, -1.082200e-002, + -1.641430e-002, -2.201648e-002, -2.710552e-002, -3.195749e-002, -3.649277e-002, -4.012370e-002, -4.320136e-002, -4.551116e-002, -4.667907e-002, + -4.724751e-002, -4.716803e-002, -4.660996e-002, -4.558051e-002, -4.405821e-002, -4.220763e-002, -3.997135e-002, -3.723699e-002, -3.432818e-002, + -3.108256e-002, -2.769270e-002, -2.381317e-002, -1.993181e-002, -1.601529e-002, -1.215511e-002, -7.974437e-003, -3.711301e-003, 5.040333e-004, + 4.524147e-003, 8.259167e-003, 1.191973e-002, 1.532632e-002, 1.848794e-002, 2.125973e-002, 2.375283e-002, 2.629281e-002, 2.892457e-002, + 3.134179e-002, 3.340889e-002, 3.515550e-002, 3.656751e-002, 3.787512e-002, 3.893071e-002, 3.961539e-002, 3.981786e-002, 3.990924e-002, + 3.939848e-002, 3.859373e-002, 3.751452e-002, 3.588884e-002, 3.405013e-002, 3.197804e-002, 2.972880e-002, 2.759941e-002, 2.546564e-002, + 2.339918e-002, 2.093479e-002, 1.839426e-002, 1.594785e-002, 1.330018e-002, 1.090515e-002, 8.845472e-003, 6.767977e-003, 4.753374e-003, + 2.727377e-003, 9.251994e-004, -1.073709e-003, -2.956624e-003, -4.900178e-003, -7.063213e-003, -9.318317e-003, -1.150778e-002, -1.378903e-002, + -1.568296e-002, -1.766367e-002, -1.971595e-002, -2.159365e-002, -2.352753e-002, -2.530709e-002, -2.683944e-002, -2.855413e-002, -3.043345e-002, + -3.230536e-002, -3.363735e-002, -3.500978e-002, -3.638806e-002, -3.793768e-002, -3.901289e-002, -3.971353e-002, -4.039272e-002, -4.122470e-002, + -4.127548e-002, -4.140999e-002, -4.167933e-002, -4.128496e-002, -4.069180e-002, -4.036119e-002, -3.946187e-002, -3.827728e-002, -3.708067e-002, + -3.509397e-002, -3.299071e-002, -3.067334e-002, -2.770009e-002, -2.467655e-002, -2.094021e-002, -1.728392e-002, -1.298017e-002, -8.677546e-003, + -4.119176e-003, 3.914347e-004, 4.987787e-003, 9.541484e-003, 1.406785e-002, 1.869771e-002, 2.319567e-002, 2.750408e-002, 3.160078e-002, + 3.547548e-002, 3.906471e-002, 4.249579e-002, 4.547527e-002, 4.836086e-002, 5.055178e-002, 5.268234e-002, 5.420818e-002, 5.567213e-002, + 5.675608e-002, 5.751439e-002, 5.827158e-002, 5.826996e-002, 5.820207e-002, 5.781884e-002, 5.704479e-002, 5.616921e-002, 5.498818e-002, + 5.350800e-002, 5.202346e-002, 5.009597e-002, 4.806783e-002, 4.599423e-002, 4.364423e-002, 4.113364e-002, 3.866013e-002, 3.609612e-002, + 3.335584e-002, 3.066471e-002, 2.806307e-002, 2.520214e-002, 2.247422e-002, 1.991643e-002, 1.722360e-002, 1.451583e-002, 1.193606e-002, + 9.520540e-003, 6.953139e-003, 4.558911e-003, 2.287653e-003, 2.052085e-004, -2.048454e-003, -4.205553e-003, -6.180249e-003, -8.077998e-003, + -9.991269e-003, -1.182419e-002, -1.354970e-002, -1.522469e-002, -1.701507e-002, -1.876009e-002, -2.043974e-002, -2.204278e-002, -2.365799e-002, + -2.528322e-002, -2.681548e-002, -2.829378e-002, -2.978555e-002, -3.129256e-002, -3.265782e-002, -3.381694e-002, -3.482830e-002, -3.577425e-002, + -3.655835e-002, -3.712273e-002, -3.751840e-002, -3.772809e-002, -3.777965e-002, -3.771771e-002, -3.748561e-002, -3.714185e-002, -3.671973e-002, + -3.638967e-002, -3.592087e-002, -3.524906e-002, -3.453363e-002, -3.367208e-002, -3.282128e-002, -3.192228e-002, -3.094828e-002, -2.996387e-002, + -2.895276e-002, -2.798544e-002, -2.699783e-002, -2.598904e-002, -2.505665e-002, -2.406740e-002, -2.317733e-002, -2.238234e-002, -2.157464e-002, + -2.079896e-002, -1.995379e-002, -1.901615e-002, -1.809261e-002, -1.710185e-002, -1.611102e-002, -1.509913e-002, -1.404581e-002, -1.302115e-002, + -1.200123e-002, -1.104464e-002, -9.955152e-003, -8.730752e-003, -7.477647e-003, -6.247350e-003, -5.104490e-003, -3.869194e-003, -2.607954e-003, + -1.385911e-003, -1.641177e-004, 9.854357e-004, 2.060707e-003, 3.154591e-003, 4.187122e-003, 5.179847e-003, 6.077607e-003, 6.777136e-003, + 7.424068e-003, 8.014570e-003, 8.583143e-003, 9.165609e-003, 9.725707e-003, 1.012095e-002, 1.055190e-002, 1.093842e-002, 1.132863e-002, + 1.166970e-002, 1.200443e-002, 1.232981e-002, 1.260928e-002, 1.288811e-002, 1.319934e-002, 1.342834e-002, 1.364720e-002, 1.389613e-002, + 1.403256e-002, 1.419949e-002, 1.436808e-002, 1.450918e-002, 1.462757e-002, 1.474620e-002, 1.484067e-002, 1.488036e-002, 1.488585e-002, + 1.485840e-002, 1.482058e-002, 1.478127e-002, 1.469202e-002, 1.440663e-002, 1.410424e-002, 1.374990e-002, 1.343710e-002, 1.311401e-002, + 1.275624e-002, 1.228563e-002, 1.161193e-002, 1.090090e-002, 1.007068e-002, 9.343667e-003, 8.678377e-003, 8.033764e-003, 7.440297e-003, + 6.761212e-003, 6.026261e-003, 5.338306e-003, 4.729615e-003, 4.308365e-003, 3.823137e-003, 3.389233e-003, 2.861451e-003, 2.335036e-003, + 1.829381e-003, 1.309549e-003, 7.877082e-004, 3.416839e-004, -1.170449e-004, -6.148117e-004, -1.155524e-003, -1.688575e-003, -2.237354e-003, + -2.823631e-003, -3.452555e-003, -4.052090e-003, -4.615889e-003, -5.217007e-003, -5.784518e-003, -6.373255e-003, -7.041528e-003, -7.614589e-003, + -8.177274e-003, -8.712735e-003, -9.077423e-003, -9.461381e-003, -9.785105e-003, -1.004130e-002, -1.020022e-002, -1.029365e-002, -1.033404e-002, + -1.023723e-002, -9.933239e-003, -9.599172e-003, -9.315980e-003, -9.005085e-003, -8.640219e-003, -8.236887e-003, -7.734047e-003, -7.192223e-003, + -6.675314e-003, -6.214613e-003, -5.635462e-003, -4.983051e-003, -4.247794e-003, -3.557568e-003, -2.866000e-003, -2.102296e-003, -1.298655e-003, + -4.638119e-004, 3.587447e-004, 1.159503e-003, 2.088161e-003, 2.984260e-003, 3.770795e-003, 4.504367e-003, 5.192513e-003, 5.833605e-003, + 6.431160e-003, 6.987493e-003, 7.534671e-003, 8.059066e-003, 8.699260e-003, 9.342952e-003, 9.986501e-003, 1.066711e-002, 1.132237e-002, + 1.199838e-002, 1.260538e-002, 1.306040e-002, 1.348763e-002, 1.382594e-002, 1.414093e-002, 1.451043e-002, 1.484075e-002, 1.509472e-002, + 1.529071e-002, 1.548732e-002, 1.561509e-002, 1.552972e-002, 1.534550e-002, 1.506998e-002, 1.477325e-002, 1.440358e-002, 1.408849e-002, + 1.366535e-002, 1.314723e-002, 1.253122e-002, 1.186016e-002, 1.103214e-002, 1.015124e-002, 9.193284e-003, 8.199210e-003, 7.176804e-003, + 6.115569e-003, 5.071359e-003, 4.173115e-003, 3.299768e-003, 2.476470e-003, 1.603802e-003, 5.976111e-004, -4.051276e-004, -1.385052e-003, + -2.347748e-003, -3.232117e-003, -4.088827e-003, -4.867963e-003, -5.591498e-003, -6.131005e-003, -6.627589e-003, -7.027709e-003, -7.474431e-003, + -7.923041e-003, -8.315020e-003, -8.613571e-003, -8.908797e-003, -9.122805e-003, -9.299557e-003, -9.409668e-003, -9.468532e-003, -9.445102e-003, + -9.323435e-003, -9.165683e-003, -9.024795e-003, -8.870623e-003, -8.762402e-003, -8.682179e-003, -8.592505e-003, -8.453258e-003, -8.263731e-003, + -8.087003e-003, -7.912042e-003, -7.736209e-003, -7.521169e-003, -7.249127e-003, -6.965512e-003, -6.762702e-003, -6.515550e-003, -6.258677e-003, + -6.013257e-003, -5.770505e-003, -5.493118e-003, -5.255936e-003, -5.034171e-003, -4.816254e-003, -4.629203e-003, -4.449963e-003, -4.336181e-003, + -4.268466e-003, -4.292624e-003, -4.391840e-003, -4.533672e-003, -4.736679e-003, -4.994259e-003, -5.228097e-003, -5.456070e-003, -5.734247e-003, + -6.046107e-003, -6.378313e-003, -6.736148e-003, -7.135370e-003, -7.632638e-003, -8.178881e-003, -8.785749e-003, -9.447093e-003, -1.012371e-002, + -1.083444e-002, -1.148722e-002, -1.206124e-002, -1.252398e-002, -1.285561e-002, -1.306116e-002, -1.313299e-002, -1.311074e-002, -1.299841e-002, + -1.280540e-002, -1.251108e-002, -1.210224e-002, -1.164463e-002, -1.119139e-002, -1.073385e-002, -1.022790e-002, -9.616787e-003, -8.907662e-003, + -8.118590e-003, -7.282146e-003, -6.398673e-003, -5.506171e-003, -4.597237e-003, -3.703928e-003, -2.829107e-003, -1.932029e-003, -9.461520e-004, + 3.753457e-005, 9.556617e-004, 1.818693e-003, 2.702970e-003, 3.613503e-003, 4.494044e-003, 5.315143e-003, 6.157584e-003, 7.025176e-003, + 7.949765e-003, 8.926645e-003, 9.954034e-003, 1.104090e-002, 1.217455e-002, 1.336638e-002, 1.460108e-002, 1.591158e-002, 1.728392e-002, + 1.855519e-002, 1.975071e-002, 2.093432e-002, 2.211945e-002, 2.334418e-002, 2.458248e-002, 2.581480e-002, 2.701267e-002, 2.815575e-002, + 2.922650e-002, 3.031731e-002, 3.140689e-002, 3.236835e-002, 3.330991e-002, 3.419446e-002, 3.499449e-002, 3.576448e-002, 3.644604e-002, + 3.699905e-002, 3.747390e-002, 3.793745e-002, 3.840063e-002, 3.881495e-002, 3.915204e-002, 3.937664e-002, 3.961970e-002, 3.988549e-002, + 4.010040e-002, 4.030473e-002, 4.051671e-002, 4.069752e-002, 4.085415e-002, 4.096553e-002, 4.102323e-002, 4.093267e-002, 4.072444e-002, + 4.047253e-002, 4.008965e-002, 3.958233e-002, 3.899244e-002, 3.831920e-002, 3.756706e-002, 3.672373e-002, 3.578440e-002, 3.472734e-002, + 3.354933e-002, 3.231533e-002, 3.099715e-002, 2.955171e-002, 2.798917e-002, 2.632923e-002, 2.458800e-002, 2.278689e-002, 2.090699e-002, + 1.898109e-002, 1.697175e-002, 1.492934e-002, 1.280709e-002, 1.057119e-002, 8.312012e-003, 6.028641e-003, 3.704349e-003, 1.368374e-003, + -9.636291e-004, -3.221179e-003, -5.194699e-003, -7.076719e-003, -8.911461e-003, -1.071179e-002, -1.249202e-002, -1.425596e-002, -1.601797e-002, + -1.774000e-002, -1.939483e-002, -2.099249e-002, -2.239130e-002, -2.357917e-002, -2.473676e-002, -2.570602e-002, -2.661983e-002, -2.753098e-002, + -2.845196e-002, -2.930966e-002, -3.015494e-002, -3.104343e-002, -3.194993e-002, -3.290752e-002, -3.361877e-002, -3.420379e-002, -3.480905e-002, + -3.549334e-002, -3.625962e-002, -3.708951e-002, -3.796340e-002, -3.869558e-002, -3.943532e-002, -4.019785e-002, -4.099971e-002, -4.181653e-002, + -4.268911e-002, -4.358275e-002, -4.417339e-002, -4.450766e-002, -4.493635e-002, -4.532211e-002, -4.566009e-002, -4.599084e-002, -4.632297e-002, + -4.665601e-002, -4.703642e-002, -4.745356e-002, -4.792047e-002, -4.839403e-002, -4.870564e-002, -4.899779e-002, -4.928525e-002, -4.959662e-002, + -4.987861e-002, -5.007376e-002, -4.998058e-002, -4.982173e-002, -4.968543e-002, -4.964423e-002, -4.963048e-002, -4.957751e-002, -4.953594e-002, + -4.950427e-002, -4.951134e-002, -4.955110e-002, -4.957089e-002, -4.955461e-002, -4.950445e-002, -4.943094e-002, -4.931222e-002, -4.917423e-002, + -4.901950e-002, -4.879597e-002, -4.833046e-002, -4.779963e-002, -4.726231e-002, -4.670867e-002, -4.615839e-002, -4.538542e-002, -4.447395e-002, + -4.353573e-002, -4.258400e-002, -4.164180e-002, -4.072973e-002, -3.983688e-002, -3.899708e-002, -3.819590e-002, -3.741894e-002, -3.659510e-002, + -3.583894e-002, -3.516010e-002, -3.451904e-002, -3.388106e-002, -3.331450e-002, -3.278897e-002, -3.228370e-002, -3.175822e-002, -3.114787e-002, + -3.043007e-002, -2.970818e-002, -2.892343e-002, -2.808010e-002, -2.716863e-002, -2.620993e-002, -2.517996e-002, -2.401188e-002, -2.280922e-002, + -2.159224e-002, -2.035964e-002, -1.885166e-002, -1.732302e-002, -1.577494e-002, -1.418543e-002, -1.255596e-002, -1.090489e-002, -9.229455e-003, + -7.559030e-003, -5.879610e-003, -4.270280e-003, -2.700607e-003, -1.172344e-003, 3.035896e-004, 1.853425e-003, 3.386585e-003, 4.889981e-003, + 6.408420e-003, 7.933404e-003, 9.464477e-003, 1.105332e-002, 1.270783e-002, 1.437190e-002, 1.603634e-002, 1.770604e-002, 1.940208e-002, + 2.107708e-002, 2.273079e-002, 2.435622e-002, 2.595365e-002, 2.754913e-002, 2.907069e-002, 3.053007e-002, 3.192599e-002, 3.322939e-002, + 3.446752e-002, 3.562821e-002, 3.670819e-002, 3.778943e-002, 3.881524e-002, 3.972464e-002, 4.057232e-002, 4.154276e-002, 4.249255e-002, + 4.333301e-002, 4.407401e-002, 4.471403e-002, 4.528109e-002, 4.575084e-002, 4.615970e-002, 4.653299e-002, 4.687983e-002, 4.719266e-002, + 4.746699e-002, 4.770137e-002, 4.790248e-002, 4.812787e-002, 4.840149e-002, 4.868858e-002, 4.899610e-002, 4.939016e-002, 4.982599e-002, + 5.028005e-002, 5.069078e-002, 5.100873e-002, 5.125548e-002, 5.143315e-002, 5.156982e-002, 5.165233e-002, 5.166331e-002, 5.161557e-002, + 5.149329e-002, 5.126921e-002, 5.100872e-002, 5.071450e-002, 5.038752e-002, 5.001719e-002, 4.958009e-002, 4.906007e-002, 4.846992e-002, + 4.778778e-002, 4.703981e-002, 4.624614e-002, 4.547481e-002, 4.470176e-002, 4.394577e-002, 4.321158e-002, 4.248757e-002, 4.180063e-002, + 4.113622e-002, 4.051122e-002, 3.993146e-002, 3.939281e-002, 3.898187e-002, 3.868661e-002, 3.841846e-002, 3.816450e-002, 3.805861e-002, + 3.797960e-002, 3.790789e-002, 3.782917e-002, 3.770065e-002, 3.755250e-002, 3.744995e-002, 3.735331e-002, 3.723614e-002, 3.703699e-002, + 3.679838e-002, 3.650905e-002, 3.614840e-002, 3.571775e-002, 3.526075e-002, 3.483020e-002, 3.442124e-002, 3.409020e-002, 3.371572e-002, + 3.329526e-002, 3.284643e-002, 3.236931e-002, 3.185238e-002, 3.129653e-002, 3.073311e-002, 3.016913e-002, 2.962126e-002, 2.916572e-002, + 2.869517e-002, 2.825708e-002, 2.780402e-002, 2.732901e-002, 2.682298e-002, 2.626712e-002, 2.566305e-002, 2.499945e-002, 2.428835e-002, + 2.372180e-002, 2.311835e-002, 2.246110e-002, 2.176425e-002, 2.104785e-002, 2.026311e-002, 1.941361e-002, 1.850893e-002, 1.753306e-002, + 1.650973e-002, 1.561526e-002, 1.469558e-002, 1.372472e-002, 1.272100e-002, 1.170618e-002, 1.068380e-002, 9.679732e-003, 8.657886e-003, + 7.651527e-003, 6.622926e-003, 5.681231e-003, 4.801839e-003, 3.873979e-003, 2.905915e-003, 1.896289e-003, 8.439484e-004, -2.630574e-004, + -1.457973e-003, -2.676914e-003, -3.964507e-003, -5.248744e-003, -6.417704e-003, -7.674556e-003, -9.026760e-003, -1.043564e-002, -1.191928e-002, + -1.341661e-002, -1.496836e-002, -1.657154e-002, -1.824421e-002, -1.995019e-002, -2.136379e-002, -2.283756e-002, -2.437045e-002, -2.595351e-002, + -2.755731e-002, -2.913923e-002, -3.074726e-002, -3.233955e-002, -3.391494e-002, -3.547784e-002, -3.672283e-002, -3.788117e-002, -3.896046e-002, + -3.997611e-002, -4.095671e-002, -4.191678e-002, -4.288731e-002, -4.386789e-002, -4.488039e-002, -4.593007e-002, -4.677068e-002, -4.748846e-002, + -4.825510e-002, -4.907650e-002, -4.996382e-002, -5.085780e-002, -5.178688e-002, -5.273962e-002, -5.370904e-002, -5.475475e-002, -5.569017e-002, + -5.640125e-002, -5.719480e-002, -5.804965e-002, -5.898991e-002, -6.001694e-002, -6.113002e-002, -6.234433e-002, -6.349029e-002, -6.466185e-002, + -6.581352e-002, -6.656992e-002, -6.738337e-002, -6.820166e-002, -6.901374e-002, -6.981737e-002, -7.061313e-002, -7.141248e-002, -7.219523e-002, + -7.291148e-002, -7.362366e-002, -7.366409e-002, -7.354246e-002, -7.336383e-002, -7.313253e-002, -7.286657e-002, -7.258232e-002, -7.228563e-002, + -7.201053e-002, -7.162843e-002, -7.120132e-002, -7.033058e-002, -6.921598e-002, -6.808678e-002, -6.689582e-002, -6.550464e-002, -6.411324e-002, + -6.274257e-002, -6.137885e-002, -6.004870e-002, -5.860752e-002, -5.687383e-002, -5.481994e-002, -5.282281e-002, -5.087249e-002, -4.893320e-002, + -4.696397e-002, -4.496778e-002, -4.270812e-002, -4.032353e-002, -3.792581e-002, -3.517678e-002, -3.177874e-002, -2.838066e-002, -2.497265e-002, + -2.153301e-002, -1.805253e-002, -1.453713e-002, -1.097515e-002, -7.373098e-003, -3.712301e-003, 1.043064e-016}, + {0.000000e+000, -2.936768e-001, -2.015949e-001, -1.220699e-001, -5.804498e-002, -5.519080e-003, 3.533058e-002, 6.731481e-002, 9.339430e-002, + 1.135458e-001, 1.279245e-001, 1.361439e-001, 1.374618e-001, 1.339212e-001, 1.257410e-001, 1.147397e-001, 1.013346e-001, 8.624868e-002, + 6.842650e-002, 5.014312e-002, 3.088195e-002, 1.082044e-002, -8.111322e-003, -2.675255e-002, -4.481194e-002, -6.168148e-002, -7.697756e-002, + -8.950938e-002, -9.937547e-002, -1.063143e-001, -1.108610e-001, -1.130846e-001, -1.137385e-001, -1.131079e-001, -1.103790e-001, -1.063397e-001, + -1.017369e-001, -9.633422e-002, -8.980022e-002, -8.223918e-002, -7.366070e-002, -6.456267e-002, -5.520378e-002, -4.477592e-002, -3.455095e-002, + -2.477141e-002, -1.444846e-002, -5.134203e-003, 3.986291e-003, 1.358703e-002, 2.267270e-002, 3.193805e-002, 4.036000e-002, 4.722075e-002, + 5.376914e-002, 5.927965e-002, 6.378966e-002, 6.772552e-002, 7.117900e-002, 7.475348e-002, 7.751042e-002, 7.978749e-002, 8.096580e-002, + 8.104600e-002, 8.078002e-002, 7.946478e-002, 7.704405e-002, 7.472491e-002, 7.213703e-002, 6.846022e-002, 6.468923e-002, 6.072799e-002, + 5.627498e-002, 5.105749e-002, 4.570861e-002, 4.156578e-002, 3.735507e-002, 3.269376e-002, 2.791665e-002, 2.311310e-002, 1.853042e-002, + 1.394646e-002, 9.929325e-003, 6.043247e-003, 2.054935e-003, -1.841048e-003, -5.745632e-003, -1.016518e-002, -1.330462e-002, -1.639998e-002, + -1.984540e-002, -2.336333e-002, -2.704136e-002, -3.027176e-002, -3.354240e-002, -3.677033e-002, -4.058446e-002, -4.484417e-002, -4.759682e-002, + -5.084941e-002, -5.429818e-002, -5.774795e-002, -6.062470e-002, -6.250695e-002, -6.459745e-002, -6.704703e-002, -6.857499e-002, -6.965070e-002, + -7.074597e-002, -7.215960e-002, -7.211470e-002, -7.192852e-002, -7.185874e-002, -7.066754e-002, -6.899936e-002, -6.736284e-002, -6.466165e-002, + -6.164115e-002, -5.857296e-002, -5.436931e-002, -5.010039e-002, -4.535452e-002, -3.988804e-002, -3.449618e-002, -2.836020e-002, -2.212858e-002, + -1.547472e-002, -8.954541e-003, -2.535012e-003, 4.128329e-003, 1.053920e-002, 1.711540e-002, 2.338534e-002, 2.924300e-002, 3.552279e-002, + 4.168130e-002, 4.740185e-002, 5.301939e-002, 5.782601e-002, 6.240031e-002, 6.657105e-002, 7.024735e-002, 7.399709e-002, 7.689109e-002, + 7.967738e-002, 8.158660e-002, 8.314143e-002, 8.457268e-002, 8.460744e-002, 8.478982e-002, 8.495112e-002, 8.412648e-002, 8.291248e-002, + 8.144217e-002, 7.923192e-002, 7.676066e-002, 7.417096e-002, 7.119955e-002, 6.825478e-002, 6.457495e-002, 6.023473e-002, 5.575412e-002, + 5.169566e-002, 4.693907e-002, 4.188323e-002, 3.685779e-002, 3.128173e-002, 2.569002e-002, 2.038308e-002, 1.483897e-002, 8.877003e-003, + 2.688030e-003, -2.741197e-003, -8.290472e-003, -1.422623e-002, -2.001177e-002, -2.543532e-002, -3.080157e-002, -3.633894e-002, -4.167820e-002, + -4.606155e-002, -5.075269e-002, -5.540333e-002, -5.924835e-002, -6.245726e-002, -6.600031e-002, -6.868650e-002, -7.084773e-002, -7.273671e-002, + -7.389852e-002, -7.487878e-002, -7.524029e-002, -7.474255e-002, -7.407999e-002, -7.259613e-002, -7.104394e-002, -6.841792e-002, -6.564814e-002, + -6.238021e-002, -5.898563e-002, -5.491038e-002, -5.065509e-002, -4.624402e-002, -4.203314e-002, -3.789913e-002, -3.363404e-002, -2.949475e-002, + -2.549986e-002, -2.178322e-002, -1.815861e-002, -1.504353e-002, -1.160480e-002, -8.864023e-003, -5.922494e-003, -3.420531e-003, -9.987396e-004, + 1.230515e-003, 3.087195e-003, 5.056426e-003, 6.588084e-003, 8.046189e-003, 9.373976e-003, 1.040771e-002, 1.174158e-002, 1.282667e-002, + 1.373572e-002, 1.468166e-002, 1.522524e-002, 1.584725e-002, 1.671026e-002, 1.724098e-002, 1.770623e-002, 1.801950e-002, 1.816463e-002, + 1.827345e-002, 1.859983e-002, 1.898470e-002, 1.888698e-002, 1.894233e-002, 1.909027e-002, 1.901929e-002, 1.891470e-002, 1.885301e-002, + 1.888202e-002, 1.860902e-002, 1.837130e-002, 1.826879e-002, 1.811034e-002, 1.765121e-002, 1.735133e-002, 1.716652e-002, 1.700131e-002, + 1.656082e-002, 1.617117e-002, 1.570243e-002, 1.545613e-002, 1.510207e-002, 1.480608e-002, 1.441838e-002, 1.410778e-002, 1.377689e-002, + 1.353286e-002, 1.343980e-002, 1.346744e-002, 1.345521e-002, 1.320992e-002, 1.306366e-002, 1.284477e-002, 1.271885e-002, 1.238879e-002, + 1.200176e-002, 1.150655e-002, 1.109537e-002, 1.083720e-002, 1.031358e-002, 9.565111e-003, 8.801930e-003, 8.079452e-003, 7.271431e-003, + 6.548226e-003, 6.026242e-003, 5.280815e-003, 4.592209e-003, 3.819072e-003, 2.880257e-003, 1.983226e-003, 1.140822e-003, 3.996909e-004, + -8.522758e-005, -6.494040e-004, -1.385765e-003, -2.153876e-003, -2.883218e-003, -3.819219e-003, -4.556017e-003, -5.188247e-003, -5.653660e-003, + -6.017518e-003, -6.380325e-003, -6.886456e-003, -7.514929e-003, -8.228115e-003, -8.785033e-003, -9.261756e-003, -9.600018e-003, -9.878417e-003, + -1.013148e-002, -1.031248e-002, -1.052252e-002, -1.085787e-002, -1.130320e-002, -1.173121e-002, -1.204879e-002, -1.233928e-002, -1.245130e-002, + -1.236927e-002, -1.221533e-002, -1.231641e-002, -1.271262e-002, -1.301432e-002, -1.307163e-002, -1.291921e-002, -1.258418e-002, -1.223215e-002, + -1.199725e-002, -1.178477e-002, -1.139687e-002, -1.105983e-002, -1.071305e-002, -1.027994e-002, -9.744510e-003, -9.218520e-003, -8.857367e-003, + -8.497478e-003, -7.943536e-003, -7.376532e-003, -6.777619e-003, -6.115300e-003, -5.627660e-003, -5.233202e-003, -4.874954e-003, -4.407527e-003, + -4.022906e-003, -3.526863e-003, -3.070908e-003, -2.408599e-003, -1.908698e-003, -1.540239e-003, -1.271974e-003, -9.739660e-004, -5.589403e-004, + -8.258355e-005, 5.415968e-004, 1.118237e-003, 1.569301e-003, 2.017566e-003, 2.445089e-003, 3.092905e-003, 3.609528e-003, 4.107092e-003, + 4.654387e-003, 5.327793e-003, 5.773269e-003, 6.292694e-003, 6.977780e-003, 7.589159e-003, 8.230404e-003, 9.010027e-003, 9.713868e-003, + 1.035827e-002, 1.075201e-002, 1.111499e-002, 1.140439e-002, 1.166118e-002, 1.198110e-002, 1.240441e-002, 1.278734e-002, 1.318441e-002, + 1.349859e-002, 1.357861e-002, 1.365388e-002, 1.380309e-002, 1.403954e-002, 1.421868e-002, 1.418233e-002, 1.411611e-002, 1.416000e-002, + 1.424730e-002, 1.409272e-002, 1.409251e-002, 1.419539e-002, 1.418713e-002, 1.421271e-002, 1.413665e-002, 1.403632e-002, 1.399163e-002, + 1.399067e-002, 1.396313e-002, 1.371654e-002, 1.333010e-002, 1.293880e-002, 1.256885e-002, 1.224894e-002, 1.199725e-002, 1.175304e-002, + 1.148493e-002, 1.119659e-002, 1.079604e-002, 1.015031e-002, 9.565293e-003, 9.019779e-003, 8.496733e-003, 8.051583e-003, 7.593983e-003, + 7.200224e-003, 6.621290e-003, 5.890945e-003, 5.285071e-003, 4.630048e-003, 3.924453e-003, 3.315777e-003, 2.738697e-003, 2.238671e-003, + 1.766340e-003, 1.187857e-003, 6.924309e-004, 2.707819e-004, -2.683250e-004, -8.025090e-004, -1.230826e-003, -1.817614e-003, -2.325287e-003, + -2.726808e-003, -3.156073e-003, -3.595694e-003, -3.975455e-003, -4.255906e-003, -4.455909e-003, -4.601438e-003, -4.721331e-003, -4.926394e-003, + -5.114726e-003, -5.453687e-003, -5.946068e-003, -6.374388e-003, -6.705998e-003, -6.979633e-003, -7.202080e-003, -7.363996e-003, -7.512350e-003, + -7.673691e-003, -7.799986e-003, -7.958815e-003, -8.296506e-003, -8.630880e-003, -9.057584e-003, -9.380145e-003, -9.623917e-003, -9.764959e-003, + -9.842687e-003, -9.841396e-003, -9.732894e-003, -9.586204e-003, -9.512504e-003, -9.428227e-003, -9.238353e-003, -8.990154e-003, -8.803221e-003, + -8.675061e-003, -8.784048e-003, -8.886083e-003, -8.957777e-003, -8.930264e-003, -8.835069e-003, -8.864927e-003, -8.827950e-003, -8.760685e-003, + -8.613337e-003, -8.493304e-003, -8.308700e-003, -8.095132e-003, -7.842690e-003, -7.608056e-003, -7.521895e-003, -7.685235e-003, -7.866583e-003, + -8.072438e-003, -8.285467e-003, -8.510732e-003, -8.618677e-003, -8.687487e-003, -8.776203e-003, -8.794228e-003, -8.749712e-003, -8.656180e-003, + -8.498556e-003, -8.311921e-003, -8.176089e-003, -8.062720e-003, -8.131976e-003, -8.257884e-003, -8.278275e-003, -8.287908e-003, -8.303266e-003, + -8.259429e-003, -8.199778e-003, -8.113108e-003, -7.985273e-003, -7.836651e-003, -7.664828e-003, -7.661636e-003, -7.691789e-003, -7.730387e-003, + -7.757622e-003, -7.661235e-003, -7.541190e-003, -7.612368e-003, -7.805112e-003, -7.983787e-003, -8.132910e-003, -8.288107e-003, -8.331002e-003, + -8.281455e-003, -8.271275e-003, -8.264693e-003, -8.275415e-003, -8.236136e-003, -8.088917e-003, -7.965373e-003, -7.827753e-003, -7.625517e-003, + -7.388793e-003, -7.107577e-003, -6.794117e-003, -6.612974e-003, -6.477065e-003, -6.242760e-003, -5.981966e-003, -5.764776e-003, -5.448761e-003, + -5.044742e-003, -4.580647e-003, -4.150102e-003, -3.751930e-003, -3.391930e-003, -3.000885e-003, -2.617643e-003, -2.231810e-003, -1.779358e-003, + -1.298472e-003, -8.489956e-004, -4.351513e-004, -1.041706e-004, 9.963910e-005, 1.274792e-004, 1.508024e-004, 1.850784e-004, 2.374298e-004, + 3.362977e-004, 5.360913e-004, 7.358193e-004, 9.807954e-004, 1.232764e-003, 1.550029e-003, 1.955373e-003, 2.402896e-003, 2.840806e-003, + 3.311111e-003, 3.769763e-003, 4.249881e-003, 4.768687e-003, 5.326996e-003, 5.924390e-003, 6.497732e-003, 6.930072e-003, 7.176890e-003, + 7.361552e-003, 7.547584e-003, 7.707608e-003, 7.854872e-003, 8.028688e-003, 8.244274e-003, 8.449570e-003, 8.607686e-003, 8.791022e-003, + 8.857488e-003, 8.906268e-003, 8.998656e-003, 9.000183e-003, 9.024599e-003, 9.095320e-003, 9.233406e-003, 9.386764e-003, 9.648181e-003, + 9.944573e-003, 1.023837e-002, 1.053789e-002, 1.072921e-002, 1.068953e-002, 1.061984e-002, 1.061898e-002, 1.060712e-002, 1.067886e-002, + 1.074872e-002, 1.085869e-002, 1.100744e-002, 1.115670e-002, 1.128521e-002, 1.140377e-002, 1.158208e-002, 1.163734e-002, 1.148630e-002, + 1.132360e-002, 1.115935e-002, 1.098737e-002, 1.082004e-002, 1.068577e-002, 1.057331e-002, 1.046538e-002, 1.037540e-002, 1.021240e-002, + 9.990249e-003, 9.671927e-003, 9.360159e-003, 9.098900e-003, 8.709533e-003, 8.166034e-003, 7.653474e-003, 7.140204e-003, 6.670485e-003, + 6.258721e-003, 5.855775e-003, 5.526621e-003, 5.285678e-003, 5.136632e-003, 5.061678e-003, 5.032726e-003, 5.007274e-003, 5.036058e-003, + 5.136255e-003, 5.269003e-003, 5.416654e-003, 5.604207e-003, 5.737814e-003, 5.906916e-003, 6.010574e-003, 5.934993e-003, 5.821985e-003, + 5.680258e-003, 5.548788e-003, 5.412331e-003, 5.255356e-003, 5.126781e-003, 5.021458e-003, 4.969456e-003, 4.924835e-003, 4.848574e-003, + 4.676046e-003, 4.482570e-003, 4.306927e-003, 4.143328e-003, 4.031528e-003, 3.957084e-003, 3.870657e-003, 3.743736e-003, 3.578103e-003, + 3.399313e-003, 3.195686e-003, 3.003193e-003, 2.831148e-003, 2.651942e-003, 2.570384e-003, 2.414776e-003, 2.207626e-003, 2.028747e-003, + 1.804843e-003, 1.437045e-003, 1.085409e-003, 7.768265e-004, 5.257356e-004, 2.832211e-004, 7.939926e-005, -8.185038e-005, -2.642934e-004, + -3.893426e-004, -4.803039e-004, -5.583388e-004, -6.912243e-004, -8.922830e-004, -1.103698e-003, -1.313241e-003, -1.539198e-003, -1.744262e-003, + -1.898197e-003, -2.000999e-003, -2.089315e-003, -2.188149e-003, -2.387017e-003, -2.549236e-003, -2.688044e-003, -2.787079e-003, -2.841859e-003, + -2.930464e-003, -3.068594e-003, -3.248214e-003, -3.437049e-003, -3.606165e-003, -3.747210e-003, -3.915627e-003, -4.083446e-003, -4.223562e-003, + -4.342259e-003, -4.500291e-003, -4.674399e-003, -4.828653e-003, -4.987135e-003, -5.080731e-003, -5.196160e-003, -5.313727e-003, -5.406999e-003, + -5.497271e-003, -5.611920e-003, -5.699726e-003, -5.774775e-003, -5.827751e-003, -5.860136e-003, -5.835875e-003, -5.812617e-003, -5.794548e-003, + -5.754962e-003, -5.696424e-003, -5.627999e-003, -5.525310e-003, -5.407427e-003, -5.340450e-003, -5.273184e-003, -5.228324e-003, -5.194937e-003, + -5.141431e-003, -5.166114e-003, -5.156070e-003, -5.140736e-003, -5.125548e-003, -5.113386e-003, -5.027214e-003, -4.929130e-003, -4.829162e-003, + -4.780176e-003, -4.767687e-003, -4.764598e-003, -4.805533e-003, -4.842525e-003, -4.877159e-003, -4.903936e-003, -4.925239e-003, -4.910348e-003, + -4.901364e-003, -4.865290e-003, -4.795979e-003, -4.682319e-003, -4.548763e-003, -4.441000e-003, -4.335957e-003, -4.195996e-003, -4.026592e-003, + -3.855323e-003, -3.679350e-003, -3.615158e-003, -3.563790e-003, -3.507993e-003, -3.400825e-003, -3.313285e-003, -3.210967e-003, -3.132612e-003, + -3.060857e-003, -2.992745e-003, -2.928459e-003, -2.873708e-003, -2.890812e-003, -2.932178e-003, -3.007318e-003, -3.126197e-003, -3.259832e-003, + -3.387900e-003, -3.523348e-003, -3.674451e-003, -3.827247e-003, -3.969389e-003, -4.095850e-003, -4.199474e-003, -4.295658e-003, -4.376171e-003, + -4.451044e-003, -4.484057e-003, -4.466344e-003, -4.433656e-003, -4.410333e-003, -4.413169e-003, -4.379511e-003, -4.319999e-003, -4.251073e-003, + -4.117601e-003, -3.926462e-003, -3.756393e-003, -3.564936e-003, -3.376022e-003, -3.148865e-003, -2.893821e-003, -2.669323e-003, -2.422560e-003, + -2.149523e-003, -1.850924e-003, -1.550038e-003, -1.295857e-003, -1.137224e-003, -1.010534e-003, -9.196924e-004, -8.734266e-004, -8.723178e-004, + -8.866582e-004, -9.112268e-004, -9.921129e-004, -1.099246e-003, -1.222787e-003, -1.362810e-003, -1.491410e-003, -1.596758e-003, -1.705382e-003, + -1.814625e-003, -1.943649e-003, -2.043062e-003, -2.134246e-003, -2.252688e-003, -2.374122e-003, -2.431223e-003, -2.439711e-003, -2.415068e-003, + -2.330210e-003, -2.182512e-003, -1.979074e-003, -1.746538e-003, -1.541191e-003, -1.402165e-003, -1.279310e-003, -1.192885e-003, -1.118719e-003, + -1.065302e-003, -1.025728e-003, -9.944830e-004, -9.799140e-004, -9.764293e-004, -9.627254e-004, -9.227947e-004, -8.766555e-004, -8.536206e-004, + -8.899056e-004, -9.019655e-004, -9.178436e-004, -9.004027e-004, -8.456344e-004, -7.583629e-004, -6.377012e-004, -4.864260e-004, -3.816787e-004, + -2.541675e-004, -9.779120e-005, 1.130500e-004, 3.961810e-004, 6.761234e-004, 9.783680e-004, 1.278447e-003, 1.638380e-003, 2.012629e-003, + 2.421799e-003, 2.842232e-003, 3.264205e-003, 3.729394e-003, 4.231021e-003, 4.765229e-003, 5.282722e-003, 5.788358e-003, 6.289456e-003, + 6.749198e-003, 7.024100e-003, 7.207942e-003, 7.334569e-003, 7.455426e-003, 7.560493e-003, 7.665876e-003, 7.734223e-003, 7.781479e-003, + 7.816486e-003, 7.813068e-003, 7.785428e-003, 7.735542e-003, 7.681516e-003, 7.602725e-003, 7.524671e-003, 7.445705e-003, 7.304258e-003, + 7.137539e-003, 6.980915e-003, 6.814667e-003, 6.628111e-003, 6.451876e-003, 6.272940e-003, 6.108483e-003, 5.931899e-003, 5.721106e-003, + 5.449523e-003, 5.169551e-003, 4.928307e-003, 4.714848e-003, 4.510251e-003, 4.303939e-003, 4.094503e-003, 3.880572e-003, 3.621729e-003, + 3.359829e-003, 3.141626e-003, 2.931855e-003, 2.740337e-003, 2.588937e-003, 2.431991e-003, 2.313353e-003, 2.210978e-003, 2.126756e-003, + 1.990951e-003, 1.822069e-003, 1.731355e-003, 1.663912e-003, 1.608721e-003, 1.581127e-003, 1.560562e-003, 1.561507e-003, 1.570561e-003, + 1.602764e-003, 1.614179e-003, 1.595199e-003, 1.595560e-003, 1.595194e-003, 1.592725e-003, 1.629488e-003, 1.653593e-003, 1.649492e-003, + 1.604501e-003, 1.520757e-003, 1.413421e-003, 1.295971e-003, 1.182100e-003, 1.073205e-003, 9.057418e-004, 7.393787e-004, 5.609143e-004, + 3.536729e-004, 1.109452e-004, -1.645280e-004, -4.681189e-004, -7.657539e-004, -1.086370e-003, -1.387928e-003, -1.696252e-003, -1.997915e-003, + -2.281441e-003, -2.574554e-003, -2.926901e-003, -3.256042e-003, -3.573890e-003, -3.882270e-003, -4.177003e-003, -4.394570e-003, -4.577888e-003, + -4.678746e-003, -4.708638e-003, -4.794129e-003, -4.880412e-003, -4.958695e-003, -5.042706e-003, -5.125962e-003, -5.227551e-003, -5.354214e-003, + -5.447163e-003, -5.527750e-003, -5.604583e-003, -5.724183e-003, -5.882734e-003, -6.076476e-003, -6.297282e-003, -6.535573e-003, -6.785053e-003, + -6.984168e-003, -7.067299e-003, -7.118797e-003, -7.146493e-003, -7.278332e-003, -7.427251e-003, -7.589382e-003, -7.779550e-003, -7.965826e-003, + -8.132341e-003, -8.298978e-003, -8.408957e-003, -8.277868e-003, -8.085832e-003, -7.844389e-003, -7.576261e-003, -7.286521e-003, -6.935731e-003, + -6.579726e-003, -6.345295e-003, -6.136374e-003, -5.949192e-003, -5.728288e-003, -5.154815e-003, -4.592246e-003, -4.013779e-003, -3.421577e-003, + -2.848376e-003, -2.301668e-003, -1.778515e-003, -1.287604e-003, -8.249034e-004, -3.974324e-004, 6.021932e-018}, + {0.000000e+000, -1.419539e-001, -1.036124e-001, -6.792405e-002, -2.708800e-002, 1.517038e-002, 4.019696e-002, 5.969302e-002, 7.318021e-002, + 7.955336e-002, 7.740017e-002, 6.786770e-002, 5.264640e-002, 3.606222e-002, 2.435019e-002, 1.902869e-002, 1.784040e-002, 2.015166e-002, + 2.176854e-002, 2.380658e-002, 2.387411e-002, 1.935135e-002, 1.038963e-002, -1.001266e-003, -1.433260e-002, -2.778570e-002, -4.081063e-002, + -5.304675e-002, -6.476809e-002, -7.332503e-002, -7.868723e-002, -8.142049e-002, -8.262070e-002, -8.287768e-002, -8.119369e-002, -7.735994e-002, + -7.207675e-002, -6.624884e-002, -5.734553e-002, -4.611067e-002, -3.305934e-002, -2.016471e-002, -6.374361e-003, 8.127395e-003, 2.130722e-002, + 3.278650e-002, 4.353354e-002, 5.205411e-002, 5.887513e-002, 6.586277e-002, 7.168401e-002, 7.693746e-002, 8.264459e-002, 8.641797e-002, + 9.037891e-002, 9.304619e-002, 9.399092e-002, 9.404765e-002, 9.111092e-002, 8.638032e-002, 7.929395e-002, 7.036889e-002, 5.953161e-002, + 4.825266e-002, 3.633480e-002, 2.367534e-002, 1.052038e-002, -2.657346e-003, -1.589899e-002, -2.896741e-002, -4.152623e-002, -5.338227e-002, + -6.409219e-002, -7.406542e-002, -8.205233e-002, -9.017282e-002, -9.689866e-002, -1.023482e-001, -1.055274e-001, -1.067142e-001, -1.067523e-001, + -1.041731e-001, -1.007724e-001, -9.483050e-002, -8.787849e-002, -7.957985e-002, -6.943558e-002, -5.877117e-002, -4.777642e-002, -3.635834e-002, + -2.434076e-002, -1.169129e-002, 4.245005e-004, 1.149325e-002, 2.258445e-002, 3.241976e-002, 4.132175e-002, 4.970854e-002, 5.553423e-002, + 6.049711e-002, 6.509272e-002, 6.927640e-002, 7.257623e-002, 7.518055e-002, 7.756234e-002, 7.945192e-002, 8.039453e-002, 8.097967e-002, + 8.128767e-002, 8.108285e-002, 7.982047e-002, 7.824626e-002, 7.656209e-002, 7.350573e-002, 7.002981e-002, 6.663025e-002, 6.219666e-002, + 5.728286e-002, 5.233078e-002, 4.657957e-002, 4.063100e-002, 3.430746e-002, 2.768315e-002, 2.085859e-002, 1.364530e-002, 6.248875e-003, + -1.250127e-003, -8.652431e-003, -1.588737e-002, -2.317202e-002, -2.976850e-002, -3.625468e-002, -4.235110e-002, -4.798755e-002, -5.290627e-002, + -5.679774e-002, -5.985921e-002, -6.230819e-002, -6.392358e-002, -6.487407e-002, -6.509388e-002, -6.480806e-002, -6.428655e-002, -6.324443e-002, + -6.185623e-002, -6.007233e-002, -5.821870e-002, -5.653940e-002, -5.441352e-002, -5.258841e-002, -5.119751e-002, -4.981180e-002, -4.884794e-002, + -4.856944e-002, -4.807488e-002, -4.738218e-002, -4.656297e-002, -4.545571e-002, -4.451550e-002, -4.302666e-002, -4.098589e-002, -3.906619e-002, + -3.745225e-002, -3.521159e-002, -3.305556e-002, -3.075919e-002, -2.816611e-002, -2.553889e-002, -2.286192e-002, -2.012358e-002, -1.677399e-002, + -1.321086e-002, -9.671028e-003, -6.194379e-003, -2.438305e-003, 1.305949e-003, 4.998369e-003, 9.002757e-003, 1.307251e-002, 1.685137e-002, + 2.035346e-002, 2.406746e-002, 2.767851e-002, 3.103426e-002, 3.420898e-002, 3.733744e-002, 4.021906e-002, 4.268473e-002, 4.501986e-002, + 4.693454e-002, 4.883143e-002, 5.010330e-002, 5.092922e-002, 5.144524e-002, 5.156375e-002, 5.134105e-002, 5.069609e-002, 4.997770e-002, + 4.896712e-002, 4.802644e-002, 4.688289e-002, 4.557125e-002, 4.409246e-002, 4.265972e-002, 4.112532e-002, 3.964203e-002, 3.808217e-002, + 3.666312e-002, 3.534755e-002, 3.392347e-002, 3.280277e-002, 3.155641e-002, 3.068470e-002, 2.954000e-002, 2.834969e-002, 2.719150e-002, + 2.587153e-002, 2.505802e-002, 2.408689e-002, 2.314924e-002, 2.221252e-002, 2.126470e-002, 2.086830e-002, 2.040811e-002, 2.010343e-002, + 1.988996e-002, 1.935163e-002, 1.899015e-002, 1.861569e-002, 1.821986e-002, 1.796585e-002, 1.752384e-002, 1.709545e-002, 1.650732e-002, + 1.589593e-002, 1.520503e-002, 1.440425e-002, 1.365198e-002, 1.264166e-002, 1.127104e-002, 9.841914e-003, 8.339769e-003, 6.712574e-003, + 4.933912e-003, 2.884468e-003, 5.985493e-004, -1.708059e-003, -3.991786e-003, -6.257966e-003, -8.585060e-003, -1.100853e-002, -1.385632e-002, + -1.659416e-002, -1.910374e-002, -2.157096e-002, -2.398674e-002, -2.617890e-002, -2.825907e-002, -3.018520e-002, -3.190477e-002, -3.336141e-002, + -3.466525e-002, -3.578901e-002, -3.680746e-002, -3.771924e-002, -3.827121e-002, -3.877290e-002, -3.913692e-002, -3.950250e-002, -3.981678e-002, + -4.004068e-002, -4.023469e-002, -4.030622e-002, -4.033762e-002, -4.025896e-002, -4.013123e-002, -3.998137e-002, -3.978413e-002, -3.967819e-002, + -3.939395e-002, -3.883163e-002, -3.810040e-002, -3.757120e-002, -3.705130e-002, -3.640822e-002, -3.584473e-002, -3.522015e-002, -3.416148e-002, + -3.278193e-002, -3.134782e-002, -2.977984e-002, -2.829860e-002, -2.696610e-002, -2.551638e-002, -2.404139e-002, -2.224460e-002, -2.022519e-002, + -1.829419e-002, -1.649663e-002, -1.473209e-002, -1.310277e-002, -1.167724e-002, -1.034436e-002, -8.846850e-003, -7.353908e-003, -5.725595e-003, + -4.293107e-003, -3.148903e-003, -2.236444e-003, -1.645302e-003, -1.233031e-003, -4.919654e-004, 1.413101e-004, 5.636048e-004, 1.077949e-003, + 1.506724e-003, 1.655457e-003, 1.694085e-003, 1.580385e-003, 1.407083e-003, 1.290979e-003, 1.241103e-003, 1.164737e-003, 7.979683e-004, + 6.047147e-004, 5.998700e-004, 4.217476e-004, 2.288406e-004, 1.315521e-004, 1.019066e-004, 1.381810e-004, 4.759979e-004, 1.007209e-003, + 1.238664e-003, 1.383367e-003, 1.443117e-003, 1.410411e-003, 1.365550e-003, 1.273418e-003, 1.424101e-003, 1.407576e-003, 1.630785e-003, + 1.940799e-003, 2.176384e-003, 2.280105e-003, 2.394124e-003, 2.835923e-003, 3.174333e-003, 3.526331e-003, 3.968612e-003, 4.578331e-003, + 5.055281e-003, 5.761884e-003, 6.541719e-003, 7.434230e-003, 8.211070e-003, 9.345373e-003, 1.054248e-002, 1.162063e-002, 1.261968e-002, + 1.374627e-002, 1.506815e-002, 1.660291e-002, 1.806392e-002, 1.970821e-002, 2.139701e-002, 2.289452e-002, 2.448306e-002, 2.601973e-002, + 2.743160e-002, 2.882312e-002, 3.021573e-002, 3.158087e-002, 3.285834e-002, 3.407062e-002, 3.516235e-002, 3.628794e-002, 3.720631e-002, + 3.800676e-002, 3.885607e-002, 3.964268e-002, 4.028641e-002, 4.062831e-002, 4.108059e-002, 4.148136e-002, 4.168244e-002, 4.176866e-002, + 4.181152e-002, 4.198007e-002, 4.218633e-002, 4.237012e-002, 4.253384e-002, 4.259278e-002, 4.243740e-002, 4.206305e-002, 4.150209e-002, + 4.086748e-002, 4.016169e-002, 3.960730e-002, 3.887417e-002, 3.806509e-002, 3.697609e-002, 3.571246e-002, 3.438925e-002, 3.301974e-002, + 3.150530e-002, 2.962946e-002, 2.769375e-002, 2.577277e-002, 2.379602e-002, 2.163412e-002, 1.946542e-002, 1.734635e-002, 1.509087e-002, + 1.262924e-002, 1.015724e-002, 7.902637e-003, 5.665930e-003, 3.406363e-003, 1.172860e-003, -9.998845e-004, -3.263325e-003, -5.638867e-003, + -8.192518e-003, -1.071512e-002, -1.299464e-002, -1.505132e-002, -1.706454e-002, -1.902563e-002, -2.067841e-002, -2.190756e-002, -2.302112e-002, + -2.424621e-002, -2.530534e-002, -2.628679e-002, -2.722248e-002, -2.806121e-002, -2.894963e-002, -2.989156e-002, -3.077421e-002, -3.140256e-002, + -3.212894e-002, -3.264985e-002, -3.302154e-002, -3.324528e-002, -3.348531e-002, -3.370481e-002, -3.382639e-002, -3.389419e-002, -3.400893e-002, + -3.426127e-002, -3.452348e-002, -3.477738e-002, -3.491843e-002, -3.492807e-002, -3.476538e-002, -3.436834e-002, -3.393744e-002, -3.331573e-002, + -3.268536e-002, -3.194480e-002, -3.123312e-002, -3.047864e-002, -2.961140e-002, -2.868011e-002, -2.774287e-002, -2.679371e-002, -2.586089e-002, + -2.482176e-002, -2.366655e-002, -2.243803e-002, -2.125467e-002, -1.989082e-002, -1.853484e-002, -1.720170e-002, -1.585174e-002, -1.459108e-002, + -1.324508e-002, -1.193631e-002, -1.071926e-002, -9.540640e-003, -8.517981e-003, -7.488759e-003, -6.416223e-003, -5.386322e-003, -4.353601e-003, + -3.370936e-003, -2.506711e-003, -1.788548e-003, -1.242683e-003, -6.646805e-004, -1.758541e-004, 3.037850e-004, 7.090468e-004, 9.946048e-004, + 1.409651e-003, 1.777146e-003, 2.075085e-003, 2.318047e-003, 2.646397e-003, 3.001634e-003, 3.335471e-003, 3.609256e-003, 3.852900e-003, + 3.941584e-003, 3.848084e-003, 3.729051e-003, 3.693554e-003, 3.659795e-003, 3.448675e-003, 3.068618e-003, 2.594031e-003, 2.213261e-003, + 1.758666e-003, 1.230598e-003, 5.974798e-004, 1.002171e-004, -3.511939e-004, -7.997501e-004, -1.291263e-003, -1.827102e-003, -2.317758e-003, + -2.901357e-003, -3.432364e-003, -3.863615e-003, -4.343495e-003, -4.930273e-003, -5.421391e-003, -5.859212e-003, -6.179578e-003, -6.463739e-003, + -6.845580e-003, -7.266627e-003, -7.589516e-003, -7.702766e-003, -7.427188e-003, -7.142174e-003, -6.800878e-003, -6.367102e-003, -5.864443e-003, + -5.382831e-003, -4.881120e-003, -4.481691e-003, -4.207930e-003, -4.041625e-003, -3.913481e-003, -3.798549e-003, -3.659885e-003, -3.447355e-003, + -3.227707e-003, -3.052418e-003, -2.849870e-003, -2.540188e-003, -2.169183e-003, -1.623437e-003, -9.806347e-004, -2.884090e-004, 4.951020e-004, + 1.268905e-003, 2.020584e-003, 2.804607e-003, 3.686111e-003, 4.623393e-003, 5.558912e-003, 6.590802e-003, 7.683101e-003, 8.814030e-003, + 9.926143e-003, 1.098814e-002, 1.202529e-002, 1.312637e-002, 1.433848e-002, 1.557870e-002, 1.672425e-002, 1.785887e-002, 1.900206e-002, + 2.008627e-002, 2.110877e-002, 2.206490e-002, 2.294132e-002, 2.383328e-002, 2.463579e-002, 2.523893e-002, 2.572642e-002, 2.616126e-002, + 2.653914e-002, 2.681667e-002, 2.698384e-002, 2.701180e-002, 2.705757e-002, 2.710002e-002, 2.717537e-002, 2.721729e-002, 2.722810e-002, + 2.723821e-002, 2.725637e-002, 2.729546e-002, 2.723936e-002, 2.713780e-002, 2.694573e-002, 2.668998e-002, 2.644269e-002, 2.620186e-002, + 2.597265e-002, 2.581422e-002, 2.569973e-002, 2.557705e-002, 2.544458e-002, 2.522740e-002, 2.494625e-002, 2.455333e-002, 2.410170e-002, + 2.351828e-002, 2.282917e-002, 2.211129e-002, 2.135670e-002, 2.053562e-002, 1.963763e-002, 1.866019e-002, 1.760652e-002, 1.658837e-002, + 1.564097e-002, 1.469566e-002, 1.369593e-002, 1.267910e-002, 1.164322e-002, 1.050212e-002, 9.382651e-003, 8.228941e-003, 7.003910e-003, + 5.715816e-003, 4.385959e-003, 3.187868e-003, 2.077364e-003, 1.056469e-003, 1.056810e-004, -8.166558e-004, -1.749681e-003, -2.671533e-003, + -3.560762e-003, -4.396105e-003, -5.183609e-003, -5.961650e-003, -6.726190e-003, -7.532381e-003, -8.388815e-003, -9.148416e-003, -1.001064e-002, + -1.090672e-002, -1.186608e-002, -1.281656e-002, -1.391314e-002, -1.509008e-002, -1.630194e-002, -1.748983e-002, -1.866812e-002, -1.990342e-002, + -2.124323e-002, -2.266669e-002, -2.409413e-002, -2.540345e-002, -2.651204e-002, -2.759958e-002, -2.860726e-002, -2.963686e-002, -3.066199e-002, + -3.170440e-002, -3.271547e-002, -3.366741e-002, -3.451744e-002, -3.526645e-002, -3.597432e-002, -3.654182e-002, -3.686749e-002, -3.708588e-002, + -3.716189e-002, -3.717350e-002, -3.712003e-002, -3.705128e-002, -3.693900e-002, -3.687041e-002, -3.681770e-002, -3.672809e-002, -3.663553e-002, + -3.660483e-002, -3.660009e-002, -3.643445e-002, -3.608549e-002, -3.582227e-002, -3.563342e-002, -3.544855e-002, -3.533970e-002, -3.521676e-002, + -3.507009e-002, -3.489187e-002, -3.474534e-002, -3.462618e-002, -3.442417e-002, -3.418774e-002, -3.397048e-002, -3.368356e-002, -3.338937e-002, + -3.305459e-002, -3.269441e-002, -3.198629e-002, -3.111630e-002, -3.022737e-002, -2.931267e-002, -2.834872e-002, -2.726518e-002, -2.609620e-002, + -2.487626e-002, -2.359541e-002, -2.236799e-002, -2.118645e-002, -2.003864e-002, -1.887809e-002, -1.777604e-002, -1.671133e-002, -1.569087e-002, + -1.470441e-002, -1.374682e-002, -1.284282e-002, -1.201802e-002, -1.114242e-002, -1.026903e-002, -9.444259e-003, -8.502198e-003, -7.545191e-003, + -6.625737e-003, -5.724872e-003, -4.875653e-003, -4.011697e-003, -3.114425e-003, -2.133028e-003, -1.132353e-003, -7.581203e-006, 1.138889e-003, + 2.263788e-003, 3.370658e-003, 4.518324e-003, 5.673439e-003, 6.774203e-003, 7.822776e-003, 8.848177e-003, 9.819092e-003, 1.071289e-002, + 1.154698e-002, 1.233961e-002, 1.308728e-002, 1.368194e-002, 1.416295e-002, 1.455475e-002, 1.490606e-002, 1.526442e-002, 1.561591e-002, + 1.594705e-002, 1.624214e-002, 1.677711e-002, 1.738704e-002, 1.801871e-002, 1.862352e-002, 1.921178e-002, 1.969546e-002, 2.012506e-002, + 2.044887e-002, 2.084089e-002, 2.135367e-002, 2.194371e-002, 2.262269e-002, 2.341510e-002, 2.427020e-002, 2.509585e-002, 2.587514e-002, + 2.668172e-002, 2.745540e-002, 2.812808e-002, 2.872863e-002, 2.924824e-002, 2.970274e-002, 3.004808e-002, 3.029362e-002, 3.046823e-002, + 3.055698e-002, 3.058566e-002, 3.056395e-002, 3.047943e-002, 3.038018e-002, 3.015302e-002, 2.981492e-002, 2.940199e-002, 2.894101e-002, + 2.849902e-002, 2.808541e-002, 2.770490e-002, 2.731710e-002, 2.688582e-002, 2.647155e-002, 2.608558e-002, 2.591296e-002, 2.581973e-002, + 2.575380e-002, 2.571715e-002, 2.572992e-002, 2.577940e-002, 2.589100e-002, 2.607847e-002, 2.634906e-002, 2.659737e-002, 2.681429e-002, + 2.698701e-002, 2.714503e-002, 2.725628e-002, 2.729188e-002, 2.723950e-002, 2.706785e-002, 2.679017e-002, 2.632206e-002, 2.570716e-002, + 2.502466e-002, 2.428319e-002, 2.334337e-002, 2.231898e-002, 2.122946e-002, 2.013992e-002, 1.905499e-002, 1.799630e-002, 1.693870e-002, + 1.588512e-002, 1.485546e-002, 1.376223e-002, 1.264511e-002, 1.151455e-002, 1.043219e-002, 9.414249e-003, 8.489298e-003, 7.670184e-003, + 6.923129e-003, 6.231918e-003, 5.602638e-003, 5.008505e-003, 4.501276e-003, 4.072697e-003, 3.668189e-003, 3.220896e-003, 2.748418e-003, + 2.191690e-003, 1.597310e-003, 9.583032e-004, 2.915937e-004, -3.414514e-004, -1.006103e-003, -1.716042e-003, -2.454044e-003, -3.131360e-003, + -3.851934e-003, -4.556372e-003, -5.288167e-003, -6.098473e-003, -6.879084e-003, -7.646168e-003, -8.386765e-003, -9.097550e-003, -9.790325e-003, + -1.046676e-002, -1.111886e-002, -1.174480e-002, -1.229662e-002, -1.275620e-002, -1.311655e-002, -1.344013e-002, -1.370861e-002, -1.395867e-002, + -1.419176e-002, -1.438439e-002, -1.452059e-002, -1.456022e-002, -1.447935e-002, -1.432253e-002, -1.405163e-002, -1.376406e-002, -1.351821e-002, + -1.326792e-002, -1.302730e-002, -1.276939e-002, -1.248687e-002, -1.210445e-002, -1.169238e-002, -1.128910e-002, -1.087348e-002, -1.046596e-002, + -1.003700e-002, -9.591587e-003, -9.137892e-003, -8.684494e-003, -8.265109e-003, -7.879819e-003, -7.487533e-003, -7.078767e-003, -6.663434e-003, + -6.218614e-003, -5.754517e-003, -5.350267e-003, -4.991624e-003, -4.649861e-003, -4.300304e-003, -3.951166e-003, -3.712821e-003, -3.546430e-003, + -3.414193e-003, -3.331016e-003, -3.299512e-003, -3.278189e-003, -3.264178e-003, -3.251670e-003, -3.279345e-003, -3.358939e-003, -3.549348e-003, + -3.799394e-003, -4.082149e-003, -4.391320e-003, -4.710511e-003, -5.044619e-003, -5.410087e-003, -5.711682e-003, -5.996753e-003, -6.340770e-003, + -6.584797e-003, -6.727178e-003, -6.768959e-003, -6.777004e-003, -6.771874e-003, -6.786380e-003, -6.778971e-003, -6.805732e-003, -6.859159e-003, + -6.962634e-003, -7.052824e-003, -7.162723e-003, -7.256644e-003, -7.347714e-003, -7.450831e-003, -7.590599e-003, -7.678282e-003, -7.770598e-003, + -7.932179e-003, -8.099055e-003, -8.254360e-003, -8.401964e-003, -8.529252e-003, -8.638737e-003, -8.706753e-003, -8.708989e-003, -8.640035e-003, + -8.552731e-003, -8.461409e-003, -8.355552e-003, -8.320266e-003, -8.273267e-003, -8.183507e-003, -8.035477e-003, -7.841541e-003, -7.603160e-003, + -7.317098e-003, -7.023905e-003, -6.754152e-003, -6.494216e-003, -6.191246e-003, -5.855130e-003, -5.490166e-003, -5.107889e-003, -4.692356e-003, + -4.183558e-003, -3.597480e-003, -3.021860e-003, -2.368777e-003, -1.732934e-003, -1.128342e-003, -5.544300e-004, -2.789279e-005, 4.621089e-004, + 9.479503e-004, 1.355681e-003, 1.661723e-003, 1.846546e-003, 1.966050e-003, 2.029690e-003, 2.058399e-003, 2.069139e-003, 2.085198e-003, + 2.086770e-003, 2.118438e-003, 2.191080e-003, 2.283026e-003, 2.280907e-003, 2.282985e-003, 2.254391e-003, 2.112161e-003, 1.946159e-003, + 1.757909e-003, 1.599493e-003, 1.512843e-003, 1.589365e-003, 1.736455e-003, 1.947146e-003, 2.212924e-003, 2.511569e-003, 2.772376e-003, + 3.034046e-003, 3.178168e-003, 3.165527e-003, 3.055279e-003, 2.938614e-003, 3.061092e-003, 3.094326e-003, 3.032048e-003, 2.882782e-003, + 2.681154e-003, 2.409797e-003, 2.075953e-003, 1.665721e-003, 1.188743e-003, 6.284173e-004, 2.064357e-017}, + {0.000000e+000, -5.884035e-003, 1.206050e-002, 3.049206e-002, 3.003751e-002, 1.966938e-002, 1.709589e-002, 1.097572e-002, 5.710203e-003, + 3.384460e-003, 5.629450e-003, 1.143536e-002, 1.917816e-002, 2.587902e-002, 2.589293e-002, 1.871079e-002, 5.688994e-003, -1.257538e-002, + -3.206193e-002, -5.227155e-002, -6.826732e-002, -7.666923e-002, -7.778825e-002, -7.387267e-002, -6.796031e-002, -5.995261e-002, -4.956054e-002, + -3.672650e-002, -2.252167e-002, -8.694235e-003, 4.488508e-003, 1.696310e-002, 2.825635e-002, 3.791796e-002, 4.659577e-002, 5.390258e-002, + 5.931234e-002, 6.192430e-002, 6.156818e-002, 5.863152e-002, 5.357603e-002, 4.669202e-002, 3.916060e-002, 3.117202e-002, 2.359120e-002, + 1.704662e-002, 1.212091e-002, 8.710634e-003, 5.831097e-003, 3.360740e-003, 5.391114e-004, -3.764149e-003, -9.071557e-003, -1.531453e-002, + -2.255599e-002, -2.966411e-002, -3.601688e-002, -4.185548e-002, -4.654736e-002, -4.908090e-002, -4.971211e-002, -4.805107e-002, -4.496298e-002, + -4.147839e-002, -3.795349e-002, -3.409726e-002, -2.930175e-002, -2.347795e-002, -1.686408e-002, -1.021798e-002, -3.376739e-003, 3.754763e-003, + 1.130456e-002, 1.885608e-002, 2.589634e-002, 3.323095e-002, 4.051716e-002, 4.714532e-002, 5.248908e-002, 5.655314e-002, 5.943179e-002, + 6.044005e-002, 6.046374e-002, 5.946990e-002, 5.665442e-002, 5.263206e-002, 4.804912e-002, 4.197308e-002, 3.557421e-002, 2.879640e-002, + 2.130866e-002, 1.412141e-002, 6.402630e-003, -3.954837e-004, -6.820323e-003, -1.268624e-002, -1.766589e-002, -2.165018e-002, -2.393816e-002, + -2.563379e-002, -2.677766e-002, -2.823566e-002, -2.937754e-002, -3.041117e-002, -3.146949e-002, -3.267914e-002, -3.384882e-002, -3.490533e-002, + -3.612919e-002, -3.728481e-002, -3.788315e-002, -3.844097e-002, -3.912957e-002, -3.894780e-002, -3.844641e-002, -3.738447e-002, -3.592443e-002, + -3.402646e-002, -3.156060e-002, -2.857174e-002, -2.479943e-002, -2.073766e-002, -1.614759e-002, -1.124309e-002, -6.154171e-003, -8.895209e-004, + 4.301080e-003, 9.167609e-003, 1.394155e-002, 1.858114e-002, 2.283719e-002, 2.696660e-002, 3.068767e-002, 3.329175e-002, 3.510834e-002, + 3.621983e-002, 3.628220e-002, 3.552816e-002, 3.418535e-002, 3.208951e-002, 2.929572e-002, 2.604667e-002, 2.294167e-002, 1.937953e-002, + 1.562590e-002, 1.189731e-002, 8.045938e-003, 4.237038e-003, 4.310596e-004, -2.706980e-003, -5.062185e-003, -7.028978e-003, -8.336149e-003, + -8.766212e-003, -8.931890e-003, -8.995374e-003, -9.001025e-003, -8.741414e-003, -8.518932e-003, -8.407627e-003, -8.284246e-003, -8.117318e-003, + -7.625531e-003, -7.526594e-003, -7.456860e-003, -7.662614e-003, -7.723305e-003, -7.689266e-003, -7.542797e-003, -7.756374e-003, -8.293640e-003, + -8.805057e-003, -9.057601e-003, -9.720454e-003, -1.066413e-002, -1.172893e-002, -1.242470e-002, -1.312386e-002, -1.394897e-002, -1.488869e-002, + -1.518750e-002, -1.563231e-002, -1.633437e-002, -1.684247e-002, -1.722660e-002, -1.777575e-002, -1.780741e-002, -1.778761e-002, -1.763925e-002, + -1.714208e-002, -1.649148e-002, -1.562869e-002, -1.416601e-002, -1.295431e-002, -1.101993e-002, -8.948106e-003, -6.121230e-003, -3.173768e-003, + 1.660451e-004, 3.487262e-003, 6.887342e-003, 1.031193e-002, 1.346142e-002, 1.656899e-002, 1.942181e-002, 2.240880e-002, 2.498714e-002, + 2.751651e-002, 2.986261e-002, 3.191622e-002, 3.363757e-002, 3.495018e-002, 3.587321e-002, 3.667872e-002, 3.692653e-002, 3.719468e-002, + 3.700629e-002, 3.650387e-002, 3.592412e-002, 3.520577e-002, 3.447157e-002, 3.337643e-002, 3.201951e-002, 3.043860e-002, 2.849360e-002, + 2.664718e-002, 2.451794e-002, 2.237383e-002, 2.002122e-002, 1.757373e-002, 1.514208e-002, 1.270289e-002, 1.043545e-002, 8.049355e-003, + 5.636358e-003, 3.536537e-003, 1.348231e-003, -8.218181e-004, -2.493908e-003, -3.951792e-003, -5.248754e-003, -6.500497e-003, -7.593310e-003, + -8.091750e-003, -8.625289e-003, -8.914666e-003, -8.996373e-003, -8.989998e-003, -8.992182e-003, -8.942410e-003, -8.802284e-003, -8.096253e-003, + -7.462768e-003, -6.823762e-003, -6.019022e-003, -5.316966e-003, -4.808466e-003, -4.183201e-003, -3.691915e-003, -3.272723e-003, -2.804278e-003, + -2.752393e-003, -2.858813e-003, -3.139648e-003, -3.631077e-003, -4.178289e-003, -4.837927e-003, -5.415356e-003, -5.852108e-003, -6.244190e-003, + -6.752684e-003, -7.179716e-003, -7.599783e-003, -8.096081e-003, -8.604389e-003, -9.210337e-003, -9.832264e-003, -1.044150e-002, -1.105628e-002, + -1.175909e-002, -1.250079e-002, -1.318013e-002, -1.376539e-002, -1.421848e-002, -1.470018e-002, -1.535426e-002, -1.593455e-002, -1.644070e-002, + -1.696452e-002, -1.762929e-002, -1.828440e-002, -1.903388e-002, -1.977325e-002, -2.060567e-002, -2.139396e-002, -2.226622e-002, -2.327479e-002, + -2.422805e-002, -2.524178e-002, -2.625225e-002, -2.732794e-002, -2.848347e-002, -2.933124e-002, -3.004910e-002, -3.077191e-002, -3.159298e-002, + -3.236835e-002, -3.290230e-002, -3.331127e-002, -3.345762e-002, -3.320700e-002, -3.271422e-002, -3.203304e-002, -3.113345e-002, -2.991892e-002, + -2.850035e-002, -2.686171e-002, -2.504731e-002, -2.311087e-002, -2.094423e-002, -1.855454e-002, -1.603842e-002, -1.345968e-002, -1.066311e-002, + -7.677695e-003, -4.693214e-003, -1.804628e-003, 1.198819e-003, 4.050774e-003, 6.727898e-003, 9.258785e-003, 1.175012e-002, 1.410771e-002, + 1.640370e-002, 1.872032e-002, 2.106224e-002, 2.347714e-002, 2.588787e-002, 2.832950e-002, 3.066889e-002, 3.292749e-002, 3.505272e-002, + 3.707771e-002, 3.897498e-002, 4.074446e-002, 4.245702e-002, 4.395962e-002, 4.521019e-002, 4.619311e-002, 4.688558e-002, 4.750285e-002, + 4.794766e-002, 4.816383e-002, 4.810482e-002, 4.792793e-002, 4.729618e-002, 4.652057e-002, 4.558017e-002, 4.452366e-002, 4.319393e-002, + 4.170817e-002, 3.992725e-002, 3.788809e-002, 3.552905e-002, 3.306365e-002, 3.054822e-002, 2.812698e-002, 2.558530e-002, 2.297020e-002, + 2.032699e-002, 1.759461e-002, 1.491980e-002, 1.230803e-002, 9.845191e-003, 7.546819e-003, 5.449565e-003, 3.372990e-003, 1.356474e-003, + -6.244383e-004, -2.610921e-003, -4.586890e-003, -6.278519e-003, -7.890025e-003, -9.517659e-003, -1.103433e-002, -1.232521e-002, -1.350277e-002, + -1.452207e-002, -1.563505e-002, -1.662070e-002, -1.747172e-002, -1.809808e-002, -1.868008e-002, -1.924296e-002, -1.972334e-002, -1.995173e-002, + -1.992581e-002, -1.988442e-002, -1.969942e-002, -1.948009e-002, -1.907908e-002, -1.854615e-002, -1.787303e-002, -1.699768e-002, -1.594111e-002, + -1.459593e-002, -1.299512e-002, -1.109364e-002, -9.132048e-003, -7.098413e-003, -5.133031e-003, -3.156067e-003, -1.176582e-003, 8.107661e-004, + 2.753242e-003, 4.567266e-003, 6.461167e-003, 8.365149e-003, 1.023503e-002, 1.199766e-002, 1.367235e-002, 1.514784e-002, 1.654619e-002, + 1.774623e-002, 1.876584e-002, 1.959939e-002, 2.027723e-002, 2.075191e-002, 2.098424e-002, 2.090633e-002, 2.048742e-002, 1.985573e-002, + 1.914052e-002, 1.828560e-002, 1.722887e-002, 1.606533e-002, 1.494413e-002, 1.375048e-002, 1.252149e-002, 1.132415e-002, 1.021822e-002, + 9.142808e-003, 7.972039e-003, 6.717654e-003, 5.346377e-003, 4.066137e-003, 2.773927e-003, 1.623577e-003, 5.727074e-004, -4.577180e-004, + -1.420504e-003, -2.300119e-003, -3.070242e-003, -3.835211e-003, -4.607136e-003, -5.486896e-003, -6.424022e-003, -7.375626e-003, -8.289346e-003, + -9.244838e-003, -1.026491e-002, -1.143799e-002, -1.272087e-002, -1.413782e-002, -1.564241e-002, -1.723947e-002, -1.889180e-002, -2.051288e-002, + -2.204456e-002, -2.367888e-002, -2.526027e-002, -2.691839e-002, -2.851257e-002, -3.000154e-002, -3.161105e-002, -3.331999e-002, -3.503159e-002, + -3.662259e-002, -3.805864e-002, -3.942737e-002, -4.068278e-002, -4.189856e-002, -4.286019e-002, -4.363595e-002, -4.429410e-002, -4.496614e-002, + -4.568508e-002, -4.638920e-002, -4.690511e-002, -4.718796e-002, -4.740586e-002, -4.759310e-002, -4.774237e-002, -4.767961e-002, -4.749608e-002, + -4.723799e-002, -4.696139e-002, -4.672770e-002, -4.646786e-002, -4.610451e-002, -4.578019e-002, -4.551681e-002, -4.523970e-002, -4.497662e-002, + -4.461293e-002, -4.408986e-002, -4.344115e-002, -4.269466e-002, -4.190986e-002, -4.101344e-002, -4.001374e-002, -3.877195e-002, -3.730987e-002, + -3.576281e-002, -3.404682e-002, -3.226266e-002, -3.029614e-002, -2.812401e-002, -2.579066e-002, -2.340318e-002, -2.092865e-002, -1.838213e-002, + -1.568175e-002, -1.289884e-002, -1.017833e-002, -7.495431e-003, -4.834545e-003, -2.142456e-003, 5.920163e-004, 3.337825e-003, 5.959578e-003, + 8.519402e-003, 1.102971e-002, 1.344931e-002, 1.576932e-002, 1.796100e-002, 1.991082e-002, 2.168644e-002, 2.337365e-002, 2.504023e-002, + 2.678373e-002, 2.862147e-002, 3.049878e-002, 3.241890e-002, 3.436431e-002, 3.641611e-002, 3.856386e-002, 4.073100e-002, 4.283885e-002, + 4.490802e-002, 4.689472e-002, 4.871002e-002, 5.046645e-002, 5.204726e-002, 5.343029e-002, 5.472832e-002, 5.599236e-002, 5.721597e-002, + 5.839899e-002, 5.957367e-002, 6.056500e-002, 6.136720e-002, 6.186965e-002, 6.211838e-002, 6.226175e-002, 6.229212e-002, 6.214178e-002, + 6.190056e-002, 6.152783e-002, 6.101349e-002, 6.050825e-002, 6.002261e-002, 5.945987e-002, 5.870049e-002, 5.780476e-002, 5.684328e-002, + 5.594430e-002, 5.514625e-002, 5.451345e-002, 5.392397e-002, 5.337463e-002, 5.275612e-002, 5.213341e-002, 5.162743e-002, 5.126525e-002, + 5.069816e-002, 5.000647e-002, 4.919533e-002, 4.829488e-002, 4.734442e-002, 4.636469e-002, 4.530876e-002, 4.408834e-002, 4.273445e-002, + 4.124301e-002, 3.971368e-002, 3.816382e-002, 3.635555e-002, 3.442473e-002, 3.242237e-002, 3.032685e-002, 2.810918e-002, 2.570025e-002, + 2.315001e-002, 2.051721e-002, 1.781774e-002, 1.504993e-002, 1.223533e-002, 9.403792e-003, 6.614334e-003, 3.961789e-003, 1.352513e-003, + -1.142669e-003, -3.556329e-003, -5.808669e-003, -7.896957e-003, -9.784636e-003, -1.150217e-002, -1.308122e-002, -1.463586e-002, -1.613689e-002, + -1.753596e-002, -1.880683e-002, -1.998000e-002, -2.100789e-002, -2.209715e-002, -2.315729e-002, -2.421098e-002, -2.529663e-002, -2.641581e-002, + -2.758166e-002, -2.869584e-002, -2.979604e-002, -3.095794e-002, -3.225884e-002, -3.361836e-002, -3.503545e-002, -3.645276e-002, -3.783637e-002, + -3.929751e-002, -4.089781e-002, -4.259918e-002, -4.432874e-002, -4.607143e-002, -4.773009e-002, -4.920081e-002, -5.047505e-002, -5.164006e-002, + -5.269007e-002, -5.353123e-002, -5.403724e-002, -5.434575e-002, -5.443941e-002, -5.436548e-002, -5.425290e-002, -5.409159e-002, -5.387804e-002, + -5.379968e-002, -5.374968e-002, -5.360891e-002, -5.324331e-002, -5.274358e-002, -5.222948e-002, -5.160853e-002, -5.102097e-002, -5.055632e-002, + -5.009110e-002, -4.961392e-002, -4.919782e-002, -4.890147e-002, -4.865878e-002, -4.853132e-002, -4.840425e-002, -4.825781e-002, -4.817701e-002, + -4.814303e-002, -4.827690e-002, -4.839454e-002, -4.851642e-002, -4.853947e-002, -4.851070e-002, -4.835915e-002, -4.806847e-002, -4.772811e-002, + -4.735464e-002, -4.686800e-002, -4.616908e-002, -4.515623e-002, -4.409057e-002, -4.273307e-002, -4.112326e-002, -3.938006e-002, -3.749485e-002, + -3.557317e-002, -3.367075e-002, -3.179954e-002, -2.987817e-002, -2.790936e-002, -2.572261e-002, -2.346290e-002, -2.115555e-002, -1.891577e-002, + -1.679818e-002, -1.477311e-002, -1.243367e-002, -1.002242e-002, -7.706430e-003, -5.560307e-003, -3.565738e-003, -1.700999e-003, 4.548754e-005, + 1.703930e-003, 3.307118e-003, 4.857230e-003, 6.397235e-003, 7.923882e-003, 9.460259e-003, 1.106621e-002, 1.270158e-002, 1.435002e-002, + 1.601426e-002, 1.764552e-002, 1.926211e-002, 2.078512e-002, 2.229888e-002, 2.374488e-002, 2.518172e-002, 2.686031e-002, 2.867680e-002, + 3.047771e-002, 3.223375e-002, 3.387937e-002, 3.545273e-002, 3.691159e-002, 3.824734e-002, 3.954426e-002, 4.082855e-002, 4.204487e-002, + 4.314479e-002, 4.410489e-002, 4.499020e-002, 4.571630e-002, 4.630931e-002, 4.683214e-002, 4.735618e-002, 4.790854e-002, 4.847886e-002, + 4.893745e-002, 4.941052e-002, 4.991920e-002, 5.043605e-002, 5.094296e-002, 5.148773e-002, 5.203289e-002, 5.260084e-002, 5.310336e-002, + 5.352573e-002, 5.382221e-002, 5.425348e-002, 5.457538e-002, 5.479892e-002, 5.487953e-002, 5.487374e-002, 5.468993e-002, 5.432971e-002, + 5.377583e-002, 5.311632e-002, 5.224699e-002, 5.121794e-002, 5.003129e-002, 4.869618e-002, 4.720427e-002, 4.546396e-002, 4.364570e-002, + 4.182177e-002, 4.002437e-002, 3.824361e-002, 3.652127e-002, 3.481240e-002, 3.322805e-002, 3.166959e-002, 3.018601e-002, 2.874048e-002, + 2.740325e-002, 2.617446e-002, 2.505664e-002, 2.406591e-002, 2.328137e-002, 2.266331e-002, 2.205704e-002, 2.146733e-002, 2.085548e-002, + 2.027054e-002, 1.963182e-002, 1.899520e-002, 1.840720e-002, 1.782242e-002, 1.715968e-002, 1.646351e-002, 1.588462e-002, 1.533953e-002, + 1.475195e-002, 1.402474e-002, 1.309852e-002, 1.207905e-002, 1.095860e-002, 9.732415e-003, 8.430559e-003, 7.069104e-003, 5.627828e-003, + 4.144554e-003, 2.612547e-003, 1.078197e-003, -3.333381e-004, -1.660112e-003, -2.819464e-003, -3.856188e-003, -4.675506e-003, -5.290251e-003, + -5.731264e-003, -6.131214e-003, -6.553499e-003, -6.806265e-003, -7.035203e-003, -7.211436e-003, -7.395562e-003, -7.599733e-003, -7.776983e-003, + -7.917681e-003, -8.115935e-003, -8.351167e-003, -8.462478e-003, -8.520384e-003, -8.543514e-003, -8.582004e-003, -8.686430e-003, -8.921784e-003, + -9.378241e-003, -9.984562e-003, -1.070819e-002, -1.144842e-002, -1.216734e-002, -1.286840e-002, -1.355399e-002, -1.423771e-002, -1.485368e-002, + -1.539165e-002, -1.587404e-002, -1.635753e-002, -1.671768e-002, -1.694643e-002, -1.704845e-002, -1.708001e-002, -1.707063e-002, -1.693722e-002, + -1.683292e-002, -1.669635e-002, -1.663055e-002, -1.664120e-002, -1.659552e-002, -1.662302e-002, -1.674169e-002, -1.693562e-002, -1.723151e-002, + -1.759452e-002, -1.803317e-002, -1.857834e-002, -1.926417e-002, -2.000791e-002, -2.079699e-002, -2.178555e-002, -2.305588e-002, -2.442474e-002, + -2.587617e-002, -2.736611e-002, -2.886701e-002, -3.039988e-002, -3.194743e-002, -3.332878e-002, -3.456006e-002, -3.567533e-002, -3.674389e-002, + -3.767837e-002, -3.860935e-002, -3.928897e-002, -3.975846e-002, -4.005642e-002, -4.022738e-002, -4.019999e-002, -4.001636e-002, -3.973286e-002, + -3.941271e-002, -3.895531e-002, -3.836477e-002, -3.766485e-002, -3.697775e-002, -3.620546e-002, -3.534119e-002, -3.439193e-002, -3.339706e-002, + -3.244995e-002, -3.156241e-002, -3.081037e-002, -3.013177e-002, -2.946770e-002, -2.880825e-002, -2.812245e-002, -2.744323e-002, -2.669691e-002, + -2.591785e-002, -2.518238e-002, -2.454735e-002, -2.400416e-002, -2.345074e-002, -2.286687e-002, -2.222466e-002, -2.150362e-002, -2.072564e-002, + -1.985872e-002, -1.896279e-002, -1.805334e-002, -1.713478e-002, -1.627721e-002, -1.540866e-002, -1.449563e-002, -1.353659e-002, -1.258583e-002, + -1.161945e-002, -1.064567e-002, -9.697183e-003, -8.766315e-003, -7.884624e-003, -7.248590e-003, -6.613830e-003, -5.981632e-003, -5.358558e-003, + -4.684666e-003, -4.061358e-003, -3.394395e-003, -2.704937e-003, -1.992494e-003, -1.265195e-003, -6.845468e-004, -1.080782e-004, 4.257485e-004, + 9.852376e-004, 1.683515e-003, 2.508954e-003, 3.467462e-003, 4.520353e-003, 5.608052e-003, 6.758289e-003, 7.913474e-003, 9.121853e-003, + 1.047055e-002, 1.199733e-002, 1.366311e-002, 1.525929e-002, 1.684295e-002, 1.845380e-002, 2.008954e-002, 2.181070e-002, 2.352426e-002, + 2.516126e-002, 2.685818e-002, 2.855091e-002, 3.021117e-002, 3.187322e-002, 3.354269e-002, 3.517659e-002, 3.652914e-002, 3.774941e-002, + 3.887767e-002, 3.973597e-002, 4.048107e-002, 4.117310e-002, 4.185933e-002, 4.257679e-002, 4.334223e-002, 4.412559e-002, 4.484661e-002, + 4.553661e-002, 4.620972e-002, 4.647402e-002, 4.666042e-002, 4.698923e-002, 4.745676e-002, 4.803383e-002, 4.868036e-002, 4.937314e-002, + 4.998720e-002, 5.037414e-002, 5.070796e-002, 5.085181e-002, 5.089037e-002, 5.094595e-002, 5.090955e-002, 5.051478e-002, 5.013270e-002, + 4.969428e-002, 4.925393e-002, 4.869329e-002, 4.782868e-002, 4.668914e-002, 4.525280e-002, 4.372563e-002, 4.212665e-002, 4.033387e-002, + 3.845674e-002, 3.655031e-002, 3.419669e-002, 3.170815e-002, 2.924282e-002, 2.647364e-002, 2.354786e-002, 2.060480e-002, 1.765068e-002, + 1.477969e-002, 1.201827e-002, 9.359265e-003, 6.832206e-003, 4.422553e-003, 2.152366e-003, 1.115139e-016}, + {0.000000e+000, -2.579398e-001, -1.209567e-001, -1.932490e-002, 2.504666e-002, 3.760578e-002, 5.783333e-002, 6.761293e-002, 7.434871e-002, + 7.908871e-002, 8.784226e-002, 9.907153e-002, 1.101218e-001, 1.189280e-001, 1.136878e-001, 9.264423e-002, 6.011690e-002, 2.173496e-002, + -1.755617e-002, -5.624576e-002, -8.941955e-002, -1.129872e-001, -1.263236e-001, -1.315514e-001, -1.303526e-001, -1.240840e-001, -1.136003e-001, + -9.992641e-002, -8.610108e-002, -7.163968e-002, -5.759440e-002, -4.251288e-002, -2.767609e-002, -1.359326e-002, 7.703830e-004, 1.501648e-002, + 2.836959e-002, 3.884818e-002, 4.631836e-002, 5.056561e-002, 5.256275e-002, 5.108808e-002, 4.837986e-002, 4.531974e-002, 4.190391e-002, + 3.886251e-002, 3.834583e-002, 3.955288e-002, 4.053713e-002, 4.172433e-002, 3.956807e-002, 3.614354e-002, 3.136865e-002, 2.491181e-002, + 1.852397e-002, 1.143958e-002, 4.409165e-003, -2.266289e-003, -8.557574e-003, -1.352364e-002, -1.756921e-002, -1.901168e-002, -1.933776e-002, + -2.004519e-002, -2.022225e-002, -1.937490e-002, -1.828170e-002, -1.674618e-002, -1.469506e-002, -1.204811e-002, -8.579812e-003, -4.427528e-003, + 3.616970e-004, 5.099766e-003, 1.097479e-002, 1.545632e-002, 1.975683e-002, 2.381555e-002, 2.770366e-002, 3.035031e-002, 3.069347e-002, + 3.000374e-002, 2.646521e-002, 2.213796e-002, 1.632744e-002, 9.611939e-003, 2.464927e-003, -5.452643e-003, -1.477599e-002, -2.404947e-002, + -3.276840e-002, -4.067040e-002, -4.763239e-002, -5.479383e-002, -5.999768e-002, -6.344228e-002, -6.477467e-002, -6.281421e-002, -5.982008e-002, + -5.487841e-002, -4.827628e-002, -4.055054e-002, -3.342795e-002, -2.628211e-002, -1.818398e-002, -9.700948e-003, -2.041464e-003, 5.061294e-003, + 1.255927e-002, 2.012797e-002, 2.589389e-002, 3.187245e-002, 3.843609e-002, 4.340302e-002, 4.789767e-002, 5.259461e-002, 5.620754e-002, + 5.940958e-002, 6.281688e-002, 6.470254e-002, 6.624533e-002, 6.730981e-002, 6.758890e-002, 6.759588e-002, 6.630336e-002, 6.442698e-002, + 6.216506e-002, 5.918142e-002, 5.575290e-002, 5.118315e-002, 4.674005e-002, 4.178763e-002, 3.653629e-002, 3.068895e-002, 2.349925e-002, + 1.581754e-002, 7.878387e-003, -6.886181e-004, -9.249308e-003, -1.821434e-002, -2.673689e-002, -3.492459e-002, -4.339498e-002, -5.120203e-002, + -5.873327e-002, -6.544557e-002, -7.133148e-002, -7.676154e-002, -8.055690e-002, -8.398265e-002, -8.641409e-002, -8.696943e-002, -8.643045e-002, + -8.427497e-002, -8.095234e-002, -7.756008e-002, -7.407493e-002, -7.043029e-002, -6.678125e-002, -6.298851e-002, -5.833634e-002, -5.378697e-002, + -4.955872e-002, -4.419111e-002, -3.872515e-002, -3.292736e-002, -2.664201e-002, -2.033412e-002, -1.446422e-002, -8.907711e-003, -2.955067e-003, + 2.399817e-003, 7.114083e-003, 1.193007e-002, 1.690367e-002, 2.174539e-002, 2.561761e-002, 2.978104e-002, 3.374028e-002, 3.771790e-002, + 4.051783e-002, 4.374519e-002, 4.664450e-002, 4.870739e-002, 4.993152e-002, 5.142378e-002, 5.211863e-002, 5.224159e-002, 5.236009e-002, + 5.158623e-002, 5.080987e-002, 4.946461e-002, 4.774334e-002, 4.624232e-002, 4.478164e-002, 4.325651e-002, 4.093693e-002, 3.838341e-002, + 3.583514e-002, 3.326730e-002, 3.064711e-002, 2.774813e-002, 2.511689e-002, 2.258792e-002, 2.041250e-002, 1.831328e-002, 1.624659e-002, + 1.420284e-002, 1.216297e-002, 9.958626e-003, 7.791126e-003, 5.516001e-003, 3.696278e-003, 1.620315e-003, -8.438702e-005, -1.743089e-003, + -3.159685e-003, -4.207882e-003, -5.405470e-003, -6.339237e-003, -7.585506e-003, -8.845937e-003, -1.026351e-002, -1.217476e-002, -1.430957e-002, + -1.664366e-002, -1.902981e-002, -2.118980e-002, -2.320684e-002, -2.539996e-002, -2.752148e-002, -2.937368e-002, -3.133944e-002, -3.315962e-002, + -3.472621e-002, -3.607433e-002, -3.697657e-002, -3.724698e-002, -3.727162e-002, -3.702979e-002, -3.667382e-002, -3.569018e-002, -3.416013e-002, + -3.221873e-002, -2.982019e-002, -2.719112e-002, -2.453182e-002, -2.166123e-002, -1.833190e-002, -1.485849e-002, -1.131786e-002, -7.878930e-003, + -4.391772e-003, -6.966317e-004, 2.750646e-003, 5.956787e-003, 9.095940e-003, 1.203525e-002, 1.447489e-002, 1.655321e-002, 1.843273e-002, + 1.982740e-002, 2.076265e-002, 2.118521e-002, 2.141936e-002, 2.128287e-002, 2.087018e-002, 2.035876e-002, 1.991192e-002, 1.983346e-002, + 2.005756e-002, 2.033808e-002, 2.060518e-002, 2.108593e-002, 2.154459e-002, 2.217383e-002, 2.276094e-002, 2.327822e-002, 2.352310e-002, + 2.370586e-002, 2.398790e-002, 2.436696e-002, 2.459088e-002, 2.467673e-002, 2.485049e-002, 2.487367e-002, 2.447821e-002, 2.417875e-002, + 2.377879e-002, 2.299162e-002, 2.185145e-002, 2.032041e-002, 1.863735e-002, 1.653366e-002, 1.419286e-002, 1.180981e-002, 9.317537e-003, + 6.749421e-003, 4.055280e-003, 1.357940e-003, -1.281496e-003, -4.161932e-003, -6.926867e-003, -9.448191e-003, -1.177807e-002, -1.358487e-002, + -1.520972e-002, -1.654234e-002, -1.753675e-002, -1.846182e-002, -1.911281e-002, -1.888710e-002, -1.808285e-002, -1.712806e-002, -1.613268e-002, + -1.494508e-002, -1.364783e-002, -1.235170e-002, -1.107139e-002, -9.711666e-003, -8.429320e-003, -7.124912e-003, -5.589019e-003, -3.905409e-003, + -2.196126e-003, -3.586341e-004, 1.586919e-003, 2.722414e-003, 3.098131e-003, 3.316094e-003, 3.332841e-003, 3.213552e-003, 3.178998e-003, + 3.364780e-003, 3.439043e-003, 3.404151e-003, 3.610437e-003, 4.061402e-003, 4.446161e-003, 5.030620e-003, 5.955267e-003, 6.882735e-003, + 7.661038e-003, 8.398200e-003, 9.215394e-003, 9.814869e-003, 1.068802e-002, 1.164980e-002, 1.234742e-002, 1.293027e-002, 1.321345e-002, + 1.358017e-002, 1.360308e-002, 1.332827e-002, 1.292256e-002, 1.253662e-002, 1.193453e-002, 1.097354e-002, 9.608262e-003, 7.727396e-003, + 5.181497e-003, 2.498065e-003, 8.392901e-005, -2.753614e-003, -5.874013e-003, -9.066884e-003, -1.221922e-002, -1.530662e-002, -1.841245e-002, + -2.134858e-002, -2.361042e-002, -2.593328e-002, -2.841748e-002, -3.068935e-002, -3.268556e-002, -3.460273e-002, -3.637718e-002, -3.784616e-002, + -3.897207e-002, -3.989913e-002, -4.084184e-002, -4.161193e-002, -4.224743e-002, -4.272912e-002, -4.332241e-002, -4.402182e-002, -4.452376e-002, + -4.506558e-002, -4.528480e-002, -4.546102e-002, -4.559239e-002, -4.611191e-002, -4.668735e-002, -4.709852e-002, -4.723655e-002, -4.745577e-002, + -4.775760e-002, -4.783295e-002, -4.757291e-002, -4.731658e-002, -4.693088e-002, -4.636473e-002, -4.527026e-002, -4.392313e-002, -4.232921e-002, + -4.058036e-002, -3.830428e-002, -3.563550e-002, -3.252757e-002, -2.947177e-002, -2.609116e-002, -2.252321e-002, -1.882878e-002, -1.536624e-002, + -1.178465e-002, -7.949011e-003, -4.070393e-003, -2.169144e-004, 3.653688e-003, 7.723708e-003, 1.168033e-002, 1.546843e-002, 1.906044e-002, + 2.267620e-002, 2.635240e-002, 2.999622e-002, 3.326988e-002, 3.609206e-002, 3.854707e-002, 4.039571e-002, 4.218781e-002, 4.365848e-002, + 4.492490e-002, 4.587969e-002, 4.670635e-002, 4.746898e-002, 4.822103e-002, 4.922597e-002, 5.035996e-002, 5.168701e-002, 5.302462e-002, + 5.431828e-002, 5.574555e-002, 5.716833e-002, 5.851182e-002, 5.970190e-002, 6.062621e-002, 6.131199e-002, 6.187983e-002, 6.242391e-002, + 6.307774e-002, 6.358222e-002, 6.392120e-002, 6.404110e-002, 6.397023e-002, 6.414935e-002, 6.390093e-002, 6.311954e-002, 6.182869e-002, + 6.025462e-002, 5.846745e-002, 5.627545e-002, 5.370097e-002, 5.086212e-002, 4.779427e-002, 4.450851e-002, 4.093421e-002, 3.710409e-002, + 3.311468e-002, 2.955969e-002, 2.605301e-002, 2.247117e-002, 1.871255e-002, 1.477813e-002, 1.093143e-002, 7.142533e-003, 3.338119e-003, + -4.218725e-004, -4.152341e-003, -7.620255e-003, -1.079695e-002, -1.377148e-002, -1.649072e-002, -1.882488e-002, -2.069452e-002, -2.226946e-002, + -2.359193e-002, -2.482650e-002, -2.621025e-002, -2.742695e-002, -2.847715e-002, -2.931658e-002, -3.001557e-002, -3.069451e-002, -3.125401e-002, + -3.177524e-002, -3.219790e-002, -3.247020e-002, -3.272664e-002, -3.279779e-002, -3.263522e-002, -3.244683e-002, -3.222611e-002, -3.213730e-002, + -3.209843e-002, -3.227508e-002, -3.256853e-002, -3.275860e-002, -3.273513e-002, -3.249075e-002, -3.201190e-002, -3.153708e-002, -3.095856e-002, + -3.013429e-002, -2.898258e-002, -2.755313e-002, -2.572948e-002, -2.360818e-002, -2.141612e-002, -1.911493e-002, -1.679410e-002, -1.461674e-002, + -1.256759e-002, -1.060445e-002, -8.690035e-003, -6.837316e-003, -5.127986e-003, -3.560941e-003, -2.282790e-003, -1.246488e-003, -2.973902e-004, + 6.036451e-004, 1.307093e-003, 1.665978e-003, 1.747005e-003, 1.506567e-003, 9.812409e-004, 2.203542e-004, -6.531253e-004, -1.681214e-003, + -2.734614e-003, -3.757117e-003, -4.544129e-003, -4.963993e-003, -4.966266e-003, -4.609048e-003, -4.097634e-003, -3.432080e-003, -2.618152e-003, + -1.660368e-003, -7.237833e-004, 6.128496e-005, 7.160381e-004, 1.312671e-003, 2.144561e-003, 3.002123e-003, 3.878402e-003, 4.715605e-003, + 5.403221e-003, 5.927875e-003, 6.323630e-003, 6.462751e-003, 6.248945e-003, 5.646920e-003, 4.782494e-003, 3.694970e-003, 2.381798e-003, + 1.009759e-003, -3.573277e-004, -1.749128e-003, -3.192651e-003, -4.741605e-003, -6.371721e-003, -8.005634e-003, -9.490331e-003, -1.071508e-002, + -1.170148e-002, -1.247470e-002, -1.319494e-002, -1.387452e-002, -1.429349e-002, -1.441293e-002, -1.413480e-002, -1.355052e-002, -1.277048e-002, + -1.187251e-002, -1.086824e-002, -9.790924e-003, -8.634118e-003, -7.568894e-003, -6.702131e-003, -6.024374e-003, -5.571644e-003, -5.145867e-003, + -4.660619e-003, -4.168417e-003, -3.612648e-003, -2.891409e-003, -2.164632e-003, -1.474701e-003, -1.017929e-003, -8.793184e-004, -1.119031e-003, + -1.625717e-003, -2.389485e-003, -3.264484e-003, -4.157508e-003, -5.140339e-003, -5.965895e-003, -6.638305e-003, -7.134637e-003, -7.518238e-003, + -7.531199e-003, -7.281284e-003, -6.756853e-003, -6.034889e-003, -5.124576e-003, -4.092030e-003, -2.867205e-003, -1.451115e-003, 2.239897e-004, + 2.110914e-003, 4.097981e-003, 6.129396e-003, 8.113317e-003, 9.938029e-003, 1.170526e-002, 1.338156e-002, 1.488051e-002, 1.625691e-002, + 1.735182e-002, 1.821789e-002, 1.884886e-002, 1.927077e-002, 1.943597e-002, 1.930072e-002, 1.876736e-002, 1.770444e-002, 1.630472e-002, + 1.469489e-002, 1.287807e-002, 1.086195e-002, 8.783380e-003, 6.777411e-003, 4.842621e-003, 2.984875e-003, 1.441480e-003, 9.822667e-005, + -9.469432e-004, -1.556145e-003, -1.622938e-003, -1.253699e-003, -6.320784e-004, 1.547057e-004, 1.161875e-003, 2.363016e-003, 3.635548e-003, + 4.995336e-003, 6.441811e-003, 7.925154e-003, 9.416030e-003, 1.112582e-002, 1.294499e-002, 1.477188e-002, 1.673515e-002, 1.875369e-002, + 2.061192e-002, 2.226624e-002, 2.370853e-002, 2.478138e-002, 2.551645e-002, 2.603810e-002, 2.642191e-002, 2.662482e-002, 2.660716e-002, + 2.629400e-002, 2.560136e-002, 2.468079e-002, 2.366704e-002, 2.259868e-002, 2.147261e-002, 2.027612e-002, 1.906563e-002, 1.796267e-002, + 1.695637e-002, 1.601241e-002, 1.507960e-002, 1.423999e-002, 1.361573e-002, 1.327799e-002, 1.318320e-002, 1.335855e-002, 1.365530e-002, + 1.403293e-002, 1.446425e-002, 1.491372e-002, 1.518441e-002, 1.508684e-002, 1.482889e-002, 1.460668e-002, 1.439235e-002, 1.421736e-002, + 1.418394e-002, 1.423821e-002, 1.396676e-002, 1.354445e-002, 1.298255e-002, 1.212716e-002, 1.097619e-002, 9.402749e-003, 7.573798e-003, + 5.595428e-003, 3.515146e-003, 1.485177e-003, -4.882044e-004, -2.362990e-003, -4.052034e-003, -5.482022e-003, -6.651650e-003, -7.596364e-003, + -8.507816e-003, -9.470378e-003, -1.028909e-002, -1.093851e-002, -1.143958e-002, -1.179755e-002, -1.208581e-002, -1.236813e-002, -1.272414e-002, + -1.309716e-002, -1.345903e-002, -1.385009e-002, -1.428607e-002, -1.479398e-002, -1.527407e-002, -1.575459e-002, -1.633419e-002, -1.703189e-002, + -1.787018e-002, -1.887978e-002, -2.003423e-002, -2.136734e-002, -2.269959e-002, -2.400660e-002, -2.528676e-002, -2.652036e-002, -2.762633e-002, + -2.855865e-002, -2.922374e-002, -2.966257e-002, -2.971810e-002, -2.947755e-002, -2.904298e-002, -2.847870e-002, -2.780494e-002, -2.710601e-002, + -2.637022e-002, -2.562690e-002, -2.505907e-002, -2.454080e-002, -2.409288e-002, -2.375043e-002, -2.345757e-002, -2.311544e-002, -2.272731e-002, + -2.229195e-002, -2.188252e-002, -2.141581e-002, -2.111549e-002, -2.106495e-002, -2.132958e-002, -2.186872e-002, -2.253665e-002, -2.326625e-002, + -2.399170e-002, -2.463249e-002, -2.508957e-002, -2.535475e-002, -2.552324e-002, -2.543599e-002, -2.506226e-002, -2.446855e-002, -2.356986e-002, + -2.241903e-002, -2.114630e-002, -1.971796e-002, -1.818763e-002, -1.663143e-002, -1.495635e-002, -1.309890e-002, -1.118190e-002, -9.284025e-003, + -7.421051e-003, -5.629939e-003, -3.948830e-003, -2.429027e-003, -1.061970e-003, 2.193347e-004, 1.338235e-003, 2.240275e-003, 2.909313e-003, + 3.341588e-003, 3.507632e-003, 3.431855e-003, 2.972452e-003, 2.230958e-003, 1.300598e-003, 1.916446e-004, -1.096381e-003, -2.459304e-003, + -3.894535e-003, -5.393157e-003, -6.819771e-003, -8.182386e-003, -9.446314e-003, -1.037507e-002, -1.090151e-002, -1.082440e-002, -1.025120e-002, + -9.172517e-003, -7.938720e-003, -6.504267e-003, -5.105796e-003, -3.747840e-003, -2.432724e-003, -1.143524e-003, 1.246597e-004, 1.377356e-003, + 2.640967e-003, 4.002917e-003, 5.459114e-003, 6.838093e-003, 8.104481e-003, 9.337010e-003, 1.050151e-002, 1.142605e-002, 1.202659e-002, + 1.226805e-002, 1.219793e-002, 1.191685e-002, 1.143601e-002, 1.088010e-002, 1.042566e-002, 1.008632e-002, 9.938196e-003, 1.003155e-002, + 1.038871e-002, 1.101200e-002, 1.182630e-002, 1.280232e-002, 1.389360e-002, 1.515404e-002, 1.652723e-002, 1.800978e-002, 1.957076e-002, + 2.120760e-002, 2.293185e-002, 2.460919e-002, 2.622679e-002, 2.760060e-002, 2.880274e-002, 2.991452e-002, 3.087578e-002, 3.175920e-002, + 3.247300e-002, 3.299391e-002, 3.326776e-002, 3.321213e-002, 3.278646e-002, 3.195643e-002, 3.073559e-002, 2.922576e-002, 2.752823e-002, + 2.569974e-002, 2.371693e-002, 2.170333e-002, 1.964248e-002, 1.757875e-002, 1.543505e-002, 1.332448e-002, 1.135122e-002, 9.568384e-003, + 8.017653e-003, 6.721457e-003, 5.781147e-003, 5.137044e-003, 4.789883e-003, 4.564974e-003, 4.333416e-003, 4.154308e-003, 4.117791e-003, + 4.174322e-003, 4.336080e-003, 4.586243e-003, 4.934014e-003, 5.360635e-003, 5.687154e-003, 5.939883e-003, 6.078859e-003, 6.193818e-003, + 6.233641e-003, 6.230615e-003, 6.171828e-003, 6.037512e-003, 5.889675e-003, 5.794554e-003, 5.789711e-003, 5.835376e-003, 5.987347e-003, + 6.108669e-003, 6.164964e-003, 6.139106e-003, 6.042786e-003, 5.886521e-003, 5.720929e-003, 5.610427e-003, 5.596352e-003, 5.689164e-003, + 5.873301e-003, 6.044754e-003, 6.199628e-003, 6.303955e-003, 6.357360e-003, 6.267259e-003, 6.137201e-003, 5.919732e-003, 5.632784e-003, + 5.270683e-003, 4.763314e-003, 4.151010e-003, 3.381284e-003, 2.459789e-003, 1.415645e-003, 2.846581e-004, -9.016040e-004, -2.072679e-003, + -3.195387e-003, -4.372159e-003, -5.493731e-003, -6.489536e-003, -7.410036e-003, -8.249778e-003, -9.020135e-003, -9.679026e-003, -1.024993e-002, + -1.059533e-002, -1.069940e-002, -1.057291e-002, -1.037040e-002, -1.008951e-002, -9.803875e-003, -9.588151e-003, -9.352192e-003, -9.079449e-003, + -8.737069e-003, -8.270414e-003, -7.721555e-003, -7.106008e-003, -6.552974e-003, -6.071447e-003, -5.627334e-003, -5.260632e-003, -4.940564e-003, + -4.680510e-003, -4.493115e-003, -4.518905e-003, -4.674544e-003, -4.982171e-003, -5.456682e-003, -6.227990e-003, -7.194331e-003, -8.369432e-003, + -9.673612e-003, -1.104694e-002, -1.238453e-002, -1.370461e-002, -1.498773e-002, -1.617976e-002, -1.726968e-002, -1.835257e-002, -1.944410e-002, + -2.039484e-002, -2.120457e-002, -2.177195e-002, -2.207613e-002, -2.203223e-002, -2.166245e-002, -2.106259e-002, -2.032756e-002, -1.951773e-002, + -1.870273e-002, -1.789780e-002, -1.709755e-002, -1.626338e-002, -1.553065e-002, -1.478031e-002, -1.399874e-002, -1.316922e-002, -1.227253e-002, + -1.133702e-002, -1.044539e-002, -9.706953e-003, -9.170190e-003, -8.882478e-003, -8.908699e-003, -9.208948e-003, -9.718379e-003, -1.033312e-002, + -1.101671e-002, -1.187972e-002, -1.258727e-002, -1.310278e-002, -1.339329e-002, -1.341135e-002, -1.308742e-002, -1.263665e-002, -1.201478e-002, + -1.118051e-002, -1.006663e-002, -8.698361e-003, -7.000645e-003, -5.014099e-003, -2.661863e-003, -9.504265e-017}, + {0.000000e+000, -2.469714e-002, -2.473741e-002, -2.405222e-002, -3.741649e-003, 2.191523e-002, 3.004823e-002, 3.490736e-002, 3.573678e-002, + 3.198222e-002, 2.153496e-002, 5.702981e-003, -1.289214e-002, -2.994308e-002, -3.871065e-002, -3.894194e-002, -3.340419e-002, -2.426176e-002, + -1.466523e-002, -3.085266e-003, 7.732097e-003, 1.503688e-002, 1.859290e-002, 1.864447e-002, 1.710718e-002, 1.421894e-002, 1.018549e-002, + 5.587017e-003, 1.192989e-003, -1.994035e-003, -4.262802e-003, -6.295929e-003, -7.636204e-003, -8.170831e-003, -8.616133e-003, -8.658765e-003, + -8.173853e-003, -6.305262e-003, -3.508473e-003, 4.313680e-005, 4.071673e-003, 8.678984e-003, 1.303724e-002, 1.668072e-002, 1.909616e-002, + 1.994462e-002, 1.870585e-002, 1.551111e-002, 1.160449e-002, 7.490275e-003, 4.442047e-003, 2.945680e-003, 2.356617e-003, 2.250466e-003, + 2.092284e-003, 1.829290e-003, 1.088032e-003, -1.959431e-004, -1.711074e-003, -4.201147e-003, -7.821432e-003, -1.258203e-002, -1.716226e-002, + -2.097909e-002, -2.367560e-002, -2.557019e-002, -2.671241e-002, -2.731694e-002, -2.703611e-002, -2.607524e-002, -2.432872e-002, -2.218602e-002, + -1.956096e-002, -1.642157e-002, -1.306824e-002, -9.128684e-003, -5.022810e-003, -9.088909e-004, 3.112213e-003, 7.408376e-003, 1.207519e-002, + 1.677878e-002, 2.180218e-002, 2.659783e-002, 3.138106e-002, 3.598792e-002, 3.984319e-002, 4.272506e-002, 4.433416e-002, 4.461072e-002, + 4.327752e-002, 4.044258e-002, 3.630138e-002, 3.116601e-002, 2.491316e-002, 1.780005e-002, 9.515282e-003, 2.064248e-004, -9.068661e-003, + -1.837233e-002, -2.730946e-002, -3.490568e-002, -4.063500e-002, -4.392272e-002, -4.557725e-002, -4.587873e-002, -4.485252e-002, -4.275832e-002, + -4.008398e-002, -3.694630e-002, -3.304749e-002, -2.867330e-002, -2.397167e-002, -1.852278e-002, -1.271055e-002, -6.908028e-003, -9.235115e-004, + 4.936529e-003, 1.046311e-002, 1.555157e-002, 1.956183e-002, 2.223251e-002, 2.328826e-002, 2.283432e-002, 2.121724e-002, 1.820797e-002, + 1.445283e-002, 1.005728e-002, 5.757214e-003, 1.708169e-003, -2.368862e-003, -5.969397e-003, -8.953819e-003, -1.091631e-002, -1.153879e-002, + -1.093272e-002, -9.583846e-003, -7.227005e-003, -4.264415e-003, -6.332222e-004, 3.563772e-003, 8.025719e-003, 1.287193e-002, 1.782613e-002, + 2.253753e-002, 2.677626e-002, 3.036717e-002, 3.329387e-002, 3.537673e-002, 3.672864e-002, 3.757941e-002, 3.725171e-002, 3.591777e-002, + 3.347496e-002, 2.979634e-002, 2.495512e-002, 1.884777e-002, 1.194969e-002, 4.639894e-003, -2.832655e-003, -1.030999e-002, -1.717243e-002, + -2.310889e-002, -2.816440e-002, -3.175951e-002, -3.384981e-002, -3.451982e-002, -3.438689e-002, -3.356070e-002, -3.223152e-002, -3.060941e-002, + -2.874502e-002, -2.639277e-002, -2.348729e-002, -1.991079e-002, -1.568495e-002, -1.065097e-002, -5.328739e-003, 3.436562e-004, 6.088442e-003, + 1.190536e-002, 1.717460e-002, 2.168178e-002, 2.516354e-002, 2.763609e-002, 2.888219e-002, 2.930595e-002, 2.878452e-002, 2.710643e-002, + 2.461488e-002, 2.113023e-002, 1.695251e-002, 1.212703e-002, 6.948012e-003, 1.508608e-003, -3.814585e-003, -8.648795e-003, -1.315810e-002, + -1.760640e-002, -2.184816e-002, -2.575361e-002, -2.903948e-002, -3.116364e-002, -3.165045e-002, -3.033117e-002, -2.780772e-002, -2.431087e-002, + -2.053059e-002, -1.683747e-002, -1.331233e-002, -1.008198e-002, -7.237729e-003, -4.673095e-003, -1.874590e-003, 1.184268e-003, 4.574012e-003, + 8.403873e-003, 1.233318e-002, 1.643578e-002, 2.011707e-002, 2.310985e-002, 2.494469e-002, 2.490363e-002, 2.320294e-002, 2.020001e-002, + 1.665536e-002, 1.270204e-002, 8.612392e-003, 4.478917e-003, 1.571016e-004, -4.365922e-003, -9.130007e-003, -1.384645e-002, -1.828565e-002, + -2.235556e-002, -2.575146e-002, -2.834808e-002, -3.020620e-002, -3.106271e-002, -3.114853e-002, -3.020945e-002, -2.813767e-002, -2.501593e-002, + -2.099232e-002, -1.602425e-002, -1.064933e-002, -5.045817e-003, 1.254503e-004, 4.967998e-003, 9.552591e-003, 1.366735e-002, 1.710611e-002, + 1.998054e-002, 2.231760e-002, 2.432907e-002, 2.600744e-002, 2.726502e-002, 2.819859e-002, 2.863906e-002, 2.822755e-002, 2.653581e-002, + 2.294667e-002, 1.778420e-002, 1.143211e-002, 4.104702e-003, -4.014319e-003, -1.202931e-002, -1.958348e-002, -2.631378e-002, -3.174873e-002, + -3.564200e-002, -3.743810e-002, -3.769528e-002, -3.679272e-002, -3.530662e-002, -3.303707e-002, -3.029167e-002, -2.702925e-002, -2.327617e-002, + -1.888226e-002, -1.401580e-002, -8.596156e-003, -2.685436e-003, 3.539384e-003, 1.005315e-002, 1.680412e-002, 2.360350e-002, 3.004834e-002, + 3.575538e-002, 4.032734e-002, 4.376997e-002, 4.607003e-002, 4.731562e-002, 4.740315e-002, 4.663758e-002, 4.480173e-002, 4.201254e-002, + 3.841397e-002, 3.402428e-002, 2.922936e-002, 2.436055e-002, 1.965072e-002, 1.519454e-002, 1.091344e-002, 6.779922e-003, 2.651729e-003, + -1.142556e-003, -4.355308e-003, -6.874424e-003, -8.187367e-003, -8.004693e-003, -6.653978e-003, -4.675131e-003, -2.412549e-003, -1.801794e-004, + 1.960451e-003, 3.827486e-003, 5.336422e-003, 6.455668e-003, 7.130686e-003, 7.704794e-003, 8.426043e-003, 9.196559e-003, 9.846271e-003, + 1.034433e-002, 1.058532e-002, 1.013297e-002, 8.621587e-003, 5.706139e-003, 1.455167e-003, -4.188250e-003, -1.052945e-002, -1.721039e-002, + -2.394485e-002, -3.058344e-002, -3.662466e-002, -4.184156e-002, -4.620814e-002, -4.926624e-002, -5.120101e-002, -5.182468e-002, -5.133663e-002, + -4.946614e-002, -4.645581e-002, -4.243800e-002, -3.769361e-002, -3.224662e-002, -2.612433e-002, -1.953594e-002, -1.264116e-002, -5.611731e-003, + 1.309327e-003, 7.759884e-003, 1.360613e-002, 1.873400e-002, 2.334811e-002, 2.703138e-002, 2.983114e-002, 3.161283e-002, 3.257043e-002, + 3.243703e-002, 3.129269e-002, 2.910848e-002, 2.596110e-002, 2.211607e-002, 1.783682e-002, 1.363941e-002, 9.775343e-003, 6.289424e-003, + 3.168244e-003, 5.442142e-004, -1.432615e-003, -2.882379e-003, -3.757345e-003, -4.019791e-003, -3.602201e-003, -2.572118e-003, -9.670831e-004, + 9.029681e-004, 2.872666e-003, 4.880329e-003, 7.085902e-003, 9.357564e-003, 1.165989e-002, 1.398067e-002, 1.630992e-002, 1.856286e-002, + 2.061765e-002, 2.214967e-002, 2.288716e-002, 2.274893e-002, 2.155210e-002, 1.984837e-002, 1.773392e-002, 1.526252e-002, 1.250028e-002, + 9.520906e-003, 6.080876e-003, 2.117947e-003, -2.154828e-003, -6.470547e-003, -1.082293e-002, -1.511478e-002, -1.901925e-002, -2.252049e-002, + -2.537071e-002, -2.770976e-002, -2.932445e-002, -3.033750e-002, -3.050300e-002, -3.011630e-002, -2.915869e-002, -2.780598e-002, -2.603283e-002, + -2.395820e-002, -2.171752e-002, -1.941299e-002, -1.700557e-002, -1.478619e-002, -1.284328e-002, -1.111396e-002, -9.706651e-003, -8.524213e-003, + -7.608755e-003, -6.815194e-003, -6.397150e-003, -6.352473e-003, -7.017941e-003, -8.643011e-003, -1.161232e-002, -1.560416e-002, -2.014740e-002, + -2.488373e-002, -2.963286e-002, -3.410267e-002, -3.790953e-002, -4.083287e-002, -4.222920e-002, -4.184246e-002, -3.988442e-002, -3.704735e-002, + -3.362409e-002, -3.007321e-002, -2.650582e-002, -2.295975e-002, -1.922940e-002, -1.514926e-002, -1.058975e-002, -5.348284e-003, 5.125951e-004, + 6.877922e-003, 1.373155e-002, 2.097317e-002, 2.830371e-002, 3.544873e-002, 4.198773e-002, 4.748402e-002, 5.180024e-002, 5.471164e-002, + 5.658613e-002, 5.737312e-002, 5.727197e-002, 5.622846e-002, 5.430131e-002, 5.140708e-002, 4.792081e-002, 4.392795e-002, 3.975572e-002, + 3.543148e-002, 3.099325e-002, 2.642971e-002, 2.175544e-002, 1.688887e-002, 1.171612e-002, 6.227816e-003, 5.519335e-004, -5.072114e-003, + -1.046543e-002, -1.551374e-002, -1.999188e-002, -2.362500e-002, -2.643011e-002, -2.782810e-002, -2.796351e-002, -2.697521e-002, -2.543845e-002, + -2.346073e-002, -2.116862e-002, -1.848509e-002, -1.557067e-002, -1.227826e-002, -8.675187e-003, -4.761031e-003, -4.820634e-004, 3.906117e-003, + 8.156725e-003, 1.215343e-002, 1.572166e-002, 1.891351e-002, 2.172695e-002, 2.402011e-002, 2.574990e-002, 2.674857e-002, 2.698458e-002, + 2.647294e-002, 2.525541e-002, 2.335007e-002, 2.088851e-002, 1.799333e-002, 1.499020e-002, 1.216037e-002, 9.681682e-003, 7.817044e-003, + 6.748233e-003, 6.583826e-003, 7.596197e-003, 9.844509e-003, 1.315159e-002, 1.710790e-002, 2.134541e-002, 2.553117e-002, 2.967176e-002, + 3.352215e-002, 3.695839e-002, 4.001679e-002, 4.261101e-002, 4.475191e-002, 4.648808e-002, 4.778138e-002, 4.860870e-002, 4.909802e-002, + 4.895594e-002, 4.811304e-002, 4.619319e-002, 4.286583e-002, 3.757626e-002, 3.078569e-002, 2.270371e-002, 1.387770e-002, 4.779164e-003, + -4.092503e-003, -1.223514e-002, -1.943952e-002, -2.501403e-002, -2.890377e-002, -3.074296e-002, -3.146610e-002, -3.118263e-002, -3.031050e-002, + -2.902008e-002, -2.750036e-002, -2.575038e-002, -2.389904e-002, -2.210426e-002, -2.036294e-002, -1.863248e-002, -1.669441e-002, -1.452353e-002, + -1.226031e-002, -1.015735e-002, -8.429232e-003, -7.551904e-003, -7.805462e-003, -9.591092e-003, -1.251954e-002, -1.624616e-002, -2.042288e-002, + -2.489127e-002, -2.961188e-002, -3.457365e-002, -3.974123e-002, -4.515161e-002, -5.066904e-002, -5.605736e-002, -6.114674e-002, -6.562307e-002, + -6.922415e-002, -7.189946e-002, -7.361066e-002, -7.433874e-002, -7.405536e-002, -7.270721e-002, -7.012071e-002, -6.611841e-002, -6.100698e-002, + -5.502489e-002, -4.868240e-002, -4.232679e-002, -3.612787e-002, -3.028428e-002, -2.487482e-002, -1.998584e-002, -1.574838e-002, -1.197147e-002, + -8.556320e-003, -5.313683e-003, -2.056393e-003, 1.300921e-003, 4.759251e-003, 7.977133e-003, 1.041292e-002, 1.159322e-002, 1.095794e-002, + 8.868775e-003, 5.720589e-003, 1.987927e-003, -2.142447e-003, -6.294556e-003, -1.019883e-002, -1.354720e-002, -1.568827e-002, -1.662010e-002, + -1.626665e-002, -1.495183e-002, -1.263052e-002, -9.424037e-003, -5.214581e-003, -3.637457e-004, 5.184059e-003, 1.123331e-002, 1.783394e-002, + 2.477580e-002, 3.196704e-002, 3.925451e-002, 4.636777e-002, 5.324160e-002, 5.970919e-002, 6.560096e-002, 7.076938e-002, 7.526375e-002, + 7.894620e-002, 8.173836e-002, 8.338935e-002, 8.389413e-002, 8.295499e-002, 8.052404e-002, 7.633860e-002, 7.064882e-002, 6.368782e-002, + 5.574961e-002, 4.712387e-002, 3.798304e-002, 2.847083e-002, 1.876894e-002, 9.199882e-003, 8.134982e-005, -8.292884e-003, -1.578281e-002, + -2.208553e-002, -2.686247e-002, -2.986153e-002, -3.109736e-002, -3.103209e-002, -3.000446e-002, -2.835409e-002, -2.610663e-002, -2.350317e-002, + -2.038541e-002, -1.674303e-002, -1.252272e-002, -7.866186e-003, -2.851779e-003, 2.492958e-003, 8.033557e-003, 1.367719e-002, 1.931524e-002, + 2.477433e-002, 2.961346e-002, 3.353311e-002, 3.612810e-002, 3.755714e-002, 3.776986e-002, 3.696566e-002, 3.494299e-002, 3.198361e-002, + 2.795162e-002, 2.309764e-002, 1.740483e-002, 1.108238e-002, 4.356670e-003, -2.321363e-003, -8.611773e-003, -1.452709e-002, -2.007548e-002, + -2.534522e-002, -3.017915e-002, -3.470007e-002, -3.849996e-002, -4.131858e-002, -4.254531e-002, -4.249640e-002, -4.128506e-002, -3.936655e-002, + -3.702202e-002, -3.448186e-002, -3.190818e-002, -2.934765e-002, -2.684640e-002, -2.460685e-002, -2.221496e-002, -1.964941e-002, -1.697355e-002, + -1.424889e-002, -1.160120e-002, -9.247429e-003, -7.441464e-003, -6.361837e-003, -6.528129e-003, -8.050804e-003, -1.102172e-002, -1.492772e-002, + -1.948966e-002, -2.436264e-002, -2.923087e-002, -3.383541e-002, -3.788422e-002, -4.107220e-002, -4.299821e-002, -4.374500e-002, -4.370206e-002, + -4.299503e-002, -4.176135e-002, -4.030828e-002, -3.855888e-002, -3.641545e-002, -3.388490e-002, -3.092344e-002, -2.770441e-002, -2.423872e-002, + -2.068526e-002, -1.718202e-002, -1.392786e-002, -1.103860e-002, -8.474431e-003, -6.318499e-003, -4.599155e-003, -3.422108e-003, -2.864479e-003, + -3.108889e-003, -4.030147e-003, -5.601586e-003, -7.665182e-003, -1.009093e-002, -1.265057e-002, -1.499711e-002, -1.699155e-002, -1.836148e-002, + -1.901478e-002, -1.855544e-002, -1.709955e-002, -1.429007e-002, -1.041143e-002, -5.460287e-003, -1.261665e-004, 5.386747e-003, 1.089976e-002, + 1.626002e-002, 2.136863e-002, 2.598781e-002, 3.025222e-002, 3.411378e-002, 3.765520e-002, 4.092193e-002, 4.407766e-002, 4.711565e-002, + 5.002545e-002, 5.253883e-002, 5.441827e-002, 5.516846e-002, 5.452273e-002, 5.231446e-002, 4.903327e-002, 4.494703e-002, 4.041311e-002, + 3.570318e-002, 3.118171e-002, 2.717054e-002, 2.386149e-002, 2.160893e-002, 2.049939e-002, 2.038334e-002, 2.116251e-002, 2.286725e-002, + 2.550860e-002, 2.894125e-002, 3.308738e-002, 3.792023e-002, 4.331846e-002, 4.918008e-002, 5.532373e-002, 6.159011e-002, 6.779338e-002, + 7.374011e-002, 7.924353e-002, 8.423250e-002, 8.856909e-002, 9.221454e-002, 9.504767e-002, 9.707952e-002, 9.807293e-002, 9.799350e-002, + 9.672070e-002, 9.415504e-002, 9.002348e-002, 8.450535e-002, 7.751431e-002, 6.940370e-002, 6.022578e-002, 5.033211e-002, 3.981972e-002, + 2.890956e-002, 1.780620e-002, 6.837274e-003, -3.649866e-003, -1.346158e-002, -2.202409e-002, -2.927461e-002, -3.418701e-002, -3.712054e-002, + -3.801048e-002, -3.799177e-002, -3.726162e-002, -3.619610e-002, -3.506033e-002, -3.400738e-002, -3.308499e-002, -3.234542e-002, -3.170849e-002, + -3.109412e-002, -3.025806e-002, -2.916112e-002, -2.775150e-002, -2.622987e-002, -2.484602e-002, -2.393411e-002, -2.409971e-002, -2.573158e-002, + -2.916639e-002, -3.379603e-002, -3.916467e-002, -4.486303e-002, -5.070799e-002, -5.639171e-002, -6.160724e-002, -6.602924e-002, -6.921359e-002, + -7.122981e-002, -7.199898e-002, -7.168501e-002, -7.024902e-002, -6.776982e-002, -6.444134e-002, -6.037314e-002, -5.562864e-002, -5.029028e-002, + -4.437816e-002, -3.802252e-002, -3.145502e-002, -2.485232e-002, -1.840639e-002, -1.235996e-002, -6.777109e-003, -1.772432e-003, 2.620537e-003, + 6.263084e-003, 9.094347e-003, 1.090423e-002, 1.170086e-002, 1.140007e-002, 1.000376e-002, 7.240458e-003, 3.183850e-003, -1.888138e-003, + -7.616923e-003, -1.385779e-002, -2.034156e-002, -2.700623e-002, -3.368848e-002, -4.017702e-002, -4.643075e-002, -5.220429e-002, -5.740459e-002, + -6.166947e-002, -6.508270e-002, -6.702545e-002, -6.779592e-002, -6.742814e-002, -6.647366e-002, -6.500989e-002, -6.315287e-002, -6.095275e-002, + -5.846067e-002, -5.555226e-002, -5.228052e-002, -4.860444e-002, -4.471134e-002, -4.066064e-002, -3.656729e-002, -3.265045e-002, -2.903471e-002, + -2.584128e-002, -2.297362e-002, -2.040460e-002, -1.796517e-002, -1.557589e-002, -1.311999e-002, -1.026399e-002, -6.838540e-003, -3.021240e-003, + 8.577324e-004, 4.517220e-003, 7.792295e-003, 1.059834e-002, 1.309582e-002, 1.548577e-002, 1.807547e-002, 2.106829e-002, 2.450881e-002, + 2.821024e-002, 3.197372e-002, 3.565061e-002, 3.905510e-002, 4.210720e-002, 4.478590e-002, 4.702872e-002, 4.874047e-002, 4.982489e-002, + 5.008851e-002, 4.962144e-002, 4.843749e-002, 4.653002e-002, 4.392504e-002, 4.065179e-002, 3.687196e-002, 3.284988e-002, 2.874829e-002, + 2.468267e-002, 2.080937e-002, 1.727332e-002, 1.404233e-002, 1.108063e-002, 8.401815e-003, 5.919254e-003, 3.778594e-003, 2.106801e-003, + 1.245779e-003, 1.378594e-003, 2.542308e-003, 4.343873e-003, 6.437735e-003, 8.610165e-003, 1.076727e-002, 1.291231e-002, 1.512545e-002, + 1.763357e-002, 2.059015e-002, 2.385508e-002, 2.733745e-002, 3.091171e-002, 3.446772e-002, 3.781009e-002, 4.082006e-002, 4.347937e-002, + 4.566470e-002, 4.742892e-002, 4.858360e-002, 4.916511e-002, 4.900863e-002, 4.814129e-002, 4.638987e-002, 4.362059e-002, 3.988572e-002, + 3.532695e-002, 2.990024e-002, 2.397584e-002, 1.788353e-002, 1.186827e-002, 6.180271e-003, 8.880984e-004, -3.978949e-003, -8.481564e-003, + -1.262243e-002, -1.627360e-002, -1.929416e-002, -2.138942e-002, -2.203095e-002, -2.130047e-002, -1.960012e-002, -1.735257e-002, -1.482877e-002, + -1.222444e-002, -9.705679e-003, -7.346903e-003, -5.179196e-003, -2.998641e-003, -7.377860e-004, 1.586075e-003, 3.984716e-003, 6.592354e-003, + 9.220608e-003, 1.159025e-002, 1.343546e-002, 1.426936e-002, 1.371354e-002, 1.144513e-002, 7.906687e-003, 3.383170e-003, -1.721122e-003, + -7.078401e-003, -1.232085e-002, -1.725658e-002, -2.157435e-002, -2.485614e-002, -2.723712e-002, -2.853628e-002, -2.936226e-002, -2.956426e-002, + -2.892067e-002, -2.713533e-002, -2.432830e-002, -2.018329e-002, -1.487482e-002, -8.065705e-003, -4.364645e-016}, + {0.000000e+000, 2.809420e-003, -1.018166e-002, -1.607233e-002, -6.531790e-003, 9.806124e-003, 1.547079e-002, 1.789322e-002, 2.090920e-002, + 2.277750e-002, 2.272582e-002, 2.096861e-002, 1.861513e-002, 1.398930e-002, 9.723537e-003, 6.832688e-003, 4.442118e-003, -3.286288e-003, + -1.543934e-002, -2.795253e-002, -3.955612e-002, -4.810746e-002, -5.207969e-002, -5.463485e-002, -5.850764e-002, -6.197131e-002, -6.348723e-002, + -6.040675e-002, -5.143065e-002, -3.913079e-002, -2.436117e-002, -8.321060e-003, 7.847207e-003, 2.324741e-002, 3.768372e-002, 4.978536e-002, + 5.901839e-002, 6.590013e-002, 7.029410e-002, 7.237472e-002, 7.293774e-002, 7.211752e-002, 6.999381e-002, 6.665861e-002, 6.280686e-002, + 5.910306e-002, 5.514026e-002, 5.083006e-002, 4.657457e-002, 4.226260e-002, 3.811827e-002, 3.144685e-002, 2.234341e-002, 1.130665e-002, + -1.228664e-003, -1.412547e-002, -2.665243e-002, -3.848116e-002, -4.958071e-002, -5.857460e-002, -6.477188e-002, -6.806169e-002, -6.988016e-002, + -7.073277e-002, -7.151384e-002, -7.239182e-002, -7.174895e-002, -7.010280e-002, -6.744340e-002, -6.444957e-002, -6.164063e-002, -5.812576e-002, + -5.367930e-002, -4.848620e-002, -4.297960e-002, -3.657945e-002, -2.995950e-002, -2.296909e-002, -1.597946e-002, -8.585166e-003, -1.697770e-003, + 4.594097e-003, 1.148932e-002, 1.864880e-002, 2.454625e-002, 2.995974e-002, 3.540992e-002, 4.048432e-002, 4.511239e-002, 4.926768e-002, + 5.268009e-002, 5.575321e-002, 5.734974e-002, 5.920056e-002, 6.032806e-002, 5.995693e-002, 5.969690e-002, 5.902042e-002, 5.781207e-002, + 5.622053e-002, 5.381067e-002, 5.049819e-002, 4.761916e-002, 4.397071e-002, 3.998229e-002, 3.513161e-002, 2.942715e-002, 2.346468e-002, + 1.659425e-002, 9.489434e-003, 2.247064e-003, -4.686881e-003, -1.174621e-002, -1.771552e-002, -2.386280e-002, -2.966788e-002, -3.437926e-002, + -3.819551e-002, -4.163036e-002, -4.412448e-002, -4.555780e-002, -4.623978e-002, -4.544021e-002, -4.476229e-002, -4.358747e-002, -4.207403e-002, + -4.058685e-002, -3.819245e-002, -3.615695e-002, -3.405509e-002, -3.179835e-002, -2.949527e-002, -2.648948e-002, -2.369028e-002, -2.114936e-002, + -1.836444e-002, -1.591602e-002, -1.363393e-002, -1.136803e-002, -9.137404e-003, -7.535997e-003, -5.620719e-003, -3.213874e-003, -1.470036e-003, + -1.923592e-004, 1.624043e-003, 3.547500e-003, 5.339353e-003, 6.709763e-003, 8.519441e-003, 1.007765e-002, 1.163250e-002, 1.367996e-002, + 1.581156e-002, 1.724416e-002, 1.839022e-002, 1.950540e-002, 2.023805e-002, 2.076328e-002, 2.068973e-002, 1.967446e-002, 1.825632e-002, + 1.712688e-002, 1.629514e-002, 1.496719e-002, 1.384224e-002, 1.222421e-002, 1.104391e-002, 9.688910e-003, 8.311743e-003, 6.099691e-003, + 4.040970e-003, 2.728207e-003, 1.296239e-003, -3.271050e-004, -1.922908e-003, -3.056780e-003, -4.447362e-003, -6.021394e-003, -7.704061e-003, + -8.476206e-003, -9.592973e-003, -1.139000e-002, -1.291937e-002, -1.483325e-002, -1.662574e-002, -1.839277e-002, -1.977263e-002, -2.159780e-002, + -2.298761e-002, -2.444793e-002, -2.541764e-002, -2.607849e-002, -2.653594e-002, -2.625631e-002, -2.607451e-002, -2.522690e-002, -2.381982e-002, + -2.209074e-002, -1.965890e-002, -1.650331e-002, -1.267991e-002, -8.316619e-003, -4.122197e-003, 6.139747e-004, 5.426511e-003, 1.008538e-002, + 1.478529e-002, 1.908357e-002, 2.327826e-002, 2.681402e-002, 3.041813e-002, 3.346004e-002, 3.662196e-002, 3.956993e-002, 4.235200e-002, + 4.478214e-002, 4.653939e-002, 4.830894e-002, 4.948398e-002, 5.054973e-002, 5.105857e-002, 5.050251e-002, 4.961175e-002, 4.782370e-002, + 4.582569e-002, 4.386202e-002, 4.110515e-002, 3.822829e-002, 3.546672e-002, 3.212723e-002, 2.876827e-002, 2.521422e-002, 2.131439e-002, + 1.761771e-002, 1.397709e-002, 1.067392e-002, 7.543316e-003, 4.366291e-003, 1.737139e-003, -7.447393e-004, -3.013053e-003, -4.591211e-003, + -5.725243e-003, -6.626321e-003, -7.209248e-003, -7.683898e-003, -7.636667e-003, -7.853054e-003, -7.986021e-003, -7.726953e-003, -7.367519e-003, + -6.967053e-003, -6.343483e-003, -5.740733e-003, -5.276809e-003, -4.778452e-003, -4.480262e-003, -4.075902e-003, -3.580844e-003, -3.453706e-003, + -3.529391e-003, -3.686823e-003, -4.354754e-003, -5.070101e-003, -5.457422e-003, -6.091960e-003, -6.374289e-003, -6.448705e-003, -6.388631e-003, + -5.845405e-003, -4.843681e-003, -3.979188e-003, -3.074580e-003, -2.277702e-003, -1.925746e-003, -1.628717e-003, -1.406584e-003, -1.278687e-003, + -1.262424e-003, -1.415470e-003, -1.777537e-003, -2.472035e-003, -3.214778e-003, -3.956296e-003, -4.650500e-003, -5.418870e-003, -6.527017e-003, + -7.932233e-003, -9.894023e-003, -1.201590e-002, -1.445854e-002, -1.729280e-002, -2.017117e-002, -2.323385e-002, -2.645051e-002, -2.976934e-002, + -3.354606e-002, -3.716312e-002, -4.084034e-002, -4.469100e-002, -4.802205e-002, -5.104382e-002, -5.378772e-002, -5.643616e-002, -5.879358e-002, + -6.113698e-002, -6.347768e-002, -6.499656e-002, -6.606218e-002, -6.666706e-002, -6.644076e-002, -6.592303e-002, -6.526618e-002, -6.405506e-002, + -6.249396e-002, -6.097593e-002, -5.937146e-002, -5.764781e-002, -5.538720e-002, -5.260856e-002, -4.966647e-002, -4.619167e-002, -4.256092e-002, + -3.869885e-002, -3.498655e-002, -3.147448e-002, -2.776321e-002, -2.440123e-002, -2.141686e-002, -1.823270e-002, -1.508519e-002, -1.260855e-002, + -1.029625e-002, -7.651348e-003, -5.079844e-003, -2.508632e-003, 3.713189e-004, 3.481791e-003, 6.670783e-003, 9.664226e-003, 1.295243e-002, + 1.639668e-002, 1.963827e-002, 2.294923e-002, 2.659309e-002, 3.039730e-002, 3.433333e-002, 3.817145e-002, 4.179942e-002, 4.539036e-002, + 4.862560e-002, 5.183228e-002, 5.502648e-002, 5.808067e-002, 6.084900e-002, 6.341277e-002, 6.574665e-002, 6.741887e-002, 6.867368e-002, + 6.959052e-002, 7.031543e-002, 7.042482e-002, 7.021797e-002, 7.007120e-002, 7.006946e-002, 6.961916e-002, 6.908332e-002, 6.829339e-002, + 6.748685e-002, 6.637344e-002, 6.538728e-002, 6.473878e-002, 6.422098e-002, 6.387686e-002, 6.329067e-002, 6.286465e-002, 6.251631e-002, + 6.188805e-002, 6.105184e-002, 6.027457e-002, 5.917069e-002, 5.799086e-002, 5.699766e-002, 5.628580e-002, 5.530932e-002, 5.426259e-002, + 5.306346e-002, 5.151547e-002, 4.933541e-002, 4.663960e-002, 4.381481e-002, 4.088084e-002, 3.784104e-002, 3.480345e-002, 3.164293e-002, + 2.852055e-002, 2.516690e-002, 2.142936e-002, 1.767626e-002, 1.381035e-002, 9.869841e-003, 5.840078e-003, 1.961443e-003, -1.358005e-003, + -4.485878e-003, -7.844382e-003, -1.106633e-002, -1.413394e-002, -1.704586e-002, -1.989014e-002, -2.259185e-002, -2.510935e-002, -2.738712e-002, + -2.965424e-002, -3.162825e-002, -3.340499e-002, -3.516393e-002, -3.685782e-002, -3.853843e-002, -3.995720e-002, -4.124214e-002, -4.253524e-002, + -4.369096e-002, -4.475482e-002, -4.585656e-002, -4.689625e-002, -4.779712e-002, -4.873259e-002, -4.978599e-002, -5.109208e-002, -5.227883e-002, + -5.339013e-002, -5.436726e-002, -5.517795e-002, -5.562611e-002, -5.606504e-002, -5.596047e-002, -5.523544e-002, -5.410832e-002, -5.231778e-002, + -5.051189e-002, -4.865704e-002, -4.707922e-002, -4.551303e-002, -4.370392e-002, -4.186493e-002, -3.984977e-002, -3.772692e-002, -3.534138e-002, + -3.281257e-002, -3.043038e-002, -2.817283e-002, -2.586624e-002, -2.348127e-002, -2.129979e-002, -1.967071e-002, -1.839842e-002, -1.758337e-002, + -1.692628e-002, -1.655944e-002, -1.635380e-002, -1.630859e-002, -1.657208e-002, -1.692339e-002, -1.738999e-002, -1.809193e-002, -1.884559e-002, + -1.934509e-002, -1.997869e-002, -2.068653e-002, -2.147315e-002, -2.232529e-002, -2.288271e-002, -2.354313e-002, -2.417639e-002, -2.481200e-002, + -2.556814e-002, -2.596303e-002, -2.634337e-002, -2.627289e-002, -2.605863e-002, -2.562108e-002, -2.464726e-002, -2.362012e-002, -2.237576e-002, + -2.099902e-002, -1.956252e-002, -1.798387e-002, -1.623161e-002, -1.425377e-002, -1.219557e-002, -1.007178e-002, -8.158232e-003, -6.325662e-003, + -4.402081e-003, -2.443807e-003, -5.249522e-004, 1.116050e-003, 2.573652e-003, 3.830355e-003, 5.231720e-003, 6.668176e-003, 7.899824e-003, + 8.566820e-003, 9.086328e-003, 9.565302e-003, 1.009236e-002, 1.041448e-002, 1.049655e-002, 1.023852e-002, 1.014630e-002, 1.049090e-002, + 1.106715e-002, 1.201162e-002, 1.303328e-002, 1.423878e-002, 1.573134e-002, 1.754041e-002, 1.944909e-002, 2.130418e-002, 2.279024e-002, + 2.414751e-002, 2.561326e-002, 2.707436e-002, 2.834315e-002, 2.955795e-002, 3.041365e-002, 3.138294e-002, 3.250166e-002, 3.341287e-002, + 3.403989e-002, 3.457351e-002, 3.486614e-002, 3.486823e-002, 3.454881e-002, 3.386356e-002, 3.300952e-002, 3.216468e-002, 3.105147e-002, + 2.995258e-002, 2.907563e-002, 2.836722e-002, 2.807916e-002, 2.819382e-002, 2.838264e-002, 2.874392e-002, 2.930124e-002, 3.000012e-002, + 3.082115e-002, 3.160027e-002, 3.202467e-002, 3.221299e-002, 3.213385e-002, 3.201174e-002, 3.195233e-002, 3.182716e-002, 3.149019e-002, + 3.104207e-002, 3.067949e-002, 3.034484e-002, 2.983977e-002, 2.880255e-002, 2.726677e-002, 2.528284e-002, 2.307693e-002, 2.086115e-002, + 1.852106e-002, 1.601943e-002, 1.343327e-002, 1.065968e-002, 7.784080e-003, 5.057599e-003, 2.257712e-003, -5.166272e-004, -3.099734e-003, + -5.503263e-003, -7.692548e-003, -9.775971e-003, -1.143168e-002, -1.273145e-002, -1.379694e-002, -1.453645e-002, -1.485248e-002, -1.478715e-002, + -1.456329e-002, -1.416304e-002, -1.378568e-002, -1.367538e-002, -1.346707e-002, -1.338091e-002, -1.337350e-002, -1.344266e-002, -1.353764e-002, + -1.371793e-002, -1.369050e-002, -1.370878e-002, -1.354285e-002, -1.367127e-002, -1.380686e-002, -1.389728e-002, -1.409517e-002, -1.467250e-002, + -1.566821e-002, -1.688552e-002, -1.812636e-002, -1.934493e-002, -2.040972e-002, -2.125185e-002, -2.191478e-002, -2.265382e-002, -2.379584e-002, + -2.447061e-002, -2.483882e-002, -2.466953e-002, -2.419094e-002, -2.354120e-002, -2.269191e-002, -2.169821e-002, -2.041436e-002, -1.892845e-002, + -1.727855e-002, -1.540800e-002, -1.364184e-002, -1.184800e-002, -1.046199e-002, -9.719390e-003, -8.879728e-003, -8.060939e-003, -7.387932e-003, + -6.826069e-003, -6.333804e-003, -6.051961e-003, -5.931181e-003, -5.879742e-003, -6.004457e-003, -6.466059e-003, -7.343483e-003, -8.425080e-003, + -9.639188e-003, -1.103471e-002, -1.231647e-002, -1.353618e-002, -1.470765e-002, -1.584574e-002, -1.708902e-002, -1.832268e-002, -1.910493e-002, + -1.925496e-002, -1.898867e-002, -1.843157e-002, -1.736933e-002, -1.577318e-002, -1.370763e-002, -1.132518e-002, -8.694927e-003, -5.833429e-003, + -2.880202e-003, 1.200538e-004, 3.182671e-003, 6.302583e-003, 9.494026e-003, 1.294633e-002, 1.644557e-002, 1.981930e-002, 2.316515e-002, + 2.634589e-002, 2.937961e-002, 3.235725e-002, 3.509975e-002, 3.762362e-002, 4.001504e-002, 4.158814e-002, 4.258871e-002, 4.334019e-002, + 4.405809e-002, 4.443160e-002, 4.448508e-002, 4.444019e-002, 4.393559e-002, 4.338478e-002, 4.269724e-002, 4.207625e-002, 4.169790e-002, + 4.121821e-002, 4.074979e-002, 4.020284e-002, 3.948899e-002, 3.893418e-002, 3.868117e-002, 3.893758e-002, 3.943516e-002, 3.980624e-002, + 4.027181e-002, 4.080484e-002, 4.131376e-002, 4.164916e-002, 4.183542e-002, 4.159049e-002, 4.116916e-002, 4.056963e-002, 3.999757e-002, + 3.944882e-002, 3.891403e-002, 3.807400e-002, 3.720542e-002, 3.616425e-002, 3.469979e-002, 3.260476e-002, 3.017073e-002, 2.754672e-002, + 2.477863e-002, 2.195307e-002, 1.907490e-002, 1.629220e-002, 1.371438e-002, 1.123741e-002, 9.049839e-003, 7.219714e-003, 5.585377e-003, + 3.969487e-003, 2.484008e-003, 8.596376e-004, -5.845881e-004, -1.981340e-003, -3.207072e-003, -4.217189e-003, -5.311963e-003, -6.446577e-003, + -7.501763e-003, -8.535742e-003, -9.563127e-003, -1.065290e-002, -1.175375e-002, -1.291877e-002, -1.412201e-002, -1.544301e-002, -1.693868e-002, + -1.861178e-002, -2.036392e-002, -2.227662e-002, -2.422983e-002, -2.606564e-002, -2.775785e-002, -2.954101e-002, -3.116040e-002, -3.252434e-002, + -3.383050e-002, -3.498702e-002, -3.587027e-002, -3.633093e-002, -3.639564e-002, -3.618091e-002, -3.586866e-002, -3.559997e-002, -3.533912e-002, + -3.522113e-002, -3.522200e-002, -3.540567e-002, -3.554025e-002, -3.575823e-002, -3.593865e-002, -3.603758e-002, -3.603350e-002, -3.588265e-002, + -3.557097e-002, -3.522465e-002, -3.501552e-002, -3.505161e-002, -3.529570e-002, -3.580193e-002, -3.648309e-002, -3.721433e-002, -3.805832e-002, + -3.878846e-002, -3.944967e-002, -3.998897e-002, -4.028615e-002, -4.051445e-002, -4.050024e-002, -4.014576e-002, -3.959262e-002, -3.869401e-002, + -3.755213e-002, -3.617108e-002, -3.462522e-002, -3.303161e-002, -3.128883e-002, -2.941250e-002, -2.744797e-002, -2.531693e-002, -2.305536e-002, + -2.085937e-002, -1.874498e-002, -1.677514e-002, -1.508296e-002, -1.364214e-002, -1.237144e-002, -1.124997e-002, -1.046911e-002, -9.808205e-003, + -9.511396e-003, -9.490375e-003, -9.878623e-003, -1.065723e-002, -1.170485e-002, -1.295802e-002, -1.434085e-002, -1.576909e-002, -1.729681e-002, + -1.886865e-002, -2.043805e-002, -2.197814e-002, -2.342369e-002, -2.479661e-002, -2.582199e-002, -2.637706e-002, -2.631817e-002, -2.577374e-002, + -2.476469e-002, -2.347682e-002, -2.193465e-002, -2.036816e-002, -1.871834e-002, -1.691508e-002, -1.507779e-002, -1.329426e-002, -1.154331e-002, + -9.832614e-003, -8.087838e-003, -6.224715e-003, -4.325074e-003, -2.329493e-003, -3.341823e-004, 1.549680e-003, 3.178575e-003, 4.457852e-003, + 5.302413e-003, 5.862484e-003, 6.293863e-003, 6.533263e-003, 6.768137e-003, 6.924227e-003, 7.212002e-003, 7.725657e-003, 8.505379e-003, + 9.444463e-003, 1.057038e-002, 1.193028e-002, 1.346425e-002, 1.525876e-002, 1.691790e-002, 1.866684e-002, 2.049635e-002, 2.218015e-002, + 2.373882e-002, 2.531557e-002, 2.699776e-002, 2.875195e-002, 3.036702e-002, 3.189079e-002, 3.327291e-002, 3.447763e-002, 3.540290e-002, + 3.607898e-002, 3.651880e-002, 3.672107e-002, 3.670173e-002, 3.634252e-002, 3.574405e-002, 3.487347e-002, 3.377866e-002, 3.239732e-002, + 3.085258e-002, 2.919181e-002, 2.744419e-002, 2.560025e-002, 2.383301e-002, 2.204382e-002, 2.027120e-002, 1.866113e-002, 1.723227e-002, + 1.596429e-002, 1.488236e-002, 1.413082e-002, 1.364276e-002, 1.345665e-002, 1.339082e-002, 1.339199e-002, 1.336845e-002, 1.334505e-002, + 1.345700e-002, 1.366851e-002, 1.391576e-002, 1.421393e-002, 1.463397e-002, 1.518057e-002, 1.562639e-002, 1.594114e-002, 1.608331e-002, + 1.614689e-002, 1.625631e-002, 1.642853e-002, 1.646063e-002, 1.647893e-002, 1.658519e-002, 1.673895e-002, 1.697354e-002, 1.719142e-002, + 1.732659e-002, 1.746007e-002, 1.763408e-002, 1.779985e-002, 1.791608e-002, 1.807997e-002, 1.831716e-002, 1.863347e-002, 1.902103e-002, + 1.942895e-002, 1.983889e-002, 2.020219e-002, 2.053772e-002, 2.089104e-002, 2.118801e-002, 2.146233e-002, 2.153422e-002, 2.140011e-002, + 2.093561e-002, 2.026548e-002, 1.942257e-002, 1.840138e-002, 1.722718e-002, 1.609799e-002, 1.489345e-002, 1.364427e-002, 1.231308e-002, + 1.101310e-002, 9.770681e-003, 8.442610e-003, 7.183730e-003, 5.965439e-003, 4.779826e-003, 3.802337e-003, 2.959676e-003, 2.220901e-003, + 1.621127e-003, 1.216553e-003, 9.882911e-004, 7.562584e-004, 5.403305e-004, 2.496082e-004, -1.623190e-004, -5.324524e-004, -8.451632e-004, + -1.211276e-003, -1.583089e-003, -1.895811e-003, -2.109040e-003, -2.431405e-003, -2.975188e-003, -3.585077e-003, -4.352275e-003, -5.128317e-003, + -5.850866e-003, -6.757719e-003, -7.814969e-003, -9.050739e-003, -1.051684e-002, -1.216976e-002, -1.401496e-002, -1.592177e-002, -1.802597e-002, + -2.026878e-002, -2.242096e-002, -2.465691e-002, -2.685876e-002, -2.900694e-002, -3.107607e-002, -3.304047e-002, -3.487575e-002, -3.665964e-002, + -3.828642e-002, -3.978452e-002, -4.072626e-002, -4.138006e-002, -4.170846e-002, -4.175056e-002, -4.160614e-002, -4.136515e-002, -4.109265e-002, + -4.082368e-002, -4.048039e-002, -4.011434e-002, -3.952376e-002, -3.882572e-002, -3.812580e-002, -3.738514e-002, -3.647261e-002, -3.553116e-002, + -3.463221e-002, -3.381664e-002, -3.312101e-002, -3.244309e-002, -3.185788e-002, -3.142738e-002, -3.129164e-002, -3.138592e-002, -3.153924e-002, + -3.167297e-002, -3.174434e-002, -3.150999e-002, -3.102688e-002, -3.028653e-002, -2.890663e-002, -2.690131e-002, -2.480032e-002, -2.257537e-002, + -2.016211e-002, -1.749404e-002, -1.459472e-002, -1.138792e-002, -7.915766e-003, -4.100576e-003, -9.188738e-017}, + {0.000000e+000, 1.775357e-001, 7.979680e-002, 1.352792e-002, -4.181026e-002, -8.132769e-002, -9.404215e-002, -9.455916e-002, -8.989608e-002, + -7.985256e-002, -6.352006e-002, -4.249475e-002, -2.158452e-002, -2.144340e-003, 1.387942e-002, 2.614182e-002, 3.623704e-002, 4.952434e-002, + 6.440980e-002, 7.656527e-002, 8.532643e-002, 9.006238e-002, 8.925514e-002, 8.553569e-002, 8.001661e-002, 7.234274e-002, 6.292727e-002, + 5.081386e-002, 3.455084e-002, 1.628341e-002, -9.577941e-004, -1.710321e-002, -3.364821e-002, -4.937845e-002, -6.160547e-002, -7.215006e-002, + -7.944840e-002, -8.551187e-002, -8.850542e-002, -8.786581e-002, -8.574996e-002, -8.164471e-002, -7.543580e-002, -6.745867e-002, -5.972414e-002, + -5.113554e-002, -4.158569e-002, -3.217542e-002, -2.235508e-002, -1.259558e-002, -4.125795e-003, 4.980666e-003, 1.542817e-002, 2.425986e-002, + 3.323126e-002, 4.138104e-002, 4.786503e-002, 5.384034e-002, 5.833674e-002, 5.985009e-002, 6.008450e-002, 5.856717e-002, 5.535562e-002, + 5.217350e-002, 4.939453e-002, 4.647304e-002, 4.302402e-002, 3.869665e-002, 3.376300e-002, 2.895096e-002, 2.508852e-002, 2.138382e-002, + 1.790209e-002, 1.397248e-002, 1.085247e-002, 7.112188e-003, 4.141784e-003, 1.008189e-003, -1.734751e-003, -4.170502e-003, -5.749999e-003, + -6.565894e-003, -8.863328e-003, -1.047774e-002, -1.134416e-002, -1.237537e-002, -1.359732e-002, -1.499437e-002, -1.656193e-002, -1.875878e-002, + -2.116694e-002, -2.377183e-002, -2.583156e-002, -2.972528e-002, -3.352681e-002, -3.652694e-002, -3.924507e-002, -4.137043e-002, -4.400875e-002, + -4.597247e-002, -4.615330e-002, -4.443518e-002, -4.269496e-002, -3.992261e-002, -3.606324e-002, -3.078620e-002, -2.500238e-002, -1.934453e-002, + -1.294978e-002, -5.985306e-003, 6.514435e-004, 7.523569e-003, 1.469514e-002, 2.019589e-002, 2.646191e-002, 3.300599e-002, 3.863131e-002, + 4.337335e-002, 4.811716e-002, 5.140081e-002, 5.356248e-002, 5.459523e-002, 5.348807e-002, 5.220604e-002, 4.915858e-002, 4.542092e-002, + 4.126595e-002, 3.610679e-002, 3.093757e-002, 2.501628e-002, 1.937139e-002, 1.391436e-002, 8.183900e-003, 3.182525e-003, -1.561228e-003, + -6.152567e-003, -1.003398e-002, -1.312406e-002, -1.580312e-002, -1.801383e-002, -1.940371e-002, -2.016203e-002, -2.105435e-002, -2.114119e-002, + -2.037169e-002, -1.994137e-002, -1.934457e-002, -1.869300e-002, -1.806662e-002, -1.770362e-002, -1.741813e-002, -1.694187e-002, -1.680878e-002, + -1.744478e-002, -1.743596e-002, -1.789348e-002, -1.848119e-002, -1.931411e-002, -2.069512e-002, -2.212333e-002, -2.299142e-002, -2.329873e-002, + -2.367479e-002, -2.315248e-002, -2.254663e-002, -2.116020e-002, -1.878980e-002, -1.636173e-002, -1.359265e-002, -1.084110e-002, -7.631074e-003, + -4.656106e-003, -2.122915e-003, 8.894334e-004, 4.645573e-003, 8.285496e-003, 1.132095e-002, 1.480997e-002, 1.825488e-002, 2.156204e-002, + 2.443520e-002, 2.778102e-002, 3.067893e-002, 3.261212e-002, 3.396389e-002, 3.433366e-002, 3.416079e-002, 3.297483e-002, 3.162678e-002, + 2.971160e-002, 2.783266e-002, 2.509069e-002, 2.207689e-002, 1.883806e-002, 1.577284e-002, 1.242304e-002, 8.554981e-003, 4.585555e-003, + 6.295037e-004, -3.454476e-003, -7.768312e-003, -1.168489e-002, -1.528311e-002, -1.806253e-002, -1.981329e-002, -2.133462e-002, -2.190818e-002, + -2.226976e-002, -2.224323e-002, -2.247921e-002, -2.243741e-002, -2.190444e-002, -2.096512e-002, -1.985596e-002, -1.875623e-002, -1.726679e-002, + -1.558051e-002, -1.292094e-002, -1.037565e-002, -8.147544e-003, -6.437165e-003, -5.227895e-003, -4.158536e-003, -3.732459e-003, -3.722019e-003, + -4.418284e-003, -5.350611e-003, -6.384840e-003, -7.369892e-003, -8.086721e-003, -8.829875e-003, -9.976483e-003, -1.128610e-002, -1.227019e-002, + -1.288059e-002, -1.316039e-002, -1.299378e-002, -1.232223e-002, -1.129450e-002, -1.033996e-002, -8.544031e-003, -6.416954e-003, -3.781462e-003, + -5.711857e-004, 3.006480e-003, 6.724098e-003, 1.076401e-002, 1.491517e-002, 1.895882e-002, 2.256829e-002, 2.590205e-002, 2.861946e-002, + 3.150810e-002, 3.415531e-002, 3.629499e-002, 3.811590e-002, 3.945154e-002, 4.027185e-002, 4.107981e-002, 4.160502e-002, 4.122586e-002, + 3.995920e-002, 3.781511e-002, 3.466041e-002, 3.106434e-002, 2.731750e-002, 2.336091e-002, 1.926572e-002, 1.498141e-002, 1.066941e-002, + 6.827950e-003, 3.929504e-003, 1.759070e-003, 5.049203e-006, -1.236753e-003, -2.031242e-003, -3.023990e-003, -3.997621e-003, -4.846515e-003, + -5.539956e-003, -5.614635e-003, -5.390870e-003, -5.293738e-003, -4.959130e-003, -4.652576e-003, -4.340632e-003, -4.029976e-003, -3.551811e-003, + -2.873659e-003, -2.737576e-003, -3.096771e-003, -3.675966e-003, -4.912411e-003, -6.339363e-003, -8.262740e-003, -1.008501e-002, -1.186835e-002, + -1.416567e-002, -1.651681e-002, -1.885461e-002, -2.101977e-002, -2.302081e-002, -2.520444e-002, -2.701756e-002, -2.872377e-002, -2.963996e-002, + -3.049702e-002, -3.142430e-002, -3.186568e-002, -3.136799e-002, -3.013262e-002, -2.838334e-002, -2.608881e-002, -2.367548e-002, -2.104875e-002, + -1.850133e-002, -1.626662e-002, -1.424306e-002, -1.216910e-002, -1.007748e-002, -8.601265e-003, -7.327093e-003, -6.156899e-003, -5.263699e-003, + -4.500261e-003, -3.868689e-003, -3.471726e-003, -3.621955e-003, -4.361121e-003, -5.825650e-003, -7.523705e-003, -9.524831e-003, -1.113932e-002, + -1.300857e-002, -1.443190e-002, -1.598567e-002, -1.755453e-002, -1.858104e-002, -1.876336e-002, -1.890419e-002, -1.853937e-002, -1.717003e-002, + -1.518982e-002, -1.340745e-002, -1.166089e-002, -9.369656e-003, -6.375983e-003, -3.794227e-003, -9.063803e-004, 2.630407e-003, 5.814687e-003, + 8.457891e-003, 1.113809e-002, 1.383462e-002, 1.630567e-002, 1.795249e-002, 1.923565e-002, 2.063875e-002, 2.170862e-002, 2.202251e-002, + 2.190855e-002, 2.131468e-002, 2.012651e-002, 1.837314e-002, 1.611885e-002, 1.369061e-002, 1.112773e-002, 8.527285e-003, 6.354191e-003, + 4.498634e-003, 3.205042e-003, 2.104224e-003, 1.452466e-003, 1.030718e-003, 8.000544e-004, 8.816518e-004, 1.198639e-003, 1.529135e-003, + 2.624371e-003, 4.219281e-003, 5.570242e-003, 6.871363e-003, 8.345820e-003, 9.974319e-003, 1.168317e-002, 1.370895e-002, 1.566419e-002, + 1.728765e-002, 1.910706e-002, 2.073761e-002, 2.189938e-002, 2.300193e-002, 2.397654e-002, 2.428721e-002, 2.408840e-002, 2.403179e-002, + 2.375233e-002, 2.336199e-002, 2.326847e-002, 2.302351e-002, 2.278487e-002, 2.246375e-002, 2.221760e-002, 2.205809e-002, 2.194189e-002, + 2.205544e-002, 2.226019e-002, 2.240831e-002, 2.316443e-002, 2.446934e-002, 2.578075e-002, 2.708407e-002, 2.885995e-002, 3.097414e-002, + 3.285330e-002, 3.454499e-002, 3.600295e-002, 3.718985e-002, 3.845224e-002, 3.961443e-002, 4.031147e-002, 4.055667e-002, 4.062118e-002, + 4.058071e-002, 4.039952e-002, 3.970058e-002, 3.854700e-002, 3.684086e-002, 3.427955e-002, 3.125401e-002, 2.787866e-002, 2.395604e-002, + 1.942683e-002, 1.482693e-002, 1.048785e-002, 6.045669e-003, 1.982673e-003, -1.302004e-003, -3.899580e-003, -5.886104e-003, -7.842257e-003, + -9.674443e-003, -1.105018e-002, -1.237017e-002, -1.361624e-002, -1.502766e-002, -1.603838e-002, -1.661329e-002, -1.706196e-002, -1.710428e-002, + -1.716246e-002, -1.704844e-002, -1.664906e-002, -1.602535e-002, -1.540476e-002, -1.505375e-002, -1.475886e-002, -1.478372e-002, -1.529264e-002, + -1.635865e-002, -1.777904e-002, -1.943414e-002, -2.142450e-002, -2.368372e-002, -2.625104e-002, -2.898016e-002, -3.201301e-002, -3.513431e-002, + -3.830305e-002, -4.121431e-002, -4.401926e-002, -4.682618e-002, -4.941242e-002, -5.210793e-002, -5.493625e-002, -5.790831e-002, -6.089795e-002, + -6.379123e-002, -6.650859e-002, -6.910548e-002, -7.126689e-002, -7.298534e-002, -7.419012e-002, -7.499471e-002, -7.476631e-002, -7.414241e-002, + -7.328070e-002, -7.232296e-002, -7.106393e-002, -6.968162e-002, -6.801872e-002, -6.629371e-002, -6.421620e-002, -6.203182e-002, -5.979665e-002, + -5.726520e-002, -5.468427e-002, -5.205914e-002, -4.954892e-002, -4.721034e-002, -4.493305e-002, -4.271330e-002, -4.077976e-002, -3.902642e-002, + -3.774334e-002, -3.660856e-002, -3.561699e-002, -3.464784e-002, -3.360078e-002, -3.245776e-002, -3.106918e-002, -2.941113e-002, -2.756369e-002, + -2.518323e-002, -2.236100e-002, -1.906614e-002, -1.513502e-002, -1.066250e-002, -5.681722e-003, -3.627199e-004, 4.849522e-003, 9.939384e-003, + 1.493057e-002, 1.974156e-002, 2.450762e-002, 2.918955e-002, 3.348365e-002, 3.766386e-002, 4.191393e-002, 4.577118e-002, 4.961030e-002, + 5.326948e-002, 5.665471e-002, 5.960551e-002, 6.212528e-002, 6.362037e-002, 6.463237e-002, 6.510611e-002, 6.501099e-002, 6.478102e-002, + 6.437586e-002, 6.401700e-002, 6.395799e-002, 6.448419e-002, 6.546915e-002, 6.707784e-002, 6.892822e-002, 7.064042e-002, 7.225311e-002, + 7.377380e-002, 7.524948e-002, 7.637023e-002, 7.737744e-002, 7.797798e-002, 7.851166e-002, 7.888344e-002, 7.909658e-002, 7.911198e-002, + 7.878248e-002, 7.778447e-002, 7.639693e-002, 7.467471e-002, 7.215210e-002, 6.866437e-002, 6.455535e-002, 5.989722e-002, 5.492741e-002, + 4.986163e-002, 4.466924e-002, 3.919746e-002, 3.343930e-002, 2.759372e-002, 2.154575e-002, 1.548573e-002, 9.867207e-003, 4.594771e-003, + -4.861828e-004, -5.317351e-003, -9.733447e-003, -1.373598e-002, -1.712451e-002, -1.994701e-002, -2.240120e-002, -2.426787e-002, -2.553491e-002, + -2.681574e-002, -2.795392e-002, -2.874376e-002, -2.909890e-002, -2.966294e-002, -3.030858e-002, -3.103860e-002, -3.196441e-002, -3.287562e-002, + -3.385058e-002, -3.484905e-002, -3.569129e-002, -3.637912e-002, -3.649371e-002, -3.642645e-002, -3.634688e-002, -3.652791e-002, -3.765951e-002, + -3.941217e-002, -4.160522e-002, -4.403155e-002, -4.650886e-002, -4.882421e-002, -5.100350e-002, -5.296732e-002, -5.371197e-002, -5.339855e-002, + -5.269341e-002, -5.153556e-002, -4.975909e-002, -4.759429e-002, -4.500084e-002, -4.201360e-002, -3.873806e-002, -3.510419e-002, -3.104240e-002, + -2.663765e-002, -2.225724e-002, -1.802804e-002, -1.384412e-002, -9.820509e-003, -5.050622e-003, -4.289706e-004, 4.052347e-003, 8.205976e-003, + 1.192988e-002, 1.524583e-002, 1.807433e-002, 2.038908e-002, 2.202368e-002, 2.313006e-002, 2.338327e-002, 2.299727e-002, 2.193849e-002, + 2.026913e-002, 1.817884e-002, 1.570489e-002, 1.309728e-002, 1.032596e-002, 7.360666e-003, 4.936064e-003, 3.377962e-003, 2.120746e-003, + 1.173231e-003, 8.454707e-004, 1.201537e-003, 2.048667e-003, 3.265170e-003, 4.649957e-003, 6.221985e-003, 7.788325e-003, 9.483841e-003, + 1.107598e-002, 1.252047e-002, 1.402649e-002, 1.551110e-002, 1.708970e-002, 1.877187e-002, 2.067124e-002, 2.263600e-002, 2.437845e-002, + 2.573648e-002, 2.668888e-002, 2.715850e-002, 2.706352e-002, 2.640040e-002, 2.515413e-002, 2.428459e-002, 2.305134e-002, 2.144984e-002, + 1.924720e-002, 1.642339e-002, 1.321792e-002, 9.807667e-003, 6.797553e-003, 3.735818e-003, 7.514496e-004, -2.166591e-003, -4.979942e-003, + -7.600587e-003, -9.896762e-003, -1.157031e-002, -1.263526e-002, -1.341388e-002, -1.373675e-002, -1.361667e-002, -1.293288e-002, -1.186383e-002, + -1.051878e-002, -8.980802e-003, -7.485443e-003, -6.153049e-003, -5.251272e-003, -4.228142e-003, -3.160194e-003, -2.085850e-003, -1.061451e-003, + 1.950122e-004, 1.459818e-003, 2.644450e-003, 3.392209e-003, 3.663072e-003, 3.417374e-003, 3.087652e-003, 2.184496e-003, 9.372766e-004, + -7.760320e-004, -2.820221e-003, -5.122270e-003, -7.301273e-003, -9.263047e-003, -1.080978e-002, -1.194177e-002, -1.257250e-002, -1.309406e-002, + -1.360562e-002, -1.414431e-002, -1.414208e-002, -1.386393e-002, -1.342487e-002, -1.288935e-002, -1.209866e-002, -1.123930e-002, -1.027309e-002, + -9.341628e-003, -8.439419e-003, -7.611592e-003, -6.857919e-003, -6.202253e-003, -5.447803e-003, -4.974656e-003, -4.786738e-003, -4.817972e-003, + -5.098632e-003, -5.534286e-003, -6.200894e-003, -6.997894e-003, -7.957233e-003, -8.975942e-003, -9.901044e-003, -1.069107e-002, -1.110689e-002, + -1.127027e-002, -1.109272e-002, -1.050726e-002, -9.507492e-003, -8.115713e-003, -6.430399e-003, -4.421938e-003, -2.272362e-003, -1.816417e-004, + 2.030257e-003, 4.134755e-003, 6.023023e-003, 7.810538e-003, 9.156440e-003, 1.024259e-002, 1.111831e-002, 1.189350e-002, 1.259942e-002, + 1.310139e-002, 1.327879e-002, 1.315171e-002, 1.252567e-002, 1.112800e-002, 8.994592e-003, 6.421979e-003, 3.538716e-003, 3.993954e-004, + -2.835571e-003, -6.087859e-003, -9.221628e-003, -1.197625e-002, -1.434723e-002, -1.632804e-002, -1.784148e-002, -1.888035e-002, -1.948038e-002, + -1.954604e-002, -1.911668e-002, -1.829814e-002, -1.730183e-002, -1.598150e-002, -1.446889e-002, -1.274503e-002, -1.078627e-002, -8.696629e-003, + -6.663931e-003, -4.758186e-003, -2.985306e-003, -1.346903e-003, 1.714629e-004, 1.503688e-003, 2.620473e-003, 3.419879e-003, 3.913133e-003, + 3.983803e-003, 3.627704e-003, 2.761205e-003, 1.373365e-003, -7.614991e-004, -3.359978e-003, -6.327332e-003, -9.489271e-003, -1.285245e-002, + -1.628861e-002, -1.966313e-002, -2.305150e-002, -2.635355e-002, -2.939505e-002, -3.182279e-002, -3.366256e-002, -3.428710e-002, -3.390725e-002, + -3.269257e-002, -3.094919e-002, -2.875207e-002, -2.632205e-002, -2.374175e-002, -2.108559e-002, -1.842597e-002, -1.582152e-002, -1.321845e-002, + -1.054523e-002, -7.588319e-003, -4.343790e-003, -1.007493e-003, 2.452652e-003, 5.753544e-003, 8.825611e-003, 1.144666e-002, 1.342857e-002, + 1.460600e-002, 1.527906e-002, 1.564168e-002, 1.556156e-002, 1.525283e-002, 1.497526e-002, 1.477464e-002, 1.477853e-002, 1.529520e-002, + 1.616800e-002, 1.737829e-002, 1.898786e-002, 2.082708e-002, 2.292831e-002, 2.539158e-002, 2.815409e-002, 3.113407e-002, 3.422539e-002, + 3.734651e-002, 4.054805e-002, 4.381825e-002, 4.694746e-002, 4.979756e-002, 5.248548e-002, 5.485682e-002, 5.697983e-002, 5.879039e-002, + 6.025996e-002, 6.138572e-002, 6.213849e-002, 6.254298e-002, 6.213726e-002, 6.111987e-002, 5.961144e-002, 5.760319e-002, 5.506664e-002, + 5.217947e-002, 4.898028e-002, 4.545073e-002, 4.171353e-002, 3.784315e-002, 3.368698e-002, 2.941330e-002, 2.514378e-002, 2.112067e-002, + 1.730015e-002, 1.380694e-002, 1.074233e-002, 8.111265e-003, 5.932291e-003, 3.967943e-003, 2.124484e-003, 2.590918e-004, -1.614750e-003, + -3.221389e-003, -4.685661e-003, -5.996490e-003, -7.168788e-003, -8.353833e-003, -9.407996e-003, -1.060870e-002, -1.197320e-002, -1.347213e-002, + -1.502766e-002, -1.650786e-002, -1.807159e-002, -1.972657e-002, -2.140553e-002, -2.310271e-002, -2.455948e-002, -2.595821e-002, -2.726262e-002, + -2.829948e-002, -2.929560e-002, -3.016580e-002, -3.104933e-002, -3.194615e-002, -3.275305e-002, -3.329386e-002, -3.348462e-002, -3.323642e-002, + -3.268994e-002, -3.197261e-002, -3.115347e-002, -3.014446e-002, -2.882002e-002, -2.734648e-002, -2.583023e-002, -2.425473e-002, -2.271960e-002, + -2.135692e-002, -2.021249e-002, -1.921181e-002, -1.840541e-002, -1.780193e-002, -1.708260e-002, -1.653625e-002, -1.605120e-002, -1.573433e-002, + -1.542857e-002, -1.488292e-002, -1.426872e-002, -1.356561e-002, -1.284821e-002, -1.216225e-002, -1.119633e-002, -1.033367e-002, -9.469171e-003, + -8.475329e-003, -7.252268e-003, -5.751224e-003, -4.212961e-003, -2.722542e-003, -1.440589e-003, -4.467704e-004, 6.579989e-004, 1.813995e-003, + 2.956321e-003, 4.202859e-003, 5.523915e-003, 6.832496e-003, 7.916275e-003, 8.690821e-003, 9.389211e-003, 9.914250e-003, 1.047504e-002, + 1.115510e-002, 1.163301e-002, 1.186959e-002, 1.178835e-002, 1.136925e-002, 1.073839e-002, 9.806369e-003, 8.580976e-003, 7.023311e-003, + 5.227929e-003, 3.581448e-003, 1.674001e-003, -2.985087e-004, -2.187038e-003, -3.948673e-003, -5.603546e-003, -7.083235e-003, -8.440955e-003, + -9.694266e-003, -1.092461e-002, -1.151291e-002, -1.176717e-002, -1.150360e-002, -1.078362e-002, -9.779732e-003, -8.650834e-003, -7.540768e-003, + -6.564140e-003, -5.746833e-003, -5.044707e-003, -4.079054e-003, -2.880693e-003, -1.766820e-003, -7.531407e-004, 1.536002e-004, 9.972695e-004, + 1.514170e-003, 1.794759e-003, 1.766574e-003, 1.399637e-003, 7.233265e-004, -3.432990e-004, -1.971344e-003, -4.029041e-003, -6.459820e-003, + -8.946882e-003, -1.117553e-002, -1.336150e-002, -1.533071e-002, -1.677262e-002, -1.736740e-002, -1.696338e-002, -1.647855e-002, -1.588990e-002, + -1.499175e-002, -1.364992e-002, -1.191554e-002, -9.664514e-003, -6.972047e-003, -3.712297e-003, 1.604006e-016}, + {0.000000e+000, 2.038977e-001, 5.986865e-002, -2.943833e-002, -7.799666e-002, -9.455111e-002, -9.181085e-002, -8.392553e-002, -7.199876e-002, + -5.644654e-002, -3.877417e-002, -1.894875e-002, 1.009213e-003, 1.790310e-002, 3.261412e-002, 4.651401e-002, 5.801635e-002, 6.683222e-002, + 7.214600e-002, 7.485705e-002, 7.358793e-002, 6.958921e-002, 6.187436e-002, 5.197944e-002, 3.971716e-002, 2.704245e-002, 1.407361e-002, + 1.397047e-003, -9.703171e-003, -1.932254e-002, -2.683198e-002, -3.274516e-002, -3.789992e-002, -4.282698e-002, -4.668334e-002, -4.931934e-002, + -5.059847e-002, -5.211500e-002, -5.158548e-002, -4.862963e-002, -4.493928e-002, -4.019584e-002, -3.521807e-002, -2.887773e-002, -2.261393e-002, + -1.728430e-002, -1.101569e-002, -4.683203e-003, 1.118357e-003, 7.670594e-003, 1.411585e-002, 1.895433e-002, 2.304213e-002, 2.609218e-002, + 2.919360e-002, 3.087543e-002, 3.124964e-002, 3.164525e-002, 3.109008e-002, 3.038019e-002, 3.022243e-002, 3.008269e-002, 2.843726e-002, + 2.660720e-002, 2.479568e-002, 2.230414e-002, 1.947827e-002, 1.670303e-002, 1.323369e-002, 9.039395e-003, 5.287494e-003, 1.356397e-003, + -2.354074e-003, -6.225131e-003, -9.184846e-003, -1.151499e-002, -1.356011e-002, -1.546715e-002, -1.719785e-002, -1.903340e-002, -2.042553e-002, + -2.092884e-002, -2.167044e-002, -2.157935e-002, -2.112037e-002, -2.044625e-002, -1.954117e-002, -1.821105e-002, -1.605512e-002, -1.398371e-002, + -1.200372e-002, -9.451352e-003, -6.988076e-003, -4.615986e-003, -2.342345e-003, 1.613535e-004, 2.904732e-003, 4.932203e-003, 6.368238e-003, + 7.562709e-003, 9.143085e-003, 1.008219e-002, 1.080866e-002, 1.151120e-002, 1.251888e-002, 1.319438e-002, 1.336476e-002, 1.308933e-002, + 1.267152e-002, 1.174073e-002, 1.118541e-002, 1.032100e-002, 9.654384e-003, 8.508705e-003, 7.499380e-003, 6.289018e-003, 4.965812e-003, + 3.897420e-003, 2.870485e-003, 1.839803e-003, 1.197311e-003, 3.646383e-004, -1.088043e-004, -1.641654e-003, -2.939876e-003, -4.559207e-003, + -6.083721e-003, -7.883860e-003, -1.042554e-002, -1.267706e-002, -1.501106e-002, -1.577742e-002, -1.642432e-002, -1.637608e-002, -1.584167e-002, + -1.460733e-002, -1.253995e-002, -1.029386e-002, -7.819679e-003, -5.157811e-003, -2.415107e-003, 1.666464e-004, 2.251748e-003, 4.320920e-003, + 6.234691e-003, 7.793974e-003, 9.442236e-003, 1.080505e-002, 1.236911e-002, 1.337776e-002, 1.377380e-002, 1.412727e-002, 1.386806e-002, + 1.331523e-002, 1.270956e-002, 1.149312e-002, 1.025597e-002, 8.388563e-003, 6.375204e-003, 3.815845e-003, 1.012968e-003, -1.399721e-003, + -3.834535e-003, -6.341051e-003, -7.555108e-003, -8.631437e-003, -9.460954e-003, -1.000364e-002, -1.074642e-002, -1.168537e-002, -1.193079e-002, + -1.182945e-002, -1.195279e-002, -1.132751e-002, -1.085958e-002, -9.892540e-003, -8.961343e-003, -7.922163e-003, -6.631306e-003, -4.882750e-003, + -3.767355e-003, -2.535538e-003, -1.054977e-003, -2.860438e-005, 5.330343e-004, 1.292331e-003, 1.433929e-003, 1.731070e-003, 2.259687e-003, + 2.852055e-003, 2.403100e-003, 1.752836e-003, 4.696530e-004, -1.098445e-003, -2.427891e-003, -2.971284e-003, -3.481177e-003, -4.161724e-003, + -3.073227e-003, -2.428219e-003, -1.484795e-003, -8.435546e-004, -3.580265e-004, 1.018132e-005, 1.240260e-003, 3.431439e-003, 5.212790e-003, + 7.105515e-003, 9.362173e-003, 1.160303e-002, 1.368285e-002, 1.625282e-002, 1.966779e-002, 2.221942e-002, 2.447969e-002, 2.651007e-002, + 2.855703e-002, 3.070740e-002, 3.202880e-002, 3.379481e-002, 3.485243e-002, 3.496797e-002, 3.460821e-002, 3.433300e-002, 3.361378e-002, + 3.179794e-002, 2.918193e-002, 2.663118e-002, 2.328188e-002, 1.964777e-002, 1.595553e-002, 1.204813e-002, 7.612108e-003, 2.669550e-003, + -2.293426e-003, -6.832927e-003, -1.186429e-002, -1.712440e-002, -2.201673e-002, -2.599345e-002, -2.957282e-002, -3.361600e-002, -3.744508e-002, + -3.962718e-002, -4.222042e-002, -4.502838e-002, -4.789559e-002, -4.996224e-002, -5.196838e-002, -5.421404e-002, -5.678315e-002, -5.764661e-002, + -5.799532e-002, -5.869838e-002, -5.931909e-002, -5.932131e-002, -5.875619e-002, -5.790336e-002, -5.684888e-002, -5.537023e-002, -5.258649e-002, + -5.004176e-002, -4.763360e-002, -4.517555e-002, -4.185483e-002, -3.785347e-002, -3.337568e-002, -2.899931e-002, -2.452926e-002, -1.914155e-002, + -1.346341e-002, -7.615584e-003, -1.541612e-003, 5.020995e-003, 1.176112e-002, 1.802937e-002, 2.344018e-002, 2.861669e-002, 3.380264e-002, + 3.889844e-002, 4.339233e-002, 4.731591e-002, 5.146970e-002, 5.537960e-002, 5.839466e-002, 6.065892e-002, 6.231486e-002, 6.378404e-002, + 6.523854e-002, 6.607171e-002, 6.599560e-002, 6.527617e-002, 6.461802e-002, 6.320286e-002, 6.117599e-002, 5.881066e-002, 5.590146e-002, + 5.301861e-002, 5.013788e-002, 4.702900e-002, 4.370030e-002, 3.995248e-002, 3.586635e-002, 3.153618e-002, 2.725323e-002, 2.291460e-002, + 1.838014e-002, 1.460601e-002, 1.102648e-002, 8.120367e-003, 5.065249e-003, 1.737932e-003, -1.011730e-003, -3.147316e-003, -4.481536e-003, + -6.074214e-003, -7.514559e-003, -9.156324e-003, -1.037794e-002, -1.107405e-002, -1.172200e-002, -1.252735e-002, -1.315486e-002, -1.272022e-002, + -1.234652e-002, -1.223645e-002, -1.227328e-002, -1.235086e-002, -1.277333e-002, -1.371944e-002, -1.478117e-002, -1.577827e-002, -1.730560e-002, + -1.900038e-002, -2.038587e-002, -2.174282e-002, -2.266705e-002, -2.333158e-002, -2.362447e-002, -2.329662e-002, -2.261270e-002, -2.224872e-002, + -2.129169e-002, -2.035654e-002, -1.924514e-002, -1.809091e-002, -1.676887e-002, -1.579749e-002, -1.455195e-002, -1.367036e-002, -1.293713e-002, + -1.217810e-002, -1.140223e-002, -1.048087e-002, -9.495843e-003, -8.751255e-003, -8.063505e-003, -7.037342e-003, -6.107426e-003, -5.328495e-003, + -4.449697e-003, -3.911455e-003, -3.501307e-003, -2.831702e-003, -2.412742e-003, -2.426512e-003, -2.280264e-003, -2.254412e-003, -2.310929e-003, + -1.822689e-003, -1.055015e-003, -3.095796e-004, 1.004677e-003, 2.104851e-003, 3.309984e-003, 4.953510e-003, 6.465559e-003, 7.755958e-003, + 8.856977e-003, 1.001807e-002, 1.103338e-002, 1.215749e-002, 1.314452e-002, 1.380485e-002, 1.416623e-002, 1.468435e-002, 1.466699e-002, + 1.437880e-002, 1.387350e-002, 1.273483e-002, 1.128784e-002, 1.034932e-002, 8.903706e-003, 7.120053e-003, 5.308982e-003, 3.312845e-003, + 7.750683e-004, -1.671693e-003, -3.925227e-003, -6.748509e-003, -1.007054e-002, -1.328866e-002, -1.663213e-002, -2.007830e-002, -2.329442e-002, + -2.628339e-002, -2.895602e-002, -3.120966e-002, -3.309309e-002, -3.483582e-002, -3.656914e-002, -3.812007e-002, -3.989632e-002, -4.157040e-002, + -4.268643e-002, -4.308429e-002, -4.314905e-002, -4.322757e-002, -4.281862e-002, -4.203354e-002, -4.116743e-002, -4.007474e-002, -3.883531e-002, + -3.774353e-002, -3.678234e-002, -3.533250e-002, -3.360346e-002, -3.176519e-002, -2.928535e-002, -2.624132e-002, -2.304734e-002, -2.001894e-002, + -1.712506e-002, -1.383000e-002, -1.047400e-002, -6.833856e-003, -2.938309e-003, 8.382916e-004, 4.974584e-003, 9.608949e-003, 1.516981e-002, + 2.059556e-002, 2.563920e-002, 3.080621e-002, 3.584224e-002, 4.029793e-002, 4.446684e-002, 4.853835e-002, 5.248158e-002, 5.603093e-002, + 5.952553e-002, 6.279237e-002, 6.560385e-002, 6.823341e-002, 7.069727e-002, 7.317271e-002, 7.509902e-002, 7.611028e-002, 7.630491e-002, + 7.590466e-002, 7.521284e-002, 7.388243e-002, 7.185528e-002, 6.955907e-002, 6.667665e-002, 6.357474e-002, 6.020980e-002, 5.652693e-002, + 5.274491e-002, 4.893944e-002, 4.515162e-002, 4.131544e-002, 3.726452e-002, 3.299605e-002, 2.885540e-002, 2.475995e-002, 2.068679e-002, + 1.675184e-002, 1.320004e-002, 9.920054e-003, 6.617535e-003, 3.704446e-003, 9.802665e-004, -1.769586e-003, -3.985101e-003, -6.020592e-003, + -7.922071e-003, -9.781234e-003, -1.151929e-002, -1.278941e-002, -1.399082e-002, -1.505156e-002, -1.600855e-002, -1.702415e-002, -1.779437e-002, + -1.822644e-002, -1.871108e-002, -1.949713e-002, -2.045065e-002, -2.174309e-002, -2.328937e-002, -2.504176e-002, -2.692291e-002, -2.895548e-002, + -3.104563e-002, -3.331170e-002, -3.556487e-002, -3.782073e-002, -3.994800e-002, -4.193592e-002, -4.369154e-002, -4.495409e-002, -4.600990e-002, + -4.683610e-002, -4.704017e-002, -4.684475e-002, -4.634755e-002, -4.547854e-002, -4.442972e-002, -4.334853e-002, -4.186357e-002, -4.037194e-002, + -3.857011e-002, -3.668145e-002, -3.504295e-002, -3.350181e-002, -3.171778e-002, -2.997645e-002, -2.839207e-002, -2.668402e-002, -2.491234e-002, + -2.330417e-002, -2.193242e-002, -2.077107e-002, -1.994255e-002, -1.935393e-002, -1.910959e-002, -1.895475e-002, -1.879883e-002, -1.872891e-002, + -1.875136e-002, -1.871446e-002, -1.857140e-002, -1.825883e-002, -1.767523e-002, -1.681002e-002, -1.596656e-002, -1.487790e-002, -1.344102e-002, + -1.162909e-002, -9.852538e-003, -8.113489e-003, -6.445951e-003, -4.641957e-003, -2.885425e-003, -1.083789e-003, 7.257122e-004, 2.550678e-003, + 4.469009e-003, 6.470269e-003, 8.153632e-003, 9.612265e-003, 1.071153e-002, 1.152931e-002, 1.198704e-002, 1.222847e-002, 1.250427e-002, + 1.286550e-002, 1.323518e-002, 1.352264e-002, 1.366316e-002, 1.368216e-002, 1.356630e-002, 1.348990e-002, 1.352584e-002, 1.366536e-002, + 1.389662e-002, 1.424056e-002, 1.468941e-002, 1.525777e-002, 1.599095e-002, 1.712325e-002, 1.853401e-002, 2.015504e-002, 2.234412e-002, + 2.463301e-002, 2.708933e-002, 2.953201e-002, 3.158713e-002, 3.339768e-002, 3.509685e-002, 3.687388e-002, 3.882640e-002, 4.059849e-002, + 4.202608e-002, 4.339748e-002, 4.472859e-002, 4.602700e-002, 4.676366e-002, 4.696798e-002, 4.685429e-002, 4.655567e-002, 4.590091e-002, + 4.486815e-002, 4.356470e-002, 4.214784e-002, 4.049342e-002, 3.877558e-002, 3.702745e-002, 3.532785e-002, 3.350575e-002, 3.150888e-002, + 2.967715e-002, 2.799735e-002, 2.661135e-002, 2.535264e-002, 2.432964e-002, 2.326283e-002, 2.237897e-002, 2.161819e-002, 2.090879e-002, + 2.010783e-002, 1.930238e-002, 1.835758e-002, 1.725111e-002, 1.584139e-002, 1.379933e-002, 1.157148e-002, 9.268491e-003, 6.705655e-003, + 3.715205e-003, 4.107081e-004, -3.087601e-003, -6.798565e-003, -1.095160e-002, -1.540491e-002, -2.002156e-002, -2.449577e-002, -2.919329e-002, + -3.395445e-002, -3.868531e-002, -4.353909e-002, -4.844245e-002, -5.329503e-002, -5.809130e-002, -6.290737e-002, -6.772933e-002, -7.189172e-002, + -7.551542e-002, -7.850489e-002, -8.063716e-002, -8.220160e-002, -8.336836e-002, -8.406753e-002, -8.441841e-002, -8.444045e-002, -8.391728e-002, + -8.295204e-002, -8.152658e-002, -7.973977e-002, -7.751409e-002, -7.500005e-002, -7.228445e-002, -6.906917e-002, -6.537511e-002, -6.141198e-002, + -5.724726e-002, -5.284759e-002, -4.848487e-002, -4.401123e-002, -3.970338e-002, -3.573580e-002, -3.260237e-002, -2.964431e-002, -2.677199e-002, + -2.404109e-002, -2.135362e-002, -1.888864e-002, -1.624491e-002, -1.332417e-002, -1.031546e-002, -7.261665e-003, -4.268121e-003, -1.508795e-003, + 1.134295e-003, 3.619571e-003, 5.828011e-003, 8.110666e-003, 1.040704e-002, 1.319549e-002, 1.620710e-002, 1.916553e-002, 2.209625e-002, + 2.500756e-002, 2.775402e-002, 3.036326e-002, 3.288114e-002, 3.527847e-002, 3.786179e-002, 4.037397e-002, 4.285528e-002, 4.534341e-002, + 4.758996e-002, 4.938518e-002, 5.116656e-002, 5.275974e-002, 5.416225e-002, 5.535033e-002, 5.590618e-002, 5.616684e-002, 5.619416e-002, + 5.602111e-002, 5.555202e-002, 5.494568e-002, 5.425079e-002, 5.352648e-002, 5.294317e-002, 5.251581e-002, 5.223673e-002, 5.199132e-002, + 5.166691e-002, 5.134220e-002, 5.096302e-002, 5.033949e-002, 4.969348e-002, 4.905626e-002, 4.825902e-002, 4.749799e-002, 4.673475e-002, + 4.582434e-002, 4.472824e-002, 4.351704e-002, 4.223126e-002, 4.085643e-002, 3.937696e-002, 3.769328e-002, 3.580202e-002, 3.366185e-002, + 3.121069e-002, 2.864057e-002, 2.601440e-002, 2.338889e-002, 2.062021e-002, 1.769810e-002, 1.490952e-002, 1.217546e-002, 9.358863e-003, + 6.301049e-003, 3.555297e-003, 1.238527e-003, -6.116535e-004, -2.176547e-003, -3.355215e-003, -4.286351e-003, -4.988579e-003, -5.747776e-003, + -6.681510e-003, -7.600147e-003, -8.416422e-003, -9.151412e-003, -9.772220e-003, -1.020583e-002, -1.056118e-002, -1.089222e-002, -1.116069e-002, + -1.135084e-002, -1.147240e-002, -1.158118e-002, -1.179887e-002, -1.230994e-002, -1.298814e-002, -1.377298e-002, -1.466114e-002, -1.544403e-002, + -1.615037e-002, -1.670681e-002, -1.709712e-002, -1.754141e-002, -1.800747e-002, -1.828585e-002, -1.838731e-002, -1.832312e-002, -1.813977e-002, + -1.784323e-002, -1.741100e-002, -1.691073e-002, -1.632508e-002, -1.554783e-002, -1.460601e-002, -1.365363e-002, -1.288288e-002, -1.231089e-002, + -1.190100e-002, -1.169113e-002, -1.170780e-002, -1.183701e-002, -1.194267e-002, -1.225889e-002, -1.279041e-002, -1.359520e-002, -1.433554e-002, + -1.504622e-002, -1.605093e-002, -1.738929e-002, -1.904751e-002, -2.101755e-002, -2.325450e-002, -2.573629e-002, -2.829256e-002, -3.092450e-002, + -3.360584e-002, -3.626469e-002, -3.879736e-002, -4.076347e-002, -4.205671e-002, -4.287240e-002, -4.339240e-002, -4.352638e-002, -4.320936e-002, + -4.234417e-002, -4.109221e-002, -3.982037e-002, -3.851914e-002, -3.699785e-002, -3.534256e-002, -3.359361e-002, -3.176071e-002, -2.988578e-002, + -2.780529e-002, -2.552479e-002, -2.306791e-002, -2.042006e-002, -1.752280e-002, -1.463915e-002, -1.188046e-002, -9.355941e-003, -7.073806e-003, + -5.058920e-003, -3.291251e-003, -1.717509e-003, -2.524424e-004, 1.023075e-003, 2.260394e-003, 3.567657e-003, 5.079620e-003, 6.856197e-003, + 8.699813e-003, 1.063617e-002, 1.267506e-002, 1.475928e-002, 1.687259e-002, 1.910209e-002, 2.126923e-002, 2.329358e-002, 2.521197e-002, + 2.701610e-002, 2.863762e-002, 3.010457e-002, 3.123741e-002, 3.206594e-002, 3.262429e-002, 3.282690e-002, 3.267768e-002, 3.219683e-002, + 3.132763e-002, 3.004464e-002, 2.832705e-002, 2.626040e-002, 2.379265e-002, 2.081880e-002, 1.762662e-002, 1.424133e-002, 1.065133e-002, + 6.965864e-003, 3.232117e-003, -5.094708e-004, -4.106098e-003, -7.623158e-003, -1.100570e-002, -1.415960e-002, -1.697216e-002, -1.938143e-002, + -2.133751e-002, -2.265492e-002, -2.348320e-002, -2.383396e-002, -2.366856e-002, -2.325705e-002, -2.271105e-002, -2.193964e-002, -2.098082e-002, + -1.980062e-002, -1.843948e-002, -1.692524e-002, -1.523926e-002, -1.334551e-002, -1.118717e-002, -8.944406e-003, -6.764265e-003, -4.646752e-003, + -2.614082e-003, -6.296331e-004, 1.150117e-003, 2.828635e-003, 4.412365e-003, 5.928408e-003, 7.507944e-003, 9.290022e-003, 1.114943e-002, + 1.287923e-002, 1.454201e-002, 1.611053e-002, 1.758332e-002, 1.898921e-002, 2.024133e-002, 2.141627e-002, 2.256373e-002, 2.373198e-002, + 2.496664e-002, 2.614804e-002, 2.716888e-002, 2.804019e-002, 2.875792e-002, 2.945554e-002, 2.996165e-002, 3.036138e-002, 3.063906e-002, + 3.067688e-002, 3.028947e-002, 2.953769e-002, 2.852818e-002, 2.737068e-002, 2.620999e-002, 2.488068e-002, 2.340507e-002, 2.180249e-002, + 2.007300e-002, 1.846771e-002, 1.687297e-002, 1.529707e-002, 1.374633e-002, 1.223441e-002, 1.080189e-002, 9.363292e-003, 8.066254e-003, + 7.057066e-003, 6.279500e-003, 5.748732e-003, 5.397326e-003, 5.210849e-003, 5.058444e-003, 5.004529e-003, 4.988791e-003, 4.955283e-003, + 4.933273e-003, 4.963275e-003, 4.974368e-003, 5.099264e-003, 5.254558e-003, 5.312861e-003, 5.220178e-003, 5.047695e-003, 4.838553e-003, + 4.549059e-003, 4.117503e-003, 3.671899e-003, 3.046092e-003, 2.163210e-003, 1.101814e-003, -7.744071e-005, -1.194064e-003, -2.514380e-003, + -4.079641e-003, -5.848599e-003, -7.838580e-003, -1.010623e-002, -1.239045e-002, -1.463796e-002, -1.681831e-002, -1.882169e-002, -2.062006e-002, + -2.234526e-002, -2.399241e-002, -2.525671e-002, -2.616381e-002, -2.677689e-002, -2.711283e-002, -2.724403e-002, -2.725471e-002, -2.720445e-002, + -2.713185e-002, -2.731374e-002, -2.753442e-002, -2.771595e-002, -2.758083e-002, -2.738584e-002, -2.701981e-002, -2.619481e-002, -2.531688e-002, + -2.447496e-002, -2.366043e-002, -2.289788e-002, -2.225163e-002, -2.187533e-002, -2.181494e-002, -2.199627e-002, -2.237662e-002, -2.311109e-002, + -2.399615e-002, -2.451233e-002, -2.442773e-002, -2.398563e-002, -2.323590e-002, -2.194075e-002, -2.036509e-002, -1.876638e-002, -1.714303e-002, + -1.538013e-002, -1.342394e-002, -1.128224e-002, -8.859936e-003, -6.198258e-003, -3.229861e-003, -1.215553e-016}, + {0.000000e+000, -4.426256e-001, -9.788617e-002, 1.103052e-001, 2.074472e-001, 2.236316e-001, 1.905781e-001, 1.553585e-001, 1.162719e-001, + 7.326630e-002, 2.565355e-002, -2.661771e-002, -7.898507e-002, -1.232298e-001, -1.535677e-001, -1.707928e-001, -1.718427e-001, -1.543418e-001, + -1.242447e-001, -8.760311e-002, -4.904209e-002, -1.852978e-002, 5.422101e-003, 2.645649e-002, 4.626530e-002, 6.276776e-002, 7.470737e-002, + 7.947473e-002, 7.874632e-002, 7.476442e-002, 6.710252e-002, 5.668877e-002, 4.690894e-002, 3.583451e-002, 2.376012e-002, 1.339382e-002, + 3.719670e-003, -2.079770e-003, -3.446964e-003, -2.730284e-003, 1.204935e-003, 4.354620e-003, 7.186059e-003, 8.658222e-003, 9.326182e-003, + 7.479452e-003, 9.300147e-004, -7.350316e-003, -1.560447e-002, -2.437501e-002, -3.139798e-002, -3.580118e-002, -3.681373e-002, -3.280146e-002, + -2.755347e-002, -2.027133e-002, -1.204846e-002, -4.985635e-003, 1.326156e-003, 7.075843e-003, 1.094889e-002, 9.658565e-003, 8.698411e-003, + 8.313470e-003, 7.450266e-003, 6.850333e-003, 6.251132e-003, 4.996391e-003, 2.673786e-003, 6.534366e-004, -1.342255e-003, -3.754922e-003, + -7.308775e-003, -9.656508e-003, -1.154943e-002, -1.340400e-002, -1.549289e-002, -1.689364e-002, -1.784053e-002, -1.864461e-002, -1.790010e-002, + -1.531855e-002, -1.206865e-002, -9.881805e-003, -5.823708e-003, -1.604496e-003, 1.683076e-003, 5.385228e-003, 8.287201e-003, 1.130908e-002, + 1.484023e-002, 1.658365e-002, 1.917724e-002, 2.101180e-002, 2.081750e-002, 2.011015e-002, 1.746059e-002, 1.414899e-002, 1.046083e-002, + 5.986478e-003, 1.928149e-003, -8.264124e-004, -3.367271e-003, -5.848097e-003, -7.483155e-003, -8.553182e-003, -8.197007e-003, -8.442523e-003, + -7.854757e-003, -6.368900e-003, -3.748691e-003, -1.691001e-003, 7.225895e-004, 2.886092e-003, 5.741766e-003, 8.288244e-003, 1.017717e-002, + 1.150823e-002, 1.195427e-002, 1.274806e-002, 1.227997e-002, 1.115023e-002, 9.023154e-003, 6.439063e-003, 4.560128e-003, 2.083562e-003, + -1.255635e-003, -5.060332e-003, -8.679987e-003, -1.290611e-002, -1.681560e-002, -2.084523e-002, -2.492939e-002, -2.789864e-002, -2.882802e-002, + -2.912818e-002, -2.756511e-002, -2.559286e-002, -2.262439e-002, -1.854269e-002, -1.365014e-002, -9.728028e-003, -5.351973e-003, 1.151219e-005, + 6.367849e-003, 1.130888e-002, 1.637912e-002, 2.062565e-002, 2.460765e-002, 2.775190e-002, 3.047390e-002, 3.243981e-002, 3.265326e-002, + 3.152360e-002, 2.963271e-002, 2.767579e-002, 2.551175e-002, 2.312655e-002, 2.055888e-002, 1.765303e-002, 1.522919e-002, 1.197632e-002, + 9.233995e-003, 6.499312e-003, 4.264982e-003, 2.984319e-003, 1.050249e-003, -1.987282e-004, -1.507741e-003, -2.796039e-003, -4.056193e-003, + -4.976315e-003, -5.481622e-003, -6.175870e-003, -6.526531e-003, -6.919749e-003, -7.450196e-003, -7.121866e-003, -6.505571e-003, -6.143038e-003, + -5.438646e-003, -5.219527e-003, -4.999457e-003, -4.246250e-003, -3.941065e-003, -3.994740e-003, -4.182282e-003, -3.993894e-003, -3.789955e-003, + -3.646234e-003, -4.460992e-003, -5.310140e-003, -6.485153e-003, -7.686435e-003, -8.934210e-003, -1.057511e-002, -1.211719e-002, -1.315324e-002, + -1.447711e-002, -1.581320e-002, -1.730214e-002, -1.873890e-002, -1.965352e-002, -1.958190e-002, -1.957804e-002, -1.890122e-002, -1.840664e-002, + -1.790402e-002, -1.621263e-002, -1.418810e-002, -1.228494e-002, -1.019742e-002, -7.162075e-003, -4.457602e-003, -1.529338e-003, 1.791121e-003, + 4.794344e-003, 7.650136e-003, 1.038940e-002, 1.372243e-002, 1.673587e-002, 1.964875e-002, 2.229075e-002, 2.369957e-002, 2.534590e-002, + 2.645219e-002, 2.719062e-002, 2.762791e-002, 2.759310e-002, 2.718226e-002, 2.641654e-002, 2.535439e-002, 2.468030e-002, 2.270722e-002, + 2.095719e-002, 1.912157e-002, 1.641603e-002, 1.370782e-002, 1.107192e-002, 9.226676e-003, 7.001006e-003, 4.527941e-003, 2.405401e-003, + 1.095899e-003, -7.735699e-004, -2.655811e-003, -4.735180e-003, -6.672915e-003, -8.651625e-003, -1.120396e-002, -1.365487e-002, -1.591794e-002, + -1.860750e-002, -2.098193e-002, -2.306572e-002, -2.525036e-002, -2.654935e-002, -2.787005e-002, -2.960896e-002, -3.188759e-002, -3.304582e-002, + -3.362509e-002, -3.421006e-002, -3.493158e-002, -3.527951e-002, -3.541151e-002, -3.616275e-002, -3.667030e-002, -3.645722e-002, -3.534547e-002, + -3.384610e-002, -3.192936e-002, -3.007744e-002, -2.816464e-002, -2.599383e-002, -2.364519e-002, -2.056853e-002, -1.721039e-002, -1.379104e-002, + -1.101966e-002, -8.279911e-003, -5.622427e-003, -3.033445e-003, -3.456622e-005, 3.156631e-003, 6.854350e-003, 1.036193e-002, 1.319639e-002, + 1.547752e-002, 1.801207e-002, 2.066166e-002, 2.227310e-002, 2.414273e-002, 2.664079e-002, 2.869163e-002, 3.030688e-002, 3.128613e-002, + 3.210737e-002, 3.323065e-002, 3.430051e-002, 3.496578e-002, 3.573864e-002, 3.690582e-002, 3.775529e-002, 3.815364e-002, 3.812283e-002, + 3.809212e-002, 3.835298e-002, 3.836190e-002, 3.849059e-002, 3.875257e-002, 3.914174e-002, 3.934953e-002, 3.915474e-002, 3.886012e-002, + 3.830582e-002, 3.761627e-002, 3.683023e-002, 3.518517e-002, 3.367588e-002, 3.253379e-002, 3.106042e-002, 2.882998e-002, 2.622605e-002, + 2.310408e-002, 2.041273e-002, 1.741670e-002, 1.413832e-002, 1.057358e-002, 6.703073e-003, 3.278225e-003, 1.649775e-004, -3.372965e-003, + -7.129515e-003, -1.079104e-002, -1.454826e-002, -1.792318e-002, -2.190900e-002, -2.550286e-002, -2.851237e-002, -3.107104e-002, -3.305261e-002, + -3.461994e-002, -3.612647e-002, -3.740351e-002, -3.872915e-002, -3.959441e-002, -4.057913e-002, -4.139544e-002, -4.191170e-002, -4.162979e-002, + -4.134529e-002, -4.105334e-002, -4.031408e-002, -3.923497e-002, -3.834592e-002, -3.734537e-002, -3.683082e-002, -3.657909e-002, -3.589161e-002, + -3.498336e-002, -3.395033e-002, -3.273263e-002, -3.183399e-002, -3.094509e-002, -2.983059e-002, -2.840917e-002, -2.697139e-002, -2.530493e-002, + -2.424594e-002, -2.312261e-002, -2.148955e-002, -1.930896e-002, -1.706080e-002, -1.525190e-002, -1.337980e-002, -1.124707e-002, -8.980724e-003, + -6.313428e-003, -3.738550e-003, -1.155300e-003, 1.462800e-003, 4.117725e-003, 6.576173e-003, 9.156960e-003, 1.127338e-002, 1.330129e-002, + 1.552762e-002, 1.760814e-002, 1.996352e-002, 2.202453e-002, 2.414522e-002, 2.611037e-002, 2.795979e-002, 2.932229e-002, 3.054205e-002, + 3.140910e-002, 3.166134e-002, 3.185657e-002, 3.184578e-002, 3.196037e-002, 3.198783e-002, 3.158186e-002, 3.074272e-002, 2.957877e-002, + 2.857800e-002, 2.762290e-002, 2.656386e-002, 2.538639e-002, 2.389762e-002, 2.246380e-002, 2.096857e-002, 1.937127e-002, 1.802197e-002, + 1.668617e-002, 1.476751e-002, 1.326889e-002, 1.232738e-002, 1.173407e-002, 1.098063e-002, 1.018137e-002, 8.966329e-003, 7.971853e-003, + 7.195106e-003, 6.592168e-003, 5.924248e-003, 5.587663e-003, 4.914722e-003, 3.926392e-003, 2.833351e-003, 1.739622e-003, 7.716546e-004, + -1.380842e-004, -7.569115e-004, -1.178506e-003, -1.528822e-003, -1.645077e-003, -1.569529e-003, -1.275292e-003, -7.566247e-004, -2.034698e-004, + 5.606138e-004, 1.390843e-003, 2.289548e-003, 3.109334e-003, 3.698633e-003, 4.000512e-003, 4.394167e-003, 5.186160e-003, 6.097267e-003, + 6.857379e-003, 7.680091e-003, 8.616599e-003, 8.980693e-003, 8.861837e-003, 8.835621e-003, 9.021308e-003, 8.928020e-003, 8.789753e-003, + 8.395309e-003, 7.397421e-003, 6.203323e-003, 4.886902e-003, 3.676173e-003, 2.702861e-003, 1.350104e-003, -2.499577e-005, -1.655689e-003, + -3.220519e-003, -4.921209e-003, -6.453772e-003, -7.723878e-003, -9.124404e-003, -1.084593e-002, -1.325368e-002, -1.553902e-002, -1.763123e-002, + -1.977015e-002, -2.151874e-002, -2.297458e-002, -2.406587e-002, -2.473961e-002, -2.485753e-002, -2.495961e-002, -2.472564e-002, -2.456694e-002, + -2.428995e-002, -2.371211e-002, -2.352511e-002, -2.351498e-002, -2.332999e-002, -2.282696e-002, -2.226596e-002, -2.177532e-002, -2.128669e-002, + -2.076872e-002, -2.050420e-002, -2.019321e-002, -1.987545e-002, -1.972984e-002, -1.932872e-002, -1.885399e-002, -1.850600e-002, -1.837970e-002, + -1.833540e-002, -1.839107e-002, -1.843416e-002, -1.862415e-002, -1.869155e-002, -1.873371e-002, -1.912060e-002, -1.936999e-002, -1.925480e-002, + -1.895933e-002, -1.855780e-002, -1.807826e-002, -1.735073e-002, -1.647183e-002, -1.582349e-002, -1.481334e-002, -1.282715e-002, -1.087725e-002, + -8.901899e-003, -6.841317e-003, -4.661811e-003, -2.376884e-003, -3.888844e-004, 1.512233e-003, 3.396649e-003, 5.589986e-003, 7.739855e-003, + 9.897991e-003, 1.213807e-002, 1.422586e-002, 1.585109e-002, 1.718941e-002, 1.834364e-002, 1.934096e-002, 2.030101e-002, 2.100210e-002, + 2.160543e-002, 2.235764e-002, 2.316455e-002, 2.402791e-002, 2.489666e-002, 2.581245e-002, 2.680838e-002, 2.806215e-002, 2.921748e-002, + 3.009971e-002, 3.071540e-002, 3.143973e-002, 3.207420e-002, 3.255228e-002, 3.285824e-002, 3.299576e-002, 3.321511e-002, 3.318542e-002, + 3.234970e-002, 3.111225e-002, 2.985794e-002, 2.849297e-002, 2.731962e-002, 2.595160e-002, 2.429608e-002, 2.257101e-002, 2.040358e-002, + 1.816682e-002, 1.598018e-002, 1.377310e-002, 1.156882e-002, 9.352989e-003, 7.217448e-003, 5.121836e-003, 3.106883e-003, 1.026752e-003, + -1.154152e-003, -3.090965e-003, -4.711256e-003, -6.128800e-003, -7.401729e-003, -8.748151e-003, -1.005783e-002, -1.134497e-002, -1.191050e-002, + -1.211458e-002, -1.228022e-002, -1.230333e-002, -1.198417e-002, -1.150072e-002, -1.105589e-002, -1.059785e-002, -1.065774e-002, -1.056733e-002, + -1.061557e-002, -1.064500e-002, -1.053697e-002, -1.033711e-002, -9.531796e-003, -8.518715e-003, -7.761465e-003, -7.395734e-003, -7.076393e-003, + -6.666961e-003, -6.179050e-003, -5.830931e-003, -5.689979e-003, -5.596322e-003, -5.533289e-003, -5.495375e-003, -5.351921e-003, -4.527135e-003, + -3.682631e-003, -2.747405e-003, -1.492281e-003, -2.266926e-004, 1.077606e-003, 2.516098e-003, 3.965081e-003, 5.034290e-003, 5.899161e-003, + 6.721525e-003, 7.365934e-003, 7.930988e-003, 8.618543e-003, 9.532525e-003, 1.080112e-002, 1.184946e-002, 1.232808e-002, 1.268872e-002, + 1.273024e-002, 1.253847e-002, 1.210405e-002, 1.142066e-002, 1.060681e-002, 9.827226e-003, 8.979523e-003, 7.749134e-003, 6.172073e-003, + 4.464178e-003, 2.671899e-003, 6.769367e-004, -1.444927e-003, -3.662987e-003, -5.961754e-003, -7.891737e-003, -9.788798e-003, -1.196441e-002, + -1.426424e-002, -1.650583e-002, -1.813978e-002, -1.952051e-002, -2.079222e-002, -2.199140e-002, -2.318612e-002, -2.428860e-002, -2.536385e-002, + -2.632518e-002, -2.708004e-002, -2.772888e-002, -2.793252e-002, -2.763354e-002, -2.729784e-002, -2.680502e-002, -2.625252e-002, -2.562801e-002, + -2.520969e-002, -2.459134e-002, -2.402998e-002, -2.367944e-002, -2.349122e-002, -2.347004e-002, -2.268655e-002, -2.177420e-002, -2.102067e-002, + -2.046612e-002, -2.017435e-002, -2.010539e-002, -1.984907e-002, -1.941804e-002, -1.894860e-002, -1.847332e-002, -1.803340e-002, -1.760023e-002, + -1.714239e-002, -1.670953e-002, -1.597351e-002, -1.467756e-002, -1.334437e-002, -1.143254e-002, -9.190748e-003, -6.932587e-003, -4.763536e-003, + -2.551227e-003, -2.080688e-004, 2.219243e-003, 4.409672e-003, 6.443073e-003, 8.377831e-003, 1.021087e-002, 1.201156e-002, 1.374064e-002, + 1.545830e-002, 1.702197e-002, 1.888390e-002, 2.071518e-002, 2.245935e-002, 2.344217e-002, 2.440418e-002, 2.522417e-002, 2.594909e-002, + 2.645563e-002, 2.669631e-002, 2.675554e-002, 2.674782e-002, 2.667195e-002, 2.652849e-002, 2.627962e-002, 2.596760e-002, 2.567768e-002, + 2.546515e-002, 2.536571e-002, 2.524167e-002, 2.506378e-002, 2.497145e-002, 2.491217e-002, 2.476054e-002, 2.489279e-002, 2.497366e-002, + 2.503084e-002, 2.515165e-002, 2.516790e-002, 2.510283e-002, 2.498922e-002, 2.482976e-002, 2.454682e-002, 2.416579e-002, 2.381887e-002, + 2.336490e-002, 2.242567e-002, 2.132110e-002, 2.024481e-002, 1.909589e-002, 1.787123e-002, 1.656772e-002, 1.532286e-002, 1.415350e-002, + 1.311266e-002, 1.220944e-002, 1.149946e-002, 1.055560e-002, 9.755882e-003, 8.992160e-003, 8.292029e-003, 7.805815e-003, 7.355886e-003, + 6.959233e-003, 6.586886e-003, 6.569916e-003, 6.496576e-003, 6.406097e-003, 6.001400e-003, 5.458051e-003, 4.926243e-003, 4.315321e-003, + 3.649063e-003, 2.913955e-003, 2.172937e-003, 1.323520e-003, 2.494032e-004, -1.016842e-003, -2.265649e-003, -3.811695e-003, -5.729069e-003, + -7.528427e-003, -9.359915e-003, -1.115366e-002, -1.289618e-002, -1.436931e-002, -1.565365e-002, -1.676667e-002, -1.776497e-002, -1.858983e-002, + -1.937746e-002, -2.036896e-002, -2.135186e-002, -2.212974e-002, -2.264182e-002, -2.316925e-002, -2.365002e-002, -2.414610e-002, -2.478922e-002, + -2.543580e-002, -2.605312e-002, -2.671318e-002, -2.741154e-002, -2.847393e-002, -2.961953e-002, -3.076283e-002, -3.149091e-002, -3.195223e-002, + -3.249157e-002, -3.313850e-002, -3.396393e-002, -3.494448e-002, -3.608686e-002, -3.721108e-002, -3.841949e-002, -3.989676e-002, -4.134524e-002, + -4.278263e-002, -4.418183e-002, -4.539317e-002, -4.600373e-002, -4.607777e-002, -4.588179e-002, -4.533499e-002, -4.441912e-002, -4.314347e-002, + -4.137390e-002, -3.963328e-002, -3.768302e-002, -3.546730e-002, -3.298884e-002, -3.033928e-002, -2.740187e-002, -2.440793e-002, -2.134315e-002, + -1.816116e-002, -1.496300e-002, -1.160043e-002, -8.079983e-003, -4.961348e-003, -1.850219e-003, 1.395687e-003, 4.545362e-003, 7.506101e-003, + 1.021898e-002, 1.274419e-002, 1.520360e-002, 1.771136e-002, 2.009179e-002, 2.236379e-002, 2.460752e-002, 2.660510e-002, 2.826751e-002, + 2.992299e-002, 3.158282e-002, 3.312083e-002, 3.459771e-002, 3.604084e-002, 3.741493e-002, 3.872687e-002, 3.999709e-002, 4.147162e-002, + 4.292926e-002, 4.437781e-002, 4.533007e-002, 4.592974e-002, 4.629982e-002, 4.658148e-002, 4.703863e-002, 4.741979e-002, 4.756681e-002, + 4.750426e-002, 4.725191e-002, 4.678453e-002, 4.614883e-002, 4.559104e-002, 4.489925e-002, 4.352546e-002, 4.195567e-002, 4.020170e-002, + 3.827532e-002, 3.620106e-002, 3.414337e-002, 3.196287e-002, 2.964226e-002, 2.729380e-002, 2.493518e-002, 2.286074e-002, 2.093077e-002, + 1.913063e-002, 1.697084e-002, 1.489076e-002, 1.292853e-002, 1.105977e-002, 9.244886e-003, 7.475782e-003, 5.788072e-003, 4.388515e-003, + 3.096280e-003, 1.877727e-003, 7.323806e-004, -3.526722e-004, -1.412134e-003, -3.003547e-003, -4.489163e-003, -5.870796e-003, -7.201572e-003, + -8.424293e-003, -9.596610e-003, -1.082831e-002, -1.199337e-002, -1.312588e-002, -1.417553e-002, -1.518676e-002, -1.613656e-002, -1.694176e-002, + -1.763842e-002, -1.849378e-002, -1.924874e-002, -1.972425e-002, -2.008452e-002, -2.044026e-002, -2.070091e-002, -2.083397e-002, -2.080791e-002, + -2.064436e-002, -2.015454e-002, -1.948716e-002, -1.874811e-002, -1.789859e-002, -1.745872e-002, -1.705482e-002, -1.657329e-002, -1.605583e-002, + -1.526581e-002, -1.441425e-002, -1.330928e-002, -1.221088e-002, -1.114925e-002, -1.007136e-002, -9.052310e-003, -8.101342e-003, -6.970330e-003, + -5.879696e-003, -5.170864e-003, -4.381743e-003, -3.250538e-003, -2.109429e-003, -1.069344e-003, 2.367350e-005, 1.139974e-003, 2.068386e-003, + 2.867085e-003, 3.617170e-003, 4.322660e-003, 5.017536e-003, 5.853732e-003, 6.643603e-003, 6.956689e-003, 7.182838e-003, 7.277807e-003, + 7.253118e-003, 7.160764e-003, 6.984247e-003, 6.586128e-003, 6.184843e-003, 6.037641e-003, 6.031915e-003, 5.939830e-003, 5.841023e-003, + 5.748898e-003, 5.558739e-003, 4.700314e-003, 3.831358e-003, 2.986117e-003, 2.095344e-003, 1.179259e-003, 5.411149e-004, -2.201710e-004, + -1.136316e-003, -2.083543e-003, -3.000939e-003, -3.863737e-003, -4.718609e-003, -5.564055e-003, -6.398729e-003, -7.477716e-003, -8.498700e-003, + -9.038157e-003, -9.474478e-003, -9.704287e-003, -9.998273e-003, -1.022132e-002, -1.041375e-002, -1.061439e-002, -1.084289e-002, -1.115954e-002, + -1.152177e-002, -1.116880e-002, -1.066547e-002, -1.016949e-002, -1.040908e-002, -1.081580e-002, -1.135060e-002, -1.212178e-002, -1.296112e-002, + -1.353674e-002, -1.423231e-002, -1.510847e-002, -1.505159e-002, -1.500618e-002, -1.508254e-002, -1.530489e-002, -1.563732e-002, -1.563577e-002, + -1.564875e-002, -1.650280e-002, -1.750175e-002, -1.851541e-002, -1.941360e-002, -1.841245e-002, -1.718508e-002, -1.572792e-002, -1.396834e-002, + -1.218440e-002, -1.033563e-002, -8.433616e-003, -6.438788e-003, -4.373265e-003, -2.216235e-003, -2.579775e-017}, + {0.000000e+000, -6.037378e-002, -6.469045e-003, 2.566552e-002, 4.210873e-002, 4.124150e-002, 3.096052e-002, 1.524907e-002, 2.785362e-003, + -7.728595e-003, -1.698578e-002, -2.445510e-002, -2.919319e-002, -3.148743e-002, -3.142251e-002, -2.962631e-002, -2.550305e-002, -1.973155e-002, + -1.427010e-002, -7.931907e-003, -6.112233e-004, 7.491481e-003, 1.559396e-002, 2.351770e-002, 2.877784e-002, 3.255009e-002, 3.492912e-002, + 3.425700e-002, 3.255751e-002, 2.866576e-002, 2.492890e-002, 2.033932e-002, 1.534457e-002, 7.894215e-003, 5.786402e-004, -5.714463e-003, + -9.626747e-003, -1.367878e-002, -1.634066e-002, -1.844778e-002, -2.053967e-002, -2.310193e-002, -2.553322e-002, -2.710448e-002, -2.760002e-002, + -2.805446e-002, -2.734367e-002, -2.496195e-002, -2.176249e-002, -1.742829e-002, -1.233449e-002, -8.869814e-003, -4.536597e-003, 2.414270e-004, + 3.938743e-003, 7.684524e-003, 1.100897e-002, 1.401072e-002, 1.595778e-002, 1.702782e-002, 1.884968e-002, 1.967238e-002, 1.974188e-002, + 2.062971e-002, 2.013866e-002, 1.842569e-002, 1.771824e-002, 1.658602e-002, 1.430307e-002, 1.201499e-002, 9.553737e-003, 6.269042e-003, + 3.360829e-003, 1.254667e-003, -5.182312e-004, -2.372817e-003, -3.097430e-003, -3.573376e-003, -4.153988e-003, -4.488018e-003, -4.492118e-003, + -5.161001e-003, -6.639471e-003, -7.166803e-003, -7.256751e-003, -7.773312e-003, -7.994558e-003, -7.889688e-003, -8.911424e-003, -9.977403e-003, + -1.122079e-002, -1.155498e-002, -1.100076e-002, -1.071660e-002, -1.011592e-002, -9.500469e-003, -7.222929e-003, -4.232981e-003, -2.761877e-003, + -1.355558e-003, 5.808436e-004, 3.069494e-003, 4.022636e-003, 3.910751e-003, 4.202777e-003, 5.278721e-003, 4.696964e-003, 3.762970e-003, + 3.120345e-003, 3.765287e-003, 3.939764e-003, 4.252810e-003, 5.437575e-003, 5.450772e-003, 6.156525e-003, 6.802959e-003, 6.699919e-003, + 6.798808e-003, 7.977533e-003, 8.510721e-003, 9.513612e-003, 1.051562e-002, 1.177616e-002, 1.312116e-002, 1.386606e-002, 1.373182e-002, + 1.313098e-002, 1.166804e-002, 9.809053e-003, 7.025868e-003, 3.968250e-003, 1.770472e-003, -5.016523e-004, -1.935521e-003, -3.686553e-003, + -4.829481e-003, -4.830921e-003, -6.160095e-003, -6.258897e-003, -6.919228e-003, -7.621123e-003, -8.593602e-003, -1.016672e-002, -1.202727e-002, + -1.408548e-002, -1.511033e-002, -1.554094e-002, -1.610314e-002, -1.532400e-002, -1.542983e-002, -1.570783e-002, -1.507096e-002, -1.528697e-002, + -1.455619e-002, -1.406848e-002, -1.362672e-002, -1.223586e-002, -1.119136e-002, -1.028329e-002, -1.046815e-002, -9.989788e-003, -9.566241e-003, + -8.986772e-003, -8.049935e-003, -5.965631e-003, -4.368594e-003, -3.173520e-003, -1.001296e-003, -3.109018e-004, 2.629551e-004, 1.745318e-003, + 3.204123e-003, 4.035660e-003, 6.218978e-003, 7.753124e-003, 9.885569e-003, 1.116178e-002, 1.245254e-002, 1.320182e-002, 1.478459e-002, + 1.509932e-002, 1.588767e-002, 1.658916e-002, 1.657746e-002, 1.548080e-002, 1.605646e-002, 1.608772e-002, 1.586954e-002, 1.626351e-002, + 1.626150e-002, 1.550502e-002, 1.512801e-002, 1.410334e-002, 1.271030e-002, 1.192894e-002, 1.140073e-002, 1.063790e-002, 9.100201e-003, + 9.333021e-003, 9.095271e-003, 8.481116e-003, 7.338499e-003, 5.751654e-003, 3.801295e-003, 1.168824e-003, -6.809504e-004, -3.116799e-003, + -5.788835e-003, -7.881047e-003, -9.965807e-003, -1.219276e-002, -1.443369e-002, -1.563531e-002, -1.753826e-002, -1.941432e-002, -2.113058e-002, + -2.274960e-002, -2.438502e-002, -2.659924e-002, -2.772699e-002, -2.903151e-002, -3.029340e-002, -3.139278e-002, -3.169041e-002, -3.153138e-002, + -3.158971e-002, -3.159664e-002, -3.035610e-002, -2.937392e-002, -2.840589e-002, -2.711180e-002, -2.504244e-002, -2.289625e-002, -2.159539e-002, + -1.963929e-002, -1.684629e-002, -1.452578e-002, -1.154706e-002, -8.112968e-003, -3.753865e-003, 1.071476e-003, 5.515752e-003, 1.012575e-002, + 1.547505e-002, 2.051150e-002, 2.510707e-002, 2.964321e-002, 3.356479e-002, 3.731168e-002, 4.017798e-002, 4.232159e-002, 4.555931e-002, + 4.874874e-002, 5.124080e-002, 5.340434e-002, 5.536813e-002, 5.637646e-002, 5.676013e-002, 5.718229e-002, 5.732404e-002, 5.762667e-002, + 5.706885e-002, 5.536933e-002, 5.288875e-002, 5.123134e-002, 4.912973e-002, 4.607073e-002, 4.270710e-002, 3.889235e-002, 3.498278e-002, + 3.045412e-002, 2.560045e-002, 2.085320e-002, 1.701732e-002, 1.295545e-002, 7.964438e-003, 3.261862e-003, -1.359544e-003, -5.152683e-003, + -8.195529e-003, -1.217268e-002, -1.566566e-002, -1.773716e-002, -1.973003e-002, -2.213997e-002, -2.455584e-002, -2.695953e-002, -2.935791e-002, + -3.138782e-002, -3.306528e-002, -3.517096e-002, -3.749783e-002, -3.829531e-002, -3.880917e-002, -3.934775e-002, -3.965057e-002, -4.061743e-002, + -4.112536e-002, -4.111388e-002, -4.129607e-002, -4.209697e-002, -4.240947e-002, -4.211714e-002, -4.203469e-002, -4.247345e-002, -4.244573e-002, + -4.312946e-002, -4.308403e-002, -4.291717e-002, -4.212285e-002, -4.151760e-002, -4.083205e-002, -3.895152e-002, -3.679633e-002, -3.512734e-002, + -3.359432e-002, -3.169090e-002, -3.006724e-002, -2.781366e-002, -2.505097e-002, -2.203950e-002, -1.970662e-002, -1.742626e-002, -1.380310e-002, + -1.021928e-002, -7.155949e-003, -4.269678e-003, -7.184659e-004, 2.214367e-003, 4.618178e-003, 7.212744e-003, 1.011789e-002, 1.197997e-002, + 1.395555e-002, 1.624820e-002, 1.828034e-002, 1.945293e-002, 2.061147e-002, 2.232654e-002, 2.488742e-002, 2.696525e-002, 2.888020e-002, + 3.086175e-002, 3.208284e-002, 3.300377e-002, 3.399652e-002, 3.501373e-002, 3.538998e-002, 3.593130e-002, 3.624569e-002, 3.671883e-002, + 3.694650e-002, 3.611745e-002, 3.491173e-002, 3.379608e-002, 3.215676e-002, 3.050474e-002, 2.942390e-002, 2.851606e-002, 2.729075e-002, + 2.556305e-002, 2.351257e-002, 2.206939e-002, 2.063788e-002, 1.874339e-002, 1.674897e-002, 1.499513e-002, 1.282930e-002, 1.031069e-002, + 8.318007e-003, 6.849225e-003, 4.868687e-003, 2.799463e-003, 6.554596e-004, -1.657586e-003, -3.815220e-003, -6.019962e-003, -8.374727e-003, + -1.060240e-002, -1.246368e-002, -1.438541e-002, -1.599249e-002, -1.711392e-002, -1.829963e-002, -1.957395e-002, -2.076187e-002, -2.192923e-002, + -2.255913e-002, -2.259167e-002, -2.263124e-002, -2.274266e-002, -2.276364e-002, -2.277775e-002, -2.255446e-002, -2.185118e-002, -2.075609e-002, + -1.997064e-002, -1.953343e-002, -1.885195e-002, -1.806234e-002, -1.703671e-002, -1.563231e-002, -1.427013e-002, -1.333103e-002, -1.232643e-002, + -1.134038e-002, -9.914061e-003, -7.802220e-003, -4.849087e-003, -2.247817e-003, 7.093138e-005, 2.131760e-003, 3.507166e-003, 5.108711e-003, + 7.054914e-003, 9.141091e-003, 1.116481e-002, 1.284738e-002, 1.467287e-002, 1.625545e-002, 1.738957e-002, 1.827707e-002, 1.889924e-002, + 1.908773e-002, 1.882092e-002, 1.811302e-002, 1.646014e-002, 1.450542e-002, 1.299139e-002, 1.174056e-002, 1.085030e-002, 9.895594e-003, + 8.697252e-003, 7.162899e-003, 5.196303e-003, 3.473491e-003, 1.543164e-003, -4.213685e-004, -2.074557e-003, -3.537647e-003, -4.668432e-003, + -6.406290e-003, -8.097026e-003, -9.015186e-003, -9.838171e-003, -1.069196e-002, -1.162989e-002, -1.209421e-002, -1.231395e-002, -1.258722e-002, + -1.212346e-002, -1.175342e-002, -1.128135e-002, -1.041603e-002, -9.455872e-003, -7.964977e-003, -6.990851e-003, -6.215775e-003, -5.414600e-003, + -4.704862e-003, -4.048329e-003, -3.610792e-003, -3.213320e-003, -2.062114e-003, -1.004225e-003, 2.307415e-004, 1.273663e-003, 2.001758e-003, + 3.443290e-003, 5.198106e-003, 6.909056e-003, 8.372257e-003, 8.707803e-003, 9.031754e-003, 9.231475e-003, 9.137415e-003, 8.805090e-003, + 8.294868e-003, 8.180670e-003, 8.338760e-003, 7.864747e-003, 7.392176e-003, 6.555473e-003, 5.747565e-003, 6.255572e-003, 6.865768e-003, + 7.337572e-003, 7.390398e-003, 6.762009e-003, 6.973934e-003, 7.537761e-003, 7.971484e-003, 8.123720e-003, 7.604849e-003, 7.216810e-003, + 7.532116e-003, 7.600449e-003, 7.221970e-003, 6.604117e-003, 5.589618e-003, 4.770874e-003, 4.229896e-003, 3.640737e-003, 2.992700e-003, + 2.006548e-003, 8.023127e-004, -1.177813e-004, -9.945589e-004, -2.032928e-003, -3.132484e-003, -4.516936e-003, -5.695809e-003, -6.470323e-003, + -7.195920e-003, -7.928012e-003, -8.794739e-003, -9.580493e-003, -9.773680e-003, -9.881701e-003, -1.006301e-002, -1.045548e-002, -1.122410e-002, + -1.164230e-002, -1.163747e-002, -1.138447e-002, -1.123806e-002, -1.134106e-002, -1.185723e-002, -1.204192e-002, -1.168398e-002, -1.126568e-002, + -1.089795e-002, -1.090845e-002, -1.105166e-002, -1.103055e-002, -1.023981e-002, -9.361922e-003, -8.371049e-003, -7.578562e-003, -7.349685e-003, + -7.310679e-003, -7.294808e-003, -7.335154e-003, -7.301579e-003, -7.330223e-003, -7.763455e-003, -8.227565e-003, -8.066161e-003, -7.718895e-003, + -7.156124e-003, -6.877906e-003, -6.921739e-003, -6.834300e-003, -6.052919e-003, -5.057393e-003, -3.905368e-003, -2.622174e-003, -1.349292e-003, + -5.034098e-004, 5.799790e-004, 1.529785e-003, 2.692727e-003, 3.428851e-003, 4.137996e-003, 4.574666e-003, 5.135323e-003, 5.730637e-003, + 6.553256e-003, 7.186724e-003, 7.605896e-003, 7.715709e-003, 7.881939e-003, 8.185649e-003, 8.335573e-003, 8.606283e-003, 8.836317e-003, + 8.999634e-003, 9.223964e-003, 9.686810e-003, 9.935024e-003, 1.040059e-002, 1.090493e-002, 1.146881e-002, 1.189723e-002, 1.261770e-002, + 1.321377e-002, 1.409098e-002, 1.510879e-002, 1.597020e-002, 1.640195e-002, 1.658232e-002, 1.679451e-002, 1.700724e-002, 1.730688e-002, + 1.765152e-002, 1.793573e-002, 1.790739e-002, 1.797367e-002, 1.782853e-002, 1.753643e-002, 1.766969e-002, 1.759952e-002, 1.742829e-002, + 1.705576e-002, 1.673999e-002, 1.628532e-002, 1.573928e-002, 1.533888e-002, 1.467525e-002, 1.365310e-002, 1.227108e-002, 1.074951e-002, + 9.222079e-003, 7.718998e-003, 6.239504e-003, 4.895915e-003, 3.669358e-003, 2.454426e-003, 1.345865e-003, 2.091807e-004, -7.286221e-004, + -1.465081e-003, -2.030929e-003, -2.784536e-003, -3.528809e-003, -4.517121e-003, -5.207065e-003, -5.788640e-003, -6.267730e-003, -6.845133e-003, + -7.835692e-003, -8.727780e-003, -9.531163e-003, -1.022097e-002, -1.088633e-002, -1.122360e-002, -1.120520e-002, -1.090742e-002, -1.051147e-002, + -9.996182e-003, -9.743216e-003, -9.607908e-003, -9.309272e-003, -8.930210e-003, -8.891010e-003, -8.848051e-003, -8.519383e-003, -7.971881e-003, + -7.183360e-003, -6.413700e-003, -5.484757e-003, -4.647386e-003, -4.008340e-003, -3.364449e-003, -2.953996e-003, -2.620614e-003, -2.334315e-003, + -2.267890e-003, -2.359967e-003, -2.927530e-003, -3.405608e-003, -4.080607e-003, -4.935338e-003, -5.752372e-003, -6.416990e-003, -7.381614e-003, + -8.781892e-003, -1.029007e-002, -1.184245e-002, -1.303781e-002, -1.434610e-002, -1.560290e-002, -1.713361e-002, -1.882600e-002, -2.064117e-002, + -2.229700e-002, -2.428934e-002, -2.626077e-002, -2.812560e-002, -2.924710e-002, -2.997286e-002, -3.080780e-002, -3.126990e-002, -3.152929e-002, + -3.173127e-002, -3.176193e-002, -3.189371e-002, -3.190178e-002, -3.184276e-002, -3.153658e-002, -3.104103e-002, -3.051133e-002, -2.995644e-002, + -2.890267e-002, -2.733478e-002, -2.565742e-002, -2.418164e-002, -2.291387e-002, -2.122296e-002, -1.944181e-002, -1.748546e-002, -1.522782e-002, + -1.309264e-002, -1.141865e-002, -9.949129e-003, -8.412726e-003, -6.820412e-003, -5.404121e-003, -4.063398e-003, -2.967430e-003, -1.739144e-003, + -5.983088e-004, 4.915583e-004, 1.805720e-003, 3.122572e-003, 4.539031e-003, 6.164998e-003, 8.052443e-003, 1.022771e-002, 1.228164e-002, + 1.396105e-002, 1.559995e-002, 1.727900e-002, 1.876976e-002, 2.034776e-002, 2.214442e-002, 2.390706e-002, 2.563993e-002, 2.734110e-002, + 2.902073e-002, 3.079046e-002, 3.247927e-002, 3.409687e-002, 3.581839e-002, 3.786579e-002, 3.975921e-002, 4.158360e-002, 4.319656e-002, + 4.466196e-002, 4.597251e-002, 4.729922e-002, 4.886675e-002, 5.051901e-002, 5.214561e-002, 5.351464e-002, 5.482532e-002, 5.588239e-002, + 5.625881e-002, 5.646446e-002, 5.666459e-002, 5.687511e-002, 5.705516e-002, 5.734115e-002, 5.764511e-002, 5.804020e-002, 5.810874e-002, + 5.800663e-002, 5.769519e-002, 5.719326e-002, 5.666811e-002, 5.596670e-002, 5.500465e-002, 5.389550e-002, 5.246805e-002, 5.085580e-002, + 4.908175e-002, 4.716307e-002, 4.540169e-002, 4.352360e-002, 4.129263e-002, 3.899745e-002, 3.640574e-002, 3.340002e-002, 3.029288e-002, + 2.713674e-002, 2.419922e-002, 2.126105e-002, 1.814286e-002, 1.467007e-002, 1.126856e-002, 8.035846e-003, 4.936749e-003, 2.034712e-003, + -8.402643e-004, -3.798186e-003, -6.713516e-003, -9.620852e-003, -1.236111e-002, -1.469108e-002, -1.673423e-002, -1.874035e-002, -2.076505e-002, + -2.275763e-002, -2.457012e-002, -2.626111e-002, -2.762188e-002, -2.881963e-002, -3.008063e-002, -3.138960e-002, -3.280146e-002, -3.417407e-002, + -3.500368e-002, -3.558858e-002, -3.613100e-002, -3.685923e-002, -3.757275e-002, -3.824011e-002, -3.881196e-002, -3.945278e-002, -4.014865e-002, + -4.089369e-002, -4.152280e-002, -4.222498e-002, -4.300391e-002, -4.357753e-002, -4.389153e-002, -4.417572e-002, -4.468131e-002, -4.536676e-002, + -4.616546e-002, -4.687856e-002, -4.775342e-002, -4.867290e-002, -4.967323e-002, -5.078950e-002, -5.206538e-002, -5.347670e-002, -5.497112e-002, + -5.641970e-002, -5.769714e-002, -5.885459e-002, -6.002815e-002, -6.126985e-002, -6.245799e-002, -6.357684e-002, -6.478398e-002, -6.606717e-002, + -6.743520e-002, -6.884572e-002, -7.012717e-002, -7.142284e-002, -7.255595e-002, -7.339904e-002, -7.408170e-002, -7.470702e-002, -7.521750e-002, + -7.548688e-002, -7.547660e-002, -7.509164e-002, -7.452128e-002, -7.366689e-002, -7.234040e-002, -7.051609e-002, -6.851947e-002, -6.624192e-002, + -6.388047e-002, -6.132734e-002, -5.849585e-002, -5.552270e-002, -5.250218e-002, -4.927400e-002, -4.567800e-002, -4.185546e-002, -3.780921e-002, + -3.356174e-002, -2.912511e-002, -2.457000e-002, -1.988600e-002, -1.517933e-002, -1.038374e-002, -5.597219e-003, -7.254290e-004, 4.143014e-003, + 8.949597e-003, 1.368268e-002, 1.831937e-002, 2.285228e-002, 2.744269e-002, 3.192047e-002, 3.617557e-002, 4.037269e-002, 4.418612e-002, + 4.761960e-002, 5.086220e-002, 5.387639e-002, 5.665861e-002, 5.920675e-002, 6.136500e-002, 6.316524e-002, 6.463713e-002, 6.592216e-002, + 6.690185e-002, 6.769238e-002, 6.832333e-002, 6.879984e-002, 6.902306e-002, 6.894227e-002, 6.865506e-002, 6.807717e-002, 6.729998e-002, + 6.639905e-002, 6.538164e-002, 6.426421e-002, 6.306813e-002, 6.194848e-002, 6.087209e-002, 5.986571e-002, 5.899887e-002, 5.816417e-002, + 5.746282e-002, 5.678570e-002, 5.594664e-002, 5.501553e-002, 5.413006e-002, 5.330730e-002, 5.257485e-002, 5.192842e-002, 5.132706e-002, + 5.074689e-002, 5.050003e-002, 5.035575e-002, 5.021581e-002, 5.003469e-002, 4.979947e-002, 4.952558e-002, 4.932980e-002, 4.909361e-002, + 4.856641e-002, 4.766466e-002, 4.700044e-002, 4.636619e-002, 4.567631e-002, 4.477307e-002, 4.382717e-002, 4.287739e-002, 4.202948e-002, + 4.108119e-002, 4.025089e-002, 3.912707e-002, 3.823687e-002, 3.738083e-002, 3.653945e-002, 3.545934e-002, 3.423688e-002, 3.288536e-002, + 3.152637e-002, 3.012073e-002, 2.871450e-002, 2.720307e-002, 2.576423e-002, 2.421299e-002, 2.252979e-002, 2.060456e-002, 1.848958e-002, + 1.630198e-002, 1.402818e-002, 1.163621e-002, 9.157455e-003, 6.467600e-003, 3.681196e-003, 1.076935e-003, -1.611882e-003, -4.520276e-003, + -7.677941e-003, -1.089190e-002, -1.424759e-002, -1.762310e-002, -2.098455e-002, -2.436754e-002, -2.750442e-002, -2.988875e-002, -3.221942e-002, + -3.458169e-002, -3.712299e-002, -3.970633e-002, -4.240767e-002, -4.501945e-002, -4.757800e-002, -5.010187e-002, -5.255989e-002, -5.471572e-002, + -5.653811e-002, -5.833453e-002, -6.018337e-002, -6.191623e-002, -6.360020e-002, -6.524241e-002, -6.684681e-002, -6.843125e-002, -7.002673e-002, + -7.152629e-002, -7.240049e-002, -7.300993e-002, -7.388027e-002, -7.498386e-002, -7.612198e-002, -7.717995e-002, -7.794049e-002, -7.872595e-002, + -7.944172e-002, -8.020577e-002, -8.077332e-002, -7.938030e-002, -7.806317e-002, -7.696373e-002, -7.589552e-002, -7.481753e-002, -7.383193e-002, + -7.278924e-002, -7.177350e-002, -7.024781e-002, -6.849253e-002, -6.630172e-002, -6.048931e-002, -5.491701e-002, -4.916730e-002, -4.324916e-002, + -3.726084e-002, -3.118685e-002, -2.504838e-002, -1.886408e-002, -1.263625e-002, -6.343596e-003, -4.415809e-016}}; -const double* getEmorCurve(int i) -{ - return kEmor[i]; -} +const double* getEmorCurve(int i) { return kEmor[i]; } - -} // namespace hdr -} // namespace aliceVision \ No newline at end of file +} // namespace hdr +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/hdr/emorCurve.hpp b/src/aliceVision/hdr/emorCurve.hpp index 62c8b58921..c7f3b68e5c 100644 --- a/src/aliceVision/hdr/emorCurve.hpp +++ b/src/aliceVision/hdr/emorCurve.hpp @@ -34,6 +34,5 @@ const double* getEmorInvCurve(int i); */ const double* getEmorCurve(int i); - -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/emorCurveInv.cpp b/src/aliceVision/hdr/emorCurveInv.cpp index 9e3cd531c3..8649fc733c 100644 --- a/src/aliceVision/hdr/emorCurveInv.cpp +++ b/src/aliceVision/hdr/emorCurveInv.cpp @@ -14,6721 +14,2972 @@ namespace aliceVision { namespace hdr { static const double kEmorInv[kEmorMDimension][kEmorQuantization] = { - { - 0.000000e+000, 1.477256e-003, 2.737898e-003, 3.858105e-003, - 4.877501e-003, 5.819777e-003, 6.700519e-003, 7.532015e-003, - 8.323902e-003, 9.085110e-003, 9.816742e-003, 1.051719e-002, - 1.119348e-002, 1.184927e-002, 1.248706e-002, 1.310836e-002, - 1.371501e-002, 1.430845e-002, 1.488919e-002, 1.545915e-002, - 1.601947e-002, 1.656868e-002, 1.710786e-002, 1.763954e-002, - 1.816409e-002, 1.868202e-002, 1.919384e-002, 1.970013e-002, - 2.020145e-002, 2.069803e-002, 2.119012e-002, 2.167658e-002, - 2.215623e-002, 2.263206e-002, 2.310436e-002, 2.357320e-002, - 2.403910e-002, 2.450196e-002, 2.496192e-002, 2.541903e-002, - 2.587329e-002, 2.632477e-002, 2.677024e-002, 2.721375e-002, - 2.765540e-002, 2.809528e-002, 2.853420e-002, 2.897388e-002, - 2.941151e-002, 2.984418e-002, 3.027385e-002, 3.070138e-002, - 3.112383e-002, 3.154393e-002, 3.196235e-002, 3.237907e-002, - 3.279429e-002, 3.320812e-002, 3.362059e-002, 3.403191e-002, - 3.444217e-002, 3.485135e-002, 3.525754e-002, 3.566134e-002, - 3.606424e-002, 3.646630e-002, 3.686738e-002, 3.726786e-002, - 3.766775e-002, 3.806709e-002, 3.846602e-002, 3.886461e-002, - 3.926180e-002, 3.965717e-002, 4.005235e-002, 4.044736e-002, - 4.084235e-002, 4.123742e-002, 4.163233e-002, 4.202693e-002, - 4.242082e-002, 4.281423e-002, 4.320657e-002, 4.359637e-002, - 4.398566e-002, 4.437448e-002, 4.476288e-002, 4.515076e-002, - 4.553832e-002, 4.592557e-002, 4.631260e-002, 4.669941e-002, - 4.708620e-002, 4.747111e-002, 4.785581e-002, 4.824055e-002, - 4.862516e-002, 4.900963e-002, 4.939392e-002, 4.977822e-002, - 5.016234e-002, 5.054640e-002, 5.093011e-002, 5.131254e-002, - 5.169442e-002, 5.207628e-002, 5.245792e-002, 5.283941e-002, - 5.322091e-002, 5.360254e-002, 5.398451e-002, 5.436656e-002, - 5.474869e-002, 5.513042e-002, 5.551149e-002, 5.589278e-002, - 5.627396e-002, 5.665528e-002, 5.703689e-002, 5.741860e-002, - 5.780036e-002, 5.818207e-002, 5.856357e-002, 5.894459e-002, - 5.932454e-002, 5.970394e-002, 6.008290e-002, 6.046159e-002, - 6.084004e-002, 6.121843e-002, 6.159673e-002, 6.197504e-002, - 6.235353e-002, 6.273225e-002, 6.311054e-002, 6.348897e-002, - 6.386763e-002, 6.424637e-002, 6.462534e-002, 6.500475e-002, - 6.538453e-002, 6.576474e-002, 6.614533e-002, 6.652623e-002, - 6.690713e-002, 6.728824e-002, 6.766955e-002, 6.805103e-002, - 6.843249e-002, 6.881406e-002, 6.919566e-002, 6.957729e-002, - 6.995908e-002, 7.034095e-002, 7.072298e-002, 7.110500e-002, - 7.148700e-002, 7.186898e-002, 7.225112e-002, 7.263336e-002, - 7.301572e-002, 7.339807e-002, 7.378068e-002, 7.416369e-002, - 7.454717e-002, 7.493111e-002, 7.531555e-002, 7.570061e-002, - 7.608625e-002, 7.647253e-002, 7.685955e-002, 7.724725e-002, - 7.763534e-002, 7.802366e-002, 7.841244e-002, 7.880214e-002, - 7.919242e-002, 7.958317e-002, 7.997455e-002, 8.036661e-002, - 8.075916e-002, 8.115193e-002, 8.154477e-002, 8.193767e-002, - 8.233068e-002, 8.272424e-002, 8.311811e-002, 8.351236e-002, - 8.390691e-002, 8.430197e-002, 8.469743e-002, 8.509340e-002, - 8.548978e-002, 8.588655e-002, 8.628385e-002, 8.668222e-002, - 8.708152e-002, 8.748141e-002, 8.788185e-002, 8.828293e-002, - 8.868463e-002, 8.908685e-002, 8.948966e-002, 8.989305e-002, - 9.029707e-002, 9.070218e-002, 9.110851e-002, 9.151570e-002, - 9.192383e-002, 9.233269e-002, 9.274223e-002, 9.315233e-002, - 9.356289e-002, 9.397371e-002, 9.438483e-002, 9.479646e-002, - 9.520934e-002, 9.562284e-002, 9.603692e-002, 9.645145e-002, - 9.686642e-002, 9.728162e-002, 9.769687e-002, 9.811201e-002, - 9.852718e-002, 9.894242e-002, 9.935901e-002, 9.977592e-002, - 1.001931e-001, 1.006107e-001, 1.010286e-001, 1.014468e-001, - 1.018654e-001, 1.022844e-001, 1.027037e-001, 1.031234e-001, - 1.035442e-001, 1.039658e-001, 1.043878e-001, 1.048103e-001, - 1.052333e-001, 1.056568e-001, 1.060809e-001, 1.065057e-001, - 1.069312e-001, 1.073576e-001, 1.077852e-001, 1.082145e-001, - 1.086448e-001, 1.090759e-001, 1.095079e-001, 1.099403e-001, - 1.103731e-001, 1.108063e-001, 1.112397e-001, 1.116734e-001, - 1.121078e-001, 1.125435e-001, 1.129796e-001, 1.134162e-001, - 1.138531e-001, 1.142904e-001, 1.147279e-001, 1.151657e-001, - 1.156038e-001, 1.160423e-001, 1.164814e-001, 1.169221e-001, - 1.173633e-001, 1.178051e-001, 1.182474e-001, 1.186904e-001, - 1.191342e-001, 1.195789e-001, 1.200245e-001, 1.204710e-001, - 1.209185e-001, 1.213680e-001, 1.218186e-001, 1.222701e-001, - 1.227224e-001, 1.231757e-001, 1.236300e-001, 1.240850e-001, - 1.245409e-001, 1.249978e-001, 1.254555e-001, 1.259149e-001, - 1.263757e-001, 1.268369e-001, 1.272983e-001, 1.277599e-001, - 1.282217e-001, 1.286837e-001, 1.291459e-001, 1.296084e-001, - 1.300712e-001, 1.305347e-001, 1.309996e-001, 1.314649e-001, - 1.319308e-001, 1.323972e-001, 1.328641e-001, 1.333315e-001, - 1.337996e-001, 1.342682e-001, 1.347375e-001, 1.352075e-001, - 1.356796e-001, 1.361524e-001, 1.366258e-001, 1.371000e-001, - 1.375749e-001, 1.380505e-001, 1.385269e-001, 1.390042e-001, - 1.394824e-001, 1.399612e-001, 1.404423e-001, 1.409239e-001, - 1.414060e-001, 1.418886e-001, 1.423715e-001, 1.428550e-001, - 1.433390e-001, 1.438233e-001, 1.443082e-001, 1.447935e-001, - 1.452803e-001, 1.457679e-001, 1.462556e-001, 1.467435e-001, - 1.472318e-001, 1.477203e-001, 1.482094e-001, 1.486990e-001, - 1.491891e-001, 1.496798e-001, 1.501717e-001, 1.506655e-001, - 1.511598e-001, 1.516549e-001, 1.521508e-001, 1.526477e-001, - 1.531454e-001, 1.536440e-001, 1.541436e-001, 1.546444e-001, - 1.551465e-001, 1.556510e-001, 1.561566e-001, 1.566633e-001, - 1.571706e-001, 1.576782e-001, 1.581861e-001, 1.586944e-001, - 1.592029e-001, 1.597115e-001, 1.602205e-001, 1.607314e-001, - 1.612429e-001, 1.617547e-001, 1.622672e-001, 1.627801e-001, - 1.632935e-001, 1.638076e-001, 1.643225e-001, 1.648382e-001, - 1.653550e-001, 1.658739e-001, 1.663941e-001, 1.669153e-001, - 1.674375e-001, 1.679605e-001, 1.684844e-001, 1.690095e-001, - 1.695355e-001, 1.700624e-001, 1.705903e-001, 1.711201e-001, - 1.716517e-001, 1.721842e-001, 1.727172e-001, 1.732512e-001, - 1.737863e-001, 1.743220e-001, 1.748586e-001, 1.753961e-001, - 1.759341e-001, 1.764733e-001, 1.770141e-001, 1.775553e-001, - 1.780969e-001, 1.786393e-001, 1.791824e-001, 1.797259e-001, - 1.802701e-001, 1.808149e-001, 1.813603e-001, 1.819062e-001, - 1.824543e-001, 1.830029e-001, 1.835522e-001, 1.841021e-001, - 1.846526e-001, 1.852037e-001, 1.857557e-001, 1.863082e-001, - 1.868613e-001, 1.874151e-001, 1.879708e-001, 1.885278e-001, - 1.890854e-001, 1.896436e-001, 1.902028e-001, 1.907630e-001, - 1.913239e-001, 1.918856e-001, 1.924481e-001, 1.930113e-001, - 1.935760e-001, 1.941417e-001, 1.947082e-001, 1.952753e-001, - 1.958428e-001, 1.964110e-001, 1.969798e-001, 1.975490e-001, - 1.981188e-001, 1.986891e-001, 1.992606e-001, 1.998335e-001, - 2.004069e-001, 2.009809e-001, 2.015556e-001, 2.021313e-001, - 2.027075e-001, 2.032845e-001, 2.038624e-001, 2.044413e-001, - 2.050214e-001, 2.056038e-001, 2.061875e-001, 2.067723e-001, - 2.073581e-001, 2.079446e-001, 2.085321e-001, 2.091204e-001, - 2.097096e-001, 2.103000e-001, 2.108914e-001, 2.114851e-001, - 2.120801e-001, 2.126761e-001, 2.132731e-001, 2.138707e-001, - 2.144686e-001, 2.150669e-001, 2.156660e-001, 2.162658e-001, - 2.168663e-001, 2.174684e-001, 2.180718e-001, 2.186760e-001, - 2.192811e-001, 2.198872e-001, 2.204940e-001, 2.211018e-001, - 2.217107e-001, 2.223205e-001, 2.229314e-001, 2.235436e-001, - 2.241575e-001, 2.247723e-001, 2.253881e-001, 2.260048e-001, - 2.266224e-001, 2.272410e-001, 2.278605e-001, 2.284810e-001, - 2.291025e-001, 2.297252e-001, 2.303496e-001, 2.309750e-001, - 2.316014e-001, 2.322288e-001, 2.328571e-001, 2.334866e-001, - 2.341173e-001, 2.347496e-001, 2.353832e-001, 2.360178e-001, - 2.366545e-001, 2.372924e-001, 2.379313e-001, 2.385714e-001, - 2.392125e-001, 2.398548e-001, 2.404980e-001, 2.411424e-001, - 2.417877e-001, 2.424340e-001, 2.430818e-001, 2.437308e-001, - 2.443806e-001, 2.450310e-001, 2.456824e-001, 2.463345e-001, - 2.469875e-001, 2.476415e-001, 2.482966e-001, 2.489524e-001, - 2.496098e-001, 2.502687e-001, 2.509285e-001, 2.515894e-001, - 2.522517e-001, 2.529152e-001, 2.535800e-001, 2.542463e-001, - 2.549144e-001, 2.555841e-001, 2.562555e-001, 2.569287e-001, - 2.576029e-001, 2.582783e-001, 2.589545e-001, 2.596323e-001, - 2.603114e-001, 2.609921e-001, 2.616743e-001, 2.623580e-001, - 2.630435e-001, 2.637309e-001, 2.644197e-001, 2.651098e-001, - 2.658010e-001, 2.664934e-001, 2.671868e-001, 2.678814e-001, - 2.685770e-001, 2.692738e-001, 2.699717e-001, 2.706715e-001, - 2.713726e-001, 2.720750e-001, 2.727788e-001, 2.734841e-001, - 2.741906e-001, 2.748987e-001, 2.756084e-001, 2.763193e-001, - 2.770315e-001, 2.777453e-001, 2.784602e-001, 2.791761e-001, - 2.798931e-001, 2.806108e-001, 2.813291e-001, 2.820484e-001, - 2.827687e-001, 2.834894e-001, 2.842107e-001, 2.849328e-001, - 2.856559e-001, 2.863794e-001, 2.871033e-001, 2.878280e-001, - 2.885538e-001, 2.892806e-001, 2.900085e-001, 2.907377e-001, - 2.914678e-001, 2.921990e-001, 2.929322e-001, 2.936666e-001, - 2.944022e-001, 2.951392e-001, 2.958773e-001, 2.966168e-001, - 2.973575e-001, 2.980994e-001, 2.988425e-001, 2.995872e-001, - 3.003341e-001, 3.010828e-001, 3.018327e-001, 3.025843e-001, - 3.033377e-001, 3.040933e-001, 3.048509e-001, 3.056102e-001, - 3.063707e-001, 3.071326e-001, 3.078965e-001, 3.086619e-001, - 3.094288e-001, 3.101971e-001, 3.109667e-001, 3.117377e-001, - 3.125102e-001, 3.132843e-001, 3.140597e-001, 3.148362e-001, - 3.156143e-001, 3.163942e-001, 3.171757e-001, 3.179586e-001, - 3.187431e-001, 3.195291e-001, 3.203168e-001, 3.211061e-001, - 3.218967e-001, 3.226890e-001, 3.234832e-001, 3.242806e-001, - 3.250800e-001, 3.258812e-001, 3.266844e-001, 3.274893e-001, - 3.282961e-001, 3.291047e-001, 3.299151e-001, 3.307276e-001, - 3.315426e-001, 3.323615e-001, 3.331823e-001, 3.340052e-001, - 3.348296e-001, 3.356559e-001, 3.364836e-001, 3.373129e-001, - 3.381440e-001, 3.389768e-001, 3.398111e-001, 3.406486e-001, - 3.414881e-001, 3.423288e-001, 3.431714e-001, 3.440159e-001, - 3.448618e-001, 3.457091e-001, 3.465585e-001, 3.474094e-001, - 3.482619e-001, 3.491170e-001, 3.499750e-001, 3.508346e-001, - 3.516960e-001, 3.525590e-001, 3.534239e-001, 3.542906e-001, - 3.551589e-001, 3.560288e-001, 3.569002e-001, 3.577739e-001, - 3.586511e-001, 3.595298e-001, 3.604101e-001, 3.612919e-001, - 3.621754e-001, 3.630602e-001, 3.639464e-001, 3.648337e-001, - 3.657226e-001, 3.666130e-001, 3.675079e-001, 3.684047e-001, - 3.693031e-001, 3.702028e-001, 3.711038e-001, 3.720055e-001, - 3.729085e-001, 3.738129e-001, 3.747189e-001, 3.756259e-001, - 3.765372e-001, 3.774503e-001, 3.783655e-001, 3.792823e-001, - 3.802004e-001, 3.811199e-001, 3.820413e-001, 3.829644e-001, - 3.838895e-001, 3.848165e-001, 3.857473e-001, 3.866814e-001, - 3.876177e-001, 3.885556e-001, 3.894956e-001, 3.904372e-001, - 3.913807e-001, 3.923261e-001, 3.932734e-001, 3.942231e-001, - 3.951765e-001, 3.961346e-001, 3.970947e-001, 3.980568e-001, - 3.990208e-001, 3.999867e-001, 4.009543e-001, 4.019241e-001, - 4.028963e-001, 4.038703e-001, 4.048471e-001, 4.058301e-001, - 4.068154e-001, 4.078022e-001, 4.087909e-001, 4.097813e-001, - 4.107738e-001, 4.117686e-001, 4.127655e-001, 4.137644e-001, - 4.147651e-001, 4.157725e-001, 4.167821e-001, 4.177943e-001, - 4.188099e-001, 4.198286e-001, 4.208502e-001, 4.218751e-001, - 4.229033e-001, 4.239342e-001, 4.249681e-001, 4.260087e-001, - 4.270545e-001, 4.281033e-001, 4.291544e-001, 4.302083e-001, - 4.312648e-001, 4.323243e-001, 4.333859e-001, 4.344498e-001, - 4.355162e-001, 4.365869e-001, 4.376633e-001, 4.387419e-001, - 4.398226e-001, 4.409054e-001, 4.419904e-001, 4.430772e-001, - 4.441661e-001, 4.452577e-001, 4.463516e-001, 4.474485e-001, - 4.485527e-001, 4.496586e-001, 4.507668e-001, 4.518776e-001, - 4.529909e-001, 4.541065e-001, 4.552241e-001, 4.563439e-001, - 4.574659e-001, 4.585899e-001, 4.597226e-001, 4.608568e-001, - 4.619925e-001, 4.631293e-001, 4.642676e-001, 4.654081e-001, - 4.665511e-001, 4.676967e-001, 4.688444e-001, 4.699939e-001, - 4.711507e-001, 4.723117e-001, 4.734752e-001, 4.746406e-001, - 4.758081e-001, 4.769782e-001, 4.781511e-001, 4.793259e-001, - 4.805027e-001, 4.816828e-001, 4.828697e-001, 4.840633e-001, - 4.852592e-001, 4.864575e-001, 4.876592e-001, 4.888639e-001, - 4.900715e-001, 4.912817e-001, 4.924951e-001, 4.937120e-001, - 4.949337e-001, 4.961640e-001, 4.973977e-001, 4.986341e-001, - 4.998730e-001, 5.011138e-001, 5.023570e-001, 5.036028e-001, - 5.048504e-001, 5.060995e-001, 5.073515e-001, 5.086140e-001, - 5.098794e-001, 5.111469e-001, 5.124161e-001, 5.136877e-001, - 5.149620e-001, 5.162395e-001, 5.175194e-001, 5.188013e-001, - 5.200862e-001, 5.213809e-001, 5.226801e-001, 5.239816e-001, - 5.252864e-001, 5.265944e-001, 5.279053e-001, 5.292193e-001, - 5.305359e-001, 5.318561e-001, 5.331789e-001, 5.345105e-001, - 5.358494e-001, 5.371919e-001, 5.385380e-001, 5.398877e-001, - 5.412416e-001, 5.425985e-001, 5.439588e-001, 5.453220e-001, - 5.466886e-001, 5.480612e-001, 5.494425e-001, 5.508270e-001, - 5.522152e-001, 5.536064e-001, 5.550010e-001, 5.563989e-001, - 5.578012e-001, 5.592064e-001, 5.606141e-001, 5.620263e-001, - 5.634495e-001, 5.648759e-001, 5.663060e-001, 5.677399e-001, - 5.691766e-001, 5.706165e-001, 5.720603e-001, 5.735081e-001, - 5.749596e-001, 5.764154e-001, 5.778844e-001, 5.793581e-001, - 5.808358e-001, 5.823175e-001, 5.838036e-001, 5.852938e-001, - 5.867884e-001, 5.882880e-001, 5.897926e-001, 5.913013e-001, - 5.928210e-001, 5.943471e-001, 5.958764e-001, 5.974098e-001, - 5.989470e-001, 6.004887e-001, 6.020334e-001, 6.035817e-001, - 6.051345e-001, 6.066907e-001, 6.082564e-001, 6.098326e-001, - 6.114136e-001, 6.129989e-001, 6.145888e-001, 6.161829e-001, - 6.177816e-001, 6.193858e-001, 6.209950e-001, 6.226082e-001, - 6.242284e-001, 6.258622e-001, 6.275011e-001, 6.291444e-001, - 6.307920e-001, 6.324457e-001, 6.341036e-001, 6.357665e-001, - 6.374354e-001, 6.391098e-001, 6.407888e-001, 6.424865e-001, - 6.441900e-001, 6.458998e-001, 6.476140e-001, 6.493339e-001, - 6.510594e-001, 6.527905e-001, 6.545270e-001, 6.562691e-001, - 6.580176e-001, 6.597829e-001, 6.615564e-001, 6.633360e-001, - 6.651216e-001, 6.669126e-001, 6.687086e-001, 6.705112e-001, - 6.723208e-001, 6.741380e-001, 6.759613e-001, 6.777997e-001, - 6.796534e-001, 6.815140e-001, 6.833799e-001, 6.852528e-001, - 6.871315e-001, 6.890163e-001, 6.909087e-001, 6.928068e-001, - 6.947108e-001, 6.966290e-001, 6.985671e-001, 7.005132e-001, - 7.024672e-001, 7.044298e-001, 7.063980e-001, 7.083728e-001, - 7.103546e-001, 7.123452e-001, 7.143444e-001, 7.163524e-001, - 7.183896e-001, 7.204359e-001, 7.224905e-001, 7.245525e-001, - 7.266246e-001, 7.287066e-001, 7.307961e-001, 7.328947e-001, - 7.350028e-001, 7.371211e-001, 7.392717e-001, 7.414343e-001, - 7.436061e-001, 7.457885e-001, 7.479840e-001, 7.501906e-001, - 7.524064e-001, 7.546306e-001, 7.568639e-001, 7.591079e-001, - 7.613802e-001, 7.636769e-001, 7.659834e-001, 7.683037e-001, - 7.706373e-001, 7.729814e-001, 7.753358e-001, 7.777012e-001, - 7.800781e-001, 7.824678e-001, 7.848826e-001, 7.873337e-001, - 7.897965e-001, 7.922734e-001, 7.947627e-001, 7.972671e-001, - 7.997864e-001, 8.023179e-001, 8.048668e-001, 8.074288e-001, - 8.100120e-001, 8.126469e-001, 8.152965e-001, 8.179622e-001, - 8.206483e-001, 8.233516e-001, 8.260734e-001, 8.288131e-001, - 8.315727e-001, 8.343446e-001, 8.371342e-001, 8.399934e-001, - 8.428773e-001, 8.457888e-001, 8.487272e-001, 8.516942e-001, - 8.546887e-001, 8.577007e-001, 8.607361e-001, 8.638108e-001, - 8.669571e-001, 8.701564e-001, 8.734027e-001, 8.766781e-001, - 8.799727e-001, 8.832960e-001, 8.866553e-001, 8.900595e-001, - 8.935105e-001, 8.969939e-001, 9.005120e-001, 9.045323e-001, - 9.082241e-001, 9.119395e-001, 9.157112e-001, 9.195482e-001, - 9.234328e-001, 9.273786e-001, 9.314110e-001, 9.355407e-001, - 9.397977e-001, 9.450196e-001, 9.509351e-001, 9.559072e-001, - 9.607881e-001, 9.656764e-001, 9.707481e-001, 9.773920e-001, - 9.829417e-001, 9.885159e-001, 9.941525e-001, 1.000000e+000 - }, - { - 0.000000e+000, -3.706679e-004, -6.814894e-004, -9.541366e-004, - -1.200249e-003, -1.426712e-003, -1.637806e-003, -1.836267e-003, - -2.024306e-003, -2.203649e-003, -2.376028e-003, -2.542911e-003, - -2.704507e-003, -2.861239e-003, -3.013712e-003, -3.162217e-003, - -3.307209e-003, -3.449026e-003, -3.587871e-003, -3.723937e-003, - -3.857590e-003, -3.989245e-003, -4.119038e-003, -4.246882e-003, - -4.372804e-003, -4.496979e-003, -4.619460e-003, -4.740346e-003, - -4.859837e-003, -4.978036e-003, -5.094972e-003, -5.210795e-003, - -5.325719e-003, -5.439404e-003, -5.551922e-003, -5.663349e-003, - -5.773777e-003, -5.883208e-003, -5.991623e-003, -6.099054e-003, - -6.205496e-003, -6.311011e-003, -6.415960e-003, -6.520054e-003, - -6.623255e-003, -6.725578e-003, -6.826867e-003, -6.926829e-003, - -7.026117e-003, -7.125291e-003, -7.223958e-003, -7.321931e-003, - -7.419616e-003, -7.516768e-003, -7.613367e-003, -7.709394e-003, - -7.804855e-003, -7.899793e-003, -7.994219e-003, -8.088160e-003, - -8.181627e-003, -8.274628e-003, -8.367352e-003, -8.459712e-003, - -8.551646e-003, -8.643177e-003, -8.734306e-003, -8.825122e-003, - -8.915633e-003, -9.005845e-003, -9.095798e-003, -9.185478e-003, - -9.275059e-003, -9.364625e-003, -9.454052e-003, -9.543292e-003, - -9.632467e-003, -9.721534e-003, -9.810445e-003, -9.899013e-003, - -9.987071e-003, -1.007457e-002, -1.016154e-002, -1.024815e-002, - -1.033421e-002, -1.041978e-002, -1.050487e-002, -1.058952e-002, - -1.067369e-002, -1.075741e-002, -1.084071e-002, -1.092361e-002, - -1.100617e-002, -1.108853e-002, -1.117067e-002, -1.125255e-002, - -1.133419e-002, -1.141559e-002, -1.149677e-002, -1.157775e-002, - -1.165856e-002, -1.173920e-002, -1.181969e-002, -1.190009e-002, - -1.198039e-002, -1.206064e-002, -1.214071e-002, -1.222067e-002, - -1.230052e-002, -1.238033e-002, -1.246016e-002, -1.253995e-002, - -1.261963e-002, -1.269928e-002, -1.277886e-002, -1.285834e-002, - -1.293768e-002, -1.301696e-002, -1.309619e-002, -1.317537e-002, - -1.325442e-002, -1.333328e-002, -1.341188e-002, -1.349014e-002, - -1.356790e-002, -1.364519e-002, -1.372204e-002, -1.379850e-002, - -1.387464e-002, -1.395054e-002, -1.402616e-002, -1.410158e-002, - -1.417690e-002, -1.425209e-002, -1.432713e-002, -1.440206e-002, - -1.447688e-002, -1.455162e-002, -1.462635e-002, -1.470105e-002, - -1.477576e-002, -1.485047e-002, -1.492514e-002, -1.499979e-002, - -1.507431e-002, -1.514875e-002, -1.522309e-002, -1.529729e-002, - -1.537128e-002, -1.544510e-002, -1.551872e-002, -1.559211e-002, - -1.566534e-002, -1.573837e-002, -1.581116e-002, -1.588368e-002, - -1.595599e-002, -1.602805e-002, -1.609989e-002, -1.617154e-002, - -1.624302e-002, -1.631431e-002, -1.638549e-002, -1.645658e-002, - -1.652758e-002, -1.659843e-002, -1.666931e-002, -1.674024e-002, - -1.681128e-002, -1.688244e-002, -1.695371e-002, -1.702501e-002, - -1.709624e-002, -1.716739e-002, -1.723849e-002, -1.730947e-002, - -1.738051e-002, -1.745163e-002, -1.752283e-002, -1.759406e-002, - -1.766524e-002, -1.773627e-002, -1.780703e-002, -1.787745e-002, - -1.794756e-002, -1.801728e-002, -1.808679e-002, -1.815616e-002, - -1.822537e-002, -1.829451e-002, -1.836356e-002, -1.843253e-002, - -1.850141e-002, -1.857021e-002, -1.863898e-002, -1.870764e-002, - -1.877626e-002, -1.884488e-002, -1.891349e-002, -1.898213e-002, - -1.905077e-002, -1.911940e-002, -1.918802e-002, -1.925665e-002, - -1.932529e-002, -1.939390e-002, -1.946250e-002, -1.953126e-002, - -1.960022e-002, -1.966929e-002, -1.973839e-002, -1.980742e-002, - -1.987635e-002, -1.994515e-002, -2.001378e-002, -2.008226e-002, - -2.015051e-002, -2.021878e-002, -2.028705e-002, -2.035529e-002, - -2.042343e-002, -2.049135e-002, -2.055896e-002, -2.062620e-002, - -2.069308e-002, -2.075966e-002, -2.082588e-002, -2.089190e-002, - -2.095780e-002, -2.102356e-002, -2.108920e-002, -2.115471e-002, - -2.122013e-002, -2.128543e-002, -2.135060e-002, -2.141563e-002, - -2.148041e-002, -2.154502e-002, -2.160958e-002, -2.167410e-002, - -2.173858e-002, -2.180305e-002, -2.186752e-002, -2.193206e-002, - -2.199664e-002, -2.206132e-002, -2.212600e-002, -2.219079e-002, - -2.225584e-002, -2.232107e-002, -2.238638e-002, -2.245158e-002, - -2.251668e-002, -2.258156e-002, -2.264624e-002, -2.271071e-002, - -2.277493e-002, -2.283878e-002, -2.290247e-002, -2.296599e-002, - -2.302931e-002, -2.309241e-002, -2.315521e-002, -2.321774e-002, - -2.328006e-002, -2.334218e-002, -2.340415e-002, -2.346577e-002, - -2.352728e-002, -2.358872e-002, -2.365008e-002, -2.371140e-002, - -2.377271e-002, -2.383406e-002, -2.389548e-002, -2.395701e-002, - -2.401868e-002, -2.408025e-002, -2.414183e-002, -2.420345e-002, - -2.426509e-002, -2.432674e-002, -2.438843e-002, -2.445014e-002, - -2.451192e-002, -2.457374e-002, -2.463560e-002, -2.469736e-002, - -2.475893e-002, -2.482029e-002, -2.488130e-002, -2.494192e-002, - -2.500218e-002, -2.506217e-002, -2.512190e-002, -2.518141e-002, - -2.524069e-002, -2.529971e-002, -2.535839e-002, -2.541694e-002, - -2.547538e-002, -2.553373e-002, -2.559201e-002, -2.565024e-002, - -2.570845e-002, -2.576659e-002, -2.582471e-002, -2.588276e-002, - -2.594057e-002, -2.599835e-002, -2.605615e-002, -2.611396e-002, - -2.617183e-002, -2.622978e-002, -2.628788e-002, -2.634617e-002, - -2.640463e-002, -2.646321e-002, -2.652163e-002, -2.657998e-002, - -2.663823e-002, -2.669640e-002, -2.675448e-002, -2.681251e-002, - -2.687051e-002, -2.692845e-002, -2.698636e-002, -2.704422e-002, - -2.710181e-002, -2.715913e-002, -2.721620e-002, -2.727303e-002, - -2.732961e-002, -2.738607e-002, -2.744239e-002, -2.749860e-002, - -2.755473e-002, -2.761084e-002, -2.766681e-002, -2.772261e-002, - -2.777842e-002, -2.783429e-002, -2.789024e-002, -2.794629e-002, - -2.800244e-002, -2.805873e-002, -2.811521e-002, -2.817192e-002, - -2.822882e-002, -2.828570e-002, -2.834280e-002, -2.840004e-002, - -2.845724e-002, -2.851419e-002, -2.857078e-002, -2.862705e-002, - -2.868299e-002, -2.873860e-002, -2.879393e-002, -2.884878e-002, - -2.890338e-002, -2.895779e-002, -2.901203e-002, -2.906603e-002, - -2.911989e-002, -2.917366e-002, -2.922736e-002, -2.928097e-002, - -2.933450e-002, -2.938782e-002, -2.944102e-002, -2.949424e-002, - -2.954742e-002, -2.960054e-002, -2.965366e-002, -2.970681e-002, - -2.975995e-002, -2.981312e-002, -2.986630e-002, -2.991938e-002, - -2.997238e-002, -3.002534e-002, -3.007822e-002, -3.013107e-002, - -3.018389e-002, -3.023667e-002, -3.028943e-002, -3.034215e-002, - -3.039476e-002, -3.044718e-002, -3.049926e-002, -3.055110e-002, - -3.060276e-002, -3.065431e-002, -3.070576e-002, -3.075712e-002, - -3.080840e-002, -3.085961e-002, -3.091070e-002, -3.096169e-002, - -3.101243e-002, -3.106309e-002, -3.111367e-002, -3.116420e-002, - -3.121469e-002, -3.126514e-002, -3.131556e-002, -3.136598e-002, - -3.141643e-002, -3.146690e-002, -3.151726e-002, -3.156767e-002, - -3.161813e-002, -3.166863e-002, -3.171924e-002, -3.176988e-002, - -3.182052e-002, -3.187118e-002, -3.192187e-002, -3.197253e-002, - -3.202301e-002, -3.207326e-002, -3.212334e-002, -3.217328e-002, - -3.222305e-002, -3.227273e-002, -3.232229e-002, -3.237167e-002, - -3.242090e-002, -3.246988e-002, -3.251861e-002, -3.256704e-002, - -3.261532e-002, -3.266345e-002, -3.271149e-002, -3.275949e-002, - -3.280743e-002, -3.285532e-002, -3.290325e-002, -3.295123e-002, - -3.299933e-002, -3.304744e-002, -3.309576e-002, -3.314423e-002, - -3.319275e-002, -3.324120e-002, -3.328962e-002, -3.333797e-002, - -3.338634e-002, -3.343478e-002, -3.348335e-002, -3.353190e-002, - -3.358053e-002, -3.362921e-002, -3.367782e-002, -3.372615e-002, - -3.377403e-002, -3.382148e-002, -3.386861e-002, -3.391553e-002, - -3.396222e-002, -3.400862e-002, -3.405479e-002, -3.410073e-002, - -3.414653e-002, -3.419220e-002, -3.423773e-002, -3.428312e-002, - -3.432841e-002, -3.437359e-002, -3.441868e-002, -3.446361e-002, - -3.450831e-002, -3.455297e-002, -3.459747e-002, -3.464185e-002, - -3.468607e-002, -3.473020e-002, -3.477423e-002, -3.481817e-002, - -3.486201e-002, -3.490574e-002, -3.494928e-002, -3.499275e-002, - -3.503618e-002, -3.507956e-002, -3.512293e-002, -3.516630e-002, - -3.520975e-002, -3.525324e-002, -3.529676e-002, -3.534028e-002, - -3.538366e-002, -3.542705e-002, -3.547042e-002, -3.551377e-002, - -3.555710e-002, -3.560045e-002, -3.564378e-002, -3.568709e-002, - -3.573027e-002, -3.577331e-002, -3.581614e-002, -3.585881e-002, - -3.590129e-002, -3.594360e-002, -3.598576e-002, -3.602773e-002, - -3.606952e-002, -3.611116e-002, -3.615268e-002, -3.619413e-002, - -3.623546e-002, -3.627668e-002, -3.631784e-002, -3.635900e-002, - -3.640014e-002, -3.644132e-002, -3.648262e-002, -3.652412e-002, - -3.656581e-002, -3.660775e-002, -3.664982e-002, -3.669177e-002, - -3.673361e-002, -3.677528e-002, -3.681678e-002, -3.685804e-002, - -3.689910e-002, -3.693993e-002, -3.698057e-002, -3.702106e-002, - -3.706138e-002, -3.710139e-002, -3.714113e-002, -3.718066e-002, - -3.721993e-002, -3.725895e-002, -3.729775e-002, -3.733637e-002, - -3.737485e-002, -3.741322e-002, -3.745144e-002, -3.748950e-002, - -3.752750e-002, -3.756548e-002, -3.760350e-002, -3.764158e-002, - -3.767977e-002, -3.771805e-002, -3.775641e-002, -3.779489e-002, - -3.783337e-002, -3.787178e-002, -3.791007e-002, -3.794830e-002, - -3.798639e-002, -3.802438e-002, -3.806230e-002, -3.810014e-002, - -3.813793e-002, -3.817563e-002, -3.821318e-002, -3.825045e-002, - -3.828737e-002, -3.832406e-002, -3.836050e-002, -3.839677e-002, - -3.843287e-002, -3.846884e-002, -3.850470e-002, -3.854052e-002, - -3.857625e-002, -3.861182e-002, -3.864716e-002, -3.868239e-002, - -3.871746e-002, -3.875244e-002, -3.878733e-002, -3.882217e-002, - -3.885693e-002, -3.889170e-002, -3.892640e-002, -3.896107e-002, - -3.899562e-002, -3.903022e-002, -3.906484e-002, -3.909959e-002, - -3.913451e-002, -3.916969e-002, -3.920516e-002, -3.924068e-002, - -3.927615e-002, -3.931141e-002, -3.934628e-002, -3.938083e-002, - -3.941521e-002, -3.944940e-002, -3.948354e-002, -3.951762e-002, - -3.955159e-002, -3.958545e-002, -3.961908e-002, -3.965240e-002, - -3.968527e-002, -3.971775e-002, -3.975003e-002, -3.978216e-002, - -3.981413e-002, -3.984592e-002, -3.987765e-002, -3.990928e-002, - -3.994083e-002, -3.997228e-002, -4.000372e-002, -4.003494e-002, - -4.006621e-002, -4.009753e-002, -4.012889e-002, -4.016029e-002, - -4.019175e-002, -4.022323e-002, -4.025482e-002, -4.028651e-002, - -4.031816e-002, -4.034928e-002, -4.038001e-002, -4.041021e-002, - -4.043987e-002, -4.046904e-002, -4.049777e-002, -4.052604e-002, - -4.055386e-002, -4.058123e-002, -4.060819e-002, -4.063445e-002, - -4.066016e-002, -4.068559e-002, -4.071071e-002, -4.073550e-002, - -4.075998e-002, -4.078422e-002, -4.080817e-002, -4.083200e-002, - -4.085568e-002, -4.087897e-002, -4.090187e-002, -4.092459e-002, - -4.094719e-002, -4.096969e-002, -4.099210e-002, -4.101449e-002, - -4.103686e-002, -4.105920e-002, -4.108158e-002, -4.110390e-002, - -4.112606e-002, -4.114836e-002, -4.117073e-002, -4.119310e-002, - -4.121553e-002, -4.123802e-002, -4.126054e-002, -4.128316e-002, - -4.130584e-002, -4.132856e-002, -4.135085e-002, -4.137302e-002, - -4.139503e-002, -4.141678e-002, -4.143807e-002, -4.145884e-002, - -4.147922e-002, -4.149924e-002, -4.151896e-002, -4.153841e-002, - -4.155709e-002, -4.157537e-002, -4.159343e-002, -4.161128e-002, - -4.162887e-002, -4.164627e-002, -4.166347e-002, -4.168053e-002, - -4.169741e-002, -4.171409e-002, -4.173020e-002, -4.174598e-002, - -4.176170e-002, -4.177730e-002, -4.179274e-002, -4.180804e-002, - -4.182322e-002, -4.183832e-002, -4.185342e-002, -4.186857e-002, - -4.188354e-002, -4.189808e-002, -4.191258e-002, -4.192692e-002, - -4.194102e-002, -4.195492e-002, -4.196866e-002, -4.198221e-002, - -4.199574e-002, -4.200906e-002, -4.202212e-002, -4.203427e-002, - -4.204614e-002, -4.205764e-002, -4.206872e-002, -4.207938e-002, - -4.208975e-002, -4.209985e-002, -4.210967e-002, -4.211916e-002, - -4.212840e-002, -4.213661e-002, -4.214455e-002, -4.215232e-002, - -4.216000e-002, -4.216734e-002, -4.217447e-002, -4.218140e-002, - -4.218806e-002, -4.219447e-002, -4.220065e-002, -4.220590e-002, - -4.221059e-002, -4.221494e-002, -4.221905e-002, -4.222291e-002, - -4.222653e-002, -4.222977e-002, -4.223266e-002, -4.223517e-002, - -4.223729e-002, -4.223869e-002, -4.223901e-002, -4.223891e-002, - -4.223840e-002, -4.223757e-002, -4.223638e-002, -4.223492e-002, - -4.223307e-002, -4.223087e-002, -4.222838e-002, -4.222556e-002, - -4.222162e-002, -4.221757e-002, -4.221348e-002, -4.220947e-002, - -4.220531e-002, -4.220112e-002, -4.219708e-002, -4.219321e-002, - -4.218940e-002, -4.218563e-002, -4.218053e-002, -4.217517e-002, - -4.216952e-002, -4.216340e-002, -4.215699e-002, -4.215031e-002, - -4.214338e-002, -4.213612e-002, -4.212862e-002, -4.212073e-002, - -4.211140e-002, -4.210105e-002, -4.209019e-002, -4.207889e-002, - -4.206721e-002, -4.205518e-002, -4.204283e-002, -4.203028e-002, - -4.201757e-002, -4.200449e-002, -4.199045e-002, -4.197544e-002, - -4.196038e-002, -4.194520e-002, -4.192981e-002, -4.191421e-002, - -4.189845e-002, -4.188271e-002, -4.186697e-002, -4.185102e-002, - -4.183452e-002, -4.181683e-002, -4.179888e-002, -4.178052e-002, - -4.176171e-002, -4.174234e-002, -4.172245e-002, -4.170208e-002, - -4.168123e-002, -4.165968e-002, -4.163740e-002, -4.161329e-002, - -4.158860e-002, -4.156345e-002, -4.153785e-002, -4.151176e-002, - -4.148527e-002, -4.145833e-002, -4.143115e-002, -4.140336e-002, - -4.137520e-002, -4.134533e-002, -4.131476e-002, -4.128380e-002, - -4.125250e-002, -4.122076e-002, -4.118866e-002, -4.115623e-002, - -4.112346e-002, -4.109010e-002, -4.105640e-002, -4.102135e-002, - -4.098537e-002, -4.094922e-002, -4.091286e-002, -4.087644e-002, - -4.083970e-002, -4.080263e-002, -4.076525e-002, -4.072758e-002, - -4.068947e-002, -4.065014e-002, -4.060925e-002, -4.056795e-002, - -4.052634e-002, -4.048442e-002, -4.044213e-002, -4.039922e-002, - -4.035567e-002, -4.031139e-002, -4.026642e-002, -4.022038e-002, - -4.017213e-002, -4.012325e-002, -4.007387e-002, -4.002381e-002, - -3.997331e-002, -3.992227e-002, -3.987075e-002, -3.981876e-002, - -3.976641e-002, -3.971344e-002, -3.965825e-002, -3.960252e-002, - -3.954651e-002, -3.949009e-002, -3.943321e-002, -3.937609e-002, - -3.931847e-002, -3.926059e-002, -3.920225e-002, -3.914344e-002, - -3.908245e-002, -3.902008e-002, -3.895700e-002, -3.889304e-002, - -3.882843e-002, -3.876326e-002, -3.869705e-002, -3.863003e-002, - -3.856213e-002, -3.849353e-002, -3.842306e-002, -3.835093e-002, - -3.827807e-002, -3.820454e-002, -3.813013e-002, -3.805510e-002, - -3.797951e-002, -3.790342e-002, -3.782664e-002, -3.774921e-002, - -3.767049e-002, -3.758917e-002, -3.750738e-002, -3.742498e-002, - -3.734200e-002, -3.725849e-002, -3.717452e-002, -3.708996e-002, - -3.700494e-002, -3.691950e-002, -3.683353e-002, -3.674412e-002, - -3.665405e-002, -3.656320e-002, -3.647169e-002, -3.637960e-002, - -3.628669e-002, -3.619290e-002, -3.609827e-002, -3.600281e-002, - -3.590650e-002, -3.580699e-002, -3.570604e-002, -3.560407e-002, - -3.550142e-002, -3.539778e-002, -3.529324e-002, -3.518797e-002, - -3.508205e-002, -3.497529e-002, -3.486767e-002, -3.475733e-002, - -3.464435e-002, -3.453039e-002, -3.441555e-002, -3.429960e-002, - -3.418293e-002, -3.406567e-002, -3.394739e-002, -3.382821e-002, - -3.370800e-002, -3.358524e-002, -3.345891e-002, -3.333127e-002, - -3.320242e-002, -3.307204e-002, -3.294059e-002, -3.280808e-002, - -3.267451e-002, -3.253969e-002, -3.240345e-002, -3.226576e-002, - -3.212292e-002, -3.197874e-002, -3.183292e-002, -3.168610e-002, - -3.153797e-002, -3.138799e-002, -3.123657e-002, -3.108369e-002, - -3.092927e-002, -3.077294e-002, -3.061155e-002, -3.044834e-002, - -3.028347e-002, -3.011670e-002, -2.994799e-002, -2.977748e-002, - -2.960529e-002, -2.943169e-002, -2.925652e-002, -2.907966e-002, - -2.889846e-002, -2.871340e-002, -2.852629e-002, -2.833704e-002, - -2.814575e-002, -2.795228e-002, -2.775726e-002, -2.755995e-002, - -2.736048e-002, -2.715863e-002, -2.695249e-002, -2.674042e-002, - -2.652666e-002, -2.630985e-002, -2.609062e-002, -2.586830e-002, - -2.564352e-002, -2.541669e-002, -2.518592e-002, -2.495234e-002, - -2.471529e-002, -2.447100e-002, -2.422366e-002, -2.397329e-002, - -2.371875e-002, -2.346003e-002, -2.319774e-002, -2.293137e-002, - -2.266066e-002, -2.238920e-002, -2.211452e-002, -2.182980e-002, - -2.154045e-002, -2.124386e-002, -2.094103e-002, -2.063227e-002, - -2.031858e-002, -2.000225e-002, -1.968033e-002, -1.934677e-002, - -1.898635e-002, -1.862573e-002, -1.826109e-002, -1.788969e-002, - -1.751771e-002, -1.714099e-002, -1.675907e-002, -1.636672e-002, - -1.596240e-002, -1.554893e-002, -1.512689e-002, -1.446370e-002, - -1.400828e-002, -1.355645e-002, -1.309401e-002, -1.261733e-002, - -1.213440e-002, -1.164163e-002, -1.113086e-002, -1.059870e-002, - -1.003953e-002, -9.121323e-003, -7.871813e-003, -7.111556e-003, - -6.411784e-003, -5.718638e-003, -4.962077e-003, -3.505064e-003, - -2.593092e-003, -1.709185e-003, -8.650688e-004, 0.000000e+000 - }, - { - 0.000000e+000, -1.380706e-003, -2.537619e-003, -3.548232e-003, - -4.452111e-003, -5.273238e-003, -6.028433e-003, -6.728530e-003, - -7.383660e-003, -7.999797e-003, -8.583057e-003, -9.135808e-003, - -9.662426e-003, -1.016601e-002, -1.064938e-002, -1.111449e-002, - -1.156316e-002, -1.199686e-002, -1.241708e-002, -1.282460e-002, - -1.321998e-002, -1.360340e-002, -1.397568e-002, -1.433806e-002, - -1.469119e-002, -1.503557e-002, -1.537149e-002, -1.569976e-002, - -1.602076e-002, -1.633465e-002, -1.664175e-002, -1.694207e-002, - -1.723519e-002, -1.752263e-002, -1.780453e-002, -1.808108e-002, - -1.835268e-002, -1.861944e-002, -1.888151e-002, -1.913918e-002, - -1.939260e-002, -1.964168e-002, -1.988536e-002, -2.012495e-002, - -2.036070e-002, -2.059275e-002, -2.082114e-002, -2.104594e-002, - -2.126740e-002, -2.148601e-002, -2.170167e-002, -2.191439e-002, - -2.212292e-002, -2.232825e-002, -2.253078e-002, -2.273052e-002, - -2.292768e-002, -2.312206e-002, -2.331385e-002, -2.350313e-002, - -2.368987e-002, -2.387418e-002, -2.405547e-002, -2.423400e-002, - -2.441034e-002, -2.458450e-002, -2.475648e-002, -2.492637e-002, - -2.509422e-002, -2.526006e-002, -2.542396e-002, -2.558596e-002, - -2.574561e-002, -2.590270e-002, -2.605803e-002, -2.621151e-002, - -2.636305e-002, -2.651292e-002, -2.666100e-002, -2.680745e-002, - -2.695221e-002, -2.709542e-002, -2.723671e-002, -2.737560e-002, - -2.751293e-002, -2.764863e-002, -2.778284e-002, -2.791539e-002, - -2.804663e-002, -2.817625e-002, -2.830440e-002, -2.843113e-002, - -2.855648e-002, -2.867969e-002, -2.880139e-002, -2.892175e-002, - -2.904081e-002, -2.915861e-002, -2.927501e-002, -2.939023e-002, - -2.950419e-002, -2.961702e-002, -2.972864e-002, -2.983874e-002, - -2.994745e-002, -3.005493e-002, -3.016126e-002, -3.026651e-002, - -3.037072e-002, -3.047370e-002, -3.057557e-002, -3.067618e-002, - -3.077586e-002, -3.087431e-002, -3.097135e-002, -3.106753e-002, - -3.116268e-002, -3.125682e-002, -3.134997e-002, -3.144200e-002, - -3.153315e-002, -3.162334e-002, -3.171247e-002, -3.180041e-002, - -3.188698e-002, -3.197249e-002, -3.205701e-002, -3.214052e-002, - -3.222297e-002, -3.230442e-002, -3.238497e-002, -3.246459e-002, - -3.254320e-002, -3.262084e-002, -3.269717e-002, -3.277273e-002, - -3.284746e-002, -3.292126e-002, -3.299417e-002, -3.306629e-002, - -3.313748e-002, -3.320776e-002, -3.327724e-002, -3.334596e-002, - -3.341351e-002, -3.348020e-002, -3.354618e-002, -3.361134e-002, - -3.367560e-002, -3.373913e-002, -3.380186e-002, -3.386380e-002, - -3.392484e-002, -3.398514e-002, -3.404450e-002, -3.410286e-002, - -3.416045e-002, -3.421727e-002, -3.427326e-002, -3.432849e-002, - -3.438296e-002, -3.443667e-002, -3.448964e-002, -3.454186e-002, - -3.459316e-002, -3.464345e-002, -3.469297e-002, -3.474187e-002, - -3.478997e-002, -3.483721e-002, -3.488369e-002, -3.492943e-002, - -3.497440e-002, -3.501855e-002, -3.506201e-002, -3.510461e-002, - -3.514654e-002, -3.518767e-002, -3.522810e-002, -3.526789e-002, - -3.530703e-002, -3.534542e-002, -3.538307e-002, -3.542007e-002, - -3.545643e-002, -3.549198e-002, -3.552687e-002, -3.556111e-002, - -3.559473e-002, -3.562756e-002, -3.565969e-002, -3.569121e-002, - -3.572212e-002, -3.575238e-002, -3.578202e-002, -3.581092e-002, - -3.583915e-002, -3.586663e-002, -3.589350e-002, -3.591977e-002, - -3.594546e-002, -3.597052e-002, -3.599497e-002, -3.601882e-002, - -3.604210e-002, -3.606463e-002, -3.608645e-002, -3.610773e-002, - -3.612835e-002, -3.614836e-002, -3.616768e-002, -3.618631e-002, - -3.620431e-002, -3.622168e-002, -3.623839e-002, -3.625450e-002, - -3.627005e-002, -3.628503e-002, -3.629932e-002, -3.631296e-002, - -3.632598e-002, -3.633847e-002, -3.635038e-002, -3.636174e-002, - -3.637250e-002, -3.638264e-002, -3.639216e-002, -3.640108e-002, - -3.640939e-002, -3.641717e-002, -3.642438e-002, -3.643106e-002, - -3.643709e-002, -3.644258e-002, -3.644748e-002, -3.645181e-002, - -3.645561e-002, -3.645881e-002, -3.646151e-002, -3.646367e-002, - -3.646530e-002, -3.646631e-002, -3.646684e-002, -3.646674e-002, - -3.646602e-002, -3.646472e-002, -3.646297e-002, -3.646074e-002, - -3.645806e-002, -3.645487e-002, -3.645102e-002, -3.644667e-002, - -3.644175e-002, -3.643632e-002, -3.643045e-002, -3.642403e-002, - -3.641717e-002, -3.640988e-002, -3.640208e-002, -3.639374e-002, - -3.638486e-002, -3.637550e-002, -3.636551e-002, -3.635498e-002, - -3.634391e-002, -3.633231e-002, -3.632014e-002, -3.630763e-002, - -3.629470e-002, -3.628122e-002, -3.626727e-002, -3.625272e-002, - -3.623763e-002, -3.622199e-002, -3.620586e-002, -3.618916e-002, - -3.617198e-002, -3.615439e-002, -3.613635e-002, -3.611781e-002, - -3.609870e-002, -3.607906e-002, -3.605882e-002, -3.603807e-002, - -3.601676e-002, -3.599492e-002, -3.597259e-002, -3.594984e-002, - -3.592654e-002, -3.590272e-002, -3.587838e-002, -3.585353e-002, - -3.582817e-002, -3.580239e-002, -3.577611e-002, -3.574945e-002, - -3.572249e-002, -3.569521e-002, -3.566761e-002, -3.563950e-002, - -3.561093e-002, -3.558190e-002, -3.555245e-002, -3.552254e-002, - -3.549221e-002, -3.546147e-002, -3.543020e-002, -3.539856e-002, - -3.536657e-002, -3.533405e-002, -3.530107e-002, -3.526770e-002, - -3.523396e-002, -3.519978e-002, -3.516531e-002, -3.513042e-002, - -3.509512e-002, -3.505951e-002, -3.502372e-002, -3.498764e-002, - -3.495120e-002, -3.491428e-002, -3.487697e-002, -3.483931e-002, - -3.480125e-002, -3.476283e-002, -3.472405e-002, -3.468486e-002, - -3.464545e-002, -3.460576e-002, -3.456561e-002, -3.452506e-002, - -3.448406e-002, -3.444270e-002, -3.440093e-002, -3.435868e-002, - -3.431606e-002, -3.427303e-002, -3.422971e-002, -3.418606e-002, - -3.414212e-002, -3.409779e-002, -3.405301e-002, -3.400776e-002, - -3.396208e-002, -3.391605e-002, -3.386957e-002, -3.382267e-002, - -3.377538e-002, -3.372800e-002, -3.368010e-002, -3.363166e-002, - -3.358280e-002, -3.353354e-002, -3.348386e-002, -3.343371e-002, - -3.338318e-002, -3.333228e-002, -3.328091e-002, -3.322929e-002, - -3.317728e-002, -3.312482e-002, -3.307187e-002, -3.301850e-002, - -3.296473e-002, -3.291048e-002, -3.285585e-002, -3.280075e-002, - -3.274511e-002, -3.268922e-002, -3.263287e-002, -3.257599e-002, - -3.251866e-002, -3.246094e-002, -3.240277e-002, -3.234405e-002, - -3.228488e-002, -3.222529e-002, -3.216523e-002, -3.210483e-002, - -3.204412e-002, -3.198296e-002, -3.192145e-002, -3.185941e-002, - -3.179673e-002, -3.173373e-002, -3.167030e-002, -3.160634e-002, - -3.154205e-002, -3.147731e-002, -3.141236e-002, -3.134714e-002, - -3.128146e-002, -3.121519e-002, -3.114855e-002, -3.108153e-002, - -3.101401e-002, -3.094607e-002, -3.087780e-002, -3.080908e-002, - -3.074024e-002, -3.067110e-002, -3.060149e-002, -3.053140e-002, - -3.046100e-002, -3.039022e-002, -3.031896e-002, -3.024738e-002, - -3.017548e-002, -3.010320e-002, -3.003072e-002, -2.995782e-002, - -2.988463e-002, -2.981105e-002, -2.973687e-002, -2.966223e-002, - -2.958726e-002, -2.951187e-002, -2.943599e-002, -2.935970e-002, - -2.928309e-002, -2.920624e-002, -2.912890e-002, -2.905129e-002, - -2.897328e-002, -2.889482e-002, -2.881596e-002, -2.873677e-002, - -2.865708e-002, -2.857700e-002, -2.849654e-002, -2.841578e-002, - -2.833468e-002, -2.825317e-002, -2.817117e-002, -2.808878e-002, - -2.800621e-002, -2.792334e-002, -2.783999e-002, -2.775609e-002, - -2.767181e-002, -2.758743e-002, -2.750263e-002, -2.741739e-002, - -2.733178e-002, -2.724585e-002, -2.715948e-002, -2.707270e-002, - -2.698558e-002, -2.689802e-002, -2.680999e-002, -2.672187e-002, - -2.663339e-002, -2.654454e-002, -2.645518e-002, -2.636532e-002, - -2.627522e-002, -2.618472e-002, -2.609368e-002, -2.600227e-002, - -2.591044e-002, -2.581830e-002, -2.572573e-002, -2.563273e-002, - -2.553920e-002, -2.544516e-002, -2.535077e-002, -2.525613e-002, - -2.516088e-002, -2.506522e-002, -2.496913e-002, -2.487276e-002, - -2.477599e-002, -2.467869e-002, -2.458091e-002, -2.448272e-002, - -2.438410e-002, -2.428506e-002, -2.418560e-002, -2.408555e-002, - -2.398503e-002, -2.388409e-002, -2.378300e-002, -2.368153e-002, - -2.357962e-002, -2.347728e-002, -2.337452e-002, -2.327135e-002, - -2.316767e-002, -2.306357e-002, -2.295886e-002, -2.285371e-002, - -2.274818e-002, -2.264224e-002, -2.253584e-002, -2.242896e-002, - -2.232174e-002, -2.221403e-002, -2.210588e-002, -2.199720e-002, - -2.188807e-002, -2.177841e-002, -2.166840e-002, -2.155796e-002, - -2.144709e-002, -2.133579e-002, -2.122389e-002, -2.111151e-002, - -2.099879e-002, -2.088554e-002, -2.077186e-002, -2.065786e-002, - -2.054348e-002, -2.042852e-002, -2.031323e-002, -2.019760e-002, - -2.008134e-002, -1.996468e-002, -1.984768e-002, -1.973028e-002, - -1.961242e-002, -1.949408e-002, -1.937530e-002, -1.925612e-002, - -1.913650e-002, -1.901646e-002, -1.889604e-002, -1.877485e-002, - -1.865308e-002, -1.853067e-002, -1.840764e-002, -1.828390e-002, - -1.815937e-002, -1.803439e-002, -1.790873e-002, -1.778231e-002, - -1.765525e-002, -1.752768e-002, -1.739967e-002, -1.727136e-002, - -1.714254e-002, -1.701325e-002, -1.688353e-002, -1.675350e-002, - -1.662305e-002, -1.649209e-002, -1.636058e-002, -1.622845e-002, - -1.609593e-002, -1.596322e-002, -1.582997e-002, -1.569629e-002, - -1.556213e-002, -1.542766e-002, -1.529277e-002, -1.515758e-002, - -1.502197e-002, -1.488613e-002, -1.475005e-002, -1.461369e-002, - -1.447692e-002, -1.433997e-002, -1.420284e-002, -1.406561e-002, - -1.392816e-002, -1.379033e-002, -1.365225e-002, -1.351381e-002, - -1.337487e-002, -1.323562e-002, -1.309598e-002, -1.295600e-002, - -1.281558e-002, -1.267476e-002, -1.253351e-002, -1.239184e-002, - -1.224976e-002, -1.210743e-002, -1.196473e-002, -1.182152e-002, - -1.167779e-002, -1.153375e-002, -1.138952e-002, -1.124491e-002, - -1.110006e-002, -1.095480e-002, -1.080910e-002, -1.066301e-002, - -1.051664e-002, -1.036987e-002, -1.022263e-002, -1.007491e-002, - -9.926863e-003, -9.778299e-003, -9.629206e-003, -9.479888e-003, - -9.330225e-003, -9.180250e-003, -9.029736e-003, -8.878773e-003, - -8.727338e-003, -8.575517e-003, -8.423305e-003, -8.270836e-003, - -8.117941e-003, -7.964661e-003, -7.810990e-003, -7.656787e-003, - -7.502118e-003, -7.347186e-003, -7.191605e-003, -7.035531e-003, - -6.879039e-003, -6.722144e-003, -6.564889e-003, -6.407487e-003, - -6.249413e-003, -6.090871e-003, -5.931831e-003, -5.772405e-003, - -5.612440e-003, -5.452035e-003, -5.291017e-003, -5.129698e-003, - -4.967789e-003, -4.805462e-003, -4.642417e-003, -4.478781e-003, - -4.314676e-003, -4.149981e-003, -3.984674e-003, -3.818684e-003, - -3.651952e-003, -3.484713e-003, -3.317105e-003, -3.149171e-003, - -2.980807e-003, -2.811812e-003, -2.642034e-003, -2.471873e-003, - -2.301294e-003, -2.130406e-003, -1.958804e-003, -1.786640e-003, - -1.613924e-003, -1.440865e-003, -1.267425e-003, -1.093432e-003, - -9.188499e-004, -7.437556e-004, -5.681595e-004, -3.918928e-004, - -2.152634e-004, -3.840933e-005, 1.388878e-004, 3.167162e-004, - 4.945427e-004, 6.728475e-004, 8.514123e-004, 1.030223e-003, - 1.209529e-003, 1.389108e-003, 1.569282e-003, 1.749760e-003, - 1.930617e-003, 2.111944e-003, 2.293420e-003, 2.475297e-003, - 2.657542e-003, 2.840094e-003, 3.022974e-003, 3.206289e-003, - 3.390227e-003, 3.574669e-003, 3.759420e-003, 3.944650e-003, - 4.129904e-003, 4.315339e-003, 4.501200e-003, 4.687318e-003, - 4.873995e-003, 5.061234e-003, 5.248981e-003, 5.437239e-003, - 5.625715e-003, 5.814437e-003, 6.003174e-003, 6.192198e-003, - 6.381767e-003, 6.571641e-003, 6.761942e-003, 6.952501e-003, - 7.143531e-003, 7.335128e-003, 7.526899e-003, 7.718937e-003, - 7.911239e-003, 8.103503e-003, 8.296160e-003, 8.489272e-003, - 8.682967e-003, 8.877145e-003, 9.071732e-003, 9.266590e-003, - 9.461674e-003, 9.657076e-003, 9.852716e-003, 1.004835e-002, - 1.024472e-002, 1.044162e-002, 1.063897e-002, 1.083673e-002, - 1.103515e-002, 1.123390e-002, 1.143302e-002, 1.163246e-002, - 1.183230e-002, 1.203209e-002, 1.223234e-002, 1.243312e-002, - 1.263429e-002, 1.283589e-002, 1.303791e-002, 1.324075e-002, - 1.344416e-002, 1.364830e-002, 1.385287e-002, 1.405732e-002, - 1.426189e-002, 1.446701e-002, 1.467249e-002, 1.487877e-002, - 1.508553e-002, 1.529243e-002, 1.549970e-002, 1.570735e-002, - 1.591524e-002, 1.612325e-002, 1.633149e-002, 1.653964e-002, - 1.674808e-002, 1.695718e-002, 1.716665e-002, 1.737638e-002, - 1.758647e-002, 1.779674e-002, 1.800720e-002, 1.821791e-002, - 1.842861e-002, 1.863974e-002, 1.885109e-002, 1.906238e-002, - 1.927413e-002, 1.948622e-002, 1.969859e-002, 1.991134e-002, - 2.012457e-002, 2.033795e-002, 2.055052e-002, 2.076334e-002, - 2.097614e-002, 2.118909e-002, 2.140257e-002, 2.161672e-002, - 2.183073e-002, 2.204468e-002, 2.225936e-002, 2.247434e-002, - 2.268879e-002, 2.290243e-002, 2.311654e-002, 2.333120e-002, - 2.354619e-002, 2.376138e-002, 2.397638e-002, 2.419221e-002, - 2.440812e-002, 2.462415e-002, 2.483977e-002, 2.505449e-002, - 2.526956e-002, 2.548532e-002, 2.570141e-002, 2.591753e-002, - 2.613366e-002, 2.635010e-002, 2.656685e-002, 2.678406e-002, - 2.700066e-002, 2.721644e-002, 2.743297e-002, 2.765008e-002, - 2.786691e-002, 2.808376e-002, 2.830037e-002, 2.851711e-002, - 2.873420e-002, 2.895173e-002, 2.916914e-002, 2.938571e-002, - 2.960230e-002, 2.981918e-002, 3.003642e-002, 3.025388e-002, - 3.047129e-002, 3.068832e-002, 3.090576e-002, 3.112341e-002, - 3.134130e-002, 3.155832e-002, 3.177516e-002, 3.199213e-002, - 3.220943e-002, 3.242669e-002, 3.264375e-002, 3.286111e-002, - 3.307851e-002, 3.329647e-002, 3.351449e-002, 3.373148e-002, - 3.394802e-002, 3.416480e-002, 3.438177e-002, 3.459905e-002, - 3.481659e-002, 3.503397e-002, 3.525162e-002, 3.546922e-002, - 3.568671e-002, 3.590381e-002, 3.611971e-002, 3.633615e-002, - 3.655233e-002, 3.676820e-002, 3.698390e-002, 3.719954e-002, - 3.741477e-002, 3.762999e-002, 3.784502e-002, 3.805927e-002, - 3.827239e-002, 3.848548e-002, 3.869846e-002, 3.891112e-002, - 3.912377e-002, 3.933628e-002, 3.954853e-002, 3.976029e-002, - 3.997216e-002, 4.018378e-002, 4.039365e-002, 4.060349e-002, - 4.081284e-002, 4.102173e-002, 4.123006e-002, 4.143837e-002, - 4.164652e-002, 4.185434e-002, 4.206080e-002, 4.226710e-002, - 4.247193e-002, 4.267628e-002, 4.288001e-002, 4.308339e-002, - 4.328656e-002, 4.348910e-002, 4.369158e-002, 4.389370e-002, - 4.409512e-002, 4.429601e-002, 4.449555e-002, 4.469309e-002, - 4.489005e-002, 4.508657e-002, 4.528221e-002, 4.547716e-002, - 4.567085e-002, 4.586390e-002, 4.605641e-002, 4.624848e-002, - 4.643978e-002, 4.662839e-002, 4.681564e-002, 4.700235e-002, - 4.718857e-002, 4.737394e-002, 4.755836e-002, 4.774268e-002, - 4.792619e-002, 4.810848e-002, 4.829004e-002, 4.846807e-002, - 4.864474e-002, 4.882018e-002, 4.899443e-002, 4.916763e-002, - 4.933912e-002, 4.950947e-002, 4.967827e-002, 4.984503e-002, - 5.001084e-002, 5.017339e-002, 5.033537e-002, 5.049553e-002, - 5.065465e-002, 5.081302e-002, 5.097031e-002, 5.112593e-002, - 5.128044e-002, 5.143252e-002, 5.158333e-002, 5.173167e-002, - 5.187521e-002, 5.201761e-002, 5.215924e-002, 5.229866e-002, - 5.243674e-002, 5.257302e-002, 5.270632e-002, 5.283815e-002, - 5.296871e-002, 5.309521e-002, 5.321711e-002, 5.333508e-002, - 5.345026e-002, 5.356186e-002, 5.367134e-002, 5.377853e-002, - 5.388269e-002, 5.398251e-002, 5.407914e-002, 5.417283e-002, - 5.425936e-002, 5.434245e-002, 5.442081e-002, 5.449691e-002, - 5.456919e-002, 5.463745e-002, 5.470278e-002, 5.476401e-002, - 5.482035e-002, 5.487145e-002, 5.491466e-002, 5.495483e-002, - 5.499070e-002, 5.502202e-002, 5.504626e-002, 5.506480e-002, - 5.507908e-002, 5.508837e-002, 5.509176e-002, 5.509037e-002, - 5.508130e-002, 5.506469e-002, 5.504226e-002, 5.501149e-002, - 5.497314e-002, 5.492907e-002, 5.487915e-002, 5.482218e-002, - 5.475930e-002, 5.468910e-002, 5.461169e-002, 5.452295e-002, - 5.442757e-002, 5.432349e-002, 5.421169e-002, 5.409030e-002, - 5.396074e-002, 5.382497e-002, 5.367777e-002, 5.352149e-002, - 5.335712e-002, 5.318157e-002, 5.299479e-002, 5.279605e-002, - 5.258339e-002, 5.235704e-002, 5.211555e-002, 5.186179e-002, - 5.159240e-002, 5.132070e-002, 5.103851e-002, 5.074100e-002, - 5.043067e-002, 5.009421e-002, 4.973575e-002, 4.935512e-002, - 4.895530e-002, 4.854740e-002, 4.811690e-002, 4.764371e-002, - 4.706587e-002, 4.650470e-002, 4.593629e-002, 4.534170e-002, - 4.474376e-002, 4.412589e-002, 4.348096e-002, 4.279121e-002, - 4.205265e-002, 4.128471e-002, 4.048974e-002, 3.877263e-002, - 3.788250e-002, 3.701317e-002, 3.610274e-002, 3.513388e-002, - 3.413506e-002, 3.308996e-002, 3.196537e-002, 3.074525e-002, - 2.939912e-002, 2.658509e-002, 2.261612e-002, 2.050075e-002, - 1.859532e-002, 1.673826e-002, 1.467582e-002, 1.015304e-002, - 7.539017e-003, 5.011997e-003, 2.597830e-003, 0.000000e+000 - }, - { - 0.000000e+000, -1.966839e-003, -3.591180e-003, -4.993148e-003, - -6.239044e-003, -7.361541e-003, -8.389511e-003, -9.339801e-003, - -1.022619e-002, -1.106100e-002, -1.185191e-002, -1.259061e-002, - -1.329160e-002, -1.396254e-002, -1.460719e-002, -1.522683e-002, - -1.582421e-002, -1.639999e-002, -1.695729e-002, -1.749608e-002, - -1.801787e-002, -1.851602e-002, -1.899179e-002, -1.945418e-002, - -1.990387e-002, -2.034123e-002, -2.076620e-002, -2.117981e-002, - -2.158230e-002, -2.197431e-002, -2.235616e-002, -2.272505e-002, - -2.307672e-002, -2.342042e-002, -2.375600e-002, -2.408425e-002, - -2.440564e-002, -2.472019e-002, -2.502813e-002, -2.532968e-002, - -2.562541e-002, -2.591441e-002, -2.618794e-002, -2.645629e-002, - -2.671985e-002, -2.697872e-002, -2.723388e-002, -2.748576e-002, - -2.773307e-002, -2.797437e-002, -2.821109e-002, -2.844384e-002, - -2.866481e-002, -2.887993e-002, -2.909166e-002, -2.929993e-002, - -2.950482e-002, -2.970608e-002, -2.990367e-002, -3.009803e-002, - -3.028875e-002, -3.047628e-002, -3.065546e-002, -3.082844e-002, - -3.099849e-002, -3.116545e-002, -3.132970e-002, -3.149063e-002, - -3.164834e-002, -3.180300e-002, -3.195478e-002, -3.210327e-002, - -3.224521e-002, -3.237865e-002, -3.250892e-002, -3.263511e-002, - -3.275758e-002, -3.287599e-002, -3.299138e-002, -3.310449e-002, - -3.321632e-002, -3.332669e-002, -3.343441e-002, -3.353524e-002, - -3.363447e-002, -3.373225e-002, -3.382829e-002, -3.392293e-002, - -3.401612e-002, -3.410786e-002, -3.419812e-002, -3.428680e-002, - -3.437388e-002, -3.445333e-002, -3.453058e-002, -3.460547e-002, - -3.467862e-002, -3.474991e-002, -3.481909e-002, -3.488623e-002, - -3.495141e-002, -3.501494e-002, -3.507679e-002, -3.513295e-002, - -3.518527e-002, -3.523577e-002, -3.528423e-002, -3.533110e-002, - -3.537587e-002, -3.541824e-002, -3.545852e-002, -3.549612e-002, - -3.553209e-002, -3.556406e-002, -3.559189e-002, -3.561836e-002, - -3.564298e-002, -3.566592e-002, -3.568723e-002, -3.570689e-002, - -3.572536e-002, -3.574251e-002, -3.575848e-002, -3.577326e-002, - -3.578487e-002, -3.579626e-002, -3.580776e-002, -3.581877e-002, - -3.582928e-002, -3.583928e-002, -3.584837e-002, -3.585611e-002, - -3.586263e-002, -3.586778e-002, -3.586777e-002, -3.586676e-002, - -3.586409e-002, -3.585986e-002, -3.585433e-002, -3.584722e-002, - -3.583791e-002, -3.582700e-002, -3.581450e-002, -3.580064e-002, - -3.578282e-002, -3.576323e-002, -3.574255e-002, -3.572062e-002, - -3.569772e-002, -3.567434e-002, -3.565025e-002, -3.562539e-002, - -3.559966e-002, -3.557335e-002, -3.554447e-002, -3.551383e-002, - -3.548306e-002, -3.545177e-002, -3.541966e-002, -3.538698e-002, - -3.535371e-002, -3.531956e-002, -3.528466e-002, -3.524860e-002, - -3.521048e-002, -3.516928e-002, -3.512678e-002, -3.508278e-002, - -3.503683e-002, -3.498877e-002, -3.493888e-002, -3.488733e-002, - -3.483457e-002, -3.478079e-002, -3.472583e-002, -3.466768e-002, - -3.460757e-002, -3.454567e-002, -3.448217e-002, -3.441719e-002, - -3.435115e-002, -3.428442e-002, -3.421753e-002, -3.415069e-002, - -3.408377e-002, -3.401517e-002, -3.394597e-002, -3.387597e-002, - -3.380535e-002, -3.373329e-002, -3.365997e-002, -3.358578e-002, - -3.351067e-002, -3.343428e-002, -3.335670e-002, -3.327714e-002, - -3.319585e-002, -3.311291e-002, -3.302862e-002, -3.294302e-002, - -3.285606e-002, -3.276788e-002, -3.267851e-002, -3.258794e-002, - -3.249626e-002, -3.240283e-002, -3.230723e-002, -3.220990e-002, - -3.211058e-002, -3.200970e-002, -3.190722e-002, -3.180380e-002, - -3.169973e-002, -3.159527e-002, -3.149043e-002, -3.138474e-002, - -3.127840e-002, -3.117102e-002, -3.106265e-002, -3.095331e-002, - -3.084328e-002, -3.073346e-002, -3.062434e-002, -3.051620e-002, - -3.040860e-002, -3.030118e-002, -3.019367e-002, -3.008615e-002, - -2.997844e-002, -2.987045e-002, -2.976202e-002, -2.965337e-002, - -2.954412e-002, -2.943475e-002, -2.932485e-002, -2.921462e-002, - -2.910414e-002, -2.899348e-002, -2.888244e-002, -2.877074e-002, - -2.865821e-002, -2.854463e-002, -2.843042e-002, -2.831500e-002, - -2.819846e-002, -2.808050e-002, -2.796133e-002, -2.784078e-002, - -2.771842e-002, -2.759459e-002, -2.746935e-002, -2.734355e-002, - -2.721726e-002, -2.709102e-002, -2.696490e-002, -2.683883e-002, - -2.671293e-002, -2.658809e-002, -2.646330e-002, -2.633841e-002, - -2.621351e-002, -2.608870e-002, -2.596409e-002, -2.583992e-002, - -2.571572e-002, -2.559167e-002, -2.546747e-002, -2.534397e-002, - -2.522034e-002, -2.509622e-002, -2.497177e-002, -2.484657e-002, - -2.472044e-002, -2.459337e-002, -2.446517e-002, -2.433571e-002, - -2.420496e-002, -2.407371e-002, -2.394179e-002, -2.380879e-002, - -2.367483e-002, -2.353998e-002, -2.340412e-002, -2.326734e-002, - -2.312955e-002, -2.299089e-002, -2.285134e-002, -2.271140e-002, - -2.257144e-002, -2.243179e-002, -2.229282e-002, -2.215476e-002, - -2.201766e-002, -2.188115e-002, -2.174519e-002, -2.160985e-002, - -2.147536e-002, -2.134149e-002, -2.120844e-002, -2.107539e-002, - -2.094222e-002, -2.080879e-002, -2.067517e-002, -2.054106e-002, - -2.040646e-002, -2.027174e-002, -2.013647e-002, -2.000085e-002, - -1.986546e-002, -1.972941e-002, -1.959259e-002, -1.945515e-002, - -1.931705e-002, -1.917788e-002, -1.903774e-002, -1.889622e-002, - -1.875334e-002, -1.860944e-002, -1.846543e-002, -1.832141e-002, - -1.817748e-002, -1.803336e-002, -1.788895e-002, -1.774428e-002, - -1.759905e-002, -1.745355e-002, -1.730772e-002, -1.716162e-002, - -1.701596e-002, -1.687088e-002, -1.672629e-002, -1.658218e-002, - -1.643854e-002, -1.629489e-002, -1.615134e-002, -1.600750e-002, - -1.586341e-002, -1.571896e-002, -1.557404e-002, -1.542910e-002, - -1.528392e-002, -1.513796e-002, -1.499090e-002, -1.484271e-002, - -1.469360e-002, -1.454325e-002, -1.439152e-002, -1.423813e-002, - -1.408320e-002, -1.392718e-002, -1.376950e-002, -1.361051e-002, - -1.345110e-002, -1.329235e-002, -1.313460e-002, -1.297773e-002, - -1.282185e-002, -1.266683e-002, -1.251242e-002, -1.235853e-002, - -1.220519e-002, -1.205217e-002, -1.189931e-002, -1.174701e-002, - -1.159466e-002, -1.144194e-002, -1.128911e-002, -1.113615e-002, - -1.098294e-002, -1.082924e-002, -1.067501e-002, -1.051984e-002, - -1.036434e-002, -1.020873e-002, -1.005245e-002, -9.895465e-003, - -9.738041e-003, -9.579849e-003, -9.420823e-003, -9.261355e-003, - -9.101149e-003, -8.940406e-003, -8.779631e-003, -8.618267e-003, - -8.456074e-003, -8.293726e-003, -8.131020e-003, -7.967837e-003, - -7.804873e-003, -7.641933e-003, -7.479290e-003, -7.317384e-003, - -7.155562e-003, -6.993296e-003, -6.831157e-003, -6.669236e-003, - -6.507261e-003, -6.345222e-003, -6.183646e-003, -6.022218e-003, - -5.860293e-003, -5.698692e-003, -5.536748e-003, -5.374494e-003, - -5.212143e-003, -5.049675e-003, -4.886855e-003, -4.723991e-003, - -4.561025e-003, -4.397728e-003, -4.233721e-003, -4.068919e-003, - -3.903604e-003, -3.737749e-003, -3.570730e-003, -3.403513e-003, - -3.235868e-003, -3.067657e-003, -2.898930e-003, -2.729813e-003, - -2.560363e-003, -2.391116e-003, -2.222493e-003, -2.054283e-003, - -1.886625e-003, -1.718936e-003, -1.551512e-003, -1.384398e-003, - -1.217584e-003, -1.051581e-003, -8.858093e-004, -7.201158e-004, - -5.547316e-004, -3.895848e-004, -2.243384e-004, -5.921775e-005, - 1.059822e-004, 2.712131e-004, 4.370167e-004, 6.036588e-004, - 7.713905e-004, 9.400408e-004, 1.110214e-003, 1.281555e-003, - 1.453370e-003, 1.625212e-003, 1.797671e-003, 1.970354e-003, - 2.143343e-003, 2.317256e-003, 2.492200e-003, 2.668139e-003, - 2.844772e-003, 3.021951e-003, 3.199321e-003, 3.376032e-003, - 3.551143e-003, 3.724886e-003, 3.897569e-003, 4.069707e-003, - 4.241390e-003, 4.412715e-003, 4.583785e-003, 4.753939e-003, - 4.924032e-003, 5.093895e-003, 5.263328e-003, 5.432351e-003, - 5.601433e-003, 5.770470e-003, 5.939402e-003, 6.108045e-003, - 6.276087e-003, 6.444282e-003, 6.612206e-003, 6.779708e-003, - 6.946687e-003, 7.113328e-003, 7.279783e-003, 7.446017e-003, - 7.612186e-003, 7.778048e-003, 7.943520e-003, 8.108631e-003, - 8.273793e-003, 8.438541e-003, 8.603300e-003, 8.768351e-003, - 8.934221e-003, 9.100179e-003, 9.266750e-003, 9.433547e-003, - 9.599827e-003, 9.766012e-003, 9.932237e-003, 1.009857e-002, - 1.026489e-002, 1.043112e-002, 1.059739e-002, 1.076378e-002, - 1.092990e-002, 1.109568e-002, 1.126048e-002, 1.142460e-002, - 1.158801e-002, 1.175065e-002, 1.191302e-002, 1.207466e-002, - 1.223591e-002, 1.239666e-002, 1.255705e-002, 1.271685e-002, - 1.287597e-002, 1.303478e-002, 1.319331e-002, 1.335189e-002, - 1.351070e-002, 1.366987e-002, 1.382948e-002, 1.398999e-002, - 1.415172e-002, 1.431458e-002, 1.447820e-002, 1.464091e-002, - 1.480350e-002, 1.496528e-002, 1.512640e-002, 1.528697e-002, - 1.544713e-002, 1.560691e-002, 1.576672e-002, 1.592610e-002, - 1.608575e-002, 1.624329e-002, 1.639994e-002, 1.655645e-002, - 1.671218e-002, 1.686687e-002, 1.702112e-002, 1.717496e-002, - 1.732864e-002, 1.748210e-002, 1.763514e-002, 1.778652e-002, - 1.793727e-002, 1.808838e-002, 1.823991e-002, 1.839187e-002, - 1.854457e-002, 1.869797e-002, 1.885149e-002, 1.900568e-002, - 1.915961e-002, 1.931247e-002, 1.946403e-002, 1.961523e-002, - 1.976594e-002, 1.991587e-002, 2.006527e-002, 2.021437e-002, - 2.036332e-002, 2.051137e-002, 2.065815e-002, 2.080310e-002, - 2.094557e-002, 2.108679e-002, 2.122632e-002, 2.136482e-002, - 2.150281e-002, 2.164089e-002, 2.177871e-002, 2.191563e-002, - 2.205207e-002, 2.218757e-002, 2.232051e-002, 2.245280e-002, - 2.258438e-002, 2.271551e-002, 2.284626e-002, 2.297666e-002, - 2.310622e-002, 2.323546e-002, 2.336454e-002, 2.349375e-002, - 2.362020e-002, 2.374685e-002, 2.387338e-002, 2.400049e-002, - 2.412878e-002, 2.425854e-002, 2.438938e-002, 2.452046e-002, - 2.465088e-002, 2.478022e-002, 2.490717e-002, 2.503224e-002, - 2.515708e-002, 2.528110e-002, 2.540465e-002, 2.552781e-002, - 2.565030e-002, 2.577199e-002, 2.589283e-002, 2.601235e-002, - 2.612937e-002, 2.624383e-002, 2.635685e-002, 2.646946e-002, - 2.658120e-002, 2.669239e-002, 2.680348e-002, 2.691442e-002, - 2.702523e-002, 2.713526e-002, 2.724457e-002, 2.735194e-002, - 2.746013e-002, 2.756777e-002, 2.767555e-002, 2.778380e-002, - 2.789195e-002, 2.799997e-002, 2.810846e-002, 2.821642e-002, - 2.832466e-002, 2.843016e-002, 2.853420e-002, 2.863659e-002, - 2.873672e-002, 2.883550e-002, 2.893277e-002, 2.902819e-002, - 2.912165e-002, 2.921310e-002, 2.930243e-002, 2.938811e-002, - 2.947135e-002, 2.955326e-002, 2.963369e-002, 2.971291e-002, - 2.979015e-002, 2.986640e-002, 2.994159e-002, 3.001591e-002, - 3.008902e-002, 3.016003e-002, 3.022845e-002, 3.029631e-002, - 3.036281e-002, 3.042880e-002, 3.049392e-002, 3.055882e-002, - 3.062330e-002, 3.068711e-002, 3.075038e-002, 3.081252e-002, - 3.087263e-002, 3.093255e-002, 3.099232e-002, 3.105140e-002, - 3.111032e-002, 3.116886e-002, 3.122663e-002, 3.128395e-002, - 3.134037e-002, 3.139612e-002, 3.144927e-002, 3.150173e-002, - 3.155311e-002, 3.160231e-002, 3.164882e-002, 3.169281e-002, - 3.173454e-002, 3.177476e-002, 3.181250e-002, 3.184878e-002, - 3.188169e-002, 3.191346e-002, 3.194373e-002, 3.197212e-002, - 3.199901e-002, 3.202554e-002, 3.205045e-002, 3.207361e-002, - 3.209555e-002, 3.211650e-002, 3.213518e-002, 3.215201e-002, - 3.216799e-002, 3.218322e-002, 3.219685e-002, 3.220924e-002, - 3.222039e-002, 3.223028e-002, 3.224050e-002, 3.225093e-002, - 3.226026e-002, 3.226707e-002, 3.227286e-002, 3.227752e-002, - 3.228153e-002, 3.228387e-002, 3.228520e-002, 3.228611e-002, - 3.228593e-002, 3.228524e-002, 3.228232e-002, 3.227557e-002, - 3.226718e-002, 3.225747e-002, 3.224612e-002, 3.223264e-002, - 3.221771e-002, 3.220026e-002, 3.218160e-002, 3.216118e-002, - 3.213981e-002, 3.211469e-002, 3.208782e-002, 3.206007e-002, - 3.203126e-002, 3.200167e-002, 3.197165e-002, 3.194031e-002, - 3.190743e-002, 3.187400e-002, 3.183946e-002, 3.180115e-002, - 3.175928e-002, 3.171587e-002, 3.167146e-002, 3.162530e-002, - 3.157741e-002, 3.152789e-002, 3.147663e-002, 3.142246e-002, - 3.136501e-002, 3.130346e-002, 3.123699e-002, 3.116953e-002, - 3.109936e-002, 3.102779e-002, 3.095295e-002, 3.087560e-002, - 3.079631e-002, 3.071589e-002, 3.063382e-002, 3.054973e-002, - 3.045998e-002, 3.036949e-002, 3.027739e-002, 3.018344e-002, - 3.008859e-002, 2.999360e-002, 2.989827e-002, 2.980283e-002, - 2.970704e-002, 2.960970e-002, 2.950349e-002, 2.939527e-002, - 2.928523e-002, 2.917266e-002, 2.905757e-002, 2.894071e-002, - 2.882180e-002, 2.869959e-002, 2.857575e-002, 2.845008e-002, - 2.831566e-002, 2.817780e-002, 2.803695e-002, 2.789301e-002, - 2.774673e-002, 2.759854e-002, 2.744850e-002, 2.729682e-002, - 2.714465e-002, 2.699143e-002, 2.683162e-002, 2.666496e-002, - 2.649675e-002, 2.632743e-002, 2.615595e-002, 2.598346e-002, - 2.581048e-002, 2.563700e-002, 2.546115e-002, 2.528375e-002, - 2.510156e-002, 2.491103e-002, 2.471859e-002, 2.452439e-002, - 2.432807e-002, 2.412877e-002, 2.392610e-002, 2.372015e-002, - 2.351167e-002, 2.329986e-002, 2.308513e-002, 2.285558e-002, - 2.262340e-002, 2.238864e-002, 2.215123e-002, 2.191134e-002, - 2.166893e-002, 2.142404e-002, 2.117648e-002, 2.092594e-002, - 2.067282e-002, 2.040455e-002, 2.013124e-002, 1.985568e-002, - 1.957826e-002, 1.929783e-002, 1.901505e-002, 1.872934e-002, - 1.844247e-002, 1.815118e-002, 1.785771e-002, 1.755223e-002, - 1.723858e-002, 1.692252e-002, 1.660536e-002, 1.628761e-002, - 1.596799e-002, 1.564605e-002, 1.532118e-002, 1.499559e-002, - 1.466648e-002, 1.432733e-002, 1.397388e-002, 1.361818e-002, - 1.326007e-002, 1.290029e-002, 1.253904e-002, 1.217461e-002, - 1.180746e-002, 1.143719e-002, 1.106476e-002, 1.068498e-002, - 1.028033e-002, 9.872624e-003, 9.461763e-003, 9.049343e-003, - 8.634534e-003, 8.216368e-003, 7.795413e-003, 7.370769e-003, - 6.943927e-003, 6.514843e-003, 6.061363e-003, 5.602106e-003, - 5.141048e-003, 4.677731e-003, 4.213148e-003, 3.746919e-003, - 3.281348e-003, 2.812686e-003, 2.341426e-003, 1.867539e-003, - 1.372858e-003, 8.657118e-004, 3.559656e-004, -1.565810e-004, - -6.724358e-004, -1.194563e-003, -1.719430e-003, -2.248734e-003, - -2.781376e-003, -3.315120e-003, -3.866493e-003, -4.438732e-003, - -5.015597e-003, -5.594566e-003, -6.175830e-003, -6.759827e-003, - -7.346464e-003, -7.936339e-003, -8.528588e-003, -9.122071e-003, - -9.725078e-003, -1.035609e-002, -1.098837e-002, -1.162109e-002, - -1.225259e-002, -1.288795e-002, -1.352328e-002, -1.415860e-002, - -1.479598e-002, -1.543840e-002, -1.608183e-002, -1.675517e-002, - -1.743171e-002, -1.810839e-002, -1.878658e-002, -1.946581e-002, - -2.014412e-002, -2.082697e-002, -2.151165e-002, -2.219821e-002, - -2.288776e-002, -2.360226e-002, -2.432553e-002, -2.504945e-002, - -2.577604e-002, -2.650347e-002, -2.723117e-002, -2.796133e-002, - -2.869360e-002, -2.942647e-002, -3.016289e-002, -3.091634e-002, - -3.168529e-002, -3.245680e-002, -3.322924e-002, -3.400264e-002, - -3.477588e-002, -3.554869e-002, -3.632230e-002, -3.709553e-002, - -3.786996e-002, -3.864972e-002, -3.944912e-002, -4.024791e-002, - -4.104873e-002, -4.185212e-002, -4.265380e-002, -4.345909e-002, - -4.426347e-002, -4.506652e-002, -4.586677e-002, -4.666974e-002, - -4.749296e-002, -4.831592e-002, -4.913525e-002, -4.995770e-002, - -5.078286e-002, -5.160197e-002, -5.242122e-002, -5.323735e-002, - -5.404900e-002, -5.485658e-002, -5.567221e-002, -5.648935e-002, - -5.730473e-002, -5.811605e-002, -5.892537e-002, -5.972585e-002, - -6.052435e-002, -6.131953e-002, -6.210712e-002, -6.288800e-002, - -6.366737e-002, -6.444464e-002, -6.521699e-002, -6.598779e-002, - -6.675729e-002, -6.751857e-002, -6.827863e-002, -6.902994e-002, - -6.977257e-002, -7.051074e-002, -7.123471e-002, -7.194008e-002, - -7.264710e-002, -7.334053e-002, -7.402887e-002, -7.471274e-002, - -7.539262e-002, -7.607043e-002, -7.672512e-002, -7.736703e-002, - -7.799883e-002, -7.859128e-002, -7.916153e-002, -7.970872e-002, - -8.023127e-002, -8.071652e-002, -8.117552e-002, -8.161300e-002, - -8.202531e-002, -8.244722e-002, -8.285887e-002, -8.320079e-002, - -8.351897e-002, -8.378219e-002, -8.401684e-002, -8.422844e-002, - -8.442230e-002, -8.459404e-002, -8.471103e-002, -8.468607e-002, - -8.432258e-002, -8.403595e-002, -8.378662e-002, -8.346766e-002, - -8.314768e-002, -8.278308e-002, -8.236903e-002, -8.184399e-002, - -8.119819e-002, -8.046697e-002, -7.966105e-002, -7.533033e-002, - -7.419390e-002, -7.321201e-002, -7.215726e-002, -7.100417e-002, - -6.984035e-002, -6.859966e-002, -6.718541e-002, -6.555212e-002, - -6.358785e-002, -5.725615e-002, -4.660865e-002, -4.258397e-002, - -3.927632e-002, -3.603078e-002, -3.204807e-002, -1.945943e-002, - -1.376409e-002, -8.588147e-003, -4.240431e-003, 0.000000e+000 - }, - { - 0.000000e+000, -3.778279e-003, -6.786376e-003, -9.305227e-003, - -1.146437e-002, -1.335215e-002, -1.503918e-002, -1.656443e-002, - -1.795737e-002, -1.927228e-002, -2.049997e-002, -2.166384e-002, - -2.275593e-002, -2.377559e-002, -2.473027e-002, -2.562439e-002, - -2.645844e-002, -2.723503e-002, -2.795514e-002, -2.862921e-002, - -2.925882e-002, -2.986054e-002, -3.043680e-002, -3.098096e-002, - -3.149602e-002, -3.198254e-002, -3.244185e-002, -3.287617e-002, - -3.328632e-002, -3.367069e-002, -3.403109e-002, -3.437539e-002, - -3.471309e-002, -3.503462e-002, -3.534025e-002, -3.563049e-002, - -3.590648e-002, -3.616836e-002, -3.641770e-002, -3.665554e-002, - -3.688275e-002, -3.710044e-002, -3.731963e-002, -3.752959e-002, - -3.773055e-002, -3.792154e-002, -3.810049e-002, -3.826385e-002, - -3.842028e-002, -3.857853e-002, -3.873136e-002, -3.887740e-002, - -3.902315e-002, -3.916298e-002, -3.929706e-002, -3.942464e-002, - -3.954672e-002, -3.966221e-002, -3.977213e-002, -3.987679e-002, - -3.997667e-002, -4.007119e-002, -4.016473e-002, -4.025457e-002, - -4.033815e-002, -4.041639e-002, -4.048804e-002, -4.055275e-002, - -4.061056e-002, -4.066069e-002, -4.070341e-002, -4.073849e-002, - -4.076647e-002, -4.078798e-002, -4.080105e-002, -4.080581e-002, - -4.080122e-002, -4.078796e-002, -4.076755e-002, -4.074489e-002, - -4.072293e-002, -4.070347e-002, -4.068556e-002, -4.067113e-002, - -4.065914e-002, -4.064936e-002, -4.064094e-002, -4.063291e-002, - -4.062728e-002, -4.062356e-002, -4.062129e-002, -4.061951e-002, - -4.061845e-002, -4.061581e-002, -4.061126e-002, -4.060528e-002, - -4.059728e-002, -4.058678e-002, -4.057251e-002, -4.055510e-002, - -4.053355e-002, -4.050791e-002, -4.047722e-002, -4.043970e-002, - -4.039611e-002, -4.034732e-002, -4.029268e-002, -4.023342e-002, - -4.016961e-002, -4.009870e-002, -4.002226e-002, -3.993963e-002, - -3.985184e-002, -3.975855e-002, -3.965877e-002, -3.955583e-002, - -3.944882e-002, -3.933742e-002, -3.922167e-002, -3.910243e-002, - -3.898025e-002, -3.885630e-002, -3.873339e-002, -3.861230e-002, - -3.849282e-002, -3.837921e-002, -3.826981e-002, -3.816366e-002, - -3.806035e-002, -3.795891e-002, -3.785762e-002, -3.775618e-002, - -3.765372e-002, -3.754939e-002, -3.743724e-002, -3.732328e-002, - -3.720648e-002, -3.708665e-002, -3.696350e-002, -3.683761e-002, - -3.670773e-002, -3.657465e-002, -3.643898e-002, -3.630097e-002, - -3.615454e-002, -3.600468e-002, -3.585407e-002, -3.570336e-002, - -3.555241e-002, -3.540270e-002, -3.525445e-002, -3.510735e-002, - -3.496037e-002, -3.481558e-002, -3.466761e-002, -3.451752e-002, - -3.436860e-002, -3.422294e-002, -3.407909e-002, -3.393714e-002, - -3.379553e-002, -3.365411e-002, -3.351250e-002, -3.337086e-002, - -3.322462e-002, -3.307061e-002, -3.291406e-002, -3.275470e-002, - -3.258993e-002, -3.242004e-002, -3.224683e-002, -3.207156e-002, - -3.189502e-002, -3.171721e-002, -3.153787e-002, -3.134765e-002, - -3.115390e-002, -3.095620e-002, -3.075535e-002, -3.055249e-002, - -3.034935e-002, -3.014740e-002, -2.994889e-002, -2.975657e-002, - -2.956913e-002, -2.937579e-002, -2.918342e-002, -2.899210e-002, - -2.880221e-002, -2.861228e-002, -2.842260e-002, -2.823329e-002, - -2.804370e-002, -2.785353e-002, -2.766238e-002, -2.746323e-002, - -2.725889e-002, -2.705256e-002, -2.684487e-002, -2.663572e-002, - -2.642563e-002, -2.621417e-002, -2.600079e-002, -2.578586e-002, - -2.556915e-002, -2.534612e-002, -2.511226e-002, -2.487396e-002, - -2.463040e-002, -2.438355e-002, -2.413528e-002, -2.388760e-002, - -2.364143e-002, -2.339698e-002, -2.315466e-002, -2.291105e-002, - -2.265831e-002, -2.240438e-002, -2.214980e-002, -2.189457e-002, - -2.164068e-002, -2.139037e-002, -2.114575e-002, -2.090801e-002, - -2.067663e-002, -2.044984e-002, -2.021510e-002, -1.998235e-002, - -1.975166e-002, -1.952281e-002, -1.929543e-002, -1.906901e-002, - -1.884372e-002, -1.862016e-002, -1.839793e-002, -1.817734e-002, - -1.794902e-002, -1.771818e-002, -1.748761e-002, -1.725687e-002, - -1.702580e-002, -1.679363e-002, -1.656042e-002, -1.632509e-002, - -1.608786e-002, -1.584773e-002, -1.559841e-002, -1.533764e-002, - -1.507097e-002, -1.479962e-002, -1.452627e-002, -1.425381e-002, - -1.398337e-002, -1.371665e-002, -1.345292e-002, -1.319278e-002, - -1.293273e-002, -1.266685e-002, -1.240360e-002, -1.214321e-002, - -1.188608e-002, -1.163218e-002, -1.138398e-002, -1.114070e-002, - -1.090141e-002, -1.066518e-002, -1.043167e-002, -1.018890e-002, - -9.947636e-003, -9.707482e-003, -9.467606e-003, -9.227907e-003, - -8.988176e-003, -8.747325e-003, -8.504651e-003, -8.259597e-003, - -8.010941e-003, -7.751194e-003, -7.487530e-003, -7.222049e-003, - -6.955965e-003, -6.689267e-003, -6.421473e-003, -6.152446e-003, - -5.881950e-003, -5.610049e-003, -5.337010e-003, -5.056806e-003, - -4.773926e-003, -4.495110e-003, -4.222550e-003, -3.957375e-003, - -3.699009e-003, -3.445065e-003, -3.195864e-003, -2.949634e-003, - -2.706695e-003, -2.463269e-003, -2.215863e-003, -1.970884e-003, - -1.727762e-003, -1.485847e-003, -1.244941e-003, -1.004227e-003, - -7.631903e-004, -5.225846e-004, -2.824247e-004, -4.058919e-005, - 2.088256e-004, 4.579873e-004, 7.083758e-004, 9.599893e-004, - 1.213376e-003, 1.469624e-003, 1.729799e-003, 1.994671e-003, - 2.264396e-003, 2.537229e-003, 2.819314e-003, 3.102117e-003, - 3.383547e-003, 3.663172e-003, 3.941704e-003, 4.220204e-003, - 4.498431e-003, 4.775988e-003, 5.053130e-003, 5.328684e-003, - 5.607015e-003, 5.884909e-003, 6.157092e-003, 6.424691e-003, - 6.687545e-003, 6.947930e-003, 7.205827e-003, 7.461321e-003, - 7.715465e-003, 7.968929e-003, 8.224624e-003, 8.484386e-003, - 8.744709e-003, 9.005815e-003, 9.268433e-003, 9.533049e-003, - 9.799401e-003, 1.006873e-002, 1.034145e-002, 1.061917e-002, - 1.090187e-002, 1.119417e-002, 1.149068e-002, 1.178929e-002, - 1.208710e-002, 1.237983e-002, 1.266487e-002, 1.294242e-002, - 1.321323e-002, 1.347745e-002, 1.373491e-002, 1.399231e-002, - 1.424472e-002, 1.449267e-002, 1.473638e-002, 1.497507e-002, - 1.521069e-002, 1.544412e-002, 1.567557e-002, 1.590417e-002, - 1.613019e-002, 1.635811e-002, 1.658504e-002, 1.681039e-002, - 1.703417e-002, 1.725669e-002, 1.747816e-002, 1.769793e-002, - 1.791665e-002, 1.813553e-002, 1.835449e-002, 1.857572e-002, - 1.879873e-002, 1.902092e-002, 1.924127e-002, 1.946022e-002, - 1.967725e-002, 1.989383e-002, 2.010889e-002, 2.032180e-002, - 2.053218e-002, 2.074033e-002, 2.094727e-002, 2.114976e-002, - 2.134810e-002, 2.154276e-002, 2.173464e-002, 2.192419e-002, - 2.211184e-002, 2.229833e-002, 2.248210e-002, 2.266389e-002, - 2.284829e-002, 2.303042e-002, 2.321075e-002, 2.339011e-002, - 2.356888e-002, 2.374685e-002, 2.392377e-002, 2.410171e-002, - 2.428013e-002, 2.445969e-002, 2.464310e-002, 2.482880e-002, - 2.501589e-002, 2.520466e-002, 2.539466e-002, 2.558473e-002, - 2.577512e-002, 2.596579e-002, 2.615712e-002, 2.634738e-002, - 2.653747e-002, 2.672515e-002, 2.690960e-002, 2.709133e-002, - 2.727007e-002, 2.744684e-002, 2.762048e-002, 2.779108e-002, - 2.795816e-002, 2.812039e-002, 2.827889e-002, 2.843507e-002, - 2.858845e-002, 2.873918e-002, 2.888721e-002, 2.903349e-002, - 2.917947e-002, 2.932488e-002, 2.946949e-002, 2.961416e-002, - 2.976193e-002, 2.991529e-002, 3.007295e-002, 3.023333e-002, - 3.039424e-002, 3.055355e-002, 3.071090e-002, 3.086741e-002, - 3.102406e-002, 3.118197e-002, 3.134134e-002, 3.150632e-002, - 3.167296e-002, 3.184051e-002, 3.200591e-002, 3.216559e-002, - 3.231644e-002, 3.245866e-002, 3.259443e-002, 3.272530e-002, - 3.285083e-002, 3.297491e-002, 3.309552e-002, 3.321129e-002, - 3.332461e-002, 3.343451e-002, 3.354172e-002, 3.364481e-002, - 3.374538e-002, 3.384330e-002, 3.393852e-002, 3.403254e-002, - 3.412545e-002, 3.421600e-002, 3.430281e-002, 3.438603e-002, - 3.446546e-002, 3.454220e-002, 3.461640e-002, 3.468709e-002, - 3.475510e-002, 3.482058e-002, 3.488620e-002, 3.494951e-002, - 3.501130e-002, 3.507118e-002, 3.513017e-002, 3.518758e-002, - 3.524496e-002, 3.530133e-002, 3.535658e-002, 3.541029e-002, - 3.546530e-002, 3.552015e-002, 3.557269e-002, 3.562289e-002, - 3.567232e-002, 3.572066e-002, 3.576720e-002, 3.581137e-002, - 3.585158e-002, 3.588632e-002, 3.591873e-002, 3.594815e-002, - 3.597272e-002, 3.599178e-002, 3.600564e-002, 3.601334e-002, - 3.601588e-002, 3.601235e-002, 3.600455e-002, 3.599313e-002, - 3.598049e-002, 3.596644e-002, 3.595112e-002, 3.593366e-002, - 3.591373e-002, 3.589394e-002, 3.587401e-002, 3.585543e-002, - 3.583887e-002, 3.582519e-002, 3.581472e-002, 3.580629e-002, - 3.579381e-002, 3.577553e-002, 3.575036e-002, 3.571467e-002, - 3.566920e-002, 3.561397e-002, 3.555059e-002, 3.547879e-002, - 3.539771e-002, 3.531256e-002, 3.521788e-002, 3.511371e-002, - 3.500000e-002, 3.487890e-002, 3.475106e-002, 3.461671e-002, - 3.447704e-002, 3.433351e-002, 3.418622e-002, 3.404142e-002, - 3.389589e-002, 3.374873e-002, 3.359978e-002, 3.345138e-002, - 3.330475e-002, 3.316183e-002, 3.301949e-002, 3.287904e-002, - 3.274005e-002, 3.260503e-002, 3.247271e-002, 3.234054e-002, - 3.220715e-002, 3.207431e-002, 3.194163e-002, 3.181024e-002, - 3.167968e-002, 3.155125e-002, 3.142206e-002, 3.129537e-002, - 3.117132e-002, 3.104506e-002, 3.091577e-002, 3.078345e-002, - 3.064711e-002, 3.050877e-002, 3.036833e-002, 3.022656e-002, - 3.008333e-002, 2.993928e-002, 2.980142e-002, 2.966119e-002, - 2.951746e-002, 2.937187e-002, 2.922562e-002, 2.907754e-002, - 2.892724e-002, 2.877700e-002, 2.862645e-002, 2.847513e-002, - 2.833268e-002, 2.819198e-002, 2.805166e-002, 2.791404e-002, - 2.778043e-002, 2.765126e-002, 2.752608e-002, 2.740240e-002, - 2.727615e-002, 2.714497e-002, 2.701512e-002, 2.688412e-002, - 2.674930e-002, 2.660927e-002, 2.646620e-002, 2.632217e-002, - 2.617570e-002, 2.602662e-002, 2.587258e-002, 2.571193e-002, - 2.554786e-002, 2.538503e-002, 2.521658e-002, 2.504415e-002, - 2.486729e-002, 2.468660e-002, 2.450211e-002, 2.431411e-002, - 2.412415e-002, 2.393243e-002, 2.374165e-002, 2.355967e-002, - 2.337595e-002, 2.319129e-002, 2.300602e-002, 2.282144e-002, - 2.263715e-002, 2.245256e-002, 2.226703e-002, 2.208080e-002, - 2.188962e-002, 2.170272e-002, 2.150401e-002, 2.129037e-002, - 2.106234e-002, 2.081796e-002, 2.056065e-002, 2.029051e-002, - 2.000564e-002, 1.970785e-002, 1.939921e-002, 1.908967e-002, - 1.877302e-002, 1.844759e-002, 1.811245e-002, 1.776782e-002, - 1.741628e-002, 1.705827e-002, 1.669040e-002, 1.631598e-002, - 1.593677e-002, 1.556193e-002, 1.518988e-002, 1.481252e-002, - 1.443024e-002, 1.404298e-002, 1.365170e-002, 1.325897e-002, - 1.286554e-002, 1.247225e-002, 1.207856e-002, 1.169218e-002, - 1.132497e-002, 1.096137e-002, 1.059795e-002, 1.023831e-002, - 9.880217e-003, 9.523918e-003, 9.170284e-003, 8.821918e-003, - 8.476080e-003, 8.134338e-003, 7.813432e-003, 7.490492e-003, - 7.165004e-003, 6.832579e-003, 6.492566e-003, 6.141795e-003, - 5.782214e-003, 5.415402e-003, 5.039957e-003, 4.660200e-003, - 4.294224e-003, 3.927508e-003, 3.556450e-003, 3.181603e-003, - 2.802940e-003, 2.420006e-003, 2.034341e-003, 1.645073e-003, - 1.253660e-003, 8.571973e-004, 4.747085e-004, 1.021895e-004, - -2.733159e-004, -6.490595e-004, -1.025983e-003, -1.404754e-003, - -1.785844e-003, -2.166046e-003, -2.545054e-003, -2.920506e-003, - -3.281447e-003, -3.622679e-003, -3.962294e-003, -4.303671e-003, - -4.649887e-003, -4.997182e-003, -5.347716e-003, -5.701594e-003, - -6.059682e-003, -6.417756e-003, -6.773829e-003, -7.105894e-003, - -7.444165e-003, -7.789014e-003, -8.142199e-003, -8.501984e-003, - -8.868333e-003, -9.240628e-003, -9.619499e-003, -1.000216e-002, - -1.038806e-002, -1.074392e-002, -1.109773e-002, -1.145422e-002, - -1.181540e-002, -1.217650e-002, -1.253541e-002, -1.289335e-002, - -1.325066e-002, -1.360607e-002, -1.396207e-002, -1.429384e-002, - -1.461788e-002, -1.494387e-002, -1.526627e-002, -1.559258e-002, - -1.591848e-002, -1.624901e-002, -1.658510e-002, -1.692640e-002, - -1.727298e-002, -1.760473e-002, -1.791894e-002, -1.823932e-002, - -1.856727e-002, -1.890077e-002, -1.924063e-002, -1.958724e-002, - -1.993984e-002, -2.030038e-002, -2.066603e-002, -2.102580e-002, - -2.135140e-002, -2.167948e-002, -2.201023e-002, -2.234486e-002, - -2.268429e-002, -2.302581e-002, -2.336487e-002, -2.370174e-002, - -2.403963e-002, -2.438464e-002, -2.468537e-002, -2.499296e-002, - -2.530747e-002, -2.563513e-002, -2.597161e-002, -2.631502e-002, - -2.666266e-002, -2.701296e-002, -2.737040e-002, -2.773330e-002, - -2.806068e-002, -2.838371e-002, -2.871775e-002, -2.905858e-002, - -2.940521e-002, -2.975703e-002, -3.011314e-002, -3.047322e-002, - -3.083481e-002, -3.120337e-002, -3.154174e-002, -3.184960e-002, - -3.215853e-002, -3.246662e-002, -3.277743e-002, -3.308532e-002, - -3.339424e-002, -3.370158e-002, -3.400935e-002, -3.431619e-002, - -3.460076e-002, -3.483435e-002, -3.507512e-002, -3.531981e-002, - -3.557086e-002, -3.583204e-002, -3.609830e-002, -3.637111e-002, - -3.664899e-002, -3.693739e-002, -3.722864e-002, -3.745590e-002, - -3.769140e-002, -3.793261e-002, -3.817856e-002, -3.842905e-002, - -3.868603e-002, -3.895068e-002, -3.921906e-002, -3.949457e-002, - -3.977421e-002, -3.998629e-002, -4.018775e-002, -4.038910e-002, - -4.060019e-002, -4.081298e-002, -4.102751e-002, -4.124583e-002, - -4.146780e-002, -4.169144e-002, -4.191705e-002, -4.208992e-002, - -4.222661e-002, -4.236221e-002, -4.249943e-002, -4.263342e-002, - -4.277061e-002, -4.290645e-002, -4.304704e-002, -4.319248e-002, - -4.334020e-002, -4.345690e-002, -4.351198e-002, -4.357233e-002, - -4.363041e-002, -4.369060e-002, -4.375327e-002, -4.381826e-002, - -4.389215e-002, -4.397413e-002, -4.406673e-002, -4.414843e-002, - -4.414210e-002, -4.414140e-002, -4.414826e-002, -4.416194e-002, - -4.417435e-002, -4.419210e-002, -4.421092e-002, -4.423286e-002, - -4.425444e-002, -4.427900e-002, -4.419100e-002, -4.409243e-002, - -4.399721e-002, -4.390398e-002, -4.380575e-002, -4.370322e-002, - -4.360344e-002, -4.350542e-002, -4.340950e-002, -4.331944e-002, - -4.314580e-002, -4.293684e-002, -4.273682e-002, -4.254710e-002, - -4.236491e-002, -4.219374e-002, -4.203339e-002, -4.187979e-002, - -4.173122e-002, -4.159195e-002, -4.140052e-002, -4.113887e-002, - -4.088439e-002, -4.063536e-002, -4.038995e-002, -4.014413e-002, - -3.990019e-002, -3.966431e-002, -3.943176e-002, -3.919557e-002, - -3.892895e-002, -3.854901e-002, -3.817390e-002, -3.779922e-002, - -3.742891e-002, -3.706369e-002, -3.669877e-002, -3.634091e-002, - -3.598140e-002, -3.561627e-002, -3.525173e-002, -3.473044e-002, - -3.420338e-002, -3.368344e-002, -3.315862e-002, -3.263199e-002, - -3.211346e-002, -3.160120e-002, -3.108926e-002, -3.057891e-002, - -3.006655e-002, -2.943242e-002, -2.876613e-002, -2.810458e-002, - -2.745152e-002, -2.680560e-002, -2.616883e-002, -2.553842e-002, - -2.491822e-002, -2.430788e-002, -2.369176e-002, -2.299806e-002, - -2.223044e-002, -2.146536e-002, -2.070532e-002, -1.994321e-002, - -1.919159e-002, -1.844348e-002, -1.769618e-002, -1.695097e-002, - -1.620312e-002, -1.541079e-002, -1.449686e-002, -1.358570e-002, - -1.267635e-002, -1.177188e-002, -1.087594e-002, -9.972071e-003, - -9.075723e-003, -8.186147e-003, -7.298838e-003, -6.403344e-003, - -5.335510e-003, -4.277439e-003, -3.222898e-003, -2.171006e-003, - -1.107659e-003, -4.540933e-005, 1.033960e-003, 2.115767e-003, - 3.193851e-003, 4.272948e-003, 5.500896e-003, 6.763265e-003, - 8.021898e-003, 9.281093e-003, 1.053455e-002, 1.178868e-002, - 1.305516e-002, 1.432125e-002, 1.557356e-002, 1.680887e-002, - 1.814650e-002, 1.954310e-002, 2.094075e-002, 2.235043e-002, - 2.375893e-002, 2.515883e-002, 2.655285e-002, 2.793617e-002, - 2.929203e-002, 3.063485e-002, 3.202690e-002, 3.349920e-002, - 3.495898e-002, 3.640019e-002, 3.783443e-002, 3.927635e-002, - 4.071000e-002, 4.214031e-002, 4.353427e-002, 4.490859e-002, - 4.629790e-002, 4.782648e-002, 4.932651e-002, 5.078565e-002, - 5.221646e-002, 5.359495e-002, 5.494292e-002, 5.627183e-002, - 5.757462e-002, 5.888570e-002, 6.021349e-002, 6.165751e-002, - 6.310311e-002, 6.453080e-002, 6.597170e-002, 6.743673e-002, - 6.893331e-002, 7.038959e-002, 7.177035e-002, 7.300503e-002, - 7.394534e-002, 7.511746e-002, 7.642173e-002, 7.758371e-002, - 7.865834e-002, 7.954943e-002, 8.028040e-002, 8.079938e-002, - 8.113260e-002, 8.136380e-002, 8.145903e-002, 7.854080e-002, - 7.841114e-002, 7.834592e-002, 7.813521e-002, 7.777400e-002, - 7.745587e-002, 7.708642e-002, 7.650118e-002, 7.566620e-002, - 7.440254e-002, 6.815737e-002, 5.930303e-002, 5.581550e-002, - 5.273708e-002, 4.957380e-002, 4.519876e-002, 2.620510e-002, - 1.889050e-002, 1.221843e-002, 6.258578e-003, 0.000000e+000 - }, - { - 0.000000e+000, -2.360896e-003, -4.274943e-003, -5.905649e-003, - -7.368838e-003, -8.715672e-003, -9.986865e-003, -1.127152e-002, - -1.255103e-002, -1.401107e-002, -1.540896e-002, -1.661416e-002, - -1.776548e-002, -1.887420e-002, -1.992056e-002, -2.089192e-002, - -2.177797e-002, -2.255967e-002, -2.323417e-002, -2.385498e-002, - -2.443536e-002, -2.494330e-002, -2.538212e-002, -2.579366e-002, - -2.618314e-002, -2.655469e-002, -2.691086e-002, -2.725425e-002, - -2.758442e-002, -2.790527e-002, -2.821810e-002, -2.850432e-002, - -2.874125e-002, -2.897076e-002, -2.919356e-002, -2.941006e-002, - -2.962066e-002, -2.982512e-002, -3.002458e-002, -3.021412e-002, - -3.039782e-002, -3.057600e-002, -3.071039e-002, -3.085683e-002, - -3.101921e-002, -3.120300e-002, -3.146204e-002, -3.188786e-002, - -3.229566e-002, -3.250488e-002, -3.264314e-002, -3.276630e-002, - -3.283809e-002, -3.289057e-002, -3.293160e-002, -3.296372e-002, - -3.298658e-002, -3.300410e-002, -3.301314e-002, -3.301770e-002, - -3.301732e-002, -3.301279e-002, -3.298054e-002, -3.292965e-002, - -3.287543e-002, -3.281661e-002, -3.275425e-002, -3.268864e-002, - -3.261840e-002, -3.254792e-002, -3.247575e-002, -3.240479e-002, - -3.231332e-002, -3.219677e-002, -3.207115e-002, -3.193980e-002, - -3.180072e-002, -3.165655e-002, -3.150798e-002, -3.136203e-002, - -3.122037e-002, -3.108775e-002, -3.095780e-002, -3.081271e-002, - -3.067594e-002, -3.054767e-002, -3.042727e-002, -3.031475e-002, - -3.021188e-002, -3.012629e-002, -3.005729e-002, -3.000457e-002, - -2.996459e-002, -2.991192e-002, -2.986609e-002, -2.982627e-002, - -2.979014e-002, -2.975685e-002, -2.972446e-002, -2.969059e-002, - -2.965303e-002, -2.961156e-002, -2.956256e-002, -2.948764e-002, - -2.939490e-002, -2.928878e-002, -2.916809e-002, -2.903948e-002, - -2.890468e-002, -2.876563e-002, -2.862065e-002, -2.846906e-002, - -2.831303e-002, -2.814246e-002, -2.795660e-002, -2.776824e-002, - -2.757558e-002, -2.737743e-002, -2.717653e-002, -2.697106e-002, - -2.676146e-002, -2.655060e-002, -2.633791e-002, -2.612573e-002, - -2.590844e-002, -2.569521e-002, -2.548775e-002, -2.528486e-002, - -2.508429e-002, -2.488695e-002, -2.469060e-002, -2.449273e-002, - -2.429495e-002, -2.409691e-002, -2.388210e-002, -2.366506e-002, - -2.344623e-002, -2.322420e-002, -2.299895e-002, -2.277070e-002, - -2.253783e-002, -2.230124e-002, -2.206187e-002, -2.181865e-002, - -2.156294e-002, -2.130231e-002, -2.104026e-002, -2.077838e-002, - -2.051815e-002, -2.025831e-002, -2.000006e-002, -1.974314e-002, - -1.948838e-002, -1.923539e-002, -1.897857e-002, -1.871924e-002, - -1.846154e-002, -1.820525e-002, -1.795018e-002, -1.769669e-002, - -1.744302e-002, -1.718785e-002, -1.693206e-002, -1.667613e-002, - -1.641509e-002, -1.614473e-002, -1.587220e-002, -1.559493e-002, - -1.531124e-002, -1.502021e-002, -1.472502e-002, -1.442649e-002, - -1.412617e-002, -1.382395e-002, -1.352061e-002, -1.320828e-002, - -1.289145e-002, -1.256965e-002, -1.224447e-002, -1.191597e-002, - -1.158634e-002, -1.125793e-002, -1.093432e-002, -1.061726e-002, - -1.030640e-002, -9.993893e-003, -9.682586e-003, -9.373169e-003, - -9.066173e-003, -8.760538e-003, -8.455617e-003, -8.151591e-003, - -7.847199e-003, -7.543073e-003, -7.237269e-003, -6.928794e-003, - -6.617495e-003, -6.305938e-003, -5.993645e-003, -5.679285e-003, - -5.364366e-003, -5.048492e-003, -4.731993e-003, -4.414662e-003, - -4.095669e-003, -3.774429e-003, -3.446633e-003, -3.114165e-003, - -2.776847e-003, -2.437561e-003, -2.099448e-003, -1.764101e-003, - -1.432330e-003, -1.104074e-003, -7.790922e-004, -4.543868e-004, - -1.275020e-004, 2.002446e-004, 5.283561e-004, 8.574387e-004, - 1.185628e-003, 1.510524e-003, 1.830254e-003, 2.141882e-003, - 2.447411e-003, 2.747272e-003, 3.044848e-003, 3.338406e-003, - 3.629571e-003, 3.919979e-003, 4.208891e-003, 4.496244e-003, - 4.781103e-003, 5.064221e-003, 5.345375e-003, 5.624560e-003, - 5.900758e-003, 6.175870e-003, 6.450757e-003, 6.726506e-003, - 7.002304e-003, 7.277071e-003, 7.552118e-003, 7.828428e-003, - 8.105385e-003, 8.384147e-003, 8.664888e-003, 8.950609e-003, - 9.243485e-003, 9.542554e-003, 9.841377e-003, 1.013791e-002, - 1.043041e-002, 1.071721e-002, 1.099986e-002, 1.127745e-002, - 1.154890e-002, 1.181582e-002, 1.207918e-002, 1.233816e-002, - 1.259130e-002, 1.283850e-002, 1.307568e-002, 1.330593e-002, - 1.352991e-002, 1.374851e-002, 1.396283e-002, 1.416982e-002, - 1.437312e-002, 1.457440e-002, 1.477219e-002, 1.496488e-002, - 1.515135e-002, 1.533460e-002, 1.551404e-002, 1.569097e-002, - 1.586731e-002, 1.603821e-002, 1.620491e-002, 1.636772e-002, - 1.652547e-002, 1.667613e-002, 1.682034e-002, 1.695802e-002, - 1.709067e-002, 1.721987e-002, 1.734386e-002, 1.746008e-002, - 1.756567e-002, 1.766106e-002, 1.774322e-002, 1.781093e-002, - 1.786764e-002, 1.791514e-002, 1.795522e-002, 1.798977e-002, - 1.801989e-002, 1.804206e-002, 1.805476e-002, 1.806296e-002, - 1.806841e-002, 1.807116e-002, 1.807273e-002, 1.807456e-002, - 1.807728e-002, 1.808166e-002, 1.808750e-002, 1.809402e-002, - 1.809617e-002, 1.810150e-002, 1.811140e-002, 1.812623e-002, - 1.814668e-002, 1.817493e-002, 1.821286e-002, 1.826138e-002, - 1.832033e-002, 1.838854e-002, 1.845877e-002, 1.853527e-002, - 1.861878e-002, 1.870843e-002, 1.880372e-002, 1.890667e-002, - 1.901622e-002, 1.913154e-002, 1.925391e-002, 1.938211e-002, - 1.950902e-002, 1.963660e-002, 1.976425e-002, 1.989111e-002, - 2.001646e-002, 2.014201e-002, 2.026922e-002, 2.039623e-002, - 2.052702e-002, 2.066212e-002, 2.079641e-002, 2.093092e-002, - 2.107065e-002, 2.121470e-002, 2.136350e-002, 2.151766e-002, - 2.167667e-002, 2.184260e-002, 2.201580e-002, 2.219621e-002, - 2.238208e-002, 2.256652e-002, 2.275779e-002, 2.295384e-002, - 2.315021e-002, 2.334222e-002, 2.352593e-002, 2.370248e-002, - 2.387301e-002, 2.403658e-002, 2.419374e-002, 2.433362e-002, - 2.446694e-002, 2.459478e-002, 2.471821e-002, 2.483618e-002, - 2.495028e-002, 2.506116e-002, 2.516786e-002, 2.527170e-002, - 2.537354e-002, 2.546428e-002, 2.554905e-002, 2.563264e-002, - 2.571565e-002, 2.579792e-002, 2.587973e-002, 2.596072e-002, - 2.604172e-002, 2.612296e-002, 2.620385e-002, 2.627993e-002, - 2.634934e-002, 2.641793e-002, 2.648479e-002, 2.654848e-002, - 2.660933e-002, 2.666906e-002, 2.672773e-002, 2.678375e-002, - 2.683798e-002, 2.688663e-002, 2.692371e-002, 2.695753e-002, - 2.698646e-002, 2.701216e-002, 2.703694e-002, 2.706029e-002, - 2.708222e-002, 2.710441e-002, 2.712650e-002, 2.714839e-002, - 2.715769e-002, 2.716565e-002, 2.717228e-002, 2.717899e-002, - 2.718524e-002, 2.719172e-002, 2.719880e-002, 2.720865e-002, - 2.722124e-002, 2.723657e-002, 2.724388e-002, 2.725178e-002, - 2.726217e-002, 2.727563e-002, 2.728934e-002, 2.730537e-002, - 2.732247e-002, 2.733968e-002, 2.735655e-002, 2.737263e-002, - 2.737971e-002, 2.737885e-002, 2.737602e-002, 2.737024e-002, - 2.736166e-002, 2.735150e-002, 2.733956e-002, 2.732451e-002, - 2.730438e-002, 2.727793e-002, 2.724206e-002, 2.719246e-002, - 2.713968e-002, 2.708444e-002, 2.702614e-002, 2.696616e-002, - 2.690682e-002, 2.684817e-002, 2.678989e-002, 2.673148e-002, - 2.667604e-002, 2.661577e-002, 2.656029e-002, 2.650729e-002, - 2.645613e-002, 2.640410e-002, 2.635084e-002, 2.629706e-002, - 2.624410e-002, 2.619299e-002, 2.614627e-002, 2.609591e-002, - 2.604961e-002, 2.600482e-002, 2.595933e-002, 2.590764e-002, - 2.584154e-002, 2.576003e-002, 2.566730e-002, 2.556480e-002, - 2.545335e-002, 2.532731e-002, 2.519074e-002, 2.504769e-002, - 2.489727e-002, 2.474134e-002, 2.458094e-002, 2.441477e-002, - 2.424408e-002, 2.406926e-002, 2.389065e-002, 2.370325e-002, - 2.350808e-002, 2.330940e-002, 2.310516e-002, 2.289756e-002, - 2.268670e-002, 2.247216e-002, 2.225307e-002, 2.203178e-002, - 2.180787e-002, 2.158273e-002, 2.134920e-002, 2.111582e-002, - 2.088052e-002, 2.064596e-002, 2.041193e-002, 2.018045e-002, - 1.995076e-002, 1.972457e-002, 1.950034e-002, 1.927617e-002, - 1.904591e-002, 1.881836e-002, 1.859350e-002, 1.837115e-002, - 1.815161e-002, 1.793664e-002, 1.772397e-002, 1.751181e-002, - 1.730085e-002, 1.709166e-002, 1.687527e-002, 1.665797e-002, - 1.644159e-002, 1.622558e-002, 1.600662e-002, 1.578945e-002, - 1.557444e-002, 1.535957e-002, 1.514813e-002, 1.493803e-002, - 1.472395e-002, 1.450698e-002, 1.429042e-002, 1.407539e-002, - 1.386222e-002, 1.365111e-002, 1.344279e-002, 1.324022e-002, - 1.304365e-002, 1.285188e-002, 1.266125e-002, 1.246627e-002, - 1.226600e-002, 1.206146e-002, 1.183758e-002, 1.156838e-002, - 1.125646e-002, 1.090574e-002, 1.051599e-002, 1.008868e-002, - 9.627202e-003, 9.128324e-003, 8.597165e-003, 8.035804e-003, - 7.447901e-003, 6.835471e-003, 6.202072e-003, 5.551010e-003, - 4.884796e-003, 4.206815e-003, 3.519273e-003, 2.818272e-003, - 2.110670e-003, 1.401120e-003, 6.903936e-004, -1.638198e-005, - -7.135974e-004, -1.398900e-003, -2.074787e-003, -2.737984e-003, - -3.384653e-003, -4.020880e-003, -4.640462e-003, -5.241373e-003, - -5.823547e-003, -6.383119e-003, -6.919046e-003, -7.425275e-003, - -7.902649e-003, -8.346696e-003, -8.756090e-003, -9.132169e-003, - -9.479906e-003, -9.791314e-003, -1.007961e-002, -1.035880e-002, - -1.063062e-002, -1.089370e-002, -1.114753e-002, -1.139318e-002, - -1.163197e-002, -1.186163e-002, -1.208560e-002, -1.230246e-002, - -1.251026e-002, -1.271252e-002, -1.290469e-002, -1.308656e-002, - -1.325855e-002, -1.342436e-002, -1.357860e-002, -1.372395e-002, - -1.386068e-002, -1.398954e-002, -1.410597e-002, -1.421204e-002, - -1.430540e-002, -1.438351e-002, -1.444606e-002, -1.449812e-002, - -1.454279e-002, -1.458684e-002, -1.463257e-002, -1.467814e-002, - -1.472142e-002, -1.476059e-002, -1.479612e-002, -1.482843e-002, - -1.485531e-002, -1.487635e-002, -1.489828e-002, -1.492339e-002, - -1.495162e-002, -1.498154e-002, -1.500862e-002, -1.503822e-002, - -1.506732e-002, -1.509371e-002, -1.511928e-002, -1.514425e-002, - -1.516862e-002, -1.518908e-002, -1.521013e-002, -1.522560e-002, - -1.523903e-002, -1.524846e-002, -1.524989e-002, -1.524585e-002, - -1.524007e-002, -1.523235e-002, -1.521934e-002, -1.520238e-002, - -1.518278e-002, -1.517016e-002, -1.516821e-002, -1.518027e-002, - -1.520240e-002, -1.523723e-002, -1.528238e-002, -1.534124e-002, - -1.541303e-002, -1.549901e-002, -1.559409e-002, -1.569997e-002, - -1.581460e-002, -1.594058e-002, -1.607554e-002, -1.621647e-002, - -1.636636e-002, -1.652340e-002, -1.668474e-002, -1.685008e-002, - -1.702049e-002, -1.720293e-002, -1.739779e-002, -1.760038e-002, - -1.780702e-002, -1.801748e-002, -1.823522e-002, -1.845753e-002, - -1.868053e-002, -1.890412e-002, -1.912907e-002, -1.935389e-002, - -1.957958e-002, -1.980122e-002, -2.001761e-002, -2.023081e-002, - -2.043903e-002, -2.064446e-002, -2.084856e-002, -2.104685e-002, - -2.123686e-002, -2.142329e-002, -2.161328e-002, -2.180088e-002, - -2.198794e-002, -2.217370e-002, -2.236667e-002, -2.256801e-002, - -2.277625e-002, -2.299049e-002, -2.320262e-002, -2.341636e-002, - -2.363713e-002, -2.385964e-002, -2.407775e-002, -2.429353e-002, - -2.450728e-002, -2.472070e-002, -2.493195e-002, -2.513812e-002, - -2.533786e-002, -2.553328e-002, -2.572258e-002, -2.590886e-002, - -2.608742e-002, -2.626136e-002, -2.642732e-002, -2.658576e-002, - -2.673664e-002, -2.687400e-002, -2.699704e-002, -2.710480e-002, - -2.720365e-002, -2.729421e-002, -2.737359e-002, -2.745109e-002, - -2.752881e-002, -2.761109e-002, -2.769737e-002, -2.778342e-002, - -2.787029e-002, -2.795776e-002, -2.804384e-002, -2.813663e-002, - -2.823293e-002, -2.833815e-002, -2.844657e-002, -2.856556e-002, - -2.869072e-002, -2.881630e-002, -2.894820e-002, -2.908186e-002, - -2.921889e-002, -2.936393e-002, -2.951308e-002, -2.966011e-002, - -2.982728e-002, -3.004358e-002, -3.030748e-002, -3.061102e-002, - -3.095579e-002, -3.133872e-002, -3.175770e-002, -3.221663e-002, - -3.270650e-002, -3.321987e-002, -3.375717e-002, -3.431416e-002, - -3.489001e-002, -3.547975e-002, -3.608464e-002, -3.670345e-002, - -3.733331e-002, -3.797427e-002, -3.862149e-002, -3.927308e-002, - -3.992326e-002, -4.057412e-002, -4.121805e-002, -4.184837e-002, - -4.246811e-002, -4.306527e-002, -4.364204e-002, -4.419689e-002, - -4.472849e-002, -4.522967e-002, -4.569501e-002, -4.612203e-002, - -4.650379e-002, -4.684126e-002, -4.712785e-002, -4.735643e-002, - -4.752612e-002, -4.763849e-002, -4.770748e-002, -4.771900e-002, - -4.767239e-002, -4.755962e-002, -4.738832e-002, -4.719806e-002, - -4.699385e-002, -4.676563e-002, -4.652292e-002, -4.626246e-002, - -4.599168e-002, -4.570958e-002, -4.542004e-002, -4.511216e-002, - -4.478959e-002, -4.444727e-002, -4.408761e-002, -4.371019e-002, - -4.331740e-002, -4.290329e-002, -4.248020e-002, -4.204135e-002, - -4.157690e-002, -4.108876e-002, -4.057962e-002, -4.005352e-002, - -3.950325e-002, -3.892208e-002, -3.831182e-002, -3.767926e-002, - -3.703787e-002, -3.638723e-002, -3.570871e-002, -3.500656e-002, - -3.428858e-002, -3.355839e-002, -3.282371e-002, -3.207244e-002, - -3.131090e-002, -3.054011e-002, -2.976208e-002, -2.898263e-002, - -2.818930e-002, -2.738675e-002, -2.657148e-002, -2.574571e-002, - -2.491286e-002, -2.406676e-002, -2.321062e-002, -2.234741e-002, - -2.146643e-002, -2.058671e-002, -1.969661e-002, -1.880264e-002, - -1.789329e-002, -1.697492e-002, -1.604023e-002, -1.509356e-002, - -1.413334e-002, -1.316553e-002, -1.219498e-002, -1.121350e-002, - -1.022420e-002, -9.218249e-003, -8.194948e-003, -7.166028e-003, - -6.118933e-003, -5.062977e-003, -4.002526e-003, -2.938600e-003, - -1.874053e-003, -8.124279e-004, 2.426266e-004, 1.299519e-003, - 2.356924e-003, 3.423349e-003, 4.494925e-003, 5.567379e-003, - 6.637724e-003, 7.705697e-003, 8.774098e-003, 9.833285e-003, - 1.086581e-002, 1.189999e-002, 1.292715e-002, 1.395299e-002, - 1.497549e-002, 1.600173e-002, 1.703227e-002, 1.805900e-002, - 1.908315e-002, 2.011093e-002, 2.112233e-002, 2.213348e-002, - 2.314845e-002, 2.416172e-002, 2.517577e-002, 2.618429e-002, - 2.719920e-002, 2.821283e-002, 2.923392e-002, 3.025381e-002, - 3.124036e-002, 3.220577e-002, 3.316308e-002, 3.410222e-002, - 3.502958e-002, 3.594870e-002, 3.685940e-002, 3.774501e-002, - 3.862478e-002, 3.950079e-002, 4.035807e-002, 4.118645e-002, - 4.201368e-002, 4.283345e-002, 4.364499e-002, 4.444504e-002, - 4.522483e-002, 4.600111e-002, 4.677178e-002, 4.752748e-002, - 4.826989e-002, 4.897104e-002, 4.966668e-002, 5.035375e-002, - 5.102643e-002, 5.169880e-002, 5.237010e-002, 5.304156e-002, - 5.370162e-002, 5.436628e-002, 5.501398e-002, 5.560999e-002, - 5.619190e-002, 5.677848e-002, 5.734037e-002, 5.788572e-002, - 5.841639e-002, 5.893213e-002, 5.943382e-002, 5.992121e-002, - 6.039367e-002, 6.080280e-002, 6.117986e-002, 6.150898e-002, - 6.180408e-002, 6.205528e-002, 6.228023e-002, 6.246853e-002, - 6.263032e-002, 6.276013e-002, 6.284891e-002, 6.287447e-002, - 6.282524e-002, 6.272784e-002, 6.258828e-002, 6.241053e-002, - 6.222478e-002, 6.201195e-002, 6.177584e-002, 6.151671e-002, - 6.123411e-002, 6.090018e-002, 6.048345e-002, 6.003673e-002, - 5.956052e-002, 5.905527e-002, 5.853810e-002, 5.799391e-002, - 5.740801e-002, 5.679262e-002, 5.615924e-002, 5.548418e-002, - 5.470384e-002, 5.389553e-002, 5.305557e-002, 5.214974e-002, - 5.116006e-002, 5.012557e-002, 4.902787e-002, 4.785994e-002, - 4.663504e-002, 4.536721e-002, 4.395278e-002, 4.248978e-002, - 4.099954e-002, 3.947331e-002, 3.791131e-002, 3.633650e-002, - 3.470608e-002, 3.305575e-002, 3.140630e-002, 2.974680e-002, - 2.798749e-002, 2.616536e-002, 2.434344e-002, 2.247793e-002, - 2.057273e-002, 1.865802e-002, 1.672675e-002, 1.478296e-002, - 1.284464e-002, 1.090682e-002, 8.908942e-003, 6.784438e-003, - 4.679112e-003, 2.573731e-003, 4.510803e-004, -1.689202e-003, - -3.864831e-003, -6.055147e-003, -8.218578e-003, -1.038528e-002, - -1.258716e-002, -1.503817e-002, -1.742672e-002, -1.978906e-002, - -2.213819e-002, -2.441817e-002, -2.674576e-002, -2.907635e-002, - -3.139250e-002, -3.364778e-002, -3.592420e-002, -3.835364e-002, - -4.077078e-002, -4.329452e-002, -4.592237e-002, -4.866156e-002, - -5.149681e-002, -5.426273e-002, -5.699712e-002, -5.989997e-002, - -6.346216e-002, -6.690931e-002, -7.034904e-002, -7.359218e-002, - -7.650300e-002, -7.901830e-002, -8.141704e-002, -8.367779e-002, - -8.590537e-002, -8.815674e-002, -9.019702e-002, -9.961888e-002, - -1.015927e-001, -1.026727e-001, -1.034023e-001, -1.038345e-001, - -1.044770e-001, -1.051726e-001, -1.057127e-001, -1.059350e-001, - -1.060081e-001, -1.063140e-001, -1.128114e-001, -1.102135e-001, - -1.078385e-001, -1.045745e-001, -9.780112e-002, -4.743233e-002, - -3.300751e-002, -2.056588e-002, -1.011251e-002, 0.000000e+000 - }, - { - 0.000000e+000, -2.773723e-003, -4.854843e-003, -6.537040e-003, - -7.956489e-003, -9.196058e-003, -1.031661e-002, -1.132384e-002, - -1.225372e-002, -1.323209e-002, -1.418436e-002, -1.500351e-002, - -1.577165e-002, -1.650604e-002, -1.720260e-002, -1.785847e-002, - -1.846182e-002, -1.901137e-002, -1.950023e-002, -1.995570e-002, - -2.038386e-002, -2.073590e-002, -2.102544e-002, -2.129326e-002, - -2.154253e-002, -2.178024e-002, -2.200291e-002, -2.221362e-002, - -2.241197e-002, -2.259943e-002, -2.277387e-002, -2.291684e-002, - -2.301039e-002, -2.310126e-002, -2.318915e-002, -2.326970e-002, - -2.334448e-002, -2.341005e-002, -2.347200e-002, -2.353164e-002, - -2.358840e-002, -2.363768e-002, -2.363332e-002, -2.362303e-002, - -2.360548e-002, -2.358069e-002, -2.353762e-002, -2.345716e-002, - -2.337799e-002, -2.334174e-002, -2.331857e-002, -2.329896e-002, - -2.324169e-002, -2.318121e-002, -2.312415e-002, -2.306789e-002, - -2.301328e-002, -2.296062e-002, -2.290671e-002, -2.285306e-002, - -2.280327e-002, -2.275406e-002, -2.268464e-002, -2.260119e-002, - -2.251352e-002, -2.242769e-002, -2.233479e-002, -2.223801e-002, - -2.213990e-002, -2.203381e-002, -2.192517e-002, -2.181069e-002, - -2.168435e-002, -2.153639e-002, -2.138741e-002, -2.123439e-002, - -2.108018e-002, -2.092026e-002, -2.075997e-002, -2.059931e-002, - -2.044242e-002, -2.029065e-002, -2.013830e-002, -1.996660e-002, - -1.979834e-002, -1.963692e-002, -1.947518e-002, -1.931697e-002, - -1.916456e-002, -1.902057e-002, -1.888423e-002, -1.875235e-002, - -1.863058e-002, -1.848985e-002, -1.834862e-002, -1.821183e-002, - -1.807312e-002, -1.793880e-002, -1.780330e-002, -1.766932e-002, - -1.753524e-002, -1.739700e-002, -1.725468e-002, -1.709396e-002, - -1.692083e-002, -1.674136e-002, -1.655437e-002, -1.636301e-002, - -1.617213e-002, -1.597793e-002, -1.578063e-002, -1.558220e-002, - -1.538167e-002, -1.516939e-002, -1.494237e-002, -1.471584e-002, - -1.448789e-002, -1.425976e-002, -1.403041e-002, -1.379895e-002, - -1.356745e-002, -1.333578e-002, -1.310682e-002, -1.287389e-002, - -1.263232e-002, -1.239941e-002, -1.217103e-002, -1.194886e-002, - -1.173000e-002, -1.151290e-002, -1.129883e-002, -1.108733e-002, - -1.087525e-002, -1.066307e-002, -1.043565e-002, -1.020888e-002, - -9.983225e-003, -9.758086e-003, -9.531589e-003, -9.304608e-003, - -9.075345e-003, -8.845927e-003, -8.618173e-003, -8.388267e-003, - -8.143587e-003, -7.897512e-003, -7.652927e-003, -7.409256e-003, - -7.169066e-003, -6.930755e-003, -6.693640e-003, -6.458103e-003, - -6.226913e-003, -5.999515e-003, -5.767580e-003, -5.530633e-003, - -5.294449e-003, -5.063163e-003, -4.833209e-003, -4.605478e-003, - -4.379075e-003, -4.154908e-003, -3.930795e-003, -3.709459e-003, - -3.485946e-003, -3.257005e-003, -3.025861e-003, -2.792860e-003, - -2.557654e-003, -2.320999e-003, -2.083722e-003, -1.847119e-003, - -1.610125e-003, -1.374844e-003, -1.139747e-003, -8.959992e-004, - -6.515005e-004, -4.064176e-004, -1.605657e-004, 8.614537e-005, - 3.322379e-004, 5.772164e-004, 8.177365e-004, 1.052941e-003, - 1.283767e-003, 1.516818e-003, 1.747570e-003, 1.976353e-003, - 2.203168e-003, 2.427776e-003, 2.650000e-003, 2.869864e-003, - 3.090004e-003, 3.309298e-003, 3.527853e-003, 3.747580e-003, - 3.966900e-003, 4.186325e-003, 4.406693e-003, 4.627078e-003, - 4.846718e-003, 5.065333e-003, 5.285225e-003, 5.504989e-003, - 5.724243e-003, 5.943804e-003, 6.164628e-003, 6.387680e-003, - 6.612736e-003, 6.838571e-003, 7.061752e-003, 7.281200e-003, - 7.497617e-003, 7.712072e-003, 7.924858e-003, 8.136520e-003, - 8.347502e-003, 8.557643e-003, 8.766034e-003, 8.973214e-003, - 9.176951e-003, 9.376415e-003, 9.570995e-003, 9.760186e-003, - 9.943433e-003, 1.012192e-002, 1.029510e-002, 1.046271e-002, - 1.062658e-002, 1.078758e-002, 1.094585e-002, 1.110277e-002, - 1.125601e-002, 1.140530e-002, 1.155114e-002, 1.169401e-002, - 1.183166e-002, 1.196807e-002, 1.210191e-002, 1.223464e-002, - 1.236576e-002, 1.249559e-002, 1.262561e-002, 1.275491e-002, - 1.288140e-002, 1.300689e-002, 1.313187e-002, 1.325814e-002, - 1.338813e-002, 1.352184e-002, 1.365515e-002, 1.378680e-002, - 1.391487e-002, 1.403752e-002, 1.415750e-002, 1.427464e-002, - 1.438744e-002, 1.449782e-002, 1.460584e-002, 1.471094e-002, - 1.481179e-002, 1.490885e-002, 1.499748e-002, 1.508268e-002, - 1.516277e-002, 1.523994e-002, 1.531415e-002, 1.538215e-002, - 1.544761e-002, 1.551175e-002, 1.557406e-002, 1.563298e-002, - 1.568836e-002, 1.574181e-002, 1.579332e-002, 1.584306e-002, - 1.589336e-002, 1.594036e-002, 1.598541e-002, 1.602722e-002, - 1.606434e-002, 1.609903e-002, 1.613283e-002, 1.616610e-002, - 1.619901e-002, 1.623229e-002, 1.626467e-002, 1.629456e-002, - 1.631907e-002, 1.633794e-002, 1.635046e-002, 1.635541e-002, - 1.635449e-002, 1.634876e-002, 1.633922e-002, 1.632753e-002, - 1.631524e-002, 1.629836e-002, 1.627631e-002, 1.625073e-002, - 1.622310e-002, 1.619299e-002, 1.616208e-002, 1.613029e-002, - 1.609781e-002, 1.606556e-002, 1.603184e-002, 1.599701e-002, - 1.596184e-002, 1.592570e-002, 1.589060e-002, 1.585592e-002, - 1.582230e-002, 1.579127e-002, 1.576421e-002, 1.574032e-002, - 1.571750e-002, 1.569755e-002, 1.567980e-002, 1.566457e-002, - 1.565092e-002, 1.563728e-002, 1.562261e-002, 1.560835e-002, - 1.559357e-002, 1.557705e-002, 1.556064e-002, 1.554346e-002, - 1.552706e-002, 1.550919e-002, 1.548602e-002, 1.545850e-002, - 1.542740e-002, 1.539396e-002, 1.535685e-002, 1.531549e-002, - 1.527369e-002, 1.523174e-002, 1.519097e-002, 1.515373e-002, - 1.511799e-002, 1.508243e-002, 1.504638e-002, 1.501094e-002, - 1.497452e-002, 1.493944e-002, 1.490661e-002, 1.487576e-002, - 1.484711e-002, 1.482622e-002, 1.480654e-002, 1.478655e-002, - 1.476559e-002, 1.474008e-002, 1.470736e-002, 1.466916e-002, - 1.462529e-002, 1.457629e-002, 1.452189e-002, 1.446931e-002, - 1.441358e-002, 1.435340e-002, 1.428936e-002, 1.422026e-002, - 1.414640e-002, 1.406896e-002, 1.398950e-002, 1.390865e-002, - 1.382658e-002, 1.374946e-002, 1.367392e-002, 1.359731e-002, - 1.352170e-002, 1.344518e-002, 1.336808e-002, 1.328958e-002, - 1.321204e-002, 1.313604e-002, 1.305930e-002, 1.298936e-002, - 1.292436e-002, 1.285846e-002, 1.279311e-002, 1.272439e-002, - 1.265292e-002, 1.258242e-002, 1.251166e-002, 1.243766e-002, - 1.236302e-002, 1.228948e-002, 1.222220e-002, 1.215235e-002, - 1.207619e-002, 1.199284e-002, 1.190767e-002, 1.182139e-002, - 1.173345e-002, 1.164387e-002, 1.155443e-002, 1.146493e-002, - 1.138295e-002, 1.129904e-002, 1.120925e-002, 1.111652e-002, - 1.102182e-002, 1.092648e-002, 1.082955e-002, 1.073332e-002, - 1.063740e-002, 1.054193e-002, 1.045484e-002, 1.036929e-002, - 1.028253e-002, 1.019515e-002, 1.010402e-002, 1.001275e-002, - 9.920484e-003, 9.826290e-003, 9.729964e-003, 9.630945e-003, - 9.536917e-003, 9.445100e-003, 9.351564e-003, 9.254625e-003, - 9.155807e-003, 9.053900e-003, 8.951083e-003, 8.847467e-003, - 8.738950e-003, 8.627321e-003, 8.514583e-003, 8.402696e-003, - 8.288394e-003, 8.173106e-003, 8.056380e-003, 7.939492e-003, - 7.823650e-003, 7.708244e-003, 7.591606e-003, 7.474110e-003, - 7.361471e-003, 7.262424e-003, 7.166043e-003, 7.071048e-003, - 6.978374e-003, 6.884690e-003, 6.788941e-003, 6.693594e-003, - 6.599997e-003, 6.507255e-003, 6.416697e-003, 6.338541e-003, - 6.265930e-003, 6.194026e-003, 6.121762e-003, 6.047146e-003, - 5.968673e-003, 5.884628e-003, 5.799016e-003, 5.713625e-003, - 5.626518e-003, 5.543415e-003, 5.462759e-003, 5.383004e-003, - 5.301757e-003, 5.221012e-003, 5.141119e-003, 5.060148e-003, - 4.979370e-003, 4.899344e-003, 4.817869e-003, 4.739570e-003, - 4.665764e-003, 4.591655e-003, 4.514425e-003, 4.436876e-003, - 4.359805e-003, 4.281968e-003, 4.202791e-003, 4.122960e-003, - 4.039086e-003, 3.956343e-003, 3.875757e-003, 3.797406e-003, - 3.719577e-003, 3.643680e-003, 3.568814e-003, 3.492859e-003, - 3.414581e-003, 3.338210e-003, 3.259886e-003, 3.181758e-003, - 3.106308e-003, 3.031204e-003, 2.954115e-003, 2.876947e-003, - 2.799848e-003, 2.723993e-003, 2.646426e-003, 2.566859e-003, - 2.484680e-003, 2.398644e-003, 2.306304e-003, 2.209953e-003, - 2.111415e-003, 2.010258e-003, 1.902428e-003, 1.791665e-003, - 1.677490e-003, 1.557822e-003, 1.432742e-003, 1.304630e-003, - 1.172295e-003, 1.035222e-003, 8.975401e-004, 7.590801e-004, - 6.216158e-004, 4.844702e-004, 3.450865e-004, 2.066534e-004, - 6.913420e-005, -6.725644e-005, -2.057265e-004, -3.467687e-004, - -4.887526e-004, -6.348563e-004, -7.905714e-004, -9.669508e-004, - -1.164926e-003, -1.381963e-003, -1.619638e-003, -1.874832e-003, - -2.148495e-003, -2.444814e-003, -2.756516e-003, -3.084647e-003, - -3.425024e-003, -3.773993e-003, -4.134545e-003, -4.507166e-003, - -4.886846e-003, -5.270575e-003, -5.657613e-003, -6.055782e-003, - -6.456531e-003, -6.859689e-003, -7.263682e-003, -7.664792e-003, - -8.058252e-003, -8.446927e-003, -8.829377e-003, -9.207939e-003, - -9.576628e-003, -9.946104e-003, -1.030922e-002, -1.066142e-002, - -1.100057e-002, -1.132720e-002, -1.164203e-002, -1.194423e-002, - -1.223340e-002, -1.250574e-002, -1.276045e-002, -1.300292e-002, - -1.323872e-002, -1.345882e-002, -1.366432e-002, -1.386224e-002, - -1.405922e-002, -1.425492e-002, -1.444750e-002, -1.463415e-002, - -1.481692e-002, -1.499512e-002, -1.517985e-002, -1.535965e-002, - -1.553151e-002, -1.569829e-002, -1.586190e-002, -1.601993e-002, - -1.616780e-002, -1.631217e-002, -1.645177e-002, -1.658583e-002, - -1.672366e-002, -1.685881e-002, -1.698234e-002, -1.709916e-002, - -1.720852e-002, -1.731054e-002, -1.739895e-002, -1.748040e-002, - -1.755821e-002, -1.763391e-002, -1.771765e-002, -1.780179e-002, - -1.788008e-002, -1.795728e-002, -1.802917e-002, -1.809748e-002, - -1.815996e-002, -1.821714e-002, -1.827226e-002, -1.832845e-002, - -1.838574e-002, -1.844509e-002, -1.850239e-002, -1.856038e-002, - -1.861472e-002, -1.866709e-002, -1.871958e-002, -1.876557e-002, - -1.880767e-002, -1.884718e-002, -1.888664e-002, -1.893083e-002, - -1.897529e-002, -1.901423e-002, -1.904504e-002, -1.907092e-002, - -1.909192e-002, -1.911067e-002, -1.912440e-002, -1.913393e-002, - -1.914382e-002, -1.915449e-002, -1.916195e-002, -1.916885e-002, - -1.917660e-002, -1.919077e-002, -1.920920e-002, -1.922931e-002, - -1.924778e-002, -1.926894e-002, -1.928924e-002, -1.931301e-002, - -1.934173e-002, -1.937471e-002, -1.940587e-002, -1.943797e-002, - -1.947026e-002, -1.950912e-002, -1.954936e-002, -1.958761e-002, - -1.962894e-002, -1.967594e-002, -1.972659e-002, -1.978702e-002, - -1.985216e-002, -1.992150e-002, -1.999448e-002, -2.007210e-002, - -2.014773e-002, -2.022535e-002, -2.030565e-002, -2.038502e-002, - -2.046522e-002, -2.054659e-002, -2.062929e-002, -2.071136e-002, - -2.079538e-002, -2.088257e-002, -2.097007e-002, -2.106025e-002, - -2.115169e-002, -2.123780e-002, -2.131658e-002, -2.139560e-002, - -2.147718e-002, -2.155657e-002, -2.163601e-002, -2.171915e-002, - -2.180377e-002, -2.188823e-002, -2.197066e-002, -2.205428e-002, - -2.213598e-002, -2.222287e-002, -2.230381e-002, -2.238141e-002, - -2.245443e-002, -2.252323e-002, -2.258872e-002, -2.264542e-002, - -2.270040e-002, -2.275400e-002, -2.279750e-002, -2.283179e-002, - -2.285570e-002, -2.287307e-002, -2.288655e-002, -2.289254e-002, - -2.289322e-002, -2.288004e-002, -2.286161e-002, -2.283576e-002, - -2.279586e-002, -2.273507e-002, -2.266675e-002, -2.259296e-002, - -2.251842e-002, -2.244123e-002, -2.235947e-002, -2.227163e-002, - -2.218445e-002, -2.209385e-002, -2.199492e-002, -2.187452e-002, - -2.174994e-002, -2.162391e-002, -2.148816e-002, -2.134779e-002, - -2.120915e-002, -2.107143e-002, -2.093258e-002, -2.078652e-002, - -2.063582e-002, -2.045986e-002, -2.027293e-002, -2.008454e-002, - -1.989405e-002, -1.969086e-002, -1.947059e-002, -1.923131e-002, - -1.897325e-002, -1.870094e-002, -1.841641e-002, -1.811045e-002, - -1.778220e-002, -1.744522e-002, -1.709403e-002, -1.672489e-002, - -1.634271e-002, -1.595194e-002, -1.555536e-002, -1.514776e-002, - -1.473221e-002, -1.430512e-002, -1.386335e-002, -1.341936e-002, - -1.297073e-002, -1.251919e-002, -1.206132e-002, -1.160160e-002, - -1.114290e-002, -1.068782e-002, -1.022132e-002, -9.757470e-003, - -9.285987e-003, -8.808177e-003, -8.330368e-003, -7.853724e-003, - -7.374809e-003, -6.894174e-003, -6.408915e-003, -5.914210e-003, - -5.421145e-003, -4.930913e-003, -4.443133e-003, -3.952020e-003, - -3.467807e-003, -3.003789e-003, -2.534258e-003, -2.063009e-003, - -1.587187e-003, -1.105597e-003, -6.145124e-004, -1.195783e-004, - 3.786283e-004, 8.472878e-004, 1.313937e-003, 1.792348e-003, - 2.281533e-003, 2.785763e-003, 3.282833e-003, 3.786963e-003, - 4.292176e-003, 4.799104e-003, 5.288551e-003, 5.780731e-003, - 6.285562e-003, 6.799689e-003, 7.326510e-003, 7.855405e-003, - 8.389570e-003, 8.933646e-003, 9.487226e-003, 1.004485e-002, - 1.059483e-002, 1.112622e-002, 1.166886e-002, 1.222207e-002, - 1.277386e-002, 1.332494e-002, 1.388038e-002, 1.443527e-002, - 1.499523e-002, 1.554618e-002, 1.610172e-002, 1.660544e-002, - 1.711204e-002, 1.761668e-002, 1.812841e-002, 1.864169e-002, - 1.916642e-002, 1.969251e-002, 2.022161e-002, 2.074220e-002, - 2.127061e-002, 2.174093e-002, 2.221935e-002, 2.271000e-002, - 2.319654e-002, 2.368338e-002, 2.416340e-002, 2.465537e-002, - 2.515596e-002, 2.566187e-002, 2.615421e-002, 2.659238e-002, - 2.699884e-002, 2.741717e-002, 2.784145e-002, 2.827092e-002, - 2.871013e-002, 2.915557e-002, 2.960184e-002, 3.006013e-002, - 3.051912e-002, 3.093138e-002, 3.126677e-002, 3.160425e-002, - 3.196362e-002, 3.234064e-002, 3.271900e-002, 3.309697e-002, - 3.346249e-002, 3.382050e-002, 3.419264e-002, 3.455212e-002, - 3.479399e-002, 3.502965e-002, 3.525196e-002, 3.548711e-002, - 3.572370e-002, 3.596105e-002, 3.620329e-002, 3.645614e-002, - 3.671814e-002, 3.697862e-002, 3.709290e-002, 3.720348e-002, - 3.732277e-002, 3.743414e-002, 3.755795e-002, 3.768074e-002, - 3.780413e-002, 3.792862e-002, 3.804353e-002, 3.817294e-002, - 3.818591e-002, 3.814615e-002, 3.809726e-002, 3.804761e-002, - 3.800844e-002, 3.797241e-002, 3.792083e-002, 3.785795e-002, - 3.779673e-002, 3.774091e-002, 3.759108e-002, 3.732045e-002, - 3.704199e-002, 3.677636e-002, 3.649953e-002, 3.621698e-002, - 3.593742e-002, 3.565432e-002, 3.537721e-002, 3.510028e-002, - 3.479529e-002, 3.430971e-002, 3.382525e-002, 3.335219e-002, - 3.290044e-002, 3.243309e-002, 3.195748e-002, 3.150535e-002, - 3.105769e-002, 3.060717e-002, 3.015091e-002, 2.949569e-002, - 2.884514e-002, 2.818003e-002, 2.752823e-002, 2.689935e-002, - 2.624807e-002, 2.558363e-002, 2.491307e-002, 2.423669e-002, - 2.357128e-002, 2.275494e-002, 2.186031e-002, 2.095985e-002, - 2.005913e-002, 1.916446e-002, 1.824767e-002, 1.730871e-002, - 1.635408e-002, 1.536332e-002, 1.436585e-002, 1.326096e-002, - 1.203139e-002, 1.078647e-002, 9.543089e-003, 8.317263e-003, - 7.048353e-003, 5.775684e-003, 4.509309e-003, 3.229642e-003, - 1.969391e-003, 6.297964e-004, -8.156995e-004, -2.259355e-003, - -3.698178e-003, -5.176835e-003, -6.670660e-003, -8.171561e-003, - -9.643531e-003, -1.116153e-002, -1.272143e-002, -1.426735e-002, - -1.590160e-002, -1.758423e-002, -1.924605e-002, -2.090828e-002, - -2.257547e-002, -2.424289e-002, -2.590629e-002, -2.753986e-002, - -2.917480e-002, -3.087626e-002, -3.255204e-002, -3.421043e-002, - -3.588567e-002, -3.755808e-002, -3.924687e-002, -4.091323e-002, - -4.253313e-002, -4.418032e-002, -4.584658e-002, -4.756897e-002, - -4.920846e-002, -5.077447e-002, -5.234610e-002, -5.381682e-002, - -5.526424e-002, -5.663091e-002, -5.797222e-002, -5.930607e-002, - -6.066814e-002, -6.208514e-002, -6.343403e-002, -6.449715e-002, - -6.556007e-002, -6.657406e-002, -6.762655e-002, -6.869886e-002, - -6.975114e-002, -7.077011e-002, -7.169135e-002, -7.257124e-002, - -7.322136e-002, -7.314771e-002, -7.311838e-002, -7.300001e-002, - -7.266621e-002, -7.217943e-002, -7.125657e-002, -7.000705e-002, - -6.839464e-002, -6.702601e-002, -6.565046e-002, -6.372023e-002, - -6.185220e-002, -5.942380e-002, -5.690567e-002, -5.426280e-002, - -5.154318e-002, -4.866927e-002, -4.523848e-002, -3.984226e-002, - -2.892381e-002, -2.036873e-002, -1.322092e-002, -6.047445e-003, - 4.023240e-004, 6.063601e-003, 1.163768e-002, 1.739665e-002, - 2.380727e-002, 3.138978e-002, 3.860991e-002, 1.101943e-001, - 1.182915e-001, 1.205385e-001, 1.208058e-001, 1.195768e-001, - 1.187807e-001, 1.185533e-001, 1.183375e-001, 1.183317e-001, - 1.199347e-001, 1.690672e-001, 2.689890e-001, 2.707646e-001, - 2.683774e-001, 2.623614e-001, 2.456289e-001, 1.179993e-001, - 8.066072e-002, 4.828174e-002, 2.318181e-002, 0.000000e+000 - }, - { - 0.000000e+000, -5.948584e-003, -1.046028e-002, -1.412192e-002, - -1.718260e-002, -1.981969e-002, -2.210697e-002, -2.395837e-002, - -2.550575e-002, -2.656707e-002, -2.756658e-002, -2.857295e-002, - -2.943111e-002, -3.020161e-002, -3.092388e-002, -3.163091e-002, - -3.235862e-002, -3.313731e-002, -3.398320e-002, -3.482197e-002, - -3.563929e-002, -3.639301e-002, -3.708183e-002, -3.774827e-002, - -3.839240e-002, -3.900497e-002, -3.958263e-002, -4.012135e-002, - -4.062204e-002, -4.108001e-002, -4.149873e-002, -4.186575e-002, - -4.215703e-002, -4.242737e-002, -4.267827e-002, -4.291057e-002, - -4.312675e-002, -4.332709e-002, -4.351663e-002, -4.370100e-002, - -4.387662e-002, -4.402898e-002, -4.408344e-002, -4.410699e-002, - -4.408507e-002, -4.400904e-002, -4.375791e-002, -4.312853e-002, - -4.252821e-002, -4.235124e-002, -4.231945e-002, -4.230396e-002, - -4.224461e-002, -4.218807e-002, -4.214307e-002, -4.210902e-002, - -4.208484e-002, -4.206985e-002, -4.205669e-002, -4.204964e-002, - -4.204478e-002, -4.204052e-002, -4.199539e-002, -4.192053e-002, - -4.184521e-002, -4.176766e-002, -4.168334e-002, -4.158970e-002, - -4.148186e-002, -4.136096e-002, -4.122021e-002, -4.106306e-002, - -4.087456e-002, -4.064578e-002, -4.040580e-002, -4.015549e-002, - -3.989355e-002, -3.961990e-002, -3.933759e-002, -3.905504e-002, - -3.877712e-002, -3.851058e-002, -3.824416e-002, -3.794630e-002, - -3.765646e-002, -3.737032e-002, -3.708395e-002, -3.679851e-002, - -3.651559e-002, -3.621877e-002, -3.590223e-002, -3.556911e-002, - -3.522037e-002, -3.482059e-002, -3.440499e-002, -3.397425e-002, - -3.353103e-002, -3.307909e-002, -3.262169e-002, -3.215940e-002, - -3.169289e-002, -3.122540e-002, -3.075826e-002, -3.027230e-002, - -2.978381e-002, -2.930554e-002, -2.884168e-002, -2.838044e-002, - -2.791220e-002, -2.743917e-002, -2.696029e-002, -2.647392e-002, - -2.598328e-002, -2.547690e-002, -2.495366e-002, -2.443086e-002, - -2.390635e-002, -2.337981e-002, -2.285216e-002, -2.232433e-002, - -2.179606e-002, -2.127013e-002, -2.075020e-002, -2.023887e-002, - -1.973521e-002, -1.925105e-002, -1.878562e-002, -1.833284e-002, - -1.788865e-002, -1.745424e-002, -1.702593e-002, -1.660021e-002, - -1.617719e-002, -1.575667e-002, -1.532677e-002, -1.489798e-002, - -1.446973e-002, -1.403917e-002, -1.360597e-002, -1.317021e-002, - -1.272858e-002, -1.228229e-002, -1.183464e-002, -1.138420e-002, - -1.092678e-002, -1.046724e-002, -1.000966e-002, -9.554884e-003, - -9.105796e-003, -8.660513e-003, -8.220711e-003, -7.785510e-003, - -7.356108e-003, -6.932939e-003, -6.515922e-003, -6.104922e-003, - -5.698882e-003, -5.303549e-003, -4.915248e-003, -4.536020e-003, - -4.162792e-003, -3.795156e-003, -3.432018e-003, -3.075013e-003, - -2.721552e-003, -2.372963e-003, -2.024064e-003, -1.671348e-003, - -1.311859e-003, -9.457630e-004, -5.768674e-004, -2.083540e-004, - 1.585965e-004, 5.251824e-004, 8.888624e-004, 1.246487e-003, - 1.608064e-003, 1.974160e-003, 2.342099e-003, 2.712027e-003, - 3.081043e-003, 3.445252e-003, 3.799746e-003, 4.139995e-003, - 4.467597e-003, 4.776563e-003, 5.077320e-003, 5.373030e-003, - 5.663168e-003, 5.949986e-003, 6.234074e-003, 6.515621e-003, - 6.797121e-003, 7.078203e-003, 7.360271e-003, 7.632729e-003, - 7.901880e-003, 8.173229e-003, 8.447280e-003, 8.725165e-003, - 9.006207e-003, 9.291544e-003, 9.581457e-003, 9.874916e-003, - 1.017250e-002, 1.046641e-002, 1.076046e-002, 1.106547e-002, - 1.138228e-002, 1.170738e-002, 1.203445e-002, 1.236181e-002, - 1.268655e-002, 1.300964e-002, 1.333124e-002, 1.365057e-002, - 1.395585e-002, 1.426371e-002, 1.457022e-002, 1.487466e-002, - 1.517438e-002, 1.546641e-002, 1.574707e-002, 1.601292e-002, - 1.626505e-002, 1.650610e-002, 1.672061e-002, 1.692630e-002, - 1.712683e-002, 1.732448e-002, 1.751945e-002, 1.771236e-002, - 1.790203e-002, 1.808812e-002, 1.827063e-002, 1.845030e-002, - 1.861054e-002, 1.876441e-002, 1.891985e-002, 1.907753e-002, - 1.923785e-002, 1.940077e-002, 1.956861e-002, 1.974178e-002, - 1.991933e-002, 2.010331e-002, 2.028513e-002, 2.046811e-002, - 2.066714e-002, 2.088008e-002, 2.109807e-002, 2.131675e-002, - 2.153312e-002, 2.174281e-002, 2.194993e-002, 2.215501e-002, - 2.235024e-002, 2.252842e-002, 2.270546e-002, 2.287972e-002, - 2.304994e-002, 2.321695e-002, 2.337129e-002, 2.352155e-002, - 2.366657e-002, 2.380986e-002, 2.395282e-002, 2.407193e-002, - 2.419302e-002, 2.431763e-002, 2.444566e-002, 2.458104e-002, - 2.472854e-002, 2.488952e-002, 2.506544e-002, 2.525627e-002, - 2.546600e-002, 2.567049e-002, 2.588437e-002, 2.610956e-002, - 2.634315e-002, 2.659193e-002, 2.685971e-002, 2.714631e-002, - 2.745098e-002, 2.777483e-002, 2.811512e-002, 2.845346e-002, - 2.878696e-002, 2.912168e-002, 2.945281e-002, 2.977797e-002, - 3.010048e-002, 3.042120e-002, 3.074058e-002, 3.106231e-002, - 3.138533e-002, 3.169710e-002, 3.198677e-002, 3.227495e-002, - 3.256339e-002, 3.285098e-002, 3.313948e-002, 3.342794e-002, - 3.371656e-002, 3.400471e-002, 3.428926e-002, 3.456718e-002, - 3.481441e-002, 3.505788e-002, 3.529949e-002, 3.553841e-002, - 3.577588e-002, 3.601093e-002, 3.624765e-002, 3.648528e-002, - 3.671898e-002, 3.694927e-002, 3.714690e-002, 3.733192e-002, - 3.750582e-002, 3.766425e-002, 3.780644e-002, 3.793386e-002, - 3.804862e-002, 3.814937e-002, 3.823731e-002, 3.831114e-002, - 3.834863e-002, 3.835488e-002, 3.833633e-002, 3.829914e-002, - 3.824622e-002, 3.818108e-002, 3.810190e-002, 3.800849e-002, - 3.790841e-002, 3.780139e-002, 3.767555e-002, 3.752722e-002, - 3.737696e-002, 3.722564e-002, 3.707149e-002, 3.691774e-002, - 3.676275e-002, 3.661098e-002, 3.646524e-002, 3.632604e-002, - 3.618752e-002, 3.603051e-002, 3.587972e-002, 3.573158e-002, - 3.558121e-002, 3.541856e-002, 3.523896e-002, 3.504661e-002, - 3.484020e-002, 3.462295e-002, 3.439420e-002, 3.412198e-002, - 3.384067e-002, 3.355145e-002, 3.325646e-002, 3.295335e-002, - 3.264685e-002, 3.233845e-002, 3.203041e-002, 3.171997e-002, - 3.140753e-002, 3.106842e-002, 3.071718e-002, 3.036373e-002, - 3.000925e-002, 2.965086e-002, 2.929255e-002, 2.893394e-002, - 2.857646e-002, 2.822257e-002, 2.786956e-002, 2.750272e-002, - 2.712157e-002, 2.674161e-002, 2.636277e-002, 2.598194e-002, - 2.560004e-002, 2.522268e-002, 2.484980e-002, 2.447667e-002, - 2.410281e-002, 2.371642e-002, 2.329970e-002, 2.287848e-002, - 2.244893e-002, 2.201228e-002, 2.157542e-002, 2.113880e-002, - 2.070140e-002, 2.026492e-002, 1.983083e-002, 1.939684e-002, - 1.893046e-002, 1.846613e-002, 1.800138e-002, 1.753855e-002, - 1.707984e-002, 1.662469e-002, 1.617480e-002, 1.573530e-002, - 1.530541e-002, 1.488438e-002, 1.444704e-002, 1.401517e-002, - 1.359411e-002, 1.318446e-002, 1.278145e-002, 1.238534e-002, - 1.199644e-002, 1.161269e-002, 1.123216e-002, 1.085487e-002, - 1.046048e-002, 1.005207e-002, 9.644108e-003, 9.235605e-003, - 8.827724e-003, 8.419217e-003, 8.013053e-003, 7.607237e-003, - 7.196789e-003, 6.782582e-003, 6.355131e-003, 5.902252e-003, - 5.449632e-003, 4.998081e-003, 4.546524e-003, 4.098905e-003, - 3.658531e-003, 3.225129e-003, 2.795811e-003, 2.371259e-003, - 1.957602e-003, 1.531842e-003, 1.120777e-003, 7.205106e-004, - 3.283913e-004, -5.933045e-005, -4.439777e-004, -8.223615e-004, - -1.194654e-003, -1.559656e-003, -1.917887e-003, -2.289807e-003, - -2.656050e-003, -3.019412e-003, -3.384292e-003, -3.754604e-003, - -4.126454e-003, -4.498876e-003, -4.871506e-003, -5.238074e-003, - -5.599641e-003, -5.970859e-003, -6.344308e-003, -6.711346e-003, - -7.074197e-003, -7.432486e-003, -7.782779e-003, -8.126682e-003, - -8.464074e-003, -8.795215e-003, -9.119613e-003, -9.446921e-003, - -9.781706e-003, -1.011206e-002, -1.043890e-002, -1.076276e-002, - -1.108266e-002, -1.140055e-002, -1.171700e-002, -1.203250e-002, - -1.234679e-002, -1.265958e-002, -1.298452e-002, -1.330717e-002, - -1.362826e-002, -1.394375e-002, -1.425439e-002, -1.455847e-002, - -1.485700e-002, -1.514793e-002, -1.543700e-002, -1.572596e-002, - -1.602852e-002, -1.633093e-002, -1.663258e-002, -1.693060e-002, - -1.722624e-002, -1.752022e-002, -1.781593e-002, -1.811509e-002, - -1.841830e-002, -1.873025e-002, -1.906276e-002, -1.940616e-002, - -1.975771e-002, -2.011914e-002, -2.049229e-002, -2.087419e-002, - -2.126528e-002, -2.166763e-002, -2.207789e-002, -2.249161e-002, - -2.291329e-002, -2.334089e-002, -2.376481e-002, -2.418249e-002, - -2.459265e-002, -2.499479e-002, -2.538694e-002, -2.576434e-002, - -2.612284e-002, -2.646375e-002, -2.679377e-002, -2.712256e-002, - -2.744789e-002, -2.776918e-002, -2.806224e-002, -2.829575e-002, - -2.847228e-002, -2.859076e-002, -2.865676e-002, -2.867214e-002, - -2.864107e-002, -2.857640e-002, -2.847552e-002, -2.834078e-002, - -2.817526e-002, -2.797742e-002, -2.774997e-002, -2.749482e-002, - -2.721815e-002, -2.691966e-002, -2.659914e-002, -2.626871e-002, - -2.592373e-002, -2.556482e-002, -2.519327e-002, -2.480871e-002, - -2.441472e-002, -2.401092e-002, -2.360332e-002, -2.319756e-002, - -2.279512e-002, -2.241146e-002, -2.204302e-002, -2.169290e-002, - -2.136524e-002, -2.106278e-002, -2.078899e-002, -2.054502e-002, - -2.033929e-002, -2.017546e-002, -2.005890e-002, -1.999876e-002, - -2.001068e-002, -2.008933e-002, -2.019889e-002, -2.031759e-002, - -2.044829e-002, -2.058973e-002, -2.074222e-002, -2.090446e-002, - -2.107718e-002, -2.125894e-002, -2.145787e-002, -2.166500e-002, - -2.187264e-002, -2.208520e-002, -2.230130e-002, -2.252226e-002, - -2.274373e-002, -2.296947e-002, -2.319341e-002, -2.341791e-002, - -2.365163e-002, -2.388496e-002, -2.410825e-002, -2.432480e-002, - -2.453164e-002, -2.472465e-002, -2.490171e-002, -2.507223e-002, - -2.524614e-002, -2.542661e-002, -2.562317e-002, -2.582726e-002, - -2.603106e-002, -2.623738e-002, -2.644263e-002, -2.664505e-002, - -2.684109e-002, -2.703369e-002, -2.722865e-002, -2.743115e-002, - -2.764978e-002, -2.788030e-002, -2.810709e-002, -2.833495e-002, - -2.856174e-002, -2.878308e-002, -2.900204e-002, -2.921894e-002, - -2.943169e-002, -2.963449e-002, -2.983521e-002, -3.003728e-002, - -3.023066e-002, -3.040947e-002, -3.057211e-002, -3.072048e-002, - -3.085741e-002, -3.098051e-002, -3.108812e-002, -3.117851e-002, - -3.126310e-002, -3.138829e-002, -3.153534e-002, -3.170706e-002, - -3.189900e-002, -3.211329e-002, -3.234613e-002, -3.259687e-002, - -3.286321e-002, -3.314521e-002, -3.343420e-002, -3.375310e-002, - -3.408381e-002, -3.442094e-002, -3.475789e-002, -3.509398e-002, - -3.542514e-002, -3.574842e-002, -3.605705e-002, -3.635363e-002, - -3.663700e-002, -3.692114e-002, -3.720193e-002, -3.746059e-002, - -3.769447e-002, -3.790588e-002, -3.809212e-002, -3.824652e-002, - -3.836200e-002, -3.844385e-002, -3.849019e-002, -3.850980e-002, - -3.852192e-002, -3.849075e-002, -3.841692e-002, -3.830307e-002, - -3.814343e-002, -3.793830e-002, -3.768983e-002, -3.739737e-002, - -3.705420e-002, -3.667035e-002, -3.631146e-002, -3.593032e-002, - -3.553103e-002, -3.511230e-002, -3.468110e-002, -3.425017e-002, - -3.381660e-002, -3.338288e-002, -3.293831e-002, -3.248550e-002, - -3.207288e-002, -3.166134e-002, -3.123436e-002, -3.079668e-002, - -3.034961e-002, -2.989507e-002, -2.942900e-002, -2.895256e-002, - -2.846753e-002, -2.797019e-002, -2.749438e-002, -2.703390e-002, - -2.656510e-002, -2.608691e-002, -2.560159e-002, -2.510515e-002, - -2.459696e-002, -2.407510e-002, -2.354001e-002, -2.298263e-002, - -2.244101e-002, -2.193445e-002, -2.141711e-002, -2.087392e-002, - -2.030779e-002, -1.972251e-002, -1.911495e-002, -1.848676e-002, - -1.783762e-002, -1.716135e-002, -1.646670e-002, -1.582304e-002, - -1.516356e-002, -1.448692e-002, -1.379425e-002, -1.308478e-002, - -1.235917e-002, -1.162134e-002, -1.086739e-002, -1.008393e-002, - -9.275987e-003, -8.527802e-003, -7.768778e-003, -6.981132e-003, - -6.119956e-003, -5.120459e-003, -3.990800e-003, -2.741776e-003, - -1.376983e-003, 9.828532e-005, 1.676223e-003, 3.280892e-003, - 4.937018e-003, 6.684108e-003, 8.512696e-003, 1.041702e-002, - 1.238370e-002, 1.440147e-002, 1.646402e-002, 1.856965e-002, - 2.070603e-002, 2.282164e-002, 2.489381e-002, 2.697639e-002, - 2.906437e-002, 3.115039e-002, 3.322969e-002, 3.529430e-002, - 3.733764e-002, 3.935526e-002, 4.134601e-002, 4.327241e-002, - 4.506649e-002, 4.681959e-002, 4.852420e-002, 5.016666e-002, - 5.174376e-002, 5.325207e-002, 5.469578e-002, 5.607293e-002, - 5.736846e-002, 5.856041e-002, 5.951937e-002, 6.036706e-002, - 6.109094e-002, 6.168262e-002, 6.216786e-002, 6.262349e-002, - 6.305866e-002, 6.347619e-002, 6.386848e-002, 6.423255e-002, - 6.447476e-002, 6.465171e-002, 6.479041e-002, 6.490509e-002, - 6.499736e-002, 6.507549e-002, 6.513269e-002, 6.517093e-002, - 6.518208e-002, 6.518070e-002, 6.508500e-002, 6.490283e-002, - 6.471472e-002, 6.450784e-002, 6.430075e-002, 6.408874e-002, - 6.387332e-002, 6.365391e-002, 6.343041e-002, 6.319381e-002, - 6.290044e-002, 6.250051e-002, 6.209105e-002, 6.166723e-002, - 6.122417e-002, 6.076333e-002, 6.027323e-002, 5.975602e-002, - 5.921839e-002, 5.864549e-002, 5.804034e-002, 5.727047e-002, - 5.648582e-002, 5.568698e-002, 5.487773e-002, 5.405462e-002, - 5.320805e-002, 5.234901e-002, 5.147712e-002, 5.057954e-002, - 4.967115e-002, 4.863255e-002, 4.756180e-002, 4.648999e-002, - 4.541126e-002, 4.433167e-002, 4.325763e-002, 4.218398e-002, - 4.111786e-002, 4.003836e-002, 3.894626e-002, 3.777685e-002, - 3.656353e-002, 3.535622e-002, 3.416935e-002, 3.299730e-002, - 3.184616e-002, 3.070309e-002, 2.956442e-002, 2.842832e-002, - 2.729558e-002, 2.610155e-002, 2.482173e-002, 2.355134e-002, - 2.229819e-002, 2.106438e-002, 1.983652e-002, 1.861767e-002, - 1.741414e-002, 1.621598e-002, 1.501925e-002, 1.380812e-002, - 1.246018e-002, 1.111967e-002, 9.788785e-003, 8.473901e-003, - 7.177705e-003, 5.906202e-003, 4.654812e-003, 3.432508e-003, - 2.229914e-003, 1.064813e-003, -2.173012e-004, -1.478580e-003, - -2.721705e-003, -3.953264e-003, -5.148483e-003, -6.304037e-003, - -7.412129e-003, -8.498079e-003, -9.550709e-003, -1.056701e-002, - -1.167472e-002, -1.281602e-002, -1.393330e-002, -1.502508e-002, - -1.611261e-002, -1.718486e-002, -1.824908e-002, -1.931062e-002, - -2.034952e-002, -2.136453e-002, -2.243274e-002, -2.358288e-002, - -2.470462e-002, -2.578893e-002, -2.682955e-002, -2.784701e-002, - -2.884065e-002, -2.979270e-002, -3.071127e-002, -3.158953e-002, - -3.247666e-002, -3.350379e-002, -3.448654e-002, -3.542902e-002, - -3.633891e-002, -3.720409e-002, -3.802047e-002, -3.879421e-002, - -3.952199e-002, -4.019919e-002, -4.082141e-002, -4.161062e-002, - -4.235605e-002, -4.304160e-002, -4.367656e-002, -4.425568e-002, - -4.479237e-002, -4.529534e-002, -4.573724e-002, -4.614122e-002, - -4.650078e-002, -4.698633e-002, -4.747143e-002, -4.786270e-002, - -4.816596e-002, -4.837634e-002, -4.848212e-002, -4.848778e-002, - -4.841266e-002, -4.824191e-002, -4.798326e-002, -4.779639e-002, - -4.765645e-002, -4.744163e-002, -4.717161e-002, -4.682562e-002, - -4.638332e-002, -4.586779e-002, -4.527898e-002, -4.460445e-002, - -4.384995e-002, -4.310654e-002, -4.252824e-002, -4.187401e-002, - -4.113536e-002, -4.034341e-002, -3.947574e-002, -3.853392e-002, - -3.754277e-002, -3.646945e-002, -3.531654e-002, -3.412551e-002, - -3.326706e-002, -3.233921e-002, -3.132969e-002, -3.016532e-002, - -2.885093e-002, -2.735353e-002, -2.568485e-002, -2.387733e-002, - -2.191919e-002, -1.981274e-002, -1.802727e-002, -1.619401e-002, - -1.423960e-002, -1.218404e-002, -1.003874e-002, -7.781791e-003, - -5.413668e-003, -2.935296e-003, -3.712999e-004, 2.247255e-003, - 4.527139e-003, 6.577857e-003, 8.691127e-003, 1.083851e-002, - 1.300684e-002, 1.518670e-002, 1.743227e-002, 1.972860e-002, - 2.209641e-002, 2.454579e-002, 2.675521e-002, 2.845350e-002, - 3.019673e-002, 3.192092e-002, 3.367314e-002, 3.546429e-002, - 3.724262e-002, 3.899910e-002, 4.075613e-002, 4.251294e-002, - 4.411621e-002, 4.480295e-002, 4.547760e-002, 4.607877e-002, - 4.661217e-002, 4.708334e-002, 4.740441e-002, 4.760143e-002, - 4.766157e-002, 4.777167e-002, 4.793095e-002, 4.711552e-002, - 4.626246e-002, 4.529052e-002, 4.434331e-002, 4.344736e-002, - 4.266091e-002, 4.181582e-002, 4.080910e-002, 3.921881e-002, - 3.583952e-002, 3.259613e-002, 2.956175e-002, 2.651886e-002, - 2.372247e-002, 2.105007e-002, 1.829475e-002, 1.532199e-002, - 1.216969e-002, 8.674116e-003, 5.452616e-003, -1.948443e-002, - -2.423340e-002, -2.695359e-002, -2.894229e-002, -3.018456e-002, - -3.128056e-002, -3.235162e-002, -3.319580e-002, -3.395398e-002, - -3.527155e-002, -5.551143e-002, -8.822319e-002, -8.978125e-002, - -9.050980e-002, -8.976921e-002, -8.507800e-002, -4.157453e-002, - -2.872418e-002, -1.732739e-002, -8.396579e-003, 0.000000e+000 - }, - { - 0.000000e+000, -5.609543e-003, -1.020292e-002, -1.400415e-002, - -1.719518e-002, -1.986023e-002, -2.209632e-002, -2.411757e-002, - -2.583390e-002, -2.697271e-002, -2.767187e-002, -2.820270e-002, - -2.873208e-002, -2.917226e-002, -2.953901e-002, -2.985445e-002, - -3.014326e-002, -3.042552e-002, -3.071728e-002, -3.096373e-002, - -3.115826e-002, -3.137235e-002, -3.158948e-002, -3.175253e-002, - -3.186987e-002, -3.195711e-002, -3.201454e-002, -3.205246e-002, - -3.206618e-002, -3.204799e-002, -3.199958e-002, -3.195769e-002, - -3.195992e-002, -3.194288e-002, -3.190385e-002, -3.184448e-002, - -3.176524e-002, -3.166172e-002, -3.153478e-002, -3.137627e-002, - -3.119349e-002, -3.099761e-002, -3.085576e-002, -3.072802e-002, - -3.062967e-002, -3.057547e-002, -3.071938e-002, -3.132002e-002, - -3.187077e-002, -3.187862e-002, -3.169455e-002, -3.148052e-002, - -3.128429e-002, -3.107780e-002, -3.086201e-002, -3.063436e-002, - -3.039270e-002, -3.013730e-002, -2.986765e-002, -2.958591e-002, - -2.929295e-002, -2.899273e-002, -2.870948e-002, -2.843268e-002, - -2.815173e-002, -2.786922e-002, -2.758173e-002, -2.729196e-002, - -2.700176e-002, -2.670976e-002, -2.641753e-002, -2.612207e-002, - -2.582397e-002, -2.552362e-002, -2.520222e-002, -2.484961e-002, - -2.447161e-002, -2.406098e-002, -2.362527e-002, -2.316570e-002, - -2.268948e-002, -2.218656e-002, -2.165324e-002, -2.111701e-002, - -2.055203e-002, -1.995805e-002, -1.933752e-002, -1.869388e-002, - -1.803501e-002, -1.735155e-002, -1.664619e-002, -1.592049e-002, - -1.518314e-002, -1.446989e-002, -1.375316e-002, -1.303381e-002, - -1.231701e-002, -1.160900e-002, -1.091711e-002, -1.024353e-002, - -9.595658e-003, -8.974928e-003, -8.387855e-003, -7.867135e-003, - -7.382747e-003, -6.916496e-003, -6.467991e-003, -6.030855e-003, - -5.597068e-003, -5.168507e-003, -4.741847e-003, -4.316002e-003, - -3.894435e-003, -3.495257e-003, -3.124098e-003, -2.757691e-003, - -2.390975e-003, -2.028343e-003, -1.666036e-003, -1.305747e-003, - -9.489322e-004, -5.927505e-004, -2.422770e-004, 8.750156e-005, - 3.706612e-004, 6.411290e-004, 9.020341e-004, 1.155620e-003, - 1.401542e-003, 1.643038e-003, 1.879963e-003, 2.113900e-003, - 2.347571e-003, 2.577458e-003, 2.756647e-003, 2.931890e-003, - 3.105964e-003, 3.278941e-003, 3.453383e-003, 3.629719e-003, - 3.808411e-003, 3.988230e-003, 4.169038e-003, 4.350300e-003, - 4.498474e-003, 4.636026e-003, 4.770906e-003, 4.905064e-003, - 5.036287e-003, 5.162918e-003, 5.286399e-003, 5.409335e-003, - 5.529413e-003, 5.644914e-003, 5.733475e-003, 5.800998e-003, - 5.866385e-003, 5.924919e-003, 5.981370e-003, 6.035549e-003, - 6.088237e-003, 6.139955e-003, 6.192982e-003, 6.246664e-003, - 6.288008e-003, 6.301096e-003, 6.321934e-003, 6.350560e-003, - 6.390177e-003, 6.440209e-003, 6.500632e-003, 6.568284e-003, - 6.643021e-003, 6.723242e-003, 6.806121e-003, 6.859700e-003, - 6.924152e-003, 6.999026e-003, 7.084535e-003, 7.182118e-003, - 7.289638e-003, 7.401751e-003, 7.515808e-003, 7.629782e-003, - 7.743879e-003, 7.825221e-003, 7.905603e-003, 7.993493e-003, - 8.089495e-003, 8.195815e-003, 8.310149e-003, 8.433364e-003, - 8.566772e-003, 8.711999e-003, 8.866846e-003, 9.006287e-003, - 9.140958e-003, 9.288091e-003, 9.446488e-003, 9.614475e-003, - 9.793774e-003, 9.985709e-003, 1.018946e-002, 1.040183e-002, - 1.062232e-002, 1.083743e-002, 1.104407e-002, 1.126445e-002, - 1.149859e-002, 1.174416e-002, 1.199751e-002, 1.225622e-002, - 1.251640e-002, 1.277863e-002, 1.304413e-002, 1.330912e-002, - 1.354784e-002, 1.379030e-002, 1.403692e-002, 1.428711e-002, - 1.454022e-002, 1.479216e-002, 1.503939e-002, 1.528018e-002, - 1.551532e-002, 1.574603e-002, 1.594304e-002, 1.613612e-002, - 1.632784e-002, 1.651897e-002, 1.671007e-002, 1.690124e-002, - 1.709246e-002, 1.727991e-002, 1.746462e-002, 1.764694e-002, - 1.780513e-002, 1.795450e-002, 1.810445e-002, 1.825489e-002, - 1.840676e-002, 1.856097e-002, 1.871700e-002, 1.887492e-002, - 1.903423e-002, 1.919784e-002, 1.935343e-002, 1.950450e-002, - 1.966746e-002, 1.984002e-002, 2.001872e-002, 2.019768e-002, - 2.037482e-002, 2.054703e-002, 2.071587e-002, 2.088163e-002, - 2.103652e-002, 2.116813e-002, 2.129715e-002, 2.142034e-002, - 2.153846e-002, 2.165173e-002, 2.175480e-002, 2.185070e-002, - 2.193956e-002, 2.202483e-002, 2.210691e-002, 2.215792e-002, - 2.220650e-002, 2.225214e-002, 2.229691e-002, 2.234433e-002, - 2.239508e-002, 2.245017e-002, 2.251060e-002, 2.257590e-002, - 2.264920e-002, 2.270431e-002, 2.275820e-002, 2.281393e-002, - 2.287054e-002, 2.292186e-002, 2.296524e-002, 2.300089e-002, - 2.303012e-002, 2.305296e-002, 2.306820e-002, 2.305910e-002, - 2.302630e-002, 2.297897e-002, 2.291270e-002, 2.282715e-002, - 2.272179e-002, 2.259517e-002, 2.244862e-002, 2.228654e-002, - 2.210611e-002, 2.190042e-002, 2.165822e-002, 2.140254e-002, - 2.113492e-002, 2.085599e-002, 2.056860e-002, 2.027364e-002, - 1.997160e-002, 1.966354e-002, 1.934877e-002, 1.902737e-002, - 1.867195e-002, 1.831300e-002, 1.795264e-002, 1.759081e-002, - 1.723051e-002, 1.687111e-002, 1.651856e-002, 1.617237e-002, - 1.583302e-002, 1.549854e-002, 1.513897e-002, 1.477886e-002, - 1.442248e-002, 1.406648e-002, 1.371336e-002, 1.336316e-002, - 1.301864e-002, 1.268391e-002, 1.235797e-002, 1.204155e-002, - 1.171069e-002, 1.137267e-002, 1.103821e-002, 1.070490e-002, - 1.037233e-002, 1.004464e-002, 9.717938e-003, 9.394737e-003, - 9.078261e-003, 8.767793e-003, 8.449835e-003, 8.117357e-003, - 7.794951e-003, 7.485632e-003, 7.186830e-003, 6.899464e-003, - 6.626385e-003, 6.368375e-003, 6.127295e-003, 5.903327e-003, - 5.690922e-003, 5.464393e-003, 5.255171e-003, 5.059746e-003, - 4.873714e-003, 4.692101e-003, 4.510382e-003, 4.330155e-003, - 4.149792e-003, 3.973841e-003, 3.800534e-003, 3.591132e-003, - 3.386525e-003, 3.188905e-003, 2.998406e-003, 2.814495e-003, - 2.639968e-003, 2.477539e-003, 2.328904e-003, 2.214565e-003, - 2.152230e-003, 2.107248e-003, 2.097384e-003, 2.132826e-003, - 2.209479e-003, 2.324765e-003, 2.479425e-003, 2.669323e-003, - 2.892824e-003, 3.150776e-003, 3.438777e-003, 3.731097e-003, - 4.028262e-003, 4.346258e-003, 4.683846e-003, 5.037701e-003, - 5.403796e-003, 5.783733e-003, 6.173879e-003, 6.571312e-003, - 6.975531e-003, 7.374744e-003, 7.743463e-003, 8.114540e-003, - 8.483192e-003, 8.848271e-003, 9.210386e-003, 9.567733e-003, - 9.916865e-003, 1.025665e-002, 1.058486e-002, 1.089737e-002, - 1.114930e-002, 1.138346e-002, 1.159686e-002, 1.178847e-002, - 1.195601e-002, 1.209684e-002, 1.221104e-002, 1.229770e-002, - 1.235401e-002, 1.237758e-002, 1.233230e-002, 1.224274e-002, - 1.211450e-002, 1.194769e-002, 1.174325e-002, 1.152313e-002, - 1.129014e-002, 1.104205e-002, 1.077954e-002, 1.050322e-002, - 1.018728e-002, 9.836217e-003, 9.469754e-003, 9.086069e-003, - 8.687566e-003, 8.273359e-003, 7.847524e-003, 7.408781e-003, - 6.954362e-003, 6.485158e-003, 5.989630e-003, 5.452539e-003, - 4.904407e-003, 4.346624e-003, 3.779418e-003, 3.206588e-003, - 2.629062e-003, 2.047606e-003, 1.458756e-003, 8.647198e-004, - 2.692816e-004, -3.544114e-004, -9.737030e-004, -1.589628e-003, - -2.206354e-003, -2.825203e-003, -3.445050e-003, -4.062865e-003, - -4.678760e-003, -5.291797e-003, -5.901404e-003, -6.533748e-003, - -7.165009e-003, -7.793608e-003, -8.422054e-003, -9.062479e-003, - -9.728908e-003, -1.041701e-002, -1.112802e-002, -1.185287e-002, - -1.259134e-002, -1.335763e-002, -1.414169e-002, -1.493571e-002, - -1.573761e-002, -1.654454e-002, -1.735285e-002, -1.816057e-002, - -1.896625e-002, -1.977008e-002, -2.056986e-002, -2.137308e-002, - -2.218369e-002, -2.299155e-002, -2.379052e-002, -2.458579e-002, - -2.537356e-002, -2.615754e-002, -2.693491e-002, -2.770569e-002, - -2.846715e-002, -2.922214e-002, -2.997941e-002, -3.072721e-002, - -3.146359e-002, -3.218500e-002, -3.289130e-002, -3.357847e-002, - -3.424629e-002, -3.489545e-002, -3.552906e-002, -3.614490e-002, - -3.675121e-002, -3.733952e-002, -3.790807e-002, -3.845563e-002, - -3.898046e-002, -3.948301e-002, -3.996247e-002, -4.041980e-002, - -4.085415e-002, -4.127160e-002, -4.167252e-002, -4.205151e-002, - -4.240900e-002, -4.274547e-002, -4.305746e-002, -4.334545e-002, - -4.360872e-002, -4.384774e-002, -4.406094e-002, -4.425287e-002, - -4.442318e-002, -4.457628e-002, -4.470857e-002, -4.481867e-002, - -4.490666e-002, -4.497520e-002, -4.502040e-002, -4.504492e-002, - -4.504375e-002, -4.501415e-002, -4.496311e-002, -4.489659e-002, - -4.481548e-002, -4.472174e-002, -4.460564e-002, -4.444506e-002, - -4.424325e-002, -4.400043e-002, -4.372041e-002, -4.340048e-002, - -4.304127e-002, -4.264799e-002, -4.222865e-002, -4.178732e-002, - -4.132973e-002, -4.085450e-002, -4.036287e-002, -3.985839e-002, - -3.934609e-002, -3.882400e-002, -3.829546e-002, -3.775228e-002, - -3.720548e-002, -3.665955e-002, -3.611718e-002, -3.558143e-002, - -3.505563e-002, -3.454224e-002, -3.404174e-002, -3.356345e-002, - -3.310855e-002, -3.267469e-002, -3.226179e-002, -3.187716e-002, - -3.152511e-002, -3.121264e-002, -3.093793e-002, -3.070672e-002, - -3.051399e-002, -3.035898e-002, -3.024288e-002, -3.016763e-002, - -3.013690e-002, -3.015837e-002, -3.021076e-002, -3.027725e-002, - -3.035939e-002, -3.045214e-002, -3.055773e-002, -3.067715e-002, - -3.080632e-002, -3.094361e-002, -3.107647e-002, -3.121544e-002, - -3.135756e-002, -3.150256e-002, -3.164995e-002, -3.179803e-002, - -3.194612e-002, -3.209115e-002, -3.223317e-002, -3.237179e-002, - -3.249983e-002, -3.261444e-002, -3.271806e-002, -3.280989e-002, - -3.288672e-002, -3.294479e-002, -3.298477e-002, -3.300870e-002, - -3.302849e-002, -3.303906e-002, -3.303823e-002, -3.302549e-002, - -3.299939e-002, -3.296521e-002, -3.291420e-002, -3.284295e-002, - -3.275281e-002, -3.264845e-002, -3.253079e-002, -3.239972e-002, - -3.225656e-002, -3.209977e-002, -3.192673e-002, -3.173207e-002, - -3.152171e-002, -3.128617e-002, -3.102538e-002, -3.073882e-002, - -3.042679e-002, -3.008612e-002, -2.972135e-002, -2.933698e-002, - -2.891916e-002, -2.846609e-002, -2.797823e-002, -2.745716e-002, - -2.690313e-002, -2.631054e-002, -2.568272e-002, -2.501522e-002, - -2.423081e-002, -2.327379e-002, -2.213933e-002, -2.083558e-002, - -1.937900e-002, -1.777280e-002, -1.602882e-002, -1.415470e-002, - -1.216174e-002, -1.005800e-002, -7.852785e-003, -5.577669e-003, - -3.225707e-003, -7.950705e-004, 1.705089e-003, 4.264522e-003, - 6.878565e-003, 9.538148e-003, 1.223065e-002, 1.495671e-002, - 1.771635e-002, 2.048061e-002, 2.324050e-002, 2.600766e-002, - 2.877234e-002, 3.151481e-002, 3.423751e-002, 3.693500e-002, - 3.958961e-002, 4.218836e-002, 4.472905e-002, 4.718425e-002, - 4.952104e-002, 5.177306e-002, 5.392289e-002, 5.595635e-002, - 5.787058e-002, 5.965460e-002, 6.129793e-002, 6.278696e-002, - 6.411225e-002, 6.528875e-002, 6.635753e-002, 6.740654e-002, - 6.842936e-002, 6.941785e-002, 7.037484e-002, 7.129397e-002, - 7.217953e-002, 7.302982e-002, 7.384295e-002, 7.462526e-002, - 7.531136e-002, 7.595260e-002, 7.656510e-002, 7.714040e-002, - 7.768690e-002, 7.820494e-002, 7.869486e-002, 7.915010e-002, - 7.957722e-002, 7.998262e-002, 8.030348e-002, 8.055689e-002, - 8.077996e-002, 8.097339e-002, 8.113253e-002, 8.126649e-002, - 8.137266e-002, 8.144338e-002, 8.148531e-002, 8.150996e-002, - 8.146725e-002, 8.133055e-002, 8.116286e-002, 8.097584e-002, - 8.077114e-002, 8.054803e-002, 8.030825e-002, 8.005100e-002, - 7.977142e-002, 7.947684e-002, 7.915042e-002, 7.868925e-002, - 7.820427e-002, 7.770436e-002, 7.718218e-002, 7.663719e-002, - 7.606605e-002, 7.546112e-002, 7.483145e-002, 7.418819e-002, - 7.353566e-002, 7.274333e-002, 7.191560e-002, 7.106967e-002, - 7.014388e-002, 6.907394e-002, 6.787116e-002, 6.652866e-002, - 6.505626e-002, 6.346981e-002, 6.177939e-002, 5.989445e-002, - 5.785643e-002, 5.573531e-002, 5.354293e-002, 5.129314e-002, - 4.898206e-002, 4.661231e-002, 4.419842e-002, 4.174299e-002, - 3.925274e-002, 3.667568e-002, 3.399441e-002, 3.130476e-002, - 2.862103e-002, 2.594957e-002, 2.329398e-002, 2.065878e-002, - 1.805871e-002, 1.549285e-002, 1.299559e-002, 1.052828e-002, - 8.012932e-003, 5.579455e-003, 3.236911e-003, 9.938308e-004, - -1.150984e-003, -3.174735e-003, -5.059686e-003, -6.809659e-003, - -8.412335e-003, -9.880705e-003, -1.134897e-002, -1.265636e-002, - -1.379655e-002, -1.477553e-002, -1.562985e-002, -1.643292e-002, - -1.718090e-002, -1.789842e-002, -1.856768e-002, -1.919719e-002, - -1.990426e-002, -2.061248e-002, -2.128293e-002, -2.192866e-002, - -2.253926e-002, -2.310378e-002, -2.362728e-002, -2.411584e-002, - -2.457203e-002, -2.499363e-002, -2.544621e-002, -2.592946e-002, - -2.638024e-002, -2.680883e-002, -2.718994e-002, -2.751874e-002, - -2.780663e-002, -2.806841e-002, -2.829710e-002, -2.849539e-002, - -2.869767e-002, -2.894609e-002, -2.917919e-002, -2.939030e-002, - -2.959010e-002, -2.976742e-002, -2.993230e-002, -3.008729e-002, - -3.022927e-002, -3.036955e-002, -3.049923e-002, -3.072052e-002, - -3.092149e-002, -3.111119e-002, -3.128595e-002, -3.144230e-002, - -3.158714e-002, -3.171439e-002, -3.183511e-002, -3.195244e-002, - -3.206430e-002, -3.223227e-002, -3.239407e-002, -3.254359e-002, - -3.268446e-002, -3.281057e-002, -3.292358e-002, -3.303140e-002, - -3.311759e-002, -3.320461e-002, -3.328666e-002, -3.339610e-002, - -3.350562e-002, -3.361439e-002, -3.371436e-002, -3.380370e-002, - -3.386828e-002, -3.392261e-002, -3.398305e-002, -3.404143e-002, - -3.410032e-002, -3.416745e-002, -3.425610e-002, -3.433801e-002, - -3.439257e-002, -3.442454e-002, -3.445381e-002, -3.446423e-002, - -3.445652e-002, -3.444137e-002, -3.442753e-002, -3.440449e-002, - -3.437195e-002, -3.434489e-002, -3.431202e-002, -3.425952e-002, - -3.419591e-002, -3.412652e-002, -3.405660e-002, -3.397037e-002, - -3.386071e-002, -3.373766e-002, -3.358638e-002, -3.342210e-002, - -3.324240e-002, -3.306331e-002, -3.287532e-002, -3.266469e-002, - -3.243061e-002, -3.219270e-002, -3.194425e-002, -3.168588e-002, - -3.137974e-002, -3.105209e-002, -3.073460e-002, -3.041001e-002, - -3.008346e-002, -2.976094e-002, -2.943923e-002, -2.909910e-002, - -2.874775e-002, -2.837853e-002, -2.798124e-002, -2.754629e-002, - -2.709499e-002, -2.663031e-002, -2.615798e-002, -2.567753e-002, - -2.517289e-002, -2.465078e-002, -2.410762e-002, -2.355098e-002, - -2.294501e-002, -2.226624e-002, -2.157321e-002, -2.086015e-002, - -2.010767e-002, -1.937109e-002, -1.861038e-002, -1.781892e-002, - -1.701951e-002, -1.620382e-002, -1.536266e-002, -1.443233e-002, - -1.347535e-002, -1.251570e-002, -1.152925e-002, -1.049380e-002, - -9.454898e-003, -8.412042e-003, -7.360208e-003, -6.294182e-003, - -5.200498e-003, -4.087726e-003, -2.968007e-003, -1.810612e-003, - -6.067522e-004, 6.302428e-004, 1.892252e-003, 3.198992e-003, - 4.546572e-003, 5.901268e-003, 7.291148e-003, 8.664949e-003, - 1.001731e-002, 1.138519e-002, 1.278020e-002, 1.423137e-002, - 1.569641e-002, 1.720541e-002, 1.874068e-002, 2.028562e-002, - 2.185744e-002, 2.336896e-002, 2.475774e-002, 2.616468e-002, - 2.759369e-002, 2.902556e-002, 3.044395e-002, 3.185632e-002, - 3.328766e-002, 3.470071e-002, 3.610923e-002, 3.750439e-002, - 3.853541e-002, 3.952907e-002, 4.051893e-002, 4.139801e-002, - 4.211495e-002, 4.274570e-002, 4.326666e-002, 4.372189e-002, - 4.408742e-002, 4.434519e-002, 4.400735e-002, 4.347527e-002, - 4.287175e-002, 4.221931e-002, 4.151379e-002, 4.081665e-002, - 4.008411e-002, 3.929407e-002, 3.843269e-002, 3.753149e-002, - 3.599452e-002, 3.409139e-002, 3.220871e-002, 3.039499e-002, - 2.859782e-002, 2.685830e-002, 2.515944e-002, 2.352059e-002, - 2.190952e-002, 2.034107e-002, 1.829571e-002, 1.549800e-002, - 1.277763e-002, 1.024268e-002, 7.809282e-003, 5.582108e-003, - 3.479477e-003, 1.563834e-003, -8.121426e-005, -1.495434e-003, - -3.060928e-003, -6.032519e-003, -8.871614e-003, -1.148263e-002, - -1.384117e-002, -1.589989e-002, -1.754176e-002, -1.881581e-002, - -1.954533e-002, -2.012737e-002, -2.044263e-002, -2.304076e-002, - -2.546563e-002, -2.722867e-002, -2.850422e-002, -2.929996e-002, - -2.978185e-002, -2.999934e-002, -2.980564e-002, -2.860301e-002, - -2.476663e-002, -2.430392e-002, -2.519189e-002, -2.564640e-002, - -2.601808e-002, -2.618570e-002, -2.617154e-002, -2.595932e-002, - -2.530687e-002, -2.424254e-002, -2.321012e-002, 1.663367e-003, - -1.540108e-003, -6.532874e-003, -1.179565e-002, -1.711408e-002, - -2.178092e-002, -2.548677e-002, -2.801166e-002, -2.912911e-002, - -2.735056e-002, 3.193593e-002, 9.422371e-002, 1.004558e-001, - 9.912590e-002, 9.465689e-002, 8.663026e-002, 4.994391e-002, - 3.638524e-002, 2.312703e-002, 1.122065e-002, 0.000000e+000 - }, - { - 0.000000e+000, -1.917446e-002, -3.357208e-002, -4.485993e-002, - -5.392290e-002, -6.133844e-002, -6.740496e-002, -7.236591e-002, - -7.645188e-002, -8.000724e-002, -8.299973e-002, -8.573523e-002, - -8.813601e-002, -9.013370e-002, -9.176863e-002, -9.305295e-002, - -9.400689e-002, -9.464648e-002, -9.498033e-002, -9.506144e-002, - -9.491476e-002, -9.476642e-002, -9.458767e-002, -9.423030e-002, - -9.371314e-002, -9.303643e-002, -9.222339e-002, -9.128921e-002, - -9.024554e-002, -8.908451e-002, -8.781100e-002, -8.654811e-002, - -8.545081e-002, -8.426564e-002, -8.299402e-002, -8.163693e-002, - -8.020261e-002, -7.869511e-002, -7.711815e-002, -7.548039e-002, - -7.378460e-002, -7.206211e-002, -7.060756e-002, -6.911360e-002, - -6.757632e-002, -6.599840e-002, -6.438893e-002, -6.277236e-002, - -6.112213e-002, -5.940795e-002, -5.765177e-002, -5.586309e-002, - -5.433067e-002, -5.282395e-002, -5.129899e-002, -4.975244e-002, - -4.818755e-002, -4.660027e-002, -4.499140e-002, -4.336808e-002, - -4.172639e-002, -4.006777e-002, -3.858851e-002, -3.720490e-002, - -3.580733e-002, -3.438978e-002, -3.294948e-002, -3.149376e-002, - -3.001498e-002, -2.852463e-002, -2.700631e-002, -2.547403e-002, - -2.402804e-002, -2.273288e-002, -2.141482e-002, -2.007256e-002, - -1.870569e-002, -1.732251e-002, -1.591873e-002, -1.450606e-002, - -1.308348e-002, -1.166399e-002, -1.026940e-002, -9.083404e-003, - -7.893131e-003, -6.695535e-003, -5.494698e-003, -4.287636e-003, - -3.085106e-003, -1.885421e-003, -6.951573e-004, 4.881600e-004, - 1.662182e-003, 2.623669e-003, 3.563596e-003, 4.505491e-003, - 5.443136e-003, 6.384833e-003, 7.320107e-003, 8.259680e-003, - 9.200667e-003, 1.014429e-002, 1.109345e-002, 1.190536e-002, - 1.266814e-002, 1.344581e-002, 1.423572e-002, 1.503570e-002, - 1.584482e-002, 1.665877e-002, 1.747891e-002, 1.830714e-002, - 1.913794e-002, 1.989411e-002, 2.055821e-002, 2.122006e-002, - 2.188852e-002, 2.255585e-002, 2.322480e-002, 2.389807e-002, - 2.456945e-002, 2.524273e-002, 2.591617e-002, 2.653953e-002, - 2.703306e-002, 2.751417e-002, 2.797891e-002, 2.843113e-002, - 2.888141e-002, 2.932330e-002, 2.975701e-002, 3.018879e-002, - 3.061672e-002, 3.103566e-002, 3.132751e-002, 3.161941e-002, - 3.191093e-002, 3.220225e-002, 3.249863e-002, 3.279823e-002, - 3.309734e-002, 3.339875e-002, 3.370476e-002, 3.401055e-002, - 3.423728e-002, 3.444550e-002, 3.465039e-002, 3.485282e-002, - 3.505077e-002, 3.524521e-002, 3.543399e-002, 3.561996e-002, - 3.580024e-002, 3.597413e-002, 3.609436e-002, 3.617334e-002, - 3.624943e-002, 3.631673e-002, 3.637839e-002, 3.643366e-002, - 3.648319e-002, 3.653290e-002, 3.658274e-002, 3.662739e-002, - 3.664504e-002, 3.661775e-002, 3.659165e-002, 3.656757e-002, - 3.654867e-002, 3.653761e-002, 3.653013e-002, 3.652263e-002, - 3.651496e-002, 3.650953e-002, 3.649904e-002, 3.643903e-002, - 3.638514e-002, 3.633951e-002, 3.629677e-002, 3.625838e-002, - 3.622128e-002, 3.618553e-002, 3.614227e-002, 3.608796e-002, - 3.602448e-002, 3.592186e-002, 3.581270e-002, 3.570182e-002, - 3.559374e-002, 3.548919e-002, 3.538912e-002, 3.528891e-002, - 3.519033e-002, 3.509710e-002, 3.500855e-002, 3.490307e-002, - 3.478914e-002, 3.467962e-002, 3.457497e-002, 3.447593e-002, - 3.438088e-002, 3.429267e-002, 3.420769e-002, 3.412331e-002, - 3.404129e-002, 3.395038e-002, 3.385377e-002, 3.376349e-002, - 3.367717e-002, 3.359452e-002, 3.351075e-002, 3.342211e-002, - 3.332489e-002, 3.321856e-002, 3.310648e-002, 3.299112e-002, - 3.286239e-002, 3.273023e-002, 3.259424e-002, 3.244970e-002, - 3.229691e-002, 3.213224e-002, 3.195314e-002, 3.175759e-002, - 3.154535e-002, 3.131696e-002, 3.107177e-002, 3.081463e-002, - 3.054844e-002, 3.027322e-002, 2.998869e-002, 2.969435e-002, - 2.939066e-002, 2.907574e-002, 2.875039e-002, 2.841608e-002, - 2.807611e-002, 2.773074e-002, 2.737984e-002, 2.702442e-002, - 2.666572e-002, 2.630341e-002, 2.593684e-002, 2.556620e-002, - 2.519267e-002, 2.481882e-002, 2.445268e-002, 2.409979e-002, - 2.375497e-002, 2.341708e-002, 2.307716e-002, 2.273164e-002, - 2.237549e-002, 2.200921e-002, 2.163239e-002, 2.124693e-002, - 2.085590e-002, 2.047269e-002, 2.008043e-002, 1.967697e-002, - 1.926279e-002, 1.883700e-002, 1.839302e-002, 1.793299e-002, - 1.745895e-002, 1.697244e-002, 1.647985e-002, 1.600584e-002, - 1.552323e-002, 1.503196e-002, 1.453342e-002, 1.402785e-002, - 1.351147e-002, 1.298759e-002, 1.245719e-002, 1.192083e-002, - 1.138255e-002, 1.086985e-002, 1.035852e-002, 9.840363e-003, - 9.312295e-003, 8.775670e-003, 8.231568e-003, 7.681165e-003, - 7.126639e-003, 6.568512e-003, 6.005336e-003, 5.462882e-003, - 4.929542e-003, 4.382658e-003, 3.815087e-003, 3.224469e-003, - 2.616268e-003, 1.990636e-003, 1.350767e-003, 7.016947e-004, - 4.389856e-005, -6.067232e-004, -1.231697e-003, -1.868920e-003, - -2.513248e-003, -3.164459e-003, -3.818774e-003, -4.476777e-003, - -5.137901e-003, -5.800929e-003, -6.464408e-003, -7.122740e-003, - -7.727417e-003, -8.332544e-003, -8.937141e-003, -9.542490e-003, - -1.014530e-002, -1.074228e-002, -1.133222e-002, -1.191453e-002, - -1.248632e-002, -1.305039e-002, -1.354845e-002, -1.403250e-002, - -1.451553e-002, -1.499934e-002, -1.548424e-002, -1.596907e-002, - -1.645463e-002, -1.693731e-002, -1.741907e-002, -1.789961e-002, - -1.833226e-002, -1.874193e-002, -1.916374e-002, -1.959630e-002, - -2.003797e-002, -2.048484e-002, -2.093619e-002, -2.139084e-002, - -2.184684e-002, -2.230407e-002, -2.272553e-002, -2.309865e-002, - -2.346817e-002, -2.383434e-002, -2.419793e-002, -2.455811e-002, - -2.491016e-002, -2.525650e-002, -2.559477e-002, -2.592536e-002, - -2.623229e-002, -2.645505e-002, -2.667012e-002, -2.688084e-002, - -2.709237e-002, -2.731168e-002, -2.754372e-002, -2.778848e-002, - -2.804347e-002, -2.830894e-002, -2.858594e-002, -2.878516e-002, - -2.899026e-002, -2.920274e-002, -2.942234e-002, -2.964643e-002, - -2.987455e-002, -3.011029e-002, -3.035074e-002, -3.058768e-002, - -3.081560e-002, -3.096361e-002, -3.107537e-002, -3.117723e-002, - -3.126817e-002, -3.134781e-002, -3.141687e-002, -3.147970e-002, - -3.153434e-002, -3.157991e-002, -3.161908e-002, -3.160049e-002, - -3.152558e-002, -3.144618e-002, -3.136124e-002, -3.127568e-002, - -3.119045e-002, -3.110238e-002, -3.101343e-002, -3.092555e-002, - -3.083977e-002, -3.073487e-002, -3.056175e-002, -3.039486e-002, - -3.024078e-002, -3.009392e-002, -2.995230e-002, -2.981630e-002, - -2.968627e-002, -2.956511e-002, -2.945145e-002, -2.934473e-002, - -2.914892e-002, -2.896167e-002, -2.878619e-002, -2.862006e-002, - -2.846599e-002, -2.832434e-002, -2.819575e-002, -2.807609e-002, - -2.796652e-002, -2.786538e-002, -2.769262e-002, -2.751077e-002, - -2.734129e-002, -2.718233e-002, -2.703879e-002, -2.690162e-002, - -2.677085e-002, -2.664745e-002, -2.653349e-002, -2.642912e-002, - -2.628001e-002, -2.609563e-002, -2.592181e-002, -2.575652e-002, - -2.560114e-002, -2.545607e-002, -2.531877e-002, -2.519355e-002, - -2.508190e-002, -2.498252e-002, -2.486268e-002, -2.469393e-002, - -2.453210e-002, -2.437617e-002, -2.422835e-002, -2.408568e-002, - -2.394476e-002, -2.380653e-002, -2.367064e-002, -2.353609e-002, - -2.338756e-002, -2.314842e-002, -2.290056e-002, -2.264949e-002, - -2.239572e-002, -2.214139e-002, -2.189076e-002, -2.164275e-002, - -2.139338e-002, -2.114066e-002, -2.088098e-002, -2.053391e-002, - -2.016921e-002, -1.980130e-002, -1.943307e-002, -1.907297e-002, - -1.872935e-002, -1.840240e-002, -1.808596e-002, -1.777645e-002, - -1.747299e-002, -1.711816e-002, -1.673914e-002, -1.636362e-002, - -1.599391e-002, -1.562728e-002, -1.526190e-002, -1.489888e-002, - -1.453651e-002, -1.417805e-002, -1.382231e-002, -1.343602e-002, - -1.300491e-002, -1.257843e-002, -1.215241e-002, -1.172580e-002, - -1.130559e-002, -1.089582e-002, -1.049300e-002, -1.009362e-002, - -9.695659e-003, -9.285519e-003, -8.816713e-003, -8.352234e-003, - -7.895409e-003, -7.441561e-003, -6.988173e-003, -6.532293e-003, - -6.076290e-003, -5.617894e-003, -5.162532e-003, -4.716524e-003, - -4.213511e-003, -3.709718e-003, -3.206436e-003, -2.707442e-003, - -2.209886e-003, -1.711461e-003, -1.218845e-003, -7.329040e-004, - -2.519923e-004, 2.189814e-004, 7.196384e-004, 1.224210e-003, - 1.719773e-003, 2.201916e-003, 2.672677e-003, 3.133300e-003, - 3.584433e-003, 4.022172e-003, 4.445831e-003, 4.859802e-003, - 5.288276e-003, 5.729418e-003, 6.164774e-003, 6.595163e-003, - 7.019463e-003, 7.435052e-003, 7.844685e-003, 8.253468e-003, - 8.662508e-003, 9.075197e-003, 9.494715e-003, 9.933839e-003, - 1.035931e-002, 1.077187e-002, 1.115711e-002, 1.148821e-002, - 1.176705e-002, 1.200211e-002, 1.219583e-002, 1.234683e-002, - 1.246053e-002, 1.256879e-002, 1.263062e-002, 1.264619e-002, - 1.262526e-002, 1.257657e-002, 1.249829e-002, 1.239119e-002, - 1.226101e-002, 1.210424e-002, 1.192751e-002, 1.176205e-002, - 1.159270e-002, 1.141325e-002, 1.122008e-002, 1.102307e-002, - 1.081814e-002, 1.061591e-002, 1.041970e-002, 1.022444e-002, - 1.003443e-002, 9.857186e-003, 9.697819e-003, 9.543568e-003, - 9.394526e-003, 9.259355e-003, 9.135011e-003, 9.023077e-003, - 8.925633e-003, 8.847889e-003, 8.792825e-003, 8.768596e-003, - 8.771325e-003, 8.793507e-003, 8.823824e-003, 8.850958e-003, - 8.873715e-003, 8.896308e-003, 8.919653e-003, 8.933983e-003, - 8.939269e-003, 8.947762e-003, 8.975280e-003, 9.002827e-003, - 9.031671e-003, 9.057938e-003, 9.084063e-003, 9.105906e-003, - 9.132004e-003, 9.164379e-003, 9.200538e-003, 9.239389e-003, - 9.298251e-003, 9.366940e-003, 9.447092e-003, 9.539115e-003, - 9.638633e-003, 9.749691e-003, 9.878277e-003, 1.001388e-002, - 1.014726e-002, 1.028210e-002, 1.043178e-002, 1.058861e-002, - 1.074030e-002, 1.088568e-002, 1.103104e-002, 1.118389e-002, - 1.134514e-002, 1.151074e-002, 1.166761e-002, 1.181343e-002, - 1.195476e-002, 1.210631e-002, 1.225648e-002, 1.240763e-002, - 1.255400e-002, 1.269543e-002, 1.283047e-002, 1.295913e-002, - 1.308551e-002, 1.321516e-002, 1.334864e-002, 1.350993e-002, - 1.366855e-002, 1.383075e-002, 1.399476e-002, 1.416295e-002, - 1.433064e-002, 1.449699e-002, 1.466705e-002, 1.483713e-002, - 1.502819e-002, 1.529944e-002, 1.560268e-002, 1.592598e-002, - 1.626904e-002, 1.662632e-002, 1.699631e-002, 1.737554e-002, - 1.775944e-002, 1.813723e-002, 1.851182e-002, 1.892832e-002, - 1.936569e-002, 1.979890e-002, 2.022158e-002, 2.062698e-002, - 2.102278e-002, 2.140795e-002, 2.178210e-002, 2.214103e-002, - 2.247572e-002, 2.281408e-002, 2.315818e-002, 2.347566e-002, - 2.376343e-002, 2.401848e-002, 2.422955e-002, 2.440734e-002, - 2.454562e-002, 2.464994e-002, 2.471761e-002, 2.477157e-002, - 2.483714e-002, 2.486794e-002, 2.484086e-002, 2.475764e-002, - 2.462347e-002, 2.443064e-002, 2.418035e-002, 2.387274e-002, - 2.350931e-002, 2.308710e-002, 2.272446e-002, 2.233632e-002, - 2.191968e-002, 2.147619e-002, 2.100039e-002, 2.048852e-002, - 1.993961e-002, 1.935590e-002, 1.874341e-002, 1.810521e-002, - 1.753298e-002, 1.695167e-002, 1.634195e-002, 1.571312e-002, - 1.506808e-002, 1.440274e-002, 1.371353e-002, 1.301384e-002, - 1.229053e-002, 1.155709e-002, 1.089028e-002, 1.026460e-002, - 9.623148e-003, 8.968709e-003, 8.297757e-003, 7.617934e-003, - 6.937591e-003, 6.259397e-003, 5.567104e-003, 4.877408e-003, - 4.234189e-003, 3.681638e-003, 3.123003e-003, 2.563191e-003, - 1.993958e-003, 1.405985e-003, 8.075303e-004, 1.878482e-004, - -4.555642e-004, -1.106193e-003, -1.741581e-003, -2.249442e-003, - -2.773752e-003, -3.310069e-003, -3.867641e-003, -4.443892e-003, - -5.038108e-003, -5.656982e-003, -6.290361e-003, -6.920992e-003, - -7.566403e-003, -8.062841e-003, -8.545951e-003, -9.037743e-003, - -9.551054e-003, -1.008472e-002, -1.063359e-002, -1.119183e-002, - -1.175770e-002, -1.233640e-002, -1.293430e-002, -1.342456e-002, - -1.388017e-002, -1.433659e-002, -1.479283e-002, -1.525609e-002, - -1.573319e-002, -1.622192e-002, -1.672732e-002, -1.724396e-002, - -1.777834e-002, -1.824211e-002, -1.859649e-002, -1.896824e-002, - -1.935861e-002, -1.976412e-002, -2.018267e-002, -2.060665e-002, - -2.104167e-002, -2.149765e-002, -2.196816e-002, -2.238555e-002, - -2.263105e-002, -2.286099e-002, -2.309679e-002, -2.335761e-002, - -2.360879e-002, -2.383739e-002, -2.404131e-002, -2.421688e-002, - -2.438284e-002, -2.455886e-002, -2.453385e-002, -2.450025e-002, - -2.446302e-002, -2.441445e-002, -2.435980e-002, -2.430471e-002, - -2.425663e-002, -2.421236e-002, -2.417048e-002, -2.412329e-002, - -2.393496e-002, -2.371439e-002, -2.349890e-002, -2.328333e-002, - -2.306380e-002, -2.285338e-002, -2.265720e-002, -2.245591e-002, - -2.225555e-002, -2.204958e-002, -2.175324e-002, -2.138768e-002, - -2.100773e-002, -2.061061e-002, -2.019770e-002, -1.979063e-002, - -1.939110e-002, -1.898675e-002, -1.857286e-002, -1.815986e-002, - -1.768190e-002, -1.707631e-002, -1.646493e-002, -1.586174e-002, - -1.527974e-002, -1.469667e-002, -1.412950e-002, -1.359741e-002, - -1.306783e-002, -1.254024e-002, -1.202383e-002, -1.135842e-002, - -1.070901e-002, -1.007462e-002, -9.442801e-003, -8.819133e-003, - -8.224198e-003, -7.651293e-003, -7.099552e-003, -6.545813e-003, - -6.013025e-003, -5.357565e-003, -4.699674e-003, -4.034334e-003, - -3.381717e-003, -2.742704e-003, -2.119204e-003, -1.518067e-003, - -9.144723e-004, -3.191589e-004, 2.692416e-004, 9.154375e-004, - 1.611244e-003, 2.300195e-003, 2.999192e-003, 3.691826e-003, - 4.367659e-003, 5.012450e-003, 5.640230e-003, 6.257071e-003, - 6.847229e-003, 7.467051e-003, 8.164145e-003, 8.843807e-003, - 9.512631e-003, 1.016594e-002, 1.079930e-002, 1.140911e-002, - 1.200100e-002, 1.258150e-002, 1.312002e-002, 1.362417e-002, - 1.421968e-002, 1.479411e-002, 1.535432e-002, 1.587872e-002, - 1.639481e-002, 1.689175e-002, 1.735602e-002, 1.779859e-002, - 1.821660e-002, 1.862369e-002, 1.910846e-002, 1.959168e-002, - 2.002075e-002, 2.042901e-002, 2.082711e-002, 2.121867e-002, - 2.158960e-002, 2.191764e-002, 2.219604e-002, 2.244719e-002, - 2.272196e-002, 2.298230e-002, 2.322330e-002, 2.343503e-002, - 2.358212e-002, 2.367929e-002, 2.376000e-002, 2.381506e-002, - 2.382425e-002, 2.378198e-002, 2.373273e-002, 2.370051e-002, - 2.363768e-002, 2.354695e-002, 2.346150e-002, 2.333068e-002, - 2.316178e-002, 2.296032e-002, 2.271135e-002, 2.245856e-002, - 2.219101e-002, 2.197463e-002, 2.172716e-002, 2.145696e-002, - 2.110164e-002, 2.071442e-002, 2.031747e-002, 1.989726e-002, - 1.943063e-002, 1.892547e-002, 1.841200e-002, 1.801767e-002, - 1.758195e-002, 1.711026e-002, 1.662520e-002, 1.606876e-002, - 1.547467e-002, 1.484573e-002, 1.419956e-002, 1.351311e-002, - 1.276732e-002, 1.221456e-002, 1.168424e-002, 1.112611e-002, - 1.048345e-002, 9.771772e-003, 9.006919e-003, 8.209925e-003, - 7.291518e-003, 6.303868e-003, 5.262990e-003, 4.369873e-003, - 3.622963e-003, 2.800930e-003, 1.908438e-003, 9.301908e-004, - -6.751147e-005, -1.194167e-003, -2.400381e-003, -3.650491e-003, - -4.973638e-003, -6.172517e-003, -7.077792e-003, -8.140636e-003, - -9.311190e-003, -1.056024e-002, -1.187086e-002, -1.318945e-002, - -1.458420e-002, -1.607262e-002, -1.762410e-002, -1.910481e-002, - -1.990147e-002, -2.075472e-002, -2.166737e-002, -2.258154e-002, - -2.354181e-002, -2.455364e-002, -2.554955e-002, -2.661309e-002, - -2.771601e-002, -2.887493e-002, -2.924331e-002, -2.948401e-002, - -2.977495e-002, -3.017102e-002, -3.077033e-002, -3.155324e-002, - -3.228826e-002, -3.296981e-002, -3.366864e-002, -3.448449e-002, - -3.440618e-002, -3.395232e-002, -3.357070e-002, -3.337851e-002, - -3.329987e-002, -3.334036e-002, -3.349073e-002, -3.367563e-002, - -3.396043e-002, -3.427566e-002, -3.387107e-002, -3.239505e-002, - -3.104770e-002, -2.987876e-002, -2.878359e-002, -2.783670e-002, - -2.702611e-002, -2.640568e-002, -2.611636e-002, -2.596032e-002, - -2.551346e-002, -2.288226e-002, -2.024662e-002, -1.773271e-002, - -1.531131e-002, -1.297201e-002, -1.082345e-002, -8.923257e-003, - -7.666208e-003, -6.381686e-003, -5.168593e-003, -7.141463e-004, - 3.875751e-003, 7.865452e-003, 1.127558e-002, 1.435102e-002, - 1.748499e-002, 2.080198e-002, 2.397681e-002, 2.641973e-002, - 2.652330e-002, 3.077613e-002, 3.656855e-002, 4.173548e-002, - 4.632218e-002, 4.950615e-002, 5.196148e-002, 5.385484e-002, - 5.571919e-002, 5.787208e-002, 6.100562e-002, 4.281794e-002, - 5.140241e-002, 6.137765e-002, 7.164735e-002, 8.226066e-002, - 9.222150e-002, 1.001564e-001, 1.059300e-001, 1.087946e-001, - 1.051641e-001, -5.533066e-002, -1.499467e-001, -1.670421e-001, - -1.696878e-001, -1.655690e-001, -1.543446e-001, -8.028114e-002, - -5.674685e-002, -3.469316e-002, -1.615907e-002, 0.000000e+000 - }, - { - 0.000000e+000, -1.513089e-002, -2.602010e-002, -3.431418e-002, - -4.090427e-002, -4.616818e-002, -5.043410e-002, -5.385045e-002, - -5.663978e-002, -5.873895e-002, -6.041027e-002, -6.202552e-002, - -6.324324e-002, -6.409526e-002, -6.465596e-002, -6.497530e-002, - -6.512038e-002, -6.510237e-002, -6.496655e-002, -6.467680e-002, - -6.425072e-002, -6.390810e-002, -6.360686e-002, -6.318816e-002, - -6.265719e-002, -6.200858e-002, -6.123204e-002, -6.034566e-002, - -5.934985e-002, -5.825208e-002, -5.706811e-002, -5.592179e-002, - -5.495033e-002, -5.391145e-002, -5.280582e-002, -5.164470e-002, - -5.043659e-002, -4.917047e-002, -4.786199e-002, -4.651296e-002, - -4.512187e-002, -4.372417e-002, -4.259674e-002, -4.142757e-002, - -4.021568e-002, -3.895281e-002, -3.756986e-002, -3.595483e-002, - -3.432941e-002, -3.291504e-002, -3.155027e-002, -3.015986e-002, - -2.900770e-002, -2.787021e-002, -2.670250e-002, -2.551076e-002, - -2.429558e-002, -2.306075e-002, -2.180418e-002, -2.053092e-002, - -1.923246e-002, -1.792083e-002, -1.676809e-002, -1.569928e-002, - -1.461997e-002, -1.352298e-002, -1.240453e-002, -1.126905e-002, - -1.011961e-002, -8.946282e-003, -7.761666e-003, -6.560747e-003, - -5.441541e-003, -4.469995e-003, -3.486162e-003, -2.482251e-003, - -1.468615e-003, -4.419604e-004, 5.903142e-004, 1.616780e-003, - 2.627548e-003, 3.614490e-003, 4.530195e-003, 5.220392e-003, - 5.888836e-003, 6.535997e-003, 7.162740e-003, 7.777999e-003, - 8.383193e-003, 8.984390e-003, 9.598139e-003, 1.021886e-002, - 1.084786e-002, 1.129105e-002, 1.173658e-002, 1.220060e-002, - 1.267773e-002, 1.317340e-002, 1.369284e-002, 1.423043e-002, - 1.478897e-002, 1.536608e-002, 1.596958e-002, 1.646712e-002, - 1.691547e-002, 1.736213e-002, 1.781475e-002, 1.827348e-002, - 1.875152e-002, 1.924053e-002, 1.974396e-002, 2.027162e-002, - 2.080416e-002, 2.126764e-002, 2.164977e-002, 2.203226e-002, - 2.243031e-002, 2.283490e-002, 2.324175e-002, 2.365723e-002, - 2.407445e-002, 2.449577e-002, 2.491619e-002, 2.528548e-002, - 2.552624e-002, 2.575306e-002, 2.596864e-002, 2.617468e-002, - 2.636997e-002, 2.656103e-002, 2.674751e-002, 2.693041e-002, - 2.711586e-002, 2.730100e-002, 2.736327e-002, 2.743111e-002, - 2.750780e-002, 2.759409e-002, 2.769150e-002, 2.779709e-002, - 2.791160e-002, 2.803236e-002, 2.815873e-002, 2.829422e-002, - 2.835326e-002, 2.839156e-002, 2.843231e-002, 2.847664e-002, - 2.852130e-002, 2.855997e-002, 2.859675e-002, 2.863326e-002, - 2.866769e-002, 2.869808e-002, 2.867806e-002, 2.861527e-002, - 2.854657e-002, 2.847173e-002, 2.838941e-002, 2.830141e-002, - 2.820868e-002, 2.811522e-002, 2.801889e-002, 2.792201e-002, - 2.779906e-002, 2.762858e-002, 2.745881e-002, 2.729264e-002, - 2.713420e-002, 2.698653e-002, 2.684412e-002, 2.670519e-002, - 2.656679e-002, 2.642590e-002, 2.627830e-002, 2.608067e-002, - 2.588844e-002, 2.570033e-002, 2.551674e-002, 2.533743e-002, - 2.515533e-002, 2.496436e-002, 2.476015e-002, 2.454025e-002, - 2.430273e-002, 2.401812e-002, 2.371948e-002, 2.341444e-002, - 2.310233e-002, 2.278806e-002, 2.246851e-002, 2.214482e-002, - 2.181777e-002, 2.149282e-002, 2.116704e-002, 2.082180e-002, - 2.046688e-002, 2.011597e-002, 1.976830e-002, 1.941910e-002, - 1.907145e-002, 1.872795e-002, 1.838517e-002, 1.804258e-002, - 1.770120e-002, 1.735962e-002, 1.701964e-002, 1.668808e-002, - 1.636594e-002, 1.604862e-002, 1.573716e-002, 1.542418e-002, - 1.510695e-002, 1.478228e-002, 1.445394e-002, 1.412564e-002, - 1.379080e-002, 1.345690e-002, 1.312278e-002, 1.278771e-002, - 1.244913e-002, 1.209882e-002, 1.173455e-002, 1.135088e-002, - 1.095260e-002, 1.054188e-002, 1.012414e-002, 9.700809e-003, - 9.271810e-003, 8.839668e-003, 8.404347e-003, 7.965961e-003, - 7.525859e-003, 7.081762e-003, 6.635759e-003, 6.189788e-003, - 5.751523e-003, 5.316125e-003, 4.882661e-003, 4.451991e-003, - 4.028038e-003, 3.611153e-003, 3.198070e-003, 2.792951e-003, - 2.393370e-003, 2.005082e-003, 1.635519e-003, 1.288480e-003, - 9.567046e-004, 6.365004e-004, 3.212361e-004, 5.928453e-006, - -3.154944e-004, -6.411000e-004, -9.740552e-004, -1.312533e-003, - -1.650670e-003, -1.975120e-003, -2.302251e-003, -2.634752e-003, - -2.972395e-003, -3.317597e-003, -3.672522e-003, -4.038256e-003, - -4.410585e-003, -4.787997e-003, -5.165835e-003, -5.515990e-003, - -5.867216e-003, -6.218242e-003, -6.567719e-003, -6.910353e-003, - -7.247149e-003, -7.572990e-003, -7.889551e-003, -8.193243e-003, - -8.481195e-003, -8.727862e-003, -8.956996e-003, -9.175427e-003, - -9.385148e-003, -9.585352e-003, -9.773606e-003, -9.949173e-003, - -1.011002e-002, -1.025693e-002, -1.039295e-002, -1.049508e-002, - -1.057427e-002, -1.065307e-002, -1.074220e-002, -1.084373e-002, - -1.095054e-002, -1.105919e-002, -1.116762e-002, -1.127459e-002, - -1.138394e-002, -1.147771e-002, -1.154217e-002, -1.160747e-002, - -1.167134e-002, -1.173498e-002, -1.179759e-002, -1.185616e-002, - -1.191271e-002, -1.196644e-002, -1.201577e-002, -1.205772e-002, - -1.205812e-002, -1.205706e-002, -1.205574e-002, -1.205392e-002, - -1.205244e-002, -1.204529e-002, -1.203073e-002, -1.201014e-002, - -1.197757e-002, -1.194330e-002, -1.187221e-002, -1.180150e-002, - -1.173956e-002, -1.168769e-002, -1.164754e-002, -1.161830e-002, - -1.159623e-002, -1.157804e-002, -1.156409e-002, -1.155810e-002, - -1.153615e-002, -1.151150e-002, -1.150891e-002, -1.152607e-002, - -1.156002e-002, -1.160697e-002, -1.166409e-002, -1.172874e-002, - -1.180173e-002, -1.188266e-002, -1.194855e-002, -1.198838e-002, - -1.203193e-002, -1.207790e-002, -1.212458e-002, -1.217171e-002, - -1.221373e-002, -1.224913e-002, -1.227811e-002, -1.230136e-002, - -1.230835e-002, -1.227079e-002, -1.222652e-002, -1.217982e-002, - -1.213951e-002, -1.211423e-002, -1.210894e-002, -1.212638e-002, - -1.216219e-002, -1.221750e-002, -1.228936e-002, -1.233243e-002, - -1.239153e-002, -1.246416e-002, -1.254977e-002, -1.264650e-002, - -1.275324e-002, -1.286705e-002, -1.299239e-002, -1.314277e-002, - -1.333006e-002, -1.351766e-002, -1.372490e-002, -1.396419e-002, - -1.423343e-002, -1.452615e-002, -1.484362e-002, -1.518574e-002, - -1.554928e-002, -1.593152e-002, -1.632979e-002, -1.672023e-002, - -1.710162e-002, -1.749700e-002, -1.790507e-002, -1.832401e-002, - -1.875083e-002, -1.918725e-002, -1.963176e-002, -2.007908e-002, - -2.053454e-002, -2.098924e-002, -2.142072e-002, -2.186073e-002, - -2.230760e-002, -2.275536e-002, -2.320168e-002, -2.364440e-002, - -2.408173e-002, -2.451069e-002, -2.493208e-002, -2.534058e-002, - -2.569365e-002, -2.603318e-002, -2.635640e-002, -2.666028e-002, - -2.694545e-002, -2.720776e-002, -2.744377e-002, -2.765074e-002, - -2.782437e-002, -2.796399e-002, -2.803225e-002, -2.805593e-002, - -2.804239e-002, -2.798377e-002, -2.789074e-002, -2.777740e-002, - -2.764849e-002, -2.750532e-002, -2.734751e-002, -2.717681e-002, - -2.697115e-002, -2.673534e-002, -2.649358e-002, -2.624620e-002, - -2.599445e-002, -2.573498e-002, -2.546694e-002, -2.519169e-002, - -2.491340e-002, -2.463827e-002, -2.435246e-002, -2.404021e-002, - -2.372551e-002, -2.340497e-002, -2.307568e-002, -2.274493e-002, - -2.240708e-002, -2.206356e-002, -2.171308e-002, -2.135277e-002, - -2.097535e-002, -2.054366e-002, -2.009561e-002, -1.963690e-002, - -1.917174e-002, -1.870415e-002, -1.823301e-002, -1.776092e-002, - -1.728131e-002, -1.679752e-002, -1.630996e-002, -1.578492e-002, - -1.525017e-002, -1.471318e-002, -1.418124e-002, -1.365779e-002, - -1.315225e-002, -1.266586e-002, -1.219142e-002, -1.172513e-002, - -1.126747e-002, -1.079278e-002, -1.030947e-002, -9.834041e-003, - -9.368302e-003, -8.910686e-003, -8.458190e-003, -8.015212e-003, - -7.581713e-003, -7.153917e-003, -6.738466e-003, -6.321619e-003, - -5.887598e-003, -5.464705e-003, -5.051872e-003, -4.648289e-003, - -4.256226e-003, -3.879666e-003, -3.519215e-003, -3.167309e-003, - -2.824317e-003, -2.481970e-003, -2.118697e-003, -1.761598e-003, - -1.412446e-003, -1.070596e-003, -7.359840e-004, -4.031155e-004, - -7.382592e-005, 2.493033e-004, 5.663020e-004, 8.801216e-004, - 1.220723e-003, 1.555849e-003, 1.886527e-003, 2.205650e-003, - 2.517195e-003, 2.825822e-003, 3.130473e-003, 3.422240e-003, - 3.699429e-003, 3.962649e-003, 4.238399e-003, 4.510396e-003, - 4.770329e-003, 5.016804e-003, 5.245843e-003, 5.459187e-003, - 5.658271e-003, 5.835690e-003, 5.997559e-003, 6.150148e-003, - 6.317235e-003, 6.496123e-003, 6.669149e-003, 6.840892e-003, - 7.011295e-003, 7.183894e-003, 7.363536e-003, 7.553048e-003, - 7.747728e-003, 7.957222e-003, 8.185795e-003, 8.444409e-003, - 8.696779e-003, 8.947623e-003, 9.205545e-003, 9.495967e-003, - 9.815850e-003, 1.016410e-002, 1.053676e-002, 1.093401e-002, - 1.135721e-002, 1.183538e-002, 1.233183e-002, 1.284113e-002, - 1.336313e-002, 1.389175e-002, 1.443221e-002, 1.498371e-002, - 1.554731e-002, 1.613216e-002, 1.672906e-002, 1.736281e-002, - 1.800920e-002, 1.865990e-002, 1.931648e-002, 1.998390e-002, - 2.068246e-002, 2.139496e-002, 2.210936e-002, 2.282174e-002, - 2.353467e-002, 2.426263e-002, 2.500194e-002, 2.573484e-002, - 2.644758e-002, 2.713721e-002, 2.780455e-002, 2.845263e-002, - 2.907733e-002, 2.967072e-002, 3.023386e-002, 3.076778e-002, - 3.126826e-002, 3.171708e-002, 3.213243e-002, 3.252981e-002, - 3.291081e-002, 3.328521e-002, 3.364506e-002, 3.398167e-002, - 3.430133e-002, 3.461760e-002, 3.495420e-002, 3.527415e-002, - 3.557752e-002, 3.585584e-002, 3.611807e-002, 3.637563e-002, - 3.661985e-002, 3.685572e-002, 3.707784e-002, 3.728932e-002, - 3.753044e-002, 3.777532e-002, 3.801211e-002, 3.823868e-002, - 3.845884e-002, 3.868194e-002, 3.890723e-002, 3.913044e-002, - 3.933410e-002, 3.951265e-002, 3.969467e-002, 3.986620e-002, - 4.000583e-002, 4.012215e-002, 4.021885e-002, 4.029810e-002, - 4.035742e-002, 4.039236e-002, 4.039712e-002, 4.036616e-002, - 4.033204e-002, 4.029648e-002, 4.022641e-002, 4.012608e-002, - 3.999744e-002, 3.984918e-002, 3.967742e-002, 3.949117e-002, - 3.928316e-002, 3.904585e-002, 3.879365e-002, 3.855934e-002, - 3.830877e-002, 3.803975e-002, 3.776626e-002, 3.747283e-002, - 3.715729e-002, 3.682571e-002, 3.648086e-002, 3.611114e-002, - 3.564106e-002, 3.506949e-002, 3.433176e-002, 3.343043e-002, - 3.237910e-002, 3.118928e-002, 2.986394e-002, 2.840973e-002, - 2.683723e-002, 2.514909e-002, 2.334923e-002, 2.149750e-002, - 1.957726e-002, 1.758677e-002, 1.553095e-002, 1.342743e-002, - 1.127428e-002, 9.082457e-003, 6.852831e-003, 4.600274e-003, - 2.330826e-003, 9.670992e-005, -2.107442e-003, -4.299697e-003, - -6.464254e-003, -8.591899e-003, -1.068140e-002, -1.271764e-002, - -1.470220e-002, -1.663056e-002, -1.848616e-002, -2.023605e-002, - -2.184191e-002, -2.334117e-002, -2.472527e-002, -2.598871e-002, - -2.713681e-002, -2.816199e-002, -2.904829e-002, -2.979090e-002, - -3.038971e-002, -3.082379e-002, -3.114503e-002, -3.143273e-002, - -3.170015e-002, -3.195930e-002, -3.222633e-002, -3.248056e-002, - -3.272020e-002, -3.295030e-002, -3.318250e-002, -3.340173e-002, - -3.354345e-002, -3.365604e-002, -3.375452e-002, -3.384938e-002, - -3.393122e-002, -3.400394e-002, -3.407440e-002, -3.413313e-002, - -3.417345e-002, -3.419318e-002, -3.415055e-002, -3.406551e-002, - -3.397012e-002, -3.387051e-002, -3.376835e-002, -3.365763e-002, - -3.354182e-002, -3.341585e-002, -3.327238e-002, -3.311810e-002, - -3.292451e-002, -3.264383e-002, -3.235724e-002, -3.206325e-002, - -3.174711e-002, -3.142888e-002, -3.110587e-002, -3.076615e-002, - -3.042550e-002, -3.008008e-002, -2.971628e-002, -2.925026e-002, - -2.879026e-002, -2.832373e-002, -2.784696e-002, -2.738232e-002, - -2.692980e-002, -2.648655e-002, -2.605672e-002, -2.562160e-002, - -2.517508e-002, -2.461720e-002, -2.404321e-002, -2.346606e-002, - -2.287759e-002, -2.222208e-002, -2.149028e-002, -2.070422e-002, - -1.987461e-002, -1.899694e-002, -1.807579e-002, -1.703213e-002, - -1.589604e-002, -1.474006e-002, -1.356661e-002, -1.236401e-002, - -1.115557e-002, -9.944304e-003, -8.711230e-003, -7.494120e-003, - -6.293101e-003, -5.044246e-003, -3.726440e-003, -2.437434e-003, - -1.155878e-003, 9.647393e-005, 1.291261e-003, 2.445642e-003, - 3.555132e-003, 4.633891e-003, 5.670950e-003, 6.696062e-003, - 7.798756e-003, 8.843143e-003, 9.824123e-003, 1.074537e-002, - 1.159825e-002, 1.238098e-002, 1.309580e-002, 1.374602e-002, - 1.432323e-002, 1.481198e-002, 1.537097e-002, 1.583122e-002, - 1.618339e-002, 1.643916e-002, 1.660044e-002, 1.670655e-002, - 1.676105e-002, 1.675012e-002, 1.669459e-002, 1.659681e-002, - 1.661749e-002, 1.663854e-002, 1.659712e-002, 1.650681e-002, - 1.636235e-002, 1.618850e-002, 1.597120e-002, 1.572013e-002, - 1.543482e-002, 1.511241e-002, 1.487642e-002, 1.470792e-002, - 1.450853e-002, 1.428672e-002, 1.402424e-002, 1.372127e-002, - 1.340156e-002, 1.305132e-002, 1.266906e-002, 1.227141e-002, - 1.191321e-002, 1.171112e-002, 1.148816e-002, 1.124837e-002, - 1.095445e-002, 1.060459e-002, 1.018300e-002, 9.717040e-003, - 9.206530e-003, 8.658816e-003, 8.082181e-003, 7.737456e-003, - 7.362713e-003, 6.935706e-003, 6.468731e-003, 5.961535e-003, - 5.401857e-003, 4.820511e-003, 4.189308e-003, 3.543445e-003, - 2.881612e-003, 2.432564e-003, 2.014114e-003, 1.546133e-003, - 1.061830e-003, 5.330611e-004, -3.362993e-005, -6.294411e-004, - -1.241024e-003, -1.885588e-003, -2.549440e-003, -3.026354e-003, - -3.377187e-003, -3.742061e-003, -4.124591e-003, -4.550839e-003, - -4.964029e-003, -5.386749e-003, -5.846407e-003, -6.332562e-003, - -6.866081e-003, -7.254485e-003, -7.450363e-003, -7.663864e-003, - -7.920798e-003, -8.217576e-003, -8.533979e-003, -8.889309e-003, - -9.311523e-003, -9.783102e-003, -1.029099e-002, -1.078664e-002, - -1.100014e-002, -1.125494e-002, -1.155534e-002, -1.187344e-002, - -1.223715e-002, -1.263889e-002, -1.305366e-002, -1.350251e-002, - -1.396097e-002, -1.446717e-002, -1.462256e-002, -1.477720e-002, - -1.490871e-002, -1.503539e-002, -1.521567e-002, -1.544783e-002, - -1.567307e-002, -1.591118e-002, -1.616483e-002, -1.645135e-002, - -1.643566e-002, -1.629165e-002, -1.618123e-002, -1.614083e-002, - -1.614478e-002, -1.621127e-002, -1.630524e-002, -1.645702e-002, - -1.665345e-002, -1.682803e-002, -1.684368e-002, -1.665494e-002, - -1.650930e-002, -1.642573e-002, -1.643562e-002, -1.648983e-002, - -1.658580e-002, -1.674169e-002, -1.693109e-002, -1.719313e-002, - -1.732301e-002, -1.703257e-002, -1.678137e-002, -1.655648e-002, - -1.628178e-002, -1.610941e-002, -1.595264e-002, -1.577218e-002, - -1.566355e-002, -1.559035e-002, -1.555006e-002, -1.497363e-002, - -1.444341e-002, -1.393114e-002, -1.345663e-002, -1.297505e-002, - -1.257072e-002, -1.220047e-002, -1.187661e-002, -1.160767e-002, - -1.141694e-002, -1.084774e-002, -1.018487e-002, -9.569694e-003, - -8.939467e-003, -8.287321e-003, -7.641859e-003, -7.056578e-003, - -6.394240e-003, -5.795823e-003, -5.263189e-003, -4.442910e-003, - -3.448303e-003, -2.535747e-003, -1.621275e-003, -7.260830e-004, - 1.375633e-004, 1.075411e-003, 1.965429e-003, 2.797509e-003, - 3.668721e-003, 4.648531e-003, 6.124067e-003, 7.634131e-003, - 9.165679e-003, 1.055493e-002, 1.193715e-002, 1.325220e-002, - 1.452959e-002, 1.563019e-002, 1.666391e-002, 1.764498e-002, - 1.914960e-002, 2.046899e-002, 2.167188e-002, 2.281378e-002, - 2.380969e-002, 2.478135e-002, 2.563907e-002, 2.648461e-002, - 2.719727e-002, 2.776752e-002, 2.892368e-002, 3.012721e-002, - 3.133186e-002, 3.250705e-002, 3.363869e-002, 3.486495e-002, - 3.586056e-002, 3.667342e-002, 3.725278e-002, 3.769292e-002, - 3.836079e-002, 3.930516e-002, 4.014423e-002, 4.085759e-002, - 4.135771e-002, 4.177264e-002, 4.202298e-002, 4.207538e-002, - 4.209631e-002, 4.182417e-002, 4.164579e-002, 4.170295e-002, - 4.160686e-002, 4.142726e-002, 4.097206e-002, 4.033538e-002, - 3.939414e-002, 3.839591e-002, 3.748286e-002, 3.653628e-002, - 3.545324e-002, 3.442050e-002, 3.292972e-002, 3.116934e-002, - 2.884119e-002, 2.620559e-002, 2.318819e-002, 1.989661e-002, - 1.709863e-002, 1.416076e-002, 1.091104e-002, 7.880190e-003, - 5.002088e-003, 2.084999e-003, -7.328482e-004, -3.575068e-003, - -7.438734e-003, -1.187920e-002, -1.685218e-002, -2.263953e-002, - -2.924758e-002, -3.557124e-002, -4.171639e-002, -4.706453e-002, - -5.128701e-002, -5.329986e-002, -5.537853e-002, -5.836344e-002, - -6.301186e-002, -6.957576e-002, -7.841475e-002, -9.388745e-002, - -1.040602e-001, -1.139593e-001, -1.247853e-001, -1.369840e-001, - -1.488220e-001, -1.587383e-001, -1.663195e-001, -1.708929e-001, - -1.675713e-001, 6.724573e-002, 1.666673e-001, 1.996120e-001, - 2.069068e-001, 2.043245e-001, 1.926459e-001, 1.199260e-001, - 9.215341e-002, 6.162724e-002, 3.163523e-002, 0.000000e+000 - }, - { - 0.000000e+000, -4.856922e-003, -7.797558e-003, -9.800473e-003, - -1.121839e-002, -1.223044e-002, -1.306480e-002, -1.417176e-002, - -1.547379e-002, -1.512333e-002, -1.392695e-002, -1.220049e-002, - -1.004090e-002, -7.701016e-003, -5.350951e-003, -3.119363e-003, - -1.152351e-003, 3.966756e-004, 1.478055e-003, 2.412627e-003, - 3.273140e-003, 4.024938e-003, 4.665786e-003, 5.210662e-003, - 5.665931e-003, 6.055951e-003, 6.384196e-003, 6.655974e-003, - 6.895986e-003, 7.106323e-003, 7.308845e-003, 7.464070e-003, - 7.542954e-003, 7.575636e-003, 7.564101e-003, 7.516342e-003, - 7.432449e-003, 7.308473e-003, 7.152465e-003, 6.978991e-003, - 6.767019e-003, 6.484863e-003, 6.086415e-003, 5.582920e-003, - 4.961080e-003, 4.205145e-003, 3.051051e-003, 1.058027e-003, - -8.547807e-004, -1.790344e-003, -2.370950e-003, -2.877308e-003, - -3.331628e-003, -3.712797e-003, -4.022733e-003, -4.260729e-003, - -4.433593e-003, -4.553958e-003, -4.617323e-003, -4.628564e-003, - -4.601372e-003, -4.525079e-003, -4.429871e-003, -4.314367e-003, - -4.192840e-003, -4.077131e-003, -3.962830e-003, -3.862601e-003, - -3.765819e-003, -3.694589e-003, -3.635897e-003, -3.601307e-003, - -3.569543e-003, -3.541411e-003, -3.503562e-003, -3.455397e-003, - -3.392916e-003, -3.323962e-003, -3.248919e-003, -3.182511e-003, - -3.123196e-003, -3.099703e-003, -3.110497e-003, -3.181069e-003, - -3.275963e-003, -3.396936e-003, -3.532542e-003, -3.672277e-003, - -3.815999e-003, -3.889596e-003, -3.894224e-003, -3.822332e-003, - -3.691888e-003, -3.518700e-003, -3.300783e-003, -3.046091e-003, - -2.757129e-003, -2.444887e-003, -2.117053e-003, -1.773479e-003, - -1.433625e-003, -1.099302e-003, -7.770000e-004, -4.867924e-004, - -2.445902e-004, -7.630767e-005, 8.833000e-006, 7.241074e-005, - 1.344135e-004, 2.047474e-004, 2.854939e-004, 3.695760e-004, - 4.624777e-004, 5.617435e-004, 6.533292e-004, 7.506734e-004, - 8.589372e-004, 9.746040e-004, 1.098536e-003, 1.233180e-003, - 1.370130e-003, 1.507251e-003, 1.647509e-003, 1.770882e-003, - 1.866994e-003, 1.953834e-003, 2.026856e-003, 2.080389e-003, - 2.127840e-003, 2.166153e-003, 2.192347e-003, 2.214957e-003, - 2.237369e-003, 2.256441e-003, 2.270853e-003, 2.295213e-003, - 2.326118e-003, 2.356767e-003, 2.396810e-003, 2.444491e-003, - 2.494487e-003, 2.547283e-003, 2.610851e-003, 2.676518e-003, - 2.740894e-003, 2.811856e-003, 2.886097e-003, 2.955066e-003, - 3.018135e-003, 3.078140e-003, 3.130240e-003, 3.177071e-003, - 3.218638e-003, 3.254481e-003, 3.286119e-003, 3.315053e-003, - 3.333162e-003, 3.335136e-003, 3.320827e-003, 3.292995e-003, - 3.249629e-003, 3.198801e-003, 3.141942e-003, 3.078167e-003, - 3.008139e-003, 2.939895e-003, 2.869716e-003, 2.798165e-003, - 2.729892e-003, 2.666711e-003, 2.606649e-003, 2.545901e-003, - 2.480095e-003, 2.407263e-003, 2.329681e-003, 2.255950e-003, - 2.184141e-003, 2.116196e-003, 2.044853e-003, 1.970801e-003, - 1.890859e-003, 1.801009e-003, 1.684110e-003, 1.540080e-003, - 1.372006e-003, 1.194146e-003, 1.000811e-003, 7.870687e-004, - 5.588102e-004, 3.167153e-004, 6.412076e-005, -2.042919e-004, - -4.845298e-004, -7.748176e-004, -1.070353e-003, -1.363857e-003, - -1.661607e-003, -1.965822e-003, -2.275591e-003, -2.587123e-003, - -2.900902e-003, -3.219790e-003, -3.539381e-003, -3.857585e-003, - -4.170251e-003, -4.472438e-003, -4.757131e-003, -5.026918e-003, - -5.277096e-003, -5.512495e-003, -5.738517e-003, -5.962576e-003, - -6.185068e-003, -6.404012e-003, -6.621647e-003, -6.836731e-003, - -7.038834e-003, -7.235891e-003, -7.423240e-003, -7.600055e-003, - -7.769626e-003, -7.940192e-003, -8.116458e-003, -8.301150e-003, - -8.491787e-003, -8.684934e-003, -8.859752e-003, -9.029611e-003, - -9.189742e-003, -9.341093e-003, -9.484292e-003, -9.618507e-003, - -9.745354e-003, -9.862700e-003, -9.971128e-003, -1.007033e-002, - -1.014919e-002, -1.021342e-002, -1.026728e-002, -1.030661e-002, - -1.033114e-002, -1.034255e-002, -1.033965e-002, -1.032033e-002, - -1.028365e-002, -1.022803e-002, -1.014893e-002, -1.004039e-002, - -9.909934e-003, -9.761733e-003, -9.606863e-003, -9.450117e-003, - -9.295345e-003, -9.143226e-003, -8.993239e-003, -8.850340e-003, - -8.707891e-003, -8.556398e-003, -8.406264e-003, -8.257916e-003, - -8.111991e-003, -7.969978e-003, -7.836645e-003, -7.712076e-003, - -7.591351e-003, -7.472876e-003, -7.354239e-003, -7.219640e-003, - -7.082564e-003, -6.940301e-003, -6.791794e-003, -6.619163e-003, - -6.410814e-003, -6.166066e-003, -5.885576e-003, -5.565149e-003, - -5.206569e-003, -4.802120e-003, -4.364344e-003, -3.898753e-003, - -3.408761e-003, -2.908409e-003, -2.403541e-003, -1.892103e-003, - -1.374557e-003, -8.509448e-004, -3.253861e-004, 2.105536e-004, - 7.491376e-004, 1.278509e-003, 1.788779e-003, 2.279869e-003, - 2.755211e-003, 3.221253e-003, 3.678170e-003, 4.129796e-003, - 4.573108e-003, 5.013763e-003, 5.454052e-003, 5.885033e-003, - 6.308304e-003, 6.724194e-003, 7.131750e-003, 7.533505e-003, - 7.927138e-003, 8.314160e-003, 8.692192e-003, 9.061267e-003, - 9.428853e-003, 9.786954e-003, 1.013314e-002, 1.046838e-002, - 1.079260e-002, 1.110901e-002, 1.142016e-002, 1.172804e-002, - 1.203487e-002, 1.232962e-002, 1.261798e-002, 1.288854e-002, - 1.313712e-002, 1.336320e-002, 1.356544e-002, 1.374656e-002, - 1.391929e-002, 1.409042e-002, 1.425882e-002, 1.442357e-002, - 1.458734e-002, 1.474970e-002, 1.490055e-002, 1.503533e-002, - 1.515102e-002, 1.525213e-002, 1.534316e-002, 1.542383e-002, - 1.549723e-002, 1.556430e-002, 1.562707e-002, 1.568857e-002, - 1.574861e-002, 1.580736e-002, 1.586792e-002, 1.593153e-002, - 1.600000e-002, 1.607358e-002, 1.615429e-002, 1.624424e-002, - 1.634407e-002, 1.645955e-002, 1.658548e-002, 1.671757e-002, - 1.684581e-002, 1.696048e-002, 1.705468e-002, 1.712895e-002, - 1.718580e-002, 1.722492e-002, 1.724802e-002, 1.725705e-002, - 1.725352e-002, 1.724027e-002, 1.721765e-002, 1.718597e-002, - 1.714919e-002, 1.710594e-002, 1.705622e-002, 1.697404e-002, - 1.684391e-002, 1.666913e-002, 1.644566e-002, 1.617152e-002, - 1.584866e-002, 1.548343e-002, 1.507842e-002, 1.463441e-002, - 1.415418e-002, 1.363896e-002, 1.309270e-002, 1.251808e-002, - 1.191517e-002, 1.128679e-002, 1.063423e-002, 9.958812e-003, - 9.261276e-003, 8.545314e-003, 7.813906e-003, 7.068469e-003, - 6.313799e-003, 5.552226e-003, 4.784047e-003, 4.013348e-003, - 3.241349e-003, 2.476287e-003, 1.720264e-003, 9.750474e-004, - 2.444405e-004, -4.656470e-004, -1.156831e-003, -1.827323e-003, - -2.471959e-003, -3.089240e-003, -3.675171e-003, -4.225426e-003, - -4.739292e-003, -5.211057e-003, -5.642171e-003, -6.024289e-003, - -6.356913e-003, -6.636672e-003, -6.861995e-003, -7.029255e-003, - -7.135036e-003, -7.174809e-003, -7.160690e-003, -7.118426e-003, - -7.050900e-003, -6.962384e-003, -6.848482e-003, -6.715869e-003, - -6.570266e-003, -6.414751e-003, -6.246789e-003, -6.069580e-003, - -5.882607e-003, -5.683664e-003, -5.479210e-003, -5.274834e-003, - -5.067081e-003, -4.864037e-003, -4.666441e-003, -4.471345e-003, - -4.276821e-003, -4.085980e-003, -3.898442e-003, -3.710466e-003, - -3.521978e-003, -3.333253e-003, -3.143460e-003, -2.955023e-003, - -2.763748e-003, -2.566963e-003, -2.361774e-003, -2.155293e-003, - -1.951647e-003, -1.756880e-003, -1.576616e-003, -1.408484e-003, - -1.250337e-003, -1.107480e-003, -9.904570e-004, -8.935136e-004, - -8.218025e-004, -7.796171e-004, -7.697472e-004, -8.119122e-004, - -9.368835e-004, -1.139844e-003, -1.413483e-003, -1.754786e-003, - -2.158373e-003, -2.618482e-003, -3.138798e-003, -3.717561e-003, - -4.349897e-003, -5.034746e-003, -5.768179e-003, -6.545524e-003, - -7.364606e-003, -8.231856e-003, -9.140267e-003, -1.008919e-002, - -1.107273e-002, -1.208841e-002, -1.313190e-002, -1.420299e-002, - -1.530630e-002, -1.643387e-002, -1.758268e-002, -1.874789e-002, - -1.992210e-002, -2.110670e-002, -2.230096e-002, -2.350307e-002, - -2.471145e-002, -2.591927e-002, -2.712059e-002, -2.831095e-002, - -2.948783e-002, -3.065368e-002, -3.180461e-002, -3.294179e-002, - -3.405473e-002, -3.514112e-002, -3.620186e-002, -3.723925e-002, - -3.825113e-002, -3.923299e-002, -4.018362e-002, -4.109978e-002, - -4.197676e-002, -4.282013e-002, -4.362876e-002, -4.439862e-002, - -4.513344e-002, -4.582962e-002, -4.648142e-002, -4.708788e-002, - -4.764182e-002, -4.814823e-002, -4.859859e-002, -4.900013e-002, - -4.935584e-002, -4.966339e-002, -4.992259e-002, -5.013327e-002, - -5.029317e-002, -5.040240e-002, -5.045486e-002, -5.044740e-002, - -5.037674e-002, -5.023967e-002, -5.004070e-002, -4.978462e-002, - -4.947275e-002, -4.911440e-002, -4.861781e-002, -4.782472e-002, - -4.674256e-002, -4.538863e-002, -4.377555e-002, -4.191599e-002, - -3.982134e-002, -3.751253e-002, -3.502048e-002, -3.235309e-002, - -2.952736e-002, -2.655978e-002, -2.345772e-002, -2.023432e-002, - -1.690001e-002, -1.348255e-002, -9.991049e-003, -6.426447e-003, - -2.815340e-003, 8.306865e-004, 4.497869e-003, 8.178001e-003, - 1.185789e-002, 1.552592e-002, 1.915924e-002, 2.274017e-002, - 2.625087e-002, 2.967588e-002, 3.299736e-002, 3.618726e-002, - 3.923067e-002, 4.211031e-002, 4.481045e-002, 4.731890e-002, - 4.961770e-002, 5.169624e-002, 5.353633e-002, 5.512204e-002, - 5.643853e-002, 5.746098e-002, 5.831167e-002, 5.908479e-002, - 5.979136e-002, 6.042757e-002, 6.099349e-002, 6.149216e-002, - 6.193154e-002, 6.230856e-002, 6.263745e-002, 6.290598e-002, - 6.310869e-002, 6.324621e-002, 6.333105e-002, 6.335752e-002, - 6.333506e-002, 6.326651e-002, 6.315326e-002, 6.298552e-002, - 6.278615e-002, 6.253807e-002, 6.224730e-002, 6.192504e-002, - 6.157565e-002, 6.119687e-002, 6.078473e-002, 6.033113e-002, - 5.983083e-002, 5.927852e-002, 5.868971e-002, 5.805212e-002, - 5.736492e-002, 5.662728e-002, 5.584500e-002, 5.502872e-002, - 5.417635e-002, 5.328846e-002, 5.234840e-002, 5.136024e-002, - 5.032991e-002, 4.926755e-002, 4.816927e-002, 4.703306e-002, - 4.585954e-002, 4.465075e-002, 4.340807e-002, 4.214199e-002, - 4.085785e-002, 3.956025e-002, 3.824528e-002, 3.693187e-002, - 3.560361e-002, 3.426674e-002, 3.291969e-002, 3.157632e-002, - 3.022915e-002, 2.887328e-002, 2.751569e-002, 2.615730e-002, - 2.469161e-002, 2.301826e-002, 2.113143e-002, 1.904336e-002, - 1.676286e-002, 1.430226e-002, 1.168306e-002, 8.909044e-003, - 5.992595e-003, 2.952766e-003, -1.892900e-004, -3.397965e-003, - -6.669562e-003, -9.999171e-003, -1.337929e-002, -1.679456e-002, - -2.021883e-002, -2.363413e-002, -2.702580e-002, -3.037485e-002, - -3.363979e-002, -3.678325e-002, -3.979290e-002, -4.266420e-002, - -4.538687e-002, -4.796075e-002, -5.037204e-002, -5.260844e-002, - -5.467233e-002, -5.653789e-002, -5.820016e-002, -5.964370e-002, - -6.084748e-002, -6.181373e-002, -6.254969e-002, -6.304255e-002, - -6.327373e-002, -6.324151e-002, -6.294476e-002, -6.236275e-002, - -6.148544e-002, -6.033713e-002, -5.903884e-002, -5.765950e-002, - -5.620022e-002, -5.467904e-002, -5.311244e-002, -5.150894e-002, - -4.987509e-002, -4.819521e-002, -4.646704e-002, -4.470178e-002, - -4.288423e-002, -4.103245e-002, -3.916343e-002, -3.727568e-002, - -3.536237e-002, -3.342494e-002, -3.148087e-002, -2.953602e-002, - -2.759655e-002, -2.566318e-002, -2.371155e-002, -2.174901e-002, - -1.979182e-002, -1.786487e-002, -1.596224e-002, -1.409055e-002, - -1.224699e-002, -1.043538e-002, -8.661397e-003, -6.916669e-003, - -5.196874e-003, -3.504715e-003, -1.841873e-003, -1.780654e-004, - 1.484192e-003, 3.149436e-003, 4.808750e-003, 6.458432e-003, - 8.106839e-003, 9.744319e-003, 1.137902e-002, 1.303264e-002, - 1.466682e-002, 1.626450e-002, 1.783889e-002, 1.937177e-002, - 2.087280e-002, 2.235456e-002, 2.380537e-002, 2.521882e-002, - 2.658731e-002, 2.797466e-002, 2.933828e-002, 3.067543e-002, - 3.187833e-002, 3.279689e-002, 3.344084e-002, 3.384055e-002, - 3.401162e-002, 3.396640e-002, 3.370251e-002, 3.325991e-002, - 3.261960e-002, 3.181643e-002, 3.085165e-002, 2.973501e-002, - 2.848709e-002, 2.710628e-002, 2.559623e-002, 2.396612e-002, - 2.223702e-002, 2.045567e-002, 1.863930e-002, 1.675435e-002, - 1.480918e-002, 1.281351e-002, 1.080206e-002, 8.788696e-003, - 6.768468e-003, 4.761420e-003, 2.791298e-003, 8.870138e-004, - -8.944459e-004, -2.607997e-003, -4.247135e-003, -5.799043e-003, - -7.232417e-003, -8.517655e-003, -9.649423e-003, -1.063188e-002, - -1.143942e-002, -1.208886e-002, -1.248187e-002, -1.266355e-002, - -1.263433e-002, -1.237607e-002, -1.195304e-002, -1.152056e-002, - -1.109690e-002, -1.067519e-002, -1.024217e-002, -9.818682e-003, - -9.357641e-003, -8.875726e-003, -8.413351e-003, -7.968957e-003, - -7.538938e-003, -7.122375e-003, -6.714695e-003, -6.319160e-003, - -5.932280e-003, -5.570293e-003, -5.172370e-003, -4.709242e-003, - -4.245202e-003, -3.783897e-003, -3.341789e-003, -2.904934e-003, - -2.482400e-003, -2.051428e-003, -1.613131e-003, -1.190421e-003, - -7.410203e-004, -2.403449e-004, 2.371472e-004, 6.749396e-004, - 1.081014e-003, 1.440097e-003, 1.748999e-003, 1.999745e-003, - 2.199480e-003, 2.360461e-003, 2.500574e-003, 2.727689e-003, - 2.902678e-003, 3.038703e-003, 3.130206e-003, 3.191180e-003, - 3.235709e-003, 3.242823e-003, 3.214985e-003, 3.147778e-003, - 3.043213e-003, 3.043040e-003, 3.058252e-003, 3.027769e-003, - 2.963041e-003, 2.873734e-003, 2.777619e-003, 2.659181e-003, - 2.527951e-003, 2.379990e-003, 2.215818e-003, 2.115075e-003, - 2.041910e-003, 1.984158e-003, 1.926378e-003, 1.842009e-003, - 1.720719e-003, 1.577646e-003, 1.416919e-003, 1.269385e-003, - 1.076693e-003, 9.256708e-004, 8.693585e-004, 7.933710e-004, - 6.953306e-004, 5.967592e-004, 4.455451e-004, 2.500369e-004, - -3.055137e-006, -2.852052e-004, -5.961842e-004, -9.679909e-004, - -1.192123e-003, -1.458065e-003, -1.772853e-003, -2.152422e-003, - -2.549979e-003, -2.978306e-003, -3.459624e-003, -4.001965e-003, - -4.573486e-003, -5.154011e-003, -5.569628e-003, -5.991373e-003, - -6.475385e-003, -6.973923e-003, -7.456755e-003, -7.939632e-003, - -8.485955e-003, -9.064276e-003, -9.641666e-003, -1.021734e-002, - -1.072896e-002, -1.121748e-002, -1.174854e-002, -1.230922e-002, - -1.287446e-002, -1.347310e-002, -1.410634e-002, -1.474239e-002, - -1.537940e-002, -1.607671e-002, -1.669049e-002, -1.715158e-002, - -1.760523e-002, -1.805979e-002, -1.850653e-002, -1.896897e-002, - -1.947440e-002, -1.996283e-002, -2.044566e-002, -2.095126e-002, - -2.141347e-002, -2.169484e-002, -2.197146e-002, -2.224554e-002, - -2.261821e-002, -2.295843e-002, -2.327421e-002, -2.355856e-002, - -2.384337e-002, -2.408142e-002, -2.435257e-002, -2.432310e-002, - -2.429629e-002, -2.428195e-002, -2.431411e-002, -2.439305e-002, - -2.442885e-002, -2.444895e-002, -2.451547e-002, -2.450285e-002, - -2.444349e-002, -2.423158e-002, -2.387136e-002, -2.333306e-002, - -2.262491e-002, -2.184613e-002, -2.092344e-002, -1.975534e-002, - -1.849346e-002, -1.712376e-002, -1.562661e-002, -1.378032e-002, - -1.169451e-002, -9.561633e-003, -7.294840e-003, -4.877750e-003, - -2.331490e-003, 1.918740e-004, 2.860863e-003, 5.597581e-003, - 8.310346e-003, 1.115764e-002, 1.419641e-002, 1.715427e-002, - 2.006114e-002, 2.316149e-002, 2.624645e-002, 2.939548e-002, - 3.258940e-002, 3.574167e-002, 3.885196e-002, 4.198922e-002, - 4.531407e-002, 4.857910e-002, 5.187097e-002, 5.495274e-002, - 5.773757e-002, 6.027281e-002, 6.251270e-002, 6.440582e-002, - 6.607591e-002, 6.746395e-002, 6.863296e-002, 6.951023e-002, - 7.001863e-002, 7.018182e-002, 6.992285e-002, 6.917695e-002, - 6.813385e-002, 6.681020e-002, 6.546399e-002, 6.383476e-002, - 6.208695e-002, 6.000727e-002, 5.764712e-002, 5.486842e-002, - 5.164122e-002, 4.821130e-002, 4.432303e-002, 4.041151e-002, - 3.600239e-002, 3.132369e-002, 2.638080e-002, 2.158386e-002, - 1.661232e-002, 1.170591e-002, 6.543272e-003, 1.020782e-003, - -4.668697e-003, -1.063140e-002, -1.666677e-002, -2.298555e-002, - -2.949187e-002, -3.568009e-002, -4.132885e-002, -4.648578e-002, - -5.071933e-002, -5.385082e-002, -5.590550e-002, -5.723617e-002, - -5.863943e-002, -6.092091e-002, -6.333213e-002, -6.650548e-002, - -7.035511e-002, -7.383440e-002, -7.883410e-002, -8.527805e-002, - -9.176060e-002, -9.663816e-002, -9.912976e-002, -9.575496e-002, - -7.726779e-002, -6.869294e-002, -6.656362e-002, -6.449412e-002, - -6.524034e-002, -6.933107e-002, -7.351112e-002, -7.429125e-002, - -7.030367e-002, -6.018157e-002, -4.679271e-002, 1.422884e-001, - 1.575366e-001, 1.583155e-001, 1.564559e-001, 1.530732e-001, - 1.468045e-001, 1.367844e-001, 1.231302e-001, 1.067971e-001, - 8.476677e-002, -1.448345e-001, -4.031660e-003, -4.795623e-002, - -7.588450e-002, -9.669753e-002, -1.090431e-001, 5.082690e-003, - 8.394798e-003, 7.591663e-003, 4.277548e-003, 0.000000e+000 - }, - { - 0.000000e+000, -4.713623e-003, -8.461471e-003, -1.131121e-002, - -1.352718e-002, -1.522101e-002, -1.646142e-002, -1.660101e-002, - -1.594242e-002, -1.616667e-002, -1.698847e-002, -1.865810e-002, - -2.058347e-002, -2.252969e-002, -2.438746e-002, -2.604271e-002, - -2.743505e-002, -2.848094e-002, -2.912798e-002, -2.957346e-002, - -2.986101e-002, -3.013659e-002, -3.037188e-002, -3.045830e-002, - -3.039691e-002, -3.020734e-002, -2.989494e-002, -2.946942e-002, - -2.894221e-002, -2.834007e-002, -2.765181e-002, -2.693955e-002, - -2.629918e-002, -2.558006e-002, -2.478699e-002, -2.391490e-002, - -2.297952e-002, -2.198773e-002, -2.094183e-002, -1.986793e-002, - -1.874830e-002, -1.755636e-002, -1.642267e-002, -1.514948e-002, - -1.370367e-002, -1.205764e-002, -9.795483e-003, -6.185665e-003, - -2.688713e-003, -7.178430e-004, 7.077328e-004, 2.041205e-003, - 3.132938e-003, 4.113482e-003, 4.994092e-003, 5.801635e-003, - 6.540405e-003, 7.212934e-003, 7.834690e-003, 8.406193e-003, - 8.938129e-003, 9.441840e-003, 9.823566e-003, 1.012927e-002, - 1.043522e-002, 1.075410e-002, 1.109851e-002, 1.146882e-002, - 1.188344e-002, 1.233604e-002, 1.283849e-002, 1.339243e-002, - 1.390364e-002, 1.434256e-002, 1.478471e-002, 1.524706e-002, - 1.572374e-002, 1.620846e-002, 1.669868e-002, 1.719232e-002, - 1.768109e-002, 1.816622e-002, 1.864482e-002, 1.900531e-002, - 1.937295e-002, 1.973439e-002, 2.009582e-002, 2.045399e-002, - 2.079784e-002, 2.109825e-002, 2.132762e-002, 2.151008e-002, - 2.163928e-002, 2.160883e-002, 2.153013e-002, 2.142231e-002, - 2.129885e-002, 2.115756e-002, 2.100997e-002, 2.087045e-002, - 2.073200e-002, 2.060230e-002, 2.049016e-002, 2.030815e-002, - 2.013726e-002, 2.001514e-002, 1.995525e-002, 1.993193e-002, - 1.991291e-002, 1.991085e-002, 1.991890e-002, 1.994203e-002, - 1.997453e-002, 1.996590e-002, 1.988990e-002, 1.981695e-002, - 1.975862e-002, 1.970583e-002, 1.965604e-002, 1.961394e-002, - 1.956972e-002, 1.952275e-002, 1.947662e-002, 1.938495e-002, - 1.918864e-002, 1.898158e-002, 1.876022e-002, 1.852217e-002, - 1.828555e-002, 1.805056e-002, 1.781089e-002, 1.757448e-002, - 1.734555e-002, 1.710835e-002, 1.677672e-002, 1.645448e-002, - 1.614119e-002, 1.582916e-002, 1.552408e-002, 1.522972e-002, - 1.494276e-002, 1.465956e-002, 1.439069e-002, 1.412751e-002, - 1.380212e-002, 1.346673e-002, 1.314077e-002, 1.281616e-002, - 1.249071e-002, 1.216388e-002, 1.183735e-002, 1.151084e-002, - 1.118527e-002, 1.086246e-002, 1.050016e-002, 1.010668e-002, - 9.706162e-003, 9.311423e-003, 8.921922e-003, 8.538639e-003, - 8.156564e-003, 7.783310e-003, 7.418576e-003, 7.065247e-003, - 6.699417e-003, 6.307754e-003, 5.921926e-003, 5.549213e-003, - 5.195271e-003, 4.858417e-003, 4.536362e-003, 4.225104e-003, - 3.918104e-003, 3.615698e-003, 3.316930e-003, 2.985200e-003, - 2.666817e-003, 2.365532e-003, 2.073442e-003, 1.790043e-003, - 1.513109e-003, 1.244039e-003, 9.693134e-004, 6.904532e-004, - 4.080493e-004, 1.031503e-004, -2.017061e-004, -5.055182e-004, - -8.028671e-004, -1.086190e-003, -1.354927e-003, -1.620734e-003, - -1.879273e-003, -2.124027e-003, -2.359218e-003, -2.595341e-003, - -2.832047e-003, -3.054551e-003, -3.269015e-003, -3.472344e-003, - -3.664995e-003, -3.851323e-003, -4.032000e-003, -4.201020e-003, - -4.358556e-003, -4.510027e-003, -4.659001e-003, -4.799358e-003, - -4.925300e-003, -5.042488e-003, -5.153462e-003, -5.266591e-003, - -5.385103e-003, -5.506836e-003, -5.629359e-003, -5.750630e-003, - -5.875117e-003, -5.994131e-003, -6.109630e-003, -6.229002e-003, - -6.355586e-003, -6.495816e-003, -6.654872e-003, -6.835951e-003, - -7.036619e-003, -7.252827e-003, -7.481668e-003, -7.720512e-003, - -7.964326e-003, -8.215241e-003, -8.473052e-003, -8.735194e-003, - -9.005095e-003, -9.281891e-003, -9.566089e-003, -9.854700e-003, - -1.014099e-002, -1.043066e-002, -1.072766e-002, -1.103076e-002, - -1.133607e-002, -1.163864e-002, -1.194147e-002, -1.223991e-002, - -1.253343e-002, -1.282155e-002, -1.309455e-002, -1.334777e-002, - -1.358747e-002, -1.381884e-002, -1.404280e-002, -1.426584e-002, - -1.448941e-002, -1.471681e-002, -1.495310e-002, -1.519490e-002, - -1.543585e-002, -1.567320e-002, -1.591181e-002, -1.615343e-002, - -1.640047e-002, -1.665469e-002, -1.691570e-002, -1.718174e-002, - -1.745506e-002, -1.773033e-002, -1.800739e-002, -1.826983e-002, - -1.853201e-002, -1.879460e-002, -1.905230e-002, -1.931746e-002, - -1.959255e-002, -1.987790e-002, -2.016933e-002, -2.046161e-002, - -2.075402e-002, -2.103312e-002, -2.130641e-002, -2.157721e-002, - -2.184507e-002, -2.209356e-002, -2.231169e-002, -2.249859e-002, - -2.265625e-002, -2.278740e-002, -2.288986e-002, -2.295752e-002, - -2.299217e-002, -2.300748e-002, -2.301311e-002, -2.300829e-002, - -2.300031e-002, -2.298415e-002, -2.295754e-002, -2.292168e-002, - -2.287775e-002, -2.281715e-002, -2.273690e-002, -2.264308e-002, - -2.253608e-002, -2.241274e-002, -2.227574e-002, -2.212487e-002, - -2.195930e-002, -2.177856e-002, -2.158219e-002, -2.137023e-002, - -2.114137e-002, -2.089585e-002, -2.063841e-002, -2.036878e-002, - -2.008531e-002, -1.979044e-002, -1.948031e-002, -1.915033e-002, - -1.880193e-002, -1.843711e-002, -1.806277e-002, -1.768532e-002, - -1.730534e-002, -1.692645e-002, -1.654619e-002, -1.616543e-002, - -1.579351e-002, -1.542930e-002, -1.508147e-002, -1.475115e-002, - -1.444791e-002, -1.417352e-002, -1.392940e-002, -1.370197e-002, - -1.348476e-002, -1.327603e-002, -1.307339e-002, -1.287541e-002, - -1.268246e-002, -1.249538e-002, -1.231546e-002, -1.214748e-002, - -1.198210e-002, -1.181727e-002, -1.165219e-002, -1.148354e-002, - -1.131155e-002, -1.113299e-002, -1.094408e-002, -1.074492e-002, - -1.053940e-002, -1.033945e-002, -1.012520e-002, -9.900338e-003, - -9.673028e-003, -9.455389e-003, -9.249096e-003, -9.050679e-003, - -8.863586e-003, -8.685030e-003, -8.517968e-003, -8.380234e-003, - -8.248764e-003, -8.123202e-003, -8.001716e-003, -7.882832e-003, - -7.762558e-003, -7.640104e-003, -7.519018e-003, -7.371249e-003, - -7.183817e-003, -6.977688e-003, -6.738099e-003, -6.457487e-003, - -6.141583e-003, -5.786206e-003, -5.390729e-003, -4.956735e-003, - -4.487566e-003, -3.985677e-003, -3.452805e-003, -2.906289e-003, - -2.343574e-003, -1.753622e-003, -1.139323e-003, -4.978658e-004, - 1.694942e-004, 8.523953e-004, 1.554296e-003, 2.271666e-003, - 2.993467e-003, 3.698789e-003, 4.364003e-003, 5.013530e-003, - 5.654338e-003, 6.287922e-003, 6.907636e-003, 7.513205e-003, - 8.101611e-003, 8.674067e-003, 9.225401e-003, 9.752114e-003, - 1.021955e-002, 1.066488e-002, 1.109314e-002, 1.149512e-002, - 1.186972e-002, 1.222058e-002, 1.254076e-002, 1.283118e-002, - 1.308874e-002, 1.331122e-002, 1.347140e-002, 1.359262e-002, - 1.368141e-002, 1.373196e-002, 1.375349e-002, 1.376232e-002, - 1.375840e-002, 1.374708e-002, 1.372930e-002, 1.370029e-002, - 1.364036e-002, 1.355215e-002, 1.344941e-002, 1.333411e-002, - 1.320734e-002, 1.307482e-002, 1.293099e-002, 1.277531e-002, - 1.261562e-002, 1.244437e-002, 1.224801e-002, 1.202732e-002, - 1.180179e-002, 1.157480e-002, 1.135231e-002, 1.112988e-002, - 1.090907e-002, 1.069138e-002, 1.048235e-002, 1.028649e-002, - 1.009986e-002, 9.894653e-003, 9.711192e-003, 9.543640e-003, - 9.389412e-003, 9.244944e-003, 9.112489e-003, 8.989307e-003, - 8.880592e-003, 8.796610e-003, 8.749073e-003, 8.705311e-003, - 8.693531e-003, 8.712899e-003, 8.758798e-003, 8.834276e-003, - 8.964297e-003, 9.146580e-003, 9.382400e-003, 9.676461e-003, - 1.003482e-002, 1.043368e-002, 1.087360e-002, 1.136367e-002, - 1.190836e-002, 1.249545e-002, 1.312966e-002, 1.381527e-002, - 1.455099e-002, 1.532904e-002, 1.615067e-002, 1.700602e-002, - 1.787936e-002, 1.878769e-002, 1.972765e-002, 2.069279e-002, - 2.167062e-002, 2.267133e-002, 2.368609e-002, 2.471565e-002, - 2.576789e-002, 2.683141e-002, 2.788334e-002, 2.893690e-002, - 2.999677e-002, 3.105740e-002, 3.211713e-002, 3.318095e-002, - 3.424568e-002, 3.529364e-002, 3.632682e-002, 3.735170e-002, - 3.835333e-002, 3.933486e-002, 4.029688e-002, 4.123149e-002, - 4.213350e-002, 4.299802e-002, 4.382735e-002, 4.462050e-002, - 4.538044e-002, 4.608513e-002, 4.673659e-002, 4.732998e-002, - 4.785523e-002, 4.831957e-002, 4.872167e-002, 4.905536e-002, - 4.931550e-002, 4.949831e-002, 4.960858e-002, 4.965961e-002, - 4.966050e-002, 4.962326e-002, 4.953502e-002, 4.939799e-002, - 4.920892e-002, 4.897286e-002, 4.869499e-002, 4.837725e-002, - 4.802540e-002, 4.764011e-002, 4.722278e-002, 4.677275e-002, - 4.628071e-002, 4.573608e-002, 4.507303e-002, 4.418428e-002, - 4.308285e-002, 4.177172e-002, 4.027178e-002, 3.859858e-002, - 3.675521e-002, 3.477892e-002, 3.264901e-002, 3.038526e-002, - 2.798872e-002, 2.546935e-002, 2.285136e-002, 2.015142e-002, - 1.738808e-002, 1.455719e-002, 1.167716e-002, 8.779001e-003, - 5.854273e-003, 2.920392e-003, -1.340298e-005, -2.949905e-003, - -5.861264e-003, -8.738511e-003, -1.158059e-002, -1.437030e-002, - -1.709062e-002, -1.971315e-002, -2.223866e-002, -2.467531e-002, - -2.699992e-002, -2.921507e-002, -3.129680e-002, -3.323237e-002, - -3.501967e-002, -3.666468e-002, -3.816118e-002, -3.947889e-002, - -4.059011e-002, -4.151853e-002, -4.235526e-002, -4.315371e-002, - -4.389491e-002, -4.459013e-002, -4.524625e-002, -4.585662e-002, - -4.641378e-002, -4.692591e-002, -4.735978e-002, -4.775180e-002, - -4.811260e-002, -4.844398e-002, -4.872604e-002, -4.896913e-002, - -4.917029e-002, -4.932932e-002, -4.944855e-002, -4.954092e-002, - -4.955247e-002, -4.951866e-002, -4.944649e-002, -4.932636e-002, - -4.915154e-002, -4.892310e-002, -4.865897e-002, -4.836382e-002, - -4.804132e-002, -4.769181e-002, -4.729224e-002, -4.685501e-002, - -4.639413e-002, -4.590446e-002, -4.539431e-002, -4.485351e-002, - -4.428826e-002, -4.371194e-002, -4.311785e-002, -4.251433e-002, - -4.187123e-002, -4.118875e-002, -4.048474e-002, -3.976142e-002, - -3.902561e-002, -3.828870e-002, -3.753574e-002, -3.676480e-002, - -3.596189e-002, -3.514502e-002, -3.429981e-002, -3.340030e-002, - -3.248095e-002, -3.154216e-002, -3.059431e-002, -2.962296e-002, - -2.863126e-002, -2.762846e-002, -2.661056e-002, -2.559059e-002, - -2.446012e-002, -2.314566e-002, -2.169830e-002, -2.012766e-002, - -1.844914e-002, -1.668148e-002, -1.481463e-002, -1.286641e-002, - -1.085741e-002, -8.800850e-003, -6.698471e-003, -4.525604e-003, - -2.318420e-003, -7.741094e-005, 2.179343e-003, 4.426094e-003, - 6.666810e-003, 8.899098e-003, 1.112830e-002, 1.331237e-002, - 1.541396e-002, 1.742510e-002, 1.933557e-002, 2.114899e-002, - 2.284487e-002, 2.441157e-002, 2.586077e-002, 2.718179e-002, - 2.836234e-002, 2.939914e-002, 3.029158e-002, 3.104601e-002, - 3.165168e-002, 3.210292e-002, 3.238983e-002, 3.250718e-002, - 3.244487e-002, 3.219354e-002, 3.175104e-002, 3.112146e-002, - 3.027953e-002, 2.925394e-002, 2.817499e-002, 2.702899e-002, - 2.582779e-002, 2.456803e-002, 2.323451e-002, 2.184043e-002, - 2.038424e-002, 1.888647e-002, 1.734005e-002, 1.576075e-002, - 1.414830e-002, 1.251981e-002, 1.085497e-002, 9.162951e-003, - 7.468766e-003, 5.781762e-003, 4.082016e-003, 2.368757e-003, - 6.386597e-004, -1.085542e-003, -2.791414e-003, -4.477473e-003, - -6.138893e-003, -7.786760e-003, -9.407374e-003, -1.099547e-002, - -1.255307e-002, -1.408202e-002, -1.556607e-002, -1.698193e-002, - -1.834070e-002, -1.964440e-002, -2.090390e-002, -2.215728e-002, - -2.339410e-002, -2.461931e-002, -2.583650e-002, -2.704105e-002, - -2.821814e-002, -2.938490e-002, -3.054566e-002, -3.168128e-002, - -3.280079e-002, -3.390817e-002, -3.497384e-002, -3.603030e-002, - -3.704888e-002, -3.804474e-002, -3.900092e-002, -3.992377e-002, - -4.081448e-002, -4.166368e-002, -4.246471e-002, -4.320552e-002, - -4.371969e-002, -4.378821e-002, -4.343905e-002, -4.269852e-002, - -4.157231e-002, -4.006949e-002, -3.824511e-002, -3.612176e-002, - -3.372429e-002, -3.103154e-002, -2.810399e-002, -2.495196e-002, - -2.158720e-002, -1.807852e-002, -1.440622e-002, -1.061231e-002, - -6.722151e-003, -2.719380e-003, 1.357687e-003, 5.475065e-003, - 9.611192e-003, 1.373340e-002, 1.783313e-002, 2.189552e-002, - 2.588848e-002, 2.981978e-002, 3.365138e-002, 3.737157e-002, - 4.098652e-002, 4.442521e-002, 4.766748e-002, 5.068531e-002, - 5.349434e-002, 5.607558e-002, 5.840248e-002, 6.043361e-002, - 6.216915e-002, 6.354925e-002, 6.461177e-002, 6.529436e-002, - 6.556189e-002, 6.542974e-002, 6.492639e-002, 6.432537e-002, - 6.365076e-002, 6.290768e-002, 6.212321e-002, 6.125987e-002, - 6.037955e-002, 5.947797e-002, 5.849970e-002, 5.745104e-002, - 5.633184e-002, 5.516051e-002, 5.391280e-002, 5.261984e-002, - 5.128178e-002, 4.989437e-002, 4.852980e-002, 4.718461e-002, - 4.581112e-002, 4.439685e-002, 4.292200e-002, 4.140369e-002, - 3.984047e-002, 3.827082e-002, 3.667324e-002, 3.502883e-002, - 3.342208e-002, 3.190241e-002, 3.033201e-002, 2.872386e-002, - 2.713366e-002, 2.550315e-002, 2.381814e-002, 2.208133e-002, - 2.030270e-002, 1.851927e-002, 1.676667e-002, 1.521299e-002, - 1.360083e-002, 1.198033e-002, 1.032670e-002, 8.637095e-003, - 6.950588e-003, 5.233412e-003, 3.488609e-003, 1.743776e-003, - -4.745230e-005, -1.602607e-003, -3.111694e-003, -4.643086e-003, - -6.211488e-003, -7.808966e-003, -9.413800e-003, -1.101909e-002, - -1.261480e-002, -1.423759e-002, -1.587844e-002, -1.730359e-002, - -1.861370e-002, -1.990528e-002, -2.117223e-002, -2.244167e-002, - -2.376683e-002, -2.510206e-002, -2.644421e-002, -2.774308e-002, - -2.908395e-002, -3.026829e-002, -3.119018e-002, -3.209791e-002, - -3.302699e-002, -3.392057e-002, -3.482963e-002, -3.575647e-002, - -3.668427e-002, -3.760748e-002, -3.850449e-002, -3.937009e-002, - -3.979700e-002, -4.021182e-002, -4.061585e-002, -4.104901e-002, - -4.144667e-002, -4.183181e-002, -4.226113e-002, -4.269487e-002, - -4.312297e-002, -4.351950e-002, -4.338229e-002, -4.319939e-002, - -4.303924e-002, -4.282204e-002, -4.256248e-002, -4.226691e-002, - -4.198461e-002, -4.168752e-002, -4.139502e-002, -4.106155e-002, - -4.034006e-002, -3.943272e-002, -3.852165e-002, -3.758874e-002, - -3.668377e-002, -3.584676e-002, -3.506036e-002, -3.429806e-002, - -3.352810e-002, -3.282830e-002, -3.185844e-002, -3.050199e-002, - -2.919978e-002, -2.792364e-002, -2.665707e-002, -2.541197e-002, - -2.425181e-002, -2.311851e-002, -2.200266e-002, -2.093763e-002, - -1.977861e-002, -1.814129e-002, -1.655312e-002, -1.499241e-002, - -1.352224e-002, -1.208473e-002, -1.065415e-002, -9.214675e-003, - -7.812041e-003, -6.484053e-003, -5.197964e-003, -3.170371e-003, - -1.233728e-003, 6.316442e-004, 2.374450e-003, 4.053007e-003, - 5.738868e-003, 7.392482e-003, 8.886661e-003, 1.040991e-002, - 1.189278e-002, 1.376890e-002, 1.582702e-002, 1.765333e-002, - 1.926291e-002, 2.058458e-002, 2.175448e-002, 2.283450e-002, - 2.373517e-002, 2.446281e-002, 2.495359e-002, 2.573104e-002, - 2.660638e-002, 2.726524e-002, 2.779764e-002, 2.820041e-002, - 2.851658e-002, 2.867275e-002, 2.873865e-002, 2.866095e-002, - 2.839745e-002, 2.810428e-002, 2.790687e-002, 2.741684e-002, - 2.666144e-002, 2.591947e-002, 2.501657e-002, 2.400407e-002, - 2.292073e-002, 2.172963e-002, 2.044169e-002, 1.904815e-002, - 1.767621e-002, 1.622684e-002, 1.489903e-002, 1.385920e-002, - 1.309787e-002, 1.273786e-002, 1.257725e-002, 1.256473e-002, - 1.292620e-002, 1.355177e-002, 1.421233e-002, 1.500685e-002, - 1.584864e-002, 1.680606e-002, 1.778773e-002, 1.883422e-002, - 1.992003e-002, 2.112893e-002, 2.283663e-002, 2.460697e-002, - 2.622387e-002, 2.758451e-002, 2.901900e-002, 3.029680e-002, - 3.132349e-002, 3.256750e-002, 3.352167e-002, 3.449371e-002, - 3.506972e-002, 3.527533e-002, 3.481014e-002, 3.385279e-002, - 3.268399e-002, 3.163099e-002, 3.023607e-002, 2.808737e-002, - 2.560235e-002, 2.279149e-002, 1.988521e-002, 1.645659e-002, - 1.234501e-002, 6.662850e-003, 1.429498e-003, -3.509806e-003, - -7.708264e-003, -1.068938e-002, -1.300923e-002, -1.493199e-002, - -1.717973e-002, -2.077967e-002, -2.471463e-002, -3.180092e-002, - -3.965489e-002, -4.678590e-002, -5.541211e-002, -6.553649e-002, - -7.577997e-002, -8.439822e-002, -9.013586e-002, -8.907055e-002, - -6.985264e-002, -6.482686e-002, -6.850541e-002, -7.163153e-002, - -7.757975e-002, -8.642750e-002, -9.521655e-002, -1.002562e-001, - -9.983558e-002, -9.283261e-002, -8.142507e-002, 1.359803e-001, - 1.456601e-001, 1.393731e-001, 1.315151e-001, 1.222863e-001, - 1.081791e-001, 8.908593e-002, 6.564607e-002, 3.880305e-002, - 8.329103e-003, -1.631899e-001, 4.213222e-002, -8.431494e-003, - -5.309251e-002, -9.238936e-002, -1.202449e-001, 1.408189e-001, - 1.287963e-001, 9.699595e-002, 5.427525e-002, 0.000000e+000 - }, - { - 0.000000e+000, -6.693485e-003, -1.294773e-002, -1.814577e-002, - -2.256261e-002, -2.634021e-002, -2.952633e-002, -3.217320e-002, - -3.421927e-002, -3.426096e-002, -3.340300e-002, -3.205595e-002, - -3.043553e-002, -2.864168e-002, -2.680805e-002, -2.503563e-002, - -2.349020e-002, -2.230590e-002, -2.154552e-002, -2.087697e-002, - -2.026709e-002, -1.967836e-002, -1.909192e-002, -1.851375e-002, - -1.793373e-002, -1.736241e-002, -1.680028e-002, -1.622133e-002, - -1.565219e-002, -1.506597e-002, -1.446187e-002, -1.385450e-002, - -1.326067e-002, -1.265091e-002, -1.202754e-002, -1.138099e-002, - -1.073003e-002, -1.007086e-002, -9.388444e-003, -8.668834e-003, - -7.917089e-003, -7.153140e-003, -6.449564e-003, -5.780813e-003, - -5.133422e-003, -4.538399e-003, -4.135674e-003, -4.182686e-003, - -4.214046e-003, -3.738145e-003, -3.101879e-003, -2.436709e-003, - -1.869963e-003, -1.300673e-003, -7.397106e-004, -1.519417e-004, - 4.244970e-004, 1.011232e-003, 1.610644e-003, 2.188950e-003, - 2.781288e-003, 3.353420e-003, 3.866476e-003, 4.332129e-003, - 4.769868e-003, 5.182462e-003, 5.556861e-003, 5.912080e-003, - 6.216131e-003, 6.504827e-003, 6.745843e-003, 6.961166e-003, - 7.109640e-003, 7.179905e-003, 7.215556e-003, 7.231026e-003, - 7.238283e-003, 7.207275e-003, 7.180739e-003, 7.203119e-003, - 7.277554e-003, 7.438674e-003, 7.700950e-003, 7.986767e-003, - 8.353675e-003, 8.811949e-003, 9.369863e-003, 9.998604e-003, - 1.071346e-002, 1.156391e-002, 1.254112e-002, 1.363230e-002, - 1.481153e-002, 1.600154e-002, 1.722121e-002, 1.847426e-002, - 1.975367e-002, 2.102355e-002, 2.228654e-002, 2.350206e-002, - 2.467333e-002, 2.576514e-002, 2.676642e-002, 2.763008e-002, - 2.837490e-002, 2.900262e-002, 2.951695e-002, 2.995902e-002, - 3.034449e-002, 3.067492e-002, 3.090827e-002, 3.109544e-002, - 3.122809e-002, 3.128815e-002, 3.128090e-002, 3.122068e-002, - 3.111723e-002, 3.097060e-002, 3.076822e-002, 3.050650e-002, - 3.021419e-002, 2.990161e-002, 2.957176e-002, 2.925286e-002, - 2.894738e-002, 2.866404e-002, 2.842325e-002, 2.819935e-002, - 2.796000e-002, 2.771697e-002, 2.747279e-002, 2.721136e-002, - 2.694066e-002, 2.663635e-002, 2.630772e-002, 2.594561e-002, - 2.555494e-002, 2.514794e-002, 2.468882e-002, 2.418716e-002, - 2.365983e-002, 2.310497e-002, 2.250999e-002, 2.188728e-002, - 2.125951e-002, 2.060970e-002, 1.994304e-002, 1.927778e-002, - 1.862575e-002, 1.797199e-002, 1.732854e-002, 1.669014e-002, - 1.605846e-002, 1.543219e-002, 1.481900e-002, 1.422666e-002, - 1.365648e-002, 1.309630e-002, 1.254524e-002, 1.199264e-002, - 1.143998e-002, 1.087712e-002, 1.030404e-002, 9.715979e-003, - 9.128826e-003, 8.541857e-003, 7.915682e-003, 7.256074e-003, - 6.560548e-003, 5.817272e-003, 5.030207e-003, 4.211878e-003, - 3.364925e-003, 2.509462e-003, 1.647285e-003, 8.119442e-004, - -5.402251e-005, -9.392538e-004, -1.845450e-003, -2.778769e-003, - -3.724100e-003, -4.646999e-003, -5.515035e-003, -6.331723e-003, - -7.093569e-003, -7.764387e-003, -8.390977e-003, -8.982185e-003, - -9.544656e-003, -1.007692e-002, -1.058629e-002, -1.107654e-002, - -1.153931e-002, -1.198036e-002, -1.241179e-002, -1.279401e-002, - -1.314022e-002, -1.346548e-002, -1.379127e-002, -1.411480e-002, - -1.443119e-002, -1.473808e-002, -1.503863e-002, -1.532941e-002, - -1.562005e-002, -1.589084e-002, -1.614571e-002, -1.643526e-002, - -1.676443e-002, -1.711711e-002, -1.748646e-002, -1.785341e-002, - -1.821472e-002, -1.855724e-002, -1.888808e-002, -1.921648e-002, - -1.949293e-002, -1.978350e-002, -2.008587e-002, -2.039188e-002, - -2.070065e-002, -2.099399e-002, -2.124832e-002, -2.145993e-002, - -2.163708e-002, -2.178363e-002, -2.185332e-002, -2.191208e-002, - -2.197305e-002, -2.203925e-002, -2.210688e-002, -2.216662e-002, - -2.223017e-002, -2.229619e-002, -2.237077e-002, -2.244720e-002, - -2.246909e-002, -2.247184e-002, -2.248659e-002, -2.252188e-002, - -2.258084e-002, -2.265803e-002, -2.275656e-002, -2.287641e-002, - -2.302607e-002, -2.321385e-002, -2.339214e-002, -2.357494e-002, - -2.381229e-002, -2.409274e-002, -2.439379e-002, -2.468684e-002, - -2.496256e-002, -2.522243e-002, -2.546393e-002, -2.567950e-002, - -2.585027e-002, -2.593755e-002, -2.601518e-002, -2.607444e-002, - -2.611492e-002, -2.613388e-002, -2.611784e-002, -2.605962e-002, - -2.598242e-002, -2.588476e-002, -2.578429e-002, -2.558481e-002, - -2.538536e-002, -2.518970e-002, -2.499249e-002, -2.478886e-002, - -2.456887e-002, -2.434148e-002, -2.411501e-002, -2.389855e-002, - -2.369737e-002, -2.343449e-002, -2.315804e-002, -2.288578e-002, - -2.261848e-002, -2.236259e-002, -2.212186e-002, -2.189675e-002, - -2.169800e-002, -2.152452e-002, -2.137112e-002, -2.117956e-002, - -2.095622e-002, -2.072166e-002, -2.044726e-002, -2.012801e-002, - -1.978345e-002, -1.941739e-002, -1.903633e-002, -1.864595e-002, - -1.824437e-002, -1.781227e-002, -1.731162e-002, -1.680984e-002, - -1.631170e-002, -1.582071e-002, -1.534803e-002, -1.489623e-002, - -1.446814e-002, -1.406447e-002, -1.368750e-002, -1.332438e-002, - -1.289673e-002, -1.249344e-002, -1.211623e-002, -1.176388e-002, - -1.144329e-002, -1.116648e-002, -1.094638e-002, -1.078607e-002, - -1.069276e-002, -1.064457e-002, -1.054423e-002, -1.045869e-002, - -1.039783e-002, -1.035797e-002, -1.034037e-002, -1.034271e-002, - -1.036491e-002, -1.041810e-002, -1.049191e-002, -1.058207e-002, - -1.061079e-002, -1.059246e-002, -1.054362e-002, -1.047493e-002, - -1.038423e-002, -1.028991e-002, -1.020030e-002, -1.012203e-002, - -1.004888e-002, -9.986849e-003, -9.898462e-003, -9.761454e-003, - -9.643756e-003, -9.561119e-003, -9.510934e-003, -9.497622e-003, - -9.530261e-003, -9.597066e-003, -9.708153e-003, -9.865028e-003, - -1.005742e-002, -1.021501e-002, -1.041573e-002, -1.064656e-002, - -1.087350e-002, -1.107320e-002, -1.122605e-002, -1.131998e-002, - -1.136358e-002, -1.135903e-002, -1.130991e-002, -1.110936e-002, - -1.086190e-002, -1.058158e-002, -1.027354e-002, -9.938190e-003, - -9.578338e-003, -9.196867e-003, -8.797478e-003, -8.350004e-003, - -7.848842e-003, -7.205530e-003, -6.480771e-003, -5.723023e-003, - -4.925750e-003, -4.092231e-003, -3.232676e-003, -2.358117e-003, - -1.463165e-003, -5.547719e-004, 3.596005e-004, 1.341049e-003, - 2.392029e-003, 3.455207e-003, 4.533609e-003, 5.616888e-003, - 6.704661e-003, 7.795771e-003, 8.885145e-003, 9.961922e-003, - 1.104659e-002, 1.217140e-002, 1.339734e-002, 1.463204e-002, - 1.588551e-002, 1.713527e-002, 1.837039e-002, 1.959724e-002, - 2.080418e-002, 2.198571e-002, 2.314644e-002, 2.428758e-002, - 2.550195e-002, 2.668274e-002, 2.783431e-002, 2.894100e-002, - 2.999709e-002, 3.099952e-002, 3.193879e-002, 3.281240e-002, - 3.361305e-002, 3.433498e-002, 3.505910e-002, 3.571928e-002, - 3.629826e-002, 3.677535e-002, 3.715820e-002, 3.748671e-002, - 3.775406e-002, 3.797455e-002, 3.815082e-002, 3.828028e-002, - 3.843320e-002, 3.861411e-002, 3.876839e-002, 3.889820e-002, - 3.900629e-002, 3.908488e-002, 3.913562e-002, 3.917206e-002, - 3.919556e-002, 3.921333e-002, 3.924672e-002, 3.934289e-002, - 3.941994e-002, 3.948043e-002, 3.952227e-002, 3.952480e-002, - 3.949563e-002, 3.942645e-002, 3.931440e-002, 3.916330e-002, - 3.896279e-002, 3.877358e-002, 3.851000e-002, 3.818189e-002, - 3.781467e-002, 3.740781e-002, 3.696524e-002, 3.649172e-002, - 3.598600e-002, 3.542723e-002, 3.480358e-002, 3.417948e-002, - 3.350221e-002, 3.276813e-002, 3.199495e-002, 3.121736e-002, - 3.045231e-002, 2.968739e-002, 2.892140e-002, 2.813863e-002, - 2.734835e-002, 2.658640e-002, 2.582387e-002, 2.504896e-002, - 2.425499e-002, 2.343191e-002, 2.258856e-002, 2.171204e-002, - 2.081311e-002, 1.990436e-002, 1.898855e-002, 1.809123e-002, - 1.720834e-002, 1.631150e-002, 1.538872e-002, 1.445443e-002, - 1.350406e-002, 1.255481e-002, 1.159189e-002, 1.061867e-002, - 9.640475e-003, 8.671699e-003, 7.727532e-003, 6.784858e-003, - 5.845462e-003, 4.899365e-003, 3.943273e-003, 2.961435e-003, - 1.947640e-003, 9.049884e-004, -1.455246e-004, -1.189896e-003, - -2.197595e-003, -3.204635e-003, -4.216577e-003, -5.228208e-003, - -6.240984e-003, -7.252716e-003, -8.260536e-003, -9.253422e-003, - -1.021743e-002, -1.115506e-002, -1.203558e-002, -1.287596e-002, - -1.367734e-002, -1.442266e-002, -1.512085e-002, -1.576614e-002, - -1.638393e-002, -1.696377e-002, -1.748441e-002, -1.797201e-002, - -1.842032e-002, -1.881223e-002, -1.918271e-002, -1.954592e-002, - -1.990405e-002, -2.024293e-002, -2.059832e-002, -2.095779e-002, - -2.131297e-002, -2.170726e-002, -2.210858e-002, -2.246456e-002, - -2.278915e-002, -2.308242e-002, -2.322342e-002, -2.306052e-002, - -2.261349e-002, -2.191240e-002, -2.095594e-002, -1.977207e-002, - -1.840487e-002, -1.681573e-002, -1.502679e-002, -1.305332e-002, - -1.091170e-002, -8.610341e-003, -6.190770e-003, -3.656235e-003, - -1.029037e-003, 1.674037e-003, 4.433113e-003, 7.212017e-003, - 1.000389e-002, 1.278387e-002, 1.552692e-002, 1.818788e-002, - 2.076655e-002, 2.324651e-002, 2.559140e-002, 2.781222e-002, - 2.991169e-002, 3.187577e-002, 3.369424e-002, 3.534980e-002, - 3.683330e-002, 3.814610e-002, 3.927348e-002, 4.019439e-002, - 4.087410e-002, 4.132283e-002, 4.151913e-002, 4.147579e-002, - 4.120426e-002, 4.066848e-002, 3.999271e-002, 3.925733e-002, - 3.846311e-002, 3.759921e-002, 3.666230e-002, 3.566932e-002, - 3.461171e-002, 3.349462e-002, 3.232089e-002, 3.107934e-002, - 2.977838e-002, 2.841009e-002, 2.700373e-002, 2.553549e-002, - 2.400854e-002, 2.240906e-002, 2.073283e-002, 1.899488e-002, - 1.722445e-002, 1.537956e-002, 1.347586e-002, 1.152001e-002, - 9.495525e-003, 7.368148e-003, 5.156433e-003, 2.890722e-003, - 5.973835e-004, -1.709326e-003, -3.996570e-003, -6.248824e-003, - -8.482986e-003, -1.067745e-002, -1.287573e-002, -1.508739e-002, - -1.730918e-002, -1.952652e-002, -2.171116e-002, -2.387995e-002, - -2.596276e-002, -2.795315e-002, -2.989260e-002, -3.180817e-002, - -3.368798e-002, -3.556614e-002, -3.744486e-002, -3.930562e-002, - -4.113043e-002, -4.295031e-002, -4.474827e-002, -4.643930e-002, - -4.813832e-002, -4.983717e-002, -5.152703e-002, -5.320793e-002, - -5.486855e-002, -5.653315e-002, -5.819642e-002, -5.987110e-002, - -6.137353e-002, -6.250467e-002, -6.336649e-002, -6.398757e-002, - -6.434836e-002, -6.449744e-002, -6.439587e-002, -6.405544e-002, - -6.349345e-002, -6.273850e-002, -6.180153e-002, -6.060963e-002, - -5.922795e-002, -5.768980e-002, -5.600828e-002, -5.421894e-002, - -5.232540e-002, -5.034515e-002, -4.825366e-002, -4.608968e-002, - -4.385939e-002, -4.145852e-002, -3.891009e-002, -3.629935e-002, - -3.367289e-002, -3.102229e-002, -2.837196e-002, -2.573596e-002, - -2.311528e-002, -2.055056e-002, -1.806803e-002, -1.562220e-002, - -1.317428e-002, -1.087637e-002, -8.701875e-003, -6.630098e-003, - -4.744750e-003, -3.070384e-003, -1.582159e-003, -2.914927e-004, - 7.335285e-004, 1.553592e-003, 2.508960e-003, 3.389987e-003, - 4.243592e-003, 5.125554e-003, 6.018631e-003, 6.936716e-003, - 7.854009e-003, 8.758132e-003, 9.636745e-003, 1.049402e-002, - 1.149073e-002, 1.250395e-002, 1.350478e-002, 1.449061e-002, - 1.542413e-002, 1.628812e-002, 1.708948e-002, 1.783974e-002, - 1.848572e-002, 1.903254e-002, 1.968004e-002, 2.035633e-002, - 2.092502e-002, 2.142063e-002, 2.184386e-002, 2.216788e-002, - 2.235990e-002, 2.247126e-002, 2.244626e-002, 2.230984e-002, - 2.215398e-002, 2.207862e-002, 2.189175e-002, 2.163851e-002, - 2.134905e-002, 2.102868e-002, 2.066273e-002, 2.026087e-002, - 1.982760e-002, 1.934869e-002, 1.883445e-002, 1.861759e-002, - 1.835068e-002, 1.804909e-002, 1.776918e-002, 1.750116e-002, - 1.723944e-002, 1.692107e-002, 1.655837e-002, 1.618388e-002, - 1.575332e-002, 1.562169e-002, 1.550359e-002, 1.531092e-002, - 1.498434e-002, 1.451629e-002, 1.384733e-002, 1.298442e-002, - 1.197104e-002, 1.079613e-002, 9.424703e-003, 8.157830e-003, - 6.937001e-003, 5.640427e-003, 4.215221e-003, 2.658892e-003, - 9.804203e-004, -8.441043e-004, -2.722842e-003, -4.638962e-003, - -6.611850e-003, -8.412307e-003, -9.986228e-003, -1.159821e-002, - -1.322716e-002, -1.484843e-002, -1.648289e-002, -1.809514e-002, - -1.971193e-002, -2.125397e-002, -2.278520e-002, -2.420092e-002, - -2.520827e-002, -2.618178e-002, -2.712257e-002, -2.805888e-002, - -2.891514e-002, -2.973233e-002, -3.051873e-002, -3.123351e-002, - -3.187630e-002, -3.240308e-002, -3.235571e-002, -3.217919e-002, - -3.188676e-002, -3.136805e-002, -3.067525e-002, -3.001468e-002, - -2.938975e-002, -2.869444e-002, -2.800532e-002, -2.731670e-002, - -2.620078e-002, -2.497031e-002, -2.375862e-002, -2.248068e-002, - -2.119424e-002, -1.990328e-002, -1.872219e-002, -1.754529e-002, - -1.638864e-002, -1.523373e-002, -1.388387e-002, -1.239738e-002, - -1.096089e-002, -9.538881e-003, -8.181707e-003, -6.950143e-003, - -5.797964e-003, -4.686261e-003, -3.651562e-003, -2.739660e-003, - -1.776657e-003, -5.363480e-004, 6.626466e-004, 1.793124e-003, - 2.950057e-003, 4.024141e-003, 5.063086e-003, 6.029834e-003, - 6.953163e-003, 7.873540e-003, 8.776532e-003, 1.008271e-002, - 1.131156e-002, 1.251903e-002, 1.368173e-002, 1.473871e-002, - 1.569125e-002, 1.656390e-002, 1.737614e-002, 1.817089e-002, - 1.890068e-002, 1.993477e-002, 2.094860e-002, 2.192921e-002, - 2.279139e-002, 2.355434e-002, 2.421806e-002, 2.486279e-002, - 2.542970e-002, 2.589966e-002, 2.621816e-002, 2.671673e-002, - 2.727773e-002, 2.775717e-002, 2.814537e-002, 2.843552e-002, - 2.854905e-002, 2.857807e-002, 2.853260e-002, 2.841490e-002, - 2.820644e-002, 2.808304e-002, 2.817982e-002, 2.820464e-002, - 2.810875e-002, 2.790979e-002, 2.769404e-002, 2.741801e-002, - 2.713019e-002, 2.677760e-002, 2.640030e-002, 2.607361e-002, - 2.606227e-002, 2.601481e-002, 2.591623e-002, 2.581737e-002, - 2.571329e-002, 2.550586e-002, 2.520680e-002, 2.491147e-002, - 2.450612e-002, 2.406190e-002, 2.391009e-002, 2.370905e-002, - 2.340746e-002, 2.302998e-002, 2.253963e-002, 2.198485e-002, - 2.142238e-002, 2.075097e-002, 1.979952e-002, 1.873667e-002, - 1.791233e-002, 1.719947e-002, 1.644673e-002, 1.569372e-002, - 1.488955e-002, 1.404621e-002, 1.314600e-002, 1.221373e-002, - 1.138797e-002, 1.054174e-002, 9.789920e-003, 9.175487e-003, - 8.475372e-003, 7.754511e-003, 7.154047e-003, 6.532273e-003, - 5.813567e-003, 5.029210e-003, 4.170620e-003, 3.290527e-003, - 2.461252e-003, 1.762054e-003, 9.264634e-004, 5.088638e-005, - -8.103291e-004, -1.731029e-003, -2.737163e-003, -3.898755e-003, - -5.094573e-003, -6.473116e-003, -7.903319e-003, -8.736905e-003, - -9.744800e-003, -1.092015e-002, -1.205169e-002, -1.326090e-002, - -1.451070e-002, -1.584482e-002, -1.733432e-002, -1.891294e-002, - -2.066027e-002, -2.187797e-002, -2.275431e-002, -2.364719e-002, - -2.441949e-002, -2.501507e-002, -2.544148e-002, -2.572159e-002, - -2.599709e-002, -2.615154e-002, -2.633808e-002, -2.596696e-002, - -2.515847e-002, -2.430194e-002, -2.344515e-002, -2.276829e-002, - -2.209294e-002, -2.135182e-002, -2.073453e-002, -2.014671e-002, - -1.940560e-002, -1.838702e-002, -1.693106e-002, -1.552453e-002, - -1.425790e-002, -1.309467e-002, -1.197368e-002, -1.101558e-002, - -1.028193e-002, -9.523562e-003, -8.670417e-003, -8.028634e-003, - -6.448768e-003, -4.857823e-003, -3.297200e-003, -2.188113e-003, - -1.207461e-003, -2.277449e-004, 3.905405e-004, 5.450266e-004, - 5.865983e-004, 5.800508e-004, 1.645117e-003, 2.698534e-003, - 3.226162e-003, 3.258084e-003, 2.865015e-003, 2.653676e-003, - 2.132567e-003, 1.151595e-003, 1.848941e-004, -1.400429e-003, - -1.808496e-003, -1.942259e-003, -2.326727e-003, -3.152509e-003, - -4.359287e-003, -5.916347e-003, -7.676281e-003, -9.729204e-003, - -1.192822e-002, -1.451381e-002, -1.567311e-002, -1.507943e-002, - -1.446563e-002, -1.393169e-002, -1.333215e-002, -1.327462e-002, - -1.359070e-002, -1.389884e-002, -1.475839e-002, -1.597238e-002, - -1.633836e-002, -1.345749e-002, -1.009753e-002, -7.133926e-003, - -4.200006e-003, -6.689719e-004, 1.349175e-003, 2.473474e-003, - 3.137331e-003, 4.086499e-003, 4.453958e-003, 9.679730e-003, - 1.532065e-002, 2.007844e-002, 2.492736e-002, 3.066648e-002, - 3.647310e-002, 3.988489e-002, 4.126106e-002, 3.799109e-002, - 2.707282e-002, 2.562059e-002, 2.847733e-002, 3.032211e-002, - 3.188822e-002, 3.292856e-002, 3.233655e-002, 2.665533e-002, - 1.806627e-002, 6.428464e-003, -1.459043e-003, -9.594090e-002, - -9.119271e-002, -7.541453e-002, -5.254466e-002, -2.774788e-002, - -1.125875e-002, -1.815898e-003, 4.892343e-004, -5.961389e-003, - -1.667448e-002, 1.712418e-001, 5.307609e-003, -4.415489e-002, - -8.993407e-002, -1.295619e-001, -1.594212e-001, 4.023480e-001, - 3.574481e-001, 2.632863e-001, 1.506829e-001, 0.000000e+000 - }, - { - 0.000000e+000, -6.532769e-003, -1.221520e-002, -1.697523e-002, - -2.106447e-002, -2.463361e-002, -2.766687e-002, -3.023530e-002, - -3.229652e-002, -3.270719e-002, -3.258645e-002, -3.225349e-002, - -3.179513e-002, -3.122651e-002, -3.056982e-002, -2.998388e-002, - -2.954841e-002, -2.932281e-002, -2.934284e-002, -2.938877e-002, - -2.942205e-002, -2.942762e-002, -2.939737e-002, -2.931269e-002, - -2.916779e-002, -2.897899e-002, -2.873675e-002, -2.844817e-002, - -2.807642e-002, -2.763964e-002, -2.714619e-002, -2.663448e-002, - -2.611184e-002, -2.552600e-002, -2.488855e-002, -2.418947e-002, - -2.342618e-002, -2.259059e-002, -2.170309e-002, -2.076071e-002, - -1.975378e-002, -1.867971e-002, -1.765358e-002, -1.656691e-002, - -1.546373e-002, -1.434789e-002, -1.325191e-002, -1.221392e-002, - -1.116856e-002, -1.007257e-002, -8.955893e-003, -7.860193e-003, - -6.875012e-003, -5.942372e-003, -5.032705e-003, -4.162771e-003, - -3.292865e-003, -2.440887e-003, -1.599497e-003, -7.645774e-004, - 4.396297e-005, 8.612120e-004, 1.563730e-003, 2.224807e-003, - 2.859342e-003, 3.464963e-003, 4.068511e-003, 4.641987e-003, - 5.214387e-003, 5.764755e-003, 6.303663e-003, 6.829034e-003, - 7.304328e-003, 7.664827e-003, 8.046272e-003, 8.422555e-003, - 8.797710e-003, 9.199348e-003, 9.620624e-003, 1.005912e-002, - 1.056150e-002, 1.113807e-002, 1.176332e-002, 1.235435e-002, - 1.303921e-002, 1.378340e-002, 1.459241e-002, 1.547492e-002, - 1.643004e-002, 1.747024e-002, 1.863229e-002, 1.987287e-002, - 2.119061e-002, 2.243226e-002, 2.372384e-002, 2.504550e-002, - 2.636809e-002, 2.769457e-002, 2.900882e-002, 3.029153e-002, - 3.152997e-002, 3.269797e-002, 3.380591e-002, 3.474244e-002, - 3.554791e-002, 3.627125e-002, 3.689908e-002, 3.746465e-002, - 3.798510e-002, 3.845275e-002, 3.887780e-002, 3.924963e-002, - 3.957058e-002, 3.979395e-002, 3.994540e-002, 4.004658e-002, - 4.009969e-002, 4.011033e-002, 4.006704e-002, 3.999811e-002, - 3.988609e-002, 3.973129e-002, 3.956733e-002, 3.936929e-002, - 3.911411e-002, 3.887087e-002, 3.860951e-002, 3.833137e-002, - 3.806034e-002, 3.776318e-002, 3.744403e-002, 3.711637e-002, - 3.675740e-002, 3.637314e-002, 3.591082e-002, 3.543117e-002, - 3.492546e-002, 3.439018e-002, 3.384263e-002, 3.327197e-002, - 3.267202e-002, 3.204874e-002, 3.140814e-002, 3.075396e-002, - 3.003942e-002, 2.930089e-002, 2.856224e-002, 2.781853e-002, - 2.706392e-002, 2.631752e-002, 2.556885e-002, 2.481096e-002, - 2.404462e-002, 2.328925e-002, 2.252295e-002, 2.174263e-002, - 2.095703e-002, 2.018365e-002, 1.940994e-002, 1.863031e-002, - 1.784201e-002, 1.705700e-002, 1.626022e-002, 1.545164e-002, - 1.461251e-002, 1.376739e-002, 1.291306e-002, 1.204207e-002, - 1.113629e-002, 1.021071e-002, 9.251255e-003, 8.271566e-003, - 7.294434e-003, 6.308318e-003, 5.308622e-003, 4.300932e-003, - 3.286563e-003, 2.258181e-003, 1.221221e-003, 1.856722e-004, - -8.484221e-004, -1.870305e-003, -2.871901e-003, -3.831116e-003, - -4.749567e-003, -5.605662e-003, -6.422759e-003, -7.214490e-003, - -7.980585e-003, -8.716716e-003, -9.414298e-003, -1.007661e-002, - -1.071457e-002, -1.132671e-002, -1.190911e-002, -1.244595e-002, - -1.294670e-002, -1.343559e-002, -1.389265e-002, -1.432164e-002, - -1.472680e-002, -1.511983e-002, -1.550296e-002, -1.586391e-002, - -1.620859e-002, -1.652523e-002, -1.680998e-002, -1.710784e-002, - -1.742824e-002, -1.776645e-002, -1.810618e-002, -1.843820e-002, - -1.875733e-002, -1.907606e-002, -1.939679e-002, -1.970824e-002, - -1.998130e-002, -2.026089e-002, -2.055476e-002, -2.086256e-002, - -2.117866e-002, -2.149220e-002, -2.179572e-002, -2.208485e-002, - -2.236024e-002, -2.263321e-002, -2.285947e-002, -2.309123e-002, - -2.332934e-002, -2.356983e-002, -2.381360e-002, -2.406797e-002, - -2.433305e-002, -2.460252e-002, -2.487001e-002, -2.513968e-002, - -2.536943e-002, -2.559775e-002, -2.583922e-002, -2.609500e-002, - -2.636832e-002, -2.664742e-002, -2.693605e-002, -2.724405e-002, - -2.756673e-002, -2.790910e-002, -2.823045e-002, -2.854201e-002, - -2.887594e-002, -2.922008e-002, -2.956359e-002, -2.989772e-002, - -3.021727e-002, -3.052412e-002, -3.081950e-002, -3.110098e-002, - -3.135615e-002, -3.155377e-002, -3.174011e-002, -3.191216e-002, - -3.207831e-002, -3.223499e-002, -3.237371e-002, -3.248931e-002, - -3.259216e-002, -3.267866e-002, -3.275664e-002, -3.275622e-002, - -3.275601e-002, -3.275557e-002, -3.275116e-002, -3.274125e-002, - -3.271000e-002, -3.266844e-002, -3.261945e-002, -3.256547e-002, - -3.251442e-002, -3.239875e-002, -3.226059e-002, -3.211191e-002, - -3.195549e-002, -3.179556e-002, -3.163133e-002, -3.146321e-002, - -3.130578e-002, -3.115138e-002, -3.099567e-002, -3.079775e-002, - -3.056239e-002, -3.031606e-002, -3.003899e-002, -2.972840e-002, - -2.940357e-002, -2.906419e-002, -2.871059e-002, -2.835501e-002, - -2.799315e-002, -2.760298e-002, -2.715330e-002, -2.670030e-002, - -2.625279e-002, -2.580607e-002, -2.536566e-002, -2.493380e-002, - -2.450932e-002, -2.409701e-002, -2.369579e-002, -2.330004e-002, - -2.285275e-002, -2.242532e-002, -2.200968e-002, -2.160385e-002, - -2.121101e-002, -2.084833e-002, -2.051915e-002, -2.021701e-002, - -1.995330e-002, -1.971270e-002, -1.944017e-002, -1.917966e-002, - -1.893869e-002, -1.871736e-002, -1.851847e-002, -1.833742e-002, - -1.816985e-002, -1.802679e-002, -1.790473e-002, -1.780269e-002, - -1.766665e-002, -1.750875e-002, -1.734317e-002, -1.717203e-002, - -1.699149e-002, -1.681776e-002, -1.664286e-002, -1.648072e-002, - -1.633152e-002, -1.619108e-002, -1.602849e-002, -1.583851e-002, - -1.566326e-002, -1.550811e-002, -1.536736e-002, -1.524332e-002, - -1.514554e-002, -1.507281e-002, -1.502812e-002, -1.500439e-002, - -1.499129e-002, -1.494226e-002, -1.490819e-002, -1.488418e-002, - -1.485479e-002, -1.480539e-002, -1.473257e-002, -1.463575e-002, - -1.449811e-002, -1.432567e-002, -1.412065e-002, -1.382656e-002, - -1.349918e-002, -1.314904e-002, -1.277454e-002, -1.236837e-002, - -1.193744e-002, -1.148267e-002, -1.101964e-002, -1.050058e-002, - -9.914246e-003, -9.210445e-003, -8.427679e-003, -7.588576e-003, - -6.699325e-003, -5.772045e-003, -4.809852e-003, -3.807464e-003, - -2.770907e-003, -1.705200e-003, -6.126022e-004, 5.347291e-004, - 1.734790e-003, 2.965399e-003, 4.222764e-003, 5.489783e-003, - 6.779274e-003, 8.078061e-003, 9.393902e-003, 1.070917e-002, - 1.203311e-002, 1.337517e-002, 1.476663e-002, 1.616265e-002, - 1.755690e-002, 1.893940e-002, 2.031305e-002, 2.167301e-002, - 2.300717e-002, 2.431966e-002, 2.560680e-002, 2.685927e-002, - 2.813641e-002, 2.938496e-002, 3.057478e-002, 3.171276e-002, - 3.280427e-002, 3.384355e-002, 3.482533e-002, 3.574690e-002, - 3.660502e-002, 3.738863e-002, 3.813544e-002, 3.880533e-002, - 3.938214e-002, 3.987092e-002, 4.028217e-002, 4.065090e-002, - 4.098059e-002, 4.126640e-002, 4.150830e-002, 4.171333e-002, - 4.190909e-002, 4.208877e-002, 4.222789e-002, 4.234609e-002, - 4.243207e-002, 4.249591e-002, 4.252966e-002, 4.254075e-002, - 4.253665e-002, 4.251042e-002, 4.247599e-002, 4.246337e-002, - 4.243445e-002, 4.239171e-002, 4.233124e-002, 4.223682e-002, - 4.212051e-002, 4.198111e-002, 4.182352e-002, 4.164358e-002, - 4.143106e-002, 4.120318e-002, 4.093083e-002, 4.061422e-002, - 4.027909e-002, 3.993275e-002, 3.957463e-002, 3.919706e-002, - 3.879307e-002, 3.835790e-002, 3.787175e-002, 3.736688e-002, - 3.683735e-002, 3.627026e-002, 3.567735e-002, 3.507217e-002, - 3.448559e-002, 3.390631e-002, 3.332196e-002, 3.273279e-002, - 3.214137e-002, 3.155165e-002, 3.095250e-002, 3.035020e-002, - 2.974177e-002, 2.911109e-002, 2.847719e-002, 2.785070e-002, - 2.720581e-002, 2.656645e-002, 2.590273e-002, 2.524310e-002, - 2.458191e-002, 2.391886e-002, 2.324437e-002, 2.256791e-002, - 2.187044e-002, 2.118013e-002, 2.048169e-002, 1.977477e-002, - 1.904599e-002, 1.832074e-002, 1.761222e-002, 1.690101e-002, - 1.618814e-002, 1.546799e-002, 1.473969e-002, 1.400776e-002, - 1.327174e-002, 1.252345e-002, 1.174386e-002, 1.096737e-002, - 1.020283e-002, 9.430363e-003, 8.654778e-003, 7.892528e-003, - 7.129173e-003, 6.362172e-003, 5.597354e-003, 4.840385e-003, - 4.073366e-003, 3.325491e-003, 2.603397e-003, 1.901038e-003, - 1.214563e-003, 5.588496e-004, -8.223302e-005, -6.981275e-004, - -1.285970e-003, -1.849201e-003, -2.418336e-003, -2.960654e-003, - -3.486445e-003, -3.977198e-003, -4.461584e-003, -4.937863e-003, - -5.408262e-003, -5.865971e-003, -6.323754e-003, -6.787078e-003, - -7.281227e-003, -7.798834e-003, -8.306025e-003, -8.781440e-003, - -9.229845e-003, -9.657470e-003, -9.975900e-003, -1.007702e-002, - -9.959517e-003, -9.645284e-003, -9.164888e-003, -8.517471e-003, - -7.731123e-003, -6.773238e-003, -5.687595e-003, -4.456536e-003, - -3.101519e-003, -1.636108e-003, -7.315816e-005, 1.555848e-003, - 3.239161e-003, 4.970823e-003, 6.759802e-003, 8.566932e-003, - 1.037725e-002, 1.217637e-002, 1.396598e-002, 1.569090e-002, - 1.736337e-002, 1.895222e-002, 2.042967e-002, 2.181469e-002, - 2.311226e-002, 2.432504e-002, 2.543318e-002, 2.641692e-002, - 2.728402e-002, 2.801533e-002, 2.858587e-002, 2.899779e-002, - 2.923672e-002, 2.929332e-002, 2.915666e-002, 2.885519e-002, - 2.836062e-002, 2.765575e-002, 2.682927e-002, 2.595272e-002, - 2.501202e-002, 2.401254e-002, 2.294979e-002, 2.183288e-002, - 2.067284e-002, 1.947250e-002, 1.824441e-002, 1.696697e-002, - 1.563558e-002, 1.424939e-002, 1.279818e-002, 1.130050e-002, - 9.761677e-003, 8.169598e-003, 6.544112e-003, 4.870416e-003, - 3.201319e-003, 1.495312e-003, -2.391932e-004, -2.028801e-003, - -3.868455e-003, -5.752482e-003, -7.677679e-003, -9.623411e-003, - -1.156313e-002, -1.350417e-002, -1.541146e-002, -1.729453e-002, - -1.916687e-002, -2.104142e-002, -2.292456e-002, -2.479666e-002, - -2.665514e-002, -2.850366e-002, -3.031820e-002, -3.209595e-002, - -3.379983e-002, -3.542183e-002, -3.703161e-002, -3.863159e-002, - -4.018645e-002, -4.171311e-002, -4.321981e-002, -4.470625e-002, - -4.615018e-002, -4.759044e-002, -4.899370e-002, -5.035916e-002, - -5.171634e-002, -5.305718e-002, -5.437612e-002, -5.567143e-002, - -5.693880e-002, -5.817563e-002, -5.938301e-002, -6.057495e-002, - -6.165224e-002, -6.239996e-002, -6.288807e-002, -6.310813e-002, - -6.304225e-002, -6.274588e-002, -6.223194e-002, -6.151374e-002, - -6.060617e-002, -5.951362e-002, -5.823577e-002, -5.669260e-002, - -5.497676e-002, -5.312546e-002, -5.115961e-002, -4.908480e-002, - -4.692624e-002, -4.469585e-002, -4.239173e-002, -4.003179e-002, - -3.762882e-002, -3.510542e-002, -3.248061e-002, -2.983615e-002, - -2.718292e-002, -2.450721e-002, -2.186227e-002, -1.928651e-002, - -1.675118e-002, -1.427079e-002, -1.186932e-002, -9.534933e-003, - -7.243229e-003, -5.089121e-003, -3.083293e-003, -1.213478e-003, - 4.731847e-004, 1.981149e-003, 3.270859e-003, 4.333614e-003, - 5.161470e-003, 5.741807e-003, 6.406489e-003, 7.034348e-003, - 7.616438e-003, 8.167287e-003, 8.714513e-003, 9.273391e-003, - 9.793022e-003, 1.028839e-002, 1.073545e-002, 1.115733e-002, - 1.169378e-002, 1.222857e-002, 1.271281e-002, 1.315413e-002, - 1.353840e-002, 1.389054e-002, 1.418729e-002, 1.443103e-002, - 1.460469e-002, 1.471177e-002, 1.487664e-002, 1.504678e-002, - 1.514392e-002, 1.516624e-002, 1.510223e-002, 1.498710e-002, - 1.481404e-002, 1.458769e-002, 1.431196e-002, 1.388478e-002, - 1.346083e-002, 1.309219e-002, 1.267070e-002, 1.224261e-002, - 1.178148e-002, 1.129222e-002, 1.077762e-002, 1.022005e-002, - 9.613861e-003, 8.964988e-003, 8.339712e-003, 7.957197e-003, - 7.547688e-003, 7.104776e-003, 6.659806e-003, 6.220743e-003, - 5.750010e-003, 5.278891e-003, 4.813104e-003, 4.305861e-003, - 3.780783e-003, 3.493868e-003, 3.213514e-003, 2.930936e-003, - 2.577392e-003, 2.103517e-003, 1.588320e-003, 1.031697e-003, - 4.160159e-004, -2.302921e-004, -9.216502e-004, -1.441445e-003, - -1.877119e-003, -2.408108e-003, -2.971337e-003, -3.592728e-003, - -4.240805e-003, -4.905256e-003, -5.591039e-003, -6.274458e-003, - -6.966553e-003, -7.533210e-003, -7.939772e-003, -8.315049e-003, - -8.679835e-003, -9.048308e-003, -9.410999e-003, -9.786281e-003, - -1.015407e-002, -1.051360e-002, -1.089604e-002, -1.122111e-002, - -1.128243e-002, -1.133351e-002, -1.137950e-002, -1.143854e-002, - -1.148168e-002, -1.158462e-002, -1.169562e-002, -1.178234e-002, - -1.188448e-002, -1.197091e-002, -1.168305e-002, -1.135325e-002, - -1.099651e-002, -1.062240e-002, -1.018116e-002, -9.751312e-003, - -9.338056e-003, -8.937818e-003, -8.577245e-003, -8.201494e-003, - -7.495357e-003, -6.715825e-003, -5.988625e-003, -5.229338e-003, - -4.485564e-003, -3.763601e-003, -3.054500e-003, -2.371826e-003, - -1.712284e-003, -1.076098e-003, -3.043154e-004, 6.480224e-004, - 1.626253e-003, 2.564702e-003, 3.408403e-003, 4.187223e-003, - 4.902202e-003, 5.590339e-003, 6.173503e-003, 6.698124e-003, - 7.307476e-003, 8.139043e-003, 8.930406e-003, 9.680552e-003, - 1.035973e-002, 1.098169e-002, 1.153050e-002, 1.206770e-002, - 1.255645e-002, 1.300816e-002, 1.347322e-002, 1.429620e-002, - 1.502401e-002, 1.570974e-002, 1.634626e-002, 1.691635e-002, - 1.742726e-002, 1.787101e-002, 1.825502e-002, 1.856556e-002, - 1.877927e-002, 1.930438e-002, 1.986019e-002, 2.038222e-002, - 2.080783e-002, 2.112561e-002, 2.135111e-002, 2.146985e-002, - 2.149711e-002, 2.142067e-002, 2.129241e-002, 2.145973e-002, - 2.168974e-002, 2.183086e-002, 2.188543e-002, 2.182534e-002, - 2.169179e-002, 2.150543e-002, 2.125158e-002, 2.092572e-002, - 2.053000e-002, 2.023790e-002, 2.019688e-002, 2.007790e-002, - 1.983821e-002, 1.956790e-002, 1.924107e-002, 1.881699e-002, - 1.823102e-002, 1.762668e-002, 1.709430e-002, 1.660872e-002, - 1.641530e-002, 1.625456e-002, 1.604916e-002, 1.568893e-002, - 1.522204e-002, 1.478688e-002, 1.434127e-002, 1.377839e-002, - 1.314822e-002, 1.241307e-002, 1.206826e-002, 1.166967e-002, - 1.121055e-002, 1.069433e-002, 1.016206e-002, 9.413642e-003, - 8.503509e-003, 7.520136e-003, 6.519220e-003, 5.500631e-003, - 4.769646e-003, 4.178043e-003, 3.592338e-003, 2.913984e-003, - 2.199171e-003, 1.462972e-003, 7.779389e-004, 2.084787e-005, - -9.078644e-004, -1.853984e-003, -2.466524e-003, -2.824434e-003, - -3.278836e-003, -3.700410e-003, -4.395717e-003, -5.134335e-003, - -5.896478e-003, -6.823869e-003, -7.822774e-003, -8.828758e-003, - -9.819009e-003, -1.041423e-002, -1.098042e-002, -1.160713e-002, - -1.224551e-002, -1.291294e-002, -1.365564e-002, -1.435375e-002, - -1.509704e-002, -1.593947e-002, -1.676769e-002, -1.732561e-002, - -1.789189e-002, -1.828965e-002, -1.888761e-002, -1.955111e-002, - -2.024290e-002, -2.095046e-002, -2.160426e-002, -2.236804e-002, - -2.314956e-002, -2.364632e-002, -2.404785e-002, -2.421678e-002, - -2.439650e-002, -2.444100e-002, -2.435540e-002, -2.441270e-002, - -2.431735e-002, -2.412379e-002, -2.387394e-002, -2.336155e-002, - -2.259983e-002, -2.164169e-002, -2.057117e-002, -1.945146e-002, - -1.832349e-002, -1.712395e-002, -1.594495e-002, -1.466801e-002, - -1.340086e-002, -1.204418e-002, -1.008648e-002, -8.133784e-003, - -6.166518e-003, -4.660371e-003, -3.121583e-003, -1.512601e-003, - 6.328473e-005, 1.461581e-003, 2.537343e-003, 3.743347e-003, - 5.776703e-003, 7.632033e-003, 9.183599e-003, 1.070731e-002, - 1.194166e-002, 1.268088e-002, 1.345295e-002, 1.411483e-002, - 1.442900e-002, 1.429950e-002, 1.505580e-002, 1.589600e-002, - 1.682960e-002, 1.775878e-002, 1.857271e-002, 1.863971e-002, - 1.847128e-002, 1.833125e-002, 1.768502e-002, 1.711928e-002, - 1.708569e-002, 1.766101e-002, 1.782570e-002, 1.759655e-002, - 1.710381e-002, 1.643837e-002, 1.561701e-002, 1.460302e-002, - 1.390973e-002, 1.331350e-002, 1.298934e-002, 1.328848e-002, - 1.346237e-002, 1.297064e-002, 1.208138e-002, 1.094080e-002, - 9.929589e-003, 8.716210e-003, 7.684127e-003, 6.666715e-003, - 5.965556e-003, 6.340534e-003, 5.833938e-003, 5.243076e-003, - 3.878449e-003, 1.108714e-003, -1.281258e-003, -3.548815e-003, - -5.998027e-003, -8.507627e-003, -1.107071e-002, -1.110974e-002, - -1.138689e-002, -1.200857e-002, -1.289555e-002, -1.499827e-002, - -1.808298e-002, -2.029365e-002, -2.191161e-002, -2.269953e-002, - -2.616956e-002, -2.731351e-002, -2.711919e-002, -2.581373e-002, - -2.250549e-002, -1.629162e-002, -8.876738e-003, 2.463739e-003, - 1.361066e-002, 2.374440e-002, 2.782711e-002, 1.328086e-002, - 1.319623e-002, 1.016226e-002, 7.906934e-007, -1.263750e-002, - -1.672337e-002, -1.318413e-002, -2.919178e-003, 1.521017e-002, - 3.611961e-002, -6.700800e-002, -3.611864e-002, 2.810918e-002, - 9.138928e-002, 1.482023e-001, 1.936481e-001, -4.153961e-001, - -3.605610e-001, -2.574813e-001, -1.429867e-001, 0.000000e+000 - }, - { - 0.000000e+000, -1.134635e-002, -1.761768e-002, -2.172598e-002, - -2.450887e-002, -2.662351e-002, -2.840945e-002, -3.073955e-002, - -3.358147e-002, -3.760858e-002, -4.122392e-002, -4.399597e-002, - -4.614641e-002, -4.788043e-002, -4.921348e-002, -5.007621e-002, - -5.042675e-002, -5.016633e-002, -4.930281e-002, -4.810463e-002, - -4.676141e-002, -4.550188e-002, -4.431838e-002, -4.308964e-002, - -4.182416e-002, -4.043271e-002, -3.893848e-002, -3.737988e-002, - -3.579509e-002, -3.417506e-002, -3.252385e-002, -3.093217e-002, - -2.950361e-002, -2.796727e-002, -2.633065e-002, -2.460131e-002, - -2.279524e-002, -2.090474e-002, -1.894569e-002, -1.690435e-002, - -1.481552e-002, -1.276824e-002, -1.105210e-002, -9.432472e-003, - -7.923308e-003, -6.576621e-003, -5.777790e-003, -6.166661e-003, - -6.449559e-003, -5.335349e-003, -3.724531e-003, -2.005136e-003, - -4.424515e-004, 1.186672e-003, 2.918413e-003, 4.750159e-003, - 6.649490e-003, 8.603801e-003, 1.062992e-002, 1.268659e-002, - 1.476939e-002, 1.687841e-002, 1.879841e-002, 2.060377e-002, - 2.239515e-002, 2.414483e-002, 2.586930e-002, 2.752673e-002, - 2.910300e-002, 3.060077e-002, 3.198057e-002, 3.326320e-002, - 3.434649e-002, 3.516638e-002, 3.586284e-002, 3.645734e-002, - 3.691280e-002, 3.724127e-002, 3.746032e-002, 3.763468e-002, - 3.782962e-002, 3.802922e-002, 3.817559e-002, 3.809230e-002, - 3.801298e-002, 3.792488e-002, 3.785148e-002, 3.775788e-002, - 3.766953e-002, 3.754227e-002, 3.735041e-002, 3.713261e-002, - 3.687700e-002, 3.636428e-002, 3.582948e-002, 3.529925e-002, - 3.480637e-002, 3.432396e-002, 3.388674e-002, 3.352976e-002, - 3.323339e-002, 3.304068e-002, 3.295169e-002, 3.281370e-002, - 3.270403e-002, 3.261787e-002, 3.259480e-002, 3.257314e-002, - 3.249488e-002, 3.233885e-002, 3.209231e-002, 3.177377e-002, - 3.138544e-002, 3.085058e-002, 3.014784e-002, 2.939064e-002, - 2.859129e-002, 2.770948e-002, 2.677170e-002, 2.577703e-002, - 2.472110e-002, 2.366825e-002, 2.264275e-002, 2.164012e-002, - 2.061104e-002, 1.968232e-002, 1.885304e-002, 1.810784e-002, - 1.741880e-002, 1.676511e-002, 1.613576e-002, 1.551279e-002, - 1.490572e-002, 1.430344e-002, 1.351942e-002, 1.273312e-002, - 1.193898e-002, 1.114232e-002, 1.032225e-002, 9.481449e-003, - 8.626952e-003, 7.760422e-003, 6.879351e-003, 5.986261e-003, - 4.966975e-003, 3.908592e-003, 2.863065e-003, 1.868125e-003, - 9.172080e-004, 1.046373e-005, -8.433607e-004, -1.644120e-003, - -2.405945e-003, -3.101746e-003, -3.830488e-003, -4.576447e-003, - -5.270790e-003, -5.886227e-003, -6.424027e-003, -6.907665e-003, - -7.345706e-003, -7.757575e-003, -8.152908e-003, -8.531001e-003, - -8.940108e-003, -9.448186e-003, -9.983748e-003, -1.055782e-002, - -1.119748e-002, -1.191269e-002, -1.268668e-002, -1.349656e-002, - -1.430581e-002, -1.511025e-002, -1.593260e-002, -1.687315e-002, - -1.785384e-002, -1.886695e-002, -1.991287e-002, -2.097233e-002, - -2.201288e-002, -2.298774e-002, -2.382877e-002, -2.450900e-002, - -2.504305e-002, -2.553002e-002, -2.593288e-002, -2.623671e-002, - -2.646666e-002, -2.660288e-002, -2.665385e-002, -2.663567e-002, - -2.654713e-002, -2.638919e-002, -2.618274e-002, -2.598657e-002, - -2.578506e-002, -2.552246e-002, -2.522183e-002, -2.488480e-002, - -2.450918e-002, -2.409939e-002, -2.366219e-002, -2.319039e-002, - -2.271563e-002, -2.227302e-002, -2.192462e-002, -2.165914e-002, - -2.149167e-002, -2.139348e-002, -2.132619e-002, -2.124987e-002, - -2.116362e-002, -2.105774e-002, -2.092112e-002, -2.078883e-002, - -2.071523e-002, -2.067486e-002, -2.065688e-002, -2.066002e-002, - -2.066081e-002, -2.062131e-002, -2.050353e-002, -2.027333e-002, - -1.994908e-002, -1.954934e-002, -1.917789e-002, -1.879192e-002, - -1.840185e-002, -1.801653e-002, -1.762463e-002, -1.722439e-002, - -1.682430e-002, -1.642662e-002, -1.603284e-002, -1.563143e-002, - -1.525672e-002, -1.489269e-002, -1.454465e-002, -1.423131e-002, - -1.394509e-002, -1.367500e-002, -1.342363e-002, -1.321398e-002, - -1.303203e-002, -1.289496e-002, -1.282691e-002, -1.288545e-002, - -1.307339e-002, -1.335941e-002, -1.366069e-002, -1.394481e-002, - -1.418422e-002, -1.438234e-002, -1.454117e-002, -1.464306e-002, - -1.470022e-002, -1.472815e-002, -1.471898e-002, -1.467934e-002, - -1.459673e-002, -1.446339e-002, -1.424109e-002, -1.392312e-002, - -1.354917e-002, -1.312112e-002, -1.265796e-002, -1.217671e-002, - -1.168649e-002, -1.120191e-002, -1.071206e-002, -1.025145e-002, - -9.822574e-003, -9.461608e-003, -9.170917e-003, -8.969195e-003, - -8.873108e-003, -8.873420e-003, -8.928497e-003, -9.043345e-003, - -9.197409e-003, -9.402699e-003, -9.669787e-003, -9.999986e-003, - -1.041953e-002, -1.091938e-002, -1.147845e-002, -1.209849e-002, - -1.272653e-002, -1.332109e-002, -1.382004e-002, -1.419416e-002, - -1.447632e-002, -1.469482e-002, -1.485244e-002, -1.495762e-002, - -1.503236e-002, -1.506365e-002, -1.504705e-002, -1.497501e-002, - -1.486682e-002, -1.471548e-002, -1.453218e-002, -1.433200e-002, - -1.413336e-002, -1.392293e-002, -1.370104e-002, -1.345866e-002, - -1.319155e-002, -1.289780e-002, -1.258034e-002, -1.223440e-002, - -1.185874e-002, -1.151726e-002, -1.121679e-002, -1.095117e-002, - -1.073422e-002, -1.052613e-002, -1.029444e-002, -1.000695e-002, - -9.640678e-003, -9.202590e-003, -8.682031e-003, -8.097335e-003, - -7.463596e-003, -6.806655e-003, -6.132800e-003, -5.421541e-003, - -4.642184e-003, -3.783921e-003, -2.794062e-003, -1.694022e-003, - -4.783440e-004, 7.974099e-004, 2.094092e-003, 3.421952e-003, - 4.763768e-003, 6.103750e-003, 7.448706e-003, 8.775891e-003, - 1.007622e-002, 1.134523e-002, 1.255775e-002, 1.369763e-002, - 1.475516e-002, 1.572160e-002, 1.658871e-002, 1.733925e-002, - 1.797791e-002, 1.849035e-002, 1.889432e-002, 1.920618e-002, - 1.949973e-002, 1.984141e-002, 2.026283e-002, 2.078879e-002, - 2.138739e-002, 2.207597e-002, 2.283496e-002, 2.365560e-002, - 2.453674e-002, 2.544379e-002, 2.636669e-002, 2.729971e-002, - 2.822946e-002, 2.914986e-002, 3.005259e-002, 3.085186e-002, - 3.148772e-002, 3.196431e-002, 3.231862e-002, 3.253866e-002, - 3.262549e-002, 3.256768e-002, 3.237697e-002, 3.206063e-002, - 3.162061e-002, 3.105718e-002, 3.036829e-002, 2.954590e-002, - 2.863446e-002, 2.763093e-002, 2.656309e-002, 2.542809e-002, - 2.423808e-002, 2.299217e-002, 2.170289e-002, 2.036785e-002, - 1.901731e-002, 1.769542e-002, 1.641823e-002, 1.519938e-002, - 1.405123e-002, 1.293986e-002, 1.186017e-002, 1.082860e-002, - 9.827379e-003, 8.851931e-003, 7.925650e-003, 7.044618e-003, - 6.204161e-003, 5.432177e-003, 4.728357e-003, 4.078939e-003, - 3.495290e-003, 2.976061e-003, 2.539528e-003, 2.155430e-003, - 1.829250e-003, 1.560023e-003, 1.346070e-003, 1.190943e-003, - 1.108082e-003, 1.083473e-003, 1.121048e-003, 1.172564e-003, - 1.236166e-003, 1.320187e-003, 1.420453e-003, 1.545502e-003, - 1.702099e-003, 1.918865e-003, 2.198350e-003, 2.520087e-003, - 2.900698e-003, 3.319243e-003, 3.790287e-003, 4.316773e-003, - 4.921100e-003, 5.622181e-003, 6.405014e-003, 7.267590e-003, - 8.176287e-003, 9.134689e-003, 1.012979e-002, 1.113330e-002, - 1.214428e-002, 1.315724e-002, 1.414591e-002, 1.510960e-002, - 1.601309e-002, 1.680948e-002, 1.749631e-002, 1.811641e-002, - 1.870098e-002, 1.927058e-002, 1.985717e-002, 2.045737e-002, - 2.101223e-002, 2.151884e-002, 2.198180e-002, 2.236947e-002, - 2.270164e-002, 2.299610e-002, 2.329606e-002, 2.363499e-002, - 2.407524e-002, 2.462409e-002, 2.520773e-002, 2.581184e-002, - 2.643603e-002, 2.704215e-002, 2.761865e-002, 2.817393e-002, - 2.868923e-002, 2.915006e-002, 2.956677e-002, 2.995479e-002, - 3.027849e-002, 3.053895e-002, 3.073239e-002, 3.088223e-002, - 3.095852e-002, 3.096713e-002, 3.093097e-002, 3.082940e-002, - 3.066766e-002, 3.046607e-002, 3.021635e-002, 2.990748e-002, - 2.952837e-002, 2.910198e-002, 2.862251e-002, 2.806914e-002, - 2.745959e-002, 2.680496e-002, 2.607363e-002, 2.527104e-002, - 2.438915e-002, 2.342297e-002, 2.236284e-002, 2.126747e-002, - 2.014990e-002, 1.895835e-002, 1.772616e-002, 1.647878e-002, - 1.519420e-002, 1.385465e-002, 1.248300e-002, 1.112366e-002, - 9.780798e-003, 8.450238e-003, 7.197687e-003, 5.995258e-003, - 4.836105e-003, 3.740746e-003, 2.744282e-003, 1.831381e-003, - 9.665268e-004, 1.915072e-004, -5.209305e-004, -1.204745e-003, - -1.871157e-003, -2.486003e-003, -3.106375e-003, -3.735158e-003, - -4.360172e-003, -5.002425e-003, -5.706630e-003, -6.521167e-003, - -7.406076e-003, -8.398183e-003, -9.457623e-003, -1.050339e-002, - -1.149675e-002, -1.245200e-002, -1.344093e-002, -1.457827e-002, - -1.585482e-002, -1.727961e-002, -1.884394e-002, -2.049308e-002, - -2.227052e-002, -2.409919e-002, -2.597378e-002, -2.784197e-002, - -2.973761e-002, -3.164099e-002, -3.356992e-002, -3.552059e-002, - -3.750655e-002, -3.953418e-002, -4.155175e-002, -4.357000e-002, - -4.559511e-002, -4.762808e-002, -4.962741e-002, -5.161729e-002, - -5.362187e-002, -5.563920e-002, -5.762012e-002, -5.956637e-002, - -6.145480e-002, -6.321095e-002, -6.481960e-002, -6.629907e-002, - -6.759619e-002, -6.871628e-002, -6.964697e-002, -7.041450e-002, - -7.096888e-002, -7.128985e-002, -7.137707e-002, -7.115765e-002, - -7.054324e-002, -6.957915e-002, -6.839251e-002, -6.706672e-002, - -6.561954e-002, -6.409303e-002, -6.249140e-002, -6.079951e-002, - -5.900849e-002, -5.715343e-002, -5.520496e-002, -5.318135e-002, - -5.109537e-002, -4.895027e-002, -4.675397e-002, -4.450474e-002, - -4.222495e-002, -3.994460e-002, -3.766433e-002, -3.539337e-002, - -3.310195e-002, -3.078810e-002, -2.848031e-002, -2.621025e-002, - -2.399685e-002, -2.187435e-002, -1.986084e-002, -1.787407e-002, - -1.586948e-002, -1.378054e-002, -1.158356e-002, -9.252804e-003, - -6.802743e-003, -4.287197e-003, -1.742612e-003, 8.169265e-004, - 3.399585e-003, 5.981001e-003, 8.651186e-003, 1.142270e-002, - 1.432335e-002, 1.731409e-002, 2.034700e-002, 2.340521e-002, - 2.647639e-002, 2.955898e-002, 3.262615e-002, 3.562890e-002, - 3.859948e-002, 4.147859e-002, 4.427144e-002, 4.699205e-002, - 4.962761e-002, 5.213458e-002, 5.443235e-002, 5.655623e-002, - 5.854896e-002, 6.040730e-002, 6.208631e-002, 6.356527e-002, - 6.466013e-002, 6.515486e-002, 6.516355e-002, 6.474298e-002, - 6.390809e-002, 6.271160e-002, 6.114620e-002, 5.924839e-002, - 5.702263e-002, 5.454731e-002, 5.178598e-002, 4.876061e-002, - 4.546938e-002, 4.197774e-002, 3.830514e-002, 3.447554e-002, - 3.048987e-002, 2.634874e-002, 2.212412e-002, 1.782598e-002, - 1.345852e-002, 9.023357e-003, 4.608042e-003, 2.105558e-004, - -4.184511e-003, -8.512561e-003, -1.269997e-002, -1.676956e-002, - -2.071264e-002, -2.453194e-002, -2.817014e-002, -3.162267e-002, - -3.492074e-002, -3.802052e-002, -4.083489e-002, -4.333077e-002, - -4.548768e-002, -4.726003e-002, -4.860121e-002, -4.953436e-002, - -5.005490e-002, -5.019485e-002, -5.026031e-002, -5.025901e-002, - -5.017142e-002, -4.994816e-002, -4.950109e-002, -4.885383e-002, - -4.805245e-002, -4.705928e-002, -4.596582e-002, -4.474548e-002, - -4.342378e-002, -4.195684e-002, -4.036735e-002, -3.867625e-002, - -3.690848e-002, -3.504698e-002, -3.308433e-002, -3.103943e-002, - -2.897716e-002, -2.684049e-002, -2.467231e-002, -2.248166e-002, - -2.025574e-002, -1.794159e-002, -1.557267e-002, -1.313351e-002, - -1.065813e-002, -8.174320e-003, -5.664156e-003, -3.140808e-003, - -6.521983e-004, 1.780212e-003, 4.187863e-003, 6.588942e-003, - 9.028893e-003, 1.150922e-002, 1.403551e-002, 1.653118e-002, - 1.898734e-002, 2.143321e-002, 2.384589e-002, 2.619629e-002, - 2.857339e-002, 3.098273e-002, 3.346770e-002, 3.610277e-002, - 3.878851e-002, 4.140053e-002, 4.402770e-002, 4.666774e-002, - 4.930988e-002, 5.181500e-002, 5.425086e-002, 5.658311e-002, - 5.865812e-002, 6.037949e-002, 6.163002e-002, 6.242446e-002, - 6.281408e-002, 6.282743e-002, 6.249198e-002, 6.180059e-002, - 6.072940e-002, 5.935801e-002, 5.768675e-002, 5.580688e-002, - 5.367001e-002, 5.127884e-002, 4.868102e-002, 4.596570e-002, - 4.315876e-002, 4.023312e-002, 3.716553e-002, 3.404403e-002, - 3.087525e-002, 2.778545e-002, 2.468402e-002, 2.154486e-002, - 1.851727e-002, 1.549370e-002, 1.256584e-002, 9.655635e-003, - 6.738544e-003, 3.898521e-003, 1.180852e-003, -1.529485e-003, - -4.137514e-003, -6.665332e-003, -9.092490e-003, -1.142159e-002, - -1.361609e-002, -1.565811e-002, -1.750006e-002, -1.898851e-002, - -2.006091e-002, -2.068463e-002, -2.103815e-002, -2.136345e-002, - -2.162603e-002, -2.185402e-002, -2.198784e-002, -2.204479e-002, - -2.212886e-002, -2.210631e-002, -2.195764e-002, -2.175342e-002, - -2.143846e-002, -2.109432e-002, -2.080349e-002, -2.046973e-002, - -2.012012e-002, -1.963370e-002, -1.915468e-002, -1.883033e-002, - -1.855668e-002, -1.835668e-002, -1.812133e-002, -1.791395e-002, - -1.776481e-002, -1.772381e-002, -1.778464e-002, -1.791073e-002, - -1.807600e-002, -1.831436e-002, -1.864740e-002, -1.889951e-002, - -1.901273e-002, -1.899729e-002, -1.891988e-002, -1.879647e-002, - -1.856912e-002, -1.825729e-002, -1.775627e-002, -1.717409e-002, - -1.655150e-002, -1.582685e-002, -1.500763e-002, -1.411156e-002, - -1.318791e-002, -1.235616e-002, -1.148102e-002, -1.055413e-002, - -9.598753e-003, -8.599481e-003, -7.702566e-003, -6.699917e-003, - -5.682008e-003, -4.645966e-003, -3.701883e-003, -2.781817e-003, - -1.835791e-003, -9.534839e-004, -1.358727e-004, 7.645920e-004, - 1.650160e-003, 2.380812e-003, 3.039520e-003, 3.667354e-003, - 4.217587e-003, 4.730223e-003, 5.257617e-003, 5.782789e-003, - 6.378371e-003, 6.958047e-003, 7.662739e-003, 8.456015e-003, - 9.257192e-003, 9.893800e-003, 1.034700e-002, 1.080941e-002, - 1.139111e-002, 1.194571e-002, 1.253214e-002, 1.325023e-002, - 1.413519e-002, 1.495963e-002, 1.581326e-002, 1.661442e-002, - 1.732529e-002, 1.798195e-002, 1.853289e-002, 1.900233e-002, - 1.927075e-002, 1.950451e-002, 1.986926e-002, 2.008369e-002, - 1.998674e-002, 1.975759e-002, 1.937581e-002, 1.880336e-002, - 1.810052e-002, 1.724109e-002, 1.622051e-002, 1.508894e-002, - 1.417387e-002, 1.336427e-002, 1.250931e-002, 1.179961e-002, - 1.107098e-002, 1.024073e-002, 9.518839e-003, 8.938140e-003, - 8.282847e-003, 7.703428e-003, 7.377786e-003, 7.167064e-003, - 6.834357e-003, 6.545050e-003, 6.282050e-003, 6.102883e-003, - 5.866253e-003, 5.460511e-003, 5.150162e-003, 4.929310e-003, - 4.548783e-003, 4.411511e-003, 4.324230e-003, 4.202749e-003, - 4.087639e-003, 3.786271e-003, 3.442715e-003, 2.974692e-003, - 2.394955e-003, 1.544147e-003, 8.778835e-004, 4.522713e-004, - 3.530497e-005, -3.063444e-004, -6.972603e-004, -1.039101e-003, - -1.309780e-003, -1.669319e-003, -2.035110e-003, -2.346166e-003, - -2.567850e-003, -2.479517e-003, -2.225912e-003, -1.964386e-003, - -2.096616e-003, -2.368138e-003, -2.593145e-003, -3.040563e-003, - -3.441872e-003, -3.874818e-003, -4.411709e-003, -4.987393e-003, - -5.445991e-003, -5.639614e-003, -6.051010e-003, -6.485067e-003, - -7.117362e-003, -7.936652e-003, -8.953812e-003, -9.892648e-003, - -1.089533e-002, -1.166788e-002, -1.201519e-002, -1.236891e-002, - -1.219419e-002, -1.211962e-002, -1.212494e-002, -1.211329e-002, - -1.180531e-002, -1.140572e-002, -1.048261e-002, -9.355053e-003, - -8.280176e-003, -6.966247e-003, -5.767157e-003, -4.951729e-003, - -4.522208e-003, -4.305421e-003, -4.249426e-003, -4.522457e-003, - -4.834559e-003, -5.059925e-003, -5.843616e-003, -6.688421e-003, - -7.958458e-003, -9.172632e-003, -1.018046e-002, -1.154384e-002, - -1.274315e-002, -1.368567e-002, -1.469643e-002, -1.594366e-002, - -1.779117e-002, -1.989509e-002, -2.216908e-002, -2.380087e-002, - -2.536860e-002, -2.637852e-002, -2.705018e-002, -2.768121e-002, - -2.750716e-002, -2.716176e-002, -2.695065e-002, -2.718447e-002, - -2.722912e-002, -2.712754e-002, -2.668310e-002, -2.614494e-002, - -2.507963e-002, -2.352640e-002, -2.022987e-002, -1.610201e-002, - -1.155828e-002, -8.420893e-003, -5.926222e-003, -3.661171e-003, - -1.491929e-003, 7.715562e-004, 3.631197e-003, 7.567342e-003, - 1.182876e-002, 1.664294e-002, 2.233888e-002, 2.625049e-002, - 2.852705e-002, 3.084693e-002, 3.224908e-002, 3.214723e-002, - 3.402001e-002, 3.717919e-002, 4.158347e-002, 4.632704e-002, - 5.109931e-002, 5.293910e-002, 5.398879e-002, 5.392872e-002, - 5.179496e-002, 4.715900e-002, 3.908661e-002, 3.299274e-002, - 2.851598e-002, 2.509327e-002, 2.093308e-002, 2.970031e-002, - 1.645794e-002, 5.349363e-004, -1.330958e-002, -2.130251e-002, - -2.298760e-002, -1.978978e-002, -1.212300e-002, -6.487657e-003, - -4.397713e-003, -4.579569e-001, -1.238441e-001, 1.341668e-002, - 6.288721e-002, 9.382289e-002, 1.147923e-001, 8.623371e-002, - 8.311075e-002, 6.155081e-002, 3.434061e-002, 0.000000e+000 - }, - { - 0.000000e+000, -2.895906e-003, -4.464823e-003, -5.525560e-003, - -6.418178e-003, -7.351525e-003, -8.433005e-003, -9.848137e-003, - -1.172874e-002, -1.475416e-002, -1.767415e-002, -2.025072e-002, - -2.253928e-002, -2.455384e-002, -2.637298e-002, -2.791555e-002, - -2.897849e-002, -2.963993e-002, -2.986200e-002, -2.984942e-002, - -2.965339e-002, -2.947005e-002, -2.934270e-002, -2.909344e-002, - -2.873585e-002, -2.823720e-002, -2.765083e-002, -2.696246e-002, - -2.622431e-002, -2.540962e-002, -2.452170e-002, -2.360582e-002, - -2.281415e-002, -2.195914e-002, -2.104289e-002, -2.004385e-002, - -1.897010e-002, -1.785561e-002, -1.664630e-002, -1.537661e-002, - -1.407451e-002, -1.281055e-002, -1.180363e-002, -1.083833e-002, - -9.926934e-003, -9.065134e-003, -8.452136e-003, -8.423315e-003, - -8.347153e-003, -7.601521e-003, -6.583448e-003, -5.488648e-003, - -4.505461e-003, -3.485897e-003, -2.393524e-003, -1.236086e-003, - -4.525137e-005, 1.226292e-003, 2.516364e-003, 3.835360e-003, - 5.175180e-003, 6.488151e-003, 7.676985e-003, 8.771173e-003, - 9.878409e-003, 1.101293e-002, 1.212535e-002, 1.324902e-002, - 1.434264e-002, 1.538974e-002, 1.642345e-002, 1.743267e-002, - 1.831022e-002, 1.905284e-002, 1.974648e-002, 2.041558e-002, - 2.103339e-002, 2.161407e-002, 2.212496e-002, 2.264361e-002, - 2.311965e-002, 2.357565e-002, 2.394200e-002, 2.412315e-002, - 2.424235e-002, 2.435109e-002, 2.443311e-002, 2.449073e-002, - 2.451687e-002, 2.448145e-002, 2.437815e-002, 2.422221e-002, - 2.401314e-002, 2.362008e-002, 2.317030e-002, 2.275354e-002, - 2.233428e-002, 2.196548e-002, 2.161464e-002, 2.129583e-002, - 2.106744e-002, 2.091527e-002, 2.085410e-002, 2.075596e-002, - 2.066528e-002, 2.066551e-002, 2.071400e-002, 2.077096e-002, - 2.082086e-002, 2.087344e-002, 2.089415e-002, 2.089392e-002, - 2.088146e-002, 2.074785e-002, 2.051784e-002, 2.026943e-002, - 1.998084e-002, 1.968316e-002, 1.936832e-002, 1.902071e-002, - 1.865995e-002, 1.828210e-002, 1.789596e-002, 1.749490e-002, - 1.698811e-002, 1.648612e-002, 1.598793e-002, 1.550545e-002, - 1.503698e-002, 1.457407e-002, 1.411040e-002, 1.364017e-002, - 1.316552e-002, 1.269563e-002, 1.207341e-002, 1.144819e-002, - 1.083859e-002, 1.022878e-002, 9.619818e-003, 9.028038e-003, - 8.429258e-003, 7.847054e-003, 7.278926e-003, 6.712614e-003, - 6.044726e-003, 5.366690e-003, 4.692043e-003, 4.032066e-003, - 3.394625e-003, 2.783495e-003, 2.195134e-003, 1.629404e-003, - 1.094357e-003, 5.715504e-004, -1.924332e-006, -5.883363e-004, - -1.148628e-003, -1.690679e-003, -2.196571e-003, -2.668668e-003, - -3.142679e-003, -3.605847e-003, -4.031041e-003, -4.440960e-003, - -4.906874e-003, -5.445514e-003, -5.960068e-003, -6.476339e-003, - -7.007528e-003, -7.544886e-003, -8.078363e-003, -8.619945e-003, - -9.157969e-003, -9.683805e-003, -1.020333e-002, -1.081033e-002, - -1.141298e-002, -1.201852e-002, -1.262208e-002, -1.321078e-002, - -1.376765e-002, -1.427985e-002, -1.473918e-002, -1.513532e-002, - -1.546545e-002, -1.581542e-002, -1.613551e-002, -1.640617e-002, - -1.661191e-002, -1.676290e-002, -1.687407e-002, -1.693655e-002, - -1.694720e-002, -1.690485e-002, -1.683114e-002, -1.675189e-002, - -1.665936e-002, -1.652267e-002, -1.634763e-002, -1.614576e-002, - -1.589294e-002, -1.559875e-002, -1.526284e-002, -1.489721e-002, - -1.451902e-002, -1.415455e-002, -1.382346e-002, -1.350522e-002, - -1.320864e-002, -1.292238e-002, -1.263742e-002, -1.234357e-002, - -1.205038e-002, -1.175984e-002, -1.146408e-002, -1.116925e-002, - -1.092516e-002, -1.068761e-002, -1.046723e-002, -1.027163e-002, - -1.008301e-002, -9.890534e-003, -9.683735e-003, -9.459873e-003, - -9.223617e-003, -8.978837e-003, -8.789214e-003, -8.604700e-003, - -8.433281e-003, -8.272549e-003, -8.114165e-003, -7.964128e-003, - -7.821400e-003, -7.694295e-003, -7.580952e-003, -7.470115e-003, - -7.394893e-003, -7.338279e-003, -7.289685e-003, -7.257113e-003, - -7.232058e-003, -7.205882e-003, -7.173739e-003, -7.150500e-003, - -7.133038e-003, -7.112905e-003, -7.112220e-003, -7.151833e-003, - -7.216250e-003, -7.311456e-003, -7.406469e-003, -7.495108e-003, - -7.567721e-003, -7.630161e-003, -7.682957e-003, -7.721432e-003, - -7.756669e-003, -7.810954e-003, -7.853388e-003, -7.885001e-003, - -7.915443e-003, -7.936768e-003, -7.939238e-003, -7.917802e-003, - -7.883684e-003, -7.830309e-003, -7.775427e-003, -7.735771e-003, - -7.693301e-003, -7.654644e-003, -7.603417e-003, -7.565258e-003, - -7.540294e-003, -7.546296e-003, -7.570913e-003, -7.612079e-003, - -7.690470e-003, -7.813111e-003, -7.946947e-003, -8.095672e-003, - -8.254648e-003, -8.428349e-003, -8.619320e-003, -8.822900e-003, - -9.058603e-003, -9.318053e-003, -9.586064e-003, -9.886948e-003, - -1.018891e-002, -1.048979e-002, -1.076981e-002, -1.100713e-002, - -1.122380e-002, -1.143523e-002, -1.162771e-002, -1.181175e-002, - -1.198539e-002, -1.214402e-002, -1.228983e-002, -1.240941e-002, - -1.251081e-002, -1.259367e-002, -1.265230e-002, -1.269296e-002, - -1.272227e-002, -1.273351e-002, -1.272334e-002, -1.268136e-002, - -1.262898e-002, -1.254743e-002, -1.243833e-002, -1.229681e-002, - -1.210789e-002, -1.191259e-002, -1.169413e-002, -1.145026e-002, - -1.119393e-002, -1.090709e-002, -1.059710e-002, -1.024378e-002, - -9.844418e-003, -9.405516e-003, -8.916922e-003, -8.386253e-003, - -7.823314e-003, -7.238365e-003, -6.632083e-003, -6.000171e-003, - -5.344412e-003, -4.665563e-003, -3.944884e-003, -3.185076e-003, - -2.374058e-003, -1.538019e-003, -6.914272e-004, 1.691596e-004, - 1.035474e-003, 1.901827e-003, 2.766182e-003, 3.606600e-003, - 4.440018e-003, 5.270932e-003, 6.090301e-003, 6.893398e-003, - 7.670102e-003, 8.419785e-003, 9.152211e-003, 9.850711e-003, - 1.050815e-002, 1.112748e-002, 1.171961e-002, 1.228205e-002, - 1.282661e-002, 1.337111e-002, 1.392199e-002, 1.449591e-002, - 1.507698e-002, 1.567535e-002, 1.627194e-002, 1.684452e-002, - 1.742683e-002, 1.800101e-002, 1.856623e-002, 1.912478e-002, - 1.967418e-002, 2.020713e-002, 2.071729e-002, 2.114986e-002, - 2.148201e-002, 2.169531e-002, 2.182030e-002, 2.186470e-002, - 2.182147e-002, 2.168727e-002, 2.148126e-002, 2.121285e-002, - 2.087042e-002, 2.045338e-002, 1.997442e-002, 1.941427e-002, - 1.879391e-002, 1.812959e-002, 1.742822e-002, 1.668334e-002, - 1.591088e-002, 1.510745e-002, 1.428426e-002, 1.343524e-002, - 1.257503e-002, 1.170492e-002, 1.081745e-002, 9.947712e-003, - 9.101623e-003, 8.278347e-003, 7.478579e-003, 6.702645e-003, - 5.945762e-003, 5.207282e-003, 4.499157e-003, 3.816988e-003, - 3.144614e-003, 2.524327e-003, 1.943203e-003, 1.402940e-003, - 9.132676e-004, 4.726362e-004, 9.247422e-005, -2.349733e-004, - -5.208951e-004, -7.558555e-004, -9.505382e-004, -1.085498e-003, - -1.153604e-003, -1.142865e-003, -1.066518e-003, -9.692178e-004, - -8.511381e-004, -7.084129e-004, -5.427060e-004, -3.561664e-004, - -1.619200e-004, 3.297684e-005, 2.574188e-004, 5.092378e-004, - 7.915528e-004, 1.098749e-003, 1.429837e-003, 1.787303e-003, - 2.186829e-003, 2.624918e-003, 3.090365e-003, 3.578799e-003, - 4.090701e-003, 4.624179e-003, 5.185787e-003, 5.766086e-003, - 6.344665e-003, 6.932760e-003, 7.526280e-003, 8.115839e-003, - 8.693244e-003, 9.239771e-003, 9.772190e-003, 1.029266e-002, - 1.081350e-002, 1.135100e-002, 1.187986e-002, 1.241125e-002, - 1.294258e-002, 1.347139e-002, 1.400196e-002, 1.451148e-002, - 1.501099e-002, 1.549069e-002, 1.598339e-002, 1.649516e-002, - 1.701565e-002, 1.755711e-002, 1.808801e-002, 1.860392e-002, - 1.911005e-002, 1.959061e-002, 2.004789e-002, 2.047338e-002, - 2.086984e-002, 2.121310e-002, 2.151204e-002, 2.179053e-002, - 2.203184e-002, 2.224275e-002, 2.240821e-002, 2.254101e-002, - 2.259524e-002, 2.260500e-002, 2.259319e-002, 2.253704e-002, - 2.243576e-002, 2.231657e-002, 2.215813e-002, 2.195189e-002, - 2.170850e-002, 2.142237e-002, 2.109643e-002, 2.073096e-002, - 2.033677e-002, 1.990054e-002, 1.942159e-002, 1.890792e-002, - 1.836862e-002, 1.779056e-002, 1.717282e-002, 1.653228e-002, - 1.587155e-002, 1.517728e-002, 1.446302e-002, 1.375210e-002, - 1.302340e-002, 1.226644e-002, 1.150086e-002, 1.074547e-002, - 1.000549e-002, 9.253581e-003, 8.522999e-003, 7.801707e-003, - 7.059598e-003, 6.353603e-003, 5.692258e-003, 5.043856e-003, - 4.401581e-003, 3.795547e-003, 3.188180e-003, 2.595718e-003, - 2.031507e-003, 1.498770e-003, 9.651665e-004, 4.240185e-004, - -1.201551e-004, -6.916726e-004, -1.288413e-003, -1.901323e-003, - -2.501959e-003, -3.120815e-003, -3.753148e-003, -4.397629e-003, - -5.001226e-003, -5.587435e-003, -6.210333e-003, -6.966526e-003, - -7.878422e-003, -8.935542e-003, -1.011580e-002, -1.136985e-002, - -1.272470e-002, -1.414021e-002, -1.562536e-002, -1.717489e-002, - -1.879780e-002, -2.045918e-002, -2.216300e-002, -2.390259e-002, - -2.568639e-002, -2.748516e-002, -2.930914e-002, -3.111260e-002, - -3.291103e-002, -3.468550e-002, -3.640377e-002, -3.809050e-002, - -3.977118e-002, -4.142429e-002, -4.300137e-002, -4.450899e-002, - -4.593934e-002, -4.725377e-002, -4.846852e-002, -4.955912e-002, - -5.048918e-002, -5.128622e-002, -5.195803e-002, -5.252838e-002, - -5.294701e-002, -5.321368e-002, -5.329822e-002, -5.314361e-002, - -5.273527e-002, -5.210057e-002, -5.134556e-002, -5.052950e-002, - -4.962850e-002, -4.866098e-002, -4.761244e-002, -4.651075e-002, - -4.534712e-002, -4.415966e-002, -4.290235e-002, -4.158936e-002, - -4.020965e-002, -3.874669e-002, -3.725596e-002, -3.579002e-002, - -3.428820e-002, -3.275328e-002, -3.118744e-002, -2.957358e-002, - -2.790267e-002, -2.622745e-002, -2.452074e-002, -2.278267e-002, - -2.104753e-002, -1.932038e-002, -1.757531e-002, -1.583212e-002, - -1.406981e-002, -1.223917e-002, -1.033164e-002, -8.374774e-003, - -6.365209e-003, -4.336652e-003, -2.282602e-003, -1.808615e-004, - 1.970489e-003, 4.128700e-003, 6.309519e-003, 8.512354e-003, - 1.077475e-002, 1.308374e-002, 1.540965e-002, 1.775649e-002, - 2.010185e-002, 2.238316e-002, 2.463366e-002, 2.689072e-002, - 2.915634e-002, 3.139433e-002, 3.359466e-002, 3.570379e-002, - 3.775920e-002, 3.975985e-002, 4.170336e-002, 4.358031e-002, - 4.539872e-002, 4.713158e-002, 4.875560e-002, 5.027912e-002, - 5.157686e-002, 5.247656e-002, 5.300849e-002, 5.319464e-002, - 5.303326e-002, 5.250785e-002, 5.167433e-002, 5.056504e-002, - 4.918997e-002, 4.753039e-002, 4.563479e-002, 4.350352e-002, - 4.116075e-002, 3.863327e-002, 3.596173e-002, 3.305089e-002, - 3.000979e-002, 2.686306e-002, 2.366395e-002, 2.038668e-002, - 1.704614e-002, 1.364654e-002, 1.024045e-002, 6.856643e-003, - 3.466348e-003, 5.875601e-005, -3.307264e-003, -6.598334e-003, - -9.749456e-003, -1.275693e-002, -1.561575e-002, -1.833906e-002, - -2.099320e-002, -2.344960e-002, -2.568928e-002, -2.767122e-002, - -2.937104e-002, -3.077514e-002, -3.191021e-002, -3.272843e-002, - -3.317282e-002, -3.325608e-002, -3.330263e-002, -3.335122e-002, - -3.336680e-002, -3.332552e-002, -3.315081e-002, -3.284741e-002, - -3.245110e-002, -3.202930e-002, -3.153729e-002, -3.097497e-002, - -3.041446e-002, -2.983495e-002, -2.917549e-002, -2.848900e-002, - -2.774751e-002, -2.691547e-002, -2.601255e-002, -2.507773e-002, - -2.408554e-002, -2.303801e-002, -2.199807e-002, -2.092559e-002, - -1.982249e-002, -1.870107e-002, -1.752915e-002, -1.625157e-002, - -1.487391e-002, -1.346619e-002, -1.207701e-002, -1.067574e-002, - -9.251440e-003, -7.814469e-003, -6.327027e-003, -4.787390e-003, - -3.271370e-003, -1.726254e-003, -1.529438e-004, 1.462697e-003, - 3.100103e-003, 4.729046e-003, 6.355159e-003, 7.890779e-003, - 9.468260e-003, 1.109560e-002, 1.273188e-002, 1.440334e-002, - 1.609472e-002, 1.777874e-002, 1.953474e-002, 2.128281e-002, - 2.300867e-002, 2.457659e-002, 2.608889e-002, 2.758439e-002, - 2.898884e-002, 3.013536e-002, 3.113125e-002, 3.192459e-002, - 3.254076e-002, 3.299312e-002, 3.323980e-002, 3.324899e-002, - 3.304963e-002, 3.274242e-002, 3.231826e-002, 3.173545e-002, - 3.104721e-002, 3.024884e-002, 2.942498e-002, 2.852565e-002, - 2.753117e-002, 2.642734e-002, 2.515901e-002, 2.388345e-002, - 2.264221e-002, 2.133101e-002, 2.004799e-002, 1.873219e-002, - 1.739506e-002, 1.605337e-002, 1.476935e-002, 1.347621e-002, - 1.211635e-002, 1.079782e-002, 9.461802e-003, 8.207426e-003, - 7.039309e-003, 5.953897e-003, 4.970915e-003, 4.002333e-003, - 3.137217e-003, 2.419656e-003, 1.695315e-003, 1.132315e-003, - 7.089885e-004, 4.230986e-004, 2.533508e-004, 1.389238e-004, - 4.120303e-005, -8.160652e-005, -1.890669e-004, -2.807092e-004, - -3.917643e-004, -5.685651e-004, -6.709751e-004, -7.719697e-004, - -8.905584e-004, -9.420413e-004, -9.454197e-004, -9.470350e-004, - -9.538474e-004, -1.020428e-003, -1.211397e-003, -1.396753e-003, - -1.547524e-003, -1.744936e-003, -1.939063e-003, -2.102766e-003, - -2.227246e-003, -2.353098e-003, -2.536886e-003, -2.843572e-003, - -3.147850e-003, -3.403881e-003, -3.615920e-003, -3.974689e-003, - -4.360772e-003, -4.666655e-003, -4.852051e-003, -4.994405e-003, - -5.125881e-003, -5.202605e-003, -5.283485e-003, -5.468525e-003, - -5.613555e-003, -5.684059e-003, -5.746751e-003, -5.806051e-003, - -5.833280e-003, -5.683889e-003, -5.516310e-003, -5.391907e-003, - -5.476400e-003, -5.496997e-003, -5.390747e-003, -5.186210e-003, - -5.102042e-003, -4.997250e-003, -4.799437e-003, -4.511355e-003, - -4.243228e-003, -3.990894e-003, -3.717442e-003, -3.463521e-003, - -3.274180e-003, -3.149733e-003, -3.002300e-003, -2.773586e-003, - -2.546415e-003, -2.321648e-003, -2.113070e-003, -1.902433e-003, - -1.703079e-003, -1.420173e-003, -1.027869e-003, -6.548129e-004, - -3.697415e-004, -6.437482e-005, 2.957334e-004, 5.142775e-004, - 6.624678e-004, 8.875241e-004, 1.128183e-003, 1.207066e-003, - 1.334665e-003, 1.469024e-003, 1.615944e-003, 1.793237e-003, - 1.962123e-003, 2.097334e-003, 1.976319e-003, 1.916191e-003, - 1.953858e-003, 1.982340e-003, 1.976629e-003, 1.943095e-003, - 1.992013e-003, 1.805357e-003, 1.625819e-003, 1.451886e-003, - 1.273445e-003, 9.913299e-004, 3.332435e-004, -4.174300e-004, - -1.007848e-003, -1.459722e-003, -2.014522e-003, -2.587113e-003, - -3.178267e-003, -3.729888e-003, -4.364633e-003, -4.924820e-003, - -5.338855e-003, -5.971534e-003, -6.563858e-003, -7.072349e-003, - -7.496660e-003, -7.799408e-003, -8.023004e-003, -8.356203e-003, - -8.492284e-003, -8.511378e-003, -8.718785e-003, -8.824603e-003, - -8.655957e-003, -8.548967e-003, -8.601374e-003, -8.682815e-003, - -8.587934e-003, -8.363263e-003, -8.268450e-003, -8.131185e-003, - -7.797836e-003, -7.669833e-003, -7.531043e-003, -6.898948e-003, - -6.383240e-003, -6.024343e-003, -5.441887e-003, -4.902526e-003, - -4.239498e-003, -3.573383e-003, -2.902921e-003, -2.372178e-003, - -1.871299e-003, -1.096393e-003, -1.208313e-004, 6.915064e-004, - 1.528646e-003, 2.242243e-003, 2.605212e-003, 3.002374e-003, - 2.825727e-003, 2.567379e-003, 2.201180e-003, 1.899184e-003, - 1.645488e-003, 1.401010e-003, 1.377255e-003, 1.031189e-003, - 7.158648e-004, 6.046962e-004, 3.416966e-004, 7.448484e-005, - -1.494720e-004, -2.331207e-004, -9.208803e-004, -1.125401e-003, - -1.790668e-003, -2.287919e-003, -2.421438e-003, -2.348767e-003, - -2.235243e-003, -1.627230e-003, -1.439908e-003, -9.333177e-004, - -8.409756e-004, -4.811531e-004, 3.662682e-004, 1.376310e-003, - 2.673120e-003, 4.076369e-003, 5.600318e-003, 7.349060e-003, - 9.409257e-003, 1.126075e-002, 1.276453e-002, 1.377358e-002, - 1.477480e-002, 1.533651e-002, 1.542818e-002, 1.577156e-002, - 1.623779e-002, 1.640112e-002, 1.668012e-002, 1.737140e-002, - 1.765654e-002, 1.732049e-002, 1.720878e-002, 1.644030e-002, - 1.618503e-002, 1.552297e-002, 1.510542e-002, 1.461779e-002, - 1.340778e-002, 1.248509e-002, 1.133303e-002, 9.203703e-003, - 6.743065e-003, 4.084167e-003, 1.782272e-003, -9.143943e-005, - -1.704768e-003, -3.155441e-003, -6.513394e-003, -9.988042e-003, - -1.324924e-002, -1.669170e-002, -1.864311e-002, -1.943906e-002, - -1.893830e-002, -1.787968e-002, -1.663707e-002, -1.596578e-002, - -1.553081e-002, -1.523850e-002, -1.571381e-002, -1.988256e-002, - -2.293123e-002, -2.511904e-002, -2.397287e-002, -1.815730e-002, - -1.279813e-002, -1.208478e-002, -1.477643e-002, -1.775459e-002, - -1.935032e-002, -2.034986e-002, -1.999212e-002, -1.947548e-002, - -1.878953e-002, -1.788303e-002, -1.265993e-002, -1.095196e-002, - -1.054474e-002, -1.312532e-002, -1.248866e-002, -3.717453e-002, - -3.313920e-002, -2.169731e-002, -8.489919e-003, 2.666525e-003, - 9.678586e-003, 1.382860e-002, 1.561125e-002, 2.100526e-002, - 3.343839e-002, 7.193176e-001, 1.717793e-001, -4.653494e-002, - -1.186034e-001, -1.577868e-001, -1.799818e-001, -1.359648e-001, - -1.210820e-001, -8.039277e-002, -4.017433e-002, 0.000000e+000 - }, - { - 0.000000e+000, -8.314882e-003, -1.214427e-002, -1.397798e-002, - -1.413916e-002, -1.309681e-002, -1.151897e-002, -1.011041e-002, - -8.980506e-003, -8.291364e-003, -7.318111e-003, -6.200238e-003, - -4.978476e-003, -3.524834e-003, -1.963995e-003, -1.493663e-004, - 1.855671e-003, 4.097649e-003, 6.638784e-003, 9.281093e-003, - 1.215902e-002, 1.493508e-002, 1.751472e-002, 2.006816e-002, - 2.256076e-002, 2.488330e-002, 2.710192e-002, 2.923334e-002, - 3.129733e-002, 3.329943e-002, 3.522162e-002, 3.683590e-002, - 3.787682e-002, 3.861551e-002, 3.902013e-002, 3.907740e-002, - 3.877123e-002, 3.813095e-002, 3.714798e-002, 3.589837e-002, - 3.435825e-002, 3.253388e-002, 3.003365e-002, 2.730811e-002, - 2.438620e-002, 2.131187e-002, 1.787936e-002, 1.373568e-002, - 9.715030e-003, 6.595445e-003, 3.848081e-003, 1.239837e-003, - -1.560779e-003, -4.258007e-003, -6.819166e-003, -9.261151e-003, - -1.161055e-002, -1.384680e-002, -1.598256e-002, -1.801644e-002, - -1.992247e-002, -2.168721e-002, -2.355726e-002, -2.542837e-002, - -2.716248e-002, -2.873893e-002, -3.012056e-002, -3.129400e-002, - -3.220952e-002, -3.288944e-002, -3.330264e-002, -3.346114e-002, - -3.343684e-002, -3.326852e-002, -3.272613e-002, -3.180066e-002, - -3.050995e-002, -2.883626e-002, -2.693497e-002, -2.504219e-002, - -2.338987e-002, -2.204601e-002, -2.108912e-002, -2.074267e-002, - -2.070341e-002, -2.096347e-002, -2.149348e-002, -2.224218e-002, - -2.326420e-002, -2.453805e-002, -2.599374e-002, -2.764303e-002, - -2.942562e-002, -3.162062e-002, -3.389692e-002, -3.622675e-002, - -3.858631e-002, -4.094184e-002, -4.325982e-002, -4.553865e-002, - -4.777001e-002, -4.992242e-002, -5.197023e-002, -5.409171e-002, - -5.614701e-002, -5.800829e-002, -5.962675e-002, -6.101010e-002, - -6.209607e-002, -6.278338e-002, -6.304502e-002, -6.286673e-002, - -6.235581e-002, -6.165680e-002, -6.083060e-002, -5.971815e-002, - -5.827016e-002, -5.649870e-002, -5.438519e-002, -5.192937e-002, - -4.919680e-002, -4.629389e-002, -4.335540e-002, -4.057667e-002, - -3.830305e-002, -3.631731e-002, -3.455504e-002, -3.294236e-002, - -3.140877e-002, -2.990113e-002, -2.837085e-002, -2.679649e-002, - -2.514481e-002, -2.340962e-002, -2.182702e-002, -2.011859e-002, - -1.824650e-002, -1.623305e-002, -1.402872e-002, -1.163013e-002, - -9.020126e-003, -6.246486e-003, -3.309229e-003, -2.192636e-004, - 2.789408e-003, 5.838591e-003, 8.932708e-003, 1.198445e-002, - 1.495843e-002, 1.783441e-002, 2.059369e-002, 2.325143e-002, - 2.585189e-002, 2.834079e-002, 3.061277e-002, 3.265585e-002, - 3.453614e-002, 3.622958e-002, 3.778459e-002, 3.921485e-002, - 4.056810e-002, 4.189163e-002, 4.320668e-002, 4.455505e-002, - 4.589989e-002, 4.719453e-002, 4.861365e-002, 5.025050e-002, - 5.219569e-002, 5.445140e-002, 5.695403e-002, 5.961118e-002, - 6.232314e-002, 6.505968e-002, 6.782083e-002, 7.050828e-002, - 7.336985e-002, 7.640868e-002, 7.958134e-002, 8.281435e-002, - 8.598961e-002, 8.896602e-002, 9.151284e-002, 9.353041e-002, - 9.504628e-002, 9.599028e-002, 9.654441e-002, 9.674534e-002, - 9.662923e-002, 9.621510e-002, 9.550850e-002, 9.451984e-002, - 9.326172e-002, 9.177010e-002, 9.005690e-002, 8.807237e-002, - 8.588413e-002, 8.354767e-002, 8.107459e-002, 7.847111e-002, - 7.571886e-002, 7.283895e-002, 6.983660e-002, 6.675107e-002, - 6.365252e-002, 6.055100e-002, 5.756604e-002, 5.482386e-002, - 5.242520e-002, 5.025176e-002, 4.820512e-002, 4.616262e-002, - 4.406634e-002, 4.189831e-002, 3.967971e-002, 3.747421e-002, - 3.522984e-002, 3.312063e-002, 3.116928e-002, 2.934707e-002, - 2.759782e-002, 2.578284e-002, 2.376928e-002, 2.143967e-002, - 1.884987e-002, 1.609959e-002, 1.323863e-002, 1.040292e-002, - 7.617090e-003, 4.897678e-003, 2.249575e-003, -3.418324e-004, - -2.851178e-003, -5.278713e-003, -7.604864e-003, -9.845528e-003, - -1.205778e-002, -1.420748e-002, -1.625914e-002, -1.816674e-002, - -1.992295e-002, -2.149943e-002, -2.290515e-002, -2.408294e-002, - -2.503741e-002, -2.575722e-002, -2.622308e-002, -2.630050e-002, - -2.591947e-002, -2.519259e-002, -2.431060e-002, -2.345588e-002, - -2.269295e-002, -2.208301e-002, -2.160905e-002, -2.125748e-002, - -2.104131e-002, -2.097284e-002, -2.098350e-002, -2.107989e-002, - -2.129818e-002, -2.164706e-002, -2.227338e-002, -2.313989e-002, - -2.419430e-002, -2.538000e-002, -2.660868e-002, -2.790422e-002, - -2.919309e-002, -3.045081e-002, -3.167193e-002, -3.282749e-002, - -3.388024e-002, -3.478495e-002, -3.546696e-002, -3.591521e-002, - -3.603301e-002, -3.589677e-002, -3.554882e-002, -3.502349e-002, - -3.437739e-002, -3.359922e-002, -3.267848e-002, -3.160964e-002, - -3.037386e-002, -2.896348e-002, -2.738427e-002, -2.569818e-002, - -2.400701e-002, -2.249387e-002, -2.134164e-002, -2.060519e-002, - -2.021226e-002, -2.005397e-002, -2.009856e-002, -2.033090e-002, - -2.071513e-002, -2.123446e-002, -2.188884e-002, -2.265927e-002, - -2.350648e-002, -2.441158e-002, -2.534408e-002, -2.630212e-002, - -2.724218e-002, -2.816622e-002, -2.909401e-002, -3.002980e-002, - -3.097580e-002, -3.191047e-002, -3.280690e-002, -3.368923e-002, - -3.450088e-002, -3.514637e-002, -3.554622e-002, -3.564167e-002, - -3.549832e-002, -3.517248e-002, -3.475900e-002, -3.434242e-002, - -3.395669e-002, -3.363093e-002, -3.335363e-002, -3.307163e-002, - -3.279399e-002, -3.245230e-002, -3.204050e-002, -3.160042e-002, - -3.119661e-002, -3.091458e-002, -3.091207e-002, -3.111855e-002, - -3.154078e-002, -3.206652e-002, -3.264087e-002, -3.325962e-002, - -3.386915e-002, -3.443535e-002, -3.493568e-002, -3.532038e-002, - -3.559964e-002, -3.569686e-002, -3.560904e-002, -3.526085e-002, - -3.467062e-002, -3.378925e-002, -3.255760e-002, -3.092663e-002, - -2.891186e-002, -2.651421e-002, -2.371395e-002, -2.066204e-002, - -1.757159e-002, -1.472297e-002, -1.222191e-002, -1.005326e-002, - -8.248768e-003, -6.781584e-003, -5.643017e-003, -4.762886e-003, - -4.144187e-003, -3.743104e-003, -3.508253e-003, -3.482403e-003, - -3.583149e-003, -3.757692e-003, -3.982184e-003, -4.238290e-003, - -4.520016e-003, -4.797167e-003, -5.100102e-003, -5.407131e-003, - -5.703626e-003, -6.003890e-003, -6.262524e-003, -6.472473e-003, - -6.622646e-003, -6.685760e-003, -6.688536e-003, -6.586974e-003, - -6.433897e-003, -6.272620e-003, -6.148543e-003, -6.000292e-003, - -5.851127e-003, -5.688492e-003, -5.515025e-003, -5.365348e-003, - -5.253670e-003, -5.233729e-003, -5.378289e-003, -5.721915e-003, - -6.244067e-003, -6.861522e-003, -7.521404e-003, -8.229376e-003, - -8.938164e-003, -9.639608e-003, -1.037826e-002, -1.114131e-002, - -1.190155e-002, -1.265114e-002, -1.336198e-002, -1.405424e-002, - -1.469614e-002, -1.523767e-002, -1.567995e-002, -1.598066e-002, - -1.610669e-002, -1.605403e-002, -1.579472e-002, -1.526666e-002, - -1.450323e-002, -1.351015e-002, -1.227650e-002, -1.088386e-002, - -9.349970e-003, -7.667860e-003, -5.879824e-003, -4.023490e-003, - -2.132192e-003, -2.948683e-004, 1.468337e-003, 3.172856e-003, - 4.791685e-003, 6.367505e-003, 7.874745e-003, 9.267990e-003, - 1.052197e-002, 1.154567e-002, 1.237899e-002, 1.301110e-002, - 1.350593e-002, 1.392912e-002, 1.430485e-002, 1.463437e-002, - 1.495012e-002, 1.529638e-002, 1.571025e-002, 1.623413e-002, - 1.698895e-002, 1.802890e-002, 1.941257e-002, 2.100582e-002, - 2.269359e-002, 2.434218e-002, 2.593060e-002, 2.747632e-002, - 2.905885e-002, 3.077923e-002, 3.271724e-002, 3.484808e-002, - 3.716065e-002, 3.955898e-002, 4.188621e-002, 4.387398e-002, - 4.532203e-002, 4.621289e-002, 4.671138e-002, 4.694314e-002, - 4.689775e-002, 4.667432e-002, 4.625877e-002, 4.564761e-002, - 4.489984e-002, 4.401184e-002, 4.300108e-002, 4.191434e-002, - 4.077846e-002, 3.961095e-002, 3.840836e-002, 3.719403e-002, - 3.594599e-002, 3.468220e-002, 3.336849e-002, 3.202811e-002, - 3.062517e-002, 2.921073e-002, 2.774179e-002, 2.625490e-002, - 2.476858e-002, 2.331878e-002, 2.190560e-002, 2.055339e-002, - 1.929193e-002, 1.811844e-002, 1.705694e-002, 1.614760e-002, - 1.549043e-002, 1.504321e-002, 1.476662e-002, 1.465042e-002, - 1.464435e-002, 1.475299e-002, 1.497324e-002, 1.528604e-002, - 1.573754e-002, 1.633743e-002, 1.705884e-002, 1.782093e-002, - 1.856909e-002, 1.918381e-002, 1.970306e-002, 2.012915e-002, - 2.043581e-002, 2.061432e-002, 2.065430e-002, 2.049266e-002, - 2.020329e-002, 1.978998e-002, 1.929382e-002, 1.873926e-002, - 1.814392e-002, 1.758190e-002, 1.704201e-002, 1.656703e-002, - 1.618391e-002, 1.596274e-002, 1.593947e-002, 1.622842e-002, - 1.688612e-002, 1.795864e-002, 1.932105e-002, 2.078411e-002, - 2.212645e-002, 2.331804e-002, 2.432497e-002, 2.509072e-002, - 2.559407e-002, 2.584017e-002, 2.588234e-002, 2.570999e-002, - 2.527885e-002, 2.466548e-002, 2.377324e-002, 2.254496e-002, - 2.097669e-002, 1.913314e-002, 1.713211e-002, 1.500224e-002, - 1.279031e-002, 1.054430e-002, 8.280434e-003, 6.037589e-003, - 3.860073e-003, 1.795740e-003, -1.577306e-004, -1.922859e-003, - -3.403175e-003, -4.518631e-003, -5.401236e-003, -6.026843e-003, - -6.433284e-003, -6.669949e-003, -6.827662e-003, -6.929924e-003, - -6.978719e-003, -6.977192e-003, -6.922183e-003, -6.766523e-003, - -6.498726e-003, -6.204775e-003, -5.976846e-003, -5.787163e-003, - -5.737284e-003, -5.875624e-003, -6.243593e-003, -6.704881e-003, - -7.218633e-003, -7.761482e-003, -8.308928e-003, -8.832836e-003, - -9.358152e-003, -9.899875e-003, -1.041207e-002, -1.091565e-002, - -1.139323e-002, -1.185717e-002, -1.226977e-002, -1.263671e-002, - -1.291934e-002, -1.312766e-002, -1.323335e-002, -1.324545e-002, - -1.309801e-002, -1.278512e-002, -1.231110e-002, -1.160575e-002, - -1.055649e-002, -9.040379e-003, -7.131954e-003, -5.092515e-003, - -3.142014e-003, -1.411262e-003, -1.898454e-005, 1.038535e-003, - 1.809495e-003, 2.334536e-003, 2.670320e-003, 2.918828e-003, - 3.097227e-003, 3.131980e-003, 2.975484e-003, 2.421463e-003, - 1.398257e-003, -4.873895e-006, -1.628040e-003, -3.457387e-003, - -5.434670e-003, -7.517325e-003, -9.618603e-003, -1.176228e-002, - -1.388537e-002, -1.593838e-002, -1.783532e-002, -1.953999e-002, - -2.103217e-002, -2.224965e-002, -2.316859e-002, -2.377240e-002, - -2.403690e-002, -2.393815e-002, -2.349852e-002, -2.269220e-002, - -2.162039e-002, -2.067846e-002, -2.002139e-002, -1.967569e-002, - -1.965994e-002, -2.004818e-002, -2.075769e-002, -2.171009e-002, - -2.298889e-002, -2.458415e-002, -2.636054e-002, -2.837136e-002, - -3.061276e-002, -3.300283e-002, -3.546464e-002, -3.799923e-002, - -4.056122e-002, -4.313535e-002, -4.567395e-002, -4.814676e-002, - -5.059900e-002, -5.300078e-002, -5.535863e-002, -5.761134e-002, - -5.975373e-002, -6.178706e-002, -6.369728e-002, -6.547494e-002, - -6.703842e-002, -6.838506e-002, -6.945415e-002, -7.015620e-002, - -7.043717e-002, -7.028019e-002, -6.974489e-002, -6.891711e-002, - -6.774633e-002, -6.628096e-002, -6.442102e-002, -6.214798e-002, - -5.942604e-002, -5.630268e-002, -5.296826e-002, -4.943303e-002, - -4.585925e-002, -4.239875e-002, -3.923106e-002, -3.645276e-002, - -3.394769e-002, -3.168728e-002, -2.966857e-002, -2.778969e-002, - -2.602952e-002, -2.439461e-002, -2.283418e-002, -2.132982e-002, - -1.986217e-002, -1.842977e-002, -1.698172e-002, -1.550134e-002, - -1.395901e-002, -1.235353e-002, -1.070910e-002, -9.023296e-003, - -7.291271e-003, -5.479705e-003, -3.557488e-003, -1.549658e-003, - 5.564255e-004, 2.802747e-003, 5.260800e-003, 8.048814e-003, - 1.107766e-002, 1.423207e-002, 1.751106e-002, 2.088465e-002, - 2.421389e-002, 2.744676e-002, 3.053203e-002, 3.355952e-002, - 3.656980e-002, 3.956623e-002, 4.250432e-002, 4.527695e-002, - 4.785232e-002, 5.018729e-002, 5.219569e-002, 5.378229e-002, - 5.509735e-002, 5.615218e-002, 5.700265e-002, 5.776914e-002, - 5.844216e-002, 5.890537e-002, 5.927925e-002, 5.969945e-002, - 6.003172e-002, 6.016181e-002, 6.028092e-002, 6.035480e-002, - 6.033611e-002, 6.029416e-002, 6.015561e-002, 5.980664e-002, - 5.932148e-002, 5.878442e-002, 5.820905e-002, 5.755276e-002, - 5.693183e-002, 5.621960e-002, 5.537998e-002, 5.439386e-002, - 5.319016e-002, 5.171648e-002, 4.995683e-002, 4.808044e-002, - 4.600721e-002, 4.371623e-002, 4.131488e-002, 3.879353e-002, - 3.618073e-002, 3.354653e-002, 3.087554e-002, 2.820287e-002, - 2.552823e-002, 2.300020e-002, 2.061985e-002, 1.843862e-002, - 1.661689e-002, 1.515581e-002, 1.419155e-002, 1.382657e-002, - 1.390681e-002, 1.420661e-002, 1.443051e-002, 1.464489e-002, - 1.489214e-002, 1.511864e-002, 1.538845e-002, 1.549986e-002, - 1.559736e-002, 1.568372e-002, 1.566740e-002, 1.547274e-002, - 1.488800e-002, 1.384174e-002, 1.251158e-002, 1.098287e-002, - 9.305562e-003, 7.634180e-003, 5.882303e-003, 4.124550e-003, - 2.414411e-003, 9.042916e-004, -6.399725e-004, -2.225526e-003, - -3.588092e-003, -4.781813e-003, -5.680894e-003, -6.299279e-003, - -6.759759e-003, -6.858433e-003, -6.624373e-003, -6.111911e-003, - -5.312695e-003, -4.321593e-003, -3.191453e-003, -2.037281e-003, - -1.008249e-003, -2.991145e-004, 1.688053e-004, 4.623919e-004, - 4.929528e-004, 2.101769e-004, -1.499626e-004, -8.580950e-004, - -1.803943e-003, -2.933338e-003, -4.197170e-003, -5.620194e-003, - -7.051558e-003, -8.564941e-003, -1.012789e-002, -1.168095e-002, - -1.335022e-002, -1.527070e-002, -1.714455e-002, -1.887578e-002, - -2.065887e-002, -2.243261e-002, -2.412717e-002, -2.576599e-002, - -2.728908e-002, -2.868775e-002, -3.001662e-002, -3.111974e-002, - -3.200917e-002, -3.268664e-002, -3.293811e-002, -3.271042e-002, - -3.227955e-002, -3.167055e-002, -3.099527e-002, -3.023967e-002, - -2.946364e-002, -2.883664e-002, -2.827613e-002, -2.768670e-002, - -2.699441e-002, -2.621280e-002, -2.543866e-002, -2.468854e-002, - -2.396084e-002, -2.358775e-002, -2.353090e-002, -2.367776e-002, - -2.414825e-002, -2.455431e-002, -2.492611e-002, -2.542570e-002, - -2.584717e-002, -2.618143e-002, -2.657014e-002, -2.672536e-002, - -2.668475e-002, -2.649623e-002, -2.596728e-002, -2.523371e-002, - -2.421047e-002, -2.288739e-002, -2.117055e-002, -1.919119e-002, - -1.690636e-002, -1.424532e-002, -1.136749e-002, -8.438141e-003, - -5.633368e-003, -3.264368e-003, -1.086725e-003, 8.744862e-004, - 2.453987e-003, 3.858068e-003, 4.934877e-003, 5.606505e-003, - 6.149258e-003, 6.458543e-003, 6.702346e-003, 6.885592e-003, - 6.829033e-003, 6.761415e-003, 6.585951e-003, 6.407147e-003, - 6.252599e-003, 5.978161e-003, 5.754342e-003, 5.686486e-003, - 5.486184e-003, 5.192213e-003, 4.963796e-003, 4.778872e-003, - 4.625216e-003, 4.378029e-003, 4.127960e-003, 4.111922e-003, - 4.423410e-003, 4.719645e-003, 5.271957e-003, 5.848503e-003, - 6.608577e-003, 7.447792e-003, 8.264125e-003, 9.329361e-003, - 1.036227e-002, 1.148208e-002, 1.272198e-002, 1.392730e-002, - 1.523307e-002, 1.651988e-002, 1.756538e-002, 1.857606e-002, - 1.933851e-002, 2.012837e-002, 2.090279e-002, 2.158067e-002, - 2.232188e-002, 2.321189e-002, 2.412642e-002, 2.491838e-002, - 2.537833e-002, 2.584297e-002, 2.605452e-002, 2.625767e-002, - 2.637555e-002, 2.682653e-002, 2.737885e-002, 2.769306e-002, - 2.807710e-002, 2.821182e-002, 2.825533e-002, 2.816799e-002, - 2.776007e-002, 2.696080e-002, 2.596051e-002, 2.478514e-002, - 2.332095e-002, 2.184115e-002, 2.000287e-002, 1.816172e-002, - 1.596944e-002, 1.358374e-002, 1.120211e-002, 8.664961e-003, - 5.983523e-003, 3.163603e-003, 4.066898e-004, -2.468366e-003, - -5.420400e-003, -8.539890e-003, -1.197731e-002, -1.535054e-002, - -1.876234e-002, -2.190554e-002, -2.490533e-002, -2.746909e-002, - -2.949696e-002, -3.100798e-002, -3.234805e-002, -3.376502e-002, - -3.521267e-002, -3.655737e-002, -3.755129e-002, -3.798594e-002, - -3.814980e-002, -3.766012e-002, -3.673401e-002, -3.554442e-002, - -3.384875e-002, -3.174523e-002, -2.978311e-002, -2.831105e-002, - -2.634376e-002, -2.417503e-002, -2.189795e-002, -1.971832e-002, - -1.733827e-002, -1.479952e-002, -1.190572e-002, -9.089458e-003, - -5.965177e-003, -3.967650e-003, -1.836910e-003, 1.753282e-004, - 2.221123e-003, 4.001362e-003, 5.826173e-003, 7.536525e-003, - 9.343409e-003, 1.124509e-002, 1.283287e-002, 1.300477e-002, - 1.271599e-002, 1.322069e-002, 1.423961e-002, 1.616196e-002, - 1.834366e-002, 1.963333e-002, 2.057535e-002, 2.138029e-002, - 2.299162e-002, 2.249253e-002, 2.096949e-002, 1.904116e-002, - 1.717823e-002, 1.503418e-002, 1.307222e-002, 1.083819e-002, - 9.001618e-003, 6.873305e-003, 5.226429e-003, 1.534100e-002, - 1.221989e-002, 8.999945e-003, 5.091102e-003, -4.323942e-005, - -5.375925e-003, -1.199750e-002, -2.052476e-002, -3.024706e-002, - -3.914775e-002, 1.101606e-002, 2.761208e-002, 1.422365e-002, - -5.480630e-003, -2.120639e-002, -3.316217e-002, -1.433359e-002, - -1.164899e-002, -9.080894e-003, -6.215513e-003, 0.000000e+000 - }, - { - 0.000000e+000, 1.501404e-002, 2.675527e-002, 3.614956e-002, - 4.362018e-002, 4.952670e-002, 5.395076e-002, 5.653874e-002, - 5.741402e-002, 5.695936e-002, 5.601914e-002, 5.467730e-002, - 5.283976e-002, 5.055186e-002, 4.795155e-002, 4.518244e-002, - 4.220474e-002, 3.939714e-002, 3.677121e-002, 3.428768e-002, - 3.182600e-002, 2.956258e-002, 2.756989e-002, 2.562994e-002, - 2.367399e-002, 2.175405e-002, 1.990211e-002, 1.816895e-002, - 1.657333e-002, 1.515477e-002, 1.383215e-002, 1.267713e-002, - 1.172603e-002, 1.080093e-002, 9.968298e-003, 9.196439e-003, - 8.456928e-003, 7.789885e-003, 7.068752e-003, 6.390930e-003, - 5.696381e-003, 4.937290e-003, 4.279773e-003, 3.423816e-003, - 2.398301e-003, 1.156128e-003, -5.018710e-004, -3.024583e-003, - -5.513283e-003, -7.161650e-003, -8.547121e-003, -9.871581e-003, - -1.099641e-002, -1.203447e-002, -1.312868e-002, -1.421610e-002, - -1.530593e-002, -1.643958e-002, -1.756278e-002, -1.873203e-002, - -1.986257e-002, -2.101171e-002, -2.204744e-002, -2.295460e-002, - -2.387843e-002, -2.473667e-002, -2.552750e-002, -2.626305e-002, - -2.696421e-002, -2.750113e-002, -2.798513e-002, -2.836832e-002, - -2.844781e-002, -2.816556e-002, -2.769364e-002, -2.696063e-002, - -2.597773e-002, -2.479971e-002, -2.345834e-002, -2.209589e-002, - -2.090948e-002, -1.993992e-002, -1.919768e-002, -1.845367e-002, - -1.795031e-002, -1.763024e-002, -1.750450e-002, -1.753807e-002, - -1.774282e-002, -1.812573e-002, -1.861620e-002, -1.920470e-002, - -1.987274e-002, -2.039575e-002, -2.094707e-002, -2.149221e-002, - -2.206314e-002, -2.263581e-002, -2.312161e-002, -2.358774e-002, - -2.394426e-002, -2.425846e-002, -2.444571e-002, -2.442703e-002, - -2.432448e-002, -2.418371e-002, -2.393621e-002, -2.358707e-002, - -2.313662e-002, -2.255561e-002, -2.186833e-002, -2.094038e-002, - -1.987396e-002, -1.870086e-002, -1.742380e-002, -1.608475e-002, - -1.467237e-002, -1.313648e-002, -1.150959e-002, -9.858806e-003, - -8.139649e-003, -6.369735e-003, -4.751138e-003, -3.385411e-003, - -2.299906e-003, -1.593823e-003, -1.149227e-003, -9.120278e-004, - -8.978519e-004, -1.024928e-003, -1.241544e-003, -1.520014e-003, - -1.770416e-003, -2.054056e-003, -2.307042e-003, -2.523748e-003, - -2.685209e-003, -2.777543e-003, -2.737716e-003, -2.597001e-003, - -2.359466e-003, -2.010643e-003, -1.557876e-003, -1.010198e-003, - -3.897581e-004, 2.810318e-004, 9.788746e-004, 1.643854e-003, - 2.244357e-003, 2.753310e-003, 3.195241e-003, 3.543008e-003, - 3.852893e-003, 4.081177e-003, 4.227028e-003, 4.240224e-003, - 4.165085e-003, 3.935951e-003, 3.579394e-003, 3.136642e-003, - 2.627024e-003, 2.068571e-003, 1.488772e-003, 9.037743e-004, - 3.213221e-004, -2.585471e-004, -7.696606e-004, -1.140207e-003, - -1.319012e-003, -1.296010e-003, -1.124094e-003, -8.652310e-004, - -5.953430e-004, -2.786981e-004, 9.548847e-005, 5.218224e-004, - 1.097731e-003, 1.814202e-003, 2.698970e-003, 3.694862e-003, - 4.719930e-003, 5.665967e-003, 6.472175e-003, 7.033085e-003, - 7.370574e-003, 7.517428e-003, 7.587684e-003, 7.651691e-003, - 7.686221e-003, 7.750179e-003, 7.821939e-003, 7.893558e-003, - 7.985629e-003, 8.146011e-003, 8.360299e-003, 8.591294e-003, - 8.876246e-003, 9.258850e-003, 9.721485e-003, 1.027418e-002, - 1.090728e-002, 1.163656e-002, 1.244671e-002, 1.331377e-002, - 1.428332e-002, 1.528764e-002, 1.640246e-002, 1.771791e-002, - 1.929181e-002, 2.103055e-002, 2.284942e-002, 2.465255e-002, - 2.637460e-002, 2.800776e-002, 2.954945e-002, 3.104880e-002, - 3.246117e-002, 3.393016e-002, 3.545474e-002, 3.697662e-002, - 3.842416e-002, 3.970082e-002, 4.076453e-002, 4.154567e-002, - 4.205222e-002, 4.233808e-002, 4.237306e-002, 4.228097e-002, - 4.209667e-002, 4.184300e-002, 4.153341e-002, 4.118159e-002, - 4.077671e-002, 4.029475e-002, 3.973094e-002, 3.911119e-002, - 3.837536e-002, 3.757707e-002, 3.677392e-002, 3.597934e-002, - 3.522889e-002, 3.454251e-002, 3.391512e-002, 3.339691e-002, - 3.298252e-002, 3.271453e-002, 3.256114e-002, 3.262468e-002, - 3.299840e-002, 3.361594e-002, 3.432675e-002, 3.501847e-002, - 3.559857e-002, 3.606495e-002, 3.638453e-002, 3.660566e-002, - 3.669078e-002, 3.659997e-002, 3.644165e-002, 3.620303e-002, - 3.581695e-002, 3.530073e-002, 3.454154e-002, 3.359402e-002, - 3.247423e-002, 3.121280e-002, 2.988042e-002, 2.834887e-002, - 2.677186e-002, 2.516195e-002, 2.354025e-002, 2.193807e-002, - 2.035768e-002, 1.881712e-002, 1.739637e-002, 1.610358e-002, - 1.500777e-002, 1.394765e-002, 1.297859e-002, 1.209973e-002, - 1.127062e-002, 1.049872e-002, 9.756006e-003, 9.071597e-003, - 8.473033e-003, 7.969857e-003, 7.566724e-003, 7.150943e-003, - 6.666785e-003, 6.039938e-003, 5.140593e-003, 3.895992e-003, - 2.406961e-003, 7.469011e-004, -1.041623e-003, -2.944114e-003, - -4.913780e-003, -7.004366e-003, -9.231060e-003, -1.151712e-002, - -1.383589e-002, -1.611668e-002, -1.835416e-002, -2.056037e-002, - -2.267186e-002, -2.472291e-002, -2.666821e-002, -2.851262e-002, - -3.042286e-002, -3.227740e-002, -3.401621e-002, -3.564719e-002, - -3.712836e-002, -3.840162e-002, -3.943143e-002, -4.015906e-002, - -4.061689e-002, -4.080478e-002, -4.093799e-002, -4.100624e-002, - -4.108497e-002, -4.114607e-002, -4.121089e-002, -4.119559e-002, - -4.113674e-002, -4.099912e-002, -4.081530e-002, -4.060031e-002, - -4.047009e-002, -4.044096e-002, -4.059466e-002, -4.091997e-002, - -4.138339e-002, -4.193076e-002, -4.247934e-002, -4.304657e-002, - -4.360595e-002, -4.414040e-002, -4.466472e-002, -4.516735e-002, - -4.561147e-002, -4.592835e-002, -4.609937e-002, -4.608153e-002, - -4.587538e-002, -4.542647e-002, -4.471680e-002, -4.372593e-002, - -4.245173e-002, -4.100314e-002, -3.930093e-002, -3.737867e-002, - -3.542577e-002, -3.360832e-002, -3.198355e-002, -3.056659e-002, - -2.937924e-002, -2.840959e-002, -2.762898e-002, -2.713571e-002, - -2.680452e-002, -2.659911e-002, -2.647545e-002, -2.644631e-002, - -2.647749e-002, -2.653655e-002, -2.660343e-002, -2.660998e-002, - -2.658651e-002, -2.661561e-002, -2.668029e-002, -2.672568e-002, - -2.671498e-002, -2.661871e-002, -2.647780e-002, -2.626927e-002, - -2.599711e-002, -2.560102e-002, -2.511724e-002, -2.454556e-002, - -2.396242e-002, -2.332751e-002, -2.268531e-002, -2.199295e-002, - -2.120769e-002, -2.040313e-002, -1.959211e-002, -1.876967e-002, - -1.796737e-002, -1.723923e-002, -1.669583e-002, -1.629057e-002, - -1.598547e-002, -1.572655e-002, -1.547378e-002, -1.521097e-002, - -1.495160e-002, -1.469595e-002, -1.443602e-002, -1.421582e-002, - -1.412983e-002, -1.406643e-002, -1.397346e-002, -1.385340e-002, - -1.371613e-002, -1.351691e-002, -1.325837e-002, -1.290921e-002, - -1.245218e-002, -1.190698e-002, -1.131765e-002, -1.057798e-002, - -9.695734e-003, -8.675923e-003, -7.522610e-003, -6.268031e-003, - -4.944671e-003, -3.579427e-003, -2.176625e-003, -7.733236e-004, - 5.722445e-004, 1.852179e-003, 3.053172e-003, 4.209109e-003, - 5.296903e-003, 6.298534e-003, 7.253376e-003, 8.138786e-003, - 8.932026e-003, 9.604197e-003, 1.007843e-002, 1.034744e-002, - 1.052302e-002, 1.068282e-002, 1.086930e-002, 1.105497e-002, - 1.128185e-002, 1.152960e-002, 1.180641e-002, 1.221662e-002, - 1.279886e-002, 1.352489e-002, 1.454168e-002, 1.573280e-002, - 1.706084e-002, 1.842875e-002, 1.978448e-002, 2.112174e-002, - 2.254353e-002, 2.412879e-002, 2.588876e-002, 2.774566e-002, - 2.978064e-002, 3.192346e-002, 3.401088e-002, 3.591440e-002, - 3.744643e-002, 3.856027e-002, 3.944359e-002, 4.017912e-002, - 4.069773e-002, 4.100396e-002, 4.116275e-002, 4.123460e-002, - 4.119629e-002, 4.107694e-002, 4.088190e-002, 4.056714e-002, - 4.021147e-002, 3.986857e-002, 3.948005e-002, 3.900581e-002, - 3.845351e-002, 3.783716e-002, 3.716798e-002, 3.644569e-002, - 3.562307e-002, 3.469138e-002, 3.365634e-002, 3.256613e-002, - 3.149847e-002, 3.038090e-002, 2.916939e-002, 2.799506e-002, - 2.687474e-002, 2.577440e-002, 2.472889e-002, 2.377324e-002, - 2.294498e-002, 2.224729e-002, 2.167081e-002, 2.116315e-002, - 2.064505e-002, 2.020041e-002, 1.978367e-002, 1.940349e-002, - 1.910798e-002, 1.893308e-002, 1.881528e-002, 1.866138e-002, - 1.852537e-002, 1.834058e-002, 1.804102e-002, 1.763313e-002, - 1.719802e-002, 1.667923e-002, 1.602037e-002, 1.528232e-002, - 1.448103e-002, 1.355136e-002, 1.259286e-002, 1.161682e-002, - 1.062728e-002, 9.596208e-003, 8.605518e-003, 7.668865e-003, - 6.812864e-003, 6.096071e-003, 5.571371e-003, 5.309490e-003, - 5.300141e-003, 5.588711e-003, 6.107579e-003, 6.724874e-003, - 7.262687e-003, 7.723409e-003, 8.099186e-003, 8.292989e-003, - 8.284691e-003, 8.201897e-003, 8.059779e-003, 7.800365e-003, - 7.400955e-003, 6.847416e-003, 6.074136e-003, 5.070236e-003, - 3.865899e-003, 2.505067e-003, 1.060338e-003, -4.681771e-004, - -2.082808e-003, -3.793828e-003, -5.516300e-003, -7.247917e-003, - -8.953507e-003, -1.063524e-002, -1.227864e-002, -1.383796e-002, - -1.517912e-002, -1.617843e-002, -1.701948e-002, -1.773273e-002, - -1.825447e-002, -1.873320e-002, -1.919589e-002, -1.962354e-002, - -1.998354e-002, -2.041876e-002, -2.081455e-002, -2.103727e-002, - -2.118270e-002, -2.133251e-002, -2.145151e-002, -2.161301e-002, - -2.200201e-002, -2.251142e-002, -2.313208e-002, -2.381234e-002, - -2.446862e-002, -2.509654e-002, -2.576868e-002, -2.647903e-002, - -2.723871e-002, -2.795656e-002, -2.872925e-002, -2.948776e-002, - -3.019841e-002, -3.092551e-002, -3.161750e-002, -3.233383e-002, - -3.292437e-002, -3.335930e-002, -3.370382e-002, -3.393837e-002, - -3.413667e-002, -3.417657e-002, -3.405492e-002, -3.362960e-002, - -3.288873e-002, -3.170337e-002, -3.010884e-002, -2.830774e-002, - -2.651975e-002, -2.473055e-002, -2.299301e-002, -2.132492e-002, - -1.974036e-002, -1.817932e-002, -1.661511e-002, -1.494371e-002, - -1.313860e-002, -1.127152e-002, -9.419480e-003, -7.830693e-003, - -6.508062e-003, -5.380248e-003, -4.346038e-003, -3.231614e-003, - -2.047696e-003, -8.816118e-004, 2.069530e-004, 1.366026e-003, - 2.575400e-003, 3.808420e-003, 5.119838e-003, 6.466931e-003, - 7.928078e-003, 9.568137e-003, 1.142905e-002, 1.344670e-002, - 1.566934e-002, 1.809514e-002, 2.065702e-002, 2.334654e-002, - 2.613439e-002, 2.870720e-002, 3.098371e-002, 3.290708e-002, - 3.450315e-002, 3.566950e-002, 3.637615e-002, 3.676933e-002, - 3.696048e-002, 3.668672e-002, 3.607253e-002, 3.517382e-002, - 3.394396e-002, 3.243441e-002, 3.075917e-002, 2.896066e-002, - 2.704964e-002, 2.503045e-002, 2.298467e-002, 2.081959e-002, - 1.858409e-002, 1.618575e-002, 1.367976e-002, 1.121953e-002, - 8.795286e-003, 6.472796e-003, 4.172173e-003, 1.976641e-003, - -5.328337e-005, -1.925131e-003, -3.636096e-003, -5.099034e-003, - -6.423456e-003, -7.478137e-003, -8.250317e-003, -8.670337e-003, - -8.867427e-003, -8.804187e-003, -8.389072e-003, -7.754179e-003, - -6.838496e-003, -5.609708e-003, -4.460076e-003, -3.136346e-003, - -1.803560e-003, -5.954180e-004, 3.657056e-004, 1.003278e-003, - 1.483799e-003, 1.725659e-003, 1.789930e-003, 1.742922e-003, - 1.292404e-003, 7.691100e-004, 1.546144e-004, -4.415753e-004, - -1.043009e-003, -1.599019e-003, -2.104413e-003, -2.540090e-003, - -2.959674e-003, -3.316073e-003, -3.719601e-003, -4.145118e-003, - -4.518890e-003, -4.757048e-003, -4.850624e-003, -4.911240e-003, - -4.766399e-003, -4.428469e-003, -3.895926e-003, -3.141637e-003, - -2.264484e-003, -1.384009e-003, -2.801248e-004, 8.829345e-004, - 2.141843e-003, 3.374391e-003, 4.554040e-003, 5.743581e-003, - 7.032032e-003, 8.356266e-003, 9.618950e-003, 1.067662e-002, - 1.160083e-002, 1.246626e-002, 1.315464e-002, 1.357332e-002, - 1.387637e-002, 1.411984e-002, 1.437675e-002, 1.465594e-002, - 1.484617e-002, 1.460485e-002, 1.437275e-002, 1.427242e-002, - 1.411926e-002, 1.372761e-002, 1.335066e-002, 1.279202e-002, - 1.222752e-002, 1.157621e-002, 1.076035e-002, 9.381629e-003, - 8.009808e-003, 6.781207e-003, 5.529658e-003, 4.186819e-003, - 2.751348e-003, 1.133561e-003, -5.333343e-004, -2.076137e-003, - -3.762820e-003, -5.875053e-003, -8.421414e-003, -1.102940e-002, - -1.360039e-002, -1.632884e-002, -1.923733e-002, -2.214881e-002, - -2.502928e-002, -2.757689e-002, -3.021137e-002, -3.305707e-002, - -3.619807e-002, -3.907154e-002, -4.172068e-002, -4.422352e-002, - -4.627127e-002, -4.779479e-002, -4.876994e-002, -4.898738e-002, - -4.885023e-002, -4.838706e-002, -4.826726e-002, -4.783396e-002, - -4.733132e-002, -4.657461e-002, -4.543613e-002, -4.433107e-002, - -4.289528e-002, -4.135788e-002, -3.990491e-002, -3.863572e-002, - -3.778555e-002, -3.705567e-002, -3.638005e-002, -3.554519e-002, - -3.469835e-002, -3.373414e-002, -3.265153e-002, -3.151729e-002, - -3.024103e-002, -2.872420e-002, -2.746105e-002, -2.622725e-002, - -2.481369e-002, -2.324112e-002, -2.141199e-002, -1.918223e-002, - -1.665116e-002, -1.390624e-002, -1.102733e-002, -7.823725e-003, - -4.397689e-003, -1.394234e-003, 1.874586e-003, 5.247523e-003, - 8.825730e-003, 1.214821e-002, 1.543379e-002, 1.831574e-002, - 2.101968e-002, 2.358845e-002, 2.614027e-002, 2.772117e-002, - 2.909679e-002, 3.037257e-002, 3.144348e-002, 3.232040e-002, - 3.294138e-002, 3.362637e-002, 3.420778e-002, 3.495432e-002, - 3.571228e-002, 3.572251e-002, 3.553804e-002, 3.545613e-002, - 3.516768e-002, 3.491466e-002, 3.472392e-002, 3.439878e-002, - 3.427980e-002, 3.441678e-002, 3.462475e-002, 3.444321e-002, - 3.400219e-002, 3.412091e-002, 3.471550e-002, 3.553293e-002, - 3.639727e-002, 3.743272e-002, 3.858050e-002, 3.981307e-002, - 4.101958e-002, 4.198502e-002, 4.221974e-002, 4.242649e-002, - 4.266842e-002, 4.308569e-002, 4.358844e-002, 4.393469e-002, - 4.419350e-002, 4.430443e-002, 4.418491e-002, 4.401873e-002, - 4.279920e-002, 4.155245e-002, 4.020462e-002, 3.871430e-002, - 3.728169e-002, 3.589779e-002, 3.468428e-002, 3.374872e-002, - 3.265843e-002, 3.168782e-002, 2.994301e-002, 2.829207e-002, - 2.664120e-002, 2.540363e-002, 2.449357e-002, 2.397366e-002, - 2.353265e-002, 2.345708e-002, 2.347587e-002, 2.361731e-002, - 2.270580e-002, 2.113998e-002, 1.950122e-002, 1.765561e-002, - 1.565946e-002, 1.376865e-002, 1.152124e-002, 9.102662e-003, - 7.218206e-003, 5.183816e-003, 2.615289e-003, -3.851779e-004, - -3.486018e-003, -6.480625e-003, -9.270210e-003, -1.197558e-002, - -1.454167e-002, -1.694198e-002, -1.944175e-002, -2.172460e-002, - -2.390355e-002, -2.710397e-002, -2.999039e-002, -3.241241e-002, - -3.464384e-002, -3.668108e-002, -3.853862e-002, -4.014019e-002, - -4.107149e-002, -4.173655e-002, -4.213476e-002, -4.390268e-002, - -4.538513e-002, -4.623604e-002, -4.683822e-002, -4.703178e-002, - -4.699299e-002, -4.629671e-002, -4.539325e-002, -4.424150e-002, - -4.337699e-002, -4.321138e-002, -4.351186e-002, -4.381334e-002, - -4.412244e-002, -4.411127e-002, -4.395834e-002, -4.366798e-002, - -4.315568e-002, -4.202498e-002, -4.108187e-002, -4.095222e-002, - -4.086003e-002, -4.089966e-002, -4.108777e-002, -4.048994e-002, - -4.010219e-002, -4.003440e-002, -3.962959e-002, -3.943975e-002, - -3.906411e-002, -3.864130e-002, -3.934392e-002, -4.020141e-002, - -4.078777e-002, -4.146702e-002, -4.211575e-002, -4.188463e-002, - -4.161813e-002, -4.064818e-002, -3.880230e-002, -3.722759e-002, - -3.687198e-002, -3.497854e-002, -3.266861e-002, -3.046936e-002, - -2.717855e-002, -2.346514e-002, -1.928974e-002, -1.472053e-002, - -1.015454e-002, -4.516786e-003, 2.329857e-004, 4.369109e-003, - 8.947472e-003, 1.412497e-002, 2.021628e-002, 2.640411e-002, - 3.340344e-002, 4.074637e-002, 4.820081e-002, 5.582310e-002, - 6.165875e-002, 6.652469e-002, 7.171450e-002, 7.664670e-002, - 8.149182e-002, 8.644357e-002, 9.082694e-002, 9.504190e-002, - 9.855155e-002, 1.010657e-001, 1.010114e-001, 9.896326e-002, - 9.662385e-002, 9.385036e-002, 9.123267e-002, 8.758923e-002, - 8.470248e-002, 8.097590e-002, 7.678541e-002, 7.146030e-002, - 6.519561e-002, 5.347126e-002, 4.219965e-002, 3.056071e-002, - 1.982283e-002, 1.022471e-002, 8.917409e-004, -8.912022e-003, - -1.834578e-002, -2.694842e-002, -3.359628e-002, -4.289161e-002, - -5.034406e-002, -5.785256e-002, -6.288975e-002, -6.750979e-002, - -6.953640e-002, -7.155991e-002, -7.309523e-002, -7.516657e-002, - -7.813948e-002, -7.791139e-002, -7.299977e-002, -6.622939e-002, - -6.147947e-002, -5.473512e-002, -4.772231e-002, -3.431692e-002, - -2.025227e-002, -8.478797e-003, 3.198007e-003, -6.584606e-002, - -6.411181e-002, -5.398894e-002, -3.316145e-002, -5.279194e-003, - 2.619006e-002, 5.776649e-002, 9.217250e-002, 1.291238e-001, - 1.624301e-001, -1.305243e-002, -1.214986e-001, -6.671667e-002, - 2.265593e-002, 7.548315e-002, 1.106684e-001, 1.015218e-001, - 6.539594e-002, 2.757593e-002, 7.803696e-003, 0.000000e+000 - }, - { - 0.000000e+000, 1.470711e-002, 2.385155e-002, 2.943746e-002, - 3.310653e-002, 3.528947e-002, 3.614555e-002, 3.573553e-002, - 3.468827e-002, 3.313360e-002, 3.151139e-002, 2.978435e-002, - 2.794378e-002, 2.602758e-002, 2.415664e-002, 2.223808e-002, - 2.034137e-002, 1.853469e-002, 1.682527e-002, 1.521173e-002, - 1.394494e-002, 1.297383e-002, 1.228537e-002, 1.168492e-002, - 1.112089e-002, 1.054153e-002, 9.993937e-003, 9.455909e-003, - 9.098443e-003, 8.994597e-003, 9.151954e-003, 9.438410e-003, - 9.759535e-003, 9.963619e-003, 1.002215e-002, 9.974793e-003, - 9.814044e-003, 9.506074e-003, 9.021554e-003, 8.357113e-003, - 7.452010e-003, 6.409430e-003, 5.346455e-003, 4.121137e-003, - 2.714912e-003, 1.167412e-003, -5.629090e-004, -2.588928e-003, - -4.663203e-003, -6.572644e-003, -8.385926e-003, -1.019694e-002, - -1.179590e-002, -1.335754e-002, -1.482028e-002, -1.630572e-002, - -1.779036e-002, -1.924632e-002, -2.076650e-002, -2.223736e-002, - -2.372259e-002, -2.518320e-002, -2.647949e-002, -2.767239e-002, - -2.870553e-002, -2.962614e-002, -3.036966e-002, -3.089289e-002, - -3.109989e-002, -3.107696e-002, -3.062923e-002, -2.984201e-002, - -2.853710e-002, -2.674290e-002, -2.435398e-002, -2.155808e-002, - -1.814607e-002, -1.433476e-002, -1.011818e-002, -6.014490e-003, - -2.227455e-003, 9.976232e-004, 3.757014e-003, 6.040084e-003, - 7.864813e-003, 9.196447e-003, 1.005105e-002, 1.051958e-002, - 1.057811e-002, 1.020957e-002, 9.597641e-003, 8.660077e-003, - 7.548973e-003, 6.315226e-003, 4.948290e-003, 3.491835e-003, - 1.917654e-003, 2.998424e-004, -1.356914e-003, -3.048465e-003, - -4.712144e-003, -6.346172e-003, -7.946848e-003, -9.448634e-003, - -1.086962e-002, -1.212656e-002, -1.325434e-002, -1.420563e-002, - -1.488492e-002, -1.514394e-002, -1.496689e-002, -1.441363e-002, - -1.356572e-002, -1.240904e-002, -1.106441e-002, -9.507677e-003, - -7.711646e-003, -5.624763e-003, -3.228497e-003, -5.318444e-004, - 2.345981e-003, 5.237627e-003, 7.952566e-003, 1.022494e-002, - 1.177790e-002, 1.263004e-002, 1.292398e-002, 1.273553e-002, - 1.224276e-002, 1.157787e-002, 1.071685e-002, 9.753229e-003, - 8.726438e-003, 7.715507e-003, 6.720108e-003, 5.812779e-003, - 5.066223e-003, 4.457271e-003, 4.103393e-003, 3.989846e-003, - 4.114796e-003, 4.432667e-003, 4.973587e-003, 5.721168e-003, - 6.634362e-003, 7.694951e-003, 8.796092e-003, 9.810055e-003, - 1.066374e-002, 1.131030e-002, 1.174820e-002, 1.203343e-002, - 1.221536e-002, 1.221320e-002, 1.203265e-002, 1.167418e-002, - 1.108941e-002, 1.017248e-002, 8.974302e-003, 7.592122e-003, - 6.090285e-003, 4.523239e-003, 2.948218e-003, 1.388182e-003, - -9.256327e-005, -1.458377e-003, -2.632660e-003, -3.514435e-003, - -3.891082e-003, -3.788542e-003, -3.312267e-003, -2.620472e-003, - -1.820118e-003, -9.344224e-004, 5.392192e-005, 1.156364e-003, - 2.562547e-003, 4.311238e-003, 6.352743e-003, 8.581351e-003, - 1.084688e-002, 1.296575e-002, 1.459358e-002, 1.555002e-002, - 1.588446e-002, 1.579683e-002, 1.541216e-002, 1.477602e-002, - 1.391546e-002, 1.291801e-002, 1.179997e-002, 1.053476e-002, - 9.165277e-003, 7.752711e-003, 6.352653e-003, 4.980773e-003, - 3.641334e-003, 2.370103e-003, 1.191830e-003, 1.116674e-004, - -8.792832e-004, -1.785580e-003, -2.609592e-003, -3.335372e-003, - -3.914223e-003, -4.299522e-003, -4.295807e-003, -3.772846e-003, - -2.655522e-003, -1.143427e-003, 5.637108e-004, 2.274672e-003, - 3.878278e-003, 5.321573e-003, 6.662664e-003, 7.956749e-003, - 9.285087e-003, 1.078610e-002, 1.245877e-002, 1.421325e-002, - 1.590190e-002, 1.735163e-002, 1.834284e-002, 1.872289e-002, - 1.854148e-002, 1.791307e-002, 1.702929e-002, 1.598325e-002, - 1.486169e-002, 1.366520e-002, 1.242341e-002, 1.114317e-002, - 9.821118e-003, 8.448514e-003, 7.016483e-003, 5.551032e-003, - 4.040702e-003, 2.556537e-003, 1.140457e-003, -1.771865e-004, - -1.340068e-003, -2.344153e-003, -3.144545e-003, -3.673781e-003, - -3.936651e-003, -3.841174e-003, -3.375091e-003, -2.277329e-003, - -4.862951e-004, 1.834071e-003, 4.364304e-003, 6.845582e-003, - 9.125441e-003, 1.116167e-002, 1.288378e-002, 1.433479e-002, - 1.547698e-002, 1.631827e-002, 1.696117e-002, 1.738144e-002, - 1.745793e-002, 1.720195e-002, 1.641115e-002, 1.516779e-002, - 1.354264e-002, 1.160831e-002, 9.489553e-003, 7.139206e-003, - 4.696358e-003, 2.207035e-003, -2.813720e-004, -2.708248e-003, - -5.043422e-003, -7.203406e-003, -9.102018e-003, -1.060872e-002, - -1.167423e-002, -1.243313e-002, -1.288619e-002, -1.308742e-002, - -1.306915e-002, -1.287343e-002, -1.246478e-002, -1.179227e-002, - -1.084795e-002, -9.631046e-003, -8.160636e-003, -6.540336e-003, - -4.900948e-003, -3.516607e-003, -2.699473e-003, -2.502866e-003, - -2.806651e-003, -3.496135e-003, -4.472033e-003, -5.713971e-003, - -7.171004e-003, -8.792053e-003, -1.057979e-002, -1.246672e-002, - -1.441861e-002, -1.638540e-002, -1.831207e-002, -2.020253e-002, - -2.198026e-002, -2.364943e-002, -2.517514e-002, -2.659319e-002, - -2.798528e-002, -2.927107e-002, -3.042557e-002, -3.144010e-002, - -3.224967e-002, -3.271862e-002, -3.272642e-002, -3.215030e-002, - -3.102259e-002, -2.954508e-002, -2.796432e-002, -2.631467e-002, - -2.476525e-002, -2.331590e-002, -2.197125e-002, -2.064269e-002, - -1.931720e-002, -1.789307e-002, -1.643602e-002, -1.503663e-002, - -1.386663e-002, -1.301899e-002, -1.269086e-002, -1.279574e-002, - -1.329274e-002, -1.405473e-002, -1.491935e-002, -1.587941e-002, - -1.692808e-002, -1.797803e-002, -1.903819e-002, -2.009670e-002, - -2.102141e-002, -2.174129e-002, -2.222438e-002, -2.235091e-002, - -2.209540e-002, -2.138649e-002, -2.012845e-002, -1.830958e-002, - -1.594769e-002, -1.311616e-002, -9.700340e-003, -5.866316e-003, - -2.012977e-003, 1.502048e-003, 4.461081e-003, 6.842384e-003, - 8.678816e-003, 1.000649e-002, 1.085659e-002, 1.116776e-002, - 1.109034e-002, 1.067505e-002, 1.004355e-002, 9.199033e-003, - 8.201746e-003, 7.133353e-003, 5.990511e-003, 4.888105e-003, - 3.845893e-003, 2.784146e-003, 1.755062e-003, 8.729911e-004, - 1.356723e-004, -4.535081e-004, -8.955309e-004, -1.126969e-003, - -1.156235e-003, -9.611690e-004, -5.628710e-004, 6.937336e-005, - 7.867038e-004, 1.582231e-003, 2.398588e-003, 3.353008e-003, - 4.391331e-003, 5.494600e-003, 6.678781e-003, 7.866830e-003, - 8.965230e-003, 9.892573e-003, 1.044324e-002, 1.065738e-002, - 1.062343e-002, 1.046079e-002, 1.019896e-002, 9.863480e-003, - 9.507157e-003, 9.078057e-003, 8.576337e-003, 8.011159e-003, - 7.262284e-003, 6.440581e-003, 5.598109e-003, 4.768016e-003, - 3.965540e-003, 3.213183e-003, 2.557267e-003, 2.049415e-003, - 1.722377e-003, 1.600211e-003, 1.594289e-003, 1.841733e-003, - 2.312273e-003, 3.035115e-003, 3.976170e-003, 5.118478e-003, - 6.424807e-003, 7.865854e-003, 9.423603e-003, 1.102541e-002, - 1.250951e-002, 1.381528e-002, 1.497229e-002, 1.600104e-002, - 1.689607e-002, 1.769248e-002, 1.835399e-002, 1.882459e-002, - 1.907794e-002, 1.897096e-002, 1.848564e-002, 1.765139e-002, - 1.660749e-002, 1.548396e-002, 1.431971e-002, 1.311694e-002, - 1.191261e-002, 1.076754e-002, 9.751177e-003, 8.927444e-003, - 8.484394e-003, 8.402696e-003, 8.895643e-003, 9.772000e-003, - 1.087216e-002, 1.198649e-002, 1.309024e-002, 1.417865e-002, - 1.536594e-002, 1.679154e-002, 1.860074e-002, 2.068077e-002, - 2.307871e-002, 2.566506e-002, 2.817436e-002, 3.019921e-002, - 3.138889e-002, 3.184115e-002, 3.172504e-002, 3.124642e-002, - 3.041845e-002, 2.927096e-002, 2.786794e-002, 2.624973e-002, - 2.446846e-002, 2.256396e-002, 2.053936e-002, 1.845890e-002, - 1.635989e-002, 1.426599e-002, 1.215043e-002, 9.982670e-003, - 7.785211e-003, 5.573257e-003, 3.354093e-003, 1.131536e-003, - -1.148988e-003, -3.448758e-003, -5.749069e-003, -8.013942e-003, - -1.022495e-002, -1.240847e-002, -1.456944e-002, -1.662273e-002, - -1.852153e-002, -2.024174e-002, -2.173623e-002, -2.297699e-002, - -2.376509e-002, -2.416364e-002, -2.424957e-002, -2.410876e-002, - -2.383642e-002, -2.341724e-002, -2.281133e-002, -2.201590e-002, - -2.103281e-002, -1.981775e-002, -1.841899e-002, -1.695471e-002, - -1.555817e-002, -1.428638e-002, -1.322024e-002, -1.235011e-002, - -1.165369e-002, -1.116427e-002, -1.087814e-002, -1.085910e-002, - -1.105487e-002, -1.136123e-002, -1.180925e-002, -1.235999e-002, - -1.301223e-002, -1.364250e-002, -1.424872e-002, -1.475660e-002, - -1.508621e-002, -1.517844e-002, -1.496501e-002, -1.420867e-002, - -1.285344e-002, -1.082202e-002, -8.365882e-003, -5.785780e-003, - -3.336778e-003, -1.193717e-003, 6.801640e-004, 2.280567e-003, - 3.602129e-003, 4.717797e-003, 5.637716e-003, 6.370149e-003, - 6.833955e-003, 7.004490e-003, 6.801548e-003, 6.264650e-003, - 5.267874e-003, 3.877531e-003, 2.242962e-003, 3.643376e-004, - -1.580770e-003, -3.577096e-003, -5.544203e-003, -7.473947e-003, - -9.300466e-003, -1.104167e-002, -1.261402e-002, -1.383091e-002, - -1.457939e-002, -1.481601e-002, -1.463911e-002, -1.409853e-002, - -1.329989e-002, -1.242430e-002, -1.145663e-002, -1.046610e-002, - -9.489297e-003, -8.566849e-003, -7.693679e-003, -6.747475e-003, - -5.757359e-003, -4.797247e-003, -3.965659e-003, -3.403659e-003, - -3.380450e-003, -3.785446e-003, -4.572035e-003, -5.607451e-003, - -6.762622e-003, -7.993625e-003, -9.263330e-003, -1.058310e-002, - -1.189013e-002, -1.319313e-002, -1.457319e-002, -1.599399e-002, - -1.742066e-002, -1.890288e-002, -2.035007e-002, -2.172099e-002, - -2.293360e-002, -2.394568e-002, -2.481295e-002, -2.547663e-002, - -2.590480e-002, -2.599411e-002, -2.579836e-002, -2.511562e-002, - -2.386895e-002, -2.192175e-002, -1.919548e-002, -1.611875e-002, - -1.307152e-002, -1.025466e-002, -7.719859e-003, -5.596299e-003, - -3.911633e-003, -2.363471e-003, -8.285868e-004, 7.752581e-004, - 2.510695e-003, 4.246003e-003, 5.642713e-003, 6.616438e-003, - 7.041042e-003, 6.961679e-003, 6.645664e-003, 6.291001e-003, - 5.824113e-003, 5.329249e-003, 4.797726e-003, 4.297902e-003, - 3.964360e-003, 3.844453e-003, 3.884863e-003, 4.100297e-003, - 4.698220e-003, 5.656172e-003, 7.071381e-003, 8.996613e-003, - 1.139343e-002, 1.421904e-002, 1.752031e-002, 2.127268e-002, - 2.540652e-002, 2.959688e-002, 3.347268e-002, 3.686824e-002, - 3.972817e-002, 4.203517e-002, 4.378206e-002, 4.508630e-002, - 4.589776e-002, 4.625833e-002, 4.610721e-002, 4.560014e-002, - 4.468902e-002, 4.348849e-002, 4.205359e-002, 4.036838e-002, - 3.845582e-002, 3.639558e-002, 3.433881e-002, 3.220125e-002, - 2.999954e-002, 2.768287e-002, 2.520036e-002, 2.273808e-002, - 2.026208e-002, 1.788083e-002, 1.559905e-002, 1.346054e-002, - 1.147134e-002, 9.689246e-003, 8.140408e-003, 6.989203e-003, - 6.207392e-003, 5.891740e-003, 5.909570e-003, 6.321913e-003, - 6.973676e-003, 7.879743e-003, 9.111558e-003, 1.064071e-002, - 1.253280e-002, 1.479296e-002, 1.725812e-002, 1.985134e-002, - 2.243682e-002, 2.473297e-002, 2.640858e-002, 2.729291e-002, - 2.765708e-002, 2.762647e-002, 2.712463e-002, 2.628925e-002, - 2.493381e-002, 2.320106e-002, 2.132884e-002, 1.940003e-002, - 1.733626e-002, 1.511586e-002, 1.283987e-002, 1.057440e-002, - 8.466864e-003, 6.365020e-003, 4.132742e-003, 1.880097e-003, - -3.885574e-004, -2.590050e-003, -4.607024e-003, -6.536851e-003, - -8.244533e-003, -9.613716e-003, -1.076934e-002, -1.134622e-002, - -1.147631e-002, -1.139889e-002, -1.111425e-002, -1.066005e-002, - -1.018438e-002, -9.681517e-003, -9.307159e-003, -9.019717e-003, - -8.557387e-003, -8.053603e-003, -7.435151e-003, -7.120488e-003, - -7.125101e-003, -7.409970e-003, -8.036044e-003, -9.167196e-003, - -1.059239e-002, -1.225682e-002, -1.415920e-002, -1.610398e-002, - -1.816266e-002, -2.050262e-002, -2.275878e-002, -2.491432e-002, - -2.687274e-002, -2.863240e-002, -3.018673e-002, -3.146643e-002, - -3.243032e-002, -3.321314e-002, -3.385925e-002, -3.468951e-002, - -3.538832e-002, -3.582917e-002, -3.603409e-002, -3.598898e-002, - -3.574494e-002, -3.550947e-002, -3.533068e-002, -3.514670e-002, - -3.496124e-002, -3.512897e-002, -3.560504e-002, -3.625453e-002, - -3.707319e-002, -3.805180e-002, -3.913440e-002, -4.032742e-002, - -4.160446e-002, -4.272987e-002, -4.390710e-002, -4.505960e-002, - -4.630658e-002, -4.731210e-002, -4.799662e-002, -4.819791e-002, - -4.785928e-002, -4.691640e-002, -4.528066e-002, -4.261562e-002, - -3.937079e-002, -3.590262e-002, -3.297435e-002, -3.015635e-002, - -2.735242e-002, -2.456243e-002, -2.182234e-002, -1.917192e-002, - -1.684551e-002, -1.424818e-002, -1.171513e-002, -9.355638e-003, - -7.544183e-003, -6.348535e-003, -5.704801e-003, -5.266788e-003, - -4.844053e-003, -4.451315e-003, -4.058483e-003, -3.646156e-003, - -3.547113e-003, -3.348308e-003, -3.013289e-003, -2.721100e-003, - -2.008997e-003, -8.561069e-004, 6.229952e-004, 2.294624e-003, - 4.136630e-003, 6.505292e-003, 9.518733e-003, 1.332785e-002, - 1.739096e-002, 2.138379e-002, 2.597635e-002, 3.056757e-002, - 3.488160e-002, 3.862853e-002, 4.171508e-002, 4.439780e-002, - 4.674723e-002, 4.876814e-002, 5.026799e-002, 5.073940e-002, - 5.081714e-002, 5.053382e-002, 4.990740e-002, 4.892916e-002, - 4.773838e-002, 4.635828e-002, 4.486604e-002, 4.338609e-002, - 4.185752e-002, 3.980144e-002, 3.775149e-002, 3.562064e-002, - 3.356114e-002, 3.139212e-002, 2.921529e-002, 2.724230e-002, - 2.521192e-002, 2.328925e-002, 2.159055e-002, 1.971744e-002, - 1.821229e-002, 1.734659e-002, 1.697206e-002, 1.685442e-002, - 1.722541e-002, 1.763724e-002, 1.818866e-002, 1.860193e-002, - 1.920408e-002, 1.932537e-002, 1.900383e-002, 1.875683e-002, - 1.864936e-002, 1.854494e-002, 1.848039e-002, 1.857740e-002, - 1.870498e-002, 1.815293e-002, 1.694992e-002, 1.511929e-002, - 1.295371e-002, 1.055159e-002, 8.137107e-003, 5.932524e-003, - 3.712202e-003, 1.514624e-003, -5.221908e-004, -2.181047e-003, - -3.453448e-003, -4.363185e-003, -5.135264e-003, -5.573816e-003, - -5.883735e-003, -5.456369e-003, -4.354433e-003, -2.902829e-003, - -9.522627e-004, 1.261179e-003, 3.982725e-003, 6.904775e-003, - 9.436941e-003, 1.123109e-002, 1.239348e-002, 1.314885e-002, - 1.357073e-002, 1.358050e-002, 1.345837e-002, 1.299462e-002, - 1.206866e-002, 1.101548e-002, 9.342446e-003, 7.181895e-003, - 4.639884e-003, 2.168803e-003, -5.403141e-004, -3.338211e-003, - -6.171053e-003, -9.023248e-003, -1.162414e-002, -1.404210e-002, - -1.674262e-002, -1.974507e-002, -2.261353e-002, -2.562683e-002, - -2.864360e-002, -3.120368e-002, -3.382842e-002, -3.609842e-002, - -3.770309e-002, -3.877005e-002, -3.951570e-002, -4.011970e-002, - -4.028098e-002, -4.029244e-002, -4.001073e-002, -3.948306e-002, - -3.907075e-002, -3.868763e-002, -3.776890e-002, -3.650010e-002, - -3.510149e-002, -3.379399e-002, -3.280304e-002, -3.133605e-002, - -2.992369e-002, -2.836996e-002, -2.665070e-002, -2.480328e-002, - -2.247509e-002, -2.065475e-002, -1.837571e-002, -1.649092e-002, - -1.536351e-002, -1.424033e-002, -1.287557e-002, -1.144852e-002, - -1.038414e-002, -8.784072e-003, -6.564428e-003, -4.390617e-003, - -1.746136e-003, 4.940023e-004, 2.163875e-003, 4.790194e-003, - 7.776668e-003, 1.056719e-002, 1.276960e-002, 1.472856e-002, - 1.674335e-002, 1.850011e-002, 2.042297e-002, 2.257840e-002, - 2.354449e-002, 2.397875e-002, 2.501819e-002, 2.611174e-002, - 2.718280e-002, 2.793536e-002, 2.940966e-002, 3.081194e-002, - 3.246195e-002, 3.477144e-002, 3.474753e-002, 3.408739e-002, - 3.393655e-002, 3.393179e-002, 3.354272e-002, 3.378031e-002, - 3.485546e-002, 3.585239e-002, 3.693504e-002, 3.557862e-002, - 3.269076e-002, 2.784042e-002, 2.238909e-002, 1.779192e-002, - 1.396547e-002, 1.038320e-002, 6.693330e-003, 3.371938e-003, - 7.805305e-004, -1.052442e-003, -3.757153e-003, -9.158682e-003, - -1.434758e-002, -1.954858e-002, -2.527382e-002, -2.960517e-002, - -3.412770e-002, -3.798694e-002, -4.088231e-002, -4.280558e-002, - -4.423660e-002, -4.927104e-002, -5.465330e-002, -5.909645e-002, - -6.273243e-002, -6.574110e-002, -6.924741e-002, -6.972638e-002, - -6.982251e-002, -7.020630e-002, -6.968040e-002, -7.097161e-002, - -6.968716e-002, -6.457170e-002, -5.561283e-002, -3.731794e-002, - -1.522860e-002, 4.483525e-003, 2.533814e-002, 4.846764e-002, - 8.473675e-002, 1.078937e-001, 1.266752e-001, 1.410687e-001, - 1.475165e-001, 1.414715e-001, 1.255990e-001, 9.738725e-002, - 6.864809e-002, 4.626563e-002, 2.822161e-002, 1.643456e-001, - 1.419734e-001, 1.064186e-001, 6.252018e-002, 1.540676e-002, - -3.864968e-002, -1.045274e-001, -1.780579e-001, -2.537739e-001, - -3.185459e-001, -1.184888e-001, 2.421630e-001, 1.608806e-001, - -2.613183e-002, -1.404076e-001, -1.937554e-001, -8.880239e-002, - -3.489049e-002, 2.083226e-002, 2.721374e-002, 0.000000e+000 - }, - { - 0.000000e+000, 2.903965e-003, 4.551270e-003, 5.550537e-003, - 7.088522e-003, 9.235748e-003, 1.183259e-002, 1.414636e-002, - 1.606116e-002, 1.773237e-002, 1.954150e-002, 2.074767e-002, - 2.163211e-002, 2.241228e-002, 2.311145e-002, 2.373633e-002, - 2.423796e-002, 2.465463e-002, 2.479083e-002, 2.449318e-002, - 2.343567e-002, 2.127563e-002, 1.836984e-002, 1.534739e-002, - 1.234594e-002, 9.432343e-003, 6.501517e-003, 3.509311e-003, - 3.097889e-004, -3.205616e-003, -7.026396e-003, -1.098717e-002, - -1.487007e-002, -1.834551e-002, -2.138312e-002, -2.392421e-002, - -2.592638e-002, -2.743838e-002, -2.842199e-002, -2.894234e-002, - -2.892510e-002, -2.848022e-002, -2.776194e-002, -2.656646e-002, - -2.498017e-002, -2.313135e-002, -2.117085e-002, -1.941252e-002, - -1.747569e-002, -1.505768e-002, -1.246127e-002, -9.862632e-003, - -7.358711e-003, -4.810425e-003, -2.180808e-003, 5.746727e-004, - 3.452635e-003, 6.446432e-003, 9.528409e-003, 1.264267e-002, - 1.577304e-002, 1.887730e-002, 2.187374e-002, 2.472561e-002, - 2.739921e-002, 2.979362e-002, 3.193125e-002, 3.354298e-002, - 3.458326e-002, 3.503686e-002, 3.475509e-002, 3.369066e-002, - 3.181080e-002, 2.898566e-002, 2.512481e-002, 2.015588e-002, - 1.417063e-002, 7.112311e-003, -5.840502e-004, -8.225754e-003, - -1.510219e-002, -2.098056e-002, -2.582160e-002, -2.960299e-002, - -3.244873e-002, -3.441860e-002, -3.553086e-002, -3.587288e-002, - -3.532921e-002, -3.406279e-002, -3.216600e-002, -2.979004e-002, - -2.695857e-002, -2.372520e-002, -2.022121e-002, -1.649154e-002, - -1.262246e-002, -8.674661e-003, -4.591839e-003, -4.831050e-004, - 3.665114e-003, 7.806292e-003, 1.187523e-002, 1.592741e-002, - 1.982119e-002, 2.348562e-002, 2.686094e-002, 2.980455e-002, - 3.210517e-002, 3.355003e-002, 3.410042e-002, 3.374502e-002, - 3.272004e-002, 3.106026e-002, 2.892735e-002, 2.631819e-002, - 2.311648e-002, 1.925999e-002, 1.474846e-002, 9.629507e-003, - 4.049881e-003, -1.671922e-003, -7.060979e-003, -1.158693e-002, - -1.472824e-002, -1.650719e-002, -1.716546e-002, -1.701081e-002, - -1.629142e-002, -1.518312e-002, -1.381375e-002, -1.227107e-002, - -1.062022e-002, -9.007506e-003, -7.501829e-003, -6.235036e-003, - -5.307823e-003, -4.733938e-003, -4.647284e-003, -5.057323e-003, - -5.966086e-003, -7.253277e-003, -8.970710e-003, -1.110617e-002, - -1.358424e-002, -1.634171e-002, -1.920731e-002, -2.186520e-002, - -2.412815e-002, -2.596909e-002, -2.739667e-002, -2.846019e-002, - -2.924857e-002, -2.966273e-002, -2.978334e-002, -2.948667e-002, - -2.867123e-002, -2.716701e-002, -2.507119e-002, -2.256560e-002, - -1.978001e-002, -1.686272e-002, -1.389290e-002, -1.097277e-002, - -8.232648e-003, -5.791433e-003, -3.738574e-003, -2.278915e-003, - -1.758821e-003, -2.250355e-003, -3.521877e-003, -5.261935e-003, - -7.193045e-003, -9.270621e-003, -1.150837e-002, -1.408357e-002, - -1.725168e-002, -2.107568e-002, -2.548093e-002, -3.026646e-002, - -3.512395e-002, -3.957360e-002, -4.297703e-002, -4.502347e-002, - -4.576292e-002, -4.563647e-002, -4.474825e-002, -4.325349e-002, - -4.117800e-002, -3.865640e-002, -3.576485e-002, -3.245915e-002, - -2.891773e-002, -2.516291e-002, -2.129258e-002, -1.737856e-002, - -1.344807e-002, -9.606887e-003, -5.848233e-003, -2.159702e-003, - 1.452782e-003, 5.020457e-003, 8.493934e-003, 1.180846e-002, - 1.489786e-002, 1.760093e-002, 1.948115e-002, 2.035584e-002, - 2.001960e-002, 1.891443e-002, 1.743112e-002, 1.594588e-002, - 1.465340e-002, 1.360020e-002, 1.273618e-002, 1.185549e-002, - 1.084541e-002, 9.477125e-003, 7.756907e-003, 5.775659e-003, - 3.807651e-003, 2.323873e-003, 1.693783e-003, 2.305711e-003, - 3.986422e-003, 6.477062e-003, 9.331964e-003, 1.241282e-002, - 1.561938e-002, 1.889441e-002, 2.220165e-002, 2.551698e-002, - 2.886633e-002, 3.226507e-002, 3.569845e-002, 3.914379e-002, - 4.253840e-002, 4.584315e-002, 4.897508e-002, 5.183965e-002, - 5.438190e-002, 5.656875e-002, 5.834856e-002, 5.959992e-002, - 6.023653e-002, 6.013837e-002, 5.918656e-002, 5.690729e-002, - 5.324077e-002, 4.850239e-002, 4.329176e-002, 3.817963e-002, - 3.345825e-002, 2.922105e-002, 2.552150e-002, 2.236279e-002, - 1.970633e-002, 1.755050e-002, 1.578780e-002, 1.443777e-002, - 1.370612e-002, 1.354713e-002, 1.437329e-002, 1.606397e-002, - 1.844334e-002, 2.134042e-002, 2.458742e-002, 2.801208e-002, - 3.154385e-002, 3.507640e-002, 3.848130e-002, 4.166550e-002, - 4.453822e-002, 4.693822e-002, 4.873894e-002, 4.966375e-002, - 4.962977e-002, 4.879704e-002, 4.727710e-002, 4.519356e-002, - 4.261602e-002, 3.965044e-002, 3.617417e-002, 3.209664e-002, - 2.744718e-002, 2.219940e-002, 1.639825e-002, 1.021042e-002, - 3.940754e-003, -1.814134e-003, -6.396885e-003, -9.754607e-003, - -1.203659e-002, -1.344233e-002, -1.413407e-002, -1.421277e-002, - -1.370530e-002, -1.279864e-002, -1.151323e-002, -9.897399e-003, - -8.013783e-003, -5.966343e-003, -3.821294e-003, -1.631281e-003, - 5.732588e-004, 2.727075e-003, 4.814094e-003, 6.843707e-003, - 8.841219e-003, 1.078738e-002, 1.265093e-002, 1.432882e-002, - 1.570842e-002, 1.658658e-002, 1.663880e-002, 1.567624e-002, - 1.370330e-002, 1.114992e-002, 8.310912e-003, 5.423848e-003, - 2.721047e-003, 3.174960e-004, -1.834667e-003, -3.883656e-003, - -5.921138e-003, -8.117367e-003, -1.037790e-002, -1.251587e-002, - -1.428429e-002, -1.547109e-002, -1.560435e-002, -1.486408e-002, - -1.334719e-002, -1.136525e-002, -9.080045e-003, -6.636922e-003, - -4.091481e-003, -1.561440e-003, 8.877030e-004, 3.183046e-003, - 5.170876e-003, 6.748899e-003, 7.804888e-003, 8.168572e-003, - 7.804114e-003, 6.557082e-003, 4.240496e-003, 7.961289e-004, - -3.791933e-003, -9.549484e-003, -1.649954e-002, -2.418507e-002, - -3.186816e-002, -3.881352e-002, -4.462729e-002, -4.926522e-002, - -5.274478e-002, -5.517225e-002, -5.654780e-002, -5.700857e-002, - -5.670113e-002, -5.567749e-002, -5.413287e-002, -5.206094e-002, - -4.961058e-002, -4.695038e-002, -4.419420e-002, -4.142630e-002, - -3.873961e-002, -3.614558e-002, -3.373720e-002, -3.158762e-002, - -2.966027e-002, -2.801392e-002, -2.670007e-002, -2.576479e-002, - -2.519870e-002, -2.508457e-002, -2.536392e-002, -2.607981e-002, - -2.708229e-002, -2.821320e-002, -2.936455e-002, -3.072838e-002, - -3.219702e-002, -3.378657e-002, -3.548186e-002, -3.716719e-002, - -3.870788e-002, -3.990459e-002, -4.050053e-002, -4.046142e-002, - -3.983771e-002, -3.886458e-002, -3.766583e-002, -3.625778e-002, - -3.470421e-002, -3.302617e-002, -3.115019e-002, -2.909447e-002, - -2.691215e-002, -2.459068e-002, -2.219684e-002, -1.981312e-002, - -1.745218e-002, -1.516696e-002, -1.308596e-002, -1.125337e-002, - -9.717934e-003, -8.548588e-003, -7.757027e-003, -7.411686e-003, - -7.532101e-003, -8.129871e-003, -9.052552e-003, -1.033411e-002, - -1.194588e-002, -1.379388e-002, -1.587451e-002, -1.804303e-002, - -2.007155e-002, -2.179881e-002, -2.321280e-002, -2.429431e-002, - -2.506179e-002, -2.565606e-002, -2.596282e-002, -2.590073e-002, - -2.540739e-002, -2.416777e-002, -2.221724e-002, -1.967879e-002, - -1.671831e-002, -1.353065e-002, -1.022668e-002, -6.874738e-003, - -3.587444e-003, -4.470855e-004, 2.460915e-003, 5.056786e-003, - 6.893127e-003, 7.725265e-003, 7.398390e-003, 6.276422e-003, - 4.757260e-003, 3.231280e-003, 1.742098e-003, 1.893774e-004, - -1.626567e-003, -3.978422e-003, -7.095687e-003, -1.099635e-002, - -1.553248e-002, -2.047565e-002, -2.533202e-002, -2.925930e-002, - -3.165973e-002, -3.266447e-002, -3.255699e-002, -3.176642e-002, - -3.033697e-002, -2.850322e-002, -2.626690e-002, -2.356390e-002, - -2.055603e-002, -1.732216e-002, -1.388217e-002, -1.036695e-002, - -6.782662e-003, -3.172544e-003, 4.666572e-004, 4.067384e-003, - 7.688181e-003, 1.137087e-002, 1.509412e-002, 1.891199e-002, - 2.281536e-002, 2.677526e-002, 3.071932e-002, 3.467252e-002, - 3.861704e-002, 4.250325e-002, 4.625008e-002, 4.983143e-002, - 5.320130e-002, 5.624774e-002, 5.892016e-002, 6.111458e-002, - 6.250517e-002, 6.322374e-002, 6.340869e-002, 6.319145e-002, - 6.268511e-002, 6.190029e-002, 6.076463e-002, 5.923023e-002, - 5.730386e-002, 5.499541e-002, 5.238516e-002, 4.966512e-002, - 4.705273e-002, 4.475538e-002, 4.283232e-002, 4.126986e-002, - 4.013926e-002, 3.943480e-002, 3.910891e-002, 3.939581e-002, - 4.010017e-002, 4.110729e-002, 4.240911e-002, 4.389196e-002, - 4.548561e-002, 4.701554e-002, 4.841366e-002, 4.953239e-002, - 5.028419e-002, 5.049994e-002, 5.001986e-002, 4.856059e-002, - 4.575029e-002, 4.148986e-002, 3.633267e-002, 3.089043e-002, - 2.559653e-002, 2.076487e-002, 1.650965e-002, 1.274772e-002, - 9.456806e-003, 6.609605e-003, 4.161350e-003, 1.946013e-003, - 1.797243e-004, -1.025282e-003, -1.485920e-003, -1.302189e-003, - -2.600500e-004, 1.580810e-003, 3.799347e-003, 6.279195e-003, - 8.946090e-003, 1.172525e-002, 1.444011e-002, 1.704434e-002, - 1.949055e-002, 2.161131e-002, 2.322780e-002, 2.420492e-002, - 2.438489e-002, 2.352068e-002, 2.194818e-002, 1.975590e-002, - 1.704693e-002, 1.398885e-002, 1.083562e-002, 7.687217e-003, - 4.645514e-003, 1.722737e-003, -1.155283e-003, -4.086258e-003, - -7.092166e-003, -9.972959e-003, -1.253068e-002, -1.441959e-002, - -1.518829e-002, -1.498858e-002, -1.395004e-002, -1.239518e-002, - -1.059267e-002, -8.638810e-003, -6.535975e-003, -4.316868e-003, - -2.030414e-003, 3.702029e-004, 2.929673e-003, 5.504399e-003, - 8.189959e-003, 1.092767e-002, 1.362203e-002, 1.615205e-002, - 1.851279e-002, 2.053284e-002, 2.211315e-002, 2.333857e-002, - 2.421483e-002, 2.455376e-002, 2.431118e-002, 2.315807e-002, - 2.083719e-002, 1.690008e-002, 1.151427e-002, 5.651011e-003, - -5.634574e-005, -5.194678e-003, -9.595223e-003, -1.316636e-002, - -1.598720e-002, -1.828934e-002, -2.046845e-002, -2.262012e-002, - -2.489354e-002, -2.700870e-002, -2.857751e-002, -2.913870e-002, - -2.860192e-002, -2.698582e-002, -2.476880e-002, -2.222908e-002, - -1.941955e-002, -1.650614e-002, -1.354547e-002, -1.064393e-002, - -7.994551e-003, -5.645866e-003, -3.762501e-003, -2.380835e-003, - -1.825044e-003, -1.966932e-003, -2.943547e-003, -4.911158e-003, - -7.883081e-003, -1.182422e-002, -1.676633e-002, -2.268884e-002, - -2.935443e-002, -3.625373e-002, -4.270168e-002, -4.828776e-002, - -5.295343e-002, -5.677413e-002, -5.968806e-002, -6.173655e-002, - -6.290016e-002, -6.327632e-002, -6.290611e-002, -6.184118e-002, - -5.997159e-002, -5.763910e-002, -5.497702e-002, -5.202501e-002, - -4.888281e-002, -4.564516e-002, -4.231252e-002, -3.884301e-002, - -3.533109e-002, -3.169639e-002, -2.809249e-002, -2.451304e-002, - -2.097820e-002, -1.755683e-002, -1.431549e-002, -1.123544e-002, - -8.319231e-003, -5.780658e-003, -3.784024e-003, -2.448145e-003, - -1.946476e-003, -2.276817e-003, -3.235685e-003, -4.722558e-003, - -6.737024e-003, -9.073209e-003, -1.172202e-002, -1.490486e-002, - -1.858991e-002, -2.274950e-002, -2.742943e-002, -3.243966e-002, - -3.741303e-002, -4.170966e-002, -4.477044e-002, -4.627941e-002, - -4.650502e-002, -4.581912e-002, -4.443573e-002, -4.242074e-002, - -3.984120e-002, -3.678194e-002, -3.336607e-002, -2.950967e-002, - -2.537150e-002, -2.110304e-002, -1.674583e-002, -1.237731e-002, - -8.039277e-003, -3.751405e-003, 5.001453e-004, 4.647559e-003, - 8.665964e-003, 1.260914e-002, 1.646962e-002, 2.003740e-002, - 2.332881e-002, 2.619192e-002, 2.834767e-002, 2.951775e-002, - 2.974790e-002, 2.916707e-002, 2.820025e-002, 2.707123e-002, - 2.592391e-002, 2.480510e-002, 2.380951e-002, 2.286337e-002, - 2.174543e-002, 2.058071e-002, 1.934375e-002, 1.814667e-002, - 1.727507e-002, 1.710032e-002, 1.776219e-002, 1.942991e-002, - 2.189586e-002, 2.490814e-002, 2.818312e-002, 3.174992e-002, - 3.541249e-002, 3.898550e-002, 4.251094e-002, 4.594722e-002, - 4.895170e-002, 5.137001e-002, 5.295146e-002, 5.373031e-002, - 5.392752e-002, 5.362938e-002, 5.283267e-002, 5.153610e-002, - 4.985130e-002, 4.781779e-002, 4.529525e-002, 4.227034e-002, - 3.894294e-002, 3.522443e-002, 3.156056e-002, 2.827878e-002, - 2.530943e-002, 2.263495e-002, 2.028609e-002, 1.837129e-002, - 1.691071e-002, 1.602413e-002, 1.560739e-002, 1.551733e-002, - 1.571671e-002, 1.621657e-002, 1.675323e-002, 1.732805e-002, - 1.779234e-002, 1.803222e-002, 1.806142e-002, 1.769858e-002, - 1.670374e-002, 1.469256e-002, 1.151409e-002, 7.162216e-003, - 1.980148e-003, -3.380628e-003, -8.549981e-003, -1.315693e-002, - -1.700542e-002, -2.015679e-002, -2.261154e-002, -2.476243e-002, - -2.688792e-002, -2.887061e-002, -3.051177e-002, -3.181000e-002, - -3.225165e-002, -3.164702e-002, -3.029224e-002, -2.822603e-002, - -2.570137e-002, -2.280467e-002, -1.966869e-002, -1.639304e-002, - -1.332997e-002, -1.038651e-002, -7.588269e-003, -5.117209e-003, - -3.102093e-003, -1.792497e-003, -1.130565e-003, -1.076477e-003, - -1.984826e-003, -3.899544e-003, -6.665022e-003, -1.035650e-002, - -1.516097e-002, -2.091116e-002, -2.717813e-002, -3.322081e-002, - -3.840594e-002, -4.274913e-002, -4.615626e-002, -4.864241e-002, - -5.026737e-002, -5.102287e-002, -5.097278e-002, -5.010137e-002, - -4.849653e-002, -4.606747e-002, -4.307629e-002, -3.972214e-002, - -3.590592e-002, -3.176558e-002, -2.734085e-002, -2.291755e-002, - -1.840974e-002, -1.368165e-002, -8.588742e-003, -3.532312e-003, - 1.412709e-003, 6.297495e-003, 1.125664e-002, 1.613608e-002, - 2.065485e-002, 2.491149e-002, 2.889154e-002, 3.247673e-002, - 3.550839e-002, 3.736617e-002, 3.801210e-002, 3.776070e-002, - 3.681005e-002, 3.534989e-002, 3.380802e-002, 3.225928e-002, - 3.073152e-002, 2.930474e-002, 2.799888e-002, 2.649529e-002, - 2.462645e-002, 2.236404e-002, 1.999340e-002, 1.772206e-002, - 1.586982e-002, 1.487248e-002, 1.490514e-002, 1.612830e-002, - 1.799089e-002, 2.017966e-002, 2.272674e-002, 2.517020e-002, - 2.766345e-002, 3.014670e-002, 3.243905e-002, 3.435746e-002, - 3.584838e-002, 3.673737e-002, 3.731557e-002, 3.727186e-002, - 3.611260e-002, 3.387466e-002, 3.040956e-002, 2.592020e-002, - 2.051163e-002, 1.391462e-002, 6.612652e-003, -7.495148e-004, - -7.361528e-003, -1.325752e-002, -1.823926e-002, -2.222734e-002, - -2.518264e-002, -2.712174e-002, -2.825703e-002, -2.910472e-002, - -2.900281e-002, -2.803136e-002, -2.597702e-002, -2.321457e-002, - -2.029995e-002, -1.732715e-002, -1.404130e-002, -1.066803e-002, - -7.531682e-003, -4.340497e-003, -9.945766e-004, 2.088904e-003, - 5.046225e-003, 8.349078e-003, 1.166875e-002, 1.452480e-002, - 1.689194e-002, 1.901252e-002, 2.054228e-002, 2.170250e-002, - 2.190509e-002, 2.162890e-002, 2.068200e-002, 1.960475e-002, - 1.843888e-002, 1.686678e-002, 1.486641e-002, 1.287485e-002, - 9.834250e-003, 6.192565e-003, 2.550508e-003, -1.244934e-003, - -4.717020e-003, -7.673204e-003, -1.036406e-002, -1.292957e-002, - -1.546390e-002, -1.768102e-002, -2.010618e-002, -2.243746e-002, - -2.511888e-002, -2.816856e-002, -3.120366e-002, -3.328967e-002, - -3.532705e-002, -3.735430e-002, -3.915202e-002, -4.083428e-002, - -4.276110e-002, -4.473334e-002, -4.658149e-002, -4.839883e-002, - -4.956778e-002, -5.063867e-002, -5.036792e-002, -4.995281e-002, - -4.906193e-002, -4.719672e-002, -4.472203e-002, -4.177322e-002, - -3.802671e-002, -3.343007e-002, -2.856957e-002, -2.286628e-002, - -1.655722e-002, -1.005405e-002, -3.272533e-003, 3.329761e-003, - 9.583982e-003, 1.514163e-002, 2.018721e-002, 2.450668e-002, - 2.810988e-002, 3.112643e-002, 3.396646e-002, 3.699023e-002, - 4.013234e-002, 4.291642e-002, 4.437672e-002, 4.466439e-002, - 4.302839e-002, 3.997029e-002, 3.713232e-002, 3.310879e-002, - 2.934735e-002, 2.600571e-002, 2.213868e-002, 1.856913e-002, - 1.369196e-002, 8.729759e-003, 3.543025e-003, -1.829430e-003, - -6.654446e-003, -1.148450e-002, -1.586154e-002, -1.979985e-002, - -2.246088e-002, -2.467860e-002, -2.704177e-002, -2.901125e-002, - -3.091356e-002, -3.223086e-002, -3.173460e-002, -3.076383e-002, - -2.789636e-002, -2.316488e-002, -1.722342e-002, -1.029480e-002, - -2.777485e-003, 5.761202e-003, 1.468155e-002, 2.467446e-002, - 3.466033e-002, 4.324878e-002, 5.026680e-002, 5.536700e-002, - 5.856712e-002, 6.149206e-002, 6.026970e-002, 5.351028e-002, - 4.122796e-002, 3.042455e-002, 2.315248e-002, 2.080529e-002, - 3.340854e-002, 2.981382e-002, 1.347153e-002, -3.023675e-003, - -1.909938e-002, -3.648361e-002, -5.485777e-002, -7.278697e-002, - -8.722149e-002, -9.239338e-002, -9.050156e-002, 9.741840e-002, - 1.041446e-001, 9.384228e-002, 7.430607e-002, 4.637446e-002, - 9.591476e-003, -3.195461e-002, -7.473722e-002, -1.167334e-001, - -1.512537e-001, -5.959754e-002, 2.222565e-001, 1.317222e-001, - -1.381181e-002, -9.940933e-002, -1.247848e-001, -9.378049e-002, - -4.058175e-002, 2.354583e-002, 3.346896e-002, 0.000000e+000 - }, - { - 0.000000e+000, 9.818952e-003, 1.499853e-002, 1.960759e-002, - 2.318434e-002, 2.574080e-002, 2.775447e-002, 2.919040e-002, - 2.986353e-002, 3.062855e-002, 3.093559e-002, 3.075389e-002, - 3.022282e-002, 2.946587e-002, 2.867888e-002, 2.776140e-002, - 2.655273e-002, 2.523487e-002, 2.388931e-002, 2.245326e-002, - 2.096800e-002, 1.952847e-002, 1.819901e-002, 1.691291e-002, - 1.559457e-002, 1.421219e-002, 1.272927e-002, 1.151155e-002, - 1.045734e-002, 9.549822e-003, 8.758938e-003, 7.988960e-003, - 7.219299e-003, 6.379215e-003, 5.495536e-003, 4.575293e-003, - 3.639805e-003, 2.729030e-003, 1.747399e-003, 7.205253e-004, - -3.480036e-004, -1.440240e-003, -2.577243e-003, -3.698620e-003, - -4.776840e-003, -5.806156e-003, -6.649715e-003, -7.099926e-003, - -7.575808e-003, -8.478272e-003, -9.537000e-003, -1.061276e-002, - -1.179234e-002, -1.295519e-002, -1.412190e-002, -1.533599e-002, - -1.651362e-002, -1.775972e-002, -1.900462e-002, -2.021569e-002, - -2.137935e-002, -2.250282e-002, -2.364676e-002, -2.478457e-002, - -2.589103e-002, -2.697979e-002, -2.796784e-002, -2.890759e-002, - -2.967078e-002, -3.027311e-002, -3.071611e-002, -3.096854e-002, - -3.105190e-002, -3.102692e-002, -3.076439e-002, -3.025368e-002, - -2.948175e-002, -2.851022e-002, -2.731511e-002, -2.607015e-002, - -2.491273e-002, -2.384010e-002, -2.296114e-002, -2.231210e-002, - -2.184028e-002, -2.143241e-002, -2.114417e-002, -2.092675e-002, - -2.080701e-002, -2.074090e-002, -2.067651e-002, -2.062297e-002, - -2.055393e-002, -2.061712e-002, -2.063863e-002, -2.064731e-002, - -2.054500e-002, -2.047579e-002, -2.030318e-002, -2.012116e-002, - -1.983829e-002, -1.945955e-002, -1.904054e-002, -1.860399e-002, - -1.818951e-002, -1.776911e-002, -1.719226e-002, -1.649034e-002, - -1.566333e-002, -1.466559e-002, -1.351006e-002, -1.213505e-002, - -1.058953e-002, -9.011192e-003, -7.436323e-003, -5.727292e-003, - -3.928316e-003, -1.992445e-003, 8.974192e-005, 2.286372e-003, - 4.583033e-003, 6.908961e-003, 9.143606e-003, 1.114208e-002, - 1.271212e-002, 1.402692e-002, 1.513441e-002, 1.607007e-002, - 1.691147e-002, 1.766337e-002, 1.832133e-002, 1.895954e-002, - 1.957698e-002, 2.018666e-002, 2.065226e-002, 2.114686e-002, - 2.166820e-002, 2.225948e-002, 2.296650e-002, 2.372614e-002, - 2.455569e-002, 2.546958e-002, 2.644958e-002, 2.752431e-002, - 2.851189e-002, 2.951335e-002, 3.050184e-002, 3.146221e-002, - 3.232278e-002, 3.303491e-002, 3.363656e-002, 3.414801e-002, - 3.459613e-002, 3.491764e-002, 3.508044e-002, 3.507885e-002, - 3.497195e-002, 3.466044e-002, 3.418780e-002, 3.362565e-002, - 3.299376e-002, 3.227039e-002, 3.150613e-002, 3.073637e-002, - 2.994313e-002, 2.903156e-002, 2.819884e-002, 2.747530e-002, - 2.690047e-002, 2.647676e-002, 2.622011e-002, 2.608115e-002, - 2.596183e-002, 2.584847e-002, 2.574799e-002, 2.560297e-002, - 2.554504e-002, 2.561403e-002, 2.582091e-002, 2.615039e-002, - 2.650537e-002, 2.677004e-002, 2.685763e-002, 2.675525e-002, - 2.640230e-002, 2.576988e-002, 2.497120e-002, 2.409940e-002, - 2.309558e-002, 2.203420e-002, 2.088754e-002, 1.969956e-002, - 1.850255e-002, 1.725753e-002, 1.600512e-002, 1.465650e-002, - 1.331349e-002, 1.201564e-002, 1.073941e-002, 9.496871e-003, - 8.295270e-003, 7.138164e-003, 6.004973e-003, 4.916170e-003, - 3.863354e-003, 2.872630e-003, 1.955567e-003, 1.257139e-003, - 7.911283e-004, 4.794258e-004, 2.494848e-004, 3.281915e-005, - -2.197102e-004, -5.021397e-004, -8.235734e-004, -1.163783e-003, - -1.541540e-003, -1.865229e-003, -2.116324e-003, -2.303805e-003, - -2.474289e-003, -2.738509e-003, -3.161664e-003, -3.818222e-003, - -4.682141e-003, -5.723067e-003, -6.864108e-003, -8.059218e-003, - -9.309062e-003, -1.057468e-002, -1.184490e-002, -1.311913e-002, - -1.438953e-002, -1.569123e-002, -1.698183e-002, -1.828132e-002, - -1.960103e-002, -2.092429e-002, -2.221503e-002, -2.344670e-002, - -2.462166e-002, -2.575667e-002, -2.680329e-002, -2.776792e-002, - -2.858255e-002, -2.926052e-002, -2.980080e-002, -3.007042e-002, - -3.005558e-002, -2.980785e-002, -2.944098e-002, -2.907803e-002, - -2.875239e-002, -2.845489e-002, -2.821704e-002, -2.804577e-002, - -2.795959e-002, -2.796684e-002, -2.801437e-002, -2.808171e-002, - -2.821775e-002, -2.841022e-002, -2.873036e-002, -2.914958e-002, - -2.963509e-002, -3.016792e-002, -3.069416e-002, -3.122114e-002, - -3.171156e-002, -3.212624e-002, -3.242061e-002, -3.254931e-002, - -3.255869e-002, -3.240474e-002, -3.207796e-002, -3.150742e-002, - -3.069440e-002, -2.973404e-002, -2.861308e-002, -2.734976e-002, - -2.592394e-002, -2.438604e-002, -2.268187e-002, -2.083735e-002, - -1.887880e-002, -1.676358e-002, -1.452828e-002, -1.222683e-002, - -9.910996e-003, -7.697149e-003, -5.716295e-003, -3.975002e-003, - -2.488011e-003, -1.229395e-003, -1.670860e-004, 7.130248e-004, - 1.411115e-003, 1.959777e-003, 2.363878e-003, 2.656359e-003, - 2.817558e-003, 2.878800e-003, 2.851522e-003, 2.722292e-003, - 2.499478e-003, 2.187900e-003, 1.849979e-003, 1.464369e-003, - 9.661331e-004, 3.871142e-004, -2.031869e-004, -7.812035e-004, - -1.339399e-003, -1.857097e-003, -2.251226e-003, -2.438960e-003, - -2.404215e-003, -2.293223e-003, -2.202542e-003, -2.091514e-003, - -2.016983e-003, -1.991163e-003, -2.003679e-003, -1.992696e-003, - -1.951984e-003, -1.847343e-003, -1.715001e-003, -1.588561e-003, - -1.534143e-003, -1.579311e-003, -1.801305e-003, -2.156941e-003, - -2.605777e-003, -3.099607e-003, -3.594543e-003, -4.052082e-003, - -4.487315e-003, -4.847348e-003, -5.198994e-003, -5.509514e-003, - -5.721553e-003, -5.828530e-003, -5.787028e-003, -5.571880e-003, - -5.160663e-003, -4.556881e-003, -3.733389e-003, -2.664242e-003, - -1.336846e-003, 1.539213e-004, 1.904126e-003, 3.781230e-003, - 5.644687e-003, 7.420633e-003, 9.022404e-003, 1.042075e-002, - 1.160056e-002, 1.257340e-002, 1.334066e-002, 1.388105e-002, - 1.426462e-002, 1.450891e-002, 1.467015e-002, 1.472679e-002, - 1.467987e-002, 1.458256e-002, 1.448421e-002, 1.438271e-002, - 1.429728e-002, 1.416026e-002, 1.400090e-002, 1.392387e-002, - 1.391444e-002, 1.399772e-002, 1.413361e-002, 1.435515e-002, - 1.463092e-002, 1.500522e-002, 1.544482e-002, 1.594138e-002, - 1.643885e-002, 1.695983e-002, 1.748986e-002, 1.805780e-002, - 1.865893e-002, 1.929642e-002, 1.995485e-002, 2.062352e-002, - 2.126716e-002, 2.179798e-002, 2.215527e-002, 2.237878e-002, - 2.249903e-002, 2.257034e-002, 2.258675e-002, 2.256356e-002, - 2.247760e-002, 2.234394e-002, 2.215448e-002, 2.191510e-002, - 2.158588e-002, 2.121658e-002, 2.083518e-002, 2.045490e-002, - 2.006889e-002, 1.964884e-002, 1.932550e-002, 1.904644e-002, - 1.880722e-002, 1.859034e-002, 1.831803e-002, 1.810240e-002, - 1.796490e-002, 1.792158e-002, 1.790376e-002, 1.797503e-002, - 1.805034e-002, 1.814337e-002, 1.825604e-002, 1.839174e-002, - 1.842334e-002, 1.837123e-002, 1.822765e-002, 1.799744e-002, - 1.769709e-002, 1.734309e-002, 1.692787e-002, 1.644510e-002, - 1.590430e-002, 1.518190e-002, 1.429688e-002, 1.326115e-002, - 1.215314e-002, 1.103236e-002, 9.895627e-003, 8.752769e-003, - 7.653537e-003, 6.603188e-003, 5.610760e-003, 4.701681e-003, - 3.924955e-003, 3.301927e-003, 2.894317e-003, 2.694415e-003, - 2.645150e-003, 2.629501e-003, 2.644132e-003, 2.747410e-003, - 2.931045e-003, 3.220153e-003, 3.733451e-003, 4.404081e-003, - 5.215752e-003, 6.166793e-003, 7.133896e-003, 7.929002e-003, - 8.467028e-003, 8.833208e-003, 9.057195e-003, 9.199244e-003, - 9.238016e-003, 9.189351e-003, 9.055917e-003, 8.858668e-003, - 8.610864e-003, 8.366360e-003, 8.075824e-003, 7.760105e-003, - 7.416999e-003, 7.060883e-003, 6.682435e-003, 6.282338e-003, - 5.837888e-003, 5.301670e-003, 4.678782e-003, 3.974181e-003, - 3.195038e-003, 2.353805e-003, 1.476325e-003, 5.434002e-004, - -5.081121e-004, -1.645974e-003, -2.853877e-003, -4.038449e-003, - -5.204055e-003, -6.369474e-003, -7.547923e-003, -8.737855e-003, - -9.825684e-003, -1.086921e-002, -1.177890e-002, -1.264025e-002, - -1.347650e-002, -1.432887e-002, -1.519078e-002, -1.600234e-002, - -1.676552e-002, -1.740941e-002, -1.800516e-002, -1.862422e-002, - -1.931601e-002, -2.005760e-002, -2.087387e-002, -2.176012e-002, - -2.269169e-002, -2.370805e-002, -2.477159e-002, -2.594063e-002, - -2.719655e-002, -2.856106e-002, -2.997227e-002, -3.140935e-002, - -3.280504e-002, -3.412025e-002, -3.538679e-002, -3.661685e-002, - -3.771098e-002, -3.862776e-002, -3.936233e-002, -3.985066e-002, - -3.989864e-002, -3.960605e-002, -3.915261e-002, -3.859154e-002, - -3.791605e-002, -3.730514e-002, -3.670521e-002, -3.607472e-002, - -3.546330e-002, -3.493799e-002, -3.442089e-002, -3.384176e-002, - -3.319052e-002, -3.251444e-002, -3.189977e-002, -3.131892e-002, - -3.090251e-002, -3.056792e-002, -3.024164e-002, -2.990268e-002, - -2.954720e-002, -2.922679e-002, -2.895843e-002, -2.871650e-002, - -2.839022e-002, -2.799755e-002, -2.747805e-002, -2.681051e-002, - -2.601491e-002, -2.509757e-002, -2.411890e-002, -2.297619e-002, - -2.171981e-002, -2.036507e-002, -1.901194e-002, -1.770296e-002, - -1.657189e-002, -1.541283e-002, -1.424472e-002, -1.303963e-002, - -1.182615e-002, -1.062778e-002, -9.587364e-003, -8.713138e-003, - -7.996271e-003, -7.467818e-003, -7.093225e-003, -6.815596e-003, - -6.590219e-003, -6.517382e-003, -6.405199e-003, -6.290927e-003, - -6.175201e-003, -6.064980e-003, -5.931363e-003, -5.914312e-003, - -6.011194e-003, -6.080280e-003, -6.118879e-003, -6.122844e-003, - -6.082825e-003, -6.014157e-003, -5.996981e-003, -5.814225e-003, - -5.510816e-003, -5.066012e-003, -4.527321e-003, -3.778197e-003, - -2.832654e-003, -1.625958e-003, -6.447437e-005, 1.594093e-003, - 3.235853e-003, 4.762420e-003, 6.190860e-003, 7.458158e-003, - 8.647440e-003, 9.717336e-003, 1.077773e-002, 1.181389e-002, - 1.293992e-002, 1.399268e-002, 1.500162e-002, 1.578588e-002, - 1.637458e-002, 1.670778e-002, 1.692333e-002, 1.700429e-002, - 1.708975e-002, 1.718453e-002, 1.724557e-002, 1.723412e-002, - 1.730729e-002, 1.737457e-002, 1.757240e-002, 1.790742e-002, - 1.841615e-002, 1.900802e-002, 1.972604e-002, 2.057461e-002, - 2.170112e-002, 2.306120e-002, 2.455276e-002, 2.624552e-002, - 2.803873e-002, 2.971778e-002, 3.126964e-002, 3.269919e-002, - 3.395767e-002, 3.504138e-002, 3.588449e-002, 3.650894e-002, - 3.698695e-002, 3.728401e-002, 3.742317e-002, 3.742415e-002, - 3.722783e-002, 3.683653e-002, 3.640420e-002, 3.598058e-002, - 3.548319e-002, 3.499686e-002, 3.451763e-002, 3.391820e-002, - 3.333686e-002, 3.270185e-002, 3.209919e-002, 3.148502e-002, - 3.089143e-002, 3.031302e-002, 2.978750e-002, 2.930912e-002, - 2.879062e-002, 2.826145e-002, 2.778948e-002, 2.729830e-002, - 2.696557e-002, 2.677843e-002, 2.677912e-002, 2.689909e-002, - 2.701968e-002, 2.710962e-002, 2.726493e-002, 2.751488e-002, - 2.786936e-002, 2.821392e-002, 2.849835e-002, 2.883997e-002, - 2.915898e-002, 2.932674e-002, 2.929333e-002, 2.889473e-002, - 2.814475e-002, 2.728518e-002, 2.629229e-002, 2.517251e-002, - 2.379686e-002, 2.235207e-002, 2.080437e-002, 1.923009e-002, - 1.757583e-002, 1.580078e-002, 1.406571e-002, 1.242824e-002, - 1.071497e-002, 8.972393e-003, 7.180904e-003, 5.235611e-003, - 3.346815e-003, 1.513523e-003, -2.579204e-004, -1.965171e-003, - -3.541809e-003, -4.926594e-003, -6.142107e-003, -7.302929e-003, - -8.271808e-003, -9.236613e-003, -1.002795e-002, -1.065096e-002, - -1.132375e-002, -1.208104e-002, -1.267029e-002, -1.328805e-002, - -1.381081e-002, -1.417126e-002, -1.462658e-002, -1.530833e-002, - -1.600202e-002, -1.677686e-002, -1.758068e-002, -1.844714e-002, - -1.946390e-002, -2.049727e-002, -2.158445e-002, -2.257904e-002, - -2.349654e-002, -2.473067e-002, -2.589758e-002, -2.705105e-002, - -2.795039e-002, -2.856232e-002, -2.890781e-002, -2.899630e-002, - -2.881555e-002, -2.838621e-002, -2.779307e-002, -2.716146e-002, - -2.642629e-002, -2.563436e-002, -2.459047e-002, -2.344287e-002, - -2.224039e-002, -2.117459e-002, -2.010681e-002, -1.883035e-002, - -1.749474e-002, -1.642101e-002, -1.572584e-002, -1.510100e-002, - -1.448735e-002, -1.384904e-002, -1.343251e-002, -1.319576e-002, - -1.299673e-002, -1.275999e-002, -1.240224e-002, -1.216959e-002, - -1.244756e-002, -1.278806e-002, -1.295762e-002, -1.309362e-002, - -1.302481e-002, -1.303374e-002, -1.304769e-002, -1.273059e-002, - -1.234906e-002, -1.165460e-002, -1.150704e-002, -1.157438e-002, - -1.183180e-002, -1.211213e-002, -1.245410e-002, -1.289924e-002, - -1.338677e-002, -1.367434e-002, -1.400805e-002, -1.448440e-002, - -1.523205e-002, -1.625185e-002, -1.724650e-002, -1.821619e-002, - -1.934396e-002, -2.034902e-002, -2.146216e-002, -2.255429e-002, - -2.345794e-002, -2.407578e-002, -2.473330e-002, -2.518774e-002, - -2.545110e-002, -2.567605e-002, -2.549367e-002, -2.532339e-002, - -2.484521e-002, -2.400662e-002, -2.284301e-002, -2.140593e-002, - -1.980293e-002, -1.802680e-002, -1.626461e-002, -1.433539e-002, - -1.243969e-002, -1.063240e-002, -8.995594e-003, -7.495592e-003, - -6.149849e-003, -4.766671e-003, -3.434923e-003, -2.462650e-003, - -1.622619e-003, -1.011871e-003, -4.786046e-004, -3.020620e-004, - -8.110432e-005, -4.487170e-005, 1.467182e-004, 3.158955e-004, - 5.757456e-004, 8.635201e-004, 9.685339e-004, 1.077888e-003, - 1.148168e-003, 1.222796e-003, 1.391458e-003, 1.372016e-003, - 1.363429e-003, 1.558088e-003, 2.060552e-003, 2.780482e-003, - 3.324287e-003, 3.885454e-003, 4.743050e-003, 6.009577e-003, - 7.458520e-003, 8.827932e-003, 1.033729e-002, 1.188023e-002, - 1.362263e-002, 1.538781e-002, 1.728339e-002, 1.895719e-002, - 2.025471e-002, 2.155070e-002, 2.310467e-002, 2.442670e-002, - 2.522437e-002, 2.581312e-002, 2.626836e-002, 2.656676e-002, - 2.664083e-002, 2.676569e-002, 2.698517e-002, 2.667176e-002, - 2.619389e-002, 2.575163e-002, 2.534815e-002, 2.466483e-002, - 2.396365e-002, 2.356251e-002, 2.311114e-002, 2.284606e-002, - 2.280984e-002, 2.289681e-002, 2.285924e-002, 2.308825e-002, - 2.379225e-002, 2.450944e-002, 2.539423e-002, 2.658280e-002, - 2.752424e-002, 2.813001e-002, 2.851226e-002, 2.856533e-002, - 2.838481e-002, 2.822024e-002, 2.801485e-002, 2.717028e-002, - 2.623165e-002, 2.539912e-002, 2.488814e-002, 2.394945e-002, - 2.332171e-002, 2.260363e-002, 2.219154e-002, 2.185633e-002, - 2.081541e-002, 1.943175e-002, 1.827618e-002, 1.682313e-002, - 1.487857e-002, 1.339682e-002, 1.203659e-002, 1.077357e-002, - 9.861793e-003, 9.231827e-003, 8.530502e-003, 8.093550e-003, - 7.343214e-003, 6.680474e-003, 6.202204e-003, 4.883752e-003, - 3.693219e-003, 2.134174e-003, 6.472897e-004, -4.158220e-004, - -1.520951e-003, -2.337441e-003, -2.751046e-003, -3.519085e-003, - -4.697765e-003, -6.970567e-003, -8.872353e-003, -1.199513e-002, - -1.504418e-002, -1.777444e-002, -2.069096e-002, -2.323264e-002, - -2.502690e-002, -2.564339e-002, -2.654564e-002, -2.730912e-002, - -2.933909e-002, -3.045904e-002, -3.150551e-002, -3.235453e-002, - -3.208958e-002, -3.136155e-002, -2.987756e-002, -2.731704e-002, - -2.489743e-002, -2.326479e-002, -2.181036e-002, -2.214287e-002, - -2.231096e-002, -2.173983e-002, -2.072493e-002, -2.063736e-002, - -1.999907e-002, -1.884769e-002, -1.759909e-002, -1.611805e-002, - -1.534827e-002, -1.392029e-002, -1.291553e-002, -1.164990e-002, - -1.059609e-002, -1.020992e-002, -1.097686e-002, -1.242700e-002, - -1.429676e-002, -1.659844e-002, -1.975934e-002, -2.150793e-002, - -2.320610e-002, -2.416167e-002, -2.508076e-002, -2.643128e-002, - -2.996249e-002, -3.435714e-002, -3.700306e-002, -3.891282e-002, - -4.046107e-002, -4.049479e-002, -4.021163e-002, -3.794242e-002, - -3.686781e-002, -3.464605e-002, -3.216059e-002, -3.042789e-002, - -2.725401e-002, -2.508145e-002, -2.170724e-002, -1.935225e-002, - -1.553053e-002, -1.210502e-002, -8.666101e-003, -6.973144e-003, - -5.024003e-003, -3.480946e-003, -5.228054e-004, 2.267057e-003, - 9.421416e-003, 1.842274e-002, 3.038311e-002, 4.458987e-002, - 6.054491e-002, 7.895482e-002, 9.876903e-002, 1.189849e-001, - 1.384521e-001, 1.575101e-001, 1.720200e-001, 1.779706e-001, - 1.762056e-001, 1.708525e-001, 1.556881e-001, 1.253158e-001, - 8.449835e-002, 5.209848e-002, 2.777749e-002, 9.482413e-003, - 5.796663e-003, -2.169195e-002, -7.291030e-002, -1.185558e-001, - -1.550983e-001, -1.821728e-001, -2.005274e-001, -2.072493e-001, - -2.072711e-001, -1.935315e-001, -1.699918e-001, 4.359585e-002, - 6.546174e-002, 6.839055e-002, 6.320990e-002, 4.901168e-002, - 2.848470e-002, 1.760527e-002, 1.620791e-002, 1.774181e-002, - 2.077249e-002, -5.406846e-002, 1.896780e-001, 1.055573e-001, - -7.790436e-004, -5.801000e-002, -6.767297e-002, -4.673779e-002, - -2.526352e-002, 1.251539e-002, 2.315873e-002, 0.000000e+000 - }, - { - 0.000000e+000, 3.321641e-003, 2.592714e-003, 4.280579e-005, - -2.882593e-003, -5.925705e-003, -7.913692e-003, -9.429694e-003, - -1.091851e-002, -1.183267e-002, -1.213341e-002, -1.224831e-002, - -1.237848e-002, -1.220007e-002, -1.199929e-002, -1.180922e-002, - -1.137188e-002, -1.112867e-002, -1.089602e-002, -1.042829e-002, - -9.658761e-003, -8.949333e-003, -8.242575e-003, -7.401133e-003, - -6.496119e-003, -5.475952e-003, -4.452048e-003, -3.293769e-003, - -1.999249e-003, -3.974334e-004, 1.446205e-003, 3.243150e-003, - 4.707863e-003, 6.007970e-003, 7.194176e-003, 8.228231e-003, - 9.146394e-003, 9.962331e-003, 1.062768e-002, 1.111311e-002, - 1.143714e-002, 1.156361e-002, 1.129829e-002, 1.085753e-002, - 1.029031e-002, 9.681833e-003, 8.853340e-003, 7.551216e-003, - 6.275372e-003, 5.500891e-003, 4.911019e-003, 4.319252e-003, - 3.708039e-003, 3.093804e-003, 2.514981e-003, 1.988017e-003, - 1.433421e-003, 8.832911e-004, 3.624873e-004, -2.069689e-004, - -8.146495e-004, -1.390755e-003, -2.007189e-003, -2.663301e-003, - -3.305663e-003, -3.917010e-003, -4.419469e-003, -4.735825e-003, - -4.750429e-003, -4.605938e-003, -4.107765e-003, -3.345906e-003, - -2.214510e-003, -7.036625e-004, 1.250681e-003, 3.629979e-003, - 6.429778e-003, 9.668604e-003, 1.308283e-002, 1.644156e-002, - 1.945215e-002, 2.200512e-002, 2.414019e-002, 2.591602e-002, - 2.731274e-002, 2.824080e-002, 2.880303e-002, 2.899341e-002, - 2.876166e-002, 2.821096e-002, 2.732553e-002, 2.624545e-002, - 2.489571e-002, 2.348202e-002, 2.193128e-002, 2.022440e-002, - 1.845329e-002, 1.657484e-002, 1.458244e-002, 1.254092e-002, - 1.038282e-002, 8.258515e-003, 6.113134e-003, 4.042283e-003, - 2.118848e-003, 2.733252e-004, -1.506062e-003, -3.124258e-003, - -4.585993e-003, -5.747893e-003, -6.586137e-003, -7.116251e-003, - -7.394826e-003, -7.391032e-003, -7.171211e-003, -6.798335e-003, - -6.203427e-003, -5.413548e-003, -4.399422e-003, -3.168143e-003, - -1.837456e-003, -4.632310e-004, 7.893468e-004, 1.679231e-003, - 2.095900e-003, 1.942356e-003, 1.277794e-003, 2.310348e-004, - -1.088318e-003, -2.608093e-003, -4.237469e-003, -5.927652e-003, - -7.668679e-003, -9.388632e-003, -1.095194e-002, -1.238680e-002, - -1.367821e-002, -1.487474e-002, -1.584987e-002, -1.661869e-002, - -1.719889e-002, -1.762585e-002, -1.784472e-002, -1.789710e-002, - -1.770237e-002, -1.732614e-002, -1.690975e-002, -1.658697e-002, - -1.635392e-002, -1.624064e-002, -1.628977e-002, -1.646193e-002, - -1.669166e-002, -1.705178e-002, -1.750696e-002, -1.800730e-002, - -1.867744e-002, -1.962459e-002, -2.080652e-002, -2.207092e-002, - -2.339631e-002, -2.479402e-002, -2.619586e-002, -2.754845e-002, - -2.879991e-002, -2.991007e-002, -3.083729e-002, -3.145902e-002, - -3.167492e-002, -3.154713e-002, -3.111039e-002, -3.046822e-002, - -2.973984e-002, -2.896846e-002, -2.806556e-002, -2.687628e-002, - -2.542783e-002, -2.376462e-002, -2.185718e-002, -1.976353e-002, - -1.765176e-002, -1.569133e-002, -1.412402e-002, -1.304862e-002, - -1.244769e-002, -1.208620e-002, -1.196199e-002, -1.199680e-002, - -1.215593e-002, -1.248310e-002, -1.290529e-002, -1.343862e-002, - -1.402539e-002, -1.461904e-002, -1.517468e-002, -1.560700e-002, - -1.595057e-002, -1.625716e-002, -1.647335e-002, -1.656054e-002, - -1.655177e-002, -1.644792e-002, -1.625446e-002, -1.594047e-002, - -1.546482e-002, -1.479407e-002, -1.372266e-002, -1.225945e-002, - -1.028976e-002, -7.997435e-003, -5.528429e-003, -3.089199e-003, - -7.783393e-004, 1.395333e-003, 3.477471e-003, 5.584500e-003, - 7.777331e-003, 1.009818e-002, 1.252344e-002, 1.498759e-002, - 1.738373e-002, 1.952703e-002, 2.126577e-002, 2.253444e-002, - 2.334288e-002, 2.381004e-002, 2.410260e-002, 2.423595e-002, - 2.425126e-002, 2.421877e-002, 2.414368e-002, 2.398768e-002, - 2.375503e-002, 2.348476e-002, 2.318161e-002, 2.285410e-002, - 2.251209e-002, 2.217114e-002, 2.184192e-002, 2.158209e-002, - 2.149380e-002, 2.153663e-002, 2.169011e-002, 2.205254e-002, - 2.257134e-002, 2.334863e-002, 2.447820e-002, 2.607915e-002, - 2.819691e-002, 3.073058e-002, 3.339784e-002, 3.602718e-002, - 3.842250e-002, 4.053192e-002, 4.236719e-002, 4.393802e-002, - 4.529136e-002, 4.645622e-002, 4.740013e-002, 4.811708e-002, - 4.850362e-002, 4.858522e-002, 4.815847e-002, 4.727514e-002, - 4.599768e-002, 4.443069e-002, 4.263553e-002, 4.072684e-002, - 3.868882e-002, 3.651920e-002, 3.426334e-002, 3.193309e-002, - 2.962606e-002, 2.738232e-002, 2.528651e-002, 2.340912e-002, - 2.182334e-002, 2.052161e-002, 1.942030e-002, 1.846308e-002, - 1.756713e-002, 1.677856e-002, 1.607202e-002, 1.552932e-002, - 1.515795e-002, 1.494451e-002, 1.491904e-002, 1.505772e-002, - 1.521077e-002, 1.514021e-002, 1.461861e-002, 1.359342e-002, - 1.219405e-002, 1.049815e-002, 8.612150e-003, 6.570142e-003, - 4.433888e-003, 2.235836e-003, 3.929407e-006, -2.306059e-003, - -4.623356e-003, -6.908913e-003, -9.117191e-003, -1.122007e-002, - -1.314495e-002, -1.494481e-002, -1.663283e-002, -1.821543e-002, - -1.965112e-002, -2.095159e-002, -2.209822e-002, -2.310385e-002, - -2.392870e-002, -2.444801e-002, -2.456159e-002, -2.422051e-002, - -2.347184e-002, -2.240059e-002, -2.113360e-002, -1.978791e-002, - -1.853191e-002, -1.738460e-002, -1.629606e-002, -1.525086e-002, - -1.423495e-002, -1.319321e-002, -1.217349e-002, -1.122856e-002, - -1.037935e-002, -9.782279e-003, -9.631885e-003, -9.885508e-003, - -1.051052e-002, -1.134994e-002, -1.233006e-002, -1.343798e-002, - -1.461849e-002, -1.586901e-002, -1.705980e-002, -1.821320e-002, - -1.930498e-002, -2.026401e-002, -2.106954e-002, -2.161733e-002, - -2.192752e-002, -2.190976e-002, -2.147518e-002, -2.059904e-002, - -1.929402e-002, -1.742499e-002, -1.518140e-002, -1.261313e-002, - -1.001896e-002, -7.708937e-003, -5.836388e-003, -4.432362e-003, - -3.487981e-003, -2.962518e-003, -2.825114e-003, -2.964421e-003, - -3.386123e-003, -4.090620e-003, -5.002058e-003, -6.109543e-003, - -7.327273e-003, -8.610146e-003, -9.900601e-003, -1.119323e-002, - -1.244158e-002, -1.356597e-002, -1.465918e-002, -1.568940e-002, - -1.658761e-002, -1.742321e-002, -1.816052e-002, -1.876619e-002, - -1.923035e-002, -1.950885e-002, -1.959682e-002, -1.939644e-002, - -1.900029e-002, -1.853808e-002, -1.805686e-002, -1.753369e-002, - -1.694627e-002, -1.627330e-002, -1.558482e-002, -1.488460e-002, - -1.424723e-002, -1.372812e-002, -1.339523e-002, -1.326354e-002, - -1.336702e-002, -1.358236e-002, -1.384628e-002, -1.416378e-002, - -1.451196e-002, -1.488297e-002, -1.527593e-002, -1.573709e-002, - -1.618844e-002, -1.670405e-002, -1.722606e-002, -1.777150e-002, - -1.822953e-002, -1.863186e-002, -1.900493e-002, -1.924612e-002, - -1.931916e-002, -1.926304e-002, -1.891365e-002, -1.827377e-002, - -1.745609e-002, -1.643715e-002, -1.524327e-002, -1.394049e-002, - -1.250083e-002, -1.087176e-002, -9.173000e-003, -7.468558e-003, - -5.720147e-003, -4.067719e-003, -2.609732e-003, -1.214611e-003, - 8.934172e-005, 1.271293e-003, 2.332239e-003, 3.245626e-003, - 3.928270e-003, 4.312802e-003, 4.528306e-003, 4.519030e-003, - 4.338210e-003, 4.053178e-003, 3.714664e-003, 3.340160e-003, - 2.994906e-003, 2.736800e-003, 2.508877e-003, 2.409950e-003, - 2.603621e-003, 3.154887e-003, 4.128622e-003, 5.408086e-003, - 6.803820e-003, 8.222514e-003, 9.569210e-003, 1.084218e-002, - 1.212399e-002, 1.359248e-002, 1.533512e-002, 1.734573e-002, - 1.962636e-002, 2.202229e-002, 2.425235e-002, 2.599880e-002, - 2.710987e-002, 2.758736e-002, 2.749764e-002, 2.714775e-002, - 2.648015e-002, 2.551842e-002, 2.435354e-002, 2.305600e-002, - 2.160904e-002, 1.999600e-002, 1.831226e-002, 1.651710e-002, - 1.466036e-002, 1.281827e-002, 1.102577e-002, 9.206446e-003, - 7.400837e-003, 5.593449e-003, 3.786305e-003, 1.972613e-003, - 2.114853e-004, -1.515759e-003, -3.281600e-003, -5.011982e-003, - -6.658060e-003, -8.222363e-003, -9.671738e-003, -1.096011e-002, - -1.212414e-002, -1.315566e-002, -1.397659e-002, -1.454402e-002, - -1.475317e-002, -1.458079e-002, -1.407919e-002, -1.344489e-002, - -1.265710e-002, -1.175546e-002, -1.069331e-002, -9.431497e-003, - -7.935916e-003, -6.251086e-003, -4.451768e-003, -2.663129e-003, - -8.648447e-004, 8.309523e-004, 2.484674e-003, 4.066302e-003, - 5.491258e-003, 6.713115e-003, 7.666358e-003, 8.399299e-003, - 9.039421e-003, 9.455859e-003, 9.730167e-003, 9.931253e-003, - 9.994235e-003, 9.993722e-003, 1.007099e-002, 1.028410e-002, - 1.054874e-002, 1.095244e-002, 1.156079e-002, 1.250425e-002, - 1.378384e-002, 1.560456e-002, 1.778354e-002, 2.002093e-002, - 2.210150e-002, 2.399389e-002, 2.561584e-002, 2.693659e-002, - 2.801939e-002, 2.891124e-002, 2.961987e-002, 3.008463e-002, - 3.036073e-002, 3.036967e-002, 3.007197e-002, 2.938955e-002, - 2.830023e-002, 2.685125e-002, 2.523869e-002, 2.351020e-002, - 2.167644e-002, 1.976183e-002, 1.782065e-002, 1.585395e-002, - 1.398938e-002, 1.221838e-002, 1.064477e-002, 9.333372e-003, - 8.346204e-003, 7.674540e-003, 7.262850e-003, 7.146377e-003, - 7.239997e-003, 7.480129e-003, 7.810700e-003, 8.086959e-003, - 8.246213e-003, 8.348484e-003, 8.448728e-003, 8.633581e-003, - 8.890279e-003, 9.046792e-003, 9.032820e-003, 8.854836e-003, - 8.394070e-003, 7.562755e-003, 6.502205e-003, 5.237063e-003, - 3.822572e-003, 2.267475e-003, 6.930835e-004, -9.495906e-004, - -2.608063e-003, -4.241719e-003, -5.971734e-003, -7.916746e-003, - -9.854647e-003, -1.162972e-002, -1.337289e-002, -1.510281e-002, - -1.677797e-002, -1.836914e-002, -1.979060e-002, -2.095685e-002, - -2.190697e-002, -2.248561e-002, -2.278490e-002, -2.283621e-002, - -2.256332e-002, -2.161577e-002, -1.999491e-002, -1.801104e-002, - -1.606761e-002, -1.441126e-002, -1.305112e-002, -1.198191e-002, - -1.105584e-002, -1.016212e-002, -9.235881e-003, -8.290432e-003, - -7.377503e-003, -6.619576e-003, -5.967005e-003, -5.555641e-003, - -5.530417e-003, -5.846932e-003, -6.501593e-003, -7.382442e-003, - -8.206221e-003, -8.908396e-003, -9.636019e-003, -1.035491e-002, - -1.105797e-002, -1.166029e-002, -1.205916e-002, -1.206248e-002, - -1.173028e-002, -1.112025e-002, -1.016664e-002, -8.800269e-003, - -7.166869e-003, -5.077839e-003, -2.654945e-003, 8.741897e-005, - 3.017198e-003, 5.973519e-003, 8.655810e-003, 1.111326e-002, - 1.311736e-002, 1.460243e-002, 1.562391e-002, 1.613643e-002, - 1.623685e-002, 1.606871e-002, 1.550503e-002, 1.471522e-002, - 1.368147e-002, 1.238285e-002, 1.077292e-002, 9.040615e-003, - 7.254797e-003, 5.292937e-003, 3.270173e-003, 1.293779e-003, - -7.762852e-004, -2.764063e-003, -4.601199e-003, -6.468118e-003, - -8.375900e-003, -1.036160e-002, -1.231126e-002, -1.413572e-002, - -1.558418e-002, -1.688216e-002, -1.803804e-002, -1.892497e-002, - -1.932002e-002, -1.931353e-002, -1.899873e-002, -1.848224e-002, - -1.772208e-002, -1.672131e-002, -1.543699e-002, -1.387757e-002, - -1.195271e-002, -9.794970e-003, -7.318321e-003, -4.693313e-003, - -2.135113e-003, 2.479421e-004, 2.288364e-003, 3.763006e-003, - 4.677839e-003, 5.084712e-003, 5.055777e-003, 4.797264e-003, - 4.580981e-003, 4.138838e-003, 3.394913e-003, 2.648068e-003, - 1.791164e-003, 8.573600e-004, -4.729002e-007, -9.352240e-004, - -1.906338e-003, -2.844634e-003, -3.709358e-003, -4.473033e-003, - -5.125306e-003, -5.737482e-003, -6.307470e-003, -6.833593e-003, - -7.423291e-003, -7.860494e-003, -7.994462e-003, -7.664141e-003, - -6.915605e-003, -5.844847e-003, -4.794827e-003, -3.664823e-003, - -2.451599e-003, -1.301137e-003, -3.349384e-004, 3.277434e-004, - 9.662997e-004, 1.805600e-003, 2.737111e-003, 3.879564e-003, - 4.688074e-003, 5.181844e-003, 5.328146e-003, 5.335267e-003, - 4.952646e-003, 4.220014e-003, 3.308466e-003, 2.204764e-003, - 1.091776e-003, 2.302012e-004, -6.852518e-004, -1.590577e-003, - -2.561536e-003, -3.659200e-003, -4.590463e-003, -5.522301e-003, - -6.496577e-003, -7.492864e-003, -8.295959e-003, -8.921894e-003, - -9.389135e-003, -9.857958e-003, -1.029640e-002, -1.063276e-002, - -1.082072e-002, -1.107845e-002, -1.141187e-002, -1.191378e-002, - -1.243624e-002, -1.309867e-002, -1.372544e-002, -1.430869e-002, - -1.519166e-002, -1.628453e-002, -1.735740e-002, -1.868485e-002, - -2.010048e-002, -2.149121e-002, -2.289539e-002, -2.412255e-002, - -2.507247e-002, -2.583849e-002, -2.648481e-002, -2.666663e-002, - -2.637743e-002, -2.589115e-002, -2.477853e-002, -2.288156e-002, - -2.034675e-002, -1.785121e-002, -1.515602e-002, -1.251034e-002, - -9.763373e-003, -6.947004e-003, -4.207495e-003, -1.683069e-003, - 6.979801e-004, 3.111165e-003, 5.657024e-003, 7.738675e-003, - 9.652138e-003, 1.107152e-002, 1.212810e-002, 1.318344e-002, - 1.401971e-002, 1.471052e-002, 1.527857e-002, 1.575929e-002, - 1.609279e-002, 1.652767e-002, 1.722855e-002, 1.784664e-002, - 1.872166e-002, 1.956102e-002, 2.093209e-002, 2.265772e-002, - 2.441846e-002, 2.629279e-002, 2.833140e-002, 3.097102e-002, - 3.388437e-002, 3.736146e-002, 4.094176e-002, 4.429463e-002, - 4.721266e-002, 4.979020e-002, 5.188926e-002, 5.363935e-002, - 5.491537e-002, 5.589863e-002, 5.659179e-002, 5.676511e-002, - 5.653564e-002, 5.591879e-002, 5.511470e-002, 5.380422e-002, - 5.199434e-002, 4.986991e-002, 4.760816e-002, 4.520731e-002, - 4.218684e-002, 3.914884e-002, 3.646176e-002, 3.357897e-002, - 3.063625e-002, 2.733737e-002, 2.419918e-002, 2.118239e-002, - 1.807655e-002, 1.485870e-002, 1.183040e-002, 9.026062e-003, - 6.620857e-003, 4.704913e-003, 2.987400e-003, 1.840756e-003, - 1.242731e-003, 8.939618e-004, 5.587037e-005, -9.270382e-004, - -1.941550e-003, -2.841552e-003, -3.702227e-003, -4.698885e-003, - -5.709013e-003, -6.632671e-003, -7.718830e-003, -8.423272e-003, - -9.357050e-003, -1.054631e-002, -1.211548e-002, -1.428143e-002, - -1.653279e-002, -1.873134e-002, -2.056412e-002, -2.283445e-002, - -2.533304e-002, -2.762163e-002, -2.978274e-002, -3.188331e-002, - -3.359572e-002, -3.475659e-002, -3.539514e-002, -3.578022e-002, - -3.562228e-002, -3.525821e-002, -3.475633e-002, -3.406298e-002, - -3.281330e-002, -3.113543e-002, -2.904255e-002, -2.702976e-002, - -2.486391e-002, -2.290872e-002, -2.124425e-002, -2.010744e-002, - -1.973424e-002, -1.953781e-002, -1.975753e-002, -2.053141e-002, - -2.179165e-002, -2.267425e-002, -2.307240e-002, -2.385640e-002, - -2.471124e-002, -2.550678e-002, -2.616918e-002, -2.670716e-002, - -2.698499e-002, -2.722388e-002, -2.737727e-002, -2.810636e-002, - -2.872728e-002, -2.901080e-002, -2.967948e-002, -3.063864e-002, - -3.143548e-002, -3.247343e-002, -3.346848e-002, -3.406640e-002, - -3.457848e-002, -3.461493e-002, -3.460132e-002, -3.435534e-002, - -3.392541e-002, -3.398802e-002, -3.418863e-002, -3.414265e-002, - -3.386730e-002, -3.300625e-002, -3.183807e-002, -3.120351e-002, - -2.980161e-002, -2.831502e-002, -2.728346e-002, -2.710408e-002, - -2.699529e-002, -2.687844e-002, -2.708906e-002, -2.688304e-002, - -2.596364e-002, -2.351500e-002, -2.067922e-002, -1.722305e-002, - -1.392980e-002, -1.039996e-002, -6.903864e-003, -3.067823e-003, - 2.028519e-003, 8.041244e-003, 1.414846e-002, 2.074916e-002, - 2.733735e-002, 3.390441e-002, 4.194735e-002, 4.936889e-002, - 5.598260e-002, 6.388697e-002, 7.169294e-002, 7.814219e-002, - 8.458951e-002, 9.041624e-002, 9.487703e-002, 9.980381e-002, - 1.052409e-001, 1.095542e-001, 1.132784e-001, 1.165321e-001, - 1.183756e-001, 1.195366e-001, 1.185189e-001, 1.156774e-001, - 1.116449e-001, 1.049208e-001, 9.821842e-002, 9.115314e-002, - 8.341884e-002, 7.504096e-002, 6.449259e-002, 5.356657e-002, - 4.090816e-002, 2.710519e-002, 1.311753e-002, -7.555144e-004, - -1.348083e-002, -2.418057e-002, -3.599344e-002, -4.658415e-002, - -5.642601e-002, -6.620353e-002, -7.438333e-002, -8.311919e-002, - -9.137699e-002, -9.985629e-002, -1.059492e-001, -1.096370e-001, - -1.115441e-001, -1.124293e-001, -1.146147e-001, -1.163821e-001, - -1.174053e-001, -1.172222e-001, -1.168887e-001, -1.149060e-001, - -1.083474e-001, -9.283483e-002, -7.553393e-002, -5.826291e-002, - -3.989058e-002, -2.035611e-002, 9.032048e-004, 2.256097e-002, - 4.528490e-002, 6.725999e-002, 8.558692e-002, 1.046502e-001, - 1.162965e-001, 1.238080e-001, 1.221044e-001, 1.128105e-001, - 9.781889e-002, 8.576052e-002, 7.408909e-002, 6.121316e-002, - 4.141812e-002, 2.286778e-002, 1.366849e-003, -2.141731e-002, - -3.636421e-002, -5.005051e-002, -5.696638e-002, -6.666192e-002, - -7.407884e-002, -7.893085e-002, -7.923572e-002, -8.687703e-002, - -7.644238e-002, -6.619838e-002, -5.361237e-002, -3.777692e-002, - -1.248031e-002, 3.198658e-002, 8.673090e-002, 1.358175e-001, - 1.768163e-001, 1.353593e-002, -5.293663e-002, -3.202199e-002, - 1.470935e-002, 3.990869e-002, 2.819456e-002, 5.089639e-002, - -2.047635e-002, -7.206468e-002, -5.793094e-002, 0.000000e+000 - }, - { - 0.000000e+000, 2.627828e-002, 4.356570e-002, 5.590066e-002, - 6.513557e-002, 7.176401e-002, 7.619190e-002, 7.902456e-002, - 8.052635e-002, 8.069023e-002, 7.973241e-002, 7.958253e-002, - 7.911345e-002, 7.803655e-002, 7.619397e-002, 7.366693e-002, - 7.072099e-002, 6.703199e-002, 6.262389e-002, 5.760897e-002, - 5.207308e-002, 4.695460e-002, 4.210567e-002, 3.705850e-002, - 3.188252e-002, 2.664688e-002, 2.123305e-002, 1.557971e-002, - 9.553154e-003, 3.115702e-003, -3.597003e-003, -9.837649e-003, - -1.495208e-002, -1.986732e-002, -2.448688e-002, -2.879064e-002, - -3.255962e-002, -3.593399e-002, -3.872280e-002, -4.113721e-002, - -4.305561e-002, -4.438058e-002, -4.419171e-002, -4.346354e-002, - -4.227857e-002, -4.070248e-002, -3.863191e-002, -3.578455e-002, - -3.300371e-002, -3.104144e-002, -2.947820e-002, -2.815277e-002, - -2.631880e-002, -2.456747e-002, -2.288192e-002, -2.122934e-002, - -1.968993e-002, -1.816338e-002, -1.675128e-002, -1.542182e-002, - -1.424042e-002, -1.321213e-002, -1.201029e-002, -1.085904e-002, - -9.875661e-003, -9.093173e-003, -8.682797e-003, -8.550510e-003, - -8.935258e-003, -9.825035e-003, -1.115528e-002, -1.310271e-002, - -1.553719e-002, -1.855467e-002, -2.229852e-002, -2.691959e-002, - -3.226086e-002, -3.838606e-002, -4.491678e-002, -5.132055e-002, - -5.707194e-002, -6.198682e-002, -6.592361e-002, -6.891807e-002, - -7.108109e-002, -7.244639e-002, -7.304935e-002, -7.306753e-002, - -7.234955e-002, -7.092714e-002, -6.894033e-002, -6.645877e-002, - -6.351020e-002, -6.009382e-002, -5.631860e-002, -5.230395e-002, - -4.806876e-002, -4.358208e-002, -3.902589e-002, -3.426490e-002, - -2.934862e-002, -2.429013e-002, -1.918815e-002, -1.416938e-002, - -9.207840e-003, -4.251368e-003, 4.599305e-004, 4.968922e-003, - 9.071060e-003, 1.253749e-002, 1.545266e-002, 1.766889e-002, - 1.938519e-002, 2.064794e-002, 2.150962e-002, 2.206519e-002, - 2.220465e-002, 2.183589e-002, 2.100164e-002, 1.974939e-002, - 1.812897e-002, 1.632388e-002, 1.471228e-002, 1.372267e-002, - 1.357852e-002, 1.435166e-002, 1.592284e-002, 1.804717e-002, - 2.055896e-002, 2.333807e-002, 2.627446e-002, 2.923533e-002, - 3.218247e-002, 3.508585e-002, 3.770380e-002, 4.009361e-002, - 4.223084e-002, 4.403938e-002, 4.543769e-002, 4.647158e-002, - 4.703238e-002, 4.721819e-002, 4.708227e-002, 4.658257e-002, - 4.562471e-002, 4.440744e-002, 4.311149e-002, 4.188610e-002, - 4.084983e-002, 4.003645e-002, 3.943564e-002, 3.902582e-002, - 3.875864e-002, 3.867983e-002, 3.867633e-002, 3.884930e-002, - 3.931250e-002, 4.016473e-002, 4.135183e-002, 4.273392e-002, - 4.423522e-002, 4.572808e-002, 4.722831e-002, 4.865312e-002, - 4.987304e-002, 5.071208e-002, 5.125245e-002, 5.128944e-002, - 5.061868e-002, 4.918345e-002, 4.718244e-002, 4.482501e-002, - 4.230871e-002, 3.965336e-002, 3.686130e-002, 3.371874e-002, - 3.012635e-002, 2.606147e-002, 2.156095e-002, 1.681697e-002, - 1.207351e-002, 7.645641e-003, 3.942093e-003, 1.261315e-003, - -4.727037e-004, -1.747022e-003, -2.521234e-003, -2.825036e-003, - -2.706748e-003, -2.289798e-003, -1.615457e-003, -6.138811e-004, - 5.522982e-004, 1.858184e-003, 3.252254e-003, 4.638838e-003, - 5.958318e-003, 7.193464e-003, 8.353948e-003, 9.482166e-003, - 1.056264e-002, 1.158997e-002, 1.257172e-002, 1.349233e-002, - 1.427194e-002, 1.475731e-002, 1.460136e-002, 1.375607e-002, - 1.206498e-002, 9.834518e-003, 7.357119e-003, 4.906672e-003, - 2.626694e-003, 5.841220e-004, -1.284419e-003, -3.180704e-003, - -5.291252e-003, -7.671960e-003, -1.028605e-002, -1.308360e-002, - -1.582610e-002, -1.821452e-002, -1.997544e-002, -2.085702e-002, - -2.094075e-002, -2.041542e-002, -1.965968e-002, -1.869600e-002, - -1.761817e-002, -1.646892e-002, -1.527820e-002, -1.408051e-002, - -1.281317e-002, -1.146979e-002, -1.006414e-002, -8.645360e-003, - -7.346602e-003, -6.177722e-003, -5.115387e-003, -4.225271e-003, - -3.526228e-003, -3.055475e-003, -2.887008e-003, -3.130046e-003, - -3.723315e-003, -4.816218e-003, -6.561010e-003, -9.329511e-003, - -1.308004e-002, -1.760819e-002, -2.241196e-002, -2.707801e-002, - -3.137429e-002, -3.521991e-002, -3.858883e-002, -4.152090e-002, - -4.406801e-002, -4.632573e-002, -4.818105e-002, -4.956581e-002, - -5.038003e-002, -5.065222e-002, -5.005562e-002, -4.870219e-002, - -4.669505e-002, -4.415117e-002, -4.119369e-002, -3.809685e-002, - -3.476382e-002, -3.125764e-002, -2.765345e-002, -2.397028e-002, - -2.033630e-002, -1.682772e-002, -1.355076e-002, -1.067354e-002, - -8.357976e-003, -6.646181e-003, -5.331191e-003, -4.304162e-003, - -3.474017e-003, -2.859178e-003, -2.496668e-003, -2.484125e-003, - -2.842075e-003, -3.545468e-003, -4.608330e-003, -6.037357e-003, - -7.548477e-003, -8.666820e-003, -8.945608e-003, -8.333319e-003, - -7.044790e-003, -5.299525e-003, -3.168177e-003, -7.802310e-004, - 1.831121e-003, 4.554385e-003, 7.302061e-003, 1.011387e-002, - 1.289861e-002, 1.560955e-002, 1.823487e-002, 2.070637e-002, - 2.295810e-002, 2.493980e-002, 2.675169e-002, 2.837913e-002, - 2.967944e-002, 3.078461e-002, 3.172341e-002, 3.238248e-002, - 3.269337e-002, 3.244795e-002, 3.151916e-002, 2.984039e-002, - 2.745177e-002, 2.448391e-002, 2.107207e-002, 1.759081e-002, - 1.421132e-002, 1.105214e-002, 8.079501e-003, 5.223456e-003, - 2.481226e-003, -2.666574e-004, -2.984635e-003, -5.541628e-003, - -7.835141e-003, -9.698211e-003, -1.073609e-002, -1.105750e-002, - -1.065264e-002, -9.808848e-003, -8.619613e-003, -7.165417e-003, - -5.528160e-003, -3.737545e-003, -2.027673e-003, -4.915721e-004, - 9.000932e-004, 2.125781e-003, 3.084694e-003, 3.606984e-003, - 3.710216e-003, 3.224418e-003, 2.047428e-003, 8.909056e-005, - -2.685746e-003, -6.463310e-003, -1.106788e-002, -1.617413e-002, - -2.126616e-002, -2.575522e-002, -2.937874e-002, -3.213212e-002, - -3.404779e-002, -3.515282e-002, -3.548356e-002, -3.530158e-002, - -3.457001e-002, -3.331769e-002, -3.166005e-002, -2.956109e-002, - -2.716868e-002, -2.460390e-002, -2.199989e-002, -1.941678e-002, - -1.687818e-002, -1.452556e-002, -1.234322e-002, -1.022435e-002, - -8.267533e-003, -6.482712e-003, -4.925990e-003, -3.662302e-003, - -2.644670e-003, -1.953745e-003, -1.541575e-003, -1.587935e-003, - -1.906619e-003, -2.335506e-003, -2.794909e-003, -3.345137e-003, - -3.966046e-003, -4.704764e-003, -5.528606e-003, -6.294894e-003, - -6.970916e-003, -7.475556e-003, -7.650764e-003, -7.321992e-003, - -6.547796e-003, -5.419448e-003, -4.151215e-003, -2.794719e-003, - -1.331699e-003, 2.278603e-004, 1.888577e-003, 3.633895e-003, - 5.296030e-003, 7.044443e-003, 8.851797e-003, 1.069586e-002, - 1.251898e-002, 1.422942e-002, 1.580922e-002, 1.718064e-002, - 1.825214e-002, 1.907148e-002, 1.937802e-002, 1.922977e-002, - 1.869040e-002, 1.781769e-002, 1.661995e-002, 1.513754e-002, - 1.341448e-002, 1.147469e-002, 9.346126e-003, 7.191739e-003, - 4.980831e-003, 2.872377e-003, 1.044545e-003, -4.911375e-004, - -1.820563e-003, -3.023930e-003, -4.040025e-003, -4.768080e-003, - -5.134973e-003, -4.922964e-003, -4.274135e-003, -3.281274e-003, - -1.981189e-003, -4.773831e-004, 1.178025e-003, 2.870916e-003, - 4.530133e-003, 6.122256e-003, 7.598488e-003, 8.869598e-003, - 9.583624e-003, 9.452668e-003, 8.576515e-003, 7.207909e-003, - 5.622641e-003, 4.157367e-003, 2.884576e-003, 1.662166e-003, - 3.617494e-004, -1.203779e-003, -3.162230e-003, -5.751424e-003, - -8.731253e-003, -1.189467e-002, -1.483600e-002, -1.705839e-002, - -1.807286e-002, -1.796341e-002, -1.698110e-002, -1.546264e-002, - -1.336805e-002, -1.097402e-002, -8.277499e-003, -5.223383e-003, - -1.952663e-003, 1.500560e-003, 5.084836e-003, 8.794249e-003, - 1.254274e-002, 1.631268e-002, 1.996194e-002, 2.348941e-002, - 2.684761e-002, 3.014619e-002, 3.339131e-002, 3.663702e-002, - 3.975877e-002, 4.280762e-002, 4.575990e-002, 4.857074e-002, - 5.123335e-002, 5.368155e-002, 5.584191e-002, 5.772099e-002, - 5.927605e-002, 6.044074e-002, 6.116607e-002, 6.139306e-002, - 6.094741e-002, 5.997073e-002, 5.839929e-002, 5.642551e-002, - 5.397851e-002, 5.127307e-002, 4.817413e-002, 4.471312e-002, - 4.091645e-002, 3.677714e-002, 3.241906e-002, 2.793954e-002, - 2.352165e-002, 1.928957e-002, 1.524484e-002, 1.144570e-002, - 7.913581e-003, 4.728555e-003, 1.894251e-003, -5.667448e-004, - -2.675146e-003, -4.370449e-003, -5.828696e-003, -7.147513e-003, - -8.359402e-003, -9.569339e-003, -1.085643e-002, -1.227528e-002, - -1.383560e-002, -1.570066e-002, -1.798045e-002, -2.088356e-002, - -2.451018e-002, -2.898965e-002, -3.404843e-002, -3.931568e-002, - -4.424036e-002, -4.868870e-002, -5.264210e-002, -5.594705e-002, - -5.865151e-002, -6.078702e-002, -6.240528e-002, -6.347179e-002, - -6.394946e-002, -6.404549e-002, -6.344627e-002, -6.200448e-002, - -5.982317e-002, -5.703574e-002, -5.381700e-002, -5.032320e-002, - -4.659501e-002, -4.277154e-002, -3.886421e-002, -3.503232e-002, - -3.137334e-002, -2.795653e-002, -2.484870e-002, -2.212347e-002, - -2.005669e-002, -1.868707e-002, -1.784391e-002, -1.745094e-002, - -1.745742e-002, -1.774705e-002, -1.826162e-002, -1.880551e-002, - -1.921331e-002, -1.964651e-002, -2.020183e-002, -2.097378e-002, - -2.173210e-002, -2.247803e-002, -2.307830e-002, -2.331256e-002, - -2.298058e-002, -2.202869e-002, -2.056929e-002, -1.873198e-002, - -1.666717e-002, -1.445166e-002, -1.210426e-002, -9.700113e-003, - -7.142979e-003, -4.511546e-003, -1.918869e-003, 7.646193e-004, - 3.455287e-003, 6.092725e-003, 8.770852e-003, 1.134070e-002, - 1.375535e-002, 1.595913e-002, 1.793026e-002, 1.967140e-002, - 2.098857e-002, 2.200259e-002, 2.260988e-002, 2.257011e-002, - 2.170842e-002, 1.980402e-002, 1.697129e-002, 1.375342e-002, - 1.072136e-002, 8.145979e-003, 6.064675e-003, 4.619938e-003, - 3.677349e-003, 3.102918e-003, 2.654093e-003, 2.276658e-003, - 1.845243e-003, 1.598734e-003, 1.714934e-003, 2.629802e-003, - 4.244820e-003, 6.453924e-003, 8.952735e-003, 1.173808e-002, - 1.460356e-002, 1.759296e-002, 2.059563e-002, 2.347594e-002, - 2.628005e-002, 2.883378e-002, 3.088405e-002, 3.233852e-002, - 3.335692e-002, 3.385749e-002, 3.362438e-002, 3.266313e-002, - 3.093414e-002, 2.838777e-002, 2.500909e-002, 2.091384e-002, - 1.620214e-002, 1.105623e-002, 6.223873e-003, 2.207844e-003, - -1.148476e-003, -3.896515e-003, -5.921253e-003, -7.175302e-003, - -7.849110e-003, -7.802389e-003, -7.113798e-003, -6.136098e-003, - -4.645588e-003, -2.724943e-003, -5.342138e-004, 1.814076e-003, - 4.357244e-003, 7.051008e-003, 9.808792e-003, 1.273379e-002, - 1.582396e-002, 1.881185e-002, 2.169359e-002, 2.466471e-002, - 2.757530e-002, 3.038334e-002, 3.308778e-002, 3.564911e-002, - 3.794767e-002, 4.004213e-002, 4.166380e-002, 4.264135e-002, - 4.269543e-002, 4.212728e-002, 4.088003e-002, 3.910308e-002, - 3.692032e-002, 3.451467e-002, 3.170472e-002, 2.846038e-002, - 2.471272e-002, 2.035176e-002, 1.520271e-002, 9.747894e-003, - 4.204373e-003, -9.206672e-004, -5.113475e-003, -8.182988e-003, - -1.037656e-002, -1.194051e-002, -1.297758e-002, -1.353458e-002, - -1.415914e-002, -1.463003e-002, -1.471139e-002, -1.459854e-002, - -1.434350e-002, -1.393638e-002, -1.341439e-002, -1.288608e-002, - -1.212842e-002, -1.159193e-002, -1.154043e-002, -1.167506e-002, - -1.183955e-002, -1.202065e-002, -1.213426e-002, -1.224055e-002, - -1.256419e-002, -1.325272e-002, -1.432872e-002, -1.593480e-002, - -1.823687e-002, -2.146896e-002, -2.492248e-002, -2.825205e-002, - -3.139219e-002, -3.411288e-002, -3.651172e-002, -3.885327e-002, - -4.111059e-002, -4.318833e-002, -4.497365e-002, -4.697960e-002, - -4.867548e-002, -4.984547e-002, -5.016826e-002, -4.956363e-002, - -4.804008e-002, -4.580773e-002, -4.307333e-002, -4.002110e-002, - -3.674657e-002, -3.380281e-002, -3.097613e-002, -2.813784e-002, - -2.504369e-002, -2.195559e-002, -1.908292e-002, -1.636935e-002, - -1.383472e-002, -1.134776e-002, -8.728267e-003, -6.545807e-003, - -4.838018e-003, -3.265082e-003, -1.787811e-003, -5.888244e-004, - 5.441743e-004, 1.908081e-003, 3.255414e-003, 4.860432e-003, - 6.896150e-003, 8.704352e-003, 1.039198e-002, 1.243576e-002, - 1.476070e-002, 1.744839e-002, 2.042175e-002, 2.368076e-002, - 2.704792e-002, 3.052133e-002, 3.405434e-002, 3.726289e-002, - 3.978704e-002, 4.201769e-002, 4.378185e-002, 4.532412e-002, - 4.633461e-002, 4.657057e-002, 4.582024e-002, 4.383243e-002, - 4.104639e-002, 3.770245e-002, 3.337082e-002, 2.917016e-002, - 2.541346e-002, 2.186189e-002, 1.839014e-002, 1.519992e-002, - 1.225911e-002, 9.619294e-003, 7.152423e-003, 5.211359e-003, - 3.037841e-003, 1.482596e-003, 8.586973e-004, 7.144907e-004, - 1.068465e-003, 1.889440e-003, 3.136620e-003, 4.446137e-003, - 5.838287e-003, 7.281508e-003, 8.178957e-003, 8.469780e-003, - 8.709169e-003, 8.774058e-003, 8.682823e-003, 8.326442e-003, - 7.176805e-003, 5.743723e-003, 3.964372e-003, 1.903091e-003, - -1.123500e-003, -5.651213e-003, -1.027072e-002, -1.489497e-002, - -1.910395e-002, -2.255696e-002, -2.530102e-002, -2.697925e-002, - -2.793650e-002, -2.812583e-002, -2.772935e-002, -2.804928e-002, - -2.758423e-002, -2.650849e-002, -2.493337e-002, -2.285047e-002, - -2.028780e-002, -1.743213e-002, -1.430325e-002, -1.066349e-002, - -6.814016e-003, -4.284213e-003, -1.743712e-003, 9.990059e-004, - 4.134005e-003, 7.182006e-003, 1.045095e-002, 1.382663e-002, - 1.737234e-002, 2.079778e-002, 2.398726e-002, 2.596310e-002, - 2.709636e-002, 2.760158e-002, 2.729220e-002, 2.622348e-002, - 2.504577e-002, 2.370765e-002, 2.198457e-002, 2.031694e-002, - 1.929364e-002, 1.792576e-002, 1.558601e-002, 1.327993e-002, - 1.112930e-002, 8.995793e-003, 6.572884e-003, 4.731314e-003, - 3.755613e-003, 3.740523e-003, 4.365604e-003, 5.350226e-003, - 5.337416e-003, 5.849048e-003, 6.781604e-003, 8.001264e-003, - 9.179451e-003, 1.029018e-002, 1.134280e-002, 1.242238e-002, - 1.334398e-002, 1.406468e-002, 1.260589e-002, 1.039404e-002, - 7.546527e-003, 4.408274e-003, 1.109559e-003, -2.896219e-003, - -7.744406e-003, -1.281613e-002, -1.816120e-002, -2.368978e-002, - -3.016315e-002, -3.638682e-002, -4.154844e-002, -4.597240e-002, - -4.961307e-002, -5.211001e-002, -5.344302e-002, -5.374298e-002, - -5.329088e-002, -5.217868e-002, -5.151090e-002, -5.154720e-002, - -5.105957e-002, -4.987514e-002, -4.863344e-002, -4.686170e-002, - -4.414574e-002, -4.104717e-002, -3.743972e-002, -3.319194e-002, - -2.911455e-002, -2.670544e-002, -2.429220e-002, -2.165405e-002, - -1.895595e-002, -1.625540e-002, -1.367699e-002, -1.108933e-002, - -8.286070e-003, -5.442355e-003, -2.881166e-003, -2.614483e-003, - -1.762658e-003, -1.165097e-003, -6.144086e-004, -2.642032e-004, - 1.843571e-004, 6.141091e-004, 9.860957e-004, 1.572735e-003, - 2.975511e-003, 3.159087e-003, 2.998500e-003, 3.637179e-003, - 4.878917e-003, 6.091669e-003, 7.524655e-003, 8.750707e-003, - 9.784903e-003, 1.074422e-002, 1.275156e-002, 1.306294e-002, - 1.285488e-002, 1.395387e-002, 1.516617e-002, 1.667991e-002, - 1.793865e-002, 1.927099e-002, 2.077174e-002, 2.212471e-002, - 2.390636e-002, 2.533258e-002, 2.500121e-002, 2.592449e-002, - 2.731590e-002, 2.964147e-002, 3.240507e-002, 3.572245e-002, - 3.892379e-002, 4.222847e-002, 4.560767e-002, 4.891419e-002, - 4.885967e-002, 4.782638e-002, 4.706603e-002, 4.697589e-002, - 4.698240e-002, 4.789162e-002, 4.933704e-002, 5.052240e-002, - 5.220375e-002, 5.425961e-002, 5.209070e-002, 4.869086e-002, - 4.508302e-002, 4.169870e-002, 3.757880e-002, 3.421410e-002, - 3.181283e-002, 2.941233e-002, 2.674478e-002, 2.418436e-002, - 1.907172e-002, 1.191656e-002, 4.452421e-003, -2.718167e-003, - -8.637004e-003, -1.443702e-002, -1.940271e-002, -2.347476e-002, - -2.777385e-002, -3.113469e-002, -3.621604e-002, -4.422043e-002, - -5.058789e-002, -5.483919e-002, -5.848729e-002, -5.970394e-002, - -5.970384e-002, -5.792439e-002, -5.725175e-002, -5.498376e-002, - -5.259520e-002, -5.493362e-002, -5.610667e-002, -5.671857e-002, - -5.623606e-002, -5.475203e-002, -5.320966e-002, -5.055131e-002, - -4.742022e-002, -4.302681e-002, -3.775431e-002, -3.654592e-002, - -3.458210e-002, -3.180384e-002, -2.721688e-002, -1.477351e-002, - 2.567142e-003, 1.660427e-002, 2.870534e-002, 3.712421e-002, - 4.358954e-002, 5.346284e-002, 7.094791e-002, 8.490620e-002, - 9.454099e-002, 9.268352e-002, 8.603546e-002, 6.487314e-002, - 4.228133e-002, 2.109514e-002, 5.503028e-003, -5.066366e-002, - -6.957882e-002, -8.013058e-002, -7.944767e-002, -6.659527e-002, - -4.008165e-002, -4.099015e-003, 3.777047e-002, 7.874712e-002, - 1.184827e-001, -1.491780e-002, -7.736965e-002, -4.061215e-002, - 1.468958e-002, 3.447876e-002, -4.454113e-003, 1.276782e-001, - 4.064773e-003, -1.035723e-001, -9.401675e-002, 0.000000e+000 - }, - { - 0.000000e+000, 3.547621e-002, 5.696037e-002, 7.054234e-002, - 7.921379e-002, 8.462385e-002, 8.677876e-002, 8.676355e-002, - 8.457211e-002, 8.161055e-002, 7.790201e-002, 7.549933e-002, - 7.295599e-002, 6.965965e-002, 6.577509e-002, 6.128597e-002, - 5.647937e-002, 5.087796e-002, 4.458235e-002, 3.811087e-002, - 3.154634e-002, 2.607396e-002, 2.160396e-002, 1.710168e-002, - 1.271629e-002, 8.405392e-003, 4.004336e-003, -4.734750e-004, - -4.990597e-003, -9.434139e-003, -1.372162e-002, -1.743055e-002, - -1.995160e-002, -2.240339e-002, -2.476201e-002, -2.699072e-002, - -2.917438e-002, -3.142751e-002, -3.355752e-002, -3.564985e-002, - -3.775528e-002, -3.981663e-002, -4.095869e-002, -4.206586e-002, - -4.316132e-002, -4.417180e-002, -4.508013e-002, -4.579583e-002, - -4.641229e-002, -4.710728e-002, -4.778820e-002, -4.839605e-002, - -4.832637e-002, -4.812894e-002, -4.785001e-002, -4.755650e-002, - -4.724626e-002, -4.681329e-002, -4.653449e-002, -4.618425e-002, - -4.576769e-002, -4.535510e-002, -4.450738e-002, -4.353494e-002, - -4.245083e-002, -4.123470e-002, -4.003358e-002, -3.860603e-002, - -3.707610e-002, -3.548101e-002, -3.367063e-002, -3.179131e-002, - -2.977682e-002, -2.747965e-002, -2.512841e-002, -2.271883e-002, - -2.026472e-002, -1.774486e-002, -1.526243e-002, -1.265968e-002, - -1.011775e-002, -7.607875e-003, -5.158386e-003, -2.665612e-003, - -2.611326e-004, 2.046611e-003, 4.233984e-003, 6.294196e-003, - 8.347134e-003, 1.029622e-002, 1.219682e-002, 1.404898e-002, - 1.587074e-002, 1.767257e-002, 1.934008e-002, 2.093646e-002, - 2.245389e-002, 2.391636e-002, 2.527077e-002, 2.658674e-002, - 2.781539e-002, 2.891410e-002, 2.984920e-002, 3.062593e-002, - 3.125746e-002, 3.182117e-002, 3.217840e-002, 3.242923e-002, - 3.258587e-002, 3.275589e-002, 3.300144e-002, 3.312341e-002, - 3.325567e-002, 3.328679e-002, 3.315130e-002, 3.299599e-002, - 3.273514e-002, 3.245011e-002, 3.220969e-002, 3.193666e-002, - 3.162981e-002, 3.121950e-002, 3.069806e-002, 3.009597e-002, - 2.928399e-002, 2.835065e-002, 2.733145e-002, 2.618496e-002, - 2.499223e-002, 2.381573e-002, 2.260288e-002, 2.138612e-002, - 2.014255e-002, 1.889497e-002, 1.748627e-002, 1.613892e-002, - 1.484245e-002, 1.350696e-002, 1.219204e-002, 1.093037e-002, - 9.702737e-003, 8.511953e-003, 7.416355e-003, 6.377393e-003, - 5.216926e-003, 4.069504e-003, 2.975464e-003, 1.879038e-003, - 8.439002e-004, -1.701288e-004, -1.117807e-003, -2.031869e-003, - -2.914836e-003, -3.768302e-003, -4.695668e-003, -5.712544e-003, - -6.722541e-003, -7.678584e-003, -8.616013e-003, -9.497367e-003, - -1.035963e-002, -1.113541e-002, -1.184981e-002, -1.250016e-002, - -1.315719e-002, -1.386956e-002, -1.454541e-002, -1.516843e-002, - -1.570525e-002, -1.616157e-002, -1.648192e-002, -1.668764e-002, - -1.686500e-002, -1.709050e-002, -1.733257e-002, -1.777004e-002, - -1.818691e-002, -1.855436e-002, -1.888693e-002, -1.915149e-002, - -1.939631e-002, -1.962951e-002, -1.979836e-002, -2.002083e-002, - -2.027469e-002, -2.062242e-002, -2.091931e-002, -2.118309e-002, - -2.135909e-002, -2.144381e-002, -2.137676e-002, -2.120710e-002, - -2.096172e-002, -2.059042e-002, -2.008648e-002, -1.950090e-002, - -1.885748e-002, -1.810397e-002, -1.720526e-002, -1.613868e-002, - -1.496593e-002, -1.368650e-002, -1.226422e-002, -1.072302e-002, - -9.049371e-003, -7.358039e-003, -5.663777e-003, -3.891258e-003, - -2.045170e-003, -1.636382e-004, 1.744967e-003, 3.621917e-003, - 5.496730e-003, 7.364511e-003, 9.164464e-003, 1.094357e-002, - 1.260896e-002, 1.434101e-002, 1.615333e-002, 1.793667e-002, - 1.971428e-002, 2.145622e-002, 2.312011e-002, 2.466105e-002, - 2.610530e-002, 2.745963e-002, 2.857697e-002, 2.963034e-002, - 3.069135e-002, 3.169880e-002, 3.265673e-002, 3.354319e-002, - 3.433260e-002, 3.505830e-002, 3.570706e-002, 3.633274e-002, - 3.679244e-002, 3.716464e-002, 3.750240e-002, 3.783582e-002, - 3.810767e-002, 3.836460e-002, 3.858148e-002, 3.883698e-002, - 3.915140e-002, 3.954073e-002, 3.991564e-002, 4.025981e-002, - 4.071078e-002, 4.119783e-002, 4.173348e-002, 4.219503e-002, - 4.257693e-002, 4.289900e-002, 4.313780e-002, 4.320722e-002, - 4.314518e-002, 4.285891e-002, 4.243274e-002, 4.186217e-002, - 4.113770e-002, 4.024297e-002, 3.920791e-002, 3.790835e-002, - 3.646747e-002, 3.481174e-002, 3.295448e-002, 3.072675e-002, - 2.836238e-002, 2.582710e-002, 2.311948e-002, 2.028903e-002, - 1.732425e-002, 1.428509e-002, 1.118910e-002, 7.970093e-003, - 4.727332e-003, 1.307236e-003, -2.212218e-003, -5.732595e-003, - -9.279046e-003, -1.280237e-002, -1.627107e-002, -1.975832e-002, - -2.318285e-002, -2.653184e-002, -2.981665e-002, -3.307268e-002, - -3.629147e-002, -3.938566e-002, -4.240172e-002, -4.536910e-002, - -4.822087e-002, -5.088447e-002, -5.338356e-002, -5.566535e-002, - -5.771809e-002, -5.958486e-002, -6.129184e-002, -6.274121e-002, - -6.394318e-002, -6.492695e-002, -6.563864e-002, -6.604333e-002, - -6.617563e-002, -6.595979e-002, -6.548823e-002, -6.470836e-002, - -6.388973e-002, -6.275980e-002, -6.137325e-002, -5.978953e-002, - -5.796512e-002, -5.589693e-002, -5.364867e-002, -5.125099e-002, - -4.866001e-002, -4.590269e-002, -4.311949e-002, -4.038648e-002, - -3.763903e-002, -3.497113e-002, -3.235027e-002, -2.973690e-002, - -2.717698e-002, -2.468817e-002, -2.224572e-002, -1.981741e-002, - -1.762449e-002, -1.560499e-002, -1.375343e-002, -1.206404e-002, - -1.063405e-002, -9.335874e-003, -8.187904e-003, -7.139321e-003, - -6.193252e-003, -5.378602e-003, -4.764400e-003, -4.314171e-003, - -3.941590e-003, -3.590591e-003, -3.306566e-003, -3.057758e-003, - -2.780313e-003, -2.527669e-003, -2.201192e-003, -1.823313e-003, - -1.492728e-003, -1.243533e-003, -8.872446e-004, -5.465315e-004, - -1.659146e-004, 1.964756e-004, 4.756516e-004, 7.243700e-004, - 9.169495e-004, 1.042731e-003, 1.065695e-003, 8.887363e-004, - 6.548888e-004, 4.257568e-004, 1.623994e-004, -1.058271e-004, - -2.925541e-004, -4.303827e-004, -5.030480e-004, -5.386141e-004, - -5.106781e-004, -5.335284e-004, -5.460528e-004, -4.991939e-004, - -4.136433e-004, -1.684378e-004, 2.416606e-004, 7.277907e-004, - 1.270833e-003, 1.859861e-003, 2.518680e-003, 3.130691e-003, - 3.707352e-003, 4.332805e-003, 4.984243e-003, 5.691873e-003, - 6.356036e-003, 7.090192e-003, 7.810875e-003, 8.563157e-003, - 9.300814e-003, 9.950161e-003, 1.045456e-002, 1.089457e-002, - 1.126804e-002, 1.164306e-002, 1.201557e-002, 1.237488e-002, - 1.278852e-002, 1.325236e-002, 1.365240e-002, 1.406841e-002, - 1.439940e-002, 1.478766e-002, 1.519718e-002, 1.563759e-002, - 1.612838e-002, 1.663351e-002, 1.712195e-002, 1.761671e-002, - 1.816357e-002, 1.879989e-002, 1.935316e-002, 1.996410e-002, - 2.065829e-002, 2.138201e-002, 2.227852e-002, 2.321649e-002, - 2.414859e-002, 2.519348e-002, 2.630376e-002, 2.734890e-002, - 2.831907e-002, 2.915795e-002, 3.005297e-002, 3.095296e-002, - 3.179249e-002, 3.264209e-002, 3.341960e-002, 3.412970e-002, - 3.481971e-002, 3.546491e-002, 3.601730e-002, 3.634579e-002, - 3.663454e-002, 3.689082e-002, 3.701661e-002, 3.712872e-002, - 3.719527e-002, 3.722811e-002, 3.722395e-002, 3.712791e-002, - 3.704749e-002, 3.690542e-002, 3.670423e-002, 3.651267e-002, - 3.626574e-002, 3.589362e-002, 3.539710e-002, 3.475347e-002, - 3.403829e-002, 3.317088e-002, 3.220293e-002, 3.105627e-002, - 2.979557e-002, 2.840891e-002, 2.686821e-002, 2.520048e-002, - 2.336865e-002, 2.127739e-002, 1.910732e-002, 1.683556e-002, - 1.441321e-002, 1.181497e-002, 9.122242e-003, 6.321727e-003, - 3.511831e-003, 7.085633e-004, -2.092576e-003, -4.886330e-003, - -7.721182e-003, -1.059578e-002, -1.332059e-002, -1.597201e-002, - -1.859856e-002, -2.106954e-002, -2.345178e-002, -2.570654e-002, - -2.783431e-002, -2.966961e-002, -3.126138e-002, -3.271318e-002, - -3.403561e-002, -3.517624e-002, -3.625713e-002, -3.723128e-002, - -3.809538e-002, -3.868026e-002, -3.911762e-002, -3.943440e-002, - -3.959776e-002, -3.963124e-002, -3.952376e-002, -3.919323e-002, - -3.888431e-002, -3.839034e-002, -3.771546e-002, -3.702977e-002, - -3.617299e-002, -3.527720e-002, -3.419630e-002, -3.295273e-002, - -3.164608e-002, -3.041417e-002, -2.928898e-002, -2.813279e-002, - -2.694768e-002, -2.578592e-002, -2.469363e-002, -2.373422e-002, - -2.286685e-002, -2.197650e-002, -2.099563e-002, -1.995272e-002, - -1.901370e-002, -1.824215e-002, -1.748614e-002, -1.677195e-002, - -1.614666e-002, -1.553161e-002, -1.488914e-002, -1.427217e-002, - -1.368859e-002, -1.311977e-002, -1.257429e-002, -1.219071e-002, - -1.191097e-002, -1.165035e-002, -1.138351e-002, -1.095540e-002, - -1.050061e-002, -9.961585e-003, -9.303637e-003, -8.627051e-003, - -8.002157e-003, -7.420625e-003, -6.769707e-003, -6.271510e-003, - -5.778752e-003, -5.253697e-003, -4.712777e-003, -4.107146e-003, - -3.322076e-003, -2.377213e-003, -1.484745e-003, -6.093864e-004, - 3.617376e-004, 1.448650e-003, 2.481607e-003, 3.630674e-003, - 4.841672e-003, 6.190834e-003, 7.577485e-003, 9.061530e-003, - 1.055121e-002, 1.197034e-002, 1.327674e-002, 1.459672e-002, - 1.585084e-002, 1.713619e-002, 1.831245e-002, 1.924772e-002, - 2.004078e-002, 2.076805e-002, 2.134358e-002, 2.170871e-002, - 2.185719e-002, 2.178140e-002, 2.143583e-002, 2.111330e-002, - 2.071848e-002, 2.030859e-002, 1.992772e-002, 1.964012e-002, - 1.933981e-002, 1.890874e-002, 1.837687e-002, 1.785506e-002, - 1.721102e-002, 1.651854e-002, 1.592481e-002, 1.532413e-002, - 1.463462e-002, 1.397644e-002, 1.336471e-002, 1.263563e-002, - 1.183665e-002, 1.102487e-002, 1.017544e-002, 9.274391e-003, - 8.459909e-003, 7.853330e-003, 7.147361e-003, 6.472924e-003, - 6.009735e-003, 5.650874e-003, 5.027562e-003, 4.306640e-003, - 3.533646e-003, 2.771919e-003, 2.100479e-003, 1.493026e-003, - 9.588593e-004, 4.967573e-004, 7.309668e-005, -2.205349e-004, - -5.520499e-004, -1.019814e-003, -1.243300e-003, -1.391244e-003, - -1.587863e-003, -1.735729e-003, -1.578080e-003, -1.421772e-003, - -1.270895e-003, -9.989600e-004, -6.305420e-004, -3.582024e-004, - -1.386532e-005, 4.372084e-004, 8.428798e-004, 1.318285e-003, - 1.888251e-003, 2.620867e-003, 3.426770e-003, 4.396926e-003, - 5.611456e-003, 6.384280e-003, 7.087899e-003, 7.693087e-003, - 8.146602e-003, 8.336808e-003, 8.491206e-003, 8.676731e-003, - 8.632668e-003, 8.373485e-003, 8.133570e-003, 7.358811e-003, - 6.365847e-003, 5.429238e-003, 4.524259e-003, 3.415815e-003, - 2.338171e-003, 1.209745e-003, -2.349047e-004, -1.761399e-003, - -3.163080e-003, -4.641567e-003, -6.310759e-003, -7.966114e-003, - -9.573688e-003, -1.128391e-002, -1.278044e-002, -1.420052e-002, - -1.551112e-002, -1.661711e-002, -1.768753e-002, -1.875845e-002, - -1.976623e-002, -2.051854e-002, -2.119314e-002, -2.195653e-002, - -2.263158e-002, -2.325306e-002, -2.370221e-002, -2.386535e-002, - -2.376975e-002, -2.353770e-002, -2.360874e-002, -2.350956e-002, - -2.318895e-002, -2.286376e-002, -2.244608e-002, -2.191418e-002, - -2.124015e-002, -2.053250e-002, -1.962835e-002, -1.863391e-002, - -1.773704e-002, -1.697285e-002, -1.613033e-002, -1.553082e-002, - -1.490081e-002, -1.424619e-002, -1.352697e-002, -1.279004e-002, - -1.169463e-002, -1.066141e-002, -9.938796e-003, -9.426431e-003, - -8.769972e-003, -8.022488e-003, -7.163118e-003, -6.230494e-003, - -5.354855e-003, -4.626499e-003, -3.800046e-003, -2.821283e-003, - -2.077848e-003, -1.720912e-003, -1.211957e-003, -5.139867e-004, - 1.672896e-004, 7.380747e-004, 1.231918e-003, 1.836812e-003, - 2.588514e-003, 3.424586e-003, 4.310351e-003, 4.492074e-003, - 4.857978e-003, 5.186351e-003, 5.287520e-003, 5.017601e-003, - 4.899668e-003, 5.075812e-003, 5.252189e-003, 5.406088e-003, - 5.649376e-003, 5.074588e-003, 4.358268e-003, 3.678599e-003, - 3.274783e-003, 3.139339e-003, 3.153597e-003, 3.426805e-003, - 3.607742e-003, 3.905392e-003, 4.436611e-003, 4.566128e-003, - 4.283526e-003, 4.044839e-003, 3.884442e-003, 3.950518e-003, - 4.381439e-003, 5.230085e-003, 6.100573e-003, 6.921528e-003, - 7.910408e-003, 8.788116e-003, 9.206493e-003, 9.604025e-003, - 1.032630e-002, 1.103261e-002, 1.194813e-002, 1.310615e-002, - 1.451625e-002, 1.596162e-002, 1.770441e-002, 1.949093e-002, - 2.061415e-002, 2.176185e-002, 2.324692e-002, 2.509668e-002, - 2.702619e-002, 2.925113e-002, 3.164147e-002, 3.382193e-002, - 3.611470e-002, 3.868504e-002, 4.032994e-002, 4.191587e-002, - 4.365399e-002, 4.505602e-002, 4.602139e-002, 4.703545e-002, - 4.828186e-002, 4.950167e-002, 5.056748e-002, 5.159618e-002, - 5.140171e-002, 5.091424e-002, 5.052093e-002, 4.952834e-002, - 4.812491e-002, 4.659471e-002, 4.530327e-002, 4.399353e-002, - 4.279523e-002, 4.131729e-002, 3.915855e-002, 3.672639e-002, - 3.422345e-002, 3.162054e-002, 2.886251e-002, 2.609826e-002, - 2.346507e-002, 2.101739e-002, 1.885243e-002, 1.670885e-002, - 1.428507e-002, 1.101200e-002, 7.376377e-003, 3.821890e-003, - 1.631110e-004, -3.441841e-003, -6.837429e-003, -9.625744e-003, - -1.232796e-002, -1.509366e-002, -1.773156e-002, -2.152922e-002, - -2.487684e-002, -2.821601e-002, -3.144637e-002, -3.439800e-002, - -3.656986e-002, -3.871948e-002, -4.066808e-002, -4.237258e-002, - -4.364921e-002, -4.626492e-002, -4.924201e-002, -5.204788e-002, - -5.437201e-002, -5.628460e-002, -5.767088e-002, -5.875988e-002, - -5.963978e-002, -5.998494e-002, -5.999040e-002, -6.086009e-002, - -6.220630e-002, -6.352688e-002, -6.450047e-002, -6.509565e-002, - -6.536775e-002, -6.543613e-002, -6.509523e-002, -6.454990e-002, - -6.332388e-002, -6.251115e-002, -6.259580e-002, -6.253274e-002, - -6.204282e-002, -6.127282e-002, -6.019903e-002, -5.879818e-002, - -5.693768e-002, -5.482688e-002, -5.256728e-002, -5.046707e-002, - -5.017654e-002, -4.930373e-002, -4.774042e-002, -4.563187e-002, - -4.323567e-002, -4.064778e-002, -3.763459e-002, -3.419645e-002, - -3.047666e-002, -2.653407e-002, -2.417219e-002, -2.160950e-002, - -1.840240e-002, -1.456073e-002, -1.049088e-002, -6.001775e-003, - -1.034906e-003, 4.698963e-003, 1.034955e-002, 1.615834e-002, - 2.027239e-002, 2.352100e-002, 2.685419e-002, 3.013953e-002, - 3.336232e-002, 3.692365e-002, 4.060177e-002, 4.403997e-002, - 4.779257e-002, 5.144838e-002, 5.346909e-002, 5.418464e-002, - 5.525373e-002, 5.576491e-002, 5.655827e-002, 5.773097e-002, - 5.916615e-002, 6.029972e-002, 6.199814e-002, 6.407098e-002, - 6.529458e-002, 6.488477e-002, 6.441998e-002, 6.396437e-002, - 6.344763e-002, 6.343303e-002, 6.340507e-002, 6.365962e-002, - 6.389971e-002, 6.415027e-002, 6.437890e-002, 6.137377e-002, - 5.916727e-002, 5.650317e-002, 5.378682e-002, 5.175786e-002, - 4.945596e-002, 4.711811e-002, 4.503453e-002, 4.304100e-002, - 4.128570e-002, 3.646880e-002, 3.115310e-002, 2.591698e-002, - 2.061017e-002, 1.564980e-002, 1.097586e-002, 6.396060e-003, - 2.617911e-003, -1.018952e-003, -3.934453e-003, -8.660800e-003, - -1.466175e-002, -2.018296e-002, -2.571675e-002, -3.061892e-002, - -3.445474e-002, -3.722479e-002, -3.890797e-002, -4.001355e-002, - -4.085412e-002, -4.220000e-002, -4.563460e-002, -4.938268e-002, - -5.240873e-002, -5.457861e-002, -5.594393e-002, -5.665137e-002, - -5.625688e-002, -5.441459e-002, -5.150242e-002, -4.847541e-002, - -4.897426e-002, -4.801716e-002, -4.628311e-002, -4.362039e-002, - -4.056376e-002, -3.591383e-002, -3.069531e-002, -2.525436e-002, - -1.867304e-002, -1.135140e-002, -7.748249e-003, -3.720367e-003, - 3.022880e-004, 5.286934e-003, 1.047223e-002, 1.602796e-002, - 2.094498e-002, 2.468319e-002, 2.842591e-002, 3.315739e-002, - 3.537407e-002, 3.557003e-002, 3.559375e-002, 3.626486e-002, - 3.562214e-002, 3.521006e-002, 3.426653e-002, 3.224883e-002, - 2.911543e-002, 2.454977e-002, 1.751733e-002, 9.782654e-003, - 2.955298e-003, -2.676502e-003, -5.892644e-003, -9.977203e-003, - -1.416451e-002, -1.698550e-002, -2.214304e-002, -2.708398e-002, - -3.271888e-002, -3.896387e-002, -4.202464e-002, -4.200670e-002, - -4.084843e-002, -3.737876e-002, -3.303173e-002, -2.581727e-002, - -1.959411e-002, -1.070993e-002, -1.840049e-003, 3.178440e-003, - 8.753807e-003, 1.388705e-002, 1.870795e-002, 2.087077e-002, - 2.178182e-002, 2.371899e-002, 2.941934e-002, 3.357092e-002, - 4.397517e-002, 4.914990e-002, 4.824721e-002, 4.816658e-002, - 4.365053e-002, 3.172038e-002, 1.818547e-002, -9.579098e-003, - -3.836193e-002, -6.022345e-002, -6.574692e-002, -4.170235e-002, - -4.118005e-002, -4.242912e-002, -3.494630e-002, -2.406701e-002, - -1.465459e-002, 1.795495e-003, 3.156696e-002, 5.742234e-002, - 8.034192e-002, -4.801566e-002, 2.853384e-002, 2.415555e-002, - 1.567828e-002, -7.567735e-004, -3.003692e-002, 7.782000e-002, - -2.678888e-002, -7.699702e-002, -5.553528e-002, 0.000000e+000 - }, - { - 0.000000e+000, 3.008405e-002, 5.354544e-002, 7.067570e-002, - 8.279900e-002, 9.123324e-002, 9.610622e-002, 9.700966e-002, - 9.608442e-002, 9.373228e-002, 8.968379e-002, 8.610728e-002, - 8.239419e-002, 7.799348e-002, 7.347316e-002, 6.856660e-002, - 6.320199e-002, 5.769970e-002, 5.210802e-002, 4.626127e-002, - 3.998981e-002, 3.439194e-002, 2.938732e-002, 2.426256e-002, - 1.896747e-002, 1.364328e-002, 8.309543e-003, 2.834052e-003, - -2.504120e-003, -7.962219e-003, -1.364893e-002, -1.910270e-002, - -2.362982e-002, -2.791792e-002, -3.199797e-002, -3.590144e-002, - -3.954212e-002, -4.285837e-002, -4.597219e-002, -4.882407e-002, - -5.138121e-002, -5.362549e-002, -5.496518e-002, -5.593701e-002, - -5.677308e-002, -5.748681e-002, -5.827729e-002, -5.930766e-002, - -6.011182e-002, -6.016183e-002, -5.985998e-002, -5.946679e-002, - -5.852603e-002, -5.745804e-002, -5.619291e-002, -5.483750e-002, - -5.321896e-002, -5.154575e-002, -4.963832e-002, -4.754704e-002, - -4.538433e-002, -4.303045e-002, -4.050474e-002, -3.790229e-002, - -3.523516e-002, -3.254073e-002, -2.980780e-002, -2.717292e-002, - -2.464618e-002, -2.209672e-002, -1.987734e-002, -1.770826e-002, - -1.580308e-002, -1.420071e-002, -1.287072e-002, -1.184785e-002, - -1.121186e-002, -1.079929e-002, -1.054156e-002, -1.041532e-002, - -1.002742e-002, -9.359219e-003, -8.610346e-003, -7.789516e-003, - -6.695096e-003, -5.453331e-003, -4.040506e-003, -2.436448e-003, - -5.938020e-004, 1.317206e-003, 3.402563e-003, 5.521493e-003, - 7.855443e-003, 9.846045e-003, 1.205460e-002, 1.413745e-002, - 1.629454e-002, 1.841037e-002, 2.053015e-002, 2.269331e-002, - 2.479379e-002, 2.682107e-002, 2.891425e-002, 3.070595e-002, - 3.237254e-002, 3.387378e-002, 3.523501e-002, 3.640997e-002, - 3.733692e-002, 3.792465e-002, 3.828606e-002, 3.832309e-002, - 3.813335e-002, 3.759891e-002, 3.678423e-002, 3.585357e-002, - 3.472445e-002, 3.344240e-002, 3.197320e-002, 3.029982e-002, - 2.851151e-002, 2.668268e-002, 2.492155e-002, 2.335095e-002, - 2.191184e-002, 2.087757e-002, 2.017537e-002, 1.971369e-002, - 1.938398e-002, 1.909021e-002, 1.891038e-002, 1.885656e-002, - 1.883243e-002, 1.889309e-002, 1.860773e-002, 1.819826e-002, - 1.770701e-002, 1.715965e-002, 1.646123e-002, 1.566800e-002, - 1.478379e-002, 1.381292e-002, 1.274049e-002, 1.163604e-002, - 1.016170e-002, 8.535842e-003, 7.029566e-003, 5.643495e-003, - 4.405247e-003, 3.376433e-003, 2.481592e-003, 1.698639e-003, - 1.063470e-003, 6.251477e-004, 1.678162e-004, -3.531079e-004, - -7.029354e-004, -7.829977e-004, -6.556778e-004, -4.086731e-004, - 4.495372e-005, 5.540523e-004, 1.066889e-003, 1.581016e-003, - 2.057286e-003, 2.265356e-003, 2.438156e-003, 2.492686e-003, - 2.310561e-003, 1.829938e-003, 1.113294e-003, 3.386383e-004, - -4.420929e-004, -1.265555e-003, -2.180625e-003, -3.340346e-003, - -4.672067e-003, -6.190333e-003, -7.859201e-003, -9.593834e-003, - -1.132352e-002, -1.296282e-002, -1.427747e-002, -1.519722e-002, - -1.569738e-002, -1.609516e-002, -1.624571e-002, -1.615155e-002, - -1.591443e-002, -1.548547e-002, -1.491936e-002, -1.420084e-002, - -1.334600e-002, -1.239079e-002, -1.135042e-002, -1.042085e-002, - -9.499740e-003, -8.555257e-003, -7.552962e-003, -6.509079e-003, - -5.408240e-003, -4.263729e-003, -3.114538e-003, -1.943377e-003, - -8.144698e-004, 1.911612e-004, 8.286117e-004, 1.271274e-003, - 1.401623e-003, 1.403688e-003, 1.300111e-003, 1.215844e-003, - 1.243042e-003, 1.354118e-003, 1.536264e-003, 1.625615e-003, - 1.614898e-003, 1.571843e-003, 1.433526e-003, 1.207264e-003, - 9.579352e-004, 8.281522e-004, 9.664205e-004, 1.477677e-003, - 2.251320e-003, 3.223537e-003, 4.235228e-003, 5.271962e-003, - 6.332567e-003, 7.376271e-003, 8.423107e-003, 9.509160e-003, - 1.061887e-002, 1.174063e-002, 1.289915e-002, 1.404457e-002, - 1.512919e-002, 1.612496e-002, 1.711214e-002, 1.803889e-002, - 1.890345e-002, 1.967306e-002, 2.035113e-002, 2.088208e-002, - 2.124164e-002, 2.139319e-002, 2.127534e-002, 2.075450e-002, - 1.979327e-002, 1.852713e-002, 1.716478e-002, 1.581413e-002, - 1.456343e-002, 1.342340e-002, 1.238704e-002, 1.148153e-002, - 1.068397e-002, 1.000988e-002, 9.376271e-003, 8.761889e-003, - 8.220361e-003, 7.777956e-003, 7.526178e-003, 7.495194e-003, - 7.569309e-003, 7.682555e-003, 7.811962e-003, 7.873380e-003, - 7.897152e-003, 7.831993e-003, 7.645498e-003, 7.319078e-003, - 6.859914e-003, 6.198263e-003, 5.296796e-003, 4.090739e-003, - 2.579168e-003, 7.711977e-004, -1.290253e-003, -3.522542e-003, - -5.906549e-003, -8.412200e-003, -1.110266e-002, -1.396291e-002, - -1.702028e-002, -2.023735e-002, -2.358479e-002, -2.700684e-002, - -3.043529e-002, -3.366038e-002, -3.646848e-002, -3.871031e-002, - -4.051018e-002, -4.186214e-002, -4.291580e-002, -4.365371e-002, - -4.406761e-002, -4.423191e-002, -4.413789e-002, -4.378445e-002, - -4.321969e-002, -4.247935e-002, -4.158693e-002, -4.049991e-002, - -3.923657e-002, -3.784975e-002, -3.637195e-002, -3.478674e-002, - -3.316805e-002, -3.145803e-002, -2.963673e-002, -2.771265e-002, - -2.574887e-002, -2.382527e-002, -2.204817e-002, -2.046586e-002, - -1.908502e-002, -1.783134e-002, -1.673068e-002, -1.562509e-002, - -1.448852e-002, -1.323276e-002, -1.189933e-002, -1.052606e-002, - -9.182220e-003, -7.918119e-003, -6.731918e-003, -5.578053e-003, - -4.430532e-003, -3.212122e-003, -1.717269e-003, 1.040194e-006, - 1.905626e-003, 3.941625e-003, 6.033642e-003, 8.117241e-003, - 1.015749e-002, 1.208732e-002, 1.391085e-002, 1.558632e-002, - 1.708237e-002, 1.837508e-002, 1.943789e-002, 2.025372e-002, - 2.082030e-002, 2.113406e-002, 2.105474e-002, 2.062285e-002, - 1.983453e-002, 1.860578e-002, 1.694913e-002, 1.509821e-002, - 1.325031e-002, 1.157746e-002, 1.028237e-002, 9.319776e-003, - 8.641473e-003, 8.296810e-003, 8.303841e-003, 8.463147e-003, - 8.819560e-003, 9.344127e-003, 9.999804e-003, 1.081452e-002, - 1.170784e-002, 1.267403e-002, 1.366467e-002, 1.464904e-002, - 1.556256e-002, 1.636577e-002, 1.704302e-002, 1.762171e-002, - 1.810833e-002, 1.843010e-002, 1.857367e-002, 1.855183e-002, - 1.840953e-002, 1.816409e-002, 1.774000e-002, 1.716043e-002, - 1.644784e-002, 1.569096e-002, 1.490404e-002, 1.402384e-002, - 1.306849e-002, 1.206425e-002, 1.104704e-002, 9.997928e-003, - 8.942546e-003, 7.951334e-003, 7.073824e-003, 6.383295e-003, - 5.917138e-003, 5.557447e-003, 5.233586e-003, 4.999578e-003, - 4.767273e-003, 4.567348e-003, 4.462775e-003, 4.456770e-003, - 4.468095e-003, 4.509381e-003, 4.540374e-003, 4.580288e-003, - 4.654521e-003, 4.753504e-003, 4.889820e-003, 5.023418e-003, - 5.073131e-003, 4.985945e-003, 4.745505e-003, 4.396557e-003, - 3.970935e-003, 3.458719e-003, 2.912104e-003, 2.288086e-003, - 1.648188e-003, 9.264925e-004, 2.283719e-004, -4.196496e-004, - -1.030445e-003, -1.605574e-003, -2.101392e-003, -2.414959e-003, - -2.602164e-003, -2.778986e-003, -2.768669e-003, -2.612937e-003, - -2.352614e-003, -1.816637e-003, -1.084505e-003, -1.919676e-004, - 8.196221e-004, 1.924901e-003, 3.085204e-003, 4.245679e-003, - 5.449790e-003, 6.576491e-003, 7.522378e-003, 8.345502e-003, - 8.906589e-003, 9.030391e-003, 8.773458e-003, 8.210692e-003, - 7.473661e-003, 6.637226e-003, 5.782729e-003, 4.973796e-003, - 3.977038e-003, 2.744982e-003, 1.202035e-003, -7.269764e-004, - -2.946195e-003, -5.283051e-003, -7.676133e-003, -9.926082e-003, - -1.176729e-002, -1.311717e-002, -1.418911e-002, -1.513843e-002, - -1.590096e-002, -1.664537e-002, -1.737418e-002, -1.795038e-002, - -1.842037e-002, -1.893250e-002, -1.937972e-002, -1.972279e-002, - -2.001546e-002, -2.032042e-002, -2.058377e-002, -2.082667e-002, - -2.103949e-002, -2.122822e-002, -2.126898e-002, -2.116514e-002, - -2.096586e-002, -2.062184e-002, -2.011895e-002, -1.957030e-002, - -1.894582e-002, -1.824327e-002, -1.752905e-002, -1.676768e-002, - -1.593264e-002, -1.504934e-002, -1.405314e-002, -1.307000e-002, - -1.214887e-002, -1.131421e-002, -1.059897e-002, -9.962355e-003, - -9.323827e-003, -8.681866e-003, -8.115931e-003, -7.588573e-003, - -7.129258e-003, -6.775399e-003, -6.454371e-003, -6.130188e-003, - -5.769189e-003, -5.259930e-003, -4.548030e-003, -3.720657e-003, - -2.794778e-003, -1.782127e-003, -6.293710e-004, 7.967352e-004, - 2.437844e-003, 4.113516e-003, 5.825785e-003, 7.540172e-003, - 9.174169e-003, 1.073491e-002, 1.229154e-002, 1.370033e-002, - 1.489371e-002, 1.583074e-002, 1.656199e-002, 1.701140e-002, - 1.702044e-002, 1.647421e-002, 1.551873e-002, 1.441185e-002, - 1.321235e-002, 1.202058e-002, 1.095902e-002, 9.952864e-003, - 9.056477e-003, 8.080416e-003, 7.151087e-003, 6.247071e-003, - 5.399146e-003, 4.530389e-003, 3.786600e-003, 3.383828e-003, - 3.094072e-003, 2.956218e-003, 3.046859e-003, 3.197785e-003, - 3.375202e-003, 3.533441e-003, 3.794123e-003, 3.993645e-003, - 4.097964e-003, 4.184038e-003, 4.228373e-003, 4.122408e-003, - 3.815368e-003, 3.244704e-003, 2.388669e-003, 1.413433e-003, - 3.385182e-004, -7.860371e-004, -1.938996e-003, -3.015892e-003, - -4.164362e-003, -5.287677e-003, -6.259267e-003, -7.035583e-003, - -7.811050e-003, -8.531654e-003, -9.053277e-003, -9.370129e-003, - -9.253522e-003, -8.816276e-003, -8.016489e-003, -6.975866e-003, - -5.853146e-003, -4.714792e-003, -3.578552e-003, -2.340743e-003, - -9.899246e-004, 3.704971e-004, 1.782649e-003, 3.146510e-003, - 4.519147e-003, 5.929608e-003, 7.363935e-003, 8.647948e-003, - 9.826751e-003, 1.090127e-002, 1.186721e-002, 1.277458e-002, - 1.352247e-002, 1.404602e-002, 1.432284e-002, 1.425350e-002, - 1.380108e-002, 1.302885e-002, 1.197329e-002, 1.074339e-002, - 9.472158e-003, 8.242226e-003, 7.161245e-003, 6.380906e-003, - 5.865844e-003, 5.447363e-003, 5.061718e-003, 4.720278e-003, - 4.340846e-003, 4.008292e-003, 3.898278e-003, 4.098702e-003, - 4.592019e-003, 5.344940e-003, 6.177450e-003, 7.088400e-003, - 8.117630e-003, 9.267160e-003, 1.031940e-002, 1.135745e-002, - 1.240766e-002, 1.339735e-002, 1.426065e-002, 1.469619e-002, - 1.488603e-002, 1.483787e-002, 1.465042e-002, 1.427137e-002, - 1.361141e-002, 1.258008e-002, 1.118583e-002, 9.587671e-003, - 7.565902e-003, 5.318216e-003, 3.037401e-003, 8.539639e-004, - -1.155478e-003, -2.857207e-003, -4.395995e-003, -5.713256e-003, - -6.710165e-003, -7.489356e-003, -8.152912e-003, -8.797044e-003, - -9.263057e-003, -9.596502e-003, -9.924186e-003, -1.009800e-002, - -1.018282e-002, -1.027376e-002, -1.045175e-002, -1.057013e-002, - -1.064196e-002, -1.080933e-002, -1.103449e-002, -1.131040e-002, - -1.152079e-002, -1.167464e-002, -1.191086e-002, -1.211246e-002, - -1.224332e-002, -1.246513e-002, -1.277643e-002, -1.331544e-002, - -1.405308e-002, -1.493622e-002, -1.579404e-002, -1.654438e-002, - -1.729663e-002, -1.785920e-002, -1.832766e-002, -1.883196e-002, - -1.949268e-002, -2.022842e-002, -2.114275e-002, -2.205966e-002, - -2.286398e-002, -2.336680e-002, -2.356865e-002, -2.340582e-002, - -2.286948e-002, -2.188795e-002, -2.059611e-002, -1.916389e-002, - -1.773876e-002, -1.598523e-002, -1.430926e-002, -1.236706e-002, - -1.029174e-002, -8.200068e-003, -5.982050e-003, -3.656390e-003, - -1.407786e-003, 9.839511e-004, 3.213069e-003, 5.335747e-003, - 7.429873e-003, 9.581503e-003, 1.174499e-002, 1.376435e-002, - 1.559577e-002, 1.729612e-002, 1.897359e-002, 2.020417e-002, - 2.110752e-002, 2.154321e-002, 2.168114e-002, 2.169016e-002, - 2.161999e-002, 2.163316e-002, 2.180160e-002, 2.170052e-002, - 2.171544e-002, 2.168486e-002, 2.144117e-002, 2.098717e-002, - 2.039692e-002, 1.998528e-002, 1.988235e-002, 2.005679e-002, - 2.044900e-002, 2.079830e-002, 2.128448e-002, 2.204007e-002, - 2.261329e-002, 2.268444e-002, 2.267682e-002, 2.263348e-002, - 2.252209e-002, 2.213057e-002, 2.140054e-002, 2.012606e-002, - 1.859418e-002, 1.680597e-002, 1.489280e-002, 1.266524e-002, - 1.025876e-002, 7.573454e-003, 4.807049e-003, 1.651448e-003, - -1.526355e-003, -4.526581e-003, -7.507863e-003, -1.007024e-002, - -1.232168e-002, -1.479224e-002, -1.723593e-002, -1.911296e-002, - -2.091134e-002, -2.230611e-002, -2.349948e-002, -2.449371e-002, - -2.495606e-002, -2.519168e-002, -2.531697e-002, -2.550824e-002, - -2.610805e-002, -2.663825e-002, -2.695437e-002, -2.683276e-002, - -2.663800e-002, -2.686426e-002, -2.730249e-002, -2.792274e-002, - -2.855388e-002, -2.888278e-002, -2.949299e-002, -2.980621e-002, - -2.984723e-002, -2.931396e-002, -2.837362e-002, -2.733471e-002, - -2.615015e-002, -2.465895e-002, -2.307224e-002, -2.138107e-002, - -1.976163e-002, -1.797382e-002, -1.582725e-002, -1.348870e-002, - -1.068983e-002, -7.705052e-003, -4.980787e-003, -2.318467e-003, - 4.168727e-004, 3.659154e-003, 6.571984e-003, 8.942336e-003, - 1.120934e-002, 1.313263e-002, 1.489564e-002, 1.612421e-002, - 1.700769e-002, 1.757036e-002, 1.801883e-002, 1.826544e-002, - 1.821835e-002, 1.739990e-002, 1.629204e-002, 1.537302e-002, - 1.467968e-002, 1.446658e-002, 1.466377e-002, 1.511077e-002, - 1.585060e-002, 1.671712e-002, 1.803559e-002, 1.865758e-002, - 1.921346e-002, 1.998747e-002, 2.096945e-002, 2.210208e-002, - 2.332777e-002, 2.468760e-002, 2.624481e-002, 2.794235e-002, - 2.962765e-002, 3.061398e-002, 3.141400e-002, 3.234296e-002, - 3.329740e-002, 3.397380e-002, 3.422033e-002, 3.456078e-002, - 3.481717e-002, 3.481675e-002, 3.464256e-002, 3.427367e-002, - 3.335879e-002, 3.209125e-002, 3.036126e-002, 2.849070e-002, - 2.618503e-002, 2.375118e-002, 2.171200e-002, 1.983796e-002, - 1.810896e-002, 1.595337e-002, 1.322287e-002, 1.060974e-002, - 8.008892e-003, 5.488808e-003, 3.177876e-003, 6.892999e-004, - -1.861419e-003, -3.905315e-003, -5.615933e-003, -6.477035e-003, - -7.722098e-003, -8.802171e-003, -9.997406e-003, -1.143432e-002, - -1.252381e-002, -1.305749e-002, -1.373092e-002, -1.430825e-002, - -1.491452e-002, -1.587437e-002, -1.799698e-002, -2.028447e-002, - -2.279145e-002, -2.518664e-002, -2.741485e-002, -2.987952e-002, - -3.337085e-002, -3.710501e-002, -3.897720e-002, -4.081985e-002, - -4.347000e-002, -4.649629e-002, -4.841765e-002, -4.979700e-002, - -5.048892e-002, -5.040954e-002, -4.970096e-002, -4.863381e-002, - -4.768624e-002, -4.706671e-002, -4.568281e-002, -4.395691e-002, - -4.190381e-002, -3.990355e-002, -3.800627e-002, -3.556637e-002, - -3.288106e-002, -3.003691e-002, -2.740138e-002, -2.464016e-002, - -2.240822e-002, -2.184800e-002, -2.026057e-002, -1.822488e-002, - -1.628862e-002, -1.400031e-002, -1.100350e-002, -8.228068e-003, - -4.996591e-003, -1.386659e-003, 1.951940e-003, 3.100905e-003, - 3.915576e-003, 5.117104e-003, 6.359899e-003, 7.180362e-003, - 8.266997e-003, 9.724186e-003, 1.130169e-002, 1.413361e-002, - 1.698530e-002, 1.885142e-002, 2.041549e-002, 2.187720e-002, - 2.417740e-002, 2.637841e-002, 2.925352e-002, 3.216048e-002, - 3.451182e-002, 3.800080e-002, 4.140761e-002, 4.350901e-002, - 4.445830e-002, 4.614707e-002, 4.797196e-002, 4.886392e-002, - 4.951336e-002, 4.987282e-002, 5.050930e-002, 5.088068e-002, - 5.119124e-002, 5.022661e-002, 4.731176e-002, 4.628844e-002, - 4.553865e-002, 4.552574e-002, 4.597417e-002, 4.649745e-002, - 4.633937e-002, 4.586071e-002, 4.531632e-002, 4.363126e-002, - 4.048021e-002, 3.668521e-002, 3.171079e-002, 2.713207e-002, - 2.318774e-002, 1.711720e-002, 1.014823e-002, 3.297289e-003, - -5.042592e-003, -1.366325e-002, -2.347715e-002, -3.374385e-002, - -4.334974e-002, -5.334651e-002, -5.932877e-002, -6.526050e-002, - -7.100234e-002, -7.405059e-002, -7.714797e-002, -7.943400e-002, - -8.326746e-002, -8.518238e-002, -8.542316e-002, -8.574789e-002, - -8.321488e-002, -8.024120e-002, -7.727505e-002, -7.213023e-002, - -6.479122e-002, -5.360654e-002, -4.304064e-002, -3.615465e-002, - -2.824676e-002, -1.914719e-002, -1.053044e-002, 3.138012e-004, - 1.229125e-002, 2.536504e-002, 4.203642e-002, 5.759952e-002, - 7.290663e-002, 7.889171e-002, 8.153252e-002, 8.120813e-002, - 7.959665e-002, 7.350519e-002, 6.594953e-002, 5.632963e-002, - 5.075325e-002, 4.602097e-002, 4.254265e-002, 3.325535e-002, - 2.223358e-002, 1.108514e-002, 2.195591e-003, -7.477773e-003, - -1.870994e-002, -2.681347e-002, -3.679152e-002, -4.646792e-002, - -7.410067e-002, -9.741271e-002, -1.163309e-001, -1.310746e-001, - -1.267222e-001, -9.415816e-002, -4.671337e-002, 2.895577e-002, - 1.067116e-001, 1.685572e-001, 1.989994e-001, 7.465895e-002, - 8.237451e-002, 9.721190e-002, 9.244105e-002, 6.791985e-002, - 3.875252e-002, -3.855179e-003, -7.265016e-002, -1.376942e-001, - -1.874766e-001, 9.983949e-002, -1.661128e-001, -1.045779e-001, - -5.498856e-003, 5.329486e-002, 6.718175e-002, 4.625283e-003, - 7.501236e-002, 4.167842e-002, 1.054109e-002, 0.000000e+000 - } -}; + {0.000000e+000, 1.477256e-003, 2.737898e-003, 3.858105e-003, 4.877501e-003, 5.819777e-003, 6.700519e-003, 7.532015e-003, 8.323902e-003, + 9.085110e-003, 9.816742e-003, 1.051719e-002, 1.119348e-002, 1.184927e-002, 1.248706e-002, 1.310836e-002, 1.371501e-002, 1.430845e-002, + 1.488919e-002, 1.545915e-002, 1.601947e-002, 1.656868e-002, 1.710786e-002, 1.763954e-002, 1.816409e-002, 1.868202e-002, 1.919384e-002, + 1.970013e-002, 2.020145e-002, 2.069803e-002, 2.119012e-002, 2.167658e-002, 2.215623e-002, 2.263206e-002, 2.310436e-002, 2.357320e-002, + 2.403910e-002, 2.450196e-002, 2.496192e-002, 2.541903e-002, 2.587329e-002, 2.632477e-002, 2.677024e-002, 2.721375e-002, 2.765540e-002, + 2.809528e-002, 2.853420e-002, 2.897388e-002, 2.941151e-002, 2.984418e-002, 3.027385e-002, 3.070138e-002, 3.112383e-002, 3.154393e-002, + 3.196235e-002, 3.237907e-002, 3.279429e-002, 3.320812e-002, 3.362059e-002, 3.403191e-002, 3.444217e-002, 3.485135e-002, 3.525754e-002, + 3.566134e-002, 3.606424e-002, 3.646630e-002, 3.686738e-002, 3.726786e-002, 3.766775e-002, 3.806709e-002, 3.846602e-002, 3.886461e-002, + 3.926180e-002, 3.965717e-002, 4.005235e-002, 4.044736e-002, 4.084235e-002, 4.123742e-002, 4.163233e-002, 4.202693e-002, 4.242082e-002, + 4.281423e-002, 4.320657e-002, 4.359637e-002, 4.398566e-002, 4.437448e-002, 4.476288e-002, 4.515076e-002, 4.553832e-002, 4.592557e-002, + 4.631260e-002, 4.669941e-002, 4.708620e-002, 4.747111e-002, 4.785581e-002, 4.824055e-002, 4.862516e-002, 4.900963e-002, 4.939392e-002, + 4.977822e-002, 5.016234e-002, 5.054640e-002, 5.093011e-002, 5.131254e-002, 5.169442e-002, 5.207628e-002, 5.245792e-002, 5.283941e-002, + 5.322091e-002, 5.360254e-002, 5.398451e-002, 5.436656e-002, 5.474869e-002, 5.513042e-002, 5.551149e-002, 5.589278e-002, 5.627396e-002, + 5.665528e-002, 5.703689e-002, 5.741860e-002, 5.780036e-002, 5.818207e-002, 5.856357e-002, 5.894459e-002, 5.932454e-002, 5.970394e-002, + 6.008290e-002, 6.046159e-002, 6.084004e-002, 6.121843e-002, 6.159673e-002, 6.197504e-002, 6.235353e-002, 6.273225e-002, 6.311054e-002, + 6.348897e-002, 6.386763e-002, 6.424637e-002, 6.462534e-002, 6.500475e-002, 6.538453e-002, 6.576474e-002, 6.614533e-002, 6.652623e-002, + 6.690713e-002, 6.728824e-002, 6.766955e-002, 6.805103e-002, 6.843249e-002, 6.881406e-002, 6.919566e-002, 6.957729e-002, 6.995908e-002, + 7.034095e-002, 7.072298e-002, 7.110500e-002, 7.148700e-002, 7.186898e-002, 7.225112e-002, 7.263336e-002, 7.301572e-002, 7.339807e-002, + 7.378068e-002, 7.416369e-002, 7.454717e-002, 7.493111e-002, 7.531555e-002, 7.570061e-002, 7.608625e-002, 7.647253e-002, 7.685955e-002, + 7.724725e-002, 7.763534e-002, 7.802366e-002, 7.841244e-002, 7.880214e-002, 7.919242e-002, 7.958317e-002, 7.997455e-002, 8.036661e-002, + 8.075916e-002, 8.115193e-002, 8.154477e-002, 8.193767e-002, 8.233068e-002, 8.272424e-002, 8.311811e-002, 8.351236e-002, 8.390691e-002, + 8.430197e-002, 8.469743e-002, 8.509340e-002, 8.548978e-002, 8.588655e-002, 8.628385e-002, 8.668222e-002, 8.708152e-002, 8.748141e-002, + 8.788185e-002, 8.828293e-002, 8.868463e-002, 8.908685e-002, 8.948966e-002, 8.989305e-002, 9.029707e-002, 9.070218e-002, 9.110851e-002, + 9.151570e-002, 9.192383e-002, 9.233269e-002, 9.274223e-002, 9.315233e-002, 9.356289e-002, 9.397371e-002, 9.438483e-002, 9.479646e-002, + 9.520934e-002, 9.562284e-002, 9.603692e-002, 9.645145e-002, 9.686642e-002, 9.728162e-002, 9.769687e-002, 9.811201e-002, 9.852718e-002, + 9.894242e-002, 9.935901e-002, 9.977592e-002, 1.001931e-001, 1.006107e-001, 1.010286e-001, 1.014468e-001, 1.018654e-001, 1.022844e-001, + 1.027037e-001, 1.031234e-001, 1.035442e-001, 1.039658e-001, 1.043878e-001, 1.048103e-001, 1.052333e-001, 1.056568e-001, 1.060809e-001, + 1.065057e-001, 1.069312e-001, 1.073576e-001, 1.077852e-001, 1.082145e-001, 1.086448e-001, 1.090759e-001, 1.095079e-001, 1.099403e-001, + 1.103731e-001, 1.108063e-001, 1.112397e-001, 1.116734e-001, 1.121078e-001, 1.125435e-001, 1.129796e-001, 1.134162e-001, 1.138531e-001, + 1.142904e-001, 1.147279e-001, 1.151657e-001, 1.156038e-001, 1.160423e-001, 1.164814e-001, 1.169221e-001, 1.173633e-001, 1.178051e-001, + 1.182474e-001, 1.186904e-001, 1.191342e-001, 1.195789e-001, 1.200245e-001, 1.204710e-001, 1.209185e-001, 1.213680e-001, 1.218186e-001, + 1.222701e-001, 1.227224e-001, 1.231757e-001, 1.236300e-001, 1.240850e-001, 1.245409e-001, 1.249978e-001, 1.254555e-001, 1.259149e-001, + 1.263757e-001, 1.268369e-001, 1.272983e-001, 1.277599e-001, 1.282217e-001, 1.286837e-001, 1.291459e-001, 1.296084e-001, 1.300712e-001, + 1.305347e-001, 1.309996e-001, 1.314649e-001, 1.319308e-001, 1.323972e-001, 1.328641e-001, 1.333315e-001, 1.337996e-001, 1.342682e-001, + 1.347375e-001, 1.352075e-001, 1.356796e-001, 1.361524e-001, 1.366258e-001, 1.371000e-001, 1.375749e-001, 1.380505e-001, 1.385269e-001, + 1.390042e-001, 1.394824e-001, 1.399612e-001, 1.404423e-001, 1.409239e-001, 1.414060e-001, 1.418886e-001, 1.423715e-001, 1.428550e-001, + 1.433390e-001, 1.438233e-001, 1.443082e-001, 1.447935e-001, 1.452803e-001, 1.457679e-001, 1.462556e-001, 1.467435e-001, 1.472318e-001, + 1.477203e-001, 1.482094e-001, 1.486990e-001, 1.491891e-001, 1.496798e-001, 1.501717e-001, 1.506655e-001, 1.511598e-001, 1.516549e-001, + 1.521508e-001, 1.526477e-001, 1.531454e-001, 1.536440e-001, 1.541436e-001, 1.546444e-001, 1.551465e-001, 1.556510e-001, 1.561566e-001, + 1.566633e-001, 1.571706e-001, 1.576782e-001, 1.581861e-001, 1.586944e-001, 1.592029e-001, 1.597115e-001, 1.602205e-001, 1.607314e-001, + 1.612429e-001, 1.617547e-001, 1.622672e-001, 1.627801e-001, 1.632935e-001, 1.638076e-001, 1.643225e-001, 1.648382e-001, 1.653550e-001, + 1.658739e-001, 1.663941e-001, 1.669153e-001, 1.674375e-001, 1.679605e-001, 1.684844e-001, 1.690095e-001, 1.695355e-001, 1.700624e-001, + 1.705903e-001, 1.711201e-001, 1.716517e-001, 1.721842e-001, 1.727172e-001, 1.732512e-001, 1.737863e-001, 1.743220e-001, 1.748586e-001, + 1.753961e-001, 1.759341e-001, 1.764733e-001, 1.770141e-001, 1.775553e-001, 1.780969e-001, 1.786393e-001, 1.791824e-001, 1.797259e-001, + 1.802701e-001, 1.808149e-001, 1.813603e-001, 1.819062e-001, 1.824543e-001, 1.830029e-001, 1.835522e-001, 1.841021e-001, 1.846526e-001, + 1.852037e-001, 1.857557e-001, 1.863082e-001, 1.868613e-001, 1.874151e-001, 1.879708e-001, 1.885278e-001, 1.890854e-001, 1.896436e-001, + 1.902028e-001, 1.907630e-001, 1.913239e-001, 1.918856e-001, 1.924481e-001, 1.930113e-001, 1.935760e-001, 1.941417e-001, 1.947082e-001, + 1.952753e-001, 1.958428e-001, 1.964110e-001, 1.969798e-001, 1.975490e-001, 1.981188e-001, 1.986891e-001, 1.992606e-001, 1.998335e-001, + 2.004069e-001, 2.009809e-001, 2.015556e-001, 2.021313e-001, 2.027075e-001, 2.032845e-001, 2.038624e-001, 2.044413e-001, 2.050214e-001, + 2.056038e-001, 2.061875e-001, 2.067723e-001, 2.073581e-001, 2.079446e-001, 2.085321e-001, 2.091204e-001, 2.097096e-001, 2.103000e-001, + 2.108914e-001, 2.114851e-001, 2.120801e-001, 2.126761e-001, 2.132731e-001, 2.138707e-001, 2.144686e-001, 2.150669e-001, 2.156660e-001, + 2.162658e-001, 2.168663e-001, 2.174684e-001, 2.180718e-001, 2.186760e-001, 2.192811e-001, 2.198872e-001, 2.204940e-001, 2.211018e-001, + 2.217107e-001, 2.223205e-001, 2.229314e-001, 2.235436e-001, 2.241575e-001, 2.247723e-001, 2.253881e-001, 2.260048e-001, 2.266224e-001, + 2.272410e-001, 2.278605e-001, 2.284810e-001, 2.291025e-001, 2.297252e-001, 2.303496e-001, 2.309750e-001, 2.316014e-001, 2.322288e-001, + 2.328571e-001, 2.334866e-001, 2.341173e-001, 2.347496e-001, 2.353832e-001, 2.360178e-001, 2.366545e-001, 2.372924e-001, 2.379313e-001, + 2.385714e-001, 2.392125e-001, 2.398548e-001, 2.404980e-001, 2.411424e-001, 2.417877e-001, 2.424340e-001, 2.430818e-001, 2.437308e-001, + 2.443806e-001, 2.450310e-001, 2.456824e-001, 2.463345e-001, 2.469875e-001, 2.476415e-001, 2.482966e-001, 2.489524e-001, 2.496098e-001, + 2.502687e-001, 2.509285e-001, 2.515894e-001, 2.522517e-001, 2.529152e-001, 2.535800e-001, 2.542463e-001, 2.549144e-001, 2.555841e-001, + 2.562555e-001, 2.569287e-001, 2.576029e-001, 2.582783e-001, 2.589545e-001, 2.596323e-001, 2.603114e-001, 2.609921e-001, 2.616743e-001, + 2.623580e-001, 2.630435e-001, 2.637309e-001, 2.644197e-001, 2.651098e-001, 2.658010e-001, 2.664934e-001, 2.671868e-001, 2.678814e-001, + 2.685770e-001, 2.692738e-001, 2.699717e-001, 2.706715e-001, 2.713726e-001, 2.720750e-001, 2.727788e-001, 2.734841e-001, 2.741906e-001, + 2.748987e-001, 2.756084e-001, 2.763193e-001, 2.770315e-001, 2.777453e-001, 2.784602e-001, 2.791761e-001, 2.798931e-001, 2.806108e-001, + 2.813291e-001, 2.820484e-001, 2.827687e-001, 2.834894e-001, 2.842107e-001, 2.849328e-001, 2.856559e-001, 2.863794e-001, 2.871033e-001, + 2.878280e-001, 2.885538e-001, 2.892806e-001, 2.900085e-001, 2.907377e-001, 2.914678e-001, 2.921990e-001, 2.929322e-001, 2.936666e-001, + 2.944022e-001, 2.951392e-001, 2.958773e-001, 2.966168e-001, 2.973575e-001, 2.980994e-001, 2.988425e-001, 2.995872e-001, 3.003341e-001, + 3.010828e-001, 3.018327e-001, 3.025843e-001, 3.033377e-001, 3.040933e-001, 3.048509e-001, 3.056102e-001, 3.063707e-001, 3.071326e-001, + 3.078965e-001, 3.086619e-001, 3.094288e-001, 3.101971e-001, 3.109667e-001, 3.117377e-001, 3.125102e-001, 3.132843e-001, 3.140597e-001, + 3.148362e-001, 3.156143e-001, 3.163942e-001, 3.171757e-001, 3.179586e-001, 3.187431e-001, 3.195291e-001, 3.203168e-001, 3.211061e-001, + 3.218967e-001, 3.226890e-001, 3.234832e-001, 3.242806e-001, 3.250800e-001, 3.258812e-001, 3.266844e-001, 3.274893e-001, 3.282961e-001, + 3.291047e-001, 3.299151e-001, 3.307276e-001, 3.315426e-001, 3.323615e-001, 3.331823e-001, 3.340052e-001, 3.348296e-001, 3.356559e-001, + 3.364836e-001, 3.373129e-001, 3.381440e-001, 3.389768e-001, 3.398111e-001, 3.406486e-001, 3.414881e-001, 3.423288e-001, 3.431714e-001, + 3.440159e-001, 3.448618e-001, 3.457091e-001, 3.465585e-001, 3.474094e-001, 3.482619e-001, 3.491170e-001, 3.499750e-001, 3.508346e-001, + 3.516960e-001, 3.525590e-001, 3.534239e-001, 3.542906e-001, 3.551589e-001, 3.560288e-001, 3.569002e-001, 3.577739e-001, 3.586511e-001, + 3.595298e-001, 3.604101e-001, 3.612919e-001, 3.621754e-001, 3.630602e-001, 3.639464e-001, 3.648337e-001, 3.657226e-001, 3.666130e-001, + 3.675079e-001, 3.684047e-001, 3.693031e-001, 3.702028e-001, 3.711038e-001, 3.720055e-001, 3.729085e-001, 3.738129e-001, 3.747189e-001, + 3.756259e-001, 3.765372e-001, 3.774503e-001, 3.783655e-001, 3.792823e-001, 3.802004e-001, 3.811199e-001, 3.820413e-001, 3.829644e-001, + 3.838895e-001, 3.848165e-001, 3.857473e-001, 3.866814e-001, 3.876177e-001, 3.885556e-001, 3.894956e-001, 3.904372e-001, 3.913807e-001, + 3.923261e-001, 3.932734e-001, 3.942231e-001, 3.951765e-001, 3.961346e-001, 3.970947e-001, 3.980568e-001, 3.990208e-001, 3.999867e-001, + 4.009543e-001, 4.019241e-001, 4.028963e-001, 4.038703e-001, 4.048471e-001, 4.058301e-001, 4.068154e-001, 4.078022e-001, 4.087909e-001, + 4.097813e-001, 4.107738e-001, 4.117686e-001, 4.127655e-001, 4.137644e-001, 4.147651e-001, 4.157725e-001, 4.167821e-001, 4.177943e-001, + 4.188099e-001, 4.198286e-001, 4.208502e-001, 4.218751e-001, 4.229033e-001, 4.239342e-001, 4.249681e-001, 4.260087e-001, 4.270545e-001, + 4.281033e-001, 4.291544e-001, 4.302083e-001, 4.312648e-001, 4.323243e-001, 4.333859e-001, 4.344498e-001, 4.355162e-001, 4.365869e-001, + 4.376633e-001, 4.387419e-001, 4.398226e-001, 4.409054e-001, 4.419904e-001, 4.430772e-001, 4.441661e-001, 4.452577e-001, 4.463516e-001, + 4.474485e-001, 4.485527e-001, 4.496586e-001, 4.507668e-001, 4.518776e-001, 4.529909e-001, 4.541065e-001, 4.552241e-001, 4.563439e-001, + 4.574659e-001, 4.585899e-001, 4.597226e-001, 4.608568e-001, 4.619925e-001, 4.631293e-001, 4.642676e-001, 4.654081e-001, 4.665511e-001, + 4.676967e-001, 4.688444e-001, 4.699939e-001, 4.711507e-001, 4.723117e-001, 4.734752e-001, 4.746406e-001, 4.758081e-001, 4.769782e-001, + 4.781511e-001, 4.793259e-001, 4.805027e-001, 4.816828e-001, 4.828697e-001, 4.840633e-001, 4.852592e-001, 4.864575e-001, 4.876592e-001, + 4.888639e-001, 4.900715e-001, 4.912817e-001, 4.924951e-001, 4.937120e-001, 4.949337e-001, 4.961640e-001, 4.973977e-001, 4.986341e-001, + 4.998730e-001, 5.011138e-001, 5.023570e-001, 5.036028e-001, 5.048504e-001, 5.060995e-001, 5.073515e-001, 5.086140e-001, 5.098794e-001, + 5.111469e-001, 5.124161e-001, 5.136877e-001, 5.149620e-001, 5.162395e-001, 5.175194e-001, 5.188013e-001, 5.200862e-001, 5.213809e-001, + 5.226801e-001, 5.239816e-001, 5.252864e-001, 5.265944e-001, 5.279053e-001, 5.292193e-001, 5.305359e-001, 5.318561e-001, 5.331789e-001, + 5.345105e-001, 5.358494e-001, 5.371919e-001, 5.385380e-001, 5.398877e-001, 5.412416e-001, 5.425985e-001, 5.439588e-001, 5.453220e-001, + 5.466886e-001, 5.480612e-001, 5.494425e-001, 5.508270e-001, 5.522152e-001, 5.536064e-001, 5.550010e-001, 5.563989e-001, 5.578012e-001, + 5.592064e-001, 5.606141e-001, 5.620263e-001, 5.634495e-001, 5.648759e-001, 5.663060e-001, 5.677399e-001, 5.691766e-001, 5.706165e-001, + 5.720603e-001, 5.735081e-001, 5.749596e-001, 5.764154e-001, 5.778844e-001, 5.793581e-001, 5.808358e-001, 5.823175e-001, 5.838036e-001, + 5.852938e-001, 5.867884e-001, 5.882880e-001, 5.897926e-001, 5.913013e-001, 5.928210e-001, 5.943471e-001, 5.958764e-001, 5.974098e-001, + 5.989470e-001, 6.004887e-001, 6.020334e-001, 6.035817e-001, 6.051345e-001, 6.066907e-001, 6.082564e-001, 6.098326e-001, 6.114136e-001, + 6.129989e-001, 6.145888e-001, 6.161829e-001, 6.177816e-001, 6.193858e-001, 6.209950e-001, 6.226082e-001, 6.242284e-001, 6.258622e-001, + 6.275011e-001, 6.291444e-001, 6.307920e-001, 6.324457e-001, 6.341036e-001, 6.357665e-001, 6.374354e-001, 6.391098e-001, 6.407888e-001, + 6.424865e-001, 6.441900e-001, 6.458998e-001, 6.476140e-001, 6.493339e-001, 6.510594e-001, 6.527905e-001, 6.545270e-001, 6.562691e-001, + 6.580176e-001, 6.597829e-001, 6.615564e-001, 6.633360e-001, 6.651216e-001, 6.669126e-001, 6.687086e-001, 6.705112e-001, 6.723208e-001, + 6.741380e-001, 6.759613e-001, 6.777997e-001, 6.796534e-001, 6.815140e-001, 6.833799e-001, 6.852528e-001, 6.871315e-001, 6.890163e-001, + 6.909087e-001, 6.928068e-001, 6.947108e-001, 6.966290e-001, 6.985671e-001, 7.005132e-001, 7.024672e-001, 7.044298e-001, 7.063980e-001, + 7.083728e-001, 7.103546e-001, 7.123452e-001, 7.143444e-001, 7.163524e-001, 7.183896e-001, 7.204359e-001, 7.224905e-001, 7.245525e-001, + 7.266246e-001, 7.287066e-001, 7.307961e-001, 7.328947e-001, 7.350028e-001, 7.371211e-001, 7.392717e-001, 7.414343e-001, 7.436061e-001, + 7.457885e-001, 7.479840e-001, 7.501906e-001, 7.524064e-001, 7.546306e-001, 7.568639e-001, 7.591079e-001, 7.613802e-001, 7.636769e-001, + 7.659834e-001, 7.683037e-001, 7.706373e-001, 7.729814e-001, 7.753358e-001, 7.777012e-001, 7.800781e-001, 7.824678e-001, 7.848826e-001, + 7.873337e-001, 7.897965e-001, 7.922734e-001, 7.947627e-001, 7.972671e-001, 7.997864e-001, 8.023179e-001, 8.048668e-001, 8.074288e-001, + 8.100120e-001, 8.126469e-001, 8.152965e-001, 8.179622e-001, 8.206483e-001, 8.233516e-001, 8.260734e-001, 8.288131e-001, 8.315727e-001, + 8.343446e-001, 8.371342e-001, 8.399934e-001, 8.428773e-001, 8.457888e-001, 8.487272e-001, 8.516942e-001, 8.546887e-001, 8.577007e-001, + 8.607361e-001, 8.638108e-001, 8.669571e-001, 8.701564e-001, 8.734027e-001, 8.766781e-001, 8.799727e-001, 8.832960e-001, 8.866553e-001, + 8.900595e-001, 8.935105e-001, 8.969939e-001, 9.005120e-001, 9.045323e-001, 9.082241e-001, 9.119395e-001, 9.157112e-001, 9.195482e-001, + 9.234328e-001, 9.273786e-001, 9.314110e-001, 9.355407e-001, 9.397977e-001, 9.450196e-001, 9.509351e-001, 9.559072e-001, 9.607881e-001, + 9.656764e-001, 9.707481e-001, 9.773920e-001, 9.829417e-001, 9.885159e-001, 9.941525e-001, 1.000000e+000}, + {0.000000e+000, -3.706679e-004, -6.814894e-004, -9.541366e-004, -1.200249e-003, -1.426712e-003, -1.637806e-003, -1.836267e-003, -2.024306e-003, + -2.203649e-003, -2.376028e-003, -2.542911e-003, -2.704507e-003, -2.861239e-003, -3.013712e-003, -3.162217e-003, -3.307209e-003, -3.449026e-003, + -3.587871e-003, -3.723937e-003, -3.857590e-003, -3.989245e-003, -4.119038e-003, -4.246882e-003, -4.372804e-003, -4.496979e-003, -4.619460e-003, + -4.740346e-003, -4.859837e-003, -4.978036e-003, -5.094972e-003, -5.210795e-003, -5.325719e-003, -5.439404e-003, -5.551922e-003, -5.663349e-003, + -5.773777e-003, -5.883208e-003, -5.991623e-003, -6.099054e-003, -6.205496e-003, -6.311011e-003, -6.415960e-003, -6.520054e-003, -6.623255e-003, + -6.725578e-003, -6.826867e-003, -6.926829e-003, -7.026117e-003, -7.125291e-003, -7.223958e-003, -7.321931e-003, -7.419616e-003, -7.516768e-003, + -7.613367e-003, -7.709394e-003, -7.804855e-003, -7.899793e-003, -7.994219e-003, -8.088160e-003, -8.181627e-003, -8.274628e-003, -8.367352e-003, + -8.459712e-003, -8.551646e-003, -8.643177e-003, -8.734306e-003, -8.825122e-003, -8.915633e-003, -9.005845e-003, -9.095798e-003, -9.185478e-003, + -9.275059e-003, -9.364625e-003, -9.454052e-003, -9.543292e-003, -9.632467e-003, -9.721534e-003, -9.810445e-003, -9.899013e-003, -9.987071e-003, + -1.007457e-002, -1.016154e-002, -1.024815e-002, -1.033421e-002, -1.041978e-002, -1.050487e-002, -1.058952e-002, -1.067369e-002, -1.075741e-002, + -1.084071e-002, -1.092361e-002, -1.100617e-002, -1.108853e-002, -1.117067e-002, -1.125255e-002, -1.133419e-002, -1.141559e-002, -1.149677e-002, + -1.157775e-002, -1.165856e-002, -1.173920e-002, -1.181969e-002, -1.190009e-002, -1.198039e-002, -1.206064e-002, -1.214071e-002, -1.222067e-002, + -1.230052e-002, -1.238033e-002, -1.246016e-002, -1.253995e-002, -1.261963e-002, -1.269928e-002, -1.277886e-002, -1.285834e-002, -1.293768e-002, + -1.301696e-002, -1.309619e-002, -1.317537e-002, -1.325442e-002, -1.333328e-002, -1.341188e-002, -1.349014e-002, -1.356790e-002, -1.364519e-002, + -1.372204e-002, -1.379850e-002, -1.387464e-002, -1.395054e-002, -1.402616e-002, -1.410158e-002, -1.417690e-002, -1.425209e-002, -1.432713e-002, + -1.440206e-002, -1.447688e-002, -1.455162e-002, -1.462635e-002, -1.470105e-002, -1.477576e-002, -1.485047e-002, -1.492514e-002, -1.499979e-002, + -1.507431e-002, -1.514875e-002, -1.522309e-002, -1.529729e-002, -1.537128e-002, -1.544510e-002, -1.551872e-002, -1.559211e-002, -1.566534e-002, + -1.573837e-002, -1.581116e-002, -1.588368e-002, -1.595599e-002, -1.602805e-002, -1.609989e-002, -1.617154e-002, -1.624302e-002, -1.631431e-002, + -1.638549e-002, -1.645658e-002, -1.652758e-002, -1.659843e-002, -1.666931e-002, -1.674024e-002, -1.681128e-002, -1.688244e-002, -1.695371e-002, + -1.702501e-002, -1.709624e-002, -1.716739e-002, -1.723849e-002, -1.730947e-002, -1.738051e-002, -1.745163e-002, -1.752283e-002, -1.759406e-002, + -1.766524e-002, -1.773627e-002, -1.780703e-002, -1.787745e-002, -1.794756e-002, -1.801728e-002, -1.808679e-002, -1.815616e-002, -1.822537e-002, + -1.829451e-002, -1.836356e-002, -1.843253e-002, -1.850141e-002, -1.857021e-002, -1.863898e-002, -1.870764e-002, -1.877626e-002, -1.884488e-002, + -1.891349e-002, -1.898213e-002, -1.905077e-002, -1.911940e-002, -1.918802e-002, -1.925665e-002, -1.932529e-002, -1.939390e-002, -1.946250e-002, + -1.953126e-002, -1.960022e-002, -1.966929e-002, -1.973839e-002, -1.980742e-002, -1.987635e-002, -1.994515e-002, -2.001378e-002, -2.008226e-002, + -2.015051e-002, -2.021878e-002, -2.028705e-002, -2.035529e-002, -2.042343e-002, -2.049135e-002, -2.055896e-002, -2.062620e-002, -2.069308e-002, + -2.075966e-002, -2.082588e-002, -2.089190e-002, -2.095780e-002, -2.102356e-002, -2.108920e-002, -2.115471e-002, -2.122013e-002, -2.128543e-002, + -2.135060e-002, -2.141563e-002, -2.148041e-002, -2.154502e-002, -2.160958e-002, -2.167410e-002, -2.173858e-002, -2.180305e-002, -2.186752e-002, + -2.193206e-002, -2.199664e-002, -2.206132e-002, -2.212600e-002, -2.219079e-002, -2.225584e-002, -2.232107e-002, -2.238638e-002, -2.245158e-002, + -2.251668e-002, -2.258156e-002, -2.264624e-002, -2.271071e-002, -2.277493e-002, -2.283878e-002, -2.290247e-002, -2.296599e-002, -2.302931e-002, + -2.309241e-002, -2.315521e-002, -2.321774e-002, -2.328006e-002, -2.334218e-002, -2.340415e-002, -2.346577e-002, -2.352728e-002, -2.358872e-002, + -2.365008e-002, -2.371140e-002, -2.377271e-002, -2.383406e-002, -2.389548e-002, -2.395701e-002, -2.401868e-002, -2.408025e-002, -2.414183e-002, + -2.420345e-002, -2.426509e-002, -2.432674e-002, -2.438843e-002, -2.445014e-002, -2.451192e-002, -2.457374e-002, -2.463560e-002, -2.469736e-002, + -2.475893e-002, -2.482029e-002, -2.488130e-002, -2.494192e-002, -2.500218e-002, -2.506217e-002, -2.512190e-002, -2.518141e-002, -2.524069e-002, + -2.529971e-002, -2.535839e-002, -2.541694e-002, -2.547538e-002, -2.553373e-002, -2.559201e-002, -2.565024e-002, -2.570845e-002, -2.576659e-002, + -2.582471e-002, -2.588276e-002, -2.594057e-002, -2.599835e-002, -2.605615e-002, -2.611396e-002, -2.617183e-002, -2.622978e-002, -2.628788e-002, + -2.634617e-002, -2.640463e-002, -2.646321e-002, -2.652163e-002, -2.657998e-002, -2.663823e-002, -2.669640e-002, -2.675448e-002, -2.681251e-002, + -2.687051e-002, -2.692845e-002, -2.698636e-002, -2.704422e-002, -2.710181e-002, -2.715913e-002, -2.721620e-002, -2.727303e-002, -2.732961e-002, + -2.738607e-002, -2.744239e-002, -2.749860e-002, -2.755473e-002, -2.761084e-002, -2.766681e-002, -2.772261e-002, -2.777842e-002, -2.783429e-002, + -2.789024e-002, -2.794629e-002, -2.800244e-002, -2.805873e-002, -2.811521e-002, -2.817192e-002, -2.822882e-002, -2.828570e-002, -2.834280e-002, + -2.840004e-002, -2.845724e-002, -2.851419e-002, -2.857078e-002, -2.862705e-002, -2.868299e-002, -2.873860e-002, -2.879393e-002, -2.884878e-002, + -2.890338e-002, -2.895779e-002, -2.901203e-002, -2.906603e-002, -2.911989e-002, -2.917366e-002, -2.922736e-002, -2.928097e-002, -2.933450e-002, + -2.938782e-002, -2.944102e-002, -2.949424e-002, -2.954742e-002, -2.960054e-002, -2.965366e-002, -2.970681e-002, -2.975995e-002, -2.981312e-002, + -2.986630e-002, -2.991938e-002, -2.997238e-002, -3.002534e-002, -3.007822e-002, -3.013107e-002, -3.018389e-002, -3.023667e-002, -3.028943e-002, + -3.034215e-002, -3.039476e-002, -3.044718e-002, -3.049926e-002, -3.055110e-002, -3.060276e-002, -3.065431e-002, -3.070576e-002, -3.075712e-002, + -3.080840e-002, -3.085961e-002, -3.091070e-002, -3.096169e-002, -3.101243e-002, -3.106309e-002, -3.111367e-002, -3.116420e-002, -3.121469e-002, + -3.126514e-002, -3.131556e-002, -3.136598e-002, -3.141643e-002, -3.146690e-002, -3.151726e-002, -3.156767e-002, -3.161813e-002, -3.166863e-002, + -3.171924e-002, -3.176988e-002, -3.182052e-002, -3.187118e-002, -3.192187e-002, -3.197253e-002, -3.202301e-002, -3.207326e-002, -3.212334e-002, + -3.217328e-002, -3.222305e-002, -3.227273e-002, -3.232229e-002, -3.237167e-002, -3.242090e-002, -3.246988e-002, -3.251861e-002, -3.256704e-002, + -3.261532e-002, -3.266345e-002, -3.271149e-002, -3.275949e-002, -3.280743e-002, -3.285532e-002, -3.290325e-002, -3.295123e-002, -3.299933e-002, + -3.304744e-002, -3.309576e-002, -3.314423e-002, -3.319275e-002, -3.324120e-002, -3.328962e-002, -3.333797e-002, -3.338634e-002, -3.343478e-002, + -3.348335e-002, -3.353190e-002, -3.358053e-002, -3.362921e-002, -3.367782e-002, -3.372615e-002, -3.377403e-002, -3.382148e-002, -3.386861e-002, + -3.391553e-002, -3.396222e-002, -3.400862e-002, -3.405479e-002, -3.410073e-002, -3.414653e-002, -3.419220e-002, -3.423773e-002, -3.428312e-002, + -3.432841e-002, -3.437359e-002, -3.441868e-002, -3.446361e-002, -3.450831e-002, -3.455297e-002, -3.459747e-002, -3.464185e-002, -3.468607e-002, + -3.473020e-002, -3.477423e-002, -3.481817e-002, -3.486201e-002, -3.490574e-002, -3.494928e-002, -3.499275e-002, -3.503618e-002, -3.507956e-002, + -3.512293e-002, -3.516630e-002, -3.520975e-002, -3.525324e-002, -3.529676e-002, -3.534028e-002, -3.538366e-002, -3.542705e-002, -3.547042e-002, + -3.551377e-002, -3.555710e-002, -3.560045e-002, -3.564378e-002, -3.568709e-002, -3.573027e-002, -3.577331e-002, -3.581614e-002, -3.585881e-002, + -3.590129e-002, -3.594360e-002, -3.598576e-002, -3.602773e-002, -3.606952e-002, -3.611116e-002, -3.615268e-002, -3.619413e-002, -3.623546e-002, + -3.627668e-002, -3.631784e-002, -3.635900e-002, -3.640014e-002, -3.644132e-002, -3.648262e-002, -3.652412e-002, -3.656581e-002, -3.660775e-002, + -3.664982e-002, -3.669177e-002, -3.673361e-002, -3.677528e-002, -3.681678e-002, -3.685804e-002, -3.689910e-002, -3.693993e-002, -3.698057e-002, + -3.702106e-002, -3.706138e-002, -3.710139e-002, -3.714113e-002, -3.718066e-002, -3.721993e-002, -3.725895e-002, -3.729775e-002, -3.733637e-002, + -3.737485e-002, -3.741322e-002, -3.745144e-002, -3.748950e-002, -3.752750e-002, -3.756548e-002, -3.760350e-002, -3.764158e-002, -3.767977e-002, + -3.771805e-002, -3.775641e-002, -3.779489e-002, -3.783337e-002, -3.787178e-002, -3.791007e-002, -3.794830e-002, -3.798639e-002, -3.802438e-002, + -3.806230e-002, -3.810014e-002, -3.813793e-002, -3.817563e-002, -3.821318e-002, -3.825045e-002, -3.828737e-002, -3.832406e-002, -3.836050e-002, + -3.839677e-002, -3.843287e-002, -3.846884e-002, -3.850470e-002, -3.854052e-002, -3.857625e-002, -3.861182e-002, -3.864716e-002, -3.868239e-002, + -3.871746e-002, -3.875244e-002, -3.878733e-002, -3.882217e-002, -3.885693e-002, -3.889170e-002, -3.892640e-002, -3.896107e-002, -3.899562e-002, + -3.903022e-002, -3.906484e-002, -3.909959e-002, -3.913451e-002, -3.916969e-002, -3.920516e-002, -3.924068e-002, -3.927615e-002, -3.931141e-002, + -3.934628e-002, -3.938083e-002, -3.941521e-002, -3.944940e-002, -3.948354e-002, -3.951762e-002, -3.955159e-002, -3.958545e-002, -3.961908e-002, + -3.965240e-002, -3.968527e-002, -3.971775e-002, -3.975003e-002, -3.978216e-002, -3.981413e-002, -3.984592e-002, -3.987765e-002, -3.990928e-002, + -3.994083e-002, -3.997228e-002, -4.000372e-002, -4.003494e-002, -4.006621e-002, -4.009753e-002, -4.012889e-002, -4.016029e-002, -4.019175e-002, + -4.022323e-002, -4.025482e-002, -4.028651e-002, -4.031816e-002, -4.034928e-002, -4.038001e-002, -4.041021e-002, -4.043987e-002, -4.046904e-002, + -4.049777e-002, -4.052604e-002, -4.055386e-002, -4.058123e-002, -4.060819e-002, -4.063445e-002, -4.066016e-002, -4.068559e-002, -4.071071e-002, + -4.073550e-002, -4.075998e-002, -4.078422e-002, -4.080817e-002, -4.083200e-002, -4.085568e-002, -4.087897e-002, -4.090187e-002, -4.092459e-002, + -4.094719e-002, -4.096969e-002, -4.099210e-002, -4.101449e-002, -4.103686e-002, -4.105920e-002, -4.108158e-002, -4.110390e-002, -4.112606e-002, + -4.114836e-002, -4.117073e-002, -4.119310e-002, -4.121553e-002, -4.123802e-002, -4.126054e-002, -4.128316e-002, -4.130584e-002, -4.132856e-002, + -4.135085e-002, -4.137302e-002, -4.139503e-002, -4.141678e-002, -4.143807e-002, -4.145884e-002, -4.147922e-002, -4.149924e-002, -4.151896e-002, + -4.153841e-002, -4.155709e-002, -4.157537e-002, -4.159343e-002, -4.161128e-002, -4.162887e-002, -4.164627e-002, -4.166347e-002, -4.168053e-002, + -4.169741e-002, -4.171409e-002, -4.173020e-002, -4.174598e-002, -4.176170e-002, -4.177730e-002, -4.179274e-002, -4.180804e-002, -4.182322e-002, + -4.183832e-002, -4.185342e-002, -4.186857e-002, -4.188354e-002, -4.189808e-002, -4.191258e-002, -4.192692e-002, -4.194102e-002, -4.195492e-002, + -4.196866e-002, -4.198221e-002, -4.199574e-002, -4.200906e-002, -4.202212e-002, -4.203427e-002, -4.204614e-002, -4.205764e-002, -4.206872e-002, + -4.207938e-002, -4.208975e-002, -4.209985e-002, -4.210967e-002, -4.211916e-002, -4.212840e-002, -4.213661e-002, -4.214455e-002, -4.215232e-002, + -4.216000e-002, -4.216734e-002, -4.217447e-002, -4.218140e-002, -4.218806e-002, -4.219447e-002, -4.220065e-002, -4.220590e-002, -4.221059e-002, + -4.221494e-002, -4.221905e-002, -4.222291e-002, -4.222653e-002, -4.222977e-002, -4.223266e-002, -4.223517e-002, -4.223729e-002, -4.223869e-002, + -4.223901e-002, -4.223891e-002, -4.223840e-002, -4.223757e-002, -4.223638e-002, -4.223492e-002, -4.223307e-002, -4.223087e-002, -4.222838e-002, + -4.222556e-002, -4.222162e-002, -4.221757e-002, -4.221348e-002, -4.220947e-002, -4.220531e-002, -4.220112e-002, -4.219708e-002, -4.219321e-002, + -4.218940e-002, -4.218563e-002, -4.218053e-002, -4.217517e-002, -4.216952e-002, -4.216340e-002, -4.215699e-002, -4.215031e-002, -4.214338e-002, + -4.213612e-002, -4.212862e-002, -4.212073e-002, -4.211140e-002, -4.210105e-002, -4.209019e-002, -4.207889e-002, -4.206721e-002, -4.205518e-002, + -4.204283e-002, -4.203028e-002, -4.201757e-002, -4.200449e-002, -4.199045e-002, -4.197544e-002, -4.196038e-002, -4.194520e-002, -4.192981e-002, + -4.191421e-002, -4.189845e-002, -4.188271e-002, -4.186697e-002, -4.185102e-002, -4.183452e-002, -4.181683e-002, -4.179888e-002, -4.178052e-002, + -4.176171e-002, -4.174234e-002, -4.172245e-002, -4.170208e-002, -4.168123e-002, -4.165968e-002, -4.163740e-002, -4.161329e-002, -4.158860e-002, + -4.156345e-002, -4.153785e-002, -4.151176e-002, -4.148527e-002, -4.145833e-002, -4.143115e-002, -4.140336e-002, -4.137520e-002, -4.134533e-002, + -4.131476e-002, -4.128380e-002, -4.125250e-002, -4.122076e-002, -4.118866e-002, -4.115623e-002, -4.112346e-002, -4.109010e-002, -4.105640e-002, + -4.102135e-002, -4.098537e-002, -4.094922e-002, -4.091286e-002, -4.087644e-002, -4.083970e-002, -4.080263e-002, -4.076525e-002, -4.072758e-002, + -4.068947e-002, -4.065014e-002, -4.060925e-002, -4.056795e-002, -4.052634e-002, -4.048442e-002, -4.044213e-002, -4.039922e-002, -4.035567e-002, + -4.031139e-002, -4.026642e-002, -4.022038e-002, -4.017213e-002, -4.012325e-002, -4.007387e-002, -4.002381e-002, -3.997331e-002, -3.992227e-002, + -3.987075e-002, -3.981876e-002, -3.976641e-002, -3.971344e-002, -3.965825e-002, -3.960252e-002, -3.954651e-002, -3.949009e-002, -3.943321e-002, + -3.937609e-002, -3.931847e-002, -3.926059e-002, -3.920225e-002, -3.914344e-002, -3.908245e-002, -3.902008e-002, -3.895700e-002, -3.889304e-002, + -3.882843e-002, -3.876326e-002, -3.869705e-002, -3.863003e-002, -3.856213e-002, -3.849353e-002, -3.842306e-002, -3.835093e-002, -3.827807e-002, + -3.820454e-002, -3.813013e-002, -3.805510e-002, -3.797951e-002, -3.790342e-002, -3.782664e-002, -3.774921e-002, -3.767049e-002, -3.758917e-002, + -3.750738e-002, -3.742498e-002, -3.734200e-002, -3.725849e-002, -3.717452e-002, -3.708996e-002, -3.700494e-002, -3.691950e-002, -3.683353e-002, + -3.674412e-002, -3.665405e-002, -3.656320e-002, -3.647169e-002, -3.637960e-002, -3.628669e-002, -3.619290e-002, -3.609827e-002, -3.600281e-002, + -3.590650e-002, -3.580699e-002, -3.570604e-002, -3.560407e-002, -3.550142e-002, -3.539778e-002, -3.529324e-002, -3.518797e-002, -3.508205e-002, + -3.497529e-002, -3.486767e-002, -3.475733e-002, -3.464435e-002, -3.453039e-002, -3.441555e-002, -3.429960e-002, -3.418293e-002, -3.406567e-002, + -3.394739e-002, -3.382821e-002, -3.370800e-002, -3.358524e-002, -3.345891e-002, -3.333127e-002, -3.320242e-002, -3.307204e-002, -3.294059e-002, + -3.280808e-002, -3.267451e-002, -3.253969e-002, -3.240345e-002, -3.226576e-002, -3.212292e-002, -3.197874e-002, -3.183292e-002, -3.168610e-002, + -3.153797e-002, -3.138799e-002, -3.123657e-002, -3.108369e-002, -3.092927e-002, -3.077294e-002, -3.061155e-002, -3.044834e-002, -3.028347e-002, + -3.011670e-002, -2.994799e-002, -2.977748e-002, -2.960529e-002, -2.943169e-002, -2.925652e-002, -2.907966e-002, -2.889846e-002, -2.871340e-002, + -2.852629e-002, -2.833704e-002, -2.814575e-002, -2.795228e-002, -2.775726e-002, -2.755995e-002, -2.736048e-002, -2.715863e-002, -2.695249e-002, + -2.674042e-002, -2.652666e-002, -2.630985e-002, -2.609062e-002, -2.586830e-002, -2.564352e-002, -2.541669e-002, -2.518592e-002, -2.495234e-002, + -2.471529e-002, -2.447100e-002, -2.422366e-002, -2.397329e-002, -2.371875e-002, -2.346003e-002, -2.319774e-002, -2.293137e-002, -2.266066e-002, + -2.238920e-002, -2.211452e-002, -2.182980e-002, -2.154045e-002, -2.124386e-002, -2.094103e-002, -2.063227e-002, -2.031858e-002, -2.000225e-002, + -1.968033e-002, -1.934677e-002, -1.898635e-002, -1.862573e-002, -1.826109e-002, -1.788969e-002, -1.751771e-002, -1.714099e-002, -1.675907e-002, + -1.636672e-002, -1.596240e-002, -1.554893e-002, -1.512689e-002, -1.446370e-002, -1.400828e-002, -1.355645e-002, -1.309401e-002, -1.261733e-002, + -1.213440e-002, -1.164163e-002, -1.113086e-002, -1.059870e-002, -1.003953e-002, -9.121323e-003, -7.871813e-003, -7.111556e-003, -6.411784e-003, + -5.718638e-003, -4.962077e-003, -3.505064e-003, -2.593092e-003, -1.709185e-003, -8.650688e-004, 0.000000e+000}, + {0.000000e+000, -1.380706e-003, -2.537619e-003, -3.548232e-003, -4.452111e-003, -5.273238e-003, -6.028433e-003, -6.728530e-003, -7.383660e-003, + -7.999797e-003, -8.583057e-003, -9.135808e-003, -9.662426e-003, -1.016601e-002, -1.064938e-002, -1.111449e-002, -1.156316e-002, -1.199686e-002, + -1.241708e-002, -1.282460e-002, -1.321998e-002, -1.360340e-002, -1.397568e-002, -1.433806e-002, -1.469119e-002, -1.503557e-002, -1.537149e-002, + -1.569976e-002, -1.602076e-002, -1.633465e-002, -1.664175e-002, -1.694207e-002, -1.723519e-002, -1.752263e-002, -1.780453e-002, -1.808108e-002, + -1.835268e-002, -1.861944e-002, -1.888151e-002, -1.913918e-002, -1.939260e-002, -1.964168e-002, -1.988536e-002, -2.012495e-002, -2.036070e-002, + -2.059275e-002, -2.082114e-002, -2.104594e-002, -2.126740e-002, -2.148601e-002, -2.170167e-002, -2.191439e-002, -2.212292e-002, -2.232825e-002, + -2.253078e-002, -2.273052e-002, -2.292768e-002, -2.312206e-002, -2.331385e-002, -2.350313e-002, -2.368987e-002, -2.387418e-002, -2.405547e-002, + -2.423400e-002, -2.441034e-002, -2.458450e-002, -2.475648e-002, -2.492637e-002, -2.509422e-002, -2.526006e-002, -2.542396e-002, -2.558596e-002, + -2.574561e-002, -2.590270e-002, -2.605803e-002, -2.621151e-002, -2.636305e-002, -2.651292e-002, -2.666100e-002, -2.680745e-002, -2.695221e-002, + -2.709542e-002, -2.723671e-002, -2.737560e-002, -2.751293e-002, -2.764863e-002, -2.778284e-002, -2.791539e-002, -2.804663e-002, -2.817625e-002, + -2.830440e-002, -2.843113e-002, -2.855648e-002, -2.867969e-002, -2.880139e-002, -2.892175e-002, -2.904081e-002, -2.915861e-002, -2.927501e-002, + -2.939023e-002, -2.950419e-002, -2.961702e-002, -2.972864e-002, -2.983874e-002, -2.994745e-002, -3.005493e-002, -3.016126e-002, -3.026651e-002, + -3.037072e-002, -3.047370e-002, -3.057557e-002, -3.067618e-002, -3.077586e-002, -3.087431e-002, -3.097135e-002, -3.106753e-002, -3.116268e-002, + -3.125682e-002, -3.134997e-002, -3.144200e-002, -3.153315e-002, -3.162334e-002, -3.171247e-002, -3.180041e-002, -3.188698e-002, -3.197249e-002, + -3.205701e-002, -3.214052e-002, -3.222297e-002, -3.230442e-002, -3.238497e-002, -3.246459e-002, -3.254320e-002, -3.262084e-002, -3.269717e-002, + -3.277273e-002, -3.284746e-002, -3.292126e-002, -3.299417e-002, -3.306629e-002, -3.313748e-002, -3.320776e-002, -3.327724e-002, -3.334596e-002, + -3.341351e-002, -3.348020e-002, -3.354618e-002, -3.361134e-002, -3.367560e-002, -3.373913e-002, -3.380186e-002, -3.386380e-002, -3.392484e-002, + -3.398514e-002, -3.404450e-002, -3.410286e-002, -3.416045e-002, -3.421727e-002, -3.427326e-002, -3.432849e-002, -3.438296e-002, -3.443667e-002, + -3.448964e-002, -3.454186e-002, -3.459316e-002, -3.464345e-002, -3.469297e-002, -3.474187e-002, -3.478997e-002, -3.483721e-002, -3.488369e-002, + -3.492943e-002, -3.497440e-002, -3.501855e-002, -3.506201e-002, -3.510461e-002, -3.514654e-002, -3.518767e-002, -3.522810e-002, -3.526789e-002, + -3.530703e-002, -3.534542e-002, -3.538307e-002, -3.542007e-002, -3.545643e-002, -3.549198e-002, -3.552687e-002, -3.556111e-002, -3.559473e-002, + -3.562756e-002, -3.565969e-002, -3.569121e-002, -3.572212e-002, -3.575238e-002, -3.578202e-002, -3.581092e-002, -3.583915e-002, -3.586663e-002, + -3.589350e-002, -3.591977e-002, -3.594546e-002, -3.597052e-002, -3.599497e-002, -3.601882e-002, -3.604210e-002, -3.606463e-002, -3.608645e-002, + -3.610773e-002, -3.612835e-002, -3.614836e-002, -3.616768e-002, -3.618631e-002, -3.620431e-002, -3.622168e-002, -3.623839e-002, -3.625450e-002, + -3.627005e-002, -3.628503e-002, -3.629932e-002, -3.631296e-002, -3.632598e-002, -3.633847e-002, -3.635038e-002, -3.636174e-002, -3.637250e-002, + -3.638264e-002, -3.639216e-002, -3.640108e-002, -3.640939e-002, -3.641717e-002, -3.642438e-002, -3.643106e-002, -3.643709e-002, -3.644258e-002, + -3.644748e-002, -3.645181e-002, -3.645561e-002, -3.645881e-002, -3.646151e-002, -3.646367e-002, -3.646530e-002, -3.646631e-002, -3.646684e-002, + -3.646674e-002, -3.646602e-002, -3.646472e-002, -3.646297e-002, -3.646074e-002, -3.645806e-002, -3.645487e-002, -3.645102e-002, -3.644667e-002, + -3.644175e-002, -3.643632e-002, -3.643045e-002, -3.642403e-002, -3.641717e-002, -3.640988e-002, -3.640208e-002, -3.639374e-002, -3.638486e-002, + -3.637550e-002, -3.636551e-002, -3.635498e-002, -3.634391e-002, -3.633231e-002, -3.632014e-002, -3.630763e-002, -3.629470e-002, -3.628122e-002, + -3.626727e-002, -3.625272e-002, -3.623763e-002, -3.622199e-002, -3.620586e-002, -3.618916e-002, -3.617198e-002, -3.615439e-002, -3.613635e-002, + -3.611781e-002, -3.609870e-002, -3.607906e-002, -3.605882e-002, -3.603807e-002, -3.601676e-002, -3.599492e-002, -3.597259e-002, -3.594984e-002, + -3.592654e-002, -3.590272e-002, -3.587838e-002, -3.585353e-002, -3.582817e-002, -3.580239e-002, -3.577611e-002, -3.574945e-002, -3.572249e-002, + -3.569521e-002, -3.566761e-002, -3.563950e-002, -3.561093e-002, -3.558190e-002, -3.555245e-002, -3.552254e-002, -3.549221e-002, -3.546147e-002, + -3.543020e-002, -3.539856e-002, -3.536657e-002, -3.533405e-002, -3.530107e-002, -3.526770e-002, -3.523396e-002, -3.519978e-002, -3.516531e-002, + -3.513042e-002, -3.509512e-002, -3.505951e-002, -3.502372e-002, -3.498764e-002, -3.495120e-002, -3.491428e-002, -3.487697e-002, -3.483931e-002, + -3.480125e-002, -3.476283e-002, -3.472405e-002, -3.468486e-002, -3.464545e-002, -3.460576e-002, -3.456561e-002, -3.452506e-002, -3.448406e-002, + -3.444270e-002, -3.440093e-002, -3.435868e-002, -3.431606e-002, -3.427303e-002, -3.422971e-002, -3.418606e-002, -3.414212e-002, -3.409779e-002, + -3.405301e-002, -3.400776e-002, -3.396208e-002, -3.391605e-002, -3.386957e-002, -3.382267e-002, -3.377538e-002, -3.372800e-002, -3.368010e-002, + -3.363166e-002, -3.358280e-002, -3.353354e-002, -3.348386e-002, -3.343371e-002, -3.338318e-002, -3.333228e-002, -3.328091e-002, -3.322929e-002, + -3.317728e-002, -3.312482e-002, -3.307187e-002, -3.301850e-002, -3.296473e-002, -3.291048e-002, -3.285585e-002, -3.280075e-002, -3.274511e-002, + -3.268922e-002, -3.263287e-002, -3.257599e-002, -3.251866e-002, -3.246094e-002, -3.240277e-002, -3.234405e-002, -3.228488e-002, -3.222529e-002, + -3.216523e-002, -3.210483e-002, -3.204412e-002, -3.198296e-002, -3.192145e-002, -3.185941e-002, -3.179673e-002, -3.173373e-002, -3.167030e-002, + -3.160634e-002, -3.154205e-002, -3.147731e-002, -3.141236e-002, -3.134714e-002, -3.128146e-002, -3.121519e-002, -3.114855e-002, -3.108153e-002, + -3.101401e-002, -3.094607e-002, -3.087780e-002, -3.080908e-002, -3.074024e-002, -3.067110e-002, -3.060149e-002, -3.053140e-002, -3.046100e-002, + -3.039022e-002, -3.031896e-002, -3.024738e-002, -3.017548e-002, -3.010320e-002, -3.003072e-002, -2.995782e-002, -2.988463e-002, -2.981105e-002, + -2.973687e-002, -2.966223e-002, -2.958726e-002, -2.951187e-002, -2.943599e-002, -2.935970e-002, -2.928309e-002, -2.920624e-002, -2.912890e-002, + -2.905129e-002, -2.897328e-002, -2.889482e-002, -2.881596e-002, -2.873677e-002, -2.865708e-002, -2.857700e-002, -2.849654e-002, -2.841578e-002, + -2.833468e-002, -2.825317e-002, -2.817117e-002, -2.808878e-002, -2.800621e-002, -2.792334e-002, -2.783999e-002, -2.775609e-002, -2.767181e-002, + -2.758743e-002, -2.750263e-002, -2.741739e-002, -2.733178e-002, -2.724585e-002, -2.715948e-002, -2.707270e-002, -2.698558e-002, -2.689802e-002, + -2.680999e-002, -2.672187e-002, -2.663339e-002, -2.654454e-002, -2.645518e-002, -2.636532e-002, -2.627522e-002, -2.618472e-002, -2.609368e-002, + -2.600227e-002, -2.591044e-002, -2.581830e-002, -2.572573e-002, -2.563273e-002, -2.553920e-002, -2.544516e-002, -2.535077e-002, -2.525613e-002, + -2.516088e-002, -2.506522e-002, -2.496913e-002, -2.487276e-002, -2.477599e-002, -2.467869e-002, -2.458091e-002, -2.448272e-002, -2.438410e-002, + -2.428506e-002, -2.418560e-002, -2.408555e-002, -2.398503e-002, -2.388409e-002, -2.378300e-002, -2.368153e-002, -2.357962e-002, -2.347728e-002, + -2.337452e-002, -2.327135e-002, -2.316767e-002, -2.306357e-002, -2.295886e-002, -2.285371e-002, -2.274818e-002, -2.264224e-002, -2.253584e-002, + -2.242896e-002, -2.232174e-002, -2.221403e-002, -2.210588e-002, -2.199720e-002, -2.188807e-002, -2.177841e-002, -2.166840e-002, -2.155796e-002, + -2.144709e-002, -2.133579e-002, -2.122389e-002, -2.111151e-002, -2.099879e-002, -2.088554e-002, -2.077186e-002, -2.065786e-002, -2.054348e-002, + -2.042852e-002, -2.031323e-002, -2.019760e-002, -2.008134e-002, -1.996468e-002, -1.984768e-002, -1.973028e-002, -1.961242e-002, -1.949408e-002, + -1.937530e-002, -1.925612e-002, -1.913650e-002, -1.901646e-002, -1.889604e-002, -1.877485e-002, -1.865308e-002, -1.853067e-002, -1.840764e-002, + -1.828390e-002, -1.815937e-002, -1.803439e-002, -1.790873e-002, -1.778231e-002, -1.765525e-002, -1.752768e-002, -1.739967e-002, -1.727136e-002, + -1.714254e-002, -1.701325e-002, -1.688353e-002, -1.675350e-002, -1.662305e-002, -1.649209e-002, -1.636058e-002, -1.622845e-002, -1.609593e-002, + -1.596322e-002, -1.582997e-002, -1.569629e-002, -1.556213e-002, -1.542766e-002, -1.529277e-002, -1.515758e-002, -1.502197e-002, -1.488613e-002, + -1.475005e-002, -1.461369e-002, -1.447692e-002, -1.433997e-002, -1.420284e-002, -1.406561e-002, -1.392816e-002, -1.379033e-002, -1.365225e-002, + -1.351381e-002, -1.337487e-002, -1.323562e-002, -1.309598e-002, -1.295600e-002, -1.281558e-002, -1.267476e-002, -1.253351e-002, -1.239184e-002, + -1.224976e-002, -1.210743e-002, -1.196473e-002, -1.182152e-002, -1.167779e-002, -1.153375e-002, -1.138952e-002, -1.124491e-002, -1.110006e-002, + -1.095480e-002, -1.080910e-002, -1.066301e-002, -1.051664e-002, -1.036987e-002, -1.022263e-002, -1.007491e-002, -9.926863e-003, -9.778299e-003, + -9.629206e-003, -9.479888e-003, -9.330225e-003, -9.180250e-003, -9.029736e-003, -8.878773e-003, -8.727338e-003, -8.575517e-003, -8.423305e-003, + -8.270836e-003, -8.117941e-003, -7.964661e-003, -7.810990e-003, -7.656787e-003, -7.502118e-003, -7.347186e-003, -7.191605e-003, -7.035531e-003, + -6.879039e-003, -6.722144e-003, -6.564889e-003, -6.407487e-003, -6.249413e-003, -6.090871e-003, -5.931831e-003, -5.772405e-003, -5.612440e-003, + -5.452035e-003, -5.291017e-003, -5.129698e-003, -4.967789e-003, -4.805462e-003, -4.642417e-003, -4.478781e-003, -4.314676e-003, -4.149981e-003, + -3.984674e-003, -3.818684e-003, -3.651952e-003, -3.484713e-003, -3.317105e-003, -3.149171e-003, -2.980807e-003, -2.811812e-003, -2.642034e-003, + -2.471873e-003, -2.301294e-003, -2.130406e-003, -1.958804e-003, -1.786640e-003, -1.613924e-003, -1.440865e-003, -1.267425e-003, -1.093432e-003, + -9.188499e-004, -7.437556e-004, -5.681595e-004, -3.918928e-004, -2.152634e-004, -3.840933e-005, 1.388878e-004, 3.167162e-004, 4.945427e-004, + 6.728475e-004, 8.514123e-004, 1.030223e-003, 1.209529e-003, 1.389108e-003, 1.569282e-003, 1.749760e-003, 1.930617e-003, 2.111944e-003, + 2.293420e-003, 2.475297e-003, 2.657542e-003, 2.840094e-003, 3.022974e-003, 3.206289e-003, 3.390227e-003, 3.574669e-003, 3.759420e-003, + 3.944650e-003, 4.129904e-003, 4.315339e-003, 4.501200e-003, 4.687318e-003, 4.873995e-003, 5.061234e-003, 5.248981e-003, 5.437239e-003, + 5.625715e-003, 5.814437e-003, 6.003174e-003, 6.192198e-003, 6.381767e-003, 6.571641e-003, 6.761942e-003, 6.952501e-003, 7.143531e-003, + 7.335128e-003, 7.526899e-003, 7.718937e-003, 7.911239e-003, 8.103503e-003, 8.296160e-003, 8.489272e-003, 8.682967e-003, 8.877145e-003, + 9.071732e-003, 9.266590e-003, 9.461674e-003, 9.657076e-003, 9.852716e-003, 1.004835e-002, 1.024472e-002, 1.044162e-002, 1.063897e-002, + 1.083673e-002, 1.103515e-002, 1.123390e-002, 1.143302e-002, 1.163246e-002, 1.183230e-002, 1.203209e-002, 1.223234e-002, 1.243312e-002, + 1.263429e-002, 1.283589e-002, 1.303791e-002, 1.324075e-002, 1.344416e-002, 1.364830e-002, 1.385287e-002, 1.405732e-002, 1.426189e-002, + 1.446701e-002, 1.467249e-002, 1.487877e-002, 1.508553e-002, 1.529243e-002, 1.549970e-002, 1.570735e-002, 1.591524e-002, 1.612325e-002, + 1.633149e-002, 1.653964e-002, 1.674808e-002, 1.695718e-002, 1.716665e-002, 1.737638e-002, 1.758647e-002, 1.779674e-002, 1.800720e-002, + 1.821791e-002, 1.842861e-002, 1.863974e-002, 1.885109e-002, 1.906238e-002, 1.927413e-002, 1.948622e-002, 1.969859e-002, 1.991134e-002, + 2.012457e-002, 2.033795e-002, 2.055052e-002, 2.076334e-002, 2.097614e-002, 2.118909e-002, 2.140257e-002, 2.161672e-002, 2.183073e-002, + 2.204468e-002, 2.225936e-002, 2.247434e-002, 2.268879e-002, 2.290243e-002, 2.311654e-002, 2.333120e-002, 2.354619e-002, 2.376138e-002, + 2.397638e-002, 2.419221e-002, 2.440812e-002, 2.462415e-002, 2.483977e-002, 2.505449e-002, 2.526956e-002, 2.548532e-002, 2.570141e-002, + 2.591753e-002, 2.613366e-002, 2.635010e-002, 2.656685e-002, 2.678406e-002, 2.700066e-002, 2.721644e-002, 2.743297e-002, 2.765008e-002, + 2.786691e-002, 2.808376e-002, 2.830037e-002, 2.851711e-002, 2.873420e-002, 2.895173e-002, 2.916914e-002, 2.938571e-002, 2.960230e-002, + 2.981918e-002, 3.003642e-002, 3.025388e-002, 3.047129e-002, 3.068832e-002, 3.090576e-002, 3.112341e-002, 3.134130e-002, 3.155832e-002, + 3.177516e-002, 3.199213e-002, 3.220943e-002, 3.242669e-002, 3.264375e-002, 3.286111e-002, 3.307851e-002, 3.329647e-002, 3.351449e-002, + 3.373148e-002, 3.394802e-002, 3.416480e-002, 3.438177e-002, 3.459905e-002, 3.481659e-002, 3.503397e-002, 3.525162e-002, 3.546922e-002, + 3.568671e-002, 3.590381e-002, 3.611971e-002, 3.633615e-002, 3.655233e-002, 3.676820e-002, 3.698390e-002, 3.719954e-002, 3.741477e-002, + 3.762999e-002, 3.784502e-002, 3.805927e-002, 3.827239e-002, 3.848548e-002, 3.869846e-002, 3.891112e-002, 3.912377e-002, 3.933628e-002, + 3.954853e-002, 3.976029e-002, 3.997216e-002, 4.018378e-002, 4.039365e-002, 4.060349e-002, 4.081284e-002, 4.102173e-002, 4.123006e-002, + 4.143837e-002, 4.164652e-002, 4.185434e-002, 4.206080e-002, 4.226710e-002, 4.247193e-002, 4.267628e-002, 4.288001e-002, 4.308339e-002, + 4.328656e-002, 4.348910e-002, 4.369158e-002, 4.389370e-002, 4.409512e-002, 4.429601e-002, 4.449555e-002, 4.469309e-002, 4.489005e-002, + 4.508657e-002, 4.528221e-002, 4.547716e-002, 4.567085e-002, 4.586390e-002, 4.605641e-002, 4.624848e-002, 4.643978e-002, 4.662839e-002, + 4.681564e-002, 4.700235e-002, 4.718857e-002, 4.737394e-002, 4.755836e-002, 4.774268e-002, 4.792619e-002, 4.810848e-002, 4.829004e-002, + 4.846807e-002, 4.864474e-002, 4.882018e-002, 4.899443e-002, 4.916763e-002, 4.933912e-002, 4.950947e-002, 4.967827e-002, 4.984503e-002, + 5.001084e-002, 5.017339e-002, 5.033537e-002, 5.049553e-002, 5.065465e-002, 5.081302e-002, 5.097031e-002, 5.112593e-002, 5.128044e-002, + 5.143252e-002, 5.158333e-002, 5.173167e-002, 5.187521e-002, 5.201761e-002, 5.215924e-002, 5.229866e-002, 5.243674e-002, 5.257302e-002, + 5.270632e-002, 5.283815e-002, 5.296871e-002, 5.309521e-002, 5.321711e-002, 5.333508e-002, 5.345026e-002, 5.356186e-002, 5.367134e-002, + 5.377853e-002, 5.388269e-002, 5.398251e-002, 5.407914e-002, 5.417283e-002, 5.425936e-002, 5.434245e-002, 5.442081e-002, 5.449691e-002, + 5.456919e-002, 5.463745e-002, 5.470278e-002, 5.476401e-002, 5.482035e-002, 5.487145e-002, 5.491466e-002, 5.495483e-002, 5.499070e-002, + 5.502202e-002, 5.504626e-002, 5.506480e-002, 5.507908e-002, 5.508837e-002, 5.509176e-002, 5.509037e-002, 5.508130e-002, 5.506469e-002, + 5.504226e-002, 5.501149e-002, 5.497314e-002, 5.492907e-002, 5.487915e-002, 5.482218e-002, 5.475930e-002, 5.468910e-002, 5.461169e-002, + 5.452295e-002, 5.442757e-002, 5.432349e-002, 5.421169e-002, 5.409030e-002, 5.396074e-002, 5.382497e-002, 5.367777e-002, 5.352149e-002, + 5.335712e-002, 5.318157e-002, 5.299479e-002, 5.279605e-002, 5.258339e-002, 5.235704e-002, 5.211555e-002, 5.186179e-002, 5.159240e-002, + 5.132070e-002, 5.103851e-002, 5.074100e-002, 5.043067e-002, 5.009421e-002, 4.973575e-002, 4.935512e-002, 4.895530e-002, 4.854740e-002, + 4.811690e-002, 4.764371e-002, 4.706587e-002, 4.650470e-002, 4.593629e-002, 4.534170e-002, 4.474376e-002, 4.412589e-002, 4.348096e-002, + 4.279121e-002, 4.205265e-002, 4.128471e-002, 4.048974e-002, 3.877263e-002, 3.788250e-002, 3.701317e-002, 3.610274e-002, 3.513388e-002, + 3.413506e-002, 3.308996e-002, 3.196537e-002, 3.074525e-002, 2.939912e-002, 2.658509e-002, 2.261612e-002, 2.050075e-002, 1.859532e-002, + 1.673826e-002, 1.467582e-002, 1.015304e-002, 7.539017e-003, 5.011997e-003, 2.597830e-003, 0.000000e+000}, + {0.000000e+000, -1.966839e-003, -3.591180e-003, -4.993148e-003, -6.239044e-003, -7.361541e-003, -8.389511e-003, -9.339801e-003, -1.022619e-002, + -1.106100e-002, -1.185191e-002, -1.259061e-002, -1.329160e-002, -1.396254e-002, -1.460719e-002, -1.522683e-002, -1.582421e-002, -1.639999e-002, + -1.695729e-002, -1.749608e-002, -1.801787e-002, -1.851602e-002, -1.899179e-002, -1.945418e-002, -1.990387e-002, -2.034123e-002, -2.076620e-002, + -2.117981e-002, -2.158230e-002, -2.197431e-002, -2.235616e-002, -2.272505e-002, -2.307672e-002, -2.342042e-002, -2.375600e-002, -2.408425e-002, + -2.440564e-002, -2.472019e-002, -2.502813e-002, -2.532968e-002, -2.562541e-002, -2.591441e-002, -2.618794e-002, -2.645629e-002, -2.671985e-002, + -2.697872e-002, -2.723388e-002, -2.748576e-002, -2.773307e-002, -2.797437e-002, -2.821109e-002, -2.844384e-002, -2.866481e-002, -2.887993e-002, + -2.909166e-002, -2.929993e-002, -2.950482e-002, -2.970608e-002, -2.990367e-002, -3.009803e-002, -3.028875e-002, -3.047628e-002, -3.065546e-002, + -3.082844e-002, -3.099849e-002, -3.116545e-002, -3.132970e-002, -3.149063e-002, -3.164834e-002, -3.180300e-002, -3.195478e-002, -3.210327e-002, + -3.224521e-002, -3.237865e-002, -3.250892e-002, -3.263511e-002, -3.275758e-002, -3.287599e-002, -3.299138e-002, -3.310449e-002, -3.321632e-002, + -3.332669e-002, -3.343441e-002, -3.353524e-002, -3.363447e-002, -3.373225e-002, -3.382829e-002, -3.392293e-002, -3.401612e-002, -3.410786e-002, + -3.419812e-002, -3.428680e-002, -3.437388e-002, -3.445333e-002, -3.453058e-002, -3.460547e-002, -3.467862e-002, -3.474991e-002, -3.481909e-002, + -3.488623e-002, -3.495141e-002, -3.501494e-002, -3.507679e-002, -3.513295e-002, -3.518527e-002, -3.523577e-002, -3.528423e-002, -3.533110e-002, + -3.537587e-002, -3.541824e-002, -3.545852e-002, -3.549612e-002, -3.553209e-002, -3.556406e-002, -3.559189e-002, -3.561836e-002, -3.564298e-002, + -3.566592e-002, -3.568723e-002, -3.570689e-002, -3.572536e-002, -3.574251e-002, -3.575848e-002, -3.577326e-002, -3.578487e-002, -3.579626e-002, + -3.580776e-002, -3.581877e-002, -3.582928e-002, -3.583928e-002, -3.584837e-002, -3.585611e-002, -3.586263e-002, -3.586778e-002, -3.586777e-002, + -3.586676e-002, -3.586409e-002, -3.585986e-002, -3.585433e-002, -3.584722e-002, -3.583791e-002, -3.582700e-002, -3.581450e-002, -3.580064e-002, + -3.578282e-002, -3.576323e-002, -3.574255e-002, -3.572062e-002, -3.569772e-002, -3.567434e-002, -3.565025e-002, -3.562539e-002, -3.559966e-002, + -3.557335e-002, -3.554447e-002, -3.551383e-002, -3.548306e-002, -3.545177e-002, -3.541966e-002, -3.538698e-002, -3.535371e-002, -3.531956e-002, + -3.528466e-002, -3.524860e-002, -3.521048e-002, -3.516928e-002, -3.512678e-002, -3.508278e-002, -3.503683e-002, -3.498877e-002, -3.493888e-002, + -3.488733e-002, -3.483457e-002, -3.478079e-002, -3.472583e-002, -3.466768e-002, -3.460757e-002, -3.454567e-002, -3.448217e-002, -3.441719e-002, + -3.435115e-002, -3.428442e-002, -3.421753e-002, -3.415069e-002, -3.408377e-002, -3.401517e-002, -3.394597e-002, -3.387597e-002, -3.380535e-002, + -3.373329e-002, -3.365997e-002, -3.358578e-002, -3.351067e-002, -3.343428e-002, -3.335670e-002, -3.327714e-002, -3.319585e-002, -3.311291e-002, + -3.302862e-002, -3.294302e-002, -3.285606e-002, -3.276788e-002, -3.267851e-002, -3.258794e-002, -3.249626e-002, -3.240283e-002, -3.230723e-002, + -3.220990e-002, -3.211058e-002, -3.200970e-002, -3.190722e-002, -3.180380e-002, -3.169973e-002, -3.159527e-002, -3.149043e-002, -3.138474e-002, + -3.127840e-002, -3.117102e-002, -3.106265e-002, -3.095331e-002, -3.084328e-002, -3.073346e-002, -3.062434e-002, -3.051620e-002, -3.040860e-002, + -3.030118e-002, -3.019367e-002, -3.008615e-002, -2.997844e-002, -2.987045e-002, -2.976202e-002, -2.965337e-002, -2.954412e-002, -2.943475e-002, + -2.932485e-002, -2.921462e-002, -2.910414e-002, -2.899348e-002, -2.888244e-002, -2.877074e-002, -2.865821e-002, -2.854463e-002, -2.843042e-002, + -2.831500e-002, -2.819846e-002, -2.808050e-002, -2.796133e-002, -2.784078e-002, -2.771842e-002, -2.759459e-002, -2.746935e-002, -2.734355e-002, + -2.721726e-002, -2.709102e-002, -2.696490e-002, -2.683883e-002, -2.671293e-002, -2.658809e-002, -2.646330e-002, -2.633841e-002, -2.621351e-002, + -2.608870e-002, -2.596409e-002, -2.583992e-002, -2.571572e-002, -2.559167e-002, -2.546747e-002, -2.534397e-002, -2.522034e-002, -2.509622e-002, + -2.497177e-002, -2.484657e-002, -2.472044e-002, -2.459337e-002, -2.446517e-002, -2.433571e-002, -2.420496e-002, -2.407371e-002, -2.394179e-002, + -2.380879e-002, -2.367483e-002, -2.353998e-002, -2.340412e-002, -2.326734e-002, -2.312955e-002, -2.299089e-002, -2.285134e-002, -2.271140e-002, + -2.257144e-002, -2.243179e-002, -2.229282e-002, -2.215476e-002, -2.201766e-002, -2.188115e-002, -2.174519e-002, -2.160985e-002, -2.147536e-002, + -2.134149e-002, -2.120844e-002, -2.107539e-002, -2.094222e-002, -2.080879e-002, -2.067517e-002, -2.054106e-002, -2.040646e-002, -2.027174e-002, + -2.013647e-002, -2.000085e-002, -1.986546e-002, -1.972941e-002, -1.959259e-002, -1.945515e-002, -1.931705e-002, -1.917788e-002, -1.903774e-002, + -1.889622e-002, -1.875334e-002, -1.860944e-002, -1.846543e-002, -1.832141e-002, -1.817748e-002, -1.803336e-002, -1.788895e-002, -1.774428e-002, + -1.759905e-002, -1.745355e-002, -1.730772e-002, -1.716162e-002, -1.701596e-002, -1.687088e-002, -1.672629e-002, -1.658218e-002, -1.643854e-002, + -1.629489e-002, -1.615134e-002, -1.600750e-002, -1.586341e-002, -1.571896e-002, -1.557404e-002, -1.542910e-002, -1.528392e-002, -1.513796e-002, + -1.499090e-002, -1.484271e-002, -1.469360e-002, -1.454325e-002, -1.439152e-002, -1.423813e-002, -1.408320e-002, -1.392718e-002, -1.376950e-002, + -1.361051e-002, -1.345110e-002, -1.329235e-002, -1.313460e-002, -1.297773e-002, -1.282185e-002, -1.266683e-002, -1.251242e-002, -1.235853e-002, + -1.220519e-002, -1.205217e-002, -1.189931e-002, -1.174701e-002, -1.159466e-002, -1.144194e-002, -1.128911e-002, -1.113615e-002, -1.098294e-002, + -1.082924e-002, -1.067501e-002, -1.051984e-002, -1.036434e-002, -1.020873e-002, -1.005245e-002, -9.895465e-003, -9.738041e-003, -9.579849e-003, + -9.420823e-003, -9.261355e-003, -9.101149e-003, -8.940406e-003, -8.779631e-003, -8.618267e-003, -8.456074e-003, -8.293726e-003, -8.131020e-003, + -7.967837e-003, -7.804873e-003, -7.641933e-003, -7.479290e-003, -7.317384e-003, -7.155562e-003, -6.993296e-003, -6.831157e-003, -6.669236e-003, + -6.507261e-003, -6.345222e-003, -6.183646e-003, -6.022218e-003, -5.860293e-003, -5.698692e-003, -5.536748e-003, -5.374494e-003, -5.212143e-003, + -5.049675e-003, -4.886855e-003, -4.723991e-003, -4.561025e-003, -4.397728e-003, -4.233721e-003, -4.068919e-003, -3.903604e-003, -3.737749e-003, + -3.570730e-003, -3.403513e-003, -3.235868e-003, -3.067657e-003, -2.898930e-003, -2.729813e-003, -2.560363e-003, -2.391116e-003, -2.222493e-003, + -2.054283e-003, -1.886625e-003, -1.718936e-003, -1.551512e-003, -1.384398e-003, -1.217584e-003, -1.051581e-003, -8.858093e-004, -7.201158e-004, + -5.547316e-004, -3.895848e-004, -2.243384e-004, -5.921775e-005, 1.059822e-004, 2.712131e-004, 4.370167e-004, 6.036588e-004, 7.713905e-004, + 9.400408e-004, 1.110214e-003, 1.281555e-003, 1.453370e-003, 1.625212e-003, 1.797671e-003, 1.970354e-003, 2.143343e-003, 2.317256e-003, + 2.492200e-003, 2.668139e-003, 2.844772e-003, 3.021951e-003, 3.199321e-003, 3.376032e-003, 3.551143e-003, 3.724886e-003, 3.897569e-003, + 4.069707e-003, 4.241390e-003, 4.412715e-003, 4.583785e-003, 4.753939e-003, 4.924032e-003, 5.093895e-003, 5.263328e-003, 5.432351e-003, + 5.601433e-003, 5.770470e-003, 5.939402e-003, 6.108045e-003, 6.276087e-003, 6.444282e-003, 6.612206e-003, 6.779708e-003, 6.946687e-003, + 7.113328e-003, 7.279783e-003, 7.446017e-003, 7.612186e-003, 7.778048e-003, 7.943520e-003, 8.108631e-003, 8.273793e-003, 8.438541e-003, + 8.603300e-003, 8.768351e-003, 8.934221e-003, 9.100179e-003, 9.266750e-003, 9.433547e-003, 9.599827e-003, 9.766012e-003, 9.932237e-003, + 1.009857e-002, 1.026489e-002, 1.043112e-002, 1.059739e-002, 1.076378e-002, 1.092990e-002, 1.109568e-002, 1.126048e-002, 1.142460e-002, + 1.158801e-002, 1.175065e-002, 1.191302e-002, 1.207466e-002, 1.223591e-002, 1.239666e-002, 1.255705e-002, 1.271685e-002, 1.287597e-002, + 1.303478e-002, 1.319331e-002, 1.335189e-002, 1.351070e-002, 1.366987e-002, 1.382948e-002, 1.398999e-002, 1.415172e-002, 1.431458e-002, + 1.447820e-002, 1.464091e-002, 1.480350e-002, 1.496528e-002, 1.512640e-002, 1.528697e-002, 1.544713e-002, 1.560691e-002, 1.576672e-002, + 1.592610e-002, 1.608575e-002, 1.624329e-002, 1.639994e-002, 1.655645e-002, 1.671218e-002, 1.686687e-002, 1.702112e-002, 1.717496e-002, + 1.732864e-002, 1.748210e-002, 1.763514e-002, 1.778652e-002, 1.793727e-002, 1.808838e-002, 1.823991e-002, 1.839187e-002, 1.854457e-002, + 1.869797e-002, 1.885149e-002, 1.900568e-002, 1.915961e-002, 1.931247e-002, 1.946403e-002, 1.961523e-002, 1.976594e-002, 1.991587e-002, + 2.006527e-002, 2.021437e-002, 2.036332e-002, 2.051137e-002, 2.065815e-002, 2.080310e-002, 2.094557e-002, 2.108679e-002, 2.122632e-002, + 2.136482e-002, 2.150281e-002, 2.164089e-002, 2.177871e-002, 2.191563e-002, 2.205207e-002, 2.218757e-002, 2.232051e-002, 2.245280e-002, + 2.258438e-002, 2.271551e-002, 2.284626e-002, 2.297666e-002, 2.310622e-002, 2.323546e-002, 2.336454e-002, 2.349375e-002, 2.362020e-002, + 2.374685e-002, 2.387338e-002, 2.400049e-002, 2.412878e-002, 2.425854e-002, 2.438938e-002, 2.452046e-002, 2.465088e-002, 2.478022e-002, + 2.490717e-002, 2.503224e-002, 2.515708e-002, 2.528110e-002, 2.540465e-002, 2.552781e-002, 2.565030e-002, 2.577199e-002, 2.589283e-002, + 2.601235e-002, 2.612937e-002, 2.624383e-002, 2.635685e-002, 2.646946e-002, 2.658120e-002, 2.669239e-002, 2.680348e-002, 2.691442e-002, + 2.702523e-002, 2.713526e-002, 2.724457e-002, 2.735194e-002, 2.746013e-002, 2.756777e-002, 2.767555e-002, 2.778380e-002, 2.789195e-002, + 2.799997e-002, 2.810846e-002, 2.821642e-002, 2.832466e-002, 2.843016e-002, 2.853420e-002, 2.863659e-002, 2.873672e-002, 2.883550e-002, + 2.893277e-002, 2.902819e-002, 2.912165e-002, 2.921310e-002, 2.930243e-002, 2.938811e-002, 2.947135e-002, 2.955326e-002, 2.963369e-002, + 2.971291e-002, 2.979015e-002, 2.986640e-002, 2.994159e-002, 3.001591e-002, 3.008902e-002, 3.016003e-002, 3.022845e-002, 3.029631e-002, + 3.036281e-002, 3.042880e-002, 3.049392e-002, 3.055882e-002, 3.062330e-002, 3.068711e-002, 3.075038e-002, 3.081252e-002, 3.087263e-002, + 3.093255e-002, 3.099232e-002, 3.105140e-002, 3.111032e-002, 3.116886e-002, 3.122663e-002, 3.128395e-002, 3.134037e-002, 3.139612e-002, + 3.144927e-002, 3.150173e-002, 3.155311e-002, 3.160231e-002, 3.164882e-002, 3.169281e-002, 3.173454e-002, 3.177476e-002, 3.181250e-002, + 3.184878e-002, 3.188169e-002, 3.191346e-002, 3.194373e-002, 3.197212e-002, 3.199901e-002, 3.202554e-002, 3.205045e-002, 3.207361e-002, + 3.209555e-002, 3.211650e-002, 3.213518e-002, 3.215201e-002, 3.216799e-002, 3.218322e-002, 3.219685e-002, 3.220924e-002, 3.222039e-002, + 3.223028e-002, 3.224050e-002, 3.225093e-002, 3.226026e-002, 3.226707e-002, 3.227286e-002, 3.227752e-002, 3.228153e-002, 3.228387e-002, + 3.228520e-002, 3.228611e-002, 3.228593e-002, 3.228524e-002, 3.228232e-002, 3.227557e-002, 3.226718e-002, 3.225747e-002, 3.224612e-002, + 3.223264e-002, 3.221771e-002, 3.220026e-002, 3.218160e-002, 3.216118e-002, 3.213981e-002, 3.211469e-002, 3.208782e-002, 3.206007e-002, + 3.203126e-002, 3.200167e-002, 3.197165e-002, 3.194031e-002, 3.190743e-002, 3.187400e-002, 3.183946e-002, 3.180115e-002, 3.175928e-002, + 3.171587e-002, 3.167146e-002, 3.162530e-002, 3.157741e-002, 3.152789e-002, 3.147663e-002, 3.142246e-002, 3.136501e-002, 3.130346e-002, + 3.123699e-002, 3.116953e-002, 3.109936e-002, 3.102779e-002, 3.095295e-002, 3.087560e-002, 3.079631e-002, 3.071589e-002, 3.063382e-002, + 3.054973e-002, 3.045998e-002, 3.036949e-002, 3.027739e-002, 3.018344e-002, 3.008859e-002, 2.999360e-002, 2.989827e-002, 2.980283e-002, + 2.970704e-002, 2.960970e-002, 2.950349e-002, 2.939527e-002, 2.928523e-002, 2.917266e-002, 2.905757e-002, 2.894071e-002, 2.882180e-002, + 2.869959e-002, 2.857575e-002, 2.845008e-002, 2.831566e-002, 2.817780e-002, 2.803695e-002, 2.789301e-002, 2.774673e-002, 2.759854e-002, + 2.744850e-002, 2.729682e-002, 2.714465e-002, 2.699143e-002, 2.683162e-002, 2.666496e-002, 2.649675e-002, 2.632743e-002, 2.615595e-002, + 2.598346e-002, 2.581048e-002, 2.563700e-002, 2.546115e-002, 2.528375e-002, 2.510156e-002, 2.491103e-002, 2.471859e-002, 2.452439e-002, + 2.432807e-002, 2.412877e-002, 2.392610e-002, 2.372015e-002, 2.351167e-002, 2.329986e-002, 2.308513e-002, 2.285558e-002, 2.262340e-002, + 2.238864e-002, 2.215123e-002, 2.191134e-002, 2.166893e-002, 2.142404e-002, 2.117648e-002, 2.092594e-002, 2.067282e-002, 2.040455e-002, + 2.013124e-002, 1.985568e-002, 1.957826e-002, 1.929783e-002, 1.901505e-002, 1.872934e-002, 1.844247e-002, 1.815118e-002, 1.785771e-002, + 1.755223e-002, 1.723858e-002, 1.692252e-002, 1.660536e-002, 1.628761e-002, 1.596799e-002, 1.564605e-002, 1.532118e-002, 1.499559e-002, + 1.466648e-002, 1.432733e-002, 1.397388e-002, 1.361818e-002, 1.326007e-002, 1.290029e-002, 1.253904e-002, 1.217461e-002, 1.180746e-002, + 1.143719e-002, 1.106476e-002, 1.068498e-002, 1.028033e-002, 9.872624e-003, 9.461763e-003, 9.049343e-003, 8.634534e-003, 8.216368e-003, + 7.795413e-003, 7.370769e-003, 6.943927e-003, 6.514843e-003, 6.061363e-003, 5.602106e-003, 5.141048e-003, 4.677731e-003, 4.213148e-003, + 3.746919e-003, 3.281348e-003, 2.812686e-003, 2.341426e-003, 1.867539e-003, 1.372858e-003, 8.657118e-004, 3.559656e-004, -1.565810e-004, + -6.724358e-004, -1.194563e-003, -1.719430e-003, -2.248734e-003, -2.781376e-003, -3.315120e-003, -3.866493e-003, -4.438732e-003, -5.015597e-003, + -5.594566e-003, -6.175830e-003, -6.759827e-003, -7.346464e-003, -7.936339e-003, -8.528588e-003, -9.122071e-003, -9.725078e-003, -1.035609e-002, + -1.098837e-002, -1.162109e-002, -1.225259e-002, -1.288795e-002, -1.352328e-002, -1.415860e-002, -1.479598e-002, -1.543840e-002, -1.608183e-002, + -1.675517e-002, -1.743171e-002, -1.810839e-002, -1.878658e-002, -1.946581e-002, -2.014412e-002, -2.082697e-002, -2.151165e-002, -2.219821e-002, + -2.288776e-002, -2.360226e-002, -2.432553e-002, -2.504945e-002, -2.577604e-002, -2.650347e-002, -2.723117e-002, -2.796133e-002, -2.869360e-002, + -2.942647e-002, -3.016289e-002, -3.091634e-002, -3.168529e-002, -3.245680e-002, -3.322924e-002, -3.400264e-002, -3.477588e-002, -3.554869e-002, + -3.632230e-002, -3.709553e-002, -3.786996e-002, -3.864972e-002, -3.944912e-002, -4.024791e-002, -4.104873e-002, -4.185212e-002, -4.265380e-002, + -4.345909e-002, -4.426347e-002, -4.506652e-002, -4.586677e-002, -4.666974e-002, -4.749296e-002, -4.831592e-002, -4.913525e-002, -4.995770e-002, + -5.078286e-002, -5.160197e-002, -5.242122e-002, -5.323735e-002, -5.404900e-002, -5.485658e-002, -5.567221e-002, -5.648935e-002, -5.730473e-002, + -5.811605e-002, -5.892537e-002, -5.972585e-002, -6.052435e-002, -6.131953e-002, -6.210712e-002, -6.288800e-002, -6.366737e-002, -6.444464e-002, + -6.521699e-002, -6.598779e-002, -6.675729e-002, -6.751857e-002, -6.827863e-002, -6.902994e-002, -6.977257e-002, -7.051074e-002, -7.123471e-002, + -7.194008e-002, -7.264710e-002, -7.334053e-002, -7.402887e-002, -7.471274e-002, -7.539262e-002, -7.607043e-002, -7.672512e-002, -7.736703e-002, + -7.799883e-002, -7.859128e-002, -7.916153e-002, -7.970872e-002, -8.023127e-002, -8.071652e-002, -8.117552e-002, -8.161300e-002, -8.202531e-002, + -8.244722e-002, -8.285887e-002, -8.320079e-002, -8.351897e-002, -8.378219e-002, -8.401684e-002, -8.422844e-002, -8.442230e-002, -8.459404e-002, + -8.471103e-002, -8.468607e-002, -8.432258e-002, -8.403595e-002, -8.378662e-002, -8.346766e-002, -8.314768e-002, -8.278308e-002, -8.236903e-002, + -8.184399e-002, -8.119819e-002, -8.046697e-002, -7.966105e-002, -7.533033e-002, -7.419390e-002, -7.321201e-002, -7.215726e-002, -7.100417e-002, + -6.984035e-002, -6.859966e-002, -6.718541e-002, -6.555212e-002, -6.358785e-002, -5.725615e-002, -4.660865e-002, -4.258397e-002, -3.927632e-002, + -3.603078e-002, -3.204807e-002, -1.945943e-002, -1.376409e-002, -8.588147e-003, -4.240431e-003, 0.000000e+000}, + {0.000000e+000, -3.778279e-003, -6.786376e-003, -9.305227e-003, -1.146437e-002, -1.335215e-002, -1.503918e-002, -1.656443e-002, -1.795737e-002, + -1.927228e-002, -2.049997e-002, -2.166384e-002, -2.275593e-002, -2.377559e-002, -2.473027e-002, -2.562439e-002, -2.645844e-002, -2.723503e-002, + -2.795514e-002, -2.862921e-002, -2.925882e-002, -2.986054e-002, -3.043680e-002, -3.098096e-002, -3.149602e-002, -3.198254e-002, -3.244185e-002, + -3.287617e-002, -3.328632e-002, -3.367069e-002, -3.403109e-002, -3.437539e-002, -3.471309e-002, -3.503462e-002, -3.534025e-002, -3.563049e-002, + -3.590648e-002, -3.616836e-002, -3.641770e-002, -3.665554e-002, -3.688275e-002, -3.710044e-002, -3.731963e-002, -3.752959e-002, -3.773055e-002, + -3.792154e-002, -3.810049e-002, -3.826385e-002, -3.842028e-002, -3.857853e-002, -3.873136e-002, -3.887740e-002, -3.902315e-002, -3.916298e-002, + -3.929706e-002, -3.942464e-002, -3.954672e-002, -3.966221e-002, -3.977213e-002, -3.987679e-002, -3.997667e-002, -4.007119e-002, -4.016473e-002, + -4.025457e-002, -4.033815e-002, -4.041639e-002, -4.048804e-002, -4.055275e-002, -4.061056e-002, -4.066069e-002, -4.070341e-002, -4.073849e-002, + -4.076647e-002, -4.078798e-002, -4.080105e-002, -4.080581e-002, -4.080122e-002, -4.078796e-002, -4.076755e-002, -4.074489e-002, -4.072293e-002, + -4.070347e-002, -4.068556e-002, -4.067113e-002, -4.065914e-002, -4.064936e-002, -4.064094e-002, -4.063291e-002, -4.062728e-002, -4.062356e-002, + -4.062129e-002, -4.061951e-002, -4.061845e-002, -4.061581e-002, -4.061126e-002, -4.060528e-002, -4.059728e-002, -4.058678e-002, -4.057251e-002, + -4.055510e-002, -4.053355e-002, -4.050791e-002, -4.047722e-002, -4.043970e-002, -4.039611e-002, -4.034732e-002, -4.029268e-002, -4.023342e-002, + -4.016961e-002, -4.009870e-002, -4.002226e-002, -3.993963e-002, -3.985184e-002, -3.975855e-002, -3.965877e-002, -3.955583e-002, -3.944882e-002, + -3.933742e-002, -3.922167e-002, -3.910243e-002, -3.898025e-002, -3.885630e-002, -3.873339e-002, -3.861230e-002, -3.849282e-002, -3.837921e-002, + -3.826981e-002, -3.816366e-002, -3.806035e-002, -3.795891e-002, -3.785762e-002, -3.775618e-002, -3.765372e-002, -3.754939e-002, -3.743724e-002, + -3.732328e-002, -3.720648e-002, -3.708665e-002, -3.696350e-002, -3.683761e-002, -3.670773e-002, -3.657465e-002, -3.643898e-002, -3.630097e-002, + -3.615454e-002, -3.600468e-002, -3.585407e-002, -3.570336e-002, -3.555241e-002, -3.540270e-002, -3.525445e-002, -3.510735e-002, -3.496037e-002, + -3.481558e-002, -3.466761e-002, -3.451752e-002, -3.436860e-002, -3.422294e-002, -3.407909e-002, -3.393714e-002, -3.379553e-002, -3.365411e-002, + -3.351250e-002, -3.337086e-002, -3.322462e-002, -3.307061e-002, -3.291406e-002, -3.275470e-002, -3.258993e-002, -3.242004e-002, -3.224683e-002, + -3.207156e-002, -3.189502e-002, -3.171721e-002, -3.153787e-002, -3.134765e-002, -3.115390e-002, -3.095620e-002, -3.075535e-002, -3.055249e-002, + -3.034935e-002, -3.014740e-002, -2.994889e-002, -2.975657e-002, -2.956913e-002, -2.937579e-002, -2.918342e-002, -2.899210e-002, -2.880221e-002, + -2.861228e-002, -2.842260e-002, -2.823329e-002, -2.804370e-002, -2.785353e-002, -2.766238e-002, -2.746323e-002, -2.725889e-002, -2.705256e-002, + -2.684487e-002, -2.663572e-002, -2.642563e-002, -2.621417e-002, -2.600079e-002, -2.578586e-002, -2.556915e-002, -2.534612e-002, -2.511226e-002, + -2.487396e-002, -2.463040e-002, -2.438355e-002, -2.413528e-002, -2.388760e-002, -2.364143e-002, -2.339698e-002, -2.315466e-002, -2.291105e-002, + -2.265831e-002, -2.240438e-002, -2.214980e-002, -2.189457e-002, -2.164068e-002, -2.139037e-002, -2.114575e-002, -2.090801e-002, -2.067663e-002, + -2.044984e-002, -2.021510e-002, -1.998235e-002, -1.975166e-002, -1.952281e-002, -1.929543e-002, -1.906901e-002, -1.884372e-002, -1.862016e-002, + -1.839793e-002, -1.817734e-002, -1.794902e-002, -1.771818e-002, -1.748761e-002, -1.725687e-002, -1.702580e-002, -1.679363e-002, -1.656042e-002, + -1.632509e-002, -1.608786e-002, -1.584773e-002, -1.559841e-002, -1.533764e-002, -1.507097e-002, -1.479962e-002, -1.452627e-002, -1.425381e-002, + -1.398337e-002, -1.371665e-002, -1.345292e-002, -1.319278e-002, -1.293273e-002, -1.266685e-002, -1.240360e-002, -1.214321e-002, -1.188608e-002, + -1.163218e-002, -1.138398e-002, -1.114070e-002, -1.090141e-002, -1.066518e-002, -1.043167e-002, -1.018890e-002, -9.947636e-003, -9.707482e-003, + -9.467606e-003, -9.227907e-003, -8.988176e-003, -8.747325e-003, -8.504651e-003, -8.259597e-003, -8.010941e-003, -7.751194e-003, -7.487530e-003, + -7.222049e-003, -6.955965e-003, -6.689267e-003, -6.421473e-003, -6.152446e-003, -5.881950e-003, -5.610049e-003, -5.337010e-003, -5.056806e-003, + -4.773926e-003, -4.495110e-003, -4.222550e-003, -3.957375e-003, -3.699009e-003, -3.445065e-003, -3.195864e-003, -2.949634e-003, -2.706695e-003, + -2.463269e-003, -2.215863e-003, -1.970884e-003, -1.727762e-003, -1.485847e-003, -1.244941e-003, -1.004227e-003, -7.631903e-004, -5.225846e-004, + -2.824247e-004, -4.058919e-005, 2.088256e-004, 4.579873e-004, 7.083758e-004, 9.599893e-004, 1.213376e-003, 1.469624e-003, 1.729799e-003, + 1.994671e-003, 2.264396e-003, 2.537229e-003, 2.819314e-003, 3.102117e-003, 3.383547e-003, 3.663172e-003, 3.941704e-003, 4.220204e-003, + 4.498431e-003, 4.775988e-003, 5.053130e-003, 5.328684e-003, 5.607015e-003, 5.884909e-003, 6.157092e-003, 6.424691e-003, 6.687545e-003, + 6.947930e-003, 7.205827e-003, 7.461321e-003, 7.715465e-003, 7.968929e-003, 8.224624e-003, 8.484386e-003, 8.744709e-003, 9.005815e-003, + 9.268433e-003, 9.533049e-003, 9.799401e-003, 1.006873e-002, 1.034145e-002, 1.061917e-002, 1.090187e-002, 1.119417e-002, 1.149068e-002, + 1.178929e-002, 1.208710e-002, 1.237983e-002, 1.266487e-002, 1.294242e-002, 1.321323e-002, 1.347745e-002, 1.373491e-002, 1.399231e-002, + 1.424472e-002, 1.449267e-002, 1.473638e-002, 1.497507e-002, 1.521069e-002, 1.544412e-002, 1.567557e-002, 1.590417e-002, 1.613019e-002, + 1.635811e-002, 1.658504e-002, 1.681039e-002, 1.703417e-002, 1.725669e-002, 1.747816e-002, 1.769793e-002, 1.791665e-002, 1.813553e-002, + 1.835449e-002, 1.857572e-002, 1.879873e-002, 1.902092e-002, 1.924127e-002, 1.946022e-002, 1.967725e-002, 1.989383e-002, 2.010889e-002, + 2.032180e-002, 2.053218e-002, 2.074033e-002, 2.094727e-002, 2.114976e-002, 2.134810e-002, 2.154276e-002, 2.173464e-002, 2.192419e-002, + 2.211184e-002, 2.229833e-002, 2.248210e-002, 2.266389e-002, 2.284829e-002, 2.303042e-002, 2.321075e-002, 2.339011e-002, 2.356888e-002, + 2.374685e-002, 2.392377e-002, 2.410171e-002, 2.428013e-002, 2.445969e-002, 2.464310e-002, 2.482880e-002, 2.501589e-002, 2.520466e-002, + 2.539466e-002, 2.558473e-002, 2.577512e-002, 2.596579e-002, 2.615712e-002, 2.634738e-002, 2.653747e-002, 2.672515e-002, 2.690960e-002, + 2.709133e-002, 2.727007e-002, 2.744684e-002, 2.762048e-002, 2.779108e-002, 2.795816e-002, 2.812039e-002, 2.827889e-002, 2.843507e-002, + 2.858845e-002, 2.873918e-002, 2.888721e-002, 2.903349e-002, 2.917947e-002, 2.932488e-002, 2.946949e-002, 2.961416e-002, 2.976193e-002, + 2.991529e-002, 3.007295e-002, 3.023333e-002, 3.039424e-002, 3.055355e-002, 3.071090e-002, 3.086741e-002, 3.102406e-002, 3.118197e-002, + 3.134134e-002, 3.150632e-002, 3.167296e-002, 3.184051e-002, 3.200591e-002, 3.216559e-002, 3.231644e-002, 3.245866e-002, 3.259443e-002, + 3.272530e-002, 3.285083e-002, 3.297491e-002, 3.309552e-002, 3.321129e-002, 3.332461e-002, 3.343451e-002, 3.354172e-002, 3.364481e-002, + 3.374538e-002, 3.384330e-002, 3.393852e-002, 3.403254e-002, 3.412545e-002, 3.421600e-002, 3.430281e-002, 3.438603e-002, 3.446546e-002, + 3.454220e-002, 3.461640e-002, 3.468709e-002, 3.475510e-002, 3.482058e-002, 3.488620e-002, 3.494951e-002, 3.501130e-002, 3.507118e-002, + 3.513017e-002, 3.518758e-002, 3.524496e-002, 3.530133e-002, 3.535658e-002, 3.541029e-002, 3.546530e-002, 3.552015e-002, 3.557269e-002, + 3.562289e-002, 3.567232e-002, 3.572066e-002, 3.576720e-002, 3.581137e-002, 3.585158e-002, 3.588632e-002, 3.591873e-002, 3.594815e-002, + 3.597272e-002, 3.599178e-002, 3.600564e-002, 3.601334e-002, 3.601588e-002, 3.601235e-002, 3.600455e-002, 3.599313e-002, 3.598049e-002, + 3.596644e-002, 3.595112e-002, 3.593366e-002, 3.591373e-002, 3.589394e-002, 3.587401e-002, 3.585543e-002, 3.583887e-002, 3.582519e-002, + 3.581472e-002, 3.580629e-002, 3.579381e-002, 3.577553e-002, 3.575036e-002, 3.571467e-002, 3.566920e-002, 3.561397e-002, 3.555059e-002, + 3.547879e-002, 3.539771e-002, 3.531256e-002, 3.521788e-002, 3.511371e-002, 3.500000e-002, 3.487890e-002, 3.475106e-002, 3.461671e-002, + 3.447704e-002, 3.433351e-002, 3.418622e-002, 3.404142e-002, 3.389589e-002, 3.374873e-002, 3.359978e-002, 3.345138e-002, 3.330475e-002, + 3.316183e-002, 3.301949e-002, 3.287904e-002, 3.274005e-002, 3.260503e-002, 3.247271e-002, 3.234054e-002, 3.220715e-002, 3.207431e-002, + 3.194163e-002, 3.181024e-002, 3.167968e-002, 3.155125e-002, 3.142206e-002, 3.129537e-002, 3.117132e-002, 3.104506e-002, 3.091577e-002, + 3.078345e-002, 3.064711e-002, 3.050877e-002, 3.036833e-002, 3.022656e-002, 3.008333e-002, 2.993928e-002, 2.980142e-002, 2.966119e-002, + 2.951746e-002, 2.937187e-002, 2.922562e-002, 2.907754e-002, 2.892724e-002, 2.877700e-002, 2.862645e-002, 2.847513e-002, 2.833268e-002, + 2.819198e-002, 2.805166e-002, 2.791404e-002, 2.778043e-002, 2.765126e-002, 2.752608e-002, 2.740240e-002, 2.727615e-002, 2.714497e-002, + 2.701512e-002, 2.688412e-002, 2.674930e-002, 2.660927e-002, 2.646620e-002, 2.632217e-002, 2.617570e-002, 2.602662e-002, 2.587258e-002, + 2.571193e-002, 2.554786e-002, 2.538503e-002, 2.521658e-002, 2.504415e-002, 2.486729e-002, 2.468660e-002, 2.450211e-002, 2.431411e-002, + 2.412415e-002, 2.393243e-002, 2.374165e-002, 2.355967e-002, 2.337595e-002, 2.319129e-002, 2.300602e-002, 2.282144e-002, 2.263715e-002, + 2.245256e-002, 2.226703e-002, 2.208080e-002, 2.188962e-002, 2.170272e-002, 2.150401e-002, 2.129037e-002, 2.106234e-002, 2.081796e-002, + 2.056065e-002, 2.029051e-002, 2.000564e-002, 1.970785e-002, 1.939921e-002, 1.908967e-002, 1.877302e-002, 1.844759e-002, 1.811245e-002, + 1.776782e-002, 1.741628e-002, 1.705827e-002, 1.669040e-002, 1.631598e-002, 1.593677e-002, 1.556193e-002, 1.518988e-002, 1.481252e-002, + 1.443024e-002, 1.404298e-002, 1.365170e-002, 1.325897e-002, 1.286554e-002, 1.247225e-002, 1.207856e-002, 1.169218e-002, 1.132497e-002, + 1.096137e-002, 1.059795e-002, 1.023831e-002, 9.880217e-003, 9.523918e-003, 9.170284e-003, 8.821918e-003, 8.476080e-003, 8.134338e-003, + 7.813432e-003, 7.490492e-003, 7.165004e-003, 6.832579e-003, 6.492566e-003, 6.141795e-003, 5.782214e-003, 5.415402e-003, 5.039957e-003, + 4.660200e-003, 4.294224e-003, 3.927508e-003, 3.556450e-003, 3.181603e-003, 2.802940e-003, 2.420006e-003, 2.034341e-003, 1.645073e-003, + 1.253660e-003, 8.571973e-004, 4.747085e-004, 1.021895e-004, -2.733159e-004, -6.490595e-004, -1.025983e-003, -1.404754e-003, -1.785844e-003, + -2.166046e-003, -2.545054e-003, -2.920506e-003, -3.281447e-003, -3.622679e-003, -3.962294e-003, -4.303671e-003, -4.649887e-003, -4.997182e-003, + -5.347716e-003, -5.701594e-003, -6.059682e-003, -6.417756e-003, -6.773829e-003, -7.105894e-003, -7.444165e-003, -7.789014e-003, -8.142199e-003, + -8.501984e-003, -8.868333e-003, -9.240628e-003, -9.619499e-003, -1.000216e-002, -1.038806e-002, -1.074392e-002, -1.109773e-002, -1.145422e-002, + -1.181540e-002, -1.217650e-002, -1.253541e-002, -1.289335e-002, -1.325066e-002, -1.360607e-002, -1.396207e-002, -1.429384e-002, -1.461788e-002, + -1.494387e-002, -1.526627e-002, -1.559258e-002, -1.591848e-002, -1.624901e-002, -1.658510e-002, -1.692640e-002, -1.727298e-002, -1.760473e-002, + -1.791894e-002, -1.823932e-002, -1.856727e-002, -1.890077e-002, -1.924063e-002, -1.958724e-002, -1.993984e-002, -2.030038e-002, -2.066603e-002, + -2.102580e-002, -2.135140e-002, -2.167948e-002, -2.201023e-002, -2.234486e-002, -2.268429e-002, -2.302581e-002, -2.336487e-002, -2.370174e-002, + -2.403963e-002, -2.438464e-002, -2.468537e-002, -2.499296e-002, -2.530747e-002, -2.563513e-002, -2.597161e-002, -2.631502e-002, -2.666266e-002, + -2.701296e-002, -2.737040e-002, -2.773330e-002, -2.806068e-002, -2.838371e-002, -2.871775e-002, -2.905858e-002, -2.940521e-002, -2.975703e-002, + -3.011314e-002, -3.047322e-002, -3.083481e-002, -3.120337e-002, -3.154174e-002, -3.184960e-002, -3.215853e-002, -3.246662e-002, -3.277743e-002, + -3.308532e-002, -3.339424e-002, -3.370158e-002, -3.400935e-002, -3.431619e-002, -3.460076e-002, -3.483435e-002, -3.507512e-002, -3.531981e-002, + -3.557086e-002, -3.583204e-002, -3.609830e-002, -3.637111e-002, -3.664899e-002, -3.693739e-002, -3.722864e-002, -3.745590e-002, -3.769140e-002, + -3.793261e-002, -3.817856e-002, -3.842905e-002, -3.868603e-002, -3.895068e-002, -3.921906e-002, -3.949457e-002, -3.977421e-002, -3.998629e-002, + -4.018775e-002, -4.038910e-002, -4.060019e-002, -4.081298e-002, -4.102751e-002, -4.124583e-002, -4.146780e-002, -4.169144e-002, -4.191705e-002, + -4.208992e-002, -4.222661e-002, -4.236221e-002, -4.249943e-002, -4.263342e-002, -4.277061e-002, -4.290645e-002, -4.304704e-002, -4.319248e-002, + -4.334020e-002, -4.345690e-002, -4.351198e-002, -4.357233e-002, -4.363041e-002, -4.369060e-002, -4.375327e-002, -4.381826e-002, -4.389215e-002, + -4.397413e-002, -4.406673e-002, -4.414843e-002, -4.414210e-002, -4.414140e-002, -4.414826e-002, -4.416194e-002, -4.417435e-002, -4.419210e-002, + -4.421092e-002, -4.423286e-002, -4.425444e-002, -4.427900e-002, -4.419100e-002, -4.409243e-002, -4.399721e-002, -4.390398e-002, -4.380575e-002, + -4.370322e-002, -4.360344e-002, -4.350542e-002, -4.340950e-002, -4.331944e-002, -4.314580e-002, -4.293684e-002, -4.273682e-002, -4.254710e-002, + -4.236491e-002, -4.219374e-002, -4.203339e-002, -4.187979e-002, -4.173122e-002, -4.159195e-002, -4.140052e-002, -4.113887e-002, -4.088439e-002, + -4.063536e-002, -4.038995e-002, -4.014413e-002, -3.990019e-002, -3.966431e-002, -3.943176e-002, -3.919557e-002, -3.892895e-002, -3.854901e-002, + -3.817390e-002, -3.779922e-002, -3.742891e-002, -3.706369e-002, -3.669877e-002, -3.634091e-002, -3.598140e-002, -3.561627e-002, -3.525173e-002, + -3.473044e-002, -3.420338e-002, -3.368344e-002, -3.315862e-002, -3.263199e-002, -3.211346e-002, -3.160120e-002, -3.108926e-002, -3.057891e-002, + -3.006655e-002, -2.943242e-002, -2.876613e-002, -2.810458e-002, -2.745152e-002, -2.680560e-002, -2.616883e-002, -2.553842e-002, -2.491822e-002, + -2.430788e-002, -2.369176e-002, -2.299806e-002, -2.223044e-002, -2.146536e-002, -2.070532e-002, -1.994321e-002, -1.919159e-002, -1.844348e-002, + -1.769618e-002, -1.695097e-002, -1.620312e-002, -1.541079e-002, -1.449686e-002, -1.358570e-002, -1.267635e-002, -1.177188e-002, -1.087594e-002, + -9.972071e-003, -9.075723e-003, -8.186147e-003, -7.298838e-003, -6.403344e-003, -5.335510e-003, -4.277439e-003, -3.222898e-003, -2.171006e-003, + -1.107659e-003, -4.540933e-005, 1.033960e-003, 2.115767e-003, 3.193851e-003, 4.272948e-003, 5.500896e-003, 6.763265e-003, 8.021898e-003, + 9.281093e-003, 1.053455e-002, 1.178868e-002, 1.305516e-002, 1.432125e-002, 1.557356e-002, 1.680887e-002, 1.814650e-002, 1.954310e-002, + 2.094075e-002, 2.235043e-002, 2.375893e-002, 2.515883e-002, 2.655285e-002, 2.793617e-002, 2.929203e-002, 3.063485e-002, 3.202690e-002, + 3.349920e-002, 3.495898e-002, 3.640019e-002, 3.783443e-002, 3.927635e-002, 4.071000e-002, 4.214031e-002, 4.353427e-002, 4.490859e-002, + 4.629790e-002, 4.782648e-002, 4.932651e-002, 5.078565e-002, 5.221646e-002, 5.359495e-002, 5.494292e-002, 5.627183e-002, 5.757462e-002, + 5.888570e-002, 6.021349e-002, 6.165751e-002, 6.310311e-002, 6.453080e-002, 6.597170e-002, 6.743673e-002, 6.893331e-002, 7.038959e-002, + 7.177035e-002, 7.300503e-002, 7.394534e-002, 7.511746e-002, 7.642173e-002, 7.758371e-002, 7.865834e-002, 7.954943e-002, 8.028040e-002, + 8.079938e-002, 8.113260e-002, 8.136380e-002, 8.145903e-002, 7.854080e-002, 7.841114e-002, 7.834592e-002, 7.813521e-002, 7.777400e-002, + 7.745587e-002, 7.708642e-002, 7.650118e-002, 7.566620e-002, 7.440254e-002, 6.815737e-002, 5.930303e-002, 5.581550e-002, 5.273708e-002, + 4.957380e-002, 4.519876e-002, 2.620510e-002, 1.889050e-002, 1.221843e-002, 6.258578e-003, 0.000000e+000}, + {0.000000e+000, -2.360896e-003, -4.274943e-003, -5.905649e-003, -7.368838e-003, -8.715672e-003, -9.986865e-003, -1.127152e-002, -1.255103e-002, + -1.401107e-002, -1.540896e-002, -1.661416e-002, -1.776548e-002, -1.887420e-002, -1.992056e-002, -2.089192e-002, -2.177797e-002, -2.255967e-002, + -2.323417e-002, -2.385498e-002, -2.443536e-002, -2.494330e-002, -2.538212e-002, -2.579366e-002, -2.618314e-002, -2.655469e-002, -2.691086e-002, + -2.725425e-002, -2.758442e-002, -2.790527e-002, -2.821810e-002, -2.850432e-002, -2.874125e-002, -2.897076e-002, -2.919356e-002, -2.941006e-002, + -2.962066e-002, -2.982512e-002, -3.002458e-002, -3.021412e-002, -3.039782e-002, -3.057600e-002, -3.071039e-002, -3.085683e-002, -3.101921e-002, + -3.120300e-002, -3.146204e-002, -3.188786e-002, -3.229566e-002, -3.250488e-002, -3.264314e-002, -3.276630e-002, -3.283809e-002, -3.289057e-002, + -3.293160e-002, -3.296372e-002, -3.298658e-002, -3.300410e-002, -3.301314e-002, -3.301770e-002, -3.301732e-002, -3.301279e-002, -3.298054e-002, + -3.292965e-002, -3.287543e-002, -3.281661e-002, -3.275425e-002, -3.268864e-002, -3.261840e-002, -3.254792e-002, -3.247575e-002, -3.240479e-002, + -3.231332e-002, -3.219677e-002, -3.207115e-002, -3.193980e-002, -3.180072e-002, -3.165655e-002, -3.150798e-002, -3.136203e-002, -3.122037e-002, + -3.108775e-002, -3.095780e-002, -3.081271e-002, -3.067594e-002, -3.054767e-002, -3.042727e-002, -3.031475e-002, -3.021188e-002, -3.012629e-002, + -3.005729e-002, -3.000457e-002, -2.996459e-002, -2.991192e-002, -2.986609e-002, -2.982627e-002, -2.979014e-002, -2.975685e-002, -2.972446e-002, + -2.969059e-002, -2.965303e-002, -2.961156e-002, -2.956256e-002, -2.948764e-002, -2.939490e-002, -2.928878e-002, -2.916809e-002, -2.903948e-002, + -2.890468e-002, -2.876563e-002, -2.862065e-002, -2.846906e-002, -2.831303e-002, -2.814246e-002, -2.795660e-002, -2.776824e-002, -2.757558e-002, + -2.737743e-002, -2.717653e-002, -2.697106e-002, -2.676146e-002, -2.655060e-002, -2.633791e-002, -2.612573e-002, -2.590844e-002, -2.569521e-002, + -2.548775e-002, -2.528486e-002, -2.508429e-002, -2.488695e-002, -2.469060e-002, -2.449273e-002, -2.429495e-002, -2.409691e-002, -2.388210e-002, + -2.366506e-002, -2.344623e-002, -2.322420e-002, -2.299895e-002, -2.277070e-002, -2.253783e-002, -2.230124e-002, -2.206187e-002, -2.181865e-002, + -2.156294e-002, -2.130231e-002, -2.104026e-002, -2.077838e-002, -2.051815e-002, -2.025831e-002, -2.000006e-002, -1.974314e-002, -1.948838e-002, + -1.923539e-002, -1.897857e-002, -1.871924e-002, -1.846154e-002, -1.820525e-002, -1.795018e-002, -1.769669e-002, -1.744302e-002, -1.718785e-002, + -1.693206e-002, -1.667613e-002, -1.641509e-002, -1.614473e-002, -1.587220e-002, -1.559493e-002, -1.531124e-002, -1.502021e-002, -1.472502e-002, + -1.442649e-002, -1.412617e-002, -1.382395e-002, -1.352061e-002, -1.320828e-002, -1.289145e-002, -1.256965e-002, -1.224447e-002, -1.191597e-002, + -1.158634e-002, -1.125793e-002, -1.093432e-002, -1.061726e-002, -1.030640e-002, -9.993893e-003, -9.682586e-003, -9.373169e-003, -9.066173e-003, + -8.760538e-003, -8.455617e-003, -8.151591e-003, -7.847199e-003, -7.543073e-003, -7.237269e-003, -6.928794e-003, -6.617495e-003, -6.305938e-003, + -5.993645e-003, -5.679285e-003, -5.364366e-003, -5.048492e-003, -4.731993e-003, -4.414662e-003, -4.095669e-003, -3.774429e-003, -3.446633e-003, + -3.114165e-003, -2.776847e-003, -2.437561e-003, -2.099448e-003, -1.764101e-003, -1.432330e-003, -1.104074e-003, -7.790922e-004, -4.543868e-004, + -1.275020e-004, 2.002446e-004, 5.283561e-004, 8.574387e-004, 1.185628e-003, 1.510524e-003, 1.830254e-003, 2.141882e-003, 2.447411e-003, + 2.747272e-003, 3.044848e-003, 3.338406e-003, 3.629571e-003, 3.919979e-003, 4.208891e-003, 4.496244e-003, 4.781103e-003, 5.064221e-003, + 5.345375e-003, 5.624560e-003, 5.900758e-003, 6.175870e-003, 6.450757e-003, 6.726506e-003, 7.002304e-003, 7.277071e-003, 7.552118e-003, + 7.828428e-003, 8.105385e-003, 8.384147e-003, 8.664888e-003, 8.950609e-003, 9.243485e-003, 9.542554e-003, 9.841377e-003, 1.013791e-002, + 1.043041e-002, 1.071721e-002, 1.099986e-002, 1.127745e-002, 1.154890e-002, 1.181582e-002, 1.207918e-002, 1.233816e-002, 1.259130e-002, + 1.283850e-002, 1.307568e-002, 1.330593e-002, 1.352991e-002, 1.374851e-002, 1.396283e-002, 1.416982e-002, 1.437312e-002, 1.457440e-002, + 1.477219e-002, 1.496488e-002, 1.515135e-002, 1.533460e-002, 1.551404e-002, 1.569097e-002, 1.586731e-002, 1.603821e-002, 1.620491e-002, + 1.636772e-002, 1.652547e-002, 1.667613e-002, 1.682034e-002, 1.695802e-002, 1.709067e-002, 1.721987e-002, 1.734386e-002, 1.746008e-002, + 1.756567e-002, 1.766106e-002, 1.774322e-002, 1.781093e-002, 1.786764e-002, 1.791514e-002, 1.795522e-002, 1.798977e-002, 1.801989e-002, + 1.804206e-002, 1.805476e-002, 1.806296e-002, 1.806841e-002, 1.807116e-002, 1.807273e-002, 1.807456e-002, 1.807728e-002, 1.808166e-002, + 1.808750e-002, 1.809402e-002, 1.809617e-002, 1.810150e-002, 1.811140e-002, 1.812623e-002, 1.814668e-002, 1.817493e-002, 1.821286e-002, + 1.826138e-002, 1.832033e-002, 1.838854e-002, 1.845877e-002, 1.853527e-002, 1.861878e-002, 1.870843e-002, 1.880372e-002, 1.890667e-002, + 1.901622e-002, 1.913154e-002, 1.925391e-002, 1.938211e-002, 1.950902e-002, 1.963660e-002, 1.976425e-002, 1.989111e-002, 2.001646e-002, + 2.014201e-002, 2.026922e-002, 2.039623e-002, 2.052702e-002, 2.066212e-002, 2.079641e-002, 2.093092e-002, 2.107065e-002, 2.121470e-002, + 2.136350e-002, 2.151766e-002, 2.167667e-002, 2.184260e-002, 2.201580e-002, 2.219621e-002, 2.238208e-002, 2.256652e-002, 2.275779e-002, + 2.295384e-002, 2.315021e-002, 2.334222e-002, 2.352593e-002, 2.370248e-002, 2.387301e-002, 2.403658e-002, 2.419374e-002, 2.433362e-002, + 2.446694e-002, 2.459478e-002, 2.471821e-002, 2.483618e-002, 2.495028e-002, 2.506116e-002, 2.516786e-002, 2.527170e-002, 2.537354e-002, + 2.546428e-002, 2.554905e-002, 2.563264e-002, 2.571565e-002, 2.579792e-002, 2.587973e-002, 2.596072e-002, 2.604172e-002, 2.612296e-002, + 2.620385e-002, 2.627993e-002, 2.634934e-002, 2.641793e-002, 2.648479e-002, 2.654848e-002, 2.660933e-002, 2.666906e-002, 2.672773e-002, + 2.678375e-002, 2.683798e-002, 2.688663e-002, 2.692371e-002, 2.695753e-002, 2.698646e-002, 2.701216e-002, 2.703694e-002, 2.706029e-002, + 2.708222e-002, 2.710441e-002, 2.712650e-002, 2.714839e-002, 2.715769e-002, 2.716565e-002, 2.717228e-002, 2.717899e-002, 2.718524e-002, + 2.719172e-002, 2.719880e-002, 2.720865e-002, 2.722124e-002, 2.723657e-002, 2.724388e-002, 2.725178e-002, 2.726217e-002, 2.727563e-002, + 2.728934e-002, 2.730537e-002, 2.732247e-002, 2.733968e-002, 2.735655e-002, 2.737263e-002, 2.737971e-002, 2.737885e-002, 2.737602e-002, + 2.737024e-002, 2.736166e-002, 2.735150e-002, 2.733956e-002, 2.732451e-002, 2.730438e-002, 2.727793e-002, 2.724206e-002, 2.719246e-002, + 2.713968e-002, 2.708444e-002, 2.702614e-002, 2.696616e-002, 2.690682e-002, 2.684817e-002, 2.678989e-002, 2.673148e-002, 2.667604e-002, + 2.661577e-002, 2.656029e-002, 2.650729e-002, 2.645613e-002, 2.640410e-002, 2.635084e-002, 2.629706e-002, 2.624410e-002, 2.619299e-002, + 2.614627e-002, 2.609591e-002, 2.604961e-002, 2.600482e-002, 2.595933e-002, 2.590764e-002, 2.584154e-002, 2.576003e-002, 2.566730e-002, + 2.556480e-002, 2.545335e-002, 2.532731e-002, 2.519074e-002, 2.504769e-002, 2.489727e-002, 2.474134e-002, 2.458094e-002, 2.441477e-002, + 2.424408e-002, 2.406926e-002, 2.389065e-002, 2.370325e-002, 2.350808e-002, 2.330940e-002, 2.310516e-002, 2.289756e-002, 2.268670e-002, + 2.247216e-002, 2.225307e-002, 2.203178e-002, 2.180787e-002, 2.158273e-002, 2.134920e-002, 2.111582e-002, 2.088052e-002, 2.064596e-002, + 2.041193e-002, 2.018045e-002, 1.995076e-002, 1.972457e-002, 1.950034e-002, 1.927617e-002, 1.904591e-002, 1.881836e-002, 1.859350e-002, + 1.837115e-002, 1.815161e-002, 1.793664e-002, 1.772397e-002, 1.751181e-002, 1.730085e-002, 1.709166e-002, 1.687527e-002, 1.665797e-002, + 1.644159e-002, 1.622558e-002, 1.600662e-002, 1.578945e-002, 1.557444e-002, 1.535957e-002, 1.514813e-002, 1.493803e-002, 1.472395e-002, + 1.450698e-002, 1.429042e-002, 1.407539e-002, 1.386222e-002, 1.365111e-002, 1.344279e-002, 1.324022e-002, 1.304365e-002, 1.285188e-002, + 1.266125e-002, 1.246627e-002, 1.226600e-002, 1.206146e-002, 1.183758e-002, 1.156838e-002, 1.125646e-002, 1.090574e-002, 1.051599e-002, + 1.008868e-002, 9.627202e-003, 9.128324e-003, 8.597165e-003, 8.035804e-003, 7.447901e-003, 6.835471e-003, 6.202072e-003, 5.551010e-003, + 4.884796e-003, 4.206815e-003, 3.519273e-003, 2.818272e-003, 2.110670e-003, 1.401120e-003, 6.903936e-004, -1.638198e-005, -7.135974e-004, + -1.398900e-003, -2.074787e-003, -2.737984e-003, -3.384653e-003, -4.020880e-003, -4.640462e-003, -5.241373e-003, -5.823547e-003, -6.383119e-003, + -6.919046e-003, -7.425275e-003, -7.902649e-003, -8.346696e-003, -8.756090e-003, -9.132169e-003, -9.479906e-003, -9.791314e-003, -1.007961e-002, + -1.035880e-002, -1.063062e-002, -1.089370e-002, -1.114753e-002, -1.139318e-002, -1.163197e-002, -1.186163e-002, -1.208560e-002, -1.230246e-002, + -1.251026e-002, -1.271252e-002, -1.290469e-002, -1.308656e-002, -1.325855e-002, -1.342436e-002, -1.357860e-002, -1.372395e-002, -1.386068e-002, + -1.398954e-002, -1.410597e-002, -1.421204e-002, -1.430540e-002, -1.438351e-002, -1.444606e-002, -1.449812e-002, -1.454279e-002, -1.458684e-002, + -1.463257e-002, -1.467814e-002, -1.472142e-002, -1.476059e-002, -1.479612e-002, -1.482843e-002, -1.485531e-002, -1.487635e-002, -1.489828e-002, + -1.492339e-002, -1.495162e-002, -1.498154e-002, -1.500862e-002, -1.503822e-002, -1.506732e-002, -1.509371e-002, -1.511928e-002, -1.514425e-002, + -1.516862e-002, -1.518908e-002, -1.521013e-002, -1.522560e-002, -1.523903e-002, -1.524846e-002, -1.524989e-002, -1.524585e-002, -1.524007e-002, + -1.523235e-002, -1.521934e-002, -1.520238e-002, -1.518278e-002, -1.517016e-002, -1.516821e-002, -1.518027e-002, -1.520240e-002, -1.523723e-002, + -1.528238e-002, -1.534124e-002, -1.541303e-002, -1.549901e-002, -1.559409e-002, -1.569997e-002, -1.581460e-002, -1.594058e-002, -1.607554e-002, + -1.621647e-002, -1.636636e-002, -1.652340e-002, -1.668474e-002, -1.685008e-002, -1.702049e-002, -1.720293e-002, -1.739779e-002, -1.760038e-002, + -1.780702e-002, -1.801748e-002, -1.823522e-002, -1.845753e-002, -1.868053e-002, -1.890412e-002, -1.912907e-002, -1.935389e-002, -1.957958e-002, + -1.980122e-002, -2.001761e-002, -2.023081e-002, -2.043903e-002, -2.064446e-002, -2.084856e-002, -2.104685e-002, -2.123686e-002, -2.142329e-002, + -2.161328e-002, -2.180088e-002, -2.198794e-002, -2.217370e-002, -2.236667e-002, -2.256801e-002, -2.277625e-002, -2.299049e-002, -2.320262e-002, + -2.341636e-002, -2.363713e-002, -2.385964e-002, -2.407775e-002, -2.429353e-002, -2.450728e-002, -2.472070e-002, -2.493195e-002, -2.513812e-002, + -2.533786e-002, -2.553328e-002, -2.572258e-002, -2.590886e-002, -2.608742e-002, -2.626136e-002, -2.642732e-002, -2.658576e-002, -2.673664e-002, + -2.687400e-002, -2.699704e-002, -2.710480e-002, -2.720365e-002, -2.729421e-002, -2.737359e-002, -2.745109e-002, -2.752881e-002, -2.761109e-002, + -2.769737e-002, -2.778342e-002, -2.787029e-002, -2.795776e-002, -2.804384e-002, -2.813663e-002, -2.823293e-002, -2.833815e-002, -2.844657e-002, + -2.856556e-002, -2.869072e-002, -2.881630e-002, -2.894820e-002, -2.908186e-002, -2.921889e-002, -2.936393e-002, -2.951308e-002, -2.966011e-002, + -2.982728e-002, -3.004358e-002, -3.030748e-002, -3.061102e-002, -3.095579e-002, -3.133872e-002, -3.175770e-002, -3.221663e-002, -3.270650e-002, + -3.321987e-002, -3.375717e-002, -3.431416e-002, -3.489001e-002, -3.547975e-002, -3.608464e-002, -3.670345e-002, -3.733331e-002, -3.797427e-002, + -3.862149e-002, -3.927308e-002, -3.992326e-002, -4.057412e-002, -4.121805e-002, -4.184837e-002, -4.246811e-002, -4.306527e-002, -4.364204e-002, + -4.419689e-002, -4.472849e-002, -4.522967e-002, -4.569501e-002, -4.612203e-002, -4.650379e-002, -4.684126e-002, -4.712785e-002, -4.735643e-002, + -4.752612e-002, -4.763849e-002, -4.770748e-002, -4.771900e-002, -4.767239e-002, -4.755962e-002, -4.738832e-002, -4.719806e-002, -4.699385e-002, + -4.676563e-002, -4.652292e-002, -4.626246e-002, -4.599168e-002, -4.570958e-002, -4.542004e-002, -4.511216e-002, -4.478959e-002, -4.444727e-002, + -4.408761e-002, -4.371019e-002, -4.331740e-002, -4.290329e-002, -4.248020e-002, -4.204135e-002, -4.157690e-002, -4.108876e-002, -4.057962e-002, + -4.005352e-002, -3.950325e-002, -3.892208e-002, -3.831182e-002, -3.767926e-002, -3.703787e-002, -3.638723e-002, -3.570871e-002, -3.500656e-002, + -3.428858e-002, -3.355839e-002, -3.282371e-002, -3.207244e-002, -3.131090e-002, -3.054011e-002, -2.976208e-002, -2.898263e-002, -2.818930e-002, + -2.738675e-002, -2.657148e-002, -2.574571e-002, -2.491286e-002, -2.406676e-002, -2.321062e-002, -2.234741e-002, -2.146643e-002, -2.058671e-002, + -1.969661e-002, -1.880264e-002, -1.789329e-002, -1.697492e-002, -1.604023e-002, -1.509356e-002, -1.413334e-002, -1.316553e-002, -1.219498e-002, + -1.121350e-002, -1.022420e-002, -9.218249e-003, -8.194948e-003, -7.166028e-003, -6.118933e-003, -5.062977e-003, -4.002526e-003, -2.938600e-003, + -1.874053e-003, -8.124279e-004, 2.426266e-004, 1.299519e-003, 2.356924e-003, 3.423349e-003, 4.494925e-003, 5.567379e-003, 6.637724e-003, + 7.705697e-003, 8.774098e-003, 9.833285e-003, 1.086581e-002, 1.189999e-002, 1.292715e-002, 1.395299e-002, 1.497549e-002, 1.600173e-002, + 1.703227e-002, 1.805900e-002, 1.908315e-002, 2.011093e-002, 2.112233e-002, 2.213348e-002, 2.314845e-002, 2.416172e-002, 2.517577e-002, + 2.618429e-002, 2.719920e-002, 2.821283e-002, 2.923392e-002, 3.025381e-002, 3.124036e-002, 3.220577e-002, 3.316308e-002, 3.410222e-002, + 3.502958e-002, 3.594870e-002, 3.685940e-002, 3.774501e-002, 3.862478e-002, 3.950079e-002, 4.035807e-002, 4.118645e-002, 4.201368e-002, + 4.283345e-002, 4.364499e-002, 4.444504e-002, 4.522483e-002, 4.600111e-002, 4.677178e-002, 4.752748e-002, 4.826989e-002, 4.897104e-002, + 4.966668e-002, 5.035375e-002, 5.102643e-002, 5.169880e-002, 5.237010e-002, 5.304156e-002, 5.370162e-002, 5.436628e-002, 5.501398e-002, + 5.560999e-002, 5.619190e-002, 5.677848e-002, 5.734037e-002, 5.788572e-002, 5.841639e-002, 5.893213e-002, 5.943382e-002, 5.992121e-002, + 6.039367e-002, 6.080280e-002, 6.117986e-002, 6.150898e-002, 6.180408e-002, 6.205528e-002, 6.228023e-002, 6.246853e-002, 6.263032e-002, + 6.276013e-002, 6.284891e-002, 6.287447e-002, 6.282524e-002, 6.272784e-002, 6.258828e-002, 6.241053e-002, 6.222478e-002, 6.201195e-002, + 6.177584e-002, 6.151671e-002, 6.123411e-002, 6.090018e-002, 6.048345e-002, 6.003673e-002, 5.956052e-002, 5.905527e-002, 5.853810e-002, + 5.799391e-002, 5.740801e-002, 5.679262e-002, 5.615924e-002, 5.548418e-002, 5.470384e-002, 5.389553e-002, 5.305557e-002, 5.214974e-002, + 5.116006e-002, 5.012557e-002, 4.902787e-002, 4.785994e-002, 4.663504e-002, 4.536721e-002, 4.395278e-002, 4.248978e-002, 4.099954e-002, + 3.947331e-002, 3.791131e-002, 3.633650e-002, 3.470608e-002, 3.305575e-002, 3.140630e-002, 2.974680e-002, 2.798749e-002, 2.616536e-002, + 2.434344e-002, 2.247793e-002, 2.057273e-002, 1.865802e-002, 1.672675e-002, 1.478296e-002, 1.284464e-002, 1.090682e-002, 8.908942e-003, + 6.784438e-003, 4.679112e-003, 2.573731e-003, 4.510803e-004, -1.689202e-003, -3.864831e-003, -6.055147e-003, -8.218578e-003, -1.038528e-002, + -1.258716e-002, -1.503817e-002, -1.742672e-002, -1.978906e-002, -2.213819e-002, -2.441817e-002, -2.674576e-002, -2.907635e-002, -3.139250e-002, + -3.364778e-002, -3.592420e-002, -3.835364e-002, -4.077078e-002, -4.329452e-002, -4.592237e-002, -4.866156e-002, -5.149681e-002, -5.426273e-002, + -5.699712e-002, -5.989997e-002, -6.346216e-002, -6.690931e-002, -7.034904e-002, -7.359218e-002, -7.650300e-002, -7.901830e-002, -8.141704e-002, + -8.367779e-002, -8.590537e-002, -8.815674e-002, -9.019702e-002, -9.961888e-002, -1.015927e-001, -1.026727e-001, -1.034023e-001, -1.038345e-001, + -1.044770e-001, -1.051726e-001, -1.057127e-001, -1.059350e-001, -1.060081e-001, -1.063140e-001, -1.128114e-001, -1.102135e-001, -1.078385e-001, + -1.045745e-001, -9.780112e-002, -4.743233e-002, -3.300751e-002, -2.056588e-002, -1.011251e-002, 0.000000e+000}, + {0.000000e+000, -2.773723e-003, -4.854843e-003, -6.537040e-003, -7.956489e-003, -9.196058e-003, -1.031661e-002, -1.132384e-002, -1.225372e-002, + -1.323209e-002, -1.418436e-002, -1.500351e-002, -1.577165e-002, -1.650604e-002, -1.720260e-002, -1.785847e-002, -1.846182e-002, -1.901137e-002, + -1.950023e-002, -1.995570e-002, -2.038386e-002, -2.073590e-002, -2.102544e-002, -2.129326e-002, -2.154253e-002, -2.178024e-002, -2.200291e-002, + -2.221362e-002, -2.241197e-002, -2.259943e-002, -2.277387e-002, -2.291684e-002, -2.301039e-002, -2.310126e-002, -2.318915e-002, -2.326970e-002, + -2.334448e-002, -2.341005e-002, -2.347200e-002, -2.353164e-002, -2.358840e-002, -2.363768e-002, -2.363332e-002, -2.362303e-002, -2.360548e-002, + -2.358069e-002, -2.353762e-002, -2.345716e-002, -2.337799e-002, -2.334174e-002, -2.331857e-002, -2.329896e-002, -2.324169e-002, -2.318121e-002, + -2.312415e-002, -2.306789e-002, -2.301328e-002, -2.296062e-002, -2.290671e-002, -2.285306e-002, -2.280327e-002, -2.275406e-002, -2.268464e-002, + -2.260119e-002, -2.251352e-002, -2.242769e-002, -2.233479e-002, -2.223801e-002, -2.213990e-002, -2.203381e-002, -2.192517e-002, -2.181069e-002, + -2.168435e-002, -2.153639e-002, -2.138741e-002, -2.123439e-002, -2.108018e-002, -2.092026e-002, -2.075997e-002, -2.059931e-002, -2.044242e-002, + -2.029065e-002, -2.013830e-002, -1.996660e-002, -1.979834e-002, -1.963692e-002, -1.947518e-002, -1.931697e-002, -1.916456e-002, -1.902057e-002, + -1.888423e-002, -1.875235e-002, -1.863058e-002, -1.848985e-002, -1.834862e-002, -1.821183e-002, -1.807312e-002, -1.793880e-002, -1.780330e-002, + -1.766932e-002, -1.753524e-002, -1.739700e-002, -1.725468e-002, -1.709396e-002, -1.692083e-002, -1.674136e-002, -1.655437e-002, -1.636301e-002, + -1.617213e-002, -1.597793e-002, -1.578063e-002, -1.558220e-002, -1.538167e-002, -1.516939e-002, -1.494237e-002, -1.471584e-002, -1.448789e-002, + -1.425976e-002, -1.403041e-002, -1.379895e-002, -1.356745e-002, -1.333578e-002, -1.310682e-002, -1.287389e-002, -1.263232e-002, -1.239941e-002, + -1.217103e-002, -1.194886e-002, -1.173000e-002, -1.151290e-002, -1.129883e-002, -1.108733e-002, -1.087525e-002, -1.066307e-002, -1.043565e-002, + -1.020888e-002, -9.983225e-003, -9.758086e-003, -9.531589e-003, -9.304608e-003, -9.075345e-003, -8.845927e-003, -8.618173e-003, -8.388267e-003, + -8.143587e-003, -7.897512e-003, -7.652927e-003, -7.409256e-003, -7.169066e-003, -6.930755e-003, -6.693640e-003, -6.458103e-003, -6.226913e-003, + -5.999515e-003, -5.767580e-003, -5.530633e-003, -5.294449e-003, -5.063163e-003, -4.833209e-003, -4.605478e-003, -4.379075e-003, -4.154908e-003, + -3.930795e-003, -3.709459e-003, -3.485946e-003, -3.257005e-003, -3.025861e-003, -2.792860e-003, -2.557654e-003, -2.320999e-003, -2.083722e-003, + -1.847119e-003, -1.610125e-003, -1.374844e-003, -1.139747e-003, -8.959992e-004, -6.515005e-004, -4.064176e-004, -1.605657e-004, 8.614537e-005, + 3.322379e-004, 5.772164e-004, 8.177365e-004, 1.052941e-003, 1.283767e-003, 1.516818e-003, 1.747570e-003, 1.976353e-003, 2.203168e-003, + 2.427776e-003, 2.650000e-003, 2.869864e-003, 3.090004e-003, 3.309298e-003, 3.527853e-003, 3.747580e-003, 3.966900e-003, 4.186325e-003, + 4.406693e-003, 4.627078e-003, 4.846718e-003, 5.065333e-003, 5.285225e-003, 5.504989e-003, 5.724243e-003, 5.943804e-003, 6.164628e-003, + 6.387680e-003, 6.612736e-003, 6.838571e-003, 7.061752e-003, 7.281200e-003, 7.497617e-003, 7.712072e-003, 7.924858e-003, 8.136520e-003, + 8.347502e-003, 8.557643e-003, 8.766034e-003, 8.973214e-003, 9.176951e-003, 9.376415e-003, 9.570995e-003, 9.760186e-003, 9.943433e-003, + 1.012192e-002, 1.029510e-002, 1.046271e-002, 1.062658e-002, 1.078758e-002, 1.094585e-002, 1.110277e-002, 1.125601e-002, 1.140530e-002, + 1.155114e-002, 1.169401e-002, 1.183166e-002, 1.196807e-002, 1.210191e-002, 1.223464e-002, 1.236576e-002, 1.249559e-002, 1.262561e-002, + 1.275491e-002, 1.288140e-002, 1.300689e-002, 1.313187e-002, 1.325814e-002, 1.338813e-002, 1.352184e-002, 1.365515e-002, 1.378680e-002, + 1.391487e-002, 1.403752e-002, 1.415750e-002, 1.427464e-002, 1.438744e-002, 1.449782e-002, 1.460584e-002, 1.471094e-002, 1.481179e-002, + 1.490885e-002, 1.499748e-002, 1.508268e-002, 1.516277e-002, 1.523994e-002, 1.531415e-002, 1.538215e-002, 1.544761e-002, 1.551175e-002, + 1.557406e-002, 1.563298e-002, 1.568836e-002, 1.574181e-002, 1.579332e-002, 1.584306e-002, 1.589336e-002, 1.594036e-002, 1.598541e-002, + 1.602722e-002, 1.606434e-002, 1.609903e-002, 1.613283e-002, 1.616610e-002, 1.619901e-002, 1.623229e-002, 1.626467e-002, 1.629456e-002, + 1.631907e-002, 1.633794e-002, 1.635046e-002, 1.635541e-002, 1.635449e-002, 1.634876e-002, 1.633922e-002, 1.632753e-002, 1.631524e-002, + 1.629836e-002, 1.627631e-002, 1.625073e-002, 1.622310e-002, 1.619299e-002, 1.616208e-002, 1.613029e-002, 1.609781e-002, 1.606556e-002, + 1.603184e-002, 1.599701e-002, 1.596184e-002, 1.592570e-002, 1.589060e-002, 1.585592e-002, 1.582230e-002, 1.579127e-002, 1.576421e-002, + 1.574032e-002, 1.571750e-002, 1.569755e-002, 1.567980e-002, 1.566457e-002, 1.565092e-002, 1.563728e-002, 1.562261e-002, 1.560835e-002, + 1.559357e-002, 1.557705e-002, 1.556064e-002, 1.554346e-002, 1.552706e-002, 1.550919e-002, 1.548602e-002, 1.545850e-002, 1.542740e-002, + 1.539396e-002, 1.535685e-002, 1.531549e-002, 1.527369e-002, 1.523174e-002, 1.519097e-002, 1.515373e-002, 1.511799e-002, 1.508243e-002, + 1.504638e-002, 1.501094e-002, 1.497452e-002, 1.493944e-002, 1.490661e-002, 1.487576e-002, 1.484711e-002, 1.482622e-002, 1.480654e-002, + 1.478655e-002, 1.476559e-002, 1.474008e-002, 1.470736e-002, 1.466916e-002, 1.462529e-002, 1.457629e-002, 1.452189e-002, 1.446931e-002, + 1.441358e-002, 1.435340e-002, 1.428936e-002, 1.422026e-002, 1.414640e-002, 1.406896e-002, 1.398950e-002, 1.390865e-002, 1.382658e-002, + 1.374946e-002, 1.367392e-002, 1.359731e-002, 1.352170e-002, 1.344518e-002, 1.336808e-002, 1.328958e-002, 1.321204e-002, 1.313604e-002, + 1.305930e-002, 1.298936e-002, 1.292436e-002, 1.285846e-002, 1.279311e-002, 1.272439e-002, 1.265292e-002, 1.258242e-002, 1.251166e-002, + 1.243766e-002, 1.236302e-002, 1.228948e-002, 1.222220e-002, 1.215235e-002, 1.207619e-002, 1.199284e-002, 1.190767e-002, 1.182139e-002, + 1.173345e-002, 1.164387e-002, 1.155443e-002, 1.146493e-002, 1.138295e-002, 1.129904e-002, 1.120925e-002, 1.111652e-002, 1.102182e-002, + 1.092648e-002, 1.082955e-002, 1.073332e-002, 1.063740e-002, 1.054193e-002, 1.045484e-002, 1.036929e-002, 1.028253e-002, 1.019515e-002, + 1.010402e-002, 1.001275e-002, 9.920484e-003, 9.826290e-003, 9.729964e-003, 9.630945e-003, 9.536917e-003, 9.445100e-003, 9.351564e-003, + 9.254625e-003, 9.155807e-003, 9.053900e-003, 8.951083e-003, 8.847467e-003, 8.738950e-003, 8.627321e-003, 8.514583e-003, 8.402696e-003, + 8.288394e-003, 8.173106e-003, 8.056380e-003, 7.939492e-003, 7.823650e-003, 7.708244e-003, 7.591606e-003, 7.474110e-003, 7.361471e-003, + 7.262424e-003, 7.166043e-003, 7.071048e-003, 6.978374e-003, 6.884690e-003, 6.788941e-003, 6.693594e-003, 6.599997e-003, 6.507255e-003, + 6.416697e-003, 6.338541e-003, 6.265930e-003, 6.194026e-003, 6.121762e-003, 6.047146e-003, 5.968673e-003, 5.884628e-003, 5.799016e-003, + 5.713625e-003, 5.626518e-003, 5.543415e-003, 5.462759e-003, 5.383004e-003, 5.301757e-003, 5.221012e-003, 5.141119e-003, 5.060148e-003, + 4.979370e-003, 4.899344e-003, 4.817869e-003, 4.739570e-003, 4.665764e-003, 4.591655e-003, 4.514425e-003, 4.436876e-003, 4.359805e-003, + 4.281968e-003, 4.202791e-003, 4.122960e-003, 4.039086e-003, 3.956343e-003, 3.875757e-003, 3.797406e-003, 3.719577e-003, 3.643680e-003, + 3.568814e-003, 3.492859e-003, 3.414581e-003, 3.338210e-003, 3.259886e-003, 3.181758e-003, 3.106308e-003, 3.031204e-003, 2.954115e-003, + 2.876947e-003, 2.799848e-003, 2.723993e-003, 2.646426e-003, 2.566859e-003, 2.484680e-003, 2.398644e-003, 2.306304e-003, 2.209953e-003, + 2.111415e-003, 2.010258e-003, 1.902428e-003, 1.791665e-003, 1.677490e-003, 1.557822e-003, 1.432742e-003, 1.304630e-003, 1.172295e-003, + 1.035222e-003, 8.975401e-004, 7.590801e-004, 6.216158e-004, 4.844702e-004, 3.450865e-004, 2.066534e-004, 6.913420e-005, -6.725644e-005, + -2.057265e-004, -3.467687e-004, -4.887526e-004, -6.348563e-004, -7.905714e-004, -9.669508e-004, -1.164926e-003, -1.381963e-003, -1.619638e-003, + -1.874832e-003, -2.148495e-003, -2.444814e-003, -2.756516e-003, -3.084647e-003, -3.425024e-003, -3.773993e-003, -4.134545e-003, -4.507166e-003, + -4.886846e-003, -5.270575e-003, -5.657613e-003, -6.055782e-003, -6.456531e-003, -6.859689e-003, -7.263682e-003, -7.664792e-003, -8.058252e-003, + -8.446927e-003, -8.829377e-003, -9.207939e-003, -9.576628e-003, -9.946104e-003, -1.030922e-002, -1.066142e-002, -1.100057e-002, -1.132720e-002, + -1.164203e-002, -1.194423e-002, -1.223340e-002, -1.250574e-002, -1.276045e-002, -1.300292e-002, -1.323872e-002, -1.345882e-002, -1.366432e-002, + -1.386224e-002, -1.405922e-002, -1.425492e-002, -1.444750e-002, -1.463415e-002, -1.481692e-002, -1.499512e-002, -1.517985e-002, -1.535965e-002, + -1.553151e-002, -1.569829e-002, -1.586190e-002, -1.601993e-002, -1.616780e-002, -1.631217e-002, -1.645177e-002, -1.658583e-002, -1.672366e-002, + -1.685881e-002, -1.698234e-002, -1.709916e-002, -1.720852e-002, -1.731054e-002, -1.739895e-002, -1.748040e-002, -1.755821e-002, -1.763391e-002, + -1.771765e-002, -1.780179e-002, -1.788008e-002, -1.795728e-002, -1.802917e-002, -1.809748e-002, -1.815996e-002, -1.821714e-002, -1.827226e-002, + -1.832845e-002, -1.838574e-002, -1.844509e-002, -1.850239e-002, -1.856038e-002, -1.861472e-002, -1.866709e-002, -1.871958e-002, -1.876557e-002, + -1.880767e-002, -1.884718e-002, -1.888664e-002, -1.893083e-002, -1.897529e-002, -1.901423e-002, -1.904504e-002, -1.907092e-002, -1.909192e-002, + -1.911067e-002, -1.912440e-002, -1.913393e-002, -1.914382e-002, -1.915449e-002, -1.916195e-002, -1.916885e-002, -1.917660e-002, -1.919077e-002, + -1.920920e-002, -1.922931e-002, -1.924778e-002, -1.926894e-002, -1.928924e-002, -1.931301e-002, -1.934173e-002, -1.937471e-002, -1.940587e-002, + -1.943797e-002, -1.947026e-002, -1.950912e-002, -1.954936e-002, -1.958761e-002, -1.962894e-002, -1.967594e-002, -1.972659e-002, -1.978702e-002, + -1.985216e-002, -1.992150e-002, -1.999448e-002, -2.007210e-002, -2.014773e-002, -2.022535e-002, -2.030565e-002, -2.038502e-002, -2.046522e-002, + -2.054659e-002, -2.062929e-002, -2.071136e-002, -2.079538e-002, -2.088257e-002, -2.097007e-002, -2.106025e-002, -2.115169e-002, -2.123780e-002, + -2.131658e-002, -2.139560e-002, -2.147718e-002, -2.155657e-002, -2.163601e-002, -2.171915e-002, -2.180377e-002, -2.188823e-002, -2.197066e-002, + -2.205428e-002, -2.213598e-002, -2.222287e-002, -2.230381e-002, -2.238141e-002, -2.245443e-002, -2.252323e-002, -2.258872e-002, -2.264542e-002, + -2.270040e-002, -2.275400e-002, -2.279750e-002, -2.283179e-002, -2.285570e-002, -2.287307e-002, -2.288655e-002, -2.289254e-002, -2.289322e-002, + -2.288004e-002, -2.286161e-002, -2.283576e-002, -2.279586e-002, -2.273507e-002, -2.266675e-002, -2.259296e-002, -2.251842e-002, -2.244123e-002, + -2.235947e-002, -2.227163e-002, -2.218445e-002, -2.209385e-002, -2.199492e-002, -2.187452e-002, -2.174994e-002, -2.162391e-002, -2.148816e-002, + -2.134779e-002, -2.120915e-002, -2.107143e-002, -2.093258e-002, -2.078652e-002, -2.063582e-002, -2.045986e-002, -2.027293e-002, -2.008454e-002, + -1.989405e-002, -1.969086e-002, -1.947059e-002, -1.923131e-002, -1.897325e-002, -1.870094e-002, -1.841641e-002, -1.811045e-002, -1.778220e-002, + -1.744522e-002, -1.709403e-002, -1.672489e-002, -1.634271e-002, -1.595194e-002, -1.555536e-002, -1.514776e-002, -1.473221e-002, -1.430512e-002, + -1.386335e-002, -1.341936e-002, -1.297073e-002, -1.251919e-002, -1.206132e-002, -1.160160e-002, -1.114290e-002, -1.068782e-002, -1.022132e-002, + -9.757470e-003, -9.285987e-003, -8.808177e-003, -8.330368e-003, -7.853724e-003, -7.374809e-003, -6.894174e-003, -6.408915e-003, -5.914210e-003, + -5.421145e-003, -4.930913e-003, -4.443133e-003, -3.952020e-003, -3.467807e-003, -3.003789e-003, -2.534258e-003, -2.063009e-003, -1.587187e-003, + -1.105597e-003, -6.145124e-004, -1.195783e-004, 3.786283e-004, 8.472878e-004, 1.313937e-003, 1.792348e-003, 2.281533e-003, 2.785763e-003, + 3.282833e-003, 3.786963e-003, 4.292176e-003, 4.799104e-003, 5.288551e-003, 5.780731e-003, 6.285562e-003, 6.799689e-003, 7.326510e-003, + 7.855405e-003, 8.389570e-003, 8.933646e-003, 9.487226e-003, 1.004485e-002, 1.059483e-002, 1.112622e-002, 1.166886e-002, 1.222207e-002, + 1.277386e-002, 1.332494e-002, 1.388038e-002, 1.443527e-002, 1.499523e-002, 1.554618e-002, 1.610172e-002, 1.660544e-002, 1.711204e-002, + 1.761668e-002, 1.812841e-002, 1.864169e-002, 1.916642e-002, 1.969251e-002, 2.022161e-002, 2.074220e-002, 2.127061e-002, 2.174093e-002, + 2.221935e-002, 2.271000e-002, 2.319654e-002, 2.368338e-002, 2.416340e-002, 2.465537e-002, 2.515596e-002, 2.566187e-002, 2.615421e-002, + 2.659238e-002, 2.699884e-002, 2.741717e-002, 2.784145e-002, 2.827092e-002, 2.871013e-002, 2.915557e-002, 2.960184e-002, 3.006013e-002, + 3.051912e-002, 3.093138e-002, 3.126677e-002, 3.160425e-002, 3.196362e-002, 3.234064e-002, 3.271900e-002, 3.309697e-002, 3.346249e-002, + 3.382050e-002, 3.419264e-002, 3.455212e-002, 3.479399e-002, 3.502965e-002, 3.525196e-002, 3.548711e-002, 3.572370e-002, 3.596105e-002, + 3.620329e-002, 3.645614e-002, 3.671814e-002, 3.697862e-002, 3.709290e-002, 3.720348e-002, 3.732277e-002, 3.743414e-002, 3.755795e-002, + 3.768074e-002, 3.780413e-002, 3.792862e-002, 3.804353e-002, 3.817294e-002, 3.818591e-002, 3.814615e-002, 3.809726e-002, 3.804761e-002, + 3.800844e-002, 3.797241e-002, 3.792083e-002, 3.785795e-002, 3.779673e-002, 3.774091e-002, 3.759108e-002, 3.732045e-002, 3.704199e-002, + 3.677636e-002, 3.649953e-002, 3.621698e-002, 3.593742e-002, 3.565432e-002, 3.537721e-002, 3.510028e-002, 3.479529e-002, 3.430971e-002, + 3.382525e-002, 3.335219e-002, 3.290044e-002, 3.243309e-002, 3.195748e-002, 3.150535e-002, 3.105769e-002, 3.060717e-002, 3.015091e-002, + 2.949569e-002, 2.884514e-002, 2.818003e-002, 2.752823e-002, 2.689935e-002, 2.624807e-002, 2.558363e-002, 2.491307e-002, 2.423669e-002, + 2.357128e-002, 2.275494e-002, 2.186031e-002, 2.095985e-002, 2.005913e-002, 1.916446e-002, 1.824767e-002, 1.730871e-002, 1.635408e-002, + 1.536332e-002, 1.436585e-002, 1.326096e-002, 1.203139e-002, 1.078647e-002, 9.543089e-003, 8.317263e-003, 7.048353e-003, 5.775684e-003, + 4.509309e-003, 3.229642e-003, 1.969391e-003, 6.297964e-004, -8.156995e-004, -2.259355e-003, -3.698178e-003, -5.176835e-003, -6.670660e-003, + -8.171561e-003, -9.643531e-003, -1.116153e-002, -1.272143e-002, -1.426735e-002, -1.590160e-002, -1.758423e-002, -1.924605e-002, -2.090828e-002, + -2.257547e-002, -2.424289e-002, -2.590629e-002, -2.753986e-002, -2.917480e-002, -3.087626e-002, -3.255204e-002, -3.421043e-002, -3.588567e-002, + -3.755808e-002, -3.924687e-002, -4.091323e-002, -4.253313e-002, -4.418032e-002, -4.584658e-002, -4.756897e-002, -4.920846e-002, -5.077447e-002, + -5.234610e-002, -5.381682e-002, -5.526424e-002, -5.663091e-002, -5.797222e-002, -5.930607e-002, -6.066814e-002, -6.208514e-002, -6.343403e-002, + -6.449715e-002, -6.556007e-002, -6.657406e-002, -6.762655e-002, -6.869886e-002, -6.975114e-002, -7.077011e-002, -7.169135e-002, -7.257124e-002, + -7.322136e-002, -7.314771e-002, -7.311838e-002, -7.300001e-002, -7.266621e-002, -7.217943e-002, -7.125657e-002, -7.000705e-002, -6.839464e-002, + -6.702601e-002, -6.565046e-002, -6.372023e-002, -6.185220e-002, -5.942380e-002, -5.690567e-002, -5.426280e-002, -5.154318e-002, -4.866927e-002, + -4.523848e-002, -3.984226e-002, -2.892381e-002, -2.036873e-002, -1.322092e-002, -6.047445e-003, 4.023240e-004, 6.063601e-003, 1.163768e-002, + 1.739665e-002, 2.380727e-002, 3.138978e-002, 3.860991e-002, 1.101943e-001, 1.182915e-001, 1.205385e-001, 1.208058e-001, 1.195768e-001, + 1.187807e-001, 1.185533e-001, 1.183375e-001, 1.183317e-001, 1.199347e-001, 1.690672e-001, 2.689890e-001, 2.707646e-001, 2.683774e-001, + 2.623614e-001, 2.456289e-001, 1.179993e-001, 8.066072e-002, 4.828174e-002, 2.318181e-002, 0.000000e+000}, + {0.000000e+000, -5.948584e-003, -1.046028e-002, -1.412192e-002, -1.718260e-002, -1.981969e-002, -2.210697e-002, -2.395837e-002, -2.550575e-002, + -2.656707e-002, -2.756658e-002, -2.857295e-002, -2.943111e-002, -3.020161e-002, -3.092388e-002, -3.163091e-002, -3.235862e-002, -3.313731e-002, + -3.398320e-002, -3.482197e-002, -3.563929e-002, -3.639301e-002, -3.708183e-002, -3.774827e-002, -3.839240e-002, -3.900497e-002, -3.958263e-002, + -4.012135e-002, -4.062204e-002, -4.108001e-002, -4.149873e-002, -4.186575e-002, -4.215703e-002, -4.242737e-002, -4.267827e-002, -4.291057e-002, + -4.312675e-002, -4.332709e-002, -4.351663e-002, -4.370100e-002, -4.387662e-002, -4.402898e-002, -4.408344e-002, -4.410699e-002, -4.408507e-002, + -4.400904e-002, -4.375791e-002, -4.312853e-002, -4.252821e-002, -4.235124e-002, -4.231945e-002, -4.230396e-002, -4.224461e-002, -4.218807e-002, + -4.214307e-002, -4.210902e-002, -4.208484e-002, -4.206985e-002, -4.205669e-002, -4.204964e-002, -4.204478e-002, -4.204052e-002, -4.199539e-002, + -4.192053e-002, -4.184521e-002, -4.176766e-002, -4.168334e-002, -4.158970e-002, -4.148186e-002, -4.136096e-002, -4.122021e-002, -4.106306e-002, + -4.087456e-002, -4.064578e-002, -4.040580e-002, -4.015549e-002, -3.989355e-002, -3.961990e-002, -3.933759e-002, -3.905504e-002, -3.877712e-002, + -3.851058e-002, -3.824416e-002, -3.794630e-002, -3.765646e-002, -3.737032e-002, -3.708395e-002, -3.679851e-002, -3.651559e-002, -3.621877e-002, + -3.590223e-002, -3.556911e-002, -3.522037e-002, -3.482059e-002, -3.440499e-002, -3.397425e-002, -3.353103e-002, -3.307909e-002, -3.262169e-002, + -3.215940e-002, -3.169289e-002, -3.122540e-002, -3.075826e-002, -3.027230e-002, -2.978381e-002, -2.930554e-002, -2.884168e-002, -2.838044e-002, + -2.791220e-002, -2.743917e-002, -2.696029e-002, -2.647392e-002, -2.598328e-002, -2.547690e-002, -2.495366e-002, -2.443086e-002, -2.390635e-002, + -2.337981e-002, -2.285216e-002, -2.232433e-002, -2.179606e-002, -2.127013e-002, -2.075020e-002, -2.023887e-002, -1.973521e-002, -1.925105e-002, + -1.878562e-002, -1.833284e-002, -1.788865e-002, -1.745424e-002, -1.702593e-002, -1.660021e-002, -1.617719e-002, -1.575667e-002, -1.532677e-002, + -1.489798e-002, -1.446973e-002, -1.403917e-002, -1.360597e-002, -1.317021e-002, -1.272858e-002, -1.228229e-002, -1.183464e-002, -1.138420e-002, + -1.092678e-002, -1.046724e-002, -1.000966e-002, -9.554884e-003, -9.105796e-003, -8.660513e-003, -8.220711e-003, -7.785510e-003, -7.356108e-003, + -6.932939e-003, -6.515922e-003, -6.104922e-003, -5.698882e-003, -5.303549e-003, -4.915248e-003, -4.536020e-003, -4.162792e-003, -3.795156e-003, + -3.432018e-003, -3.075013e-003, -2.721552e-003, -2.372963e-003, -2.024064e-003, -1.671348e-003, -1.311859e-003, -9.457630e-004, -5.768674e-004, + -2.083540e-004, 1.585965e-004, 5.251824e-004, 8.888624e-004, 1.246487e-003, 1.608064e-003, 1.974160e-003, 2.342099e-003, 2.712027e-003, + 3.081043e-003, 3.445252e-003, 3.799746e-003, 4.139995e-003, 4.467597e-003, 4.776563e-003, 5.077320e-003, 5.373030e-003, 5.663168e-003, + 5.949986e-003, 6.234074e-003, 6.515621e-003, 6.797121e-003, 7.078203e-003, 7.360271e-003, 7.632729e-003, 7.901880e-003, 8.173229e-003, + 8.447280e-003, 8.725165e-003, 9.006207e-003, 9.291544e-003, 9.581457e-003, 9.874916e-003, 1.017250e-002, 1.046641e-002, 1.076046e-002, + 1.106547e-002, 1.138228e-002, 1.170738e-002, 1.203445e-002, 1.236181e-002, 1.268655e-002, 1.300964e-002, 1.333124e-002, 1.365057e-002, + 1.395585e-002, 1.426371e-002, 1.457022e-002, 1.487466e-002, 1.517438e-002, 1.546641e-002, 1.574707e-002, 1.601292e-002, 1.626505e-002, + 1.650610e-002, 1.672061e-002, 1.692630e-002, 1.712683e-002, 1.732448e-002, 1.751945e-002, 1.771236e-002, 1.790203e-002, 1.808812e-002, + 1.827063e-002, 1.845030e-002, 1.861054e-002, 1.876441e-002, 1.891985e-002, 1.907753e-002, 1.923785e-002, 1.940077e-002, 1.956861e-002, + 1.974178e-002, 1.991933e-002, 2.010331e-002, 2.028513e-002, 2.046811e-002, 2.066714e-002, 2.088008e-002, 2.109807e-002, 2.131675e-002, + 2.153312e-002, 2.174281e-002, 2.194993e-002, 2.215501e-002, 2.235024e-002, 2.252842e-002, 2.270546e-002, 2.287972e-002, 2.304994e-002, + 2.321695e-002, 2.337129e-002, 2.352155e-002, 2.366657e-002, 2.380986e-002, 2.395282e-002, 2.407193e-002, 2.419302e-002, 2.431763e-002, + 2.444566e-002, 2.458104e-002, 2.472854e-002, 2.488952e-002, 2.506544e-002, 2.525627e-002, 2.546600e-002, 2.567049e-002, 2.588437e-002, + 2.610956e-002, 2.634315e-002, 2.659193e-002, 2.685971e-002, 2.714631e-002, 2.745098e-002, 2.777483e-002, 2.811512e-002, 2.845346e-002, + 2.878696e-002, 2.912168e-002, 2.945281e-002, 2.977797e-002, 3.010048e-002, 3.042120e-002, 3.074058e-002, 3.106231e-002, 3.138533e-002, + 3.169710e-002, 3.198677e-002, 3.227495e-002, 3.256339e-002, 3.285098e-002, 3.313948e-002, 3.342794e-002, 3.371656e-002, 3.400471e-002, + 3.428926e-002, 3.456718e-002, 3.481441e-002, 3.505788e-002, 3.529949e-002, 3.553841e-002, 3.577588e-002, 3.601093e-002, 3.624765e-002, + 3.648528e-002, 3.671898e-002, 3.694927e-002, 3.714690e-002, 3.733192e-002, 3.750582e-002, 3.766425e-002, 3.780644e-002, 3.793386e-002, + 3.804862e-002, 3.814937e-002, 3.823731e-002, 3.831114e-002, 3.834863e-002, 3.835488e-002, 3.833633e-002, 3.829914e-002, 3.824622e-002, + 3.818108e-002, 3.810190e-002, 3.800849e-002, 3.790841e-002, 3.780139e-002, 3.767555e-002, 3.752722e-002, 3.737696e-002, 3.722564e-002, + 3.707149e-002, 3.691774e-002, 3.676275e-002, 3.661098e-002, 3.646524e-002, 3.632604e-002, 3.618752e-002, 3.603051e-002, 3.587972e-002, + 3.573158e-002, 3.558121e-002, 3.541856e-002, 3.523896e-002, 3.504661e-002, 3.484020e-002, 3.462295e-002, 3.439420e-002, 3.412198e-002, + 3.384067e-002, 3.355145e-002, 3.325646e-002, 3.295335e-002, 3.264685e-002, 3.233845e-002, 3.203041e-002, 3.171997e-002, 3.140753e-002, + 3.106842e-002, 3.071718e-002, 3.036373e-002, 3.000925e-002, 2.965086e-002, 2.929255e-002, 2.893394e-002, 2.857646e-002, 2.822257e-002, + 2.786956e-002, 2.750272e-002, 2.712157e-002, 2.674161e-002, 2.636277e-002, 2.598194e-002, 2.560004e-002, 2.522268e-002, 2.484980e-002, + 2.447667e-002, 2.410281e-002, 2.371642e-002, 2.329970e-002, 2.287848e-002, 2.244893e-002, 2.201228e-002, 2.157542e-002, 2.113880e-002, + 2.070140e-002, 2.026492e-002, 1.983083e-002, 1.939684e-002, 1.893046e-002, 1.846613e-002, 1.800138e-002, 1.753855e-002, 1.707984e-002, + 1.662469e-002, 1.617480e-002, 1.573530e-002, 1.530541e-002, 1.488438e-002, 1.444704e-002, 1.401517e-002, 1.359411e-002, 1.318446e-002, + 1.278145e-002, 1.238534e-002, 1.199644e-002, 1.161269e-002, 1.123216e-002, 1.085487e-002, 1.046048e-002, 1.005207e-002, 9.644108e-003, + 9.235605e-003, 8.827724e-003, 8.419217e-003, 8.013053e-003, 7.607237e-003, 7.196789e-003, 6.782582e-003, 6.355131e-003, 5.902252e-003, + 5.449632e-003, 4.998081e-003, 4.546524e-003, 4.098905e-003, 3.658531e-003, 3.225129e-003, 2.795811e-003, 2.371259e-003, 1.957602e-003, + 1.531842e-003, 1.120777e-003, 7.205106e-004, 3.283913e-004, -5.933045e-005, -4.439777e-004, -8.223615e-004, -1.194654e-003, -1.559656e-003, + -1.917887e-003, -2.289807e-003, -2.656050e-003, -3.019412e-003, -3.384292e-003, -3.754604e-003, -4.126454e-003, -4.498876e-003, -4.871506e-003, + -5.238074e-003, -5.599641e-003, -5.970859e-003, -6.344308e-003, -6.711346e-003, -7.074197e-003, -7.432486e-003, -7.782779e-003, -8.126682e-003, + -8.464074e-003, -8.795215e-003, -9.119613e-003, -9.446921e-003, -9.781706e-003, -1.011206e-002, -1.043890e-002, -1.076276e-002, -1.108266e-002, + -1.140055e-002, -1.171700e-002, -1.203250e-002, -1.234679e-002, -1.265958e-002, -1.298452e-002, -1.330717e-002, -1.362826e-002, -1.394375e-002, + -1.425439e-002, -1.455847e-002, -1.485700e-002, -1.514793e-002, -1.543700e-002, -1.572596e-002, -1.602852e-002, -1.633093e-002, -1.663258e-002, + -1.693060e-002, -1.722624e-002, -1.752022e-002, -1.781593e-002, -1.811509e-002, -1.841830e-002, -1.873025e-002, -1.906276e-002, -1.940616e-002, + -1.975771e-002, -2.011914e-002, -2.049229e-002, -2.087419e-002, -2.126528e-002, -2.166763e-002, -2.207789e-002, -2.249161e-002, -2.291329e-002, + -2.334089e-002, -2.376481e-002, -2.418249e-002, -2.459265e-002, -2.499479e-002, -2.538694e-002, -2.576434e-002, -2.612284e-002, -2.646375e-002, + -2.679377e-002, -2.712256e-002, -2.744789e-002, -2.776918e-002, -2.806224e-002, -2.829575e-002, -2.847228e-002, -2.859076e-002, -2.865676e-002, + -2.867214e-002, -2.864107e-002, -2.857640e-002, -2.847552e-002, -2.834078e-002, -2.817526e-002, -2.797742e-002, -2.774997e-002, -2.749482e-002, + -2.721815e-002, -2.691966e-002, -2.659914e-002, -2.626871e-002, -2.592373e-002, -2.556482e-002, -2.519327e-002, -2.480871e-002, -2.441472e-002, + -2.401092e-002, -2.360332e-002, -2.319756e-002, -2.279512e-002, -2.241146e-002, -2.204302e-002, -2.169290e-002, -2.136524e-002, -2.106278e-002, + -2.078899e-002, -2.054502e-002, -2.033929e-002, -2.017546e-002, -2.005890e-002, -1.999876e-002, -2.001068e-002, -2.008933e-002, -2.019889e-002, + -2.031759e-002, -2.044829e-002, -2.058973e-002, -2.074222e-002, -2.090446e-002, -2.107718e-002, -2.125894e-002, -2.145787e-002, -2.166500e-002, + -2.187264e-002, -2.208520e-002, -2.230130e-002, -2.252226e-002, -2.274373e-002, -2.296947e-002, -2.319341e-002, -2.341791e-002, -2.365163e-002, + -2.388496e-002, -2.410825e-002, -2.432480e-002, -2.453164e-002, -2.472465e-002, -2.490171e-002, -2.507223e-002, -2.524614e-002, -2.542661e-002, + -2.562317e-002, -2.582726e-002, -2.603106e-002, -2.623738e-002, -2.644263e-002, -2.664505e-002, -2.684109e-002, -2.703369e-002, -2.722865e-002, + -2.743115e-002, -2.764978e-002, -2.788030e-002, -2.810709e-002, -2.833495e-002, -2.856174e-002, -2.878308e-002, -2.900204e-002, -2.921894e-002, + -2.943169e-002, -2.963449e-002, -2.983521e-002, -3.003728e-002, -3.023066e-002, -3.040947e-002, -3.057211e-002, -3.072048e-002, -3.085741e-002, + -3.098051e-002, -3.108812e-002, -3.117851e-002, -3.126310e-002, -3.138829e-002, -3.153534e-002, -3.170706e-002, -3.189900e-002, -3.211329e-002, + -3.234613e-002, -3.259687e-002, -3.286321e-002, -3.314521e-002, -3.343420e-002, -3.375310e-002, -3.408381e-002, -3.442094e-002, -3.475789e-002, + -3.509398e-002, -3.542514e-002, -3.574842e-002, -3.605705e-002, -3.635363e-002, -3.663700e-002, -3.692114e-002, -3.720193e-002, -3.746059e-002, + -3.769447e-002, -3.790588e-002, -3.809212e-002, -3.824652e-002, -3.836200e-002, -3.844385e-002, -3.849019e-002, -3.850980e-002, -3.852192e-002, + -3.849075e-002, -3.841692e-002, -3.830307e-002, -3.814343e-002, -3.793830e-002, -3.768983e-002, -3.739737e-002, -3.705420e-002, -3.667035e-002, + -3.631146e-002, -3.593032e-002, -3.553103e-002, -3.511230e-002, -3.468110e-002, -3.425017e-002, -3.381660e-002, -3.338288e-002, -3.293831e-002, + -3.248550e-002, -3.207288e-002, -3.166134e-002, -3.123436e-002, -3.079668e-002, -3.034961e-002, -2.989507e-002, -2.942900e-002, -2.895256e-002, + -2.846753e-002, -2.797019e-002, -2.749438e-002, -2.703390e-002, -2.656510e-002, -2.608691e-002, -2.560159e-002, -2.510515e-002, -2.459696e-002, + -2.407510e-002, -2.354001e-002, -2.298263e-002, -2.244101e-002, -2.193445e-002, -2.141711e-002, -2.087392e-002, -2.030779e-002, -1.972251e-002, + -1.911495e-002, -1.848676e-002, -1.783762e-002, -1.716135e-002, -1.646670e-002, -1.582304e-002, -1.516356e-002, -1.448692e-002, -1.379425e-002, + -1.308478e-002, -1.235917e-002, -1.162134e-002, -1.086739e-002, -1.008393e-002, -9.275987e-003, -8.527802e-003, -7.768778e-003, -6.981132e-003, + -6.119956e-003, -5.120459e-003, -3.990800e-003, -2.741776e-003, -1.376983e-003, 9.828532e-005, 1.676223e-003, 3.280892e-003, 4.937018e-003, + 6.684108e-003, 8.512696e-003, 1.041702e-002, 1.238370e-002, 1.440147e-002, 1.646402e-002, 1.856965e-002, 2.070603e-002, 2.282164e-002, + 2.489381e-002, 2.697639e-002, 2.906437e-002, 3.115039e-002, 3.322969e-002, 3.529430e-002, 3.733764e-002, 3.935526e-002, 4.134601e-002, + 4.327241e-002, 4.506649e-002, 4.681959e-002, 4.852420e-002, 5.016666e-002, 5.174376e-002, 5.325207e-002, 5.469578e-002, 5.607293e-002, + 5.736846e-002, 5.856041e-002, 5.951937e-002, 6.036706e-002, 6.109094e-002, 6.168262e-002, 6.216786e-002, 6.262349e-002, 6.305866e-002, + 6.347619e-002, 6.386848e-002, 6.423255e-002, 6.447476e-002, 6.465171e-002, 6.479041e-002, 6.490509e-002, 6.499736e-002, 6.507549e-002, + 6.513269e-002, 6.517093e-002, 6.518208e-002, 6.518070e-002, 6.508500e-002, 6.490283e-002, 6.471472e-002, 6.450784e-002, 6.430075e-002, + 6.408874e-002, 6.387332e-002, 6.365391e-002, 6.343041e-002, 6.319381e-002, 6.290044e-002, 6.250051e-002, 6.209105e-002, 6.166723e-002, + 6.122417e-002, 6.076333e-002, 6.027323e-002, 5.975602e-002, 5.921839e-002, 5.864549e-002, 5.804034e-002, 5.727047e-002, 5.648582e-002, + 5.568698e-002, 5.487773e-002, 5.405462e-002, 5.320805e-002, 5.234901e-002, 5.147712e-002, 5.057954e-002, 4.967115e-002, 4.863255e-002, + 4.756180e-002, 4.648999e-002, 4.541126e-002, 4.433167e-002, 4.325763e-002, 4.218398e-002, 4.111786e-002, 4.003836e-002, 3.894626e-002, + 3.777685e-002, 3.656353e-002, 3.535622e-002, 3.416935e-002, 3.299730e-002, 3.184616e-002, 3.070309e-002, 2.956442e-002, 2.842832e-002, + 2.729558e-002, 2.610155e-002, 2.482173e-002, 2.355134e-002, 2.229819e-002, 2.106438e-002, 1.983652e-002, 1.861767e-002, 1.741414e-002, + 1.621598e-002, 1.501925e-002, 1.380812e-002, 1.246018e-002, 1.111967e-002, 9.788785e-003, 8.473901e-003, 7.177705e-003, 5.906202e-003, + 4.654812e-003, 3.432508e-003, 2.229914e-003, 1.064813e-003, -2.173012e-004, -1.478580e-003, -2.721705e-003, -3.953264e-003, -5.148483e-003, + -6.304037e-003, -7.412129e-003, -8.498079e-003, -9.550709e-003, -1.056701e-002, -1.167472e-002, -1.281602e-002, -1.393330e-002, -1.502508e-002, + -1.611261e-002, -1.718486e-002, -1.824908e-002, -1.931062e-002, -2.034952e-002, -2.136453e-002, -2.243274e-002, -2.358288e-002, -2.470462e-002, + -2.578893e-002, -2.682955e-002, -2.784701e-002, -2.884065e-002, -2.979270e-002, -3.071127e-002, -3.158953e-002, -3.247666e-002, -3.350379e-002, + -3.448654e-002, -3.542902e-002, -3.633891e-002, -3.720409e-002, -3.802047e-002, -3.879421e-002, -3.952199e-002, -4.019919e-002, -4.082141e-002, + -4.161062e-002, -4.235605e-002, -4.304160e-002, -4.367656e-002, -4.425568e-002, -4.479237e-002, -4.529534e-002, -4.573724e-002, -4.614122e-002, + -4.650078e-002, -4.698633e-002, -4.747143e-002, -4.786270e-002, -4.816596e-002, -4.837634e-002, -4.848212e-002, -4.848778e-002, -4.841266e-002, + -4.824191e-002, -4.798326e-002, -4.779639e-002, -4.765645e-002, -4.744163e-002, -4.717161e-002, -4.682562e-002, -4.638332e-002, -4.586779e-002, + -4.527898e-002, -4.460445e-002, -4.384995e-002, -4.310654e-002, -4.252824e-002, -4.187401e-002, -4.113536e-002, -4.034341e-002, -3.947574e-002, + -3.853392e-002, -3.754277e-002, -3.646945e-002, -3.531654e-002, -3.412551e-002, -3.326706e-002, -3.233921e-002, -3.132969e-002, -3.016532e-002, + -2.885093e-002, -2.735353e-002, -2.568485e-002, -2.387733e-002, -2.191919e-002, -1.981274e-002, -1.802727e-002, -1.619401e-002, -1.423960e-002, + -1.218404e-002, -1.003874e-002, -7.781791e-003, -5.413668e-003, -2.935296e-003, -3.712999e-004, 2.247255e-003, 4.527139e-003, 6.577857e-003, + 8.691127e-003, 1.083851e-002, 1.300684e-002, 1.518670e-002, 1.743227e-002, 1.972860e-002, 2.209641e-002, 2.454579e-002, 2.675521e-002, + 2.845350e-002, 3.019673e-002, 3.192092e-002, 3.367314e-002, 3.546429e-002, 3.724262e-002, 3.899910e-002, 4.075613e-002, 4.251294e-002, + 4.411621e-002, 4.480295e-002, 4.547760e-002, 4.607877e-002, 4.661217e-002, 4.708334e-002, 4.740441e-002, 4.760143e-002, 4.766157e-002, + 4.777167e-002, 4.793095e-002, 4.711552e-002, 4.626246e-002, 4.529052e-002, 4.434331e-002, 4.344736e-002, 4.266091e-002, 4.181582e-002, + 4.080910e-002, 3.921881e-002, 3.583952e-002, 3.259613e-002, 2.956175e-002, 2.651886e-002, 2.372247e-002, 2.105007e-002, 1.829475e-002, + 1.532199e-002, 1.216969e-002, 8.674116e-003, 5.452616e-003, -1.948443e-002, -2.423340e-002, -2.695359e-002, -2.894229e-002, -3.018456e-002, + -3.128056e-002, -3.235162e-002, -3.319580e-002, -3.395398e-002, -3.527155e-002, -5.551143e-002, -8.822319e-002, -8.978125e-002, -9.050980e-002, + -8.976921e-002, -8.507800e-002, -4.157453e-002, -2.872418e-002, -1.732739e-002, -8.396579e-003, 0.000000e+000}, + {0.000000e+000, -5.609543e-003, -1.020292e-002, -1.400415e-002, -1.719518e-002, -1.986023e-002, -2.209632e-002, -2.411757e-002, -2.583390e-002, + -2.697271e-002, -2.767187e-002, -2.820270e-002, -2.873208e-002, -2.917226e-002, -2.953901e-002, -2.985445e-002, -3.014326e-002, -3.042552e-002, + -3.071728e-002, -3.096373e-002, -3.115826e-002, -3.137235e-002, -3.158948e-002, -3.175253e-002, -3.186987e-002, -3.195711e-002, -3.201454e-002, + -3.205246e-002, -3.206618e-002, -3.204799e-002, -3.199958e-002, -3.195769e-002, -3.195992e-002, -3.194288e-002, -3.190385e-002, -3.184448e-002, + -3.176524e-002, -3.166172e-002, -3.153478e-002, -3.137627e-002, -3.119349e-002, -3.099761e-002, -3.085576e-002, -3.072802e-002, -3.062967e-002, + -3.057547e-002, -3.071938e-002, -3.132002e-002, -3.187077e-002, -3.187862e-002, -3.169455e-002, -3.148052e-002, -3.128429e-002, -3.107780e-002, + -3.086201e-002, -3.063436e-002, -3.039270e-002, -3.013730e-002, -2.986765e-002, -2.958591e-002, -2.929295e-002, -2.899273e-002, -2.870948e-002, + -2.843268e-002, -2.815173e-002, -2.786922e-002, -2.758173e-002, -2.729196e-002, -2.700176e-002, -2.670976e-002, -2.641753e-002, -2.612207e-002, + -2.582397e-002, -2.552362e-002, -2.520222e-002, -2.484961e-002, -2.447161e-002, -2.406098e-002, -2.362527e-002, -2.316570e-002, -2.268948e-002, + -2.218656e-002, -2.165324e-002, -2.111701e-002, -2.055203e-002, -1.995805e-002, -1.933752e-002, -1.869388e-002, -1.803501e-002, -1.735155e-002, + -1.664619e-002, -1.592049e-002, -1.518314e-002, -1.446989e-002, -1.375316e-002, -1.303381e-002, -1.231701e-002, -1.160900e-002, -1.091711e-002, + -1.024353e-002, -9.595658e-003, -8.974928e-003, -8.387855e-003, -7.867135e-003, -7.382747e-003, -6.916496e-003, -6.467991e-003, -6.030855e-003, + -5.597068e-003, -5.168507e-003, -4.741847e-003, -4.316002e-003, -3.894435e-003, -3.495257e-003, -3.124098e-003, -2.757691e-003, -2.390975e-003, + -2.028343e-003, -1.666036e-003, -1.305747e-003, -9.489322e-004, -5.927505e-004, -2.422770e-004, 8.750156e-005, 3.706612e-004, 6.411290e-004, + 9.020341e-004, 1.155620e-003, 1.401542e-003, 1.643038e-003, 1.879963e-003, 2.113900e-003, 2.347571e-003, 2.577458e-003, 2.756647e-003, + 2.931890e-003, 3.105964e-003, 3.278941e-003, 3.453383e-003, 3.629719e-003, 3.808411e-003, 3.988230e-003, 4.169038e-003, 4.350300e-003, + 4.498474e-003, 4.636026e-003, 4.770906e-003, 4.905064e-003, 5.036287e-003, 5.162918e-003, 5.286399e-003, 5.409335e-003, 5.529413e-003, + 5.644914e-003, 5.733475e-003, 5.800998e-003, 5.866385e-003, 5.924919e-003, 5.981370e-003, 6.035549e-003, 6.088237e-003, 6.139955e-003, + 6.192982e-003, 6.246664e-003, 6.288008e-003, 6.301096e-003, 6.321934e-003, 6.350560e-003, 6.390177e-003, 6.440209e-003, 6.500632e-003, + 6.568284e-003, 6.643021e-003, 6.723242e-003, 6.806121e-003, 6.859700e-003, 6.924152e-003, 6.999026e-003, 7.084535e-003, 7.182118e-003, + 7.289638e-003, 7.401751e-003, 7.515808e-003, 7.629782e-003, 7.743879e-003, 7.825221e-003, 7.905603e-003, 7.993493e-003, 8.089495e-003, + 8.195815e-003, 8.310149e-003, 8.433364e-003, 8.566772e-003, 8.711999e-003, 8.866846e-003, 9.006287e-003, 9.140958e-003, 9.288091e-003, + 9.446488e-003, 9.614475e-003, 9.793774e-003, 9.985709e-003, 1.018946e-002, 1.040183e-002, 1.062232e-002, 1.083743e-002, 1.104407e-002, + 1.126445e-002, 1.149859e-002, 1.174416e-002, 1.199751e-002, 1.225622e-002, 1.251640e-002, 1.277863e-002, 1.304413e-002, 1.330912e-002, + 1.354784e-002, 1.379030e-002, 1.403692e-002, 1.428711e-002, 1.454022e-002, 1.479216e-002, 1.503939e-002, 1.528018e-002, 1.551532e-002, + 1.574603e-002, 1.594304e-002, 1.613612e-002, 1.632784e-002, 1.651897e-002, 1.671007e-002, 1.690124e-002, 1.709246e-002, 1.727991e-002, + 1.746462e-002, 1.764694e-002, 1.780513e-002, 1.795450e-002, 1.810445e-002, 1.825489e-002, 1.840676e-002, 1.856097e-002, 1.871700e-002, + 1.887492e-002, 1.903423e-002, 1.919784e-002, 1.935343e-002, 1.950450e-002, 1.966746e-002, 1.984002e-002, 2.001872e-002, 2.019768e-002, + 2.037482e-002, 2.054703e-002, 2.071587e-002, 2.088163e-002, 2.103652e-002, 2.116813e-002, 2.129715e-002, 2.142034e-002, 2.153846e-002, + 2.165173e-002, 2.175480e-002, 2.185070e-002, 2.193956e-002, 2.202483e-002, 2.210691e-002, 2.215792e-002, 2.220650e-002, 2.225214e-002, + 2.229691e-002, 2.234433e-002, 2.239508e-002, 2.245017e-002, 2.251060e-002, 2.257590e-002, 2.264920e-002, 2.270431e-002, 2.275820e-002, + 2.281393e-002, 2.287054e-002, 2.292186e-002, 2.296524e-002, 2.300089e-002, 2.303012e-002, 2.305296e-002, 2.306820e-002, 2.305910e-002, + 2.302630e-002, 2.297897e-002, 2.291270e-002, 2.282715e-002, 2.272179e-002, 2.259517e-002, 2.244862e-002, 2.228654e-002, 2.210611e-002, + 2.190042e-002, 2.165822e-002, 2.140254e-002, 2.113492e-002, 2.085599e-002, 2.056860e-002, 2.027364e-002, 1.997160e-002, 1.966354e-002, + 1.934877e-002, 1.902737e-002, 1.867195e-002, 1.831300e-002, 1.795264e-002, 1.759081e-002, 1.723051e-002, 1.687111e-002, 1.651856e-002, + 1.617237e-002, 1.583302e-002, 1.549854e-002, 1.513897e-002, 1.477886e-002, 1.442248e-002, 1.406648e-002, 1.371336e-002, 1.336316e-002, + 1.301864e-002, 1.268391e-002, 1.235797e-002, 1.204155e-002, 1.171069e-002, 1.137267e-002, 1.103821e-002, 1.070490e-002, 1.037233e-002, + 1.004464e-002, 9.717938e-003, 9.394737e-003, 9.078261e-003, 8.767793e-003, 8.449835e-003, 8.117357e-003, 7.794951e-003, 7.485632e-003, + 7.186830e-003, 6.899464e-003, 6.626385e-003, 6.368375e-003, 6.127295e-003, 5.903327e-003, 5.690922e-003, 5.464393e-003, 5.255171e-003, + 5.059746e-003, 4.873714e-003, 4.692101e-003, 4.510382e-003, 4.330155e-003, 4.149792e-003, 3.973841e-003, 3.800534e-003, 3.591132e-003, + 3.386525e-003, 3.188905e-003, 2.998406e-003, 2.814495e-003, 2.639968e-003, 2.477539e-003, 2.328904e-003, 2.214565e-003, 2.152230e-003, + 2.107248e-003, 2.097384e-003, 2.132826e-003, 2.209479e-003, 2.324765e-003, 2.479425e-003, 2.669323e-003, 2.892824e-003, 3.150776e-003, + 3.438777e-003, 3.731097e-003, 4.028262e-003, 4.346258e-003, 4.683846e-003, 5.037701e-003, 5.403796e-003, 5.783733e-003, 6.173879e-003, + 6.571312e-003, 6.975531e-003, 7.374744e-003, 7.743463e-003, 8.114540e-003, 8.483192e-003, 8.848271e-003, 9.210386e-003, 9.567733e-003, + 9.916865e-003, 1.025665e-002, 1.058486e-002, 1.089737e-002, 1.114930e-002, 1.138346e-002, 1.159686e-002, 1.178847e-002, 1.195601e-002, + 1.209684e-002, 1.221104e-002, 1.229770e-002, 1.235401e-002, 1.237758e-002, 1.233230e-002, 1.224274e-002, 1.211450e-002, 1.194769e-002, + 1.174325e-002, 1.152313e-002, 1.129014e-002, 1.104205e-002, 1.077954e-002, 1.050322e-002, 1.018728e-002, 9.836217e-003, 9.469754e-003, + 9.086069e-003, 8.687566e-003, 8.273359e-003, 7.847524e-003, 7.408781e-003, 6.954362e-003, 6.485158e-003, 5.989630e-003, 5.452539e-003, + 4.904407e-003, 4.346624e-003, 3.779418e-003, 3.206588e-003, 2.629062e-003, 2.047606e-003, 1.458756e-003, 8.647198e-004, 2.692816e-004, + -3.544114e-004, -9.737030e-004, -1.589628e-003, -2.206354e-003, -2.825203e-003, -3.445050e-003, -4.062865e-003, -4.678760e-003, -5.291797e-003, + -5.901404e-003, -6.533748e-003, -7.165009e-003, -7.793608e-003, -8.422054e-003, -9.062479e-003, -9.728908e-003, -1.041701e-002, -1.112802e-002, + -1.185287e-002, -1.259134e-002, -1.335763e-002, -1.414169e-002, -1.493571e-002, -1.573761e-002, -1.654454e-002, -1.735285e-002, -1.816057e-002, + -1.896625e-002, -1.977008e-002, -2.056986e-002, -2.137308e-002, -2.218369e-002, -2.299155e-002, -2.379052e-002, -2.458579e-002, -2.537356e-002, + -2.615754e-002, -2.693491e-002, -2.770569e-002, -2.846715e-002, -2.922214e-002, -2.997941e-002, -3.072721e-002, -3.146359e-002, -3.218500e-002, + -3.289130e-002, -3.357847e-002, -3.424629e-002, -3.489545e-002, -3.552906e-002, -3.614490e-002, -3.675121e-002, -3.733952e-002, -3.790807e-002, + -3.845563e-002, -3.898046e-002, -3.948301e-002, -3.996247e-002, -4.041980e-002, -4.085415e-002, -4.127160e-002, -4.167252e-002, -4.205151e-002, + -4.240900e-002, -4.274547e-002, -4.305746e-002, -4.334545e-002, -4.360872e-002, -4.384774e-002, -4.406094e-002, -4.425287e-002, -4.442318e-002, + -4.457628e-002, -4.470857e-002, -4.481867e-002, -4.490666e-002, -4.497520e-002, -4.502040e-002, -4.504492e-002, -4.504375e-002, -4.501415e-002, + -4.496311e-002, -4.489659e-002, -4.481548e-002, -4.472174e-002, -4.460564e-002, -4.444506e-002, -4.424325e-002, -4.400043e-002, -4.372041e-002, + -4.340048e-002, -4.304127e-002, -4.264799e-002, -4.222865e-002, -4.178732e-002, -4.132973e-002, -4.085450e-002, -4.036287e-002, -3.985839e-002, + -3.934609e-002, -3.882400e-002, -3.829546e-002, -3.775228e-002, -3.720548e-002, -3.665955e-002, -3.611718e-002, -3.558143e-002, -3.505563e-002, + -3.454224e-002, -3.404174e-002, -3.356345e-002, -3.310855e-002, -3.267469e-002, -3.226179e-002, -3.187716e-002, -3.152511e-002, -3.121264e-002, + -3.093793e-002, -3.070672e-002, -3.051399e-002, -3.035898e-002, -3.024288e-002, -3.016763e-002, -3.013690e-002, -3.015837e-002, -3.021076e-002, + -3.027725e-002, -3.035939e-002, -3.045214e-002, -3.055773e-002, -3.067715e-002, -3.080632e-002, -3.094361e-002, -3.107647e-002, -3.121544e-002, + -3.135756e-002, -3.150256e-002, -3.164995e-002, -3.179803e-002, -3.194612e-002, -3.209115e-002, -3.223317e-002, -3.237179e-002, -3.249983e-002, + -3.261444e-002, -3.271806e-002, -3.280989e-002, -3.288672e-002, -3.294479e-002, -3.298477e-002, -3.300870e-002, -3.302849e-002, -3.303906e-002, + -3.303823e-002, -3.302549e-002, -3.299939e-002, -3.296521e-002, -3.291420e-002, -3.284295e-002, -3.275281e-002, -3.264845e-002, -3.253079e-002, + -3.239972e-002, -3.225656e-002, -3.209977e-002, -3.192673e-002, -3.173207e-002, -3.152171e-002, -3.128617e-002, -3.102538e-002, -3.073882e-002, + -3.042679e-002, -3.008612e-002, -2.972135e-002, -2.933698e-002, -2.891916e-002, -2.846609e-002, -2.797823e-002, -2.745716e-002, -2.690313e-002, + -2.631054e-002, -2.568272e-002, -2.501522e-002, -2.423081e-002, -2.327379e-002, -2.213933e-002, -2.083558e-002, -1.937900e-002, -1.777280e-002, + -1.602882e-002, -1.415470e-002, -1.216174e-002, -1.005800e-002, -7.852785e-003, -5.577669e-003, -3.225707e-003, -7.950705e-004, 1.705089e-003, + 4.264522e-003, 6.878565e-003, 9.538148e-003, 1.223065e-002, 1.495671e-002, 1.771635e-002, 2.048061e-002, 2.324050e-002, 2.600766e-002, + 2.877234e-002, 3.151481e-002, 3.423751e-002, 3.693500e-002, 3.958961e-002, 4.218836e-002, 4.472905e-002, 4.718425e-002, 4.952104e-002, + 5.177306e-002, 5.392289e-002, 5.595635e-002, 5.787058e-002, 5.965460e-002, 6.129793e-002, 6.278696e-002, 6.411225e-002, 6.528875e-002, + 6.635753e-002, 6.740654e-002, 6.842936e-002, 6.941785e-002, 7.037484e-002, 7.129397e-002, 7.217953e-002, 7.302982e-002, 7.384295e-002, + 7.462526e-002, 7.531136e-002, 7.595260e-002, 7.656510e-002, 7.714040e-002, 7.768690e-002, 7.820494e-002, 7.869486e-002, 7.915010e-002, + 7.957722e-002, 7.998262e-002, 8.030348e-002, 8.055689e-002, 8.077996e-002, 8.097339e-002, 8.113253e-002, 8.126649e-002, 8.137266e-002, + 8.144338e-002, 8.148531e-002, 8.150996e-002, 8.146725e-002, 8.133055e-002, 8.116286e-002, 8.097584e-002, 8.077114e-002, 8.054803e-002, + 8.030825e-002, 8.005100e-002, 7.977142e-002, 7.947684e-002, 7.915042e-002, 7.868925e-002, 7.820427e-002, 7.770436e-002, 7.718218e-002, + 7.663719e-002, 7.606605e-002, 7.546112e-002, 7.483145e-002, 7.418819e-002, 7.353566e-002, 7.274333e-002, 7.191560e-002, 7.106967e-002, + 7.014388e-002, 6.907394e-002, 6.787116e-002, 6.652866e-002, 6.505626e-002, 6.346981e-002, 6.177939e-002, 5.989445e-002, 5.785643e-002, + 5.573531e-002, 5.354293e-002, 5.129314e-002, 4.898206e-002, 4.661231e-002, 4.419842e-002, 4.174299e-002, 3.925274e-002, 3.667568e-002, + 3.399441e-002, 3.130476e-002, 2.862103e-002, 2.594957e-002, 2.329398e-002, 2.065878e-002, 1.805871e-002, 1.549285e-002, 1.299559e-002, + 1.052828e-002, 8.012932e-003, 5.579455e-003, 3.236911e-003, 9.938308e-004, -1.150984e-003, -3.174735e-003, -5.059686e-003, -6.809659e-003, + -8.412335e-003, -9.880705e-003, -1.134897e-002, -1.265636e-002, -1.379655e-002, -1.477553e-002, -1.562985e-002, -1.643292e-002, -1.718090e-002, + -1.789842e-002, -1.856768e-002, -1.919719e-002, -1.990426e-002, -2.061248e-002, -2.128293e-002, -2.192866e-002, -2.253926e-002, -2.310378e-002, + -2.362728e-002, -2.411584e-002, -2.457203e-002, -2.499363e-002, -2.544621e-002, -2.592946e-002, -2.638024e-002, -2.680883e-002, -2.718994e-002, + -2.751874e-002, -2.780663e-002, -2.806841e-002, -2.829710e-002, -2.849539e-002, -2.869767e-002, -2.894609e-002, -2.917919e-002, -2.939030e-002, + -2.959010e-002, -2.976742e-002, -2.993230e-002, -3.008729e-002, -3.022927e-002, -3.036955e-002, -3.049923e-002, -3.072052e-002, -3.092149e-002, + -3.111119e-002, -3.128595e-002, -3.144230e-002, -3.158714e-002, -3.171439e-002, -3.183511e-002, -3.195244e-002, -3.206430e-002, -3.223227e-002, + -3.239407e-002, -3.254359e-002, -3.268446e-002, -3.281057e-002, -3.292358e-002, -3.303140e-002, -3.311759e-002, -3.320461e-002, -3.328666e-002, + -3.339610e-002, -3.350562e-002, -3.361439e-002, -3.371436e-002, -3.380370e-002, -3.386828e-002, -3.392261e-002, -3.398305e-002, -3.404143e-002, + -3.410032e-002, -3.416745e-002, -3.425610e-002, -3.433801e-002, -3.439257e-002, -3.442454e-002, -3.445381e-002, -3.446423e-002, -3.445652e-002, + -3.444137e-002, -3.442753e-002, -3.440449e-002, -3.437195e-002, -3.434489e-002, -3.431202e-002, -3.425952e-002, -3.419591e-002, -3.412652e-002, + -3.405660e-002, -3.397037e-002, -3.386071e-002, -3.373766e-002, -3.358638e-002, -3.342210e-002, -3.324240e-002, -3.306331e-002, -3.287532e-002, + -3.266469e-002, -3.243061e-002, -3.219270e-002, -3.194425e-002, -3.168588e-002, -3.137974e-002, -3.105209e-002, -3.073460e-002, -3.041001e-002, + -3.008346e-002, -2.976094e-002, -2.943923e-002, -2.909910e-002, -2.874775e-002, -2.837853e-002, -2.798124e-002, -2.754629e-002, -2.709499e-002, + -2.663031e-002, -2.615798e-002, -2.567753e-002, -2.517289e-002, -2.465078e-002, -2.410762e-002, -2.355098e-002, -2.294501e-002, -2.226624e-002, + -2.157321e-002, -2.086015e-002, -2.010767e-002, -1.937109e-002, -1.861038e-002, -1.781892e-002, -1.701951e-002, -1.620382e-002, -1.536266e-002, + -1.443233e-002, -1.347535e-002, -1.251570e-002, -1.152925e-002, -1.049380e-002, -9.454898e-003, -8.412042e-003, -7.360208e-003, -6.294182e-003, + -5.200498e-003, -4.087726e-003, -2.968007e-003, -1.810612e-003, -6.067522e-004, 6.302428e-004, 1.892252e-003, 3.198992e-003, 4.546572e-003, + 5.901268e-003, 7.291148e-003, 8.664949e-003, 1.001731e-002, 1.138519e-002, 1.278020e-002, 1.423137e-002, 1.569641e-002, 1.720541e-002, + 1.874068e-002, 2.028562e-002, 2.185744e-002, 2.336896e-002, 2.475774e-002, 2.616468e-002, 2.759369e-002, 2.902556e-002, 3.044395e-002, + 3.185632e-002, 3.328766e-002, 3.470071e-002, 3.610923e-002, 3.750439e-002, 3.853541e-002, 3.952907e-002, 4.051893e-002, 4.139801e-002, + 4.211495e-002, 4.274570e-002, 4.326666e-002, 4.372189e-002, 4.408742e-002, 4.434519e-002, 4.400735e-002, 4.347527e-002, 4.287175e-002, + 4.221931e-002, 4.151379e-002, 4.081665e-002, 4.008411e-002, 3.929407e-002, 3.843269e-002, 3.753149e-002, 3.599452e-002, 3.409139e-002, + 3.220871e-002, 3.039499e-002, 2.859782e-002, 2.685830e-002, 2.515944e-002, 2.352059e-002, 2.190952e-002, 2.034107e-002, 1.829571e-002, + 1.549800e-002, 1.277763e-002, 1.024268e-002, 7.809282e-003, 5.582108e-003, 3.479477e-003, 1.563834e-003, -8.121426e-005, -1.495434e-003, + -3.060928e-003, -6.032519e-003, -8.871614e-003, -1.148263e-002, -1.384117e-002, -1.589989e-002, -1.754176e-002, -1.881581e-002, -1.954533e-002, + -2.012737e-002, -2.044263e-002, -2.304076e-002, -2.546563e-002, -2.722867e-002, -2.850422e-002, -2.929996e-002, -2.978185e-002, -2.999934e-002, + -2.980564e-002, -2.860301e-002, -2.476663e-002, -2.430392e-002, -2.519189e-002, -2.564640e-002, -2.601808e-002, -2.618570e-002, -2.617154e-002, + -2.595932e-002, -2.530687e-002, -2.424254e-002, -2.321012e-002, 1.663367e-003, -1.540108e-003, -6.532874e-003, -1.179565e-002, -1.711408e-002, + -2.178092e-002, -2.548677e-002, -2.801166e-002, -2.912911e-002, -2.735056e-002, 3.193593e-002, 9.422371e-002, 1.004558e-001, 9.912590e-002, + 9.465689e-002, 8.663026e-002, 4.994391e-002, 3.638524e-002, 2.312703e-002, 1.122065e-002, 0.000000e+000}, + {0.000000e+000, -1.917446e-002, -3.357208e-002, -4.485993e-002, -5.392290e-002, -6.133844e-002, -6.740496e-002, -7.236591e-002, -7.645188e-002, + -8.000724e-002, -8.299973e-002, -8.573523e-002, -8.813601e-002, -9.013370e-002, -9.176863e-002, -9.305295e-002, -9.400689e-002, -9.464648e-002, + -9.498033e-002, -9.506144e-002, -9.491476e-002, -9.476642e-002, -9.458767e-002, -9.423030e-002, -9.371314e-002, -9.303643e-002, -9.222339e-002, + -9.128921e-002, -9.024554e-002, -8.908451e-002, -8.781100e-002, -8.654811e-002, -8.545081e-002, -8.426564e-002, -8.299402e-002, -8.163693e-002, + -8.020261e-002, -7.869511e-002, -7.711815e-002, -7.548039e-002, -7.378460e-002, -7.206211e-002, -7.060756e-002, -6.911360e-002, -6.757632e-002, + -6.599840e-002, -6.438893e-002, -6.277236e-002, -6.112213e-002, -5.940795e-002, -5.765177e-002, -5.586309e-002, -5.433067e-002, -5.282395e-002, + -5.129899e-002, -4.975244e-002, -4.818755e-002, -4.660027e-002, -4.499140e-002, -4.336808e-002, -4.172639e-002, -4.006777e-002, -3.858851e-002, + -3.720490e-002, -3.580733e-002, -3.438978e-002, -3.294948e-002, -3.149376e-002, -3.001498e-002, -2.852463e-002, -2.700631e-002, -2.547403e-002, + -2.402804e-002, -2.273288e-002, -2.141482e-002, -2.007256e-002, -1.870569e-002, -1.732251e-002, -1.591873e-002, -1.450606e-002, -1.308348e-002, + -1.166399e-002, -1.026940e-002, -9.083404e-003, -7.893131e-003, -6.695535e-003, -5.494698e-003, -4.287636e-003, -3.085106e-003, -1.885421e-003, + -6.951573e-004, 4.881600e-004, 1.662182e-003, 2.623669e-003, 3.563596e-003, 4.505491e-003, 5.443136e-003, 6.384833e-003, 7.320107e-003, + 8.259680e-003, 9.200667e-003, 1.014429e-002, 1.109345e-002, 1.190536e-002, 1.266814e-002, 1.344581e-002, 1.423572e-002, 1.503570e-002, + 1.584482e-002, 1.665877e-002, 1.747891e-002, 1.830714e-002, 1.913794e-002, 1.989411e-002, 2.055821e-002, 2.122006e-002, 2.188852e-002, + 2.255585e-002, 2.322480e-002, 2.389807e-002, 2.456945e-002, 2.524273e-002, 2.591617e-002, 2.653953e-002, 2.703306e-002, 2.751417e-002, + 2.797891e-002, 2.843113e-002, 2.888141e-002, 2.932330e-002, 2.975701e-002, 3.018879e-002, 3.061672e-002, 3.103566e-002, 3.132751e-002, + 3.161941e-002, 3.191093e-002, 3.220225e-002, 3.249863e-002, 3.279823e-002, 3.309734e-002, 3.339875e-002, 3.370476e-002, 3.401055e-002, + 3.423728e-002, 3.444550e-002, 3.465039e-002, 3.485282e-002, 3.505077e-002, 3.524521e-002, 3.543399e-002, 3.561996e-002, 3.580024e-002, + 3.597413e-002, 3.609436e-002, 3.617334e-002, 3.624943e-002, 3.631673e-002, 3.637839e-002, 3.643366e-002, 3.648319e-002, 3.653290e-002, + 3.658274e-002, 3.662739e-002, 3.664504e-002, 3.661775e-002, 3.659165e-002, 3.656757e-002, 3.654867e-002, 3.653761e-002, 3.653013e-002, + 3.652263e-002, 3.651496e-002, 3.650953e-002, 3.649904e-002, 3.643903e-002, 3.638514e-002, 3.633951e-002, 3.629677e-002, 3.625838e-002, + 3.622128e-002, 3.618553e-002, 3.614227e-002, 3.608796e-002, 3.602448e-002, 3.592186e-002, 3.581270e-002, 3.570182e-002, 3.559374e-002, + 3.548919e-002, 3.538912e-002, 3.528891e-002, 3.519033e-002, 3.509710e-002, 3.500855e-002, 3.490307e-002, 3.478914e-002, 3.467962e-002, + 3.457497e-002, 3.447593e-002, 3.438088e-002, 3.429267e-002, 3.420769e-002, 3.412331e-002, 3.404129e-002, 3.395038e-002, 3.385377e-002, + 3.376349e-002, 3.367717e-002, 3.359452e-002, 3.351075e-002, 3.342211e-002, 3.332489e-002, 3.321856e-002, 3.310648e-002, 3.299112e-002, + 3.286239e-002, 3.273023e-002, 3.259424e-002, 3.244970e-002, 3.229691e-002, 3.213224e-002, 3.195314e-002, 3.175759e-002, 3.154535e-002, + 3.131696e-002, 3.107177e-002, 3.081463e-002, 3.054844e-002, 3.027322e-002, 2.998869e-002, 2.969435e-002, 2.939066e-002, 2.907574e-002, + 2.875039e-002, 2.841608e-002, 2.807611e-002, 2.773074e-002, 2.737984e-002, 2.702442e-002, 2.666572e-002, 2.630341e-002, 2.593684e-002, + 2.556620e-002, 2.519267e-002, 2.481882e-002, 2.445268e-002, 2.409979e-002, 2.375497e-002, 2.341708e-002, 2.307716e-002, 2.273164e-002, + 2.237549e-002, 2.200921e-002, 2.163239e-002, 2.124693e-002, 2.085590e-002, 2.047269e-002, 2.008043e-002, 1.967697e-002, 1.926279e-002, + 1.883700e-002, 1.839302e-002, 1.793299e-002, 1.745895e-002, 1.697244e-002, 1.647985e-002, 1.600584e-002, 1.552323e-002, 1.503196e-002, + 1.453342e-002, 1.402785e-002, 1.351147e-002, 1.298759e-002, 1.245719e-002, 1.192083e-002, 1.138255e-002, 1.086985e-002, 1.035852e-002, + 9.840363e-003, 9.312295e-003, 8.775670e-003, 8.231568e-003, 7.681165e-003, 7.126639e-003, 6.568512e-003, 6.005336e-003, 5.462882e-003, + 4.929542e-003, 4.382658e-003, 3.815087e-003, 3.224469e-003, 2.616268e-003, 1.990636e-003, 1.350767e-003, 7.016947e-004, 4.389856e-005, + -6.067232e-004, -1.231697e-003, -1.868920e-003, -2.513248e-003, -3.164459e-003, -3.818774e-003, -4.476777e-003, -5.137901e-003, -5.800929e-003, + -6.464408e-003, -7.122740e-003, -7.727417e-003, -8.332544e-003, -8.937141e-003, -9.542490e-003, -1.014530e-002, -1.074228e-002, -1.133222e-002, + -1.191453e-002, -1.248632e-002, -1.305039e-002, -1.354845e-002, -1.403250e-002, -1.451553e-002, -1.499934e-002, -1.548424e-002, -1.596907e-002, + -1.645463e-002, -1.693731e-002, -1.741907e-002, -1.789961e-002, -1.833226e-002, -1.874193e-002, -1.916374e-002, -1.959630e-002, -2.003797e-002, + -2.048484e-002, -2.093619e-002, -2.139084e-002, -2.184684e-002, -2.230407e-002, -2.272553e-002, -2.309865e-002, -2.346817e-002, -2.383434e-002, + -2.419793e-002, -2.455811e-002, -2.491016e-002, -2.525650e-002, -2.559477e-002, -2.592536e-002, -2.623229e-002, -2.645505e-002, -2.667012e-002, + -2.688084e-002, -2.709237e-002, -2.731168e-002, -2.754372e-002, -2.778848e-002, -2.804347e-002, -2.830894e-002, -2.858594e-002, -2.878516e-002, + -2.899026e-002, -2.920274e-002, -2.942234e-002, -2.964643e-002, -2.987455e-002, -3.011029e-002, -3.035074e-002, -3.058768e-002, -3.081560e-002, + -3.096361e-002, -3.107537e-002, -3.117723e-002, -3.126817e-002, -3.134781e-002, -3.141687e-002, -3.147970e-002, -3.153434e-002, -3.157991e-002, + -3.161908e-002, -3.160049e-002, -3.152558e-002, -3.144618e-002, -3.136124e-002, -3.127568e-002, -3.119045e-002, -3.110238e-002, -3.101343e-002, + -3.092555e-002, -3.083977e-002, -3.073487e-002, -3.056175e-002, -3.039486e-002, -3.024078e-002, -3.009392e-002, -2.995230e-002, -2.981630e-002, + -2.968627e-002, -2.956511e-002, -2.945145e-002, -2.934473e-002, -2.914892e-002, -2.896167e-002, -2.878619e-002, -2.862006e-002, -2.846599e-002, + -2.832434e-002, -2.819575e-002, -2.807609e-002, -2.796652e-002, -2.786538e-002, -2.769262e-002, -2.751077e-002, -2.734129e-002, -2.718233e-002, + -2.703879e-002, -2.690162e-002, -2.677085e-002, -2.664745e-002, -2.653349e-002, -2.642912e-002, -2.628001e-002, -2.609563e-002, -2.592181e-002, + -2.575652e-002, -2.560114e-002, -2.545607e-002, -2.531877e-002, -2.519355e-002, -2.508190e-002, -2.498252e-002, -2.486268e-002, -2.469393e-002, + -2.453210e-002, -2.437617e-002, -2.422835e-002, -2.408568e-002, -2.394476e-002, -2.380653e-002, -2.367064e-002, -2.353609e-002, -2.338756e-002, + -2.314842e-002, -2.290056e-002, -2.264949e-002, -2.239572e-002, -2.214139e-002, -2.189076e-002, -2.164275e-002, -2.139338e-002, -2.114066e-002, + -2.088098e-002, -2.053391e-002, -2.016921e-002, -1.980130e-002, -1.943307e-002, -1.907297e-002, -1.872935e-002, -1.840240e-002, -1.808596e-002, + -1.777645e-002, -1.747299e-002, -1.711816e-002, -1.673914e-002, -1.636362e-002, -1.599391e-002, -1.562728e-002, -1.526190e-002, -1.489888e-002, + -1.453651e-002, -1.417805e-002, -1.382231e-002, -1.343602e-002, -1.300491e-002, -1.257843e-002, -1.215241e-002, -1.172580e-002, -1.130559e-002, + -1.089582e-002, -1.049300e-002, -1.009362e-002, -9.695659e-003, -9.285519e-003, -8.816713e-003, -8.352234e-003, -7.895409e-003, -7.441561e-003, + -6.988173e-003, -6.532293e-003, -6.076290e-003, -5.617894e-003, -5.162532e-003, -4.716524e-003, -4.213511e-003, -3.709718e-003, -3.206436e-003, + -2.707442e-003, -2.209886e-003, -1.711461e-003, -1.218845e-003, -7.329040e-004, -2.519923e-004, 2.189814e-004, 7.196384e-004, 1.224210e-003, + 1.719773e-003, 2.201916e-003, 2.672677e-003, 3.133300e-003, 3.584433e-003, 4.022172e-003, 4.445831e-003, 4.859802e-003, 5.288276e-003, + 5.729418e-003, 6.164774e-003, 6.595163e-003, 7.019463e-003, 7.435052e-003, 7.844685e-003, 8.253468e-003, 8.662508e-003, 9.075197e-003, + 9.494715e-003, 9.933839e-003, 1.035931e-002, 1.077187e-002, 1.115711e-002, 1.148821e-002, 1.176705e-002, 1.200211e-002, 1.219583e-002, + 1.234683e-002, 1.246053e-002, 1.256879e-002, 1.263062e-002, 1.264619e-002, 1.262526e-002, 1.257657e-002, 1.249829e-002, 1.239119e-002, + 1.226101e-002, 1.210424e-002, 1.192751e-002, 1.176205e-002, 1.159270e-002, 1.141325e-002, 1.122008e-002, 1.102307e-002, 1.081814e-002, + 1.061591e-002, 1.041970e-002, 1.022444e-002, 1.003443e-002, 9.857186e-003, 9.697819e-003, 9.543568e-003, 9.394526e-003, 9.259355e-003, + 9.135011e-003, 9.023077e-003, 8.925633e-003, 8.847889e-003, 8.792825e-003, 8.768596e-003, 8.771325e-003, 8.793507e-003, 8.823824e-003, + 8.850958e-003, 8.873715e-003, 8.896308e-003, 8.919653e-003, 8.933983e-003, 8.939269e-003, 8.947762e-003, 8.975280e-003, 9.002827e-003, + 9.031671e-003, 9.057938e-003, 9.084063e-003, 9.105906e-003, 9.132004e-003, 9.164379e-003, 9.200538e-003, 9.239389e-003, 9.298251e-003, + 9.366940e-003, 9.447092e-003, 9.539115e-003, 9.638633e-003, 9.749691e-003, 9.878277e-003, 1.001388e-002, 1.014726e-002, 1.028210e-002, + 1.043178e-002, 1.058861e-002, 1.074030e-002, 1.088568e-002, 1.103104e-002, 1.118389e-002, 1.134514e-002, 1.151074e-002, 1.166761e-002, + 1.181343e-002, 1.195476e-002, 1.210631e-002, 1.225648e-002, 1.240763e-002, 1.255400e-002, 1.269543e-002, 1.283047e-002, 1.295913e-002, + 1.308551e-002, 1.321516e-002, 1.334864e-002, 1.350993e-002, 1.366855e-002, 1.383075e-002, 1.399476e-002, 1.416295e-002, 1.433064e-002, + 1.449699e-002, 1.466705e-002, 1.483713e-002, 1.502819e-002, 1.529944e-002, 1.560268e-002, 1.592598e-002, 1.626904e-002, 1.662632e-002, + 1.699631e-002, 1.737554e-002, 1.775944e-002, 1.813723e-002, 1.851182e-002, 1.892832e-002, 1.936569e-002, 1.979890e-002, 2.022158e-002, + 2.062698e-002, 2.102278e-002, 2.140795e-002, 2.178210e-002, 2.214103e-002, 2.247572e-002, 2.281408e-002, 2.315818e-002, 2.347566e-002, + 2.376343e-002, 2.401848e-002, 2.422955e-002, 2.440734e-002, 2.454562e-002, 2.464994e-002, 2.471761e-002, 2.477157e-002, 2.483714e-002, + 2.486794e-002, 2.484086e-002, 2.475764e-002, 2.462347e-002, 2.443064e-002, 2.418035e-002, 2.387274e-002, 2.350931e-002, 2.308710e-002, + 2.272446e-002, 2.233632e-002, 2.191968e-002, 2.147619e-002, 2.100039e-002, 2.048852e-002, 1.993961e-002, 1.935590e-002, 1.874341e-002, + 1.810521e-002, 1.753298e-002, 1.695167e-002, 1.634195e-002, 1.571312e-002, 1.506808e-002, 1.440274e-002, 1.371353e-002, 1.301384e-002, + 1.229053e-002, 1.155709e-002, 1.089028e-002, 1.026460e-002, 9.623148e-003, 8.968709e-003, 8.297757e-003, 7.617934e-003, 6.937591e-003, + 6.259397e-003, 5.567104e-003, 4.877408e-003, 4.234189e-003, 3.681638e-003, 3.123003e-003, 2.563191e-003, 1.993958e-003, 1.405985e-003, + 8.075303e-004, 1.878482e-004, -4.555642e-004, -1.106193e-003, -1.741581e-003, -2.249442e-003, -2.773752e-003, -3.310069e-003, -3.867641e-003, + -4.443892e-003, -5.038108e-003, -5.656982e-003, -6.290361e-003, -6.920992e-003, -7.566403e-003, -8.062841e-003, -8.545951e-003, -9.037743e-003, + -9.551054e-003, -1.008472e-002, -1.063359e-002, -1.119183e-002, -1.175770e-002, -1.233640e-002, -1.293430e-002, -1.342456e-002, -1.388017e-002, + -1.433659e-002, -1.479283e-002, -1.525609e-002, -1.573319e-002, -1.622192e-002, -1.672732e-002, -1.724396e-002, -1.777834e-002, -1.824211e-002, + -1.859649e-002, -1.896824e-002, -1.935861e-002, -1.976412e-002, -2.018267e-002, -2.060665e-002, -2.104167e-002, -2.149765e-002, -2.196816e-002, + -2.238555e-002, -2.263105e-002, -2.286099e-002, -2.309679e-002, -2.335761e-002, -2.360879e-002, -2.383739e-002, -2.404131e-002, -2.421688e-002, + -2.438284e-002, -2.455886e-002, -2.453385e-002, -2.450025e-002, -2.446302e-002, -2.441445e-002, -2.435980e-002, -2.430471e-002, -2.425663e-002, + -2.421236e-002, -2.417048e-002, -2.412329e-002, -2.393496e-002, -2.371439e-002, -2.349890e-002, -2.328333e-002, -2.306380e-002, -2.285338e-002, + -2.265720e-002, -2.245591e-002, -2.225555e-002, -2.204958e-002, -2.175324e-002, -2.138768e-002, -2.100773e-002, -2.061061e-002, -2.019770e-002, + -1.979063e-002, -1.939110e-002, -1.898675e-002, -1.857286e-002, -1.815986e-002, -1.768190e-002, -1.707631e-002, -1.646493e-002, -1.586174e-002, + -1.527974e-002, -1.469667e-002, -1.412950e-002, -1.359741e-002, -1.306783e-002, -1.254024e-002, -1.202383e-002, -1.135842e-002, -1.070901e-002, + -1.007462e-002, -9.442801e-003, -8.819133e-003, -8.224198e-003, -7.651293e-003, -7.099552e-003, -6.545813e-003, -6.013025e-003, -5.357565e-003, + -4.699674e-003, -4.034334e-003, -3.381717e-003, -2.742704e-003, -2.119204e-003, -1.518067e-003, -9.144723e-004, -3.191589e-004, 2.692416e-004, + 9.154375e-004, 1.611244e-003, 2.300195e-003, 2.999192e-003, 3.691826e-003, 4.367659e-003, 5.012450e-003, 5.640230e-003, 6.257071e-003, + 6.847229e-003, 7.467051e-003, 8.164145e-003, 8.843807e-003, 9.512631e-003, 1.016594e-002, 1.079930e-002, 1.140911e-002, 1.200100e-002, + 1.258150e-002, 1.312002e-002, 1.362417e-002, 1.421968e-002, 1.479411e-002, 1.535432e-002, 1.587872e-002, 1.639481e-002, 1.689175e-002, + 1.735602e-002, 1.779859e-002, 1.821660e-002, 1.862369e-002, 1.910846e-002, 1.959168e-002, 2.002075e-002, 2.042901e-002, 2.082711e-002, + 2.121867e-002, 2.158960e-002, 2.191764e-002, 2.219604e-002, 2.244719e-002, 2.272196e-002, 2.298230e-002, 2.322330e-002, 2.343503e-002, + 2.358212e-002, 2.367929e-002, 2.376000e-002, 2.381506e-002, 2.382425e-002, 2.378198e-002, 2.373273e-002, 2.370051e-002, 2.363768e-002, + 2.354695e-002, 2.346150e-002, 2.333068e-002, 2.316178e-002, 2.296032e-002, 2.271135e-002, 2.245856e-002, 2.219101e-002, 2.197463e-002, + 2.172716e-002, 2.145696e-002, 2.110164e-002, 2.071442e-002, 2.031747e-002, 1.989726e-002, 1.943063e-002, 1.892547e-002, 1.841200e-002, + 1.801767e-002, 1.758195e-002, 1.711026e-002, 1.662520e-002, 1.606876e-002, 1.547467e-002, 1.484573e-002, 1.419956e-002, 1.351311e-002, + 1.276732e-002, 1.221456e-002, 1.168424e-002, 1.112611e-002, 1.048345e-002, 9.771772e-003, 9.006919e-003, 8.209925e-003, 7.291518e-003, + 6.303868e-003, 5.262990e-003, 4.369873e-003, 3.622963e-003, 2.800930e-003, 1.908438e-003, 9.301908e-004, -6.751147e-005, -1.194167e-003, + -2.400381e-003, -3.650491e-003, -4.973638e-003, -6.172517e-003, -7.077792e-003, -8.140636e-003, -9.311190e-003, -1.056024e-002, -1.187086e-002, + -1.318945e-002, -1.458420e-002, -1.607262e-002, -1.762410e-002, -1.910481e-002, -1.990147e-002, -2.075472e-002, -2.166737e-002, -2.258154e-002, + -2.354181e-002, -2.455364e-002, -2.554955e-002, -2.661309e-002, -2.771601e-002, -2.887493e-002, -2.924331e-002, -2.948401e-002, -2.977495e-002, + -3.017102e-002, -3.077033e-002, -3.155324e-002, -3.228826e-002, -3.296981e-002, -3.366864e-002, -3.448449e-002, -3.440618e-002, -3.395232e-002, + -3.357070e-002, -3.337851e-002, -3.329987e-002, -3.334036e-002, -3.349073e-002, -3.367563e-002, -3.396043e-002, -3.427566e-002, -3.387107e-002, + -3.239505e-002, -3.104770e-002, -2.987876e-002, -2.878359e-002, -2.783670e-002, -2.702611e-002, -2.640568e-002, -2.611636e-002, -2.596032e-002, + -2.551346e-002, -2.288226e-002, -2.024662e-002, -1.773271e-002, -1.531131e-002, -1.297201e-002, -1.082345e-002, -8.923257e-003, -7.666208e-003, + -6.381686e-003, -5.168593e-003, -7.141463e-004, 3.875751e-003, 7.865452e-003, 1.127558e-002, 1.435102e-002, 1.748499e-002, 2.080198e-002, + 2.397681e-002, 2.641973e-002, 2.652330e-002, 3.077613e-002, 3.656855e-002, 4.173548e-002, 4.632218e-002, 4.950615e-002, 5.196148e-002, + 5.385484e-002, 5.571919e-002, 5.787208e-002, 6.100562e-002, 4.281794e-002, 5.140241e-002, 6.137765e-002, 7.164735e-002, 8.226066e-002, + 9.222150e-002, 1.001564e-001, 1.059300e-001, 1.087946e-001, 1.051641e-001, -5.533066e-002, -1.499467e-001, -1.670421e-001, -1.696878e-001, + -1.655690e-001, -1.543446e-001, -8.028114e-002, -5.674685e-002, -3.469316e-002, -1.615907e-002, 0.000000e+000}, + {0.000000e+000, -1.513089e-002, -2.602010e-002, -3.431418e-002, -4.090427e-002, -4.616818e-002, -5.043410e-002, -5.385045e-002, -5.663978e-002, + -5.873895e-002, -6.041027e-002, -6.202552e-002, -6.324324e-002, -6.409526e-002, -6.465596e-002, -6.497530e-002, -6.512038e-002, -6.510237e-002, + -6.496655e-002, -6.467680e-002, -6.425072e-002, -6.390810e-002, -6.360686e-002, -6.318816e-002, -6.265719e-002, -6.200858e-002, -6.123204e-002, + -6.034566e-002, -5.934985e-002, -5.825208e-002, -5.706811e-002, -5.592179e-002, -5.495033e-002, -5.391145e-002, -5.280582e-002, -5.164470e-002, + -5.043659e-002, -4.917047e-002, -4.786199e-002, -4.651296e-002, -4.512187e-002, -4.372417e-002, -4.259674e-002, -4.142757e-002, -4.021568e-002, + -3.895281e-002, -3.756986e-002, -3.595483e-002, -3.432941e-002, -3.291504e-002, -3.155027e-002, -3.015986e-002, -2.900770e-002, -2.787021e-002, + -2.670250e-002, -2.551076e-002, -2.429558e-002, -2.306075e-002, -2.180418e-002, -2.053092e-002, -1.923246e-002, -1.792083e-002, -1.676809e-002, + -1.569928e-002, -1.461997e-002, -1.352298e-002, -1.240453e-002, -1.126905e-002, -1.011961e-002, -8.946282e-003, -7.761666e-003, -6.560747e-003, + -5.441541e-003, -4.469995e-003, -3.486162e-003, -2.482251e-003, -1.468615e-003, -4.419604e-004, 5.903142e-004, 1.616780e-003, 2.627548e-003, + 3.614490e-003, 4.530195e-003, 5.220392e-003, 5.888836e-003, 6.535997e-003, 7.162740e-003, 7.777999e-003, 8.383193e-003, 8.984390e-003, + 9.598139e-003, 1.021886e-002, 1.084786e-002, 1.129105e-002, 1.173658e-002, 1.220060e-002, 1.267773e-002, 1.317340e-002, 1.369284e-002, + 1.423043e-002, 1.478897e-002, 1.536608e-002, 1.596958e-002, 1.646712e-002, 1.691547e-002, 1.736213e-002, 1.781475e-002, 1.827348e-002, + 1.875152e-002, 1.924053e-002, 1.974396e-002, 2.027162e-002, 2.080416e-002, 2.126764e-002, 2.164977e-002, 2.203226e-002, 2.243031e-002, + 2.283490e-002, 2.324175e-002, 2.365723e-002, 2.407445e-002, 2.449577e-002, 2.491619e-002, 2.528548e-002, 2.552624e-002, 2.575306e-002, + 2.596864e-002, 2.617468e-002, 2.636997e-002, 2.656103e-002, 2.674751e-002, 2.693041e-002, 2.711586e-002, 2.730100e-002, 2.736327e-002, + 2.743111e-002, 2.750780e-002, 2.759409e-002, 2.769150e-002, 2.779709e-002, 2.791160e-002, 2.803236e-002, 2.815873e-002, 2.829422e-002, + 2.835326e-002, 2.839156e-002, 2.843231e-002, 2.847664e-002, 2.852130e-002, 2.855997e-002, 2.859675e-002, 2.863326e-002, 2.866769e-002, + 2.869808e-002, 2.867806e-002, 2.861527e-002, 2.854657e-002, 2.847173e-002, 2.838941e-002, 2.830141e-002, 2.820868e-002, 2.811522e-002, + 2.801889e-002, 2.792201e-002, 2.779906e-002, 2.762858e-002, 2.745881e-002, 2.729264e-002, 2.713420e-002, 2.698653e-002, 2.684412e-002, + 2.670519e-002, 2.656679e-002, 2.642590e-002, 2.627830e-002, 2.608067e-002, 2.588844e-002, 2.570033e-002, 2.551674e-002, 2.533743e-002, + 2.515533e-002, 2.496436e-002, 2.476015e-002, 2.454025e-002, 2.430273e-002, 2.401812e-002, 2.371948e-002, 2.341444e-002, 2.310233e-002, + 2.278806e-002, 2.246851e-002, 2.214482e-002, 2.181777e-002, 2.149282e-002, 2.116704e-002, 2.082180e-002, 2.046688e-002, 2.011597e-002, + 1.976830e-002, 1.941910e-002, 1.907145e-002, 1.872795e-002, 1.838517e-002, 1.804258e-002, 1.770120e-002, 1.735962e-002, 1.701964e-002, + 1.668808e-002, 1.636594e-002, 1.604862e-002, 1.573716e-002, 1.542418e-002, 1.510695e-002, 1.478228e-002, 1.445394e-002, 1.412564e-002, + 1.379080e-002, 1.345690e-002, 1.312278e-002, 1.278771e-002, 1.244913e-002, 1.209882e-002, 1.173455e-002, 1.135088e-002, 1.095260e-002, + 1.054188e-002, 1.012414e-002, 9.700809e-003, 9.271810e-003, 8.839668e-003, 8.404347e-003, 7.965961e-003, 7.525859e-003, 7.081762e-003, + 6.635759e-003, 6.189788e-003, 5.751523e-003, 5.316125e-003, 4.882661e-003, 4.451991e-003, 4.028038e-003, 3.611153e-003, 3.198070e-003, + 2.792951e-003, 2.393370e-003, 2.005082e-003, 1.635519e-003, 1.288480e-003, 9.567046e-004, 6.365004e-004, 3.212361e-004, 5.928453e-006, + -3.154944e-004, -6.411000e-004, -9.740552e-004, -1.312533e-003, -1.650670e-003, -1.975120e-003, -2.302251e-003, -2.634752e-003, -2.972395e-003, + -3.317597e-003, -3.672522e-003, -4.038256e-003, -4.410585e-003, -4.787997e-003, -5.165835e-003, -5.515990e-003, -5.867216e-003, -6.218242e-003, + -6.567719e-003, -6.910353e-003, -7.247149e-003, -7.572990e-003, -7.889551e-003, -8.193243e-003, -8.481195e-003, -8.727862e-003, -8.956996e-003, + -9.175427e-003, -9.385148e-003, -9.585352e-003, -9.773606e-003, -9.949173e-003, -1.011002e-002, -1.025693e-002, -1.039295e-002, -1.049508e-002, + -1.057427e-002, -1.065307e-002, -1.074220e-002, -1.084373e-002, -1.095054e-002, -1.105919e-002, -1.116762e-002, -1.127459e-002, -1.138394e-002, + -1.147771e-002, -1.154217e-002, -1.160747e-002, -1.167134e-002, -1.173498e-002, -1.179759e-002, -1.185616e-002, -1.191271e-002, -1.196644e-002, + -1.201577e-002, -1.205772e-002, -1.205812e-002, -1.205706e-002, -1.205574e-002, -1.205392e-002, -1.205244e-002, -1.204529e-002, -1.203073e-002, + -1.201014e-002, -1.197757e-002, -1.194330e-002, -1.187221e-002, -1.180150e-002, -1.173956e-002, -1.168769e-002, -1.164754e-002, -1.161830e-002, + -1.159623e-002, -1.157804e-002, -1.156409e-002, -1.155810e-002, -1.153615e-002, -1.151150e-002, -1.150891e-002, -1.152607e-002, -1.156002e-002, + -1.160697e-002, -1.166409e-002, -1.172874e-002, -1.180173e-002, -1.188266e-002, -1.194855e-002, -1.198838e-002, -1.203193e-002, -1.207790e-002, + -1.212458e-002, -1.217171e-002, -1.221373e-002, -1.224913e-002, -1.227811e-002, -1.230136e-002, -1.230835e-002, -1.227079e-002, -1.222652e-002, + -1.217982e-002, -1.213951e-002, -1.211423e-002, -1.210894e-002, -1.212638e-002, -1.216219e-002, -1.221750e-002, -1.228936e-002, -1.233243e-002, + -1.239153e-002, -1.246416e-002, -1.254977e-002, -1.264650e-002, -1.275324e-002, -1.286705e-002, -1.299239e-002, -1.314277e-002, -1.333006e-002, + -1.351766e-002, -1.372490e-002, -1.396419e-002, -1.423343e-002, -1.452615e-002, -1.484362e-002, -1.518574e-002, -1.554928e-002, -1.593152e-002, + -1.632979e-002, -1.672023e-002, -1.710162e-002, -1.749700e-002, -1.790507e-002, -1.832401e-002, -1.875083e-002, -1.918725e-002, -1.963176e-002, + -2.007908e-002, -2.053454e-002, -2.098924e-002, -2.142072e-002, -2.186073e-002, -2.230760e-002, -2.275536e-002, -2.320168e-002, -2.364440e-002, + -2.408173e-002, -2.451069e-002, -2.493208e-002, -2.534058e-002, -2.569365e-002, -2.603318e-002, -2.635640e-002, -2.666028e-002, -2.694545e-002, + -2.720776e-002, -2.744377e-002, -2.765074e-002, -2.782437e-002, -2.796399e-002, -2.803225e-002, -2.805593e-002, -2.804239e-002, -2.798377e-002, + -2.789074e-002, -2.777740e-002, -2.764849e-002, -2.750532e-002, -2.734751e-002, -2.717681e-002, -2.697115e-002, -2.673534e-002, -2.649358e-002, + -2.624620e-002, -2.599445e-002, -2.573498e-002, -2.546694e-002, -2.519169e-002, -2.491340e-002, -2.463827e-002, -2.435246e-002, -2.404021e-002, + -2.372551e-002, -2.340497e-002, -2.307568e-002, -2.274493e-002, -2.240708e-002, -2.206356e-002, -2.171308e-002, -2.135277e-002, -2.097535e-002, + -2.054366e-002, -2.009561e-002, -1.963690e-002, -1.917174e-002, -1.870415e-002, -1.823301e-002, -1.776092e-002, -1.728131e-002, -1.679752e-002, + -1.630996e-002, -1.578492e-002, -1.525017e-002, -1.471318e-002, -1.418124e-002, -1.365779e-002, -1.315225e-002, -1.266586e-002, -1.219142e-002, + -1.172513e-002, -1.126747e-002, -1.079278e-002, -1.030947e-002, -9.834041e-003, -9.368302e-003, -8.910686e-003, -8.458190e-003, -8.015212e-003, + -7.581713e-003, -7.153917e-003, -6.738466e-003, -6.321619e-003, -5.887598e-003, -5.464705e-003, -5.051872e-003, -4.648289e-003, -4.256226e-003, + -3.879666e-003, -3.519215e-003, -3.167309e-003, -2.824317e-003, -2.481970e-003, -2.118697e-003, -1.761598e-003, -1.412446e-003, -1.070596e-003, + -7.359840e-004, -4.031155e-004, -7.382592e-005, 2.493033e-004, 5.663020e-004, 8.801216e-004, 1.220723e-003, 1.555849e-003, 1.886527e-003, + 2.205650e-003, 2.517195e-003, 2.825822e-003, 3.130473e-003, 3.422240e-003, 3.699429e-003, 3.962649e-003, 4.238399e-003, 4.510396e-003, + 4.770329e-003, 5.016804e-003, 5.245843e-003, 5.459187e-003, 5.658271e-003, 5.835690e-003, 5.997559e-003, 6.150148e-003, 6.317235e-003, + 6.496123e-003, 6.669149e-003, 6.840892e-003, 7.011295e-003, 7.183894e-003, 7.363536e-003, 7.553048e-003, 7.747728e-003, 7.957222e-003, + 8.185795e-003, 8.444409e-003, 8.696779e-003, 8.947623e-003, 9.205545e-003, 9.495967e-003, 9.815850e-003, 1.016410e-002, 1.053676e-002, + 1.093401e-002, 1.135721e-002, 1.183538e-002, 1.233183e-002, 1.284113e-002, 1.336313e-002, 1.389175e-002, 1.443221e-002, 1.498371e-002, + 1.554731e-002, 1.613216e-002, 1.672906e-002, 1.736281e-002, 1.800920e-002, 1.865990e-002, 1.931648e-002, 1.998390e-002, 2.068246e-002, + 2.139496e-002, 2.210936e-002, 2.282174e-002, 2.353467e-002, 2.426263e-002, 2.500194e-002, 2.573484e-002, 2.644758e-002, 2.713721e-002, + 2.780455e-002, 2.845263e-002, 2.907733e-002, 2.967072e-002, 3.023386e-002, 3.076778e-002, 3.126826e-002, 3.171708e-002, 3.213243e-002, + 3.252981e-002, 3.291081e-002, 3.328521e-002, 3.364506e-002, 3.398167e-002, 3.430133e-002, 3.461760e-002, 3.495420e-002, 3.527415e-002, + 3.557752e-002, 3.585584e-002, 3.611807e-002, 3.637563e-002, 3.661985e-002, 3.685572e-002, 3.707784e-002, 3.728932e-002, 3.753044e-002, + 3.777532e-002, 3.801211e-002, 3.823868e-002, 3.845884e-002, 3.868194e-002, 3.890723e-002, 3.913044e-002, 3.933410e-002, 3.951265e-002, + 3.969467e-002, 3.986620e-002, 4.000583e-002, 4.012215e-002, 4.021885e-002, 4.029810e-002, 4.035742e-002, 4.039236e-002, 4.039712e-002, + 4.036616e-002, 4.033204e-002, 4.029648e-002, 4.022641e-002, 4.012608e-002, 3.999744e-002, 3.984918e-002, 3.967742e-002, 3.949117e-002, + 3.928316e-002, 3.904585e-002, 3.879365e-002, 3.855934e-002, 3.830877e-002, 3.803975e-002, 3.776626e-002, 3.747283e-002, 3.715729e-002, + 3.682571e-002, 3.648086e-002, 3.611114e-002, 3.564106e-002, 3.506949e-002, 3.433176e-002, 3.343043e-002, 3.237910e-002, 3.118928e-002, + 2.986394e-002, 2.840973e-002, 2.683723e-002, 2.514909e-002, 2.334923e-002, 2.149750e-002, 1.957726e-002, 1.758677e-002, 1.553095e-002, + 1.342743e-002, 1.127428e-002, 9.082457e-003, 6.852831e-003, 4.600274e-003, 2.330826e-003, 9.670992e-005, -2.107442e-003, -4.299697e-003, + -6.464254e-003, -8.591899e-003, -1.068140e-002, -1.271764e-002, -1.470220e-002, -1.663056e-002, -1.848616e-002, -2.023605e-002, -2.184191e-002, + -2.334117e-002, -2.472527e-002, -2.598871e-002, -2.713681e-002, -2.816199e-002, -2.904829e-002, -2.979090e-002, -3.038971e-002, -3.082379e-002, + -3.114503e-002, -3.143273e-002, -3.170015e-002, -3.195930e-002, -3.222633e-002, -3.248056e-002, -3.272020e-002, -3.295030e-002, -3.318250e-002, + -3.340173e-002, -3.354345e-002, -3.365604e-002, -3.375452e-002, -3.384938e-002, -3.393122e-002, -3.400394e-002, -3.407440e-002, -3.413313e-002, + -3.417345e-002, -3.419318e-002, -3.415055e-002, -3.406551e-002, -3.397012e-002, -3.387051e-002, -3.376835e-002, -3.365763e-002, -3.354182e-002, + -3.341585e-002, -3.327238e-002, -3.311810e-002, -3.292451e-002, -3.264383e-002, -3.235724e-002, -3.206325e-002, -3.174711e-002, -3.142888e-002, + -3.110587e-002, -3.076615e-002, -3.042550e-002, -3.008008e-002, -2.971628e-002, -2.925026e-002, -2.879026e-002, -2.832373e-002, -2.784696e-002, + -2.738232e-002, -2.692980e-002, -2.648655e-002, -2.605672e-002, -2.562160e-002, -2.517508e-002, -2.461720e-002, -2.404321e-002, -2.346606e-002, + -2.287759e-002, -2.222208e-002, -2.149028e-002, -2.070422e-002, -1.987461e-002, -1.899694e-002, -1.807579e-002, -1.703213e-002, -1.589604e-002, + -1.474006e-002, -1.356661e-002, -1.236401e-002, -1.115557e-002, -9.944304e-003, -8.711230e-003, -7.494120e-003, -6.293101e-003, -5.044246e-003, + -3.726440e-003, -2.437434e-003, -1.155878e-003, 9.647393e-005, 1.291261e-003, 2.445642e-003, 3.555132e-003, 4.633891e-003, 5.670950e-003, + 6.696062e-003, 7.798756e-003, 8.843143e-003, 9.824123e-003, 1.074537e-002, 1.159825e-002, 1.238098e-002, 1.309580e-002, 1.374602e-002, + 1.432323e-002, 1.481198e-002, 1.537097e-002, 1.583122e-002, 1.618339e-002, 1.643916e-002, 1.660044e-002, 1.670655e-002, 1.676105e-002, + 1.675012e-002, 1.669459e-002, 1.659681e-002, 1.661749e-002, 1.663854e-002, 1.659712e-002, 1.650681e-002, 1.636235e-002, 1.618850e-002, + 1.597120e-002, 1.572013e-002, 1.543482e-002, 1.511241e-002, 1.487642e-002, 1.470792e-002, 1.450853e-002, 1.428672e-002, 1.402424e-002, + 1.372127e-002, 1.340156e-002, 1.305132e-002, 1.266906e-002, 1.227141e-002, 1.191321e-002, 1.171112e-002, 1.148816e-002, 1.124837e-002, + 1.095445e-002, 1.060459e-002, 1.018300e-002, 9.717040e-003, 9.206530e-003, 8.658816e-003, 8.082181e-003, 7.737456e-003, 7.362713e-003, + 6.935706e-003, 6.468731e-003, 5.961535e-003, 5.401857e-003, 4.820511e-003, 4.189308e-003, 3.543445e-003, 2.881612e-003, 2.432564e-003, + 2.014114e-003, 1.546133e-003, 1.061830e-003, 5.330611e-004, -3.362993e-005, -6.294411e-004, -1.241024e-003, -1.885588e-003, -2.549440e-003, + -3.026354e-003, -3.377187e-003, -3.742061e-003, -4.124591e-003, -4.550839e-003, -4.964029e-003, -5.386749e-003, -5.846407e-003, -6.332562e-003, + -6.866081e-003, -7.254485e-003, -7.450363e-003, -7.663864e-003, -7.920798e-003, -8.217576e-003, -8.533979e-003, -8.889309e-003, -9.311523e-003, + -9.783102e-003, -1.029099e-002, -1.078664e-002, -1.100014e-002, -1.125494e-002, -1.155534e-002, -1.187344e-002, -1.223715e-002, -1.263889e-002, + -1.305366e-002, -1.350251e-002, -1.396097e-002, -1.446717e-002, -1.462256e-002, -1.477720e-002, -1.490871e-002, -1.503539e-002, -1.521567e-002, + -1.544783e-002, -1.567307e-002, -1.591118e-002, -1.616483e-002, -1.645135e-002, -1.643566e-002, -1.629165e-002, -1.618123e-002, -1.614083e-002, + -1.614478e-002, -1.621127e-002, -1.630524e-002, -1.645702e-002, -1.665345e-002, -1.682803e-002, -1.684368e-002, -1.665494e-002, -1.650930e-002, + -1.642573e-002, -1.643562e-002, -1.648983e-002, -1.658580e-002, -1.674169e-002, -1.693109e-002, -1.719313e-002, -1.732301e-002, -1.703257e-002, + -1.678137e-002, -1.655648e-002, -1.628178e-002, -1.610941e-002, -1.595264e-002, -1.577218e-002, -1.566355e-002, -1.559035e-002, -1.555006e-002, + -1.497363e-002, -1.444341e-002, -1.393114e-002, -1.345663e-002, -1.297505e-002, -1.257072e-002, -1.220047e-002, -1.187661e-002, -1.160767e-002, + -1.141694e-002, -1.084774e-002, -1.018487e-002, -9.569694e-003, -8.939467e-003, -8.287321e-003, -7.641859e-003, -7.056578e-003, -6.394240e-003, + -5.795823e-003, -5.263189e-003, -4.442910e-003, -3.448303e-003, -2.535747e-003, -1.621275e-003, -7.260830e-004, 1.375633e-004, 1.075411e-003, + 1.965429e-003, 2.797509e-003, 3.668721e-003, 4.648531e-003, 6.124067e-003, 7.634131e-003, 9.165679e-003, 1.055493e-002, 1.193715e-002, + 1.325220e-002, 1.452959e-002, 1.563019e-002, 1.666391e-002, 1.764498e-002, 1.914960e-002, 2.046899e-002, 2.167188e-002, 2.281378e-002, + 2.380969e-002, 2.478135e-002, 2.563907e-002, 2.648461e-002, 2.719727e-002, 2.776752e-002, 2.892368e-002, 3.012721e-002, 3.133186e-002, + 3.250705e-002, 3.363869e-002, 3.486495e-002, 3.586056e-002, 3.667342e-002, 3.725278e-002, 3.769292e-002, 3.836079e-002, 3.930516e-002, + 4.014423e-002, 4.085759e-002, 4.135771e-002, 4.177264e-002, 4.202298e-002, 4.207538e-002, 4.209631e-002, 4.182417e-002, 4.164579e-002, + 4.170295e-002, 4.160686e-002, 4.142726e-002, 4.097206e-002, 4.033538e-002, 3.939414e-002, 3.839591e-002, 3.748286e-002, 3.653628e-002, + 3.545324e-002, 3.442050e-002, 3.292972e-002, 3.116934e-002, 2.884119e-002, 2.620559e-002, 2.318819e-002, 1.989661e-002, 1.709863e-002, + 1.416076e-002, 1.091104e-002, 7.880190e-003, 5.002088e-003, 2.084999e-003, -7.328482e-004, -3.575068e-003, -7.438734e-003, -1.187920e-002, + -1.685218e-002, -2.263953e-002, -2.924758e-002, -3.557124e-002, -4.171639e-002, -4.706453e-002, -5.128701e-002, -5.329986e-002, -5.537853e-002, + -5.836344e-002, -6.301186e-002, -6.957576e-002, -7.841475e-002, -9.388745e-002, -1.040602e-001, -1.139593e-001, -1.247853e-001, -1.369840e-001, + -1.488220e-001, -1.587383e-001, -1.663195e-001, -1.708929e-001, -1.675713e-001, 6.724573e-002, 1.666673e-001, 1.996120e-001, 2.069068e-001, + 2.043245e-001, 1.926459e-001, 1.199260e-001, 9.215341e-002, 6.162724e-002, 3.163523e-002, 0.000000e+000}, + {0.000000e+000, -4.856922e-003, -7.797558e-003, -9.800473e-003, -1.121839e-002, -1.223044e-002, -1.306480e-002, -1.417176e-002, -1.547379e-002, + -1.512333e-002, -1.392695e-002, -1.220049e-002, -1.004090e-002, -7.701016e-003, -5.350951e-003, -3.119363e-003, -1.152351e-003, 3.966756e-004, + 1.478055e-003, 2.412627e-003, 3.273140e-003, 4.024938e-003, 4.665786e-003, 5.210662e-003, 5.665931e-003, 6.055951e-003, 6.384196e-003, + 6.655974e-003, 6.895986e-003, 7.106323e-003, 7.308845e-003, 7.464070e-003, 7.542954e-003, 7.575636e-003, 7.564101e-003, 7.516342e-003, + 7.432449e-003, 7.308473e-003, 7.152465e-003, 6.978991e-003, 6.767019e-003, 6.484863e-003, 6.086415e-003, 5.582920e-003, 4.961080e-003, + 4.205145e-003, 3.051051e-003, 1.058027e-003, -8.547807e-004, -1.790344e-003, -2.370950e-003, -2.877308e-003, -3.331628e-003, -3.712797e-003, + -4.022733e-003, -4.260729e-003, -4.433593e-003, -4.553958e-003, -4.617323e-003, -4.628564e-003, -4.601372e-003, -4.525079e-003, -4.429871e-003, + -4.314367e-003, -4.192840e-003, -4.077131e-003, -3.962830e-003, -3.862601e-003, -3.765819e-003, -3.694589e-003, -3.635897e-003, -3.601307e-003, + -3.569543e-003, -3.541411e-003, -3.503562e-003, -3.455397e-003, -3.392916e-003, -3.323962e-003, -3.248919e-003, -3.182511e-003, -3.123196e-003, + -3.099703e-003, -3.110497e-003, -3.181069e-003, -3.275963e-003, -3.396936e-003, -3.532542e-003, -3.672277e-003, -3.815999e-003, -3.889596e-003, + -3.894224e-003, -3.822332e-003, -3.691888e-003, -3.518700e-003, -3.300783e-003, -3.046091e-003, -2.757129e-003, -2.444887e-003, -2.117053e-003, + -1.773479e-003, -1.433625e-003, -1.099302e-003, -7.770000e-004, -4.867924e-004, -2.445902e-004, -7.630767e-005, 8.833000e-006, 7.241074e-005, + 1.344135e-004, 2.047474e-004, 2.854939e-004, 3.695760e-004, 4.624777e-004, 5.617435e-004, 6.533292e-004, 7.506734e-004, 8.589372e-004, + 9.746040e-004, 1.098536e-003, 1.233180e-003, 1.370130e-003, 1.507251e-003, 1.647509e-003, 1.770882e-003, 1.866994e-003, 1.953834e-003, + 2.026856e-003, 2.080389e-003, 2.127840e-003, 2.166153e-003, 2.192347e-003, 2.214957e-003, 2.237369e-003, 2.256441e-003, 2.270853e-003, + 2.295213e-003, 2.326118e-003, 2.356767e-003, 2.396810e-003, 2.444491e-003, 2.494487e-003, 2.547283e-003, 2.610851e-003, 2.676518e-003, + 2.740894e-003, 2.811856e-003, 2.886097e-003, 2.955066e-003, 3.018135e-003, 3.078140e-003, 3.130240e-003, 3.177071e-003, 3.218638e-003, + 3.254481e-003, 3.286119e-003, 3.315053e-003, 3.333162e-003, 3.335136e-003, 3.320827e-003, 3.292995e-003, 3.249629e-003, 3.198801e-003, + 3.141942e-003, 3.078167e-003, 3.008139e-003, 2.939895e-003, 2.869716e-003, 2.798165e-003, 2.729892e-003, 2.666711e-003, 2.606649e-003, + 2.545901e-003, 2.480095e-003, 2.407263e-003, 2.329681e-003, 2.255950e-003, 2.184141e-003, 2.116196e-003, 2.044853e-003, 1.970801e-003, + 1.890859e-003, 1.801009e-003, 1.684110e-003, 1.540080e-003, 1.372006e-003, 1.194146e-003, 1.000811e-003, 7.870687e-004, 5.588102e-004, + 3.167153e-004, 6.412076e-005, -2.042919e-004, -4.845298e-004, -7.748176e-004, -1.070353e-003, -1.363857e-003, -1.661607e-003, -1.965822e-003, + -2.275591e-003, -2.587123e-003, -2.900902e-003, -3.219790e-003, -3.539381e-003, -3.857585e-003, -4.170251e-003, -4.472438e-003, -4.757131e-003, + -5.026918e-003, -5.277096e-003, -5.512495e-003, -5.738517e-003, -5.962576e-003, -6.185068e-003, -6.404012e-003, -6.621647e-003, -6.836731e-003, + -7.038834e-003, -7.235891e-003, -7.423240e-003, -7.600055e-003, -7.769626e-003, -7.940192e-003, -8.116458e-003, -8.301150e-003, -8.491787e-003, + -8.684934e-003, -8.859752e-003, -9.029611e-003, -9.189742e-003, -9.341093e-003, -9.484292e-003, -9.618507e-003, -9.745354e-003, -9.862700e-003, + -9.971128e-003, -1.007033e-002, -1.014919e-002, -1.021342e-002, -1.026728e-002, -1.030661e-002, -1.033114e-002, -1.034255e-002, -1.033965e-002, + -1.032033e-002, -1.028365e-002, -1.022803e-002, -1.014893e-002, -1.004039e-002, -9.909934e-003, -9.761733e-003, -9.606863e-003, -9.450117e-003, + -9.295345e-003, -9.143226e-003, -8.993239e-003, -8.850340e-003, -8.707891e-003, -8.556398e-003, -8.406264e-003, -8.257916e-003, -8.111991e-003, + -7.969978e-003, -7.836645e-003, -7.712076e-003, -7.591351e-003, -7.472876e-003, -7.354239e-003, -7.219640e-003, -7.082564e-003, -6.940301e-003, + -6.791794e-003, -6.619163e-003, -6.410814e-003, -6.166066e-003, -5.885576e-003, -5.565149e-003, -5.206569e-003, -4.802120e-003, -4.364344e-003, + -3.898753e-003, -3.408761e-003, -2.908409e-003, -2.403541e-003, -1.892103e-003, -1.374557e-003, -8.509448e-004, -3.253861e-004, 2.105536e-004, + 7.491376e-004, 1.278509e-003, 1.788779e-003, 2.279869e-003, 2.755211e-003, 3.221253e-003, 3.678170e-003, 4.129796e-003, 4.573108e-003, + 5.013763e-003, 5.454052e-003, 5.885033e-003, 6.308304e-003, 6.724194e-003, 7.131750e-003, 7.533505e-003, 7.927138e-003, 8.314160e-003, + 8.692192e-003, 9.061267e-003, 9.428853e-003, 9.786954e-003, 1.013314e-002, 1.046838e-002, 1.079260e-002, 1.110901e-002, 1.142016e-002, + 1.172804e-002, 1.203487e-002, 1.232962e-002, 1.261798e-002, 1.288854e-002, 1.313712e-002, 1.336320e-002, 1.356544e-002, 1.374656e-002, + 1.391929e-002, 1.409042e-002, 1.425882e-002, 1.442357e-002, 1.458734e-002, 1.474970e-002, 1.490055e-002, 1.503533e-002, 1.515102e-002, + 1.525213e-002, 1.534316e-002, 1.542383e-002, 1.549723e-002, 1.556430e-002, 1.562707e-002, 1.568857e-002, 1.574861e-002, 1.580736e-002, + 1.586792e-002, 1.593153e-002, 1.600000e-002, 1.607358e-002, 1.615429e-002, 1.624424e-002, 1.634407e-002, 1.645955e-002, 1.658548e-002, + 1.671757e-002, 1.684581e-002, 1.696048e-002, 1.705468e-002, 1.712895e-002, 1.718580e-002, 1.722492e-002, 1.724802e-002, 1.725705e-002, + 1.725352e-002, 1.724027e-002, 1.721765e-002, 1.718597e-002, 1.714919e-002, 1.710594e-002, 1.705622e-002, 1.697404e-002, 1.684391e-002, + 1.666913e-002, 1.644566e-002, 1.617152e-002, 1.584866e-002, 1.548343e-002, 1.507842e-002, 1.463441e-002, 1.415418e-002, 1.363896e-002, + 1.309270e-002, 1.251808e-002, 1.191517e-002, 1.128679e-002, 1.063423e-002, 9.958812e-003, 9.261276e-003, 8.545314e-003, 7.813906e-003, + 7.068469e-003, 6.313799e-003, 5.552226e-003, 4.784047e-003, 4.013348e-003, 3.241349e-003, 2.476287e-003, 1.720264e-003, 9.750474e-004, + 2.444405e-004, -4.656470e-004, -1.156831e-003, -1.827323e-003, -2.471959e-003, -3.089240e-003, -3.675171e-003, -4.225426e-003, -4.739292e-003, + -5.211057e-003, -5.642171e-003, -6.024289e-003, -6.356913e-003, -6.636672e-003, -6.861995e-003, -7.029255e-003, -7.135036e-003, -7.174809e-003, + -7.160690e-003, -7.118426e-003, -7.050900e-003, -6.962384e-003, -6.848482e-003, -6.715869e-003, -6.570266e-003, -6.414751e-003, -6.246789e-003, + -6.069580e-003, -5.882607e-003, -5.683664e-003, -5.479210e-003, -5.274834e-003, -5.067081e-003, -4.864037e-003, -4.666441e-003, -4.471345e-003, + -4.276821e-003, -4.085980e-003, -3.898442e-003, -3.710466e-003, -3.521978e-003, -3.333253e-003, -3.143460e-003, -2.955023e-003, -2.763748e-003, + -2.566963e-003, -2.361774e-003, -2.155293e-003, -1.951647e-003, -1.756880e-003, -1.576616e-003, -1.408484e-003, -1.250337e-003, -1.107480e-003, + -9.904570e-004, -8.935136e-004, -8.218025e-004, -7.796171e-004, -7.697472e-004, -8.119122e-004, -9.368835e-004, -1.139844e-003, -1.413483e-003, + -1.754786e-003, -2.158373e-003, -2.618482e-003, -3.138798e-003, -3.717561e-003, -4.349897e-003, -5.034746e-003, -5.768179e-003, -6.545524e-003, + -7.364606e-003, -8.231856e-003, -9.140267e-003, -1.008919e-002, -1.107273e-002, -1.208841e-002, -1.313190e-002, -1.420299e-002, -1.530630e-002, + -1.643387e-002, -1.758268e-002, -1.874789e-002, -1.992210e-002, -2.110670e-002, -2.230096e-002, -2.350307e-002, -2.471145e-002, -2.591927e-002, + -2.712059e-002, -2.831095e-002, -2.948783e-002, -3.065368e-002, -3.180461e-002, -3.294179e-002, -3.405473e-002, -3.514112e-002, -3.620186e-002, + -3.723925e-002, -3.825113e-002, -3.923299e-002, -4.018362e-002, -4.109978e-002, -4.197676e-002, -4.282013e-002, -4.362876e-002, -4.439862e-002, + -4.513344e-002, -4.582962e-002, -4.648142e-002, -4.708788e-002, -4.764182e-002, -4.814823e-002, -4.859859e-002, -4.900013e-002, -4.935584e-002, + -4.966339e-002, -4.992259e-002, -5.013327e-002, -5.029317e-002, -5.040240e-002, -5.045486e-002, -5.044740e-002, -5.037674e-002, -5.023967e-002, + -5.004070e-002, -4.978462e-002, -4.947275e-002, -4.911440e-002, -4.861781e-002, -4.782472e-002, -4.674256e-002, -4.538863e-002, -4.377555e-002, + -4.191599e-002, -3.982134e-002, -3.751253e-002, -3.502048e-002, -3.235309e-002, -2.952736e-002, -2.655978e-002, -2.345772e-002, -2.023432e-002, + -1.690001e-002, -1.348255e-002, -9.991049e-003, -6.426447e-003, -2.815340e-003, 8.306865e-004, 4.497869e-003, 8.178001e-003, 1.185789e-002, + 1.552592e-002, 1.915924e-002, 2.274017e-002, 2.625087e-002, 2.967588e-002, 3.299736e-002, 3.618726e-002, 3.923067e-002, 4.211031e-002, + 4.481045e-002, 4.731890e-002, 4.961770e-002, 5.169624e-002, 5.353633e-002, 5.512204e-002, 5.643853e-002, 5.746098e-002, 5.831167e-002, + 5.908479e-002, 5.979136e-002, 6.042757e-002, 6.099349e-002, 6.149216e-002, 6.193154e-002, 6.230856e-002, 6.263745e-002, 6.290598e-002, + 6.310869e-002, 6.324621e-002, 6.333105e-002, 6.335752e-002, 6.333506e-002, 6.326651e-002, 6.315326e-002, 6.298552e-002, 6.278615e-002, + 6.253807e-002, 6.224730e-002, 6.192504e-002, 6.157565e-002, 6.119687e-002, 6.078473e-002, 6.033113e-002, 5.983083e-002, 5.927852e-002, + 5.868971e-002, 5.805212e-002, 5.736492e-002, 5.662728e-002, 5.584500e-002, 5.502872e-002, 5.417635e-002, 5.328846e-002, 5.234840e-002, + 5.136024e-002, 5.032991e-002, 4.926755e-002, 4.816927e-002, 4.703306e-002, 4.585954e-002, 4.465075e-002, 4.340807e-002, 4.214199e-002, + 4.085785e-002, 3.956025e-002, 3.824528e-002, 3.693187e-002, 3.560361e-002, 3.426674e-002, 3.291969e-002, 3.157632e-002, 3.022915e-002, + 2.887328e-002, 2.751569e-002, 2.615730e-002, 2.469161e-002, 2.301826e-002, 2.113143e-002, 1.904336e-002, 1.676286e-002, 1.430226e-002, + 1.168306e-002, 8.909044e-003, 5.992595e-003, 2.952766e-003, -1.892900e-004, -3.397965e-003, -6.669562e-003, -9.999171e-003, -1.337929e-002, + -1.679456e-002, -2.021883e-002, -2.363413e-002, -2.702580e-002, -3.037485e-002, -3.363979e-002, -3.678325e-002, -3.979290e-002, -4.266420e-002, + -4.538687e-002, -4.796075e-002, -5.037204e-002, -5.260844e-002, -5.467233e-002, -5.653789e-002, -5.820016e-002, -5.964370e-002, -6.084748e-002, + -6.181373e-002, -6.254969e-002, -6.304255e-002, -6.327373e-002, -6.324151e-002, -6.294476e-002, -6.236275e-002, -6.148544e-002, -6.033713e-002, + -5.903884e-002, -5.765950e-002, -5.620022e-002, -5.467904e-002, -5.311244e-002, -5.150894e-002, -4.987509e-002, -4.819521e-002, -4.646704e-002, + -4.470178e-002, -4.288423e-002, -4.103245e-002, -3.916343e-002, -3.727568e-002, -3.536237e-002, -3.342494e-002, -3.148087e-002, -2.953602e-002, + -2.759655e-002, -2.566318e-002, -2.371155e-002, -2.174901e-002, -1.979182e-002, -1.786487e-002, -1.596224e-002, -1.409055e-002, -1.224699e-002, + -1.043538e-002, -8.661397e-003, -6.916669e-003, -5.196874e-003, -3.504715e-003, -1.841873e-003, -1.780654e-004, 1.484192e-003, 3.149436e-003, + 4.808750e-003, 6.458432e-003, 8.106839e-003, 9.744319e-003, 1.137902e-002, 1.303264e-002, 1.466682e-002, 1.626450e-002, 1.783889e-002, + 1.937177e-002, 2.087280e-002, 2.235456e-002, 2.380537e-002, 2.521882e-002, 2.658731e-002, 2.797466e-002, 2.933828e-002, 3.067543e-002, + 3.187833e-002, 3.279689e-002, 3.344084e-002, 3.384055e-002, 3.401162e-002, 3.396640e-002, 3.370251e-002, 3.325991e-002, 3.261960e-002, + 3.181643e-002, 3.085165e-002, 2.973501e-002, 2.848709e-002, 2.710628e-002, 2.559623e-002, 2.396612e-002, 2.223702e-002, 2.045567e-002, + 1.863930e-002, 1.675435e-002, 1.480918e-002, 1.281351e-002, 1.080206e-002, 8.788696e-003, 6.768468e-003, 4.761420e-003, 2.791298e-003, + 8.870138e-004, -8.944459e-004, -2.607997e-003, -4.247135e-003, -5.799043e-003, -7.232417e-003, -8.517655e-003, -9.649423e-003, -1.063188e-002, + -1.143942e-002, -1.208886e-002, -1.248187e-002, -1.266355e-002, -1.263433e-002, -1.237607e-002, -1.195304e-002, -1.152056e-002, -1.109690e-002, + -1.067519e-002, -1.024217e-002, -9.818682e-003, -9.357641e-003, -8.875726e-003, -8.413351e-003, -7.968957e-003, -7.538938e-003, -7.122375e-003, + -6.714695e-003, -6.319160e-003, -5.932280e-003, -5.570293e-003, -5.172370e-003, -4.709242e-003, -4.245202e-003, -3.783897e-003, -3.341789e-003, + -2.904934e-003, -2.482400e-003, -2.051428e-003, -1.613131e-003, -1.190421e-003, -7.410203e-004, -2.403449e-004, 2.371472e-004, 6.749396e-004, + 1.081014e-003, 1.440097e-003, 1.748999e-003, 1.999745e-003, 2.199480e-003, 2.360461e-003, 2.500574e-003, 2.727689e-003, 2.902678e-003, + 3.038703e-003, 3.130206e-003, 3.191180e-003, 3.235709e-003, 3.242823e-003, 3.214985e-003, 3.147778e-003, 3.043213e-003, 3.043040e-003, + 3.058252e-003, 3.027769e-003, 2.963041e-003, 2.873734e-003, 2.777619e-003, 2.659181e-003, 2.527951e-003, 2.379990e-003, 2.215818e-003, + 2.115075e-003, 2.041910e-003, 1.984158e-003, 1.926378e-003, 1.842009e-003, 1.720719e-003, 1.577646e-003, 1.416919e-003, 1.269385e-003, + 1.076693e-003, 9.256708e-004, 8.693585e-004, 7.933710e-004, 6.953306e-004, 5.967592e-004, 4.455451e-004, 2.500369e-004, -3.055137e-006, + -2.852052e-004, -5.961842e-004, -9.679909e-004, -1.192123e-003, -1.458065e-003, -1.772853e-003, -2.152422e-003, -2.549979e-003, -2.978306e-003, + -3.459624e-003, -4.001965e-003, -4.573486e-003, -5.154011e-003, -5.569628e-003, -5.991373e-003, -6.475385e-003, -6.973923e-003, -7.456755e-003, + -7.939632e-003, -8.485955e-003, -9.064276e-003, -9.641666e-003, -1.021734e-002, -1.072896e-002, -1.121748e-002, -1.174854e-002, -1.230922e-002, + -1.287446e-002, -1.347310e-002, -1.410634e-002, -1.474239e-002, -1.537940e-002, -1.607671e-002, -1.669049e-002, -1.715158e-002, -1.760523e-002, + -1.805979e-002, -1.850653e-002, -1.896897e-002, -1.947440e-002, -1.996283e-002, -2.044566e-002, -2.095126e-002, -2.141347e-002, -2.169484e-002, + -2.197146e-002, -2.224554e-002, -2.261821e-002, -2.295843e-002, -2.327421e-002, -2.355856e-002, -2.384337e-002, -2.408142e-002, -2.435257e-002, + -2.432310e-002, -2.429629e-002, -2.428195e-002, -2.431411e-002, -2.439305e-002, -2.442885e-002, -2.444895e-002, -2.451547e-002, -2.450285e-002, + -2.444349e-002, -2.423158e-002, -2.387136e-002, -2.333306e-002, -2.262491e-002, -2.184613e-002, -2.092344e-002, -1.975534e-002, -1.849346e-002, + -1.712376e-002, -1.562661e-002, -1.378032e-002, -1.169451e-002, -9.561633e-003, -7.294840e-003, -4.877750e-003, -2.331490e-003, 1.918740e-004, + 2.860863e-003, 5.597581e-003, 8.310346e-003, 1.115764e-002, 1.419641e-002, 1.715427e-002, 2.006114e-002, 2.316149e-002, 2.624645e-002, + 2.939548e-002, 3.258940e-002, 3.574167e-002, 3.885196e-002, 4.198922e-002, 4.531407e-002, 4.857910e-002, 5.187097e-002, 5.495274e-002, + 5.773757e-002, 6.027281e-002, 6.251270e-002, 6.440582e-002, 6.607591e-002, 6.746395e-002, 6.863296e-002, 6.951023e-002, 7.001863e-002, + 7.018182e-002, 6.992285e-002, 6.917695e-002, 6.813385e-002, 6.681020e-002, 6.546399e-002, 6.383476e-002, 6.208695e-002, 6.000727e-002, + 5.764712e-002, 5.486842e-002, 5.164122e-002, 4.821130e-002, 4.432303e-002, 4.041151e-002, 3.600239e-002, 3.132369e-002, 2.638080e-002, + 2.158386e-002, 1.661232e-002, 1.170591e-002, 6.543272e-003, 1.020782e-003, -4.668697e-003, -1.063140e-002, -1.666677e-002, -2.298555e-002, + -2.949187e-002, -3.568009e-002, -4.132885e-002, -4.648578e-002, -5.071933e-002, -5.385082e-002, -5.590550e-002, -5.723617e-002, -5.863943e-002, + -6.092091e-002, -6.333213e-002, -6.650548e-002, -7.035511e-002, -7.383440e-002, -7.883410e-002, -8.527805e-002, -9.176060e-002, -9.663816e-002, + -9.912976e-002, -9.575496e-002, -7.726779e-002, -6.869294e-002, -6.656362e-002, -6.449412e-002, -6.524034e-002, -6.933107e-002, -7.351112e-002, + -7.429125e-002, -7.030367e-002, -6.018157e-002, -4.679271e-002, 1.422884e-001, 1.575366e-001, 1.583155e-001, 1.564559e-001, 1.530732e-001, + 1.468045e-001, 1.367844e-001, 1.231302e-001, 1.067971e-001, 8.476677e-002, -1.448345e-001, -4.031660e-003, -4.795623e-002, -7.588450e-002, + -9.669753e-002, -1.090431e-001, 5.082690e-003, 8.394798e-003, 7.591663e-003, 4.277548e-003, 0.000000e+000}, + {0.000000e+000, -4.713623e-003, -8.461471e-003, -1.131121e-002, -1.352718e-002, -1.522101e-002, -1.646142e-002, -1.660101e-002, -1.594242e-002, + -1.616667e-002, -1.698847e-002, -1.865810e-002, -2.058347e-002, -2.252969e-002, -2.438746e-002, -2.604271e-002, -2.743505e-002, -2.848094e-002, + -2.912798e-002, -2.957346e-002, -2.986101e-002, -3.013659e-002, -3.037188e-002, -3.045830e-002, -3.039691e-002, -3.020734e-002, -2.989494e-002, + -2.946942e-002, -2.894221e-002, -2.834007e-002, -2.765181e-002, -2.693955e-002, -2.629918e-002, -2.558006e-002, -2.478699e-002, -2.391490e-002, + -2.297952e-002, -2.198773e-002, -2.094183e-002, -1.986793e-002, -1.874830e-002, -1.755636e-002, -1.642267e-002, -1.514948e-002, -1.370367e-002, + -1.205764e-002, -9.795483e-003, -6.185665e-003, -2.688713e-003, -7.178430e-004, 7.077328e-004, 2.041205e-003, 3.132938e-003, 4.113482e-003, + 4.994092e-003, 5.801635e-003, 6.540405e-003, 7.212934e-003, 7.834690e-003, 8.406193e-003, 8.938129e-003, 9.441840e-003, 9.823566e-003, + 1.012927e-002, 1.043522e-002, 1.075410e-002, 1.109851e-002, 1.146882e-002, 1.188344e-002, 1.233604e-002, 1.283849e-002, 1.339243e-002, + 1.390364e-002, 1.434256e-002, 1.478471e-002, 1.524706e-002, 1.572374e-002, 1.620846e-002, 1.669868e-002, 1.719232e-002, 1.768109e-002, + 1.816622e-002, 1.864482e-002, 1.900531e-002, 1.937295e-002, 1.973439e-002, 2.009582e-002, 2.045399e-002, 2.079784e-002, 2.109825e-002, + 2.132762e-002, 2.151008e-002, 2.163928e-002, 2.160883e-002, 2.153013e-002, 2.142231e-002, 2.129885e-002, 2.115756e-002, 2.100997e-002, + 2.087045e-002, 2.073200e-002, 2.060230e-002, 2.049016e-002, 2.030815e-002, 2.013726e-002, 2.001514e-002, 1.995525e-002, 1.993193e-002, + 1.991291e-002, 1.991085e-002, 1.991890e-002, 1.994203e-002, 1.997453e-002, 1.996590e-002, 1.988990e-002, 1.981695e-002, 1.975862e-002, + 1.970583e-002, 1.965604e-002, 1.961394e-002, 1.956972e-002, 1.952275e-002, 1.947662e-002, 1.938495e-002, 1.918864e-002, 1.898158e-002, + 1.876022e-002, 1.852217e-002, 1.828555e-002, 1.805056e-002, 1.781089e-002, 1.757448e-002, 1.734555e-002, 1.710835e-002, 1.677672e-002, + 1.645448e-002, 1.614119e-002, 1.582916e-002, 1.552408e-002, 1.522972e-002, 1.494276e-002, 1.465956e-002, 1.439069e-002, 1.412751e-002, + 1.380212e-002, 1.346673e-002, 1.314077e-002, 1.281616e-002, 1.249071e-002, 1.216388e-002, 1.183735e-002, 1.151084e-002, 1.118527e-002, + 1.086246e-002, 1.050016e-002, 1.010668e-002, 9.706162e-003, 9.311423e-003, 8.921922e-003, 8.538639e-003, 8.156564e-003, 7.783310e-003, + 7.418576e-003, 7.065247e-003, 6.699417e-003, 6.307754e-003, 5.921926e-003, 5.549213e-003, 5.195271e-003, 4.858417e-003, 4.536362e-003, + 4.225104e-003, 3.918104e-003, 3.615698e-003, 3.316930e-003, 2.985200e-003, 2.666817e-003, 2.365532e-003, 2.073442e-003, 1.790043e-003, + 1.513109e-003, 1.244039e-003, 9.693134e-004, 6.904532e-004, 4.080493e-004, 1.031503e-004, -2.017061e-004, -5.055182e-004, -8.028671e-004, + -1.086190e-003, -1.354927e-003, -1.620734e-003, -1.879273e-003, -2.124027e-003, -2.359218e-003, -2.595341e-003, -2.832047e-003, -3.054551e-003, + -3.269015e-003, -3.472344e-003, -3.664995e-003, -3.851323e-003, -4.032000e-003, -4.201020e-003, -4.358556e-003, -4.510027e-003, -4.659001e-003, + -4.799358e-003, -4.925300e-003, -5.042488e-003, -5.153462e-003, -5.266591e-003, -5.385103e-003, -5.506836e-003, -5.629359e-003, -5.750630e-003, + -5.875117e-003, -5.994131e-003, -6.109630e-003, -6.229002e-003, -6.355586e-003, -6.495816e-003, -6.654872e-003, -6.835951e-003, -7.036619e-003, + -7.252827e-003, -7.481668e-003, -7.720512e-003, -7.964326e-003, -8.215241e-003, -8.473052e-003, -8.735194e-003, -9.005095e-003, -9.281891e-003, + -9.566089e-003, -9.854700e-003, -1.014099e-002, -1.043066e-002, -1.072766e-002, -1.103076e-002, -1.133607e-002, -1.163864e-002, -1.194147e-002, + -1.223991e-002, -1.253343e-002, -1.282155e-002, -1.309455e-002, -1.334777e-002, -1.358747e-002, -1.381884e-002, -1.404280e-002, -1.426584e-002, + -1.448941e-002, -1.471681e-002, -1.495310e-002, -1.519490e-002, -1.543585e-002, -1.567320e-002, -1.591181e-002, -1.615343e-002, -1.640047e-002, + -1.665469e-002, -1.691570e-002, -1.718174e-002, -1.745506e-002, -1.773033e-002, -1.800739e-002, -1.826983e-002, -1.853201e-002, -1.879460e-002, + -1.905230e-002, -1.931746e-002, -1.959255e-002, -1.987790e-002, -2.016933e-002, -2.046161e-002, -2.075402e-002, -2.103312e-002, -2.130641e-002, + -2.157721e-002, -2.184507e-002, -2.209356e-002, -2.231169e-002, -2.249859e-002, -2.265625e-002, -2.278740e-002, -2.288986e-002, -2.295752e-002, + -2.299217e-002, -2.300748e-002, -2.301311e-002, -2.300829e-002, -2.300031e-002, -2.298415e-002, -2.295754e-002, -2.292168e-002, -2.287775e-002, + -2.281715e-002, -2.273690e-002, -2.264308e-002, -2.253608e-002, -2.241274e-002, -2.227574e-002, -2.212487e-002, -2.195930e-002, -2.177856e-002, + -2.158219e-002, -2.137023e-002, -2.114137e-002, -2.089585e-002, -2.063841e-002, -2.036878e-002, -2.008531e-002, -1.979044e-002, -1.948031e-002, + -1.915033e-002, -1.880193e-002, -1.843711e-002, -1.806277e-002, -1.768532e-002, -1.730534e-002, -1.692645e-002, -1.654619e-002, -1.616543e-002, + -1.579351e-002, -1.542930e-002, -1.508147e-002, -1.475115e-002, -1.444791e-002, -1.417352e-002, -1.392940e-002, -1.370197e-002, -1.348476e-002, + -1.327603e-002, -1.307339e-002, -1.287541e-002, -1.268246e-002, -1.249538e-002, -1.231546e-002, -1.214748e-002, -1.198210e-002, -1.181727e-002, + -1.165219e-002, -1.148354e-002, -1.131155e-002, -1.113299e-002, -1.094408e-002, -1.074492e-002, -1.053940e-002, -1.033945e-002, -1.012520e-002, + -9.900338e-003, -9.673028e-003, -9.455389e-003, -9.249096e-003, -9.050679e-003, -8.863586e-003, -8.685030e-003, -8.517968e-003, -8.380234e-003, + -8.248764e-003, -8.123202e-003, -8.001716e-003, -7.882832e-003, -7.762558e-003, -7.640104e-003, -7.519018e-003, -7.371249e-003, -7.183817e-003, + -6.977688e-003, -6.738099e-003, -6.457487e-003, -6.141583e-003, -5.786206e-003, -5.390729e-003, -4.956735e-003, -4.487566e-003, -3.985677e-003, + -3.452805e-003, -2.906289e-003, -2.343574e-003, -1.753622e-003, -1.139323e-003, -4.978658e-004, 1.694942e-004, 8.523953e-004, 1.554296e-003, + 2.271666e-003, 2.993467e-003, 3.698789e-003, 4.364003e-003, 5.013530e-003, 5.654338e-003, 6.287922e-003, 6.907636e-003, 7.513205e-003, + 8.101611e-003, 8.674067e-003, 9.225401e-003, 9.752114e-003, 1.021955e-002, 1.066488e-002, 1.109314e-002, 1.149512e-002, 1.186972e-002, + 1.222058e-002, 1.254076e-002, 1.283118e-002, 1.308874e-002, 1.331122e-002, 1.347140e-002, 1.359262e-002, 1.368141e-002, 1.373196e-002, + 1.375349e-002, 1.376232e-002, 1.375840e-002, 1.374708e-002, 1.372930e-002, 1.370029e-002, 1.364036e-002, 1.355215e-002, 1.344941e-002, + 1.333411e-002, 1.320734e-002, 1.307482e-002, 1.293099e-002, 1.277531e-002, 1.261562e-002, 1.244437e-002, 1.224801e-002, 1.202732e-002, + 1.180179e-002, 1.157480e-002, 1.135231e-002, 1.112988e-002, 1.090907e-002, 1.069138e-002, 1.048235e-002, 1.028649e-002, 1.009986e-002, + 9.894653e-003, 9.711192e-003, 9.543640e-003, 9.389412e-003, 9.244944e-003, 9.112489e-003, 8.989307e-003, 8.880592e-003, 8.796610e-003, + 8.749073e-003, 8.705311e-003, 8.693531e-003, 8.712899e-003, 8.758798e-003, 8.834276e-003, 8.964297e-003, 9.146580e-003, 9.382400e-003, + 9.676461e-003, 1.003482e-002, 1.043368e-002, 1.087360e-002, 1.136367e-002, 1.190836e-002, 1.249545e-002, 1.312966e-002, 1.381527e-002, + 1.455099e-002, 1.532904e-002, 1.615067e-002, 1.700602e-002, 1.787936e-002, 1.878769e-002, 1.972765e-002, 2.069279e-002, 2.167062e-002, + 2.267133e-002, 2.368609e-002, 2.471565e-002, 2.576789e-002, 2.683141e-002, 2.788334e-002, 2.893690e-002, 2.999677e-002, 3.105740e-002, + 3.211713e-002, 3.318095e-002, 3.424568e-002, 3.529364e-002, 3.632682e-002, 3.735170e-002, 3.835333e-002, 3.933486e-002, 4.029688e-002, + 4.123149e-002, 4.213350e-002, 4.299802e-002, 4.382735e-002, 4.462050e-002, 4.538044e-002, 4.608513e-002, 4.673659e-002, 4.732998e-002, + 4.785523e-002, 4.831957e-002, 4.872167e-002, 4.905536e-002, 4.931550e-002, 4.949831e-002, 4.960858e-002, 4.965961e-002, 4.966050e-002, + 4.962326e-002, 4.953502e-002, 4.939799e-002, 4.920892e-002, 4.897286e-002, 4.869499e-002, 4.837725e-002, 4.802540e-002, 4.764011e-002, + 4.722278e-002, 4.677275e-002, 4.628071e-002, 4.573608e-002, 4.507303e-002, 4.418428e-002, 4.308285e-002, 4.177172e-002, 4.027178e-002, + 3.859858e-002, 3.675521e-002, 3.477892e-002, 3.264901e-002, 3.038526e-002, 2.798872e-002, 2.546935e-002, 2.285136e-002, 2.015142e-002, + 1.738808e-002, 1.455719e-002, 1.167716e-002, 8.779001e-003, 5.854273e-003, 2.920392e-003, -1.340298e-005, -2.949905e-003, -5.861264e-003, + -8.738511e-003, -1.158059e-002, -1.437030e-002, -1.709062e-002, -1.971315e-002, -2.223866e-002, -2.467531e-002, -2.699992e-002, -2.921507e-002, + -3.129680e-002, -3.323237e-002, -3.501967e-002, -3.666468e-002, -3.816118e-002, -3.947889e-002, -4.059011e-002, -4.151853e-002, -4.235526e-002, + -4.315371e-002, -4.389491e-002, -4.459013e-002, -4.524625e-002, -4.585662e-002, -4.641378e-002, -4.692591e-002, -4.735978e-002, -4.775180e-002, + -4.811260e-002, -4.844398e-002, -4.872604e-002, -4.896913e-002, -4.917029e-002, -4.932932e-002, -4.944855e-002, -4.954092e-002, -4.955247e-002, + -4.951866e-002, -4.944649e-002, -4.932636e-002, -4.915154e-002, -4.892310e-002, -4.865897e-002, -4.836382e-002, -4.804132e-002, -4.769181e-002, + -4.729224e-002, -4.685501e-002, -4.639413e-002, -4.590446e-002, -4.539431e-002, -4.485351e-002, -4.428826e-002, -4.371194e-002, -4.311785e-002, + -4.251433e-002, -4.187123e-002, -4.118875e-002, -4.048474e-002, -3.976142e-002, -3.902561e-002, -3.828870e-002, -3.753574e-002, -3.676480e-002, + -3.596189e-002, -3.514502e-002, -3.429981e-002, -3.340030e-002, -3.248095e-002, -3.154216e-002, -3.059431e-002, -2.962296e-002, -2.863126e-002, + -2.762846e-002, -2.661056e-002, -2.559059e-002, -2.446012e-002, -2.314566e-002, -2.169830e-002, -2.012766e-002, -1.844914e-002, -1.668148e-002, + -1.481463e-002, -1.286641e-002, -1.085741e-002, -8.800850e-003, -6.698471e-003, -4.525604e-003, -2.318420e-003, -7.741094e-005, 2.179343e-003, + 4.426094e-003, 6.666810e-003, 8.899098e-003, 1.112830e-002, 1.331237e-002, 1.541396e-002, 1.742510e-002, 1.933557e-002, 2.114899e-002, + 2.284487e-002, 2.441157e-002, 2.586077e-002, 2.718179e-002, 2.836234e-002, 2.939914e-002, 3.029158e-002, 3.104601e-002, 3.165168e-002, + 3.210292e-002, 3.238983e-002, 3.250718e-002, 3.244487e-002, 3.219354e-002, 3.175104e-002, 3.112146e-002, 3.027953e-002, 2.925394e-002, + 2.817499e-002, 2.702899e-002, 2.582779e-002, 2.456803e-002, 2.323451e-002, 2.184043e-002, 2.038424e-002, 1.888647e-002, 1.734005e-002, + 1.576075e-002, 1.414830e-002, 1.251981e-002, 1.085497e-002, 9.162951e-003, 7.468766e-003, 5.781762e-003, 4.082016e-003, 2.368757e-003, + 6.386597e-004, -1.085542e-003, -2.791414e-003, -4.477473e-003, -6.138893e-003, -7.786760e-003, -9.407374e-003, -1.099547e-002, -1.255307e-002, + -1.408202e-002, -1.556607e-002, -1.698193e-002, -1.834070e-002, -1.964440e-002, -2.090390e-002, -2.215728e-002, -2.339410e-002, -2.461931e-002, + -2.583650e-002, -2.704105e-002, -2.821814e-002, -2.938490e-002, -3.054566e-002, -3.168128e-002, -3.280079e-002, -3.390817e-002, -3.497384e-002, + -3.603030e-002, -3.704888e-002, -3.804474e-002, -3.900092e-002, -3.992377e-002, -4.081448e-002, -4.166368e-002, -4.246471e-002, -4.320552e-002, + -4.371969e-002, -4.378821e-002, -4.343905e-002, -4.269852e-002, -4.157231e-002, -4.006949e-002, -3.824511e-002, -3.612176e-002, -3.372429e-002, + -3.103154e-002, -2.810399e-002, -2.495196e-002, -2.158720e-002, -1.807852e-002, -1.440622e-002, -1.061231e-002, -6.722151e-003, -2.719380e-003, + 1.357687e-003, 5.475065e-003, 9.611192e-003, 1.373340e-002, 1.783313e-002, 2.189552e-002, 2.588848e-002, 2.981978e-002, 3.365138e-002, + 3.737157e-002, 4.098652e-002, 4.442521e-002, 4.766748e-002, 5.068531e-002, 5.349434e-002, 5.607558e-002, 5.840248e-002, 6.043361e-002, + 6.216915e-002, 6.354925e-002, 6.461177e-002, 6.529436e-002, 6.556189e-002, 6.542974e-002, 6.492639e-002, 6.432537e-002, 6.365076e-002, + 6.290768e-002, 6.212321e-002, 6.125987e-002, 6.037955e-002, 5.947797e-002, 5.849970e-002, 5.745104e-002, 5.633184e-002, 5.516051e-002, + 5.391280e-002, 5.261984e-002, 5.128178e-002, 4.989437e-002, 4.852980e-002, 4.718461e-002, 4.581112e-002, 4.439685e-002, 4.292200e-002, + 4.140369e-002, 3.984047e-002, 3.827082e-002, 3.667324e-002, 3.502883e-002, 3.342208e-002, 3.190241e-002, 3.033201e-002, 2.872386e-002, + 2.713366e-002, 2.550315e-002, 2.381814e-002, 2.208133e-002, 2.030270e-002, 1.851927e-002, 1.676667e-002, 1.521299e-002, 1.360083e-002, + 1.198033e-002, 1.032670e-002, 8.637095e-003, 6.950588e-003, 5.233412e-003, 3.488609e-003, 1.743776e-003, -4.745230e-005, -1.602607e-003, + -3.111694e-003, -4.643086e-003, -6.211488e-003, -7.808966e-003, -9.413800e-003, -1.101909e-002, -1.261480e-002, -1.423759e-002, -1.587844e-002, + -1.730359e-002, -1.861370e-002, -1.990528e-002, -2.117223e-002, -2.244167e-002, -2.376683e-002, -2.510206e-002, -2.644421e-002, -2.774308e-002, + -2.908395e-002, -3.026829e-002, -3.119018e-002, -3.209791e-002, -3.302699e-002, -3.392057e-002, -3.482963e-002, -3.575647e-002, -3.668427e-002, + -3.760748e-002, -3.850449e-002, -3.937009e-002, -3.979700e-002, -4.021182e-002, -4.061585e-002, -4.104901e-002, -4.144667e-002, -4.183181e-002, + -4.226113e-002, -4.269487e-002, -4.312297e-002, -4.351950e-002, -4.338229e-002, -4.319939e-002, -4.303924e-002, -4.282204e-002, -4.256248e-002, + -4.226691e-002, -4.198461e-002, -4.168752e-002, -4.139502e-002, -4.106155e-002, -4.034006e-002, -3.943272e-002, -3.852165e-002, -3.758874e-002, + -3.668377e-002, -3.584676e-002, -3.506036e-002, -3.429806e-002, -3.352810e-002, -3.282830e-002, -3.185844e-002, -3.050199e-002, -2.919978e-002, + -2.792364e-002, -2.665707e-002, -2.541197e-002, -2.425181e-002, -2.311851e-002, -2.200266e-002, -2.093763e-002, -1.977861e-002, -1.814129e-002, + -1.655312e-002, -1.499241e-002, -1.352224e-002, -1.208473e-002, -1.065415e-002, -9.214675e-003, -7.812041e-003, -6.484053e-003, -5.197964e-003, + -3.170371e-003, -1.233728e-003, 6.316442e-004, 2.374450e-003, 4.053007e-003, 5.738868e-003, 7.392482e-003, 8.886661e-003, 1.040991e-002, + 1.189278e-002, 1.376890e-002, 1.582702e-002, 1.765333e-002, 1.926291e-002, 2.058458e-002, 2.175448e-002, 2.283450e-002, 2.373517e-002, + 2.446281e-002, 2.495359e-002, 2.573104e-002, 2.660638e-002, 2.726524e-002, 2.779764e-002, 2.820041e-002, 2.851658e-002, 2.867275e-002, + 2.873865e-002, 2.866095e-002, 2.839745e-002, 2.810428e-002, 2.790687e-002, 2.741684e-002, 2.666144e-002, 2.591947e-002, 2.501657e-002, + 2.400407e-002, 2.292073e-002, 2.172963e-002, 2.044169e-002, 1.904815e-002, 1.767621e-002, 1.622684e-002, 1.489903e-002, 1.385920e-002, + 1.309787e-002, 1.273786e-002, 1.257725e-002, 1.256473e-002, 1.292620e-002, 1.355177e-002, 1.421233e-002, 1.500685e-002, 1.584864e-002, + 1.680606e-002, 1.778773e-002, 1.883422e-002, 1.992003e-002, 2.112893e-002, 2.283663e-002, 2.460697e-002, 2.622387e-002, 2.758451e-002, + 2.901900e-002, 3.029680e-002, 3.132349e-002, 3.256750e-002, 3.352167e-002, 3.449371e-002, 3.506972e-002, 3.527533e-002, 3.481014e-002, + 3.385279e-002, 3.268399e-002, 3.163099e-002, 3.023607e-002, 2.808737e-002, 2.560235e-002, 2.279149e-002, 1.988521e-002, 1.645659e-002, + 1.234501e-002, 6.662850e-003, 1.429498e-003, -3.509806e-003, -7.708264e-003, -1.068938e-002, -1.300923e-002, -1.493199e-002, -1.717973e-002, + -2.077967e-002, -2.471463e-002, -3.180092e-002, -3.965489e-002, -4.678590e-002, -5.541211e-002, -6.553649e-002, -7.577997e-002, -8.439822e-002, + -9.013586e-002, -8.907055e-002, -6.985264e-002, -6.482686e-002, -6.850541e-002, -7.163153e-002, -7.757975e-002, -8.642750e-002, -9.521655e-002, + -1.002562e-001, -9.983558e-002, -9.283261e-002, -8.142507e-002, 1.359803e-001, 1.456601e-001, 1.393731e-001, 1.315151e-001, 1.222863e-001, + 1.081791e-001, 8.908593e-002, 6.564607e-002, 3.880305e-002, 8.329103e-003, -1.631899e-001, 4.213222e-002, -8.431494e-003, -5.309251e-002, + -9.238936e-002, -1.202449e-001, 1.408189e-001, 1.287963e-001, 9.699595e-002, 5.427525e-002, 0.000000e+000}, + {0.000000e+000, -6.693485e-003, -1.294773e-002, -1.814577e-002, -2.256261e-002, -2.634021e-002, -2.952633e-002, -3.217320e-002, -3.421927e-002, + -3.426096e-002, -3.340300e-002, -3.205595e-002, -3.043553e-002, -2.864168e-002, -2.680805e-002, -2.503563e-002, -2.349020e-002, -2.230590e-002, + -2.154552e-002, -2.087697e-002, -2.026709e-002, -1.967836e-002, -1.909192e-002, -1.851375e-002, -1.793373e-002, -1.736241e-002, -1.680028e-002, + -1.622133e-002, -1.565219e-002, -1.506597e-002, -1.446187e-002, -1.385450e-002, -1.326067e-002, -1.265091e-002, -1.202754e-002, -1.138099e-002, + -1.073003e-002, -1.007086e-002, -9.388444e-003, -8.668834e-003, -7.917089e-003, -7.153140e-003, -6.449564e-003, -5.780813e-003, -5.133422e-003, + -4.538399e-003, -4.135674e-003, -4.182686e-003, -4.214046e-003, -3.738145e-003, -3.101879e-003, -2.436709e-003, -1.869963e-003, -1.300673e-003, + -7.397106e-004, -1.519417e-004, 4.244970e-004, 1.011232e-003, 1.610644e-003, 2.188950e-003, 2.781288e-003, 3.353420e-003, 3.866476e-003, + 4.332129e-003, 4.769868e-003, 5.182462e-003, 5.556861e-003, 5.912080e-003, 6.216131e-003, 6.504827e-003, 6.745843e-003, 6.961166e-003, + 7.109640e-003, 7.179905e-003, 7.215556e-003, 7.231026e-003, 7.238283e-003, 7.207275e-003, 7.180739e-003, 7.203119e-003, 7.277554e-003, + 7.438674e-003, 7.700950e-003, 7.986767e-003, 8.353675e-003, 8.811949e-003, 9.369863e-003, 9.998604e-003, 1.071346e-002, 1.156391e-002, + 1.254112e-002, 1.363230e-002, 1.481153e-002, 1.600154e-002, 1.722121e-002, 1.847426e-002, 1.975367e-002, 2.102355e-002, 2.228654e-002, + 2.350206e-002, 2.467333e-002, 2.576514e-002, 2.676642e-002, 2.763008e-002, 2.837490e-002, 2.900262e-002, 2.951695e-002, 2.995902e-002, + 3.034449e-002, 3.067492e-002, 3.090827e-002, 3.109544e-002, 3.122809e-002, 3.128815e-002, 3.128090e-002, 3.122068e-002, 3.111723e-002, + 3.097060e-002, 3.076822e-002, 3.050650e-002, 3.021419e-002, 2.990161e-002, 2.957176e-002, 2.925286e-002, 2.894738e-002, 2.866404e-002, + 2.842325e-002, 2.819935e-002, 2.796000e-002, 2.771697e-002, 2.747279e-002, 2.721136e-002, 2.694066e-002, 2.663635e-002, 2.630772e-002, + 2.594561e-002, 2.555494e-002, 2.514794e-002, 2.468882e-002, 2.418716e-002, 2.365983e-002, 2.310497e-002, 2.250999e-002, 2.188728e-002, + 2.125951e-002, 2.060970e-002, 1.994304e-002, 1.927778e-002, 1.862575e-002, 1.797199e-002, 1.732854e-002, 1.669014e-002, 1.605846e-002, + 1.543219e-002, 1.481900e-002, 1.422666e-002, 1.365648e-002, 1.309630e-002, 1.254524e-002, 1.199264e-002, 1.143998e-002, 1.087712e-002, + 1.030404e-002, 9.715979e-003, 9.128826e-003, 8.541857e-003, 7.915682e-003, 7.256074e-003, 6.560548e-003, 5.817272e-003, 5.030207e-003, + 4.211878e-003, 3.364925e-003, 2.509462e-003, 1.647285e-003, 8.119442e-004, -5.402251e-005, -9.392538e-004, -1.845450e-003, -2.778769e-003, + -3.724100e-003, -4.646999e-003, -5.515035e-003, -6.331723e-003, -7.093569e-003, -7.764387e-003, -8.390977e-003, -8.982185e-003, -9.544656e-003, + -1.007692e-002, -1.058629e-002, -1.107654e-002, -1.153931e-002, -1.198036e-002, -1.241179e-002, -1.279401e-002, -1.314022e-002, -1.346548e-002, + -1.379127e-002, -1.411480e-002, -1.443119e-002, -1.473808e-002, -1.503863e-002, -1.532941e-002, -1.562005e-002, -1.589084e-002, -1.614571e-002, + -1.643526e-002, -1.676443e-002, -1.711711e-002, -1.748646e-002, -1.785341e-002, -1.821472e-002, -1.855724e-002, -1.888808e-002, -1.921648e-002, + -1.949293e-002, -1.978350e-002, -2.008587e-002, -2.039188e-002, -2.070065e-002, -2.099399e-002, -2.124832e-002, -2.145993e-002, -2.163708e-002, + -2.178363e-002, -2.185332e-002, -2.191208e-002, -2.197305e-002, -2.203925e-002, -2.210688e-002, -2.216662e-002, -2.223017e-002, -2.229619e-002, + -2.237077e-002, -2.244720e-002, -2.246909e-002, -2.247184e-002, -2.248659e-002, -2.252188e-002, -2.258084e-002, -2.265803e-002, -2.275656e-002, + -2.287641e-002, -2.302607e-002, -2.321385e-002, -2.339214e-002, -2.357494e-002, -2.381229e-002, -2.409274e-002, -2.439379e-002, -2.468684e-002, + -2.496256e-002, -2.522243e-002, -2.546393e-002, -2.567950e-002, -2.585027e-002, -2.593755e-002, -2.601518e-002, -2.607444e-002, -2.611492e-002, + -2.613388e-002, -2.611784e-002, -2.605962e-002, -2.598242e-002, -2.588476e-002, -2.578429e-002, -2.558481e-002, -2.538536e-002, -2.518970e-002, + -2.499249e-002, -2.478886e-002, -2.456887e-002, -2.434148e-002, -2.411501e-002, -2.389855e-002, -2.369737e-002, -2.343449e-002, -2.315804e-002, + -2.288578e-002, -2.261848e-002, -2.236259e-002, -2.212186e-002, -2.189675e-002, -2.169800e-002, -2.152452e-002, -2.137112e-002, -2.117956e-002, + -2.095622e-002, -2.072166e-002, -2.044726e-002, -2.012801e-002, -1.978345e-002, -1.941739e-002, -1.903633e-002, -1.864595e-002, -1.824437e-002, + -1.781227e-002, -1.731162e-002, -1.680984e-002, -1.631170e-002, -1.582071e-002, -1.534803e-002, -1.489623e-002, -1.446814e-002, -1.406447e-002, + -1.368750e-002, -1.332438e-002, -1.289673e-002, -1.249344e-002, -1.211623e-002, -1.176388e-002, -1.144329e-002, -1.116648e-002, -1.094638e-002, + -1.078607e-002, -1.069276e-002, -1.064457e-002, -1.054423e-002, -1.045869e-002, -1.039783e-002, -1.035797e-002, -1.034037e-002, -1.034271e-002, + -1.036491e-002, -1.041810e-002, -1.049191e-002, -1.058207e-002, -1.061079e-002, -1.059246e-002, -1.054362e-002, -1.047493e-002, -1.038423e-002, + -1.028991e-002, -1.020030e-002, -1.012203e-002, -1.004888e-002, -9.986849e-003, -9.898462e-003, -9.761454e-003, -9.643756e-003, -9.561119e-003, + -9.510934e-003, -9.497622e-003, -9.530261e-003, -9.597066e-003, -9.708153e-003, -9.865028e-003, -1.005742e-002, -1.021501e-002, -1.041573e-002, + -1.064656e-002, -1.087350e-002, -1.107320e-002, -1.122605e-002, -1.131998e-002, -1.136358e-002, -1.135903e-002, -1.130991e-002, -1.110936e-002, + -1.086190e-002, -1.058158e-002, -1.027354e-002, -9.938190e-003, -9.578338e-003, -9.196867e-003, -8.797478e-003, -8.350004e-003, -7.848842e-003, + -7.205530e-003, -6.480771e-003, -5.723023e-003, -4.925750e-003, -4.092231e-003, -3.232676e-003, -2.358117e-003, -1.463165e-003, -5.547719e-004, + 3.596005e-004, 1.341049e-003, 2.392029e-003, 3.455207e-003, 4.533609e-003, 5.616888e-003, 6.704661e-003, 7.795771e-003, 8.885145e-003, + 9.961922e-003, 1.104659e-002, 1.217140e-002, 1.339734e-002, 1.463204e-002, 1.588551e-002, 1.713527e-002, 1.837039e-002, 1.959724e-002, + 2.080418e-002, 2.198571e-002, 2.314644e-002, 2.428758e-002, 2.550195e-002, 2.668274e-002, 2.783431e-002, 2.894100e-002, 2.999709e-002, + 3.099952e-002, 3.193879e-002, 3.281240e-002, 3.361305e-002, 3.433498e-002, 3.505910e-002, 3.571928e-002, 3.629826e-002, 3.677535e-002, + 3.715820e-002, 3.748671e-002, 3.775406e-002, 3.797455e-002, 3.815082e-002, 3.828028e-002, 3.843320e-002, 3.861411e-002, 3.876839e-002, + 3.889820e-002, 3.900629e-002, 3.908488e-002, 3.913562e-002, 3.917206e-002, 3.919556e-002, 3.921333e-002, 3.924672e-002, 3.934289e-002, + 3.941994e-002, 3.948043e-002, 3.952227e-002, 3.952480e-002, 3.949563e-002, 3.942645e-002, 3.931440e-002, 3.916330e-002, 3.896279e-002, + 3.877358e-002, 3.851000e-002, 3.818189e-002, 3.781467e-002, 3.740781e-002, 3.696524e-002, 3.649172e-002, 3.598600e-002, 3.542723e-002, + 3.480358e-002, 3.417948e-002, 3.350221e-002, 3.276813e-002, 3.199495e-002, 3.121736e-002, 3.045231e-002, 2.968739e-002, 2.892140e-002, + 2.813863e-002, 2.734835e-002, 2.658640e-002, 2.582387e-002, 2.504896e-002, 2.425499e-002, 2.343191e-002, 2.258856e-002, 2.171204e-002, + 2.081311e-002, 1.990436e-002, 1.898855e-002, 1.809123e-002, 1.720834e-002, 1.631150e-002, 1.538872e-002, 1.445443e-002, 1.350406e-002, + 1.255481e-002, 1.159189e-002, 1.061867e-002, 9.640475e-003, 8.671699e-003, 7.727532e-003, 6.784858e-003, 5.845462e-003, 4.899365e-003, + 3.943273e-003, 2.961435e-003, 1.947640e-003, 9.049884e-004, -1.455246e-004, -1.189896e-003, -2.197595e-003, -3.204635e-003, -4.216577e-003, + -5.228208e-003, -6.240984e-003, -7.252716e-003, -8.260536e-003, -9.253422e-003, -1.021743e-002, -1.115506e-002, -1.203558e-002, -1.287596e-002, + -1.367734e-002, -1.442266e-002, -1.512085e-002, -1.576614e-002, -1.638393e-002, -1.696377e-002, -1.748441e-002, -1.797201e-002, -1.842032e-002, + -1.881223e-002, -1.918271e-002, -1.954592e-002, -1.990405e-002, -2.024293e-002, -2.059832e-002, -2.095779e-002, -2.131297e-002, -2.170726e-002, + -2.210858e-002, -2.246456e-002, -2.278915e-002, -2.308242e-002, -2.322342e-002, -2.306052e-002, -2.261349e-002, -2.191240e-002, -2.095594e-002, + -1.977207e-002, -1.840487e-002, -1.681573e-002, -1.502679e-002, -1.305332e-002, -1.091170e-002, -8.610341e-003, -6.190770e-003, -3.656235e-003, + -1.029037e-003, 1.674037e-003, 4.433113e-003, 7.212017e-003, 1.000389e-002, 1.278387e-002, 1.552692e-002, 1.818788e-002, 2.076655e-002, + 2.324651e-002, 2.559140e-002, 2.781222e-002, 2.991169e-002, 3.187577e-002, 3.369424e-002, 3.534980e-002, 3.683330e-002, 3.814610e-002, + 3.927348e-002, 4.019439e-002, 4.087410e-002, 4.132283e-002, 4.151913e-002, 4.147579e-002, 4.120426e-002, 4.066848e-002, 3.999271e-002, + 3.925733e-002, 3.846311e-002, 3.759921e-002, 3.666230e-002, 3.566932e-002, 3.461171e-002, 3.349462e-002, 3.232089e-002, 3.107934e-002, + 2.977838e-002, 2.841009e-002, 2.700373e-002, 2.553549e-002, 2.400854e-002, 2.240906e-002, 2.073283e-002, 1.899488e-002, 1.722445e-002, + 1.537956e-002, 1.347586e-002, 1.152001e-002, 9.495525e-003, 7.368148e-003, 5.156433e-003, 2.890722e-003, 5.973835e-004, -1.709326e-003, + -3.996570e-003, -6.248824e-003, -8.482986e-003, -1.067745e-002, -1.287573e-002, -1.508739e-002, -1.730918e-002, -1.952652e-002, -2.171116e-002, + -2.387995e-002, -2.596276e-002, -2.795315e-002, -2.989260e-002, -3.180817e-002, -3.368798e-002, -3.556614e-002, -3.744486e-002, -3.930562e-002, + -4.113043e-002, -4.295031e-002, -4.474827e-002, -4.643930e-002, -4.813832e-002, -4.983717e-002, -5.152703e-002, -5.320793e-002, -5.486855e-002, + -5.653315e-002, -5.819642e-002, -5.987110e-002, -6.137353e-002, -6.250467e-002, -6.336649e-002, -6.398757e-002, -6.434836e-002, -6.449744e-002, + -6.439587e-002, -6.405544e-002, -6.349345e-002, -6.273850e-002, -6.180153e-002, -6.060963e-002, -5.922795e-002, -5.768980e-002, -5.600828e-002, + -5.421894e-002, -5.232540e-002, -5.034515e-002, -4.825366e-002, -4.608968e-002, -4.385939e-002, -4.145852e-002, -3.891009e-002, -3.629935e-002, + -3.367289e-002, -3.102229e-002, -2.837196e-002, -2.573596e-002, -2.311528e-002, -2.055056e-002, -1.806803e-002, -1.562220e-002, -1.317428e-002, + -1.087637e-002, -8.701875e-003, -6.630098e-003, -4.744750e-003, -3.070384e-003, -1.582159e-003, -2.914927e-004, 7.335285e-004, 1.553592e-003, + 2.508960e-003, 3.389987e-003, 4.243592e-003, 5.125554e-003, 6.018631e-003, 6.936716e-003, 7.854009e-003, 8.758132e-003, 9.636745e-003, + 1.049402e-002, 1.149073e-002, 1.250395e-002, 1.350478e-002, 1.449061e-002, 1.542413e-002, 1.628812e-002, 1.708948e-002, 1.783974e-002, + 1.848572e-002, 1.903254e-002, 1.968004e-002, 2.035633e-002, 2.092502e-002, 2.142063e-002, 2.184386e-002, 2.216788e-002, 2.235990e-002, + 2.247126e-002, 2.244626e-002, 2.230984e-002, 2.215398e-002, 2.207862e-002, 2.189175e-002, 2.163851e-002, 2.134905e-002, 2.102868e-002, + 2.066273e-002, 2.026087e-002, 1.982760e-002, 1.934869e-002, 1.883445e-002, 1.861759e-002, 1.835068e-002, 1.804909e-002, 1.776918e-002, + 1.750116e-002, 1.723944e-002, 1.692107e-002, 1.655837e-002, 1.618388e-002, 1.575332e-002, 1.562169e-002, 1.550359e-002, 1.531092e-002, + 1.498434e-002, 1.451629e-002, 1.384733e-002, 1.298442e-002, 1.197104e-002, 1.079613e-002, 9.424703e-003, 8.157830e-003, 6.937001e-003, + 5.640427e-003, 4.215221e-003, 2.658892e-003, 9.804203e-004, -8.441043e-004, -2.722842e-003, -4.638962e-003, -6.611850e-003, -8.412307e-003, + -9.986228e-003, -1.159821e-002, -1.322716e-002, -1.484843e-002, -1.648289e-002, -1.809514e-002, -1.971193e-002, -2.125397e-002, -2.278520e-002, + -2.420092e-002, -2.520827e-002, -2.618178e-002, -2.712257e-002, -2.805888e-002, -2.891514e-002, -2.973233e-002, -3.051873e-002, -3.123351e-002, + -3.187630e-002, -3.240308e-002, -3.235571e-002, -3.217919e-002, -3.188676e-002, -3.136805e-002, -3.067525e-002, -3.001468e-002, -2.938975e-002, + -2.869444e-002, -2.800532e-002, -2.731670e-002, -2.620078e-002, -2.497031e-002, -2.375862e-002, -2.248068e-002, -2.119424e-002, -1.990328e-002, + -1.872219e-002, -1.754529e-002, -1.638864e-002, -1.523373e-002, -1.388387e-002, -1.239738e-002, -1.096089e-002, -9.538881e-003, -8.181707e-003, + -6.950143e-003, -5.797964e-003, -4.686261e-003, -3.651562e-003, -2.739660e-003, -1.776657e-003, -5.363480e-004, 6.626466e-004, 1.793124e-003, + 2.950057e-003, 4.024141e-003, 5.063086e-003, 6.029834e-003, 6.953163e-003, 7.873540e-003, 8.776532e-003, 1.008271e-002, 1.131156e-002, + 1.251903e-002, 1.368173e-002, 1.473871e-002, 1.569125e-002, 1.656390e-002, 1.737614e-002, 1.817089e-002, 1.890068e-002, 1.993477e-002, + 2.094860e-002, 2.192921e-002, 2.279139e-002, 2.355434e-002, 2.421806e-002, 2.486279e-002, 2.542970e-002, 2.589966e-002, 2.621816e-002, + 2.671673e-002, 2.727773e-002, 2.775717e-002, 2.814537e-002, 2.843552e-002, 2.854905e-002, 2.857807e-002, 2.853260e-002, 2.841490e-002, + 2.820644e-002, 2.808304e-002, 2.817982e-002, 2.820464e-002, 2.810875e-002, 2.790979e-002, 2.769404e-002, 2.741801e-002, 2.713019e-002, + 2.677760e-002, 2.640030e-002, 2.607361e-002, 2.606227e-002, 2.601481e-002, 2.591623e-002, 2.581737e-002, 2.571329e-002, 2.550586e-002, + 2.520680e-002, 2.491147e-002, 2.450612e-002, 2.406190e-002, 2.391009e-002, 2.370905e-002, 2.340746e-002, 2.302998e-002, 2.253963e-002, + 2.198485e-002, 2.142238e-002, 2.075097e-002, 1.979952e-002, 1.873667e-002, 1.791233e-002, 1.719947e-002, 1.644673e-002, 1.569372e-002, + 1.488955e-002, 1.404621e-002, 1.314600e-002, 1.221373e-002, 1.138797e-002, 1.054174e-002, 9.789920e-003, 9.175487e-003, 8.475372e-003, + 7.754511e-003, 7.154047e-003, 6.532273e-003, 5.813567e-003, 5.029210e-003, 4.170620e-003, 3.290527e-003, 2.461252e-003, 1.762054e-003, + 9.264634e-004, 5.088638e-005, -8.103291e-004, -1.731029e-003, -2.737163e-003, -3.898755e-003, -5.094573e-003, -6.473116e-003, -7.903319e-003, + -8.736905e-003, -9.744800e-003, -1.092015e-002, -1.205169e-002, -1.326090e-002, -1.451070e-002, -1.584482e-002, -1.733432e-002, -1.891294e-002, + -2.066027e-002, -2.187797e-002, -2.275431e-002, -2.364719e-002, -2.441949e-002, -2.501507e-002, -2.544148e-002, -2.572159e-002, -2.599709e-002, + -2.615154e-002, -2.633808e-002, -2.596696e-002, -2.515847e-002, -2.430194e-002, -2.344515e-002, -2.276829e-002, -2.209294e-002, -2.135182e-002, + -2.073453e-002, -2.014671e-002, -1.940560e-002, -1.838702e-002, -1.693106e-002, -1.552453e-002, -1.425790e-002, -1.309467e-002, -1.197368e-002, + -1.101558e-002, -1.028193e-002, -9.523562e-003, -8.670417e-003, -8.028634e-003, -6.448768e-003, -4.857823e-003, -3.297200e-003, -2.188113e-003, + -1.207461e-003, -2.277449e-004, 3.905405e-004, 5.450266e-004, 5.865983e-004, 5.800508e-004, 1.645117e-003, 2.698534e-003, 3.226162e-003, + 3.258084e-003, 2.865015e-003, 2.653676e-003, 2.132567e-003, 1.151595e-003, 1.848941e-004, -1.400429e-003, -1.808496e-003, -1.942259e-003, + -2.326727e-003, -3.152509e-003, -4.359287e-003, -5.916347e-003, -7.676281e-003, -9.729204e-003, -1.192822e-002, -1.451381e-002, -1.567311e-002, + -1.507943e-002, -1.446563e-002, -1.393169e-002, -1.333215e-002, -1.327462e-002, -1.359070e-002, -1.389884e-002, -1.475839e-002, -1.597238e-002, + -1.633836e-002, -1.345749e-002, -1.009753e-002, -7.133926e-003, -4.200006e-003, -6.689719e-004, 1.349175e-003, 2.473474e-003, 3.137331e-003, + 4.086499e-003, 4.453958e-003, 9.679730e-003, 1.532065e-002, 2.007844e-002, 2.492736e-002, 3.066648e-002, 3.647310e-002, 3.988489e-002, + 4.126106e-002, 3.799109e-002, 2.707282e-002, 2.562059e-002, 2.847733e-002, 3.032211e-002, 3.188822e-002, 3.292856e-002, 3.233655e-002, + 2.665533e-002, 1.806627e-002, 6.428464e-003, -1.459043e-003, -9.594090e-002, -9.119271e-002, -7.541453e-002, -5.254466e-002, -2.774788e-002, + -1.125875e-002, -1.815898e-003, 4.892343e-004, -5.961389e-003, -1.667448e-002, 1.712418e-001, 5.307609e-003, -4.415489e-002, -8.993407e-002, + -1.295619e-001, -1.594212e-001, 4.023480e-001, 3.574481e-001, 2.632863e-001, 1.506829e-001, 0.000000e+000}, + {0.000000e+000, -6.532769e-003, -1.221520e-002, -1.697523e-002, -2.106447e-002, -2.463361e-002, -2.766687e-002, -3.023530e-002, -3.229652e-002, + -3.270719e-002, -3.258645e-002, -3.225349e-002, -3.179513e-002, -3.122651e-002, -3.056982e-002, -2.998388e-002, -2.954841e-002, -2.932281e-002, + -2.934284e-002, -2.938877e-002, -2.942205e-002, -2.942762e-002, -2.939737e-002, -2.931269e-002, -2.916779e-002, -2.897899e-002, -2.873675e-002, + -2.844817e-002, -2.807642e-002, -2.763964e-002, -2.714619e-002, -2.663448e-002, -2.611184e-002, -2.552600e-002, -2.488855e-002, -2.418947e-002, + -2.342618e-002, -2.259059e-002, -2.170309e-002, -2.076071e-002, -1.975378e-002, -1.867971e-002, -1.765358e-002, -1.656691e-002, -1.546373e-002, + -1.434789e-002, -1.325191e-002, -1.221392e-002, -1.116856e-002, -1.007257e-002, -8.955893e-003, -7.860193e-003, -6.875012e-003, -5.942372e-003, + -5.032705e-003, -4.162771e-003, -3.292865e-003, -2.440887e-003, -1.599497e-003, -7.645774e-004, 4.396297e-005, 8.612120e-004, 1.563730e-003, + 2.224807e-003, 2.859342e-003, 3.464963e-003, 4.068511e-003, 4.641987e-003, 5.214387e-003, 5.764755e-003, 6.303663e-003, 6.829034e-003, + 7.304328e-003, 7.664827e-003, 8.046272e-003, 8.422555e-003, 8.797710e-003, 9.199348e-003, 9.620624e-003, 1.005912e-002, 1.056150e-002, + 1.113807e-002, 1.176332e-002, 1.235435e-002, 1.303921e-002, 1.378340e-002, 1.459241e-002, 1.547492e-002, 1.643004e-002, 1.747024e-002, + 1.863229e-002, 1.987287e-002, 2.119061e-002, 2.243226e-002, 2.372384e-002, 2.504550e-002, 2.636809e-002, 2.769457e-002, 2.900882e-002, + 3.029153e-002, 3.152997e-002, 3.269797e-002, 3.380591e-002, 3.474244e-002, 3.554791e-002, 3.627125e-002, 3.689908e-002, 3.746465e-002, + 3.798510e-002, 3.845275e-002, 3.887780e-002, 3.924963e-002, 3.957058e-002, 3.979395e-002, 3.994540e-002, 4.004658e-002, 4.009969e-002, + 4.011033e-002, 4.006704e-002, 3.999811e-002, 3.988609e-002, 3.973129e-002, 3.956733e-002, 3.936929e-002, 3.911411e-002, 3.887087e-002, + 3.860951e-002, 3.833137e-002, 3.806034e-002, 3.776318e-002, 3.744403e-002, 3.711637e-002, 3.675740e-002, 3.637314e-002, 3.591082e-002, + 3.543117e-002, 3.492546e-002, 3.439018e-002, 3.384263e-002, 3.327197e-002, 3.267202e-002, 3.204874e-002, 3.140814e-002, 3.075396e-002, + 3.003942e-002, 2.930089e-002, 2.856224e-002, 2.781853e-002, 2.706392e-002, 2.631752e-002, 2.556885e-002, 2.481096e-002, 2.404462e-002, + 2.328925e-002, 2.252295e-002, 2.174263e-002, 2.095703e-002, 2.018365e-002, 1.940994e-002, 1.863031e-002, 1.784201e-002, 1.705700e-002, + 1.626022e-002, 1.545164e-002, 1.461251e-002, 1.376739e-002, 1.291306e-002, 1.204207e-002, 1.113629e-002, 1.021071e-002, 9.251255e-003, + 8.271566e-003, 7.294434e-003, 6.308318e-003, 5.308622e-003, 4.300932e-003, 3.286563e-003, 2.258181e-003, 1.221221e-003, 1.856722e-004, + -8.484221e-004, -1.870305e-003, -2.871901e-003, -3.831116e-003, -4.749567e-003, -5.605662e-003, -6.422759e-003, -7.214490e-003, -7.980585e-003, + -8.716716e-003, -9.414298e-003, -1.007661e-002, -1.071457e-002, -1.132671e-002, -1.190911e-002, -1.244595e-002, -1.294670e-002, -1.343559e-002, + -1.389265e-002, -1.432164e-002, -1.472680e-002, -1.511983e-002, -1.550296e-002, -1.586391e-002, -1.620859e-002, -1.652523e-002, -1.680998e-002, + -1.710784e-002, -1.742824e-002, -1.776645e-002, -1.810618e-002, -1.843820e-002, -1.875733e-002, -1.907606e-002, -1.939679e-002, -1.970824e-002, + -1.998130e-002, -2.026089e-002, -2.055476e-002, -2.086256e-002, -2.117866e-002, -2.149220e-002, -2.179572e-002, -2.208485e-002, -2.236024e-002, + -2.263321e-002, -2.285947e-002, -2.309123e-002, -2.332934e-002, -2.356983e-002, -2.381360e-002, -2.406797e-002, -2.433305e-002, -2.460252e-002, + -2.487001e-002, -2.513968e-002, -2.536943e-002, -2.559775e-002, -2.583922e-002, -2.609500e-002, -2.636832e-002, -2.664742e-002, -2.693605e-002, + -2.724405e-002, -2.756673e-002, -2.790910e-002, -2.823045e-002, -2.854201e-002, -2.887594e-002, -2.922008e-002, -2.956359e-002, -2.989772e-002, + -3.021727e-002, -3.052412e-002, -3.081950e-002, -3.110098e-002, -3.135615e-002, -3.155377e-002, -3.174011e-002, -3.191216e-002, -3.207831e-002, + -3.223499e-002, -3.237371e-002, -3.248931e-002, -3.259216e-002, -3.267866e-002, -3.275664e-002, -3.275622e-002, -3.275601e-002, -3.275557e-002, + -3.275116e-002, -3.274125e-002, -3.271000e-002, -3.266844e-002, -3.261945e-002, -3.256547e-002, -3.251442e-002, -3.239875e-002, -3.226059e-002, + -3.211191e-002, -3.195549e-002, -3.179556e-002, -3.163133e-002, -3.146321e-002, -3.130578e-002, -3.115138e-002, -3.099567e-002, -3.079775e-002, + -3.056239e-002, -3.031606e-002, -3.003899e-002, -2.972840e-002, -2.940357e-002, -2.906419e-002, -2.871059e-002, -2.835501e-002, -2.799315e-002, + -2.760298e-002, -2.715330e-002, -2.670030e-002, -2.625279e-002, -2.580607e-002, -2.536566e-002, -2.493380e-002, -2.450932e-002, -2.409701e-002, + -2.369579e-002, -2.330004e-002, -2.285275e-002, -2.242532e-002, -2.200968e-002, -2.160385e-002, -2.121101e-002, -2.084833e-002, -2.051915e-002, + -2.021701e-002, -1.995330e-002, -1.971270e-002, -1.944017e-002, -1.917966e-002, -1.893869e-002, -1.871736e-002, -1.851847e-002, -1.833742e-002, + -1.816985e-002, -1.802679e-002, -1.790473e-002, -1.780269e-002, -1.766665e-002, -1.750875e-002, -1.734317e-002, -1.717203e-002, -1.699149e-002, + -1.681776e-002, -1.664286e-002, -1.648072e-002, -1.633152e-002, -1.619108e-002, -1.602849e-002, -1.583851e-002, -1.566326e-002, -1.550811e-002, + -1.536736e-002, -1.524332e-002, -1.514554e-002, -1.507281e-002, -1.502812e-002, -1.500439e-002, -1.499129e-002, -1.494226e-002, -1.490819e-002, + -1.488418e-002, -1.485479e-002, -1.480539e-002, -1.473257e-002, -1.463575e-002, -1.449811e-002, -1.432567e-002, -1.412065e-002, -1.382656e-002, + -1.349918e-002, -1.314904e-002, -1.277454e-002, -1.236837e-002, -1.193744e-002, -1.148267e-002, -1.101964e-002, -1.050058e-002, -9.914246e-003, + -9.210445e-003, -8.427679e-003, -7.588576e-003, -6.699325e-003, -5.772045e-003, -4.809852e-003, -3.807464e-003, -2.770907e-003, -1.705200e-003, + -6.126022e-004, 5.347291e-004, 1.734790e-003, 2.965399e-003, 4.222764e-003, 5.489783e-003, 6.779274e-003, 8.078061e-003, 9.393902e-003, + 1.070917e-002, 1.203311e-002, 1.337517e-002, 1.476663e-002, 1.616265e-002, 1.755690e-002, 1.893940e-002, 2.031305e-002, 2.167301e-002, + 2.300717e-002, 2.431966e-002, 2.560680e-002, 2.685927e-002, 2.813641e-002, 2.938496e-002, 3.057478e-002, 3.171276e-002, 3.280427e-002, + 3.384355e-002, 3.482533e-002, 3.574690e-002, 3.660502e-002, 3.738863e-002, 3.813544e-002, 3.880533e-002, 3.938214e-002, 3.987092e-002, + 4.028217e-002, 4.065090e-002, 4.098059e-002, 4.126640e-002, 4.150830e-002, 4.171333e-002, 4.190909e-002, 4.208877e-002, 4.222789e-002, + 4.234609e-002, 4.243207e-002, 4.249591e-002, 4.252966e-002, 4.254075e-002, 4.253665e-002, 4.251042e-002, 4.247599e-002, 4.246337e-002, + 4.243445e-002, 4.239171e-002, 4.233124e-002, 4.223682e-002, 4.212051e-002, 4.198111e-002, 4.182352e-002, 4.164358e-002, 4.143106e-002, + 4.120318e-002, 4.093083e-002, 4.061422e-002, 4.027909e-002, 3.993275e-002, 3.957463e-002, 3.919706e-002, 3.879307e-002, 3.835790e-002, + 3.787175e-002, 3.736688e-002, 3.683735e-002, 3.627026e-002, 3.567735e-002, 3.507217e-002, 3.448559e-002, 3.390631e-002, 3.332196e-002, + 3.273279e-002, 3.214137e-002, 3.155165e-002, 3.095250e-002, 3.035020e-002, 2.974177e-002, 2.911109e-002, 2.847719e-002, 2.785070e-002, + 2.720581e-002, 2.656645e-002, 2.590273e-002, 2.524310e-002, 2.458191e-002, 2.391886e-002, 2.324437e-002, 2.256791e-002, 2.187044e-002, + 2.118013e-002, 2.048169e-002, 1.977477e-002, 1.904599e-002, 1.832074e-002, 1.761222e-002, 1.690101e-002, 1.618814e-002, 1.546799e-002, + 1.473969e-002, 1.400776e-002, 1.327174e-002, 1.252345e-002, 1.174386e-002, 1.096737e-002, 1.020283e-002, 9.430363e-003, 8.654778e-003, + 7.892528e-003, 7.129173e-003, 6.362172e-003, 5.597354e-003, 4.840385e-003, 4.073366e-003, 3.325491e-003, 2.603397e-003, 1.901038e-003, + 1.214563e-003, 5.588496e-004, -8.223302e-005, -6.981275e-004, -1.285970e-003, -1.849201e-003, -2.418336e-003, -2.960654e-003, -3.486445e-003, + -3.977198e-003, -4.461584e-003, -4.937863e-003, -5.408262e-003, -5.865971e-003, -6.323754e-003, -6.787078e-003, -7.281227e-003, -7.798834e-003, + -8.306025e-003, -8.781440e-003, -9.229845e-003, -9.657470e-003, -9.975900e-003, -1.007702e-002, -9.959517e-003, -9.645284e-003, -9.164888e-003, + -8.517471e-003, -7.731123e-003, -6.773238e-003, -5.687595e-003, -4.456536e-003, -3.101519e-003, -1.636108e-003, -7.315816e-005, 1.555848e-003, + 3.239161e-003, 4.970823e-003, 6.759802e-003, 8.566932e-003, 1.037725e-002, 1.217637e-002, 1.396598e-002, 1.569090e-002, 1.736337e-002, + 1.895222e-002, 2.042967e-002, 2.181469e-002, 2.311226e-002, 2.432504e-002, 2.543318e-002, 2.641692e-002, 2.728402e-002, 2.801533e-002, + 2.858587e-002, 2.899779e-002, 2.923672e-002, 2.929332e-002, 2.915666e-002, 2.885519e-002, 2.836062e-002, 2.765575e-002, 2.682927e-002, + 2.595272e-002, 2.501202e-002, 2.401254e-002, 2.294979e-002, 2.183288e-002, 2.067284e-002, 1.947250e-002, 1.824441e-002, 1.696697e-002, + 1.563558e-002, 1.424939e-002, 1.279818e-002, 1.130050e-002, 9.761677e-003, 8.169598e-003, 6.544112e-003, 4.870416e-003, 3.201319e-003, + 1.495312e-003, -2.391932e-004, -2.028801e-003, -3.868455e-003, -5.752482e-003, -7.677679e-003, -9.623411e-003, -1.156313e-002, -1.350417e-002, + -1.541146e-002, -1.729453e-002, -1.916687e-002, -2.104142e-002, -2.292456e-002, -2.479666e-002, -2.665514e-002, -2.850366e-002, -3.031820e-002, + -3.209595e-002, -3.379983e-002, -3.542183e-002, -3.703161e-002, -3.863159e-002, -4.018645e-002, -4.171311e-002, -4.321981e-002, -4.470625e-002, + -4.615018e-002, -4.759044e-002, -4.899370e-002, -5.035916e-002, -5.171634e-002, -5.305718e-002, -5.437612e-002, -5.567143e-002, -5.693880e-002, + -5.817563e-002, -5.938301e-002, -6.057495e-002, -6.165224e-002, -6.239996e-002, -6.288807e-002, -6.310813e-002, -6.304225e-002, -6.274588e-002, + -6.223194e-002, -6.151374e-002, -6.060617e-002, -5.951362e-002, -5.823577e-002, -5.669260e-002, -5.497676e-002, -5.312546e-002, -5.115961e-002, + -4.908480e-002, -4.692624e-002, -4.469585e-002, -4.239173e-002, -4.003179e-002, -3.762882e-002, -3.510542e-002, -3.248061e-002, -2.983615e-002, + -2.718292e-002, -2.450721e-002, -2.186227e-002, -1.928651e-002, -1.675118e-002, -1.427079e-002, -1.186932e-002, -9.534933e-003, -7.243229e-003, + -5.089121e-003, -3.083293e-003, -1.213478e-003, 4.731847e-004, 1.981149e-003, 3.270859e-003, 4.333614e-003, 5.161470e-003, 5.741807e-003, + 6.406489e-003, 7.034348e-003, 7.616438e-003, 8.167287e-003, 8.714513e-003, 9.273391e-003, 9.793022e-003, 1.028839e-002, 1.073545e-002, + 1.115733e-002, 1.169378e-002, 1.222857e-002, 1.271281e-002, 1.315413e-002, 1.353840e-002, 1.389054e-002, 1.418729e-002, 1.443103e-002, + 1.460469e-002, 1.471177e-002, 1.487664e-002, 1.504678e-002, 1.514392e-002, 1.516624e-002, 1.510223e-002, 1.498710e-002, 1.481404e-002, + 1.458769e-002, 1.431196e-002, 1.388478e-002, 1.346083e-002, 1.309219e-002, 1.267070e-002, 1.224261e-002, 1.178148e-002, 1.129222e-002, + 1.077762e-002, 1.022005e-002, 9.613861e-003, 8.964988e-003, 8.339712e-003, 7.957197e-003, 7.547688e-003, 7.104776e-003, 6.659806e-003, + 6.220743e-003, 5.750010e-003, 5.278891e-003, 4.813104e-003, 4.305861e-003, 3.780783e-003, 3.493868e-003, 3.213514e-003, 2.930936e-003, + 2.577392e-003, 2.103517e-003, 1.588320e-003, 1.031697e-003, 4.160159e-004, -2.302921e-004, -9.216502e-004, -1.441445e-003, -1.877119e-003, + -2.408108e-003, -2.971337e-003, -3.592728e-003, -4.240805e-003, -4.905256e-003, -5.591039e-003, -6.274458e-003, -6.966553e-003, -7.533210e-003, + -7.939772e-003, -8.315049e-003, -8.679835e-003, -9.048308e-003, -9.410999e-003, -9.786281e-003, -1.015407e-002, -1.051360e-002, -1.089604e-002, + -1.122111e-002, -1.128243e-002, -1.133351e-002, -1.137950e-002, -1.143854e-002, -1.148168e-002, -1.158462e-002, -1.169562e-002, -1.178234e-002, + -1.188448e-002, -1.197091e-002, -1.168305e-002, -1.135325e-002, -1.099651e-002, -1.062240e-002, -1.018116e-002, -9.751312e-003, -9.338056e-003, + -8.937818e-003, -8.577245e-003, -8.201494e-003, -7.495357e-003, -6.715825e-003, -5.988625e-003, -5.229338e-003, -4.485564e-003, -3.763601e-003, + -3.054500e-003, -2.371826e-003, -1.712284e-003, -1.076098e-003, -3.043154e-004, 6.480224e-004, 1.626253e-003, 2.564702e-003, 3.408403e-003, + 4.187223e-003, 4.902202e-003, 5.590339e-003, 6.173503e-003, 6.698124e-003, 7.307476e-003, 8.139043e-003, 8.930406e-003, 9.680552e-003, + 1.035973e-002, 1.098169e-002, 1.153050e-002, 1.206770e-002, 1.255645e-002, 1.300816e-002, 1.347322e-002, 1.429620e-002, 1.502401e-002, + 1.570974e-002, 1.634626e-002, 1.691635e-002, 1.742726e-002, 1.787101e-002, 1.825502e-002, 1.856556e-002, 1.877927e-002, 1.930438e-002, + 1.986019e-002, 2.038222e-002, 2.080783e-002, 2.112561e-002, 2.135111e-002, 2.146985e-002, 2.149711e-002, 2.142067e-002, 2.129241e-002, + 2.145973e-002, 2.168974e-002, 2.183086e-002, 2.188543e-002, 2.182534e-002, 2.169179e-002, 2.150543e-002, 2.125158e-002, 2.092572e-002, + 2.053000e-002, 2.023790e-002, 2.019688e-002, 2.007790e-002, 1.983821e-002, 1.956790e-002, 1.924107e-002, 1.881699e-002, 1.823102e-002, + 1.762668e-002, 1.709430e-002, 1.660872e-002, 1.641530e-002, 1.625456e-002, 1.604916e-002, 1.568893e-002, 1.522204e-002, 1.478688e-002, + 1.434127e-002, 1.377839e-002, 1.314822e-002, 1.241307e-002, 1.206826e-002, 1.166967e-002, 1.121055e-002, 1.069433e-002, 1.016206e-002, + 9.413642e-003, 8.503509e-003, 7.520136e-003, 6.519220e-003, 5.500631e-003, 4.769646e-003, 4.178043e-003, 3.592338e-003, 2.913984e-003, + 2.199171e-003, 1.462972e-003, 7.779389e-004, 2.084787e-005, -9.078644e-004, -1.853984e-003, -2.466524e-003, -2.824434e-003, -3.278836e-003, + -3.700410e-003, -4.395717e-003, -5.134335e-003, -5.896478e-003, -6.823869e-003, -7.822774e-003, -8.828758e-003, -9.819009e-003, -1.041423e-002, + -1.098042e-002, -1.160713e-002, -1.224551e-002, -1.291294e-002, -1.365564e-002, -1.435375e-002, -1.509704e-002, -1.593947e-002, -1.676769e-002, + -1.732561e-002, -1.789189e-002, -1.828965e-002, -1.888761e-002, -1.955111e-002, -2.024290e-002, -2.095046e-002, -2.160426e-002, -2.236804e-002, + -2.314956e-002, -2.364632e-002, -2.404785e-002, -2.421678e-002, -2.439650e-002, -2.444100e-002, -2.435540e-002, -2.441270e-002, -2.431735e-002, + -2.412379e-002, -2.387394e-002, -2.336155e-002, -2.259983e-002, -2.164169e-002, -2.057117e-002, -1.945146e-002, -1.832349e-002, -1.712395e-002, + -1.594495e-002, -1.466801e-002, -1.340086e-002, -1.204418e-002, -1.008648e-002, -8.133784e-003, -6.166518e-003, -4.660371e-003, -3.121583e-003, + -1.512601e-003, 6.328473e-005, 1.461581e-003, 2.537343e-003, 3.743347e-003, 5.776703e-003, 7.632033e-003, 9.183599e-003, 1.070731e-002, + 1.194166e-002, 1.268088e-002, 1.345295e-002, 1.411483e-002, 1.442900e-002, 1.429950e-002, 1.505580e-002, 1.589600e-002, 1.682960e-002, + 1.775878e-002, 1.857271e-002, 1.863971e-002, 1.847128e-002, 1.833125e-002, 1.768502e-002, 1.711928e-002, 1.708569e-002, 1.766101e-002, + 1.782570e-002, 1.759655e-002, 1.710381e-002, 1.643837e-002, 1.561701e-002, 1.460302e-002, 1.390973e-002, 1.331350e-002, 1.298934e-002, + 1.328848e-002, 1.346237e-002, 1.297064e-002, 1.208138e-002, 1.094080e-002, 9.929589e-003, 8.716210e-003, 7.684127e-003, 6.666715e-003, + 5.965556e-003, 6.340534e-003, 5.833938e-003, 5.243076e-003, 3.878449e-003, 1.108714e-003, -1.281258e-003, -3.548815e-003, -5.998027e-003, + -8.507627e-003, -1.107071e-002, -1.110974e-002, -1.138689e-002, -1.200857e-002, -1.289555e-002, -1.499827e-002, -1.808298e-002, -2.029365e-002, + -2.191161e-002, -2.269953e-002, -2.616956e-002, -2.731351e-002, -2.711919e-002, -2.581373e-002, -2.250549e-002, -1.629162e-002, -8.876738e-003, + 2.463739e-003, 1.361066e-002, 2.374440e-002, 2.782711e-002, 1.328086e-002, 1.319623e-002, 1.016226e-002, 7.906934e-007, -1.263750e-002, + -1.672337e-002, -1.318413e-002, -2.919178e-003, 1.521017e-002, 3.611961e-002, -6.700800e-002, -3.611864e-002, 2.810918e-002, 9.138928e-002, + 1.482023e-001, 1.936481e-001, -4.153961e-001, -3.605610e-001, -2.574813e-001, -1.429867e-001, 0.000000e+000}, + {0.000000e+000, -1.134635e-002, -1.761768e-002, -2.172598e-002, -2.450887e-002, -2.662351e-002, -2.840945e-002, -3.073955e-002, -3.358147e-002, + -3.760858e-002, -4.122392e-002, -4.399597e-002, -4.614641e-002, -4.788043e-002, -4.921348e-002, -5.007621e-002, -5.042675e-002, -5.016633e-002, + -4.930281e-002, -4.810463e-002, -4.676141e-002, -4.550188e-002, -4.431838e-002, -4.308964e-002, -4.182416e-002, -4.043271e-002, -3.893848e-002, + -3.737988e-002, -3.579509e-002, -3.417506e-002, -3.252385e-002, -3.093217e-002, -2.950361e-002, -2.796727e-002, -2.633065e-002, -2.460131e-002, + -2.279524e-002, -2.090474e-002, -1.894569e-002, -1.690435e-002, -1.481552e-002, -1.276824e-002, -1.105210e-002, -9.432472e-003, -7.923308e-003, + -6.576621e-003, -5.777790e-003, -6.166661e-003, -6.449559e-003, -5.335349e-003, -3.724531e-003, -2.005136e-003, -4.424515e-004, 1.186672e-003, + 2.918413e-003, 4.750159e-003, 6.649490e-003, 8.603801e-003, 1.062992e-002, 1.268659e-002, 1.476939e-002, 1.687841e-002, 1.879841e-002, + 2.060377e-002, 2.239515e-002, 2.414483e-002, 2.586930e-002, 2.752673e-002, 2.910300e-002, 3.060077e-002, 3.198057e-002, 3.326320e-002, + 3.434649e-002, 3.516638e-002, 3.586284e-002, 3.645734e-002, 3.691280e-002, 3.724127e-002, 3.746032e-002, 3.763468e-002, 3.782962e-002, + 3.802922e-002, 3.817559e-002, 3.809230e-002, 3.801298e-002, 3.792488e-002, 3.785148e-002, 3.775788e-002, 3.766953e-002, 3.754227e-002, + 3.735041e-002, 3.713261e-002, 3.687700e-002, 3.636428e-002, 3.582948e-002, 3.529925e-002, 3.480637e-002, 3.432396e-002, 3.388674e-002, + 3.352976e-002, 3.323339e-002, 3.304068e-002, 3.295169e-002, 3.281370e-002, 3.270403e-002, 3.261787e-002, 3.259480e-002, 3.257314e-002, + 3.249488e-002, 3.233885e-002, 3.209231e-002, 3.177377e-002, 3.138544e-002, 3.085058e-002, 3.014784e-002, 2.939064e-002, 2.859129e-002, + 2.770948e-002, 2.677170e-002, 2.577703e-002, 2.472110e-002, 2.366825e-002, 2.264275e-002, 2.164012e-002, 2.061104e-002, 1.968232e-002, + 1.885304e-002, 1.810784e-002, 1.741880e-002, 1.676511e-002, 1.613576e-002, 1.551279e-002, 1.490572e-002, 1.430344e-002, 1.351942e-002, + 1.273312e-002, 1.193898e-002, 1.114232e-002, 1.032225e-002, 9.481449e-003, 8.626952e-003, 7.760422e-003, 6.879351e-003, 5.986261e-003, + 4.966975e-003, 3.908592e-003, 2.863065e-003, 1.868125e-003, 9.172080e-004, 1.046373e-005, -8.433607e-004, -1.644120e-003, -2.405945e-003, + -3.101746e-003, -3.830488e-003, -4.576447e-003, -5.270790e-003, -5.886227e-003, -6.424027e-003, -6.907665e-003, -7.345706e-003, -7.757575e-003, + -8.152908e-003, -8.531001e-003, -8.940108e-003, -9.448186e-003, -9.983748e-003, -1.055782e-002, -1.119748e-002, -1.191269e-002, -1.268668e-002, + -1.349656e-002, -1.430581e-002, -1.511025e-002, -1.593260e-002, -1.687315e-002, -1.785384e-002, -1.886695e-002, -1.991287e-002, -2.097233e-002, + -2.201288e-002, -2.298774e-002, -2.382877e-002, -2.450900e-002, -2.504305e-002, -2.553002e-002, -2.593288e-002, -2.623671e-002, -2.646666e-002, + -2.660288e-002, -2.665385e-002, -2.663567e-002, -2.654713e-002, -2.638919e-002, -2.618274e-002, -2.598657e-002, -2.578506e-002, -2.552246e-002, + -2.522183e-002, -2.488480e-002, -2.450918e-002, -2.409939e-002, -2.366219e-002, -2.319039e-002, -2.271563e-002, -2.227302e-002, -2.192462e-002, + -2.165914e-002, -2.149167e-002, -2.139348e-002, -2.132619e-002, -2.124987e-002, -2.116362e-002, -2.105774e-002, -2.092112e-002, -2.078883e-002, + -2.071523e-002, -2.067486e-002, -2.065688e-002, -2.066002e-002, -2.066081e-002, -2.062131e-002, -2.050353e-002, -2.027333e-002, -1.994908e-002, + -1.954934e-002, -1.917789e-002, -1.879192e-002, -1.840185e-002, -1.801653e-002, -1.762463e-002, -1.722439e-002, -1.682430e-002, -1.642662e-002, + -1.603284e-002, -1.563143e-002, -1.525672e-002, -1.489269e-002, -1.454465e-002, -1.423131e-002, -1.394509e-002, -1.367500e-002, -1.342363e-002, + -1.321398e-002, -1.303203e-002, -1.289496e-002, -1.282691e-002, -1.288545e-002, -1.307339e-002, -1.335941e-002, -1.366069e-002, -1.394481e-002, + -1.418422e-002, -1.438234e-002, -1.454117e-002, -1.464306e-002, -1.470022e-002, -1.472815e-002, -1.471898e-002, -1.467934e-002, -1.459673e-002, + -1.446339e-002, -1.424109e-002, -1.392312e-002, -1.354917e-002, -1.312112e-002, -1.265796e-002, -1.217671e-002, -1.168649e-002, -1.120191e-002, + -1.071206e-002, -1.025145e-002, -9.822574e-003, -9.461608e-003, -9.170917e-003, -8.969195e-003, -8.873108e-003, -8.873420e-003, -8.928497e-003, + -9.043345e-003, -9.197409e-003, -9.402699e-003, -9.669787e-003, -9.999986e-003, -1.041953e-002, -1.091938e-002, -1.147845e-002, -1.209849e-002, + -1.272653e-002, -1.332109e-002, -1.382004e-002, -1.419416e-002, -1.447632e-002, -1.469482e-002, -1.485244e-002, -1.495762e-002, -1.503236e-002, + -1.506365e-002, -1.504705e-002, -1.497501e-002, -1.486682e-002, -1.471548e-002, -1.453218e-002, -1.433200e-002, -1.413336e-002, -1.392293e-002, + -1.370104e-002, -1.345866e-002, -1.319155e-002, -1.289780e-002, -1.258034e-002, -1.223440e-002, -1.185874e-002, -1.151726e-002, -1.121679e-002, + -1.095117e-002, -1.073422e-002, -1.052613e-002, -1.029444e-002, -1.000695e-002, -9.640678e-003, -9.202590e-003, -8.682031e-003, -8.097335e-003, + -7.463596e-003, -6.806655e-003, -6.132800e-003, -5.421541e-003, -4.642184e-003, -3.783921e-003, -2.794062e-003, -1.694022e-003, -4.783440e-004, + 7.974099e-004, 2.094092e-003, 3.421952e-003, 4.763768e-003, 6.103750e-003, 7.448706e-003, 8.775891e-003, 1.007622e-002, 1.134523e-002, + 1.255775e-002, 1.369763e-002, 1.475516e-002, 1.572160e-002, 1.658871e-002, 1.733925e-002, 1.797791e-002, 1.849035e-002, 1.889432e-002, + 1.920618e-002, 1.949973e-002, 1.984141e-002, 2.026283e-002, 2.078879e-002, 2.138739e-002, 2.207597e-002, 2.283496e-002, 2.365560e-002, + 2.453674e-002, 2.544379e-002, 2.636669e-002, 2.729971e-002, 2.822946e-002, 2.914986e-002, 3.005259e-002, 3.085186e-002, 3.148772e-002, + 3.196431e-002, 3.231862e-002, 3.253866e-002, 3.262549e-002, 3.256768e-002, 3.237697e-002, 3.206063e-002, 3.162061e-002, 3.105718e-002, + 3.036829e-002, 2.954590e-002, 2.863446e-002, 2.763093e-002, 2.656309e-002, 2.542809e-002, 2.423808e-002, 2.299217e-002, 2.170289e-002, + 2.036785e-002, 1.901731e-002, 1.769542e-002, 1.641823e-002, 1.519938e-002, 1.405123e-002, 1.293986e-002, 1.186017e-002, 1.082860e-002, + 9.827379e-003, 8.851931e-003, 7.925650e-003, 7.044618e-003, 6.204161e-003, 5.432177e-003, 4.728357e-003, 4.078939e-003, 3.495290e-003, + 2.976061e-003, 2.539528e-003, 2.155430e-003, 1.829250e-003, 1.560023e-003, 1.346070e-003, 1.190943e-003, 1.108082e-003, 1.083473e-003, + 1.121048e-003, 1.172564e-003, 1.236166e-003, 1.320187e-003, 1.420453e-003, 1.545502e-003, 1.702099e-003, 1.918865e-003, 2.198350e-003, + 2.520087e-003, 2.900698e-003, 3.319243e-003, 3.790287e-003, 4.316773e-003, 4.921100e-003, 5.622181e-003, 6.405014e-003, 7.267590e-003, + 8.176287e-003, 9.134689e-003, 1.012979e-002, 1.113330e-002, 1.214428e-002, 1.315724e-002, 1.414591e-002, 1.510960e-002, 1.601309e-002, + 1.680948e-002, 1.749631e-002, 1.811641e-002, 1.870098e-002, 1.927058e-002, 1.985717e-002, 2.045737e-002, 2.101223e-002, 2.151884e-002, + 2.198180e-002, 2.236947e-002, 2.270164e-002, 2.299610e-002, 2.329606e-002, 2.363499e-002, 2.407524e-002, 2.462409e-002, 2.520773e-002, + 2.581184e-002, 2.643603e-002, 2.704215e-002, 2.761865e-002, 2.817393e-002, 2.868923e-002, 2.915006e-002, 2.956677e-002, 2.995479e-002, + 3.027849e-002, 3.053895e-002, 3.073239e-002, 3.088223e-002, 3.095852e-002, 3.096713e-002, 3.093097e-002, 3.082940e-002, 3.066766e-002, + 3.046607e-002, 3.021635e-002, 2.990748e-002, 2.952837e-002, 2.910198e-002, 2.862251e-002, 2.806914e-002, 2.745959e-002, 2.680496e-002, + 2.607363e-002, 2.527104e-002, 2.438915e-002, 2.342297e-002, 2.236284e-002, 2.126747e-002, 2.014990e-002, 1.895835e-002, 1.772616e-002, + 1.647878e-002, 1.519420e-002, 1.385465e-002, 1.248300e-002, 1.112366e-002, 9.780798e-003, 8.450238e-003, 7.197687e-003, 5.995258e-003, + 4.836105e-003, 3.740746e-003, 2.744282e-003, 1.831381e-003, 9.665268e-004, 1.915072e-004, -5.209305e-004, -1.204745e-003, -1.871157e-003, + -2.486003e-003, -3.106375e-003, -3.735158e-003, -4.360172e-003, -5.002425e-003, -5.706630e-003, -6.521167e-003, -7.406076e-003, -8.398183e-003, + -9.457623e-003, -1.050339e-002, -1.149675e-002, -1.245200e-002, -1.344093e-002, -1.457827e-002, -1.585482e-002, -1.727961e-002, -1.884394e-002, + -2.049308e-002, -2.227052e-002, -2.409919e-002, -2.597378e-002, -2.784197e-002, -2.973761e-002, -3.164099e-002, -3.356992e-002, -3.552059e-002, + -3.750655e-002, -3.953418e-002, -4.155175e-002, -4.357000e-002, -4.559511e-002, -4.762808e-002, -4.962741e-002, -5.161729e-002, -5.362187e-002, + -5.563920e-002, -5.762012e-002, -5.956637e-002, -6.145480e-002, -6.321095e-002, -6.481960e-002, -6.629907e-002, -6.759619e-002, -6.871628e-002, + -6.964697e-002, -7.041450e-002, -7.096888e-002, -7.128985e-002, -7.137707e-002, -7.115765e-002, -7.054324e-002, -6.957915e-002, -6.839251e-002, + -6.706672e-002, -6.561954e-002, -6.409303e-002, -6.249140e-002, -6.079951e-002, -5.900849e-002, -5.715343e-002, -5.520496e-002, -5.318135e-002, + -5.109537e-002, -4.895027e-002, -4.675397e-002, -4.450474e-002, -4.222495e-002, -3.994460e-002, -3.766433e-002, -3.539337e-002, -3.310195e-002, + -3.078810e-002, -2.848031e-002, -2.621025e-002, -2.399685e-002, -2.187435e-002, -1.986084e-002, -1.787407e-002, -1.586948e-002, -1.378054e-002, + -1.158356e-002, -9.252804e-003, -6.802743e-003, -4.287197e-003, -1.742612e-003, 8.169265e-004, 3.399585e-003, 5.981001e-003, 8.651186e-003, + 1.142270e-002, 1.432335e-002, 1.731409e-002, 2.034700e-002, 2.340521e-002, 2.647639e-002, 2.955898e-002, 3.262615e-002, 3.562890e-002, + 3.859948e-002, 4.147859e-002, 4.427144e-002, 4.699205e-002, 4.962761e-002, 5.213458e-002, 5.443235e-002, 5.655623e-002, 5.854896e-002, + 6.040730e-002, 6.208631e-002, 6.356527e-002, 6.466013e-002, 6.515486e-002, 6.516355e-002, 6.474298e-002, 6.390809e-002, 6.271160e-002, + 6.114620e-002, 5.924839e-002, 5.702263e-002, 5.454731e-002, 5.178598e-002, 4.876061e-002, 4.546938e-002, 4.197774e-002, 3.830514e-002, + 3.447554e-002, 3.048987e-002, 2.634874e-002, 2.212412e-002, 1.782598e-002, 1.345852e-002, 9.023357e-003, 4.608042e-003, 2.105558e-004, + -4.184511e-003, -8.512561e-003, -1.269997e-002, -1.676956e-002, -2.071264e-002, -2.453194e-002, -2.817014e-002, -3.162267e-002, -3.492074e-002, + -3.802052e-002, -4.083489e-002, -4.333077e-002, -4.548768e-002, -4.726003e-002, -4.860121e-002, -4.953436e-002, -5.005490e-002, -5.019485e-002, + -5.026031e-002, -5.025901e-002, -5.017142e-002, -4.994816e-002, -4.950109e-002, -4.885383e-002, -4.805245e-002, -4.705928e-002, -4.596582e-002, + -4.474548e-002, -4.342378e-002, -4.195684e-002, -4.036735e-002, -3.867625e-002, -3.690848e-002, -3.504698e-002, -3.308433e-002, -3.103943e-002, + -2.897716e-002, -2.684049e-002, -2.467231e-002, -2.248166e-002, -2.025574e-002, -1.794159e-002, -1.557267e-002, -1.313351e-002, -1.065813e-002, + -8.174320e-003, -5.664156e-003, -3.140808e-003, -6.521983e-004, 1.780212e-003, 4.187863e-003, 6.588942e-003, 9.028893e-003, 1.150922e-002, + 1.403551e-002, 1.653118e-002, 1.898734e-002, 2.143321e-002, 2.384589e-002, 2.619629e-002, 2.857339e-002, 3.098273e-002, 3.346770e-002, + 3.610277e-002, 3.878851e-002, 4.140053e-002, 4.402770e-002, 4.666774e-002, 4.930988e-002, 5.181500e-002, 5.425086e-002, 5.658311e-002, + 5.865812e-002, 6.037949e-002, 6.163002e-002, 6.242446e-002, 6.281408e-002, 6.282743e-002, 6.249198e-002, 6.180059e-002, 6.072940e-002, + 5.935801e-002, 5.768675e-002, 5.580688e-002, 5.367001e-002, 5.127884e-002, 4.868102e-002, 4.596570e-002, 4.315876e-002, 4.023312e-002, + 3.716553e-002, 3.404403e-002, 3.087525e-002, 2.778545e-002, 2.468402e-002, 2.154486e-002, 1.851727e-002, 1.549370e-002, 1.256584e-002, + 9.655635e-003, 6.738544e-003, 3.898521e-003, 1.180852e-003, -1.529485e-003, -4.137514e-003, -6.665332e-003, -9.092490e-003, -1.142159e-002, + -1.361609e-002, -1.565811e-002, -1.750006e-002, -1.898851e-002, -2.006091e-002, -2.068463e-002, -2.103815e-002, -2.136345e-002, -2.162603e-002, + -2.185402e-002, -2.198784e-002, -2.204479e-002, -2.212886e-002, -2.210631e-002, -2.195764e-002, -2.175342e-002, -2.143846e-002, -2.109432e-002, + -2.080349e-002, -2.046973e-002, -2.012012e-002, -1.963370e-002, -1.915468e-002, -1.883033e-002, -1.855668e-002, -1.835668e-002, -1.812133e-002, + -1.791395e-002, -1.776481e-002, -1.772381e-002, -1.778464e-002, -1.791073e-002, -1.807600e-002, -1.831436e-002, -1.864740e-002, -1.889951e-002, + -1.901273e-002, -1.899729e-002, -1.891988e-002, -1.879647e-002, -1.856912e-002, -1.825729e-002, -1.775627e-002, -1.717409e-002, -1.655150e-002, + -1.582685e-002, -1.500763e-002, -1.411156e-002, -1.318791e-002, -1.235616e-002, -1.148102e-002, -1.055413e-002, -9.598753e-003, -8.599481e-003, + -7.702566e-003, -6.699917e-003, -5.682008e-003, -4.645966e-003, -3.701883e-003, -2.781817e-003, -1.835791e-003, -9.534839e-004, -1.358727e-004, + 7.645920e-004, 1.650160e-003, 2.380812e-003, 3.039520e-003, 3.667354e-003, 4.217587e-003, 4.730223e-003, 5.257617e-003, 5.782789e-003, + 6.378371e-003, 6.958047e-003, 7.662739e-003, 8.456015e-003, 9.257192e-003, 9.893800e-003, 1.034700e-002, 1.080941e-002, 1.139111e-002, + 1.194571e-002, 1.253214e-002, 1.325023e-002, 1.413519e-002, 1.495963e-002, 1.581326e-002, 1.661442e-002, 1.732529e-002, 1.798195e-002, + 1.853289e-002, 1.900233e-002, 1.927075e-002, 1.950451e-002, 1.986926e-002, 2.008369e-002, 1.998674e-002, 1.975759e-002, 1.937581e-002, + 1.880336e-002, 1.810052e-002, 1.724109e-002, 1.622051e-002, 1.508894e-002, 1.417387e-002, 1.336427e-002, 1.250931e-002, 1.179961e-002, + 1.107098e-002, 1.024073e-002, 9.518839e-003, 8.938140e-003, 8.282847e-003, 7.703428e-003, 7.377786e-003, 7.167064e-003, 6.834357e-003, + 6.545050e-003, 6.282050e-003, 6.102883e-003, 5.866253e-003, 5.460511e-003, 5.150162e-003, 4.929310e-003, 4.548783e-003, 4.411511e-003, + 4.324230e-003, 4.202749e-003, 4.087639e-003, 3.786271e-003, 3.442715e-003, 2.974692e-003, 2.394955e-003, 1.544147e-003, 8.778835e-004, + 4.522713e-004, 3.530497e-005, -3.063444e-004, -6.972603e-004, -1.039101e-003, -1.309780e-003, -1.669319e-003, -2.035110e-003, -2.346166e-003, + -2.567850e-003, -2.479517e-003, -2.225912e-003, -1.964386e-003, -2.096616e-003, -2.368138e-003, -2.593145e-003, -3.040563e-003, -3.441872e-003, + -3.874818e-003, -4.411709e-003, -4.987393e-003, -5.445991e-003, -5.639614e-003, -6.051010e-003, -6.485067e-003, -7.117362e-003, -7.936652e-003, + -8.953812e-003, -9.892648e-003, -1.089533e-002, -1.166788e-002, -1.201519e-002, -1.236891e-002, -1.219419e-002, -1.211962e-002, -1.212494e-002, + -1.211329e-002, -1.180531e-002, -1.140572e-002, -1.048261e-002, -9.355053e-003, -8.280176e-003, -6.966247e-003, -5.767157e-003, -4.951729e-003, + -4.522208e-003, -4.305421e-003, -4.249426e-003, -4.522457e-003, -4.834559e-003, -5.059925e-003, -5.843616e-003, -6.688421e-003, -7.958458e-003, + -9.172632e-003, -1.018046e-002, -1.154384e-002, -1.274315e-002, -1.368567e-002, -1.469643e-002, -1.594366e-002, -1.779117e-002, -1.989509e-002, + -2.216908e-002, -2.380087e-002, -2.536860e-002, -2.637852e-002, -2.705018e-002, -2.768121e-002, -2.750716e-002, -2.716176e-002, -2.695065e-002, + -2.718447e-002, -2.722912e-002, -2.712754e-002, -2.668310e-002, -2.614494e-002, -2.507963e-002, -2.352640e-002, -2.022987e-002, -1.610201e-002, + -1.155828e-002, -8.420893e-003, -5.926222e-003, -3.661171e-003, -1.491929e-003, 7.715562e-004, 3.631197e-003, 7.567342e-003, 1.182876e-002, + 1.664294e-002, 2.233888e-002, 2.625049e-002, 2.852705e-002, 3.084693e-002, 3.224908e-002, 3.214723e-002, 3.402001e-002, 3.717919e-002, + 4.158347e-002, 4.632704e-002, 5.109931e-002, 5.293910e-002, 5.398879e-002, 5.392872e-002, 5.179496e-002, 4.715900e-002, 3.908661e-002, + 3.299274e-002, 2.851598e-002, 2.509327e-002, 2.093308e-002, 2.970031e-002, 1.645794e-002, 5.349363e-004, -1.330958e-002, -2.130251e-002, + -2.298760e-002, -1.978978e-002, -1.212300e-002, -6.487657e-003, -4.397713e-003, -4.579569e-001, -1.238441e-001, 1.341668e-002, 6.288721e-002, + 9.382289e-002, 1.147923e-001, 8.623371e-002, 8.311075e-002, 6.155081e-002, 3.434061e-002, 0.000000e+000}, + {0.000000e+000, -2.895906e-003, -4.464823e-003, -5.525560e-003, -6.418178e-003, -7.351525e-003, -8.433005e-003, -9.848137e-003, -1.172874e-002, + -1.475416e-002, -1.767415e-002, -2.025072e-002, -2.253928e-002, -2.455384e-002, -2.637298e-002, -2.791555e-002, -2.897849e-002, -2.963993e-002, + -2.986200e-002, -2.984942e-002, -2.965339e-002, -2.947005e-002, -2.934270e-002, -2.909344e-002, -2.873585e-002, -2.823720e-002, -2.765083e-002, + -2.696246e-002, -2.622431e-002, -2.540962e-002, -2.452170e-002, -2.360582e-002, -2.281415e-002, -2.195914e-002, -2.104289e-002, -2.004385e-002, + -1.897010e-002, -1.785561e-002, -1.664630e-002, -1.537661e-002, -1.407451e-002, -1.281055e-002, -1.180363e-002, -1.083833e-002, -9.926934e-003, + -9.065134e-003, -8.452136e-003, -8.423315e-003, -8.347153e-003, -7.601521e-003, -6.583448e-003, -5.488648e-003, -4.505461e-003, -3.485897e-003, + -2.393524e-003, -1.236086e-003, -4.525137e-005, 1.226292e-003, 2.516364e-003, 3.835360e-003, 5.175180e-003, 6.488151e-003, 7.676985e-003, + 8.771173e-003, 9.878409e-003, 1.101293e-002, 1.212535e-002, 1.324902e-002, 1.434264e-002, 1.538974e-002, 1.642345e-002, 1.743267e-002, + 1.831022e-002, 1.905284e-002, 1.974648e-002, 2.041558e-002, 2.103339e-002, 2.161407e-002, 2.212496e-002, 2.264361e-002, 2.311965e-002, + 2.357565e-002, 2.394200e-002, 2.412315e-002, 2.424235e-002, 2.435109e-002, 2.443311e-002, 2.449073e-002, 2.451687e-002, 2.448145e-002, + 2.437815e-002, 2.422221e-002, 2.401314e-002, 2.362008e-002, 2.317030e-002, 2.275354e-002, 2.233428e-002, 2.196548e-002, 2.161464e-002, + 2.129583e-002, 2.106744e-002, 2.091527e-002, 2.085410e-002, 2.075596e-002, 2.066528e-002, 2.066551e-002, 2.071400e-002, 2.077096e-002, + 2.082086e-002, 2.087344e-002, 2.089415e-002, 2.089392e-002, 2.088146e-002, 2.074785e-002, 2.051784e-002, 2.026943e-002, 1.998084e-002, + 1.968316e-002, 1.936832e-002, 1.902071e-002, 1.865995e-002, 1.828210e-002, 1.789596e-002, 1.749490e-002, 1.698811e-002, 1.648612e-002, + 1.598793e-002, 1.550545e-002, 1.503698e-002, 1.457407e-002, 1.411040e-002, 1.364017e-002, 1.316552e-002, 1.269563e-002, 1.207341e-002, + 1.144819e-002, 1.083859e-002, 1.022878e-002, 9.619818e-003, 9.028038e-003, 8.429258e-003, 7.847054e-003, 7.278926e-003, 6.712614e-003, + 6.044726e-003, 5.366690e-003, 4.692043e-003, 4.032066e-003, 3.394625e-003, 2.783495e-003, 2.195134e-003, 1.629404e-003, 1.094357e-003, + 5.715504e-004, -1.924332e-006, -5.883363e-004, -1.148628e-003, -1.690679e-003, -2.196571e-003, -2.668668e-003, -3.142679e-003, -3.605847e-003, + -4.031041e-003, -4.440960e-003, -4.906874e-003, -5.445514e-003, -5.960068e-003, -6.476339e-003, -7.007528e-003, -7.544886e-003, -8.078363e-003, + -8.619945e-003, -9.157969e-003, -9.683805e-003, -1.020333e-002, -1.081033e-002, -1.141298e-002, -1.201852e-002, -1.262208e-002, -1.321078e-002, + -1.376765e-002, -1.427985e-002, -1.473918e-002, -1.513532e-002, -1.546545e-002, -1.581542e-002, -1.613551e-002, -1.640617e-002, -1.661191e-002, + -1.676290e-002, -1.687407e-002, -1.693655e-002, -1.694720e-002, -1.690485e-002, -1.683114e-002, -1.675189e-002, -1.665936e-002, -1.652267e-002, + -1.634763e-002, -1.614576e-002, -1.589294e-002, -1.559875e-002, -1.526284e-002, -1.489721e-002, -1.451902e-002, -1.415455e-002, -1.382346e-002, + -1.350522e-002, -1.320864e-002, -1.292238e-002, -1.263742e-002, -1.234357e-002, -1.205038e-002, -1.175984e-002, -1.146408e-002, -1.116925e-002, + -1.092516e-002, -1.068761e-002, -1.046723e-002, -1.027163e-002, -1.008301e-002, -9.890534e-003, -9.683735e-003, -9.459873e-003, -9.223617e-003, + -8.978837e-003, -8.789214e-003, -8.604700e-003, -8.433281e-003, -8.272549e-003, -8.114165e-003, -7.964128e-003, -7.821400e-003, -7.694295e-003, + -7.580952e-003, -7.470115e-003, -7.394893e-003, -7.338279e-003, -7.289685e-003, -7.257113e-003, -7.232058e-003, -7.205882e-003, -7.173739e-003, + -7.150500e-003, -7.133038e-003, -7.112905e-003, -7.112220e-003, -7.151833e-003, -7.216250e-003, -7.311456e-003, -7.406469e-003, -7.495108e-003, + -7.567721e-003, -7.630161e-003, -7.682957e-003, -7.721432e-003, -7.756669e-003, -7.810954e-003, -7.853388e-003, -7.885001e-003, -7.915443e-003, + -7.936768e-003, -7.939238e-003, -7.917802e-003, -7.883684e-003, -7.830309e-003, -7.775427e-003, -7.735771e-003, -7.693301e-003, -7.654644e-003, + -7.603417e-003, -7.565258e-003, -7.540294e-003, -7.546296e-003, -7.570913e-003, -7.612079e-003, -7.690470e-003, -7.813111e-003, -7.946947e-003, + -8.095672e-003, -8.254648e-003, -8.428349e-003, -8.619320e-003, -8.822900e-003, -9.058603e-003, -9.318053e-003, -9.586064e-003, -9.886948e-003, + -1.018891e-002, -1.048979e-002, -1.076981e-002, -1.100713e-002, -1.122380e-002, -1.143523e-002, -1.162771e-002, -1.181175e-002, -1.198539e-002, + -1.214402e-002, -1.228983e-002, -1.240941e-002, -1.251081e-002, -1.259367e-002, -1.265230e-002, -1.269296e-002, -1.272227e-002, -1.273351e-002, + -1.272334e-002, -1.268136e-002, -1.262898e-002, -1.254743e-002, -1.243833e-002, -1.229681e-002, -1.210789e-002, -1.191259e-002, -1.169413e-002, + -1.145026e-002, -1.119393e-002, -1.090709e-002, -1.059710e-002, -1.024378e-002, -9.844418e-003, -9.405516e-003, -8.916922e-003, -8.386253e-003, + -7.823314e-003, -7.238365e-003, -6.632083e-003, -6.000171e-003, -5.344412e-003, -4.665563e-003, -3.944884e-003, -3.185076e-003, -2.374058e-003, + -1.538019e-003, -6.914272e-004, 1.691596e-004, 1.035474e-003, 1.901827e-003, 2.766182e-003, 3.606600e-003, 4.440018e-003, 5.270932e-003, + 6.090301e-003, 6.893398e-003, 7.670102e-003, 8.419785e-003, 9.152211e-003, 9.850711e-003, 1.050815e-002, 1.112748e-002, 1.171961e-002, + 1.228205e-002, 1.282661e-002, 1.337111e-002, 1.392199e-002, 1.449591e-002, 1.507698e-002, 1.567535e-002, 1.627194e-002, 1.684452e-002, + 1.742683e-002, 1.800101e-002, 1.856623e-002, 1.912478e-002, 1.967418e-002, 2.020713e-002, 2.071729e-002, 2.114986e-002, 2.148201e-002, + 2.169531e-002, 2.182030e-002, 2.186470e-002, 2.182147e-002, 2.168727e-002, 2.148126e-002, 2.121285e-002, 2.087042e-002, 2.045338e-002, + 1.997442e-002, 1.941427e-002, 1.879391e-002, 1.812959e-002, 1.742822e-002, 1.668334e-002, 1.591088e-002, 1.510745e-002, 1.428426e-002, + 1.343524e-002, 1.257503e-002, 1.170492e-002, 1.081745e-002, 9.947712e-003, 9.101623e-003, 8.278347e-003, 7.478579e-003, 6.702645e-003, + 5.945762e-003, 5.207282e-003, 4.499157e-003, 3.816988e-003, 3.144614e-003, 2.524327e-003, 1.943203e-003, 1.402940e-003, 9.132676e-004, + 4.726362e-004, 9.247422e-005, -2.349733e-004, -5.208951e-004, -7.558555e-004, -9.505382e-004, -1.085498e-003, -1.153604e-003, -1.142865e-003, + -1.066518e-003, -9.692178e-004, -8.511381e-004, -7.084129e-004, -5.427060e-004, -3.561664e-004, -1.619200e-004, 3.297684e-005, 2.574188e-004, + 5.092378e-004, 7.915528e-004, 1.098749e-003, 1.429837e-003, 1.787303e-003, 2.186829e-003, 2.624918e-003, 3.090365e-003, 3.578799e-003, + 4.090701e-003, 4.624179e-003, 5.185787e-003, 5.766086e-003, 6.344665e-003, 6.932760e-003, 7.526280e-003, 8.115839e-003, 8.693244e-003, + 9.239771e-003, 9.772190e-003, 1.029266e-002, 1.081350e-002, 1.135100e-002, 1.187986e-002, 1.241125e-002, 1.294258e-002, 1.347139e-002, + 1.400196e-002, 1.451148e-002, 1.501099e-002, 1.549069e-002, 1.598339e-002, 1.649516e-002, 1.701565e-002, 1.755711e-002, 1.808801e-002, + 1.860392e-002, 1.911005e-002, 1.959061e-002, 2.004789e-002, 2.047338e-002, 2.086984e-002, 2.121310e-002, 2.151204e-002, 2.179053e-002, + 2.203184e-002, 2.224275e-002, 2.240821e-002, 2.254101e-002, 2.259524e-002, 2.260500e-002, 2.259319e-002, 2.253704e-002, 2.243576e-002, + 2.231657e-002, 2.215813e-002, 2.195189e-002, 2.170850e-002, 2.142237e-002, 2.109643e-002, 2.073096e-002, 2.033677e-002, 1.990054e-002, + 1.942159e-002, 1.890792e-002, 1.836862e-002, 1.779056e-002, 1.717282e-002, 1.653228e-002, 1.587155e-002, 1.517728e-002, 1.446302e-002, + 1.375210e-002, 1.302340e-002, 1.226644e-002, 1.150086e-002, 1.074547e-002, 1.000549e-002, 9.253581e-003, 8.522999e-003, 7.801707e-003, + 7.059598e-003, 6.353603e-003, 5.692258e-003, 5.043856e-003, 4.401581e-003, 3.795547e-003, 3.188180e-003, 2.595718e-003, 2.031507e-003, + 1.498770e-003, 9.651665e-004, 4.240185e-004, -1.201551e-004, -6.916726e-004, -1.288413e-003, -1.901323e-003, -2.501959e-003, -3.120815e-003, + -3.753148e-003, -4.397629e-003, -5.001226e-003, -5.587435e-003, -6.210333e-003, -6.966526e-003, -7.878422e-003, -8.935542e-003, -1.011580e-002, + -1.136985e-002, -1.272470e-002, -1.414021e-002, -1.562536e-002, -1.717489e-002, -1.879780e-002, -2.045918e-002, -2.216300e-002, -2.390259e-002, + -2.568639e-002, -2.748516e-002, -2.930914e-002, -3.111260e-002, -3.291103e-002, -3.468550e-002, -3.640377e-002, -3.809050e-002, -3.977118e-002, + -4.142429e-002, -4.300137e-002, -4.450899e-002, -4.593934e-002, -4.725377e-002, -4.846852e-002, -4.955912e-002, -5.048918e-002, -5.128622e-002, + -5.195803e-002, -5.252838e-002, -5.294701e-002, -5.321368e-002, -5.329822e-002, -5.314361e-002, -5.273527e-002, -5.210057e-002, -5.134556e-002, + -5.052950e-002, -4.962850e-002, -4.866098e-002, -4.761244e-002, -4.651075e-002, -4.534712e-002, -4.415966e-002, -4.290235e-002, -4.158936e-002, + -4.020965e-002, -3.874669e-002, -3.725596e-002, -3.579002e-002, -3.428820e-002, -3.275328e-002, -3.118744e-002, -2.957358e-002, -2.790267e-002, + -2.622745e-002, -2.452074e-002, -2.278267e-002, -2.104753e-002, -1.932038e-002, -1.757531e-002, -1.583212e-002, -1.406981e-002, -1.223917e-002, + -1.033164e-002, -8.374774e-003, -6.365209e-003, -4.336652e-003, -2.282602e-003, -1.808615e-004, 1.970489e-003, 4.128700e-003, 6.309519e-003, + 8.512354e-003, 1.077475e-002, 1.308374e-002, 1.540965e-002, 1.775649e-002, 2.010185e-002, 2.238316e-002, 2.463366e-002, 2.689072e-002, + 2.915634e-002, 3.139433e-002, 3.359466e-002, 3.570379e-002, 3.775920e-002, 3.975985e-002, 4.170336e-002, 4.358031e-002, 4.539872e-002, + 4.713158e-002, 4.875560e-002, 5.027912e-002, 5.157686e-002, 5.247656e-002, 5.300849e-002, 5.319464e-002, 5.303326e-002, 5.250785e-002, + 5.167433e-002, 5.056504e-002, 4.918997e-002, 4.753039e-002, 4.563479e-002, 4.350352e-002, 4.116075e-002, 3.863327e-002, 3.596173e-002, + 3.305089e-002, 3.000979e-002, 2.686306e-002, 2.366395e-002, 2.038668e-002, 1.704614e-002, 1.364654e-002, 1.024045e-002, 6.856643e-003, + 3.466348e-003, 5.875601e-005, -3.307264e-003, -6.598334e-003, -9.749456e-003, -1.275693e-002, -1.561575e-002, -1.833906e-002, -2.099320e-002, + -2.344960e-002, -2.568928e-002, -2.767122e-002, -2.937104e-002, -3.077514e-002, -3.191021e-002, -3.272843e-002, -3.317282e-002, -3.325608e-002, + -3.330263e-002, -3.335122e-002, -3.336680e-002, -3.332552e-002, -3.315081e-002, -3.284741e-002, -3.245110e-002, -3.202930e-002, -3.153729e-002, + -3.097497e-002, -3.041446e-002, -2.983495e-002, -2.917549e-002, -2.848900e-002, -2.774751e-002, -2.691547e-002, -2.601255e-002, -2.507773e-002, + -2.408554e-002, -2.303801e-002, -2.199807e-002, -2.092559e-002, -1.982249e-002, -1.870107e-002, -1.752915e-002, -1.625157e-002, -1.487391e-002, + -1.346619e-002, -1.207701e-002, -1.067574e-002, -9.251440e-003, -7.814469e-003, -6.327027e-003, -4.787390e-003, -3.271370e-003, -1.726254e-003, + -1.529438e-004, 1.462697e-003, 3.100103e-003, 4.729046e-003, 6.355159e-003, 7.890779e-003, 9.468260e-003, 1.109560e-002, 1.273188e-002, + 1.440334e-002, 1.609472e-002, 1.777874e-002, 1.953474e-002, 2.128281e-002, 2.300867e-002, 2.457659e-002, 2.608889e-002, 2.758439e-002, + 2.898884e-002, 3.013536e-002, 3.113125e-002, 3.192459e-002, 3.254076e-002, 3.299312e-002, 3.323980e-002, 3.324899e-002, 3.304963e-002, + 3.274242e-002, 3.231826e-002, 3.173545e-002, 3.104721e-002, 3.024884e-002, 2.942498e-002, 2.852565e-002, 2.753117e-002, 2.642734e-002, + 2.515901e-002, 2.388345e-002, 2.264221e-002, 2.133101e-002, 2.004799e-002, 1.873219e-002, 1.739506e-002, 1.605337e-002, 1.476935e-002, + 1.347621e-002, 1.211635e-002, 1.079782e-002, 9.461802e-003, 8.207426e-003, 7.039309e-003, 5.953897e-003, 4.970915e-003, 4.002333e-003, + 3.137217e-003, 2.419656e-003, 1.695315e-003, 1.132315e-003, 7.089885e-004, 4.230986e-004, 2.533508e-004, 1.389238e-004, 4.120303e-005, + -8.160652e-005, -1.890669e-004, -2.807092e-004, -3.917643e-004, -5.685651e-004, -6.709751e-004, -7.719697e-004, -8.905584e-004, -9.420413e-004, + -9.454197e-004, -9.470350e-004, -9.538474e-004, -1.020428e-003, -1.211397e-003, -1.396753e-003, -1.547524e-003, -1.744936e-003, -1.939063e-003, + -2.102766e-003, -2.227246e-003, -2.353098e-003, -2.536886e-003, -2.843572e-003, -3.147850e-003, -3.403881e-003, -3.615920e-003, -3.974689e-003, + -4.360772e-003, -4.666655e-003, -4.852051e-003, -4.994405e-003, -5.125881e-003, -5.202605e-003, -5.283485e-003, -5.468525e-003, -5.613555e-003, + -5.684059e-003, -5.746751e-003, -5.806051e-003, -5.833280e-003, -5.683889e-003, -5.516310e-003, -5.391907e-003, -5.476400e-003, -5.496997e-003, + -5.390747e-003, -5.186210e-003, -5.102042e-003, -4.997250e-003, -4.799437e-003, -4.511355e-003, -4.243228e-003, -3.990894e-003, -3.717442e-003, + -3.463521e-003, -3.274180e-003, -3.149733e-003, -3.002300e-003, -2.773586e-003, -2.546415e-003, -2.321648e-003, -2.113070e-003, -1.902433e-003, + -1.703079e-003, -1.420173e-003, -1.027869e-003, -6.548129e-004, -3.697415e-004, -6.437482e-005, 2.957334e-004, 5.142775e-004, 6.624678e-004, + 8.875241e-004, 1.128183e-003, 1.207066e-003, 1.334665e-003, 1.469024e-003, 1.615944e-003, 1.793237e-003, 1.962123e-003, 2.097334e-003, + 1.976319e-003, 1.916191e-003, 1.953858e-003, 1.982340e-003, 1.976629e-003, 1.943095e-003, 1.992013e-003, 1.805357e-003, 1.625819e-003, + 1.451886e-003, 1.273445e-003, 9.913299e-004, 3.332435e-004, -4.174300e-004, -1.007848e-003, -1.459722e-003, -2.014522e-003, -2.587113e-003, + -3.178267e-003, -3.729888e-003, -4.364633e-003, -4.924820e-003, -5.338855e-003, -5.971534e-003, -6.563858e-003, -7.072349e-003, -7.496660e-003, + -7.799408e-003, -8.023004e-003, -8.356203e-003, -8.492284e-003, -8.511378e-003, -8.718785e-003, -8.824603e-003, -8.655957e-003, -8.548967e-003, + -8.601374e-003, -8.682815e-003, -8.587934e-003, -8.363263e-003, -8.268450e-003, -8.131185e-003, -7.797836e-003, -7.669833e-003, -7.531043e-003, + -6.898948e-003, -6.383240e-003, -6.024343e-003, -5.441887e-003, -4.902526e-003, -4.239498e-003, -3.573383e-003, -2.902921e-003, -2.372178e-003, + -1.871299e-003, -1.096393e-003, -1.208313e-004, 6.915064e-004, 1.528646e-003, 2.242243e-003, 2.605212e-003, 3.002374e-003, 2.825727e-003, + 2.567379e-003, 2.201180e-003, 1.899184e-003, 1.645488e-003, 1.401010e-003, 1.377255e-003, 1.031189e-003, 7.158648e-004, 6.046962e-004, + 3.416966e-004, 7.448484e-005, -1.494720e-004, -2.331207e-004, -9.208803e-004, -1.125401e-003, -1.790668e-003, -2.287919e-003, -2.421438e-003, + -2.348767e-003, -2.235243e-003, -1.627230e-003, -1.439908e-003, -9.333177e-004, -8.409756e-004, -4.811531e-004, 3.662682e-004, 1.376310e-003, + 2.673120e-003, 4.076369e-003, 5.600318e-003, 7.349060e-003, 9.409257e-003, 1.126075e-002, 1.276453e-002, 1.377358e-002, 1.477480e-002, + 1.533651e-002, 1.542818e-002, 1.577156e-002, 1.623779e-002, 1.640112e-002, 1.668012e-002, 1.737140e-002, 1.765654e-002, 1.732049e-002, + 1.720878e-002, 1.644030e-002, 1.618503e-002, 1.552297e-002, 1.510542e-002, 1.461779e-002, 1.340778e-002, 1.248509e-002, 1.133303e-002, + 9.203703e-003, 6.743065e-003, 4.084167e-003, 1.782272e-003, -9.143943e-005, -1.704768e-003, -3.155441e-003, -6.513394e-003, -9.988042e-003, + -1.324924e-002, -1.669170e-002, -1.864311e-002, -1.943906e-002, -1.893830e-002, -1.787968e-002, -1.663707e-002, -1.596578e-002, -1.553081e-002, + -1.523850e-002, -1.571381e-002, -1.988256e-002, -2.293123e-002, -2.511904e-002, -2.397287e-002, -1.815730e-002, -1.279813e-002, -1.208478e-002, + -1.477643e-002, -1.775459e-002, -1.935032e-002, -2.034986e-002, -1.999212e-002, -1.947548e-002, -1.878953e-002, -1.788303e-002, -1.265993e-002, + -1.095196e-002, -1.054474e-002, -1.312532e-002, -1.248866e-002, -3.717453e-002, -3.313920e-002, -2.169731e-002, -8.489919e-003, 2.666525e-003, + 9.678586e-003, 1.382860e-002, 1.561125e-002, 2.100526e-002, 3.343839e-002, 7.193176e-001, 1.717793e-001, -4.653494e-002, -1.186034e-001, + -1.577868e-001, -1.799818e-001, -1.359648e-001, -1.210820e-001, -8.039277e-002, -4.017433e-002, 0.000000e+000}, + {0.000000e+000, -8.314882e-003, -1.214427e-002, -1.397798e-002, -1.413916e-002, -1.309681e-002, -1.151897e-002, -1.011041e-002, -8.980506e-003, + -8.291364e-003, -7.318111e-003, -6.200238e-003, -4.978476e-003, -3.524834e-003, -1.963995e-003, -1.493663e-004, 1.855671e-003, 4.097649e-003, + 6.638784e-003, 9.281093e-003, 1.215902e-002, 1.493508e-002, 1.751472e-002, 2.006816e-002, 2.256076e-002, 2.488330e-002, 2.710192e-002, + 2.923334e-002, 3.129733e-002, 3.329943e-002, 3.522162e-002, 3.683590e-002, 3.787682e-002, 3.861551e-002, 3.902013e-002, 3.907740e-002, + 3.877123e-002, 3.813095e-002, 3.714798e-002, 3.589837e-002, 3.435825e-002, 3.253388e-002, 3.003365e-002, 2.730811e-002, 2.438620e-002, + 2.131187e-002, 1.787936e-002, 1.373568e-002, 9.715030e-003, 6.595445e-003, 3.848081e-003, 1.239837e-003, -1.560779e-003, -4.258007e-003, + -6.819166e-003, -9.261151e-003, -1.161055e-002, -1.384680e-002, -1.598256e-002, -1.801644e-002, -1.992247e-002, -2.168721e-002, -2.355726e-002, + -2.542837e-002, -2.716248e-002, -2.873893e-002, -3.012056e-002, -3.129400e-002, -3.220952e-002, -3.288944e-002, -3.330264e-002, -3.346114e-002, + -3.343684e-002, -3.326852e-002, -3.272613e-002, -3.180066e-002, -3.050995e-002, -2.883626e-002, -2.693497e-002, -2.504219e-002, -2.338987e-002, + -2.204601e-002, -2.108912e-002, -2.074267e-002, -2.070341e-002, -2.096347e-002, -2.149348e-002, -2.224218e-002, -2.326420e-002, -2.453805e-002, + -2.599374e-002, -2.764303e-002, -2.942562e-002, -3.162062e-002, -3.389692e-002, -3.622675e-002, -3.858631e-002, -4.094184e-002, -4.325982e-002, + -4.553865e-002, -4.777001e-002, -4.992242e-002, -5.197023e-002, -5.409171e-002, -5.614701e-002, -5.800829e-002, -5.962675e-002, -6.101010e-002, + -6.209607e-002, -6.278338e-002, -6.304502e-002, -6.286673e-002, -6.235581e-002, -6.165680e-002, -6.083060e-002, -5.971815e-002, -5.827016e-002, + -5.649870e-002, -5.438519e-002, -5.192937e-002, -4.919680e-002, -4.629389e-002, -4.335540e-002, -4.057667e-002, -3.830305e-002, -3.631731e-002, + -3.455504e-002, -3.294236e-002, -3.140877e-002, -2.990113e-002, -2.837085e-002, -2.679649e-002, -2.514481e-002, -2.340962e-002, -2.182702e-002, + -2.011859e-002, -1.824650e-002, -1.623305e-002, -1.402872e-002, -1.163013e-002, -9.020126e-003, -6.246486e-003, -3.309229e-003, -2.192636e-004, + 2.789408e-003, 5.838591e-003, 8.932708e-003, 1.198445e-002, 1.495843e-002, 1.783441e-002, 2.059369e-002, 2.325143e-002, 2.585189e-002, + 2.834079e-002, 3.061277e-002, 3.265585e-002, 3.453614e-002, 3.622958e-002, 3.778459e-002, 3.921485e-002, 4.056810e-002, 4.189163e-002, + 4.320668e-002, 4.455505e-002, 4.589989e-002, 4.719453e-002, 4.861365e-002, 5.025050e-002, 5.219569e-002, 5.445140e-002, 5.695403e-002, + 5.961118e-002, 6.232314e-002, 6.505968e-002, 6.782083e-002, 7.050828e-002, 7.336985e-002, 7.640868e-002, 7.958134e-002, 8.281435e-002, + 8.598961e-002, 8.896602e-002, 9.151284e-002, 9.353041e-002, 9.504628e-002, 9.599028e-002, 9.654441e-002, 9.674534e-002, 9.662923e-002, + 9.621510e-002, 9.550850e-002, 9.451984e-002, 9.326172e-002, 9.177010e-002, 9.005690e-002, 8.807237e-002, 8.588413e-002, 8.354767e-002, + 8.107459e-002, 7.847111e-002, 7.571886e-002, 7.283895e-002, 6.983660e-002, 6.675107e-002, 6.365252e-002, 6.055100e-002, 5.756604e-002, + 5.482386e-002, 5.242520e-002, 5.025176e-002, 4.820512e-002, 4.616262e-002, 4.406634e-002, 4.189831e-002, 3.967971e-002, 3.747421e-002, + 3.522984e-002, 3.312063e-002, 3.116928e-002, 2.934707e-002, 2.759782e-002, 2.578284e-002, 2.376928e-002, 2.143967e-002, 1.884987e-002, + 1.609959e-002, 1.323863e-002, 1.040292e-002, 7.617090e-003, 4.897678e-003, 2.249575e-003, -3.418324e-004, -2.851178e-003, -5.278713e-003, + -7.604864e-003, -9.845528e-003, -1.205778e-002, -1.420748e-002, -1.625914e-002, -1.816674e-002, -1.992295e-002, -2.149943e-002, -2.290515e-002, + -2.408294e-002, -2.503741e-002, -2.575722e-002, -2.622308e-002, -2.630050e-002, -2.591947e-002, -2.519259e-002, -2.431060e-002, -2.345588e-002, + -2.269295e-002, -2.208301e-002, -2.160905e-002, -2.125748e-002, -2.104131e-002, -2.097284e-002, -2.098350e-002, -2.107989e-002, -2.129818e-002, + -2.164706e-002, -2.227338e-002, -2.313989e-002, -2.419430e-002, -2.538000e-002, -2.660868e-002, -2.790422e-002, -2.919309e-002, -3.045081e-002, + -3.167193e-002, -3.282749e-002, -3.388024e-002, -3.478495e-002, -3.546696e-002, -3.591521e-002, -3.603301e-002, -3.589677e-002, -3.554882e-002, + -3.502349e-002, -3.437739e-002, -3.359922e-002, -3.267848e-002, -3.160964e-002, -3.037386e-002, -2.896348e-002, -2.738427e-002, -2.569818e-002, + -2.400701e-002, -2.249387e-002, -2.134164e-002, -2.060519e-002, -2.021226e-002, -2.005397e-002, -2.009856e-002, -2.033090e-002, -2.071513e-002, + -2.123446e-002, -2.188884e-002, -2.265927e-002, -2.350648e-002, -2.441158e-002, -2.534408e-002, -2.630212e-002, -2.724218e-002, -2.816622e-002, + -2.909401e-002, -3.002980e-002, -3.097580e-002, -3.191047e-002, -3.280690e-002, -3.368923e-002, -3.450088e-002, -3.514637e-002, -3.554622e-002, + -3.564167e-002, -3.549832e-002, -3.517248e-002, -3.475900e-002, -3.434242e-002, -3.395669e-002, -3.363093e-002, -3.335363e-002, -3.307163e-002, + -3.279399e-002, -3.245230e-002, -3.204050e-002, -3.160042e-002, -3.119661e-002, -3.091458e-002, -3.091207e-002, -3.111855e-002, -3.154078e-002, + -3.206652e-002, -3.264087e-002, -3.325962e-002, -3.386915e-002, -3.443535e-002, -3.493568e-002, -3.532038e-002, -3.559964e-002, -3.569686e-002, + -3.560904e-002, -3.526085e-002, -3.467062e-002, -3.378925e-002, -3.255760e-002, -3.092663e-002, -2.891186e-002, -2.651421e-002, -2.371395e-002, + -2.066204e-002, -1.757159e-002, -1.472297e-002, -1.222191e-002, -1.005326e-002, -8.248768e-003, -6.781584e-003, -5.643017e-003, -4.762886e-003, + -4.144187e-003, -3.743104e-003, -3.508253e-003, -3.482403e-003, -3.583149e-003, -3.757692e-003, -3.982184e-003, -4.238290e-003, -4.520016e-003, + -4.797167e-003, -5.100102e-003, -5.407131e-003, -5.703626e-003, -6.003890e-003, -6.262524e-003, -6.472473e-003, -6.622646e-003, -6.685760e-003, + -6.688536e-003, -6.586974e-003, -6.433897e-003, -6.272620e-003, -6.148543e-003, -6.000292e-003, -5.851127e-003, -5.688492e-003, -5.515025e-003, + -5.365348e-003, -5.253670e-003, -5.233729e-003, -5.378289e-003, -5.721915e-003, -6.244067e-003, -6.861522e-003, -7.521404e-003, -8.229376e-003, + -8.938164e-003, -9.639608e-003, -1.037826e-002, -1.114131e-002, -1.190155e-002, -1.265114e-002, -1.336198e-002, -1.405424e-002, -1.469614e-002, + -1.523767e-002, -1.567995e-002, -1.598066e-002, -1.610669e-002, -1.605403e-002, -1.579472e-002, -1.526666e-002, -1.450323e-002, -1.351015e-002, + -1.227650e-002, -1.088386e-002, -9.349970e-003, -7.667860e-003, -5.879824e-003, -4.023490e-003, -2.132192e-003, -2.948683e-004, 1.468337e-003, + 3.172856e-003, 4.791685e-003, 6.367505e-003, 7.874745e-003, 9.267990e-003, 1.052197e-002, 1.154567e-002, 1.237899e-002, 1.301110e-002, + 1.350593e-002, 1.392912e-002, 1.430485e-002, 1.463437e-002, 1.495012e-002, 1.529638e-002, 1.571025e-002, 1.623413e-002, 1.698895e-002, + 1.802890e-002, 1.941257e-002, 2.100582e-002, 2.269359e-002, 2.434218e-002, 2.593060e-002, 2.747632e-002, 2.905885e-002, 3.077923e-002, + 3.271724e-002, 3.484808e-002, 3.716065e-002, 3.955898e-002, 4.188621e-002, 4.387398e-002, 4.532203e-002, 4.621289e-002, 4.671138e-002, + 4.694314e-002, 4.689775e-002, 4.667432e-002, 4.625877e-002, 4.564761e-002, 4.489984e-002, 4.401184e-002, 4.300108e-002, 4.191434e-002, + 4.077846e-002, 3.961095e-002, 3.840836e-002, 3.719403e-002, 3.594599e-002, 3.468220e-002, 3.336849e-002, 3.202811e-002, 3.062517e-002, + 2.921073e-002, 2.774179e-002, 2.625490e-002, 2.476858e-002, 2.331878e-002, 2.190560e-002, 2.055339e-002, 1.929193e-002, 1.811844e-002, + 1.705694e-002, 1.614760e-002, 1.549043e-002, 1.504321e-002, 1.476662e-002, 1.465042e-002, 1.464435e-002, 1.475299e-002, 1.497324e-002, + 1.528604e-002, 1.573754e-002, 1.633743e-002, 1.705884e-002, 1.782093e-002, 1.856909e-002, 1.918381e-002, 1.970306e-002, 2.012915e-002, + 2.043581e-002, 2.061432e-002, 2.065430e-002, 2.049266e-002, 2.020329e-002, 1.978998e-002, 1.929382e-002, 1.873926e-002, 1.814392e-002, + 1.758190e-002, 1.704201e-002, 1.656703e-002, 1.618391e-002, 1.596274e-002, 1.593947e-002, 1.622842e-002, 1.688612e-002, 1.795864e-002, + 1.932105e-002, 2.078411e-002, 2.212645e-002, 2.331804e-002, 2.432497e-002, 2.509072e-002, 2.559407e-002, 2.584017e-002, 2.588234e-002, + 2.570999e-002, 2.527885e-002, 2.466548e-002, 2.377324e-002, 2.254496e-002, 2.097669e-002, 1.913314e-002, 1.713211e-002, 1.500224e-002, + 1.279031e-002, 1.054430e-002, 8.280434e-003, 6.037589e-003, 3.860073e-003, 1.795740e-003, -1.577306e-004, -1.922859e-003, -3.403175e-003, + -4.518631e-003, -5.401236e-003, -6.026843e-003, -6.433284e-003, -6.669949e-003, -6.827662e-003, -6.929924e-003, -6.978719e-003, -6.977192e-003, + -6.922183e-003, -6.766523e-003, -6.498726e-003, -6.204775e-003, -5.976846e-003, -5.787163e-003, -5.737284e-003, -5.875624e-003, -6.243593e-003, + -6.704881e-003, -7.218633e-003, -7.761482e-003, -8.308928e-003, -8.832836e-003, -9.358152e-003, -9.899875e-003, -1.041207e-002, -1.091565e-002, + -1.139323e-002, -1.185717e-002, -1.226977e-002, -1.263671e-002, -1.291934e-002, -1.312766e-002, -1.323335e-002, -1.324545e-002, -1.309801e-002, + -1.278512e-002, -1.231110e-002, -1.160575e-002, -1.055649e-002, -9.040379e-003, -7.131954e-003, -5.092515e-003, -3.142014e-003, -1.411262e-003, + -1.898454e-005, 1.038535e-003, 1.809495e-003, 2.334536e-003, 2.670320e-003, 2.918828e-003, 3.097227e-003, 3.131980e-003, 2.975484e-003, + 2.421463e-003, 1.398257e-003, -4.873895e-006, -1.628040e-003, -3.457387e-003, -5.434670e-003, -7.517325e-003, -9.618603e-003, -1.176228e-002, + -1.388537e-002, -1.593838e-002, -1.783532e-002, -1.953999e-002, -2.103217e-002, -2.224965e-002, -2.316859e-002, -2.377240e-002, -2.403690e-002, + -2.393815e-002, -2.349852e-002, -2.269220e-002, -2.162039e-002, -2.067846e-002, -2.002139e-002, -1.967569e-002, -1.965994e-002, -2.004818e-002, + -2.075769e-002, -2.171009e-002, -2.298889e-002, -2.458415e-002, -2.636054e-002, -2.837136e-002, -3.061276e-002, -3.300283e-002, -3.546464e-002, + -3.799923e-002, -4.056122e-002, -4.313535e-002, -4.567395e-002, -4.814676e-002, -5.059900e-002, -5.300078e-002, -5.535863e-002, -5.761134e-002, + -5.975373e-002, -6.178706e-002, -6.369728e-002, -6.547494e-002, -6.703842e-002, -6.838506e-002, -6.945415e-002, -7.015620e-002, -7.043717e-002, + -7.028019e-002, -6.974489e-002, -6.891711e-002, -6.774633e-002, -6.628096e-002, -6.442102e-002, -6.214798e-002, -5.942604e-002, -5.630268e-002, + -5.296826e-002, -4.943303e-002, -4.585925e-002, -4.239875e-002, -3.923106e-002, -3.645276e-002, -3.394769e-002, -3.168728e-002, -2.966857e-002, + -2.778969e-002, -2.602952e-002, -2.439461e-002, -2.283418e-002, -2.132982e-002, -1.986217e-002, -1.842977e-002, -1.698172e-002, -1.550134e-002, + -1.395901e-002, -1.235353e-002, -1.070910e-002, -9.023296e-003, -7.291271e-003, -5.479705e-003, -3.557488e-003, -1.549658e-003, 5.564255e-004, + 2.802747e-003, 5.260800e-003, 8.048814e-003, 1.107766e-002, 1.423207e-002, 1.751106e-002, 2.088465e-002, 2.421389e-002, 2.744676e-002, + 3.053203e-002, 3.355952e-002, 3.656980e-002, 3.956623e-002, 4.250432e-002, 4.527695e-002, 4.785232e-002, 5.018729e-002, 5.219569e-002, + 5.378229e-002, 5.509735e-002, 5.615218e-002, 5.700265e-002, 5.776914e-002, 5.844216e-002, 5.890537e-002, 5.927925e-002, 5.969945e-002, + 6.003172e-002, 6.016181e-002, 6.028092e-002, 6.035480e-002, 6.033611e-002, 6.029416e-002, 6.015561e-002, 5.980664e-002, 5.932148e-002, + 5.878442e-002, 5.820905e-002, 5.755276e-002, 5.693183e-002, 5.621960e-002, 5.537998e-002, 5.439386e-002, 5.319016e-002, 5.171648e-002, + 4.995683e-002, 4.808044e-002, 4.600721e-002, 4.371623e-002, 4.131488e-002, 3.879353e-002, 3.618073e-002, 3.354653e-002, 3.087554e-002, + 2.820287e-002, 2.552823e-002, 2.300020e-002, 2.061985e-002, 1.843862e-002, 1.661689e-002, 1.515581e-002, 1.419155e-002, 1.382657e-002, + 1.390681e-002, 1.420661e-002, 1.443051e-002, 1.464489e-002, 1.489214e-002, 1.511864e-002, 1.538845e-002, 1.549986e-002, 1.559736e-002, + 1.568372e-002, 1.566740e-002, 1.547274e-002, 1.488800e-002, 1.384174e-002, 1.251158e-002, 1.098287e-002, 9.305562e-003, 7.634180e-003, + 5.882303e-003, 4.124550e-003, 2.414411e-003, 9.042916e-004, -6.399725e-004, -2.225526e-003, -3.588092e-003, -4.781813e-003, -5.680894e-003, + -6.299279e-003, -6.759759e-003, -6.858433e-003, -6.624373e-003, -6.111911e-003, -5.312695e-003, -4.321593e-003, -3.191453e-003, -2.037281e-003, + -1.008249e-003, -2.991145e-004, 1.688053e-004, 4.623919e-004, 4.929528e-004, 2.101769e-004, -1.499626e-004, -8.580950e-004, -1.803943e-003, + -2.933338e-003, -4.197170e-003, -5.620194e-003, -7.051558e-003, -8.564941e-003, -1.012789e-002, -1.168095e-002, -1.335022e-002, -1.527070e-002, + -1.714455e-002, -1.887578e-002, -2.065887e-002, -2.243261e-002, -2.412717e-002, -2.576599e-002, -2.728908e-002, -2.868775e-002, -3.001662e-002, + -3.111974e-002, -3.200917e-002, -3.268664e-002, -3.293811e-002, -3.271042e-002, -3.227955e-002, -3.167055e-002, -3.099527e-002, -3.023967e-002, + -2.946364e-002, -2.883664e-002, -2.827613e-002, -2.768670e-002, -2.699441e-002, -2.621280e-002, -2.543866e-002, -2.468854e-002, -2.396084e-002, + -2.358775e-002, -2.353090e-002, -2.367776e-002, -2.414825e-002, -2.455431e-002, -2.492611e-002, -2.542570e-002, -2.584717e-002, -2.618143e-002, + -2.657014e-002, -2.672536e-002, -2.668475e-002, -2.649623e-002, -2.596728e-002, -2.523371e-002, -2.421047e-002, -2.288739e-002, -2.117055e-002, + -1.919119e-002, -1.690636e-002, -1.424532e-002, -1.136749e-002, -8.438141e-003, -5.633368e-003, -3.264368e-003, -1.086725e-003, 8.744862e-004, + 2.453987e-003, 3.858068e-003, 4.934877e-003, 5.606505e-003, 6.149258e-003, 6.458543e-003, 6.702346e-003, 6.885592e-003, 6.829033e-003, + 6.761415e-003, 6.585951e-003, 6.407147e-003, 6.252599e-003, 5.978161e-003, 5.754342e-003, 5.686486e-003, 5.486184e-003, 5.192213e-003, + 4.963796e-003, 4.778872e-003, 4.625216e-003, 4.378029e-003, 4.127960e-003, 4.111922e-003, 4.423410e-003, 4.719645e-003, 5.271957e-003, + 5.848503e-003, 6.608577e-003, 7.447792e-003, 8.264125e-003, 9.329361e-003, 1.036227e-002, 1.148208e-002, 1.272198e-002, 1.392730e-002, + 1.523307e-002, 1.651988e-002, 1.756538e-002, 1.857606e-002, 1.933851e-002, 2.012837e-002, 2.090279e-002, 2.158067e-002, 2.232188e-002, + 2.321189e-002, 2.412642e-002, 2.491838e-002, 2.537833e-002, 2.584297e-002, 2.605452e-002, 2.625767e-002, 2.637555e-002, 2.682653e-002, + 2.737885e-002, 2.769306e-002, 2.807710e-002, 2.821182e-002, 2.825533e-002, 2.816799e-002, 2.776007e-002, 2.696080e-002, 2.596051e-002, + 2.478514e-002, 2.332095e-002, 2.184115e-002, 2.000287e-002, 1.816172e-002, 1.596944e-002, 1.358374e-002, 1.120211e-002, 8.664961e-003, + 5.983523e-003, 3.163603e-003, 4.066898e-004, -2.468366e-003, -5.420400e-003, -8.539890e-003, -1.197731e-002, -1.535054e-002, -1.876234e-002, + -2.190554e-002, -2.490533e-002, -2.746909e-002, -2.949696e-002, -3.100798e-002, -3.234805e-002, -3.376502e-002, -3.521267e-002, -3.655737e-002, + -3.755129e-002, -3.798594e-002, -3.814980e-002, -3.766012e-002, -3.673401e-002, -3.554442e-002, -3.384875e-002, -3.174523e-002, -2.978311e-002, + -2.831105e-002, -2.634376e-002, -2.417503e-002, -2.189795e-002, -1.971832e-002, -1.733827e-002, -1.479952e-002, -1.190572e-002, -9.089458e-003, + -5.965177e-003, -3.967650e-003, -1.836910e-003, 1.753282e-004, 2.221123e-003, 4.001362e-003, 5.826173e-003, 7.536525e-003, 9.343409e-003, + 1.124509e-002, 1.283287e-002, 1.300477e-002, 1.271599e-002, 1.322069e-002, 1.423961e-002, 1.616196e-002, 1.834366e-002, 1.963333e-002, + 2.057535e-002, 2.138029e-002, 2.299162e-002, 2.249253e-002, 2.096949e-002, 1.904116e-002, 1.717823e-002, 1.503418e-002, 1.307222e-002, + 1.083819e-002, 9.001618e-003, 6.873305e-003, 5.226429e-003, 1.534100e-002, 1.221989e-002, 8.999945e-003, 5.091102e-003, -4.323942e-005, + -5.375925e-003, -1.199750e-002, -2.052476e-002, -3.024706e-002, -3.914775e-002, 1.101606e-002, 2.761208e-002, 1.422365e-002, -5.480630e-003, + -2.120639e-002, -3.316217e-002, -1.433359e-002, -1.164899e-002, -9.080894e-003, -6.215513e-003, 0.000000e+000}, + {0.000000e+000, 1.501404e-002, 2.675527e-002, 3.614956e-002, 4.362018e-002, 4.952670e-002, 5.395076e-002, 5.653874e-002, 5.741402e-002, + 5.695936e-002, 5.601914e-002, 5.467730e-002, 5.283976e-002, 5.055186e-002, 4.795155e-002, 4.518244e-002, 4.220474e-002, 3.939714e-002, + 3.677121e-002, 3.428768e-002, 3.182600e-002, 2.956258e-002, 2.756989e-002, 2.562994e-002, 2.367399e-002, 2.175405e-002, 1.990211e-002, + 1.816895e-002, 1.657333e-002, 1.515477e-002, 1.383215e-002, 1.267713e-002, 1.172603e-002, 1.080093e-002, 9.968298e-003, 9.196439e-003, + 8.456928e-003, 7.789885e-003, 7.068752e-003, 6.390930e-003, 5.696381e-003, 4.937290e-003, 4.279773e-003, 3.423816e-003, 2.398301e-003, + 1.156128e-003, -5.018710e-004, -3.024583e-003, -5.513283e-003, -7.161650e-003, -8.547121e-003, -9.871581e-003, -1.099641e-002, -1.203447e-002, + -1.312868e-002, -1.421610e-002, -1.530593e-002, -1.643958e-002, -1.756278e-002, -1.873203e-002, -1.986257e-002, -2.101171e-002, -2.204744e-002, + -2.295460e-002, -2.387843e-002, -2.473667e-002, -2.552750e-002, -2.626305e-002, -2.696421e-002, -2.750113e-002, -2.798513e-002, -2.836832e-002, + -2.844781e-002, -2.816556e-002, -2.769364e-002, -2.696063e-002, -2.597773e-002, -2.479971e-002, -2.345834e-002, -2.209589e-002, -2.090948e-002, + -1.993992e-002, -1.919768e-002, -1.845367e-002, -1.795031e-002, -1.763024e-002, -1.750450e-002, -1.753807e-002, -1.774282e-002, -1.812573e-002, + -1.861620e-002, -1.920470e-002, -1.987274e-002, -2.039575e-002, -2.094707e-002, -2.149221e-002, -2.206314e-002, -2.263581e-002, -2.312161e-002, + -2.358774e-002, -2.394426e-002, -2.425846e-002, -2.444571e-002, -2.442703e-002, -2.432448e-002, -2.418371e-002, -2.393621e-002, -2.358707e-002, + -2.313662e-002, -2.255561e-002, -2.186833e-002, -2.094038e-002, -1.987396e-002, -1.870086e-002, -1.742380e-002, -1.608475e-002, -1.467237e-002, + -1.313648e-002, -1.150959e-002, -9.858806e-003, -8.139649e-003, -6.369735e-003, -4.751138e-003, -3.385411e-003, -2.299906e-003, -1.593823e-003, + -1.149227e-003, -9.120278e-004, -8.978519e-004, -1.024928e-003, -1.241544e-003, -1.520014e-003, -1.770416e-003, -2.054056e-003, -2.307042e-003, + -2.523748e-003, -2.685209e-003, -2.777543e-003, -2.737716e-003, -2.597001e-003, -2.359466e-003, -2.010643e-003, -1.557876e-003, -1.010198e-003, + -3.897581e-004, 2.810318e-004, 9.788746e-004, 1.643854e-003, 2.244357e-003, 2.753310e-003, 3.195241e-003, 3.543008e-003, 3.852893e-003, + 4.081177e-003, 4.227028e-003, 4.240224e-003, 4.165085e-003, 3.935951e-003, 3.579394e-003, 3.136642e-003, 2.627024e-003, 2.068571e-003, + 1.488772e-003, 9.037743e-004, 3.213221e-004, -2.585471e-004, -7.696606e-004, -1.140207e-003, -1.319012e-003, -1.296010e-003, -1.124094e-003, + -8.652310e-004, -5.953430e-004, -2.786981e-004, 9.548847e-005, 5.218224e-004, 1.097731e-003, 1.814202e-003, 2.698970e-003, 3.694862e-003, + 4.719930e-003, 5.665967e-003, 6.472175e-003, 7.033085e-003, 7.370574e-003, 7.517428e-003, 7.587684e-003, 7.651691e-003, 7.686221e-003, + 7.750179e-003, 7.821939e-003, 7.893558e-003, 7.985629e-003, 8.146011e-003, 8.360299e-003, 8.591294e-003, 8.876246e-003, 9.258850e-003, + 9.721485e-003, 1.027418e-002, 1.090728e-002, 1.163656e-002, 1.244671e-002, 1.331377e-002, 1.428332e-002, 1.528764e-002, 1.640246e-002, + 1.771791e-002, 1.929181e-002, 2.103055e-002, 2.284942e-002, 2.465255e-002, 2.637460e-002, 2.800776e-002, 2.954945e-002, 3.104880e-002, + 3.246117e-002, 3.393016e-002, 3.545474e-002, 3.697662e-002, 3.842416e-002, 3.970082e-002, 4.076453e-002, 4.154567e-002, 4.205222e-002, + 4.233808e-002, 4.237306e-002, 4.228097e-002, 4.209667e-002, 4.184300e-002, 4.153341e-002, 4.118159e-002, 4.077671e-002, 4.029475e-002, + 3.973094e-002, 3.911119e-002, 3.837536e-002, 3.757707e-002, 3.677392e-002, 3.597934e-002, 3.522889e-002, 3.454251e-002, 3.391512e-002, + 3.339691e-002, 3.298252e-002, 3.271453e-002, 3.256114e-002, 3.262468e-002, 3.299840e-002, 3.361594e-002, 3.432675e-002, 3.501847e-002, + 3.559857e-002, 3.606495e-002, 3.638453e-002, 3.660566e-002, 3.669078e-002, 3.659997e-002, 3.644165e-002, 3.620303e-002, 3.581695e-002, + 3.530073e-002, 3.454154e-002, 3.359402e-002, 3.247423e-002, 3.121280e-002, 2.988042e-002, 2.834887e-002, 2.677186e-002, 2.516195e-002, + 2.354025e-002, 2.193807e-002, 2.035768e-002, 1.881712e-002, 1.739637e-002, 1.610358e-002, 1.500777e-002, 1.394765e-002, 1.297859e-002, + 1.209973e-002, 1.127062e-002, 1.049872e-002, 9.756006e-003, 9.071597e-003, 8.473033e-003, 7.969857e-003, 7.566724e-003, 7.150943e-003, + 6.666785e-003, 6.039938e-003, 5.140593e-003, 3.895992e-003, 2.406961e-003, 7.469011e-004, -1.041623e-003, -2.944114e-003, -4.913780e-003, + -7.004366e-003, -9.231060e-003, -1.151712e-002, -1.383589e-002, -1.611668e-002, -1.835416e-002, -2.056037e-002, -2.267186e-002, -2.472291e-002, + -2.666821e-002, -2.851262e-002, -3.042286e-002, -3.227740e-002, -3.401621e-002, -3.564719e-002, -3.712836e-002, -3.840162e-002, -3.943143e-002, + -4.015906e-002, -4.061689e-002, -4.080478e-002, -4.093799e-002, -4.100624e-002, -4.108497e-002, -4.114607e-002, -4.121089e-002, -4.119559e-002, + -4.113674e-002, -4.099912e-002, -4.081530e-002, -4.060031e-002, -4.047009e-002, -4.044096e-002, -4.059466e-002, -4.091997e-002, -4.138339e-002, + -4.193076e-002, -4.247934e-002, -4.304657e-002, -4.360595e-002, -4.414040e-002, -4.466472e-002, -4.516735e-002, -4.561147e-002, -4.592835e-002, + -4.609937e-002, -4.608153e-002, -4.587538e-002, -4.542647e-002, -4.471680e-002, -4.372593e-002, -4.245173e-002, -4.100314e-002, -3.930093e-002, + -3.737867e-002, -3.542577e-002, -3.360832e-002, -3.198355e-002, -3.056659e-002, -2.937924e-002, -2.840959e-002, -2.762898e-002, -2.713571e-002, + -2.680452e-002, -2.659911e-002, -2.647545e-002, -2.644631e-002, -2.647749e-002, -2.653655e-002, -2.660343e-002, -2.660998e-002, -2.658651e-002, + -2.661561e-002, -2.668029e-002, -2.672568e-002, -2.671498e-002, -2.661871e-002, -2.647780e-002, -2.626927e-002, -2.599711e-002, -2.560102e-002, + -2.511724e-002, -2.454556e-002, -2.396242e-002, -2.332751e-002, -2.268531e-002, -2.199295e-002, -2.120769e-002, -2.040313e-002, -1.959211e-002, + -1.876967e-002, -1.796737e-002, -1.723923e-002, -1.669583e-002, -1.629057e-002, -1.598547e-002, -1.572655e-002, -1.547378e-002, -1.521097e-002, + -1.495160e-002, -1.469595e-002, -1.443602e-002, -1.421582e-002, -1.412983e-002, -1.406643e-002, -1.397346e-002, -1.385340e-002, -1.371613e-002, + -1.351691e-002, -1.325837e-002, -1.290921e-002, -1.245218e-002, -1.190698e-002, -1.131765e-002, -1.057798e-002, -9.695734e-003, -8.675923e-003, + -7.522610e-003, -6.268031e-003, -4.944671e-003, -3.579427e-003, -2.176625e-003, -7.733236e-004, 5.722445e-004, 1.852179e-003, 3.053172e-003, + 4.209109e-003, 5.296903e-003, 6.298534e-003, 7.253376e-003, 8.138786e-003, 8.932026e-003, 9.604197e-003, 1.007843e-002, 1.034744e-002, + 1.052302e-002, 1.068282e-002, 1.086930e-002, 1.105497e-002, 1.128185e-002, 1.152960e-002, 1.180641e-002, 1.221662e-002, 1.279886e-002, + 1.352489e-002, 1.454168e-002, 1.573280e-002, 1.706084e-002, 1.842875e-002, 1.978448e-002, 2.112174e-002, 2.254353e-002, 2.412879e-002, + 2.588876e-002, 2.774566e-002, 2.978064e-002, 3.192346e-002, 3.401088e-002, 3.591440e-002, 3.744643e-002, 3.856027e-002, 3.944359e-002, + 4.017912e-002, 4.069773e-002, 4.100396e-002, 4.116275e-002, 4.123460e-002, 4.119629e-002, 4.107694e-002, 4.088190e-002, 4.056714e-002, + 4.021147e-002, 3.986857e-002, 3.948005e-002, 3.900581e-002, 3.845351e-002, 3.783716e-002, 3.716798e-002, 3.644569e-002, 3.562307e-002, + 3.469138e-002, 3.365634e-002, 3.256613e-002, 3.149847e-002, 3.038090e-002, 2.916939e-002, 2.799506e-002, 2.687474e-002, 2.577440e-002, + 2.472889e-002, 2.377324e-002, 2.294498e-002, 2.224729e-002, 2.167081e-002, 2.116315e-002, 2.064505e-002, 2.020041e-002, 1.978367e-002, + 1.940349e-002, 1.910798e-002, 1.893308e-002, 1.881528e-002, 1.866138e-002, 1.852537e-002, 1.834058e-002, 1.804102e-002, 1.763313e-002, + 1.719802e-002, 1.667923e-002, 1.602037e-002, 1.528232e-002, 1.448103e-002, 1.355136e-002, 1.259286e-002, 1.161682e-002, 1.062728e-002, + 9.596208e-003, 8.605518e-003, 7.668865e-003, 6.812864e-003, 6.096071e-003, 5.571371e-003, 5.309490e-003, 5.300141e-003, 5.588711e-003, + 6.107579e-003, 6.724874e-003, 7.262687e-003, 7.723409e-003, 8.099186e-003, 8.292989e-003, 8.284691e-003, 8.201897e-003, 8.059779e-003, + 7.800365e-003, 7.400955e-003, 6.847416e-003, 6.074136e-003, 5.070236e-003, 3.865899e-003, 2.505067e-003, 1.060338e-003, -4.681771e-004, + -2.082808e-003, -3.793828e-003, -5.516300e-003, -7.247917e-003, -8.953507e-003, -1.063524e-002, -1.227864e-002, -1.383796e-002, -1.517912e-002, + -1.617843e-002, -1.701948e-002, -1.773273e-002, -1.825447e-002, -1.873320e-002, -1.919589e-002, -1.962354e-002, -1.998354e-002, -2.041876e-002, + -2.081455e-002, -2.103727e-002, -2.118270e-002, -2.133251e-002, -2.145151e-002, -2.161301e-002, -2.200201e-002, -2.251142e-002, -2.313208e-002, + -2.381234e-002, -2.446862e-002, -2.509654e-002, -2.576868e-002, -2.647903e-002, -2.723871e-002, -2.795656e-002, -2.872925e-002, -2.948776e-002, + -3.019841e-002, -3.092551e-002, -3.161750e-002, -3.233383e-002, -3.292437e-002, -3.335930e-002, -3.370382e-002, -3.393837e-002, -3.413667e-002, + -3.417657e-002, -3.405492e-002, -3.362960e-002, -3.288873e-002, -3.170337e-002, -3.010884e-002, -2.830774e-002, -2.651975e-002, -2.473055e-002, + -2.299301e-002, -2.132492e-002, -1.974036e-002, -1.817932e-002, -1.661511e-002, -1.494371e-002, -1.313860e-002, -1.127152e-002, -9.419480e-003, + -7.830693e-003, -6.508062e-003, -5.380248e-003, -4.346038e-003, -3.231614e-003, -2.047696e-003, -8.816118e-004, 2.069530e-004, 1.366026e-003, + 2.575400e-003, 3.808420e-003, 5.119838e-003, 6.466931e-003, 7.928078e-003, 9.568137e-003, 1.142905e-002, 1.344670e-002, 1.566934e-002, + 1.809514e-002, 2.065702e-002, 2.334654e-002, 2.613439e-002, 2.870720e-002, 3.098371e-002, 3.290708e-002, 3.450315e-002, 3.566950e-002, + 3.637615e-002, 3.676933e-002, 3.696048e-002, 3.668672e-002, 3.607253e-002, 3.517382e-002, 3.394396e-002, 3.243441e-002, 3.075917e-002, + 2.896066e-002, 2.704964e-002, 2.503045e-002, 2.298467e-002, 2.081959e-002, 1.858409e-002, 1.618575e-002, 1.367976e-002, 1.121953e-002, + 8.795286e-003, 6.472796e-003, 4.172173e-003, 1.976641e-003, -5.328337e-005, -1.925131e-003, -3.636096e-003, -5.099034e-003, -6.423456e-003, + -7.478137e-003, -8.250317e-003, -8.670337e-003, -8.867427e-003, -8.804187e-003, -8.389072e-003, -7.754179e-003, -6.838496e-003, -5.609708e-003, + -4.460076e-003, -3.136346e-003, -1.803560e-003, -5.954180e-004, 3.657056e-004, 1.003278e-003, 1.483799e-003, 1.725659e-003, 1.789930e-003, + 1.742922e-003, 1.292404e-003, 7.691100e-004, 1.546144e-004, -4.415753e-004, -1.043009e-003, -1.599019e-003, -2.104413e-003, -2.540090e-003, + -2.959674e-003, -3.316073e-003, -3.719601e-003, -4.145118e-003, -4.518890e-003, -4.757048e-003, -4.850624e-003, -4.911240e-003, -4.766399e-003, + -4.428469e-003, -3.895926e-003, -3.141637e-003, -2.264484e-003, -1.384009e-003, -2.801248e-004, 8.829345e-004, 2.141843e-003, 3.374391e-003, + 4.554040e-003, 5.743581e-003, 7.032032e-003, 8.356266e-003, 9.618950e-003, 1.067662e-002, 1.160083e-002, 1.246626e-002, 1.315464e-002, + 1.357332e-002, 1.387637e-002, 1.411984e-002, 1.437675e-002, 1.465594e-002, 1.484617e-002, 1.460485e-002, 1.437275e-002, 1.427242e-002, + 1.411926e-002, 1.372761e-002, 1.335066e-002, 1.279202e-002, 1.222752e-002, 1.157621e-002, 1.076035e-002, 9.381629e-003, 8.009808e-003, + 6.781207e-003, 5.529658e-003, 4.186819e-003, 2.751348e-003, 1.133561e-003, -5.333343e-004, -2.076137e-003, -3.762820e-003, -5.875053e-003, + -8.421414e-003, -1.102940e-002, -1.360039e-002, -1.632884e-002, -1.923733e-002, -2.214881e-002, -2.502928e-002, -2.757689e-002, -3.021137e-002, + -3.305707e-002, -3.619807e-002, -3.907154e-002, -4.172068e-002, -4.422352e-002, -4.627127e-002, -4.779479e-002, -4.876994e-002, -4.898738e-002, + -4.885023e-002, -4.838706e-002, -4.826726e-002, -4.783396e-002, -4.733132e-002, -4.657461e-002, -4.543613e-002, -4.433107e-002, -4.289528e-002, + -4.135788e-002, -3.990491e-002, -3.863572e-002, -3.778555e-002, -3.705567e-002, -3.638005e-002, -3.554519e-002, -3.469835e-002, -3.373414e-002, + -3.265153e-002, -3.151729e-002, -3.024103e-002, -2.872420e-002, -2.746105e-002, -2.622725e-002, -2.481369e-002, -2.324112e-002, -2.141199e-002, + -1.918223e-002, -1.665116e-002, -1.390624e-002, -1.102733e-002, -7.823725e-003, -4.397689e-003, -1.394234e-003, 1.874586e-003, 5.247523e-003, + 8.825730e-003, 1.214821e-002, 1.543379e-002, 1.831574e-002, 2.101968e-002, 2.358845e-002, 2.614027e-002, 2.772117e-002, 2.909679e-002, + 3.037257e-002, 3.144348e-002, 3.232040e-002, 3.294138e-002, 3.362637e-002, 3.420778e-002, 3.495432e-002, 3.571228e-002, 3.572251e-002, + 3.553804e-002, 3.545613e-002, 3.516768e-002, 3.491466e-002, 3.472392e-002, 3.439878e-002, 3.427980e-002, 3.441678e-002, 3.462475e-002, + 3.444321e-002, 3.400219e-002, 3.412091e-002, 3.471550e-002, 3.553293e-002, 3.639727e-002, 3.743272e-002, 3.858050e-002, 3.981307e-002, + 4.101958e-002, 4.198502e-002, 4.221974e-002, 4.242649e-002, 4.266842e-002, 4.308569e-002, 4.358844e-002, 4.393469e-002, 4.419350e-002, + 4.430443e-002, 4.418491e-002, 4.401873e-002, 4.279920e-002, 4.155245e-002, 4.020462e-002, 3.871430e-002, 3.728169e-002, 3.589779e-002, + 3.468428e-002, 3.374872e-002, 3.265843e-002, 3.168782e-002, 2.994301e-002, 2.829207e-002, 2.664120e-002, 2.540363e-002, 2.449357e-002, + 2.397366e-002, 2.353265e-002, 2.345708e-002, 2.347587e-002, 2.361731e-002, 2.270580e-002, 2.113998e-002, 1.950122e-002, 1.765561e-002, + 1.565946e-002, 1.376865e-002, 1.152124e-002, 9.102662e-003, 7.218206e-003, 5.183816e-003, 2.615289e-003, -3.851779e-004, -3.486018e-003, + -6.480625e-003, -9.270210e-003, -1.197558e-002, -1.454167e-002, -1.694198e-002, -1.944175e-002, -2.172460e-002, -2.390355e-002, -2.710397e-002, + -2.999039e-002, -3.241241e-002, -3.464384e-002, -3.668108e-002, -3.853862e-002, -4.014019e-002, -4.107149e-002, -4.173655e-002, -4.213476e-002, + -4.390268e-002, -4.538513e-002, -4.623604e-002, -4.683822e-002, -4.703178e-002, -4.699299e-002, -4.629671e-002, -4.539325e-002, -4.424150e-002, + -4.337699e-002, -4.321138e-002, -4.351186e-002, -4.381334e-002, -4.412244e-002, -4.411127e-002, -4.395834e-002, -4.366798e-002, -4.315568e-002, + -4.202498e-002, -4.108187e-002, -4.095222e-002, -4.086003e-002, -4.089966e-002, -4.108777e-002, -4.048994e-002, -4.010219e-002, -4.003440e-002, + -3.962959e-002, -3.943975e-002, -3.906411e-002, -3.864130e-002, -3.934392e-002, -4.020141e-002, -4.078777e-002, -4.146702e-002, -4.211575e-002, + -4.188463e-002, -4.161813e-002, -4.064818e-002, -3.880230e-002, -3.722759e-002, -3.687198e-002, -3.497854e-002, -3.266861e-002, -3.046936e-002, + -2.717855e-002, -2.346514e-002, -1.928974e-002, -1.472053e-002, -1.015454e-002, -4.516786e-003, 2.329857e-004, 4.369109e-003, 8.947472e-003, + 1.412497e-002, 2.021628e-002, 2.640411e-002, 3.340344e-002, 4.074637e-002, 4.820081e-002, 5.582310e-002, 6.165875e-002, 6.652469e-002, + 7.171450e-002, 7.664670e-002, 8.149182e-002, 8.644357e-002, 9.082694e-002, 9.504190e-002, 9.855155e-002, 1.010657e-001, 1.010114e-001, + 9.896326e-002, 9.662385e-002, 9.385036e-002, 9.123267e-002, 8.758923e-002, 8.470248e-002, 8.097590e-002, 7.678541e-002, 7.146030e-002, + 6.519561e-002, 5.347126e-002, 4.219965e-002, 3.056071e-002, 1.982283e-002, 1.022471e-002, 8.917409e-004, -8.912022e-003, -1.834578e-002, + -2.694842e-002, -3.359628e-002, -4.289161e-002, -5.034406e-002, -5.785256e-002, -6.288975e-002, -6.750979e-002, -6.953640e-002, -7.155991e-002, + -7.309523e-002, -7.516657e-002, -7.813948e-002, -7.791139e-002, -7.299977e-002, -6.622939e-002, -6.147947e-002, -5.473512e-002, -4.772231e-002, + -3.431692e-002, -2.025227e-002, -8.478797e-003, 3.198007e-003, -6.584606e-002, -6.411181e-002, -5.398894e-002, -3.316145e-002, -5.279194e-003, + 2.619006e-002, 5.776649e-002, 9.217250e-002, 1.291238e-001, 1.624301e-001, -1.305243e-002, -1.214986e-001, -6.671667e-002, 2.265593e-002, + 7.548315e-002, 1.106684e-001, 1.015218e-001, 6.539594e-002, 2.757593e-002, 7.803696e-003, 0.000000e+000}, + {0.000000e+000, 1.470711e-002, 2.385155e-002, 2.943746e-002, 3.310653e-002, 3.528947e-002, 3.614555e-002, 3.573553e-002, 3.468827e-002, + 3.313360e-002, 3.151139e-002, 2.978435e-002, 2.794378e-002, 2.602758e-002, 2.415664e-002, 2.223808e-002, 2.034137e-002, 1.853469e-002, + 1.682527e-002, 1.521173e-002, 1.394494e-002, 1.297383e-002, 1.228537e-002, 1.168492e-002, 1.112089e-002, 1.054153e-002, 9.993937e-003, + 9.455909e-003, 9.098443e-003, 8.994597e-003, 9.151954e-003, 9.438410e-003, 9.759535e-003, 9.963619e-003, 1.002215e-002, 9.974793e-003, + 9.814044e-003, 9.506074e-003, 9.021554e-003, 8.357113e-003, 7.452010e-003, 6.409430e-003, 5.346455e-003, 4.121137e-003, 2.714912e-003, + 1.167412e-003, -5.629090e-004, -2.588928e-003, -4.663203e-003, -6.572644e-003, -8.385926e-003, -1.019694e-002, -1.179590e-002, -1.335754e-002, + -1.482028e-002, -1.630572e-002, -1.779036e-002, -1.924632e-002, -2.076650e-002, -2.223736e-002, -2.372259e-002, -2.518320e-002, -2.647949e-002, + -2.767239e-002, -2.870553e-002, -2.962614e-002, -3.036966e-002, -3.089289e-002, -3.109989e-002, -3.107696e-002, -3.062923e-002, -2.984201e-002, + -2.853710e-002, -2.674290e-002, -2.435398e-002, -2.155808e-002, -1.814607e-002, -1.433476e-002, -1.011818e-002, -6.014490e-003, -2.227455e-003, + 9.976232e-004, 3.757014e-003, 6.040084e-003, 7.864813e-003, 9.196447e-003, 1.005105e-002, 1.051958e-002, 1.057811e-002, 1.020957e-002, + 9.597641e-003, 8.660077e-003, 7.548973e-003, 6.315226e-003, 4.948290e-003, 3.491835e-003, 1.917654e-003, 2.998424e-004, -1.356914e-003, + -3.048465e-003, -4.712144e-003, -6.346172e-003, -7.946848e-003, -9.448634e-003, -1.086962e-002, -1.212656e-002, -1.325434e-002, -1.420563e-002, + -1.488492e-002, -1.514394e-002, -1.496689e-002, -1.441363e-002, -1.356572e-002, -1.240904e-002, -1.106441e-002, -9.507677e-003, -7.711646e-003, + -5.624763e-003, -3.228497e-003, -5.318444e-004, 2.345981e-003, 5.237627e-003, 7.952566e-003, 1.022494e-002, 1.177790e-002, 1.263004e-002, + 1.292398e-002, 1.273553e-002, 1.224276e-002, 1.157787e-002, 1.071685e-002, 9.753229e-003, 8.726438e-003, 7.715507e-003, 6.720108e-003, + 5.812779e-003, 5.066223e-003, 4.457271e-003, 4.103393e-003, 3.989846e-003, 4.114796e-003, 4.432667e-003, 4.973587e-003, 5.721168e-003, + 6.634362e-003, 7.694951e-003, 8.796092e-003, 9.810055e-003, 1.066374e-002, 1.131030e-002, 1.174820e-002, 1.203343e-002, 1.221536e-002, + 1.221320e-002, 1.203265e-002, 1.167418e-002, 1.108941e-002, 1.017248e-002, 8.974302e-003, 7.592122e-003, 6.090285e-003, 4.523239e-003, + 2.948218e-003, 1.388182e-003, -9.256327e-005, -1.458377e-003, -2.632660e-003, -3.514435e-003, -3.891082e-003, -3.788542e-003, -3.312267e-003, + -2.620472e-003, -1.820118e-003, -9.344224e-004, 5.392192e-005, 1.156364e-003, 2.562547e-003, 4.311238e-003, 6.352743e-003, 8.581351e-003, + 1.084688e-002, 1.296575e-002, 1.459358e-002, 1.555002e-002, 1.588446e-002, 1.579683e-002, 1.541216e-002, 1.477602e-002, 1.391546e-002, + 1.291801e-002, 1.179997e-002, 1.053476e-002, 9.165277e-003, 7.752711e-003, 6.352653e-003, 4.980773e-003, 3.641334e-003, 2.370103e-003, + 1.191830e-003, 1.116674e-004, -8.792832e-004, -1.785580e-003, -2.609592e-003, -3.335372e-003, -3.914223e-003, -4.299522e-003, -4.295807e-003, + -3.772846e-003, -2.655522e-003, -1.143427e-003, 5.637108e-004, 2.274672e-003, 3.878278e-003, 5.321573e-003, 6.662664e-003, 7.956749e-003, + 9.285087e-003, 1.078610e-002, 1.245877e-002, 1.421325e-002, 1.590190e-002, 1.735163e-002, 1.834284e-002, 1.872289e-002, 1.854148e-002, + 1.791307e-002, 1.702929e-002, 1.598325e-002, 1.486169e-002, 1.366520e-002, 1.242341e-002, 1.114317e-002, 9.821118e-003, 8.448514e-003, + 7.016483e-003, 5.551032e-003, 4.040702e-003, 2.556537e-003, 1.140457e-003, -1.771865e-004, -1.340068e-003, -2.344153e-003, -3.144545e-003, + -3.673781e-003, -3.936651e-003, -3.841174e-003, -3.375091e-003, -2.277329e-003, -4.862951e-004, 1.834071e-003, 4.364304e-003, 6.845582e-003, + 9.125441e-003, 1.116167e-002, 1.288378e-002, 1.433479e-002, 1.547698e-002, 1.631827e-002, 1.696117e-002, 1.738144e-002, 1.745793e-002, + 1.720195e-002, 1.641115e-002, 1.516779e-002, 1.354264e-002, 1.160831e-002, 9.489553e-003, 7.139206e-003, 4.696358e-003, 2.207035e-003, + -2.813720e-004, -2.708248e-003, -5.043422e-003, -7.203406e-003, -9.102018e-003, -1.060872e-002, -1.167423e-002, -1.243313e-002, -1.288619e-002, + -1.308742e-002, -1.306915e-002, -1.287343e-002, -1.246478e-002, -1.179227e-002, -1.084795e-002, -9.631046e-003, -8.160636e-003, -6.540336e-003, + -4.900948e-003, -3.516607e-003, -2.699473e-003, -2.502866e-003, -2.806651e-003, -3.496135e-003, -4.472033e-003, -5.713971e-003, -7.171004e-003, + -8.792053e-003, -1.057979e-002, -1.246672e-002, -1.441861e-002, -1.638540e-002, -1.831207e-002, -2.020253e-002, -2.198026e-002, -2.364943e-002, + -2.517514e-002, -2.659319e-002, -2.798528e-002, -2.927107e-002, -3.042557e-002, -3.144010e-002, -3.224967e-002, -3.271862e-002, -3.272642e-002, + -3.215030e-002, -3.102259e-002, -2.954508e-002, -2.796432e-002, -2.631467e-002, -2.476525e-002, -2.331590e-002, -2.197125e-002, -2.064269e-002, + -1.931720e-002, -1.789307e-002, -1.643602e-002, -1.503663e-002, -1.386663e-002, -1.301899e-002, -1.269086e-002, -1.279574e-002, -1.329274e-002, + -1.405473e-002, -1.491935e-002, -1.587941e-002, -1.692808e-002, -1.797803e-002, -1.903819e-002, -2.009670e-002, -2.102141e-002, -2.174129e-002, + -2.222438e-002, -2.235091e-002, -2.209540e-002, -2.138649e-002, -2.012845e-002, -1.830958e-002, -1.594769e-002, -1.311616e-002, -9.700340e-003, + -5.866316e-003, -2.012977e-003, 1.502048e-003, 4.461081e-003, 6.842384e-003, 8.678816e-003, 1.000649e-002, 1.085659e-002, 1.116776e-002, + 1.109034e-002, 1.067505e-002, 1.004355e-002, 9.199033e-003, 8.201746e-003, 7.133353e-003, 5.990511e-003, 4.888105e-003, 3.845893e-003, + 2.784146e-003, 1.755062e-003, 8.729911e-004, 1.356723e-004, -4.535081e-004, -8.955309e-004, -1.126969e-003, -1.156235e-003, -9.611690e-004, + -5.628710e-004, 6.937336e-005, 7.867038e-004, 1.582231e-003, 2.398588e-003, 3.353008e-003, 4.391331e-003, 5.494600e-003, 6.678781e-003, + 7.866830e-003, 8.965230e-003, 9.892573e-003, 1.044324e-002, 1.065738e-002, 1.062343e-002, 1.046079e-002, 1.019896e-002, 9.863480e-003, + 9.507157e-003, 9.078057e-003, 8.576337e-003, 8.011159e-003, 7.262284e-003, 6.440581e-003, 5.598109e-003, 4.768016e-003, 3.965540e-003, + 3.213183e-003, 2.557267e-003, 2.049415e-003, 1.722377e-003, 1.600211e-003, 1.594289e-003, 1.841733e-003, 2.312273e-003, 3.035115e-003, + 3.976170e-003, 5.118478e-003, 6.424807e-003, 7.865854e-003, 9.423603e-003, 1.102541e-002, 1.250951e-002, 1.381528e-002, 1.497229e-002, + 1.600104e-002, 1.689607e-002, 1.769248e-002, 1.835399e-002, 1.882459e-002, 1.907794e-002, 1.897096e-002, 1.848564e-002, 1.765139e-002, + 1.660749e-002, 1.548396e-002, 1.431971e-002, 1.311694e-002, 1.191261e-002, 1.076754e-002, 9.751177e-003, 8.927444e-003, 8.484394e-003, + 8.402696e-003, 8.895643e-003, 9.772000e-003, 1.087216e-002, 1.198649e-002, 1.309024e-002, 1.417865e-002, 1.536594e-002, 1.679154e-002, + 1.860074e-002, 2.068077e-002, 2.307871e-002, 2.566506e-002, 2.817436e-002, 3.019921e-002, 3.138889e-002, 3.184115e-002, 3.172504e-002, + 3.124642e-002, 3.041845e-002, 2.927096e-002, 2.786794e-002, 2.624973e-002, 2.446846e-002, 2.256396e-002, 2.053936e-002, 1.845890e-002, + 1.635989e-002, 1.426599e-002, 1.215043e-002, 9.982670e-003, 7.785211e-003, 5.573257e-003, 3.354093e-003, 1.131536e-003, -1.148988e-003, + -3.448758e-003, -5.749069e-003, -8.013942e-003, -1.022495e-002, -1.240847e-002, -1.456944e-002, -1.662273e-002, -1.852153e-002, -2.024174e-002, + -2.173623e-002, -2.297699e-002, -2.376509e-002, -2.416364e-002, -2.424957e-002, -2.410876e-002, -2.383642e-002, -2.341724e-002, -2.281133e-002, + -2.201590e-002, -2.103281e-002, -1.981775e-002, -1.841899e-002, -1.695471e-002, -1.555817e-002, -1.428638e-002, -1.322024e-002, -1.235011e-002, + -1.165369e-002, -1.116427e-002, -1.087814e-002, -1.085910e-002, -1.105487e-002, -1.136123e-002, -1.180925e-002, -1.235999e-002, -1.301223e-002, + -1.364250e-002, -1.424872e-002, -1.475660e-002, -1.508621e-002, -1.517844e-002, -1.496501e-002, -1.420867e-002, -1.285344e-002, -1.082202e-002, + -8.365882e-003, -5.785780e-003, -3.336778e-003, -1.193717e-003, 6.801640e-004, 2.280567e-003, 3.602129e-003, 4.717797e-003, 5.637716e-003, + 6.370149e-003, 6.833955e-003, 7.004490e-003, 6.801548e-003, 6.264650e-003, 5.267874e-003, 3.877531e-003, 2.242962e-003, 3.643376e-004, + -1.580770e-003, -3.577096e-003, -5.544203e-003, -7.473947e-003, -9.300466e-003, -1.104167e-002, -1.261402e-002, -1.383091e-002, -1.457939e-002, + -1.481601e-002, -1.463911e-002, -1.409853e-002, -1.329989e-002, -1.242430e-002, -1.145663e-002, -1.046610e-002, -9.489297e-003, -8.566849e-003, + -7.693679e-003, -6.747475e-003, -5.757359e-003, -4.797247e-003, -3.965659e-003, -3.403659e-003, -3.380450e-003, -3.785446e-003, -4.572035e-003, + -5.607451e-003, -6.762622e-003, -7.993625e-003, -9.263330e-003, -1.058310e-002, -1.189013e-002, -1.319313e-002, -1.457319e-002, -1.599399e-002, + -1.742066e-002, -1.890288e-002, -2.035007e-002, -2.172099e-002, -2.293360e-002, -2.394568e-002, -2.481295e-002, -2.547663e-002, -2.590480e-002, + -2.599411e-002, -2.579836e-002, -2.511562e-002, -2.386895e-002, -2.192175e-002, -1.919548e-002, -1.611875e-002, -1.307152e-002, -1.025466e-002, + -7.719859e-003, -5.596299e-003, -3.911633e-003, -2.363471e-003, -8.285868e-004, 7.752581e-004, 2.510695e-003, 4.246003e-003, 5.642713e-003, + 6.616438e-003, 7.041042e-003, 6.961679e-003, 6.645664e-003, 6.291001e-003, 5.824113e-003, 5.329249e-003, 4.797726e-003, 4.297902e-003, + 3.964360e-003, 3.844453e-003, 3.884863e-003, 4.100297e-003, 4.698220e-003, 5.656172e-003, 7.071381e-003, 8.996613e-003, 1.139343e-002, + 1.421904e-002, 1.752031e-002, 2.127268e-002, 2.540652e-002, 2.959688e-002, 3.347268e-002, 3.686824e-002, 3.972817e-002, 4.203517e-002, + 4.378206e-002, 4.508630e-002, 4.589776e-002, 4.625833e-002, 4.610721e-002, 4.560014e-002, 4.468902e-002, 4.348849e-002, 4.205359e-002, + 4.036838e-002, 3.845582e-002, 3.639558e-002, 3.433881e-002, 3.220125e-002, 2.999954e-002, 2.768287e-002, 2.520036e-002, 2.273808e-002, + 2.026208e-002, 1.788083e-002, 1.559905e-002, 1.346054e-002, 1.147134e-002, 9.689246e-003, 8.140408e-003, 6.989203e-003, 6.207392e-003, + 5.891740e-003, 5.909570e-003, 6.321913e-003, 6.973676e-003, 7.879743e-003, 9.111558e-003, 1.064071e-002, 1.253280e-002, 1.479296e-002, + 1.725812e-002, 1.985134e-002, 2.243682e-002, 2.473297e-002, 2.640858e-002, 2.729291e-002, 2.765708e-002, 2.762647e-002, 2.712463e-002, + 2.628925e-002, 2.493381e-002, 2.320106e-002, 2.132884e-002, 1.940003e-002, 1.733626e-002, 1.511586e-002, 1.283987e-002, 1.057440e-002, + 8.466864e-003, 6.365020e-003, 4.132742e-003, 1.880097e-003, -3.885574e-004, -2.590050e-003, -4.607024e-003, -6.536851e-003, -8.244533e-003, + -9.613716e-003, -1.076934e-002, -1.134622e-002, -1.147631e-002, -1.139889e-002, -1.111425e-002, -1.066005e-002, -1.018438e-002, -9.681517e-003, + -9.307159e-003, -9.019717e-003, -8.557387e-003, -8.053603e-003, -7.435151e-003, -7.120488e-003, -7.125101e-003, -7.409970e-003, -8.036044e-003, + -9.167196e-003, -1.059239e-002, -1.225682e-002, -1.415920e-002, -1.610398e-002, -1.816266e-002, -2.050262e-002, -2.275878e-002, -2.491432e-002, + -2.687274e-002, -2.863240e-002, -3.018673e-002, -3.146643e-002, -3.243032e-002, -3.321314e-002, -3.385925e-002, -3.468951e-002, -3.538832e-002, + -3.582917e-002, -3.603409e-002, -3.598898e-002, -3.574494e-002, -3.550947e-002, -3.533068e-002, -3.514670e-002, -3.496124e-002, -3.512897e-002, + -3.560504e-002, -3.625453e-002, -3.707319e-002, -3.805180e-002, -3.913440e-002, -4.032742e-002, -4.160446e-002, -4.272987e-002, -4.390710e-002, + -4.505960e-002, -4.630658e-002, -4.731210e-002, -4.799662e-002, -4.819791e-002, -4.785928e-002, -4.691640e-002, -4.528066e-002, -4.261562e-002, + -3.937079e-002, -3.590262e-002, -3.297435e-002, -3.015635e-002, -2.735242e-002, -2.456243e-002, -2.182234e-002, -1.917192e-002, -1.684551e-002, + -1.424818e-002, -1.171513e-002, -9.355638e-003, -7.544183e-003, -6.348535e-003, -5.704801e-003, -5.266788e-003, -4.844053e-003, -4.451315e-003, + -4.058483e-003, -3.646156e-003, -3.547113e-003, -3.348308e-003, -3.013289e-003, -2.721100e-003, -2.008997e-003, -8.561069e-004, 6.229952e-004, + 2.294624e-003, 4.136630e-003, 6.505292e-003, 9.518733e-003, 1.332785e-002, 1.739096e-002, 2.138379e-002, 2.597635e-002, 3.056757e-002, + 3.488160e-002, 3.862853e-002, 4.171508e-002, 4.439780e-002, 4.674723e-002, 4.876814e-002, 5.026799e-002, 5.073940e-002, 5.081714e-002, + 5.053382e-002, 4.990740e-002, 4.892916e-002, 4.773838e-002, 4.635828e-002, 4.486604e-002, 4.338609e-002, 4.185752e-002, 3.980144e-002, + 3.775149e-002, 3.562064e-002, 3.356114e-002, 3.139212e-002, 2.921529e-002, 2.724230e-002, 2.521192e-002, 2.328925e-002, 2.159055e-002, + 1.971744e-002, 1.821229e-002, 1.734659e-002, 1.697206e-002, 1.685442e-002, 1.722541e-002, 1.763724e-002, 1.818866e-002, 1.860193e-002, + 1.920408e-002, 1.932537e-002, 1.900383e-002, 1.875683e-002, 1.864936e-002, 1.854494e-002, 1.848039e-002, 1.857740e-002, 1.870498e-002, + 1.815293e-002, 1.694992e-002, 1.511929e-002, 1.295371e-002, 1.055159e-002, 8.137107e-003, 5.932524e-003, 3.712202e-003, 1.514624e-003, + -5.221908e-004, -2.181047e-003, -3.453448e-003, -4.363185e-003, -5.135264e-003, -5.573816e-003, -5.883735e-003, -5.456369e-003, -4.354433e-003, + -2.902829e-003, -9.522627e-004, 1.261179e-003, 3.982725e-003, 6.904775e-003, 9.436941e-003, 1.123109e-002, 1.239348e-002, 1.314885e-002, + 1.357073e-002, 1.358050e-002, 1.345837e-002, 1.299462e-002, 1.206866e-002, 1.101548e-002, 9.342446e-003, 7.181895e-003, 4.639884e-003, + 2.168803e-003, -5.403141e-004, -3.338211e-003, -6.171053e-003, -9.023248e-003, -1.162414e-002, -1.404210e-002, -1.674262e-002, -1.974507e-002, + -2.261353e-002, -2.562683e-002, -2.864360e-002, -3.120368e-002, -3.382842e-002, -3.609842e-002, -3.770309e-002, -3.877005e-002, -3.951570e-002, + -4.011970e-002, -4.028098e-002, -4.029244e-002, -4.001073e-002, -3.948306e-002, -3.907075e-002, -3.868763e-002, -3.776890e-002, -3.650010e-002, + -3.510149e-002, -3.379399e-002, -3.280304e-002, -3.133605e-002, -2.992369e-002, -2.836996e-002, -2.665070e-002, -2.480328e-002, -2.247509e-002, + -2.065475e-002, -1.837571e-002, -1.649092e-002, -1.536351e-002, -1.424033e-002, -1.287557e-002, -1.144852e-002, -1.038414e-002, -8.784072e-003, + -6.564428e-003, -4.390617e-003, -1.746136e-003, 4.940023e-004, 2.163875e-003, 4.790194e-003, 7.776668e-003, 1.056719e-002, 1.276960e-002, + 1.472856e-002, 1.674335e-002, 1.850011e-002, 2.042297e-002, 2.257840e-002, 2.354449e-002, 2.397875e-002, 2.501819e-002, 2.611174e-002, + 2.718280e-002, 2.793536e-002, 2.940966e-002, 3.081194e-002, 3.246195e-002, 3.477144e-002, 3.474753e-002, 3.408739e-002, 3.393655e-002, + 3.393179e-002, 3.354272e-002, 3.378031e-002, 3.485546e-002, 3.585239e-002, 3.693504e-002, 3.557862e-002, 3.269076e-002, 2.784042e-002, + 2.238909e-002, 1.779192e-002, 1.396547e-002, 1.038320e-002, 6.693330e-003, 3.371938e-003, 7.805305e-004, -1.052442e-003, -3.757153e-003, + -9.158682e-003, -1.434758e-002, -1.954858e-002, -2.527382e-002, -2.960517e-002, -3.412770e-002, -3.798694e-002, -4.088231e-002, -4.280558e-002, + -4.423660e-002, -4.927104e-002, -5.465330e-002, -5.909645e-002, -6.273243e-002, -6.574110e-002, -6.924741e-002, -6.972638e-002, -6.982251e-002, + -7.020630e-002, -6.968040e-002, -7.097161e-002, -6.968716e-002, -6.457170e-002, -5.561283e-002, -3.731794e-002, -1.522860e-002, 4.483525e-003, + 2.533814e-002, 4.846764e-002, 8.473675e-002, 1.078937e-001, 1.266752e-001, 1.410687e-001, 1.475165e-001, 1.414715e-001, 1.255990e-001, + 9.738725e-002, 6.864809e-002, 4.626563e-002, 2.822161e-002, 1.643456e-001, 1.419734e-001, 1.064186e-001, 6.252018e-002, 1.540676e-002, + -3.864968e-002, -1.045274e-001, -1.780579e-001, -2.537739e-001, -3.185459e-001, -1.184888e-001, 2.421630e-001, 1.608806e-001, -2.613183e-002, + -1.404076e-001, -1.937554e-001, -8.880239e-002, -3.489049e-002, 2.083226e-002, 2.721374e-002, 0.000000e+000}, + {0.000000e+000, 2.903965e-003, 4.551270e-003, 5.550537e-003, 7.088522e-003, 9.235748e-003, 1.183259e-002, 1.414636e-002, 1.606116e-002, + 1.773237e-002, 1.954150e-002, 2.074767e-002, 2.163211e-002, 2.241228e-002, 2.311145e-002, 2.373633e-002, 2.423796e-002, 2.465463e-002, + 2.479083e-002, 2.449318e-002, 2.343567e-002, 2.127563e-002, 1.836984e-002, 1.534739e-002, 1.234594e-002, 9.432343e-003, 6.501517e-003, + 3.509311e-003, 3.097889e-004, -3.205616e-003, -7.026396e-003, -1.098717e-002, -1.487007e-002, -1.834551e-002, -2.138312e-002, -2.392421e-002, + -2.592638e-002, -2.743838e-002, -2.842199e-002, -2.894234e-002, -2.892510e-002, -2.848022e-002, -2.776194e-002, -2.656646e-002, -2.498017e-002, + -2.313135e-002, -2.117085e-002, -1.941252e-002, -1.747569e-002, -1.505768e-002, -1.246127e-002, -9.862632e-003, -7.358711e-003, -4.810425e-003, + -2.180808e-003, 5.746727e-004, 3.452635e-003, 6.446432e-003, 9.528409e-003, 1.264267e-002, 1.577304e-002, 1.887730e-002, 2.187374e-002, + 2.472561e-002, 2.739921e-002, 2.979362e-002, 3.193125e-002, 3.354298e-002, 3.458326e-002, 3.503686e-002, 3.475509e-002, 3.369066e-002, + 3.181080e-002, 2.898566e-002, 2.512481e-002, 2.015588e-002, 1.417063e-002, 7.112311e-003, -5.840502e-004, -8.225754e-003, -1.510219e-002, + -2.098056e-002, -2.582160e-002, -2.960299e-002, -3.244873e-002, -3.441860e-002, -3.553086e-002, -3.587288e-002, -3.532921e-002, -3.406279e-002, + -3.216600e-002, -2.979004e-002, -2.695857e-002, -2.372520e-002, -2.022121e-002, -1.649154e-002, -1.262246e-002, -8.674661e-003, -4.591839e-003, + -4.831050e-004, 3.665114e-003, 7.806292e-003, 1.187523e-002, 1.592741e-002, 1.982119e-002, 2.348562e-002, 2.686094e-002, 2.980455e-002, + 3.210517e-002, 3.355003e-002, 3.410042e-002, 3.374502e-002, 3.272004e-002, 3.106026e-002, 2.892735e-002, 2.631819e-002, 2.311648e-002, + 1.925999e-002, 1.474846e-002, 9.629507e-003, 4.049881e-003, -1.671922e-003, -7.060979e-003, -1.158693e-002, -1.472824e-002, -1.650719e-002, + -1.716546e-002, -1.701081e-002, -1.629142e-002, -1.518312e-002, -1.381375e-002, -1.227107e-002, -1.062022e-002, -9.007506e-003, -7.501829e-003, + -6.235036e-003, -5.307823e-003, -4.733938e-003, -4.647284e-003, -5.057323e-003, -5.966086e-003, -7.253277e-003, -8.970710e-003, -1.110617e-002, + -1.358424e-002, -1.634171e-002, -1.920731e-002, -2.186520e-002, -2.412815e-002, -2.596909e-002, -2.739667e-002, -2.846019e-002, -2.924857e-002, + -2.966273e-002, -2.978334e-002, -2.948667e-002, -2.867123e-002, -2.716701e-002, -2.507119e-002, -2.256560e-002, -1.978001e-002, -1.686272e-002, + -1.389290e-002, -1.097277e-002, -8.232648e-003, -5.791433e-003, -3.738574e-003, -2.278915e-003, -1.758821e-003, -2.250355e-003, -3.521877e-003, + -5.261935e-003, -7.193045e-003, -9.270621e-003, -1.150837e-002, -1.408357e-002, -1.725168e-002, -2.107568e-002, -2.548093e-002, -3.026646e-002, + -3.512395e-002, -3.957360e-002, -4.297703e-002, -4.502347e-002, -4.576292e-002, -4.563647e-002, -4.474825e-002, -4.325349e-002, -4.117800e-002, + -3.865640e-002, -3.576485e-002, -3.245915e-002, -2.891773e-002, -2.516291e-002, -2.129258e-002, -1.737856e-002, -1.344807e-002, -9.606887e-003, + -5.848233e-003, -2.159702e-003, 1.452782e-003, 5.020457e-003, 8.493934e-003, 1.180846e-002, 1.489786e-002, 1.760093e-002, 1.948115e-002, + 2.035584e-002, 2.001960e-002, 1.891443e-002, 1.743112e-002, 1.594588e-002, 1.465340e-002, 1.360020e-002, 1.273618e-002, 1.185549e-002, + 1.084541e-002, 9.477125e-003, 7.756907e-003, 5.775659e-003, 3.807651e-003, 2.323873e-003, 1.693783e-003, 2.305711e-003, 3.986422e-003, + 6.477062e-003, 9.331964e-003, 1.241282e-002, 1.561938e-002, 1.889441e-002, 2.220165e-002, 2.551698e-002, 2.886633e-002, 3.226507e-002, + 3.569845e-002, 3.914379e-002, 4.253840e-002, 4.584315e-002, 4.897508e-002, 5.183965e-002, 5.438190e-002, 5.656875e-002, 5.834856e-002, + 5.959992e-002, 6.023653e-002, 6.013837e-002, 5.918656e-002, 5.690729e-002, 5.324077e-002, 4.850239e-002, 4.329176e-002, 3.817963e-002, + 3.345825e-002, 2.922105e-002, 2.552150e-002, 2.236279e-002, 1.970633e-002, 1.755050e-002, 1.578780e-002, 1.443777e-002, 1.370612e-002, + 1.354713e-002, 1.437329e-002, 1.606397e-002, 1.844334e-002, 2.134042e-002, 2.458742e-002, 2.801208e-002, 3.154385e-002, 3.507640e-002, + 3.848130e-002, 4.166550e-002, 4.453822e-002, 4.693822e-002, 4.873894e-002, 4.966375e-002, 4.962977e-002, 4.879704e-002, 4.727710e-002, + 4.519356e-002, 4.261602e-002, 3.965044e-002, 3.617417e-002, 3.209664e-002, 2.744718e-002, 2.219940e-002, 1.639825e-002, 1.021042e-002, + 3.940754e-003, -1.814134e-003, -6.396885e-003, -9.754607e-003, -1.203659e-002, -1.344233e-002, -1.413407e-002, -1.421277e-002, -1.370530e-002, + -1.279864e-002, -1.151323e-002, -9.897399e-003, -8.013783e-003, -5.966343e-003, -3.821294e-003, -1.631281e-003, 5.732588e-004, 2.727075e-003, + 4.814094e-003, 6.843707e-003, 8.841219e-003, 1.078738e-002, 1.265093e-002, 1.432882e-002, 1.570842e-002, 1.658658e-002, 1.663880e-002, + 1.567624e-002, 1.370330e-002, 1.114992e-002, 8.310912e-003, 5.423848e-003, 2.721047e-003, 3.174960e-004, -1.834667e-003, -3.883656e-003, + -5.921138e-003, -8.117367e-003, -1.037790e-002, -1.251587e-002, -1.428429e-002, -1.547109e-002, -1.560435e-002, -1.486408e-002, -1.334719e-002, + -1.136525e-002, -9.080045e-003, -6.636922e-003, -4.091481e-003, -1.561440e-003, 8.877030e-004, 3.183046e-003, 5.170876e-003, 6.748899e-003, + 7.804888e-003, 8.168572e-003, 7.804114e-003, 6.557082e-003, 4.240496e-003, 7.961289e-004, -3.791933e-003, -9.549484e-003, -1.649954e-002, + -2.418507e-002, -3.186816e-002, -3.881352e-002, -4.462729e-002, -4.926522e-002, -5.274478e-002, -5.517225e-002, -5.654780e-002, -5.700857e-002, + -5.670113e-002, -5.567749e-002, -5.413287e-002, -5.206094e-002, -4.961058e-002, -4.695038e-002, -4.419420e-002, -4.142630e-002, -3.873961e-002, + -3.614558e-002, -3.373720e-002, -3.158762e-002, -2.966027e-002, -2.801392e-002, -2.670007e-002, -2.576479e-002, -2.519870e-002, -2.508457e-002, + -2.536392e-002, -2.607981e-002, -2.708229e-002, -2.821320e-002, -2.936455e-002, -3.072838e-002, -3.219702e-002, -3.378657e-002, -3.548186e-002, + -3.716719e-002, -3.870788e-002, -3.990459e-002, -4.050053e-002, -4.046142e-002, -3.983771e-002, -3.886458e-002, -3.766583e-002, -3.625778e-002, + -3.470421e-002, -3.302617e-002, -3.115019e-002, -2.909447e-002, -2.691215e-002, -2.459068e-002, -2.219684e-002, -1.981312e-002, -1.745218e-002, + -1.516696e-002, -1.308596e-002, -1.125337e-002, -9.717934e-003, -8.548588e-003, -7.757027e-003, -7.411686e-003, -7.532101e-003, -8.129871e-003, + -9.052552e-003, -1.033411e-002, -1.194588e-002, -1.379388e-002, -1.587451e-002, -1.804303e-002, -2.007155e-002, -2.179881e-002, -2.321280e-002, + -2.429431e-002, -2.506179e-002, -2.565606e-002, -2.596282e-002, -2.590073e-002, -2.540739e-002, -2.416777e-002, -2.221724e-002, -1.967879e-002, + -1.671831e-002, -1.353065e-002, -1.022668e-002, -6.874738e-003, -3.587444e-003, -4.470855e-004, 2.460915e-003, 5.056786e-003, 6.893127e-003, + 7.725265e-003, 7.398390e-003, 6.276422e-003, 4.757260e-003, 3.231280e-003, 1.742098e-003, 1.893774e-004, -1.626567e-003, -3.978422e-003, + -7.095687e-003, -1.099635e-002, -1.553248e-002, -2.047565e-002, -2.533202e-002, -2.925930e-002, -3.165973e-002, -3.266447e-002, -3.255699e-002, + -3.176642e-002, -3.033697e-002, -2.850322e-002, -2.626690e-002, -2.356390e-002, -2.055603e-002, -1.732216e-002, -1.388217e-002, -1.036695e-002, + -6.782662e-003, -3.172544e-003, 4.666572e-004, 4.067384e-003, 7.688181e-003, 1.137087e-002, 1.509412e-002, 1.891199e-002, 2.281536e-002, + 2.677526e-002, 3.071932e-002, 3.467252e-002, 3.861704e-002, 4.250325e-002, 4.625008e-002, 4.983143e-002, 5.320130e-002, 5.624774e-002, + 5.892016e-002, 6.111458e-002, 6.250517e-002, 6.322374e-002, 6.340869e-002, 6.319145e-002, 6.268511e-002, 6.190029e-002, 6.076463e-002, + 5.923023e-002, 5.730386e-002, 5.499541e-002, 5.238516e-002, 4.966512e-002, 4.705273e-002, 4.475538e-002, 4.283232e-002, 4.126986e-002, + 4.013926e-002, 3.943480e-002, 3.910891e-002, 3.939581e-002, 4.010017e-002, 4.110729e-002, 4.240911e-002, 4.389196e-002, 4.548561e-002, + 4.701554e-002, 4.841366e-002, 4.953239e-002, 5.028419e-002, 5.049994e-002, 5.001986e-002, 4.856059e-002, 4.575029e-002, 4.148986e-002, + 3.633267e-002, 3.089043e-002, 2.559653e-002, 2.076487e-002, 1.650965e-002, 1.274772e-002, 9.456806e-003, 6.609605e-003, 4.161350e-003, + 1.946013e-003, 1.797243e-004, -1.025282e-003, -1.485920e-003, -1.302189e-003, -2.600500e-004, 1.580810e-003, 3.799347e-003, 6.279195e-003, + 8.946090e-003, 1.172525e-002, 1.444011e-002, 1.704434e-002, 1.949055e-002, 2.161131e-002, 2.322780e-002, 2.420492e-002, 2.438489e-002, + 2.352068e-002, 2.194818e-002, 1.975590e-002, 1.704693e-002, 1.398885e-002, 1.083562e-002, 7.687217e-003, 4.645514e-003, 1.722737e-003, + -1.155283e-003, -4.086258e-003, -7.092166e-003, -9.972959e-003, -1.253068e-002, -1.441959e-002, -1.518829e-002, -1.498858e-002, -1.395004e-002, + -1.239518e-002, -1.059267e-002, -8.638810e-003, -6.535975e-003, -4.316868e-003, -2.030414e-003, 3.702029e-004, 2.929673e-003, 5.504399e-003, + 8.189959e-003, 1.092767e-002, 1.362203e-002, 1.615205e-002, 1.851279e-002, 2.053284e-002, 2.211315e-002, 2.333857e-002, 2.421483e-002, + 2.455376e-002, 2.431118e-002, 2.315807e-002, 2.083719e-002, 1.690008e-002, 1.151427e-002, 5.651011e-003, -5.634574e-005, -5.194678e-003, + -9.595223e-003, -1.316636e-002, -1.598720e-002, -1.828934e-002, -2.046845e-002, -2.262012e-002, -2.489354e-002, -2.700870e-002, -2.857751e-002, + -2.913870e-002, -2.860192e-002, -2.698582e-002, -2.476880e-002, -2.222908e-002, -1.941955e-002, -1.650614e-002, -1.354547e-002, -1.064393e-002, + -7.994551e-003, -5.645866e-003, -3.762501e-003, -2.380835e-003, -1.825044e-003, -1.966932e-003, -2.943547e-003, -4.911158e-003, -7.883081e-003, + -1.182422e-002, -1.676633e-002, -2.268884e-002, -2.935443e-002, -3.625373e-002, -4.270168e-002, -4.828776e-002, -5.295343e-002, -5.677413e-002, + -5.968806e-002, -6.173655e-002, -6.290016e-002, -6.327632e-002, -6.290611e-002, -6.184118e-002, -5.997159e-002, -5.763910e-002, -5.497702e-002, + -5.202501e-002, -4.888281e-002, -4.564516e-002, -4.231252e-002, -3.884301e-002, -3.533109e-002, -3.169639e-002, -2.809249e-002, -2.451304e-002, + -2.097820e-002, -1.755683e-002, -1.431549e-002, -1.123544e-002, -8.319231e-003, -5.780658e-003, -3.784024e-003, -2.448145e-003, -1.946476e-003, + -2.276817e-003, -3.235685e-003, -4.722558e-003, -6.737024e-003, -9.073209e-003, -1.172202e-002, -1.490486e-002, -1.858991e-002, -2.274950e-002, + -2.742943e-002, -3.243966e-002, -3.741303e-002, -4.170966e-002, -4.477044e-002, -4.627941e-002, -4.650502e-002, -4.581912e-002, -4.443573e-002, + -4.242074e-002, -3.984120e-002, -3.678194e-002, -3.336607e-002, -2.950967e-002, -2.537150e-002, -2.110304e-002, -1.674583e-002, -1.237731e-002, + -8.039277e-003, -3.751405e-003, 5.001453e-004, 4.647559e-003, 8.665964e-003, 1.260914e-002, 1.646962e-002, 2.003740e-002, 2.332881e-002, + 2.619192e-002, 2.834767e-002, 2.951775e-002, 2.974790e-002, 2.916707e-002, 2.820025e-002, 2.707123e-002, 2.592391e-002, 2.480510e-002, + 2.380951e-002, 2.286337e-002, 2.174543e-002, 2.058071e-002, 1.934375e-002, 1.814667e-002, 1.727507e-002, 1.710032e-002, 1.776219e-002, + 1.942991e-002, 2.189586e-002, 2.490814e-002, 2.818312e-002, 3.174992e-002, 3.541249e-002, 3.898550e-002, 4.251094e-002, 4.594722e-002, + 4.895170e-002, 5.137001e-002, 5.295146e-002, 5.373031e-002, 5.392752e-002, 5.362938e-002, 5.283267e-002, 5.153610e-002, 4.985130e-002, + 4.781779e-002, 4.529525e-002, 4.227034e-002, 3.894294e-002, 3.522443e-002, 3.156056e-002, 2.827878e-002, 2.530943e-002, 2.263495e-002, + 2.028609e-002, 1.837129e-002, 1.691071e-002, 1.602413e-002, 1.560739e-002, 1.551733e-002, 1.571671e-002, 1.621657e-002, 1.675323e-002, + 1.732805e-002, 1.779234e-002, 1.803222e-002, 1.806142e-002, 1.769858e-002, 1.670374e-002, 1.469256e-002, 1.151409e-002, 7.162216e-003, + 1.980148e-003, -3.380628e-003, -8.549981e-003, -1.315693e-002, -1.700542e-002, -2.015679e-002, -2.261154e-002, -2.476243e-002, -2.688792e-002, + -2.887061e-002, -3.051177e-002, -3.181000e-002, -3.225165e-002, -3.164702e-002, -3.029224e-002, -2.822603e-002, -2.570137e-002, -2.280467e-002, + -1.966869e-002, -1.639304e-002, -1.332997e-002, -1.038651e-002, -7.588269e-003, -5.117209e-003, -3.102093e-003, -1.792497e-003, -1.130565e-003, + -1.076477e-003, -1.984826e-003, -3.899544e-003, -6.665022e-003, -1.035650e-002, -1.516097e-002, -2.091116e-002, -2.717813e-002, -3.322081e-002, + -3.840594e-002, -4.274913e-002, -4.615626e-002, -4.864241e-002, -5.026737e-002, -5.102287e-002, -5.097278e-002, -5.010137e-002, -4.849653e-002, + -4.606747e-002, -4.307629e-002, -3.972214e-002, -3.590592e-002, -3.176558e-002, -2.734085e-002, -2.291755e-002, -1.840974e-002, -1.368165e-002, + -8.588742e-003, -3.532312e-003, 1.412709e-003, 6.297495e-003, 1.125664e-002, 1.613608e-002, 2.065485e-002, 2.491149e-002, 2.889154e-002, + 3.247673e-002, 3.550839e-002, 3.736617e-002, 3.801210e-002, 3.776070e-002, 3.681005e-002, 3.534989e-002, 3.380802e-002, 3.225928e-002, + 3.073152e-002, 2.930474e-002, 2.799888e-002, 2.649529e-002, 2.462645e-002, 2.236404e-002, 1.999340e-002, 1.772206e-002, 1.586982e-002, + 1.487248e-002, 1.490514e-002, 1.612830e-002, 1.799089e-002, 2.017966e-002, 2.272674e-002, 2.517020e-002, 2.766345e-002, 3.014670e-002, + 3.243905e-002, 3.435746e-002, 3.584838e-002, 3.673737e-002, 3.731557e-002, 3.727186e-002, 3.611260e-002, 3.387466e-002, 3.040956e-002, + 2.592020e-002, 2.051163e-002, 1.391462e-002, 6.612652e-003, -7.495148e-004, -7.361528e-003, -1.325752e-002, -1.823926e-002, -2.222734e-002, + -2.518264e-002, -2.712174e-002, -2.825703e-002, -2.910472e-002, -2.900281e-002, -2.803136e-002, -2.597702e-002, -2.321457e-002, -2.029995e-002, + -1.732715e-002, -1.404130e-002, -1.066803e-002, -7.531682e-003, -4.340497e-003, -9.945766e-004, 2.088904e-003, 5.046225e-003, 8.349078e-003, + 1.166875e-002, 1.452480e-002, 1.689194e-002, 1.901252e-002, 2.054228e-002, 2.170250e-002, 2.190509e-002, 2.162890e-002, 2.068200e-002, + 1.960475e-002, 1.843888e-002, 1.686678e-002, 1.486641e-002, 1.287485e-002, 9.834250e-003, 6.192565e-003, 2.550508e-003, -1.244934e-003, + -4.717020e-003, -7.673204e-003, -1.036406e-002, -1.292957e-002, -1.546390e-002, -1.768102e-002, -2.010618e-002, -2.243746e-002, -2.511888e-002, + -2.816856e-002, -3.120366e-002, -3.328967e-002, -3.532705e-002, -3.735430e-002, -3.915202e-002, -4.083428e-002, -4.276110e-002, -4.473334e-002, + -4.658149e-002, -4.839883e-002, -4.956778e-002, -5.063867e-002, -5.036792e-002, -4.995281e-002, -4.906193e-002, -4.719672e-002, -4.472203e-002, + -4.177322e-002, -3.802671e-002, -3.343007e-002, -2.856957e-002, -2.286628e-002, -1.655722e-002, -1.005405e-002, -3.272533e-003, 3.329761e-003, + 9.583982e-003, 1.514163e-002, 2.018721e-002, 2.450668e-002, 2.810988e-002, 3.112643e-002, 3.396646e-002, 3.699023e-002, 4.013234e-002, + 4.291642e-002, 4.437672e-002, 4.466439e-002, 4.302839e-002, 3.997029e-002, 3.713232e-002, 3.310879e-002, 2.934735e-002, 2.600571e-002, + 2.213868e-002, 1.856913e-002, 1.369196e-002, 8.729759e-003, 3.543025e-003, -1.829430e-003, -6.654446e-003, -1.148450e-002, -1.586154e-002, + -1.979985e-002, -2.246088e-002, -2.467860e-002, -2.704177e-002, -2.901125e-002, -3.091356e-002, -3.223086e-002, -3.173460e-002, -3.076383e-002, + -2.789636e-002, -2.316488e-002, -1.722342e-002, -1.029480e-002, -2.777485e-003, 5.761202e-003, 1.468155e-002, 2.467446e-002, 3.466033e-002, + 4.324878e-002, 5.026680e-002, 5.536700e-002, 5.856712e-002, 6.149206e-002, 6.026970e-002, 5.351028e-002, 4.122796e-002, 3.042455e-002, + 2.315248e-002, 2.080529e-002, 3.340854e-002, 2.981382e-002, 1.347153e-002, -3.023675e-003, -1.909938e-002, -3.648361e-002, -5.485777e-002, + -7.278697e-002, -8.722149e-002, -9.239338e-002, -9.050156e-002, 9.741840e-002, 1.041446e-001, 9.384228e-002, 7.430607e-002, 4.637446e-002, + 9.591476e-003, -3.195461e-002, -7.473722e-002, -1.167334e-001, -1.512537e-001, -5.959754e-002, 2.222565e-001, 1.317222e-001, -1.381181e-002, + -9.940933e-002, -1.247848e-001, -9.378049e-002, -4.058175e-002, 2.354583e-002, 3.346896e-002, 0.000000e+000}, + {0.000000e+000, 9.818952e-003, 1.499853e-002, 1.960759e-002, 2.318434e-002, 2.574080e-002, 2.775447e-002, 2.919040e-002, 2.986353e-002, + 3.062855e-002, 3.093559e-002, 3.075389e-002, 3.022282e-002, 2.946587e-002, 2.867888e-002, 2.776140e-002, 2.655273e-002, 2.523487e-002, + 2.388931e-002, 2.245326e-002, 2.096800e-002, 1.952847e-002, 1.819901e-002, 1.691291e-002, 1.559457e-002, 1.421219e-002, 1.272927e-002, + 1.151155e-002, 1.045734e-002, 9.549822e-003, 8.758938e-003, 7.988960e-003, 7.219299e-003, 6.379215e-003, 5.495536e-003, 4.575293e-003, + 3.639805e-003, 2.729030e-003, 1.747399e-003, 7.205253e-004, -3.480036e-004, -1.440240e-003, -2.577243e-003, -3.698620e-003, -4.776840e-003, + -5.806156e-003, -6.649715e-003, -7.099926e-003, -7.575808e-003, -8.478272e-003, -9.537000e-003, -1.061276e-002, -1.179234e-002, -1.295519e-002, + -1.412190e-002, -1.533599e-002, -1.651362e-002, -1.775972e-002, -1.900462e-002, -2.021569e-002, -2.137935e-002, -2.250282e-002, -2.364676e-002, + -2.478457e-002, -2.589103e-002, -2.697979e-002, -2.796784e-002, -2.890759e-002, -2.967078e-002, -3.027311e-002, -3.071611e-002, -3.096854e-002, + -3.105190e-002, -3.102692e-002, -3.076439e-002, -3.025368e-002, -2.948175e-002, -2.851022e-002, -2.731511e-002, -2.607015e-002, -2.491273e-002, + -2.384010e-002, -2.296114e-002, -2.231210e-002, -2.184028e-002, -2.143241e-002, -2.114417e-002, -2.092675e-002, -2.080701e-002, -2.074090e-002, + -2.067651e-002, -2.062297e-002, -2.055393e-002, -2.061712e-002, -2.063863e-002, -2.064731e-002, -2.054500e-002, -2.047579e-002, -2.030318e-002, + -2.012116e-002, -1.983829e-002, -1.945955e-002, -1.904054e-002, -1.860399e-002, -1.818951e-002, -1.776911e-002, -1.719226e-002, -1.649034e-002, + -1.566333e-002, -1.466559e-002, -1.351006e-002, -1.213505e-002, -1.058953e-002, -9.011192e-003, -7.436323e-003, -5.727292e-003, -3.928316e-003, + -1.992445e-003, 8.974192e-005, 2.286372e-003, 4.583033e-003, 6.908961e-003, 9.143606e-003, 1.114208e-002, 1.271212e-002, 1.402692e-002, + 1.513441e-002, 1.607007e-002, 1.691147e-002, 1.766337e-002, 1.832133e-002, 1.895954e-002, 1.957698e-002, 2.018666e-002, 2.065226e-002, + 2.114686e-002, 2.166820e-002, 2.225948e-002, 2.296650e-002, 2.372614e-002, 2.455569e-002, 2.546958e-002, 2.644958e-002, 2.752431e-002, + 2.851189e-002, 2.951335e-002, 3.050184e-002, 3.146221e-002, 3.232278e-002, 3.303491e-002, 3.363656e-002, 3.414801e-002, 3.459613e-002, + 3.491764e-002, 3.508044e-002, 3.507885e-002, 3.497195e-002, 3.466044e-002, 3.418780e-002, 3.362565e-002, 3.299376e-002, 3.227039e-002, + 3.150613e-002, 3.073637e-002, 2.994313e-002, 2.903156e-002, 2.819884e-002, 2.747530e-002, 2.690047e-002, 2.647676e-002, 2.622011e-002, + 2.608115e-002, 2.596183e-002, 2.584847e-002, 2.574799e-002, 2.560297e-002, 2.554504e-002, 2.561403e-002, 2.582091e-002, 2.615039e-002, + 2.650537e-002, 2.677004e-002, 2.685763e-002, 2.675525e-002, 2.640230e-002, 2.576988e-002, 2.497120e-002, 2.409940e-002, 2.309558e-002, + 2.203420e-002, 2.088754e-002, 1.969956e-002, 1.850255e-002, 1.725753e-002, 1.600512e-002, 1.465650e-002, 1.331349e-002, 1.201564e-002, + 1.073941e-002, 9.496871e-003, 8.295270e-003, 7.138164e-003, 6.004973e-003, 4.916170e-003, 3.863354e-003, 2.872630e-003, 1.955567e-003, + 1.257139e-003, 7.911283e-004, 4.794258e-004, 2.494848e-004, 3.281915e-005, -2.197102e-004, -5.021397e-004, -8.235734e-004, -1.163783e-003, + -1.541540e-003, -1.865229e-003, -2.116324e-003, -2.303805e-003, -2.474289e-003, -2.738509e-003, -3.161664e-003, -3.818222e-003, -4.682141e-003, + -5.723067e-003, -6.864108e-003, -8.059218e-003, -9.309062e-003, -1.057468e-002, -1.184490e-002, -1.311913e-002, -1.438953e-002, -1.569123e-002, + -1.698183e-002, -1.828132e-002, -1.960103e-002, -2.092429e-002, -2.221503e-002, -2.344670e-002, -2.462166e-002, -2.575667e-002, -2.680329e-002, + -2.776792e-002, -2.858255e-002, -2.926052e-002, -2.980080e-002, -3.007042e-002, -3.005558e-002, -2.980785e-002, -2.944098e-002, -2.907803e-002, + -2.875239e-002, -2.845489e-002, -2.821704e-002, -2.804577e-002, -2.795959e-002, -2.796684e-002, -2.801437e-002, -2.808171e-002, -2.821775e-002, + -2.841022e-002, -2.873036e-002, -2.914958e-002, -2.963509e-002, -3.016792e-002, -3.069416e-002, -3.122114e-002, -3.171156e-002, -3.212624e-002, + -3.242061e-002, -3.254931e-002, -3.255869e-002, -3.240474e-002, -3.207796e-002, -3.150742e-002, -3.069440e-002, -2.973404e-002, -2.861308e-002, + -2.734976e-002, -2.592394e-002, -2.438604e-002, -2.268187e-002, -2.083735e-002, -1.887880e-002, -1.676358e-002, -1.452828e-002, -1.222683e-002, + -9.910996e-003, -7.697149e-003, -5.716295e-003, -3.975002e-003, -2.488011e-003, -1.229395e-003, -1.670860e-004, 7.130248e-004, 1.411115e-003, + 1.959777e-003, 2.363878e-003, 2.656359e-003, 2.817558e-003, 2.878800e-003, 2.851522e-003, 2.722292e-003, 2.499478e-003, 2.187900e-003, + 1.849979e-003, 1.464369e-003, 9.661331e-004, 3.871142e-004, -2.031869e-004, -7.812035e-004, -1.339399e-003, -1.857097e-003, -2.251226e-003, + -2.438960e-003, -2.404215e-003, -2.293223e-003, -2.202542e-003, -2.091514e-003, -2.016983e-003, -1.991163e-003, -2.003679e-003, -1.992696e-003, + -1.951984e-003, -1.847343e-003, -1.715001e-003, -1.588561e-003, -1.534143e-003, -1.579311e-003, -1.801305e-003, -2.156941e-003, -2.605777e-003, + -3.099607e-003, -3.594543e-003, -4.052082e-003, -4.487315e-003, -4.847348e-003, -5.198994e-003, -5.509514e-003, -5.721553e-003, -5.828530e-003, + -5.787028e-003, -5.571880e-003, -5.160663e-003, -4.556881e-003, -3.733389e-003, -2.664242e-003, -1.336846e-003, 1.539213e-004, 1.904126e-003, + 3.781230e-003, 5.644687e-003, 7.420633e-003, 9.022404e-003, 1.042075e-002, 1.160056e-002, 1.257340e-002, 1.334066e-002, 1.388105e-002, + 1.426462e-002, 1.450891e-002, 1.467015e-002, 1.472679e-002, 1.467987e-002, 1.458256e-002, 1.448421e-002, 1.438271e-002, 1.429728e-002, + 1.416026e-002, 1.400090e-002, 1.392387e-002, 1.391444e-002, 1.399772e-002, 1.413361e-002, 1.435515e-002, 1.463092e-002, 1.500522e-002, + 1.544482e-002, 1.594138e-002, 1.643885e-002, 1.695983e-002, 1.748986e-002, 1.805780e-002, 1.865893e-002, 1.929642e-002, 1.995485e-002, + 2.062352e-002, 2.126716e-002, 2.179798e-002, 2.215527e-002, 2.237878e-002, 2.249903e-002, 2.257034e-002, 2.258675e-002, 2.256356e-002, + 2.247760e-002, 2.234394e-002, 2.215448e-002, 2.191510e-002, 2.158588e-002, 2.121658e-002, 2.083518e-002, 2.045490e-002, 2.006889e-002, + 1.964884e-002, 1.932550e-002, 1.904644e-002, 1.880722e-002, 1.859034e-002, 1.831803e-002, 1.810240e-002, 1.796490e-002, 1.792158e-002, + 1.790376e-002, 1.797503e-002, 1.805034e-002, 1.814337e-002, 1.825604e-002, 1.839174e-002, 1.842334e-002, 1.837123e-002, 1.822765e-002, + 1.799744e-002, 1.769709e-002, 1.734309e-002, 1.692787e-002, 1.644510e-002, 1.590430e-002, 1.518190e-002, 1.429688e-002, 1.326115e-002, + 1.215314e-002, 1.103236e-002, 9.895627e-003, 8.752769e-003, 7.653537e-003, 6.603188e-003, 5.610760e-003, 4.701681e-003, 3.924955e-003, + 3.301927e-003, 2.894317e-003, 2.694415e-003, 2.645150e-003, 2.629501e-003, 2.644132e-003, 2.747410e-003, 2.931045e-003, 3.220153e-003, + 3.733451e-003, 4.404081e-003, 5.215752e-003, 6.166793e-003, 7.133896e-003, 7.929002e-003, 8.467028e-003, 8.833208e-003, 9.057195e-003, + 9.199244e-003, 9.238016e-003, 9.189351e-003, 9.055917e-003, 8.858668e-003, 8.610864e-003, 8.366360e-003, 8.075824e-003, 7.760105e-003, + 7.416999e-003, 7.060883e-003, 6.682435e-003, 6.282338e-003, 5.837888e-003, 5.301670e-003, 4.678782e-003, 3.974181e-003, 3.195038e-003, + 2.353805e-003, 1.476325e-003, 5.434002e-004, -5.081121e-004, -1.645974e-003, -2.853877e-003, -4.038449e-003, -5.204055e-003, -6.369474e-003, + -7.547923e-003, -8.737855e-003, -9.825684e-003, -1.086921e-002, -1.177890e-002, -1.264025e-002, -1.347650e-002, -1.432887e-002, -1.519078e-002, + -1.600234e-002, -1.676552e-002, -1.740941e-002, -1.800516e-002, -1.862422e-002, -1.931601e-002, -2.005760e-002, -2.087387e-002, -2.176012e-002, + -2.269169e-002, -2.370805e-002, -2.477159e-002, -2.594063e-002, -2.719655e-002, -2.856106e-002, -2.997227e-002, -3.140935e-002, -3.280504e-002, + -3.412025e-002, -3.538679e-002, -3.661685e-002, -3.771098e-002, -3.862776e-002, -3.936233e-002, -3.985066e-002, -3.989864e-002, -3.960605e-002, + -3.915261e-002, -3.859154e-002, -3.791605e-002, -3.730514e-002, -3.670521e-002, -3.607472e-002, -3.546330e-002, -3.493799e-002, -3.442089e-002, + -3.384176e-002, -3.319052e-002, -3.251444e-002, -3.189977e-002, -3.131892e-002, -3.090251e-002, -3.056792e-002, -3.024164e-002, -2.990268e-002, + -2.954720e-002, -2.922679e-002, -2.895843e-002, -2.871650e-002, -2.839022e-002, -2.799755e-002, -2.747805e-002, -2.681051e-002, -2.601491e-002, + -2.509757e-002, -2.411890e-002, -2.297619e-002, -2.171981e-002, -2.036507e-002, -1.901194e-002, -1.770296e-002, -1.657189e-002, -1.541283e-002, + -1.424472e-002, -1.303963e-002, -1.182615e-002, -1.062778e-002, -9.587364e-003, -8.713138e-003, -7.996271e-003, -7.467818e-003, -7.093225e-003, + -6.815596e-003, -6.590219e-003, -6.517382e-003, -6.405199e-003, -6.290927e-003, -6.175201e-003, -6.064980e-003, -5.931363e-003, -5.914312e-003, + -6.011194e-003, -6.080280e-003, -6.118879e-003, -6.122844e-003, -6.082825e-003, -6.014157e-003, -5.996981e-003, -5.814225e-003, -5.510816e-003, + -5.066012e-003, -4.527321e-003, -3.778197e-003, -2.832654e-003, -1.625958e-003, -6.447437e-005, 1.594093e-003, 3.235853e-003, 4.762420e-003, + 6.190860e-003, 7.458158e-003, 8.647440e-003, 9.717336e-003, 1.077773e-002, 1.181389e-002, 1.293992e-002, 1.399268e-002, 1.500162e-002, + 1.578588e-002, 1.637458e-002, 1.670778e-002, 1.692333e-002, 1.700429e-002, 1.708975e-002, 1.718453e-002, 1.724557e-002, 1.723412e-002, + 1.730729e-002, 1.737457e-002, 1.757240e-002, 1.790742e-002, 1.841615e-002, 1.900802e-002, 1.972604e-002, 2.057461e-002, 2.170112e-002, + 2.306120e-002, 2.455276e-002, 2.624552e-002, 2.803873e-002, 2.971778e-002, 3.126964e-002, 3.269919e-002, 3.395767e-002, 3.504138e-002, + 3.588449e-002, 3.650894e-002, 3.698695e-002, 3.728401e-002, 3.742317e-002, 3.742415e-002, 3.722783e-002, 3.683653e-002, 3.640420e-002, + 3.598058e-002, 3.548319e-002, 3.499686e-002, 3.451763e-002, 3.391820e-002, 3.333686e-002, 3.270185e-002, 3.209919e-002, 3.148502e-002, + 3.089143e-002, 3.031302e-002, 2.978750e-002, 2.930912e-002, 2.879062e-002, 2.826145e-002, 2.778948e-002, 2.729830e-002, 2.696557e-002, + 2.677843e-002, 2.677912e-002, 2.689909e-002, 2.701968e-002, 2.710962e-002, 2.726493e-002, 2.751488e-002, 2.786936e-002, 2.821392e-002, + 2.849835e-002, 2.883997e-002, 2.915898e-002, 2.932674e-002, 2.929333e-002, 2.889473e-002, 2.814475e-002, 2.728518e-002, 2.629229e-002, + 2.517251e-002, 2.379686e-002, 2.235207e-002, 2.080437e-002, 1.923009e-002, 1.757583e-002, 1.580078e-002, 1.406571e-002, 1.242824e-002, + 1.071497e-002, 8.972393e-003, 7.180904e-003, 5.235611e-003, 3.346815e-003, 1.513523e-003, -2.579204e-004, -1.965171e-003, -3.541809e-003, + -4.926594e-003, -6.142107e-003, -7.302929e-003, -8.271808e-003, -9.236613e-003, -1.002795e-002, -1.065096e-002, -1.132375e-002, -1.208104e-002, + -1.267029e-002, -1.328805e-002, -1.381081e-002, -1.417126e-002, -1.462658e-002, -1.530833e-002, -1.600202e-002, -1.677686e-002, -1.758068e-002, + -1.844714e-002, -1.946390e-002, -2.049727e-002, -2.158445e-002, -2.257904e-002, -2.349654e-002, -2.473067e-002, -2.589758e-002, -2.705105e-002, + -2.795039e-002, -2.856232e-002, -2.890781e-002, -2.899630e-002, -2.881555e-002, -2.838621e-002, -2.779307e-002, -2.716146e-002, -2.642629e-002, + -2.563436e-002, -2.459047e-002, -2.344287e-002, -2.224039e-002, -2.117459e-002, -2.010681e-002, -1.883035e-002, -1.749474e-002, -1.642101e-002, + -1.572584e-002, -1.510100e-002, -1.448735e-002, -1.384904e-002, -1.343251e-002, -1.319576e-002, -1.299673e-002, -1.275999e-002, -1.240224e-002, + -1.216959e-002, -1.244756e-002, -1.278806e-002, -1.295762e-002, -1.309362e-002, -1.302481e-002, -1.303374e-002, -1.304769e-002, -1.273059e-002, + -1.234906e-002, -1.165460e-002, -1.150704e-002, -1.157438e-002, -1.183180e-002, -1.211213e-002, -1.245410e-002, -1.289924e-002, -1.338677e-002, + -1.367434e-002, -1.400805e-002, -1.448440e-002, -1.523205e-002, -1.625185e-002, -1.724650e-002, -1.821619e-002, -1.934396e-002, -2.034902e-002, + -2.146216e-002, -2.255429e-002, -2.345794e-002, -2.407578e-002, -2.473330e-002, -2.518774e-002, -2.545110e-002, -2.567605e-002, -2.549367e-002, + -2.532339e-002, -2.484521e-002, -2.400662e-002, -2.284301e-002, -2.140593e-002, -1.980293e-002, -1.802680e-002, -1.626461e-002, -1.433539e-002, + -1.243969e-002, -1.063240e-002, -8.995594e-003, -7.495592e-003, -6.149849e-003, -4.766671e-003, -3.434923e-003, -2.462650e-003, -1.622619e-003, + -1.011871e-003, -4.786046e-004, -3.020620e-004, -8.110432e-005, -4.487170e-005, 1.467182e-004, 3.158955e-004, 5.757456e-004, 8.635201e-004, + 9.685339e-004, 1.077888e-003, 1.148168e-003, 1.222796e-003, 1.391458e-003, 1.372016e-003, 1.363429e-003, 1.558088e-003, 2.060552e-003, + 2.780482e-003, 3.324287e-003, 3.885454e-003, 4.743050e-003, 6.009577e-003, 7.458520e-003, 8.827932e-003, 1.033729e-002, 1.188023e-002, + 1.362263e-002, 1.538781e-002, 1.728339e-002, 1.895719e-002, 2.025471e-002, 2.155070e-002, 2.310467e-002, 2.442670e-002, 2.522437e-002, + 2.581312e-002, 2.626836e-002, 2.656676e-002, 2.664083e-002, 2.676569e-002, 2.698517e-002, 2.667176e-002, 2.619389e-002, 2.575163e-002, + 2.534815e-002, 2.466483e-002, 2.396365e-002, 2.356251e-002, 2.311114e-002, 2.284606e-002, 2.280984e-002, 2.289681e-002, 2.285924e-002, + 2.308825e-002, 2.379225e-002, 2.450944e-002, 2.539423e-002, 2.658280e-002, 2.752424e-002, 2.813001e-002, 2.851226e-002, 2.856533e-002, + 2.838481e-002, 2.822024e-002, 2.801485e-002, 2.717028e-002, 2.623165e-002, 2.539912e-002, 2.488814e-002, 2.394945e-002, 2.332171e-002, + 2.260363e-002, 2.219154e-002, 2.185633e-002, 2.081541e-002, 1.943175e-002, 1.827618e-002, 1.682313e-002, 1.487857e-002, 1.339682e-002, + 1.203659e-002, 1.077357e-002, 9.861793e-003, 9.231827e-003, 8.530502e-003, 8.093550e-003, 7.343214e-003, 6.680474e-003, 6.202204e-003, + 4.883752e-003, 3.693219e-003, 2.134174e-003, 6.472897e-004, -4.158220e-004, -1.520951e-003, -2.337441e-003, -2.751046e-003, -3.519085e-003, + -4.697765e-003, -6.970567e-003, -8.872353e-003, -1.199513e-002, -1.504418e-002, -1.777444e-002, -2.069096e-002, -2.323264e-002, -2.502690e-002, + -2.564339e-002, -2.654564e-002, -2.730912e-002, -2.933909e-002, -3.045904e-002, -3.150551e-002, -3.235453e-002, -3.208958e-002, -3.136155e-002, + -2.987756e-002, -2.731704e-002, -2.489743e-002, -2.326479e-002, -2.181036e-002, -2.214287e-002, -2.231096e-002, -2.173983e-002, -2.072493e-002, + -2.063736e-002, -1.999907e-002, -1.884769e-002, -1.759909e-002, -1.611805e-002, -1.534827e-002, -1.392029e-002, -1.291553e-002, -1.164990e-002, + -1.059609e-002, -1.020992e-002, -1.097686e-002, -1.242700e-002, -1.429676e-002, -1.659844e-002, -1.975934e-002, -2.150793e-002, -2.320610e-002, + -2.416167e-002, -2.508076e-002, -2.643128e-002, -2.996249e-002, -3.435714e-002, -3.700306e-002, -3.891282e-002, -4.046107e-002, -4.049479e-002, + -4.021163e-002, -3.794242e-002, -3.686781e-002, -3.464605e-002, -3.216059e-002, -3.042789e-002, -2.725401e-002, -2.508145e-002, -2.170724e-002, + -1.935225e-002, -1.553053e-002, -1.210502e-002, -8.666101e-003, -6.973144e-003, -5.024003e-003, -3.480946e-003, -5.228054e-004, 2.267057e-003, + 9.421416e-003, 1.842274e-002, 3.038311e-002, 4.458987e-002, 6.054491e-002, 7.895482e-002, 9.876903e-002, 1.189849e-001, 1.384521e-001, + 1.575101e-001, 1.720200e-001, 1.779706e-001, 1.762056e-001, 1.708525e-001, 1.556881e-001, 1.253158e-001, 8.449835e-002, 5.209848e-002, + 2.777749e-002, 9.482413e-003, 5.796663e-003, -2.169195e-002, -7.291030e-002, -1.185558e-001, -1.550983e-001, -1.821728e-001, -2.005274e-001, + -2.072493e-001, -2.072711e-001, -1.935315e-001, -1.699918e-001, 4.359585e-002, 6.546174e-002, 6.839055e-002, 6.320990e-002, 4.901168e-002, + 2.848470e-002, 1.760527e-002, 1.620791e-002, 1.774181e-002, 2.077249e-002, -5.406846e-002, 1.896780e-001, 1.055573e-001, -7.790436e-004, + -5.801000e-002, -6.767297e-002, -4.673779e-002, -2.526352e-002, 1.251539e-002, 2.315873e-002, 0.000000e+000}, + {0.000000e+000, 3.321641e-003, 2.592714e-003, 4.280579e-005, -2.882593e-003, -5.925705e-003, -7.913692e-003, -9.429694e-003, -1.091851e-002, + -1.183267e-002, -1.213341e-002, -1.224831e-002, -1.237848e-002, -1.220007e-002, -1.199929e-002, -1.180922e-002, -1.137188e-002, -1.112867e-002, + -1.089602e-002, -1.042829e-002, -9.658761e-003, -8.949333e-003, -8.242575e-003, -7.401133e-003, -6.496119e-003, -5.475952e-003, -4.452048e-003, + -3.293769e-003, -1.999249e-003, -3.974334e-004, 1.446205e-003, 3.243150e-003, 4.707863e-003, 6.007970e-003, 7.194176e-003, 8.228231e-003, + 9.146394e-003, 9.962331e-003, 1.062768e-002, 1.111311e-002, 1.143714e-002, 1.156361e-002, 1.129829e-002, 1.085753e-002, 1.029031e-002, + 9.681833e-003, 8.853340e-003, 7.551216e-003, 6.275372e-003, 5.500891e-003, 4.911019e-003, 4.319252e-003, 3.708039e-003, 3.093804e-003, + 2.514981e-003, 1.988017e-003, 1.433421e-003, 8.832911e-004, 3.624873e-004, -2.069689e-004, -8.146495e-004, -1.390755e-003, -2.007189e-003, + -2.663301e-003, -3.305663e-003, -3.917010e-003, -4.419469e-003, -4.735825e-003, -4.750429e-003, -4.605938e-003, -4.107765e-003, -3.345906e-003, + -2.214510e-003, -7.036625e-004, 1.250681e-003, 3.629979e-003, 6.429778e-003, 9.668604e-003, 1.308283e-002, 1.644156e-002, 1.945215e-002, + 2.200512e-002, 2.414019e-002, 2.591602e-002, 2.731274e-002, 2.824080e-002, 2.880303e-002, 2.899341e-002, 2.876166e-002, 2.821096e-002, + 2.732553e-002, 2.624545e-002, 2.489571e-002, 2.348202e-002, 2.193128e-002, 2.022440e-002, 1.845329e-002, 1.657484e-002, 1.458244e-002, + 1.254092e-002, 1.038282e-002, 8.258515e-003, 6.113134e-003, 4.042283e-003, 2.118848e-003, 2.733252e-004, -1.506062e-003, -3.124258e-003, + -4.585993e-003, -5.747893e-003, -6.586137e-003, -7.116251e-003, -7.394826e-003, -7.391032e-003, -7.171211e-003, -6.798335e-003, -6.203427e-003, + -5.413548e-003, -4.399422e-003, -3.168143e-003, -1.837456e-003, -4.632310e-004, 7.893468e-004, 1.679231e-003, 2.095900e-003, 1.942356e-003, + 1.277794e-003, 2.310348e-004, -1.088318e-003, -2.608093e-003, -4.237469e-003, -5.927652e-003, -7.668679e-003, -9.388632e-003, -1.095194e-002, + -1.238680e-002, -1.367821e-002, -1.487474e-002, -1.584987e-002, -1.661869e-002, -1.719889e-002, -1.762585e-002, -1.784472e-002, -1.789710e-002, + -1.770237e-002, -1.732614e-002, -1.690975e-002, -1.658697e-002, -1.635392e-002, -1.624064e-002, -1.628977e-002, -1.646193e-002, -1.669166e-002, + -1.705178e-002, -1.750696e-002, -1.800730e-002, -1.867744e-002, -1.962459e-002, -2.080652e-002, -2.207092e-002, -2.339631e-002, -2.479402e-002, + -2.619586e-002, -2.754845e-002, -2.879991e-002, -2.991007e-002, -3.083729e-002, -3.145902e-002, -3.167492e-002, -3.154713e-002, -3.111039e-002, + -3.046822e-002, -2.973984e-002, -2.896846e-002, -2.806556e-002, -2.687628e-002, -2.542783e-002, -2.376462e-002, -2.185718e-002, -1.976353e-002, + -1.765176e-002, -1.569133e-002, -1.412402e-002, -1.304862e-002, -1.244769e-002, -1.208620e-002, -1.196199e-002, -1.199680e-002, -1.215593e-002, + -1.248310e-002, -1.290529e-002, -1.343862e-002, -1.402539e-002, -1.461904e-002, -1.517468e-002, -1.560700e-002, -1.595057e-002, -1.625716e-002, + -1.647335e-002, -1.656054e-002, -1.655177e-002, -1.644792e-002, -1.625446e-002, -1.594047e-002, -1.546482e-002, -1.479407e-002, -1.372266e-002, + -1.225945e-002, -1.028976e-002, -7.997435e-003, -5.528429e-003, -3.089199e-003, -7.783393e-004, 1.395333e-003, 3.477471e-003, 5.584500e-003, + 7.777331e-003, 1.009818e-002, 1.252344e-002, 1.498759e-002, 1.738373e-002, 1.952703e-002, 2.126577e-002, 2.253444e-002, 2.334288e-002, + 2.381004e-002, 2.410260e-002, 2.423595e-002, 2.425126e-002, 2.421877e-002, 2.414368e-002, 2.398768e-002, 2.375503e-002, 2.348476e-002, + 2.318161e-002, 2.285410e-002, 2.251209e-002, 2.217114e-002, 2.184192e-002, 2.158209e-002, 2.149380e-002, 2.153663e-002, 2.169011e-002, + 2.205254e-002, 2.257134e-002, 2.334863e-002, 2.447820e-002, 2.607915e-002, 2.819691e-002, 3.073058e-002, 3.339784e-002, 3.602718e-002, + 3.842250e-002, 4.053192e-002, 4.236719e-002, 4.393802e-002, 4.529136e-002, 4.645622e-002, 4.740013e-002, 4.811708e-002, 4.850362e-002, + 4.858522e-002, 4.815847e-002, 4.727514e-002, 4.599768e-002, 4.443069e-002, 4.263553e-002, 4.072684e-002, 3.868882e-002, 3.651920e-002, + 3.426334e-002, 3.193309e-002, 2.962606e-002, 2.738232e-002, 2.528651e-002, 2.340912e-002, 2.182334e-002, 2.052161e-002, 1.942030e-002, + 1.846308e-002, 1.756713e-002, 1.677856e-002, 1.607202e-002, 1.552932e-002, 1.515795e-002, 1.494451e-002, 1.491904e-002, 1.505772e-002, + 1.521077e-002, 1.514021e-002, 1.461861e-002, 1.359342e-002, 1.219405e-002, 1.049815e-002, 8.612150e-003, 6.570142e-003, 4.433888e-003, + 2.235836e-003, 3.929407e-006, -2.306059e-003, -4.623356e-003, -6.908913e-003, -9.117191e-003, -1.122007e-002, -1.314495e-002, -1.494481e-002, + -1.663283e-002, -1.821543e-002, -1.965112e-002, -2.095159e-002, -2.209822e-002, -2.310385e-002, -2.392870e-002, -2.444801e-002, -2.456159e-002, + -2.422051e-002, -2.347184e-002, -2.240059e-002, -2.113360e-002, -1.978791e-002, -1.853191e-002, -1.738460e-002, -1.629606e-002, -1.525086e-002, + -1.423495e-002, -1.319321e-002, -1.217349e-002, -1.122856e-002, -1.037935e-002, -9.782279e-003, -9.631885e-003, -9.885508e-003, -1.051052e-002, + -1.134994e-002, -1.233006e-002, -1.343798e-002, -1.461849e-002, -1.586901e-002, -1.705980e-002, -1.821320e-002, -1.930498e-002, -2.026401e-002, + -2.106954e-002, -2.161733e-002, -2.192752e-002, -2.190976e-002, -2.147518e-002, -2.059904e-002, -1.929402e-002, -1.742499e-002, -1.518140e-002, + -1.261313e-002, -1.001896e-002, -7.708937e-003, -5.836388e-003, -4.432362e-003, -3.487981e-003, -2.962518e-003, -2.825114e-003, -2.964421e-003, + -3.386123e-003, -4.090620e-003, -5.002058e-003, -6.109543e-003, -7.327273e-003, -8.610146e-003, -9.900601e-003, -1.119323e-002, -1.244158e-002, + -1.356597e-002, -1.465918e-002, -1.568940e-002, -1.658761e-002, -1.742321e-002, -1.816052e-002, -1.876619e-002, -1.923035e-002, -1.950885e-002, + -1.959682e-002, -1.939644e-002, -1.900029e-002, -1.853808e-002, -1.805686e-002, -1.753369e-002, -1.694627e-002, -1.627330e-002, -1.558482e-002, + -1.488460e-002, -1.424723e-002, -1.372812e-002, -1.339523e-002, -1.326354e-002, -1.336702e-002, -1.358236e-002, -1.384628e-002, -1.416378e-002, + -1.451196e-002, -1.488297e-002, -1.527593e-002, -1.573709e-002, -1.618844e-002, -1.670405e-002, -1.722606e-002, -1.777150e-002, -1.822953e-002, + -1.863186e-002, -1.900493e-002, -1.924612e-002, -1.931916e-002, -1.926304e-002, -1.891365e-002, -1.827377e-002, -1.745609e-002, -1.643715e-002, + -1.524327e-002, -1.394049e-002, -1.250083e-002, -1.087176e-002, -9.173000e-003, -7.468558e-003, -5.720147e-003, -4.067719e-003, -2.609732e-003, + -1.214611e-003, 8.934172e-005, 1.271293e-003, 2.332239e-003, 3.245626e-003, 3.928270e-003, 4.312802e-003, 4.528306e-003, 4.519030e-003, + 4.338210e-003, 4.053178e-003, 3.714664e-003, 3.340160e-003, 2.994906e-003, 2.736800e-003, 2.508877e-003, 2.409950e-003, 2.603621e-003, + 3.154887e-003, 4.128622e-003, 5.408086e-003, 6.803820e-003, 8.222514e-003, 9.569210e-003, 1.084218e-002, 1.212399e-002, 1.359248e-002, + 1.533512e-002, 1.734573e-002, 1.962636e-002, 2.202229e-002, 2.425235e-002, 2.599880e-002, 2.710987e-002, 2.758736e-002, 2.749764e-002, + 2.714775e-002, 2.648015e-002, 2.551842e-002, 2.435354e-002, 2.305600e-002, 2.160904e-002, 1.999600e-002, 1.831226e-002, 1.651710e-002, + 1.466036e-002, 1.281827e-002, 1.102577e-002, 9.206446e-003, 7.400837e-003, 5.593449e-003, 3.786305e-003, 1.972613e-003, 2.114853e-004, + -1.515759e-003, -3.281600e-003, -5.011982e-003, -6.658060e-003, -8.222363e-003, -9.671738e-003, -1.096011e-002, -1.212414e-002, -1.315566e-002, + -1.397659e-002, -1.454402e-002, -1.475317e-002, -1.458079e-002, -1.407919e-002, -1.344489e-002, -1.265710e-002, -1.175546e-002, -1.069331e-002, + -9.431497e-003, -7.935916e-003, -6.251086e-003, -4.451768e-003, -2.663129e-003, -8.648447e-004, 8.309523e-004, 2.484674e-003, 4.066302e-003, + 5.491258e-003, 6.713115e-003, 7.666358e-003, 8.399299e-003, 9.039421e-003, 9.455859e-003, 9.730167e-003, 9.931253e-003, 9.994235e-003, + 9.993722e-003, 1.007099e-002, 1.028410e-002, 1.054874e-002, 1.095244e-002, 1.156079e-002, 1.250425e-002, 1.378384e-002, 1.560456e-002, + 1.778354e-002, 2.002093e-002, 2.210150e-002, 2.399389e-002, 2.561584e-002, 2.693659e-002, 2.801939e-002, 2.891124e-002, 2.961987e-002, + 3.008463e-002, 3.036073e-002, 3.036967e-002, 3.007197e-002, 2.938955e-002, 2.830023e-002, 2.685125e-002, 2.523869e-002, 2.351020e-002, + 2.167644e-002, 1.976183e-002, 1.782065e-002, 1.585395e-002, 1.398938e-002, 1.221838e-002, 1.064477e-002, 9.333372e-003, 8.346204e-003, + 7.674540e-003, 7.262850e-003, 7.146377e-003, 7.239997e-003, 7.480129e-003, 7.810700e-003, 8.086959e-003, 8.246213e-003, 8.348484e-003, + 8.448728e-003, 8.633581e-003, 8.890279e-003, 9.046792e-003, 9.032820e-003, 8.854836e-003, 8.394070e-003, 7.562755e-003, 6.502205e-003, + 5.237063e-003, 3.822572e-003, 2.267475e-003, 6.930835e-004, -9.495906e-004, -2.608063e-003, -4.241719e-003, -5.971734e-003, -7.916746e-003, + -9.854647e-003, -1.162972e-002, -1.337289e-002, -1.510281e-002, -1.677797e-002, -1.836914e-002, -1.979060e-002, -2.095685e-002, -2.190697e-002, + -2.248561e-002, -2.278490e-002, -2.283621e-002, -2.256332e-002, -2.161577e-002, -1.999491e-002, -1.801104e-002, -1.606761e-002, -1.441126e-002, + -1.305112e-002, -1.198191e-002, -1.105584e-002, -1.016212e-002, -9.235881e-003, -8.290432e-003, -7.377503e-003, -6.619576e-003, -5.967005e-003, + -5.555641e-003, -5.530417e-003, -5.846932e-003, -6.501593e-003, -7.382442e-003, -8.206221e-003, -8.908396e-003, -9.636019e-003, -1.035491e-002, + -1.105797e-002, -1.166029e-002, -1.205916e-002, -1.206248e-002, -1.173028e-002, -1.112025e-002, -1.016664e-002, -8.800269e-003, -7.166869e-003, + -5.077839e-003, -2.654945e-003, 8.741897e-005, 3.017198e-003, 5.973519e-003, 8.655810e-003, 1.111326e-002, 1.311736e-002, 1.460243e-002, + 1.562391e-002, 1.613643e-002, 1.623685e-002, 1.606871e-002, 1.550503e-002, 1.471522e-002, 1.368147e-002, 1.238285e-002, 1.077292e-002, + 9.040615e-003, 7.254797e-003, 5.292937e-003, 3.270173e-003, 1.293779e-003, -7.762852e-004, -2.764063e-003, -4.601199e-003, -6.468118e-003, + -8.375900e-003, -1.036160e-002, -1.231126e-002, -1.413572e-002, -1.558418e-002, -1.688216e-002, -1.803804e-002, -1.892497e-002, -1.932002e-002, + -1.931353e-002, -1.899873e-002, -1.848224e-002, -1.772208e-002, -1.672131e-002, -1.543699e-002, -1.387757e-002, -1.195271e-002, -9.794970e-003, + -7.318321e-003, -4.693313e-003, -2.135113e-003, 2.479421e-004, 2.288364e-003, 3.763006e-003, 4.677839e-003, 5.084712e-003, 5.055777e-003, + 4.797264e-003, 4.580981e-003, 4.138838e-003, 3.394913e-003, 2.648068e-003, 1.791164e-003, 8.573600e-004, -4.729002e-007, -9.352240e-004, + -1.906338e-003, -2.844634e-003, -3.709358e-003, -4.473033e-003, -5.125306e-003, -5.737482e-003, -6.307470e-003, -6.833593e-003, -7.423291e-003, + -7.860494e-003, -7.994462e-003, -7.664141e-003, -6.915605e-003, -5.844847e-003, -4.794827e-003, -3.664823e-003, -2.451599e-003, -1.301137e-003, + -3.349384e-004, 3.277434e-004, 9.662997e-004, 1.805600e-003, 2.737111e-003, 3.879564e-003, 4.688074e-003, 5.181844e-003, 5.328146e-003, + 5.335267e-003, 4.952646e-003, 4.220014e-003, 3.308466e-003, 2.204764e-003, 1.091776e-003, 2.302012e-004, -6.852518e-004, -1.590577e-003, + -2.561536e-003, -3.659200e-003, -4.590463e-003, -5.522301e-003, -6.496577e-003, -7.492864e-003, -8.295959e-003, -8.921894e-003, -9.389135e-003, + -9.857958e-003, -1.029640e-002, -1.063276e-002, -1.082072e-002, -1.107845e-002, -1.141187e-002, -1.191378e-002, -1.243624e-002, -1.309867e-002, + -1.372544e-002, -1.430869e-002, -1.519166e-002, -1.628453e-002, -1.735740e-002, -1.868485e-002, -2.010048e-002, -2.149121e-002, -2.289539e-002, + -2.412255e-002, -2.507247e-002, -2.583849e-002, -2.648481e-002, -2.666663e-002, -2.637743e-002, -2.589115e-002, -2.477853e-002, -2.288156e-002, + -2.034675e-002, -1.785121e-002, -1.515602e-002, -1.251034e-002, -9.763373e-003, -6.947004e-003, -4.207495e-003, -1.683069e-003, 6.979801e-004, + 3.111165e-003, 5.657024e-003, 7.738675e-003, 9.652138e-003, 1.107152e-002, 1.212810e-002, 1.318344e-002, 1.401971e-002, 1.471052e-002, + 1.527857e-002, 1.575929e-002, 1.609279e-002, 1.652767e-002, 1.722855e-002, 1.784664e-002, 1.872166e-002, 1.956102e-002, 2.093209e-002, + 2.265772e-002, 2.441846e-002, 2.629279e-002, 2.833140e-002, 3.097102e-002, 3.388437e-002, 3.736146e-002, 4.094176e-002, 4.429463e-002, + 4.721266e-002, 4.979020e-002, 5.188926e-002, 5.363935e-002, 5.491537e-002, 5.589863e-002, 5.659179e-002, 5.676511e-002, 5.653564e-002, + 5.591879e-002, 5.511470e-002, 5.380422e-002, 5.199434e-002, 4.986991e-002, 4.760816e-002, 4.520731e-002, 4.218684e-002, 3.914884e-002, + 3.646176e-002, 3.357897e-002, 3.063625e-002, 2.733737e-002, 2.419918e-002, 2.118239e-002, 1.807655e-002, 1.485870e-002, 1.183040e-002, + 9.026062e-003, 6.620857e-003, 4.704913e-003, 2.987400e-003, 1.840756e-003, 1.242731e-003, 8.939618e-004, 5.587037e-005, -9.270382e-004, + -1.941550e-003, -2.841552e-003, -3.702227e-003, -4.698885e-003, -5.709013e-003, -6.632671e-003, -7.718830e-003, -8.423272e-003, -9.357050e-003, + -1.054631e-002, -1.211548e-002, -1.428143e-002, -1.653279e-002, -1.873134e-002, -2.056412e-002, -2.283445e-002, -2.533304e-002, -2.762163e-002, + -2.978274e-002, -3.188331e-002, -3.359572e-002, -3.475659e-002, -3.539514e-002, -3.578022e-002, -3.562228e-002, -3.525821e-002, -3.475633e-002, + -3.406298e-002, -3.281330e-002, -3.113543e-002, -2.904255e-002, -2.702976e-002, -2.486391e-002, -2.290872e-002, -2.124425e-002, -2.010744e-002, + -1.973424e-002, -1.953781e-002, -1.975753e-002, -2.053141e-002, -2.179165e-002, -2.267425e-002, -2.307240e-002, -2.385640e-002, -2.471124e-002, + -2.550678e-002, -2.616918e-002, -2.670716e-002, -2.698499e-002, -2.722388e-002, -2.737727e-002, -2.810636e-002, -2.872728e-002, -2.901080e-002, + -2.967948e-002, -3.063864e-002, -3.143548e-002, -3.247343e-002, -3.346848e-002, -3.406640e-002, -3.457848e-002, -3.461493e-002, -3.460132e-002, + -3.435534e-002, -3.392541e-002, -3.398802e-002, -3.418863e-002, -3.414265e-002, -3.386730e-002, -3.300625e-002, -3.183807e-002, -3.120351e-002, + -2.980161e-002, -2.831502e-002, -2.728346e-002, -2.710408e-002, -2.699529e-002, -2.687844e-002, -2.708906e-002, -2.688304e-002, -2.596364e-002, + -2.351500e-002, -2.067922e-002, -1.722305e-002, -1.392980e-002, -1.039996e-002, -6.903864e-003, -3.067823e-003, 2.028519e-003, 8.041244e-003, + 1.414846e-002, 2.074916e-002, 2.733735e-002, 3.390441e-002, 4.194735e-002, 4.936889e-002, 5.598260e-002, 6.388697e-002, 7.169294e-002, + 7.814219e-002, 8.458951e-002, 9.041624e-002, 9.487703e-002, 9.980381e-002, 1.052409e-001, 1.095542e-001, 1.132784e-001, 1.165321e-001, + 1.183756e-001, 1.195366e-001, 1.185189e-001, 1.156774e-001, 1.116449e-001, 1.049208e-001, 9.821842e-002, 9.115314e-002, 8.341884e-002, + 7.504096e-002, 6.449259e-002, 5.356657e-002, 4.090816e-002, 2.710519e-002, 1.311753e-002, -7.555144e-004, -1.348083e-002, -2.418057e-002, + -3.599344e-002, -4.658415e-002, -5.642601e-002, -6.620353e-002, -7.438333e-002, -8.311919e-002, -9.137699e-002, -9.985629e-002, -1.059492e-001, + -1.096370e-001, -1.115441e-001, -1.124293e-001, -1.146147e-001, -1.163821e-001, -1.174053e-001, -1.172222e-001, -1.168887e-001, -1.149060e-001, + -1.083474e-001, -9.283483e-002, -7.553393e-002, -5.826291e-002, -3.989058e-002, -2.035611e-002, 9.032048e-004, 2.256097e-002, 4.528490e-002, + 6.725999e-002, 8.558692e-002, 1.046502e-001, 1.162965e-001, 1.238080e-001, 1.221044e-001, 1.128105e-001, 9.781889e-002, 8.576052e-002, + 7.408909e-002, 6.121316e-002, 4.141812e-002, 2.286778e-002, 1.366849e-003, -2.141731e-002, -3.636421e-002, -5.005051e-002, -5.696638e-002, + -6.666192e-002, -7.407884e-002, -7.893085e-002, -7.923572e-002, -8.687703e-002, -7.644238e-002, -6.619838e-002, -5.361237e-002, -3.777692e-002, + -1.248031e-002, 3.198658e-002, 8.673090e-002, 1.358175e-001, 1.768163e-001, 1.353593e-002, -5.293663e-002, -3.202199e-002, 1.470935e-002, + 3.990869e-002, 2.819456e-002, 5.089639e-002, -2.047635e-002, -7.206468e-002, -5.793094e-002, 0.000000e+000}, + {0.000000e+000, 2.627828e-002, 4.356570e-002, 5.590066e-002, 6.513557e-002, 7.176401e-002, 7.619190e-002, 7.902456e-002, 8.052635e-002, + 8.069023e-002, 7.973241e-002, 7.958253e-002, 7.911345e-002, 7.803655e-002, 7.619397e-002, 7.366693e-002, 7.072099e-002, 6.703199e-002, + 6.262389e-002, 5.760897e-002, 5.207308e-002, 4.695460e-002, 4.210567e-002, 3.705850e-002, 3.188252e-002, 2.664688e-002, 2.123305e-002, + 1.557971e-002, 9.553154e-003, 3.115702e-003, -3.597003e-003, -9.837649e-003, -1.495208e-002, -1.986732e-002, -2.448688e-002, -2.879064e-002, + -3.255962e-002, -3.593399e-002, -3.872280e-002, -4.113721e-002, -4.305561e-002, -4.438058e-002, -4.419171e-002, -4.346354e-002, -4.227857e-002, + -4.070248e-002, -3.863191e-002, -3.578455e-002, -3.300371e-002, -3.104144e-002, -2.947820e-002, -2.815277e-002, -2.631880e-002, -2.456747e-002, + -2.288192e-002, -2.122934e-002, -1.968993e-002, -1.816338e-002, -1.675128e-002, -1.542182e-002, -1.424042e-002, -1.321213e-002, -1.201029e-002, + -1.085904e-002, -9.875661e-003, -9.093173e-003, -8.682797e-003, -8.550510e-003, -8.935258e-003, -9.825035e-003, -1.115528e-002, -1.310271e-002, + -1.553719e-002, -1.855467e-002, -2.229852e-002, -2.691959e-002, -3.226086e-002, -3.838606e-002, -4.491678e-002, -5.132055e-002, -5.707194e-002, + -6.198682e-002, -6.592361e-002, -6.891807e-002, -7.108109e-002, -7.244639e-002, -7.304935e-002, -7.306753e-002, -7.234955e-002, -7.092714e-002, + -6.894033e-002, -6.645877e-002, -6.351020e-002, -6.009382e-002, -5.631860e-002, -5.230395e-002, -4.806876e-002, -4.358208e-002, -3.902589e-002, + -3.426490e-002, -2.934862e-002, -2.429013e-002, -1.918815e-002, -1.416938e-002, -9.207840e-003, -4.251368e-003, 4.599305e-004, 4.968922e-003, + 9.071060e-003, 1.253749e-002, 1.545266e-002, 1.766889e-002, 1.938519e-002, 2.064794e-002, 2.150962e-002, 2.206519e-002, 2.220465e-002, + 2.183589e-002, 2.100164e-002, 1.974939e-002, 1.812897e-002, 1.632388e-002, 1.471228e-002, 1.372267e-002, 1.357852e-002, 1.435166e-002, + 1.592284e-002, 1.804717e-002, 2.055896e-002, 2.333807e-002, 2.627446e-002, 2.923533e-002, 3.218247e-002, 3.508585e-002, 3.770380e-002, + 4.009361e-002, 4.223084e-002, 4.403938e-002, 4.543769e-002, 4.647158e-002, 4.703238e-002, 4.721819e-002, 4.708227e-002, 4.658257e-002, + 4.562471e-002, 4.440744e-002, 4.311149e-002, 4.188610e-002, 4.084983e-002, 4.003645e-002, 3.943564e-002, 3.902582e-002, 3.875864e-002, + 3.867983e-002, 3.867633e-002, 3.884930e-002, 3.931250e-002, 4.016473e-002, 4.135183e-002, 4.273392e-002, 4.423522e-002, 4.572808e-002, + 4.722831e-002, 4.865312e-002, 4.987304e-002, 5.071208e-002, 5.125245e-002, 5.128944e-002, 5.061868e-002, 4.918345e-002, 4.718244e-002, + 4.482501e-002, 4.230871e-002, 3.965336e-002, 3.686130e-002, 3.371874e-002, 3.012635e-002, 2.606147e-002, 2.156095e-002, 1.681697e-002, + 1.207351e-002, 7.645641e-003, 3.942093e-003, 1.261315e-003, -4.727037e-004, -1.747022e-003, -2.521234e-003, -2.825036e-003, -2.706748e-003, + -2.289798e-003, -1.615457e-003, -6.138811e-004, 5.522982e-004, 1.858184e-003, 3.252254e-003, 4.638838e-003, 5.958318e-003, 7.193464e-003, + 8.353948e-003, 9.482166e-003, 1.056264e-002, 1.158997e-002, 1.257172e-002, 1.349233e-002, 1.427194e-002, 1.475731e-002, 1.460136e-002, + 1.375607e-002, 1.206498e-002, 9.834518e-003, 7.357119e-003, 4.906672e-003, 2.626694e-003, 5.841220e-004, -1.284419e-003, -3.180704e-003, + -5.291252e-003, -7.671960e-003, -1.028605e-002, -1.308360e-002, -1.582610e-002, -1.821452e-002, -1.997544e-002, -2.085702e-002, -2.094075e-002, + -2.041542e-002, -1.965968e-002, -1.869600e-002, -1.761817e-002, -1.646892e-002, -1.527820e-002, -1.408051e-002, -1.281317e-002, -1.146979e-002, + -1.006414e-002, -8.645360e-003, -7.346602e-003, -6.177722e-003, -5.115387e-003, -4.225271e-003, -3.526228e-003, -3.055475e-003, -2.887008e-003, + -3.130046e-003, -3.723315e-003, -4.816218e-003, -6.561010e-003, -9.329511e-003, -1.308004e-002, -1.760819e-002, -2.241196e-002, -2.707801e-002, + -3.137429e-002, -3.521991e-002, -3.858883e-002, -4.152090e-002, -4.406801e-002, -4.632573e-002, -4.818105e-002, -4.956581e-002, -5.038003e-002, + -5.065222e-002, -5.005562e-002, -4.870219e-002, -4.669505e-002, -4.415117e-002, -4.119369e-002, -3.809685e-002, -3.476382e-002, -3.125764e-002, + -2.765345e-002, -2.397028e-002, -2.033630e-002, -1.682772e-002, -1.355076e-002, -1.067354e-002, -8.357976e-003, -6.646181e-003, -5.331191e-003, + -4.304162e-003, -3.474017e-003, -2.859178e-003, -2.496668e-003, -2.484125e-003, -2.842075e-003, -3.545468e-003, -4.608330e-003, -6.037357e-003, + -7.548477e-003, -8.666820e-003, -8.945608e-003, -8.333319e-003, -7.044790e-003, -5.299525e-003, -3.168177e-003, -7.802310e-004, 1.831121e-003, + 4.554385e-003, 7.302061e-003, 1.011387e-002, 1.289861e-002, 1.560955e-002, 1.823487e-002, 2.070637e-002, 2.295810e-002, 2.493980e-002, + 2.675169e-002, 2.837913e-002, 2.967944e-002, 3.078461e-002, 3.172341e-002, 3.238248e-002, 3.269337e-002, 3.244795e-002, 3.151916e-002, + 2.984039e-002, 2.745177e-002, 2.448391e-002, 2.107207e-002, 1.759081e-002, 1.421132e-002, 1.105214e-002, 8.079501e-003, 5.223456e-003, + 2.481226e-003, -2.666574e-004, -2.984635e-003, -5.541628e-003, -7.835141e-003, -9.698211e-003, -1.073609e-002, -1.105750e-002, -1.065264e-002, + -9.808848e-003, -8.619613e-003, -7.165417e-003, -5.528160e-003, -3.737545e-003, -2.027673e-003, -4.915721e-004, 9.000932e-004, 2.125781e-003, + 3.084694e-003, 3.606984e-003, 3.710216e-003, 3.224418e-003, 2.047428e-003, 8.909056e-005, -2.685746e-003, -6.463310e-003, -1.106788e-002, + -1.617413e-002, -2.126616e-002, -2.575522e-002, -2.937874e-002, -3.213212e-002, -3.404779e-002, -3.515282e-002, -3.548356e-002, -3.530158e-002, + -3.457001e-002, -3.331769e-002, -3.166005e-002, -2.956109e-002, -2.716868e-002, -2.460390e-002, -2.199989e-002, -1.941678e-002, -1.687818e-002, + -1.452556e-002, -1.234322e-002, -1.022435e-002, -8.267533e-003, -6.482712e-003, -4.925990e-003, -3.662302e-003, -2.644670e-003, -1.953745e-003, + -1.541575e-003, -1.587935e-003, -1.906619e-003, -2.335506e-003, -2.794909e-003, -3.345137e-003, -3.966046e-003, -4.704764e-003, -5.528606e-003, + -6.294894e-003, -6.970916e-003, -7.475556e-003, -7.650764e-003, -7.321992e-003, -6.547796e-003, -5.419448e-003, -4.151215e-003, -2.794719e-003, + -1.331699e-003, 2.278603e-004, 1.888577e-003, 3.633895e-003, 5.296030e-003, 7.044443e-003, 8.851797e-003, 1.069586e-002, 1.251898e-002, + 1.422942e-002, 1.580922e-002, 1.718064e-002, 1.825214e-002, 1.907148e-002, 1.937802e-002, 1.922977e-002, 1.869040e-002, 1.781769e-002, + 1.661995e-002, 1.513754e-002, 1.341448e-002, 1.147469e-002, 9.346126e-003, 7.191739e-003, 4.980831e-003, 2.872377e-003, 1.044545e-003, + -4.911375e-004, -1.820563e-003, -3.023930e-003, -4.040025e-003, -4.768080e-003, -5.134973e-003, -4.922964e-003, -4.274135e-003, -3.281274e-003, + -1.981189e-003, -4.773831e-004, 1.178025e-003, 2.870916e-003, 4.530133e-003, 6.122256e-003, 7.598488e-003, 8.869598e-003, 9.583624e-003, + 9.452668e-003, 8.576515e-003, 7.207909e-003, 5.622641e-003, 4.157367e-003, 2.884576e-003, 1.662166e-003, 3.617494e-004, -1.203779e-003, + -3.162230e-003, -5.751424e-003, -8.731253e-003, -1.189467e-002, -1.483600e-002, -1.705839e-002, -1.807286e-002, -1.796341e-002, -1.698110e-002, + -1.546264e-002, -1.336805e-002, -1.097402e-002, -8.277499e-003, -5.223383e-003, -1.952663e-003, 1.500560e-003, 5.084836e-003, 8.794249e-003, + 1.254274e-002, 1.631268e-002, 1.996194e-002, 2.348941e-002, 2.684761e-002, 3.014619e-002, 3.339131e-002, 3.663702e-002, 3.975877e-002, + 4.280762e-002, 4.575990e-002, 4.857074e-002, 5.123335e-002, 5.368155e-002, 5.584191e-002, 5.772099e-002, 5.927605e-002, 6.044074e-002, + 6.116607e-002, 6.139306e-002, 6.094741e-002, 5.997073e-002, 5.839929e-002, 5.642551e-002, 5.397851e-002, 5.127307e-002, 4.817413e-002, + 4.471312e-002, 4.091645e-002, 3.677714e-002, 3.241906e-002, 2.793954e-002, 2.352165e-002, 1.928957e-002, 1.524484e-002, 1.144570e-002, + 7.913581e-003, 4.728555e-003, 1.894251e-003, -5.667448e-004, -2.675146e-003, -4.370449e-003, -5.828696e-003, -7.147513e-003, -8.359402e-003, + -9.569339e-003, -1.085643e-002, -1.227528e-002, -1.383560e-002, -1.570066e-002, -1.798045e-002, -2.088356e-002, -2.451018e-002, -2.898965e-002, + -3.404843e-002, -3.931568e-002, -4.424036e-002, -4.868870e-002, -5.264210e-002, -5.594705e-002, -5.865151e-002, -6.078702e-002, -6.240528e-002, + -6.347179e-002, -6.394946e-002, -6.404549e-002, -6.344627e-002, -6.200448e-002, -5.982317e-002, -5.703574e-002, -5.381700e-002, -5.032320e-002, + -4.659501e-002, -4.277154e-002, -3.886421e-002, -3.503232e-002, -3.137334e-002, -2.795653e-002, -2.484870e-002, -2.212347e-002, -2.005669e-002, + -1.868707e-002, -1.784391e-002, -1.745094e-002, -1.745742e-002, -1.774705e-002, -1.826162e-002, -1.880551e-002, -1.921331e-002, -1.964651e-002, + -2.020183e-002, -2.097378e-002, -2.173210e-002, -2.247803e-002, -2.307830e-002, -2.331256e-002, -2.298058e-002, -2.202869e-002, -2.056929e-002, + -1.873198e-002, -1.666717e-002, -1.445166e-002, -1.210426e-002, -9.700113e-003, -7.142979e-003, -4.511546e-003, -1.918869e-003, 7.646193e-004, + 3.455287e-003, 6.092725e-003, 8.770852e-003, 1.134070e-002, 1.375535e-002, 1.595913e-002, 1.793026e-002, 1.967140e-002, 2.098857e-002, + 2.200259e-002, 2.260988e-002, 2.257011e-002, 2.170842e-002, 1.980402e-002, 1.697129e-002, 1.375342e-002, 1.072136e-002, 8.145979e-003, + 6.064675e-003, 4.619938e-003, 3.677349e-003, 3.102918e-003, 2.654093e-003, 2.276658e-003, 1.845243e-003, 1.598734e-003, 1.714934e-003, + 2.629802e-003, 4.244820e-003, 6.453924e-003, 8.952735e-003, 1.173808e-002, 1.460356e-002, 1.759296e-002, 2.059563e-002, 2.347594e-002, + 2.628005e-002, 2.883378e-002, 3.088405e-002, 3.233852e-002, 3.335692e-002, 3.385749e-002, 3.362438e-002, 3.266313e-002, 3.093414e-002, + 2.838777e-002, 2.500909e-002, 2.091384e-002, 1.620214e-002, 1.105623e-002, 6.223873e-003, 2.207844e-003, -1.148476e-003, -3.896515e-003, + -5.921253e-003, -7.175302e-003, -7.849110e-003, -7.802389e-003, -7.113798e-003, -6.136098e-003, -4.645588e-003, -2.724943e-003, -5.342138e-004, + 1.814076e-003, 4.357244e-003, 7.051008e-003, 9.808792e-003, 1.273379e-002, 1.582396e-002, 1.881185e-002, 2.169359e-002, 2.466471e-002, + 2.757530e-002, 3.038334e-002, 3.308778e-002, 3.564911e-002, 3.794767e-002, 4.004213e-002, 4.166380e-002, 4.264135e-002, 4.269543e-002, + 4.212728e-002, 4.088003e-002, 3.910308e-002, 3.692032e-002, 3.451467e-002, 3.170472e-002, 2.846038e-002, 2.471272e-002, 2.035176e-002, + 1.520271e-002, 9.747894e-003, 4.204373e-003, -9.206672e-004, -5.113475e-003, -8.182988e-003, -1.037656e-002, -1.194051e-002, -1.297758e-002, + -1.353458e-002, -1.415914e-002, -1.463003e-002, -1.471139e-002, -1.459854e-002, -1.434350e-002, -1.393638e-002, -1.341439e-002, -1.288608e-002, + -1.212842e-002, -1.159193e-002, -1.154043e-002, -1.167506e-002, -1.183955e-002, -1.202065e-002, -1.213426e-002, -1.224055e-002, -1.256419e-002, + -1.325272e-002, -1.432872e-002, -1.593480e-002, -1.823687e-002, -2.146896e-002, -2.492248e-002, -2.825205e-002, -3.139219e-002, -3.411288e-002, + -3.651172e-002, -3.885327e-002, -4.111059e-002, -4.318833e-002, -4.497365e-002, -4.697960e-002, -4.867548e-002, -4.984547e-002, -5.016826e-002, + -4.956363e-002, -4.804008e-002, -4.580773e-002, -4.307333e-002, -4.002110e-002, -3.674657e-002, -3.380281e-002, -3.097613e-002, -2.813784e-002, + -2.504369e-002, -2.195559e-002, -1.908292e-002, -1.636935e-002, -1.383472e-002, -1.134776e-002, -8.728267e-003, -6.545807e-003, -4.838018e-003, + -3.265082e-003, -1.787811e-003, -5.888244e-004, 5.441743e-004, 1.908081e-003, 3.255414e-003, 4.860432e-003, 6.896150e-003, 8.704352e-003, + 1.039198e-002, 1.243576e-002, 1.476070e-002, 1.744839e-002, 2.042175e-002, 2.368076e-002, 2.704792e-002, 3.052133e-002, 3.405434e-002, + 3.726289e-002, 3.978704e-002, 4.201769e-002, 4.378185e-002, 4.532412e-002, 4.633461e-002, 4.657057e-002, 4.582024e-002, 4.383243e-002, + 4.104639e-002, 3.770245e-002, 3.337082e-002, 2.917016e-002, 2.541346e-002, 2.186189e-002, 1.839014e-002, 1.519992e-002, 1.225911e-002, + 9.619294e-003, 7.152423e-003, 5.211359e-003, 3.037841e-003, 1.482596e-003, 8.586973e-004, 7.144907e-004, 1.068465e-003, 1.889440e-003, + 3.136620e-003, 4.446137e-003, 5.838287e-003, 7.281508e-003, 8.178957e-003, 8.469780e-003, 8.709169e-003, 8.774058e-003, 8.682823e-003, + 8.326442e-003, 7.176805e-003, 5.743723e-003, 3.964372e-003, 1.903091e-003, -1.123500e-003, -5.651213e-003, -1.027072e-002, -1.489497e-002, + -1.910395e-002, -2.255696e-002, -2.530102e-002, -2.697925e-002, -2.793650e-002, -2.812583e-002, -2.772935e-002, -2.804928e-002, -2.758423e-002, + -2.650849e-002, -2.493337e-002, -2.285047e-002, -2.028780e-002, -1.743213e-002, -1.430325e-002, -1.066349e-002, -6.814016e-003, -4.284213e-003, + -1.743712e-003, 9.990059e-004, 4.134005e-003, 7.182006e-003, 1.045095e-002, 1.382663e-002, 1.737234e-002, 2.079778e-002, 2.398726e-002, + 2.596310e-002, 2.709636e-002, 2.760158e-002, 2.729220e-002, 2.622348e-002, 2.504577e-002, 2.370765e-002, 2.198457e-002, 2.031694e-002, + 1.929364e-002, 1.792576e-002, 1.558601e-002, 1.327993e-002, 1.112930e-002, 8.995793e-003, 6.572884e-003, 4.731314e-003, 3.755613e-003, + 3.740523e-003, 4.365604e-003, 5.350226e-003, 5.337416e-003, 5.849048e-003, 6.781604e-003, 8.001264e-003, 9.179451e-003, 1.029018e-002, + 1.134280e-002, 1.242238e-002, 1.334398e-002, 1.406468e-002, 1.260589e-002, 1.039404e-002, 7.546527e-003, 4.408274e-003, 1.109559e-003, + -2.896219e-003, -7.744406e-003, -1.281613e-002, -1.816120e-002, -2.368978e-002, -3.016315e-002, -3.638682e-002, -4.154844e-002, -4.597240e-002, + -4.961307e-002, -5.211001e-002, -5.344302e-002, -5.374298e-002, -5.329088e-002, -5.217868e-002, -5.151090e-002, -5.154720e-002, -5.105957e-002, + -4.987514e-002, -4.863344e-002, -4.686170e-002, -4.414574e-002, -4.104717e-002, -3.743972e-002, -3.319194e-002, -2.911455e-002, -2.670544e-002, + -2.429220e-002, -2.165405e-002, -1.895595e-002, -1.625540e-002, -1.367699e-002, -1.108933e-002, -8.286070e-003, -5.442355e-003, -2.881166e-003, + -2.614483e-003, -1.762658e-003, -1.165097e-003, -6.144086e-004, -2.642032e-004, 1.843571e-004, 6.141091e-004, 9.860957e-004, 1.572735e-003, + 2.975511e-003, 3.159087e-003, 2.998500e-003, 3.637179e-003, 4.878917e-003, 6.091669e-003, 7.524655e-003, 8.750707e-003, 9.784903e-003, + 1.074422e-002, 1.275156e-002, 1.306294e-002, 1.285488e-002, 1.395387e-002, 1.516617e-002, 1.667991e-002, 1.793865e-002, 1.927099e-002, + 2.077174e-002, 2.212471e-002, 2.390636e-002, 2.533258e-002, 2.500121e-002, 2.592449e-002, 2.731590e-002, 2.964147e-002, 3.240507e-002, + 3.572245e-002, 3.892379e-002, 4.222847e-002, 4.560767e-002, 4.891419e-002, 4.885967e-002, 4.782638e-002, 4.706603e-002, 4.697589e-002, + 4.698240e-002, 4.789162e-002, 4.933704e-002, 5.052240e-002, 5.220375e-002, 5.425961e-002, 5.209070e-002, 4.869086e-002, 4.508302e-002, + 4.169870e-002, 3.757880e-002, 3.421410e-002, 3.181283e-002, 2.941233e-002, 2.674478e-002, 2.418436e-002, 1.907172e-002, 1.191656e-002, + 4.452421e-003, -2.718167e-003, -8.637004e-003, -1.443702e-002, -1.940271e-002, -2.347476e-002, -2.777385e-002, -3.113469e-002, -3.621604e-002, + -4.422043e-002, -5.058789e-002, -5.483919e-002, -5.848729e-002, -5.970394e-002, -5.970384e-002, -5.792439e-002, -5.725175e-002, -5.498376e-002, + -5.259520e-002, -5.493362e-002, -5.610667e-002, -5.671857e-002, -5.623606e-002, -5.475203e-002, -5.320966e-002, -5.055131e-002, -4.742022e-002, + -4.302681e-002, -3.775431e-002, -3.654592e-002, -3.458210e-002, -3.180384e-002, -2.721688e-002, -1.477351e-002, 2.567142e-003, 1.660427e-002, + 2.870534e-002, 3.712421e-002, 4.358954e-002, 5.346284e-002, 7.094791e-002, 8.490620e-002, 9.454099e-002, 9.268352e-002, 8.603546e-002, + 6.487314e-002, 4.228133e-002, 2.109514e-002, 5.503028e-003, -5.066366e-002, -6.957882e-002, -8.013058e-002, -7.944767e-002, -6.659527e-002, + -4.008165e-002, -4.099015e-003, 3.777047e-002, 7.874712e-002, 1.184827e-001, -1.491780e-002, -7.736965e-002, -4.061215e-002, 1.468958e-002, + 3.447876e-002, -4.454113e-003, 1.276782e-001, 4.064773e-003, -1.035723e-001, -9.401675e-002, 0.000000e+000}, + {0.000000e+000, 3.547621e-002, 5.696037e-002, 7.054234e-002, 7.921379e-002, 8.462385e-002, 8.677876e-002, 8.676355e-002, 8.457211e-002, + 8.161055e-002, 7.790201e-002, 7.549933e-002, 7.295599e-002, 6.965965e-002, 6.577509e-002, 6.128597e-002, 5.647937e-002, 5.087796e-002, + 4.458235e-002, 3.811087e-002, 3.154634e-002, 2.607396e-002, 2.160396e-002, 1.710168e-002, 1.271629e-002, 8.405392e-003, 4.004336e-003, + -4.734750e-004, -4.990597e-003, -9.434139e-003, -1.372162e-002, -1.743055e-002, -1.995160e-002, -2.240339e-002, -2.476201e-002, -2.699072e-002, + -2.917438e-002, -3.142751e-002, -3.355752e-002, -3.564985e-002, -3.775528e-002, -3.981663e-002, -4.095869e-002, -4.206586e-002, -4.316132e-002, + -4.417180e-002, -4.508013e-002, -4.579583e-002, -4.641229e-002, -4.710728e-002, -4.778820e-002, -4.839605e-002, -4.832637e-002, -4.812894e-002, + -4.785001e-002, -4.755650e-002, -4.724626e-002, -4.681329e-002, -4.653449e-002, -4.618425e-002, -4.576769e-002, -4.535510e-002, -4.450738e-002, + -4.353494e-002, -4.245083e-002, -4.123470e-002, -4.003358e-002, -3.860603e-002, -3.707610e-002, -3.548101e-002, -3.367063e-002, -3.179131e-002, + -2.977682e-002, -2.747965e-002, -2.512841e-002, -2.271883e-002, -2.026472e-002, -1.774486e-002, -1.526243e-002, -1.265968e-002, -1.011775e-002, + -7.607875e-003, -5.158386e-003, -2.665612e-003, -2.611326e-004, 2.046611e-003, 4.233984e-003, 6.294196e-003, 8.347134e-003, 1.029622e-002, + 1.219682e-002, 1.404898e-002, 1.587074e-002, 1.767257e-002, 1.934008e-002, 2.093646e-002, 2.245389e-002, 2.391636e-002, 2.527077e-002, + 2.658674e-002, 2.781539e-002, 2.891410e-002, 2.984920e-002, 3.062593e-002, 3.125746e-002, 3.182117e-002, 3.217840e-002, 3.242923e-002, + 3.258587e-002, 3.275589e-002, 3.300144e-002, 3.312341e-002, 3.325567e-002, 3.328679e-002, 3.315130e-002, 3.299599e-002, 3.273514e-002, + 3.245011e-002, 3.220969e-002, 3.193666e-002, 3.162981e-002, 3.121950e-002, 3.069806e-002, 3.009597e-002, 2.928399e-002, 2.835065e-002, + 2.733145e-002, 2.618496e-002, 2.499223e-002, 2.381573e-002, 2.260288e-002, 2.138612e-002, 2.014255e-002, 1.889497e-002, 1.748627e-002, + 1.613892e-002, 1.484245e-002, 1.350696e-002, 1.219204e-002, 1.093037e-002, 9.702737e-003, 8.511953e-003, 7.416355e-003, 6.377393e-003, + 5.216926e-003, 4.069504e-003, 2.975464e-003, 1.879038e-003, 8.439002e-004, -1.701288e-004, -1.117807e-003, -2.031869e-003, -2.914836e-003, + -3.768302e-003, -4.695668e-003, -5.712544e-003, -6.722541e-003, -7.678584e-003, -8.616013e-003, -9.497367e-003, -1.035963e-002, -1.113541e-002, + -1.184981e-002, -1.250016e-002, -1.315719e-002, -1.386956e-002, -1.454541e-002, -1.516843e-002, -1.570525e-002, -1.616157e-002, -1.648192e-002, + -1.668764e-002, -1.686500e-002, -1.709050e-002, -1.733257e-002, -1.777004e-002, -1.818691e-002, -1.855436e-002, -1.888693e-002, -1.915149e-002, + -1.939631e-002, -1.962951e-002, -1.979836e-002, -2.002083e-002, -2.027469e-002, -2.062242e-002, -2.091931e-002, -2.118309e-002, -2.135909e-002, + -2.144381e-002, -2.137676e-002, -2.120710e-002, -2.096172e-002, -2.059042e-002, -2.008648e-002, -1.950090e-002, -1.885748e-002, -1.810397e-002, + -1.720526e-002, -1.613868e-002, -1.496593e-002, -1.368650e-002, -1.226422e-002, -1.072302e-002, -9.049371e-003, -7.358039e-003, -5.663777e-003, + -3.891258e-003, -2.045170e-003, -1.636382e-004, 1.744967e-003, 3.621917e-003, 5.496730e-003, 7.364511e-003, 9.164464e-003, 1.094357e-002, + 1.260896e-002, 1.434101e-002, 1.615333e-002, 1.793667e-002, 1.971428e-002, 2.145622e-002, 2.312011e-002, 2.466105e-002, 2.610530e-002, + 2.745963e-002, 2.857697e-002, 2.963034e-002, 3.069135e-002, 3.169880e-002, 3.265673e-002, 3.354319e-002, 3.433260e-002, 3.505830e-002, + 3.570706e-002, 3.633274e-002, 3.679244e-002, 3.716464e-002, 3.750240e-002, 3.783582e-002, 3.810767e-002, 3.836460e-002, 3.858148e-002, + 3.883698e-002, 3.915140e-002, 3.954073e-002, 3.991564e-002, 4.025981e-002, 4.071078e-002, 4.119783e-002, 4.173348e-002, 4.219503e-002, + 4.257693e-002, 4.289900e-002, 4.313780e-002, 4.320722e-002, 4.314518e-002, 4.285891e-002, 4.243274e-002, 4.186217e-002, 4.113770e-002, + 4.024297e-002, 3.920791e-002, 3.790835e-002, 3.646747e-002, 3.481174e-002, 3.295448e-002, 3.072675e-002, 2.836238e-002, 2.582710e-002, + 2.311948e-002, 2.028903e-002, 1.732425e-002, 1.428509e-002, 1.118910e-002, 7.970093e-003, 4.727332e-003, 1.307236e-003, -2.212218e-003, + -5.732595e-003, -9.279046e-003, -1.280237e-002, -1.627107e-002, -1.975832e-002, -2.318285e-002, -2.653184e-002, -2.981665e-002, -3.307268e-002, + -3.629147e-002, -3.938566e-002, -4.240172e-002, -4.536910e-002, -4.822087e-002, -5.088447e-002, -5.338356e-002, -5.566535e-002, -5.771809e-002, + -5.958486e-002, -6.129184e-002, -6.274121e-002, -6.394318e-002, -6.492695e-002, -6.563864e-002, -6.604333e-002, -6.617563e-002, -6.595979e-002, + -6.548823e-002, -6.470836e-002, -6.388973e-002, -6.275980e-002, -6.137325e-002, -5.978953e-002, -5.796512e-002, -5.589693e-002, -5.364867e-002, + -5.125099e-002, -4.866001e-002, -4.590269e-002, -4.311949e-002, -4.038648e-002, -3.763903e-002, -3.497113e-002, -3.235027e-002, -2.973690e-002, + -2.717698e-002, -2.468817e-002, -2.224572e-002, -1.981741e-002, -1.762449e-002, -1.560499e-002, -1.375343e-002, -1.206404e-002, -1.063405e-002, + -9.335874e-003, -8.187904e-003, -7.139321e-003, -6.193252e-003, -5.378602e-003, -4.764400e-003, -4.314171e-003, -3.941590e-003, -3.590591e-003, + -3.306566e-003, -3.057758e-003, -2.780313e-003, -2.527669e-003, -2.201192e-003, -1.823313e-003, -1.492728e-003, -1.243533e-003, -8.872446e-004, + -5.465315e-004, -1.659146e-004, 1.964756e-004, 4.756516e-004, 7.243700e-004, 9.169495e-004, 1.042731e-003, 1.065695e-003, 8.887363e-004, + 6.548888e-004, 4.257568e-004, 1.623994e-004, -1.058271e-004, -2.925541e-004, -4.303827e-004, -5.030480e-004, -5.386141e-004, -5.106781e-004, + -5.335284e-004, -5.460528e-004, -4.991939e-004, -4.136433e-004, -1.684378e-004, 2.416606e-004, 7.277907e-004, 1.270833e-003, 1.859861e-003, + 2.518680e-003, 3.130691e-003, 3.707352e-003, 4.332805e-003, 4.984243e-003, 5.691873e-003, 6.356036e-003, 7.090192e-003, 7.810875e-003, + 8.563157e-003, 9.300814e-003, 9.950161e-003, 1.045456e-002, 1.089457e-002, 1.126804e-002, 1.164306e-002, 1.201557e-002, 1.237488e-002, + 1.278852e-002, 1.325236e-002, 1.365240e-002, 1.406841e-002, 1.439940e-002, 1.478766e-002, 1.519718e-002, 1.563759e-002, 1.612838e-002, + 1.663351e-002, 1.712195e-002, 1.761671e-002, 1.816357e-002, 1.879989e-002, 1.935316e-002, 1.996410e-002, 2.065829e-002, 2.138201e-002, + 2.227852e-002, 2.321649e-002, 2.414859e-002, 2.519348e-002, 2.630376e-002, 2.734890e-002, 2.831907e-002, 2.915795e-002, 3.005297e-002, + 3.095296e-002, 3.179249e-002, 3.264209e-002, 3.341960e-002, 3.412970e-002, 3.481971e-002, 3.546491e-002, 3.601730e-002, 3.634579e-002, + 3.663454e-002, 3.689082e-002, 3.701661e-002, 3.712872e-002, 3.719527e-002, 3.722811e-002, 3.722395e-002, 3.712791e-002, 3.704749e-002, + 3.690542e-002, 3.670423e-002, 3.651267e-002, 3.626574e-002, 3.589362e-002, 3.539710e-002, 3.475347e-002, 3.403829e-002, 3.317088e-002, + 3.220293e-002, 3.105627e-002, 2.979557e-002, 2.840891e-002, 2.686821e-002, 2.520048e-002, 2.336865e-002, 2.127739e-002, 1.910732e-002, + 1.683556e-002, 1.441321e-002, 1.181497e-002, 9.122242e-003, 6.321727e-003, 3.511831e-003, 7.085633e-004, -2.092576e-003, -4.886330e-003, + -7.721182e-003, -1.059578e-002, -1.332059e-002, -1.597201e-002, -1.859856e-002, -2.106954e-002, -2.345178e-002, -2.570654e-002, -2.783431e-002, + -2.966961e-002, -3.126138e-002, -3.271318e-002, -3.403561e-002, -3.517624e-002, -3.625713e-002, -3.723128e-002, -3.809538e-002, -3.868026e-002, + -3.911762e-002, -3.943440e-002, -3.959776e-002, -3.963124e-002, -3.952376e-002, -3.919323e-002, -3.888431e-002, -3.839034e-002, -3.771546e-002, + -3.702977e-002, -3.617299e-002, -3.527720e-002, -3.419630e-002, -3.295273e-002, -3.164608e-002, -3.041417e-002, -2.928898e-002, -2.813279e-002, + -2.694768e-002, -2.578592e-002, -2.469363e-002, -2.373422e-002, -2.286685e-002, -2.197650e-002, -2.099563e-002, -1.995272e-002, -1.901370e-002, + -1.824215e-002, -1.748614e-002, -1.677195e-002, -1.614666e-002, -1.553161e-002, -1.488914e-002, -1.427217e-002, -1.368859e-002, -1.311977e-002, + -1.257429e-002, -1.219071e-002, -1.191097e-002, -1.165035e-002, -1.138351e-002, -1.095540e-002, -1.050061e-002, -9.961585e-003, -9.303637e-003, + -8.627051e-003, -8.002157e-003, -7.420625e-003, -6.769707e-003, -6.271510e-003, -5.778752e-003, -5.253697e-003, -4.712777e-003, -4.107146e-003, + -3.322076e-003, -2.377213e-003, -1.484745e-003, -6.093864e-004, 3.617376e-004, 1.448650e-003, 2.481607e-003, 3.630674e-003, 4.841672e-003, + 6.190834e-003, 7.577485e-003, 9.061530e-003, 1.055121e-002, 1.197034e-002, 1.327674e-002, 1.459672e-002, 1.585084e-002, 1.713619e-002, + 1.831245e-002, 1.924772e-002, 2.004078e-002, 2.076805e-002, 2.134358e-002, 2.170871e-002, 2.185719e-002, 2.178140e-002, 2.143583e-002, + 2.111330e-002, 2.071848e-002, 2.030859e-002, 1.992772e-002, 1.964012e-002, 1.933981e-002, 1.890874e-002, 1.837687e-002, 1.785506e-002, + 1.721102e-002, 1.651854e-002, 1.592481e-002, 1.532413e-002, 1.463462e-002, 1.397644e-002, 1.336471e-002, 1.263563e-002, 1.183665e-002, + 1.102487e-002, 1.017544e-002, 9.274391e-003, 8.459909e-003, 7.853330e-003, 7.147361e-003, 6.472924e-003, 6.009735e-003, 5.650874e-003, + 5.027562e-003, 4.306640e-003, 3.533646e-003, 2.771919e-003, 2.100479e-003, 1.493026e-003, 9.588593e-004, 4.967573e-004, 7.309668e-005, + -2.205349e-004, -5.520499e-004, -1.019814e-003, -1.243300e-003, -1.391244e-003, -1.587863e-003, -1.735729e-003, -1.578080e-003, -1.421772e-003, + -1.270895e-003, -9.989600e-004, -6.305420e-004, -3.582024e-004, -1.386532e-005, 4.372084e-004, 8.428798e-004, 1.318285e-003, 1.888251e-003, + 2.620867e-003, 3.426770e-003, 4.396926e-003, 5.611456e-003, 6.384280e-003, 7.087899e-003, 7.693087e-003, 8.146602e-003, 8.336808e-003, + 8.491206e-003, 8.676731e-003, 8.632668e-003, 8.373485e-003, 8.133570e-003, 7.358811e-003, 6.365847e-003, 5.429238e-003, 4.524259e-003, + 3.415815e-003, 2.338171e-003, 1.209745e-003, -2.349047e-004, -1.761399e-003, -3.163080e-003, -4.641567e-003, -6.310759e-003, -7.966114e-003, + -9.573688e-003, -1.128391e-002, -1.278044e-002, -1.420052e-002, -1.551112e-002, -1.661711e-002, -1.768753e-002, -1.875845e-002, -1.976623e-002, + -2.051854e-002, -2.119314e-002, -2.195653e-002, -2.263158e-002, -2.325306e-002, -2.370221e-002, -2.386535e-002, -2.376975e-002, -2.353770e-002, + -2.360874e-002, -2.350956e-002, -2.318895e-002, -2.286376e-002, -2.244608e-002, -2.191418e-002, -2.124015e-002, -2.053250e-002, -1.962835e-002, + -1.863391e-002, -1.773704e-002, -1.697285e-002, -1.613033e-002, -1.553082e-002, -1.490081e-002, -1.424619e-002, -1.352697e-002, -1.279004e-002, + -1.169463e-002, -1.066141e-002, -9.938796e-003, -9.426431e-003, -8.769972e-003, -8.022488e-003, -7.163118e-003, -6.230494e-003, -5.354855e-003, + -4.626499e-003, -3.800046e-003, -2.821283e-003, -2.077848e-003, -1.720912e-003, -1.211957e-003, -5.139867e-004, 1.672896e-004, 7.380747e-004, + 1.231918e-003, 1.836812e-003, 2.588514e-003, 3.424586e-003, 4.310351e-003, 4.492074e-003, 4.857978e-003, 5.186351e-003, 5.287520e-003, + 5.017601e-003, 4.899668e-003, 5.075812e-003, 5.252189e-003, 5.406088e-003, 5.649376e-003, 5.074588e-003, 4.358268e-003, 3.678599e-003, + 3.274783e-003, 3.139339e-003, 3.153597e-003, 3.426805e-003, 3.607742e-003, 3.905392e-003, 4.436611e-003, 4.566128e-003, 4.283526e-003, + 4.044839e-003, 3.884442e-003, 3.950518e-003, 4.381439e-003, 5.230085e-003, 6.100573e-003, 6.921528e-003, 7.910408e-003, 8.788116e-003, + 9.206493e-003, 9.604025e-003, 1.032630e-002, 1.103261e-002, 1.194813e-002, 1.310615e-002, 1.451625e-002, 1.596162e-002, 1.770441e-002, + 1.949093e-002, 2.061415e-002, 2.176185e-002, 2.324692e-002, 2.509668e-002, 2.702619e-002, 2.925113e-002, 3.164147e-002, 3.382193e-002, + 3.611470e-002, 3.868504e-002, 4.032994e-002, 4.191587e-002, 4.365399e-002, 4.505602e-002, 4.602139e-002, 4.703545e-002, 4.828186e-002, + 4.950167e-002, 5.056748e-002, 5.159618e-002, 5.140171e-002, 5.091424e-002, 5.052093e-002, 4.952834e-002, 4.812491e-002, 4.659471e-002, + 4.530327e-002, 4.399353e-002, 4.279523e-002, 4.131729e-002, 3.915855e-002, 3.672639e-002, 3.422345e-002, 3.162054e-002, 2.886251e-002, + 2.609826e-002, 2.346507e-002, 2.101739e-002, 1.885243e-002, 1.670885e-002, 1.428507e-002, 1.101200e-002, 7.376377e-003, 3.821890e-003, + 1.631110e-004, -3.441841e-003, -6.837429e-003, -9.625744e-003, -1.232796e-002, -1.509366e-002, -1.773156e-002, -2.152922e-002, -2.487684e-002, + -2.821601e-002, -3.144637e-002, -3.439800e-002, -3.656986e-002, -3.871948e-002, -4.066808e-002, -4.237258e-002, -4.364921e-002, -4.626492e-002, + -4.924201e-002, -5.204788e-002, -5.437201e-002, -5.628460e-002, -5.767088e-002, -5.875988e-002, -5.963978e-002, -5.998494e-002, -5.999040e-002, + -6.086009e-002, -6.220630e-002, -6.352688e-002, -6.450047e-002, -6.509565e-002, -6.536775e-002, -6.543613e-002, -6.509523e-002, -6.454990e-002, + -6.332388e-002, -6.251115e-002, -6.259580e-002, -6.253274e-002, -6.204282e-002, -6.127282e-002, -6.019903e-002, -5.879818e-002, -5.693768e-002, + -5.482688e-002, -5.256728e-002, -5.046707e-002, -5.017654e-002, -4.930373e-002, -4.774042e-002, -4.563187e-002, -4.323567e-002, -4.064778e-002, + -3.763459e-002, -3.419645e-002, -3.047666e-002, -2.653407e-002, -2.417219e-002, -2.160950e-002, -1.840240e-002, -1.456073e-002, -1.049088e-002, + -6.001775e-003, -1.034906e-003, 4.698963e-003, 1.034955e-002, 1.615834e-002, 2.027239e-002, 2.352100e-002, 2.685419e-002, 3.013953e-002, + 3.336232e-002, 3.692365e-002, 4.060177e-002, 4.403997e-002, 4.779257e-002, 5.144838e-002, 5.346909e-002, 5.418464e-002, 5.525373e-002, + 5.576491e-002, 5.655827e-002, 5.773097e-002, 5.916615e-002, 6.029972e-002, 6.199814e-002, 6.407098e-002, 6.529458e-002, 6.488477e-002, + 6.441998e-002, 6.396437e-002, 6.344763e-002, 6.343303e-002, 6.340507e-002, 6.365962e-002, 6.389971e-002, 6.415027e-002, 6.437890e-002, + 6.137377e-002, 5.916727e-002, 5.650317e-002, 5.378682e-002, 5.175786e-002, 4.945596e-002, 4.711811e-002, 4.503453e-002, 4.304100e-002, + 4.128570e-002, 3.646880e-002, 3.115310e-002, 2.591698e-002, 2.061017e-002, 1.564980e-002, 1.097586e-002, 6.396060e-003, 2.617911e-003, + -1.018952e-003, -3.934453e-003, -8.660800e-003, -1.466175e-002, -2.018296e-002, -2.571675e-002, -3.061892e-002, -3.445474e-002, -3.722479e-002, + -3.890797e-002, -4.001355e-002, -4.085412e-002, -4.220000e-002, -4.563460e-002, -4.938268e-002, -5.240873e-002, -5.457861e-002, -5.594393e-002, + -5.665137e-002, -5.625688e-002, -5.441459e-002, -5.150242e-002, -4.847541e-002, -4.897426e-002, -4.801716e-002, -4.628311e-002, -4.362039e-002, + -4.056376e-002, -3.591383e-002, -3.069531e-002, -2.525436e-002, -1.867304e-002, -1.135140e-002, -7.748249e-003, -3.720367e-003, 3.022880e-004, + 5.286934e-003, 1.047223e-002, 1.602796e-002, 2.094498e-002, 2.468319e-002, 2.842591e-002, 3.315739e-002, 3.537407e-002, 3.557003e-002, + 3.559375e-002, 3.626486e-002, 3.562214e-002, 3.521006e-002, 3.426653e-002, 3.224883e-002, 2.911543e-002, 2.454977e-002, 1.751733e-002, + 9.782654e-003, 2.955298e-003, -2.676502e-003, -5.892644e-003, -9.977203e-003, -1.416451e-002, -1.698550e-002, -2.214304e-002, -2.708398e-002, + -3.271888e-002, -3.896387e-002, -4.202464e-002, -4.200670e-002, -4.084843e-002, -3.737876e-002, -3.303173e-002, -2.581727e-002, -1.959411e-002, + -1.070993e-002, -1.840049e-003, 3.178440e-003, 8.753807e-003, 1.388705e-002, 1.870795e-002, 2.087077e-002, 2.178182e-002, 2.371899e-002, + 2.941934e-002, 3.357092e-002, 4.397517e-002, 4.914990e-002, 4.824721e-002, 4.816658e-002, 4.365053e-002, 3.172038e-002, 1.818547e-002, + -9.579098e-003, -3.836193e-002, -6.022345e-002, -6.574692e-002, -4.170235e-002, -4.118005e-002, -4.242912e-002, -3.494630e-002, -2.406701e-002, + -1.465459e-002, 1.795495e-003, 3.156696e-002, 5.742234e-002, 8.034192e-002, -4.801566e-002, 2.853384e-002, 2.415555e-002, 1.567828e-002, + -7.567735e-004, -3.003692e-002, 7.782000e-002, -2.678888e-002, -7.699702e-002, -5.553528e-002, 0.000000e+000}, + {0.000000e+000, 3.008405e-002, 5.354544e-002, 7.067570e-002, 8.279900e-002, 9.123324e-002, 9.610622e-002, 9.700966e-002, 9.608442e-002, + 9.373228e-002, 8.968379e-002, 8.610728e-002, 8.239419e-002, 7.799348e-002, 7.347316e-002, 6.856660e-002, 6.320199e-002, 5.769970e-002, + 5.210802e-002, 4.626127e-002, 3.998981e-002, 3.439194e-002, 2.938732e-002, 2.426256e-002, 1.896747e-002, 1.364328e-002, 8.309543e-003, + 2.834052e-003, -2.504120e-003, -7.962219e-003, -1.364893e-002, -1.910270e-002, -2.362982e-002, -2.791792e-002, -3.199797e-002, -3.590144e-002, + -3.954212e-002, -4.285837e-002, -4.597219e-002, -4.882407e-002, -5.138121e-002, -5.362549e-002, -5.496518e-002, -5.593701e-002, -5.677308e-002, + -5.748681e-002, -5.827729e-002, -5.930766e-002, -6.011182e-002, -6.016183e-002, -5.985998e-002, -5.946679e-002, -5.852603e-002, -5.745804e-002, + -5.619291e-002, -5.483750e-002, -5.321896e-002, -5.154575e-002, -4.963832e-002, -4.754704e-002, -4.538433e-002, -4.303045e-002, -4.050474e-002, + -3.790229e-002, -3.523516e-002, -3.254073e-002, -2.980780e-002, -2.717292e-002, -2.464618e-002, -2.209672e-002, -1.987734e-002, -1.770826e-002, + -1.580308e-002, -1.420071e-002, -1.287072e-002, -1.184785e-002, -1.121186e-002, -1.079929e-002, -1.054156e-002, -1.041532e-002, -1.002742e-002, + -9.359219e-003, -8.610346e-003, -7.789516e-003, -6.695096e-003, -5.453331e-003, -4.040506e-003, -2.436448e-003, -5.938020e-004, 1.317206e-003, + 3.402563e-003, 5.521493e-003, 7.855443e-003, 9.846045e-003, 1.205460e-002, 1.413745e-002, 1.629454e-002, 1.841037e-002, 2.053015e-002, + 2.269331e-002, 2.479379e-002, 2.682107e-002, 2.891425e-002, 3.070595e-002, 3.237254e-002, 3.387378e-002, 3.523501e-002, 3.640997e-002, + 3.733692e-002, 3.792465e-002, 3.828606e-002, 3.832309e-002, 3.813335e-002, 3.759891e-002, 3.678423e-002, 3.585357e-002, 3.472445e-002, + 3.344240e-002, 3.197320e-002, 3.029982e-002, 2.851151e-002, 2.668268e-002, 2.492155e-002, 2.335095e-002, 2.191184e-002, 2.087757e-002, + 2.017537e-002, 1.971369e-002, 1.938398e-002, 1.909021e-002, 1.891038e-002, 1.885656e-002, 1.883243e-002, 1.889309e-002, 1.860773e-002, + 1.819826e-002, 1.770701e-002, 1.715965e-002, 1.646123e-002, 1.566800e-002, 1.478379e-002, 1.381292e-002, 1.274049e-002, 1.163604e-002, + 1.016170e-002, 8.535842e-003, 7.029566e-003, 5.643495e-003, 4.405247e-003, 3.376433e-003, 2.481592e-003, 1.698639e-003, 1.063470e-003, + 6.251477e-004, 1.678162e-004, -3.531079e-004, -7.029354e-004, -7.829977e-004, -6.556778e-004, -4.086731e-004, 4.495372e-005, 5.540523e-004, + 1.066889e-003, 1.581016e-003, 2.057286e-003, 2.265356e-003, 2.438156e-003, 2.492686e-003, 2.310561e-003, 1.829938e-003, 1.113294e-003, + 3.386383e-004, -4.420929e-004, -1.265555e-003, -2.180625e-003, -3.340346e-003, -4.672067e-003, -6.190333e-003, -7.859201e-003, -9.593834e-003, + -1.132352e-002, -1.296282e-002, -1.427747e-002, -1.519722e-002, -1.569738e-002, -1.609516e-002, -1.624571e-002, -1.615155e-002, -1.591443e-002, + -1.548547e-002, -1.491936e-002, -1.420084e-002, -1.334600e-002, -1.239079e-002, -1.135042e-002, -1.042085e-002, -9.499740e-003, -8.555257e-003, + -7.552962e-003, -6.509079e-003, -5.408240e-003, -4.263729e-003, -3.114538e-003, -1.943377e-003, -8.144698e-004, 1.911612e-004, 8.286117e-004, + 1.271274e-003, 1.401623e-003, 1.403688e-003, 1.300111e-003, 1.215844e-003, 1.243042e-003, 1.354118e-003, 1.536264e-003, 1.625615e-003, + 1.614898e-003, 1.571843e-003, 1.433526e-003, 1.207264e-003, 9.579352e-004, 8.281522e-004, 9.664205e-004, 1.477677e-003, 2.251320e-003, + 3.223537e-003, 4.235228e-003, 5.271962e-003, 6.332567e-003, 7.376271e-003, 8.423107e-003, 9.509160e-003, 1.061887e-002, 1.174063e-002, + 1.289915e-002, 1.404457e-002, 1.512919e-002, 1.612496e-002, 1.711214e-002, 1.803889e-002, 1.890345e-002, 1.967306e-002, 2.035113e-002, + 2.088208e-002, 2.124164e-002, 2.139319e-002, 2.127534e-002, 2.075450e-002, 1.979327e-002, 1.852713e-002, 1.716478e-002, 1.581413e-002, + 1.456343e-002, 1.342340e-002, 1.238704e-002, 1.148153e-002, 1.068397e-002, 1.000988e-002, 9.376271e-003, 8.761889e-003, 8.220361e-003, + 7.777956e-003, 7.526178e-003, 7.495194e-003, 7.569309e-003, 7.682555e-003, 7.811962e-003, 7.873380e-003, 7.897152e-003, 7.831993e-003, + 7.645498e-003, 7.319078e-003, 6.859914e-003, 6.198263e-003, 5.296796e-003, 4.090739e-003, 2.579168e-003, 7.711977e-004, -1.290253e-003, + -3.522542e-003, -5.906549e-003, -8.412200e-003, -1.110266e-002, -1.396291e-002, -1.702028e-002, -2.023735e-002, -2.358479e-002, -2.700684e-002, + -3.043529e-002, -3.366038e-002, -3.646848e-002, -3.871031e-002, -4.051018e-002, -4.186214e-002, -4.291580e-002, -4.365371e-002, -4.406761e-002, + -4.423191e-002, -4.413789e-002, -4.378445e-002, -4.321969e-002, -4.247935e-002, -4.158693e-002, -4.049991e-002, -3.923657e-002, -3.784975e-002, + -3.637195e-002, -3.478674e-002, -3.316805e-002, -3.145803e-002, -2.963673e-002, -2.771265e-002, -2.574887e-002, -2.382527e-002, -2.204817e-002, + -2.046586e-002, -1.908502e-002, -1.783134e-002, -1.673068e-002, -1.562509e-002, -1.448852e-002, -1.323276e-002, -1.189933e-002, -1.052606e-002, + -9.182220e-003, -7.918119e-003, -6.731918e-003, -5.578053e-003, -4.430532e-003, -3.212122e-003, -1.717269e-003, 1.040194e-006, 1.905626e-003, + 3.941625e-003, 6.033642e-003, 8.117241e-003, 1.015749e-002, 1.208732e-002, 1.391085e-002, 1.558632e-002, 1.708237e-002, 1.837508e-002, + 1.943789e-002, 2.025372e-002, 2.082030e-002, 2.113406e-002, 2.105474e-002, 2.062285e-002, 1.983453e-002, 1.860578e-002, 1.694913e-002, + 1.509821e-002, 1.325031e-002, 1.157746e-002, 1.028237e-002, 9.319776e-003, 8.641473e-003, 8.296810e-003, 8.303841e-003, 8.463147e-003, + 8.819560e-003, 9.344127e-003, 9.999804e-003, 1.081452e-002, 1.170784e-002, 1.267403e-002, 1.366467e-002, 1.464904e-002, 1.556256e-002, + 1.636577e-002, 1.704302e-002, 1.762171e-002, 1.810833e-002, 1.843010e-002, 1.857367e-002, 1.855183e-002, 1.840953e-002, 1.816409e-002, + 1.774000e-002, 1.716043e-002, 1.644784e-002, 1.569096e-002, 1.490404e-002, 1.402384e-002, 1.306849e-002, 1.206425e-002, 1.104704e-002, + 9.997928e-003, 8.942546e-003, 7.951334e-003, 7.073824e-003, 6.383295e-003, 5.917138e-003, 5.557447e-003, 5.233586e-003, 4.999578e-003, + 4.767273e-003, 4.567348e-003, 4.462775e-003, 4.456770e-003, 4.468095e-003, 4.509381e-003, 4.540374e-003, 4.580288e-003, 4.654521e-003, + 4.753504e-003, 4.889820e-003, 5.023418e-003, 5.073131e-003, 4.985945e-003, 4.745505e-003, 4.396557e-003, 3.970935e-003, 3.458719e-003, + 2.912104e-003, 2.288086e-003, 1.648188e-003, 9.264925e-004, 2.283719e-004, -4.196496e-004, -1.030445e-003, -1.605574e-003, -2.101392e-003, + -2.414959e-003, -2.602164e-003, -2.778986e-003, -2.768669e-003, -2.612937e-003, -2.352614e-003, -1.816637e-003, -1.084505e-003, -1.919676e-004, + 8.196221e-004, 1.924901e-003, 3.085204e-003, 4.245679e-003, 5.449790e-003, 6.576491e-003, 7.522378e-003, 8.345502e-003, 8.906589e-003, + 9.030391e-003, 8.773458e-003, 8.210692e-003, 7.473661e-003, 6.637226e-003, 5.782729e-003, 4.973796e-003, 3.977038e-003, 2.744982e-003, + 1.202035e-003, -7.269764e-004, -2.946195e-003, -5.283051e-003, -7.676133e-003, -9.926082e-003, -1.176729e-002, -1.311717e-002, -1.418911e-002, + -1.513843e-002, -1.590096e-002, -1.664537e-002, -1.737418e-002, -1.795038e-002, -1.842037e-002, -1.893250e-002, -1.937972e-002, -1.972279e-002, + -2.001546e-002, -2.032042e-002, -2.058377e-002, -2.082667e-002, -2.103949e-002, -2.122822e-002, -2.126898e-002, -2.116514e-002, -2.096586e-002, + -2.062184e-002, -2.011895e-002, -1.957030e-002, -1.894582e-002, -1.824327e-002, -1.752905e-002, -1.676768e-002, -1.593264e-002, -1.504934e-002, + -1.405314e-002, -1.307000e-002, -1.214887e-002, -1.131421e-002, -1.059897e-002, -9.962355e-003, -9.323827e-003, -8.681866e-003, -8.115931e-003, + -7.588573e-003, -7.129258e-003, -6.775399e-003, -6.454371e-003, -6.130188e-003, -5.769189e-003, -5.259930e-003, -4.548030e-003, -3.720657e-003, + -2.794778e-003, -1.782127e-003, -6.293710e-004, 7.967352e-004, 2.437844e-003, 4.113516e-003, 5.825785e-003, 7.540172e-003, 9.174169e-003, + 1.073491e-002, 1.229154e-002, 1.370033e-002, 1.489371e-002, 1.583074e-002, 1.656199e-002, 1.701140e-002, 1.702044e-002, 1.647421e-002, + 1.551873e-002, 1.441185e-002, 1.321235e-002, 1.202058e-002, 1.095902e-002, 9.952864e-003, 9.056477e-003, 8.080416e-003, 7.151087e-003, + 6.247071e-003, 5.399146e-003, 4.530389e-003, 3.786600e-003, 3.383828e-003, 3.094072e-003, 2.956218e-003, 3.046859e-003, 3.197785e-003, + 3.375202e-003, 3.533441e-003, 3.794123e-003, 3.993645e-003, 4.097964e-003, 4.184038e-003, 4.228373e-003, 4.122408e-003, 3.815368e-003, + 3.244704e-003, 2.388669e-003, 1.413433e-003, 3.385182e-004, -7.860371e-004, -1.938996e-003, -3.015892e-003, -4.164362e-003, -5.287677e-003, + -6.259267e-003, -7.035583e-003, -7.811050e-003, -8.531654e-003, -9.053277e-003, -9.370129e-003, -9.253522e-003, -8.816276e-003, -8.016489e-003, + -6.975866e-003, -5.853146e-003, -4.714792e-003, -3.578552e-003, -2.340743e-003, -9.899246e-004, 3.704971e-004, 1.782649e-003, 3.146510e-003, + 4.519147e-003, 5.929608e-003, 7.363935e-003, 8.647948e-003, 9.826751e-003, 1.090127e-002, 1.186721e-002, 1.277458e-002, 1.352247e-002, + 1.404602e-002, 1.432284e-002, 1.425350e-002, 1.380108e-002, 1.302885e-002, 1.197329e-002, 1.074339e-002, 9.472158e-003, 8.242226e-003, + 7.161245e-003, 6.380906e-003, 5.865844e-003, 5.447363e-003, 5.061718e-003, 4.720278e-003, 4.340846e-003, 4.008292e-003, 3.898278e-003, + 4.098702e-003, 4.592019e-003, 5.344940e-003, 6.177450e-003, 7.088400e-003, 8.117630e-003, 9.267160e-003, 1.031940e-002, 1.135745e-002, + 1.240766e-002, 1.339735e-002, 1.426065e-002, 1.469619e-002, 1.488603e-002, 1.483787e-002, 1.465042e-002, 1.427137e-002, 1.361141e-002, + 1.258008e-002, 1.118583e-002, 9.587671e-003, 7.565902e-003, 5.318216e-003, 3.037401e-003, 8.539639e-004, -1.155478e-003, -2.857207e-003, + -4.395995e-003, -5.713256e-003, -6.710165e-003, -7.489356e-003, -8.152912e-003, -8.797044e-003, -9.263057e-003, -9.596502e-003, -9.924186e-003, + -1.009800e-002, -1.018282e-002, -1.027376e-002, -1.045175e-002, -1.057013e-002, -1.064196e-002, -1.080933e-002, -1.103449e-002, -1.131040e-002, + -1.152079e-002, -1.167464e-002, -1.191086e-002, -1.211246e-002, -1.224332e-002, -1.246513e-002, -1.277643e-002, -1.331544e-002, -1.405308e-002, + -1.493622e-002, -1.579404e-002, -1.654438e-002, -1.729663e-002, -1.785920e-002, -1.832766e-002, -1.883196e-002, -1.949268e-002, -2.022842e-002, + -2.114275e-002, -2.205966e-002, -2.286398e-002, -2.336680e-002, -2.356865e-002, -2.340582e-002, -2.286948e-002, -2.188795e-002, -2.059611e-002, + -1.916389e-002, -1.773876e-002, -1.598523e-002, -1.430926e-002, -1.236706e-002, -1.029174e-002, -8.200068e-003, -5.982050e-003, -3.656390e-003, + -1.407786e-003, 9.839511e-004, 3.213069e-003, 5.335747e-003, 7.429873e-003, 9.581503e-003, 1.174499e-002, 1.376435e-002, 1.559577e-002, + 1.729612e-002, 1.897359e-002, 2.020417e-002, 2.110752e-002, 2.154321e-002, 2.168114e-002, 2.169016e-002, 2.161999e-002, 2.163316e-002, + 2.180160e-002, 2.170052e-002, 2.171544e-002, 2.168486e-002, 2.144117e-002, 2.098717e-002, 2.039692e-002, 1.998528e-002, 1.988235e-002, + 2.005679e-002, 2.044900e-002, 2.079830e-002, 2.128448e-002, 2.204007e-002, 2.261329e-002, 2.268444e-002, 2.267682e-002, 2.263348e-002, + 2.252209e-002, 2.213057e-002, 2.140054e-002, 2.012606e-002, 1.859418e-002, 1.680597e-002, 1.489280e-002, 1.266524e-002, 1.025876e-002, + 7.573454e-003, 4.807049e-003, 1.651448e-003, -1.526355e-003, -4.526581e-003, -7.507863e-003, -1.007024e-002, -1.232168e-002, -1.479224e-002, + -1.723593e-002, -1.911296e-002, -2.091134e-002, -2.230611e-002, -2.349948e-002, -2.449371e-002, -2.495606e-002, -2.519168e-002, -2.531697e-002, + -2.550824e-002, -2.610805e-002, -2.663825e-002, -2.695437e-002, -2.683276e-002, -2.663800e-002, -2.686426e-002, -2.730249e-002, -2.792274e-002, + -2.855388e-002, -2.888278e-002, -2.949299e-002, -2.980621e-002, -2.984723e-002, -2.931396e-002, -2.837362e-002, -2.733471e-002, -2.615015e-002, + -2.465895e-002, -2.307224e-002, -2.138107e-002, -1.976163e-002, -1.797382e-002, -1.582725e-002, -1.348870e-002, -1.068983e-002, -7.705052e-003, + -4.980787e-003, -2.318467e-003, 4.168727e-004, 3.659154e-003, 6.571984e-003, 8.942336e-003, 1.120934e-002, 1.313263e-002, 1.489564e-002, + 1.612421e-002, 1.700769e-002, 1.757036e-002, 1.801883e-002, 1.826544e-002, 1.821835e-002, 1.739990e-002, 1.629204e-002, 1.537302e-002, + 1.467968e-002, 1.446658e-002, 1.466377e-002, 1.511077e-002, 1.585060e-002, 1.671712e-002, 1.803559e-002, 1.865758e-002, 1.921346e-002, + 1.998747e-002, 2.096945e-002, 2.210208e-002, 2.332777e-002, 2.468760e-002, 2.624481e-002, 2.794235e-002, 2.962765e-002, 3.061398e-002, + 3.141400e-002, 3.234296e-002, 3.329740e-002, 3.397380e-002, 3.422033e-002, 3.456078e-002, 3.481717e-002, 3.481675e-002, 3.464256e-002, + 3.427367e-002, 3.335879e-002, 3.209125e-002, 3.036126e-002, 2.849070e-002, 2.618503e-002, 2.375118e-002, 2.171200e-002, 1.983796e-002, + 1.810896e-002, 1.595337e-002, 1.322287e-002, 1.060974e-002, 8.008892e-003, 5.488808e-003, 3.177876e-003, 6.892999e-004, -1.861419e-003, + -3.905315e-003, -5.615933e-003, -6.477035e-003, -7.722098e-003, -8.802171e-003, -9.997406e-003, -1.143432e-002, -1.252381e-002, -1.305749e-002, + -1.373092e-002, -1.430825e-002, -1.491452e-002, -1.587437e-002, -1.799698e-002, -2.028447e-002, -2.279145e-002, -2.518664e-002, -2.741485e-002, + -2.987952e-002, -3.337085e-002, -3.710501e-002, -3.897720e-002, -4.081985e-002, -4.347000e-002, -4.649629e-002, -4.841765e-002, -4.979700e-002, + -5.048892e-002, -5.040954e-002, -4.970096e-002, -4.863381e-002, -4.768624e-002, -4.706671e-002, -4.568281e-002, -4.395691e-002, -4.190381e-002, + -3.990355e-002, -3.800627e-002, -3.556637e-002, -3.288106e-002, -3.003691e-002, -2.740138e-002, -2.464016e-002, -2.240822e-002, -2.184800e-002, + -2.026057e-002, -1.822488e-002, -1.628862e-002, -1.400031e-002, -1.100350e-002, -8.228068e-003, -4.996591e-003, -1.386659e-003, 1.951940e-003, + 3.100905e-003, 3.915576e-003, 5.117104e-003, 6.359899e-003, 7.180362e-003, 8.266997e-003, 9.724186e-003, 1.130169e-002, 1.413361e-002, + 1.698530e-002, 1.885142e-002, 2.041549e-002, 2.187720e-002, 2.417740e-002, 2.637841e-002, 2.925352e-002, 3.216048e-002, 3.451182e-002, + 3.800080e-002, 4.140761e-002, 4.350901e-002, 4.445830e-002, 4.614707e-002, 4.797196e-002, 4.886392e-002, 4.951336e-002, 4.987282e-002, + 5.050930e-002, 5.088068e-002, 5.119124e-002, 5.022661e-002, 4.731176e-002, 4.628844e-002, 4.553865e-002, 4.552574e-002, 4.597417e-002, + 4.649745e-002, 4.633937e-002, 4.586071e-002, 4.531632e-002, 4.363126e-002, 4.048021e-002, 3.668521e-002, 3.171079e-002, 2.713207e-002, + 2.318774e-002, 1.711720e-002, 1.014823e-002, 3.297289e-003, -5.042592e-003, -1.366325e-002, -2.347715e-002, -3.374385e-002, -4.334974e-002, + -5.334651e-002, -5.932877e-002, -6.526050e-002, -7.100234e-002, -7.405059e-002, -7.714797e-002, -7.943400e-002, -8.326746e-002, -8.518238e-002, + -8.542316e-002, -8.574789e-002, -8.321488e-002, -8.024120e-002, -7.727505e-002, -7.213023e-002, -6.479122e-002, -5.360654e-002, -4.304064e-002, + -3.615465e-002, -2.824676e-002, -1.914719e-002, -1.053044e-002, 3.138012e-004, 1.229125e-002, 2.536504e-002, 4.203642e-002, 5.759952e-002, + 7.290663e-002, 7.889171e-002, 8.153252e-002, 8.120813e-002, 7.959665e-002, 7.350519e-002, 6.594953e-002, 5.632963e-002, 5.075325e-002, + 4.602097e-002, 4.254265e-002, 3.325535e-002, 2.223358e-002, 1.108514e-002, 2.195591e-003, -7.477773e-003, -1.870994e-002, -2.681347e-002, + -3.679152e-002, -4.646792e-002, -7.410067e-002, -9.741271e-002, -1.163309e-001, -1.310746e-001, -1.267222e-001, -9.415816e-002, -4.671337e-002, + 2.895577e-002, 1.067116e-001, 1.685572e-001, 1.989994e-001, 7.465895e-002, 8.237451e-002, 9.721190e-002, 9.244105e-002, 6.791985e-002, + 3.875252e-002, -3.855179e-003, -7.265016e-002, -1.376942e-001, -1.874766e-001, 9.983949e-002, -1.661128e-001, -1.045779e-001, -5.498856e-003, + 5.329486e-002, 6.718175e-002, 4.625283e-003, 7.501236e-002, 4.167842e-002, 1.054109e-002, 0.000000e+000}}; -const double* getEmorInvCurve(int i) -{ - return kEmorInv[i]; -} +const double* getEmorInvCurve(int i) { return kEmorInv[i]; } - -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/hdrDebevec_test.cpp b/src/aliceVision/hdr/hdrDebevec_test.cpp index bf2903d73c..63cb37ad12 100644 --- a/src/aliceVision/hdr/hdrDebevec_test.cpp +++ b/src/aliceVision/hdr/hdrDebevec_test.cpp @@ -23,7 +23,7 @@ BOOST_AUTO_TEST_CASE(hdr_debevec) hdr::rgbCurve gt_curve(quantization); std::array laguerreParams = {-0.8, 0.8, -0.3}; - for(int i = 0; i < quantization; i++) + for (int i = 0; i < quantization; i++) { float x = float(i) / float(quantization - 1); gt_curve.getCurve(0)[i] = hdr::laguerreFunction(laguerreParams[0], x); @@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(hdr_debevec) calib.process(samples, exposures, quantization, calibrationWeight, 0.001, response); response.exponential(); - for(int imageId = 0; imageId < paths.size() - 1; imageId++) + for (int imageId = 0; imageId < paths.size() - 1; imageId++) { image::Image imgA, imgB; image::readImage(paths[imageId], imgA, image::EImageColorSpace::LINEAR); @@ -60,13 +60,13 @@ BOOST_AUTO_TEST_CASE(hdr_debevec) double ratioExposures = times[imageId] / times[imageId + 1]; double max_diff = 0.0; - for(int i = 0; i < imgA.Height(); i++) + for (int i = 0; i < imgA.Height(); i++) { - for(int j = 0; j < imgA.Width(); j++) + for (int j = 0; j < imgA.Width(); j++) { image::RGBfColor Ba = imgA(i, j); image::RGBfColor Bb = imgB(i, j); - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { double responseA = response(Ba(k), k); double responseB = response(Bb(k), k); @@ -74,18 +74,20 @@ BOOST_AUTO_TEST_CASE(hdr_debevec) double hdrB = responseB / times[imageId + 1]; double diff = std::abs(responseA - ratioExposures * responseB); - if (Ba(k) < 0.01) diff = 0.0; - if (Bb(k) > 0.96) diff = 0.0; - if (hdrA > 0.99) diff = 0.0; - if (hdrB > 0.99) diff = 0.0; + if (Ba(k) < 0.01) + diff = 0.0; + if (Bb(k) > 0.96) + diff = 0.0; + if (hdrA > 0.99) + diff = 0.0; + if (hdrB > 0.99) + diff = 0.0; max_diff = std::max(diff, max_diff); - } } } - BOOST_CHECK(std::isfinite(max_diff)); BOOST_CHECK_SMALL(max_diff, 0.01); } diff --git a/src/aliceVision/hdr/hdrGrossberg_test.cpp b/src/aliceVision/hdr/hdrGrossberg_test.cpp index 887078fdd1..3a809b0607 100644 --- a/src/aliceVision/hdr/hdrGrossberg_test.cpp +++ b/src/aliceVision/hdr/hdrGrossberg_test.cpp @@ -22,18 +22,14 @@ BOOST_AUTO_TEST_CASE(hdr_grossberg) hdr::rgbCurve gt_curve(quantization); gt_curve.setEmor(0); - std::array grossberg_params[3] = { - {0.8, 0.3, 0.2}, - {-0.1, -0.3, 0.2}, - {2.1, 0.05, 0.4} - }; + std::array grossberg_params[3] = {{0.8, 0.3, 0.2}, {-0.1, -0.3, 0.2}, {2.1, 0.05, 0.4}}; - for(int dim = 0; dim < 3; dim++) + for (int dim = 0; dim < 3; dim++) { hdr::rgbCurve dim_curve(quantization); dim_curve.setEmor(dim + 1); - for(int k = 0; k < quantization; k++) + for (int k = 0; k < quantization; k++) { gt_curve.getCurve(0)[k] += grossberg_params[0][dim] * dim_curve.getCurve(0)[k]; gt_curve.getCurve(1)[k] += grossberg_params[1][dim] * dim_curve.getCurve(0)[k]; @@ -56,7 +52,7 @@ BOOST_AUTO_TEST_CASE(hdr_grossberg) hdr::test::extractSamplesGroups(samples, all_paths, exposures, quantization); calib.process(samples, exposures, quantization, response); - for(int imageId = 0; imageId < paths.size() - 1; imageId++) + for (int imageId = 0; imageId < paths.size() - 1; imageId++) { image::Image imgA, imgB; image::readImage(paths[imageId], imgA, image::EImageColorSpace::LINEAR); @@ -66,13 +62,13 @@ BOOST_AUTO_TEST_CASE(hdr_grossberg) double ratioExposures = times[imageId] / times[imageId + 1]; double max_diff = 0.0; - for(int i = 0; i < imgA.Height(); i++) + for (int i = 0; i < imgA.Height(); i++) { - for(int j = 0; j < imgA.Width(); j++) + for (int j = 0; j < imgA.Width(); j++) { image::RGBfColor Ba = imgA(i, j); image::RGBfColor Bb = imgB(i, j); - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { double responseA = response(Ba(k), k); double responseB = response(Bb(k), k); @@ -80,17 +76,18 @@ BOOST_AUTO_TEST_CASE(hdr_grossberg) double hdrB = responseB / times[imageId + 1]; double diff = std::abs(responseA - ratioExposures * responseB); - if (Bb(k) > 0.93) diff = 0.0; - if (hdrA > 0.99) diff = 0.0; - if (hdrB > 0.99) diff = 0.0; + if (Bb(k) > 0.93) + diff = 0.0; + if (hdrA > 0.99) + diff = 0.0; + if (hdrB > 0.99) + diff = 0.0; max_diff = std::max(diff, max_diff); - } } } - BOOST_CHECK(std::isfinite(max_diff)); BOOST_CHECK_SMALL(max_diff, 2.0 * 1e-3); } diff --git a/src/aliceVision/hdr/hdrLaguerre_test.cpp b/src/aliceVision/hdr/hdrLaguerre_test.cpp index d04cf8b18d..2ed1e9cecd 100644 --- a/src/aliceVision/hdr/hdrLaguerre_test.cpp +++ b/src/aliceVision/hdr/hdrLaguerre_test.cpp @@ -22,7 +22,7 @@ BOOST_AUTO_TEST_CASE(hdr_laguerre) hdr::rgbCurve gt_curve(quantization); std::array laguerreParams = {-0.2, 0.4, -0.3}; - for(int i = 0; i < quantization; i++) + for (int i = 0; i < quantization; i++) { float x = float(i) / float(quantization - 1); gt_curve.getCurve(0)[i] = hdr::laguerreFunction(laguerreParams[0], x); @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(hdr_laguerre) hdr::test::extractSamplesGroups(samples, all_paths, exposures, quantization); calib.process(samples, exposures, quantization, false, response); - for(int imageId = 0; imageId < paths.size() - 1; imageId++) + for (int imageId = 0; imageId < paths.size() - 1; imageId++) { image::Image imgA, imgB; image::readImage(paths[imageId], imgA, image::EImageColorSpace::LINEAR); @@ -53,13 +53,13 @@ BOOST_AUTO_TEST_CASE(hdr_laguerre) double ratioExposures = times[imageId] / times[imageId + 1]; double max_diff = 0.0; - for(int i = 0; i < imgA.Height(); i++) + for (int i = 0; i < imgA.Height(); i++) { - for(int j = 0; j < imgA.Width(); j++) + for (int j = 0; j < imgA.Width(); j++) { image::RGBfColor Ba = imgA(i, j); image::RGBfColor Bb = imgB(i, j); - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { double responseA = response(Ba(k), k); double responseB = response(Bb(k), k); @@ -67,17 +67,18 @@ BOOST_AUTO_TEST_CASE(hdr_laguerre) double hdrB = responseB / times[imageId + 1]; double diff = std::abs(responseA - ratioExposures * responseB); - if (Bb(k) > 0.99) diff = 0.0; - if (hdrA > 1.0) diff = 0.0; - if (hdrB > 1.0) diff = 0.0; + if (Bb(k) > 0.99) + diff = 0.0; + if (hdrA > 1.0) + diff = 0.0; + if (hdrB > 1.0) + diff = 0.0; max_diff = std::max(diff, max_diff); - } } } - BOOST_CHECK(std::isfinite(max_diff)); BOOST_CHECK_SMALL(max_diff, 1e-3); } diff --git a/src/aliceVision/hdr/hdrMerge.cpp b/src/aliceVision/hdr/hdrMerge.cpp index 07cb0eb7f5..e9c1f21c24 100644 --- a/src/aliceVision/hdr/hdrMerge.cpp +++ b/src/aliceVision/hdr/hdrMerge.cpp @@ -14,7 +14,6 @@ #include #include - namespace aliceVision { namespace hdr { @@ -42,170 +41,168 @@ inline float sigmoidInv(float zeroVal, float endVal, float sigwidth, float sigMi return zeroVal + (endVal - zeroVal) * (1.0f / (1.0f + expf(10.0f * ((sigMid - xval) / sigwidth)))); } -void hdrMerge::process(const std::vector< image::Image > &images, - const std::vector ×, - const rgbCurve &weight, - const rgbCurve &response, - image::Image &radiance, - image::Image &lowLight, - image::Image &highLight, - image::Image &noMidLight, - MergingParams &mergingParams) +void hdrMerge::process(const std::vector>& images, + const std::vector& times, + const rgbCurve& weight, + const rgbCurve& response, + image::Image& radiance, + image::Image& lowLight, + image::Image& highLight, + image::Image& noMidLight, + MergingParams& mergingParams) { - //checks - assert(!response.isEmpty()); - assert(!images.empty()); - assert(images.size() == times.size()); - - // get images width, height - const std::size_t width = images.front().Width(); - const std::size_t height = images.front().Height(); - - // resize and reset radiance image to 0.0 - radiance.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); - - ALICEVISION_LOG_TRACE("[hdrMerge] Images to fuse:"); - for(int i = 0; i < images.size(); ++i) - { - ALICEVISION_LOG_TRACE(images[i].Width() << "x" << images[i].Height() << ", time: " << times[i]); - } - - rgbCurve weightShortestExposure = weight; - weightShortestExposure.freezeSecondPartValues(); - rgbCurve weightLongestExposure = weight; - weightLongestExposure.freezeFirstPartValues(); - - const std::vector v_minValue = {response(mergingParams.minSignificantValue, 0), - response(mergingParams.minSignificantValue, 1), - response(mergingParams.minSignificantValue, 2)}; - const std::vector v_maxValue = {response(mergingParams.maxSignificantValue, 0), - response(mergingParams.maxSignificantValue, 1), - response(mergingParams.maxSignificantValue, 2)}; - - highLight.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); - lowLight.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); - noMidLight.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); - - #pragma omp parallel for - for(int y = 0; y < height; ++y) - { - for(int x = 0; x < width; ++x) + // checks + assert(!response.isEmpty()); + assert(!images.empty()); + assert(images.size() == times.size()); + + // get images width, height + const std::size_t width = images.front().Width(); + const std::size_t height = images.front().Height(); + + // resize and reset radiance image to 0.0 + radiance.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); + + ALICEVISION_LOG_TRACE("[hdrMerge] Images to fuse:"); + for (int i = 0; i < images.size(); ++i) + { + ALICEVISION_LOG_TRACE(images[i].Width() << "x" << images[i].Height() << ", time: " << times[i]); + } + + rgbCurve weightShortestExposure = weight; + weightShortestExposure.freezeSecondPartValues(); + rgbCurve weightLongestExposure = weight; + weightLongestExposure.freezeFirstPartValues(); + + const std::vector v_minValue = { + response(mergingParams.minSignificantValue, 0), response(mergingParams.minSignificantValue, 1), response(mergingParams.minSignificantValue, 2)}; + const std::vector v_maxValue = { + response(mergingParams.maxSignificantValue, 0), response(mergingParams.maxSignificantValue, 1), response(mergingParams.maxSignificantValue, 2)}; + + highLight.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); + lowLight.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); + noMidLight.resize(width, height, true, image::RGBfColor(0.f, 0.f, 0.f)); + +#pragma omp parallel for + for (int y = 0; y < height; ++y) { - //for each pixels - image::RGBfColor& radianceColor = radiance(y, x); - image::RGBfColor& highLightColor = highLight(y, x); - image::RGBfColor& lowLightColor = lowLight(y, x); - image::RGBfColor& noMidLightColor = noMidLight(y, x); - - std::vector> vv_coeff; - std::vector> vv_value; - std::vector> vv_normalizedValue; - - // Compute merging range - std::vector v_firstIndex; - std::vector v_lastIndex; - for(std::size_t channel = 0; channel < 3; ++channel) + for (int x = 0; x < width; ++x) { - int firstIndex = mergingParams.refImageIndex; - while(firstIndex > 0 && (response(images[firstIndex](y, x)(channel), channel) > v_minValue[channel] || - firstIndex == images.size() - 1)) + // for each pixels + image::RGBfColor& radianceColor = radiance(y, x); + image::RGBfColor& highLightColor = highLight(y, x); + image::RGBfColor& lowLightColor = lowLight(y, x); + image::RGBfColor& noMidLightColor = noMidLight(y, x); + + std::vector> vv_coeff; + std::vector> vv_value; + std::vector> vv_normalizedValue; + + // Compute merging range + std::vector v_firstIndex; + std::vector v_lastIndex; + for (std::size_t channel = 0; channel < 3; ++channel) { - firstIndex--; + int firstIndex = mergingParams.refImageIndex; + while (firstIndex > 0 && + (response(images[firstIndex](y, x)(channel), channel) > v_minValue[channel] || firstIndex == images.size() - 1)) + { + firstIndex--; + } + v_firstIndex.push_back(firstIndex); + + int lastIndex = v_firstIndex[channel] + 1; + while (lastIndex < images.size() - 1 && response(images[lastIndex](y, x)(channel), channel) < v_maxValue[channel]) + { + lastIndex++; + } + v_lastIndex.push_back(lastIndex); } - v_firstIndex.push_back(firstIndex); - int lastIndex = v_firstIndex[channel] + 1; - while(lastIndex < images.size() - 1 && response(images[lastIndex](y, x)(channel), channel) < v_maxValue[channel]) + // Compute merging coeffs and values to be merged + for (std::size_t channel = 0; channel < 3; ++channel) { - lastIndex++; - } - v_lastIndex.push_back(lastIndex); - } + std::vector v_coeff; + std::vector v_normalizedValue; + std::vector v_value; - // Compute merging coeffs and values to be merged - for(std::size_t channel = 0; channel < 3; ++channel) - { - std::vector v_coeff; - std::vector v_normalizedValue; - std::vector v_value; + for (std::size_t e = 0; e < images.size(); ++e) + { + const double value = images[e](y, x)(channel); + const double resp = response(value, channel); + const double normalizedValue = resp / times[e]; + double coeff = std::max(0.001f, + e == 0 ? weightShortestExposure(value, channel) + : (e == images.size() - 1 ? weightLongestExposure(value, channel) : weight(value, channel))); + + v_value.push_back(value); + v_normalizedValue.push_back(normalizedValue); + v_coeff.push_back(coeff); + } - for(std::size_t e = 0; e < images.size(); ++e) - { - const double value = images[e](y, x)(channel); - const double resp = response(value, channel); - const double normalizedValue = resp / times[e]; - double coeff = std::max(0.001f, e == 0 ? weightShortestExposure(value, channel) : - (e == images.size() - 1 ? weightLongestExposure(value, channel) : - weight(value, channel))); - - v_value.push_back(value); - v_normalizedValue.push_back(normalizedValue); - v_coeff.push_back(coeff); + vv_coeff.push_back(v_coeff); + vv_normalizedValue.push_back(v_normalizedValue); + vv_value.push_back(v_value); } - vv_coeff.push_back(v_coeff); - vv_normalizedValue.push_back(v_normalizedValue); - vv_value.push_back(v_value); - } - - // Compute light masks if required (monitoring and debug purposes) - if(mergingParams.computeLightMasks) - { - for(std::size_t channel = 0; channel < 3; ++channel) + // Compute light masks if required (monitoring and debug purposes) + if (mergingParams.computeLightMasks) { - int idxMaxValue = 0; - int idxMinValue = 0; - double maxValue = 0.0; - double minValue = 10000.0; - bool jump = true; - for(std::size_t e = 0; e < images.size(); ++e) + for (std::size_t channel = 0; channel < 3; ++channel) { - if(vv_value[channel][e] > maxValue) + int idxMaxValue = 0; + int idxMinValue = 0; + double maxValue = 0.0; + double minValue = 10000.0; + bool jump = true; + for (std::size_t e = 0; e < images.size(); ++e) { - maxValue = vv_value[channel][e]; - idxMaxValue = e; + if (vv_value[channel][e] > maxValue) + { + maxValue = vv_value[channel][e]; + idxMaxValue = e; + } + if (vv_value[channel][e] < minValue) + { + minValue = vv_value[channel][e]; + idxMinValue = e; + } + jump = jump && ((vv_value[channel][e] < mergingParams.minSignificantValue && e < images.size() - 1) || + (vv_value[channel][e] > mergingParams.maxSignificantValue && e > 0)); } - if(vv_value[channel][e] < minValue) - { - minValue = vv_value[channel][e]; - idxMinValue = e; - } - jump = jump && ((vv_value[channel][e] < mergingParams.minSignificantValue && e < images.size() - 1) || - (vv_value[channel][e] > mergingParams.maxSignificantValue && e > 0)); + highLightColor(channel) = minValue > mergingParams.maxSignificantValue ? 1.0 : 0.0; + lowLightColor(channel) = maxValue < mergingParams.minSignificantValue ? 1.0 : 0.0; + noMidLightColor(channel) = jump ? 1.0 : 0.0; } - highLightColor(channel) = minValue > mergingParams.maxSignificantValue ? 1.0 : 0.0; - lowLightColor(channel) = maxValue < mergingParams.minSignificantValue ? 1.0 : 0.0; - noMidLightColor(channel) = jump ? 1.0 : 0.0; } - } - // Compute the final result and adjust the exposure to the reference one. - for(std::size_t channel = 0; channel < 3; ++channel) - { - double v = 0.0; - double sumCoeff = 0.0; - for(std::size_t i = v_firstIndex[channel]; i <= v_lastIndex[channel]; ++i) + // Compute the final result and adjust the exposure to the reference one. + for (std::size_t channel = 0; channel < 3; ++channel) { - v += vv_coeff[channel][i] * vv_normalizedValue[channel][i]; - sumCoeff += vv_coeff[channel][i]; + double v = 0.0; + double sumCoeff = 0.0; + for (std::size_t i = v_firstIndex[channel]; i <= v_lastIndex[channel]; ++i) + { + v += vv_coeff[channel][i] * vv_normalizedValue[channel][i]; + sumCoeff += vv_coeff[channel][i]; + } + radianceColor(channel) = + mergingParams.targetCameraExposure * (sumCoeff != 0.0 ? v / sumCoeff : vv_normalizedValue[channel][mergingParams.refImageIndex]); } - radianceColor(channel) = mergingParams.targetCameraExposure * - (sumCoeff != 0.0 ? v / sumCoeff : vv_normalizedValue[channel][mergingParams.refImageIndex]); } } - } } -void hdrMerge::postProcessHighlight(const std::vector< image::Image > &images, - const std::vector ×, - const rgbCurve &weight, - const rgbCurve &response, - image::Image &radiance, - float targetCameraExposure, - float highlightCorrectionFactor, - float highlightTargetLux) +void hdrMerge::postProcessHighlight(const std::vector>& images, + const std::vector& times, + const rgbCurve& weight, + const rgbCurve& response, + image::Image& radiance, + float targetCameraExposure, + float highlightCorrectionFactor, + float highlightTargetLux) { - //checks + // checks assert(!response.isEmpty()); assert(!images.empty()); assert(images.size() == times.size()); @@ -228,8 +225,8 @@ void hdrMerge::postProcessHighlight(const std::vector< image::Image radianceColor(channel)) + if (highlightTarget > radianceColor(channel)) { radianceColor(channel) = float(clampingCompensation * highlightTarget + clampingCompensationInv * radianceColor(channel)); } @@ -274,5 +271,5 @@ void hdrMerge::postProcessHighlight(const std::vector< image::Image #include - namespace aliceVision { namespace hdr { @@ -21,33 +20,37 @@ struct MergingParams int refImageIndex; bool computeLightMasks = false; }; - -class hdrMerge { -public: - /** - * @brief - * @param images - * @param radiance - * @param times - * @param targetCameraExposure - * @param response - */ - void process(const std::vector>& images, const std::vector& times, - const rgbCurve& weight, const rgbCurve& response, image::Image& radiance, - image::Image& lowLight, image::Image& highLight, image::Image& noMidLight, +class hdrMerge +{ + public: + /** + * @brief + * @param images + * @param radiance + * @param times + * @param targetCameraExposure + * @param response + */ + void process(const std::vector>& images, + const std::vector& times, + const rgbCurve& weight, + const rgbCurve& response, + image::Image& radiance, + image::Image& lowLight, + image::Image& highLight, + image::Image& noMidLight, MergingParams& mergingParams); - void postProcessHighlight(const std::vector< image::Image > &images, - const std::vector ×, - const rgbCurve &weight, - const rgbCurve &response, - image::Image &radiance, - float clampedValueCorrection, - float targetCameraExposure, - float highlightMaxLumimance); - + void postProcessHighlight(const std::vector>& images, + const std::vector& times, + const rgbCurve& weight, + const rgbCurve& response, + image::Image& radiance, + float clampedValueCorrection, + float targetCameraExposure, + float highlightMaxLumimance); }; -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/hdrTestCommon.hpp b/src/aliceVision/hdr/hdrTestCommon.hpp index 085b04ec64..d0475ab45c 100644 --- a/src/aliceVision/hdr/hdrTestCommon.hpp +++ b/src/aliceVision/hdr/hdrTestCommon.hpp @@ -29,7 +29,6 @@ bool extractSamplesGroups(std::vector>& out_samples, for (int idGroup = 0; idGroup < imagePaths.size(); idGroup++) { - int width = 0; int height = 0; image::readImageSize(imagePaths[idGroup][0], width, height); @@ -45,10 +44,8 @@ bool extractSamplesGroups(std::vector>& out_samples, } std::vector groupSamples; - if (!Sampling::extractSamplesFromImages(groupSamples, imagePaths[idGroup], viewIds, - times[idGroup], width, height, - channelQuantization, imgReadOptions, - Sampling::Params{})) + if (!Sampling::extractSamplesFromImages( + groupSamples, imagePaths[idGroup], viewIds, times[idGroup], width, height, channelQuantization, imgReadOptions, Sampling::Params{})) { return false; } @@ -58,11 +55,10 @@ bool extractSamplesGroups(std::vector>& out_samples, for (int idGroup = 0; idGroup < imagePaths.size(); idGroup++) { - std::vector& groupSamples = nonFilteredSamples[idGroup]; - for (int idSample = 0; idSample < groupSamples.size(); idSample++) { - + for (int idSample = 0; idSample < groupSamples.size(); idSample++) + { SampleRef s; s.first = idGroup; s.second = idSample; @@ -71,17 +67,15 @@ bool extractSamplesGroups(std::vector>& out_samples, for (int idDesc = 0; idDesc < sample.descriptions.size(); idDesc++) { - UniqueDescriptor desc; desc.exposure = sample.descriptions[idDesc].exposure; - for (int channel = 0; channel < 3; channel++) { - + for (int channel = 0; channel < 3; channel++) + { desc.channel = channel; /* Get quantized value */ - desc.quantizedValue = int(std::round(sample.descriptions[idDesc].mean(channel) * - (channelQuantization - 1))); + desc.quantizedValue = int(std::round(sample.descriptions[idDesc].mean(channel) * (channelQuantization - 1))); if (desc.quantizedValue < 0 || desc.quantizedValue >= channelQuantization) { continue; @@ -96,16 +90,16 @@ bool extractSamplesGroups(std::vector>& out_samples, const size_t maxCountSample = 100; std::random_device rd; std::mt19937 gen(rd()); - for (auto & list : mapSampleRefList) + for (auto& list : mapSampleRefList) { if (list.second.size() > maxCountSample) { - /*Shuffle and ignore the exceeding samples*/ + /*Shuffle and ignore the exceeding samples*/ std::shuffle(list.second.begin(), list.second.end(), gen); list.second.resize(maxCountSample); } - for (auto & item : list.second) + for (auto& item : list.second) { if (nonFilteredSamples[item.first][item.second].descriptions.size() > 0) { @@ -118,8 +112,7 @@ bool extractSamplesGroups(std::vector>& out_samples, return true; } -bool buildBrackets(std::vector& paths, std::vector& times, - const rgbCurve& gt_response) +bool buildBrackets(std::vector& paths, std::vector& times, const rgbCurve& gt_response) { /* Exposure time for each bracket */ times = {0.05f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f}; @@ -134,11 +127,11 @@ bool buildBrackets(std::vector& paths, std::vector& times, /* Generate a random image made of flat blocks (to pass variancetest )*/ image::Image img(512, 512, true, image::RGBfColor(0.0f)); int val = 0; - for(int i = 0; i < img.Height(); i++) + for (int i = 0; i < img.Height(); i++) { int y = i / size_region; - for(int j = 0; j < img.Width(); j++) + for (int j = 0; j < img.Width(); j++) { int x = j / size_region; int val = y * regions_count_per_col + x; @@ -152,16 +145,16 @@ bool buildBrackets(std::vector& paths, std::vector& times, } } - for(double time : times) + for (double time : times) { image::Image img_bracket(img.Width(), img.Height()); - for(int i = 0; i < img.Height(); i++) + for (int i = 0; i < img.Height(); i++) { - for(int j = 0; j < img.Width(); j++) + for (int j = 0; j < img.Width(); j++) { image::RGBfColor color = img(i, j); - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { float radiance = color[k]; float radiance_dt = radiance * time; @@ -178,15 +171,13 @@ bool buildBrackets(std::vector& paths, std::vector& times, ALICEVISION_LOG_INFO("writing to " << temp.string()); - image::writeImage(temp.string(), img_bracket, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::LINEAR)); + image::writeImage(temp.string(), img_bracket, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::LINEAR)); paths.push_back(temp.string()); } return true; } -} // namespace test -} // namespace hdr -} // namespace aliceVision - +} // namespace test +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/rgbCurve.cpp b/src/aliceVision/hdr/rgbCurve.cpp index 6c9dddb7de..0f26c69dd6 100644 --- a/src/aliceVision/hdr/rgbCurve.cpp +++ b/src/aliceVision/hdr/rgbCurve.cpp @@ -16,42 +16,50 @@ #include - namespace aliceVision { namespace hdr { - + rgbCurve::rgbCurve(std::size_t size) { - for(auto &curve : _data) + for (auto& curve : _data) { curve.resize(size); } setZero(); } -rgbCurve::rgbCurve(const std::string &path) -{ - read(path); -} +rgbCurve::rgbCurve(const std::string& path) { read(path); } void rgbCurve::setFunction(EFunctionType functionType) { - switch(functionType) - { - case EFunctionType::LINEAR: setLinear(); return; - case EFunctionType::GAUSSIAN: setGaussian(); return; - case EFunctionType::TRIANGLE: setTriangular(); return; - case EFunctionType::PLATEAU: setPlateauSigmoid(); return; - case EFunctionType::GAMMA: setGamma(); return; - case EFunctionType::LOG10: setLog10(); return; + switch (functionType) + { + case EFunctionType::LINEAR: + setLinear(); + return; + case EFunctionType::GAUSSIAN: + setGaussian(); + return; + case EFunctionType::TRIANGLE: + setTriangular(); + return; + case EFunctionType::PLATEAU: + setPlateauSigmoid(); + return; + case EFunctionType::GAMMA: + setGamma(); + return; + case EFunctionType::LOG10: + setLog10(); + return; } throw std::out_of_range("Invalid function type enum"); } -void rgbCurve::setLinear() +void rgbCurve::setLinear() { const float coefficient = 1.f / static_cast(getSize() - 1); - for(std::size_t i = 0; i < getSize(); ++i) + for (std::size_t i = 0; i < getSize(); ++i) { setAllChannels(i, i * coefficient); } @@ -60,106 +68,106 @@ void rgbCurve::setLinear() void rgbCurve::setGamma() { const float coefficient = 1.f / static_cast(getSize() - 1); - for(std::size_t i = 0; i < getSize(); ++i) + for (std::size_t i = 0; i < getSize(); ++i) { setAllChannels(i, std::pow(4.0f * i * coefficient, 1.7f) + 1e-4); } } -void rgbCurve::setEmor(size_t dim ) +void rgbCurve::setEmor(size_t dim) { - const std::size_t emorSize = std::pow(2, 10); - const std::size_t curveSize = getSize(); - const double* ptrf0 = getEmorCurve(dim); - - std::vector f0; - if(curveSize == emorSize) - { - for(auto &curve : _data) - curve.assign(ptrf0, ptrf0 + emorSize); - } - else if(emorSize > curveSize) - { - f0.assign(ptrf0, ptrf0 + emorSize); - std::vector emor = std::vector(f0.begin(), f0.end()); + const std::size_t emorSize = std::pow(2, 10); + const std::size_t curveSize = getSize(); + const double* ptrf0 = getEmorCurve(dim); - std::size_t step = emorSize/curveSize; - for(auto &curve : _data) + std::vector f0; + if (curveSize == emorSize) { - for(std::size_t i = 0; i emor = std::vector(f0.begin(), f0.end()); + else if (emorSize > curveSize) + { + f0.assign(ptrf0, ptrf0 + emorSize); + std::vector emor = std::vector(f0.begin(), f0.end()); - std::size_t step = curveSize/emorSize; - for(auto &curve : _data) + std::size_t step = emorSize / curveSize; + for (auto& curve : _data) + { + for (std::size_t i = 0; i < curveSize; ++i) + curve.at(i) = emor.at(step * i); + } + } + else { - for(std::size_t i = 0; i emor = std::vector(f0.begin(), f0.end()); + + std::size_t step = curveSize / emorSize; + for (auto& curve : _data) + { + for (std::size_t i = 0; i < emorSize - 1; ++i) + curve.at(i * step) = emor.at(i); + curve.at(emorSize * step - 1) = emor.at(emorSize - 1); + } + interpolateMissingValues(); } - interpolateMissingValues(); - } } -void rgbCurve::setEmorInv(size_t dim ) +void rgbCurve::setEmorInv(size_t dim) { - const std::size_t emorSize = std::pow(2, 10); - const std::size_t curveSize = getSize(); - const double* ptrf0 = getEmorInvCurve(dim); - - std::vector f0; - if(curveSize == emorSize) - { - for(auto &curve : _data) - curve.assign(ptrf0, ptrf0 + emorSize); - } - else if(emorSize > curveSize) - { - f0.assign(ptrf0, ptrf0 + emorSize); - std::vector emor = std::vector(f0.begin(), f0.end()); + const std::size_t emorSize = std::pow(2, 10); + const std::size_t curveSize = getSize(); + const double* ptrf0 = getEmorInvCurve(dim); - std::size_t step = emorSize/curveSize; - for(auto &curve : _data) + std::vector f0; + if (curveSize == emorSize) { - for(std::size_t i = 0; i emor = std::vector(f0.begin(), f0.end()); + else if (emorSize > curveSize) + { + f0.assign(ptrf0, ptrf0 + emorSize); + std::vector emor = std::vector(f0.begin(), f0.end()); - std::size_t step = curveSize/emorSize; - for(auto &curve : _data) + std::size_t step = emorSize / curveSize; + for (auto& curve : _data) + { + for (std::size_t i = 0; i < curveSize; ++i) + curve.at(i) = emor.at(step * i); + } + } + else { - for(std::size_t i = 0; i emor = std::vector(f0.begin(), f0.end()); + + std::size_t step = curveSize / emorSize; + for (auto& curve : _data) + { + for (std::size_t i = 0; i < emorSize - 1; ++i) + curve.at(i * step) = emor.at(i); + curve.at(emorSize * step - 1) = emor.at(emorSize - 1); + } + interpolateMissingValues(); } - interpolateMissingValues(); - } } void rgbCurve::setGaussian(double mu, double sigma) { // https://www.desmos.com/calculator/s3q3ow1mpy - for(std::size_t i = 0; i < getSize(); ++i) + for (std::size_t i = 0; i < getSize(); ++i) { float factor = i / (static_cast(getSize() - 1)) - mu; - setAllChannels(i, std::exp( -factor * factor / (2.0 * sigma * sigma))); + setAllChannels(i, std::exp(-factor * factor / (2.0 * sigma * sigma))); } } void rgbCurve::setTriangular() { const float coefficient = 2.f / static_cast(getSize() - 1); - for(std::size_t i = 0; i < getSize(); ++i) + for (std::size_t i = 0; i < getSize(); ++i) { float value = i * coefficient; if (value > 1.0f) @@ -170,13 +178,12 @@ void rgbCurve::setTriangular() } } - void rgbCurve::setPlateau(float weight) { // https://www.desmos.com/calculator/mouwyuvjvw const float coefficient = 1.f / static_cast(getSize() - 1); - for(std::size_t i = 0; i < getSize(); ++i) + for (std::size_t i = 0; i < getSize(); ++i) { setAllChannels(i, 1.0f - std::pow((2.0f * i * coefficient - 1.0f), weight)); } @@ -202,7 +209,7 @@ void rgbCurve::setLog10() const float coefficient = 1.f / static_cast(getSize() - 1); const float logInverseNorm = 1.0f / 0.0625f; const float logInverseMaxValue = 1.0f / 1e8; - for(std::size_t i = 0; i < getSize(); ++i) + for (std::size_t i = 0; i < getSize(); ++i) { setAllChannels(i, logInverseMaxValue * std::pow(10.0f, (i * coefficient * logInverseNorm) - 8.f)); } @@ -210,11 +217,11 @@ void rgbCurve::setLog10() void rgbCurve::inverseAllValues() { - for(auto &curve : _data) + for (auto& curve : _data) { - for(auto &value : curve) + for (auto& value : curve) { - if(value != 0.f) + if (value != 0.f) { value = 1.f / value; } @@ -224,7 +231,7 @@ void rgbCurve::inverseAllValues() void rgbCurve::freezeFirstPartValues() { - for (auto &curve : _data) + for (auto& curve : _data) { std::size_t midIndex = (curve.size() / 2); for (std::size_t i = 0; i < midIndex; ++i) @@ -236,7 +243,7 @@ void rgbCurve::freezeFirstPartValues() void rgbCurve::freezeSecondPartValues() { - for (auto &curve : _data) + for (auto& curve : _data) { std::size_t midIndex = (curve.size() / 2); for (std::size_t i = midIndex + 1; i < curve.size(); ++i) @@ -248,9 +255,9 @@ void rgbCurve::freezeSecondPartValues() void rgbCurve::invertAndScaleSecondPart(float scale) { - for (auto &curve : _data) + for (auto& curve : _data) { - for (std::size_t i = curve.size()/2; i < curve.size(); ++i) + for (std::size_t i = curve.size() / 2; i < curve.size(); ++i) { curve[i] = (1.f - curve[i]) * scale; } @@ -259,9 +266,9 @@ void rgbCurve::invertAndScaleSecondPart(float scale) void rgbCurve::setAllAbsolute() { - for(auto &curve : _data) + for (auto& curve : _data) { - for(auto &value : curve) + for (auto& value : curve) { value = std::abs(value); } @@ -270,14 +277,16 @@ void rgbCurve::setAllAbsolute() void rgbCurve::normalize() { - for(auto &curve : _data) + for (auto& curve : _data) { std::size_t first = 0; std::size_t last = curve.size() - 1; // find first and last value not null - for (; (first < curve.size()) && (curve[first] == 0) ; ++first); - for (; (last > 0) && (curve[last] == 0) ; --last); + for (; (first < curve.size()) && (curve[first] == 0); ++first) + ; + for (; (last > 0) && (curve[last] == 0); --last) + ; std::size_t middle = first + ((last - first) / 2); float midValue = curve[middle]; @@ -285,14 +294,15 @@ void rgbCurve::normalize() if (midValue == 0.0f) { // find first non-zero middle response - for (; (middle < last) && (curve[middle] == 0.0f) ; ++middle); + for (; (middle < last) && (curve[middle] == 0.0f); ++middle) + ; midValue = curve[middle]; } -// ALICEVISION_LOG_TRACE("-> middle [" << middle << "]: " << midValue); + // ALICEVISION_LOG_TRACE("-> middle [" << middle << "]: " << midValue); const float coefficient = 1 / midValue; - for(auto &value : curve) + for (auto& value : curve) { value *= coefficient; } @@ -303,45 +313,46 @@ void rgbCurve::scale() { float minTot = std::numeric_limits::max(); float maxTot = std::numeric_limits::min(); - for(auto &curve : _data) + for (auto& curve : _data) { float minV = *std::min_element(curve.begin(), curve.end()); float maxV = *std::max_element(curve.begin(), curve.end()); minTot = std::min(minTot, minV); maxTot = std::max(maxTot, maxV); } - for(auto &curve : _data) - for(auto &value : curve) + for (auto& curve : _data) + for (auto& value : curve) value = (value - minTot) / (maxTot - minTot); } void rgbCurve::scaleChannelWise() { - for(auto &curve : _data) + for (auto& curve : _data) { float minV = *std::min_element(curve.begin(), curve.end()); float maxV = *std::max_element(curve.begin(), curve.end()); - for(auto &value : curve) { + for (auto& value : curve) + { value = (value - minV) / (maxV - minV); } - } + } } void rgbCurve::interpolateMissingValues() { - for(auto &curve : _data) + for (auto& curve : _data) { std::size_t previousValidIndex = 0; - for(std::size_t index = 1; index < curve.size(); ++index) + for (std::size_t index = 1; index < curve.size(); ++index) { - if(curve[index] != 0.0f) + if (curve[index] != 0.0f) { - if(previousValidIndex+1 < index) + if (previousValidIndex + 1 < index) { const float inter = (curve[index] - curve[previousValidIndex]) / (index - previousValidIndex); - for(std::size_t j = previousValidIndex+1; j < index; ++j) + for (std::size_t j = previousValidIndex + 1; j < index; ++j) { - curve[j] = curve[previousValidIndex] + inter * (j-previousValidIndex); + curve[j] = curve[previousValidIndex] + inter * (j - previousValidIndex); } } previousValidIndex = index; @@ -352,130 +363,120 @@ void rgbCurve::interpolateMissingValues() void rgbCurve::exponential() { - for(auto &curve : _data) + for (auto& curve : _data) { - for(auto &value : curve) + for (auto& value : curve) value = std::exp(value); } } -float rgbCurve::operator() (float sample, std::size_t channel) const +float rgbCurve::operator()(float sample, std::size_t channel) const { - assert(channel < _data.size()); - - float fractionalPart = 0.0; - std::size_t infIndex = getIndex(sample, fractionalPart); - - /* Do not interpolate 1.0 */ - if (infIndex == getSize() - 1) { - return _data[channel][infIndex]; - } + assert(channel < _data.size()); - return (1.0f - fractionalPart) * _data[channel][infIndex] + fractionalPart * _data[channel][infIndex + 1]; -} + float fractionalPart = 0.0; + std::size_t infIndex = getIndex(sample, fractionalPart); -const rgbCurve rgbCurve::operator+(const rgbCurve &other) const -{ - return sum(other); -} + /* Do not interpolate 1.0 */ + if (infIndex == getSize() - 1) + { + return _data[channel][infIndex]; + } -const rgbCurve rgbCurve::operator-(const rgbCurve &other) const -{ - return subtract(other); + return (1.0f - fractionalPart) * _data[channel][infIndex] + fractionalPart * _data[channel][infIndex + 1]; } -void rgbCurve::operator*=(const rgbCurve &other) -{ - multiply(other); -} +const rgbCurve rgbCurve::operator+(const rgbCurve& other) const { return sum(other); } -const rgbCurve rgbCurve::operator*(const float coefficient) -{ - return multiply(coefficient); -} +const rgbCurve rgbCurve::operator-(const rgbCurve& other) const { return subtract(other); } + +void rgbCurve::operator*=(const rgbCurve& other) { multiply(other); } + +const rgbCurve rgbCurve::operator*(const float coefficient) { return multiply(coefficient); } -const rgbCurve rgbCurve::sum(const rgbCurve &other) const +const rgbCurve rgbCurve::sum(const rgbCurve& other) const { assert(getSize() == other.getSize()); rgbCurve sum = rgbCurve(*this); - for(std::size_t channel = 0; channel < getNbChannels(); ++channel) + for (std::size_t channel = 0; channel < getNbChannels(); ++channel) { - auto &sumCurve = sum.getCurve(channel); - const auto &otherCurve = other.getCurve(channel); + auto& sumCurve = sum.getCurve(channel); + const auto& otherCurve = other.getCurve(channel); - //sum member channel by the other channel - std::transform (sumCurve.begin(), sumCurve.end(), otherCurve.begin(), sumCurve.begin(), std::plus()); + // sum member channel by the other channel + std::transform(sumCurve.begin(), sumCurve.end(), otherCurve.begin(), sumCurve.begin(), std::plus()); } return sum; } -const rgbCurve rgbCurve::subtract(const rgbCurve &other) const +const rgbCurve rgbCurve::subtract(const rgbCurve& other) const { assert(getSize() == other.getSize()); rgbCurve sub = rgbCurve(*this); - for(std::size_t channel = 0; channel < getNbChannels(); ++channel) + for (std::size_t channel = 0; channel < getNbChannels(); ++channel) { - auto &subCurve = sub.getCurve(channel); - const auto &otherCurve = other.getCurve(channel); + auto& subCurve = sub.getCurve(channel); + const auto& otherCurve = other.getCurve(channel); - //subtract member channel by the other channel - std::transform (subCurve.begin(), subCurve.end(), otherCurve.begin(), subCurve.begin(), std::minus()); + // subtract member channel by the other channel + std::transform(subCurve.begin(), subCurve.end(), otherCurve.begin(), subCurve.begin(), std::minus()); } return sub; } -void rgbCurve::multiply(const rgbCurve &other) +void rgbCurve::multiply(const rgbCurve& other) { assert(getSize() == other.getSize()); - for(std::size_t channel = 0; channel < getNbChannels(); ++channel) + for (std::size_t channel = 0; channel < getNbChannels(); ++channel) { - auto &curve = getCurve(channel); - const auto &otherCurve = other.getCurve(channel); + auto& curve = getCurve(channel); + const auto& otherCurve = other.getCurve(channel); - //multiply member channel by the other channel - std::transform (curve.begin(), curve.end(), otherCurve.begin(), curve.begin(), std::multiplies()); + // multiply member channel by the other channel + std::transform(curve.begin(), curve.end(), otherCurve.begin(), curve.begin(), std::multiplies()); } } const rgbCurve rgbCurve::multiply(const float coefficient) { - for(auto &curve : _data) - for(auto &value : curve) value *= coefficient; + for (auto& curve : _data) + for (auto& value : curve) + value *= coefficient; return (*this); } const rgbCurve rgbCurve::meanCurves() const { - std::size_t nbChannels = getNbChannels(); - rgbCurve mean = rgbCurve(nbChannels); - std::vector calculateMean(getSize()); + std::size_t nbChannels = getNbChannels(); + rgbCurve mean = rgbCurve(nbChannels); + std::vector calculateMean(getSize()); - for(auto &curve : _data) - std::transform (calculateMean.begin(), calculateMean.end(), curve.begin(), calculateMean.begin(), std::plus()); + for (auto& curve : _data) + std::transform(calculateMean.begin(), calculateMean.end(), curve.begin(), calculateMean.begin(), std::plus()); - for(std::size_t channel = 0; channel xBin(getCurveRed().size()); std::iota(xBin.begin(), xBin.end(), 0); - std::pair< std::pair, std::pair > range = autoJSXGraphViewport(xBin, getCurveRed()); + std::pair, std::pair> range = autoJSXGraphViewport(xBin, getCurveRed()); JSXGraphWrapper jsxGraph; jsxGraph.init(title, 800, 600); @@ -511,52 +512,53 @@ void rgbCurve::writeHtml(const std::string& path, const std::string& title) cons htmlFileStream << jsxGraph.toStr(); } - -void rgbCurve::read(const std::string &path) +void rgbCurve::read(const std::string& path) { std::ifstream file(path); - std::vector > fileData; + std::vector> fileData; - if(!file) + if (!file) { throw std::logic_error("Can't open curves file"); } - //create fileData + // create fileData while (file) { std::string line; - if (!getline( file, line )) break; + if (!getline(file, line)) + break; - std::istringstream sline( line ); + std::istringstream sline(line); std::vector record; while (sline) { std::string value; - if (!getline( sline, value, ',' )) break; - record.push_back( value ); + if (!getline(sline, value, ',')) + break; + record.push_back(value); } - fileData.push_back( record ); + fileData.push_back(record); } - //clear rgbCurve - for(auto &curve : _data) + // clear rgbCurve + for (auto& curve : _data) { curve.clear(); } - //fill rgbCurve + // fill rgbCurve try { - for(std::size_t line = 1; line < fileData.size(); ++line) + for (std::size_t line = 1; line < fileData.size(); ++line) { _data[0].push_back(std::stof(fileData[line][1])); _data[1].push_back(std::stof(fileData[line][2])); _data[2].push_back(std::stof(fileData[line][3])); } } - catch(std::exception &e) + catch (std::exception& e) { throw std::logic_error("Invalid Curve File"); } @@ -564,14 +566,14 @@ void rgbCurve::read(const std::string &path) file.close(); } -double rgbCurve::sumAll(const rgbCurve &curve) +double rgbCurve::sumAll(const rgbCurve& curve) { double sum = 0.0f; - for(std::size_t channel = 0; channel < curve.getNbChannels(); ++channel) + for (std::size_t channel = 0; channel < curve.getNbChannels(); ++channel) { - auto const &sumCurve = curve.getCurve(channel); + auto const& sumCurve = curve.getCurve(channel); - for(auto value : sumCurve) + for (auto value : sumCurve) { sum += value; } @@ -579,5 +581,5 @@ double rgbCurve::sumAll(const rgbCurve &curve) return sum; } -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/rgbCurve.hpp b/src/aliceVision/hdr/rgbCurve.hpp index b3aa8ae8ca..f6e0f3c85e 100644 --- a/src/aliceVision/hdr/rgbCurve.hpp +++ b/src/aliceVision/hdr/rgbCurve.hpp @@ -16,18 +16,17 @@ #include #include "emorCurve.hpp" - namespace aliceVision { namespace hdr { enum class EFunctionType { - LINEAR, - GAUSSIAN, - TRIANGLE, - PLATEAU, - GAMMA, - LOG10 + LINEAR, + GAUSSIAN, + TRIANGLE, + PLATEAU, + GAMMA, + LOG10 }; /** @@ -37,16 +36,22 @@ enum class EFunctionType */ inline std::string EFunctionType_enumToString(const EFunctionType functionType) { - switch(functionType) - { - case EFunctionType::LINEAR: return "linear"; - case EFunctionType::GAUSSIAN: return "gaussian"; - case EFunctionType::TRIANGLE: return "triangle"; - case EFunctionType::PLATEAU: return "plateau"; - case EFunctionType::GAMMA: return "gamma"; - case EFunctionType::LOG10: return "log10"; - } - throw std::out_of_range("Invalid function type enum"); + switch (functionType) + { + case EFunctionType::LINEAR: + return "linear"; + case EFunctionType::GAUSSIAN: + return "gaussian"; + case EFunctionType::TRIANGLE: + return "triangle"; + case EFunctionType::PLATEAU: + return "plateau"; + case EFunctionType::GAMMA: + return "gamma"; + case EFunctionType::LOG10: + return "log10"; + } + throw std::out_of_range("Invalid function type enum"); } /** @@ -56,394 +61,368 @@ inline std::string EFunctionType_enumToString(const EFunctionType functionType) */ inline EFunctionType EFunctionType_stringToEnum(const std::string& functionType) { - std::string type = functionType; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); - - if(type == "linear") return EFunctionType::LINEAR; - if(type == "gaussian") return EFunctionType::GAUSSIAN; - if(type == "triangle") return EFunctionType::TRIANGLE; - if(type == "plateau") return EFunctionType::PLATEAU; - if(type == "gamma") return EFunctionType::GAMMA; - if(type == "log10") return EFunctionType::LOG10; - - throw std::out_of_range("Invalid function type : '" + functionType + "'"); + std::string type = functionType; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + + if (type == "linear") + return EFunctionType::LINEAR; + if (type == "gaussian") + return EFunctionType::GAUSSIAN; + if (type == "triangle") + return EFunctionType::TRIANGLE; + if (type == "plateau") + return EFunctionType::PLATEAU; + if (type == "gamma") + return EFunctionType::GAMMA; + if (type == "log10") + return EFunctionType::LOG10; + + throw std::out_of_range("Invalid function type : '" + functionType + "'"); } inline std::ostream& operator<<(std::ostream& os, const EFunctionType functionType) { - os << EFunctionType_enumToString(functionType); - return os; + os << EFunctionType_enumToString(functionType); + return os; } inline std::istream& operator>>(std::istream& in, EFunctionType& functionType) { - std::string token; - in >> token; - functionType = EFunctionType_stringToEnum(token); - return in; + std::string token; + in >> token; + functionType = EFunctionType_stringToEnum(token); + return in; } - class rgbCurve { -public: - - /** - * @brief rgbCurve constructor - * @param[in] size - size of each curve - */ - explicit rgbCurve(std::size_t size); - - /** - * @brief rgbCurve constructor - * @param[in] path - filepath of an rgbCurve file - */ - explicit rgbCurve(const std::string &path); - - void resize(std::size_t newSize) - { - for(auto &curve : _data) - curve.resize(newSize); - assert(getSize() == newSize); - } - - /** - * @brief set curves to zero - */ - void setZero() - { - for(auto &curve : _data) + public: + /** + * @brief rgbCurve constructor + * @param[in] size - size of each curve + */ + explicit rgbCurve(std::size_t size); + + /** + * @brief rgbCurve constructor + * @param[in] path - filepath of an rgbCurve file + */ + explicit rgbCurve(const std::string& path); + + void resize(std::size_t newSize) { - std::fill(curve.begin(), curve.end(), 0.0f); + for (auto& curve : _data) + curve.resize(newSize); + assert(getSize() == newSize); } - } - - /** - * @brief set curves to one - */ - void setOne() - { - for(auto &curve : _data) + + /** + * @brief set curves to zero + */ + void setZero() { - std::fill(curve.begin(), curve.end(), 1.0f); + for (auto& curve : _data) + { + std::fill(curve.begin(), curve.end(), 0.0f); + } } - } - - /** - * @brief set a value at an index for each curves - * @param index - * @param value - */ - void setAllChannels(std::size_t index , float value) - { - assert(index < getSize()); - - for(auto &curve : _data) + + /** + * @brief set curves to one + */ + void setOne() + { + for (auto& curve : _data) + { + std::fill(curve.begin(), curve.end(), 1.0f); + } + } + + /** + * @brief set a value at an index for each curves + * @param index + * @param value + */ + void setAllChannels(std::size_t index, float value) + { + assert(index < getSize()); + + for (auto& curve : _data) + { + curve[index] = value; + } + } + + void setFunction(EFunctionType functionType); + + /** + * @brief Set curves to linear + */ + void setLinear(); + + /** + * @brief Set curves to gamma + */ + void setGamma(); + + /** + * @brief Set curves to Grossberg mean function of emor model + */ + void setEmorInv(size_t dim = 0); + + /** + * @brief Set curves to Grossberg mean function of emor model + */ + void setEmor(size_t dim = 0); + + /** + *@brief Set curves to gaussian + */ + void setGaussian(double mu = 0.5, double sigma = 1.0 / (5.0 * sqrt(2.0))); + + /** + *@brief Set curves to triangular + */ + void setTriangular(); + + /** + * @brief Set curves to plateau + */ + void setPlateau(float weight = 8.0f); + + void setPlateauSigmoid(float cA = 0.2, float wA = 0.5, float cB = 0.85, float wB = 0.22); + + /** + *@brief Set curves to log10 + */ + void setLog10(); + + /** + * @brief inverse all values of the image + * rgbCurve(i) = 1/i + */ + void inverseAllValues(); + + void freezeFirstPartValues(); + + void freezeSecondPartValues(); + + void invertAndScaleSecondPart(float scale); + + /** + * @brief change all value of the image by their absolute value + */ + void setAllAbsolute(); + + /** + * @brief normalize the curve + */ + void normalize(); + + /** + * @brief scale the curve between 0 and 1 + */ + void scale(); + + /** + * @brief scale the curve between 0 and 1 + */ + void scaleChannelWise(); + + /** + * @brief interpolates all values at zero with the previous an the next value + */ + void interpolateMissingValues(); + + /** + * @brief calculate the exponential of the curve + */ + void exponential(); + + /** + * @brief Right accessor + * @param[in] sample + * @param[in] channel + * @return the value at the index corresponding to the sample of the channel curve + */ + float operator()(float sample, std::size_t channel) const; + + /** + * @brief Operator+ Call sum method + * @param[in] other + * @return rgbCurve of the sum + */ + const rgbCurve operator+(const rgbCurve& other) const; + + /** + * @brief Operator- Call subtract method + * @param[in] other + * @return rgbCurve of the subtraction + */ + const rgbCurve operator-(const rgbCurve& other) const; + + /** + * @brief Operator*= Call multiply method + * @param[in] other - rgbCurve + */ + void operator*=(const rgbCurve& other); + + /** + * @brief Operator*= Call multiply method with float parameter + * @param[in] coefficient + * @return rgbCurve of the multiplication + */ + const rgbCurve operator*(const float coefficient); + + /** + * @brief Sum all value of an rgbCurve by another of the same size + * @param[in] other + * @return rgbCurve of the sum + */ + const rgbCurve sum(const rgbCurve& other) const; + + /** + * @brief Subtract all value of an rgbCurve by another of the same size + * @param[in] other + * @return rgbCurve of the subtraction + */ + const rgbCurve subtract(const rgbCurve& other) const; + + /** + * @brief Multiply all value of an rgbCurve by another of the same size + * @param[in] other + */ + void multiply(const rgbCurve& other); + + /** + * @brief Multiply all value of an rgbCurve by a float coefficient + * @param[in] coefficient + * @return rgbCurve of the multiplication + */ + const rgbCurve multiply(const float coefficient); + + /** + * @brief Calculate the mean curve of all curves of an rgbCurve + * @return rgbCurve of the mean curve + */ + const rgbCurve meanCurves() const; + + /** + * @brief Write in a csv file + * @param[in] path + */ + void write(const std::string& path, const std::string& name = "rgbCurve") const; + + /** + * @brief Write in an html file + * @param[in] path + */ + void writeHtml(const std::string& path, const std::string& title) const; + + /** + * @brief Read and fill curves from a csv file + * @param[in] path + */ + void read(const std::string& path); + + bool isEmpty() const { return _data.front().empty(); } + + /** + * @brief Access curve value at the specified index and channel + * @param[in] index + * @param[in] channel + * @return the value at the index of the channel curve + */ + float& getValue(std::size_t index, std::size_t channel) + { + assert(channel < _data.size()); + assert(index < getSize()); + return _data.at(channel).at(index); + } + + /** + * @brief Access curve value at the specified index and channel + * @param[in] index + * @param[in] channel + * @return the value at the index of the channel curve + */ + float getValue(std::size_t index, std::size_t channel) const + { + assert(channel < _data.size()); + assert(index < getSize()); + return _data.at(channel).at(index); + } + + /** + * @brief Set curve value at the specified index and channel + * @param[in] index + * @param[in] channel + * @param[in] new desired value + */ + void setValue(std::size_t index, std::size_t channel, float value) { - curve[index] = value; + assert(channel < _data.size()); + assert(index < getSize()); + _data.at(channel).at(index) = value; } - } - void setFunction(EFunctionType functionType); - - /** - * @brief Set curves to linear - */ - void setLinear(); - - /** - * @brief Set curves to gamma - */ - void setGamma(); - - /** - * @brief Set curves to Grossberg mean function of emor model - */ - void setEmorInv(size_t dim = 0); - - - /** - * @brief Set curves to Grossberg mean function of emor model - */ - void setEmor(size_t dim = 0); - - /** - *@brief Set curves to gaussian - */ - void setGaussian(double mu = 0.5, double sigma = 1.0 / (5.0 * sqrt(2.0))); - - - /** - *@brief Set curves to triangular - */ - void setTriangular(); - - /** - * @brief Set curves to plateau - */ - void setPlateau(float weight = 8.0f); - - void setPlateauSigmoid(float cA = 0.2, float wA = 0.5, float cB = 0.85, float wB = 0.22); - - /** - *@brief Set curves to log10 - */ - void setLog10(); - - /** - * @brief inverse all values of the image - * rgbCurve(i) = 1/i - */ - void inverseAllValues(); - - void freezeFirstPartValues(); - - void freezeSecondPartValues(); - - void invertAndScaleSecondPart(float scale); - - /** - * @brief change all value of the image by their absolute value - */ - void setAllAbsolute(); - - /** - * @brief normalize the curve - */ - void normalize(); - - /** - * @brief scale the curve between 0 and 1 - */ - void scale(); - - /** - * @brief scale the curve between 0 and 1 - */ - void scaleChannelWise(); - - /** - * @brief interpolates all values at zero with the previous an the next value - */ - void interpolateMissingValues(); - - /** - * @brief calculate the exponential of the curve - */ - void exponential(); - - - /** - * @brief Right accessor - * @param[in] sample - * @param[in] channel - * @return the value at the index corresponding to the sample of the channel curve - */ - float operator() (float sample , std::size_t channel) const; - - /** - * @brief Operator+ Call sum method - * @param[in] other - * @return rgbCurve of the sum - */ - const rgbCurve operator+(const rgbCurve &other) const; - - /** - * @brief Operator- Call subtract method - * @param[in] other - * @return rgbCurve of the subtraction - */ - const rgbCurve operator-(const rgbCurve &other) const; - - /** - * @brief Operator*= Call multiply method - * @param[in] other - rgbCurve - */ - void operator*=(const rgbCurve &other); - - /** - * @brief Operator*= Call multiply method with float parameter - * @param[in] coefficient - * @return rgbCurve of the multiplication - */ - const rgbCurve operator*(const float coefficient); - - /** - * @brief Sum all value of an rgbCurve by another of the same size - * @param[in] other - * @return rgbCurve of the sum - */ - const rgbCurve sum(const rgbCurve &other) const; - - /** - * @brief Subtract all value of an rgbCurve by another of the same size - * @param[in] other - * @return rgbCurve of the subtraction - */ - const rgbCurve subtract(const rgbCurve &other) const; - - /** - * @brief Multiply all value of an rgbCurve by another of the same size - * @param[in] other - */ - void multiply(const rgbCurve &other); - - /** - * @brief Multiply all value of an rgbCurve by a float coefficient - * @param[in] coefficient - * @return rgbCurve of the multiplication - */ - const rgbCurve multiply(const float coefficient); - - /** - * @brief Calculate the mean curve of all curves of an rgbCurve - * @return rgbCurve of the mean curve - */ - const rgbCurve meanCurves() const; - - /** - * @brief Write in a csv file - * @param[in] path - */ - void write(const std::string &path, const std::string &name = "rgbCurve") const; - - /** - * @brief Write in an html file - * @param[in] path - */ - void writeHtml(const std::string &path, const std::string& title) const; - - /** - * @brief Read and fill curves from a csv file - * @param[in] path - */ - void read(const std::string &path); - - bool isEmpty() const - { - return _data.front().empty(); - } - - /** - * @brief Access curve value at the specified index and channel - * @param[in] index - * @param[in] channel - * @return the value at the index of the channel curve - */ - float& getValue(std::size_t index, std::size_t channel) - { - assert(channel < _data.size()); - assert(index < getSize()); - return _data.at(channel).at(index); - } - - /** - * @brief Access curve value at the specified index and channel - * @param[in] index - * @param[in] channel - * @return the value at the index of the channel curve - */ - float getValue(std::size_t index, std::size_t channel) const - { - assert(channel < _data.size()); - assert(index < getSize()); - return _data.at(channel).at(index); - } - - /** - * @brief Set curve value at the specified index and channel - * @param[in] index - * @param[in] channel - * @param[in] new desired value - */ - void setValue(std::size_t index, std::size_t channel, float value) - { - assert(channel < _data.size()); - assert(index < getSize()); - _data.at(channel).at(index) = value; - } - - /** - * @brief Get inferior index value corresponding to float sample and fractionalPart between inferior and superior indexes - * @param[in] sample between 0 and 1 - * @param[out] fractionalPart between inferior and superior indexes - * @return inferior index of curve - */ - std::size_t getIndex(float sample, float& fractionalPart) const - { - assert(getSize() != 0); - - float infIndex; - float size = getSize() - 1.0f; - float valueScaled = std::max(0.f, std::min(1.f, sample)) * size; - fractionalPart = std::modf(valueScaled, &infIndex); - - return std::size_t(infIndex); - } - - std::size_t getSize() const - { - return _data.front().size(); - } - - std::size_t getNbChannels() const - { - return _data.size(); - } - - const std::vector& getCurve(std::size_t channel) const - { - assert(channel < 3); - return _data[channel]; - } - - std::vector& getCurve(std::size_t channel) - { - assert(channel < 3); - return _data[channel]; - } - - const std::vector& getCurveRed() const - { - return _data[0]; - } - - std::vector& getCurveRed() - { - return _data[0]; - } - - const std::vector& getCurveGreen() const - { - return _data[1]; - } - - std::vector& getCurveGreen() - { - return _data[1]; - } - - const std::vector& getCurveBlue() const - { - return _data[2]; - } - - std::vector& getCurveBlue() - { - return _data[2]; - } - - /** - * @brief Sum of all value of all channel - * @param[in] curve - * @return the sum scalar - */ - static double sumAll(const rgbCurve &curve); - -private: - /// Array containing the curves as vectors of float for each channel - std::array< std::vector, 3 > _data; + /** + * @brief Get inferior index value corresponding to float sample and fractionalPart between inferior and superior indexes + * @param[in] sample between 0 and 1 + * @param[out] fractionalPart between inferior and superior indexes + * @return inferior index of curve + */ + std::size_t getIndex(float sample, float& fractionalPart) const + { + assert(getSize() != 0); + + float infIndex; + float size = getSize() - 1.0f; + float valueScaled = std::max(0.f, std::min(1.f, sample)) * size; + fractionalPart = std::modf(valueScaled, &infIndex); + + return std::size_t(infIndex); + } + + std::size_t getSize() const { return _data.front().size(); } + + std::size_t getNbChannels() const { return _data.size(); } + + const std::vector& getCurve(std::size_t channel) const + { + assert(channel < 3); + return _data[channel]; + } + + std::vector& getCurve(std::size_t channel) + { + assert(channel < 3); + return _data[channel]; + } + + const std::vector& getCurveRed() const { return _data[0]; } + + std::vector& getCurveRed() { return _data[0]; } + + const std::vector& getCurveGreen() const { return _data[1]; } + + std::vector& getCurveGreen() { return _data[1]; } + + const std::vector& getCurveBlue() const { return _data[2]; } + + std::vector& getCurveBlue() { return _data[2]; } + + /** + * @brief Sum of all value of all channel + * @param[in] curve + * @return the sum scalar + */ + static double sumAll(const rgbCurve& curve); + + private: + /// Array containing the curves as vectors of float for each channel + std::array, 3> _data; }; -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/sampling.cpp b/src/aliceVision/hdr/sampling.cpp index db9245aaa5..11a54ca327 100644 --- a/src/aliceVision/hdr/sampling.cpp +++ b/src/aliceVision/hdr/sampling.cpp @@ -12,13 +12,12 @@ #include #include - namespace aliceVision { namespace hdr { using namespace aliceVision::image; -bool UniqueDescriptor::operator<(const UniqueDescriptor &o ) const +bool UniqueDescriptor::operator<(const UniqueDescriptor& o) const { if (exposure < o.exposure) return true; @@ -30,7 +29,7 @@ bool UniqueDescriptor::operator<(const UniqueDescriptor &o ) const return false; } -std::ostream & operator<<(std::ostream& os, const ImageSample & s) +std::ostream& operator<<(std::ostream& os, const ImageSample& s) { os.write((const char*)&s.x, sizeof(s.x)); os.write((const char*)&s.y, sizeof(s.y)); @@ -38,7 +37,7 @@ std::ostream & operator<<(std::ostream& os, const ImageSample & s) std::size_t size = s.descriptions.size(); os.write((const char*)&size, sizeof(size)); - for (int i = 0; i < s.descriptions.size(); ++i) + for (int i = 0; i < s.descriptions.size(); ++i) { os << s.descriptions[i]; } @@ -46,16 +45,16 @@ std::ostream & operator<<(std::ostream& os, const ImageSample & s) return os; } -std::istream & operator>>(std::istream& is, ImageSample & s) +std::istream& operator>>(std::istream& is, ImageSample& s) { std::size_t size; - is.read((char *)&s.x, sizeof(s.x)); - is.read((char *)&s.y, sizeof(s.y)); - is.read((char *)&size, sizeof(size)); + is.read((char*)&s.x, sizeof(s.x)); + is.read((char*)&s.y, sizeof(s.y)); + is.read((char*)&size, sizeof(size)); s.descriptions.resize(size); - for (int i = 0; i < size; ++i) + for (int i = 0; i < size; ++i) { is >> s.descriptions[i]; } @@ -63,42 +62,42 @@ std::istream & operator>>(std::istream& is, ImageSample & s) return is; } -std::ostream & operator<<(std::ostream& os, const PixelDescription & p) +std::ostream& operator<<(std::ostream& os, const PixelDescription& p) { os.write((const char*)&p.srcId, sizeof(p.srcId)); os.write((const char*)&p.exposure, sizeof(p.exposure)); - os.write((const char *)&p.mean.r(), sizeof(p.mean.r())); - os.write((const char *)&p.mean.g(), sizeof(p.mean.g())); - os.write((const char *)&p.mean.b(), sizeof(p.mean.b())); - os.write((const char *)&p.variance.r(), sizeof(p.variance.r())); - os.write((const char *)&p.variance.g(), sizeof(p.variance.g())); - os.write((const char *)&p.variance.b(), sizeof(p.variance.b())); + os.write((const char*)&p.mean.r(), sizeof(p.mean.r())); + os.write((const char*)&p.mean.g(), sizeof(p.mean.g())); + os.write((const char*)&p.mean.b(), sizeof(p.mean.b())); + os.write((const char*)&p.variance.r(), sizeof(p.variance.r())); + os.write((const char*)&p.variance.g(), sizeof(p.variance.g())); + os.write((const char*)&p.variance.b(), sizeof(p.variance.b())); return os; } -std::istream & operator>>(std::istream& is, PixelDescription & p) +std::istream& operator>>(std::istream& is, PixelDescription& p) { is.read((char*)&p.srcId, sizeof(p.srcId)); is.read((char*)&p.exposure, sizeof(p.exposure)); - is.read((char *)&p.mean.r(), sizeof(p.mean.r())); - is.read((char *)&p.mean.g(), sizeof(p.mean.g())); - is.read((char *)&p.mean.b(), sizeof(p.mean.b())); - is.read((char *)&p.variance.r(), sizeof(p.variance.r())); - is.read((char *)&p.variance.g(), sizeof(p.variance.g())); - is.read((char *)&p.variance.b(), sizeof(p.variance.b())); + is.read((char*)&p.mean.r(), sizeof(p.mean.r())); + is.read((char*)&p.mean.g(), sizeof(p.mean.g())); + is.read((char*)&p.mean.b(), sizeof(p.mean.b())); + is.read((char*)&p.variance.r(), sizeof(p.variance.r())); + is.read((char*)&p.variance.g(), sizeof(p.variance.g())); + is.read((char*)&p.variance.b(), sizeof(p.variance.b())); return is; } -void integral(image::Image> & dest, const Eigen::Matrix & source) +void integral(image::Image>& dest, const Eigen::Matrix& source) { /* - A B C - D E F + A B C + D E F G H I J K L - = + = A A+B A+B+C A+D A+B+D+E A+B+C+D+E+F A+D+G A+B+D+E+G+H A+B+C+D+E+F+G+H+I @@ -133,7 +132,7 @@ void integral(image::Image> & dest, const Eigen::Matrix & dest, const Eigen::Matrix & source) +void square(image::Image& dest, const Eigen::Matrix& source) { dest.resize(source.cols(), source.rows()); @@ -148,7 +147,16 @@ void square(image::Image & dest, const Eigen::Matrix& out_samples, const std::vector& imagePaths, const std::vector& viewIds, const std::vector& times, const size_t imageWidth, const size_t imageHeight, const size_t channelQuantization, const image::ImageReadOptions & imgReadOptions, const Sampling::Params params, const bool simplified) +bool Sampling::extractSamplesFromImages(std::vector& out_samples, + const std::vector& imagePaths, + const std::vector& viewIds, + const std::vector& times, + const size_t imageWidth, + const size_t imageHeight, + const size_t channelQuantization, + const image::ImageReadOptions& imgReadOptions, + const Sampling::Params params, + const bool simplified) { const int radiusp1 = params.radius + 1; const int diameter = (params.radius * 2) + 1; @@ -157,9 +165,9 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c std::vector> vec_blocks; const auto step = params.blockSize - diameter; vec_blocks.reserve(int(imageHeight / step) * int(imageWidth / step)); - for(int cy = 0; cy < imageHeight; cy += step) + for (int cy = 0; cy < imageHeight; cy += step) { - for(int cx = 0; cx < imageWidth; cx += step) + for (int cx = 0; cx < imageWidth; cx += step) { vec_blocks.push_back(std::make_pair(cx, cy)); } @@ -176,12 +184,12 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c // Load image readImage(imagePaths[idBracket], img, imgReadOptions); - if(img.Width() != imageWidth || img.Height() != imageHeight) + if (img.Width() != imageWidth || img.Height() != imageHeight) { std::stringstream ss; ss << "Failed to extract samples, the images with multi-bracketing do not have the same image resolution.\n" - << " Current image resolution is: " << img.Width() << "x" << img.Height() - << ", instead of: " << imageWidth<< "x" << imageHeight << ".\n" + << " Current image resolution is: " << img.Width() << "x" << img.Height() << ", instead of: " << imageWidth << "x" << imageHeight + << ".\n" << "Current image path is: " << imagePaths[idBracket]; throw std::runtime_error(ss.str()); } @@ -209,7 +217,7 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c const int sampling = 16; - #pragma omp parallel for +#pragma omp parallel for for (int r = rmin; r < rmax; r = r + sampling) { const int cmin = (r < hH) ? a1 - r : r - a3; @@ -236,7 +244,7 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c } else { - #pragma omp parallel for +#pragma omp parallel for for (int idx = 0; idx < vec_blocks.size(); ++idx) { int cx = vec_blocks[idx].first; @@ -260,8 +268,11 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c { for (int x = radiusp1; x < imgIntegral.Width() - params.radius; ++x) { - image::Rgb S1 = imgIntegral(y + params.radius, x + params.radius) + imgIntegral(y - radiusp1, x - radiusp1) - imgIntegral(y + params.radius, x - radiusp1) - imgIntegral(y - radiusp1, x + params.radius); - image::Rgb S2 = imgIntegralSquare(y + params.radius, x + params.radius) + imgIntegralSquare(y - radiusp1, x - radiusp1) - imgIntegralSquare(y + params.radius, x - radiusp1) - imgIntegralSquare(y - radiusp1, x + params.radius); + image::Rgb S1 = imgIntegral(y + params.radius, x + params.radius) + imgIntegral(y - radiusp1, x - radiusp1) - + imgIntegral(y + params.radius, x - radiusp1) - imgIntegral(y - radiusp1, x + params.radius); + image::Rgb S2 = imgIntegralSquare(y + params.radius, x + params.radius) + + imgIntegralSquare(y - radiusp1, x - radiusp1) - imgIntegralSquare(y + params.radius, x - radiusp1) - + imgIntegralSquare(y - radiusp1, x + params.radius); PixelDescription pd; @@ -291,8 +302,8 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c if (!simplified) { - // Create samples image - #pragma omp parallel for +// Create samples image +#pragma omp parallel for for (int y = params.radius; y < samples.Height() - params.radius; ++y) { for (int x = params.radius; x < samples.Width() - params.radius; ++x) @@ -311,8 +322,7 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c const float maxVariance = 0.05f; for (int k = 0; k < sample.descriptions.size(); ++k) { - if (sample.descriptions[k].variance.r() > maxVariance || - sample.descriptions[k].variance.g() > maxVariance || + if (sample.descriptions[k].variance.r() > maxVariance || sample.descriptions[k].variance.g() > maxVariance || sample.descriptions[k].variance.b() > maxVariance) { valid = false; @@ -336,8 +346,7 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c // Threshold on the max values, to avoid using fully saturated pixels // TODO: on RAW images, values can be higher. May need to be computed dynamically? const float maxValue = 0.99f; - if (sample.descriptions[k].mean.r() > maxValue || - sample.descriptions[k].mean.g() > maxValue || + if (sample.descriptions[k].mean.r() > maxValue || sample.descriptions[k].mean.g() > maxValue || sample.descriptions[k].mean.b() > maxValue) { continue; @@ -371,7 +380,7 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c const float dot = sample.descriptions[k - 1].mean.dot(sample.descriptions[k].mean); const float cosa = dot / (n1 * n2); - const float maxCosa = 0.95f; // ~ 18deg + const float maxCosa = 0.95f; // ~ 18deg if (cosa < maxCosa) { valid = false; @@ -423,14 +432,14 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c { std::vector counters_vec(omp_get_max_threads()); - #pragma omp parallel for +#pragma omp parallel for for (int y = params.radius; y < samples.Height() - params.radius; ++y) { - Counters & counters_thread = counters_vec[omp_get_thread_num()]; + Counters& counters_thread = counters_vec[omp_get_thread_num()]; for (int x = params.radius; x < samples.Width() - params.radius; ++x) { - const ImageSample & sample = samples(y, x); + const ImageSample& sample = samples(y, x); UniqueDescriptor desc; for (int k = 0; k < sample.descriptions.size(); ++k) @@ -441,11 +450,11 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c { desc.channel = channel; // Get quantized value - desc.quantizedValue = int(std::round(sample.descriptions[k].mean(channel) * (channelQuantization - 1))); + desc.quantizedValue = int(std::round(sample.descriptions[k].mean(channel) * (channelQuantization - 1))); if (desc.quantizedValue < 0 || desc.quantizedValue >= channelQuantization) { continue; - } + } Coordinates coordinates = std::make_pair(sample.x, sample.y); counters_thread[desc].push_back(coordinates); } @@ -453,9 +462,9 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c } } - for(int i = 0; i < counters_vec.size(); ++i) + for (int i = 0; i < counters_vec.size(); ++i) { - for (auto & item : counters_vec[i]) + for (auto& item : counters_vec[i]) { auto found = counters.find(item.first); if (found != counters.end()) @@ -473,7 +482,7 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c std::random_device randomDevice; std::mt19937 rng(randomDevice()); - for (auto & item : counters) + for (auto& item : counters) { if (item.second.size() > params.maxCountSample) { @@ -497,21 +506,21 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c return true; } -void Sampling::analyzeSource(std::vector & samples, int channelQuantization, int imageIndex) +void Sampling::analyzeSource(std::vector& samples, int channelQuantization, int imageIndex) { for (std::size_t sampleIndex = 0; sampleIndex < samples.size(); ++sampleIndex) { - ImageSample & sample = samples[sampleIndex]; + ImageSample& sample = samples[sampleIndex]; - for (auto & desc : sample.descriptions) + for (auto& desc : sample.descriptions) { UniqueDescriptor udesc; udesc.exposure = desc.exposure; - + for (int channel = 0; channel < 3; ++channel) { udesc.channel = channel; - udesc.quantizedValue = int(std::round(desc.mean(channel) * (channelQuantization - 1))); + udesc.quantizedValue = int(std::round(desc.mean(channel) * (channelQuantization - 1))); if (udesc.quantizedValue < 0 || udesc.quantizedValue >= channelQuantization) { continue; @@ -529,11 +538,11 @@ void Sampling::analyzeSource(std::vector & samples, int channelQuan std::random_device randomDevice; std::mt19937 rng(randomDevice()); - for (auto & item : _positions) + for (auto& item : _positions) { // TODO: expose as parameters const std::size_t maxSamples = 500; - if(item.second.size() > maxSamples) + if (item.second.size() > maxSamples) { // Shuffle and ignore the exceeding samples std::shuffle(item.second.begin(), item.second.end(), rng); @@ -556,7 +565,7 @@ void Sampling::filter(size_t maxTotalPoints) limitPerGroup = limitPerGroup - 10; total_points = 0; - for (auto & item : _positions) + for (auto& item : _positions) { if (item.second.size() > limitPerGroup) { @@ -570,13 +579,13 @@ void Sampling::filter(size_t maxTotalPoints) } } -void Sampling::extractUsefulSamples(std::vector & out_samples, const std::vector & samples, int imageIndex) const +void Sampling::extractUsefulSamples(std::vector& out_samples, const std::vector& samples, int imageIndex) const { std::set uniqueIndices; - for (auto & item : _positions) + for (auto& item : _positions) { - for (auto & pos : item.second) + for (auto& pos : item.second) { if (pos.imageIndex == imageIndex) { @@ -586,7 +595,7 @@ void Sampling::extractUsefulSamples(std::vector & out_samples, cons } // Export non-empty samples - for (auto & index : uniqueIndices) + for (auto& index : uniqueIndices) { if (!samples[index].descriptions.empty()) { @@ -595,5 +604,5 @@ void Sampling::extractUsefulSamples(std::vector & out_samples, cons } } -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/hdr/sampling.hpp b/src/aliceVision/hdr/sampling.hpp index c5ca4fb717..8732399892 100644 --- a/src/aliceVision/hdr/sampling.hpp +++ b/src/aliceVision/hdr/sampling.hpp @@ -19,7 +19,7 @@ struct UniqueDescriptor int channel; int quantizedValue; - bool operator<(const UniqueDescriptor &o) const; + bool operator<(const UniqueDescriptor& o) const; }; struct PixelDescription @@ -39,15 +39,14 @@ struct ImageSample ImageSample() = default; }; -std::ostream & operator<<(std::ostream& os, const ImageSample & s); -std::ostream & operator<<(std::ostream& os, const PixelDescription & p); -std::istream & operator>>(std::istream& os, ImageSample & s); -std::istream & operator>>(std::istream& os, PixelDescription & p); - +std::ostream& operator<<(std::ostream& os, const ImageSample& s); +std::ostream& operator<<(std::ostream& os, const PixelDescription& p); +std::istream& operator>>(std::istream& os, ImageSample& s); +std::istream& operator>>(std::istream& os, PixelDescription& p); class Sampling { -public: + public: struct Coordinates { unsigned int imageIndex; @@ -62,16 +61,25 @@ class Sampling using MapSampleRefList = std::map>; -public: - void analyzeSource(std::vector & samples, int channelQuantization, int imageIndex); + public: + void analyzeSource(std::vector& samples, int channelQuantization, int imageIndex); void filter(size_t maxTotalPoints); - void extractUsefulSamples(std::vector & out_samples, const std::vector & samples, int imageIndex) const; - - static bool extractSamplesFromImages(std::vector& out_samples, const std::vector & imagePaths, const std::vector& viewIds, const std::vector& times, const size_t imageWidth, const size_t imageHeight, const size_t channelQuantization, const image::ImageReadOptions & imgReadOptions, const Params params, const bool simplified = false); + void extractUsefulSamples(std::vector& out_samples, const std::vector& samples, int imageIndex) const; + + static bool extractSamplesFromImages(std::vector& out_samples, + const std::vector& imagePaths, + const std::vector& viewIds, + const std::vector& times, + const size_t imageWidth, + const size_t imageHeight, + const size_t channelQuantization, + const image::ImageReadOptions& imgReadOptions, + const Params params, + const bool simplified = false); -private: + private: MapSampleRefList _positions; }; -} // namespace hdr -} // namespace aliceVision +} // namespace hdr +} // namespace aliceVision diff --git a/src/aliceVision/image/Image.hpp b/src/aliceVision/image/Image.hpp index 55b081d0e8..82368d8b2c 100644 --- a/src/aliceVision/image/Image.hpp +++ b/src/aliceVision/image/Image.hpp @@ -20,292 +20,244 @@ //-- Pixel access is done with operator(y,x) // [2/3/2011 pierre MOULON] //--------------------------- -namespace aliceVision +namespace aliceVision { +namespace image { +template +class Image : public Eigen::Matrix { - namespace image - { - template - class Image : public Eigen::Matrix - { + public: + typedef T Tpixel; //-- Pixel data type + typedef Eigen::Matrix Base; - public: - typedef T Tpixel; //-- Pixel data type - typedef Eigen::Matrix Base; - - - /** - * @brief Default constructor - * @note This create an empty image - */ - inline Image() - { - Base::resize( 0, 0 ); - } - - /** - * @brief Full constructor - * @param width Width of the image (ie number of column) - * @param height Height of the image (ie number of row) - * @param fInit Tell if the image should be initialized - * @param val If fInit is true, set all pixel to the specified value - */ - inline Image( int width, int height, bool fInit = false, const T val = T{} ) - { - Base::resize( height, width ); - if ( fInit ) - { - Base::fill( val ); - } - }; - - /** - * @brief Copy constructor - * @param I Source image - */ - inline Image( const Base& I ) - : Base( I ) - { - - } - - /** - * @brief Move constructor - * @param src Source image - */ - inline Image( Base && src ) - : Base( std::move( src ) ) - { - - } - - /** - * @brief Assignment operator - * @param I Source image - * @return Image after assignment - */ - inline Image& operator=( const Base& I ) - { - Base::operator=( I ); - return *this; - } - - /** - * @brief destructor - */ - virtual ~Image() = default; - //-- Image construction method - //------------------------------ - - - /** - * @brief Change geometry of image - * @param width New width of image - * @param height New height of image - * @param fInit Indicate if new image should be initialized - * @param val if fInit is true all pixel in the new image are set to this value - */ - inline void resize( int width, int height, bool fInit = true, const T val = T{} ) - { - Base::resize( height, width ); - if ( fInit ) + /** + * @brief Default constructor + * @note This create an empty image + */ + inline Image() { Base::resize(0, 0); } + + /** + * @brief Full constructor + * @param width Width of the image (ie number of column) + * @param height Height of the image (ie number of row) + * @param fInit Tell if the image should be initialized + * @param val If fInit is true, set all pixel to the specified value + */ + inline Image(int width, int height, bool fInit = false, const T val = T{}) + { + Base::resize(height, width); + if (fInit) { - Base::fill( val ); + Base::fill(val); } - } - - //------------------------------ - //-- accessors/getters methods - /** - * @brief Retrieve the width of the image - * @return Width of image - */ - inline int Width() const - { - return static_cast( Base::cols() ); - } - - /** - * @brief Retrieve the height of the image - * @return Height of the image - */ - inline int Height() const - { - return static_cast( Base::rows() ); - } - - /** - * @brief Return the depth in byte of the pixel - * @return depth of the pixel (in byte) - * @note (T=unsigned char will return 1) - */ - inline int Depth() const - { - return sizeof( Tpixel ); - } - - /** - * @brief Retrieve the size in byte of the image - * @return size of the image (in byte) - * @note We use unsigned long long integers to avoid issues with large images, which can exceed several GB. - */ - inline unsigned long long int MemorySize() const - { - return static_cast(Width()) * - static_cast(Height()) * - static_cast(Depth()); - } - - - /** - * @brief Return the number of channels - * @return number of channels - */ - inline int Channels() const - { - return NbChannels::size; - } - - /** - * @brief constant random pixel access - * @param y Index of the row - * @param x Index of the column - * @return Constant pixel reference at position (y,x) - */ - inline const T& operator()( int y, int x ) const - { - return Base::operator()( y, x ); - } - - /** - * @brief random pixel access - * @param y Index of the row - * @param x Index of the column - * @return Pixel reference at position (y,x) - */ - inline T& operator()( int y, int x ) - { - return Base::operator()( y, x ); - } - - inline const T& operator()( int i ) const - { - return Base::operator()( i ); - } - inline T& operator()( int i ) - { - return Base::operator()( i ); - } - - /** - * @brief Get low level access to the internal pixel data - * @return const reference to internal matrix data - */ - inline const Base& GetMat() const - { - return ( *this ); - } - inline Base& GetMat() - { - return (*this); - } - - //-- accessors/getters methods - //------------------------------ - - /** - * @brief Tell if a point is inside the image. - * @param y Index of the row - * @param x Index of the column - * @retval true If pixel (y,x) is inside the image - * @retval false If pixel (y,x) is outside the image - */ - inline bool Contains( int y, int x ) const - { - return 0 <= x && x < Base::cols() - && 0 <= y && y < Base::rows(); - } - - /** - * @brief Pixelwise addition of two images - * @param imgA First image - * @param imgB Second image - * @return pixelwise imgA + imgB - * @note Images must have the same size - */ - template< typename T1> - friend Image operator+( const Image & imgA , const Image & imgB ) ; - - /** - * @brief Pixelwise subtraction of two images - * @param imgA First image - * @param imgB Second image - * @return pixelwise imgA - imgB - * @note Images must have the same size - */ - template< typename T1> - friend Image operator-( const Image & imgA , const Image & imgB ) ; - - template - bool perPixelOperation(UnaryFunction f) - { - for(auto row : this->rowwise()) - { - std::transform(row.begin(), row.end(), row.begin(), f); - } - - return true; - } - - protected : - //-- Image data are stored by inheritance of a matrix }; /** - * @brief Pixelwise addition of two images - * @param imgA First image - * @param imgB Second image - * @return pixelwise imgA + imgB - * @note Images must have the same size - */ - template< typename T1 > - Image operator+( const Image & imgA , const Image & imgB ) + * @brief Copy constructor + * @param I Source image + */ + inline Image(const Base& I) + : Base(I) + {} + + /** + * @brief Move constructor + * @param src Source image + */ + inline Image(Base&& src) + : Base(std::move(src)) + {} + + /** + * @brief Assignment operator + * @param I Source image + * @return Image after assignment + */ + inline Image& operator=(const Base& I) { - return Image( imgA.Image::operator+( imgB ) ) ; + Base::operator=(I); + return *this; } /** - * @brief Pixelwise subtraction of two images - * @param imgA First image - * @param imgB Second image - * @return pixelwise imgA - imgB - * @note Images must have the same size - */ - template< typename T1> - Image operator-( const Image & imgA , const Image & imgB ) + * @brief destructor + */ + virtual ~Image() = default; + //-- Image construction method + //------------------------------ + + /** + * @brief Change geometry of image + * @param width New width of image + * @param height New height of image + * @param fInit Indicate if new image should be initialized + * @param val if fInit is true all pixel in the new image are set to this value + */ + inline void resize(int width, int height, bool fInit = true, const T val = T{}) + { + Base::resize(height, width); + if (fInit) + { + Base::fill(val); + } + } + + //------------------------------ + //-- accessors/getters methods + /** + * @brief Retrieve the width of the image + * @return Width of image + */ + inline int Width() const { return static_cast(Base::cols()); } + + /** + * @brief Retrieve the height of the image + * @return Height of the image + */ + inline int Height() const { return static_cast(Base::rows()); } + + /** + * @brief Return the depth in byte of the pixel + * @return depth of the pixel (in byte) + * @note (T=unsigned char will return 1) + */ + inline int Depth() const { return sizeof(Tpixel); } + + /** + * @brief Retrieve the size in byte of the image + * @return size of the image (in byte) + * @note We use unsigned long long integers to avoid issues with large images, which can exceed several GB. + */ + inline unsigned long long int MemorySize() const { - return Image( imgA.Image::operator-( imgB ) ) ; + return static_cast(Width()) * static_cast(Height()) * + static_cast(Depth()); } - template< typename T > - T getInterpolateColor(const Image& img, double y, double x) + /** + * @brief Return the number of channels + * @return number of channels + */ + inline int Channels() const { return NbChannels::size; } + + /** + * @brief constant random pixel access + * @param y Index of the row + * @param x Index of the column + * @return Constant pixel reference at position (y,x) + */ + inline const T& operator()(int y, int x) const { return Base::operator()(y, x); } + + /** + * @brief random pixel access + * @param y Index of the row + * @param x Index of the column + * @return Pixel reference at position (y,x) + */ + inline T& operator()(int y, int x) { return Base::operator()(y, x); } + + inline const T& operator()(int i) const { return Base::operator()(i); } + inline T& operator()(int i) { return Base::operator()(i); } + + /** + * @brief Get low level access to the internal pixel data + * @return const reference to internal matrix data + */ + inline const Base& GetMat() const { return (*this); } + inline Base& GetMat() { return (*this); } + + //-- accessors/getters methods + //------------------------------ + + /** + * @brief Tell if a point is inside the image. + * @param y Index of the row + * @param x Index of the column + * @retval true If pixel (y,x) is inside the image + * @retval false If pixel (y,x) is outside the image + */ + inline bool Contains(int y, int x) const { return 0 <= x && x < Base::cols() && 0 <= y && y < Base::rows(); } + + /** + * @brief Pixelwise addition of two images + * @param imgA First image + * @param imgB Second image + * @return pixelwise imgA + imgB + * @note Images must have the same size + */ + template + friend Image operator+(const Image& imgA, const Image& imgB); + + /** + * @brief Pixelwise subtraction of two images + * @param imgA First image + * @param imgB Second image + * @return pixelwise imgA - imgB + * @note Images must have the same size + */ + template + friend Image operator-(const Image& imgA, const Image& imgB); + + template + bool perPixelOperation(UnaryFunction f) { - const int xp = std::min(static_cast(x), img.Width() - 2); - const int yp = std::min(static_cast(y), img.Height() - 2); - - // precision to 4 decimal places - const float ui = x - static_cast(xp); - const float vi = y - static_cast(yp); - - const T lu = img(yp, xp); - const T ru = img(yp, (xp + 1)); - const T rd = img((yp + 1), (xp + 1)); - const T ld = img((yp + 1), xp); - - // bilinear interpolation of the pixel intensity value - const T u = lu + (ru - lu) * ui; - const T d = ld + (rd - ld) * ui; - const T out = u + (d - u) * vi; - return out; + for (auto row : this->rowwise()) + { + std::transform(row.begin(), row.end(), row.begin(), f); + } + + return true; } - } // namespace image -} // namespace aliceVision + + protected: + //-- Image data are stored by inheritance of a matrix +}; + +/** + * @brief Pixelwise addition of two images + * @param imgA First image + * @param imgB Second image + * @return pixelwise imgA + imgB + * @note Images must have the same size + */ +template +Image operator+(const Image& imgA, const Image& imgB) +{ + return Image(imgA.Image::operator+(imgB)); +} + +/** + * @brief Pixelwise subtraction of two images + * @param imgA First image + * @param imgB Second image + * @return pixelwise imgA - imgB + * @note Images must have the same size + */ +template +Image operator-(const Image& imgA, const Image& imgB) +{ + return Image(imgA.Image::operator-(imgB)); +} + +template +T getInterpolateColor(const Image& img, double y, double x) +{ + const int xp = std::min(static_cast(x), img.Width() - 2); + const int yp = std::min(static_cast(y), img.Height() - 2); + + // precision to 4 decimal places + const float ui = x - static_cast(xp); + const float vi = y - static_cast(yp); + + const T lu = img(yp, xp); + const T ru = img(yp, (xp + 1)); + const T rd = img((yp + 1), (xp + 1)); + const T ld = img((yp + 1), xp); + + // bilinear interpolation of the pixel intensity value + const T u = lu + (ru - lu) * ui; + const T d = ld + (rd - ld) * ui; + const T out = u + (d - u) * vi; + return out; +} +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/ImageCache.cpp b/src/aliceVision/image/ImageCache.cpp index 05b401fbad..a5a78d525f 100644 --- a/src/aliceVision/image/ImageCache.cpp +++ b/src/aliceVision/image/ImageCache.cpp @@ -8,13 +8,10 @@ #include - namespace aliceVision { namespace image { -CacheValue::CacheValue() -{ -} +CacheValue::CacheValue() {} CacheValue CacheValue::wrap(std::shared_ptr> img) { @@ -116,15 +113,12 @@ unsigned long long int CacheValue::memorySize() const return 0; } -ImageCache::ImageCache(float capacity_MiB, float maxSize_MiB, const ImageReadOptions& options) : - _info(capacity_MiB, maxSize_MiB), +ImageCache::ImageCache(float capacity_MiB, float maxSize_MiB, const ImageReadOptions& options) + : _info(capacity_MiB, maxSize_MiB), _options(options) -{ -} +{} -ImageCache::~ImageCache() -{ -} +ImageCache::~ImageCache() {} std::string ImageCache::toString() const { @@ -132,30 +126,27 @@ std::string ImageCache::toString() const for (const CacheKey& key : _keys) { - std::string keyDesc = key.filename + - ", nbChannels: " + std::to_string(key.nbChannels) + - ", typeDesc: " + std::to_string(key.typeDesc) + - ", downscaleLevel: " + std::to_string(key.downscaleLevel) + - ", usages: " + std::to_string(_imagePtrs.at(key).useCount()) + + std::string keyDesc = key.filename + ", nbChannels: " + std::to_string(key.nbChannels) + ", typeDesc: " + std::to_string(key.typeDesc) + + ", downscaleLevel: " + std::to_string(key.downscaleLevel) + + ", usages: " + std::to_string(_imagePtrs.at(key).useCount()) + ", size: " + std::to_string(_imagePtrs.at(key).memorySize()); description += "\n * " + keyDesc; } std::string memUsageDesc = "\nMemory usage: " - "\n * capacity: " + std::to_string(_info.capacity) + - "\n * max size: " + std::to_string(_info.maxSize) + - "\n * nb images: " + std::to_string(_info.nbImages) + - "\n * content size: " + std::to_string(_info.contentSize); + "\n * capacity: " + + std::to_string(_info.capacity) + "\n * max size: " + std::to_string(_info.maxSize) + + "\n * nb images: " + std::to_string(_info.nbImages) + "\n * content size: " + std::to_string(_info.contentSize); description += memUsageDesc; std::string statsDesc = "\nUsage statistics: " - "\n * nb load from disk: " + std::to_string(_info.nbLoadFromDisk) + - "\n * nb load from cache: " + std::to_string(_info.nbLoadFromCache) + + "\n * nb load from disk: " + + std::to_string(_info.nbLoadFromDisk) + "\n * nb load from cache: " + std::to_string(_info.nbLoadFromCache) + "\n * nb remove unused: " + std::to_string(_info.nbRemoveUnused); description += statsDesc; - + return description; } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/ImageCache.hpp b/src/aliceVision/image/ImageCache.hpp index e1ca7a319f..11dd6db712 100644 --- a/src/aliceVision/image/ImageCache.hpp +++ b/src/aliceVision/image/ImageCache.hpp @@ -24,14 +24,13 @@ #include #include - namespace aliceVision { namespace image { /** * @brief A struct used to identify a cached image using its file description, color type info and downscale level. */ -struct CacheKey +struct CacheKey { std::string filename; int nbChannels; @@ -39,22 +38,18 @@ struct CacheKey int downscaleLevel; std::time_t lastWriteTime; - CacheKey(const std::string& path, int nchannels, oiio::TypeDesc::BASETYPE baseType, int level, std::time_t time) : - filename(path), - nbChannels(nchannels), - typeDesc(baseType), - downscaleLevel(level), + CacheKey(const std::string& path, int nchannels, oiio::TypeDesc::BASETYPE baseType, int level, std::time_t time) + : filename(path), + nbChannels(nchannels), + typeDesc(baseType), + downscaleLevel(level), lastWriteTime(time) - { - } + {} bool operator==(const CacheKey& other) const { - return (filename == other.filename && - nbChannels == other.nbChannels && - typeDesc == other.typeDesc && - downscaleLevel == other.downscaleLevel && - lastWriteTime == other.lastWriteTime); + return (filename == other.filename && nbChannels == other.nbChannels && typeDesc == other.typeDesc && + downscaleLevel == other.downscaleLevel && lastWriteTime == other.lastWriteTime); } }; @@ -72,7 +67,6 @@ struct CacheKeyHasher } }; - /** * @brief A struct to store information about the cache current state and usage. */ @@ -91,8 +85,8 @@ struct CacheInfo int nbLoadFromCache = 0; int nbRemoveUnused = 0; - CacheInfo(float capacity_MiB, float maxSize_MiB) : - capacity(capacity_MiB * 1024 * 1024), + CacheInfo(float capacity_MiB, float maxSize_MiB) + : capacity(capacity_MiB * 1024 * 1024), maxSize(maxSize_MiB * 1024 * 1024) { // Check that max size is higher than capacity @@ -103,16 +97,15 @@ struct CacheInfo } }; - /** * @brief A class to support shared pointers for all types of images. */ class CacheValue { -public: + public: /** * @brief Factory method to create a CacheValue instance that wraps a shared pointer to an image - * @param[in] img shared pointer to an image + * @param[in] img shared pointer to an image * @result CacheValue instance wrapping the shared pointer */ static CacheValue wrap(std::shared_ptr> img); @@ -122,12 +115,12 @@ class CacheValue static CacheValue wrap(std::shared_ptr> img); static CacheValue wrap(std::shared_ptr> img); -private: + private: /// constructor is private to only allow creating instances using the static methods above /// thus ensuring that only one of the shared pointers is non-null CacheValue(); -public: + public: /** * @brief Template method to get a shared pointer to the image with pixel type given as template argument. * @note At most one of the generated methods will provide a non-null pointer. @@ -148,55 +141,71 @@ class CacheValue */ unsigned long long int memorySize() const; -private: + private: std::shared_ptr> imgUChar; std::shared_ptr> imgFloat; std::shared_ptr> imgRGB; std::shared_ptr> imgRGBf; std::shared_ptr> imgRGBA; std::shared_ptr> imgRGBAf; - }; template<> -inline std::shared_ptr> CacheValue::get() { return imgUChar; } +inline std::shared_ptr> CacheValue::get() +{ + return imgUChar; +} template<> -inline std::shared_ptr> CacheValue::get() { return imgFloat; } +inline std::shared_ptr> CacheValue::get() +{ + return imgFloat; +} template<> -inline std::shared_ptr> CacheValue::get() { return imgRGB; } +inline std::shared_ptr> CacheValue::get() +{ + return imgRGB; +} template<> -inline std::shared_ptr> CacheValue::get() { return imgRGBf; } +inline std::shared_ptr> CacheValue::get() +{ + return imgRGBf; +} template<> -inline std::shared_ptr> CacheValue::get() { return imgRGBA; } +inline std::shared_ptr> CacheValue::get() +{ + return imgRGBA; +} template<> -inline std::shared_ptr> CacheValue::get() { return imgRGBAf; } - +inline std::shared_ptr> CacheValue::get() +{ + return imgRGBAf; +} /** * @brief A class for retrieving images from disk (optionally downscaled) that implements a caching mechanism. - * - * When creating an instance of this class, one must provide two memory size limits: + * + * When creating an instance of this class, one must provide two memory size limits: * - a capacity: the amount of space dedicated to storing images for later use * - a maximal size: the amount of space that cannot be exceeded. - * - * When one attempts to retrieve an image through this cache, the following policy applies: + * + * When one attempts to retrieve an image through this cache, the following policy applies: * 1. if the image is already in the cache, return it * 2. if the image fits in the capacity, load it, store it in the cache and return it - * 3. find the Leat-Recently-Used image in the cache that is not used externally and which size is bigger than the missing capacity, + * 3. find the Leat-Recently-Used image in the cache that is not used externally and which size is bigger than the missing capacity, * if such an image exists remove it and apply step 2 * 4. remove all images that are not used externally from Least- to Most-Recently-Used until the capacity is big enough to fit the image * or until there is nothing to remove * 5. if the image fits in the maximal size, load it, store it and return it * 6. the image is too big for the cache, throw an error. */ -class ImageCache +class ImageCache { -public: + public: /** * @brief Create a new image cache by defining memory usage limits and image reading options. * @param[in] capacity_MiB the cache capacity (in MiB) @@ -240,18 +249,12 @@ class ImageCache /** * @return information on the current cache state and usage */ - inline const CacheInfo& info() const - { - return _info; - } + inline const CacheInfo& info() const { return _info; } /** * @return the image reading options of the cache */ - inline const ImageReadOptions& readOptions() const - { - return _options; - } + inline const ImageReadOptions& readOptions() const { return _options; } /** * @brief Provide a description of the current internal state of the cache (useful for logging). @@ -259,11 +262,11 @@ class ImageCache */ std::string toString() const; -private: + private: /** * @brief Load a new image corresponding to the given key and add it as a new entry in the cache. * @param[in] key the key used to identify the entry in the cache - * @param[in] lockPeek lock on the peeking mutex, will be released + * @param[in] lockPeek lock on the peeking mutex, will be released */ template void load(const CacheKey& key, std::unique_lock& lockPeek); @@ -276,10 +279,8 @@ class ImageCache mutable std::mutex _mutexGeneral; mutable std::mutex _mutexPeek; - }; - // Since some methods in the ImageCache class are templated // their definition must be given in this header file @@ -294,9 +295,8 @@ std::shared_ptr> ImageCache::get(const std::string& filename, int do std::unique_lock lockPeek(_mutexPeek); - ALICEVISION_LOG_TRACE("[image] ImageCache: reading " << filename - << " with downscale level " << downscaleLevel - << " from thread " << std::this_thread::get_id()); + ALICEVISION_LOG_TRACE("[image] ImageCache: reading " << filename << " with downscale level " << downscaleLevel << " from thread " + << std::this_thread::get_id()); using TInfo = ColorTypeInfo; @@ -329,9 +329,9 @@ std::shared_ptr> ImageCache::get(const std::string& filename, int do int width, height; readImageSize(filename, width, height); unsigned long long int memSize = (width / downscaleLevel) * (height / downscaleLevel) * sizeof(TPix); - + // add image to cache if it fits in capacity - if (memSize + _info.contentSize <= _info.capacity) + if (memSize + _info.contentSize <= _info.capacity) { load(keyReq, lockPeek); @@ -372,10 +372,8 @@ std::shared_ptr> ImageCache::get(const std::string& filename, int do // remove as few unused images as possible while (missingCapacity > 0) { - auto it = std::find_if(_keys.begin(), _keys.end(), [this](const CacheKey& k){ - return _imagePtrs.at(k).useCount() == 1; - }); - + auto it = std::find_if(_keys.begin(), _keys.end(), [this](const CacheKey& k) { return _imagePtrs.at(k).useCount() == 1; }); + if (it != _keys.end()) { const CacheKey& key = *it; @@ -390,14 +388,14 @@ std::shared_ptr> ImageCache::get(const std::string& filename, int do missingCapacity = memSize + _info.contentSize - _info.capacity; } - else + else { break; } } // add image to cache if it fits in maxSize - if (memSize + _info.contentSize <= _info.maxSize) + if (memSize + _info.contentSize <= _info.maxSize) { load(keyReq, lockPeek); @@ -459,9 +457,9 @@ bool ImageCache::contains(const std::string& filename, int downscaleLevel) const CacheKey keyReq(filename, TInfo::size, TInfo::typeDesc, downscaleLevel, lastWriteTime); auto it = std::find(_keys.begin(), _keys.end(), keyReq); - + return it != _keys.end(); } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/Rgb.hpp b/src/aliceVision/image/Rgb.hpp index ab06e65344..a103020bce 100644 --- a/src/aliceVision/image/Rgb.hpp +++ b/src/aliceVision/image/Rgb.hpp @@ -22,15 +22,9 @@ struct rgb b = _b; } - inline rgb operator-(const rgb& _p) const - { - return rgb(r - _p.r, g - _p.g, b - _p.b); - } + inline rgb operator-(const rgb& _p) const { return rgb(r - _p.r, g - _p.g, b - _p.b); } - inline rgb operator+(const rgb& _p) const - { - return rgb(r + _p.r, g + _p.g, b + _p.b); - } + inline rgb operator+(const rgb& _p) const { return rgb(r + _p.r, g + _p.g, b + _p.b); } }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/image/Sampler.hpp b/src/aliceVision/image/Sampler.hpp index 0d28d07d90..d25c0a1161 100644 --- a/src/aliceVision/image/Sampler.hpp +++ b/src/aliceVision/image/Sampler.hpp @@ -28,54 +28,51 @@ namespace image { ** They all contains the neighbor_width variable that specify the number of neighbors used for sampling */ - /** ** Nearest sampling (ie: find the nearest pixel to a specified position) **/ struct SamplerNearest { -public: - // Nearest sampling is only between two pixels - static const int neighbor_width = 2 ; - - /** - ** @brief Computes weight associated to neighboring pixels - ** @author Romuald Perrot - ** @param x Sampling position - ** @param[out] weigth Sampling factors associated to the neighboring - ** @note weight must be at least neighbor_width length - **/ - void operator()( const double x , double * const weight ) const - { - weight[0] = ( x < 0.5 ) ? 1.0 : 0.0 ; - weight[1] = ( x >= 0.5 ) ? 1.0 : 0.0 ; - } -} ; - + public: + // Nearest sampling is only between two pixels + static const int neighbor_width = 2; + + /** + ** @brief Computes weight associated to neighboring pixels + ** @author Romuald Perrot + ** @param x Sampling position + ** @param[out] weigth Sampling factors associated to the neighboring + ** @note weight must be at least neighbor_width length + **/ + void operator()(const double x, double* const weight) const + { + weight[0] = (x < 0.5) ? 1.0 : 0.0; + weight[1] = (x >= 0.5) ? 1.0 : 0.0; + } +}; /** ** Linear sampling (ie: linear interpolation between two pixels) **/ struct SamplerLinear { -public: - // Linear sampling is between two pixels - static const int neighbor_width = 2 ; - - /** - ** @brief Computes weight associated to neighboring pixels - ** @author Romuald Perrot - ** @param x Sampling position - ** @param[out] weigth Sampling factors associated to the neighboring - ** @note weight must be at least neighbor_width length - **/ - void operator()( const double x , double * const weigth ) const - { - weigth[0] = 1.0 - x ; - weigth[1] = x ; - } -} ; - + public: + // Linear sampling is between two pixels + static const int neighbor_width = 2; + + /** + ** @brief Computes weight associated to neighboring pixels + ** @author Romuald Perrot + ** @param x Sampling position + ** @param[out] weigth Sampling factors associated to the neighboring + ** @note weight must be at least neighbor_width length + **/ + void operator()(const double x, double* const weigth) const + { + weigth[0] = 1.0 - x; + weigth[1] = x; + } +}; /** ** Cubic interpolation between 4 pixels @@ -88,67 +85,64 @@ struct SamplerLinear **/ struct SamplerCubic { -public: - // Cubic interpolation is between 4 pixels - static const int neighbor_width = 4 ; - - /** - ** @brief Constructor - ** @param sharpness_coef Sharpness coefficient used to control sharpness of the cubic curve - ** @note sharpnedd_coef must be between -0.75 to -0.5 - ** -0.5 gives better mathematically result (ie: approximation at 3 order precision) - **/ - SamplerCubic( const double sharpness_coef = -0.5 ) - : _sharpness( sharpness_coef ) - { - - } - - /** - ** @brief Computes weight associated to neighboring pixels - ** @author Romuald Perrot - ** @param x Sampling position - ** @param[out] weigth Sampling factors associated to the neighboring - ** @note weight must be at least neighbor_width length - **/ - void operator()( const double x , double * const weigth ) const - { - // remember : - // A B x C D - - // weigth[0] -> weight for A - // weight[1] -> weight for B - // weight[2] -> weight for C - // weight[3] -> weigth for D - - weigth[0] = CubicInter12( _sharpness , x + 1.0 ) ; - weigth[1] = CubicInter01( _sharpness , x ) ; - weigth[2] = CubicInter01( _sharpness , 1.0 - x ) ; - weigth[3] = CubicInter12( _sharpness , 2.0 - x ) ; - } - -private: - // Cubic interpolation for x \in [0 ; 1 [ - static double CubicInter01( const double sharpness , const double x ) - { - // A = sharpness - // f(x) = ( A + 2 ) * x ^ 3 - ( A + 3 ) * x ^ 2 + 1 - return ( ( sharpness + 2.0 ) * x - ( sharpness + 3.0 ) ) * x * x + 1.0 ; - } - - // Cubic interpolation for x \in [1 ; 2 [ - static double CubicInter12( const double sharpness , const double x ) - { - - // A = sharpness - // f(x) = A * x^3 - 5 * A * x^2 + 8 * A * x - 4 * a - - return ( ( sharpness * x - 5.0 * sharpness ) * x + 8.0 * sharpness ) * x - 4.0 * sharpness; - } - - // Sharpness coefficient - double _sharpness ; -} ; + public: + // Cubic interpolation is between 4 pixels + static const int neighbor_width = 4; + + /** + ** @brief Constructor + ** @param sharpness_coef Sharpness coefficient used to control sharpness of the cubic curve + ** @note sharpnedd_coef must be between -0.75 to -0.5 + ** -0.5 gives better mathematically result (ie: approximation at 3 order precision) + **/ + SamplerCubic(const double sharpness_coef = -0.5) + : _sharpness(sharpness_coef) + {} + + /** + ** @brief Computes weight associated to neighboring pixels + ** @author Romuald Perrot + ** @param x Sampling position + ** @param[out] weigth Sampling factors associated to the neighboring + ** @note weight must be at least neighbor_width length + **/ + void operator()(const double x, double* const weigth) const + { + // remember : + // A B x C D + + // weigth[0] -> weight for A + // weight[1] -> weight for B + // weight[2] -> weight for C + // weight[3] -> weigth for D + + weigth[0] = CubicInter12(_sharpness, x + 1.0); + weigth[1] = CubicInter01(_sharpness, x); + weigth[2] = CubicInter01(_sharpness, 1.0 - x); + weigth[3] = CubicInter12(_sharpness, 2.0 - x); + } + + private: + // Cubic interpolation for x \in [0 ; 1 [ + static double CubicInter01(const double sharpness, const double x) + { + // A = sharpness + // f(x) = ( A + 2 ) * x ^ 3 - ( A + 3 ) * x ^ 2 + 1 + return ((sharpness + 2.0) * x - (sharpness + 3.0)) * x * x + 1.0; + } + + // Cubic interpolation for x \in [1 ; 2 [ + static double CubicInter12(const double sharpness, const double x) + { + // A = sharpness + // f(x) = A * x^3 - 5 * A * x^2 + 8 * A * x - 4 * a + + return ((sharpness * x - 5.0 * sharpness) * x + 8.0 * sharpness) * x - 4.0 * sharpness; + } + + // Sharpness coefficient + double _sharpness; +}; /** ** Sampler spline16 -> Interpolation on 4 points used for 2d ressampling (16 = 4x4 sampling) @@ -216,25 +210,24 @@ struct SamplerCubic **/ struct SamplerSpline16 { -public: - static const int neighbor_width = 4 ; - - - /** - ** @brief Computes weight associated to neighboring pixels - ** @author Romuald Perrot - ** @param x Sampling position - ** @param[out] weigth Sampling factors associated to the neighboring - ** @note weight must be at least neighbor_width length - **/ - void operator()( const double x , double * const weigth ) const - { - weigth[0] = ( ( -1.0 / 3.0 * x + 4.0 / 5.0 ) * x - 7.0 / 15.0 ) * x ; - weigth[1] = ( ( x - 9.0 / 5.0 ) * x - 1.0 / 5.0 ) * x + 1.0 ; - weigth[2] = ( ( 6.0 / 5.0 - x ) * x + 4.0 / 5.0 ) * x ; - weigth[3] = ( ( 1.0 / 3.0 * x - 1.0 / 5.0 ) * x - 2.0 / 15.0 ) * x ; - } -} ; + public: + static const int neighbor_width = 4; + + /** + ** @brief Computes weight associated to neighboring pixels + ** @author Romuald Perrot + ** @param x Sampling position + ** @param[out] weigth Sampling factors associated to the neighboring + ** @note weight must be at least neighbor_width length + **/ + void operator()(const double x, double* const weigth) const + { + weigth[0] = ((-1.0 / 3.0 * x + 4.0 / 5.0) * x - 7.0 / 15.0) * x; + weigth[1] = ((x - 9.0 / 5.0) * x - 1.0 / 5.0) * x + 1.0; + weigth[2] = ((6.0 / 5.0 - x) * x + 4.0 / 5.0) * x; + weigth[3] = ((1.0 / 3.0 * x - 1.0 / 5.0) * x - 2.0 / 15.0) * x; + } +}; /** ** Sampler spline 36 @@ -242,26 +235,26 @@ struct SamplerSpline16 **/ struct SamplerSpline36 { -public: - static const int neighbor_width = 6 ; - - /** - ** @brief Computes weight associated to neighboring pixels - ** @author Romuald Perrot - ** @param x Sampling position - ** @param[out] weigth Sampling factors associated to the neighboring - ** @note weight must be at least neighbor_width length - **/ - void operator()( const double x , double * const weigth ) const - { - weigth[0] = ( ( 1.0 / 11.0 * x - 45.0 / 209.0 ) * x + 26.0 / 209.0 ) * x ; - weigth[1] = ( ( - 6.0 / 11.0 * x + 270.0 / 209.0 ) * x - 156.0 / 209.0 ) * x ; - weigth[2] = ( ( 13.0 / 11.0 * x - 453.0 / 209.0 ) * x - 3.0 / 209.0 ) * x + 1.0 ; - weigth[3] = ( ( - 13.0 / 11.0 * x + 288.0 / 209.0 ) * x + 168.0 / 209.0 ) * x ; - weigth[4] = ( ( 6.0 / 11.0 * x - 72.0 / 209.0 ) * x - 42.0 / 209.0 ) * x ; - weigth[5] = ( ( - 1.0 / 11.0 * x + 12.0 / 209.0 ) * x + 7.0 / 209.0 ) * x ; - } -} ; + public: + static const int neighbor_width = 6; + + /** + ** @brief Computes weight associated to neighboring pixels + ** @author Romuald Perrot + ** @param x Sampling position + ** @param[out] weigth Sampling factors associated to the neighboring + ** @note weight must be at least neighbor_width length + **/ + void operator()(const double x, double* const weigth) const + { + weigth[0] = ((1.0 / 11.0 * x - 45.0 / 209.0) * x + 26.0 / 209.0) * x; + weigth[1] = ((-6.0 / 11.0 * x + 270.0 / 209.0) * x - 156.0 / 209.0) * x; + weigth[2] = ((13.0 / 11.0 * x - 453.0 / 209.0) * x - 3.0 / 209.0) * x + 1.0; + weigth[3] = ((-13.0 / 11.0 * x + 288.0 / 209.0) * x + 168.0 / 209.0) * x; + weigth[4] = ((6.0 / 11.0 * x - 72.0 / 209.0) * x - 42.0 / 209.0) * x; + weigth[5] = ((-1.0 / 11.0 * x + 12.0 / 209.0) * x + 7.0 / 209.0) * x; + } +}; /** ** Sampler spline 64 @@ -269,262 +262,222 @@ struct SamplerSpline36 **/ struct SamplerSpline64 { -public: - static const int neighbor_width = 8 ; - - /** - ** @brief Computes weight associated to neighboring pixels - ** @author Romuald Perrot - ** @param x Sampling position - ** @param[out] weigth Sampling factors associated to the neighboring - ** @note weight must be at least neighbor_width length - **/ - void operator()( const double x , double * const weigth ) const - { - weigth[0] = ( ( - 1.0 / 41.0 * x + 168.0 / 2911.0 ) * x - 97.0 / 2911.0 ) * x ; - weigth[1] = ( ( 6.0 / 41.0 * x - 1008.0 / 2911.0 ) * x + 582.0 / 2911.0 ) * x ; - weigth[2] = ( ( -24.0 / 41.0 * x + 4032.0 / 2911.0 ) * x - 2328.0 / 2911.0 ) * x ; - weigth[3] = ( ( 49.0 / 41.0 * x - 6387.0 / 2911.0 ) * x - 3.0 / 2911.0 ) * x + 1.0 ; - weigth[4] = ( ( -49.0 / 41.0 * x + 4050.0 / 2911.0 ) * x + 2340.0 / 2911.0 ) * x ; - weigth[5] = ( ( 24.0 / 41.0 * x - 1080.0 / 2911.0 ) * x - 624.0 / 2911.0 ) * x ; - weigth[6] = ( ( - 6.0 / 41.0 * x + 270.0 / 2911.0 ) * x + 156.0 / 2911.0 ) * x ; - weigth[7] = ( ( 1.0 / 41.0 * x - 45.0 / 2911.0 ) * x - 26.0 / 2911.0 ) * x ; - } -} ; - - -template< typename T> + public: + static const int neighbor_width = 8; + + /** + ** @brief Computes weight associated to neighboring pixels + ** @author Romuald Perrot + ** @param x Sampling position + ** @param[out] weigth Sampling factors associated to the neighboring + ** @note weight must be at least neighbor_width length + **/ + void operator()(const double x, double* const weigth) const + { + weigth[0] = ((-1.0 / 41.0 * x + 168.0 / 2911.0) * x - 97.0 / 2911.0) * x; + weigth[1] = ((6.0 / 41.0 * x - 1008.0 / 2911.0) * x + 582.0 / 2911.0) * x; + weigth[2] = ((-24.0 / 41.0 * x + 4032.0 / 2911.0) * x - 2328.0 / 2911.0) * x; + weigth[3] = ((49.0 / 41.0 * x - 6387.0 / 2911.0) * x - 3.0 / 2911.0) * x + 1.0; + weigth[4] = ((-49.0 / 41.0 * x + 4050.0 / 2911.0) * x + 2340.0 / 2911.0) * x; + weigth[5] = ((24.0 / 41.0 * x - 1080.0 / 2911.0) * x - 624.0 / 2911.0) * x; + weigth[6] = ((-6.0 / 41.0 * x + 270.0 / 2911.0) * x + 156.0 / 2911.0) * x; + weigth[7] = ((1.0 / 41.0 * x - 45.0 / 2911.0) * x - 26.0 / 2911.0) * x; + } +}; + +template struct RealPixel { - typedef T base_type ; - typedef double real_type ; - - static real_type convert_to_real( const base_type & val ) - { - return static_cast( val ) ; - } - - static base_type convert_from_real( const real_type & val ) - { - return static_cast( val ); - } - - static real_type zero() - { - return real_type(0); - } + typedef T base_type; + typedef double real_type; + + static real_type convert_to_real(const base_type& val) { return static_cast(val); } + + static base_type convert_from_real(const real_type& val) { return static_cast(val); } + + static real_type zero() { return real_type(0); } }; // overloading for unsigned char template<> struct RealPixel { - typedef unsigned char base_type ; - typedef double real_type ; - - static real_type convert_to_real( const base_type & val ) - { - return static_cast( val ) ; - } - - static base_type convert_from_real( const real_type & val ) - { - // handle out of range values. - return (val < 0.0) ? - 0 : - (val > static_cast( std::numeric_limits::max() ) ? - std::numeric_limits::max() : - static_cast( val + 0.5 ) ) ; - } - - static real_type zero() - { - return 0.; - } -} ; + typedef unsigned char base_type; + typedef double real_type; + + static real_type convert_to_real(const base_type& val) { return static_cast(val); } + + static base_type convert_from_real(const real_type& val) + { + // handle out of range values. + return (val < 0.0) ? 0 + : (val > static_cast(std::numeric_limits::max()) ? std::numeric_limits::max() + : static_cast(val + 0.5)); + } + + static real_type zero() { return 0.; } +}; // overloading for float template<> struct RealPixel { - typedef float base_type ; - typedef double real_type ; - - static real_type convert_to_real( const base_type & val ) - { - return static_cast(val); - } - - static base_type convert_from_real( const real_type & val ) - { - return static_cast(val); - } - - static real_type zero() - { - return real_type(0); - } + typedef float base_type; + typedef double real_type; + + static real_type convert_to_real(const base_type& val) { return static_cast(val); } + + static base_type convert_from_real(const real_type& val) { return static_cast(val); } + + static real_type zero() { return real_type(0); } }; // overloading for Rgb -template< typename T > -struct RealPixel< Rgb > +template +struct RealPixel> { - typedef Rgb base_type ; - typedef Rgb real_type ; - - static real_type convert_to_real( const base_type & val ) - { - return real_type( val.template cast() ) ; - } - - static base_type convert_from_real( const real_type & val ) - { - return base_type( RealPixel::convert_from_real( val.r() ) , - RealPixel::convert_from_real( val.g() ) , - RealPixel::convert_from_real( val.b() ) ) ; - } - - static real_type zero() - { - return real_type(real_type::Zero()); - } -} ; + typedef Rgb base_type; + typedef Rgb real_type; + + static real_type convert_to_real(const base_type& val) { return real_type(val.template cast()); } + + static base_type convert_from_real(const real_type& val) + { + return base_type( + RealPixel::convert_from_real(val.r()), RealPixel::convert_from_real(val.g()), RealPixel::convert_from_real(val.b())); + } + + static real_type zero() { return real_type(real_type::Zero()); } +}; // overloading for rgba -template< typename T> -struct RealPixel< Rgba > +template +struct RealPixel> { - typedef Rgba base_type ; - typedef Rgba real_type ; - - static real_type convert_to_real( const base_type & val ) - { - return real_type( val.template cast() ); - } - - static base_type convert_from_real( const real_type & val ) - { - return base_type( RealPixel::convert_from_real( val.r() ) , - RealPixel::convert_from_real( val.g() ) , - RealPixel::convert_from_real( val.b() ) , - RealPixel::convert_from_real( val.a() ) ) ; - } - - static real_type zero() - { - return real_type(real_type::Zero()); - } -} ; + typedef Rgba base_type; + typedef Rgba real_type; + + static real_type convert_to_real(const base_type& val) { return real_type(val.template cast()); } + + static base_type convert_from_real(const real_type& val) + { + return base_type(RealPixel::convert_from_real(val.r()), + RealPixel::convert_from_real(val.g()), + RealPixel::convert_from_real(val.b()), + RealPixel::convert_from_real(val.a())); + } + + static real_type zero() { return real_type(real_type::Zero()); } +}; /** ** Generic sampling of image using a sampling function **/ -template< typename SamplerFunc> +template struct Sampler2d { - Sampler2d( const SamplerFunc & sampler = SamplerFunc() ) - : _sampler( sampler ) , - _half_width( SamplerFunc::neighbor_width / 2 ) - { - - } - - /** - ** Sample image at a specified position - ** @param src Input image - ** @param y Y-coordinate of sampling - ** @param x X-coordinate of sampling - ** @return Sampled value - **/ - template - T operator()( const Image & src , const float y , const float x ) const - { - const int im_width = src.Width() ; - const int im_height = src.Height() ; - - // Get sampler coefficients - double coefs_x[ SamplerFunc::neighbor_width ] ; - double coefs_y[ SamplerFunc::neighbor_width ] ; - - // Compute difference between exact pixel location and sample - const double dx = static_cast( x ) - floor( x ) ; - const double dy = static_cast( y ) - floor( y ) ; - - // Get sampler weights - _sampler( dx , coefs_x ) ; - _sampler( dy , coefs_y ) ; - - auto res = RealPixel::zero(); - - // integer position of sample (x,y) - const int grid_x = static_cast( floor( x ) ); - const int grid_y = static_cast( floor( y ) ); - - // Sample a grid around specified grid point - double total_weight = 0.0 ; - for( int i = 0 ; i < SamplerFunc::neighbor_width ; ++i ) + Sampler2d(const SamplerFunc& sampler = SamplerFunc()) + : _sampler(sampler), + _half_width(SamplerFunc::neighbor_width / 2) + {} + + /** + ** Sample image at a specified position + ** @param src Input image + ** @param y Y-coordinate of sampling + ** @param x X-coordinate of sampling + ** @return Sampled value + **/ + template + T operator()(const Image& src, const float y, const float x) const { - // Get current i value - // +1 for correct scheme (draw it to be conviced) - const int cur_i = grid_y + 1 + i - _half_width ; - - // handle out of range - if( cur_i < 0 || cur_i >= im_height ) - { - continue ; - } - - for( int j = 0 ; j < SamplerFunc::neighbor_width ; ++j ) - { - // Get current j value - // +1 for the same reason - const int cur_j = grid_x + 1 + j - _half_width ; - - // handle out of range - if( cur_j < 0 || cur_j >= im_width ) - { - continue ; - } + const int im_width = src.Width(); + const int im_height = src.Height(); + // Get sampler coefficients + double coefs_x[SamplerFunc::neighbor_width]; + double coefs_y[SamplerFunc::neighbor_width]; - // sample input image and weight according to sampler - const double w = coefs_x[ j ] * coefs_y[ i ] ; - const typename RealPixel::real_type pix = RealPixel::convert_to_real( src( cur_i , cur_j ) ) ; - const typename RealPixel::real_type wp = pix * w ; - res += wp ; + // Compute difference between exact pixel location and sample + const double dx = static_cast(x) - floor(x); + const double dy = static_cast(y) - floor(y); - total_weight += w ; - } - } + // Get sampler weights + _sampler(dx, coefs_x); + _sampler(dy, coefs_y); - // If value too small, it should be so instable, so return the sampled value - if( total_weight <= 0.2 ) - { - int row = floor(y); - int col = floor(x); + auto res = RealPixel::zero(); - if (row < 0) row = 0; - if (col < 0) col = 0; - if (row >= im_height) row = im_height - 1; - if (col >= im_width) col = im_width - 1; + // integer position of sample (x,y) + const int grid_x = static_cast(floor(x)); + const int grid_y = static_cast(floor(y)); - return src(row, col); - } + // Sample a grid around specified grid point + double total_weight = 0.0; + for (int i = 0; i < SamplerFunc::neighbor_width; ++i) + { + // Get current i value + // +1 for correct scheme (draw it to be conviced) + const int cur_i = grid_y + 1 + i - _half_width; + + // handle out of range + if (cur_i < 0 || cur_i >= im_height) + { + continue; + } + + for (int j = 0; j < SamplerFunc::neighbor_width; ++j) + { + // Get current j value + // +1 for the same reason + const int cur_j = grid_x + 1 + j - _half_width; + + // handle out of range + if (cur_j < 0 || cur_j >= im_width) + { + continue; + } + + // sample input image and weight according to sampler + const double w = coefs_x[j] * coefs_y[i]; + const typename RealPixel::real_type pix = RealPixel::convert_to_real(src(cur_i, cur_j)); + const typename RealPixel::real_type wp = pix * w; + res += wp; + + total_weight += w; + } + } - if( total_weight != 1.0 ) - { - res /= total_weight ; - } + // If value too small, it should be so instable, so return the sampled value + if (total_weight <= 0.2) + { + int row = floor(y); + int col = floor(x); + + if (row < 0) + row = 0; + if (col < 0) + col = 0; + if (row >= im_height) + row = im_height - 1; + if (col >= im_width) + col = im_width - 1; + + return src(row, col); + } + if (total_weight != 1.0) + { + res /= total_weight; + } - return RealPixel::convert_from_real( res ) ; - } + return RealPixel::convert_from_real(res); + } -private: - SamplerFunc _sampler ; - const int _half_width ; + private: + SamplerFunc _sampler; + const int _half_width; }; -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/all.hpp b/src/aliceVision/image/all.hpp index e6ee650b03..9819b41871 100644 --- a/src/aliceVision/image/all.hpp +++ b/src/aliceVision/image/all.hpp @@ -9,7 +9,7 @@ // Get rid of the specific MSVC compiler warnings. #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -# define _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS #endif #include "aliceVision/image/Image.hpp" diff --git a/src/aliceVision/image/cache.cpp b/src/aliceVision/image/cache.cpp index 70e383686e..576fff0bc7 100644 --- a/src/aliceVision/image/cache.cpp +++ b/src/aliceVision/image/cache.cpp @@ -4,336 +4,343 @@ #include - -namespace aliceVision -{ -namespace image +namespace aliceVision { +namespace image { + +CacheManager::CacheManager(const std::string& pathStorage, size_t blockSize, size_t maxBlocksPerIndex) + : _blockSize(blockSize), + _incoreBlockUsageCount(0), + _incoreBlockUsageMax(10), + _blockCountPerIndex(maxBlocksPerIndex), + _basePathStorage(pathStorage) { + wipe(); +} -CacheManager::CacheManager(const std::string & pathStorage, size_t blockSize, size_t maxBlocksPerIndex) : -_blockSize(blockSize), -_incoreBlockUsageCount(0), -_incoreBlockUsageMax(10), -_blockCountPerIndex(maxBlocksPerIndex), -_basePathStorage(pathStorage) +CacheManager::~CacheManager() { wipe(); } + +void CacheManager::wipe() { - wipe(); -} + deleteIndexFiles(); -CacheManager::~CacheManager() { - wipe(); + _mru.clear(); + + _incoreBlockUsageCount = 0; + _nextStartBlockId = 0; + _nextObjectId = 0; } -void CacheManager::wipe() { - deleteIndexFiles(); +void CacheManager::setMaxMemory(size_t maxMemorySize) { _incoreBlockUsageMax = maxMemorySize / _blockSize; } - _mru.clear(); - - _incoreBlockUsageCount = 0; - _nextStartBlockId = 0; - _nextObjectId = 0; -} +void CacheManager::setInCoreMaxObjectCount(size_t max) { _incoreBlockUsageMax = max; } + +std::string CacheManager::getPathForIndex(size_t indexId) +{ + if (_indexPaths.find(indexId) == _indexPaths.end()) + { + boost::filesystem::path path(_basePathStorage); + path /= boost::filesystem::unique_path(); + path += ".idx"; + _indexPaths[indexId] = path.string(); + } -void CacheManager::setMaxMemory(size_t maxMemorySize) { - _incoreBlockUsageMax = maxMemorySize / _blockSize; + return _indexPaths[indexId]; } -void CacheManager::setInCoreMaxObjectCount(size_t max) { - _incoreBlockUsageMax = max; +void CacheManager::deleteIndexFiles() +{ + std::size_t cacheSize = 0; + for (std::pair& p : _indexPaths) + { + const std::size_t s = boost::filesystem::file_size(p.second); + ALICEVISION_LOG_TRACE("CacheManager::deleteIndexFiles: '" << p.second << "': " << s / (1024 * 1024) << "MB."); + cacheSize += s; + } + ALICEVISION_LOG_DEBUG("CacheManager::deleteIndexFiles: cache size is " << cacheSize / (1024 * 1024) << "MB."); + + // Remove all cache files + for (std::pair& p : _indexPaths) + { + boost::filesystem::path path(p.second); + boost::filesystem::remove(path); + } + + // Remove list of cache files + _indexPaths.clear(); } -std::string CacheManager::getPathForIndex(size_t indexId) { +bool CacheManager::prepareBlockGroup(size_t startBlockId, size_t blocksCount) +{ + size_t index_id = startBlockId / _blockCountPerIndex; + size_t block_id_in_index = startBlockId % _blockCountPerIndex; + size_t position_in_index = block_id_in_index * _blockSize; + size_t len = _blockSize * blocksCount; - if (_indexPaths.find(indexId) == _indexPaths.end()) { + std::string pathname = getPathForIndex(index_id); + boost::filesystem::path path(pathname); - boost::filesystem::path path(_basePathStorage); - path /= boost::filesystem::unique_path(); - path += ".idx"; + std::ofstream file_index; + if (boost::filesystem::exists(path)) + { + file_index.open(pathname, std::ios::binary | std::ios::out | std::ios::in); + } + else + { + file_index.open(pathname, std::ios::binary | std::ios::out); + } - _indexPaths[indexId] = path.string(); - } - - return _indexPaths[indexId]; -} + if (!file_index.is_open()) + { + return false; + } -void CacheManager::deleteIndexFiles() { - - std::size_t cacheSize = 0; - for (std::pair & p : _indexPaths) - { - const std::size_t s = boost::filesystem::file_size(p.second); - ALICEVISION_LOG_TRACE("CacheManager::deleteIndexFiles: '" << p.second << "': " << s / (1024*1024) << "MB."); - cacheSize += s; - } - ALICEVISION_LOG_DEBUG("CacheManager::deleteIndexFiles: cache size is " << cacheSize / (1024*1024) << "MB."); - - // Remove all cache files - for (std::pair & p : _indexPaths) - { - boost::filesystem::path path(p.second); - boost::filesystem::remove(path); - } - - // Remove list of cache files - _indexPaths.clear(); -} + ALICEVISION_LOG_TRACE("CacheManager::prepareBlockGroup: " << blocksCount * _blockSize << " bytes to '" << path << "'."); -bool CacheManager::prepareBlockGroup(size_t startBlockId, size_t blocksCount) { - - size_t index_id = startBlockId / _blockCountPerIndex; - size_t block_id_in_index = startBlockId % _blockCountPerIndex; - size_t position_in_index = block_id_in_index * _blockSize; - size_t len = _blockSize * blocksCount; - - std::string pathname = getPathForIndex(index_id); - boost::filesystem::path path(pathname); - - std::ofstream file_index; - if (boost::filesystem::exists(path)) { - file_index.open(pathname, std::ios::binary | std::ios::out | std::ios::in); - } - else { - file_index.open(pathname, std::ios::binary | std::ios::out); - } - - if (!file_index.is_open()) { - return false; - } - - ALICEVISION_LOG_TRACE("CacheManager::prepareBlockGroup: " << blocksCount * _blockSize << " bytes to '" << path << "'."); - - /*write a dummy byte at the end of the tile to "book" this place on disk*/ - file_index.seekp(position_in_index + len - 1, file_index.beg); - if (!file_index) { - return false; - } - - char c[1]; - c[0] = 0xff; - file_index.write(c, 1); - - return true; -} + /*write a dummy byte at the end of the tile to "book" this place on disk*/ + file_index.seekp(position_in_index + len - 1, file_index.beg); + if (!file_index) + { + return false; + } -std::unique_ptr CacheManager::load(size_t startBlockId, size_t blockCount) { - - const size_t indexId = startBlockId / _blockCountPerIndex; - const size_t blockIdInIndex = startBlockId % _blockCountPerIndex; - const size_t positionInIndex = blockIdInIndex * _blockSize; - const size_t groupLength = _blockSize * blockCount; - - const std::string path = getPathForIndex(indexId); - - std::ifstream file_index(path, std::ios::binary); - if (!file_index.is_open()) { - return std::unique_ptr(); - } - - file_index.seekg(positionInIndex, std::ios::beg); - if (file_index.fail()) { - return std::unique_ptr(); - } - - ALICEVISION_LOG_TRACE("CacheManager::load: read " << groupLength << " bytes from '" << path << "' at position " << positionInIndex << "."); - - std::unique_ptr data(new unsigned char[groupLength]); - file_index.read(reinterpret_cast(data.get()), groupLength); - if (!file_index) { - return std::unique_ptr(); - } - - return data; + char c[1]; + c[0] = 0xff; + file_index.write(c, 1); + + return true; } -bool CacheManager::save(std::unique_ptr && data, size_t startBlockId, size_t blockCount) { - - const size_t indexId = startBlockId / _blockCountPerIndex; - const size_t blockIdInIndex = startBlockId % _blockCountPerIndex; - const size_t positionInIndex = blockIdInIndex * _blockSize; - const size_t groupLength = _blockSize * blockCount; - - const std::string path = getPathForIndex(indexId); - - std::ofstream file_index(path, std::ios::binary | std::ios::out | std::ios::in); - if (!file_index.is_open()) { - return false; - } - - file_index.seekp(positionInIndex, std::ios::beg); - if (file_index.fail()) { - return false; - } - - const unsigned char * bytesToWrite = data.get(); - if (bytesToWrite == nullptr) { - return false; - } - else { - // Write data - ALICEVISION_LOG_TRACE("CacheManager::save: write " << groupLength << " bytes to '" << path << "' at position " << positionInIndex << "."); - file_index.write(reinterpret_cast(bytesToWrite), groupLength); - } - - if (!file_index) { - return false; - } - - file_index.close(); - - return true; +std::unique_ptr CacheManager::load(size_t startBlockId, size_t blockCount) +{ + const size_t indexId = startBlockId / _blockCountPerIndex; + const size_t blockIdInIndex = startBlockId % _blockCountPerIndex; + const size_t positionInIndex = blockIdInIndex * _blockSize; + const size_t groupLength = _blockSize * blockCount; + + const std::string path = getPathForIndex(indexId); + + std::ifstream file_index(path, std::ios::binary); + if (!file_index.is_open()) + { + return std::unique_ptr(); + } + + file_index.seekg(positionInIndex, std::ios::beg); + if (file_index.fail()) + { + return std::unique_ptr(); + } + + ALICEVISION_LOG_TRACE("CacheManager::load: read " << groupLength << " bytes from '" << path << "' at position " << positionInIndex << "."); + + std::unique_ptr data(new unsigned char[groupLength]); + file_index.read(reinterpret_cast(data.get()), groupLength); + if (!file_index) + { + return std::unique_ptr(); + } + + return data; } -size_t CacheManager::getFreeBlockId(size_t blockCount) { - - size_t ret; - std::list & freeBlocksForCount = _freeBlocks[blockCount]; - - if (freeBlocksForCount.empty()) { - ret = _nextStartBlockId; - _nextStartBlockId += blockCount; - } - else { - ret = freeBlocksForCount.front(); - freeBlocksForCount.pop_front(); - } - - return ret; +bool CacheManager::save(std::unique_ptr&& data, size_t startBlockId, size_t blockCount) +{ + const size_t indexId = startBlockId / _blockCountPerIndex; + const size_t blockIdInIndex = startBlockId % _blockCountPerIndex; + const size_t positionInIndex = blockIdInIndex * _blockSize; + const size_t groupLength = _blockSize * blockCount; + + const std::string path = getPathForIndex(indexId); + + std::ofstream file_index(path, std::ios::binary | std::ios::out | std::ios::in); + if (!file_index.is_open()) + { + return false; + } + + file_index.seekp(positionInIndex, std::ios::beg); + if (file_index.fail()) + { + return false; + } + + const unsigned char* bytesToWrite = data.get(); + if (bytesToWrite == nullptr) + { + return false; + } + else + { + // Write data + ALICEVISION_LOG_TRACE("CacheManager::save: write " << groupLength << " bytes to '" << path << "' at position " << positionInIndex << "."); + file_index.write(reinterpret_cast(bytesToWrite), groupLength); + } + + if (!file_index) + { + return false; + } + + file_index.close(); + + return true; } -bool CacheManager::createObject(size_t & objectId, size_t blockCount) { - - objectId = _nextObjectId; - _nextObjectId++; +size_t CacheManager::getFreeBlockId(size_t blockCount) +{ + size_t ret; + std::list& freeBlocksForCount = _freeBlocks[blockCount]; - MemoryItem item; - item.startBlockId = ~0; - item.countBlock = blockCount; - _memoryMap[objectId] = item; + if (freeBlocksForCount.empty()) + { + ret = _nextStartBlockId; + _nextStartBlockId += blockCount; + } + else + { + ret = freeBlocksForCount.front(); + freeBlocksForCount.pop_front(); + } - return true; + return ret; } -bool CacheManager::acquireObject(std::unique_ptr & data, size_t objectId) { - - MemoryMap::iterator itfind = _memoryMap.find(objectId); - if (itfind == _memoryMap.end()) { - return false; - } - - MemoryItem memitem = itfind->second; - MRUItem item; - item.objectId = objectId; - item.objectSize = memitem.countBlock; - - /* Check mru */ - std::pair p = _mru.push_front(item); - if (p.second) { - - /* - Effectively added to the mru. - This means that we have to find this in the storage - */ - if (memitem.startBlockId == ~0) { - std::unique_ptr buffer(new unsigned char[_blockSize * memitem.countBlock]); - data = std::move(buffer); - } - else { - data = std::move(load(memitem.startBlockId, memitem.countBlock)); - } - - /*Update memory usage*/ - _incoreBlockUsageCount += memitem.countBlock; - } - else { - /* - The uid is present in the mru, put it in first position. - Note that the item may contain a previously deleted info - */ - _mru.relocate(_mru.begin(), p.first); - } - - while (_incoreBlockUsageCount > _incoreBlockUsageMax && _mru.size() > 1) { - - MRUItem item = _mru.back(); - - /*Remove item from mru*/ - _mru.pop_back(); - - /*Update memory usage*/ - _incoreBlockUsageCount -= item.objectSize; - - onRemovedFromMRU(item.objectId); - } - - return true; +bool CacheManager::createObject(size_t& objectId, size_t blockCount) +{ + objectId = _nextObjectId; + _nextObjectId++; + + MemoryItem item; + item.startBlockId = ~0; + item.countBlock = blockCount; + _memoryMap[objectId] = item; + + return true; } -bool CacheManager::saveObject(std::unique_ptr && data, size_t objectId) { - - MemoryMap::iterator itfind = _memoryMap.find(objectId); - if (itfind == _memoryMap.end()) { - return false; - } +bool CacheManager::acquireObject(std::unique_ptr& data, size_t objectId) +{ + MemoryMap::iterator itfind = _memoryMap.find(objectId); + if (itfind == _memoryMap.end()) + { + return false; + } - MemoryItem item = itfind->second; + MemoryItem memitem = itfind->second; + MRUItem item; + item.objectId = objectId; + item.objectSize = memitem.countBlock; - if (itfind->second.startBlockId == ~0) { - - item.startBlockId = getFreeBlockId(item.countBlock); - _memoryMap[objectId] = item; + /* Check mru */ + std::pair p = _mru.push_front(item); + if (p.second) + { + /* + Effectively added to the mru. + This means that we have to find this in the storage + */ + if (memitem.startBlockId == ~0) + { + std::unique_ptr buffer(new unsigned char[_blockSize * memitem.countBlock]); + data = std::move(buffer); + } + else + { + data = std::move(load(memitem.startBlockId, memitem.countBlock)); + } - prepareBlockGroup(item.startBlockId, item.countBlock); - } + /*Update memory usage*/ + _incoreBlockUsageCount += memitem.countBlock; + } + else + { + /* + The uid is present in the mru, put it in first position. + Note that the item may contain a previously deleted info + */ + _mru.relocate(_mru.begin(), p.first); + } - if (!save(std::move(data), item.startBlockId, item.countBlock)) { - return false; - } + while (_incoreBlockUsageCount > _incoreBlockUsageMax && _mru.size() > 1) + { + MRUItem item = _mru.back(); - return true; -} + /*Remove item from mru*/ + _mru.pop_back(); -void CacheManager::addFreeBlock(size_t blockId, size_t blockCount) { + /*Update memory usage*/ + _incoreBlockUsageCount -= item.objectSize; - _freeBlocks[blockCount].push_back(blockId); -} + onRemovedFromMRU(item.objectId); + } -size_t CacheManager::getActiveBlocks() const { - return _memoryMap.size(); + return true; } -CachedTile::~CachedTile() { - - std::shared_ptr manager = _manager.lock(); - if (manager) { - manager->notifyDestroy(_uid); - } +bool CacheManager::saveObject(std::unique_ptr&& data, size_t objectId) +{ + MemoryMap::iterator itfind = _memoryMap.find(objectId); + if (itfind == _memoryMap.end()) + { + return false; + } + + MemoryItem item = itfind->second; + + if (itfind->second.startBlockId == ~0) + { + item.startBlockId = getFreeBlockId(item.countBlock); + _memoryMap[objectId] = item; + + prepareBlockGroup(item.startBlockId, item.countBlock); + } + + if (!save(std::move(data), item.startBlockId, item.countBlock)) + { + return false; + } + + return true; } -bool CachedTile::acquire() { +void CacheManager::addFreeBlock(size_t blockId, size_t blockCount) { _freeBlocks[blockCount].push_back(blockId); } - std::shared_ptr manager = _manager.lock(); - if (!manager) { - return false; - } - - - return manager->acquire(_uid); +size_t CacheManager::getActiveBlocks() const { return _memoryMap.size(); } - return true; +CachedTile::~CachedTile() +{ + std::shared_ptr manager = _manager.lock(); + if (manager) + { + manager->notifyDestroy(_uid); + } } -TileCacheManager::TileCacheManager(const std::string & pathStorage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex) : -CacheManager(pathStorage, tileWidth * tileHeight, maxTilesPerIndex), -_tileWidth(tileWidth), _tileHeight(tileHeight) +bool CachedTile::acquire() { + std::shared_ptr manager = _manager.lock(); + if (!manager) + { + return false; + } + + return manager->acquire(_uid); + + return true; } -static unsigned int bitCount (unsigned int value) +TileCacheManager::TileCacheManager(const std::string& pathStorage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex) + : CacheManager(pathStorage, tileWidth * tileHeight, maxTilesPerIndex), + _tileWidth(tileWidth), + _tileHeight(tileHeight) +{} + +static unsigned int bitCount(unsigned int value) { unsigned int count = 0; - while (value > 0) + while (value > 0) { if ((value & 1) == 1) { @@ -346,108 +353,114 @@ static unsigned int bitCount (unsigned int value) return count; } +std::shared_ptr TileCacheManager::create(const std::string& path_storage, + size_t tileWidth, + size_t tileHeight, + size_t maxTilesPerIndex) +{ + if (bitCount(tileWidth) != 1) + { + return nullptr; + } -std::shared_ptr TileCacheManager::create(const std::string & path_storage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex) { - - if (bitCount(tileWidth) != 1) - { - return nullptr; - } + if (bitCount(tileHeight) != 1) + { + return nullptr; + } - if (bitCount(tileHeight) != 1) - { - return nullptr; - } + TileCacheManager* obj = new TileCacheManager(path_storage, tileWidth, tileHeight, maxTilesPerIndex); - TileCacheManager * obj = new TileCacheManager(path_storage, tileWidth, tileHeight, maxTilesPerIndex); - - return std::shared_ptr(obj); + return std::shared_ptr(obj); } -std::shared_ptr TileCacheManager::requireNewCachedTile(size_t width, size_t height, size_t blockCount) { - - - CachedTile::smart_pointer ret; - size_t uid; +std::shared_ptr TileCacheManager::requireNewCachedTile(size_t width, size_t height, size_t blockCount) +{ + CachedTile::smart_pointer ret; + size_t uid; - if (!CacheManager::createObject(uid, blockCount)) { - return ret; - } + if (!CacheManager::createObject(uid, blockCount)) + { + return ret; + } - /* Create container */ - std::shared_ptr sptr = shared_from_this(); - ret.reset(new CachedTile(sptr, uid, _tileWidth, _tileHeight, width, height, blockCount)); + /* Create container */ + std::shared_ptr sptr = shared_from_this(); + ret.reset(new CachedTile(sptr, uid, _tileWidth, _tileHeight, width, height, blockCount)); - /*Store weak pointer internally*/ - _objectMap[uid] = ret; + /*Store weak pointer internally*/ + _objectMap[uid] = ret; - return ret; + return ret; } -void TileCacheManager::notifyDestroy(size_t tileId) { - - /* Remove weak pointer */ - _objectMap.erase(tileId); - - /* Remove map from object to block id*/ - MemoryMap::iterator it = _memoryMap.find(tileId); - if (it == _memoryMap.end()) { - return; - } - size_t blockId = it->second.startBlockId; - size_t blockCount = it->second.countBlock; - _memoryMap.erase(it); - - /*If memory block is valid*/ - if (blockId != ~0) { - - /*Add block to list of available*/ - addFreeBlock(blockId, blockCount); - } -} +void TileCacheManager::notifyDestroy(size_t tileId) +{ + /* Remove weak pointer */ + _objectMap.erase(tileId); -bool TileCacheManager::acquire(size_t tileId) { + /* Remove map from object to block id*/ + MemoryMap::iterator it = _memoryMap.find(tileId); + if (it == _memoryMap.end()) + { + return; + } + size_t blockId = it->second.startBlockId; + size_t blockCount = it->second.countBlock; + _memoryMap.erase(it); + /*If memory block is valid*/ + if (blockId != ~0) + { + /*Add block to list of available*/ + addFreeBlock(blockId, blockCount); + } +} - MapCachedTile::iterator itfind = _objectMap.find(tileId); - if (itfind == _objectMap.end()) { - return false; - } +bool TileCacheManager::acquire(size_t tileId) +{ + MapCachedTile::iterator itfind = _objectMap.find(tileId); + if (itfind == _objectMap.end()) + { + return false; + } - CachedTile::smart_pointer tile = itfind->second.lock(); - if (!tile) { - return false; - } - - /*Acquire the object*/ - std::unique_ptr content = tile->getData(); - if (!CacheManager::acquireObject(content, tileId)) { - return false; - } + CachedTile::smart_pointer tile = itfind->second.lock(); + if (!tile) + { + return false; + } - /*Update tile data*/ - tile->setData(std::move(content)); + /*Acquire the object*/ + std::unique_ptr content = tile->getData(); + if (!CacheManager::acquireObject(content, tileId)) + { + return false; + } + /*Update tile data*/ + tile->setData(std::move(content)); - return true; + return true; } -void TileCacheManager::onRemovedFromMRU(size_t objectId) { - - MapCachedTile::iterator itfind = _objectMap.find(objectId); - if (itfind == _objectMap.end()) { - return; - } - - CachedTile::smart_pointer tile = itfind->second.lock(); - if (!tile) { - return; - } +void TileCacheManager::onRemovedFromMRU(size_t objectId) +{ + MapCachedTile::iterator itfind = _objectMap.find(objectId); + if (itfind == _objectMap.end()) + { + return; + } - /* Save object and set the tile data to nullptr */ - std::unique_ptr content = tile->getData(); - CacheManager::saveObject(std::move(content), objectId); -} + CachedTile::smart_pointer tile = itfind->second.lock(); + if (!tile) + { + return; + } + /* Save object and set the tile data to nullptr */ + std::unique_ptr content = tile->getData(); + CacheManager::saveObject(std::move(content), objectId); } -} + +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/cache.hpp b/src/aliceVision/image/cache.hpp index a049b71722..435fcee900 100644 --- a/src/aliceVision/image/cache.hpp +++ b/src/aliceVision/image/cache.hpp @@ -22,11 +22,8 @@ #include #include -namespace aliceVision -{ -namespace image -{ - +namespace aliceVision { +namespace image { class TileCacheManager; @@ -37,304 +34,287 @@ class TileCacheManager; * This is because a tile may be on a border of the image and not fully used. * It contains a pointer to its data which may be null if the data is out of core */ -class CachedTile { -public: - using weak_pointer = std::weak_ptr; - using smart_pointer = std::shared_ptr; - -public: - - CachedTile() = delete; - - CachedTile(const std::shared_ptr & manager, size_t uid, size_t tileWidth, size_t tileHeight, size_t width, size_t height, size_t depth) - : _manager(manager),_uid(uid), _tileWidth(tileWidth), _tileHeight(tileHeight), _requiredWidth(width), _requiredHeight(height), _depth(depth) { - - /*Make sure the required size is less or equal than the tile size*/ - _requiredWidth = std::min(_requiredWidth, _tileWidth); - _requiredHeight = std::min(_requiredHeight, _tileHeight); - } - - ~CachedTile(); - - size_t getUid() const { - return _uid; - } - - size_t getTileWidth() const { - return _tileWidth; - } - - size_t getTileHeight() const { - return _tileHeight; - } - - size_t getRequiredWidth() const { - return _requiredWidth; - } - - size_t getRequiredHeight() const { - return _requiredHeight; - } - - size_t getDepth() const { - return _depth; - } - - /* - Tells the system that we need the data for this tile. - This means that the data is out of core, we want it back. - @return false if the process failed to grab data. - */ - bool acquire(); - - /** - * Update data with a new buffer - * Move the data parameter to the _data property. - * @note the parameter is invalidated ! - */ - void setData(std::unique_ptr && data) { - _data = std::move(data); - } - - /** - * Get a pointer to the contained data - * @return nullptr if the data is cached - */ - unsigned char * getDataPointer() const { - - if (!_data) { - return nullptr; +class CachedTile +{ + public: + using weak_pointer = std::weak_ptr; + using smart_pointer = std::shared_ptr; + + public: + CachedTile() = delete; + + CachedTile(const std::shared_ptr& manager, + size_t uid, + size_t tileWidth, + size_t tileHeight, + size_t width, + size_t height, + size_t depth) + : _manager(manager), + _uid(uid), + _tileWidth(tileWidth), + _tileHeight(tileHeight), + _requiredWidth(width), + _requiredHeight(height), + _depth(depth) + { + /*Make sure the required size is less or equal than the tile size*/ + _requiredWidth = std::min(_requiredWidth, _tileWidth); + _requiredHeight = std::min(_requiredHeight, _tileHeight); } - return _data.get(); - } - - /** - * Move the data. - * The data is returned and the object property is set to nullptr - */ - std::unique_ptr getData() { - return std::move(_data); - } - -private: - std::unique_ptr _data = nullptr; - std::weak_ptr _manager; - - size_t _uid; - size_t _tileWidth; - size_t _tileHeight; - size_t _requiredWidth; - size_t _requiredHeight; - size_t _depth; + ~CachedTile(); + + size_t getUid() const { return _uid; } + + size_t getTileWidth() const { return _tileWidth; } + + size_t getTileHeight() const { return _tileHeight; } + + size_t getRequiredWidth() const { return _requiredWidth; } + + size_t getRequiredHeight() const { return _requiredHeight; } + + size_t getDepth() const { return _depth; } + + /* + Tells the system that we need the data for this tile. + This means that the data is out of core, we want it back. + @return false if the process failed to grab data. + */ + bool acquire(); + + /** + * Update data with a new buffer + * Move the data parameter to the _data property. + * @note the parameter is invalidated ! + */ + void setData(std::unique_ptr&& data) { _data = std::move(data); } + + /** + * Get a pointer to the contained data + * @return nullptr if the data is cached + */ + unsigned char* getDataPointer() const + { + if (!_data) + { + return nullptr; + } + + return _data.get(); + } + + /** + * Move the data. + * The data is returned and the object property is set to nullptr + */ + std::unique_ptr getData() { return std::move(_data); } + + private: + std::unique_ptr _data = nullptr; + std::weak_ptr _manager; + + size_t _uid; + size_t _tileWidth; + size_t _tileHeight; + size_t _requiredWidth; + size_t _requiredHeight; + size_t _depth; }; /* An abstract concept of cache management for generic objects */ -class CacheManager { -public: - using IndexedStoragePaths = std::unordered_map; - using IndexedFreeBlocks = std::unordered_map>; - - /* - An item of the Most Recently used container - */ - struct MRUItem - { - size_t objectId; - size_t objectSize; - }; - - /* - An item of the object to memory block associative array - */ - struct MemoryItem - { - size_t startBlockId; - size_t countBlock; - }; - - using MemoryMap = std::map; - - /** - Most recently used object container - Used to know which objects have not been used for some time - */ - using MRUType = boost::multi_index::multi_index_container< - MRUItem, - boost::multi_index::indexed_by< - boost::multi_index::sequenced<>, - boost::multi_index::hashed_unique< - boost::multi_index::member< - MRUItem, - size_t, - &MRUItem::objectId> - > - > - >; - -public: - CacheManager() = delete; - - /** - * Create a cache manager - * Each created object is associated to this manager. - * @param pathStorage the path to the directory where the file will be stored - * @param blockSize the base size of an object - * @param maxTilesPerIndex the maximal number of blocks for a given file (give a maximal size for a cache file) - */ - CacheManager(const std::string & pathStorage, size_t blockSize, size_t maxBlocksPerIndex); - virtual ~CacheManager(); - - /** - * Set the maximal memory size - * @param max the maximal memory size - */ - void setMaxMemory(size_t maxMemorySize); - - /** - * Set the maximal number number of items simultaneously in core - * @param max the maximal number of items - */ - void setInCoreMaxObjectCount(size_t max); - - /** - * Create a new object of size block count - * @param objectId the created object index - * @param blockCount the required size for this object (In number of blocks) - * @return true if the object was created - */ - bool createObject(size_t & objectId, size_t blockCount); - - /** - * Acquire a given object - * @param data the result data acquired - * @param objectId the object index to acquire - * @return true if the object was acquired - */ - bool acquireObject(std::unique_ptr & data, size_t objectId); - - /** - * Get the number of managed blocks - * @return a block count - */ - size_t getActiveBlocks() const; - -protected: - - std::string getPathForIndex(size_t indexId); - void deleteIndexFiles(); - void wipe(); - - bool prepareBlockGroup(size_t startBlockId, size_t blocksCount); - std::unique_ptr load(size_t startBlockId, size_t blocksCount); - bool save(std::unique_ptr && data, size_t startBlockId, size_t blockCount); - bool saveObject(std::unique_ptr && data, size_t objectId); - - virtual void onRemovedFromMRU(size_t objectId) = 0; - - void addFreeBlock(size_t blockId, size_t blockCount); - size_t getFreeBlockId(size_t blockCount); - - - -protected: - size_t _blockSize{0}; - size_t _incoreBlockUsageCount{0}; - size_t _incoreBlockUsageMax{0}; - size_t _blockCountPerIndex{0}; - size_t _nextStartBlockId{0}; - size_t _nextObjectId{0}; - - std::string _basePathStorage; - IndexedStoragePaths _indexPaths; - IndexedFreeBlocks _freeBlocks; - - MRUType _mru; - MemoryMap _memoryMap; +class CacheManager +{ + public: + using IndexedStoragePaths = std::unordered_map; + using IndexedFreeBlocks = std::unordered_map>; + + /* + An item of the Most Recently used container + */ + struct MRUItem + { + size_t objectId; + size_t objectSize; + }; + + /* + An item of the object to memory block associative array + */ + struct MemoryItem + { + size_t startBlockId; + size_t countBlock; + }; + + using MemoryMap = std::map; + + /** + Most recently used object container + Used to know which objects have not been used for some time + */ + using MRUType = boost::multi_index::multi_index_container< + MRUItem, + boost::multi_index::indexed_by, + boost::multi_index::hashed_unique>>>; + + public: + CacheManager() = delete; + + /** + * Create a cache manager + * Each created object is associated to this manager. + * @param pathStorage the path to the directory where the file will be stored + * @param blockSize the base size of an object + * @param maxTilesPerIndex the maximal number of blocks for a given file (give a maximal size for a cache file) + */ + CacheManager(const std::string& pathStorage, size_t blockSize, size_t maxBlocksPerIndex); + virtual ~CacheManager(); + + /** + * Set the maximal memory size + * @param max the maximal memory size + */ + void setMaxMemory(size_t maxMemorySize); + + /** + * Set the maximal number number of items simultaneously in core + * @param max the maximal number of items + */ + void setInCoreMaxObjectCount(size_t max); + + /** + * Create a new object of size block count + * @param objectId the created object index + * @param blockCount the required size for this object (In number of blocks) + * @return true if the object was created + */ + bool createObject(size_t& objectId, size_t blockCount); + + /** + * Acquire a given object + * @param data the result data acquired + * @param objectId the object index to acquire + * @return true if the object was acquired + */ + bool acquireObject(std::unique_ptr& data, size_t objectId); + + /** + * Get the number of managed blocks + * @return a block count + */ + size_t getActiveBlocks() const; + + protected: + std::string getPathForIndex(size_t indexId); + void deleteIndexFiles(); + void wipe(); + + bool prepareBlockGroup(size_t startBlockId, size_t blocksCount); + std::unique_ptr load(size_t startBlockId, size_t blocksCount); + bool save(std::unique_ptr&& data, size_t startBlockId, size_t blockCount); + bool saveObject(std::unique_ptr&& data, size_t objectId); + + virtual void onRemovedFromMRU(size_t objectId) = 0; + + void addFreeBlock(size_t blockId, size_t blockCount); + size_t getFreeBlockId(size_t blockCount); + + protected: + size_t _blockSize{0}; + size_t _incoreBlockUsageCount{0}; + size_t _incoreBlockUsageMax{0}; + size_t _blockCountPerIndex{0}; + size_t _nextStartBlockId{0}; + size_t _nextObjectId{0}; + + std::string _basePathStorage; + IndexedStoragePaths _indexPaths; + IndexedFreeBlocks _freeBlocks; + + MRUType _mru; + MemoryMap _memoryMap; }; /** * A cache manager specialized for image tiles * All tiles in this image have a size multiple of a given base tile size. */ -class TileCacheManager : public CacheManager, public std::enable_shared_from_this { -public: - using shared_ptr = std::shared_ptr; - using MapCachedTile = std::map; -public: - - TileCacheManager() = delete; - - /** - * There is no explicit constructor. - * We want to force the use of shared pointer for the manager - * @param pathStorage the path to the directory where the file will be stored - * @param tileWidth the base width of a tile - * @param tileWidth the base height of a tile - * @param maxTilesPerIndex the maximal number of tiles for a given file (give a maximal size for a cache file) - * @retuurn the manager shared pointer - */ - static std::shared_ptr create(const std::string & pathStorage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex); - - /** - * Notify the manager that a given tile was destroyed. - * @param tileId the tile index which was destroyed - * @note this method should not be explicitly called except by CachedTile - */ - void notifyDestroy(size_t tileId); - - /** - * Acquire a given tile - * @param tileId the tile index to acquire - * @return true if the tile was acquired - */ - bool acquire(size_t tileId); - - /** - * Acquire a given tile - * @param width the requested tile size (less or equal to the base tile size) - * @param height the requested tile size (less or equal to the base tile size) - * @param blockCount the requested tile size in depth - * @return a pointer to the cached tile created - */ - std::shared_ptr requireNewCachedTile(size_t width, size_t height, size_t blockCount); - - template - std::shared_ptr requireNewCachedTile(size_t width, size_t height) { - return requireNewCachedTile(width, height, sizeof(T)); - } - - /** - * Return the base tile width - * @return a width - */ - size_t getTileWidth() const { - return _tileWidth; - } - - /** - * Return the base tile height - * @return a width - */ - size_t getTileHeight() const { - return _tileHeight; - } - -protected: - - TileCacheManager(const std::string & pathStorage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex); - - virtual void onRemovedFromMRU(size_t objectId); - -protected: - - size_t _tileWidth; - size_t _tileHeight; - - MapCachedTile _objectMap; +class TileCacheManager : public CacheManager, public std::enable_shared_from_this +{ + public: + using shared_ptr = std::shared_ptr; + using MapCachedTile = std::map; + + public: + TileCacheManager() = delete; + + /** + * There is no explicit constructor. + * We want to force the use of shared pointer for the manager + * @param pathStorage the path to the directory where the file will be stored + * @param tileWidth the base width of a tile + * @param tileWidth the base height of a tile + * @param maxTilesPerIndex the maximal number of tiles for a given file (give a maximal size for a cache file) + * @retuurn the manager shared pointer + */ + static std::shared_ptr create(const std::string& pathStorage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex); + + /** + * Notify the manager that a given tile was destroyed. + * @param tileId the tile index which was destroyed + * @note this method should not be explicitly called except by CachedTile + */ + void notifyDestroy(size_t tileId); + + /** + * Acquire a given tile + * @param tileId the tile index to acquire + * @return true if the tile was acquired + */ + bool acquire(size_t tileId); + + /** + * Acquire a given tile + * @param width the requested tile size (less or equal to the base tile size) + * @param height the requested tile size (less or equal to the base tile size) + * @param blockCount the requested tile size in depth + * @return a pointer to the cached tile created + */ + std::shared_ptr requireNewCachedTile(size_t width, size_t height, size_t blockCount); + + template + std::shared_ptr requireNewCachedTile(size_t width, size_t height) + { + return requireNewCachedTile(width, height, sizeof(T)); + } + + /** + * Return the base tile width + * @return a width + */ + size_t getTileWidth() const { return _tileWidth; } + + /** + * Return the base tile height + * @return a width + */ + size_t getTileHeight() const { return _tileHeight; } + + protected: + TileCacheManager(const std::string& pathStorage, size_t tileWidth, size_t tileHeight, size_t maxTilesPerIndex); + + virtual void onRemovedFromMRU(size_t objectId); + + protected: + size_t _tileWidth; + size_t _tileHeight; + + MapCachedTile _objectMap; }; -} -} +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/colorspace.cpp b/src/aliceVision/image/colorspace.cpp index d3a97f77c9..134017e27e 100644 --- a/src/aliceVision/image/colorspace.cpp +++ b/src/aliceVision/image/colorspace.cpp @@ -13,21 +13,16 @@ #include #include - namespace fs = boost::filesystem; namespace aliceVision { namespace image { -namespace -{ - oiio::ColorConfig colorConfigOCIO(getDefaultColorConfigFilePath()); +namespace { +oiio::ColorConfig colorConfigOCIO(getDefaultColorConfigFilePath()); } -oiio::ColorConfig& getGlobalColorConfigOCIO() -{ - return colorConfigOCIO; -} +oiio::ColorConfig& getGlobalColorConfigOCIO() { return colorConfigOCIO; } std::string getColorConfigFilePathFromSourceCode() { @@ -45,7 +40,7 @@ std::string getDefaultColorConfigFilePath() configOCIOFilePath = std::string(ALICEVISION_OCIO); if (fs::exists(configOCIOFilePath)) { - // Check if a sRGB linear color space named "scene-linear Rec.709-sRGB" is present and set as scene_linear role + // Check if a sRGB linear color space named "scene-linear Rec.709-sRGB" is present and set as scene_linear role oiio::ColorConfig colorConfig(configOCIOFilePath); const std::string linearColorSpace = colorConfig.getColorSpaceNameByRole("scene_linear"); if (linearColorSpace == "scene-linear Rec.709-sRGB") @@ -55,9 +50,11 @@ std::string getDefaultColorConfigFilePath() } else { - ALICEVISION_LOG_WARNING("ALICEVISION_OCIO configuration file is not valid: '" << configOCIOFilePath << "'.\n" - "The scene_linear role named \"scene-linear Rec.709-sRGB\" is required.\n" - "Skip this OCIO configuration file and use the embedded one."); + ALICEVISION_LOG_WARNING("ALICEVISION_OCIO configuration file is not valid: '" + << configOCIOFilePath + << "'.\n" + "The scene_linear role named \"scene-linear Rec.709-sRGB\" is required.\n" + "Skip this OCIO configuration file and use the embedded one."); } } else if (configOCIOFilePath.empty()) @@ -71,18 +68,18 @@ std::string getDefaultColorConfigFilePath() } // To be enabled if we decide to take OCIO env var in consideration before using the enbedded config file - if(false) + if (false) { char const* OCIO = std::getenv("OCIO"); - if(OCIO != NULL) + if (OCIO != NULL) { configOCIOFilePath = std::string(OCIO); - if(fs::exists(configOCIOFilePath)) + if (fs::exists(configOCIOFilePath)) { ALICEVISION_LOG_TRACE("OCIO configuration file: '" << configOCIOFilePath << "' found."); return configOCIOFilePath; } - else if(configOCIOFilePath == "") + else if (configOCIOFilePath == "") { ALICEVISION_LOG_TRACE("OCIO is empty. Use embedded config..."); } @@ -97,7 +94,7 @@ std::string getDefaultColorConfigFilePath() if (ALICEVISION_ROOT == NULL) { const std::string configFromSource = getColorConfigFilePathFromSourceCode(); - if(fs::exists(configFromSource)) + if (fs::exists(configFromSource)) { ALICEVISION_LOG_DEBUG("ALICEVISION_ROOT is not defined, use embedded OCIO config file from source code: " << configFromSource); return configFromSource; @@ -112,7 +109,7 @@ std::string getDefaultColorConfigFilePath() if (!fs::exists(configOCIOFilePath)) { const std::string configFromSource = getColorConfigFilePathFromSourceCode(); - if(fs::exists(configFromSource)) + if (fs::exists(configFromSource)) { ALICEVISION_LOG_DEBUG("Embedded OCIO config file in ALICEVISION_ROOT does not exist, use config from source code: " << configFromSource); return configFromSource; @@ -146,38 +143,34 @@ void initColorConfigOCIO(const std::string& colorConfigFilePath) std::string EImageColorSpace_informations() { - return EImageColorSpace_enumToString(EImageColorSpace::AUTO) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::LINEAR) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::SRGB) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::ACES2065_1) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::ACEScg) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::REC709) + " (ODT.Academy.Rec709_100nits), " + - EImageColorSpace_enumToString(EImageColorSpace::LAB) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::XYZ) + ", " + - EImageColorSpace_enumToString(EImageColorSpace::NO_CONVERSION); + return EImageColorSpace_enumToString(EImageColorSpace::AUTO) + ", " + EImageColorSpace_enumToString(EImageColorSpace::LINEAR) + ", " + + EImageColorSpace_enumToString(EImageColorSpace::SRGB) + ", " + EImageColorSpace_enumToString(EImageColorSpace::ACES2065_1) + ", " + + EImageColorSpace_enumToString(EImageColorSpace::ACEScg) + ", " + EImageColorSpace_enumToString(EImageColorSpace::REC709) + + " (ODT.Academy.Rec709_100nits), " + EImageColorSpace_enumToString(EImageColorSpace::LAB) + ", " + + EImageColorSpace_enumToString(EImageColorSpace::XYZ) + ", " + EImageColorSpace_enumToString(EImageColorSpace::NO_CONVERSION); } EImageColorSpace EImageColorSpace_stringToEnum(const std::string& dataType) { const std::string type = boost::to_lower_copy(dataType); - if(type == "auto") + if (type == "auto") return EImageColorSpace::AUTO; - if(type == "linear") + if (type == "linear") return EImageColorSpace::LINEAR; - if(type == "srgb") + if (type == "srgb") return EImageColorSpace::SRGB; - if(type == "aces2065-1") + if (type == "aces2065-1") return EImageColorSpace::ACES2065_1; if (type == "acescg") return EImageColorSpace::ACEScg; if ((type == "aces_lut") || (type == "rec709")) return EImageColorSpace::REC709; - if(type == "lab") + if (type == "lab") return EImageColorSpace::LAB; - if(type == "xyz") + if (type == "xyz") return EImageColorSpace::XYZ; - if(type == "no_conversion") + if (type == "no_conversion") return EImageColorSpace::NO_CONVERSION; throw std::out_of_range("Invalid EImageColorSpace: " + dataType); @@ -185,7 +178,7 @@ EImageColorSpace EImageColorSpace_stringToEnum(const std::string& dataType) std::string EImageColorSpace_enumToString(const EImageColorSpace dataType) { - switch(dataType) + switch (dataType) { case EImageColorSpace::AUTO: return "auto"; @@ -211,57 +204,74 @@ std::string EImageColorSpace_enumToString(const EImageColorSpace dataType) std::string EImageColorSpace_enumToOIIOString(const EImageColorSpace colorSpace) { - switch(colorSpace) + switch (colorSpace) { - case EImageColorSpace::SRGB: return "sRGB"; - case EImageColorSpace::LINEAR: return "Linear"; - case EImageColorSpace::ACES2065_1: return "aces2065-1"; - case EImageColorSpace::ACEScg: return "ACEScg"; - case EImageColorSpace::REC709: return "rec709"; - default: ; + case EImageColorSpace::SRGB: + return "sRGB"; + case EImageColorSpace::LINEAR: + return "Linear"; + case EImageColorSpace::ACES2065_1: + return "aces2065-1"; + case EImageColorSpace::ACEScg: + return "ACEScg"; + case EImageColorSpace::REC709: + return "rec709"; + default:; } - throw std::out_of_range("No string defined for EImageColorSpace to OIIO conversion: " + - std::to_string(int(colorSpace))); + throw std::out_of_range("No string defined for EImageColorSpace to OIIO conversion: " + std::to_string(int(colorSpace))); } EImageColorSpace EImageColorSpace_OIIOstringToEnum(const std::string& colorspace) { - if (colorspace == "Linear") return EImageColorSpace::LINEAR; - if (colorspace == "sRGB") return EImageColorSpace::SRGB; - if (colorspace == "aces2065-1") return EImageColorSpace::ACES2065_1; - if (colorspace == "ACEScg") return EImageColorSpace::ACEScg; - if ((colorspace == "REC709") || (colorspace == "ACES_LUT")) return EImageColorSpace::REC709; + if (colorspace == "Linear") + return EImageColorSpace::LINEAR; + if (colorspace == "sRGB") + return EImageColorSpace::SRGB; + if (colorspace == "aces2065-1") + return EImageColorSpace::ACES2065_1; + if (colorspace == "ACEScg") + return EImageColorSpace::ACEScg; + if ((colorspace == "REC709") || (colorspace == "ACES_LUT")) + return EImageColorSpace::REC709; throw std::out_of_range("No EImageColorSpace defined for string: " + colorspace); } bool EImageColorSpace_isSupportedOIIOEnum(const EImageColorSpace& colorspace) { - switch(colorspace) + switch (colorspace) { - case EImageColorSpace::SRGB: return true; - case EImageColorSpace::LINEAR: return true; - case EImageColorSpace::ACES2065_1: return true; - case EImageColorSpace::ACEScg: return true; - case EImageColorSpace::REC709: return true; - default: return false; + case EImageColorSpace::SRGB: + return true; + case EImageColorSpace::LINEAR: + return true; + case EImageColorSpace::ACES2065_1: + return true; + case EImageColorSpace::ACEScg: + return true; + case EImageColorSpace::REC709: + return true; + default: + return false; } } bool EImageColorSpace_isSupportedOIIOstring(const std::string& colorspace) { - if (colorspace == "Linear") return true; - if (colorspace == "sRGB") return true; - if (colorspace == "aces2065-1") return true; - if (colorspace == "ACEScg") return true; - if (colorspace == "REC709") return true; + if (colorspace == "Linear") + return true; + if (colorspace == "sRGB") + return true; + if (colorspace == "aces2065-1") + return true; + if (colorspace == "ACEScg") + return true; + if (colorspace == "REC709") + return true; return false; } -std::ostream& operator<<(std::ostream& os, EImageColorSpace dataType) -{ - return os << EImageColorSpace_enumToString(dataType); -} +std::ostream& operator<<(std::ostream& os, EImageColorSpace dataType) { return os << EImageColorSpace_enumToString(dataType); } std::istream& operator>>(std::istream& in, EImageColorSpace& dataType) { @@ -271,5 +281,5 @@ std::istream& operator>>(std::istream& in, EImageColorSpace& dataType) return in; } -} -} +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/colorspace.hpp b/src/aliceVision/image/colorspace.hpp index 334db72525..9bb1d1f6a5 100644 --- a/src/aliceVision/image/colorspace.hpp +++ b/src/aliceVision/image/colorspace.hpp @@ -22,15 +22,15 @@ namespace image { */ enum class EImageColorSpace { - AUTO, - LINEAR, - SRGB, - ACES2065_1, - ACEScg, - REC709, - LAB, - XYZ, - NO_CONVERSION + AUTO, + LINEAR, + SRGB, + ACES2065_1, + ACEScg, + REC709, + LAB, + XYZ, + NO_CONVERSION }; std::string EImageColorSpace_informations(); @@ -47,5 +47,5 @@ std::string getDefaultColorConfigFilePath(); void initColorConfigOCIO(const std::string& colorConfigFilePath); oiio::ColorConfig& getGlobalColorConfigOCIO(); -} -} +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/concat.hpp b/src/aliceVision/image/concat.hpp index d82f299631..c077ada63d 100644 --- a/src/aliceVision/image/concat.hpp +++ b/src/aliceVision/image/concat.hpp @@ -13,38 +13,38 @@ namespace aliceVision { namespace image { /// Horizontal concatenation of images -template < class Image > -void ConcatH(const Image & imageA, const Image & imageB, Image & Out) +template +void ConcatH(const Image& imageA, const Image& imageB, Image& Out) { - // Compute new dimensions // |imgA|+|imgB| - int ww = imageA.Width() + imageB.Width(); - Out.resize(ww, std::max(imageA.Height(), imageB.Height())); - - // Copy the first image |imgA|...| - Out.block(0,0, imageA.Height(), imageA.Width()) = imageA.GetMat(); - // Copy the second image |imgA|imgB| - Out.block(0, imageA.Width(), imageB.Height(), imageB.Width()) = imageB.GetMat(); + // Compute new dimensions // |imgA|+|imgB| + int ww = imageA.Width() + imageB.Width(); + Out.resize(ww, std::max(imageA.Height(), imageB.Height())); + + // Copy the first image |imgA|...| + Out.block(0, 0, imageA.Height(), imageA.Width()) = imageA.GetMat(); + // Copy the second image |imgA|imgB| + Out.block(0, imageA.Width(), imageB.Height(), imageB.Width()) = imageB.GetMat(); } /// Vertical concatenation of images -template < class Image > -void ConcatV(const Image & imageA, const Image & imageB, Image & Out) +template +void ConcatV(const Image& imageA, const Image& imageB, Image& Out) { - // Compute new dimensions - // |imgA| - // |imgB| - int hh = imageA.Height() + imageB.Height(); - Out.resize(max(imageA.Width(), imageB.Width()), hh); - - // Copy the first image - // |imgA| - // |....| - Out.block(0,0, imageA.Height(), imageA.Width()) = imageA.GetMat(); - // Copy the second image - // |imgA| - // |imgB| - Out.block(imageA.Height(), 0, imageB.Height(), imageB.Width()) = imageB.GetMat(); + // Compute new dimensions + // |imgA| + // |imgB| + int hh = imageA.Height() + imageB.Height(); + Out.resize(max(imageA.Width(), imageB.Width()), hh); + + // Copy the first image + // |imgA| + // |....| + Out.block(0, 0, imageA.Height(), imageA.Width()) = imageA.GetMat(); + // Copy the second image + // |imgA| + // |imgB| + Out.block(imageA.Height(), 0, imageB.Height(), imageB.Width()) = imageB.GetMat(); } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/convertion.hpp b/src/aliceVision/image/convertion.hpp index 68e2b07493..263ee44146 100644 --- a/src/aliceVision/image/convertion.hpp +++ b/src/aliceVision/image/convertion.hpp @@ -10,164 +10,148 @@ #include "aliceVision/image/Image.hpp" #include "aliceVision/image/pixelTypes.hpp" -namespace aliceVision{ +namespace aliceVision { namespace image { // The factor comes from http://www.easyrgb.com/ // RGB to XYZ : Y is the luminance channel // var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722 -inline float Rgb2GrayLinear(const float r, const float g, const float b) -{ - return r * 0.2126f + g * 0.7152f + b * 0.0722f; -} +inline float Rgb2GrayLinear(const float r, const float g, const float b) { return r * 0.2126f + g * 0.7152f + b * 0.0722f; } -inline float Rgb2Gray(const float r, const float g, const float b) -{ - return r * 0.299f + g * 0.587f + b * 0.114f; -} +inline float Rgb2Gray(const float r, const float g, const float b) { return r * 0.299f + g * 0.587f + b * 0.114f; } -template +template inline void Convert(const Tin& valin, Tout& out) { - out = static_cast(valin); + out = static_cast(valin); } template<> inline void Convert(const unsigned char& valin, RGBColor& valOut) { - valOut = RGBColor(valin); + valOut = RGBColor(valin); } template<> inline void Convert(const RGBColor& valin, unsigned char& valOut) { - valOut = static_cast(Rgb2GrayLinear(valin.r(), valin.g(), valin.b())); + valOut = static_cast(Rgb2GrayLinear(valin.r(), valin.g(), valin.b())); } template<> inline void Convert(const unsigned char& valin, RGBAColor& valOut) { - valOut = RGBAColor(valin, valin, valin, 255); + valOut = RGBAColor(valin, valin, valin, 255); } template<> inline void Convert(const RGBAColor& valin, unsigned char& valOut) { - valOut = static_cast((valin.a() / 255.f) * Rgb2GrayLinear(valin.r(), valin.g(), valin.b())); + valOut = static_cast((valin.a() / 255.f) * Rgb2GrayLinear(valin.r(), valin.g(), valin.b())); } template<> inline void Convert(const RGBAColor& valin, RGBColor& valOut) { - valOut = RGBColor(static_cast((valin.a() / 255.f) * valin.r()), - static_cast((valin.a() / 255.f) * valin.g()), - static_cast((valin.a() / 255.f) * valin.b())); + valOut = RGBColor(static_cast((valin.a() / 255.f) * valin.r()), + static_cast((valin.a() / 255.f) * valin.g()), + static_cast((valin.a() / 255.f) * valin.b())); } template<> inline void Convert(const RGBColor& valin, RGBAColor& valOut) { - valOut = RGBAColor(valin.r(), valin.g(), valin.b(), static_cast(255)); + valOut = RGBAColor(valin.r(), valin.g(), valin.b(), static_cast(255)); } template<> inline void Convert(const float& valin, RGBfColor& valOut) { - valOut = RGBfColor(valin); + valOut = RGBfColor(valin); } template<> inline void Convert(const RGBfColor& valin, float& valOut) { - valOut = Rgb2GrayLinear(valin.r(), valin.g(), valin.b()); + valOut = Rgb2GrayLinear(valin.r(), valin.g(), valin.b()); } template<> inline void Convert(const float& valin, RGBAfColor& valOut) { - valOut = RGBAfColor(valin); + valOut = RGBAfColor(valin); } template<> inline void Convert(const RGBAfColor& valin, float& valOut) { - valOut = Rgb2GrayLinear(valin.a() * valin.r(), valin.a() * valin.g(), valin.a() * valin.b()); + valOut = Rgb2GrayLinear(valin.a() * valin.r(), valin.a() * valin.g(), valin.a() * valin.b()); } template<> inline void Convert(const RGBAfColor& valin, RGBfColor& valOut) { - valOut = RGBfColor(valin.a() * valin.r(), valin.a() * valin.g(), valin.a() * valin.b()); + valOut = RGBfColor(valin.a() * valin.r(), valin.a() * valin.g(), valin.a() * valin.b()); } template<> inline void Convert(const RGBfColor& valin, RGBAfColor& valOut) { - // alpha 1 by default - valOut = RGBAfColor(valin.r(), valin.g(), valin.b()); + // alpha 1 by default + valOut = RGBAfColor(valin.r(), valin.g(), valin.b()); } template -void ConvertPixelType(const ImageIn& imaIn, ImageOut *imaOut) +void ConvertPixelType(const ImageIn& imaIn, ImageOut* imaOut) { - (*imaOut) = ImageOut(imaIn.Width(), imaIn.Height()); - // Convert each input pixel to destination pixel - for(int j = 0; j < imaIn.Height(); ++j) - for(int i = 0; i < imaIn.Width(); ++i) - Convert(imaIn(j,i), (*imaOut)(j,i)); + (*imaOut) = ImageOut(imaIn.Width(), imaIn.Height()); + // Convert each input pixel to destination pixel + for (int j = 0; j < imaIn.Height(); ++j) + for (int i = 0; i < imaIn.Width(); ++i) + Convert(imaIn(j, i), (*imaOut)(j, i)); } //-------------------------------------------------------------------------- // RGB ( unsigned char or int ) to Float //-------------------------------------------------------------------------- -template< typename Tin, typename Tout > -inline void convertRGB2Float( - const Tin& valIn, - Tout& valOut, - float factor = 1.0f / 255.f) +template +inline void convertRGB2Float(const Tin& valIn, Tout& valOut, float factor = 1.0f / 255.f) { - for( int channel = 0; channel < 3; ++channel ) - valOut(channel) = (float)((int)(valIn(channel)) * factor); + for (int channel = 0; channel < 3; ++channel) + valOut(channel) = (float)((int)(valIn(channel)) * factor); } -template< typename ImageIn > -void rgb2Float( const ImageIn& imaIn, - Image< RGBfColor > *imaOut, float factor = 1.0f / 255.f ) +template +void rgb2Float(const ImageIn& imaIn, Image* imaOut, float factor = 1.0f / 255.f) { - assert( imaIn.Depth() == 3 ); - (*imaOut).resize(imaIn.Width(), imaIn.Height()); - // Convert each int RGB to float RGB values - for( int j = 0; j < imaIn.Height(); ++j ) - for( int i = 0; i < imaIn.Width(); ++i ) - convertRGB2Float( imaIn( j, i ), ( *imaOut )( j, i ), factor ); + assert(imaIn.Depth() == 3); + (*imaOut).resize(imaIn.Width(), imaIn.Height()); + // Convert each int RGB to float RGB values + for (int j = 0; j < imaIn.Height(); ++j) + for (int i = 0; i < imaIn.Width(); ++i) + convertRGB2Float(imaIn(j, i), (*imaOut)(j, i), factor); } //-------------------------------------------------------------------------- // Float to RGB ( unsigned char or int ) //-------------------------------------------------------------------------- -inline void convertFloatToInt( - const RGBfColor& valIn, - RGBColor& valOut, - float factor = 255.f) +inline void convertFloatToInt(const RGBfColor& valIn, RGBColor& valOut, float factor = 255.f) { - for( int channel = 0; channel < 3; ++channel ) - valOut(channel) = (int)(valIn(channel) * factor); + for (int channel = 0; channel < 3; ++channel) + valOut(channel) = (int)(valIn(channel) * factor); } -inline void rgbFloat2rgbInt( - const Image< RGBfColor >& imaIn, - Image< RGBColor > *imaOut, - float factor = 255.f) +inline void rgbFloat2rgbInt(const Image& imaIn, Image* imaOut, float factor = 255.f) { - assert( imaIn.Depth() == 3 ); - (*imaOut).resize(imaIn.Width(), imaIn.Height()); - // Convert each int RGB to float RGB values - for( int j = 0; j < imaIn.Height(); ++j ) - for( int i = 0; i < imaIn.Width(); ++i ) - convertFloatToInt( imaIn( j, i ), (*imaOut)( j, i ), factor ); + assert(imaIn.Depth() == 3); + (*imaOut).resize(imaIn.Width(), imaIn.Height()); + // Convert each int RGB to float RGB values + for (int j = 0; j < imaIn.Height(); ++j) + for (int i = 0; i < imaIn.Width(); ++i) + convertFloatToInt(imaIn(j, i), (*imaOut)(j, i), factor); } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/convertionOpenCV.hpp b/src/aliceVision/image/convertionOpenCV.hpp index 720dc17a83..8e13ef88e4 100644 --- a/src/aliceVision/image/convertionOpenCV.hpp +++ b/src/aliceVision/image/convertionOpenCV.hpp @@ -10,16 +10,13 @@ #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#include "aliceVision/image/Image.hpp" -#include + #include "aliceVision/image/Image.hpp" + #include + #include -#include - -namespace aliceVision{ -namespace image -{ - +namespace aliceVision { +namespace image { /** * @brief Sets the value at the specified pixel of a OpenCV BGR image matrix @@ -33,16 +30,14 @@ namespace image * @param[in] factor - optional scale factor * @param[in] delta - optional delta added to the scaled values */ -template -inline void setValueCvMatBGR(cv::Mat& mat, int i, int j, const image::RGBAfColor& color, float factor = 1.f, - float delta = 0.f) +template +inline void setValueCvMatBGR(cv::Mat& mat, int i, int j, const image::RGBAfColor& color, float factor = 1.f, float delta = 0.f) { - mat.at(i, j)[0] = (ValueType) clamp(color.b() * factor + delta, 0.f, 255.f); - mat.at(i, j)[1] = (ValueType) clamp(color.g() * factor + delta, 0.f, 255.f); - mat.at(i, j)[2] = (ValueType) clamp(color.r() * factor + delta, 0.f, 255.f); + mat.at(i, j)[0] = (ValueType)clamp(color.b() * factor + delta, 0.f, 255.f); + mat.at(i, j)[1] = (ValueType)clamp(color.g() * factor + delta, 0.f, 255.f); + mat.at(i, j)[2] = (ValueType)clamp(color.r() * factor + delta, 0.f, 255.f); } - /** * @brief Converts an aliceVision image to an OpenCV image (cv::Mat) in BGR * Ignores the alpha channel of the source image @@ -53,11 +48,11 @@ inline void setValueCvMatBGR(cv::Mat& mat, int i, int j, const image::RGBAfColor inline cv::Mat imageRGBAToCvMatBGR(const image::Image& img, int cvtype = CV_32FC3) { cv::Mat mat(img.Height(), img.Width(), cvtype); - for(int i = 0; i < img.Height(); i++) + for (int i = 0; i < img.Height(); i++) { - for(int j = 0; j < img.Width(); j++) + for (int j = 0; j < img.Width(); j++) { - switch(cvtype) + switch (cvtype) { case CV_32FC3: setValueCvMatBGR(mat, i, j, img(i, j)); @@ -73,7 +68,6 @@ inline cv::Mat imageRGBAToCvMatBGR(const image::Image& img, i return mat; } - /** * @brief Implements the conversion of an OpenCV image (cv::Mat) in BGR to an aliceVision image * Keeps the alpha channel of the output image unchanged @@ -82,22 +76,20 @@ inline cv::Mat imageRGBAToCvMatBGR(const image::Image& img, i * @param[inout] imageOut - output RGBA aliceVision image * @param[in] factor - optional scale factor */ -template +template inline void cvMatBGRToImageRGBAImpl(const cv::Mat& img, image::Image& imageOut, float factor = 1.f) { - for(int row = 0; row < imageOut.Height(); row++) + for (int row = 0; row < imageOut.Height(); row++) { const VecType* rowPtr = img.ptr(row); - for(int col = 0; col < imageOut.Width(); col++) + for (int col = 0; col < imageOut.Width(); col++) { const VecType& matPixel = rowPtr[col]; - imageOut(row, col) = image::RGBAfColor(matPixel[2] * factor, matPixel[1] * factor, - matPixel[0] * factor, imageOut(row, col).a()); + imageOut(row, col) = image::RGBAfColor(matPixel[2] * factor, matPixel[1] * factor, matPixel[0] * factor, imageOut(row, col).a()); } } } - /** * @brief Converts an OpenCV image (cv::Mat) in BGR to an aliceVision image * Keeps the alpha channel of the output image unchanged @@ -107,7 +99,7 @@ inline void cvMatBGRToImageRGBAImpl(const cv::Mat& img, image::Image& imageOut) { - switch(img.type()) + switch (img.type()) { case CV_32FC3: cvMatBGRToImageRGBAImpl(img, imageOut); @@ -120,7 +112,7 @@ inline void cvMatBGRToImageRGBA(const cv::Mat& img, image::Image& kernel_x, const Eigen::Matrix& kernel_y, - RowMatrixXf* out) { - const int sigma_y = static_cast( kernel_y.cols() ); - const int half_sigma_y = static_cast( kernel_y.cols() ) / 2; + RowMatrixXf* out) +{ + const int sigma_y = static_cast(kernel_y.cols()); + const int half_sigma_y = static_cast(kernel_y.cols()) / 2; - // Convolving a vertical filter across rows is the same thing as transpose - // multiply i.e. kernel_y^t * rows. This will give us the convoled value for - // each row. However, care must be taken at the top and bottom borders. - const Eigen::Matrix reverse_kernel_y = kernel_y.reverse(); + // Convolving a vertical filter across rows is the same thing as transpose + // multiply i.e. kernel_y^t * rows. This will give us the convoled value for + // each row. However, care must be taken at the top and bottom borders. + const Eigen::Matrix reverse_kernel_y = kernel_y.reverse(); - #pragma omp parallel for schedule(dynamic) - for (int i = 0; i < half_sigma_y; i++) - { - const int forward_size = i + half_sigma_y + 1; - const int reverse_size = sigma_y - forward_size; - out->row(i) = kernel_y.tail(forward_size) * - image.block(0, 0, forward_size, image.cols()) + - reverse_kernel_y.tail(reverse_size) * - image.block(1, 0, reverse_size, image.cols()); +#pragma omp parallel for schedule(dynamic) + for (int i = 0; i < half_sigma_y; i++) + { + const int forward_size = i + half_sigma_y + 1; + const int reverse_size = sigma_y - forward_size; + out->row(i) = kernel_y.tail(forward_size) * image.block(0, 0, forward_size, image.cols()) + + reverse_kernel_y.tail(reverse_size) * image.block(1, 0, reverse_size, image.cols()); - // Apply the same technique for the end rows. - out->row(image.rows() - i - 1) = - kernel_y.head(forward_size) * - image.block(image.rows() - forward_size, 0, forward_size, image.cols()) - + - reverse_kernel_y.head(reverse_size) * - image.block(image.rows() - reverse_size - 1, 0, reverse_size, image.cols()); - } + // Apply the same technique for the end rows. + out->row(image.rows() - i - 1) = + kernel_y.head(forward_size) * image.block(image.rows() - forward_size, 0, forward_size, image.cols()) + + reverse_kernel_y.head(reverse_size) * image.block(image.rows() - reverse_size - 1, 0, reverse_size, image.cols()); + } - // Applying the rest of the y filter. - #pragma omp parallel for schedule(dynamic) - for (int row = half_sigma_y; row < image.rows() - half_sigma_y; row++) - { - out->row(row) = kernel_y * image.block(row - half_sigma_y, 0, sigma_y, out->cols()); - } +// Applying the rest of the y filter. +#pragma omp parallel for schedule(dynamic) + for (int row = half_sigma_y; row < image.rows() - half_sigma_y; row++) + { + out->row(row) = kernel_y * image.block(row - half_sigma_y, 0, sigma_y, out->cols()); + } - const int sigma_x = static_cast(kernel_x.cols()); - const int half_sigma_x = static_cast(kernel_x.cols() / 2); + const int sigma_x = static_cast(kernel_x.cols()); + const int half_sigma_x = static_cast(kernel_x.cols() / 2); - // Convolving with the horizontal filter is easy. Rather than using the kernel - // as a sliding window, we use the row pixels as a sliding window around the - // filter. We prepend and append the proper border values so that we are sure - // to end up with the correct convolved values. - Eigen::RowVectorXf temp_row(image.cols() + sigma_x - 1); + // Convolving with the horizontal filter is easy. Rather than using the kernel + // as a sliding window, we use the row pixels as a sliding window around the + // filter. We prepend and append the proper border values so that we are sure + // to end up with the correct convolved values. + Eigen::RowVectorXf temp_row(image.cols() + sigma_x - 1); - #pragma omp parallel for firstprivate(temp_row), schedule(dynamic) - for (int row = 0; row < out->rows(); row++) - { - temp_row.head(half_sigma_x) = - out->row(row).segment(1, half_sigma_x).reverse(); - temp_row.segment(half_sigma_x, image.cols()) = out->row(row); - temp_row.tail(half_sigma_x) = - out->row(row) - .segment(image.cols() - 2 - half_sigma_x, half_sigma_x) - .reverse(); +#pragma omp parallel for firstprivate(temp_row), schedule(dynamic) + for (int row = 0; row < out->rows(); row++) + { + temp_row.head(half_sigma_x) = out->row(row).segment(1, half_sigma_x).reverse(); + temp_row.segment(half_sigma_x, image.cols()) = out->row(row); + temp_row.tail(half_sigma_x) = out->row(row).segment(image.cols() - 2 - half_sigma_x, half_sigma_x).reverse(); - // Convolve the row. We perform the first step here explicitly so that we - // avoid setting the row equal to zero. - out->row(row) = kernel_x(0) * temp_row.head(image.cols()); - for (int i = 1; i < sigma_x; i++) { - out->row(row) += kernel_x(i) * temp_row.segment(i, image.cols()); + // Convolve the row. We perform the first step here explicitly so that we + // avoid setting the row equal to zero. + out->row(row) = kernel_x(0) * temp_row.head(image.cols()); + for (int i = 1; i < sigma_x; i++) + { + out->row(row) += kernel_x(i) * temp_row.segment(i, image.cols()); + } } - } } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/convolution.hpp b/src/aliceVision/image/convolution.hpp index 5e916bff0f..05df4d7bc4 100644 --- a/src/aliceVision/image/convolution.hpp +++ b/src/aliceVision/image/convolution.hpp @@ -33,42 +33,42 @@ namespace image { ** @param kernel convolution kernel ** @param out resulting image **/ -template< typename Image > -void ImageConvolution( const Image & img , const Mat & kernel , Image & out) +template +void ImageConvolution(const Image& img, const Mat& kernel, Image& out) { - const int kernel_width = kernel.cols() ; - const int kernel_height = kernel.rows() ; + const int kernel_width = kernel.cols(); + const int kernel_height = kernel.rows(); - assert( kernel_width % 2 != 0 && kernel_height % 2 != 0 ) ; + assert(kernel_width % 2 != 0 && kernel_height % 2 != 0); - typedef typename Image::Tpixel pix_t ; - typedef typename Accumulator< pix_t >::Type acc_pix_t ; + typedef typename Image::Tpixel pix_t; + typedef typename Accumulator::Type acc_pix_t; - out.resize( img.Width() , img.Height() ) ; + out.resize(img.Width(), img.Height()); - for( int row = 0 ; row < img.rows() ; ++row ) - { - for( int col = 0 ; col < img.cols() ; ++col ) + for (int row = 0; row < img.rows(); ++row) { - acc_pix_t sum = acc_pix_t( ) ; - - for( int i = 0 ; i < kernel_height ; ++i ) - { - for( int j = 0 ; j < kernel_width ; ++j ) + for (int col = 0; col < img.cols(); ++col) { - int idy = row + i - kernel_height / 2 ; - int idx = col + j - kernel_width / 2 ; - - // Get correct value - idx = idx < 0 ? 0 : ( idx >= img.cols() ? img.cols() - 1 : idx ) ; - idy = idy < 0 ? 0 : ( idy >= img.rows() ? img.rows() - 1 : idy ) ; - - sum += kernel( i , j ) * img( idy , idx ) ; + acc_pix_t sum = acc_pix_t(); + + for (int i = 0; i < kernel_height; ++i) + { + for (int j = 0; j < kernel_width; ++j) + { + int idy = row + i - kernel_height / 2; + int idx = col + j - kernel_width / 2; + + // Get correct value + idx = idx < 0 ? 0 : (idx >= img.cols() ? img.cols() - 1 : idx); + idy = idy < 0 ? 0 : (idy >= img.rows() ? img.rows() - 1 : idy); + + sum += kernel(i, j) * img(idy, idx); + } + } + out(row, col) = sum; } - } - out( row , col ) = sum ; } - } } /** @@ -78,41 +78,41 @@ void ImageConvolution( const Image & img , const Mat & kernel , Image & out) ** @param kernel convolution kernel ** @param out Output image **/ -template< typename ImageTypeIn , typename ImageTypeOut, typename Kernel > -void ImageHorizontalConvolution( const ImageTypeIn & img , const Kernel & kernel , ImageTypeOut & out) +template +void ImageHorizontalConvolution(const ImageTypeIn& img, const Kernel& kernel, ImageTypeOut& out) { - typedef typename ImageTypeIn::Tpixel pix_t ; + typedef typename ImageTypeIn::Tpixel pix_t; - const int rows ( img.rows() ); - const int cols ( img.cols() ); + const int rows(img.rows()); + const int cols(img.cols()); - out.resize( cols , rows ) ; + out.resize(cols, rows); - const int kernel_width = kernel.size() ; - const int half_kernel_width = kernel_width / 2 ; + const int kernel_width = kernel.size(); + const int half_kernel_width = kernel_width / 2; - std::vector > line( cols + kernel_width ); + std::vector> line(cols + kernel_width); - for( int row = 0 ; row < rows ; ++row ) - { - // Copy line - const pix_t start_pix = img.coeffRef( row , 0 ) ; - for( int k = 0 ; k < half_kernel_width ; ++k ) // pad before + for (int row = 0; row < rows; ++row) { - line[ k ] = start_pix ; - } - memcpy(&line[0] + half_kernel_width, img.data() + row * cols, sizeof(pix_t) * cols); - const pix_t end_pix = img.coeffRef( row , cols - 1 ) ; - for( int k = 0 ; k < half_kernel_width ; ++k ) // pad after - { - line[ k + half_kernel_width + cols ] = end_pix ; - } + // Copy line + const pix_t start_pix = img.coeffRef(row, 0); + for (int k = 0; k < half_kernel_width; ++k) // pad before + { + line[k] = start_pix; + } + memcpy(&line[0] + half_kernel_width, img.data() + row * cols, sizeof(pix_t) * cols); + const pix_t end_pix = img.coeffRef(row, cols - 1); + for (int k = 0; k < half_kernel_width; ++k) // pad after + { + line[k + half_kernel_width + cols] = end_pix; + } - // Apply convolution - conv_buffer_( &line[0] , kernel.data() , cols , kernel_width ); + // Apply convolution + conv_buffer_(&line[0], kernel.data(), cols, kernel_width); - memcpy(out.data() + row * cols, &line[0], sizeof(pix_t) * cols); - } + memcpy(out.data() + row * cols, &line[0], sizeof(pix_t) * cols); + } } /** @@ -122,45 +122,45 @@ void ImageHorizontalConvolution( const ImageTypeIn & img , const Kernel & kernel ** @param kernel convolution kernel ** @param out Output image **/ -template< typename ImageTypeIn , typename ImageTypeOut, typename Kernel > -void ImageVerticalConvolution( const ImageTypeIn & img , const Kernel & kernel , ImageTypeOut & out) +template +void ImageVerticalConvolution(const ImageTypeIn& img, const Kernel& kernel, ImageTypeOut& out) { - typedef typename ImageTypeIn::Tpixel pix_t ; + typedef typename ImageTypeIn::Tpixel pix_t; - const int kernel_width = kernel.size() ; - const int half_kernel_width = kernel_width / 2 ; + const int kernel_width = kernel.size(); + const int half_kernel_width = kernel_width / 2; - const int rows = img.rows() ; - const int cols = img.cols() ; + const int rows = img.rows(); + const int cols = img.cols(); - out.resize( cols , rows ) ; + out.resize(cols, rows); - std::vector > line( rows + kernel_width ); + std::vector> line(rows + kernel_width); - for( int col = 0 ; col < cols ; ++col ) - { - // Copy column - for( int k = 0 ; k < half_kernel_width ; ++k ) - { - line[ k ] = img.coeffRef( 0 , col ) ; - } - for( int k = 0 ; k < rows ; ++k ) + for (int col = 0; col < cols; ++col) { - line[ k + half_kernel_width ] = img.coeffRef( k , col ) ; - } - for( int k = 0 ; k < half_kernel_width ; ++k ) - { - line[ k + half_kernel_width + rows ] = img.coeffRef( rows - 1 , col ) ; - } + // Copy column + for (int k = 0; k < half_kernel_width; ++k) + { + line[k] = img.coeffRef(0, col); + } + for (int k = 0; k < rows; ++k) + { + line[k + half_kernel_width] = img.coeffRef(k, col); + } + for (int k = 0; k < half_kernel_width; ++k) + { + line[k + half_kernel_width + rows] = img.coeffRef(rows - 1, col); + } - // Apply convolution - conv_buffer_( &line[0] , kernel.data() , rows , kernel_width ); + // Apply convolution + conv_buffer_(&line[0], kernel.data(), rows, kernel_width); - for( int row = 0 ; row < rows ; ++row ) - { - out.coeffRef( row , col ) = line[row]; + for (int row = 0; row < rows; ++row) + { + out.coeffRef(row, col) = line[row]; + } } - } } /** @@ -171,21 +171,18 @@ void ImageVerticalConvolution( const ImageTypeIn & img , const Kernel & kernel , ** @param vert_k vertical kernel ** @param out output image **/ -template< typename ImageType, typename Kernel > -void ImageSeparableConvolution( const ImageType & img , - const Kernel & horiz_k , - const Kernel & vert_k , - ImageType & out) +template +void ImageSeparableConvolution(const ImageType& img, const Kernel& horiz_k, const Kernel& vert_k, ImageType& out) { - // Cast the Kernel to the appropriate type - typedef typename ImageType::Tpixel pix_t; - typedef Eigen::Matrix::Type, Eigen::Dynamic, 1> VecKernel; - const VecKernel horiz_k_cast = horiz_k.template cast< typename Accumulator::Type >(); - const VecKernel vert_k_cast = vert_k.template cast< typename Accumulator::Type >(); - - ImageType tmp ; - ImageHorizontalConvolution( img , horiz_k_cast , tmp ) ; - ImageVerticalConvolution( tmp , vert_k_cast , out ) ; + // Cast the Kernel to the appropriate type + typedef typename ImageType::Tpixel pix_t; + typedef Eigen::Matrix::Type, Eigen::Dynamic, 1> VecKernel; + const VecKernel horiz_k_cast = horiz_k.template cast::Type>(); + const VecKernel vert_k_cast = vert_k.template cast::Type>(); + + ImageType tmp; + ImageHorizontalConvolution(img, horiz_k_cast, tmp); + ImageVerticalConvolution(tmp, vert_k_cast, out); } typedef Eigen::Matrix RowMatrixXf; @@ -198,20 +195,17 @@ void SeparableConvolution2d(const RowMatrixXf& image, // Specialization for Image in order to use SeparableConvolution2d template -void ImageSeparableConvolution( const Image & img , - const Kernel & horiz_k , - const Kernel & vert_k , - Image & out) +void ImageSeparableConvolution(const Image& img, const Kernel& horiz_k, const Kernel& vert_k, Image& out) { - // Cast the Kernel to the appropriate type - typedef Image::Tpixel pix_t; - typedef Eigen::Matrix::Type, Eigen::Dynamic, 1> VecKernel; - const VecKernel horiz_k_cast = horiz_k.template cast< typename aliceVision::Accumulator::Type >(); - const VecKernel vert_k_cast = vert_k.template cast< typename aliceVision::Accumulator::Type >(); - - out.resize(img.Width(), img.Height()); - SeparableConvolution2d(img.GetMat(), horiz_k_cast, vert_k_cast, &((Image::Base&)out)); + // Cast the Kernel to the appropriate type + typedef Image::Tpixel pix_t; + typedef Eigen::Matrix::Type, Eigen::Dynamic, 1> VecKernel; + const VecKernel horiz_k_cast = horiz_k.template cast::Type>(); + const VecKernel vert_k_cast = vert_k.template cast::Type>(); + + out.resize(img.Width(), img.Height()); + SeparableConvolution2d(img.GetMat(), horiz_k_cast, vert_k_cast, &((Image::Base&)out)); } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/convolutionBase.hpp b/src/aliceVision/image/convolutionBase.hpp index 19a4f0008c..1f231d96e9 100644 --- a/src/aliceVision/image/convolutionBase.hpp +++ b/src/aliceVision/image/convolutionBase.hpp @@ -11,25 +11,25 @@ namespace aliceVision { namespace image { - /** - ** Filter an extended row [halfKernelSize][row][halfKernelSize] - ** @param buffer data to filter - ** @param kernel kernel array - ** @param rsize buffer length - ** @param ksize kernel length - **/ - template inline - void conv_buffer_( T1* buffer, const T2* kernel, int rsize, int ksize ) - { - for( std::size_t i = 0; i < rsize; ++i ) +/** + ** Filter an extended row [halfKernelSize][row][halfKernelSize] + ** @param buffer data to filter + ** @param kernel kernel array + ** @param rsize buffer length + ** @param ksize kernel length + **/ +template +inline void conv_buffer_(T1* buffer, const T2* kernel, int rsize, int ksize) +{ + for (std::size_t i = 0; i < rsize; ++i) { - T2 sum( 0 ); - for ( std::size_t j = 0; j < ksize; ++j ) - { - sum += buffer[i + j] * kernel[j]; - } - buffer[i] = sum; + T2 sum(0); + for (std::size_t j = 0; j < ksize; ++j) + { + sum += buffer[i + j] * kernel[j]; + } + buffer[i] = sum; } - } -} // namespace image -} // namespace aliceVision +} +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/dcp.cpp b/src/aliceVision/image/dcp.cpp index 1c704bb6d1..76ad942287 100644 --- a/src/aliceVision/image/dcp.cpp +++ b/src/aliceVision/image/dcp.cpp @@ -22,7 +22,7 @@ namespace bfs = boost::filesystem; double calibrationIlluminantToTemperature(const LightSource light) { // These temperatures are those found in DNG SDK reference code - switch(light) + switch (light) { case LightSource::STANDARD_LIGHT_A: case LightSource::TUNGSTEN: @@ -180,8 +180,8 @@ inline void rgb2hsvtc(float r, float g, float b, float& h, float& s, float& v) inline void hsv2rgbdcp(float h, float s, float v, float& r, float& g, float& b) { // special version for dcp which saves 1 division (in caller) and six multiplications (inside this function) - const int sector = h; // sector 0 to 5, floor() is very slow, and h is always > 0 - const float f = h - sector; // fractional part of h + const int sector = h; // sector 0 to 5, floor() is very slow, and h is always > 0 + const float f = h - sector; // fractional part of h v *= 65535.f; const float vs = v * s; @@ -191,90 +191,73 @@ inline void hsv2rgbdcp(float h, float s, float v, float& r, float& g, float& b) switch (sector) { - case 1: - r = q; - g = v; - b = p; - break; - - case 2: - r = p; - g = v; - b = t; - break; - - case 3: - r = p; - g = q; - b = v; - break; - - case 4: - r = t; - g = p; - b = v; - break; - - case 5: - r = v; - g = p; - b = q; - break; - - default: - r = v; - g = t; - b = p; + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + case 5: + r = v; + g = p; + b = q; + break; + + default: + r = v; + g = t; + b = p; } } namespace { /// Wyszecki & Stiles', 'Color Science - Concepts and Methods Data and Formulae - Second Edition', Page 228. /// (Reciprocal Megakelvin, CIE 1960 Chromaticity Coordinates 'u', CIE 1960 Chromaticity Coordinates 'v', Slope) -const std::vector> WYSZECKI_ROBERSTON_TABLE = - { {0, 0.18006, 0.26352, -0.24341}, - {10, 0.18066, 0.26589, -0.25479}, - {20, 0.18133, 0.26846, -0.26876}, - {30, 0.18208, 0.27119, -0.28539}, - {40, 0.18293, 0.27407, -0.30470}, - {50, 0.18388, 0.27709, -0.32675}, - {60, 0.18494, 0.28021, -0.35156}, - {70, 0.18611, 0.28342, -0.37915}, - {80, 0.18740, 0.28668, -0.40955}, - {90, 0.18880, 0.28997, -0.44278}, - {100, 0.19032, 0.29326, -0.47888}, - {125, 0.19462, 0.30141, -0.58204}, - {150, 0.19962, 0.30921, -0.70471}, - {175, 0.20525, 0.31647, -0.84901}, - {200, 0.21142, 0.32312, -1.0182}, - {225, 0.21807, 0.32909, -1.2168}, - {250, 0.22511, 0.33439, -1.4512}, - {275, 0.23247, 0.33904, -1.7298}, - {300, 0.24010, 0.34308, -2.0637}, - {325, 0.24792, 0.34655, -2.4681}, // 0.24702 ---> 0.24792 Bruce Lindbloom - {350, 0.25591, 0.34951, -2.9641}, - {375, 0.26400, 0.35200, -3.5814}, - {400, 0.27218, 0.35407, -4.3633}, - {425, 0.28039, 0.35577, -5.3762}, - {450, 0.28863, 0.35714, -6.7262}, - {475, 0.29685, 0.35823, -8.5955}, - {500, 0.30505, 0.35907, -11.324}, - {525, 0.31320, 0.35968, -15.628}, - {550, 0.32129, 0.36011, -23.325}, - {575, 0.32931, 0.36038, -40.770}, - {600, 0.33724, 0.36051, -116.45} }; +const std::vector> WYSZECKI_ROBERSTON_TABLE = { + {0, 0.18006, 0.26352, -0.24341}, {10, 0.18066, 0.26589, -0.25479}, {20, 0.18133, 0.26846, -0.26876}, + {30, 0.18208, 0.27119, -0.28539}, {40, 0.18293, 0.27407, -0.30470}, {50, 0.18388, 0.27709, -0.32675}, + {60, 0.18494, 0.28021, -0.35156}, {70, 0.18611, 0.28342, -0.37915}, {80, 0.18740, 0.28668, -0.40955}, + {90, 0.18880, 0.28997, -0.44278}, {100, 0.19032, 0.29326, -0.47888}, {125, 0.19462, 0.30141, -0.58204}, + {150, 0.19962, 0.30921, -0.70471}, {175, 0.20525, 0.31647, -0.84901}, {200, 0.21142, 0.32312, -1.0182}, + {225, 0.21807, 0.32909, -1.2168}, {250, 0.22511, 0.33439, -1.4512}, {275, 0.23247, 0.33904, -1.7298}, + {300, 0.24010, 0.34308, -2.0637}, {325, 0.24792, 0.34655, -2.4681}, // 0.24702 ---> 0.24792 Bruce Lindbloom + {350, 0.25591, 0.34951, -2.9641}, {375, 0.26400, 0.35200, -3.5814}, {400, 0.27218, 0.35407, -4.3633}, + {425, 0.28039, 0.35577, -5.3762}, {450, 0.28863, 0.35714, -6.7262}, {475, 0.29685, 0.35823, -8.5955}, + {500, 0.30505, 0.35907, -11.324}, {525, 0.31320, 0.35968, -15.628}, {550, 0.32129, 0.36011, -23.325}, + {575, 0.32931, 0.36038, -40.770}, {600, 0.33724, 0.36051, -116.45}}; // Useful matrices const DCPProfile::Matrix IdentityMatrix = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; const DCPProfile::Matrix CAT02_MATRIX = {0.7328, 0.4296, -0.1624, -0.7036, 1.6975, 0.0061, 0.0030, 0.0136, 0.9834}; -const DCPProfile::Matrix xyzD50ToSrgbD65LinearMatrix = { 3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252 }; -const DCPProfile::Matrix xyzD50ToSrgbD50LinearMatrix = { 3.1338561, -1.6168667, -0.4906146, -0.9787684, 1.9161415, 0.0334540, 0.0719453, -0.2289914, 1.4052427 }; +const DCPProfile::Matrix xyzD50ToSrgbD65LinearMatrix = + {3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252}; +const DCPProfile::Matrix xyzD50ToSrgbD50LinearMatrix = + {3.1338561, -1.6168667, -0.4906146, -0.9787684, 1.9161415, 0.0334540, 0.0719453, -0.2289914, 1.4052427}; // xyzD50ToACES2065Matrix = xyzD60ToACES2065 * xyzD50ToXyzD60 -const DCPProfile::Matrix xyzD50ToACES2065Matrix = { 1.019573375, -0.022815668, 0.048147546, -0.503070253, 1.384421764, 0.121965628, 0.000961591, 0.003054793, 1.207019111 }; +const DCPProfile::Matrix xyzD50ToACES2065Matrix = + {1.019573375, -0.022815668, 0.048147546, -0.503070253, 1.384421764, 0.121965628, 0.000961591, 0.003054793, 1.207019111}; const double TINT_SCALE = -3000.0; -} // namespace +} // namespace enum class TagType : int { @@ -304,15 +287,13 @@ enum class Endianness : int inline static int getTypeSize(TagType type) { - return ((type == TagType::T_INVALID || type == TagType::T_BYTE || type == TagType::T_ASCII || - type == TagType::T_SBYTE || type == TagType::T_UNDEFINED) - ? 1 - : (type == TagType::T_SHORT || type == TagType::T_SSHORT) ? 2 - : (type == TagType::T_LONG || type == TagType::T_SLONG || type == TagType::T_FLOAT || - type == TagType::T_OLYUNDEF) - ? 4 - : (type == TagType::T_RATIONAL || type == TagType::T_SRATIONAL || type == TagType::T_DOUBLE) ? 8 - : 0); + return ( + (type == TagType::T_INVALID || type == TagType::T_BYTE || type == TagType::T_ASCII || type == TagType::T_SBYTE || type == TagType::T_UNDEFINED) + ? 1 + : (type == TagType::T_SHORT || type == TagType::T_SSHORT) ? 2 + : (type == TagType::T_LONG || type == TagType::T_SLONG || type == TagType::T_FLOAT || type == TagType::T_OLYUNDEF) ? 4 + : (type == TagType::T_RATIONAL || type == TagType::T_SRATIONAL || type == TagType::T_DOUBLE) ? 8 + : 0); } enum class TagKey : int @@ -343,7 +324,7 @@ enum class TagKey : int //----------------------------------------------------------------------------- unsigned short sget2(const unsigned char* s, Endianness order) { - if(order == Endianness::LITTLE) + if (order == Endianness::LITTLE) { return s[0] | s[1] << 8; } @@ -354,7 +335,7 @@ unsigned short sget2(const unsigned char* s, Endianness order) } int sget4(const unsigned char* s, Endianness order) { - if(order == Endianness::LITTLE) + if (order == Endianness::LITTLE) { return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24; } @@ -391,8 +372,7 @@ short int int2_to_signed(short unsigned int i) */ class Tag { - -public: + public: unsigned short tagID{0}; TagType type{TagType::T_INVALID}; unsigned int datasize{0}; @@ -414,17 +394,16 @@ class Tag }; Tag::Tag(unsigned short tag, TagType type, unsigned int datasize, Endianness order, FILE* f) - : // file index supposed to be at rigth location to read datasize - tagID(tag) - , type(type) - , datasize(datasize) - , order(order) + : // file index supposed to be at rigth location to read datasize + tagID(tag), + type(type), + datasize(datasize), + order(order) { - // load value field (possibly seek before) const int valuesize = datasize * getTypeSize(type); - if(valuesize > 4) + if (valuesize > 4) { fseek(f, get4(f, order), SEEK_SET); } @@ -437,19 +416,16 @@ Tag::Tag(unsigned short tag, TagType type, unsigned int datasize, Endianness ord v_value[readSize] = '\0'; } -bool Tag::operator==(const Tag& t) const -{ - return (tagID == t.tagID); -} +bool Tag::operator==(const Tag& t) const { return (tagID == t.tagID); } int Tag::toInt(int ofs, TagType astype) const { - if(astype == TagType::T_INVALID) + if (astype == TagType::T_INVALID) { astype = type; } - switch(astype) + switch (astype) { case TagType::T_SBYTE: return int(static_cast(v_value[ofs])); @@ -489,7 +465,7 @@ int Tag::toInt(int ofs, TagType astype) const return 0; default: - return 0; // Quick fix for missing cases (INVALID, DOUBLE, OLYUNDEF, SUBDIR) + return 0; // Quick fix for missing cases (INVALID, DOUBLE, OLYUNDEF, SUBDIR) } return 0; @@ -505,7 +481,7 @@ double Tag::toDouble(int ofs) const double ud, dd; - switch(type) + switch (type) { case TagType::T_SBYTE: return (double)(static_cast(v_value[ofs])); @@ -550,45 +526,45 @@ double Tag::toDouble(int ofs) const void Tag::toString(char* buffer, std::size_t size, int ofs) const { - if(!buffer || !size) + if (!buffer || !size) { return; } - if(type == TagType::T_UNDEFINED) + if (type == TagType::T_UNDEFINED) { bool isstring = true; - for(unsigned int i = 0; (i + ofs < datasize) && (i < 64) && v_value[i + ofs]; ++i) + for (unsigned int i = 0; (i + ofs < datasize) && (i < 64) && v_value[i + ofs]; ++i) { - if((v_value[i + ofs] < 32) || (v_value[i + ofs] > 126)) + if ((v_value[i + ofs] < 32) || (v_value[i + ofs] > 126)) { isstring = false; } } - if(isstring) + if (isstring) { - if(size < 3) + if (size < 3) { return; } std::size_t j = 0; - for(unsigned int i = 0; (i + ofs < datasize) && (i < 64) && v_value[i + ofs]; ++i) + for (unsigned int i = 0; (i + ofs < datasize) && (i < 64) && v_value[i + ofs]; ++i) { - if(v_value[i + ofs] == '<' || v_value[i + ofs] == '>') + if (v_value[i + ofs] == '<' || v_value[i + ofs] == '>') { buffer[j++] = '\\'; - if(j > size - 2) + if (j > size - 2) { break; } } buffer[j++] = v_value[i + ofs]; - if(j > size - 2) + if (j > size - 2) { break; } @@ -598,7 +574,7 @@ void Tag::toString(char* buffer, std::size_t size, int ofs) const return; } } - else if(type == TagType::T_ASCII) + else if (type == TagType::T_ASCII) { snprintf(buffer, size, "%.64s", v_value.data() + ofs); return; @@ -608,11 +584,11 @@ void Tag::toString(char* buffer, std::size_t size, int ofs) const buffer[0] = 0; - for(int i = 0; i < std::min(maxcount, datasize * getTypeSize(type) - ofs); i++) + for (int i = 0; i < std::min(maxcount, datasize * getTypeSize(type) - ofs); i++) { std::size_t len = strlen(buffer); - if(i > 0 && size - len > 2) + if (i > 0 && size - len > 2) { strcat(buffer, ", "); len += 2; @@ -620,7 +596,7 @@ void Tag::toString(char* buffer, std::size_t size, int ofs) const char* b = buffer + len; - switch(type) + switch (type) { case TagType::T_UNDEFINED: case TagType::T_BYTE: @@ -644,12 +620,15 @@ void Tag::toString(char* buffer, std::size_t size, int ofs) const break; case TagType::T_SRATIONAL: - snprintf(b, size - len, "%d/%d", (int)sget4(v_value.data() + 8 * i + ofs, order), - (int)sget4(v_value.data() + 8 * i + ofs + 4, order)); + snprintf( + b, size - len, "%d/%d", (int)sget4(v_value.data() + 8 * i + ofs, order), (int)sget4(v_value.data() + 8 * i + ofs + 4, order)); break; case TagType::T_RATIONAL: - snprintf(b, size - len, "%u/%u", (uint32_t)sget4(v_value.data() + 8 * i + ofs, order), + snprintf(b, + size - len, + "%u/%u", + (uint32_t)sget4(v_value.data() + 8 * i + ofs, order), (uint32_t)sget4(v_value.data() + 8 * i + ofs + 4, order)); break; @@ -662,7 +641,7 @@ void Tag::toString(char* buffer, std::size_t size, int ofs) const } } - if(datasize > maxcount && size - strlen(buffer) > 3) + if (datasize > maxcount && size - strlen(buffer) > 3) { strcat(buffer, "..."); } @@ -678,17 +657,15 @@ std::string Tag::valueToString() const const Tag* findTag(TagKey tagID, const std::vector& v_Tags) { size_t idx = 0; - while(idx < v_Tags.size() && (v_Tags[idx].tagID != (unsigned short)tagID)) + while (idx < v_Tags.size() && (v_Tags[idx].tagID != (unsigned short)tagID)) { idx++; } - if(idx == v_Tags.size()) + if (idx == v_Tags.size()) return nullptr; return &(v_Tags[idx]); } - - // Spline interpolation from user data to get the internal tone curve with 65536 values // https://en.wikipedia.org/wiki/Spline_interpolation // @@ -700,7 +677,7 @@ void SplineToneCurve::Set(const std::vector& v_xy) const size_t N = v_xy.size() / 2; - for(int i = 0; i < N; ++i) + for (int i = 0; i < N; ++i) { v_x.push_back(v_xy[2 * i]); v_y.push_back(v_xy[2 * i + 1]); @@ -710,7 +687,7 @@ void SplineToneCurve::Set(const std::vector& v_xy) v_ypp[0] = v_u[0] = 0.0; /* Natural spline */ - for(int i = 1; i < N - 1; ++i) + for (int i = 1; i < N - 1; ++i) { const double sig = (v_x[i] - v_x[i - 1]) / (v_x[i + 1] - v_x[i - 1]); const double p = sig * v_ypp[i - 1] + 2.0; @@ -721,18 +698,17 @@ void SplineToneCurve::Set(const std::vector& v_xy) v_ypp[N - 1] = 0.0; - for(int k = N - 2; k >= 0; --k) + for (int k = N - 2; k >= 0; --k) { v_ypp[k] = v_ypp[k] * v_ypp[k + 1] + v_u[k]; } int k_hi = 0; - for(int i = 0; i < 65536; ++i) + for (int i = 0; i < 65536; ++i) { - const float t = float(i) / 65535.f; - while((v_x[k_hi] <= t) && (k_hi < v_x.size() - 1)) + while ((v_x[k_hi] <= t) && (k_hi < v_x.size() - 1)) k_hi++; const int k_lo = k_hi - 1; @@ -740,8 +716,7 @@ void SplineToneCurve::Set(const std::vector& v_xy) const double a = (v_x[k_hi] - t) / h; const double b = (t - v_x[k_lo]) / h; const double r = a * v_y[k_lo] + b * v_y[k_hi] + - ((a * a * a - a) * v_ypp[k_lo] + (b * b * b - b) * v_ypp[k_hi]) * (h * h) * - 0.1666666666666666666666666666666; + ((a * a * a - a) * v_ypp[k_lo] + (b * b * b - b) * v_ypp[k_hi]) * (h * h) * 0.1666666666666666666666666666666; ffffToneCurve[i] = (float)(r > 0.0 ? (r < 1.0 ? r : 1.0) : 0.0) * 65535.f; } @@ -753,22 +728,22 @@ void SplineToneCurve::Apply(float& ir, float& ig, float& ib) const float g = clamp(ig, 0.f, 65535.0f); float b = clamp(ib, 0.f, 65535.0f); - if(r >= g) + if (r >= g) { - if(g > b) + if (g > b) { - RGBTone(r, g, b); // Case 1: r >= g > b + RGBTone(r, g, b); // Case 1: r >= g > b } - else if(b > r) + else if (b > r) { - RGBTone(b, r, g); // Case 2: b > r >= g + RGBTone(b, r, g); // Case 2: b > r >= g } - else if(b > g) + else if (b > g) { - RGBTone(r, b, g); // Case 3: r >= b > g + RGBTone(r, b, g); // Case 3: r >= b > g } else - { // Case 4: r == g == b + { // Case 4: r == g == b r = getval(r); g = getval(g); b = g; @@ -776,21 +751,21 @@ void SplineToneCurve::Apply(float& ir, float& ig, float& ib) const } else { - if(r >= b) + if (r >= b) { - RGBTone(g, r, b); // Case 5: g > r >= b + RGBTone(g, r, b); // Case 5: g > r >= b } - else if(b > g) + else if (b > g) { - RGBTone(b, g, r); // Case 6: b > g > r + RGBTone(b, g, r); // Case 6: b > g > r } else { - RGBTone(g, b, r); // Case 7: g >= b > r + RGBTone(g, b, r); // Case 7: g >= b > r } } - if(!(ir < 0.0 || ir > 65535.0) || !(ig < 0.0 || ig > 65535.0) || !(ib < 0.0 || ib > 65535.0)) + if (!(ir < 0.0 || ir > 65535.0) || !(ig < 0.0 || ig > 65535.0) || !(ib < 0.0 || ib > 65535.0)) { ir = r; ig = g; @@ -812,39 +787,38 @@ float SplineToneCurve::getval(const float idx) const const int idx_int = (int)idx; const float idx_dec = idx - idx_int; - if(idx_int < 0) + if (idx_int < 0) return ffffToneCurve.front(); - else if(idx_int >= 65535) + else if (idx_int >= 65535) return ffffToneCurve.back(); else return idx_dec * ffffToneCurve[idx_int] + (1.f - idx_dec) * ffffToneCurve[idx_int + 1]; } - DCPProfile::DCPProfile() - : baseline_exposure_offset(0.0) - , analogBalance(IdentityMatrix) - , camera_calibration_1(IdentityMatrix) - , camera_calibration_2(IdentityMatrix) - , color_matrix_1(IdentityMatrix) - , color_matrix_2(IdentityMatrix) - , forward_matrix_1(IdentityMatrix) - , forward_matrix_2(IdentityMatrix) - , ws_sRGB(IdentityMatrix) - , sRGB_ws(IdentityMatrix) + : baseline_exposure_offset(0.0), + analogBalance(IdentityMatrix), + camera_calibration_1(IdentityMatrix), + camera_calibration_2(IdentityMatrix), + color_matrix_1(IdentityMatrix), + color_matrix_2(IdentityMatrix), + forward_matrix_1(IdentityMatrix), + forward_matrix_2(IdentityMatrix), + ws_sRGB(IdentityMatrix), + sRGB_ws(IdentityMatrix) {} DCPProfile::DCPProfile(const std::string& filename) - : baseline_exposure_offset(0.0) - , analogBalance(IdentityMatrix) - , camera_calibration_1(IdentityMatrix) - , camera_calibration_2(IdentityMatrix) - , color_matrix_1(IdentityMatrix) - , color_matrix_2(IdentityMatrix) - , forward_matrix_1(IdentityMatrix) - , forward_matrix_2(IdentityMatrix) - , ws_sRGB(IdentityMatrix) - , sRGB_ws(IdentityMatrix) + : baseline_exposure_offset(0.0), + analogBalance(IdentityMatrix), + camera_calibration_1(IdentityMatrix), + camera_calibration_2(IdentityMatrix), + color_matrix_1(IdentityMatrix), + color_matrix_2(IdentityMatrix), + forward_matrix_1(IdentityMatrix), + forward_matrix_2(IdentityMatrix), + ws_sRGB(IdentityMatrix), + sRGB_ws(IdentityMatrix) { Load(filename); } @@ -857,100 +831,80 @@ void DCPProfile::Load(const std::string& filename) constexpr int tiff_float_size = 4; static const float adobe_camera_raw_default_curve[] = { - 0.00000f, 0.00078f, 0.00160f, 0.00242f, 0.00314f, 0.00385f, 0.00460f, 0.00539f, 0.00623f, 0.00712f, 0.00806f, - 0.00906f, 0.01012f, 0.01122f, 0.01238f, 0.01359f, 0.01485f, 0.01616f, 0.01751f, 0.01890f, 0.02033f, 0.02180f, - 0.02331f, 0.02485f, 0.02643f, 0.02804f, 0.02967f, 0.03134f, 0.03303f, 0.03475f, 0.03648f, 0.03824f, 0.04002f, - 0.04181f, 0.04362f, 0.04545f, 0.04730f, 0.04916f, 0.05103f, 0.05292f, 0.05483f, 0.05675f, 0.05868f, 0.06063f, - 0.06259f, 0.06457f, 0.06655f, 0.06856f, 0.07057f, 0.07259f, 0.07463f, 0.07668f, 0.07874f, 0.08081f, 0.08290f, - 0.08499f, 0.08710f, 0.08921f, 0.09134f, 0.09348f, 0.09563f, 0.09779f, 0.09996f, 0.10214f, 0.10433f, 0.10652f, - 0.10873f, 0.11095f, 0.11318f, 0.11541f, 0.11766f, 0.11991f, 0.12218f, 0.12445f, 0.12673f, 0.12902f, 0.13132f, - 0.13363f, 0.13595f, 0.13827f, 0.14061f, 0.14295f, 0.14530f, 0.14765f, 0.15002f, 0.15239f, 0.15477f, 0.15716f, - 0.15956f, 0.16197f, 0.16438f, 0.16680f, 0.16923f, 0.17166f, 0.17410f, 0.17655f, 0.17901f, 0.18148f, 0.18395f, - 0.18643f, 0.18891f, 0.19141f, 0.19391f, 0.19641f, 0.19893f, 0.20145f, 0.20398f, 0.20651f, 0.20905f, 0.21160f, - 0.21416f, 0.21672f, 0.21929f, 0.22185f, 0.22440f, 0.22696f, 0.22950f, 0.23204f, 0.23458f, 0.23711f, 0.23963f, - 0.24215f, 0.24466f, 0.24717f, 0.24967f, 0.25216f, 0.25465f, 0.25713f, 0.25961f, 0.26208f, 0.26454f, 0.26700f, - 0.26945f, 0.27189f, 0.27433f, 0.27676f, 0.27918f, 0.28160f, 0.28401f, 0.28641f, 0.28881f, 0.29120f, 0.29358f, - 0.29596f, 0.29833f, 0.30069f, 0.30305f, 0.30540f, 0.30774f, 0.31008f, 0.31241f, 0.31473f, 0.31704f, 0.31935f, - 0.32165f, 0.32395f, 0.32623f, 0.32851f, 0.33079f, 0.33305f, 0.33531f, 0.33756f, 0.33981f, 0.34205f, 0.34428f, - 0.34650f, 0.34872f, 0.35093f, 0.35313f, 0.35532f, 0.35751f, 0.35969f, 0.36187f, 0.36404f, 0.36620f, 0.36835f, - 0.37050f, 0.37264f, 0.37477f, 0.37689f, 0.37901f, 0.38112f, 0.38323f, 0.38533f, 0.38742f, 0.38950f, 0.39158f, - 0.39365f, 0.39571f, 0.39777f, 0.39982f, 0.40186f, 0.40389f, 0.40592f, 0.40794f, 0.40996f, 0.41197f, 0.41397f, - 0.41596f, 0.41795f, 0.41993f, 0.42191f, 0.42388f, 0.42584f, 0.42779f, 0.42974f, 0.43168f, 0.43362f, 0.43554f, - 0.43747f, 0.43938f, 0.44129f, 0.44319f, 0.44509f, 0.44698f, 0.44886f, 0.45073f, 0.45260f, 0.45447f, 0.45632f, - 0.45817f, 0.46002f, 0.46186f, 0.46369f, 0.46551f, 0.46733f, 0.46914f, 0.47095f, 0.47275f, 0.47454f, 0.47633f, - 0.47811f, 0.47989f, 0.48166f, 0.48342f, 0.48518f, 0.48693f, 0.48867f, 0.49041f, 0.49214f, 0.49387f, 0.49559f, - 0.49730f, 0.49901f, 0.50072f, 0.50241f, 0.50410f, 0.50579f, 0.50747f, 0.50914f, 0.51081f, 0.51247f, 0.51413f, - 0.51578f, 0.51742f, 0.51906f, 0.52069f, 0.52232f, 0.52394f, 0.52556f, 0.52717f, 0.52878f, 0.53038f, 0.53197f, - 0.53356f, 0.53514f, 0.53672f, 0.53829f, 0.53986f, 0.54142f, 0.54297f, 0.54452f, 0.54607f, 0.54761f, 0.54914f, - 0.55067f, 0.55220f, 0.55371f, 0.55523f, 0.55673f, 0.55824f, 0.55973f, 0.56123f, 0.56271f, 0.56420f, 0.56567f, - 0.56715f, 0.56861f, 0.57007f, 0.57153f, 0.57298f, 0.57443f, 0.57587f, 0.57731f, 0.57874f, 0.58017f, 0.58159f, - 0.58301f, 0.58443f, 0.58583f, 0.58724f, 0.58864f, 0.59003f, 0.59142f, 0.59281f, 0.59419f, 0.59556f, 0.59694f, - 0.59830f, 0.59966f, 0.60102f, 0.60238f, 0.60373f, 0.60507f, 0.60641f, 0.60775f, 0.60908f, 0.61040f, 0.61173f, - 0.61305f, 0.61436f, 0.61567f, 0.61698f, 0.61828f, 0.61957f, 0.62087f, 0.62216f, 0.62344f, 0.62472f, 0.62600f, - 0.62727f, 0.62854f, 0.62980f, 0.63106f, 0.63232f, 0.63357f, 0.63482f, 0.63606f, 0.63730f, 0.63854f, 0.63977f, - 0.64100f, 0.64222f, 0.64344f, 0.64466f, 0.64587f, 0.64708f, 0.64829f, 0.64949f, 0.65069f, 0.65188f, 0.65307f, - 0.65426f, 0.65544f, 0.65662f, 0.65779f, 0.65897f, 0.66013f, 0.66130f, 0.66246f, 0.66362f, 0.66477f, 0.66592f, - 0.66707f, 0.66821f, 0.66935f, 0.67048f, 0.67162f, 0.67275f, 0.67387f, 0.67499f, 0.67611f, 0.67723f, 0.67834f, - 0.67945f, 0.68055f, 0.68165f, 0.68275f, 0.68385f, 0.68494f, 0.68603f, 0.68711f, 0.68819f, 0.68927f, 0.69035f, - 0.69142f, 0.69249f, 0.69355f, 0.69461f, 0.69567f, 0.69673f, 0.69778f, 0.69883f, 0.69988f, 0.70092f, 0.70196f, - 0.70300f, 0.70403f, 0.70506f, 0.70609f, 0.70711f, 0.70813f, 0.70915f, 0.71017f, 0.71118f, 0.71219f, 0.71319f, - 0.71420f, 0.71520f, 0.71620f, 0.71719f, 0.71818f, 0.71917f, 0.72016f, 0.72114f, 0.72212f, 0.72309f, 0.72407f, - 0.72504f, 0.72601f, 0.72697f, 0.72794f, 0.72890f, 0.72985f, 0.73081f, 0.73176f, 0.73271f, 0.73365f, 0.73460f, - 0.73554f, 0.73647f, 0.73741f, 0.73834f, 0.73927f, 0.74020f, 0.74112f, 0.74204f, 0.74296f, 0.74388f, 0.74479f, - 0.74570f, 0.74661f, 0.74751f, 0.74842f, 0.74932f, 0.75021f, 0.75111f, 0.75200f, 0.75289f, 0.75378f, 0.75466f, - 0.75555f, 0.75643f, 0.75730f, 0.75818f, 0.75905f, 0.75992f, 0.76079f, 0.76165f, 0.76251f, 0.76337f, 0.76423f, - 0.76508f, 0.76594f, 0.76679f, 0.76763f, 0.76848f, 0.76932f, 0.77016f, 0.77100f, 0.77183f, 0.77267f, 0.77350f, - 0.77432f, 0.77515f, 0.77597f, 0.77680f, 0.77761f, 0.77843f, 0.77924f, 0.78006f, 0.78087f, 0.78167f, 0.78248f, - 0.78328f, 0.78408f, 0.78488f, 0.78568f, 0.78647f, 0.78726f, 0.78805f, 0.78884f, 0.78962f, 0.79040f, 0.79118f, - 0.79196f, 0.79274f, 0.79351f, 0.79428f, 0.79505f, 0.79582f, 0.79658f, 0.79735f, 0.79811f, 0.79887f, 0.79962f, - 0.80038f, 0.80113f, 0.80188f, 0.80263f, 0.80337f, 0.80412f, 0.80486f, 0.80560f, 0.80634f, 0.80707f, 0.80780f, - 0.80854f, 0.80926f, 0.80999f, 0.81072f, 0.81144f, 0.81216f, 0.81288f, 0.81360f, 0.81431f, 0.81503f, 0.81574f, - 0.81645f, 0.81715f, 0.81786f, 0.81856f, 0.81926f, 0.81996f, 0.82066f, 0.82135f, 0.82205f, 0.82274f, 0.82343f, - 0.82412f, 0.82480f, 0.82549f, 0.82617f, 0.82685f, 0.82753f, 0.82820f, 0.82888f, 0.82955f, 0.83022f, 0.83089f, - 0.83155f, 0.83222f, 0.83288f, 0.83354f, 0.83420f, 0.83486f, 0.83552f, 0.83617f, 0.83682f, 0.83747f, 0.83812f, - 0.83877f, 0.83941f, 0.84005f, 0.84069f, 0.84133f, 0.84197f, 0.84261f, 0.84324f, 0.84387f, 0.84450f, 0.84513f, - 0.84576f, 0.84639f, 0.84701f, 0.84763f, 0.84825f, 0.84887f, 0.84949f, 0.85010f, 0.85071f, 0.85132f, 0.85193f, - 0.85254f, 0.85315f, 0.85375f, 0.85436f, 0.85496f, 0.85556f, 0.85615f, 0.85675f, 0.85735f, 0.85794f, 0.85853f, - 0.85912f, 0.85971f, 0.86029f, 0.86088f, 0.86146f, 0.86204f, 0.86262f, 0.86320f, 0.86378f, 0.86435f, 0.86493f, - 0.86550f, 0.86607f, 0.86664f, 0.86720f, 0.86777f, 0.86833f, 0.86889f, 0.86945f, 0.87001f, 0.87057f, 0.87113f, - 0.87168f, 0.87223f, 0.87278f, 0.87333f, 0.87388f, 0.87443f, 0.87497f, 0.87552f, 0.87606f, 0.87660f, 0.87714f, - 0.87768f, 0.87821f, 0.87875f, 0.87928f, 0.87981f, 0.88034f, 0.88087f, 0.88140f, 0.88192f, 0.88244f, 0.88297f, - 0.88349f, 0.88401f, 0.88453f, 0.88504f, 0.88556f, 0.88607f, 0.88658f, 0.88709f, 0.88760f, 0.88811f, 0.88862f, - 0.88912f, 0.88963f, 0.89013f, 0.89063f, 0.89113f, 0.89163f, 0.89212f, 0.89262f, 0.89311f, 0.89360f, 0.89409f, - 0.89458f, 0.89507f, 0.89556f, 0.89604f, 0.89653f, 0.89701f, 0.89749f, 0.89797f, 0.89845f, 0.89892f, 0.89940f, - 0.89987f, 0.90035f, 0.90082f, 0.90129f, 0.90176f, 0.90222f, 0.90269f, 0.90316f, 0.90362f, 0.90408f, 0.90454f, - 0.90500f, 0.90546f, 0.90592f, 0.90637f, 0.90683f, 0.90728f, 0.90773f, 0.90818f, 0.90863f, 0.90908f, 0.90952f, - 0.90997f, 0.91041f, 0.91085f, 0.91130f, 0.91173f, 0.91217f, 0.91261f, 0.91305f, 0.91348f, 0.91392f, 0.91435f, - 0.91478f, 0.91521f, 0.91564f, 0.91606f, 0.91649f, 0.91691f, 0.91734f, 0.91776f, 0.91818f, 0.91860f, 0.91902f, - 0.91944f, 0.91985f, 0.92027f, 0.92068f, 0.92109f, 0.92150f, 0.92191f, 0.92232f, 0.92273f, 0.92314f, 0.92354f, - 0.92395f, 0.92435f, 0.92475f, 0.92515f, 0.92555f, 0.92595f, 0.92634f, 0.92674f, 0.92713f, 0.92753f, 0.92792f, - 0.92831f, 0.92870f, 0.92909f, 0.92947f, 0.92986f, 0.93025f, 0.93063f, 0.93101f, 0.93139f, 0.93177f, 0.93215f, - 0.93253f, 0.93291f, 0.93328f, 0.93366f, 0.93403f, 0.93440f, 0.93478f, 0.93515f, 0.93551f, 0.93588f, 0.93625f, - 0.93661f, 0.93698f, 0.93734f, 0.93770f, 0.93807f, 0.93843f, 0.93878f, 0.93914f, 0.93950f, 0.93986f, 0.94021f, - 0.94056f, 0.94092f, 0.94127f, 0.94162f, 0.94197f, 0.94231f, 0.94266f, 0.94301f, 0.94335f, 0.94369f, 0.94404f, - 0.94438f, 0.94472f, 0.94506f, 0.94540f, 0.94573f, 0.94607f, 0.94641f, 0.94674f, 0.94707f, 0.94740f, 0.94774f, - 0.94807f, 0.94839f, 0.94872f, 0.94905f, 0.94937f, 0.94970f, 0.95002f, 0.95035f, 0.95067f, 0.95099f, 0.95131f, - 0.95163f, 0.95194f, 0.95226f, 0.95257f, 0.95289f, 0.95320f, 0.95351f, 0.95383f, 0.95414f, 0.95445f, 0.95475f, - 0.95506f, 0.95537f, 0.95567f, 0.95598f, 0.95628f, 0.95658f, 0.95688f, 0.95718f, 0.95748f, 0.95778f, 0.95808f, - 0.95838f, 0.95867f, 0.95897f, 0.95926f, 0.95955f, 0.95984f, 0.96013f, 0.96042f, 0.96071f, 0.96100f, 0.96129f, - 0.96157f, 0.96186f, 0.96214f, 0.96242f, 0.96271f, 0.96299f, 0.96327f, 0.96355f, 0.96382f, 0.96410f, 0.96438f, - 0.96465f, 0.96493f, 0.96520f, 0.96547f, 0.96574f, 0.96602f, 0.96629f, 0.96655f, 0.96682f, 0.96709f, 0.96735f, - 0.96762f, 0.96788f, 0.96815f, 0.96841f, 0.96867f, 0.96893f, 0.96919f, 0.96945f, 0.96971f, 0.96996f, 0.97022f, - 0.97047f, 0.97073f, 0.97098f, 0.97123f, 0.97149f, 0.97174f, 0.97199f, 0.97223f, 0.97248f, 0.97273f, 0.97297f, - 0.97322f, 0.97346f, 0.97371f, 0.97395f, 0.97419f, 0.97443f, 0.97467f, 0.97491f, 0.97515f, 0.97539f, 0.97562f, - 0.97586f, 0.97609f, 0.97633f, 0.97656f, 0.97679f, 0.97702f, 0.97725f, 0.97748f, 0.97771f, 0.97794f, 0.97817f, - 0.97839f, 0.97862f, 0.97884f, 0.97907f, 0.97929f, 0.97951f, 0.97973f, 0.97995f, 0.98017f, 0.98039f, 0.98061f, - 0.98082f, 0.98104f, 0.98125f, 0.98147f, 0.98168f, 0.98189f, 0.98211f, 0.98232f, 0.98253f, 0.98274f, 0.98295f, - 0.98315f, 0.98336f, 0.98357f, 0.98377f, 0.98398f, 0.98418f, 0.98438f, 0.98458f, 0.98478f, 0.98498f, 0.98518f, - 0.98538f, 0.98558f, 0.98578f, 0.98597f, 0.98617f, 0.98636f, 0.98656f, 0.98675f, 0.98694f, 0.98714f, 0.98733f, - 0.98752f, 0.98771f, 0.98789f, 0.98808f, 0.98827f, 0.98845f, 0.98864f, 0.98882f, 0.98901f, 0.98919f, 0.98937f, - 0.98955f, 0.98973f, 0.98991f, 0.99009f, 0.99027f, 0.99045f, 0.99063f, 0.99080f, 0.99098f, 0.99115f, 0.99133f, - 0.99150f, 0.99167f, 0.99184f, 0.99201f, 0.99218f, 0.99235f, 0.99252f, 0.99269f, 0.99285f, 0.99302f, 0.99319f, - 0.99335f, 0.99351f, 0.99368f, 0.99384f, 0.99400f, 0.99416f, 0.99432f, 0.99448f, 0.99464f, 0.99480f, 0.99495f, - 0.99511f, 0.99527f, 0.99542f, 0.99558f, 0.99573f, 0.99588f, 0.99603f, 0.99619f, 0.99634f, 0.99649f, 0.99664f, - 0.99678f, 0.99693f, 0.99708f, 0.99722f, 0.99737f, 0.99751f, 0.99766f, 0.99780f, 0.99794f, 0.99809f, 0.99823f, - 0.99837f, 0.99851f, 0.99865f, 0.99879f, 0.99892f, 0.99906f, 0.99920f, 0.99933f, 0.99947f, 0.99960f, 0.99974f, - 0.99987f, 1.00000f }; + 0.00000f, 0.00078f, 0.00160f, 0.00242f, 0.00314f, 0.00385f, 0.00460f, 0.00539f, 0.00623f, 0.00712f, 0.00806f, 0.00906f, 0.01012f, 0.01122f, + 0.01238f, 0.01359f, 0.01485f, 0.01616f, 0.01751f, 0.01890f, 0.02033f, 0.02180f, 0.02331f, 0.02485f, 0.02643f, 0.02804f, 0.02967f, 0.03134f, + 0.03303f, 0.03475f, 0.03648f, 0.03824f, 0.04002f, 0.04181f, 0.04362f, 0.04545f, 0.04730f, 0.04916f, 0.05103f, 0.05292f, 0.05483f, 0.05675f, + 0.05868f, 0.06063f, 0.06259f, 0.06457f, 0.06655f, 0.06856f, 0.07057f, 0.07259f, 0.07463f, 0.07668f, 0.07874f, 0.08081f, 0.08290f, 0.08499f, + 0.08710f, 0.08921f, 0.09134f, 0.09348f, 0.09563f, 0.09779f, 0.09996f, 0.10214f, 0.10433f, 0.10652f, 0.10873f, 0.11095f, 0.11318f, 0.11541f, + 0.11766f, 0.11991f, 0.12218f, 0.12445f, 0.12673f, 0.12902f, 0.13132f, 0.13363f, 0.13595f, 0.13827f, 0.14061f, 0.14295f, 0.14530f, 0.14765f, + 0.15002f, 0.15239f, 0.15477f, 0.15716f, 0.15956f, 0.16197f, 0.16438f, 0.16680f, 0.16923f, 0.17166f, 0.17410f, 0.17655f, 0.17901f, 0.18148f, + 0.18395f, 0.18643f, 0.18891f, 0.19141f, 0.19391f, 0.19641f, 0.19893f, 0.20145f, 0.20398f, 0.20651f, 0.20905f, 0.21160f, 0.21416f, 0.21672f, + 0.21929f, 0.22185f, 0.22440f, 0.22696f, 0.22950f, 0.23204f, 0.23458f, 0.23711f, 0.23963f, 0.24215f, 0.24466f, 0.24717f, 0.24967f, 0.25216f, + 0.25465f, 0.25713f, 0.25961f, 0.26208f, 0.26454f, 0.26700f, 0.26945f, 0.27189f, 0.27433f, 0.27676f, 0.27918f, 0.28160f, 0.28401f, 0.28641f, + 0.28881f, 0.29120f, 0.29358f, 0.29596f, 0.29833f, 0.30069f, 0.30305f, 0.30540f, 0.30774f, 0.31008f, 0.31241f, 0.31473f, 0.31704f, 0.31935f, + 0.32165f, 0.32395f, 0.32623f, 0.32851f, 0.33079f, 0.33305f, 0.33531f, 0.33756f, 0.33981f, 0.34205f, 0.34428f, 0.34650f, 0.34872f, 0.35093f, + 0.35313f, 0.35532f, 0.35751f, 0.35969f, 0.36187f, 0.36404f, 0.36620f, 0.36835f, 0.37050f, 0.37264f, 0.37477f, 0.37689f, 0.37901f, 0.38112f, + 0.38323f, 0.38533f, 0.38742f, 0.38950f, 0.39158f, 0.39365f, 0.39571f, 0.39777f, 0.39982f, 0.40186f, 0.40389f, 0.40592f, 0.40794f, 0.40996f, + 0.41197f, 0.41397f, 0.41596f, 0.41795f, 0.41993f, 0.42191f, 0.42388f, 0.42584f, 0.42779f, 0.42974f, 0.43168f, 0.43362f, 0.43554f, 0.43747f, + 0.43938f, 0.44129f, 0.44319f, 0.44509f, 0.44698f, 0.44886f, 0.45073f, 0.45260f, 0.45447f, 0.45632f, 0.45817f, 0.46002f, 0.46186f, 0.46369f, + 0.46551f, 0.46733f, 0.46914f, 0.47095f, 0.47275f, 0.47454f, 0.47633f, 0.47811f, 0.47989f, 0.48166f, 0.48342f, 0.48518f, 0.48693f, 0.48867f, + 0.49041f, 0.49214f, 0.49387f, 0.49559f, 0.49730f, 0.49901f, 0.50072f, 0.50241f, 0.50410f, 0.50579f, 0.50747f, 0.50914f, 0.51081f, 0.51247f, + 0.51413f, 0.51578f, 0.51742f, 0.51906f, 0.52069f, 0.52232f, 0.52394f, 0.52556f, 0.52717f, 0.52878f, 0.53038f, 0.53197f, 0.53356f, 0.53514f, + 0.53672f, 0.53829f, 0.53986f, 0.54142f, 0.54297f, 0.54452f, 0.54607f, 0.54761f, 0.54914f, 0.55067f, 0.55220f, 0.55371f, 0.55523f, 0.55673f, + 0.55824f, 0.55973f, 0.56123f, 0.56271f, 0.56420f, 0.56567f, 0.56715f, 0.56861f, 0.57007f, 0.57153f, 0.57298f, 0.57443f, 0.57587f, 0.57731f, + 0.57874f, 0.58017f, 0.58159f, 0.58301f, 0.58443f, 0.58583f, 0.58724f, 0.58864f, 0.59003f, 0.59142f, 0.59281f, 0.59419f, 0.59556f, 0.59694f, + 0.59830f, 0.59966f, 0.60102f, 0.60238f, 0.60373f, 0.60507f, 0.60641f, 0.60775f, 0.60908f, 0.61040f, 0.61173f, 0.61305f, 0.61436f, 0.61567f, + 0.61698f, 0.61828f, 0.61957f, 0.62087f, 0.62216f, 0.62344f, 0.62472f, 0.62600f, 0.62727f, 0.62854f, 0.62980f, 0.63106f, 0.63232f, 0.63357f, + 0.63482f, 0.63606f, 0.63730f, 0.63854f, 0.63977f, 0.64100f, 0.64222f, 0.64344f, 0.64466f, 0.64587f, 0.64708f, 0.64829f, 0.64949f, 0.65069f, + 0.65188f, 0.65307f, 0.65426f, 0.65544f, 0.65662f, 0.65779f, 0.65897f, 0.66013f, 0.66130f, 0.66246f, 0.66362f, 0.66477f, 0.66592f, 0.66707f, + 0.66821f, 0.66935f, 0.67048f, 0.67162f, 0.67275f, 0.67387f, 0.67499f, 0.67611f, 0.67723f, 0.67834f, 0.67945f, 0.68055f, 0.68165f, 0.68275f, + 0.68385f, 0.68494f, 0.68603f, 0.68711f, 0.68819f, 0.68927f, 0.69035f, 0.69142f, 0.69249f, 0.69355f, 0.69461f, 0.69567f, 0.69673f, 0.69778f, + 0.69883f, 0.69988f, 0.70092f, 0.70196f, 0.70300f, 0.70403f, 0.70506f, 0.70609f, 0.70711f, 0.70813f, 0.70915f, 0.71017f, 0.71118f, 0.71219f, + 0.71319f, 0.71420f, 0.71520f, 0.71620f, 0.71719f, 0.71818f, 0.71917f, 0.72016f, 0.72114f, 0.72212f, 0.72309f, 0.72407f, 0.72504f, 0.72601f, + 0.72697f, 0.72794f, 0.72890f, 0.72985f, 0.73081f, 0.73176f, 0.73271f, 0.73365f, 0.73460f, 0.73554f, 0.73647f, 0.73741f, 0.73834f, 0.73927f, + 0.74020f, 0.74112f, 0.74204f, 0.74296f, 0.74388f, 0.74479f, 0.74570f, 0.74661f, 0.74751f, 0.74842f, 0.74932f, 0.75021f, 0.75111f, 0.75200f, + 0.75289f, 0.75378f, 0.75466f, 0.75555f, 0.75643f, 0.75730f, 0.75818f, 0.75905f, 0.75992f, 0.76079f, 0.76165f, 0.76251f, 0.76337f, 0.76423f, + 0.76508f, 0.76594f, 0.76679f, 0.76763f, 0.76848f, 0.76932f, 0.77016f, 0.77100f, 0.77183f, 0.77267f, 0.77350f, 0.77432f, 0.77515f, 0.77597f, + 0.77680f, 0.77761f, 0.77843f, 0.77924f, 0.78006f, 0.78087f, 0.78167f, 0.78248f, 0.78328f, 0.78408f, 0.78488f, 0.78568f, 0.78647f, 0.78726f, + 0.78805f, 0.78884f, 0.78962f, 0.79040f, 0.79118f, 0.79196f, 0.79274f, 0.79351f, 0.79428f, 0.79505f, 0.79582f, 0.79658f, 0.79735f, 0.79811f, + 0.79887f, 0.79962f, 0.80038f, 0.80113f, 0.80188f, 0.80263f, 0.80337f, 0.80412f, 0.80486f, 0.80560f, 0.80634f, 0.80707f, 0.80780f, 0.80854f, + 0.80926f, 0.80999f, 0.81072f, 0.81144f, 0.81216f, 0.81288f, 0.81360f, 0.81431f, 0.81503f, 0.81574f, 0.81645f, 0.81715f, 0.81786f, 0.81856f, + 0.81926f, 0.81996f, 0.82066f, 0.82135f, 0.82205f, 0.82274f, 0.82343f, 0.82412f, 0.82480f, 0.82549f, 0.82617f, 0.82685f, 0.82753f, 0.82820f, + 0.82888f, 0.82955f, 0.83022f, 0.83089f, 0.83155f, 0.83222f, 0.83288f, 0.83354f, 0.83420f, 0.83486f, 0.83552f, 0.83617f, 0.83682f, 0.83747f, + 0.83812f, 0.83877f, 0.83941f, 0.84005f, 0.84069f, 0.84133f, 0.84197f, 0.84261f, 0.84324f, 0.84387f, 0.84450f, 0.84513f, 0.84576f, 0.84639f, + 0.84701f, 0.84763f, 0.84825f, 0.84887f, 0.84949f, 0.85010f, 0.85071f, 0.85132f, 0.85193f, 0.85254f, 0.85315f, 0.85375f, 0.85436f, 0.85496f, + 0.85556f, 0.85615f, 0.85675f, 0.85735f, 0.85794f, 0.85853f, 0.85912f, 0.85971f, 0.86029f, 0.86088f, 0.86146f, 0.86204f, 0.86262f, 0.86320f, + 0.86378f, 0.86435f, 0.86493f, 0.86550f, 0.86607f, 0.86664f, 0.86720f, 0.86777f, 0.86833f, 0.86889f, 0.86945f, 0.87001f, 0.87057f, 0.87113f, + 0.87168f, 0.87223f, 0.87278f, 0.87333f, 0.87388f, 0.87443f, 0.87497f, 0.87552f, 0.87606f, 0.87660f, 0.87714f, 0.87768f, 0.87821f, 0.87875f, + 0.87928f, 0.87981f, 0.88034f, 0.88087f, 0.88140f, 0.88192f, 0.88244f, 0.88297f, 0.88349f, 0.88401f, 0.88453f, 0.88504f, 0.88556f, 0.88607f, + 0.88658f, 0.88709f, 0.88760f, 0.88811f, 0.88862f, 0.88912f, 0.88963f, 0.89013f, 0.89063f, 0.89113f, 0.89163f, 0.89212f, 0.89262f, 0.89311f, + 0.89360f, 0.89409f, 0.89458f, 0.89507f, 0.89556f, 0.89604f, 0.89653f, 0.89701f, 0.89749f, 0.89797f, 0.89845f, 0.89892f, 0.89940f, 0.89987f, + 0.90035f, 0.90082f, 0.90129f, 0.90176f, 0.90222f, 0.90269f, 0.90316f, 0.90362f, 0.90408f, 0.90454f, 0.90500f, 0.90546f, 0.90592f, 0.90637f, + 0.90683f, 0.90728f, 0.90773f, 0.90818f, 0.90863f, 0.90908f, 0.90952f, 0.90997f, 0.91041f, 0.91085f, 0.91130f, 0.91173f, 0.91217f, 0.91261f, + 0.91305f, 0.91348f, 0.91392f, 0.91435f, 0.91478f, 0.91521f, 0.91564f, 0.91606f, 0.91649f, 0.91691f, 0.91734f, 0.91776f, 0.91818f, 0.91860f, + 0.91902f, 0.91944f, 0.91985f, 0.92027f, 0.92068f, 0.92109f, 0.92150f, 0.92191f, 0.92232f, 0.92273f, 0.92314f, 0.92354f, 0.92395f, 0.92435f, + 0.92475f, 0.92515f, 0.92555f, 0.92595f, 0.92634f, 0.92674f, 0.92713f, 0.92753f, 0.92792f, 0.92831f, 0.92870f, 0.92909f, 0.92947f, 0.92986f, + 0.93025f, 0.93063f, 0.93101f, 0.93139f, 0.93177f, 0.93215f, 0.93253f, 0.93291f, 0.93328f, 0.93366f, 0.93403f, 0.93440f, 0.93478f, 0.93515f, + 0.93551f, 0.93588f, 0.93625f, 0.93661f, 0.93698f, 0.93734f, 0.93770f, 0.93807f, 0.93843f, 0.93878f, 0.93914f, 0.93950f, 0.93986f, 0.94021f, + 0.94056f, 0.94092f, 0.94127f, 0.94162f, 0.94197f, 0.94231f, 0.94266f, 0.94301f, 0.94335f, 0.94369f, 0.94404f, 0.94438f, 0.94472f, 0.94506f, + 0.94540f, 0.94573f, 0.94607f, 0.94641f, 0.94674f, 0.94707f, 0.94740f, 0.94774f, 0.94807f, 0.94839f, 0.94872f, 0.94905f, 0.94937f, 0.94970f, + 0.95002f, 0.95035f, 0.95067f, 0.95099f, 0.95131f, 0.95163f, 0.95194f, 0.95226f, 0.95257f, 0.95289f, 0.95320f, 0.95351f, 0.95383f, 0.95414f, + 0.95445f, 0.95475f, 0.95506f, 0.95537f, 0.95567f, 0.95598f, 0.95628f, 0.95658f, 0.95688f, 0.95718f, 0.95748f, 0.95778f, 0.95808f, 0.95838f, + 0.95867f, 0.95897f, 0.95926f, 0.95955f, 0.95984f, 0.96013f, 0.96042f, 0.96071f, 0.96100f, 0.96129f, 0.96157f, 0.96186f, 0.96214f, 0.96242f, + 0.96271f, 0.96299f, 0.96327f, 0.96355f, 0.96382f, 0.96410f, 0.96438f, 0.96465f, 0.96493f, 0.96520f, 0.96547f, 0.96574f, 0.96602f, 0.96629f, + 0.96655f, 0.96682f, 0.96709f, 0.96735f, 0.96762f, 0.96788f, 0.96815f, 0.96841f, 0.96867f, 0.96893f, 0.96919f, 0.96945f, 0.96971f, 0.96996f, + 0.97022f, 0.97047f, 0.97073f, 0.97098f, 0.97123f, 0.97149f, 0.97174f, 0.97199f, 0.97223f, 0.97248f, 0.97273f, 0.97297f, 0.97322f, 0.97346f, + 0.97371f, 0.97395f, 0.97419f, 0.97443f, 0.97467f, 0.97491f, 0.97515f, 0.97539f, 0.97562f, 0.97586f, 0.97609f, 0.97633f, 0.97656f, 0.97679f, + 0.97702f, 0.97725f, 0.97748f, 0.97771f, 0.97794f, 0.97817f, 0.97839f, 0.97862f, 0.97884f, 0.97907f, 0.97929f, 0.97951f, 0.97973f, 0.97995f, + 0.98017f, 0.98039f, 0.98061f, 0.98082f, 0.98104f, 0.98125f, 0.98147f, 0.98168f, 0.98189f, 0.98211f, 0.98232f, 0.98253f, 0.98274f, 0.98295f, + 0.98315f, 0.98336f, 0.98357f, 0.98377f, 0.98398f, 0.98418f, 0.98438f, 0.98458f, 0.98478f, 0.98498f, 0.98518f, 0.98538f, 0.98558f, 0.98578f, + 0.98597f, 0.98617f, 0.98636f, 0.98656f, 0.98675f, 0.98694f, 0.98714f, 0.98733f, 0.98752f, 0.98771f, 0.98789f, 0.98808f, 0.98827f, 0.98845f, + 0.98864f, 0.98882f, 0.98901f, 0.98919f, 0.98937f, 0.98955f, 0.98973f, 0.98991f, 0.99009f, 0.99027f, 0.99045f, 0.99063f, 0.99080f, 0.99098f, + 0.99115f, 0.99133f, 0.99150f, 0.99167f, 0.99184f, 0.99201f, 0.99218f, 0.99235f, 0.99252f, 0.99269f, 0.99285f, 0.99302f, 0.99319f, 0.99335f, + 0.99351f, 0.99368f, 0.99384f, 0.99400f, 0.99416f, 0.99432f, 0.99448f, 0.99464f, 0.99480f, 0.99495f, 0.99511f, 0.99527f, 0.99542f, 0.99558f, + 0.99573f, 0.99588f, 0.99603f, 0.99619f, 0.99634f, 0.99649f, 0.99664f, 0.99678f, 0.99693f, 0.99708f, 0.99722f, 0.99737f, 0.99751f, 0.99766f, + 0.99780f, 0.99794f, 0.99809f, 0.99823f, 0.99837f, 0.99851f, 0.99865f, 0.99879f, 0.99892f, 0.99906f, 0.99920f, 0.99933f, 0.99947f, 0.99960f, + 0.99974f, 0.99987f, 1.00000f}; FILE* const file = fopen(filename.c_str(), "rb"); @@ -981,7 +935,6 @@ void DCPProfile::Load(const std::string& filename) for (int i = 0; i < numOfTags; i++) { - unsigned short tag = get2(file, order); TagType type = (TagType)get2(file, order); unsigned int datasize = get4(file, order); @@ -1023,8 +976,7 @@ void DCPProfile::Load(const std::string& filename) info.temperature_1 = calibrationIlluminantToTemperature(LightSource(info.light_source_1)); info.temperature_2 = calibrationIlluminantToTemperature(LightSource(info.light_source_2)); - const bool has_second_hue_sat = - findTag(TagKey::PROFILE_HUE_SAT_MAP_DATA_2, v_tag); // Some profiles have two matrices, but just one huesat + const bool has_second_hue_sat = findTag(TagKey::PROFILE_HUE_SAT_MAP_DATA_2, v_tag); // Some profiles have two matrices, but just one huesat // Fetch Forward Matrices, if any tag = findTag(TagKey::FORWARD_MATRIX_1, v_tag); @@ -1150,8 +1102,7 @@ void DCPProfile::Load(const std::string& filename) deltas_1[i].val_scale = tag->toDouble((i * 3 + 2) * tiff_float_size); } - delta_info.pc.h_scale = - delta_info.hue_divisions < 2 ? 0.0f : static_cast(delta_info.hue_divisions) / 6.0f; + delta_info.pc.h_scale = delta_info.hue_divisions < 2 ? 0.0f : static_cast(delta_info.hue_divisions) / 6.0f; delta_info.pc.s_scale = delta_info.sat_divisions - 1; delta_info.pc.v_scale = delta_info.val_divisions - 1; delta_info.pc.max_hue_index0 = delta_info.hue_divisions - 1; @@ -1167,7 +1118,7 @@ void DCPProfile::Load(const std::string& filename) { tag = findTag(TagKey::COLOR_MATRIX_2, v_tag); - if (tag) // Second color matrix is not mandatory + if (tag) // Second color matrix is not mandatory { info.has_color_matrix_2 = true; @@ -1260,8 +1211,7 @@ void DCPProfile::Load(const std::string& filename) // An Adobe profile without tone curve is expected to have the Adobe Default Curve std::vector AS_curve_points; - constexpr size_t tc_len = - sizeof(adobe_camera_raw_default_curve) / sizeof(adobe_camera_raw_default_curve[0]); + constexpr size_t tc_len = sizeof(adobe_camera_raw_default_curve) / sizeof(adobe_camera_raw_default_curve[0]); for (size_t i = 0; i < tc_len; ++i) { @@ -1288,8 +1238,8 @@ void DCPProfile::Load(const std::string& filename) for (int i = 0; i < 65536; i++) { double x = i / 65535.0; - gammatab_srgb_data.push_back((x <= 0.003040) ? (x * 12.92310) : (1.055 * exp(log(x) / 2.4) - 0.055)); // from RT - igammatab_srgb_data.push_back((x <= 0.039286) ? (x / 12.92310) : (exp(log((x + 0.055) / 1.055) * 2.4))); // from RT + gammatab_srgb_data.push_back((x <= 0.003040) ? (x * 12.92310) : (1.055 * exp(log(x) / 2.4) - 0.055)); // from RT + igammatab_srgb_data.push_back((x <= 0.039286) ? (x / 12.92310) : (exp(log((x + 0.055) / 1.055) * 2.4))); // from RT } gammatab_srgb.Set(gammatab_srgb_data); igammatab_srgb.Set(igammatab_srgb_data); @@ -1298,9 +1248,9 @@ void DCPProfile::Load(const std::string& filename) void DCPProfile::Load(const std::map& metadata) { bool dcpMetadataOK = aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:Temp1") && - aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:Temp2") && - aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ForwardMatrixNumber") && - aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ColorMatrixNumber"); + aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:Temp2") && + aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ForwardMatrixNumber") && + aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ColorMatrixNumber"); int colorMatrixNb; int fwdMatrixNb; @@ -1312,8 +1262,8 @@ void DCPProfile::Load(const std::map& metadata) ALICEVISION_LOG_INFO("Matrix Number : " << colorMatrixNb << " ; " << fwdMatrixNb); - dcpMetadataOK = !((colorMatrixNb == 0) || - ((colorMatrixNb > 0) && !aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ColorMat1")) || + dcpMetadataOK = + !((colorMatrixNb == 0) || ((colorMatrixNb > 0) && !aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ColorMat1")) || ((colorMatrixNb > 1) && !aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ColorMat2")) || ((fwdMatrixNb > 0) && !aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ForwardMat1")) || ((fwdMatrixNb > 1) && !aliceVision::map_has_non_empty_value(metadata, "AliceVision:DCP:ForwardMat2"))); @@ -1355,8 +1305,8 @@ void DCPProfile::Load(const std::map& metadata) void DCPProfile::apply(OIIO::ImageBuf& image, const DCPProfileApplyParams& params) { // Compute matrices to and from selected working space - ws_sRGB = { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; - sRGB_ws = { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; + ws_sRGB = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; + sRGB_ws = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; if (params.working_space.compare("sRGB")) { @@ -1420,9 +1370,8 @@ void DCPProfile::apply(OIIO::ImageBuf& image, const DCPProfileApplyParams& param void DCPProfile::apply(float* rgb, const DCPProfileApplyParams& params) const { - const float exp_scale = (params.apply_baseline_exposure_offset && info.has_baseline_exposure_offset) - ? std::pow(2.0, baseline_exposure_offset) - : 1.0f; + const float exp_scale = + (params.apply_baseline_exposure_offset && info.has_baseline_exposure_offset) ? std::pow(2.0, baseline_exposure_offset) : 1.0f; if (!params.use_tone_curve && !params.apply_look_table) { @@ -1438,13 +1387,12 @@ void DCPProfile::apply(float* rgb, const DCPProfileApplyParams& params) const } else { - for (int c = 0; c < 3; ++c) { rgb[c] *= exp_scale; } - float ws_rgb[3] = { 0.0, 0.0, 0.0 }; + float ws_rgb[3] = {0.0, 0.0, 0.0}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; ++j) @@ -1476,9 +1424,7 @@ void DCPProfile::apply(float* rgb, const DCPProfileApplyParams& params) const hsv2rgbdcp(h, s, v, clipped_ws_rgb[0], clipped_ws_rgb[1], clipped_ws_rgb[2]); - if (!(ws_rgb[0] < 0.0 || ws_rgb[0] > 65535.0) || - !(ws_rgb[1] < 0.0 || ws_rgb[1] > 65535.0) || - !(ws_rgb[2] < 0.0 || ws_rgb[2] > 65535.0)) + if (!(ws_rgb[0] < 0.0 || ws_rgb[0] > 65535.0) || !(ws_rgb[1] < 0.0 || ws_rgb[1] > 65535.0) || !(ws_rgb[2] < 0.0 || ws_rgb[2] > 65535.0)) { ws_rgb[0] = clipped_ws_rgb[0]; ws_rgb[1] = clipped_ws_rgb[1]; @@ -1502,8 +1448,7 @@ void DCPProfile::apply(float* rgb, const DCPProfileApplyParams& params) const } } -inline void DCPProfile::hsdApply(const HsdTableInfo& table_info, const std::vector& table_base, float& h, - float& s, float& v) const +inline void DCPProfile::hsdApply(const HsdTableInfo& table_info, const std::vector& table_base, float& h, float& s, float& v) const { // Apply the HueSatMap. Ported from Adobes reference implementation. float hue_shift; @@ -1537,12 +1482,9 @@ inline void DCPProfile::hsdApply(const HsdTableInfo& table_info, const std::vect std::vector::size_type e00_index = h_index0 * table_info.pc.hue_step + s_index0; std::vector::size_type e01_index = e00_index + (h_index1 - h_index0) * table_info.pc.hue_step; - const float hue_shift0 = - h_fract0 * table_base[e00_index].hue_shift + h_fract1 * table_base[e01_index].hue_shift; - const float sat_scale0 = - h_fract0 * table_base[e00_index].sat_scale + h_fract1 * table_base[e01_index].sat_scale; - const float val_scale0 = - h_fract0 * table_base[e00_index].val_scale + h_fract1 * table_base[e01_index].val_scale; + const float hue_shift0 = h_fract0 * table_base[e00_index].hue_shift + h_fract1 * table_base[e01_index].hue_shift; + const float sat_scale0 = h_fract0 * table_base[e00_index].sat_scale + h_fract1 * table_base[e01_index].sat_scale; + const float val_scale0 = h_fract0 * table_base[e00_index].val_scale + h_fract1 * table_base[e01_index].val_scale; ++e00_index; ++e01_index; @@ -1587,46 +1529,39 @@ inline void DCPProfile::hsdApply(const HsdTableInfo& table_info, const std::vect const float s_fract0 = 1.0f - s_fract1; const float v_fract0 = 1.0f - v_fract1; - std::vector::size_type e00_index = - v_index0 * table_info.pc.val_step + h_index0 * table_info.pc.hue_step + s_index0; + std::vector::size_type e00_index = v_index0 * table_info.pc.val_step + h_index0 * table_info.pc.hue_step + s_index0; std::vector::size_type e01_index = e00_index + (h_index1 - h_index0) * table_info.pc.hue_step; std::vector::size_type e10_index = e00_index + table_info.pc.val_step; std::vector::size_type e11_index = e01_index + table_info.pc.val_step; - const float hueShift0 = - v_fract0 * (h_fract0 * table_base[e00_index].hue_shift + h_fract1 * table_base[e01_index].hue_shift) + - v_fract1 * (h_fract0 * table_base[e10_index].hue_shift + h_fract1 * table_base[e11_index].hue_shift); - const float satScale0 = - v_fract0 * (h_fract0 * table_base[e00_index].sat_scale + h_fract1 * table_base[e01_index].sat_scale) + - v_fract1 * (h_fract0 * table_base[e10_index].sat_scale + h_fract1 * table_base[e11_index].sat_scale); - const float valScale0 = - v_fract0 * (h_fract0 * table_base[e00_index].val_scale + h_fract1 * table_base[e01_index].val_scale) + - v_fract1 * (h_fract0 * table_base[e10_index].val_scale + h_fract1 * table_base[e11_index].val_scale); + const float hueShift0 = v_fract0 * (h_fract0 * table_base[e00_index].hue_shift + h_fract1 * table_base[e01_index].hue_shift) + + v_fract1 * (h_fract0 * table_base[e10_index].hue_shift + h_fract1 * table_base[e11_index].hue_shift); + const float satScale0 = v_fract0 * (h_fract0 * table_base[e00_index].sat_scale + h_fract1 * table_base[e01_index].sat_scale) + + v_fract1 * (h_fract0 * table_base[e10_index].sat_scale + h_fract1 * table_base[e11_index].sat_scale); + const float valScale0 = v_fract0 * (h_fract0 * table_base[e00_index].val_scale + h_fract1 * table_base[e01_index].val_scale) + + v_fract1 * (h_fract0 * table_base[e10_index].val_scale + h_fract1 * table_base[e11_index].val_scale); ++e00_index; ++e01_index; ++e10_index; ++e11_index; - const float hueShift1 = - v_fract0 * (h_fract0 * table_base[e00_index].hue_shift + h_fract1 * table_base[e01_index].hue_shift) + - v_fract1 * (h_fract0 * table_base[e10_index].hue_shift + h_fract1 * table_base[e11_index].hue_shift); - const float satScale1 = - v_fract0 * (h_fract0 * table_base[e00_index].sat_scale + h_fract1 * table_base[e01_index].sat_scale) + - v_fract1 * (h_fract0 * table_base[e10_index].sat_scale + h_fract1 * table_base[e11_index].sat_scale); - const float valScale1 = - v_fract0 * (h_fract0 * table_base[e00_index].val_scale + h_fract1 * table_base[e01_index].val_scale) + - v_fract1 * (h_fract0 * table_base[e10_index].val_scale + h_fract1 * table_base[e11_index].val_scale); + const float hueShift1 = v_fract0 * (h_fract0 * table_base[e00_index].hue_shift + h_fract1 * table_base[e01_index].hue_shift) + + v_fract1 * (h_fract0 * table_base[e10_index].hue_shift + h_fract1 * table_base[e11_index].hue_shift); + const float satScale1 = v_fract0 * (h_fract0 * table_base[e00_index].sat_scale + h_fract1 * table_base[e01_index].sat_scale) + + v_fract1 * (h_fract0 * table_base[e10_index].sat_scale + h_fract1 * table_base[e11_index].sat_scale); + const float valScale1 = v_fract0 * (h_fract0 * table_base[e00_index].val_scale + h_fract1 * table_base[e01_index].val_scale) + + v_fract1 * (h_fract0 * table_base[e10_index].val_scale + h_fract1 * table_base[e11_index].val_scale); hue_shift = s_fract0 * hueShift0 + s_fract1 * hueShift1; sat_scale = s_fract0 * satScale0 + s_fract1 * satScale1; val_scale = s_fract0 * valScale0 + s_fract1 * valScale1; } - hue_shift *= 6.0f / 360.0f; // Convert to internal hue range. + hue_shift *= 6.0f / 360.0f; // Convert to internal hue range. h += hue_shift; - s *= sat_scale; // No clipping here, we are RT float :-) + s *= sat_scale; // No clipping here, we are RT float :-) if (table_info.srgb_gamma) { @@ -1649,7 +1584,7 @@ DCPProfile::Matrix DCPProfile::getInterpolatedMatrix(const double cct, const std Matrix interpolatedMatrix; for (int i = 0; i < 3; ++i) { - for (int j=0; j < 3; ++j) + for (int j = 0; j < 3; ++j) { interpolatedMatrix[i][j] = Ma[i][j] + ((c - a) / (b - a)) * (Mb[i][j] - Ma[i][j]); } @@ -1691,7 +1626,8 @@ DCPProfile::Matrix DCPProfile::matInv(const Matrix& M) const { Matrix inv = M; - const float det = M[0][0]*M[1][1]*M[2][2] + M[0][1]*M[1][2]*M[2][0] + M[0][2]*M[1][0]*M[2][1] - M[2][0]*M[1][1]*M[0][2] - M[1][0]*M[0][1]*M[2][2] - M[0][0]*M[2][1]*M[1][2]; + const float det = M[0][0] * M[1][1] * M[2][2] + M[0][1] * M[1][2] * M[2][0] + M[0][2] * M[1][0] * M[2][1] - M[2][0] * M[1][1] * M[0][2] - + M[1][0] * M[0][1] * M[2][2] - M[0][0] * M[2][1] * M[1][2]; if (det != 0.f) { @@ -1752,7 +1688,6 @@ void DCPProfile::setChromaticityCoordinates(const double x, const double y, doub if (dt <= 0.f || i == 30) { - dt = -std::min(dt, 0.f); const double f = (i == 1) ? 0.f : dt / (lastDt + dt); @@ -1791,7 +1726,7 @@ void DCPProfile::setChromaticityCoordinates(const double x, const double y, doub * * Returns: * tuple. Chromaticity coordinates. -*/ + */ void DCPProfile::getChromaticityCoordinates(const double cct, const double tint, double& x, double& y) const { const double r = 1.0e6 / cct; @@ -1804,7 +1739,6 @@ void DCPProfile::getChromaticityCoordinates(const double cct, const double tint, if (r < wrRuvtNext[0] || i == 29) { - const double f = (wrRuvtNext[0] - r) / (wrRuvtNext[0] - wrRuvt[0]); double u = wrRuvt[1] * f + wrRuvtNext[1] * (1.0 - f); @@ -1843,7 +1777,6 @@ void DCPProfile::getChromaticityCoordinates(const double cct, const double tint, } } - void DCPProfile::getChromaticityCoordinatesFromXyz(const Triple& xyz, double& x, double& y) const { x = xyz[0] / (xyz[0] + xyz[1] + xyz[2]); @@ -1867,7 +1800,6 @@ DCPProfile::Triple DCPProfile::getXyzFromTemperature(const double cct, const dou return xyz; } - /** * @brief Returns the chromaticity coordinates from 'As Shot Neutral' matrix. * @@ -1892,7 +1824,7 @@ DCPProfile::Triple DCPProfile::getXyzFromTemperature(const double cct, const dou * numpy.identity(3), \ * numpy.matrix([0.305672, 1., 0.905393]).reshape((3, 1))) * (0.27389027140925454, 0.28376817722941361) - * + * * Args: * calibrationIlluminant1Cct(float) : Calibration Illuminant 1 correlated color temperature. * calibrationIlluminant2Cct(float) : Calibration Illuminant 2 correlated color temperature. @@ -1905,7 +1837,7 @@ DCPProfile::Triple DCPProfile::getXyzFromTemperature(const double cct, const dou * * Kwargs : * verbose(bool) : Verbose value - * + * * Returns : * tuple.Chromaticity coordinates. */ @@ -1961,25 +1893,25 @@ void DCPProfile::getChromaticityCoordinatesFromCameraNeutral(const Matrix& analo /** * @brief Returns the 'Chromatic Adaptation' matrix using given 'XYZ' source and target matrices. - * + * * 'http://brucelindbloom.com/index.html?Eqn_ChromAdapt.html' - * + * * Usage:: - * + * * >>> getChromaticAdaptationMatrix(numpy.matrix([1.09923822, 1.000, 0.35445412]).reshape((3, 1)), \ * numpy.matrix([0.96907232, 1.000, 1.121792157]).reshape((3, 1))) * matrix([[ 0.87145615, -0.13204674, 0.40394832], * [-0.09638805, 1.04909781, 0.1604033 ], * [ 0.0080207 , 0.02826367, 3.06023196]]) * - * + * * Args: * xyzSource (Matrix): XYZ source matrix ( 3 x 1 ). * xyzTarget (Matrix): XYZ target matrix ( 3 x 1 ). - * + * * Kwargs: * verbose (bool): Verbose value - * + * * Returns: * Matrix. Chromatic Adaptation matrix ( 3 x 3 ). */ @@ -2002,12 +1934,12 @@ DCPProfile::Matrix DCPProfile::getChromaticAdaptationMatrix(const Triple& xyzSou * @brief Returns the 'camera to XYZ ( D50 )' matrix. * 'colorMatrix1', 'colorMatrix2', 'forwardMatrix1' and 'forwardMatrix2' are considered as non existing if given as * identity matrices. - * + * * 'Adobe DNG SDK 1.3.0.0': dng_sdk_1_3/dng_sdk/documents/dng_spec_1_3_0_0.pdf: 'Mapping Camera Color Space to CIE * XYZ Space'. - * + * * Usage:: - * + * * >>> getCameraToXyzD50Matrix(0.24559589702841558, \ * 0.24240399461846152, \ * 2850, \ @@ -2034,7 +1966,7 @@ DCPProfile::Matrix DCPProfile::getChromaticAdaptationMatrix(const Triple& xyzSou * matrix([[ 3.48428482, -0.1041 , 0.14605003], * [ 1.69880359, 0.6621 , -0.08065945], * [ 0.1971721 , -0.1562 , 0.77240552]]) - * + * * Args: * x (float): X chromaticity coordinate * y (float): Y chromaticity coordinate @@ -2047,10 +1979,10 @@ DCPProfile::Matrix DCPProfile::getChromaticAdaptationMatrix(const Triple& xyzSou * analogBalance (Matrix): Analog Balance matrix ( 3 x 3 ). * forwardMatrix1 (Matrix): Forward Matrix 1 matrix ( 3 x 3 ). * forwardMatrix2 (Matrix): Forward Matrix 2 matrix ( 3 x 3 ). - * + * * Kwargs: * verbose (bool): Verbose value - * + * * Returns: * Matrix. Camera to XYZ ( D50 ) matrix ( 3 x 3 ). */ @@ -2058,10 +1990,7 @@ DCPProfile::Matrix DCPProfile::getCameraToXyzD50Matrix(const double x, const dou { double cct, tint; setChromaticityCoordinates(x, y, cct, tint); - const Triple xyz = { - x * 1.f / y, - 1.f, - (1.f - x - y) * 1.f / y}; + const Triple xyz = {x * 1.f / y, 1.f, (1.f - x - y) * 1.f / y}; Matrix interpolatedColorMatrix; if (info.has_color_matrix_1 && info.has_color_matrix_2) @@ -2075,10 +2004,7 @@ DCPProfile::Matrix DCPProfile::getCameraToXyzD50Matrix(const double x, const dou const Matrix interpolatedCalibMatrix = getInterpolatedMatrix(cct, "calib"); const Triple rgb = matMult(interpolatedColorMatrix, xyz); - const Triple asShotNeutral = { - rgb[0] / rgb[1], - rgb[1] / rgb[1], - rgb[2] / rgb[1]}; + const Triple asShotNeutral = {rgb[0] / rgb[1], rgb[1] / rgb[1], rgb[2] / rgb[1]}; const Triple referenceNeutral = matMult(matInv(matMult(analogBalance, interpolatedCalibMatrix)), asShotNeutral); @@ -2096,8 +2022,8 @@ DCPProfile::Matrix DCPProfile::getCameraToXyzD50Matrix(const double x, const dou const Matrix xyzToCamera = matMult(analogBalance, matMult(interpolatedCalibMatrix, interpolatedColorMatrix)); const Matrix cameraToXyz = matInv(xyzToCamera); - const double D50_cct = 5000.706605070579; // - const double D50_tint = 9.562965495510433; // Using x, y = 0.3457, 0.3585 + const double D50_cct = 5000.706605070579; // + const double D50_tint = 9.562965495510433; // Using x, y = 0.3457, 0.3585 const Matrix cat = getChromaticAdaptationMatrix(getXyzFromChromaticityCoordinates(x, y), getXyzFromTemperature(D50_cct, D50_tint)); cameraToXyzD50 = matMult(cat, cameraToXyz); } @@ -2119,10 +2045,7 @@ DCPProfile::Matrix DCPProfile::getCameraToSrgbLinearMatrix(const double x, const { double cct, tint; setChromaticityCoordinates(x, y, cct, tint); - const Triple xyz = { - x * 1.f / y, - 1.f, - (1.f - x - y) * 1.f / y }; + const Triple xyz = {x * 1.f / y, 1.f, (1.f - x - y) * 1.f / y}; Matrix interpolatedColorMatrix; if (info.has_color_matrix_1 && info.has_color_matrix_2) @@ -2143,8 +2066,8 @@ DCPProfile::Matrix DCPProfile::getCameraToSrgbLinearMatrix(const double x, const const Matrix xyzToCamera = interpolatedColorMatrix; const Matrix cameraToXyz = matInv(xyzToCamera); - const double D50_cct = 5000.706605070579; // - const double D50_tint = 9.562965495510433; // Using x, y = 0.3457, 0.3585 + const double D50_cct = 5000.706605070579; // + const double D50_tint = 9.562965495510433; // Using x, y = 0.3457, 0.3585 const Matrix cat = getChromaticAdaptationMatrix(getXyzFromChromaticityCoordinates(x, y), getXyzFromTemperature(D50_cct, D50_tint)); cameraToXyzD50 = matMult(cat, cameraToXyz); } @@ -2164,9 +2087,12 @@ DCPProfile::Matrix DCPProfile::getCameraToSrgbLinearMatrix(const double x, const return cameraToSrgbLinear; } -DCPProfile::Matrix DCPProfile::getCameraToACES2065Matrix(const Triple& asShotNeutral, double& cct, const bool sourceIsRaw, const bool useColorMatrixOnly) const +DCPProfile::Matrix DCPProfile::getCameraToACES2065Matrix(const Triple& asShotNeutral, + double& cct, + const bool sourceIsRaw, + const bool useColorMatrixOnly) const { - Triple cctNeutral = { 1.0, 1.0, 1.0 }; + Triple cctNeutral = {1.0, 1.0, 1.0}; double cctLocal, tintLocal; Matrix invNeutralFromCct = IdentityMatrix; @@ -2245,8 +2171,8 @@ DCPProfile::Matrix DCPProfile::getCameraToACES2065Matrix(const Triple& asShotNeu } const Matrix cameraToXyz = matMult(matInv(xyzToCamera), wbInv); - const double D50_cct = 5000.706605070579; // - const double D50_tint = 9.562965495510433; // Using x, y = 0.3457, 0.3585 + const double D50_cct = 5000.706605070579; // + const double D50_tint = 9.562965495510433; // Using x, y = 0.3457, 0.3585 const Matrix cat = getChromaticAdaptationMatrix(getXyzFromChromaticityCoordinates(x, y), getXyzFromTemperature(D50_cct, D50_tint)); cameraToXyzD50 = matMult(cat, cameraToXyz); @@ -2267,7 +2193,6 @@ DCPProfile::Matrix DCPProfile::getCameraToACES2065Matrix(const Triple& asShotNeu return cameraToACES2065; } - void DCPProfile::getMatrices(const std::string& type, std::vector& v_Mat) const { v_Mat.clear(); @@ -2301,7 +2226,7 @@ void DCPProfile::getMatricesAsStrings(const std::string& type, std::vector v_Mat; getMatrices(type, v_Mat); - for (const auto &mat : v_Mat) + for (const auto& mat : v_Mat) { std::string strMat = ""; for (int i = 0; i < 3; i++) @@ -2371,7 +2296,7 @@ void DCPProfile::setMatricesFromStrings(const std::string& type, std::vector& image, const Triple& neutral, double& cct, const bool sourceIsRaw, const bool useColorMatrixOnly) const +void DCPProfile::applyLinear(Image& image, + const Triple& neutral, + double& cct, + const bool sourceIsRaw, + const bool useColorMatrixOnly) const { const Matrix cameraToACES2065Matrix = getCameraToACES2065Matrix(neutral, cct, sourceIsRaw, useColorMatrixOnly); ALICEVISION_LOG_INFO("cameraToACES2065Matrix: " << cameraToACES2065Matrix); - #pragma omp parallel for +#pragma omp parallel for for (int i = 0; i < image.Height(); ++i) for (int j = 0; j < image.Width(); ++j) { @@ -2431,16 +2360,13 @@ void DCPProfile::applyLinear(Image& image, const Triple& neut void DCPProfile::getColorTemperatureAndTintFromNeutral(const Triple& neutral, double& cct, double& tint) const { - const Triple invNeutral = { 1.0/neutral[0], 1.0/neutral[1], 1.0/neutral[2]}; + const Triple invNeutral = {1.0 / neutral[0], 1.0 / neutral[1], 1.0 / neutral[2]}; double x, y; getChromaticityCoordinatesFromCameraNeutral(IdentityMatrix, invNeutral, x, y); setChromaticityCoordinates(x, y, cct, tint); } -DCPDatabase::DCPDatabase(const std::string& databaseDirPath) -{ - load(databaseDirPath, true); -} +DCPDatabase::DCPDatabase(const std::string& databaseDirPath) { load(databaseDirPath, true); } int DCPDatabase::load(const std::string& databaseDirPath, bool force) { @@ -2460,7 +2386,7 @@ int DCPDatabase::load(const std::string& databaseDirPath, bool force) bfs::path targetDir(databaseDirPath); bfs::directory_iterator it(targetDir), eod; - BOOST_FOREACH(bfs::path const& p, std::make_pair(it, eod)) + BOOST_FOREACH (bfs::path const& p, std::make_pair(it, eod)) { if (bfs::is_regular_file(p)) { @@ -2494,8 +2420,10 @@ bool DCPDatabase::retrieveDcpForCamera(const std::string& make, const std::strin { // Load DCPProfile from disk - const std::vector::iterator it = std::find_if(dcpFilenamesList.begin(), dcpFilenamesList.end(), [make, model](const std::string& s) - { return (s.find(make) != std::string::npos) && (s.find(model) != std::string::npos); }); + const std::vector::iterator it = + std::find_if(dcpFilenamesList.begin(), dcpFilenamesList.end(), [make, model](const std::string& s) { + return (s.find(make) != std::string::npos) && (s.find(model) != std::string::npos); + }); if (it != dcpFilenamesList.end()) { @@ -2529,9 +2457,5 @@ void DCPDatabase::add_or_replace(DCPProfile& dcpProf, const std::string& make, c } } -} // namespace image -} // namespace aliceVision - - - - +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/dcp.hpp b/src/aliceVision/image/dcp.hpp index 7b6b69c91d..2eae191b8e 100644 --- a/src/aliceVision/image/dcp.hpp +++ b/src/aliceVision/image/dcp.hpp @@ -19,38 +19,14 @@ namespace image { * Source : Bruce Lindbloom's website : http://www.brucelindbloom.com/ * @{ */ -constexpr double xyz_sRGB[3][3] = { - {0.4360747, 0.3850649, 0.1430804}, - {0.2225045, 0.7168786, 0.0606169}, - {0.0139322, 0.0971045, 0.7141733} -}; -constexpr double sRGB_xyz[3][3] = { - {3.1338561, -1.6168667, -0.4906146}, - { -0.9787684, 1.9161415, 0.0334540}, - {0.0719453, -0.2289914, 1.4052427} -}; +constexpr double xyz_sRGB[3][3] = {{0.4360747, 0.3850649, 0.1430804}, {0.2225045, 0.7168786, 0.0606169}, {0.0139322, 0.0971045, 0.7141733}}; +constexpr double sRGB_xyz[3][3] = {{3.1338561, -1.6168667, -0.4906146}, {-0.9787684, 1.9161415, 0.0334540}, {0.0719453, -0.2289914, 1.4052427}}; -constexpr double xyz_prophoto[3][3] = { - {0.7976749, 0.1351917, 0.0313534}, - {0.2880402, 0.7118741, 0.0000857}, - {0.0000000, 0.0000000, 0.8252100} -}; -constexpr double prophoto_xyz[3][3] = { - {1.3459433, -0.2556075, -0.0511118}, - { -0.5445989, 1.5081673, 0.0205351}, - {0.0000000, 0.0000000, 1.2118128} -}; +constexpr double xyz_prophoto[3][3] = {{0.7976749, 0.1351917, 0.0313534}, {0.2880402, 0.7118741, 0.0000857}, {0.0000000, 0.0000000, 0.8252100}}; +constexpr double prophoto_xyz[3][3] = {{1.3459433, -0.2556075, -0.0511118}, {-0.5445989, 1.5081673, 0.0205351}, {0.0000000, 0.0000000, 1.2118128}}; -constexpr double xyz_AdobeRGB[3][3] = { - {0.6097559, 0.2052401, 0.1492240}, - {0.3111242, 0.6256560, 0.0632197}, - {0.0194811, 0.0608902, 0.7448387} -}; -constexpr double AdobeRGB_xyz[3][3] = { - { 1.9624274, -0.6105343, -0.3413404}, - {-0.9787684, 1.9161415, 0.0334540}, - { 0.0286869, -0.1406752, 1.3487655} -}; +constexpr double xyz_AdobeRGB[3][3] = {{0.6097559, 0.2052401, 0.1492240}, {0.3111242, 0.6256560, 0.0632197}, {0.0194811, 0.0608902, 0.7448387}}; +constexpr double AdobeRGB_xyz[3][3] = {{1.9624274, -0.6105343, -0.3413404}, {-0.9787684, 1.9161415, 0.0334540}, {0.0286869, -0.1406752, 1.3487655}}; /// @} enum class LightSource @@ -63,11 +39,11 @@ enum class LightSource FINE_WEATHER = 9, CLOUDY_WEATHER = 10, SHADE = 11, - DAYLIGHT_FLUORESCENT = 12, // D 5700 - 7100K - DAYWHITE_FLUORESCENT = 13, // N 4600 - 5500K - COOL_WHITE_FLUORESCENT = 14, // W 3800 - 4500K - WHITE_FLUORESCENT = 15, // WW 3250 - 3800K - WARM_WHITE_FLUORESCENT = 16, // L 2600 - 3250K + DAYLIGHT_FLUORESCENT = 12, // D 5700 - 7100K + DAYWHITE_FLUORESCENT = 13, // N 4600 - 5500K + COOL_WHITE_FLUORESCENT = 14, // W 3800 - 4500K + WHITE_FLUORESCENT = 15, // WW 3250 - 3800K + WARM_WHITE_FLUORESCENT = 16, // L 2600 - 3250K STANDARD_LIGHT_A = 17, STANDARD_LIGHT_B = 18, STANDARD_LIGHT_C = 19, @@ -81,13 +57,12 @@ enum class LightSource double calibrationIlluminantToTemperature(LightSource light); - /** * @brief SplineToneCurve represents a tone curve that can be embedded within a DCP color profile */ class SplineToneCurve final { -public: + public: SplineToneCurve() = default; ~SplineToneCurve() = default; @@ -95,8 +70,7 @@ class SplineToneCurve final void Apply(float& ir, float& ig, float& ib) const; float operator[](const float idx) const { return getval(idx); } -private: - + private: void RGBTone(float& maxval, float& medval, float& minval) const; float getval(const float idx) const; @@ -115,9 +89,9 @@ struct DCPProfileApplyParams std::string working_space = "sRGB"; }; - /** - * @brief DCPProfileInfo contains information about matrices, table and curves contained in the profile - */ +/** + * @brief DCPProfileInfo contains information about matrices, table and curves contained in the profile + */ struct DCPProfileInfo { std::string filename = ""; @@ -141,13 +115,13 @@ struct DCPProfileInfo }; /** -* @brief DCPProfile contains a Dng Color Profile as specified by Adobe -* DNG specification can be found here: https://helpx.adobe.com/content/dam/help/en/photoshop/pdf/dng_spec_1_6_0_0.pdf -* Profiles with more than 2 illuminants are not supported -*/ + * @brief DCPProfile contains a Dng Color Profile as specified by Adobe + * DNG specification can be found here: https://helpx.adobe.com/content/dam/help/en/photoshop/pdf/dng_spec_1_6_0_0.pdf + * Profiles with more than 2 illuminants are not supported + */ class DCPProfile final { -public: + public: DCPProfileInfo info; using Triple = std::array; @@ -156,22 +130,22 @@ class DCPProfile final DCPProfile(); /** - * @brief DCPProfile constructor - * @param[in] filename The dcp path on disk - */ + * @brief DCPProfile constructor + * @param[in] filename The dcp path on disk + */ explicit DCPProfile(const std::string& filename); ~DCPProfile(); /** - * @brief DCPProfile loader - * @param[in] filename The dcp path on disk - */ + * @brief DCPProfile loader + * @param[in] filename The dcp path on disk + */ void Load(const std::string& filename); /** - * @brief DCPProfile loader - * @param[in] map of metadata - */ + * @brief DCPProfile loader + * @param[in] map of metadata + */ void Load(const std::map& metadata); /** @@ -210,7 +184,11 @@ class DCPProfile final * param[in] sourceIsRaw indicates that the image buffer contains data in raw space (no neutralization <=> cam_mul not applied) * param[in] useColorMatrixOnly indicates to apply a DCP profile computed only from the color matrices */ - void applyLinear(OIIO::ImageBuf& image, const Triple& neutral, double& cct, const bool sourceIsRaw = false, const bool useColorMatrixOnly = true) const; + void applyLinear(OIIO::ImageBuf& image, + const Triple& neutral, + double& cct, + const bool sourceIsRaw = false, + const bool useColorMatrixOnly = true) const; /** * @brief applyLinear applies the linear part of a DCP profile on an aliceVision image @@ -220,7 +198,11 @@ class DCPProfile final * param[in] sourceIsRaw indicates that the image buffer contains data in raw space (no neutralization <=> cam_mul not applied) * param[in] useColorMatrixOnly indicates to apply a DCP profile computed only from the color matrices */ - void applyLinear(Image& image, const Triple& neutral, double& cct, const bool sourceIsRaw = false, const bool useColorMatrixOnly = false) const; + void applyLinear(Image& image, + const Triple& neutral, + double& cct, + const bool sourceIsRaw = false, + const bool useColorMatrixOnly = false) const; /** * @brief apply applies the non linear part of a DCP profile on an OIIO image buffer @@ -243,7 +225,7 @@ class DCPProfile final */ void getColorTemperatureAndTintFromNeutral(const Triple& neutral, double& cct, double& tint) const; -private: + private: struct HsbModify { float hue_shift; @@ -260,7 +242,8 @@ class DCPProfile final int val_step; unsigned int array_count; bool srgb_gamma; - struct { + struct + { float h_scale; float s_scale; float v_scale; @@ -288,43 +271,46 @@ class DCPProfile final Matrix getChromaticAdaptationMatrix(const Triple& xyzSource, const Triple& xyzTarget) const; Matrix getCameraToXyzD50Matrix(const double x, const double y) const; Matrix getCameraToSrgbLinearMatrix(const double x, const double y) const; - Matrix getCameraToACES2065Matrix(const Triple& asShotNeutral, double& cct, const bool sourceIsRaw = false, const bool useColorMatrixOnly = false) const; - - Matrix ws_sRGB; // working color space to sRGB - Matrix sRGB_ws; // sRGB to working color space - Matrix color_matrix_1; // Color matrix for illuminant 1 - Matrix color_matrix_2; // Color matrix for illuminant 2 - Matrix camera_calibration_1; // Calibration matrix for illuminant 1 - Matrix camera_calibration_2; // Calibration matrix for illuminant 2 + Matrix getCameraToACES2065Matrix(const Triple& asShotNeutral, + double& cct, + const bool sourceIsRaw = false, + const bool useColorMatrixOnly = false) const; + + Matrix ws_sRGB; // working color space to sRGB + Matrix sRGB_ws; // sRGB to working color space + Matrix color_matrix_1; // Color matrix for illuminant 1 + Matrix color_matrix_2; // Color matrix for illuminant 2 + Matrix camera_calibration_1; // Calibration matrix for illuminant 1 + Matrix camera_calibration_2; // Calibration matrix for illuminant 2 Matrix analogBalance; - Matrix forward_matrix_1; // white balanced raw to xyzD50 for illumimant 1 - Matrix forward_matrix_2; // white balanced raw to xyzD50 for illumimant 2 + Matrix forward_matrix_1; // white balanced raw to xyzD50 for illumimant 1 + Matrix forward_matrix_2; // white balanced raw to xyzD50 for illumimant 2 double baseline_exposure_offset; - std::vector deltas_1; // Basic Hue Sat update table for illumimant 1 - std::vector deltas_2; // Basic Hue Sat update table for illumimant 2 - std::vector look_table; // Hue Sat update table for look modification - HsdTableInfo delta_info; // Information for basic Hue Sat updates - HsdTableInfo look_info; // Information for look modification + std::vector deltas_1; // Basic Hue Sat update table for illumimant 1 + std::vector deltas_2; // Basic Hue Sat update table for illumimant 2 + std::vector look_table; // Hue Sat update table for look modification + HsdTableInfo delta_info; // Information for basic Hue Sat updates + HsdTableInfo look_info; // Information for look modification - SplineToneCurve AS_tone_curve; // Adobe standard tone curve + SplineToneCurve AS_tone_curve; // Adobe standard tone curve SplineToneCurve gammatab_srgb; SplineToneCurve igammatab_srgb; }; /** -* @brief DCPDatabase manages DCP profiles loading and caching -*/ + * @brief DCPDatabase manages DCP profiles loading and caching + */ class DCPDatabase final { -public: + public: DCPDatabase() = default; DCPDatabase(const std::string& databaseDirPath); ~DCPDatabase() = default; /** - * @brief load stores in a file list all the valid dcp filenames found in a folder (including subfolders). + * @brief load stores in a file list all the valid dcp filenames found in a folder (including subfolders). * None of them are loaded in cache. * param[in] database folder name. * param[in] if true loading is forced even if a database with the same folder name is already loaded. @@ -351,7 +337,7 @@ class DCPDatabase final /** * @brief add_or_replace adds or replaces an existing DCP profile in the cache. - * Update the DCP file list with dcpProf.info.filename. + * Update the DCP file list with dcpProf.info.filename. * param[in] DCP profile to be stored. * param[in] DSLR Maker. * param[in] DSLR Model. @@ -369,17 +355,13 @@ class DCPDatabase final */ bool retrieveDcpForCamera(const std::string& make, const std::string& model, DCPProfile& dcpProf); -private: - + private: std::string folderName; std::vector dcpFilenamesList; std::map dcpStore; - }; - - -} -} +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/diffusion.hpp b/src/aliceVision/image/diffusion.hpp index f917ca5adf..ee9b8ddd3c 100644 --- a/src/aliceVision/image/diffusion.hpp +++ b/src/aliceVision/image/diffusion.hpp @@ -13,7 +13,7 @@ #include #ifdef _MSC_VER -#pragma warning(once:4244) + #pragma warning(once : 4244) #endif namespace aliceVision { @@ -27,18 +27,19 @@ namespace image { ** @param out output coefficient ** NOTE : assume Lx and Ly have same size and image are in float format **/ -template < typename Image > -void ImagePeronaMalikG2DiffusionCoef( const Image & Lx , const Image & Ly , const typename Image::Tpixel k , Image & out ) +template +void ImagePeronaMalikG2DiffusionCoef(const Image& Lx, const Image& Ly, const typename Image::Tpixel k, Image& out) { - const int width = Lx.Width(); - const int height = Lx.Height(); + const int width = Lx.Width(); + const int height = Lx.Height(); - if( width != out.Width() || height != out.Height() ) { - out.resize( width , height ) ; - } + if (width != out.Width() || height != out.Height()) + { + out.resize(width, height); + } - typedef typename Image::Tpixel Real; - out.array() = ( static_cast(1.f) + (Lx.array().square()+Ly.array().square() )/(k*k) ).inverse(); + typedef typename Image::Tpixel Real; + out.array() = (static_cast(1.f) + (Lx.array().square() + Ly.array().square()) / (k * k)).inverse(); } /** @@ -50,40 +51,39 @@ void ImagePeronaMalikG2DiffusionCoef( const Image & Lx , const Image & Ly , cons ** @param row_start Row range beginning (range is [row_start ; row_end [ ) ** @param row_end Row range end (range is [row_start ; row_end [ ) **/ -template< typename Image > -void ImageFEDCentral( const Image & src , const Image & diff , const typename Image::Tpixel half_t , Image & out , - const int row_start , const int row_end ) +template +void ImageFEDCentral(const Image& src, const Image& diff, const typename Image::Tpixel half_t, Image& out, const int row_start, const int row_end) { - typedef typename Image::Tpixel Real ; - const int width = src.Width() ; - Real n_diff[4] ; - Real n_src[4] ; - // Compute FED step on general range - for( int i = row_start ; i < row_end ; ++i ) - { - for( int j = 1 ; j < width - 1 ; ++j ) + typedef typename Image::Tpixel Real; + const int width = src.Width(); + Real n_diff[4]; + Real n_src[4]; + // Compute FED step on general range + for (int i = row_start; i < row_end; ++i) { - // Retrieve neighbors : TODO check if we need a cache efficient version ? - n_diff[0] = diff( i , j + 1 ) ; - n_diff[1] = diff( i - 1 , j ) ; - n_diff[2] = diff( i , j - 1 ) ; - n_diff[3] = diff( i + 1 , j ) ; - n_src[0] = src( i , j + 1 ) ; - n_src[1] = src( i - 1 , j ) ; - n_src[2] = src( i , j - 1 ) ; - n_src[3] = src( i + 1 , j ) ; - - // Compute diffusion factor for given pixel - const Real cur_src = src( i , j ) ; - const Real cur_diff = diff( i , j ) ; - const Real a = ( cur_diff + n_diff[0] ) * ( n_src[0] - cur_src ) ; - const Real b = ( cur_diff + n_diff[1] ) * ( cur_src - n_src[1] ) ; - const Real c = ( cur_diff + n_diff[2] ) * ( cur_src - n_src[2] ) ; - const Real d = ( cur_diff + n_diff[3] ) * ( n_src[3] - cur_src ) ; - const Real value = half_t * ( a - c + d - b ) ; - out( i , j ) = value ; + for (int j = 1; j < width - 1; ++j) + { + // Retrieve neighbors : TODO check if we need a cache efficient version ? + n_diff[0] = diff(i, j + 1); + n_diff[1] = diff(i - 1, j); + n_diff[2] = diff(i, j - 1); + n_diff[3] = diff(i + 1, j); + n_src[0] = src(i, j + 1); + n_src[1] = src(i - 1, j); + n_src[2] = src(i, j - 1); + n_src[3] = src(i + 1, j); + + // Compute diffusion factor for given pixel + const Real cur_src = src(i, j); + const Real cur_diff = diff(i, j); + const Real a = (cur_diff + n_diff[0]) * (n_src[0] - cur_src); + const Real b = (cur_diff + n_diff[1]) * (cur_src - n_src[1]); + const Real c = (cur_diff + n_diff[2]) * (cur_src - n_src[2]); + const Real d = (cur_diff + n_diff[3]) * (n_src[3] - cur_src); + const Real value = half_t * (a - c + d - b); + out(i, j) = value; + } } - } } /** @@ -93,20 +93,20 @@ void ImageFEDCentral( const Image & src , const Image & diff , const typename Im ** @param half_t Half diffusion time ** @param out Output image **/ -template< typename Image > -void ImageFEDCentralCPPThread( const Image & src , const Image & diff , const typename Image::Tpixel half_t , Image & out ) +template +void ImageFEDCentralCPPThread(const Image& src, const Image& diff, const typename Image::Tpixel half_t, Image& out) { - const int nb_thread = omp_get_max_threads(); + const int nb_thread = omp_get_max_threads(); - // Compute ranges - std::vector< int > range; - SplitRange( 1 , (int) ( src.rows() - 1 ) , nb_thread , range ) ; + // Compute ranges + std::vector range; + SplitRange(1, (int)(src.rows() - 1), nb_thread, range); - #pragma omp parallel for schedule(dynamic) - for( int i = 1 ; i < static_cast(range.size()) ; ++i ) - { - ImageFEDCentral( src, diff, half_t, out, range[i-1] , range[i]) ; - } +#pragma omp parallel for schedule(dynamic) + for (int i = 1; i < static_cast(range.size()); ++i) + { + ImageFEDCentral(src, diff, half_t, out, range[i - 1], range[i]); + } } /** @@ -116,106 +116,106 @@ void ImageFEDCentralCPPThread( const Image & src , const Image & diff , const ty ** @param t diffusion time ** @param out output image **/ -template< typename Image > -void ImageFED( const Image & src , const Image & diff , const typename Image::Tpixel t , Image & out ) +template +void ImageFED(const Image& src, const Image& diff, const typename Image::Tpixel t, Image& out) { - typedef typename Image::Tpixel Real ; - const int width = src.Width() ; - const int height = src.Height() ; - const Real half_t = t * static_cast( 0.5 ) ; - if( out.Width() != width || out.Height() != height ) - { - out.resize( width , height ) ; - } - Real n_diff[4] ; - Real n_src[4] ; - - // Take care of the central part - ImageFEDCentralCPPThread( src , diff , half_t , out ) ; - - // Take care of the border - // - first/last row - // - first/last col - - // Compute FED step on first row - for( int j = 1 ; j < width - 1 ; ++j ) - { - n_diff[0] = diff( 0 , j + 1 ) ; - n_diff[2] = diff( 0 , j - 1 ) ; - n_diff[3] = diff( 1 , j ) ; - n_src[0] = src( 0 , j + 1 ) ; - n_src[2] = src( 0 , j - 1 ) ; - n_src[3] = src( 1 , j ) ; - - // Compute diffusion factor for given pixel - const Real cur_src = src( 0 , j ) ; - const Real cur_diff = diff( 0 , j ) ; - const Real a = ( cur_diff + n_diff[0] ) * ( n_src[0] - cur_src ) ; - const Real c = ( cur_diff + n_diff[2] ) * ( cur_src - n_src[2] ) ; - const Real d = ( cur_diff + n_diff[3] ) * ( n_src[3] - cur_src ) ; - const Real value = half_t * ( a - c + d ) ; - out( 0 , j ) = value ; - } - - // Compute FED step on last row - for( int j = 1 ; j < width - 1 ; ++j ) - { - n_diff[0] = diff( height - 1 , j + 1 ) ; - n_diff[1] = diff( height - 2 , j ) ; - n_diff[2] = diff( height - 1 , j - 1 ) ; - n_src[0] = src( height - 1 , j + 1 ) ; - n_src[1] = src( height - 2 , j ) ; - n_src[2] = src( height - 1 , j - 1 ) ; - - // Compute diffusion factor for given pixel - const Real cur_src = src( height - 1 , j ) ; - const Real cur_diff = diff( height - 1 , j ) ; - const Real a = ( cur_diff + n_diff[0] ) * ( n_src[0] - cur_src ) ; - const Real b = ( cur_diff + n_diff[1] ) * ( cur_src - n_src[1] ) ; - const Real c = ( cur_diff + n_diff[2] ) * ( cur_src - n_src[2] ) ; - const Real value = half_t * ( a - c - b ) ; - out( height - 1 , j ) = value ; - } - - // Compute FED step on first col - for( int i = 1 ; i < height - 1 ; ++i ) - { - n_diff[0] = diff( i , 1 ) ; - n_diff[1] = diff( i - 1 , 0 ) ; - n_diff[3] = diff( i + 1 , 0 ) ; - n_src[0] = src( i , 1 ) ; - n_src[1] = src( i - 1 , 0 ) ; - n_src[3] = src( i + 1 , 0 ) ; - - // Compute diffusion factor for given pixel - const Real cur_src = src( i , 0 ) ; - const Real cur_diff = diff( i , 0 ) ; - const Real a = ( cur_diff + n_diff[0] ) * ( n_src[0] - cur_src ) ; - const Real b = ( cur_diff + n_diff[1] ) * ( cur_src - n_src[1] ) ; - const Real d = ( cur_diff + n_diff[3] ) * ( n_src[3] - cur_src ) ; - const Real value = half_t * ( a + d - b ) ; - out( i , 0 ) = value ; - } - - // Compute FED step on last col - for( int i = 1 ; i < height - 1 ; ++i ) - { - n_diff[1] = diff( i - 1 , width - 1 ) ; - n_diff[2] = diff( i , width - 2 ) ; - n_diff[3] = diff( i + 1 , width - 1 ) ; - n_src[1] = src( i - 1 , width - 1 ) ; - n_src[2] = src( i , width - 2 ) ; - n_src[3] = src( i + 1 , width - 1 ) ; - - // Compute diffusion factor for given pixel - const Real cur_src = src( i , width - 1 ) ; - const Real cur_diff = diff( i , width - 1 ) ; - const Real b = ( cur_diff + n_diff[1] ) * ( cur_src - n_src[1] ) ; - const Real c = ( cur_diff + n_diff[2] ) * ( cur_src - n_src[2] ) ; - const Real d = ( cur_diff + n_diff[3] ) * ( n_src[3] - cur_src ) ; - const Real value = half_t * ( - c + d - b ) ; - out( i , width - 1 ) = value ; - } + typedef typename Image::Tpixel Real; + const int width = src.Width(); + const int height = src.Height(); + const Real half_t = t * static_cast(0.5); + if (out.Width() != width || out.Height() != height) + { + out.resize(width, height); + } + Real n_diff[4]; + Real n_src[4]; + + // Take care of the central part + ImageFEDCentralCPPThread(src, diff, half_t, out); + + // Take care of the border + // - first/last row + // - first/last col + + // Compute FED step on first row + for (int j = 1; j < width - 1; ++j) + { + n_diff[0] = diff(0, j + 1); + n_diff[2] = diff(0, j - 1); + n_diff[3] = diff(1, j); + n_src[0] = src(0, j + 1); + n_src[2] = src(0, j - 1); + n_src[3] = src(1, j); + + // Compute diffusion factor for given pixel + const Real cur_src = src(0, j); + const Real cur_diff = diff(0, j); + const Real a = (cur_diff + n_diff[0]) * (n_src[0] - cur_src); + const Real c = (cur_diff + n_diff[2]) * (cur_src - n_src[2]); + const Real d = (cur_diff + n_diff[3]) * (n_src[3] - cur_src); + const Real value = half_t * (a - c + d); + out(0, j) = value; + } + + // Compute FED step on last row + for (int j = 1; j < width - 1; ++j) + { + n_diff[0] = diff(height - 1, j + 1); + n_diff[1] = diff(height - 2, j); + n_diff[2] = diff(height - 1, j - 1); + n_src[0] = src(height - 1, j + 1); + n_src[1] = src(height - 2, j); + n_src[2] = src(height - 1, j - 1); + + // Compute diffusion factor for given pixel + const Real cur_src = src(height - 1, j); + const Real cur_diff = diff(height - 1, j); + const Real a = (cur_diff + n_diff[0]) * (n_src[0] - cur_src); + const Real b = (cur_diff + n_diff[1]) * (cur_src - n_src[1]); + const Real c = (cur_diff + n_diff[2]) * (cur_src - n_src[2]); + const Real value = half_t * (a - c - b); + out(height - 1, j) = value; + } + + // Compute FED step on first col + for (int i = 1; i < height - 1; ++i) + { + n_diff[0] = diff(i, 1); + n_diff[1] = diff(i - 1, 0); + n_diff[3] = diff(i + 1, 0); + n_src[0] = src(i, 1); + n_src[1] = src(i - 1, 0); + n_src[3] = src(i + 1, 0); + + // Compute diffusion factor for given pixel + const Real cur_src = src(i, 0); + const Real cur_diff = diff(i, 0); + const Real a = (cur_diff + n_diff[0]) * (n_src[0] - cur_src); + const Real b = (cur_diff + n_diff[1]) * (cur_src - n_src[1]); + const Real d = (cur_diff + n_diff[3]) * (n_src[3] - cur_src); + const Real value = half_t * (a + d - b); + out(i, 0) = value; + } + + // Compute FED step on last col + for (int i = 1; i < height - 1; ++i) + { + n_diff[1] = diff(i - 1, width - 1); + n_diff[2] = diff(i, width - 2); + n_diff[3] = diff(i + 1, width - 1); + n_src[1] = src(i - 1, width - 1); + n_src[2] = src(i, width - 2); + n_src[3] = src(i + 1, width - 1); + + // Compute diffusion factor for given pixel + const Real cur_src = src(i, width - 1); + const Real cur_diff = diff(i, width - 1); + const Real b = (cur_diff + n_diff[1]) * (cur_src - n_src[1]); + const Real c = (cur_diff + n_diff[2]) * (cur_src - n_src[2]); + const Real d = (cur_diff + n_diff[3]) * (n_src[3] - cur_src); + const Real value = half_t * (-c + d - b); + out(i, width - 1) = value; + } } /** @@ -224,61 +224,61 @@ void ImageFED( const Image & src , const Image & diff , const typename Image::Tp ** @param diff diffusion coefficient ** @param tau cycle timing vector **/ -template< typename Image > -void ImageFEDCycle( Image & self , const Image & diff , const std::vector< typename Image::Tpixel > & tau ) +template +void ImageFEDCycle(Image& self, const Image& diff, const std::vector& tau) { - Image tmp; - for( int i = 0 ; i < tau.size() ; ++i ) - { - ImageFED( self , diff , tau[i] , tmp ) ; - self.array() += tmp.array(); - } + Image tmp; + for (int i = 0; i < tau.size(); ++i) + { + ImageFED(self, diff, tau[i], tmp); + self.array() += tmp.array(); + } } // Compute if a number is prime of not -inline bool IsPrime( const int i ) +inline bool IsPrime(const int i) { - if( i == 1 ) - { - return false ; - } - if( i == 2 || i == 3 ) - { - return true ; - } - if ( i % 2 == 0 ) - { - return false; - } - - const size_t i_root = static_cast( sqrt( static_cast( i + 1 ) ) ) ; - - for ( size_t cur = 3; cur <= i_root; cur += 2 ) - { - if( i % cur == 0 ) + if (i == 1) + { + return false; + } + if (i == 2 || i == 3) + { + return true; + } + if (i % 2 == 0) + { + return false; + } + + const size_t i_root = static_cast(sqrt(static_cast(i + 1))); + + for (size_t cur = 3; cur <= i_root; cur += 2) { - return false; + if (i % cur == 0) + { + return false; + } } - } - return true; + return true; } -inline int NextPrimeGreaterOrEqualTo( const int i ) +inline int NextPrimeGreaterOrEqualTo(const int i) { - if( IsPrime( i ) ) - { - return i ; - } - else - { - int cur = i + 1 ; - - while( ! IsPrime( cur ) ) + if (IsPrime(i)) { - ++cur ; + return i; + } + else + { + int cur = i + 1; + + while (!IsPrime(cur)) + { + ++cur; + } + return cur; } - return cur ; - } } /** @@ -288,49 +288,49 @@ inline int NextPrimeGreaterOrEqualTo( const int i ) ** @param tau vector of FED cycle timings ** @return number of cycle timings **/ -template< typename Real > -int FEDCycleTimings( const Real T , const Real Tmax , std::vector< Real > & tau ) +template +int FEDCycleTimings(const Real T, const Real Tmax, std::vector& tau) { - // Number of timings - const int n = ceil( sqrt( ( 3.0 * static_cast( T ) ) / Tmax + 0.25 ) - 0.5 ) + 0.5 ; - - // Scaling factor - const Real scale = 3.0 * T / ( Tmax * static_cast( n * n + n ) ) ; - - // only constants - const Real cos_fact = 1.0 / ( static_cast( 4 * n ) + 2.0 ) ; - const Real glo_fact = scale * Tmax / 2.0 ; - - // Compute cycle timings - tau.resize( n ) ; - for( int j = 0 ; j < n ; ++j ) - { - const Real cos_j = cos( M_PI * ( static_cast( 2 * j + 1 ) ) * cos_fact ) ; - tau[ j ] = glo_fact / ( cos_j * cos_j ) ; - } - - // Compute Kappa reordering using kappa = n / 2 - const int kappa = n / 2 ; - - const int p = NextPrimeGreaterOrEqualTo( n + 1 ) ; - - // Store new positions - std::vector< Real > tmp( n ) ; - for( int i = 0 , k = 0 ; i < n ; ++i , ++k ) - { - // Search new index - int index = n ; - while( ( index = ( ( k + 1 ) * kappa ) % p - 1 ) >= n ) + // Number of timings + const int n = ceil(sqrt((3.0 * static_cast(T)) / Tmax + 0.25) - 0.5) + 0.5; + + // Scaling factor + const Real scale = 3.0 * T / (Tmax * static_cast(n * n + n)); + + // only constants + const Real cos_fact = 1.0 / (static_cast(4 * n) + 2.0); + const Real glo_fact = scale * Tmax / 2.0; + + // Compute cycle timings + tau.resize(n); + for (int j = 0; j < n; ++j) { - ++k ; + const Real cos_j = cos(M_PI * (static_cast(2 * j + 1)) * cos_fact); + tau[j] = glo_fact / (cos_j * cos_j); } - tmp[ i ] = tau[ index ] ; - } + // Compute Kappa reordering using kappa = n / 2 + const int kappa = n / 2; + + const int p = NextPrimeGreaterOrEqualTo(n + 1); + + // Store new positions + std::vector tmp(n); + for (int i = 0, k = 0; i < n; ++i, ++k) + { + // Search new index + int index = n; + while ((index = ((k + 1) * kappa) % p - 1) >= n) + { + ++k; + } + + tmp[i] = tau[index]; + } - // Get new vector - std::swap( tmp , tau ) ; - return n ; + // Get new vector + std::swap(tmp, tau); + return n; } } // namespace image diff --git a/src/aliceVision/image/drawing.hpp b/src/aliceVision/image/drawing.hpp index 9b267c38ce..d519f89965 100644 --- a/src/aliceVision/image/drawing.hpp +++ b/src/aliceVision/image/drawing.hpp @@ -15,377 +15,454 @@ namespace image { /// Put the pixel in the image to the given color only if the point (xc,yc) /// is inside the image. /// /!\ Be careful at the order (Y,X). -template -inline void SafePutPixel(int yc, int xc, const Color& col, Image *pim) +template +inline void SafePutPixel(int yc, int xc, const Color& col, Image* pim) { - if (pim) { - if (pim->Contains(yc, xc)) - (*pim)(yc, xc) = col; - } + if (pim) + { + if (pim->Contains(yc, xc)) + (*pim)(yc, xc) = col; + } } // Bresenham approach to draw ellipse. // http://raphaello.univ-fcomte.fr/ig/algorithme/ExemplesGLUt/BresenhamEllipse.htm // Add the rotation of the ellipse. // As the algo. use symmetry we must use 4 rotations. -template -void DrawEllipse(int xc, int yc, int radiusA, int radiusB, - const Color& col, Image *pim, double angle = 0.0) +template +void DrawEllipse(int xc, int yc, int radiusA, int radiusB, const Color& col, Image* pim, double angle = 0.0) { - int a = radiusA, b = radiusB; - - // Counter Clockwise rotation matrix. - double matXY[4] = { cos(angle), sin(angle), - -sin(angle), cos(angle)}; - int x, y; - double d1, d2; - x = 0; - y = b; - d1 = b*b - a*a*b + a*a/4; + int a = radiusA, b = radiusB; - int rotX = ceil(matXY[0] * x + matXY[1] * y); - int rotY = ceil(matXY[2] * x + matXY[3] * y); - SafePutPixel( yc + rotY, xc + rotX, col, pim); - rotX = matXY[0] * x - matXY[1] * y; - rotY = matXY[2] * x - matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - rotX = -matXY[0] * x - matXY[1] * y; - rotY = -matXY[2] * x - matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - rotX = -matXY[0] * x + matXY[1] * y; - rotY = -matXY[2] * x + matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); + // Counter Clockwise rotation matrix. + double matXY[4] = {cos(angle), sin(angle), -sin(angle), cos(angle)}; + int x, y; + double d1, d2; + x = 0; + y = b; + d1 = b * b - a * a * b + a * a / 4; - while ( a*a*(y-.5) > b*b*(x+1) ) { - if ( d1 < 0 ) { - d1 += b*b*(2*x+3); - ++x; - } - else { - d1 += b*b*(2*x+3) + a*a*(-2*y+2); - ++x; - --y; - } - rotX = matXY[0] * x + matXY[1] * y; - rotY = matXY[2] * x + matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); + int rotX = ceil(matXY[0] * x + matXY[1] * y); + int rotY = ceil(matXY[2] * x + matXY[3] * y); + SafePutPixel(yc + rotY, xc + rotX, col, pim); rotX = matXY[0] * x - matXY[1] * y; rotY = matXY[2] * x - matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); + SafePutPixel(yc + rotY, xc + rotX, col, pim); rotX = -matXY[0] * x - matXY[1] * y; rotY = -matXY[2] * x - matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); + SafePutPixel(yc + rotY, xc + rotX, col, pim); rotX = -matXY[0] * x + matXY[1] * y; rotY = -matXY[2] * x + matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - } - d2 = b*b*(x+.5)*(x+.5) + a*a*(y-1)*(y-1) - a*a*b*b; - while ( y > 0 ) { - if ( d2 < 0 ) { - d2 += b*b*(2*x+2) + a*a*(-2*y+3); - --y; - ++x; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + + while (a * a * (y - .5) > b * b * (x + 1)) + { + if (d1 < 0) + { + d1 += b * b * (2 * x + 3); + ++x; + } + else + { + d1 += b * b * (2 * x + 3) + a * a * (-2 * y + 2); + ++x; + --y; + } + rotX = matXY[0] * x + matXY[1] * y; + rotY = matXY[2] * x + matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + rotX = matXY[0] * x - matXY[1] * y; + rotY = matXY[2] * x - matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + rotX = -matXY[0] * x - matXY[1] * y; + rotY = -matXY[2] * x - matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + rotX = -matXY[0] * x + matXY[1] * y; + rotY = -matXY[2] * x + matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); } - else { - d2 += a*a*(-2*y+3); - --y; + d2 = b * b * (x + .5) * (x + .5) + a * a * (y - 1) * (y - 1) - a * a * b * b; + while (y > 0) + { + if (d2 < 0) + { + d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3); + --y; + ++x; + } + else + { + d2 += a * a * (-2 * y + 3); + --y; + } + rotX = matXY[0] * x + matXY[1] * y; + rotY = matXY[2] * x + matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + rotX = matXY[0] * x - matXY[1] * y; + rotY = matXY[2] * x - matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + rotX = -matXY[0] * x - matXY[1] * y; + rotY = -matXY[2] * x - matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); + rotX = -matXY[0] * x + matXY[1] * y; + rotY = -matXY[2] * x + matXY[3] * y; + SafePutPixel(yc + rotY, xc + rotX, col, pim); } - rotX = matXY[0] * x + matXY[1] * y; - rotY = matXY[2] * x + matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - rotX = matXY[0] * x - matXY[1] * y; - rotY = matXY[2] * x - matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - rotX = -matXY[0] * x - matXY[1] * y; - rotY = -matXY[2] * x - matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - rotX = -matXY[0] * x + matXY[1] * y; - rotY = -matXY[2] * x + matXY[3] * y; - SafePutPixel( yc + rotY, xc + rotX, col, pim); - } } // Bresenham approach do not allow to draw concentric circle without holes. // So it's better the use the Andres method. // http://fr.wikipedia.org/wiki/Algorithme_de_tracé_de_cercle_d'Andres. -template -void DrawCircle(int x, int y, int radius, const Color& col, Image *pim) +template +void DrawCircle(int x, int y, int radius, const Color& col, Image* pim) { - Image &im = *pim; - if ( im.Contains(y + radius, x + radius) - || im.Contains(y + radius, x - radius) - || im.Contains(y - radius, x + radius) - || im.Contains(y - radius, x - radius)) { - int x1 = 0; - int y1 = radius; - int d = radius - 1; - while (y1 >= x1) { - // Draw the point for each octant. - SafePutPixel( y1 + y, x1 + x, col, pim); - SafePutPixel( x1 + y, y1 + x, col, pim); - SafePutPixel( y1 + y, -x1 + x, col, pim); - SafePutPixel( x1 + y, -y1 + x, col, pim); - SafePutPixel(-y1 + y, x1 + x, col, pim); - SafePutPixel(-x1 + y, y1 + x, col, pim); - SafePutPixel(-y1 + y, -x1 + x, col, pim); - SafePutPixel(-x1 + y, -y1 + x, col, pim); - if (d >= 2 * x1) { - d = d - 2 * x1 - 1; - x1 += 1; - } else { - if (d <= 2 * (radius - y1)) { - d = d + 2 * y1 - 1; - y1 -= 1; - } else { - d = d + 2 * (y1 - x1 - 1); - y1 -= 1; - x1 += 1; + Image& im = *pim; + if (im.Contains(y + radius, x + radius) || im.Contains(y + radius, x - radius) || im.Contains(y - radius, x + radius) || + im.Contains(y - radius, x - radius)) + { + int x1 = 0; + int y1 = radius; + int d = radius - 1; + while (y1 >= x1) + { + // Draw the point for each octant. + SafePutPixel(y1 + y, x1 + x, col, pim); + SafePutPixel(x1 + y, y1 + x, col, pim); + SafePutPixel(y1 + y, -x1 + x, col, pim); + SafePutPixel(x1 + y, -y1 + x, col, pim); + SafePutPixel(-y1 + y, x1 + x, col, pim); + SafePutPixel(-x1 + y, y1 + x, col, pim); + SafePutPixel(-y1 + y, -x1 + x, col, pim); + SafePutPixel(-x1 + y, -y1 + x, col, pim); + if (d >= 2 * x1) + { + d = d - 2 * x1 - 1; + x1 += 1; + } + else + { + if (d <= 2 * (radius - y1)) + { + d = d + 2 * y1 - 1; + y1 -= 1; + } + else + { + d = d + 2 * (y1 - x1 - 1); + y1 -= 1; + x1 += 1; + } + } } - } } - } } // Bresenham algorithm -template -void DrawLine(int xa, int ya, int xb, int yb, const Color& col, Image *pim) +template +void DrawLine(int xa, int ya, int xb, int yb, const Color& col, Image* pim) { - Image &im = *pim; + Image& im = *pim; - // If one point is outside the image - // Replace the outside point by the intersection of the line and - // the limit (either x=width or y=height). - if (!im.Contains(ya, xa) || !im.Contains(yb, xb)) { + // If one point is outside the image + // Replace the outside point by the intersection of the line and + // the limit (either x=width or y=height). + if (!im.Contains(ya, xa) || !im.Contains(yb, xb)) + { + int width = pim->Width(), height = pim->Height(); + const bool xdir = xa < xb, ydir = ya < yb; + float nx0 = float(xa), nx1 = float(xb), ny0 = float(ya), ny1 = float(yb), &xleft = xdir ? nx0 : nx1, &yleft = xdir ? ny0 : ny1, + &xright = xdir ? nx1 : nx0, &yright = xdir ? ny1 : ny0, &xup = ydir ? nx0 : nx1, &yup = ydir ? ny0 : ny1, &xdown = ydir ? nx1 : nx0, + &ydown = ydir ? ny1 : ny0; - int width = pim->Width(), height = pim->Height(); - const bool xdir = xa= width) + return; + if (xleft < 0) + { + yleft -= xleft * (yright - yleft) / (xright - xleft); + xleft = 0; + } + if (xright >= width) + { + yright -= (xright - width) * (yright - yleft) / (xright - xleft); + xright = float(width) - 1; + } + if (ydown < 0 || yup >= height) + return; + if (yup < 0) + { + xup -= yup * (xdown - xup) / (ydown - yup); + yup = 0; + } + if (ydown >= height) + { + xdown -= (ydown - height) * (xdown - xup) / (ydown - yup); + ydown = float(height) - 1; + } - if (xright<0 || xleft>=width) return; - if (xleft<0) { - yleft -= xleft*(yright - yleft)/(xright - xleft); - xleft = 0; + xa = (int)xleft; + xb = (int)xright; + ya = (int)yleft; + yb = (int)yright; } - if (xright>=width) { - yright -= (xright - width)*(yright - yleft)/(xright - xleft); - xright = float(width) - 1; + + int xbas, xhaut, ybas, yhaut; + // Check the condition ybas < yhaut. + if (ya <= yb) + { + xbas = xa; + ybas = ya; + xhaut = xb; + yhaut = yb; } - if (ydown<0 || yup>=height) return; - if (yup<0) { - xup -= yup*(xdown - xup)/(ydown - yup); - yup = 0; + else + { + xbas = xb; + ybas = yb; + xhaut = xa; + yhaut = ya; } - if (ydown>=height) { - xdown -= (ydown - height)*(xdown - xup)/(ydown - yup); - ydown = float(height) - 1; + // Initialize slope. + int x, y, dx, dy, incrmX, incrmY, dp, N, S; + dx = xhaut - xbas; + dy = yhaut - ybas; + if (dx > 0) + { // If xhaut > xbas we will increment X. + incrmX = 1; } - - xa = (int) xleft; xb = (int) xright; - ya = (int) yleft; yb = (int) yright; - } - - int xbas, xhaut, ybas, yhaut; - // Check the condition ybas < yhaut. - if (ya <= yb) { - xbas = xa; ybas = ya; - xhaut = xb; yhaut = yb; - } else { - xbas = xb; ybas = yb; - xhaut = xa; yhaut = ya; - } - // Initialize slope. - int x, y, dx, dy, incrmX, incrmY, dp, N, S; - dx = xhaut - xbas; - dy = yhaut - ybas; - if (dx > 0) { // If xhaut > xbas we will increment X. - incrmX = 1; - } else { - incrmX = -1; // else we will decrement X. - dx *= -1; - } - if (dy > 0) { // Positive slope will increment X. - incrmY = 1; - } else { // Negative slope. - incrmY = -1; - } - if (dx >= dy) { - dp = 2 * dy - dx; - S = 2 * dy; - N = 2 * (dy - dx); - y = ybas; - x = xbas; - while (x != xhaut) { - SafePutPixel( y, x, col, pim); - x += incrmX; - if (dp <= 0) { // Go in direction of the South Pixel. - dp += S; - } else { // Go to the North. - dp += N; - y+=incrmY; - } + else + { + incrmX = -1; // else we will decrement X. + dx *= -1; + } + if (dy > 0) + { // Positive slope will increment X. + incrmY = 1; + } + else + { // Negative slope. + incrmY = -1; + } + if (dx >= dy) + { + dp = 2 * dy - dx; + S = 2 * dy; + N = 2 * (dy - dx); + y = ybas; + x = xbas; + while (x != xhaut) + { + SafePutPixel(y, x, col, pim); + x += incrmX; + if (dp <= 0) + { // Go in direction of the South Pixel. + dp += S; + } + else + { // Go to the North. + dp += N; + y += incrmY; + } + } } - } else { - dp = 2 * dx - dy; - S = 2 * dx; - N = 2 * (dx - dy); - x = xbas; - y = ybas; - while (y < yhaut) { - SafePutPixel( y, x, col, pim); - y += incrmY; - if (dp <= 0) { // Go in direction of the South Pixel. - dp += S; - } else { // Go to the North. - dp += N; - x += incrmX; - } + else + { + dp = 2 * dx - dy; + S = 2 * dx; + N = 2 * (dx - dy); + x = xbas; + y = ybas; + while (y < yhaut) + { + SafePutPixel(y, x, col, pim); + y += incrmY; + if (dp <= 0) + { // Go in direction of the South Pixel. + dp += S; + } + else + { // Go to the North. + dp += N; + x += incrmX; + } + } } - } - SafePutPixel( y, x, col, pim); + SafePutPixel(y, x, col, pim); } // Filled circle // Exterior point computed with bresenham approach // i.e: DrawCircle -template -void FilledCircle(int x, int y, int radius, const Color& col, Image *pim) +template +void FilledCircle(int x, int y, int radius, const Color& col, Image* pim) { - Image &im = *pim; - if ( im.Contains(y + radius, x + radius) - || im.Contains(y + radius, x - radius) - || im.Contains(y - radius, x + radius) - || im.Contains(y - radius, x - radius)) { - int x1 = 0; - int y1 = radius; - int d = radius - 1; - while (y1 >= x1) { - DrawLine(x1 + x, y1 + y, x1 + x, -y1 + y, col, pim); - DrawLine(y1 + x, x1 + y, y1 + x, -x1 + y, col, pim); - DrawLine(-x1 + x, y1 + y, -x1 + x, -y1 + y, col, pim); - DrawLine(-y1 + x, x1 + y, -y1 + x, -x1 + y, col, pim); - if (d >= 2 * x1) { - d = d - 2 * x1 - 1; - x1 += 1; - } else { - if (d <= 2 * (radius - y1)) { - d = d + 2 * y1 - 1; - y1 -= 1; - } else { - d = d + 2 * (y1 - x1 - 1); - y1 -= 1; - x1 += 1; + Image& im = *pim; + if (im.Contains(y + radius, x + radius) || im.Contains(y + radius, x - radius) || im.Contains(y - radius, x + radius) || + im.Contains(y - radius, x - radius)) + { + int x1 = 0; + int y1 = radius; + int d = radius - 1; + while (y1 >= x1) + { + DrawLine(x1 + x, y1 + y, x1 + x, -y1 + y, col, pim); + DrawLine(y1 + x, x1 + y, y1 + x, -x1 + y, col, pim); + DrawLine(-x1 + x, y1 + y, -x1 + x, -y1 + y, col, pim); + DrawLine(-y1 + x, x1 + y, -y1 + x, -x1 + y, col, pim); + if (d >= 2 * x1) + { + d = d - 2 * x1 - 1; + x1 += 1; + } + else + { + if (d <= 2 * (radius - y1)) + { + d = d + 2 * y1 - 1; + y1 -= 1; + } + else + { + d = d + 2 * (y1 - x1 - 1); + y1 -= 1; + x1 += 1; + } + } } - } } - } } // Draw a serie of circles along the line, the algorithm is slow but accurate -template -void DrawLineThickness(int xa, int ya, int xb, int yb, const Color& col, int thickness, Image *pim) +template +void DrawLineThickness(int xa, int ya, int xb, int yb, const Color& col, int thickness, Image* pim) { - Image &im = *pim; - int halfThickness = (thickness+1)/2; + Image& im = *pim; + int halfThickness = (thickness + 1) / 2; - // If one point is outside the image - // Replace the outside point by the intersection of the line and - // the limit (either x=width or y=height). - if (!im.Contains(ya, xa) || !im.Contains(yb, xb)) { + // If one point is outside the image + // Replace the outside point by the intersection of the line and + // the limit (either x=width or y=height). + if (!im.Contains(ya, xa) || !im.Contains(yb, xb)) + { + int width = pim->Width(), height = pim->Height(); + const bool xdir = xa < xb, ydir = ya < yb; + float nx0 = float(xa), nx1 = float(xb), ny0 = float(ya), ny1 = float(yb), &xleft = xdir ? nx0 : nx1, &yleft = xdir ? ny0 : ny1, + &xright = xdir ? nx1 : nx0, &yright = xdir ? ny1 : ny0, &xup = ydir ? nx0 : nx1, &yup = ydir ? ny0 : ny1, &xdown = ydir ? nx1 : nx0, + &ydown = ydir ? ny1 : ny0; - int width = pim->Width(), height = pim->Height(); - const bool xdir = xa= width) + return; + if (xleft < 0) + { + yleft -= xleft * (yright - yleft) / (xright - xleft); + xleft = 0; + } + if (xright >= width) + { + yright -= (xright - width) * (yright - yleft) / (xright - xleft); + xright = float(width) - 1; + } + if (ydown < 0 || yup >= height) + return; + if (yup < 0) + { + xup -= yup * (xdown - xup) / (ydown - yup); + yup = 0; + } + if (ydown >= height) + { + xdown -= (ydown - height) * (xdown - xup) / (ydown - yup); + ydown = float(height) - 1; + } - if (xright<0 || xleft>=width) return; - if (xleft<0) { - yleft -= xleft*(yright - yleft)/(xright - xleft); - xleft = 0; + xa = (int)xleft; + xb = (int)xright; + ya = (int)yleft; + yb = (int)yright; } - if (xright>=width) { - yright -= (xright - width)*(yright - yleft)/(xright - xleft); - xright = float(width) - 1; + + int xbas, xhaut, ybas, yhaut; + // Check the condition ybas < yhaut. + if (ya <= yb) + { + xbas = xa; + ybas = ya; + xhaut = xb; + yhaut = yb; } - if (ydown<0 || yup>=height) return; - if (yup<0) { - xup -= yup*(xdown - xup)/(ydown - yup); - yup = 0; + else + { + xbas = xb; + ybas = yb; + xhaut = xa; + yhaut = ya; } - if (ydown>=height) { - xdown -= (ydown - height)*(xdown - xup)/(ydown - yup); - ydown = float(height) - 1; + // Initialize slope. + int x, y, dx, dy, incrmX, incrmY, dp, N, S; + dx = xhaut - xbas; + dy = yhaut - ybas; + if (dx > 0) + { // If xhaut > xbas we will increment X. + incrmX = 1; } - - xa = (int) xleft; xb = (int) xright; - ya = (int) yleft; yb = (int) yright; - } - - int xbas, xhaut, ybas, yhaut; - // Check the condition ybas < yhaut. - if (ya <= yb) { - xbas = xa; ybas = ya; - xhaut = xb; yhaut = yb; - } else { - xbas = xb; ybas = yb; - xhaut = xa; yhaut = ya; - } - // Initialize slope. - int x, y, dx, dy, incrmX, incrmY, dp, N, S; - dx = xhaut - xbas; - dy = yhaut - ybas; - if (dx > 0) { // If xhaut > xbas we will increment X. - incrmX = 1; - } else { - incrmX = -1; // else we will decrement X. - dx *= -1; - } - if (dy > 0) { // Positive slope will increment X. - incrmY = 1; - } else { // Negative slope. - incrmY = -1; - } - if (dx >= dy) { - dp = 2 * dy - dx; - S = 2 * dy; - N = 2 * (dy - dx); - y = ybas; - x = xbas; - while (x != xhaut) { - DrawCircle(x, y, halfThickness, col, pim); - x += incrmX; - if (dp <= 0) { // Go in direction of the South Pixel. - dp += S; - } else { // Go to the North. - dp += N; - y+=incrmY; - } + else + { + incrmX = -1; // else we will decrement X. + dx *= -1; + } + if (dy > 0) + { // Positive slope will increment X. + incrmY = 1; } - } else { - dp = 2 * dx - dy; - S = 2 * dx; - N = 2 * (dx - dy); - x = xbas; - y = ybas; - while (y < yhaut) { - DrawCircle(x, y, halfThickness, col, pim); - y += incrmY; - if (dp <= 0) { // Go in direction of the South Pixel. - dp += S; - } else { // Go to the North. - dp += N; - x += incrmX; - } + else + { // Negative slope. + incrmY = -1; + } + if (dx >= dy) + { + dp = 2 * dy - dx; + S = 2 * dy; + N = 2 * (dy - dx); + y = ybas; + x = xbas; + while (x != xhaut) + { + DrawCircle(x, y, halfThickness, col, pim); + x += incrmX; + if (dp <= 0) + { // Go in direction of the South Pixel. + dp += S; + } + else + { // Go to the North. + dp += N; + y += incrmY; + } + } + } + else + { + dp = 2 * dx - dy; + S = 2 * dx; + N = 2 * (dx - dy); + x = xbas; + y = ybas; + while (y < yhaut) + { + DrawCircle(x, y, halfThickness, col, pim); + y += incrmY; + if (dp <= 0) + { // Go in direction of the South Pixel. + dp += S; + } + else + { // Go to the North. + dp += N; + x += incrmX; + } + } } - } - DrawCircle(x, y, halfThickness, col, pim); + DrawCircle(x, y, halfThickness, col, pim); } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/drawing_test.cpp b/src/aliceVision/image/drawing_test.cpp index d918f25264..6556ce6465 100644 --- a/src/aliceVision/image/drawing_test.cpp +++ b/src/aliceVision/image/drawing_test.cpp @@ -17,217 +17,219 @@ using namespace aliceVision::image; // Horizontal / Vertical scanlines // Assert that pixels was drawn at the good place -BOOST_AUTO_TEST_CASE(ImageDrawing_Scanlines) { - - const int w = 10, h = 10; - Image image(h,w); - image.fill(0); - - // horizontal scanline - // __________ - // | | - // |__here___| - // | | - // |_________| - const int y = 5; - DrawLine( 0, y, w-1, y, 255, &image); - for(int i=0; i < w; ++i) - BOOST_CHECK_EQUAL( image(y,i), 255); - - image.fill(0); - - // Vertical scanline - // __________ - // | h| | - // | e| | - // | r| | - // |____e|___| - const int x = 5; - DrawLine( x, 0, x, h-1, 255, &image); - for (int i = 0; i < h; ++i) - BOOST_CHECK_EQUAL(image(i,y), 255); +BOOST_AUTO_TEST_CASE(ImageDrawing_Scanlines) +{ + const int w = 10, h = 10; + Image image(h, w); + image.fill(0); + + // horizontal scanline + // __________ + // | | + // |__here___| + // | | + // |_________| + const int y = 5; + DrawLine(0, y, w - 1, y, 255, &image); + for (int i = 0; i < w; ++i) + BOOST_CHECK_EQUAL(image(y, i), 255); + + image.fill(0); + + // Vertical scanline + // __________ + // | h| | + // | e| | + // | r| | + // |____e|___| + const int x = 5; + DrawLine(x, 0, x, h - 1, 255, &image); + for (int i = 0; i < h; ++i) + BOOST_CHECK_EQUAL(image(i, y), 255); } -BOOST_AUTO_TEST_CASE(ImageDrawing_Scanlines_RGB) { - - const int w = 10, h = 10; - Image image(h,w); - image.fill(RGBColor(BLACK)); - - // horizontal scanline - // __________ - // | | - // |__here___| - // | | - // |_________| - const int y = 5; - DrawLine( 0, y, w-1, y, RGBColor(GREEN), &image); - for(int i=0; i < w; ++i) - BOOST_CHECK_EQUAL( image(y,i), RGBColor(GREEN)); - - image.fill(RGBColor(BLACK)); - - // Vertical scanline - // __________ - // | h| | - // | e| | - // | r| | - // |____e|___| - const int x = 5; - DrawLine( x, 0, x, h-1, RGBColor(YELLOW), &image); - for (int i = 0; i < h; ++i) - BOOST_CHECK_EQUAL(image(i,y), RGBColor(YELLOW)); +BOOST_AUTO_TEST_CASE(ImageDrawing_Scanlines_RGB) +{ + const int w = 10, h = 10; + Image image(h, w); + image.fill(RGBColor(BLACK)); + + // horizontal scanline + // __________ + // | | + // |__here___| + // | | + // |_________| + const int y = 5; + DrawLine(0, y, w - 1, y, RGBColor(GREEN), &image); + for (int i = 0; i < w; ++i) + BOOST_CHECK_EQUAL(image(y, i), RGBColor(GREEN)); + + image.fill(RGBColor(BLACK)); + + // Vertical scanline + // __________ + // | h| | + // | e| | + // | r| | + // |____e|___| + const int x = 5; + DrawLine(x, 0, x, h - 1, RGBColor(YELLOW), &image); + for (int i = 0; i < h; ++i) + BOOST_CHECK_EQUAL(image(i, y), RGBColor(YELLOW)); } // Lines with a given angle +/-45° // Assert that pixels was drawn at the good place -BOOST_AUTO_TEST_CASE(ImageDrawing_Lines45) { - - const int w = 10, h = 10; - Image image(h,w); - image.fill(0); - - // _____ - // |\ | - // | \ | - // |__\| - - DrawLine(0, 0, w-1, h-1, 255, &image); - for (int i = 0; i < w; ++i) - BOOST_CHECK_EQUAL(image(i,i), 255); - - image.fill(0); - - // _____ - // | / | - // | / | - // |/___|_ - DrawLine(0, h-1, w-1, 0, 255, &image); - for (int i = 0; i < h; ++i) - BOOST_CHECK_EQUAL(image(h-1-i,i), 255); +BOOST_AUTO_TEST_CASE(ImageDrawing_Lines45) +{ + const int w = 10, h = 10; + Image image(h, w); + image.fill(0); + + // _____ + // |\ | + // | \ | + // |__\| + + DrawLine(0, 0, w - 1, h - 1, 255, &image); + for (int i = 0; i < w; ++i) + BOOST_CHECK_EQUAL(image(i, i), 255); + + image.fill(0); + + // _____ + // | / | + // | / | + // |/___|_ + DrawLine(0, h - 1, w - 1, 0, 255, &image); + for (int i = 0; i < h; ++i) + BOOST_CHECK_EQUAL(image(h - 1 - i, i), 255); } // Draw a circle in an image and assert that all the points are // at a distance equal to the radius. -BOOST_AUTO_TEST_CASE(ImageDrawing_Circle) { - - Image image(10,10); - image.fill(0); - - const int radius = 3; - const int x = 5, y = 5; - - DrawCircle(x, y, radius, (unsigned char)255, &image); - - // Distance checking : - for ( int j = 0; j < image.Height(); ++j) - for ( int i = 0; i < image.Width(); ++i) { - if (image(j, i) == 255) { - const float distance = std::hypot((float)(j-y), (float)(i-x)); - BOOST_CHECK_SMALL(radius-distance, 1.0f); - // Due to discretisation we cannot expect better precision - } - } +BOOST_AUTO_TEST_CASE(ImageDrawing_Circle) +{ + Image image(10, 10); + image.fill(0); + + const int radius = 3; + const int x = 5, y = 5; + + DrawCircle(x, y, radius, (unsigned char)255, &image); + + // Distance checking : + for (int j = 0; j < image.Height(); ++j) + for (int i = 0; i < image.Width(); ++i) + { + if (image(j, i) == 255) + { + const float distance = std::hypot((float)(j - y), (float)(i - x)); + BOOST_CHECK_SMALL(radius - distance, 1.0f); + // Due to discretisation we cannot expect better precision + } + } } // Draw an ellipse with the two radius equal each other... // in an image and assert that all the points are // at a distance equal to the radius. -BOOST_AUTO_TEST_CASE(ImageDrawing_Ellipse) { +BOOST_AUTO_TEST_CASE(ImageDrawing_Ellipse) +{ + Image image(10, 10); + image.fill(0); - Image image(10,10); - image.fill(0); + const int radius = 3, angle = 0; + const int x = 5, y = 5; - const int radius = 3, angle = 0; - const int x = 5, y = 5; + DrawEllipse(x, y, radius, radius, (unsigned char)255, &image, (double)angle); - DrawEllipse(x, y, radius, radius, (unsigned char)255, &image, (double)angle); - - // Distance checking : - for(int j = 0; j < image.Height(); ++j) - { - for(int i = 0; i < image.Width(); ++i) + // Distance checking : + for (int j = 0; j < image.Height(); ++j) { - if(image(j, i) == 255) - { - const float distance = std::hypot((float) (j - y), (float) (i - x)); - BOOST_CHECK_SMALL(radius-distance, 1.0f); - // Due to discretisation we cannot expect better precision - } + for (int i = 0; i < image.Width(); ++i) + { + if (image(j, i) == 255) + { + const float distance = std::hypot((float)(j - y), (float)(i - x)); + BOOST_CHECK_SMALL(radius - distance, 1.0f); + // Due to discretisation we cannot expect better precision + } + } } - } } // Draw an ellipse with the two radius and rotated ... // in an image and assert that all the points are // within the given radius. -BOOST_AUTO_TEST_CASE(ImageDrawing_RotatedEllipse) { - - Image image(30,30); - image.fill(0); +BOOST_AUTO_TEST_CASE(ImageDrawing_RotatedEllipse) +{ + Image image(30, 30); + image.fill(0); - const int radius = 6; - const int x = 10, y = 10; + const int radius = 6; + const int x = 10, y = 10; - DrawEllipse(x, y, radius, radius/2.0, static_cast(255), &image, M_PI/4.0); + DrawEllipse(x, y, radius, radius / 2.0, static_cast(255), &image, M_PI / 4.0); - // Distance checking : - for(int j = 0; j < image.Height(); ++j) - { - for(int i = 0; i < image.Width(); ++i) + // Distance checking : + for (int j = 0; j < image.Height(); ++j) { - if(image(j, i) == 255) - { - const float distance = std::hypot((float) (j - y), (float) (i - x)); - BOOST_CHECK_EQUAL(radius + 1 >= distance && radius / 2.0 - 1 <= distance, true); - // Due to discretization we cannot expect better precision - // Use +-1 to avoid rasterization error. - } + for (int i = 0; i < image.Width(); ++i) + { + if (image(j, i) == 255) + { + const float distance = std::hypot((float)(j - y), (float)(i - x)); + BOOST_CHECK_EQUAL(radius + 1 >= distance && radius / 2.0 - 1 <= distance, true); + // Due to discretization we cannot expect better precision + // Use +-1 to avoid rasterization error. + } + } } - } } /// Assert that the DrawLine function do not crash /// when one point is outside the image -BOOST_AUTO_TEST_CASE(ImageDrawing_DrawLine_PointOutsideTheImage) { - - Image image(30,30); - image.fill(0); - - const int radius = 20; - int x = 15, y = 15; - - // Distance checking : - for (double i=0; i < 2.0*3.14; i+=3.14/12) - { - int x1 = int(cos(i) * radius + 0.5); - int y1 = int(sin(i) * radius + 0.5); - DrawLine( x, y, x+x1, y+y1, 255, &image); - } - // Translate : - x += int(15/2.0+0.5); - for (double i=0; i < 2.0*3.14; i+=3.14/12) - { - int x1 = int(cos(i) * radius + 0.5); - int y1 = int(sin(i) * radius + 0.5); - DrawLine( x, y, x+x1, y+y1, 255, &image); - } - // Translate : - x += int(15/2.0+0.5); - for (double i=0; i < 2.0*3.14; i+=3.14/12) - { - int x1 = int(cos(i) * radius + 0.5); - int y1 = int(sin(i) * radius + 0.5); - DrawLine( x, y, x+x1, y+y1, 255, &image); - } - - //Point totally outside the image - x = y = -100; - for (double i=0; i < 2.0*3.14; i+=3.14/12) - { - int x1 = int(cos(i) * radius + 0.5); - int y1 = int(sin(i) * radius + 0.5); - DrawLine( x, y, x+x1, y+y1, 255, &image); - } - //writeImage( image, "toto.png"); +BOOST_AUTO_TEST_CASE(ImageDrawing_DrawLine_PointOutsideTheImage) +{ + Image image(30, 30); + image.fill(0); + + const int radius = 20; + int x = 15, y = 15; + + // Distance checking : + for (double i = 0; i < 2.0 * 3.14; i += 3.14 / 12) + { + int x1 = int(cos(i) * radius + 0.5); + int y1 = int(sin(i) * radius + 0.5); + DrawLine(x, y, x + x1, y + y1, 255, &image); + } + // Translate : + x += int(15 / 2.0 + 0.5); + for (double i = 0; i < 2.0 * 3.14; i += 3.14 / 12) + { + int x1 = int(cos(i) * radius + 0.5); + int y1 = int(sin(i) * radius + 0.5); + DrawLine(x, y, x + x1, y + y1, 255, &image); + } + // Translate : + x += int(15 / 2.0 + 0.5); + for (double i = 0; i < 2.0 * 3.14; i += 3.14 / 12) + { + int x1 = int(cos(i) * radius + 0.5); + int y1 = int(sin(i) * radius + 0.5); + DrawLine(x, y, x + x1, y + y1, 255, &image); + } + + // Point totally outside the image + x = y = -100; + for (double i = 0; i < 2.0 * 3.14; i += 3.14 / 12) + { + int x1 = int(cos(i) * radius + 0.5); + int y1 = int(sin(i) * radius + 0.5); + DrawLine(x, y, x + x1, y + y1, 255, &image); + } + // writeImage( image, "toto.png"); } diff --git a/src/aliceVision/image/filtering.cpp b/src/aliceVision/image/filtering.cpp index eea73d6c69..29f748d046 100644 --- a/src/aliceVision/image/filtering.cpp +++ b/src/aliceVision/image/filtering.cpp @@ -12,35 +12,35 @@ namespace image { Vec ComputeGaussianKernel(const std::size_t size, const double sigma) { - // If kernel size is 0 computes it's size using uber formula - std::size_t k_size = ( size == 0 ) ? ceil(2.0*(1.0 + (sigma-0.8)/(0.3))) : size ; - - // Make kernel odd width - k_size = ( k_size % 2 == 0 ) ? k_size + 1 : k_size ; - const std::size_t half_k_size = ( k_size - 1 ) / 2 ; - - Vec res( k_size ) ; - - const double exp_scale = 1.0 / ( 2.0 * sigma * sigma ) ; - - // Compute unnormalized kernel - double sum = 0.0 ; - for( std::size_t i = 0 ; i < k_size ; ++i ) - { - const double dx = ( static_cast(i) - static_cast(half_k_size) ) ; - res( i ) = exp( - dx * dx * exp_scale ) ; - sum += res( i ) ; - } - - // Normalize kernel - const double inv = 1.0 / sum ; - for( std::size_t i = 0 ; i < k_size ; ++i ) - { - res( i ) *= inv ; - } - - return res ; + // If kernel size is 0 computes it's size using uber formula + std::size_t k_size = (size == 0) ? ceil(2.0 * (1.0 + (sigma - 0.8) / (0.3))) : size; + + // Make kernel odd width + k_size = (k_size % 2 == 0) ? k_size + 1 : k_size; + const std::size_t half_k_size = (k_size - 1) / 2; + + Vec res(k_size); + + const double exp_scale = 1.0 / (2.0 * sigma * sigma); + + // Compute unnormalized kernel + double sum = 0.0; + for (std::size_t i = 0; i < k_size; ++i) + { + const double dx = (static_cast(i) - static_cast(half_k_size)); + res(i) = exp(-dx * dx * exp_scale); + sum += res(i); + } + + // Normalize kernel + const double inv = 1.0 / sum; + for (std::size_t i = 0; i < k_size; ++i) + { + res(i) *= inv; + } + + return res; } -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/filtering.hpp b/src/aliceVision/image/filtering.hpp index 7b3789ad62..f6829d6ad6 100644 --- a/src/aliceVision/image/filtering.hpp +++ b/src/aliceVision/image/filtering.hpp @@ -13,7 +13,7 @@ ** - Gaussian blur **/ - //------------------ +//------------------ //-- Bibliography -- //------------------ //- [1] "A Scheme for Coherence-Enhancing Diffusion Filtering with Optimized Rotation Invariance." @@ -26,146 +26,142 @@ namespace aliceVision { namespace image { - /** - ** Compute X-derivative using central difference - ** @param img Input image - ** @param out Output image - ** @param normalize true if kernel must be scaled by 1/2 - **/ - template< typename Image > - void ImageXDerivative( const Image & img , Image & out , const bool normalize = true) - { - Vec3 kernel( -1.0, 0.0, 1.0); - - if( normalize ) - kernel *= 0.5 ; - - ImageHorizontalConvolution( img , kernel , out) ; - } - - - /** - ** Compute Y-derivative using central difference - ** @param img Input image - ** @param out Output image - ** @param normalize true if kernel must be normalized - **/ - template< typename Image > - void ImageYDerivative( const Image & img , Image & out , const bool normalize = true) - { - Vec3 kernel( -1.0, 0.0, 1.0) ; - - if( normalize ) - kernel *= 0.5 ; - - ImageVerticalConvolution( img , kernel , out ) ; - } - - - /** - ** Compute X-derivative using 3x3 Sobel kernel - ** @param img Input image - ** @param out Output image - ** @param normalize true if kernel must be scaled by 1/8 - **/ - template< typename Image > - void ImageSobelXDerivative( const Image & img , Image & out , const bool normalize = true) - { - Vec3 kernel_horiz( -1.0, 0.0, 1.0) ; - - if( normalize ) - kernel_horiz *= 0.5 ; - - Vec3 kernel_vert( 1.0, 2.0, 1.0) ; - - if( normalize ) - kernel_vert *= 0.25 ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out ) ; - } - - - /** - ** Compute Y-derivative using 3x3 Sobel kernel - ** @param img Input image - ** @param out Output image - ** @param normalize true if kernel must be scaled by 1/8 - **/ - template< typename Image > - void ImageSobelYDerivative( const Image & img , Image & out , const bool normalize = true) - { - Vec3 kernel_horiz( 1.0, 2.0, 1.0) ; - - if( normalize ) - kernel_horiz *= 0.25 ; - - Vec3 kernel_vert( -1.0, 0.0, 1.0) ; - - if( normalize ) - kernel_vert *= 0.5 ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } - - /** - ** Compute X-derivative using 3x3 Scharr kernel - ** @param img Input image - ** @param out Output image - ** @param normalize true if kernel must be scaled by 1/32 - **/ - template< typename Image > - void ImageScharrXDerivative( const Image & img , Image & out , const bool normalize = true) - { - Vec3 kernel_horiz( -1.0, 0.0, 1.0) ; - - if( normalize ) - kernel_horiz *= 0.5 ; - - Vec3 kernel_vert( 3.0, 10.0, 3.0) ; - - if( normalize ) - kernel_vert *= 1.0 / 16.0 ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } - - /** - ** Compute Y-derivative using 3x3 Scharr filter - ** @param img Input image - ** @param out Output image - ** @param normalize true if kernel must be scaled by 1/32 - **/ - template< typename Image > - void ImageScharrYDerivative( const Image & img , Image & out , const bool normalize = true) - { - Vec3 kernel_horiz( 3.0, 10.0, 3.0) ; - - if( normalize ) - kernel_horiz *= 1.0 / 16.0 ; - - Vec3 kernel_vert( -1.0, 0.0, 1.0) ; - - if( normalize ) - kernel_vert *= 0.5 ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } - - - /** - ** Compute X-derivative using scaled Scharr filter - ** @param img Input image - ** @param out Output image - ** @param scale scale of filter (1 -> 3x3 filter ; 2 -> 5x5, ...) - ** @param bNormalize true if kernel must be normalized - **/ - template< typename Image > - void ImageScaledScharrXDerivative( const Image & img , Image & out , const int scale , const bool bNormalize = true) - { - const int kernel_size = 3 + 2 * ( scale - 1 ) ; - - Vec kernel_vert( kernel_size ) ; - Vec kernel_horiz( kernel_size ) ; +/** + ** Compute X-derivative using central difference + ** @param img Input image + ** @param out Output image + ** @param normalize true if kernel must be scaled by 1/2 + **/ +template +void ImageXDerivative(const Image& img, Image& out, const bool normalize = true) +{ + Vec3 kernel(-1.0, 0.0, 1.0); + + if (normalize) + kernel *= 0.5; + + ImageHorizontalConvolution(img, kernel, out); +} + +/** + ** Compute Y-derivative using central difference + ** @param img Input image + ** @param out Output image + ** @param normalize true if kernel must be normalized + **/ +template +void ImageYDerivative(const Image& img, Image& out, const bool normalize = true) +{ + Vec3 kernel(-1.0, 0.0, 1.0); + + if (normalize) + kernel *= 0.5; + + ImageVerticalConvolution(img, kernel, out); +} + +/** + ** Compute X-derivative using 3x3 Sobel kernel + ** @param img Input image + ** @param out Output image + ** @param normalize true if kernel must be scaled by 1/8 + **/ +template +void ImageSobelXDerivative(const Image& img, Image& out, const bool normalize = true) +{ + Vec3 kernel_horiz(-1.0, 0.0, 1.0); + + if (normalize) + kernel_horiz *= 0.5; + + Vec3 kernel_vert(1.0, 2.0, 1.0); + + if (normalize) + kernel_vert *= 0.25; + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +/** + ** Compute Y-derivative using 3x3 Sobel kernel + ** @param img Input image + ** @param out Output image + ** @param normalize true if kernel must be scaled by 1/8 + **/ +template +void ImageSobelYDerivative(const Image& img, Image& out, const bool normalize = true) +{ + Vec3 kernel_horiz(1.0, 2.0, 1.0); + + if (normalize) + kernel_horiz *= 0.25; + + Vec3 kernel_vert(-1.0, 0.0, 1.0); + + if (normalize) + kernel_vert *= 0.5; + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +/** + ** Compute X-derivative using 3x3 Scharr kernel + ** @param img Input image + ** @param out Output image + ** @param normalize true if kernel must be scaled by 1/32 + **/ +template +void ImageScharrXDerivative(const Image& img, Image& out, const bool normalize = true) +{ + Vec3 kernel_horiz(-1.0, 0.0, 1.0); + + if (normalize) + kernel_horiz *= 0.5; + + Vec3 kernel_vert(3.0, 10.0, 3.0); + + if (normalize) + kernel_vert *= 1.0 / 16.0; + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +/** + ** Compute Y-derivative using 3x3 Scharr filter + ** @param img Input image + ** @param out Output image + ** @param normalize true if kernel must be scaled by 1/32 + **/ +template +void ImageScharrYDerivative(const Image& img, Image& out, const bool normalize = true) +{ + Vec3 kernel_horiz(3.0, 10.0, 3.0); + + if (normalize) + kernel_horiz *= 1.0 / 16.0; + + Vec3 kernel_vert(-1.0, 0.0, 1.0); + + if (normalize) + kernel_vert *= 0.5; + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +/** + ** Compute X-derivative using scaled Scharr filter + ** @param img Input image + ** @param out Output image + ** @param scale scale of filter (1 -> 3x3 filter ; 2 -> 5x5, ...) + ** @param bNormalize true if kernel must be normalized + **/ +template +void ImageScaledScharrXDerivative(const Image& img, Image& out, const int scale, const bool bNormalize = true) +{ + const int kernel_size = 3 + 2 * (scale - 1); + + Vec kernel_vert(kernel_size); + Vec kernel_horiz(kernel_size); /* General X-derivative function @@ -174,37 +170,35 @@ namespace image { | -1 0 1 | */ - kernel_horiz.fill( 0.0 ) ; - kernel_horiz( 0 ) = -1.0 ; + kernel_horiz.fill(0.0); + kernel_horiz(0) = -1.0; // kernel_horiz( kernel_size / 2 ) = 0.0 ; - kernel_horiz( kernel_size - 1 ) = 1.0 ; + kernel_horiz(kernel_size - 1) = 1.0; // Scharr parameter for derivative - const double w = 10.0 / 3.0 ; - - kernel_vert.fill( 0.0 ) ; - kernel_vert( 0 ) = 1.0 ; - kernel_vert( kernel_size / 2 ) = w ; - kernel_vert( kernel_size - 1 ) = 1.0 ; + const double w = 10.0 / 3.0; - if( bNormalize ) - kernel_vert *= 1.0 / ( 2.0 * scale * ( w + 2.0 ) ) ; + kernel_vert.fill(0.0); + kernel_vert(0) = 1.0; + kernel_vert(kernel_size / 2) = w; + kernel_vert(kernel_size - 1) = 1.0; - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } + if (bNormalize) + kernel_vert *= 1.0 / (2.0 * scale * (w + 2.0)); + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} - - /** - ** Compute Y-derivative using scaled Scharr filter - ** @param img Input image - ** @param out Output image - ** @param scale scale of filter (1 -> 3x3 filter ; 2 -> 5x5, ...) - ** @param bNormalize true if kernel must be normalized - **/ - template< typename Image > - void ImageScaledScharrYDerivative( const Image & img , Image & out , const int scale , const bool bNormalize = true) - { +/** + ** Compute Y-derivative using scaled Scharr filter + ** @param img Input image + ** @param out Output image + ** @param scale scale of filter (1 -> 3x3 filter ; 2 -> 5x5, ...) + ** @param bNormalize true if kernel must be normalized + **/ +template +void ImageScaledScharrYDerivative(const Image& img, Image& out, const int scale, const bool bNormalize = true) +{ /* General Y-derivative function | -1 -w -1 | @@ -212,101 +206,98 @@ namespace image { | 1 w 1 | */ - const int kernel_size = 3 + 2 * ( scale - 1 ) ; + const int kernel_size = 3 + 2 * (scale - 1); - Vec kernel_vert( kernel_size ) ; - Vec kernel_horiz( kernel_size ) ; + Vec kernel_vert(kernel_size); + Vec kernel_horiz(kernel_size); // Scharr parameter for derivative - const double w = 10.0 / 3.0 ; + const double w = 10.0 / 3.0; + kernel_horiz.fill(0.0); + kernel_horiz(0) = 1.0; + kernel_horiz(kernel_size / 2) = w; + kernel_horiz(kernel_size - 1) = 1.0; - kernel_horiz.fill( 0.0 ) ; - kernel_horiz( 0 ) = 1.0 ; - kernel_horiz( kernel_size / 2 ) = w ; - kernel_horiz( kernel_size - 1 ) = 1.0 ; + if (bNormalize) + kernel_horiz *= 1.0 / (2.0 * scale * (w + 2.0)); - if( bNormalize ) - kernel_horiz *= 1.0 / ( 2.0 * scale * ( w + 2.0 ) ) ; - - kernel_vert.fill( 0.0 ) ; - kernel_vert( 0 ) = - 1.0 ; + kernel_vert.fill(0.0); + kernel_vert(0) = -1.0; // kernel_vert( kernel_size / 2 ) = 0.0 ; - kernel_vert( kernel_size - 1 ) = 1.0 ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } - - - /** - ** Compute (isotropic) gaussian filtering of an image using filter width of k * sigma - ** @param img Input image - ** @param sigma standard deviation of kernel - ** @param out Output image - ** @param k confidence interval param - kernel is width k * sigma * 2 + 1 -- using k = 3 gives 99% of gaussian curve - ** @param border_mgmt either BORDER_COPY or BORDER_CROP to tell what to do with borders - **/ - template< typename Image > - void ImageGaussianFilter( const Image & img , const double sigma , Image & out , const int k = 3) - { + kernel_vert(kernel_size - 1) = 1.0; + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +/** + ** Compute (isotropic) gaussian filtering of an image using filter width of k * sigma + ** @param img Input image + ** @param sigma standard deviation of kernel + ** @param out Output image + ** @param k confidence interval param - kernel is width k * sigma * 2 + 1 -- using k = 3 gives 99% of gaussian curve + ** @param border_mgmt either BORDER_COPY or BORDER_CROP to tell what to do with borders + **/ +template +void ImageGaussianFilter(const Image& img, const double sigma, Image& out, const int k = 3) +{ // Compute Gaussian filter - const int k_size = (int) 2 * k * sigma + 1 ; - const int half_k_size = k_size / 2 ; + const int k_size = (int)2 * k * sigma + 1; + const int half_k_size = k_size / 2; - const double exp_scale = 1.0 / ( 2.0 * sigma * sigma ) ; + const double exp_scale = 1.0 / (2.0 * sigma * sigma); // Compute 1D Gaussian filter - Vec kernel_horiz( k_size ) ; + Vec kernel_horiz(k_size); - double sum = 0 ; - for( int i = 0 ; i < k_size ; ++i ) + double sum = 0; + for (int i = 0; i < k_size; ++i) { - const double dx = (i - half_k_size) ; - kernel_horiz( i ) = exp( - dx * dx * exp_scale ) ; - sum += kernel_horiz( i ) ; + const double dx = (i - half_k_size); + kernel_horiz(i) = exp(-dx * dx * exp_scale); + sum += kernel_horiz(i); } // Normalize kernel (to have \sum_i kernel_horiz( i ) = 1 and avoid energy loss) - const double inv = 1.0 / sum ; - for( int i = 0 ; i < k_size ; ++i ) + const double inv = 1.0 / sum; + for (int i = 0; i < k_size; ++i) { - kernel_horiz( i ) *= inv ; + kernel_horiz(i) *= inv; } // Vertical kernel is the same as the horizontal one - const Vec & kernel_vert = kernel_horiz ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } - - /** - ** @brief Compute 1D gaussian kernel of specified width - ** @param size Size of kernel (0 for automatic window) - ** @param sigma Gaussian scale - ** @return Kernel using specified parameters - **/ - Vec ComputeGaussianKernel(const std::size_t size, const double sigma); - - /** - ** Compute gaussian filtering of an image using user defined filter widths - ** @param img Input image - ** @param sigma standard deviation of kernel - ** @param out Output image - ** @param kernel_size_x Size of horizontal kernel (must be an odd number or 0 for automatic computation) - ** @param kernel_size_y Size of vertical kernel (must be an add number or 0 for automatic computation) - **/ - template< typename Image > - void ImageGaussianFilter( const Image & img , const double sigma , Image & out , - const size_t kernel_size_x , const size_t kernel_size_y) - { - assert( kernel_size_x % 2 == 1 || kernel_size_x == 0 ) ; - assert( kernel_size_y % 2 == 1 || kernel_size_y == 0 ) ; - - const Vec kernel_horiz = ComputeGaussianKernel( kernel_size_x , sigma ) ; - const Vec kernel_vert = ComputeGaussianKernel( kernel_size_y , sigma ) ; - - ImageSeparableConvolution( img , kernel_horiz , kernel_vert , out) ; - } - -} // namespace image -} // namespace aliceVision + const Vec& kernel_vert = kernel_horiz; + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +/** + ** @brief Compute 1D gaussian kernel of specified width + ** @param size Size of kernel (0 for automatic window) + ** @param sigma Gaussian scale + ** @return Kernel using specified parameters + **/ +Vec ComputeGaussianKernel(const std::size_t size, const double sigma); + +/** + ** Compute gaussian filtering of an image using user defined filter widths + ** @param img Input image + ** @param sigma standard deviation of kernel + ** @param out Output image + ** @param kernel_size_x Size of horizontal kernel (must be an odd number or 0 for automatic computation) + ** @param kernel_size_y Size of vertical kernel (must be an add number or 0 for automatic computation) + **/ +template +void ImageGaussianFilter(const Image& img, const double sigma, Image& out, const size_t kernel_size_x, const size_t kernel_size_y) +{ + assert(kernel_size_x % 2 == 1 || kernel_size_x == 0); + assert(kernel_size_y % 2 == 1 || kernel_size_y == 0); + + const Vec kernel_horiz = ComputeGaussianKernel(kernel_size_x, sigma); + const Vec kernel_vert = ComputeGaussianKernel(kernel_size_y, sigma); + + ImageSeparableConvolution(img, kernel_horiz, kernel_vert, out); +} + +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/filtering_test.cpp b/src/aliceVision/image/filtering_test.cpp index 4c62fc8812..cfc567f754 100644 --- a/src/aliceVision/image/filtering_test.cpp +++ b/src/aliceVision/image/filtering_test.cpp @@ -19,127 +19,123 @@ using namespace aliceVision::image; BOOST_AUTO_TEST_CASE(Image_Convolution) { - Image in(250,250,true); - for( int i = 10; i < 250-10; i++) - for( int j = 10; j < 250-10; j++) - { - in(j,i) = rand()%127+127; - } - - BOOST_CHECK(in(5,5) == 0); - BOOST_CHECK(in(249-5,249-5) == 0); - - // filter - Image outFiltered(250, 250, true); - ImageGaussianFilter( in, 6.0, outFiltered); - - BOOST_CHECK_NO_THROW(writeImage("in.png", in, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - BOOST_CHECK_NO_THROW(writeImage("outfilter.png", outFiltered, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - // Check that gaussian filtering have smooth at border of the white random square - BOOST_CHECK(outFiltered(5,5)>0); - BOOST_CHECK(outFiltered(250-5,250-5)>0); + Image in(250, 250, true); + for (int i = 10; i < 250 - 10; i++) + for (int j = 10; j < 250 - 10; j++) + { + in(j, i) = rand() % 127 + 127; + } + + BOOST_CHECK(in(5, 5) == 0); + BOOST_CHECK(in(249 - 5, 249 - 5) == 0); + + // filter + Image outFiltered(250, 250, true); + ImageGaussianFilter(in, 6.0, outFiltered); + + BOOST_CHECK_NO_THROW(writeImage("in.png", in, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + BOOST_CHECK_NO_THROW(writeImage("outfilter.png", outFiltered, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + // Check that gaussian filtering have smooth at border of the white random square + BOOST_CHECK(outFiltered(5, 5) > 0); + BOOST_CHECK(outFiltered(250 - 5, 250 - 5) > 0); } BOOST_AUTO_TEST_CASE(Image_Convolution_MeanBoxFilter_Separable) { - Image in(40,40,true); - in.block(10,10,20,20).fill(255.f); - Vec3 meanBoxFilterKernel(1.f/3.f, 1.f/3.f, 1.f/3.f); - Image out; - ImageSeparableConvolution( in , meanBoxFilterKernel, meanBoxFilterKernel , out); + Image in(40, 40, true); + in.block(10, 10, 20, 20).fill(255.f); + Vec3 meanBoxFilterKernel(1.f / 3.f, 1.f / 3.f, 1.f / 3.f); + Image out; + ImageSeparableConvolution(in, meanBoxFilterKernel, meanBoxFilterKernel, out); } BOOST_AUTO_TEST_CASE(Image_Convolution_MeanBoxFilter) { - Image in(40,40,true); - in.block(10,10,20,20).fill(255.f); - Mat3 meanBoxFilterKernel; - meanBoxFilterKernel.fill(1.f/9.f); - Image out; - ImageConvolution(in, meanBoxFilterKernel, out); + Image in(40, 40, true); + in.block(10, 10, 20, 20).fill(255.f); + Mat3 meanBoxFilterKernel; + meanBoxFilterKernel.fill(1.f / 9.f); + Image out; + ImageConvolution(in, meanBoxFilterKernel, out); } BOOST_AUTO_TEST_CASE(Image_Convolution_Scharr_X_Y) { - Image in(40,40,true); - in.block(10,10,20,20).fill(255.f); - - Image outFiltered(40,40,true); - - ImageScaledScharrXDerivative( in, outFiltered, 1); - - // X dir - BOOST_CHECK_EQUAL(127.5f, outFiltered(20,10)); - BOOST_CHECK_EQUAL(-127.5f, outFiltered(20,30)); - // Y dir - BOOST_CHECK_EQUAL(0.f, outFiltered(10,20)); - BOOST_CHECK_EQUAL(0.f, outFiltered(30,20)); - // Check it exist a vertical black band - BOOST_CHECK_EQUAL(0.f, outFiltered.block(0,10+3,40,20-2*3).array().abs().sum()); - - Image inCast = Image(in.cast()); - Image outFilteredCast = Image(outFiltered.cast()); - BOOST_CHECK_NO_THROW(writeImage("in_Scharr.png", inCast, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - BOOST_CHECK_NO_THROW(writeImage("out_ScharrX.png", outFilteredCast, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - outFiltered.fill(0.0f); - ImageScaledScharrYDerivative( in, outFiltered, 1); - - // X dir - BOOST_CHECK_EQUAL(0.f, outFiltered(20,10)); - BOOST_CHECK_EQUAL(0.f, outFiltered(20,30)); - // Y dir - BOOST_CHECK_EQUAL(127.5f, outFiltered(10,20)); - BOOST_CHECK_EQUAL(-127.5f, outFiltered(30,20)); - // Check it exist a horizontal black band - BOOST_CHECK_EQUAL(0.f, outFiltered.block(10+3,0,20-2*3,40).array().abs().sum()); - outFilteredCast = Image(outFiltered.cast()); - BOOST_CHECK_NO_THROW(writeImage("out_ScharrY.png", outFilteredCast, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + Image in(40, 40, true); + in.block(10, 10, 20, 20).fill(255.f); + + Image outFiltered(40, 40, true); + + ImageScaledScharrXDerivative(in, outFiltered, 1); + + // X dir + BOOST_CHECK_EQUAL(127.5f, outFiltered(20, 10)); + BOOST_CHECK_EQUAL(-127.5f, outFiltered(20, 30)); + // Y dir + BOOST_CHECK_EQUAL(0.f, outFiltered(10, 20)); + BOOST_CHECK_EQUAL(0.f, outFiltered(30, 20)); + // Check it exist a vertical black band + BOOST_CHECK_EQUAL(0.f, outFiltered.block(0, 10 + 3, 40, 20 - 2 * 3).array().abs().sum()); + + Image inCast = Image(in.cast()); + Image outFilteredCast = Image(outFiltered.cast()); + BOOST_CHECK_NO_THROW(writeImage("in_Scharr.png", inCast, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + BOOST_CHECK_NO_THROW( + writeImage("out_ScharrX.png", outFilteredCast, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + outFiltered.fill(0.0f); + ImageScaledScharrYDerivative(in, outFiltered, 1); + + // X dir + BOOST_CHECK_EQUAL(0.f, outFiltered(20, 10)); + BOOST_CHECK_EQUAL(0.f, outFiltered(20, 30)); + // Y dir + BOOST_CHECK_EQUAL(127.5f, outFiltered(10, 20)); + BOOST_CHECK_EQUAL(-127.5f, outFiltered(30, 20)); + // Check it exist a horizontal black band + BOOST_CHECK_EQUAL(0.f, outFiltered.block(10 + 3, 0, 20 - 2 * 3, 40).array().abs().sum()); + outFilteredCast = Image(outFiltered.cast()); + BOOST_CHECK_NO_THROW( + writeImage("out_ScharrY.png", outFilteredCast, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); } BOOST_AUTO_TEST_CASE(Image_Convolution_Sobel_X_Y) { - Image in(40,40,true); - in.block(10,10,20,20).fill(255.f); - - Image outFiltered(40,40,true); - - ImageSobelXDerivative( in, outFiltered); - - // X dir - BOOST_CHECK_EQUAL(127.5f, outFiltered(20,10)); - BOOST_CHECK_EQUAL(-127.5f, outFiltered(20,30)); - // Y dir - BOOST_CHECK_EQUAL(0.f, outFiltered(10,20)); - BOOST_CHECK_EQUAL(0.f, outFiltered(30,20)); - // Check it exist a vertical black band - BOOST_CHECK_EQUAL(0.f, outFiltered.block(0,10+3,40,20-2*3).array().abs().sum()); - - Image inCast = Image(in.cast()); - Image outFilteredCast = Image(outFiltered.cast()); - BOOST_CHECK_NO_THROW(writeImage("in_Scharr.png", inCast, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - BOOST_CHECK_NO_THROW(writeImage("out_SobelX.png", outFilteredCast, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - outFiltered.fill(0.0f); - ImageSobelYDerivative( in, outFiltered); - - // X dir - BOOST_CHECK_EQUAL(0.f, outFiltered(20,10)); - BOOST_CHECK_EQUAL(0.f, outFiltered(20,30)); - // Y dir - BOOST_CHECK_EQUAL(127.5f, outFiltered(10,20)); - BOOST_CHECK_EQUAL(-127.5f, outFiltered(30,20)); - // Check it exist a horizontal black band - BOOST_CHECK_EQUAL(0.f, outFiltered.block(10+3,0,20-2*3,40).array().abs().sum()); - outFilteredCast = Image(outFiltered.cast()); - BOOST_CHECK_NO_THROW(writeImage("out_SobelY.png", outFilteredCast, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + Image in(40, 40, true); + in.block(10, 10, 20, 20).fill(255.f); + + Image outFiltered(40, 40, true); + + ImageSobelXDerivative(in, outFiltered); + + // X dir + BOOST_CHECK_EQUAL(127.5f, outFiltered(20, 10)); + BOOST_CHECK_EQUAL(-127.5f, outFiltered(20, 30)); + // Y dir + BOOST_CHECK_EQUAL(0.f, outFiltered(10, 20)); + BOOST_CHECK_EQUAL(0.f, outFiltered(30, 20)); + // Check it exist a vertical black band + BOOST_CHECK_EQUAL(0.f, outFiltered.block(0, 10 + 3, 40, 20 - 2 * 3).array().abs().sum()); + + Image inCast = Image(in.cast()); + Image outFilteredCast = Image(outFiltered.cast()); + BOOST_CHECK_NO_THROW(writeImage("in_Scharr.png", inCast, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + BOOST_CHECK_NO_THROW( + writeImage("out_SobelX.png", outFilteredCast, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + outFiltered.fill(0.0f); + ImageSobelYDerivative(in, outFiltered); + + // X dir + BOOST_CHECK_EQUAL(0.f, outFiltered(20, 10)); + BOOST_CHECK_EQUAL(0.f, outFiltered(20, 30)); + // Y dir + BOOST_CHECK_EQUAL(127.5f, outFiltered(10, 20)); + BOOST_CHECK_EQUAL(-127.5f, outFiltered(30, 20)); + // Check it exist a horizontal black band + BOOST_CHECK_EQUAL(0.f, outFiltered.block(10 + 3, 0, 20 - 2 * 3, 40).array().abs().sum()); + outFilteredCast = Image(outFiltered.cast()); + BOOST_CHECK_NO_THROW( + writeImage("out_SobelY.png", outFilteredCast, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); } diff --git a/src/aliceVision/image/imageAlgo.cpp b/src/aliceVision/image/imageAlgo.cpp index a5616b1787..29d2932ce0 100644 --- a/src/aliceVision/image/imageAlgo.cpp +++ b/src/aliceVision/image/imageAlgo.cpp @@ -10,29 +10,26 @@ #include #include - #include #include #include #include -namespace aliceVision -{ -namespace imageAlgo -{ +namespace aliceVision { +namespace imageAlgo { float func_XYZtoLAB(float t) { - if(t > 0.008856f) - return std::pow(t, 1.0f/3.0f); + if (t > 0.008856f) + return std::pow(t, 1.0f / 3.0f); else return t / 0.1284f + 0.1379f; } float func_LABtoXYZ(float t) { - if(t > 0.2069f) + if (t > 0.2069f) return std::pow(t, 3.0f); else return 0.1284f * (t - 0.1379f); @@ -42,9 +39,7 @@ void RGBtoXYZ(oiio::ImageBuf::Iterator& pixel) { const Eigen::Vector3f rgb(pixel[0], pixel[1], pixel[2]); Eigen::Matrix3f M; - M << 0.4124f, 0.3576f, 0.1805f, - 0.2126f, 0.7152f, 0.0722f, - 0.0193f, 0.1192f, 0.9504f; + M << 0.4124f, 0.3576f, 0.1805f, 0.2126f, 0.7152f, 0.0722f, 0.0193f, 0.1192f, 0.9504f; const Eigen::Vector3f xyz_vec = M * rgb; pixel[0] = xyz_vec[0] * 0.9505f; @@ -56,9 +51,7 @@ void XYZtoRGB(oiio::ImageBuf::Iterator& pixel) { const Eigen::Vector3f xyz(pixel[0] / 0.9505f, pixel[1], pixel[2] / 1.0890f); Eigen::Matrix3f M; - M << 3.2406f, -1.5372f, -0.4986f, - -0.9689f, 1.8758f, 0.0415f, - 0.0557f, -0.2040f, 1.0570f; + M << 3.2406f, -1.5372f, -0.4986f, -0.9689f, 1.8758f, 0.0415f, 0.0557f, -0.2040f, 1.0570f; const Eigen::Vector3f rgb_vec = M * xyz; pixel[0] = rgb_vec[0]; @@ -101,7 +94,7 @@ void LABtoRGB(oiio::ImageBuf::Iterator& pixel) void processImage(oiio::ImageBuf& image, std::function&)> pixelFunc) { oiio::ImageBufAlgo::parallel_image(image.roi(), [&image, &pixelFunc](oiio::ROI roi) { - for(oiio::ImageBuf::Iterator pixel(image, roi); !pixel.done(); ++pixel) + for (oiio::ImageBuf::Iterator pixel(image, roi); !pixel.done(); ++pixel) { pixelFunc(pixel); } @@ -114,8 +107,7 @@ void processImage(oiio::ImageBuf& dst, const oiio::ImageBuf& src, std::function< processImage(dst, pixelFunc); } -void colorconvert(oiio::ImageBuf& imgBuf, const std::string& fromColorSpaceOIIOName, - image::EImageColorSpace toColorSpace) +void colorconvert(oiio::ImageBuf& imgBuf, const std::string& fromColorSpaceOIIOName, image::EImageColorSpace toColorSpace) { using image::EImageColorSpace; @@ -129,14 +121,13 @@ void colorconvert(oiio::ImageBuf& imgBuf, const std::string& fromColorSpaceOIION // We don't know about OIIO format and OIIO does not know about the destination format. // Convert to LINEAR and then do conversion as usual (colorconvert will handle // formats unknown to OIIO) - oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, fromColorSpaceOIIOName, - image::EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); + oiio::ImageBufAlgo::colorconvert( + imgBuf, imgBuf, fromColorSpaceOIIOName, image::EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); colorconvert(imgBuf, EImageColorSpace::LINEAR, toColorSpace); return; } // We don't know about OIIO format, but OIIO knows about the destination format - oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, fromColorSpaceOIIOName, - image::EImageColorSpace_enumToOIIOString(toColorSpace)); + oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, fromColorSpaceOIIOName, image::EImageColorSpace_enumToOIIOString(toColorSpace)); } else { @@ -145,68 +136,63 @@ void colorconvert(oiio::ImageBuf& imgBuf, const std::string& fromColorSpaceOIION } } -void colorconvert(oiio::ImageBuf& imgBuf, image::EImageColorSpace fromColorSpace, - image::EImageColorSpace toColorSpace) +void colorconvert(oiio::ImageBuf& imgBuf, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace) { using image::EImageColorSpace; - if(fromColorSpace == toColorSpace) + if (fromColorSpace == toColorSpace) return; - else if(toColorSpace == EImageColorSpace::LINEAR) + else if (toColorSpace == EImageColorSpace::LINEAR) { - if(fromColorSpace == EImageColorSpace::SRGB) - oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, - EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB), - EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); - else if(fromColorSpace == EImageColorSpace::XYZ) + if (fromColorSpace == EImageColorSpace::SRGB) + oiio::ImageBufAlgo::colorconvert( + imgBuf, imgBuf, EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB), EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); + else if (fromColorSpace == EImageColorSpace::XYZ) processImage(imgBuf, &XYZtoRGB); - else if(fromColorSpace == EImageColorSpace::LAB) + else if (fromColorSpace == EImageColorSpace::LAB) processImage(imgBuf, &LABtoRGB); } - else if(toColorSpace == EImageColorSpace::SRGB) + else if (toColorSpace == EImageColorSpace::SRGB) { - if(fromColorSpace == EImageColorSpace::XYZ) + if (fromColorSpace == EImageColorSpace::XYZ) processImage(imgBuf, &XYZtoRGB); - else if(fromColorSpace == EImageColorSpace::LAB) + else if (fromColorSpace == EImageColorSpace::LAB) processImage(imgBuf, &LABtoRGB); - oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, - EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR), - EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB)); + oiio::ImageBufAlgo::colorconvert( + imgBuf, imgBuf, EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR), EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB)); } - else if(toColorSpace == EImageColorSpace::XYZ) + else if (toColorSpace == EImageColorSpace::XYZ) { - if(fromColorSpace == EImageColorSpace::LINEAR) + if (fromColorSpace == EImageColorSpace::LINEAR) processImage(imgBuf, &RGBtoXYZ); - else if(fromColorSpace == EImageColorSpace::SRGB) + else if (fromColorSpace == EImageColorSpace::SRGB) { - oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, - EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB), - EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); + oiio::ImageBufAlgo::colorconvert( + imgBuf, imgBuf, EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB), EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); processImage(imgBuf, &RGBtoXYZ); } - else if(fromColorSpace == EImageColorSpace::LAB) + else if (fromColorSpace == EImageColorSpace::LAB) processImage(imgBuf, &LABtoXYZ); } - else if(toColorSpace == EImageColorSpace::LAB) + else if (toColorSpace == EImageColorSpace::LAB) { - if(fromColorSpace == EImageColorSpace::LINEAR) + if (fromColorSpace == EImageColorSpace::LINEAR) processImage(imgBuf, &RGBtoLAB); - else if(fromColorSpace == EImageColorSpace::SRGB) + else if (fromColorSpace == EImageColorSpace::SRGB) { - oiio::ImageBufAlgo::colorconvert(imgBuf, imgBuf, - EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB), - EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); + oiio::ImageBufAlgo::colorconvert( + imgBuf, imgBuf, EImageColorSpace_enumToOIIOString(EImageColorSpace::SRGB), EImageColorSpace_enumToOIIOString(EImageColorSpace::LINEAR)); processImage(imgBuf, &RGBtoLAB); } - else if(fromColorSpace == EImageColorSpace::XYZ) + else if (fromColorSpace == EImageColorSpace::XYZ) processImage(imgBuf, &XYZtoLAB); } - ALICEVISION_LOG_TRACE("Convert image from " << EImageColorSpace_enumToString(fromColorSpace) << " to " << EImageColorSpace_enumToString(toColorSpace)); + ALICEVISION_LOG_TRACE("Convert image from " << EImageColorSpace_enumToString(fromColorSpace) << " to " + << EImageColorSpace_enumToString(toColorSpace)); } -void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, - image::EImageColorSpace toColorSpace) +void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace) { oiio::ImageSpec imageSpec(image.Width(), image.Height(), 3, oiio::TypeDesc::FLOAT); auto* buffer = image.data(); @@ -215,8 +201,7 @@ void colorconvert(image::Image& image, image::EImageColorSpace colorconvert(imageBuf, fromColorSpace, toColorSpace); } -void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, - image::EImageColorSpace toColorSpace) +void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace) { oiio::ImageSpec imageSpec(image.Width(), image.Height(), 4, oiio::TypeDesc::FLOAT); auto* buffer = image.data(); @@ -225,8 +210,7 @@ void colorconvert(image::Image& image, image::EImageColorSpac colorconvert(imageBuf, fromColorSpace, toColorSpace); } -void colorconvert(oiio::ImageBuf& dst, const oiio::ImageBuf& src, - image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace) +void colorconvert(oiio::ImageBuf& dst, const oiio::ImageBuf& src, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace) { dst.copy(src); colorconvert(dst, fromColorSpace, toColorSpace); @@ -244,25 +228,26 @@ void resizeImage(oiio::TypeDesc typeDesc, const std::string& filter, float filterSize) { - const oiio::ImageBuf inBuf(oiio::ImageSpec(inWidth, inHeight, nchannels, typeDesc), - const_cast(inBuffer)); + const oiio::ImageBuf inBuf(oiio::ImageSpec(inWidth, inHeight, nchannels, typeDesc), const_cast(inBuffer)); oiio::ImageBuf outBuf(oiio::ImageSpec(outWidth, outHeight, nchannels, typeDesc), outBuffer); oiio::ImageBufAlgo::resize(outBuf, inBuf, filter, filterSize, oiio::ROI::All()); } -void resizeImage(int downscale, const image::Image& inImage, - image::Image& outImage, const std::string& filter, float filterSize) +void resizeImage(int downscale, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, + float filterSize) { const int outWidth = inImage.Width() / downscale; const int outHeight = inImage.Height() / downscale; outImage.resize(outWidth, outHeight); - resizeImage(oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), outWidth, outHeight, 1, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), outWidth, outHeight, 1, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter, float filterSize) +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter, float filterSize) { if (downscale <= 1) { @@ -274,24 +259,22 @@ void resizeImage(int downscale, image::Image& inoutImage, image::Image rescaled(outWidth, outHeight); - resizeImage(oiio::TypeDesc::UINT8, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 1, - inoutImage.data(), rescaled.data(), filter, filterSize); - + resizeImage( + oiio::TypeDesc::UINT8, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 1, inoutImage.data(), rescaled.data(), filter, filterSize); + inoutImage.swap(rescaled); } -void resizeImage(int downscale, const image::Image& inImage, - image::Image& outImage, const std::string& filter, float filterSize) +void resizeImage(int downscale, const image::Image& inImage, image::Image& outImage, const std::string& filter, float filterSize) { const int outWidth = inImage.Width() / downscale; const int outHeight = inImage.Height() / downscale; outImage.resize(outWidth, outHeight); - resizeImage(oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), outWidth, outHeight, 1, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), outWidth, outHeight, 1, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter, float filterSize) +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter, float filterSize) { if (downscale <= 1) { @@ -303,25 +286,26 @@ void resizeImage(int downscale, image::Image& inoutImage, image::Image rescaled(outWidth, outHeight); - resizeImage(oiio::TypeDesc::FLOAT, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 1, - inoutImage.data(), rescaled.data(), filter, filterSize); - + resizeImage( + oiio::TypeDesc::FLOAT, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 1, inoutImage.data(), rescaled.data(), filter, filterSize); + inoutImage.swap(rescaled); } -void resizeImage(int downscale, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(int downscale, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { const int outWidth = inImage.Width() / downscale; const int outHeight = inImage.Height() / downscale; outImage.resize(outWidth, outHeight); - resizeImage(oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), outWidth, outHeight, 3, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), outWidth, outHeight, 3, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter, float filterSize) +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter, float filterSize) { if (downscale <= 1) { @@ -333,25 +317,26 @@ void resizeImage(int downscale, image::Image& inoutImage, image::Image rescaled(outWidth, outHeight); - resizeImage(oiio::TypeDesc::UINT8, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 3, - inoutImage.data(), rescaled.data(), filter, filterSize); - + resizeImage( + oiio::TypeDesc::UINT8, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 3, inoutImage.data(), rescaled.data(), filter, filterSize); + inoutImage.swap(rescaled); } -void resizeImage(int downscale, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(int downscale, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { const int outWidth = inImage.Width() / downscale; const int outHeight = inImage.Height() / downscale; outImage.resize(outWidth, outHeight); - resizeImage(oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), outWidth, outHeight, 3, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), outWidth, outHeight, 3, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter, float filterSize) +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter, float filterSize) { if (downscale <= 1) { @@ -363,25 +348,26 @@ void resizeImage(int downscale, image::Image& inoutImage, image::Image rescaled(outWidth, outHeight); - resizeImage(oiio::TypeDesc::FLOAT, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 3, - inoutImage.data(), rescaled.data(), filter, filterSize); - + resizeImage( + oiio::TypeDesc::FLOAT, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 3, inoutImage.data(), rescaled.data(), filter, filterSize); + inoutImage.swap(rescaled); } -void resizeImage(int downscale, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(int downscale, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { const int outWidth = inImage.Width() / downscale; const int outHeight = inImage.Height() / downscale; outImage.resize(outWidth, outHeight); - resizeImage(oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), outWidth, outHeight, 4, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), outWidth, outHeight, 4, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter, float filterSize) +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter, float filterSize) { if (downscale <= 1) { @@ -393,25 +379,26 @@ void resizeImage(int downscale, image::Image& inoutImage, image::Image rescaled(outWidth, outHeight); - resizeImage(oiio::TypeDesc::UINT8, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 4, - inoutImage.data(), rescaled.data(), filter, filterSize); - + resizeImage( + oiio::TypeDesc::UINT8, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 4, inoutImage.data(), rescaled.data(), filter, filterSize); + inoutImage.swap(rescaled); } -void resizeImage(int downscale, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(int downscale, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { const int outWidth = inImage.Width() / downscale; const int outHeight = inImage.Height() / downscale; outImage.resize(outWidth, outHeight); - resizeImage(oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), outWidth, outHeight, 4, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), outWidth, outHeight, 4, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter, float filterSize) +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter, float filterSize) { if (downscale <= 1) { @@ -423,99 +410,117 @@ void resizeImage(int downscale, image::Image& inoutImage, image::Image rescaled(outWidth, outHeight); - resizeImage(oiio::TypeDesc::FLOAT, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 4, - inoutImage.data(), rescaled.data(), filter, filterSize); - + resizeImage( + oiio::TypeDesc::FLOAT, inoutImage.Width(), inoutImage.Height(), outWidth, outHeight, 4, inoutImage.data(), rescaled.data(), filter, filterSize); + inoutImage.swap(rescaled); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::UINT32, inImage.Width(), inImage.Height(), newWidth, newHeight, 1, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT32, inImage.Width(), inImage.Height(), newWidth, newHeight, 1, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), newWidth, newHeight, 1, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), newWidth, newHeight, 1, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), newWidth, newHeight, 1, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), newWidth, newHeight, 1, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), newWidth, newHeight, 3, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), newWidth, newHeight, 3, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), newWidth, newHeight, 3, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), newWidth, newHeight, 3, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), newWidth, newHeight, 4, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::UINT8, inImage.Width(), inImage.Height(), newWidth, newHeight, 4, inImage.data(), outImage.data(), filter, filterSize); } -void resizeImage(const int newWidth, const int newHeight, const image::Image &inImage, - image::Image &outImage, const std::string &filter, +void resizeImage(const int newWidth, + const int newHeight, + const image::Image& inImage, + image::Image& outImage, + const std::string& filter, float filterSize) { outImage.resize(newWidth, newHeight); - resizeImage(oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), newWidth, newHeight, 4, - inImage.data(), outImage.data(), filter, filterSize); + resizeImage( + oiio::TypeDesc::FLOAT, inImage.Width(), inImage.Height(), newWidth, newHeight, 4, inImage.data(), outImage.data(), filter, filterSize); } template void resampleImage(oiio::TypeDesc typeDesc, - int inWidth, - int inHeight, - int outWidth, - int outHeight, - int nchannels, - const T* inBuffer, - T* outBuffer, - bool interpolate) + int inWidth, + int inHeight, + int outWidth, + int outHeight, + int nchannels, + const T* inBuffer, + T* outBuffer, + bool interpolate) { - const oiio::ImageBuf inBuf(oiio::ImageSpec(inWidth, inHeight, nchannels, typeDesc), - const_cast(inBuffer)); + const oiio::ImageBuf inBuf(oiio::ImageSpec(inWidth, inHeight, nchannels, typeDesc), const_cast(inBuffer)); oiio::ImageBuf outBuf(oiio::ImageSpec(outWidth, outHeight, nchannels, typeDesc), outBuffer); oiio::ImageBufAlgo::resample(outBuf, inBuf, interpolate); } -void resampleImage(int outWidth, int outHeight, const image::Image& inImage, - image::Image& outImage, bool interpolate) +void resampleImage(int outWidth, int outHeight, const image::Image& inImage, image::Image& outImage, bool interpolate) { outImage.resize(outWidth, outHeight); - resampleImage(oiio::TypeDesc::UINT32, inImage.Width(), inImage.Height(), outWidth, outHeight, 1, - inImage.data(), outImage.data(), interpolate); + resampleImage(oiio::TypeDesc::UINT32, inImage.Width(), inImage.Height(), outWidth, outHeight, 1, inImage.data(), outImage.data(), interpolate); } template @@ -539,44 +544,45 @@ void convolveImage(oiio::TypeDesc typeDesc, void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, - const std::string& kernel, float kernelWidth, float kernelHeight) + const std::string& kernel, + float kernelWidth, + float kernelHeight) { outBuffer.resize(inBuffer.Width(), inBuffer.Height()); - convolveImage(oiio::TypeDesc::UCHAR, inBuffer.Width(), inBuffer.Height(), 1, - inBuffer.data(), outBuffer.data(), - kernel, kernelWidth, kernelHeight); + convolveImage( + oiio::TypeDesc::UCHAR, inBuffer.Width(), inBuffer.Height(), 1, inBuffer.data(), outBuffer.data(), kernel, kernelWidth, kernelHeight); } -void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, - const std::string& kernel, float kernelWidth, float kernelHeight) +void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, const std::string& kernel, float kernelWidth, float kernelHeight) { outBuffer.resize(inBuffer.Width(), inBuffer.Height()); - convolveImage(oiio::TypeDesc::UCHAR, inBuffer.Width(), inBuffer.Height(), 3, - inBuffer.data(), outBuffer.data(), - kernel, kernelWidth, kernelHeight); + convolveImage( + oiio::TypeDesc::UCHAR, inBuffer.Width(), inBuffer.Height(), 3, inBuffer.data(), outBuffer.data(), kernel, kernelWidth, kernelHeight); } -void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, - const std::string& kernel, float kernelWidth, float kernelHeight) +void convolveImage(const image::Image& inBuffer, + image::Image& outBuffer, + const std::string& kernel, + float kernelWidth, + float kernelHeight) { outBuffer.resize(inBuffer.Width(), inBuffer.Height()); - convolveImage(oiio::TypeDesc::FLOAT, inBuffer.Width(), inBuffer.Height(), 1, - inBuffer.data(), outBuffer.data(), - kernel, kernelWidth, kernelHeight); + convolveImage( + oiio::TypeDesc::FLOAT, inBuffer.Width(), inBuffer.Height(), 1, inBuffer.data(), outBuffer.data(), kernel, kernelWidth, kernelHeight); } void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, - const std::string& kernel, float kernelWidth, float kernelHeight) + const std::string& kernel, + float kernelWidth, + float kernelHeight) { outBuffer.resize(inBuffer.Width(), inBuffer.Height()); - convolveImage(oiio::TypeDesc::FLOAT, inBuffer.Width(), inBuffer.Height(), 3, - inBuffer.data(), outBuffer.data(), - kernel, kernelWidth, kernelHeight); + convolveImage( + oiio::TypeDesc::FLOAT, inBuffer.Width(), inBuffer.Height(), 3, inBuffer.data(), outBuffer.data(), kernel, kernelWidth, kernelHeight); } -void fillHoles(int inWidth, int inHeight, image::RGBfColor* colorBuffer, - const std::vector& alphaBuffer) +void fillHoles(int inWidth, int inHeight, image::RGBfColor* colorBuffer, const std::vector& alphaBuffer) { oiio::ImageBuf rgbBuf(oiio::ImageSpec(inWidth, inHeight, 3, oiio::TypeDesc::FLOAT), colorBuffer); const oiio::ImageBuf alphaBuf(oiio::ImageSpec(inWidth, inHeight, 1, oiio::TypeDesc::FLOAT), const_cast(alphaBuffer.data())); @@ -603,7 +609,8 @@ void fillHoles(image::Image& image, const std::vector& void imageDiff(const image::Image& inImg, const image::Image& inImgDownscaled, - image::Image& outImg, unsigned int downscale) + image::Image& outImg, + unsigned int downscale) { outImg.resize(inImg.Width(), inImg.Height()); @@ -611,42 +618,42 @@ void imageDiff(const image::Image& inImg, { for (int ix = 0; ix < inImg.Width(); ix++) { - outImg(iy, ix) = inImg(iy, ix) - getInterpolateColor(inImgDownscaled, - iy / downscale, ix / downscale); + outImg(iy, ix) = inImg(iy, ix) - getInterpolateColor(inImgDownscaled, iy / downscale, ix / downscale); } } } void laplacianPyramid(std::vector>& out_pyramidL, - const image::Image& image, int nbBand, unsigned int downscale) + const image::Image& image, + int nbBand, + unsigned int downscale) { assert(nbBand >= 1); image::Image img(image); - int outW = static_cast(img.Width()/downscale); - int outH = static_cast(img.Height()/downscale); + int outW = static_cast(img.Width() / downscale); + int outH = static_cast(img.Height() / downscale); image::Image imgDownscaled(outW, outH); out_pyramidL.resize(nbBand); - //Create Laplacian pyramid - for(int b = 0; b < nbBand-1; ++b) + // Create Laplacian pyramid + for (int b = 0; b < nbBand - 1; ++b) { imageAlgo::resizeImage(static_cast(downscale), img, imgDownscaled, "gaussian"); imageDiff(img, imgDownscaled, out_pyramidL[b], downscale); img.swap(imgDownscaled); -/* - outW = static_cast(outW/downscale); - outH = static_cast(outH/downscale); - imgDownscaled.resize(outW, outH); -*/ + /* + outW = static_cast(outW/downscale); + outH = static_cast(outH/downscale); + imgDownscaled.resize(outW, outH); + */ } - out_pyramidL[nbBand-1] = img; + out_pyramidL[nbBand - 1] = img; - for(std::size_t i = 0; i < out_pyramidL.size(); ++i) - ALICEVISION_LOG_DEBUG("laplacianDownscalePyramid: Size level " << i << " : " - << out_pyramidL[i].Width() << "x" << out_pyramidL[i].Height()); + for (std::size_t i = 0; i < out_pyramidL.size(); ++i) + ALICEVISION_LOG_DEBUG("laplacianDownscalePyramid: Size level " << i << " : " << out_pyramidL[i].Width() << "x" << out_pyramidL[i].Height()); } -} // namespace imageAlgo -} // namspace aliceVision +} // namespace imageAlgo +} // namespace aliceVision diff --git a/src/aliceVision/image/imageAlgo.hpp b/src/aliceVision/image/imageAlgo.hpp index 73f283831c..3084ac330c 100644 --- a/src/aliceVision/image/imageAlgo.hpp +++ b/src/aliceVision/image/imageAlgo.hpp @@ -7,16 +7,13 @@ #include #include - namespace oiio = OIIO; -namespace aliceVision -{ +namespace aliceVision { class rgb; -namespace imageAlgo -{ +namespace imageAlgo { void RGBtoXYZ(oiio::ImageBuf::Iterator& pixel); void XYZtoRGB(oiio::ImageBuf::Iterator& pixel); @@ -35,16 +32,11 @@ void LABtoRGB(oiio::ImageBuf::Iterator& pixel); void processImage(oiio::ImageBuf& image, std::function&)> pixelFunc); void processImage(oiio::ImageBuf& dst, const oiio::ImageBuf& src, std::function&)> pixelFunc); -void colorconvert(oiio::ImageBuf& image, const std::string& fromColorSpaceOIIOName, - image::EImageColorSpace toColorSpace); -void colorconvert(oiio::ImageBuf& image, image::EImageColorSpace fromColorSpace, - image::EImageColorSpace toColorSpace); -void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, - image::EImageColorSpace toColorSpace); -void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, - image::EImageColorSpace toColorSpace); -void colorconvert(oiio::ImageBuf& dst, const oiio::ImageBuf& src, - image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace); +void colorconvert(oiio::ImageBuf& image, const std::string& fromColorSpaceOIIOName, image::EImageColorSpace toColorSpace); +void colorconvert(oiio::ImageBuf& image, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace); +void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace); +void colorconvert(image::Image& image, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace); +void colorconvert(oiio::ImageBuf& dst, const oiio::ImageBuf& src, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace); /** * @brief Resize a given image buffer. @@ -56,24 +48,36 @@ void colorconvert(oiio::ImageBuf& dst, const oiio::ImageBuf& src, * See openImageIO documentation "ImageBufAlgo filtername" * @param[in] filterSize The resize filter size */ -void resizeImage(int downscale, const image::Image& inImage, +void resizeImage(int downscale, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int downscale, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int downscale, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int downscale, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int downscale, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int downscale, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); + const std::string& filter = "", + float filterSize = 0); /** * @brief Resize a given image buffer. @@ -86,27 +90,48 @@ void resizeImage(int downscale, const image::Image& inImage, * See openImageIO documentation "ImageBufAlgo filtername" * @param[in] filterSize The resize filter size */ -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int newWidth, int newHeight, const image::Image& inImage, + const std::string& filter = "", + float filterSize = 0); +void resizeImage(int newWidth, + int newHeight, + const image::Image& inImage, image::Image& outImage, - const std::string& filter = "", float filterSize = 0); + const std::string& filter = "", + float filterSize = 0); /** * @brief Resize a given image buffer in place. @@ -117,19 +142,12 @@ void resizeImage(int newWidth, int newHeight, const image::Image& inoutImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter = "", float filterSize = 0); -void resizeImage(int downscale, image::Image& inoutImage, - const std::string& filter = "", float filterSize = 0); - +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter = "", float filterSize = 0); +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter = "", float filterSize = 0); +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter = "", float filterSize = 0); +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter = "", float filterSize = 0); +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter = "", float filterSize = 0); +void resizeImage(int downscale, image::Image& inoutImage, const std::string& filter = "", float filterSize = 0); /** * @brief resample a given image buffer. @@ -139,8 +157,7 @@ void resizeImage(int downscale, image::Image& inoutImage, * @param[out] outImage The output image buffer * @param[in] interpolate use interpolation (bilinear) ? */ -void resampleImage(int newWidth, int newHeight, const image::Image& inImage, - image::Image& outImage, bool interpolate); +void resampleImage(int newWidth, int newHeight, const image::Image& inImage, image::Image& outImage, bool interpolate); /** * @brief convolve a given image buffer @@ -155,21 +172,26 @@ void resampleImage(int newWidth, int newHeight, const image::Image& inIm void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, const std::string& kernel = "gaussian", - float kernelWidth = 5.0f, float kernelHeight = 5.0f); + float kernelWidth = 5.0f, + float kernelHeight = 5.0f); -void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, +void convolveImage(const image::Image& inBuffer, + image::Image& outBuffer, const std::string& kernel = "gaussian", - float kernelWidth = 5.0f, float kernelHeight = 5.0f); + float kernelWidth = 5.0f, + float kernelHeight = 5.0f); -void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, +void convolveImage(const image::Image& inBuffer, + image::Image& outBuffer, const std::string& kernel = "gaussian", - float kernelWidth = 5.0f, float kernelHeight = 5.0f); + float kernelWidth = 5.0f, + float kernelHeight = 5.0f); void convolveImage(const image::Image& inBuffer, image::Image& outBuffer, const std::string& kernel = "gaussian", - float kernelWidth = 5.0f, float kernelHeight = 5.0f); - + float kernelWidth = 5.0f, + float kernelHeight = 5.0f); /** * @brief fill holes in a given image buffer with plausible values @@ -179,25 +201,27 @@ void convolveImage(const image::Image& inBuffer, void fillHoles(image::Image& image, const std::vector& alphaBuffer); /** -* @brief Calculate the difference between images of different sizes -* @param [inImgDownscaled] the smaller image -* @param [outImg] the difference -* @param [downscale] the downscale coefficient between image sizes -*/ + * @brief Calculate the difference between images of different sizes + * @param [inImgDownscaled] the smaller image + * @param [outImg] the difference + * @param [downscale] the downscale coefficient between image sizes + */ void imageDiff(const image::Image& inImg, const image::Image& inImgDownscaled, - image::Image& outImg, unsigned int downscale); + image::Image& outImg, + unsigned int downscale); /** -* @brief Calculate the laplacian pyramid of a given image, -* ie. its decomposition in frequency bands -* @param [out_pyramidL] the laplacian pyramid -* @param [nbBand] the number of frequency bands -* @param [downscale] the downscale coefficient between floors of the pyramid -*/ + * @brief Calculate the laplacian pyramid of a given image, + * ie. its decomposition in frequency bands + * @param [out_pyramidL] the laplacian pyramid + * @param [nbBand] the number of frequency bands + * @param [downscale] the downscale coefficient between floors of the pyramid + */ void laplacianPyramid(std::vector>& out_pyramidL, - const image::Image& image, int nbBand, unsigned int downscale); - + const image::Image& image, + int nbBand, + unsigned int downscale); -} -} +} // namespace imageAlgo +} // namespace aliceVision diff --git a/src/aliceVision/image/imageCaching_test.cpp b/src/aliceVision/image/imageCaching_test.cpp index 921127ee98..1e832252cd 100644 --- a/src/aliceVision/image/imageCaching_test.cpp +++ b/src/aliceVision/image/imageCaching_test.cpp @@ -13,7 +13,8 @@ using namespace aliceVision; using namespace aliceVision::image; -BOOST_AUTO_TEST_CASE(create_cache) { +BOOST_AUTO_TEST_CASE(create_cache) +{ ImageCache cache(256, 1024, EImageColorSpace::LINEAR); BOOST_CHECK_EQUAL(cache.info().capacity, 256 * 1024 * 1024); BOOST_CHECK_EQUAL(cache.info().maxSize, 1024 * 1024 * 1024); @@ -22,13 +23,15 @@ BOOST_AUTO_TEST_CASE(create_cache) { BOOST_CHECK_EQUAL(cache.info().contentSize, 0); } -BOOST_AUTO_TEST_CASE(load_without_space) { +BOOST_AUTO_TEST_CASE(load_without_space) +{ ImageCache cache(0, 0, EImageColorSpace::LINEAR); const std::string filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; BOOST_CHECK_THROW(auto img = cache.get(filename), std::exception); } -BOOST_AUTO_TEST_CASE(load_image_twice) { +BOOST_AUTO_TEST_CASE(load_image_twice) +{ ImageCache cache(256, 1024, EImageColorSpace::LINEAR); const std::string filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; auto img1 = cache.get(filename); @@ -40,7 +43,8 @@ BOOST_AUTO_TEST_CASE(load_image_twice) { BOOST_CHECK_EQUAL(cache.info().nbLoadFromCache, 1); } -BOOST_AUTO_TEST_CASE(load_all_pixel_types) { +BOOST_AUTO_TEST_CASE(load_all_pixel_types) +{ ImageCache cache(256, 1024, EImageColorSpace::LINEAR); const std::string filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; auto imgUChar = cache.get(filename); diff --git a/src/aliceVision/image/image_test.cpp b/src/aliceVision/image/image_test.cpp index 9c823a49b5..618325a55e 100644 --- a/src/aliceVision/image/image_test.cpp +++ b/src/aliceVision/image/image_test.cpp @@ -19,70 +19,70 @@ using namespace aliceVision::image; BOOST_AUTO_TEST_CASE(Image_Basis) { - //-- Gray(unsigned char) Image creation - Image imaGray(10,10); - imaGray(1,1) = 1; //-- Pixel modification - imaGray(2,2) = 2; - imaGray(5,0) = 2; - - //cout << imaGray << endl << endl; - //-- Get raw ptr to image data : - const unsigned char * ptr = imaGray.data(); - ((unsigned char*)ptr)[0] = 2; - std::fill(((unsigned char*)ptr+9*10),((unsigned char*)ptr+10*10),2); - //cout << "After" << endl << imaGray; - - // Construction by re-copy - Image imageGray2(imaGray); - - // Construction by matrix - Eigen::Matrix matrix(5,5); - Image imageGray3 = matrix; - - //- Get back matrix contained in the image - matrix = imaGray.GetMat(); - - // clone of a matrix - Image imageGray4; - imageGray4 = matrix; - - Image imageGray5; - imageGray5 = imaGray; - - //-- RGB Image creation - Image imaRGB(10,10); - imaRGB(0,0) = RGBColor(0,1,2); - - //-- RGBA Image creation - Image imaRGBA(10,10); - imaRGBA(0,0) = RGBAColor(0,1,2,1); - imaRGBA(1,0) = RGBAColor(1,1,1); - - // Image resizing - Image imaToResize; - imaToResize.resize(5,10); - BOOST_CHECK_EQUAL(10, imaToResize.Height()); - BOOST_CHECK_EQUAL(5, imaToResize.Width()); + //-- Gray(unsigned char) Image creation + Image imaGray(10, 10); + imaGray(1, 1) = 1; //-- Pixel modification + imaGray(2, 2) = 2; + imaGray(5, 0) = 2; + + // cout << imaGray << endl << endl; + //-- Get raw ptr to image data : + const unsigned char* ptr = imaGray.data(); + ((unsigned char*)ptr)[0] = 2; + std::fill(((unsigned char*)ptr + 9 * 10), ((unsigned char*)ptr + 10 * 10), 2); + // cout << "After" << endl << imaGray; + + // Construction by re-copy + Image imageGray2(imaGray); + + // Construction by matrix + Eigen::Matrix matrix(5, 5); + Image imageGray3 = matrix; + + //- Get back matrix contained in the image + matrix = imaGray.GetMat(); + + // clone of a matrix + Image imageGray4; + imageGray4 = matrix; + + Image imageGray5; + imageGray5 = imaGray; + + //-- RGB Image creation + Image imaRGB(10, 10); + imaRGB(0, 0) = RGBColor(0, 1, 2); + + //-- RGBA Image creation + Image imaRGBA(10, 10); + imaRGBA(0, 0) = RGBAColor(0, 1, 2, 1); + imaRGBA(1, 0) = RGBAColor(1, 1, 1); + + // Image resizing + Image imaToResize; + imaToResize.resize(5, 10); + BOOST_CHECK_EQUAL(10, imaToResize.Height()); + BOOST_CHECK_EQUAL(5, imaToResize.Width()); } BOOST_AUTO_TEST_CASE(Image_PixelTypes) { - RGBColor a(BLACK); - // RGBColor c(0); // Not accepted because can cause bad pixel affectation value (mixed type...) - // The following issue must used : (at your own risk) - RGBColor b(static_cast(0)); - RGBAColor d(BLACK, 255); + RGBColor a(BLACK); + // RGBColor c(0); // Not accepted because can cause bad pixel affectation value (mixed type...) + // The following issue must used : (at your own risk) + RGBColor b(static_cast(0)); + RGBAColor d(BLACK, 255); } BOOST_AUTO_TEST_CASE(Image_ImageConverter) { - Image imaColorRGB(5,5); - imaColorRGB.fill(RGBColor(10,10,10)); - Image imaGray; - ConvertPixelType(imaColorRGB, &imaGray); - - //RGBA - Image imaColorRGBA(5,5); - imaColorRGBA.fill(RGBAColor(10,10,10, 255)); - ConvertPixelType(imaColorRGBA, &imaGray); + Image imaColorRGB(5, 5); + imaColorRGB.fill(RGBColor(10, 10, 10)); + Image imaGray; + ConvertPixelType(imaColorRGB, &imaGray); + + // RGBA + Image imaColorRGBA(5, 5); + imaColorRGBA.fill(RGBAColor(10, 10, 10, 255)); + ConvertPixelType(imaColorRGBA, &imaGray); } diff --git a/src/aliceVision/image/io.cpp b/src/aliceVision/image/io.cpp index e54e85139c..e2e5c37ec7 100644 --- a/src/aliceVision/image/io.cpp +++ b/src/aliceVision/image/io.cpp @@ -31,32 +31,34 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace image { - EImageColorSpace getImageColorSpace(const std::string& imagePath) { oiio::ImageSpec metadataSpec; metadataSpec.extra_attribs = readImageMetadata(imagePath); - std::string colorSpace = metadataSpec.get_string_attribute("AliceVision:ColorSpace", ""); // default image color space is empty + std::string colorSpace = metadataSpec.get_string_attribute("AliceVision:ColorSpace", ""); // default image color space is empty if (!colorSpace.empty()) { - ALICEVISION_LOG_TRACE("Read image " << imagePath << " (encoded in " << colorSpace << " colorspace according to AliceVision:ColorSpace metadata)."); + ALICEVISION_LOG_TRACE("Read image " << imagePath << " (encoded in " << colorSpace + << " colorspace according to AliceVision:ColorSpace metadata)."); } else { - colorSpace = metadataSpec.get_string_attribute("oiio:ColorSpace", ""); // Check oiio metadata + colorSpace = metadataSpec.get_string_attribute("oiio:ColorSpace", ""); // Check oiio metadata if ((colorSpace == "Linear") || (colorSpace == "")) { const std::string colorSpaceFromFileName = getGlobalColorConfigOCIO().getColorSpaceFromFilepath(imagePath); if (!colorSpaceFromFileName.empty()) { - ALICEVISION_LOG_TRACE("Read image " << imagePath << " (encoded in " << colorSpaceFromFileName << " colorspace according to file name)."); + ALICEVISION_LOG_TRACE("Read image " << imagePath << " (encoded in " << colorSpaceFromFileName + << " colorspace according to file name)."); colorSpace = colorSpaceFromFileName; } else if (!colorSpace.empty()) { - ALICEVISION_LOG_TRACE("Read image " << imagePath << " (encoded in " << colorSpace << " colorspace according to oiio:ColorSpace metadata)."); + ALICEVISION_LOG_TRACE("Read image " << imagePath << " (encoded in " << colorSpace + << " colorspace according to oiio:ColorSpace metadata)."); } else { @@ -72,7 +74,8 @@ EImageColorSpace getImageColorSpace(const std::string& imagePath) const std::string ext = imagePath.substr(npos + 1); const std::string forcedColorSpace = (ext == "exr" || ext == "EXR") ? "linear" : "sRGB"; - ALICEVISION_LOG_WARNING("The color space " << colorSpace << " detected for " << imagePath << " is not supported. Force Color space to " << forcedColorSpace << "."); + ALICEVISION_LOG_WARNING("The color space " << colorSpace << " detected for " << imagePath << " is not supported. Force Color space to " + << forcedColorSpace << "."); colorSpace = forcedColorSpace; } @@ -81,72 +84,80 @@ EImageColorSpace getImageColorSpace(const std::string& imagePath) std::string EImageFileType_informations() { - return "Image file type :\n" - "* jpg \n" - "* png \n" - "* tif \n" - "* exr (half)"; + return "Image file type :\n" + "* jpg \n" + "* png \n" + "* tif \n" + "* exr (half)"; } EImageFileType EImageFileType_stringToEnum(const std::string& imageFileType) { - const std::string type = boost::to_lower_copy(imageFileType); + const std::string type = boost::to_lower_copy(imageFileType); - if(type == "jpg" || type == "jpeg") return EImageFileType::JPEG; - if(type == "png") return EImageFileType::PNG; - if(type == "tif" || type == "tiff") return EImageFileType::TIFF; - if(type == "exr") return EImageFileType::EXR; + if (type == "jpg" || type == "jpeg") + return EImageFileType::JPEG; + if (type == "png") + return EImageFileType::PNG; + if (type == "tif" || type == "tiff") + return EImageFileType::TIFF; + if (type == "exr") + return EImageFileType::EXR; - throw std::out_of_range("Invalid image file type: " + imageFileType); + throw std::out_of_range("Invalid image file type: " + imageFileType); } std::string EImageFileType_enumToString(const EImageFileType imageFileType) { - switch(imageFileType) - { - case EImageFileType::JPEG: return "jpg"; - case EImageFileType::PNG: return "png"; - case EImageFileType::TIFF: return "tif"; - case EImageFileType::EXR: return "exr"; - case EImageFileType::NONE: return "none"; - } - throw std::out_of_range("Invalid EImageType enum"); + switch (imageFileType) + { + case EImageFileType::JPEG: + return "jpg"; + case EImageFileType::PNG: + return "png"; + case EImageFileType::TIFF: + return "tif"; + case EImageFileType::EXR: + return "exr"; + case EImageFileType::NONE: + return "none"; + } + throw std::out_of_range("Invalid EImageType enum"); } -std::ostream& operator<<(std::ostream& os, EImageFileType imageFileType) -{ - return os << EImageFileType_enumToString(imageFileType); -} +std::ostream& operator<<(std::ostream& os, EImageFileType imageFileType) { return os << EImageFileType_enumToString(imageFileType); } std::istream& operator>>(std::istream& in, EImageFileType& imageFileType) { - std::string token; - in >> token; - imageFileType = EImageFileType_stringToEnum(token); - return in; + std::string token; + in >> token; + imageFileType = EImageFileType_stringToEnum(token); + return in; } std::vector getSupportedExtensions() { - std::vector supportedExtensions; + std::vector supportedExtensions; - // Map containing the parsed "extension_list" with each supported format and its associated extensions - static std::map> extensionList = oiio::get_extension_map(); + // Map containing the parsed "extension_list" with each supported format and its associated extensions + static std::map> extensionList = oiio::get_extension_map(); - for (auto& format : extensionList) { - for (auto& extension : format.second) { - supportedExtensions.push_back(extension.insert(0, ".")); + for (auto& format : extensionList) + { + for (auto& extension : format.second) + { + supportedExtensions.push_back(extension.insert(0, ".")); + } } - } - return supportedExtensions; + return supportedExtensions; } bool isSupported(const std::string& extension) { - static const std::vector supportedExtensions = getSupportedExtensions(); - const auto start = supportedExtensions.begin(); - const auto end = supportedExtensions.end(); - return (std::find(start, end, boost::to_lower_copy(extension)) != end); + static const std::vector supportedExtensions = getSupportedExtensions(); + const auto start = supportedExtensions.begin(); + const auto end = supportedExtensions.end(); + return (std::find(start, end, boost::to_lower_copy(extension)) != end); } bool isVideoExtension(const std::string& extension) @@ -154,9 +165,7 @@ bool isVideoExtension(const std::string& extension) // List provided by OpenImageIO: // https://openimageio.readthedocs.io/en/latest/builtinplugins.html#movie-formats-using-ffmpeg static const std::array supportedExtensions = { - ".avi", ".qt", ".mov", ".mp4", ".m4a", ".m4v", - ".3gp", ".3g2", ".mj2", ".m4v", ".mpg" - }; + ".avi", ".qt", ".mov", ".mp4", ".m4a", ".m4v", ".3gp", ".3g2", ".mj2", ".m4v", ".mpg"}; const auto start = supportedExtensions.begin(); const auto end = supportedExtensions.end(); return (std::find(start, end, boost::to_lower_copy(extension)) != end); @@ -164,23 +173,26 @@ bool isVideoExtension(const std::string& extension) std::string EStorageDataType_informations() { - return EStorageDataType_enumToString(EStorageDataType::Float) + ", " + - EStorageDataType_enumToString(EStorageDataType::Half) + ", " + - EStorageDataType_enumToString(EStorageDataType::HalfFinite) + ", " + - EStorageDataType_enumToString(EStorageDataType::Auto) + ", " + - EStorageDataType_enumToString(EStorageDataType::Undefined); + return EStorageDataType_enumToString(EStorageDataType::Float) + ", " + EStorageDataType_enumToString(EStorageDataType::Half) + ", " + + EStorageDataType_enumToString(EStorageDataType::HalfFinite) + ", " + EStorageDataType_enumToString(EStorageDataType::Auto) + ", " + + EStorageDataType_enumToString(EStorageDataType::Undefined); } EStorageDataType EStorageDataType_stringToEnum(const std::string& dataType) { std::string type = dataType; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower - - if (type == "float") return EStorageDataType::Float; - if (type == "half") return EStorageDataType::Half; - if (type == "halffinite") return EStorageDataType::HalfFinite; - if (type == "auto") return EStorageDataType::Auto; - if (type == "undefined") return EStorageDataType::Undefined; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); // tolower + + if (type == "float") + return EStorageDataType::Float; + if (type == "half") + return EStorageDataType::Half; + if (type == "halffinite") + return EStorageDataType::HalfFinite; + if (type == "auto") + return EStorageDataType::Auto; + if (type == "undefined") + return EStorageDataType::Undefined; throw std::out_of_range("Invalid EStorageDataType: " + dataType); } @@ -189,19 +201,21 @@ std::string EStorageDataType_enumToString(const EStorageDataType dataType) { switch (dataType) { - case EStorageDataType::Float: return "float"; - case EStorageDataType::Half: return "half"; - case EStorageDataType::HalfFinite: return "halfFinite"; - case EStorageDataType::Auto: return "auto"; - case EStorageDataType::Undefined: return "undefined"; + case EStorageDataType::Float: + return "float"; + case EStorageDataType::Half: + return "half"; + case EStorageDataType::HalfFinite: + return "halfFinite"; + case EStorageDataType::Auto: + return "auto"; + case EStorageDataType::Undefined: + return "undefined"; } throw std::out_of_range("Invalid EStorageDataType enum"); } -std::ostream& operator<<(std::ostream& os, EStorageDataType dataType) -{ - return os << EStorageDataType_enumToString(dataType); -} +std::ostream& operator<<(std::ostream& os, EStorageDataType dataType) { return os << EStorageDataType_enumToString(dataType); } std::istream& operator>>(std::istream& in, EStorageDataType& dataType) { @@ -213,35 +227,42 @@ std::istream& operator>>(std::istream& in, EStorageDataType& dataType) std::string EImageExrCompression_informations() { - return EImageExrCompression_enumToString(EImageExrCompression::None) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::Auto) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::RLE) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::ZIP) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::ZIPS) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::PIZ) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::PXR24) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::B44) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::B44A) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::DWAA) + ", " + - EImageExrCompression_enumToString(EImageExrCompression::DWAB); + return EImageExrCompression_enumToString(EImageExrCompression::None) + ", " + EImageExrCompression_enumToString(EImageExrCompression::Auto) + + ", " + EImageExrCompression_enumToString(EImageExrCompression::RLE) + ", " + EImageExrCompression_enumToString(EImageExrCompression::ZIP) + + ", " + EImageExrCompression_enumToString(EImageExrCompression::ZIPS) + ", " + + EImageExrCompression_enumToString(EImageExrCompression::PIZ) + ", " + EImageExrCompression_enumToString(EImageExrCompression::PXR24) + + ", " + EImageExrCompression_enumToString(EImageExrCompression::B44) + ", " + + EImageExrCompression_enumToString(EImageExrCompression::B44A) + ", " + EImageExrCompression_enumToString(EImageExrCompression::DWAA) + + ", " + EImageExrCompression_enumToString(EImageExrCompression::DWAB); } EImageExrCompression EImageExrCompression_stringToEnum(const std::string& exrCompression) { std::string type = exrCompression; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower - - if (type == "none") return EImageExrCompression::None; - if (type == "auto") return EImageExrCompression::Auto; - if (type == "rle") return EImageExrCompression::RLE; - if (type == "zip") return EImageExrCompression::ZIP; - if (type == "zips") return EImageExrCompression::ZIPS; - if (type == "piz") return EImageExrCompression::PIZ; - if (type == "pxr24") return EImageExrCompression::PXR24; - if (type == "b44") return EImageExrCompression::B44; - if (type == "b44a") return EImageExrCompression::B44A; - if (type == "dwaa") return EImageExrCompression::DWAA; - if (type == "dwab") return EImageExrCompression::DWAB; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); // tolower + + if (type == "none") + return EImageExrCompression::None; + if (type == "auto") + return EImageExrCompression::Auto; + if (type == "rle") + return EImageExrCompression::RLE; + if (type == "zip") + return EImageExrCompression::ZIP; + if (type == "zips") + return EImageExrCompression::ZIPS; + if (type == "piz") + return EImageExrCompression::PIZ; + if (type == "pxr24") + return EImageExrCompression::PXR24; + if (type == "b44") + return EImageExrCompression::B44; + if (type == "b44a") + return EImageExrCompression::B44A; + if (type == "dwaa") + return EImageExrCompression::DWAA; + if (type == "dwab") + return EImageExrCompression::DWAB; throw std::out_of_range("Invalid EImageExrCompression: " + exrCompression); } @@ -250,25 +271,33 @@ std::string EImageExrCompression_enumToString(const EImageExrCompression exrComp { switch (exrCompression) { - case EImageExrCompression::None: return "none"; - case EImageExrCompression::Auto: return "auto"; - case EImageExrCompression::RLE: return "rle"; - case EImageExrCompression::ZIP: return "zip"; - case EImageExrCompression::ZIPS: return "zips"; - case EImageExrCompression::PIZ: return "piz"; - case EImageExrCompression::PXR24: return "pxr24"; - case EImageExrCompression::B44: return "b44"; - case EImageExrCompression::B44A: return "b44a"; - case EImageExrCompression::DWAA: return "dwaa"; - case EImageExrCompression::DWAB: return "dwab"; + case EImageExrCompression::None: + return "none"; + case EImageExrCompression::Auto: + return "auto"; + case EImageExrCompression::RLE: + return "rle"; + case EImageExrCompression::ZIP: + return "zip"; + case EImageExrCompression::ZIPS: + return "zips"; + case EImageExrCompression::PIZ: + return "piz"; + case EImageExrCompression::PXR24: + return "pxr24"; + case EImageExrCompression::B44: + return "b44"; + case EImageExrCompression::B44A: + return "b44a"; + case EImageExrCompression::DWAA: + return "dwaa"; + case EImageExrCompression::DWAB: + return "dwab"; } throw std::out_of_range("Invalid EImageExrCompression enum"); } -std::ostream& operator<<(std::ostream& os, EImageExrCompression exrCompression) -{ - return os << EImageExrCompression_enumToString(exrCompression); -} +std::ostream& operator<<(std::ostream& os, EImageExrCompression exrCompression) { return os << EImageExrCompression_enumToString(exrCompression); } std::istream& operator>>(std::istream& in, EImageExrCompression& exrCompression) { @@ -288,7 +317,7 @@ std::string EImageQuality_informations() EImageQuality EImageQuality_stringToEnum(const std::string& imageQuality) { std::string type = imageQuality; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower + std::transform(type.begin(), type.end(), type.begin(), ::tolower); // tolower if (type == "optimized") return EImageQuality::OPTIMIZED; @@ -300,18 +329,17 @@ EImageQuality EImageQuality_stringToEnum(const std::string& imageQuality) std::string EImageQuality_enumToString(const EImageQuality imageQuality) { - switch(imageQuality) + switch (imageQuality) { - case EImageQuality::OPTIMIZED: return "optimized"; - case EImageQuality::LOSSLESS: return "lossless"; + case EImageQuality::OPTIMIZED: + return "optimized"; + case EImageQuality::LOSSLESS: + return "lossless"; } throw std::out_of_range("Invalid EImageQuality enum"); } -std::ostream& operator<<(std::ostream& os, EImageQuality imageQuality) -{ - return os << EImageQuality_enumToString(imageQuality); -} +std::ostream& operator<<(std::ostream& os, EImageQuality imageQuality) { return os << EImageQuality_enumToString(imageQuality); } std::istream& operator>>(std::istream& in, EImageQuality& imageQuality) { @@ -321,14 +349,12 @@ std::istream& operator>>(std::istream& in, EImageQuality& imageQuality) return in; } -bool isSupportedUndistortFormat(const std::string &ext) +bool isSupportedUndistortFormat(const std::string& ext) { - static const std::array supportedExtensions = { - ".jpg", ".jpeg", ".png", ".tif", ".tiff", ".exr" - }; + static const std::array supportedExtensions = {".jpg", ".jpeg", ".png", ".tif", ".tiff", ".exr"}; const auto start = supportedExtensions.begin(); const auto end = supportedExtensions.end(); - return(std::find(start, end, boost::to_lower_copy(ext)) != end); + return (std::find(start, end, boost::to_lower_copy(ext)) != end); } std::string ERawColorInterpretation_informations() @@ -344,7 +370,7 @@ std::string ERawColorInterpretation_informations() ERawColorInterpretation ERawColorInterpretation_stringToEnum(const std::string& rawColorInterpretation) { std::string type = rawColorInterpretation; - std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower + std::transform(type.begin(), type.end(), type.begin(), ::tolower); // tolower if (type == "none" || type == "") return ERawColorInterpretation::None; @@ -360,19 +386,24 @@ ERawColorInterpretation ERawColorInterpretation_stringToEnum(const std::string& return ERawColorInterpretation::Auto; throw std::out_of_range("Invalid raw color interpretation : " + rawColorInterpretation); - } std::string ERawColorInterpretation_enumToString(const ERawColorInterpretation rawColorInterpretation) { switch (rawColorInterpretation) { - case ERawColorInterpretation::None: return "none"; - case ERawColorInterpretation::LibRawNoWhiteBalancing: return "librawnowhitebalancing"; - case ERawColorInterpretation::LibRawWhiteBalancing: return "librawwhitebalancing"; - case ERawColorInterpretation::DcpLinearProcessing: return "dcpLinearprocessing"; - case ERawColorInterpretation::DcpMetadata: return "dcpmetadata"; - case ERawColorInterpretation::Auto: return "auto"; + case ERawColorInterpretation::None: + return "none"; + case ERawColorInterpretation::LibRawNoWhiteBalancing: + return "librawnowhitebalancing"; + case ERawColorInterpretation::LibRawWhiteBalancing: + return "librawwhitebalancing"; + case ERawColorInterpretation::DcpLinearProcessing: + return "dcpLinearprocessing"; + case ERawColorInterpretation::DcpMetadata: + return "dcpmetadata"; + case ERawColorInterpretation::Auto: + return "auto"; } throw std::out_of_range("Invalid ERawColorInterpretation enum"); } @@ -393,10 +424,10 @@ std::istream& operator>>(std::istream& in, ERawColorInterpretation& rawColorInte // Warning: type conversion problems from string to param value, we may lose some metadata with string maps oiio::ParamValueList getMetadataFromMap(const std::map& metadataMap) { - oiio::ParamValueList metadata; - for(const auto& metadataPair : metadataMap) - metadata.push_back(oiio::ParamValue(metadataPair.first, metadataPair.second)); - return metadata; + oiio::ParamValueList metadata; + for (const auto& metadataPair : metadataMap) + metadata.push_back(oiio::ParamValue(metadataPair.first, metadataPair.second)); + return metadata; } std::map getMapFromMetadata(const oiio::ParamValueList& metadata) @@ -419,29 +450,26 @@ oiio::ParamValueList readImageMetadata(const std::string& path, int& width, int& oiio::ImageSpec readImageSpec(const std::string& path) { - oiio::ImageSpec configSpec; -#if OIIO_VERSION >= (10000 * 2 + 100 * 4 + 12) // OIIO_VERSION >= 2.4.12 + oiio::ImageSpec configSpec; +#if OIIO_VERSION >= (10000 * 2 + 100 * 4 + 12) // OIIO_VERSION >= 2.4.12 // To disable the application of the orientation, we need the PR https://github.com/OpenImageIO/oiio/pull/3669, // so we can disable the auto orientation and keep the metadata. - configSpec.attribute("raw:user_flip", 0); // disable auto rotation of the image buffer but keep exif metadata orientation valid -#endif + configSpec.attribute("raw:user_flip", 0); // disable auto rotation of the image buffer but keep exif metadata orientation valid +#endif - std::unique_ptr in(oiio::ImageInput::open(path, &configSpec)); + std::unique_ptr in(oiio::ImageInput::open(path, &configSpec)); - if(!in) - throw std::runtime_error("Can't find/open image file '" + path + "'."); + if (!in) + throw std::runtime_error("Can't find/open image file '" + path + "'."); - oiio::ImageSpec spec = in->spec(); + oiio::ImageSpec spec = in->spec(); - in->close(); + in->close(); - return spec; + return spec; } -oiio::ParamValueList readImageMetadata(const std::string& path) -{ - return readImageSpec(path).extra_attribs; -} +oiio::ParamValueList readImageMetadata(const std::string& path) { return readImageSpec(path).extra_attribs; } void readImageSize(const std::string& path, int& width, int& height) { @@ -451,63 +479,37 @@ void readImageSize(const std::string& path, int& width, int& height) } template -void getBufferFromImage(Image& image, - oiio::TypeDesc format, - int nchannels, - oiio::ImageBuf& buffer) +void getBufferFromImage(Image& image, oiio::TypeDesc format, int nchannels, oiio::ImageBuf& buffer) { - const oiio::ImageSpec imageSpec(image.Width(), image.Height(), nchannels, format); - oiio::ImageBuf imageBuf(imageSpec, image.data()); - buffer.swap(imageBuf); + const oiio::ImageSpec imageSpec(image.Width(), image.Height(), nchannels, format); + oiio::ImageBuf imageBuf(imageSpec, image.data()); + buffer.swap(imageBuf); } -void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) -{ - getBufferFromImage(image, oiio::TypeDesc::FLOAT, 1, buffer); -} +void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) { getBufferFromImage(image, oiio::TypeDesc::FLOAT, 1, buffer); } -void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) -{ - getBufferFromImage(image, oiio::TypeDesc::UINT8, 1, buffer); -} +void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) { getBufferFromImage(image, oiio::TypeDesc::UINT8, 1, buffer); } -void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) -{ - getBufferFromImage(image, oiio::TypeDesc::FLOAT, 4, buffer); -} +void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) { getBufferFromImage(image, oiio::TypeDesc::FLOAT, 4, buffer); } -void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) -{ - getBufferFromImage(image, oiio::TypeDesc::UINT8, 4, buffer); -} +void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) { getBufferFromImage(image, oiio::TypeDesc::UINT8, 4, buffer); } -void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) -{ - getBufferFromImage(image, oiio::TypeDesc::FLOAT, 3, buffer); -} +void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) { getBufferFromImage(image, oiio::TypeDesc::FLOAT, 3, buffer); } -void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) -{ - getBufferFromImage(image, oiio::TypeDesc::UINT8, 3, buffer); -} +void getBufferFromImage(Image& image, oiio::ImageBuf& buffer) { getBufferFromImage(image, oiio::TypeDesc::UINT8, 3, buffer); } bool isRawFormat(const std::string& path) { std::unique_ptr in(oiio::ImageInput::open(path)); - if(!in) + if (!in) return false; std::string imgFormat = in->format_name(); return (imgFormat.compare("raw") == 0); } - template -void readImage(const std::string& path, - oiio::TypeDesc format, - int nchannels, - Image& image, - const ImageReadOptions& imageReadOptions) +void readImage(const std::string& path, oiio::TypeDesc format, int nchannels, Image& image, const ImageReadOptions& imageReadOptions) { ALICEVISION_LOG_DEBUG("[IO] Read Image: " << path); @@ -517,13 +519,13 @@ void readImage(const std::string& path, if (nchannels == 2) ALICEVISION_THROW_ERROR("Load of 2 channels is not supported. Image file: '" + path + "'."); - if(!fs::exists(path)) + if (!fs::exists(path)) ALICEVISION_THROW_ERROR("No such image file: '" << path << "'."); oiio::ImageSpec configSpec; const bool isRawImage = isRawFormat(path); - image::DCPProfile::Triple neutral = {1.0,1.0,1.0}; + image::DCPProfile::Triple neutral = {1.0, 1.0, 1.0}; if (isRawImage) { @@ -535,7 +537,8 @@ void readImage(const std::string& path, if (!imgMetadata.getattribute("raw:cam_mul", cam_mul)) { cam_mul = "{1024, 1024, 1024, 1024}"; - ALICEVISION_LOG_WARNING("[readImage]: cam_mul metadata not available, the openImageIO version might be too old (>= 2.4.5.0 requested for dcp management)."); + ALICEVISION_LOG_WARNING( + "[readImage]: cam_mul metadata not available, the openImageIO version might be too old (>= 2.4.5.0 requested for dcp management)."); } std::vector v_mult; @@ -558,12 +561,13 @@ void readImage(const std::string& path, // libRAW configuration // See https://openimageio.readthedocs.io/en/master/builtinplugins.html#raw-digital-camera-files - // and https://www.libraw.org/docs/API-datastruct-eng.html#libraw_raw_unpack_params_t for raw:balance_clamped and raw:adjust_maximum_thr behavior + // and https://www.libraw.org/docs/API-datastruct-eng.html#libraw_raw_unpack_params_t for raw:balance_clamped and raw:adjust_maximum_thr + // behavior -#if OIIO_VERSION >= (10000 * 2 + 100 * 4 + 12) // OIIO_VERSION >= 2.4.12 - // To disable the application of the orientation, we need the PR https://github.com/OpenImageIO/oiio/pull/3669, - // so we can disable the auto orientation and keep the metadata. - configSpec.attribute("raw:user_flip", 0); // disable auto rotation of the image buffer but keep exif metadata orientation valid +#if OIIO_VERSION >= (10000 * 2 + 100 * 4 + 12) // OIIO_VERSION >= 2.4.12 + // To disable the application of the orientation, we need the PR https://github.com/OpenImageIO/oiio/pull/3669, + // so we can disable the auto orientation and keep the metadata. + configSpec.attribute("raw:user_flip", 0); // disable auto rotation of the image buffer but keep exif metadata orientation valid #endif if (imageReadOptions.rawColorInterpretation == ERawColorInterpretation::None) @@ -575,38 +579,41 @@ void readImage(const std::string& path, float user_mul[4] = {1.f, 1.f, 1.f, 1.f}; - configSpec.attribute("raw:auto_bright", 0); // disable exposure correction - configSpec.attribute("raw:use_camera_wb", 0); // no white balance correction - configSpec.attribute("raw:user_mul", oiio::TypeDesc(oiio::TypeDesc::FLOAT, 4), user_mul); // no neutralization - configSpec.attribute("raw:use_camera_matrix", 0); // do not use embeded color profile if any - configSpec.attribute("raw:ColorSpace", "raw"); // use raw data + configSpec.attribute("raw:auto_bright", 0); // disable exposure correction + configSpec.attribute("raw:use_camera_wb", 0); // no white balance correction + configSpec.attribute("raw:user_mul", oiio::TypeDesc(oiio::TypeDesc::FLOAT, 4), user_mul); // no neutralization + configSpec.attribute("raw:use_camera_matrix", 0); // do not use embeded color profile if any + configSpec.attribute("raw:ColorSpace", "raw"); // use raw data configSpec.attribute("raw:HighlightMode", imageReadOptions.highlightMode); configSpec.attribute("raw:balance_clamped", (imageReadOptions.highlightMode == 0) ? 1 : 0); - configSpec.attribute("raw:adjust_maximum_thr", static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. + configSpec.attribute("raw:adjust_maximum_thr", + static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. configSpec.attribute("raw:Demosaic", imageReadOptions.demosaicingAlgo); } else if (imageReadOptions.rawColorInterpretation == ERawColorInterpretation::LibRawNoWhiteBalancing) { - configSpec.attribute("raw:auto_bright", imageReadOptions.rawAutoBright); // automatic exposure correction - configSpec.attribute("raw:Exposure", imageReadOptions.rawExposureAdjustment); // manual exposure adjustment - configSpec.attribute("raw:use_camera_wb", 0); // no white balance correction - configSpec.attribute("raw:use_camera_matrix", 1); // do not use embeded color profile if any, except for dng files - configSpec.attribute("raw:ColorSpace", "Linear"); // use linear colorspace with sRGB primaries + configSpec.attribute("raw:auto_bright", imageReadOptions.rawAutoBright); // automatic exposure correction + configSpec.attribute("raw:Exposure", imageReadOptions.rawExposureAdjustment); // manual exposure adjustment + configSpec.attribute("raw:use_camera_wb", 0); // no white balance correction + configSpec.attribute("raw:use_camera_matrix", 1); // do not use embeded color profile if any, except for dng files + configSpec.attribute("raw:ColorSpace", "Linear"); // use linear colorspace with sRGB primaries configSpec.attribute("raw:HighlightMode", imageReadOptions.highlightMode); configSpec.attribute("raw:balance_clamped", (imageReadOptions.highlightMode == 0) ? 1 : 0); - configSpec.attribute("raw:adjust_maximum_thr", static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. + configSpec.attribute("raw:adjust_maximum_thr", + static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. configSpec.attribute("raw:Demosaic", imageReadOptions.demosaicingAlgo); } else if (imageReadOptions.rawColorInterpretation == ERawColorInterpretation::LibRawWhiteBalancing) { - configSpec.attribute("raw:auto_bright", imageReadOptions.rawAutoBright); // automatic exposure correction - configSpec.attribute("raw:Exposure", imageReadOptions.rawExposureAdjustment); // manual exposure adjustment - configSpec.attribute("raw:use_camera_wb", 1); // white balance correction - configSpec.attribute("raw:use_camera_matrix", 1); // do not use embeded color profile if any, except for dng files - configSpec.attribute("raw:ColorSpace", "Linear"); // use linear colorspace with sRGB primaries + configSpec.attribute("raw:auto_bright", imageReadOptions.rawAutoBright); // automatic exposure correction + configSpec.attribute("raw:Exposure", imageReadOptions.rawExposureAdjustment); // manual exposure adjustment + configSpec.attribute("raw:use_camera_wb", 1); // white balance correction + configSpec.attribute("raw:use_camera_matrix", 1); // do not use embeded color profile if any, except for dng files + configSpec.attribute("raw:ColorSpace", "Linear"); // use linear colorspace with sRGB primaries configSpec.attribute("raw:HighlightMode", imageReadOptions.highlightMode); configSpec.attribute("raw:balance_clamped", (imageReadOptions.highlightMode == 0) ? 1 : 0); - configSpec.attribute("raw:adjust_maximum_thr", static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. + configSpec.attribute("raw:adjust_maximum_thr", + static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. configSpec.attribute("raw:Demosaic", imageReadOptions.demosaicingAlgo); } else if (imageReadOptions.rawColorInterpretation == ERawColorInterpretation::DcpLinearProcessing) @@ -615,8 +622,8 @@ void readImage(const std::string& path, { ALICEVISION_THROW_ERROR("A DCP color profile is required but cannot be found"); } - float user_mul[4] = {static_cast(neutral[0]), static_cast(neutral[1]), - static_cast(neutral[2]), static_cast(neutral[1])}; + float user_mul[4] = { + static_cast(neutral[0]), static_cast(neutral[1]), static_cast(neutral[2]), static_cast(neutral[1])}; if (imageReadOptions.doWBAfterDemosaicing) { for (int i = 0; i < 4; ++i) @@ -624,15 +631,16 @@ void readImage(const std::string& path, user_mul[i] = 1.0; } } - configSpec.attribute("raw:auto_bright", imageReadOptions.rawAutoBright); // automatic exposure correction - configSpec.attribute("raw:Exposure", imageReadOptions.rawExposureAdjustment); // manual exposure adjustment - configSpec.attribute("raw:use_camera_wb", 0); // No White balance correction => user_mul is used + configSpec.attribute("raw:auto_bright", imageReadOptions.rawAutoBright); // automatic exposure correction + configSpec.attribute("raw:Exposure", imageReadOptions.rawExposureAdjustment); // manual exposure adjustment + configSpec.attribute("raw:use_camera_wb", 0); // No White balance correction => user_mul is used configSpec.attribute("raw:user_mul", oiio::TypeDesc(oiio::TypeDesc::FLOAT, 4), user_mul); - configSpec.attribute("raw:use_camera_matrix", 0); // do not use embeded color profile if any + configSpec.attribute("raw:use_camera_matrix", 0); // do not use embeded color profile if any configSpec.attribute("raw:ColorSpace", "raw"); configSpec.attribute("raw:HighlightMode", imageReadOptions.highlightMode); configSpec.attribute("raw:balance_clamped", (imageReadOptions.highlightMode == 0) ? 1 : 0); - configSpec.attribute("raw:adjust_maximum_thr", static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. + configSpec.attribute("raw:adjust_maximum_thr", + static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. configSpec.attribute("raw:Demosaic", imageReadOptions.demosaicingAlgo); } else if (imageReadOptions.rawColorInterpretation == ERawColorInterpretation::DcpMetadata) @@ -641,8 +649,8 @@ void readImage(const std::string& path, { ALICEVISION_THROW_ERROR("A DCP color profile is required but cannot be found"); } - float user_mul[4] = {static_cast(neutral[0]), static_cast(neutral[1]), - static_cast(neutral[2]), static_cast(neutral[1])}; + float user_mul[4] = { + static_cast(neutral[0]), static_cast(neutral[1]), static_cast(neutral[2]), static_cast(neutral[1])}; if (imageReadOptions.doWBAfterDemosaicing) { for (int i = 0; i < 4; ++i) @@ -650,27 +658,29 @@ void readImage(const std::string& path, user_mul[i] = 1.0; } } - configSpec.attribute("raw:auto_bright", 0); // disable exposure correction - configSpec.attribute("raw:use_camera_wb", 0); // no white balance correction - configSpec.attribute("raw:user_mul", oiio::TypeDesc(oiio::TypeDesc::FLOAT, 4), user_mul); // no neutralization - configSpec.attribute("raw:use_camera_matrix", 0); // do not use embeded color profile if any - configSpec.attribute("raw:ColorSpace", "raw"); // use raw data + configSpec.attribute("raw:auto_bright", 0); // disable exposure correction + configSpec.attribute("raw:use_camera_wb", 0); // no white balance correction + configSpec.attribute("raw:user_mul", oiio::TypeDesc(oiio::TypeDesc::FLOAT, 4), user_mul); // no neutralization + configSpec.attribute("raw:use_camera_matrix", 0); // do not use embeded color profile if any + configSpec.attribute("raw:ColorSpace", "raw"); // use raw data configSpec.attribute("raw:HighlightMode", imageReadOptions.highlightMode); configSpec.attribute("raw:balance_clamped", (imageReadOptions.highlightMode == 0) ? 1 : 0); - configSpec.attribute("raw:adjust_maximum_thr", static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. + configSpec.attribute("raw:adjust_maximum_thr", + static_cast(1.0)); // Use libRaw default value: values above 75% of max are clamped to max. configSpec.attribute("raw:Demosaic", imageReadOptions.demosaicingAlgo); } else { - ALICEVISION_THROW_ERROR("[image] readImage: invalid rawColorInterpretation " << ERawColorInterpretation_enumToString(imageReadOptions.rawColorInterpretation) << "."); + ALICEVISION_THROW_ERROR("[image] readImage: invalid rawColorInterpretation " + << ERawColorInterpretation_enumToString(imageReadOptions.rawColorInterpretation) << "."); } } oiio::ImageBuf inBuf(path, 0, 0, NULL, &configSpec); - inBuf.read(0, 0, true, oiio::TypeDesc::FLOAT); // force image convertion to float (for grayscale and color space convertion) + inBuf.read(0, 0, true, oiio::TypeDesc::FLOAT); // force image convertion to float (for grayscale and color space convertion) - if(!inBuf.initialized()) + if (!inBuf.initialized()) ALICEVISION_THROW_ERROR("Failed to open the image file: '" << path << "'."); // check picture channels number @@ -697,17 +707,17 @@ void readImage(const std::string& path, } // Apply DCP profile - if (!imageReadOptions.colorProfileFileName.empty() && - imageReadOptions.rawColorInterpretation == ERawColorInterpretation::DcpLinearProcessing) + if (!imageReadOptions.colorProfileFileName.empty() && imageReadOptions.rawColorInterpretation == ERawColorInterpretation::DcpLinearProcessing) { image::DCPProfile dcpProfile(imageReadOptions.colorProfileFileName); - //oiio::ParamValueList imgMetadata = readImageMetadata(path); + // oiio::ParamValueList imgMetadata = readImageMetadata(path); std::string cam_mul = ""; if (!imgMetadata.getattribute("raw:cam_mul", cam_mul)) { cam_mul = "{1024, 1024, 1024, 1024}"; - ALICEVISION_LOG_WARNING("[readImage]: cam_mul metadata not available, the openImageIO version might be too old (>= 2.4.5.0 requested for dcp management)."); + ALICEVISION_LOG_WARNING( + "[readImage]: cam_mul metadata not available, the openImageIO version might be too old (>= 2.4.5.0 requested for dcp management)."); } std::vector v_mult; @@ -734,19 +744,19 @@ void readImage(const std::string& path, } // color conversion - if(imageReadOptions.workingColorSpace == EImageColorSpace::AUTO) + if (imageReadOptions.workingColorSpace == EImageColorSpace::AUTO) ALICEVISION_THROW_ERROR("You must specify a requested color space for image file '" + path + "'."); // Get color space name. Default image color space is sRGB - const std::string colorSpaceFromMetadata = inBuf.spec().get_string_attribute("aliceVision:ColorSpace", inBuf.spec().get_string_attribute("oiio:ColorSpace", "sRGB")); + const std::string colorSpaceFromMetadata = + inBuf.spec().get_string_attribute("aliceVision:ColorSpace", inBuf.spec().get_string_attribute("oiio:ColorSpace", "sRGB")); std::string fromColorSpaceName = (isRawImage && imageReadOptions.rawColorInterpretation == ERawColorInterpretation::DcpLinearProcessing) - ? "aces2065-1" - : (isRawImage - ? "linear" - : (imageReadOptions.inputColorSpace == EImageColorSpace::AUTO - ? colorSpaceFromMetadata - : EImageColorSpace_enumToString(imageReadOptions.inputColorSpace))); + ? "aces2065-1" + : (isRawImage ? "linear" + : (imageReadOptions.inputColorSpace == EImageColorSpace::AUTO + ? colorSpaceFromMetadata + : EImageColorSpace_enumToString(imageReadOptions.inputColorSpace))); ALICEVISION_LOG_TRACE("Read image " << path << " (encoded in " << fromColorSpaceName << " colorspace)."); @@ -774,7 +784,8 @@ void readImage(const std::string& path, // load DCP metadata from metadata. An error will be thrown if all required metadata are not there. dcpProf.Load(imageMetadata); - std::string cam_mul = map_has_non_empty_value(imageMetadata, "raw:cam_mul") ? imageMetadata.at("raw:cam_mul") : imageMetadata.at("AliceVision:raw:cam_mul"); + std::string cam_mul = + map_has_non_empty_value(imageMetadata, "raw:cam_mul") ? imageMetadata.at("raw:cam_mul") : imageMetadata.at("AliceVision:raw:cam_mul"); std::vector v_mult; size_t last = 0; size_t next = 1; @@ -802,8 +813,10 @@ void readImage(const std::string& path, // Do nothing. Note that calling imageAlgo::colorconvert() will copy the source buffer // even if no conversion is needed. } - else if ((imageReadOptions.workingColorSpace == EImageColorSpace::ACES2065_1) || (imageReadOptions.workingColorSpace == EImageColorSpace::ACEScg) || - (EImageColorSpace_stringToEnum(fromColorSpaceName) == EImageColorSpace::ACES2065_1) || (EImageColorSpace_stringToEnum(fromColorSpaceName) == EImageColorSpace::ACEScg) || + else if ((imageReadOptions.workingColorSpace == EImageColorSpace::ACES2065_1) || + (imageReadOptions.workingColorSpace == EImageColorSpace::ACEScg) || + (EImageColorSpace_stringToEnum(fromColorSpaceName) == EImageColorSpace::ACES2065_1) || + (EImageColorSpace_stringToEnum(fromColorSpaceName) == EImageColorSpace::ACEScg) || (EImageColorSpace_stringToEnum(fromColorSpaceName) == EImageColorSpace::REC709)) { const auto colorConfigPath = getAliceVisionOCIOConfig(); @@ -813,21 +826,26 @@ void readImage(const std::string& path, } oiio::ImageBuf colorspaceBuf; oiio::ColorConfig colorConfig(colorConfigPath); - oiio::ImageBufAlgo::colorconvert(colorspaceBuf, inBuf, - fromColorSpaceName, - EImageColorSpace_enumToOIIOString(imageReadOptions.workingColorSpace), true, "", "", - &colorConfig); + oiio::ImageBufAlgo::colorconvert(colorspaceBuf, + inBuf, + fromColorSpaceName, + EImageColorSpace_enumToOIIOString(imageReadOptions.workingColorSpace), + true, + "", + "", + &colorConfig); inBuf = colorspaceBuf; } else { oiio::ImageBuf colorspaceBuf; - oiio::ImageBufAlgo::colorconvert(colorspaceBuf, inBuf, fromColorSpaceName, EImageColorSpace_enumToOIIOString(imageReadOptions.workingColorSpace)); + oiio::ImageBufAlgo::colorconvert( + colorspaceBuf, inBuf, fromColorSpaceName, EImageColorSpace_enumToOIIOString(imageReadOptions.workingColorSpace)); inBuf = colorspaceBuf; } // convert to grayscale if needed - if(nchannels == 1 && inBuf.spec().nchannels >= 3) + if (nchannels == 1 && inBuf.spec().nchannels >= 3) { // convertion region of interest (for inBuf.spec().nchannels > 3) oiio::ROI convertionROI = inBuf.roi(); @@ -836,7 +854,7 @@ void readImage(const std::string& path, // compute luminance via a weighted sum of R,G,B // (assuming Rec709 primaries and a linear scale) - const float weights[3] = {.2126f, .7152f, .0722f}; // To be changed if not sRGB Rec 709 Linear. + const float weights[3] = {.2126f, .7152f, .0722f}; // To be changed if not sRGB Rec 709 Linear. oiio::ImageBuf grayscaleBuf; oiio::ImageBufAlgo::channel_sum(grayscaleBuf, inBuf, weights, convertionROI); inBuf.copy(grayscaleBuf); @@ -849,8 +867,8 @@ void readImage(const std::string& path, { oiio::ImageSpec requestedSpec(inBuf.spec().width, inBuf.spec().height, 3, format); oiio::ImageBuf requestedBuf(requestedSpec); - int channelOrder[] = { 0, 0, 0 }; - float channelValues[] = { 0 /*ignore*/, 0 /*ignore*/, 0 /*ignore*/ }; + int channelOrder[] = {0, 0, 0}; + float channelValues[] = {0 /*ignore*/, 0 /*ignore*/, 0 /*ignore*/}; oiio::ImageBufAlgo::channels(requestedBuf, inBuf, 3, channelOrder, channelValues); inBuf.swap(requestedBuf); } @@ -860,12 +878,13 @@ void readImage(const std::string& path, { oiio::ImageSpec requestedSpec(inBuf.spec().width, inBuf.spec().height, 3, format); oiio::ImageBuf requestedBuf(requestedSpec); - int channelOrder[] = { 0, 1, 2, -1 /*constant value*/ }; - float channelValues[] = { 0 /*ignore*/, 0 /*ignore*/, 0 /*ignore*/, 1.0 }; - oiio::ImageBufAlgo::channels(requestedBuf, inBuf, - 4, // create an image with 4 channels - channelOrder, - channelValues); // only the 4th value is used + int channelOrder[] = {0, 1, 2, -1 /*constant value*/}; + float channelValues[] = {0 /*ignore*/, 0 /*ignore*/, 0 /*ignore*/, 1.0}; + oiio::ImageBufAlgo::channels(requestedBuf, + inBuf, + 4, // create an image with 4 channels + channelOrder, + channelValues); // only the 4th value is used inBuf.swap(requestedBuf); } @@ -881,48 +900,46 @@ void readImage(const std::string& path, } template -void readImageNoFloat(const std::string& path, - oiio::TypeDesc format, - Image& image) +void readImageNoFloat(const std::string& path, oiio::TypeDesc format, Image& image) { - oiio::ImageSpec configSpec; - - oiio::ImageBuf inBuf(path, 0, 0, NULL, &configSpec); - - inBuf.read(0, 0, true, format); - - if(!inBuf.initialized()) - { - throw std::runtime_error("Cannot find/open image file '" + path + "'."); - } - - // check picture channels number - if(inBuf.spec().nchannels != 1) - { - throw std::runtime_error("Can't load channels of image file '" + path + "'."); - } - - // copy pixels from oiio to eigen - image.resize(inBuf.spec().width, inBuf.spec().height, false); - { - oiio::ROI exportROI = inBuf.roi(); - exportROI.chbegin = 0; - exportROI.chend = 1; - - inBuf.get_pixels(exportROI, format, image.data()); - } + oiio::ImageSpec configSpec; + + oiio::ImageBuf inBuf(path, 0, 0, NULL, &configSpec); + + inBuf.read(0, 0, true, format); + + if (!inBuf.initialized()) + { + throw std::runtime_error("Cannot find/open image file '" + path + "'."); + } + + // check picture channels number + if (inBuf.spec().nchannels != 1) + { + throw std::runtime_error("Can't load channels of image file '" + path + "'."); + } + + // copy pixels from oiio to eigen + image.resize(inBuf.spec().width, inBuf.spec().height, false); + { + oiio::ROI exportROI = inBuf.roi(); + exportROI.chbegin = 0; + exportROI.chend = 1; + + inBuf.get_pixels(exportROI, format, image.data()); + } } bool containsHalfFloatOverflow(const oiio::ImageBuf& image) { auto stats = oiio::ImageBufAlgo::computePixelStats(image); - for(auto maxValue: stats.max) + for (auto maxValue : stats.max) { - if(maxValue > HALF_MAX) + if (maxValue > HALF_MAX) return true; } - for (auto minValue: stats.min) + for (auto minValue : stats.min) { if (minValue < -HALF_MAX) return true; @@ -937,14 +954,14 @@ void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList(), - const oiio::ROI& displayRoi = oiio::ROI(), + const oiio::ROI& displayRoi = oiio::ROI(), const oiio::ROI& pixelRoi = oiio::ROI()) { const fs::path bPath = fs::path(path); const std::string extension = boost::to_lower_copy(bPath.extension().string()); - const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + extension; + const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + extension; const bool isEXR = (extension == ".exr"); - //const bool isTIF = (extension == ".tif"); + // const bool isTIF = (extension == ".tif"); const bool isJPG = (extension == ".jpg"); const bool isPNG = (extension == ".png"); @@ -960,14 +977,14 @@ void writeImage(const std::string& path, } ALICEVISION_LOG_DEBUG("[IO] Write Image: " << path << "\n" - << "\t- width: " << image.Width() << "\n" - << "\t- height: " << image.Height() << "\n" - << "\t- channels: " << nchannels); + << "\t- width: " << image.Width() << "\n" + << "\t- height: " << image.Height() << "\n" + << "\t- channels: " << nchannels); oiio::ImageSpec imageSpec(image.Width(), image.Height(), nchannels, typeDesc); - imageSpec.extra_attribs = metadata; // add custom metadata + imageSpec.extra_attribs = metadata; // add custom metadata - imageSpec.attribute("jpeg:subsampling", "4:4:4"); // if possible, always subsampling 4:4:4 for jpeg + imageSpec.attribute("jpeg:subsampling", "4:4:4"); // if possible, always subsampling 4:4:4 for jpeg std::string compressionMethod = "none"; if (isEXR) @@ -982,12 +999,14 @@ void writeImage(const std::string& path, break; case EImageExrCompression::DWAA: case EImageExrCompression::DWAB: - if (compressionLevel > 0) suffix = ":" + std::to_string(compressionLevel); + if (compressionLevel > 0) + suffix = ":" + std::to_string(compressionLevel); compressionMethod = methodName + suffix; break; case EImageExrCompression::ZIP: case EImageExrCompression::ZIPS: - if (compressionLevel > 0) suffix = ":" + std::to_string(std::min(compressionLevel, 9)); + if (compressionLevel > 0) + suffix = ":" + std::to_string(std::min(compressionLevel, 9)); compressionMethod = methodName + suffix; break; default: @@ -1005,24 +1024,24 @@ void writeImage(const std::string& path, imageSpec.attribute("compression", compressionMethod); - if(displayRoi.defined() && isEXR) + if (displayRoi.defined() && isEXR) { imageSpec.set_roi_full(displayRoi); } - if(pixelRoi.defined() && isEXR) + if (pixelRoi.defined() && isEXR) { imageSpec.set_roi(pixelRoi); } imageSpec.attribute("AliceVision:ColorSpace", - (toColorSpace == EImageColorSpace::NO_CONVERSION) - ? EImageColorSpace_enumToString(fromColorSpace) : EImageColorSpace_enumToString(toColorSpace)); - - const oiio::ImageBuf imgBuf = oiio::ImageBuf(imageSpec, const_cast(image.data())); // original image buffer - const oiio::ImageBuf* outBuf = &imgBuf; // buffer to write - - oiio::ImageBuf colorspaceBuf = oiio::ImageBuf(imageSpec, const_cast(image.data())); // buffer for image colorspace modification + (toColorSpace == EImageColorSpace::NO_CONVERSION) ? EImageColorSpace_enumToString(fromColorSpace) + : EImageColorSpace_enumToString(toColorSpace)); + + const oiio::ImageBuf imgBuf = oiio::ImageBuf(imageSpec, const_cast(image.data())); // original image buffer + const oiio::ImageBuf* outBuf = &imgBuf; // buffer to write + + oiio::ImageBuf colorspaceBuf = oiio::ImageBuf(imageSpec, const_cast(image.data())); // buffer for image colorspace modification if ((fromColorSpace == toColorSpace) || (toColorSpace == EImageColorSpace::NO_CONVERSION)) { // Do nothing. Note that calling imageAlgo::colorconvert() will copy the source buffer @@ -1038,32 +1057,37 @@ void writeImage(const std::string& path, throw std::runtime_error("ALICEVISION_ROOT is not defined, OCIO config file cannot be accessed."); } oiio::ColorConfig colorConfig(colorConfigPath); - oiio::ImageBufAlgo::colorconvert(colorspaceBuf, *outBuf, + oiio::ImageBufAlgo::colorconvert(colorspaceBuf, + *outBuf, EImageColorSpace_enumToOIIOString(fromColorSpace), - EImageColorSpace_enumToOIIOString(toColorSpace), true, "", "", + EImageColorSpace_enumToOIIOString(toColorSpace), + true, + "", + "", &colorConfig); outBuf = &colorspaceBuf; } else { - oiio::ImageBufAlgo::colorconvert(colorspaceBuf, *outBuf, EImageColorSpace_enumToOIIOString(fromColorSpace), EImageColorSpace_enumToOIIOString(toColorSpace)); + oiio::ImageBufAlgo::colorconvert( + colorspaceBuf, *outBuf, EImageColorSpace_enumToOIIOString(fromColorSpace), EImageColorSpace_enumToOIIOString(toColorSpace)); outBuf = &colorspaceBuf; } oiio::ImageBuf formatBuf; // buffer for image format modification - if(isEXR) + if (isEXR) { // Storage data type may be saved as attributes to formats that support it and then come back // as metadata to this function. Therefore we store the storage data type to attributes if it // is set and load it from attributes if it isn't set. if (options.getStorageDataType() != EStorageDataType::Undefined) { - imageSpec.attribute("AliceVision:storageDataType", - EStorageDataType_enumToString(options.getStorageDataType())); + imageSpec.attribute("AliceVision:storageDataType", EStorageDataType_enumToString(options.getStorageDataType())); } - const std::string storageDataTypeStr = imageSpec.get_string_attribute("AliceVision:storageDataType", EStorageDataType_enumToString(EStorageDataType::HalfFinite)); - EStorageDataType storageDataType = EStorageDataType_stringToEnum(storageDataTypeStr); + const std::string storageDataTypeStr = + imageSpec.get_string_attribute("AliceVision:storageDataType", EStorageDataType_enumToString(EStorageDataType::HalfFinite)); + EStorageDataType storageDataType = EStorageDataType_stringToEnum(storageDataTypeStr); if (storageDataType == EStorageDataType::Auto) { @@ -1084,16 +1108,15 @@ void writeImage(const std::string& path, outBuf = &colorspaceBuf; } - if (storageDataType == EStorageDataType::Half || - storageDataType == EStorageDataType::HalfFinite) + if (storageDataType == EStorageDataType::Half || storageDataType == EStorageDataType::HalfFinite) { - formatBuf.copy(*outBuf, oiio::TypeDesc::HALF); // override format, use half instead of float + formatBuf.copy(*outBuf, oiio::TypeDesc::HALF); // override format, use half instead of float outBuf = &formatBuf; } } // write image - if(!outBuf->write(tmpPath)) + if (!outBuf->write(tmpPath)) ALICEVISION_THROW_ERROR("Can't write output image file '" + path + "'."); // rename temporary filename @@ -1102,167 +1125,153 @@ void writeImage(const std::string& path, template void writeImageNoFloat(const std::string& path, - oiio::TypeDesc typeDesc, - const Image& image, - const ImageWriteOptions& options, - const oiio::ParamValueList& metadata = oiio::ParamValueList()) + oiio::TypeDesc typeDesc, + const Image& image, + const ImageWriteOptions& options, + const oiio::ParamValueList& metadata = oiio::ParamValueList()) { - const fs::path bPath = fs::path(path); - const std::string extension = boost::to_lower_copy(bPath.extension().string()); - const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + extension; - const bool isEXR = (extension == ".exr"); - //const bool isTIF = (extension == ".tif"); - const bool isJPG = (extension == ".jpg"); - const bool isPNG = (extension == ".png"); - - auto imageColorSpace = options.getToColorSpace(); - if(imageColorSpace == EImageColorSpace::AUTO) - { - if(isJPG || isPNG) - imageColorSpace = EImageColorSpace::SRGB; - else - imageColorSpace = EImageColorSpace::LINEAR; - } + const fs::path bPath = fs::path(path); + const std::string extension = boost::to_lower_copy(bPath.extension().string()); + const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + extension; + const bool isEXR = (extension == ".exr"); + // const bool isTIF = (extension == ".tif"); + const bool isJPG = (extension == ".jpg"); + const bool isPNG = (extension == ".png"); + + auto imageColorSpace = options.getToColorSpace(); + if (imageColorSpace == EImageColorSpace::AUTO) + { + if (isJPG || isPNG) + imageColorSpace = EImageColorSpace::SRGB; + else + imageColorSpace = EImageColorSpace::LINEAR; + } - oiio::ImageSpec imageSpec(image.Width(), image.Height(), 1, typeDesc); - imageSpec.extra_attribs = metadata; // add custom metadata + oiio::ImageSpec imageSpec(image.Width(), image.Height(), 1, typeDesc); + imageSpec.extra_attribs = metadata; // add custom metadata - imageSpec.attribute("jpeg:subsampling", "4:4:4"); // if possible, always subsampling 4:4:4 for jpeg - imageSpec.attribute("compression", isEXR ? "zips" : "none"); // if possible, set compression (zips for EXR, none for the other) + imageSpec.attribute("jpeg:subsampling", "4:4:4"); // if possible, always subsampling 4:4:4 for jpeg + imageSpec.attribute("compression", isEXR ? "zips" : "none"); // if possible, set compression (zips for EXR, none for the other) - const oiio::ImageBuf imgBuf = oiio::ImageBuf(imageSpec, const_cast(image.data())); // original image buffer - const oiio::ImageBuf* outBuf = &imgBuf; // buffer to write + const oiio::ImageBuf imgBuf = oiio::ImageBuf(imageSpec, const_cast(image.data())); // original image buffer + const oiio::ImageBuf* outBuf = &imgBuf; // buffer to write - oiio::ImageBuf formatBuf; // buffer for image format modification - if(isEXR) - { - - formatBuf.copy(*outBuf, typeDesc); // override format, use half instead of float - outBuf = &formatBuf; - } + oiio::ImageBuf formatBuf; // buffer for image format modification + if (isEXR) + { + formatBuf.copy(*outBuf, typeDesc); // override format, use half instead of float + outBuf = &formatBuf; + } - // write image - if(!outBuf->write(tmpPath)) - throw std::runtime_error("Can't write output image file '" + path + "'."); + // write image + if (!outBuf->write(tmpPath)) + throw std::runtime_error("Can't write output image file '" + path + "'."); - // rename temporary filename - fs::rename(tmpPath, path); + // rename temporary filename + fs::rename(tmpPath, path); } -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions) +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions) { - readImage(path, oiio::TypeDesc::FLOAT, 1, image, imageReadOptions); + readImage(path, oiio::TypeDesc::FLOAT, 1, image, imageReadOptions); } -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions) +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions) { - readImage(path, oiio::TypeDesc::UINT8, 1, image, imageReadOptions); + readImage(path, oiio::TypeDesc::UINT8, 1, image, imageReadOptions); } -void readImageDirect(const std::string& path, Image& image) -{ - readImageNoFloat(path, oiio::TypeDesc::UINT8, image); -} +void readImageDirect(const std::string& path, Image& image) { readImageNoFloat(path, oiio::TypeDesc::UINT8, image); } -void readImageDirect(const std::string& path, Image& image) -{ - readImageNoFloat(path, oiio::TypeDesc::UINT32, image); -} +void readImageDirect(const std::string& path, Image& image) { readImageNoFloat(path, oiio::TypeDesc::UINT32, image); } -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions) +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions) { - readImage(path, oiio::TypeDesc::FLOAT, 4, image, imageReadOptions); + readImage(path, oiio::TypeDesc::FLOAT, 4, image, imageReadOptions); } -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions) +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions) { - readImage(path, oiio::TypeDesc::UINT8, 4, image, imageReadOptions); + readImage(path, oiio::TypeDesc::UINT8, 4, image, imageReadOptions); } -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions) +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions) { - readImage(path, oiio::TypeDesc::FLOAT, 3, image, imageReadOptions); + readImage(path, oiio::TypeDesc::FLOAT, 3, image, imageReadOptions); } -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions) +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions) { - readImage(path, oiio::TypeDesc::UINT8, 3, image, imageReadOptions); + readImage(path, oiio::TypeDesc::UINT8, 3, image, imageReadOptions); } void logOIIOImageCacheInfo() { - oiio::ImageCache* cache = oiio::ImageCache::create(true); + oiio::ImageCache* cache = oiio::ImageCache::create(true); - int maxOpenFiles = -1; - cache->getattribute("max_open_files", maxOpenFiles); + int maxOpenFiles = -1; + cache->getattribute("max_open_files", maxOpenFiles); - int totalFiles = -1; - cache->getattribute("total_files", totalFiles); + int totalFiles = -1; + cache->getattribute("total_files", totalFiles); - float maxMemoryMB = -1.f; - cache->getattribute("max_memory_MB", maxMemoryMB); + float maxMemoryMB = -1.f; + cache->getattribute("max_memory_MB", maxMemoryMB); - int64_t cacheMemoryUsed = -1; - cache->getattribute("stat:cache_memory_used", oiio::TypeDesc::INT64, &cacheMemoryUsed); + int64_t cacheMemoryUsed = -1; + cache->getattribute("stat:cache_memory_used", oiio::TypeDesc::INT64, &cacheMemoryUsed); - int64_t bytesRead = -1; - cache->getattribute("stat:bytes_read", oiio::TypeDesc::INT64, &bytesRead); + int64_t bytesRead = -1; + cache->getattribute("stat:bytes_read", oiio::TypeDesc::INT64, &bytesRead); - ALICEVISION_LOG_INFO("OIIO image cache info: " << - "\n * max open files: " << maxOpenFiles << - "\n * total files: " << totalFiles << - "\n * max memory (MB): " << maxMemoryMB << - "\n * cache memory used: " << cacheMemoryUsed << - "\n * bytes read: " << bytesRead); + ALICEVISION_LOG_INFO("OIIO image cache info: " + << "\n * max open files: " << maxOpenFiles << "\n * total files: " << totalFiles << "\n * max memory (MB): " << maxMemoryMB + << "\n * cache memory used: " << cacheMemoryUsed << "\n * bytes read: " << bytesRead); } -void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImageNoFloat(path, oiio::TypeDesc::UINT8, image, options, metadata); } -void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImageNoFloat(path, oiio::TypeDesc::INT32, image, options, metadata); } -void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImageNoFloat(path, oiio::TypeDesc::UINT32, image, options, metadata); } -void writeImage(const std::string& path, +void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata, - const oiio::ROI& displayRoi, + const oiio::ROI& displayRoi, const oiio::ROI& pixelRoi) { writeImage(path, oiio::TypeDesc::FLOAT, 1, image, options, metadata, displayRoi, pixelRoi); } -void writeImage(const std::string& path, +void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, + const ImageWriteOptions& options, const oiio::ParamValueList& metadata, - const oiio::ROI& displayRoi, + const oiio::ROI& displayRoi, const oiio::ROI& pixelRoi) { writeImage(path, oiio::TypeDesc::FLOAT, 4, image, options, metadata, displayRoi, pixelRoi); } -void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImage(path, oiio::TypeDesc::UINT8, 4, image, options, metadata); } -void writeImage(const std::string& path, +void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, + const ImageWriteOptions& options, const oiio::ParamValueList& metadata, const oiio::ROI& displayRoi, const oiio::ROI& pixelRoi) @@ -1270,35 +1279,36 @@ void writeImage(const std::string& path, writeImage(path, oiio::TypeDesc::FLOAT, 3, image, options, metadata, displayRoi, pixelRoi); } -void writeImage(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImage(path, oiio::TypeDesc::UINT8, 3, image, options, metadata); } -void writeImageWithFloat(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImageWithFloat(const std::string& path, + const Image& image, + const ImageWriteOptions& options, + const oiio::ParamValueList& metadata) { writeImage(path, oiio::TypeDesc::UINT8, 1, image, options, metadata); } -void writeImageWithFloat(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImageWithFloat(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImage(path, oiio::TypeDesc::INT32, 1, image, options, metadata); } -void writeImageWithFloat(const std::string& path, const Image& image, - const ImageWriteOptions& options, const oiio::ParamValueList& metadata) +void writeImageWithFloat(const std::string& path, const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata) { writeImage(path, oiio::TypeDesc::UINT32, 1, image, options, metadata); } - -bool tryLoadMask(Image* mask, const std::vector& masksFolders, - const IndexT viewId, const std::string& srcImage, const std::string& fileExtension) +bool tryLoadMask(Image* mask, + const std::vector& masksFolders, + const IndexT viewId, + const std::string& srcImage, + const std::string& fileExtension) { - for (const auto & masksFolder_str : masksFolders) + for (const auto& masksFolder_str : masksFolders) { if (!masksFolder_str.empty() && fs::exists(masksFolder_str)) { @@ -1338,10 +1348,7 @@ std::string getAliceVisionOCIOConfig() return {}; } -void setAliceVisionRootOverride(const std::string& value) -{ - aliceVisionRootOverride = value; -} +void setAliceVisionRootOverride(const std::string& value) { aliceVisionRootOverride = value; } } // namespace image } // namespace aliceVision diff --git a/src/aliceVision/image/io.hpp b/src/aliceVision/image/io.hpp index 130f8f2e19..8fa77b6ffd 100644 --- a/src/aliceVision/image/io.hpp +++ b/src/aliceVision/image/io.hpp @@ -19,7 +19,6 @@ #include - namespace aliceVision { class rgb; @@ -56,11 +55,11 @@ std::istream& operator>>(std::istream& in, ERawColorInterpretation& dataType); */ enum class EImageFileType { - JPEG, - PNG, - TIFF, - EXR, - NONE + JPEG, + PNG, + TIFF, + EXR, + NONE }; /** @@ -124,15 +123,15 @@ bool isSupported(const std::string& extension); bool isVideoExtension(const std::string& extension); /** -* @brief Data type use to write the image -*/ + * @brief Data type use to write the image + */ enum class EStorageDataType { - Float, //< Use full floating point precision to store - Half, //< Use half (values our of range could become inf or nan) - HalfFinite, //< Use half, but ensures out-of-range pixels are clamps to keep finite pixel values - Auto, //< Use half if all pixels can be stored in half without clamp, else use full float - Undefined //< Storage data type is not defined and should be inferred from other sources + Float, //< Use full floating point precision to store + Half, //< Use half (values our of range could become inf or nan) + HalfFinite, //< Use half, but ensures out-of-range pixels are clamps to keep finite pixel values + Auto, //< Use half if all pixels can be stored in half without clamp, else use full float + Undefined //< Storage data type is not defined and should be inferred from other sources }; std::string EStorageDataType_informations(); @@ -142,8 +141,8 @@ std::ostream& operator<<(std::ostream& os, EStorageDataType dataType); std::istream& operator>>(std::istream& in, EStorageDataType& dataType); /** -* @brief Compression method used to write an exr image -*/ + * @brief Compression method used to write an exr image + */ enum class EImageExrCompression { None, @@ -170,8 +169,8 @@ std::istream& operator>>(std::istream& in, EImageExrCompression& dataType); */ enum class EImageQuality { - OPTIMIZED, - LOSSLESS + OPTIMIZED, + LOSSLESS }; /** @@ -215,18 +214,25 @@ std::istream& operator>>(std::istream& in, EImageQuality& imageQuality); */ struct ImageReadOptions { - ImageReadOptions(EImageColorSpace workingColorSpace = EImageColorSpace::AUTO, EImageColorSpace inputColorSpace = EImageColorSpace::AUTO, - ERawColorInterpretation rawColorInterpretation = ERawColorInterpretation::LibRawWhiteBalancing, - const std::string& colorProfile = "", const bool useDCPColorMatrixOnly = true, const oiio::ROI& roi = oiio::ROI()) : - workingColorSpace(workingColorSpace), + ImageReadOptions(EImageColorSpace workingColorSpace = EImageColorSpace::AUTO, + EImageColorSpace inputColorSpace = EImageColorSpace::AUTO, + ERawColorInterpretation rawColorInterpretation = ERawColorInterpretation::LibRawWhiteBalancing, + const std::string& colorProfile = "", + const bool useDCPColorMatrixOnly = true, + const oiio::ROI& roi = oiio::ROI()) + : workingColorSpace(workingColorSpace), inputColorSpace(inputColorSpace), rawColorInterpretation(rawColorInterpretation), colorProfileFileName(colorProfile), useDCPColorMatrixOnly(useDCPColorMatrixOnly), - doWBAfterDemosaicing(false), demosaicingAlgo("AHD"), highlightMode(0), rawAutoBright(false), rawExposureAdjustment(1.0), - correlatedColorTemperature(-1.0), subROI(roi) - { - } + doWBAfterDemosaicing(false), + demosaicingAlgo("AHD"), + highlightMode(0), + rawAutoBright(false), + rawExposureAdjustment(1.0), + correlatedColorTemperature(-1.0), + subROI(roi) + {} EImageColorSpace workingColorSpace; EImageColorSpace inputColorSpace; @@ -239,8 +245,8 @@ struct ImageReadOptions bool rawAutoBright; float rawExposureAdjustment; double correlatedColorTemperature; - //ROI for this image. - //If the image contains an roi, this is the roi INSIDE the roi. + // ROI for this image. + // If the image contains an roi, this is the roi INSIDE the roi. oiio::ROI subROI; }; @@ -249,7 +255,7 @@ struct ImageReadOptions */ class ImageWriteOptions { -public: + public: ImageWriteOptions() = default; EImageColorSpace getFromColorSpace() const { return _fromColorSpace; } @@ -302,7 +308,7 @@ class ImageWriteOptions return *this; } -private: + private: EImageColorSpace _fromColorSpace{EImageColorSpace::LINEAR}; EImageColorSpace _toColorSpace{EImageColorSpace::AUTO}; EStorageDataType _storageDataType{EStorageDataType::Undefined}; @@ -317,7 +323,7 @@ class ImageWriteOptions * @param[in] ext The extension with the dot (eg ".png") * @return \p true if the extension is supported. */ -bool isSupportedUndistortFormat(const std::string &ext); +bool isSupportedUndistortFormat(const std::string& ext); /** * @brief convert a metadata string map into an oiio::ParamValueList @@ -383,13 +389,13 @@ void getBufferFromImage(Image& image, oiio::ImageBuf& buffer); * @param[out] image The output image buffer * @param[in] image color space */ -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); -void readImage(const std::string& path, Image& image, const ImageReadOptions & imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); +void readImage(const std::string& path, Image& image, const ImageReadOptions& imageReadOptions); /** * @brief read an image with a given path and buffer without any processing such as color conversion @@ -400,7 +406,7 @@ void readImageDirect(const std::string& path, Image& image); void readImageDirect(const std::string& path, Image& image); /** - * @brief log information about the memory usage of the OIIO default shared image cache + * @brief log information about the memory usage of the OIIO default shared image cache */ void logOIIOImageCacheInfo(); @@ -409,43 +415,50 @@ void logOIIOImageCacheInfo(); * @param[in] path The given path to the image * @param[in] image The output image buffer */ -void writeImage(const std::string& path, const Image& image, +void writeImage(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -void writeImage(const std::string& path, const Image& image, +void writeImage(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -void writeImage(const std::string& path, const Image& image, +void writeImage(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -void writeImage(const std::string& path, - const Image& image, +void writeImage(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList(), - const oiio::ROI& displayRoi = oiio::ROI(), + const oiio::ROI& displayRoi = oiio::ROI(), const oiio::ROI& pixelRoi = oiio::ROI()); -void writeImage(const std::string& path, - const Image& image, +void writeImage(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList(), const oiio::ROI& displayRoi = oiio::ROI(), const oiio::ROI& pixelRoi = oiio::ROI()); -void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, +void writeImage(const std::string& path, + const Image& image, + const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -void writeImage(const std::string& path, - const Image& image, +void writeImage(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList(), - const oiio::ROI& displayRoi = oiio::ROI(), + const oiio::ROI& displayRoi = oiio::ROI(), const oiio::ROI& pixelRoi = oiio::ROI()); -void writeImage(const std::string& path, const Image& image, const ImageWriteOptions& options, +void writeImage(const std::string& path, + const Image& image, + const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); /** @@ -454,64 +467,70 @@ void writeImage(const std::string& path, const Image& image, const Ima * @param[in] path The given path to the image * @param[in] image The output image buffer */ -void writeImageWithFloat(const std::string& path, const Image& image, +void writeImageWithFloat(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -void writeImageWithFloat(const std::string& path, const Image& image, +void writeImageWithFloat(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -void writeImageWithFloat(const std::string& path, const Image& image, +void writeImageWithFloat(const std::string& path, + const Image& image, const ImageWriteOptions& options, const oiio::ParamValueList& metadata = oiio::ParamValueList()); -template +template struct ColorTypeInfo { // no size parameter, so no default value. // An error will be raise at compile time if this type traits is not defined. }; -template <> +template<> struct ColorTypeInfo { static const int size = 1; static const oiio::TypeDesc::BASETYPE typeDesc = oiio::TypeDesc::UINT8; }; -template <> +template<> struct ColorTypeInfo { static const int size = 1; static const oiio::TypeDesc::BASETYPE typeDesc = oiio::TypeDesc::FLOAT; }; -template <> +template<> struct ColorTypeInfo { static const int size = 3; static const oiio::TypeDesc::BASETYPE typeDesc = oiio::TypeDesc::UINT8; }; -template <> +template<> struct ColorTypeInfo { static const int size = 3; static const oiio::TypeDesc::BASETYPE typeDesc = oiio::TypeDesc::FLOAT; }; -template <> +template<> struct ColorTypeInfo { static const int size = 4; static const oiio::TypeDesc::BASETYPE typeDesc = oiio::TypeDesc::UINT8; }; -template <> +template<> struct ColorTypeInfo { static const int size = 4; static const oiio::TypeDesc::BASETYPE typeDesc = oiio::TypeDesc::FLOAT; }; -bool tryLoadMask(Image* mask, const std::vector& masksFolders, - const IndexT viewId, const std::string& srcImage, const std::string& fileExtension); +bool tryLoadMask(Image* mask, + const std::vector& masksFolders, + const IndexT viewId, + const std::string& srcImage, + const std::string& fileExtension); /** * Returns the value of ALICEVISION_ROOT environmental variable, or empty string if it is not diff --git a/src/aliceVision/image/io_test.cpp b/src/aliceVision/image/io_test.cpp index c8f31e3b6d..dfe7c44d86 100644 --- a/src/aliceVision/image/io_test.cpp +++ b/src/aliceVision/image/io_test.cpp @@ -23,243 +23,250 @@ using namespace aliceVision::image; using std::string; // tested extensions -static std::vector extensions = { "jpg", "png", "pgm", "ppm", "tiff", "exr" }; +static std::vector extensions = {"jpg", "png", "pgm", "ppm", "tiff", "exr"}; -BOOST_AUTO_TEST_CASE(read_unexisting) { - Image image; - const std::string filename = string(THIS_SOURCE_DIR) + "/unexisting.jpg"; - BOOST_CHECK_THROW(readImage(filename, image, image::EImageColorSpace::NO_CONVERSION), std::exception); +BOOST_AUTO_TEST_CASE(read_unexisting) +{ + Image image; + const std::string filename = string(THIS_SOURCE_DIR) + "/unexisting.jpg"; + BOOST_CHECK_THROW(readImage(filename, image, image::EImageColorSpace::NO_CONVERSION), std::exception); } -BOOST_AUTO_TEST_CASE(read_jpg_grayscale) { - Image image; - const std::string jpg_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_monochrome.jpg"; - BOOST_CHECK_NO_THROW(readImage(jpg_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(1, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0), (unsigned char)255); - BOOST_CHECK_EQUAL(image(0,1), (unsigned char) 0); +BOOST_AUTO_TEST_CASE(read_jpg_grayscale) +{ + Image image; + const std::string jpg_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_monochrome.jpg"; + BOOST_CHECK_NO_THROW(readImage(jpg_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(1, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0), (unsigned char)255); + BOOST_CHECK_EQUAL(image(0, 1), (unsigned char)0); } -BOOST_AUTO_TEST_CASE(read_jpg_rgb) { - Image image; - const std::string jpg_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_color.jpg"; - BOOST_CHECK_NO_THROW(readImage(jpg_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(3, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0).r(), 255); - BOOST_CHECK_EQUAL(image(0,0).g(), 125); - BOOST_CHECK_EQUAL(image(0,0).b(), 11); - BOOST_CHECK_EQUAL(image(0,1).r(), 20); - BOOST_CHECK_EQUAL(image(0,1).g(), 127); - BOOST_CHECK_EQUAL(image(0,1).b(), 255); +BOOST_AUTO_TEST_CASE(read_jpg_rgb) +{ + Image image; + const std::string jpg_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_color.jpg"; + BOOST_CHECK_NO_THROW(readImage(jpg_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(3, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0).r(), 255); + BOOST_CHECK_EQUAL(image(0, 0).g(), 125); + BOOST_CHECK_EQUAL(image(0, 0).b(), 11); + BOOST_CHECK_EQUAL(image(0, 1).r(), 20); + BOOST_CHECK_EQUAL(image(0, 1).g(), 127); + BOOST_CHECK_EQUAL(image(0, 1).b(), 255); } -BOOST_AUTO_TEST_CASE(read_png_grayscale) { - Image image; - const std::string png_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_monochrome.png"; - BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(1, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0), (unsigned char)255); - BOOST_CHECK_EQUAL(image(0,1), (unsigned char) 0); +BOOST_AUTO_TEST_CASE(read_png_grayscale) +{ + Image image; + const std::string png_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_monochrome.png"; + BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(1, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0), (unsigned char)255); + BOOST_CHECK_EQUAL(image(0, 1), (unsigned char)0); } -BOOST_AUTO_TEST_CASE(read_png_rgb) { - Image image; - const std::string png_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_color.png"; - BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(3, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0).r(), 255); - BOOST_CHECK_EQUAL(image(0,0).g(), 125); - BOOST_CHECK_EQUAL(image(0,0).b(), 10); - BOOST_CHECK_EQUAL(image(0,1).r(), 20); - BOOST_CHECK_EQUAL(image(0,1).g(), 127); - BOOST_CHECK_EQUAL(image(0,1).b(), 255); +BOOST_AUTO_TEST_CASE(read_png_rgb) +{ + Image image; + const std::string png_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_color.png"; + BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(3, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0).r(), 255); + BOOST_CHECK_EQUAL(image(0, 0).g(), 125); + BOOST_CHECK_EQUAL(image(0, 0).b(), 10); + BOOST_CHECK_EQUAL(image(0, 1).r(), 20); + BOOST_CHECK_EQUAL(image(0, 1).g(), 127); + BOOST_CHECK_EQUAL(image(0, 1).b(), 255); } -BOOST_AUTO_TEST_CASE(read_png_rgba) { - Image image; - const std::string png_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_color.png"; - BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(4, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0).r(), 255); - BOOST_CHECK_EQUAL(image(0,0).g(), 125); - BOOST_CHECK_EQUAL(image(0,0).b(), 10); - BOOST_CHECK_EQUAL(image(0,0).a(), 255); - BOOST_CHECK_EQUAL(image(0,1).r(), 20); - BOOST_CHECK_EQUAL(image(0,1).g(), 127); - BOOST_CHECK_EQUAL(image(0,1).b(), 255); - BOOST_CHECK_EQUAL(image(0,0).a(), 255); +BOOST_AUTO_TEST_CASE(read_png_rgba) +{ + Image image; + const std::string png_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_color.png"; + BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(4, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0).r(), 255); + BOOST_CHECK_EQUAL(image(0, 0).g(), 125); + BOOST_CHECK_EQUAL(image(0, 0).b(), 10); + BOOST_CHECK_EQUAL(image(0, 0).a(), 255); + BOOST_CHECK_EQUAL(image(0, 1).r(), 20); + BOOST_CHECK_EQUAL(image(0, 1).g(), 127); + BOOST_CHECK_EQUAL(image(0, 1).b(), 255); + BOOST_CHECK_EQUAL(image(0, 0).a(), 255); } -BOOST_AUTO_TEST_CASE(read_pgm) { - Image image; - const std::string pgm_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels.pgm"; - BOOST_CHECK_NO_THROW(readImage(pgm_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(1, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0), (unsigned char)255); - BOOST_CHECK_EQUAL(image(0,1), (unsigned char) 0); +BOOST_AUTO_TEST_CASE(read_pgm) +{ + Image image; + const std::string pgm_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels.pgm"; + BOOST_CHECK_NO_THROW(readImage(pgm_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(1, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0), (unsigned char)255); + BOOST_CHECK_EQUAL(image(0, 1), (unsigned char)0); } -BOOST_AUTO_TEST_CASE(read_pgm_withcomments) { - Image image; - const std::string pgm_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_gray.pgm"; - BOOST_CHECK_NO_THROW(readImage(pgm_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(1, image.Depth()); - BOOST_CHECK_EQUAL(image(0,0), (unsigned char)255); - BOOST_CHECK_EQUAL(image(0,1), (unsigned char) 0); +BOOST_AUTO_TEST_CASE(read_pgm_withcomments) +{ + Image image; + const std::string pgm_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels_gray.pgm"; + BOOST_CHECK_NO_THROW(readImage(pgm_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(1, image.Depth()); + BOOST_CHECK_EQUAL(image(0, 0), (unsigned char)255); + BOOST_CHECK_EQUAL(image(0, 1), (unsigned char)0); } -BOOST_AUTO_TEST_CASE(read_ppm) { - Image image; - const std::string ppm_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels.ppm"; - BOOST_CHECK_NO_THROW(readImage(ppm_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(2, image.Width()); - BOOST_CHECK_EQUAL(1, image.Height()); - BOOST_CHECK_EQUAL(3, image.Depth()); - - BOOST_CHECK_EQUAL(image(0,0).r(), 255); - BOOST_CHECK_EQUAL(image(0,0).g(), 255); - BOOST_CHECK_EQUAL(image(0,0).b(), 255); - BOOST_CHECK_EQUAL(image(0,1).r(), 0); - BOOST_CHECK_EQUAL(image(0,1).g(), 0); - BOOST_CHECK_EQUAL(image(0,1).b(), 0); +BOOST_AUTO_TEST_CASE(read_ppm) +{ + Image image; + const std::string ppm_filename = string(THIS_SOURCE_DIR) + "/image_test/two_pixels.ppm"; + BOOST_CHECK_NO_THROW(readImage(ppm_filename, image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(2, image.Width()); + BOOST_CHECK_EQUAL(1, image.Height()); + BOOST_CHECK_EQUAL(3, image.Depth()); + + BOOST_CHECK_EQUAL(image(0, 0).r(), 255); + BOOST_CHECK_EQUAL(image(0, 0).g(), 255); + BOOST_CHECK_EQUAL(image(0, 0).b(), 255); + BOOST_CHECK_EQUAL(image(0, 1).r(), 0); + BOOST_CHECK_EQUAL(image(0, 1).g(), 0); + BOOST_CHECK_EQUAL(image(0, 1).b(), 0); } -BOOST_AUTO_TEST_CASE(read_write_grayscale) { - Image image(1,2); - image(0,0) = 255; - image(1,0) = 0; - - for(const auto& extension : extensions) - { - const std::string filename = "test_write." + extension; - BOOST_CHECK_NO_THROW(writeImage(filename, image, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - Image read_image; - BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(read_image(0,0), image(0,0)); - BOOST_CHECK_EQUAL(read_image(1,0), image(1,0)); - remove(filename.c_str()); - } +BOOST_AUTO_TEST_CASE(read_write_grayscale) +{ + Image image(1, 2); + image(0, 0) = 255; + image(1, 0) = 0; + + for (const auto& extension : extensions) + { + const std::string filename = "test_write." + extension; + BOOST_CHECK_NO_THROW(writeImage(filename, image, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + Image read_image; + BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(read_image(0, 0), image(0, 0)); + BOOST_CHECK_EQUAL(read_image(1, 0), image(1, 0)); + remove(filename.c_str()); + } } -BOOST_AUTO_TEST_CASE(read_write_rgb) { - Image image(1,2); - image(0,0) = RGBColor(255, 0, 255); - image(1,0) = RGBColor( 0, 255, 0); +BOOST_AUTO_TEST_CASE(read_write_rgb) +{ + Image image(1, 2); + image(0, 0) = RGBColor(255, 0, 255); + image(1, 0) = RGBColor(0, 255, 0); - for(const auto& extension : extensions) - { - const std::string filename = "test_write_rgb." + extension; - BOOST_CHECK_NO_THROW(writeImage(filename, image, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + for (const auto& extension : extensions) + { + const std::string filename = "test_write_rgb." + extension; + BOOST_CHECK_NO_THROW(writeImage(filename, image, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + Image read_image; + BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); + + if (extension != "jpg") + { + // doesn't have compression + BOOST_CHECK_EQUAL(read_image(0, 0).r(), image(0, 0).r()); + BOOST_CHECK_EQUAL(read_image(0, 0).g(), image(0, 0).g()); + BOOST_CHECK_EQUAL(read_image(0, 0).b(), image(0, 0).b()); + BOOST_CHECK_EQUAL(read_image(1, 0).r(), image(1, 0).r()); + BOOST_CHECK_EQUAL(read_image(1, 0).g(), image(1, 0).g()); + BOOST_CHECK_EQUAL(read_image(1, 0).b(), image(1, 0).b()); + } + remove(filename.c_str()); + } +} - Image read_image; - BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); +BOOST_AUTO_TEST_CASE(read_write_rgba) +{ + Image image(1, 2); + image(0, 0) = RGBAColor(255, 0, 255, 255); + image(1, 0) = RGBAColor(0, 255, 0, 255); - if(extension != "jpg") + for (const auto& extension : extensions) { - // doesn't have compression - BOOST_CHECK_EQUAL(read_image(0,0).r(), image(0,0).r()); - BOOST_CHECK_EQUAL(read_image(0,0).g(), image(0,0).g()); - BOOST_CHECK_EQUAL(read_image(0,0).b(), image(0,0).b()); - BOOST_CHECK_EQUAL(read_image(1,0).r(), image(1,0).r()); - BOOST_CHECK_EQUAL(read_image(1,0).g(), image(1,0).g()); - BOOST_CHECK_EQUAL(read_image(1,0).b(), image(1,0).b()); + if (extension == "jpg" || extension == "pgm" || extension == "ppm") + continue; // doesn't support 4 channels + + const std::string filename = "test_write_rgba." + extension; + BOOST_CHECK_NO_THROW(writeImage(filename, image, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + Image read_image; + BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(read_image(0, 0).r(), image(0, 0).r()); + BOOST_CHECK_EQUAL(read_image(0, 0).g(), image(0, 0).g()); + BOOST_CHECK_EQUAL(read_image(0, 0).b(), image(0, 0).b()); + BOOST_CHECK_EQUAL(read_image(0, 0).a(), image(0, 0).a()); + BOOST_CHECK_EQUAL(read_image(1, 0).r(), image(1, 0).r()); + BOOST_CHECK_EQUAL(read_image(1, 0).g(), image(1, 0).g()); + BOOST_CHECK_EQUAL(read_image(1, 0).b(), image(1, 0).b()); + BOOST_CHECK_EQUAL(read_image(1, 0).a(), image(1, 0).a()); + remove(filename.c_str()); } - remove(filename.c_str()); - } } -BOOST_AUTO_TEST_CASE(read_write_rgba) { - Image image(1,2); - image(0,0) = RGBAColor(255, 0, 255, 255); - image(1,0) = RGBAColor( 0, 255, 0, 255); - - for(const auto& extension : extensions) - { - if(extension == "jpg" || - extension == "pgm" || - extension == "ppm") - continue; // doesn't support 4 channels - - const std::string filename = "test_write_rgba." + extension; - BOOST_CHECK_NO_THROW(writeImage(filename, image, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - Image read_image; - BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(read_image(0,0).r(), image(0,0).r()); - BOOST_CHECK_EQUAL(read_image(0,0).g(), image(0,0).g()); - BOOST_CHECK_EQUAL(read_image(0,0).b(), image(0,0).b()); - BOOST_CHECK_EQUAL(read_image(0,0).a(), image(0,0).a()); - BOOST_CHECK_EQUAL(read_image(1,0).r(), image(1,0).r()); - BOOST_CHECK_EQUAL(read_image(1,0).g(), image(1,0).g()); - BOOST_CHECK_EQUAL(read_image(1,0).b(), image(1,0).b()); - BOOST_CHECK_EQUAL(read_image(1,0).a(), image(1,0).a()); - remove(filename.c_str()); - } -} +BOOST_AUTO_TEST_CASE(read_write_from_rgb_to_grayscale) +{ + Image imageRGB(1, 2); + imageRGB(0, 0) = RGBColor(0, 0, 0); + imageRGB(1, 0) = RGBColor(255, 255, 255); + + Image imageGrayscale(1, 2); + imageGrayscale(0, 0) = 0; + imageGrayscale(1, 0) = 255; -BOOST_AUTO_TEST_CASE(read_write_from_rgb_to_grayscale) { - Image imageRGB(1,2); - imageRGB(0,0) = RGBColor( 0, 0, 0); - imageRGB(1,0) = RGBColor(255, 255, 255); - - Image imageGrayscale(1,2); - imageGrayscale(0,0) = 0; - imageGrayscale(1,0) = 255; - - for(const auto& extension : extensions) - { - const std::string filename = "test_write_from_grayscale." + extension; - BOOST_CHECK_NO_THROW(writeImage(filename, imageRGB, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - Image read_image; - BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); - //BOOST_CHECK_EQUAL(read_image, imageGrayscale); - remove(filename.c_str()); - } + for (const auto& extension : extensions) + { + const std::string filename = "test_write_from_grayscale." + extension; + BOOST_CHECK_NO_THROW(writeImage(filename, imageRGB, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + Image read_image; + BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); + // BOOST_CHECK_EQUAL(read_image, imageGrayscale); + remove(filename.c_str()); + } } -BOOST_AUTO_TEST_CASE(read_write_from_grayscale_to_rgb) { - Image imageRGB(1,2); - imageRGB(0,0) = RGBColor( 0, 0, 0); - imageRGB(1,0) = RGBColor(255, 255, 255); - - Image imageGrayscale(1,2); - imageGrayscale(0,0) = 0; - imageGrayscale(1,0) = 255; - - for(const auto& extension : extensions) - { - const std::string filename = "test_write_from_rgb." + extension; - BOOST_CHECK_NO_THROW(writeImage(filename, imageGrayscale, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); - - Image read_image; - BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_EQUAL(read_image(0,0).r(), imageRGB(0,0).r()); - BOOST_CHECK_EQUAL(read_image(0,0).g(), imageRGB(0,0).g()); - BOOST_CHECK_EQUAL(read_image(0,0).b(), imageRGB(0,0).b()); - BOOST_CHECK_EQUAL(read_image(1,0).r(), imageRGB(1,0).r()); - BOOST_CHECK_EQUAL(read_image(1,0).g(), imageRGB(1,0).g()); - BOOST_CHECK_EQUAL(read_image(1,0).b(), imageRGB(1,0).b()); - remove(filename.c_str()); - } +BOOST_AUTO_TEST_CASE(read_write_from_grayscale_to_rgb) +{ + Image imageRGB(1, 2); + imageRGB(0, 0) = RGBColor(0, 0, 0); + imageRGB(1, 0) = RGBColor(255, 255, 255); + + Image imageGrayscale(1, 2); + imageGrayscale(0, 0) = 0; + imageGrayscale(1, 0) = 255; + + for (const auto& extension : extensions) + { + const std::string filename = "test_write_from_rgb." + extension; + BOOST_CHECK_NO_THROW(writeImage(filename, imageGrayscale, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + + Image read_image; + BOOST_CHECK_NO_THROW(readImage(filename, read_image, image::EImageColorSpace::NO_CONVERSION)); + BOOST_CHECK_EQUAL(read_image(0, 0).r(), imageRGB(0, 0).r()); + BOOST_CHECK_EQUAL(read_image(0, 0).g(), imageRGB(0, 0).g()); + BOOST_CHECK_EQUAL(read_image(0, 0).b(), imageRGB(0, 0).b()); + BOOST_CHECK_EQUAL(read_image(1, 0).r(), imageRGB(1, 0).r()); + BOOST_CHECK_EQUAL(read_image(1, 0).g(), imageRGB(1, 0).g()); + BOOST_CHECK_EQUAL(read_image(1, 0).b(), imageRGB(1, 0).b()); + remove(filename.c_str()); + } } diff --git a/src/aliceVision/image/jetColorMap.cpp b/src/aliceVision/image/jetColorMap.cpp index abe070ad85..d2d1736a6c 100644 --- a/src/aliceVision/image/jetColorMap.cpp +++ b/src/aliceVision/image/jetColorMap.cpp @@ -8,33 +8,29 @@ namespace aliceVision { -static float jetr[64] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0.0625, 0.1250, 0.1875, 0.2500, 0.3125, 0.3750, 0.4375, 0.5000, 0.5625, - 0.6250, 0.6875, 0.7500, 0.8125, 0.8750, 0.9375, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, - 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, - 1.0000, 0.9375, 0.8750, 0.8125, 0.7500, 0.6875, 0.6250, 0.5625, 0.5000}; - -static float jetg[64] = {0, 0, 0, 0, 0, 0, 0, 0, 0.0625, 0.1250, 0.1875, - 0.2500, 0.3125, 0.3750, 0.4375, 0.5000, 0.5625, 0.6250, 0.6875, 0.7500, 0.8125, 0.8750, - 0.9375, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, - 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.9375, 0.8750, 0.8125, 0.7500, - 0.6875, 0.6250, 0.5625, 0.5000, 0.4375, 0.3750, 0.3125, 0.2500, 0.1875, 0.1250, 0.0625, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -static float jetb[64] = {0.5625, 0.6250, 0.6875, 0.7500, 0.8125, 0.8750, 0.9375, 1.0000, 1.0000, 1.0000, 1.0000, - 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, - 1.0000, 1.0000, 0.9375, 0.8750, 0.8125, 0.7500, 0.6875, 0.6250, 0.5625, 0.5000, 0.4375, - 0.3750, 0.3125, 0.2500, 0.1875, 0.1250, 0.0625, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; - +static float jetr[64] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0625, 0.1250, + 0.1875, 0.2500, 0.3125, 0.3750, 0.4375, 0.5000, 0.5625, 0.6250, 0.6875, 0.7500, 0.8125, 0.8750, 0.9375, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 0.9375, 0.8750, 0.8125, 0.7500, 0.6875, 0.6250, 0.5625, 0.5000}; + +static float jetg[64] = {0, 0, 0, 0, 0, 0, 0, 0, 0.0625, 0.1250, 0.1875, 0.2500, 0.3125, + 0.3750, 0.4375, 0.5000, 0.5625, 0.6250, 0.6875, 0.7500, 0.8125, 0.8750, 0.9375, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 0.9375, 0.8750, 0.8125, 0.7500, 0.6875, 0.6250, 0.5625, 0.5000, 0.4375, 0.3750, 0.3125, 0.2500, + 0.1875, 0.1250, 0.0625, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static float jetb[64] = {0.5625, 0.6250, 0.6875, 0.7500, 0.8125, 0.8750, 0.9375, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.9375, 0.8750, + 0.8125, 0.7500, 0.6875, 0.6250, 0.5625, 0.5000, 0.4375, 0.3750, 0.3125, 0.2500, 0.1875, 0.1250, 0.0625, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; image::RGBfColor getColorFromJetColorMap(float value) { - if(value <= 0.0f) + if (value <= 0.0f) return image::RGBfColor(0, 0, 0); - if(value >= 1.0f) + if (value >= 1.0f) return image::RGBfColor(1.0f, 1.0f, 1.0f); const float idx_f = value * 63.0f; float integral; @@ -51,9 +47,8 @@ image::RGBfColor getColorFromJetColorMap(float value) rgb getRGBFromJetColorMap(float value) { const image::RGBfColor color = getColorFromJetColorMap(value); - return {static_cast(color.r() * 255.0f), - static_cast(color.g() * 255.0f), - static_cast(color.b() * 255.0f)}; + return { + static_cast(color.r() * 255.0f), static_cast(color.g() * 255.0f), static_cast(color.b() * 255.0f)}; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/image/jetColorMap.hpp b/src/aliceVision/image/jetColorMap.hpp index 6b5ec40adc..9cd7549ad8 100644 --- a/src/aliceVision/image/jetColorMap.hpp +++ b/src/aliceVision/image/jetColorMap.hpp @@ -31,4 +31,4 @@ rgb getRGBFromJetColorMap(float value); */ image::RGBfColor getColorFromJetColorMap(float value); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/image/pixelTypes.hpp b/src/aliceVision/image/pixelTypes.hpp index aeacf82ce1..ad949ebefa 100644 --- a/src/aliceVision/image/pixelTypes.hpp +++ b/src/aliceVision/image/pixelTypes.hpp @@ -9,473 +9,389 @@ #include "aliceVision/numeric/numeric.hpp" -namespace aliceVision +namespace aliceVision { +namespace image { + +/** + * Red Greeen Blue template pixel type + * @tparam T type of each channel + */ +template +class Rgb : public Eigen::Matrix { - namespace image - { + using Base = Eigen::Matrix; + using TBase = T; + public: + //------------------------------ + //-- construction method /** - * Red Greeen Blue template pixel type - * @tparam T type of each channel + * @brief Full constructor + * @param red Red value + * @param green value + * @param blue value */ - template - class Rgb : public Eigen::Matrix + inline Rgb(T red, T green, T blue) + : Base(red, green, blue) + {} + + /** + * @brief Copy constructor + * @param val Source RGB value + */ + explicit inline Rgb(const Base& val) + : Base(val) + {} + + /** + * @brief Single value construction + * @param val Value to set in each channel + * @note This is equivalent to Rgb( val , val , val ) + */ + explicit inline Rgb(const T val = 0) + : Base(val, val, val) + {} + //-- construction method + //------------------------------ + + //------------------------------ + //-- accessors/getters methods + /** + * @brief Get readonly Red channel value + * @return Red channel value + */ + inline const T& r() const { return (*this)(0); } + + /** + * @brief Get rw Red channel value + * @return Red channel value + */ + inline T& r() { return (*this)(0); } + + /** + * @brief Get readonly Green channel value + * @return Green channel value + */ + inline const T& g() const { return (*this)(1); } + + /** + * @brief Get rw Green channel value + * @return Green channel value + */ + inline T& g() { return (*this)(1); } + + /** + * @brief Get readonly Blue channel value + * @return Blue channel value + */ + inline const T& b() const { return (*this)(2); } + + /** + * @brief Get rw Blue channel value + * @return Blue channel value + */ + inline T& b() { return (*this)(2); } + + /** + * @brief Get approximate Gray value using rec601 Luma conversion + * @return Luminance value of pixel + */ + inline operator T() const { return T(0.299 * r() + 0.587 * g() + 0.114 * b()); } + + //-- accessors/getters methods + //------------------------------ + + /** + * @brief stream operator + * @param os Stream in which rgb value is outputed + * @param col Color to store into the stream + * @return stream after output + */ + friend std::ostream& operator<<(std::ostream& os, const Rgb& col) { - using Base = Eigen::Matrix; - using TBase = T; - public: - - //------------------------------ - //-- construction method - /** - * @brief Full constructor - * @param red Red value - * @param green value - * @param blue value - */ - inline Rgb( T red, T green, T blue ) - : Base( red, green, blue ) - { - - } - - /** - * @brief Copy constructor - * @param val Source RGB value - */ - explicit inline Rgb( const Base& val ) - : Base( val ) - { - - } - - /** - * @brief Single value construction - * @param val Value to set in each channel - * @note This is equivalent to Rgb( val , val , val ) - */ - explicit inline Rgb( const T val = 0 ) - : Base( val, val, val ) - { - - } - //-- construction method - //------------------------------ - - //------------------------------ - //-- accessors/getters methods - /** - * @brief Get readonly Red channel value - * @return Red channel value - */ - inline const T& r() const - { - return ( *this )( 0 ); - } - - /** - * @brief Get rw Red channel value - * @return Red channel value - */ - inline T& r() - { - return ( *this )( 0 ); - } - - /** - * @brief Get readonly Green channel value - * @return Green channel value - */ - inline const T& g() const - { - return ( *this )( 1 ); - } - - /** - * @brief Get rw Green channel value - * @return Green channel value - */ - inline T& g() - { - return ( *this )( 1 ); - } - - /** - * @brief Get readonly Blue channel value - * @return Blue channel value - */ - inline const T& b() const - { - return ( *this )( 2 ); - } - - /** - * @brief Get rw Blue channel value - * @return Blue channel value - */ - inline T& b() - { - return ( *this )( 2 ); - } - - /** - * @brief Get approximate Gray value using rec601 Luma conversion - * @return Luminance value of pixel - */ - inline operator T() const - { - return T( 0.299 * r() + 0.587 * g() + 0.114 * b() ); - } - - //-- accessors/getters methods - //------------------------------ - - /** - * @brief stream operator - * @param os Stream in which rgb value is outputed - * @param col Color to store into the stream - * @return stream after output - */ - friend std::ostream& operator<<( std::ostream& os, const Rgb& col ) - { - os << " {" ; - for( int i = 0; i < 2; ++i ) + os << " {"; + for (int i = 0; i < 2; ++i) { - os << col( i ) << ","; + os << col(i) << ","; } - os << col( 2 ) << "} "; + os << col(2) << "} "; return os; - } - - /** - * @brief Elementwise substraction - * @param other the other element to substract - * @return Rgb color after substraction - * @note This does not modify the Rgb value (ie: only return a modified copy) - */ - inline Rgb operator -( const Rgb& other ) const - { - return Rgb( ((*this)(0) - other(0)), ((*this)(1) - other(1)), ((*this)(2) - other(2))); - } - - /** - * @brief Elementwise addition - * @param other the other element to substract - * @return Rgb color after addition - * @note This does not modify the Rgb value (ie: only return a modified copy) - */ - inline Rgb operator +( const Rgb& other ) const - { - return Rgb( ((*this)(0) + other(0)), ((*this)(1) + other(1)), ((*this)(2) + other(2))); - } - - /** - * @brief Elementwise multiplication - * @param other the other element to multiply - * @return Rgb color after multiply - * @note This does not modify the Rgb value (ie: only return a modified copy) - */ - inline Rgb operator *( const Rgb& other ) const - { - return Rgb( ((*this)(0) * other(0)), ((*this)(1) * other(1)), ((*this)(2) * other(2))); - } - - - /** - * @brief scalar division - * @param val Scalar divisor factor - * @return Rgb color after scalar division - * @note This does not modify the Rgb value (ie: only return a modified copy) - */ - template - inline Rgb operator /( const Z& val ) const - { - return Rgb( T( ( Z )( ( *this )( 0 ) ) / val ), - T( ( Z )( ( *this )( 1 ) ) / val ), - T( ( Z )( ( *this )( 2 ) ) / val ) ); - } - - - /** - * @brief scalar multiplication - * @param val Scale multiplication factor - * @return Rgb color after scalar multiplication - * @note This does not modify the Rgb value (ie: only return a modified copy) - */ - template - inline Rgb operator *( const Z& val ) const - { - return Rgb( T( ( Z )( *this )( 0 ) * val ), - T( ( Z )( *this )( 1 ) * val ), - T( ( Z )( *this )( 2 ) * val ) ); - } - }; - - /// Instantiation for unsigned char color component - using RGBColor = Rgb; - /// Instantiation for float color component - using RGBfColor = Rgb; - - /** - * @brief RGBA templated pixel type - */ - template - class Rgba : public Eigen::Matrix + } + + /** + * @brief Elementwise substraction + * @param other the other element to substract + * @return Rgb color after substraction + * @note This does not modify the Rgb value (ie: only return a modified copy) + */ + inline Rgb operator-(const Rgb& other) const { return Rgb(((*this)(0) - other(0)), ((*this)(1) - other(1)), ((*this)(2) - other(2))); } + + /** + * @brief Elementwise addition + * @param other the other element to substract + * @return Rgb color after addition + * @note This does not modify the Rgb value (ie: only return a modified copy) + */ + inline Rgb operator+(const Rgb& other) const { return Rgb(((*this)(0) + other(0)), ((*this)(1) + other(1)), ((*this)(2) + other(2))); } + + /** + * @brief Elementwise multiplication + * @param other the other element to multiply + * @return Rgb color after multiply + * @note This does not modify the Rgb value (ie: only return a modified copy) + */ + inline Rgb operator*(const Rgb& other) const { return Rgb(((*this)(0) * other(0)), ((*this)(1) * other(1)), ((*this)(2) * other(2))); } + + /** + * @brief scalar division + * @param val Scalar divisor factor + * @return Rgb color after scalar division + * @note This does not modify the Rgb value (ie: only return a modified copy) + */ + template + inline Rgb operator/(const Z& val) const { - using Base = Eigen::Matrix; - public: - - //------------------------------ - //-- construction method - /** - * @brief Full constructor - * @param red Red component value - * @param green Green component value - * @param blue component value - * @param alpha component value - */ - inline Rgba( const T red, const T green, const T blue, const T alpha = T(1) ) - : Base( red, green, blue, alpha ) - { - } - - /** - * @brief Copy constructor from internal data - * @param val Source RGBA value - */ - explicit inline Rgba( const Base& val ) - : Base( val ) - { - } - - /** - * @brief RGBA constructor with default alpha value to 1 - * @param val Value to set in each RGB component - * @note This is equivalent to RGBA( val , val , val , 1 ) - */ - explicit inline Rgba( const T val ) - : Base( val, val, val, T(1) ) - { - } - - /** - * @brief Default RGBA constructor set all channels to zero (including the alpha channel) - * @warning The alpha channel is initialized to 0. - * It is used in generic/templated code like "sampler" - * which creates an empty color and accumulate color contributions. - */ - explicit inline Rgba() - : Base( T(0), T(0), T(0), T(0) ) - { - } - - /** - * @brief Copy constructor - * @param val Source RGBA value - */ - inline Rgba( const RGBColor & val, const T alpha ) - : Base( val.r(), val.g(), val.b(), alpha ) - { - } - - //-- construction method - //------------------------------ - - //------------------------------ - //-- accessors/getters methods - /** - * @brief Get readonly Red channel value - * @return Red channel value - */ - inline const T& r() const - { - return ( *this )( 0 ); - } - /** - * @brief Get rw Red channel value - * @return Red channel value - */ - inline T& r() - { - return ( *this )( 0 ); - } - /** - * @brief Get readonly Green channel value - * @return Green channel value - */ - inline const T& g() const - { - return ( *this )( 1 ); - } - /** - * @brief Get rw Green channel value - * @return Green channel value - */ - inline T& g() - { - return ( *this )( 1 ); - } - /** - * @brief Get readonly Blue channel value - * @return Blue channel value - */ - inline const T& b() const - { - return ( *this )( 2 ); - } - /** - * @brief Get rw Blue channel value - * @return Blue channel value - */ - inline T& b() - { - return ( *this )( 2 ); - } - /** - * @brief Get readonly Alpha channel value - * @return Alpha channel value - */ - inline const T& a() const - { - return ( *this )( 3 ); - } - /** - * @brief Get rw Alpha channel value - * @return Alpha channel value - */ - inline T& a() - { - return ( *this )( 3 ); - } - - /** - * @brief Get approximate Gray value using rec601 Luma conversion - * @return Luminance value of pixel - */ - inline operator T() const - { - return T( 0.299 * r() + 0.587 * g() + 0.114 * b() ); - } - //-- accessors/getters methods - //------------------------------ - /** - * @brief stream operator - * @param os Stream in which rgb value is outputed - * @param col Color to store into the stream - * @return stream after output - */ - friend std::ostream& operator<<( std::ostream& os, const Rgba& col ) - { - os << " {" ; - for( int i = 0; i < 3; ++i ) + return Rgb(T((Z)((*this)(0)) / val), T((Z)((*this)(1)) / val), T((Z)((*this)(2)) / val)); + } + + /** + * @brief scalar multiplication + * @param val Scale multiplication factor + * @return Rgb color after scalar multiplication + * @note This does not modify the Rgb value (ie: only return a modified copy) + */ + template + inline Rgb operator*(const Z& val) const + { + return Rgb(T((Z)(*this)(0) * val), T((Z)(*this)(1) * val), T((Z)(*this)(2) * val)); + } +}; + +/// Instantiation for unsigned char color component +using RGBColor = Rgb; +/// Instantiation for float color component +using RGBfColor = Rgb; + +/** + * @brief RGBA templated pixel type + */ +template +class Rgba : public Eigen::Matrix +{ + using Base = Eigen::Matrix; + + public: + //------------------------------ + //-- construction method + /** + * @brief Full constructor + * @param red Red component value + * @param green Green component value + * @param blue component value + * @param alpha component value + */ + inline Rgba(const T red, const T green, const T blue, const T alpha = T(1)) + : Base(red, green, blue, alpha) + {} + + /** + * @brief Copy constructor from internal data + * @param val Source RGBA value + */ + explicit inline Rgba(const Base& val) + : Base(val) + {} + + /** + * @brief RGBA constructor with default alpha value to 1 + * @param val Value to set in each RGB component + * @note This is equivalent to RGBA( val , val , val , 1 ) + */ + explicit inline Rgba(const T val) + : Base(val, val, val, T(1)) + {} + + /** + * @brief Default RGBA constructor set all channels to zero (including the alpha channel) + * @warning The alpha channel is initialized to 0. + * It is used in generic/templated code like "sampler" + * which creates an empty color and accumulate color contributions. + */ + explicit inline Rgba() + : Base(T(0), T(0), T(0), T(0)) + {} + + /** + * @brief Copy constructor + * @param val Source RGBA value + */ + inline Rgba(const RGBColor& val, const T alpha) + : Base(val.r(), val.g(), val.b(), alpha) + {} + + //-- construction method + //------------------------------ + + //------------------------------ + //-- accessors/getters methods + /** + * @brief Get readonly Red channel value + * @return Red channel value + */ + inline const T& r() const { return (*this)(0); } + /** + * @brief Get rw Red channel value + * @return Red channel value + */ + inline T& r() { return (*this)(0); } + /** + * @brief Get readonly Green channel value + * @return Green channel value + */ + inline const T& g() const { return (*this)(1); } + /** + * @brief Get rw Green channel value + * @return Green channel value + */ + inline T& g() { return (*this)(1); } + /** + * @brief Get readonly Blue channel value + * @return Blue channel value + */ + inline const T& b() const { return (*this)(2); } + /** + * @brief Get rw Blue channel value + * @return Blue channel value + */ + inline T& b() { return (*this)(2); } + /** + * @brief Get readonly Alpha channel value + * @return Alpha channel value + */ + inline const T& a() const { return (*this)(3); } + /** + * @brief Get rw Alpha channel value + * @return Alpha channel value + */ + inline T& a() { return (*this)(3); } + + /** + * @brief Get approximate Gray value using rec601 Luma conversion + * @return Luminance value of pixel + */ + inline operator T() const { return T(0.299 * r() + 0.587 * g() + 0.114 * b()); } + //-- accessors/getters methods + //------------------------------ + /** + * @brief stream operator + * @param os Stream in which rgb value is outputed + * @param col Color to store into the stream + * @return stream after output + */ + friend std::ostream& operator<<(std::ostream& os, const Rgba& col) + { + os << " {"; + for (int i = 0; i < 3; ++i) { - os << col( i ) << ","; + os << col(i) << ","; } - os << col( 3 ) << "} "; + os << col(3) << "} "; return os; - } - - /** - * @brief scalar division - * @param val Scalar divisor factor - * @return Rgba color after scalar division - * @note This does not modify the Rgba value (ie: only return a modified copy) - */ - template - inline Rgba operator /( const Z& val ) const - { - return Rgba( T( ( Z )( *this )( 0 ) / val ), - T( ( Z )( *this )( 1 ) / val ), - T( ( Z )( *this )( 2 ) / val ), - T( ( Z )( *this )( 3 ) / val ) ); - } - - /** - * @brief scalar multiplication - * @param val Scale multiplication factor - * @return Rgba color after scalar multiplication - * @note This does not modify the Rgba value (ie: only return a modified copy) - */ - template - inline Rgba operator *( const Z& val ) const - { - return Rgba( T( ( Z )( *this )( 0 ) * val ), - T( ( Z )( *this )( 1 ) * val ), - T( ( Z )( *this )( 2 ) * val ), - T( ( Z )( *this )( 3 ) * val ) ); - } - - /** - * @brief Elementwise addition - * @param other the other element to substract - * @return Rgb color after addition - * @note This does not modify the Rgb value (ie: only return a modified copy) - */ - inline Rgba operator +( const Rgba& other ) const - { - return Rgba( ((*this)(0) + other(0)), ((*this)(1) + other(1)), ((*this)(2) + other(2)), ((*this)(3) + other(3))); - } - }; - - typedef Rgba RGBAColor; - typedef Rgba RGBAfColor; - - /// Instantiation for unsigned char color component - using RGBAColor = Rgba; - /// Instantiation for float color component - using RGBAfColor = Rgba; - - const RGBColor WHITE( 255, 255, 255 ); - const RGBColor BLACK( 0, 0, 0 ); - const RGBColor BLUE( 0, 0, 255 ); - const RGBColor RED( 255, 0, 0 ); - const RGBColor GREEN( 0, 255, 0 ); - const RGBColor YELLOW( 255, 255, 0 ); - const RGBColor CYAN( 0, 255, 255 ); - const RGBColor MAGENTA( 255, 0, 255 ); - - const RGBfColor FWHITE(1.0f, 1.0f, 1.0f); - const RGBfColor FBLACK( .0f, .0f, .0f); - - template - struct NbChannels - { - // no size parameter, so no default value. - // An error will be raise at compile time if this type traits is not defined. - }; + } - template <> - struct NbChannels - { - static const int size = 1; - }; - template <> - struct NbChannels - { - static const int size = 1; - }; - template <> - struct NbChannels - { - static const int size = 3; - }; - template <> - struct NbChannels + /** + * @brief scalar division + * @param val Scalar divisor factor + * @return Rgba color after scalar division + * @note This does not modify the Rgba value (ie: only return a modified copy) + */ + template + inline Rgba operator/(const Z& val) const { - static const int size = 3; - }; - template <> - struct NbChannels + return Rgba(T((Z)(*this)(0) / val), T((Z)(*this)(1) / val), T((Z)(*this)(2) / val), T((Z)(*this)(3) / val)); + } + + /** + * @brief scalar multiplication + * @param val Scale multiplication factor + * @return Rgba color after scalar multiplication + * @note This does not modify the Rgba value (ie: only return a modified copy) + */ + template + inline Rgba operator*(const Z& val) const { - static const int size = 4; - }; - template <> - struct NbChannels + return Rgba(T((Z)(*this)(0) * val), T((Z)(*this)(1) * val), T((Z)(*this)(2) * val), T((Z)(*this)(3) * val)); + } + + /** + * @brief Elementwise addition + * @param other the other element to substract + * @return Rgb color after addition + * @note This does not modify the Rgb value (ie: only return a modified copy) + */ + inline Rgba operator+(const Rgba& other) const { - static const int size = 4; - }; + return Rgba(((*this)(0) + other(0)), ((*this)(1) + other(1)), ((*this)(2) + other(2)), ((*this)(3) + other(3))); + } +}; + +typedef Rgba RGBAColor; +typedef Rgba RGBAfColor; + +/// Instantiation for unsigned char color component +using RGBAColor = Rgba; +/// Instantiation for float color component +using RGBAfColor = Rgba; + +const RGBColor WHITE(255, 255, 255); +const RGBColor BLACK(0, 0, 0); +const RGBColor BLUE(0, 0, 255); +const RGBColor RED(255, 0, 0); +const RGBColor GREEN(0, 255, 0); +const RGBColor YELLOW(255, 255, 0); +const RGBColor CYAN(0, 255, 255); +const RGBColor MAGENTA(255, 0, 255); + +const RGBfColor FWHITE(1.0f, 1.0f, 1.0f); +const RGBfColor FBLACK(.0f, .0f, .0f); + +template +struct NbChannels +{ + // no size parameter, so no default value. + // An error will be raise at compile time if this type traits is not defined. +}; - - } // namespace image -} // namespace aliceVision +template<> +struct NbChannels +{ + static const int size = 1; +}; +template<> +struct NbChannels +{ + static const int size = 1; +}; +template<> +struct NbChannels +{ + static const int size = 3; +}; +template<> +struct NbChannels +{ + static const int size = 3; +}; +template<> +struct NbChannels +{ + static const int size = 4; +}; +template<> +struct NbChannels +{ + static const int size = 4; +}; +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/resampling.hpp b/src/aliceVision/image/resampling.hpp index 4489c40930..d57425868e 100644 --- a/src/aliceVision/image/resampling.hpp +++ b/src/aliceVision/image/resampling.hpp @@ -20,79 +20,79 @@ namespace oiio = OIIO; namespace aliceVision { namespace image { - - /** - * @brief Downscale an image using a given type of sampler. - * @param[in] src source image to downscale - * @param[out] out image to store the downscaled result - * @param[in] downscale downscale value - */ - template - void downscaleImage(const Image& src, Image& out, int downscale) - { - const int new_width = src.Width() / downscale; - const int new_height = src.Height() / downscale; - - out.resize(new_width, new_height); - - const Sampler2d sampler; - const float downscalef = downscale; - for(int i = 0; i < new_height; ++i) - { - for(int j = 0; j < new_width; ++j) - { - // Use .5f offset to ensure mid pixel and correct sampling - out(i, j) = sampler(src, downscalef * (i + .5f), downscalef * (j + .5f)); - } - } - } - - /** - ** Half sample an image (ie reduce its size by a factor 2) using bilinear interpolation - ** @param[in] src input image - ** @param[out] out output image - **/ - template < typename Image > - void ImageHalfSample( const Image & src , Image & out ) - { + +/** + * @brief Downscale an image using a given type of sampler. + * @param[in] src source image to downscale + * @param[out] out image to store the downscaled result + * @param[in] downscale downscale value + */ +template +void downscaleImage(const Image& src, Image& out, int downscale) +{ + const int new_width = src.Width() / downscale; + const int new_height = src.Height() / downscale; + + out.resize(new_width, new_height); + + const Sampler2d sampler; + const float downscalef = downscale; + for (int i = 0; i < new_height; ++i) + { + for (int j = 0; j < new_width; ++j) + { + // Use .5f offset to ensure mid pixel and correct sampling + out(i, j) = sampler(src, downscalef * (i + .5f), downscalef * (j + .5f)); + } + } +} + +/** + ** Half sample an image (ie reduce its size by a factor 2) using bilinear interpolation + ** @param[in] src input image + ** @param[out] out output image + **/ +template +void ImageHalfSample(const Image& src, Image& out) +{ downscaleImage(src, out, 2); - } - - /** - ** @brief Ressample an image using given sampling positions - ** @param src Input image - ** @param sampling_pos A list of coordinates where the image needs to be ressampled (samples are (Y,X) ) - ** @param output_width Width of the output image. - ** @param output_height Height of the output image - ** @param sampling_func Ressampling functor used to sample the Input image - ** @param[out] Output image - ** @note sampling_pos.size() must be equal to output_width * output_height - **/ - template - void GenericRessample( const Image & src , - const std::vector< std::pair< float , float > > & sampling_pos , - const int output_width , - const int output_height , - const RessamplingFunctor & sampling_func , - Image & out ) - { - assert( sampling_pos.size() == output_width * output_height ); - - out.resize( output_width , output_height ); - - std::vector< std::pair< float , float > >::const_iterator it_pos = sampling_pos.begin(); - - for( int i = 0 ; i < output_height ; ++i ) +} + +/** + ** @brief Ressample an image using given sampling positions + ** @param src Input image + ** @param sampling_pos A list of coordinates where the image needs to be ressampled (samples are (Y,X) ) + ** @param output_width Width of the output image. + ** @param output_height Height of the output image + ** @param sampling_func Ressampling functor used to sample the Input image + ** @param[out] Output image + ** @note sampling_pos.size() must be equal to output_width * output_height + **/ +template +void GenericRessample(const Image& src, + const std::vector>& sampling_pos, + const int output_width, + const int output_height, + const RessamplingFunctor& sampling_func, + Image& out) +{ + assert(sampling_pos.size() == output_width * output_height); + + out.resize(output_width, output_height); + + std::vector>::const_iterator it_pos = sampling_pos.begin(); + + for (int i = 0; i < output_height; ++i) { - for( int j = 0 ; j < output_width ; ++j , ++it_pos ) - { - const float input_x = it_pos->second ; - const float input_y = it_pos->first ; + for (int j = 0; j < output_width; ++j, ++it_pos) + { + const float input_x = it_pos->second; + const float input_y = it_pos->first; - out( i , j ) = sampling_func( src , input_y , input_x ) ; - } + out(i, j) = sampling_func(src, input_y, input_x); + } } - } +} -} // namespace image -} // namespace aliceVision +} // namespace image +} // namespace aliceVision diff --git a/src/aliceVision/image/resampling_test.cpp b/src/aliceVision/image/resampling_test.cpp index 21621df3c1..250d618898 100644 --- a/src/aliceVision/image/resampling_test.cpp +++ b/src/aliceVision/image/resampling_test.cpp @@ -19,110 +19,103 @@ using namespace aliceVision; using namespace aliceVision::image; - BOOST_AUTO_TEST_CASE(Ressampling_SampleSamePosition) { - Image image; - std::string png_filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; - ALICEVISION_LOG_DEBUG(png_filename); - BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); - - - // Build sampling grid - std::vector< std::pair< float , float > > sampling_grid ; - for( int i = 0 ; i < image.Height() ; ++i ) - { - for( int j = 0 ; j < image.Width() ; ++j ) + Image image; + std::string png_filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; + ALICEVISION_LOG_DEBUG(png_filename); + BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); + + // Build sampling grid + std::vector> sampling_grid; + for (int i = 0; i < image.Height(); ++i) { - sampling_grid.push_back( std::make_pair( i , j ) ) ; + for (int j = 0; j < image.Width(); ++j) + { + sampling_grid.push_back(std::make_pair(i, j)); + } } - } - // Ressample image - Sampler2d< SamplerLinear > sampler ; + // Ressample image + Sampler2d sampler; - Image imageOut ; + Image imageOut; - GenericRessample( image , sampling_grid , image.Width() , image.Height() , sampler , imageOut ) ; + GenericRessample(image, sampling_grid, image.Width(), image.Height(), sampler, imageOut); - std::string out_filename = ("test_ressample_same.png"); - BOOST_CHECK_NO_THROW( writeImage( out_filename, imageOut, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); + std::string out_filename = ("test_ressample_same.png"); + BOOST_CHECK_NO_THROW(writeImage(out_filename, imageOut, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION))); } // Iterative image rotations // Allow to check if the sampling function have some signal loss. template -void ImageRotation( - const ImageT & imageIn, - const SamplerT & sampler, - const std::string & samplerString) +void ImageRotation(const ImageT& imageIn, const SamplerT& sampler, const std::string& samplerString) { - bool bOk = true; - ImageT image = imageIn; + bool bOk = true; + ImageT image = imageIn; - const int nb_rot = 6 ; - const float delta = ( 2.0 * 3.141592 ) / nb_rot ; + const int nb_rot = 6; + const float delta = (2.0 * 3.141592) / nb_rot; - const float middle_x = image.Width() / 2.0 ; - const float middle_y = image.Height() / 2.0 ; + const float middle_x = image.Width() / 2.0; + const float middle_y = image.Height() / 2.0; - // Rotate image then set starting image as source - for( int id_rot = 0 ; id_rot < nb_rot ; ++id_rot ) - { - // angle of rotation (negative because it's inverse transformation) - const float cur_angle = delta ; + // Rotate image then set starting image as source + for (int id_rot = 0; id_rot < nb_rot; ++id_rot) + { + // angle of rotation (negative because it's inverse transformation) + const float cur_angle = delta; - const float cs = cosf( -cur_angle ) ; - const float ss = sinf( -cur_angle ) ; + const float cs = cosf(-cur_angle); + const float ss = sinf(-cur_angle); - std::vector< std::pair > sampling_grid; - // Compute sampling grid - for( int i = 0 ; i < image.Height() ; ++i ) - { - for( int j = 0 ; j < image.Width() ; ++j ) - { - // Compute rotation of pixel (i,j) around center of image + std::vector> sampling_grid; + // Compute sampling grid + for (int i = 0; i < image.Height(); ++i) + { + for (int j = 0; j < image.Width(); ++j) + { + // Compute rotation of pixel (i,j) around center of image - // Center pixel - const float dx = static_cast(j) - middle_x ; - const float dy = static_cast(i) - middle_y ; + // Center pixel + const float dx = static_cast(j) - middle_x; + const float dy = static_cast(i) - middle_y; - const float rotated_x = cs * dx - ss * dy ; - const float rotated_y = ss * dx + cs * dy ; + const float rotated_x = cs * dx - ss * dy; + const float rotated_y = ss * dx + cs * dy; - // Get back to original center - const float cur_x = rotated_x + middle_x ; - const float cur_y = rotated_y + middle_y ; + // Get back to original center + const float cur_x = rotated_x + middle_x; + const float cur_y = rotated_y + middle_y; - sampling_grid.push_back( std::make_pair( cur_y , cur_x ) ) ; - } - } + sampling_grid.push_back(std::make_pair(cur_y, cur_x)); + } + } - // Sample input image - ImageT imageOut ; + // Sample input image + ImageT imageOut; - GenericRessample( image , sampling_grid , image.Width() , image.Height() , sampler , imageOut ) ; + GenericRessample(image, sampling_grid, image.Width(), image.Height(), sampler, imageOut); - std::stringstream str ; - str << "test_ressample_"<< samplerString <<"_rotate_" << id_rot << ".png" ; - writeImage(str.str(), imageOut, - image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION)); - image = imageOut ; - } + std::stringstream str; + str << "test_ressample_" << samplerString << "_rotate_" << id_rot << ".png"; + writeImage(str.str(), imageOut, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION)); + image = imageOut; + } } BOOST_AUTO_TEST_CASE(Ressampling_SampleRotate) { - Image image; + Image image; - std::string png_filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; - ALICEVISION_LOG_DEBUG(png_filename); - BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); + std::string png_filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; + ALICEVISION_LOG_DEBUG(png_filename); + BOOST_CHECK_NO_THROW(readImage(png_filename, image, image::EImageColorSpace::NO_CONVERSION)); - BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d< SamplerNearest >(), "SamplerNearest")); - BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d< SamplerLinear >(), "SamplerLinear")); - BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d< SamplerCubic >(), "SamplerCubic")); - BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d< SamplerSpline16 >(), "SamplerSpline16")); - BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d< SamplerSpline64 >(), "SamplerSpline64")); + BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d(), "SamplerNearest")); + BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d(), "SamplerLinear")); + BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d(), "SamplerCubic")); + BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d(), "SamplerSpline16")); + BOOST_CHECK_NO_THROW(ImageRotation(image, Sampler2d(), "SamplerSpline64")); } diff --git a/src/aliceVision/image/warping.hpp b/src/aliceVision/image/warping.hpp index bf095ae5d1..2eb7407790 100644 --- a/src/aliceVision/image/warping.hpp +++ b/src/aliceVision/image/warping.hpp @@ -9,40 +9,39 @@ #include -namespace aliceVision{ -namespace image{ +namespace aliceVision { +namespace image { /// Apply inplace homography transform for the given point (x,y). /// Return true if H is orientation preserving around the point. -bool ApplyH_AndCheckOrientation(const Mat3 &H, double &x, double &y) +bool ApplyH_AndCheckOrientation(const Mat3& H, double& x, double& y) { - Vec3 X(x, y, 1.0); - X = H*X; - X /= X(2); - x = X(0); - y = X(1); - return (X(2) * H(2,2) > 0.0); + Vec3 X(x, y, 1.0); + X = H * X; + X /= X(2); + x = X(0); + y = X(1); + return (X(2) * H(2, 2) > 0.0); } /// Warp an image im given a homography H with a backward approach /// H must be already have been resized accordingly -template -void Warp(const Image &im, const Mat3 & H, Image &out) +template +void Warp(const Image& im, const Mat3& H, Image& out) { - const int wOut = static_cast(out.Width()); - const int hOut = static_cast(out.Height()); + const int wOut = static_cast(out.Width()); + const int hOut = static_cast(out.Height()); - const Sampler2d sampler; - for (int j = 0; j < hOut; ++j) - #pragma omp parallel for - for (int i = 0; i < wOut; ++i) - { - double xT = i, yT = j; - if (ApplyH_AndCheckOrientation(H, xT, yT) - && im.Contains(yT,xT)) - out(j,i) = sampler(im, (float)yT, (float)xT); - } + const Sampler2d sampler; + for (int j = 0; j < hOut; ++j) +#pragma omp parallel for + for (int i = 0; i < wOut; ++i) + { + double xT = i, yT = j; + if (ApplyH_AndCheckOrientation(H, xT, yT) && im.Contains(yT, xT)) + out(j, i) = sampler(im, (float)yT, (float)xT); + } } -}; // namespace image -}; // namespace aliceVision +}; // namespace image +}; // namespace aliceVision diff --git a/src/aliceVision/imageMasking/eigen2cvHelpers.hpp b/src/aliceVision/imageMasking/eigen2cvHelpers.hpp index 896fe145e1..69030d79b2 100644 --- a/src/aliceVision/imageMasking/eigen2cvHelpers.hpp +++ b/src/aliceVision/imageMasking/eigen2cvHelpers.hpp @@ -3,20 +3,22 @@ #include - -namespace cv{ -template<> class DataType +namespace cv { +template<> +class DataType { -public: + public: using value_type = uchar; using work_type = int; - using channel_type = value_type ; + using channel_type = value_type; using vec_type = value_type; - enum { generic_type = 0, - depth = CV_8U, - channels = 3, - fmt = (int)'u', - type = CV_MAKETYPE(depth, channels) - }; + enum + { + generic_type = 0, + depth = CV_8U, + channels = 3, + fmt = (int)'u', + type = CV_MAKETYPE(depth, channels) + }; }; -} +} // namespace cv diff --git a/src/aliceVision/imageMasking/imageMasking.cpp b/src/aliceVision/imageMasking/imageMasking.cpp index d9b9aaee12..ea639b39e3 100644 --- a/src/aliceVision/imageMasking/imageMasking.cpp +++ b/src/aliceVision/imageMasking/imageMasking.cpp @@ -16,41 +16,39 @@ #include #include -namespace aliceVision{ -namespace imageMasking{ +namespace aliceVision { +namespace imageMasking { -namespace{ -float clamp(float value) -{ - return std::min(1.f, std::max(0.f, value)); -} +namespace { +float clamp(float value) { return std::min(1.f, std::max(0.f, value)); } -uint8_t remap_float2uint8(float value) -{ - return uint8_t(clamp(value) * 255.f + 0.49999f); -} +uint8_t remap_float2uint8(float value) { return uint8_t(clamp(value) * 255.f + 0.49999f); } -cv::Mat wrapCvMask(OutImage& result) -{ - return cv::Mat(result.rows(), result.cols(), CV_8UC1, result.data(), result.rowStride()); -} +cv::Mat wrapCvMask(OutImage& result) { return cv::Mat(result.rows(), result.cols(), CV_8UC1, result.data(), result.rowStride()); } -void rotateHue(cv::Mat & hsv, uint8_t delta) +void rotateHue(cv::Mat& hsv, uint8_t delta) { // cannot use cv::add because it saturates the output. // do not use forEach because this call is already parallelized. for (int r = 0; r < hsv.rows; r++) { - for ( int c = 0; c < hsv.cols; c++) + for (int c = 0; c < hsv.cols; c++) { - auto & pixel = hsv.at>(r, c); + auto& pixel = hsv.at>(r, c); pixel.x += delta; } } } -} - -void hsv(OutImage& result, const std::string& inputPath, float hue, float hueRange, float minSaturation, float maxSaturation, float minValue, float maxValue) +} // namespace + +void hsv(OutImage& result, + const std::string& inputPath, + float hue, + float hueRange, + float minSaturation, + float maxSaturation, + float minValue, + float maxValue) { // HSV's hue channel is an rotation angle: it wraps at 0deg/360deg. // Hue for blue is 240deg. With hueRange=0.1, pixels are selected if (hue > 204deg && hue < 276deg). @@ -61,11 +59,11 @@ void hsv(OutImage& result, const std::string& inputPath, float hue, float hueRan image::readImage(inputPath, input, image::EImageColorSpace::SRGB); cv::Mat input_hsv; - cv::eigen2cv(input.GetMat(), input_hsv); // copy the buffer, but a copy is needed to convert to HSV colorspace anyway. - cv::cvtColor(input_hsv, input_hsv, cv::COLOR_RGB2HSV_FULL); // "_FULL" to encode hue in the [0, 255] range. - rotateHue(input_hsv, uint8_t((0.5f - hue)*256.f)); // hue == 0 <=> hue == 1 + cv::eigen2cv(input.GetMat(), input_hsv); // copy the buffer, but a copy is needed to convert to HSV colorspace anyway. + cv::cvtColor(input_hsv, input_hsv, cv::COLOR_RGB2HSV_FULL); // "_FULL" to encode hue in the [0, 255] range. + rotateHue(input_hsv, uint8_t((0.5f - hue) * 256.f)); // hue == 0 <=> hue == 1 - result.resize(input.Width(), input.Height(), false); // allocate un-initialized + result.resize(input.Width(), input.Height(), false); // allocate un-initialized const cv::Mat result_cv = wrapCvMask(result); const uint8_t lowH = remap_float2uint8(0.5f - hueRange); @@ -90,7 +88,7 @@ void autoGrayscaleThreshold(OutImage& result, const std::string& inputPath) const cv::Mat result_cv = wrapCvMask(result); assert(result_cv.data); - cv::threshold(input_cv, result_cv, 0, 255, cv::THRESH_BINARY|cv::THRESH_OTSU); + cv::threshold(input_cv, result_cv, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); } void postprocess_invert(OutImage& result) @@ -99,8 +97,7 @@ void postprocess_invert(OutImage& result) cv::bitwise_not(result_cv, result_cv); } -namespace -{ +namespace { void morph(OutImage& result, cv::MorphTypes type, int iterations) { const cv::Mat result_cv = wrapCvMask(result); @@ -108,22 +105,13 @@ void morph(OutImage& result, cv::MorphTypes type, int iterations) const auto anchor = cv::Point(-1, -1); cv::morphologyEx(result_cv, result_cv, type, kernel, anchor, iterations); } -} +} // namespace -void postprocess_closing(OutImage& result, int iterations) -{ - morph(result, cv::MORPH_CLOSE, iterations); -} +void postprocess_closing(OutImage& result, int iterations) { morph(result, cv::MORPH_CLOSE, iterations); } -void postprocess_dilate(OutImage& result, int iterations) -{ - morph(result, cv::MORPH_DILATE, iterations); -} +void postprocess_dilate(OutImage& result, int iterations) { morph(result, cv::MORPH_DILATE, iterations); } -void postprocess_erode(OutImage& result, int iterations) -{ - morph(result, cv::MORPH_ERODE, iterations); -} +void postprocess_erode(OutImage& result, int iterations) { morph(result, cv::MORPH_ERODE, iterations); } -}//namespace imageMasking -}//namespace aliceVision +} // namespace imageMasking +} // namespace aliceVision diff --git a/src/aliceVision/imageMasking/imageMasking.hpp b/src/aliceVision/imageMasking/imageMasking.hpp index cd5f1fca3e..e4406ad71a 100644 --- a/src/aliceVision/imageMasking/imageMasking.hpp +++ b/src/aliceVision/imageMasking/imageMasking.hpp @@ -8,13 +8,14 @@ #include -namespace aliceVision{ +namespace aliceVision { -namespace image{ -template class Image; +namespace image { +template +class Image; } -namespace imageMasking{ +namespace imageMasking { using OutImage = image::Image; @@ -30,14 +31,20 @@ using OutImage = image::Image; * @param[in] minValue Hue is meaningless if value is low. Do not mask pixels below this threshold. * @param[in] Do not mask pixels above this threshold. It might be useful to mask white/black pixels. */ -void hsv(OutImage& result, const std::string& inputPath, float hue, float hueRange, float minSaturation, float maxSaturation, float minValue, float maxValue); +void hsv(OutImage& result, + const std::string& inputPath, + float hue, + float hueRange, + float minSaturation, + float maxSaturation, + float minValue, + float maxValue); /** * @Brief Otsu's Binarization of an image in grayscale. */ void autoGrayscaleThreshold(OutImage& result, const std::string& inputPath); - /** * @brief Invert a binary image (white <-> black) * @param[inout] result A binary image @@ -65,5 +72,5 @@ void postprocess_dilate(OutImage& result, int iterations); */ void postprocess_erode(OutImage& result, int iterations); -}//namespace imageMasking -}//namespace aliceVision +} // namespace imageMasking +} // namespace aliceVision diff --git a/src/aliceVision/imageMatching/ImageMatching.cpp b/src/aliceVision/imageMatching/ImageMatching.cpp index b5eea00f16..2d5b8af652 100644 --- a/src/aliceVision/imageMatching/ImageMatching.cpp +++ b/src/aliceVision/imageMatching/ImageMatching.cpp @@ -26,7 +26,7 @@ std::ostream& operator<<(std::ostream& os, const PairList& pl) std::string EImageMatchingMethod_enumToString(EImageMatchingMethod m) { - switch(m) + switch (m) { case EImageMatchingMethod::EXHAUSTIVE: return "Exhaustive"; @@ -65,10 +65,7 @@ EImageMatchingMethod EImageMatchingMethod_stringToEnum(const std::string& m) throw std::out_of_range("Invalid EImageMatchingMethod: " + m); } -std::ostream& operator<<(std::ostream& os, EImageMatchingMethod m) -{ - return os << EImageMatchingMethod_enumToString(m); -} +std::ostream& operator<<(std::ostream& os, EImageMatchingMethod m) { return os << EImageMatchingMethod_enumToString(m); } std::istream& operator>>(std::istream& in, EImageMatchingMethod& m) { @@ -81,21 +78,24 @@ std::istream& operator>>(std::istream& in, EImageMatchingMethod& m) std::string EImageMatchingMode_description() { return "The mode to combine image matching between the input SfMData A and B: \n" - "* a/a+a/b : A with A + A with B\n" - "* a/ab : A with A and B\n" - "* a/b : A with B\n" - "* a/a : A with A"; + "* a/a+a/b : A with A + A with B\n" + "* a/ab : A with A and B\n" + "* a/b : A with B\n" + "* a/a : A with A"; } - std::string EImageMatchingMode_enumToString(EImageMatchingMode modeMultiSfM) { - switch(modeMultiSfM) + switch (modeMultiSfM) { - case EImageMatchingMode::A_A_AND_A_B: return "a/a+a/b"; - case EImageMatchingMode::A_AB: return "a/ab"; - case EImageMatchingMode::A_B: return "a/b"; - case EImageMatchingMode::A_A: return "a/a"; + case EImageMatchingMode::A_A_AND_A_B: + return "a/a+a/b"; + case EImageMatchingMode::A_AB: + return "a/ab"; + case EImageMatchingMode::A_B: + return "a/b"; + case EImageMatchingMode::A_A: + return "a/a"; } throw std::out_of_range("Invalid modeMultiSfM enum"); } @@ -103,18 +103,21 @@ std::string EImageMatchingMode_enumToString(EImageMatchingMode modeMultiSfM) EImageMatchingMode EImageMatchingMode_stringToEnum(const std::string& modeMultiSfM) { std::string mode = modeMultiSfM; - std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower); //tolower + std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower); // tolower - if (mode == "a/a+a/b") return EImageMatchingMode::A_A_AND_A_B; - if (mode == "a/ab") return EImageMatchingMode::A_AB; - if (mode == "a/b") return EImageMatchingMode::A_B; - if (mode == "a/a") return EImageMatchingMode::A_A; + if (mode == "a/a+a/b") + return EImageMatchingMode::A_A_AND_A_B; + if (mode == "a/ab") + return EImageMatchingMode::A_AB; + if (mode == "a/b") + return EImageMatchingMode::A_B; + if (mode == "a/a") + return EImageMatchingMode::A_A; throw std::out_of_range("Invalid modeMultiSfM : " + modeMultiSfM); } -void convertAllMatchesToPairList(const PairList &allMatches, std::size_t numMatches, - OrderedPairList &outPairList) +void convertAllMatchesToPairList(const PairList& allMatches, std::size_t numMatches, OrderedPairList& outPairList) { outPairList.clear(); @@ -134,13 +137,12 @@ void convertAllMatchesToPairList(const PairList &allMatches, std::size_t numMatc // if the currMatchId ID is lower than the current image ID and // the current image ID is not already in the list of currMatchId - //BOOST_ASSERT( ( currMatchId < currImageId ) && + // BOOST_ASSERT( ( currMatchId < currImageId ) && // ( outPairList.find( currMatchId ) != outPairList.end() ) ); if (currMatchId < currImageId) { OrderedPairList::const_iterator currMatches = outPairList.find(currMatchId); - if (currMatches != outPairList.end() && - currMatches->second.find(currImageId) == currMatches->second.end()) + if (currMatches != outPairList.end() && currMatches->second.find(currImageId) == currMatches->second.end()) { // then add it to the list bestMatches.insert(currMatchId); @@ -152,7 +154,7 @@ void convertAllMatchesToPairList(const PairList &allMatches, std::size_t numMatc } // stop if numMatches is satisfied - if(bestMatches.size() == numMatches) + if (bestMatches.size() == numMatches) break; } @@ -164,19 +166,18 @@ void convertAllMatchesToPairList(const PairList &allMatches, std::size_t numMatc } } -void generateSequentialMatches(const sfmData::SfMData& sfmData, size_t nbMatches, - OrderedPairList& outPairList) +void generateSequentialMatches(const sfmData::SfMData& sfmData, size_t nbMatches, OrderedPairList& outPairList) { std::vector> sortedImagePaths; sortedImagePaths.reserve(sfmData.getViews().size()); - for (const auto& vIt: sfmData.getViews()) + for (const auto& vIt : sfmData.getViews()) { sortedImagePaths.emplace_back(vIt.second->getImage().getImagePath(), vIt.first); } std::sort(sortedImagePaths.begin(), sortedImagePaths.end()); for (size_t i = 0; i < sortedImagePaths.size(); ++i) { - for (size_t n = i+1, nMax = std::min(i+nbMatches+1, sortedImagePaths.size()); n < nMax; ++n) + for (size_t n = i + 1, nMax = std::min(i + nbMatches + 1, sortedImagePaths.size()); n < nMax; ++n) { size_t a = sortedImagePaths[i].second; size_t b = sortedImagePaths[n].second; @@ -209,9 +210,7 @@ void generateAllMatchesInOneMap(const std::set& viewIds, OrderedPairList } } -void generateAllMatchesBetweenTwoMap(const std::set& viewIdsA, - const std::set& viewIdsB, - OrderedPairList& outPairList) +void generateAllMatchesBetweenTwoMap(const std::set& viewIdsA, const std::set& viewIdsB, OrderedPairList& outPairList) { for (const IndexT imgA : viewIdsA) { @@ -240,8 +239,7 @@ void generateFromVoctree(PairList& allMatches, std::size_t nbMaxDescriptors, std::size_t numImageQuery) { - ALICEVISION_LOG_INFO("Generate matches in mode: " + - EImageMatchingMode_enumToString(modeMultiSfM)); + ALICEVISION_LOG_INFO("Generate matches in mode: " + EImageMatchingMode_enumToString(modeMultiSfM)); if (numImageQuery == 0) { @@ -249,7 +247,7 @@ void generateFromVoctree(PairList& allMatches, numImageQuery = db.size(); } - //initialize allMatches + // initialize allMatches for (const auto& descriptorPair : descriptorsFiles) { @@ -273,7 +271,7 @@ void generateFromVoctree(PairList& allMatches, // sparse histogram of A is already computed in the DB imageSH = db.getSparseHistogramPerImage().at(viewIdA); } - else // mode AB + else // mode AB { // compute the sparse histogram of each image A std::vector descriptors; @@ -296,7 +294,8 @@ void generateFromVoctree(PairList& allMatches, } } -void conditionVocTree(const std::string& treeName, bool withWeights, +void conditionVocTree(const std::string& treeName, + bool withWeights, const std::string& weightsName, const EImageMatchingMode matchingMode, const std::vector& featuresFolders, @@ -320,8 +319,7 @@ void conditionVocTree(const std::string& treeName, bool withWeights, auto loadVoctree_start = std::chrono::steady_clock::now(); aliceVision::voctree::VocabularyTree tree(treeName); - auto loadVoctree_elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - loadVoctree_start); + auto loadVoctree_elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - loadVoctree_start); { std::stringstream ss; ss << "tree loaded with:" << std::endl << "\t- " << tree.levels() << " levels" << std::endl; @@ -337,7 +335,7 @@ void conditionVocTree(const std::string& treeName, bool withWeights, aliceVision::voctree::Database db(tree.words()); aliceVision::voctree::Database db2; - if(withWeights) + if (withWeights) { ALICEVISION_LOG_INFO("Loading weights..."); db.loadWeights(weightsName); @@ -348,14 +346,14 @@ void conditionVocTree(const std::string& treeName, bool withWeights, } if (matchingMode == EImageMatchingMode::A_A_AND_A_B) - db2 = db; // initialize database2 with database1 initialization + db2 = db; // initialize database2 with database1 initialization // read the descriptors and populate the databases { std::stringstream ss; for (const std::string& featuresFolder : featuresFolders) - ss << "\t- " << featuresFolder << std::endl; + ss << "\t- " << featuresFolder << std::endl; ALICEVISION_LOG_INFO("Reading descriptors from: " << std::endl << ss.str()); @@ -365,32 +363,27 @@ void conditionVocTree(const std::string& treeName, bool withWeights, auto detect_start = std::chrono::steady_clock::now(); { - if ((matchingMode == EImageMatchingMode::A_A_AND_A_B) || - (matchingMode == EImageMatchingMode::A_AB) || + if ((matchingMode == EImageMatchingMode::A_A_AND_A_B) || (matchingMode == EImageMatchingMode::A_AB) || (matchingMode == EImageMatchingMode::A_A)) { - nbFeaturesLoadedInputA = voctree::populateDatabase( - sfmDataA, featuresFolders, tree, db, nbMaxDescriptors); + nbFeaturesLoadedInputA = voctree::populateDatabase(sfmDataA, featuresFolders, tree, db, nbMaxDescriptors); nbSetDescriptors = db.getSparseHistogramPerImage().size(); - if(nbFeaturesLoadedInputA == 0) + if (nbFeaturesLoadedInputA == 0) { - throw std::runtime_error("No descriptors loaded in '" + sfmDataFilenameA + "'"); + throw std::runtime_error("No descriptors loaded in '" + sfmDataFilenameA + "'"); } } - if ((matchingMode == EImageMatchingMode::A_AB) || - (matchingMode == EImageMatchingMode::A_B)) + if ((matchingMode == EImageMatchingMode::A_AB) || (matchingMode == EImageMatchingMode::A_B)) { - nbFeaturesLoadedInputB = voctree::populateDatabase( - sfmDataB, featuresFolders, tree, db, nbMaxDescriptors); + nbFeaturesLoadedInputB = voctree::populateDatabase(sfmDataB, featuresFolders, tree, db, nbMaxDescriptors); nbSetDescriptors = db.getSparseHistogramPerImage().size(); } if (matchingMode == EImageMatchingMode::A_A_AND_A_B) { - nbFeaturesLoadedInputB = voctree::populateDatabase( - sfmDataB, featuresFolders, tree, db2, nbMaxDescriptors); + nbFeaturesLoadedInputB = voctree::populateDatabase(sfmDataB, featuresFolders, tree, db2, nbMaxDescriptors); nbSetDescriptors += db2.getSparseHistogramPerImage().size(); } @@ -399,11 +392,10 @@ void conditionVocTree(const std::string& treeName, bool withWeights, throw std::runtime_error("No descriptors loaded in '" + sfmDataFilenameB + "'"); } } - auto detect_elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - detect_start); + auto detect_elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - detect_start); ALICEVISION_LOG_INFO("Read " << nbSetDescriptors << " sets of descriptors for a total of " - << (nbFeaturesLoadedInputA + nbFeaturesLoadedInputB) << " features"); + << (nbFeaturesLoadedInputA + nbFeaturesLoadedInputB) << " features"); ALICEVISION_LOG_INFO("Reading took " << detect_elapsed.count() << " sec."); } @@ -427,19 +419,15 @@ void conditionVocTree(const std::string& treeName, bool withWeights, if (matchingMode == EImageMatchingMode::A_A_AND_A_B) { - generateFromVoctree(allMatches, descriptorsFilesA, db, tree, EImageMatchingMode::A_A, - nbMaxDescriptors, numImageQuery); - generateFromVoctree(allMatches, descriptorsFilesA, db2, tree, EImageMatchingMode::A_B, - nbMaxDescriptors, numImageQuery); + generateFromVoctree(allMatches, descriptorsFilesA, db, tree, EImageMatchingMode::A_A, nbMaxDescriptors, numImageQuery); + generateFromVoctree(allMatches, descriptorsFilesA, db2, tree, EImageMatchingMode::A_B, nbMaxDescriptors, numImageQuery); } else { - generateFromVoctree(allMatches, descriptorsFilesA, db, tree, matchingMode, - nbMaxDescriptors, numImageQuery); + generateFromVoctree(allMatches, descriptorsFilesA, db, tree, matchingMode, nbMaxDescriptors, numImageQuery); } - auto detect_elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - detect_start); + auto detect_elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - detect_start); ALICEVISION_LOG_INFO("Query all documents took " << detect_elapsed.count() << " sec."); // process pair list @@ -447,8 +435,7 @@ void conditionVocTree(const std::string& treeName, bool withWeights, ALICEVISION_LOG_INFO("Convert all matches to pairList"); convertAllMatchesToPairList(allMatches, numImageQuery, selectedPairs); - detect_elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - detect_start); + detect_elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - detect_start); ALICEVISION_LOG_INFO("Convert all matches to pairList took " << detect_elapsed.count() << " sec."); } } @@ -464,7 +451,7 @@ EImageMatchingMethod selectImageMatchingMethod(EImageMatchingMethod method, bool onlyPinhole = true; for (auto& cam : sfmDataA.getIntrinsics()) { - if(!camera::isPinhole(cam.second->getType())) + if (!camera::isPinhole(cam.second->getType())) { onlyPinhole = false; break; @@ -479,8 +466,7 @@ EImageMatchingMethod selectImageMatchingMethod(EImageMatchingMethod method, } else if (!onlyPinhole) { - ALICEVISION_LOG_INFO( - "FRUSTUM_OR_VOCABULARYTREE: Use VOCABULARYTREE matching, as the scene contains non-pinhole cameras."); + ALICEVISION_LOG_INFO("FRUSTUM_OR_VOCABULARYTREE: Use VOCABULARYTREE matching, as the scene contains non-pinhole cameras."); method = EImageMatchingMethod::VOCABULARYTREE; } else if (reconstructedViews == sfmDataA.getViews().size()) @@ -490,15 +476,13 @@ EImageMatchingMethod selectImageMatchingMethod(EImageMatchingMethod method, } else { - ALICEVISION_LOG_ERROR(reconstructedViews << " reconstructed views for " << sfmDataA.getViews().size() - << " views."); + ALICEVISION_LOG_ERROR(reconstructedViews << " reconstructed views for " << sfmDataA.getViews().size() << " views."); throw std::runtime_error("FRUSTUM_OR_VOCABULARYTREE: Mixing reconstructed and unreconstructed Views."); } } // if not enough images to use the VOCABULARYTREE use the EXHAUSTIVE method - if (method == EImageMatchingMethod::VOCABULARYTREE || - method == EImageMatchingMethod::SEQUENTIAL_AND_VOCABULARYTREE) + if (method == EImageMatchingMethod::VOCABULARYTREE || method == EImageMatchingMethod::SEQUENTIAL_AND_VOCABULARYTREE) { if ((sfmDataA.getViews().size() + sfmDataB.getViews().size()) < minNbImages) { @@ -509,5 +493,5 @@ EImageMatchingMethod selectImageMatchingMethod(EImageMatchingMethod method, return method; } -} // namespace imageMatching -} // namespace aliceVision +} // namespace imageMatching +} // namespace aliceVision diff --git a/src/aliceVision/imageMatching/ImageMatching.hpp b/src/aliceVision/imageMatching/ImageMatching.hpp index 80bad5b579..8f4eab43b0 100644 --- a/src/aliceVision/imageMatching/ImageMatching.hpp +++ b/src/aliceVision/imageMatching/ImageMatching.hpp @@ -19,8 +19,8 @@ namespace imageMatching { static const int DIMENSION = 128; -using DescriptorFloat = feature::Descriptor ; -using DescriptorUChar = feature::Descriptor ; +using DescriptorFloat = feature::Descriptor; +using DescriptorUChar = feature::Descriptor; using ImageID = std::size_t; @@ -36,7 +36,6 @@ using PairList = std::map; // For each image ID it contains the ordered list of matching images using OrderedPairList = std::map; - /** * @brief Function that prints a PairList * @param os The stream on which to print @@ -114,15 +113,11 @@ EImageMatchingMode EImageMatchingMode_stringToEnum(const std::string& modeMultiS * @param[in] numMatches The maximum number of matching images to consider for each image (if 0, consider all matches) * @param[out] matches A processed version of allMatches that consider only the first numMatches without repetitions */ -void convertAllMatchesToPairList(const PairList &allMatches, std::size_t numMatches, - OrderedPairList &outPairList); +void convertAllMatchesToPairList(const PairList& allMatches, std::size_t numMatches, OrderedPairList& outPairList); -void generateSequentialMatches(const sfmData::SfMData& sfmData, size_t nbMatches, - OrderedPairList& outPairList); +void generateSequentialMatches(const sfmData::SfMData& sfmData, size_t nbMatches, OrderedPairList& outPairList); void generateAllMatchesInOneMap(const std::set& viewIds, OrderedPairList& outPairList); -void generateAllMatchesBetweenTwoMap(const std::set& viewIdsA, - const std::set& viewIdsB, - OrderedPairList& outPairList); +void generateAllMatchesBetweenTwoMap(const std::set& viewIdsA, const std::set& viewIdsB, OrderedPairList& outPairList); void generateFromVoctree(PairList& allMatches, const std::map& descriptorsFiles, @@ -132,7 +127,8 @@ void generateFromVoctree(PairList& allMatches, std::size_t nbMaxDescriptors, std::size_t numImageQuery); -void conditionVocTree(const std::string& treeName, bool withWeights, +void conditionVocTree(const std::string& treeName, + bool withWeights, const std::string& weightsName, const EImageMatchingMode matchingMode, const std::vector& featuresFolders, @@ -151,5 +147,5 @@ EImageMatchingMethod selectImageMatchingMethod(EImageMatchingMethod method, const sfmData::SfMData& sfmDataB, std::size_t minNbImages); -} // namespace imageMatching -} // namespace aliceVision +} // namespace imageMatching +} // namespace aliceVision diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index 32a72cafdb..4df3d51f97 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -18,7 +18,6 @@ #include #include - namespace fs = boost::filesystem; namespace aliceVision { @@ -31,7 +30,7 @@ namespace keyframe { */ int getRandomInt() { - std::random_device rd; // will be used to obtain a seed for the random number engine + std::random_device rd; // will be used to obtain a seed for the random number engine std::mt19937 randomTwEngine(rd()); // standard mersenne_twister_engine seeded with rd() std::uniform_int_distribution<> randomDist(0, std::numeric_limits::max()); return randomDist(randomTwEngine); @@ -45,7 +44,8 @@ int getRandomInt() double findMedian(const std::vector& vec) { std::vector vecCopy = vec; - if (vecCopy.size() > 0 && vecCopy.size() % 2 == 0) { + if (vecCopy.size() > 0 && vecCopy.size() % 2 == 0) + { const auto medianIt1 = vecCopy.begin() + vecCopy.size() / 2 - 1; std::nth_element(vecCopy.begin(), medianIt1, vecCopy.end()); const auto med1 = *medianIt1; @@ -55,7 +55,9 @@ double findMedian(const std::vector& vec) const auto med2 = *medianIt2; return (med1 + med2) / 2.0; - } else if (vecCopy.size() > 0) { + } + else if (vecCopy.size() > 0) + { const auto medianIt = vecCopy.begin() + vecCopy.size() / 2; std::nth_element(vecCopy.begin(), medianIt, vecCopy.end()); return *medianIt; @@ -70,15 +72,16 @@ KeyframeSelector::KeyframeSelector(const std::vector& mediaPaths, const std::string& outputFolder, const std::string& outputSfmKeyframes, const std::string& outputSfmFrames) - : _mediaPaths(mediaPaths), - _maskPaths(maskPaths), - _sensorDbPath(sensorDbPath), - _outputFolder(outputFolder), - _outputSfmKeyframesPath(outputSfmKeyframes), - _outputSfmFramesPath(outputSfmFrames) + : _mediaPaths(mediaPaths), + _maskPaths(maskPaths), + _sensorDbPath(sensorDbPath), + _outputFolder(outputFolder), + _outputSfmKeyframesPath(outputSfmKeyframes), + _outputSfmFramesPath(outputSfmFrames) { // Check that a least one media file path has been provided - if (mediaPaths.empty()) { + if (mediaPaths.empty()) + { ALICEVISION_THROW(std::invalid_argument, "Cannot create KeyframeSelector without at least one media file path!"); } @@ -86,7 +89,8 @@ KeyframeSelector::KeyframeSelector(const std::vector& mediaPaths, scoresMap["OpticalFlow"] = &_flowScores; // Parse the sensor database if the path is not empty - if (!_sensorDbPath.empty() && sensorDB::parseDatabase(_sensorDbPath, _sensorDatabase)) { + if (!_sensorDbPath.empty() && sensorDB::parseDatabase(_sensorDbPath, _sensorDatabase)) + { _parsedSensorDb = true; } } @@ -102,7 +106,8 @@ void KeyframeSelector::processRegular() std::size_t nbFrames = std::numeric_limits::max(); std::vector> feeds; - for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) { + for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) + { const auto& path = _mediaPaths.at(mediaIndex); // Create a feed provider per mediaPaths @@ -110,7 +115,8 @@ void KeyframeSelector::processRegular() const auto& feed = *feeds.back(); // Check if feed is initialized - if (!feed.isInit()) { + if (!feed.isInit()) + { ALICEVISION_THROW(std::invalid_argument, "Cannot initialize the FeedProvider with " << path); } @@ -119,7 +125,8 @@ void KeyframeSelector::processRegular() } // Check if minimum number of frame is zero - if (nbFrames == 0) { + if (nbFrames == 0) + { ALICEVISION_THROW(std::invalid_argument, "One or multiple medias can't be found or empty!"); } @@ -128,7 +135,8 @@ void KeyframeSelector::processRegular() std::fill(_selectedFrames.begin(), _selectedFrames.end(), '0'); unsigned int step = _minFrameStep; - if (_maxFrameStep > 0) { + if (_maxFrameStep > 0) + { // By default, if _maxFrameStep is set, set the step to be right between _minFrameStep and _maxFrameStep step = step + static_cast((_maxFrameStep - _minFrameStep) / 2); } @@ -141,9 +149,11 @@ void KeyframeSelector::processRegular() * the step should be set to _maxFrameStep - in that case, _maxOutFrames might be reached before the end of * the sequence */ - if (_maxOutFrames > 0 && nbFrames / _maxOutFrames > step) { + if (_maxOutFrames > 0 && nbFrames / _maxOutFrames > step) + { step = (nbFrames / _maxOutFrames) + 1; // + 1 to prevent ending up with more than _maxOutFrame selected frames - if (_maxFrameStep > 0 && step > _maxFrameStep) { + if (_maxFrameStep > 0 && step > _maxFrameStep) + { step = _maxFrameStep; } } @@ -151,7 +161,8 @@ void KeyframeSelector::processRegular() // Ensure the step is always larger than 0, thus ensuring that the 'for' loop will always run smoothly step = std::max(step, static_cast(1)); - for (unsigned int id = 0; id < nbFrames; id += step) { + for (unsigned int id = 0; id < nbFrames; id += step) + { ALICEVISION_LOG_INFO("Selecting frame with ID " << id); _selectedKeyframes.push_back(id); _selectedFrames.at(id) = '1'; @@ -159,13 +170,15 @@ void KeyframeSelector::processRegular() break; } - ALICEVISION_LOG_INFO("Finished selecting all the keyframes! " << _selectedKeyframes.size() << "/" << - nbFrames << " frames have been selected."); + ALICEVISION_LOG_INFO("Finished selecting all the keyframes! " << _selectedKeyframes.size() << "/" << nbFrames << " frames have been selected."); } -void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_t rescaledWidthSharpness, - const std::size_t rescaledWidthFlow, const std::size_t sharpnessWindowSize, - const std::size_t flowCellSize, const bool skipSharpnessComputation) +void KeyframeSelector::processSmart(const float pxDisplacement, + const std::size_t rescaledWidthSharpness, + const std::size_t rescaledWidthFlow, + const std::size_t sharpnessWindowSize, + const std::size_t flowCellSize, + const bool skipSharpnessComputation) { _selectedKeyframes.clear(); _selectedFrames.clear(); @@ -191,9 +204,11 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ /* Starts at 1 because the first frame's motion score will be -1. * Ends at sequenceSize - 1 to ensure the last frame cannot be pushed twice. */ - for (std::size_t i = 1; i < sequenceSize - 1; ++i) { + for (std::size_t i = 1; i < sequenceSize - 1; ++i) + { motionAcc += _flowScores.at(i) > -1.f ? _flowScores.at(i) : 0.f; - if (motionAcc >= step) { + if (motionAcc >= step) + { subsequenceLimits.push_back(i); motionAcc = 0.0; // Reset the motion accumulator } @@ -201,33 +216,38 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ subsequenceLimits.push_back(sequenceSize - 1); // Step 2: check whether the min/max output frames constraints are respected - if (!(subsequenceLimits.size() - 1 >= _minOutFrames && subsequenceLimits.size() - 1 <= _maxOutFrames)) { + if (!(subsequenceLimits.size() - 1 >= _minOutFrames && subsequenceLimits.size() - 1 <= _maxOutFrames)) + { ALICEVISION_LOG_INFO("Preliminary selection does not provide the right number of frames (" - << subsequenceLimits.size() - 1 << " keyframes, should be between " << _minOutFrames - << " and " << _maxOutFrames << ")."); + << subsequenceLimits.size() - 1 << " keyframes, should be between " << _minOutFrames << " and " << _maxOutFrames + << ")."); std::vector newLimits = subsequenceLimits; // Prevents first 'newLimits.size() - 1' from overflowing - const double displacementDiff = 0.5; // The displacement must be 0.5px smaller/bigger than the previous one + const double displacementDiff = 0.5; // The displacement must be 0.5px smaller/bigger than the previous one - if (subsequenceLimits.size() - 1 < _minOutFrames) { + if (subsequenceLimits.size() - 1 < _minOutFrames) + { // Not enough frames, reduce the motion step - ALICEVISION_LOG_INFO("Not enough keyframes, the motion step will be reduced of " << displacementDiff - << "%."); + ALICEVISION_LOG_INFO("Not enough keyframes, the motion step will be reduced of " << displacementDiff << "%."); bool sampleRegularly = false; - while (newLimits.size() - 1 < _minOutFrames) { + while (newLimits.size() - 1 < _minOutFrames) + { newLimits.clear(); newLimits.push_back(0); step = std::max(0.0, step - displacementDiff); - if (step == 0.0) { // The criterion does not make sense anymore, exit to sample regularly instead + if (step == 0.0) + { // The criterion does not make sense anymore, exit to sample regularly instead sampleRegularly = true; break; } motionAcc = 0.0; - for (std::size_t i = 1; i < sequenceSize - 1; ++i) { + for (std::size_t i = 1; i < sequenceSize - 1; ++i) + { motionAcc += _flowScores.at(i) > -1.f ? _flowScores.at(i) : 0.f; - if (motionAcc >= step) { + if (motionAcc >= step) + { newLimits.push_back(i); motionAcc = 0.0; } @@ -235,7 +255,8 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ newLimits.push_back(sequenceSize - 1); } - if (sampleRegularly) { + if (sampleRegularly) + { // Sample regularly the whole sequence to get minOutFrames subsequences ALICEVISION_LOG_INFO("The motion step has been reduced to 0 and cannot be used anymore. Keyframes will " "be sampled regularly instead."); @@ -247,19 +268,23 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ newLimits.push_back(i); newLimits.push_back(sequenceSize - 1); } - } else { + } + else + { // Too many frames, increase the motion step - ALICEVISION_LOG_INFO("Too many keyframes, the motion step will be increased of " << displacementDiff - << "%."); - while (newLimits.size() - 1 > _maxOutFrames) { + ALICEVISION_LOG_INFO("Too many keyframes, the motion step will be increased of " << displacementDiff << "%."); + while (newLimits.size() - 1 > _maxOutFrames) + { newLimits.clear(); newLimits.push_back(0); step = step + displacementDiff; motionAcc = 0.0; - for (std::size_t i = 1; i < sequenceSize - 1; ++i) { + for (std::size_t i = 1; i < sequenceSize - 1; ++i) + { motionAcc += _flowScores.at(i) > -1.f ? _flowScores.at(i) : 0.f; - if (motionAcc >= step) { + if (motionAcc >= step) + { newLimits.push_back(i); motionAcc = 0.0; } @@ -273,7 +298,8 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ } // Step 3: for each subsequence, find the keyframe - for (std::size_t i = 1; i < subsequenceLimits.size(); ++i) { + for (std::size_t i = 1; i < subsequenceLimits.size(); ++i) + { double bestSharpness = 0.0; std::size_t bestIndex = 0; std::size_t subsequenceSize = subsequenceLimits.at(i) - subsequenceLimits.at(i - 1); @@ -287,17 +313,20 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ weights.push_back(2.0); // For subsequences of even size, two frames are equally in the middle float currentWeight = 2.0; - while (weights.size() != subsequenceSize) { + while (weights.size() != subsequenceSize) + { currentWeight -= weightStep; weights.push_front(currentWeight); weights.push_back(currentWeight); } std::size_t weightPosition = 0; - for (std::size_t j = subsequenceLimits.at(i - 1); j < subsequenceLimits.at(i); ++j) { + for (std::size_t j = subsequenceLimits.at(i - 1); j < subsequenceLimits.at(i); ++j) + { auto sharpness = _sharpnessScores.at(j) * weights.at(weightPosition); ++weightPosition; - if (sharpness > bestSharpness) { + if (sharpness > bestSharpness) + { bestIndex = j; bestSharpness = sharpness; } @@ -307,12 +336,14 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ _selectedFrames.at(bestIndex) = '1'; // The frame has been selected, flip it to 1 } - ALICEVISION_LOG_INFO("Finished selecting all the keyframes! " << _selectedKeyframes.size() << "/" << - sequenceSize << " frames have been selected."); + ALICEVISION_LOG_INFO("Finished selecting all the keyframes! " << _selectedKeyframes.size() << "/" << sequenceSize + << " frames have been selected."); } -bool KeyframeSelector::computeScores(const std::size_t rescaledWidthSharpness, const std::size_t rescaledWidthFlow, - const std::size_t sharpnessWindowSize, const std::size_t flowCellSize, +bool KeyframeSelector::computeScores(const std::size_t rescaledWidthSharpness, + const std::size_t rescaledWidthFlow, + const std::size_t sharpnessWindowSize, + const std::size_t flowCellSize, const bool skipSharpnessComputation) { // Reset the computed scores @@ -326,21 +357,24 @@ bool KeyframeSelector::computeScores(const std::size_t rescaledWidthSharpness, c // Create single feed and count minimum number of frames std::size_t nbFrames = std::numeric_limits::max(); - for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) { + for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) + { const auto& path = _mediaPaths.at(mediaIndex); // Create a feed provider per mediaPaths auto feed = std::make_unique(path); // Check if feed is initialized - if (!feed->isInit()) { + if (!feed->isInit()) + { ALICEVISION_THROW(std::invalid_argument, "Cannot initialize the FeedProvider with " << path); } // Number of frames in the rig might slightly differ nbFrames = std::min(nbFrames, static_cast(feed->nbFrames())); - if (mediaIndex == 0) { + if (mediaIndex == 0) + { // Read first image and set _frameWidth and _frameHeight, since the feeds have been initialized feed->goToFrame(0); cv::Mat mat = readImage(*feed, rescaledWidthFlow); @@ -349,23 +383,27 @@ bool KeyframeSelector::computeScores(const std::size_t rescaledWidthSharpness, c _frameHeight = mat.size().height; } - if (_maskPaths.size() > 0) { + if (_maskPaths.size() > 0) + { const auto& maskPath = _maskPaths.at(mediaIndex); auto maskFeed = std::make_unique(maskPath); - if (!maskFeed->isInit()) { + if (!maskFeed->isInit()) + { ALICEVISION_THROW(std::invalid_argument, "Invalid path to masks: " << maskPath); } const std::size_t nbMasks = static_cast(feed->nbFrames()); - if (nbMasks != nbFrames) { + if (nbMasks != nbFrames) + { ALICEVISION_THROW_ERROR("The number of masks does not match the number of frames."); } } } // Check if minimum number of frame is zero - if (nbFrames == 0) { + if (nbFrames == 0) + { ALICEVISION_THROW(std::invalid_argument, "One or multiple medias can't be found or is empty!"); } @@ -379,7 +417,8 @@ bool KeyframeSelector::computeScores(const std::size_t rescaledWidthSharpness, c // of frames, for example), resize it: less threads will be spawned, but since new FeedProvider objects need to be // created for each thread, we prevent spawing thread that will need to create FeedProvider objects // for very few frames. - if (blockSize < _minBlockSize && nbFrames >= _minBlockSize) { + if (blockSize < _minBlockSize && nbFrames >= _minBlockSize) + { blockSize = _minBlockSize; nbThreads = static_cast(nbFrames / blockSize) + 1; // +1 to ensure that every frame in processed by a thread } @@ -387,41 +426,56 @@ bool KeyframeSelector::computeScores(const std::size_t rescaledWidthSharpness, c std::vector threads; ALICEVISION_LOG_INFO("Splitting " << nbFrames << " frames into " << nbThreads << " threads of size " << blockSize << "."); - for (std::size_t i = 0; i < nbThreads; i++) { + for (std::size_t i = 0; i < nbThreads; i++) + { std::size_t startFrame = static_cast(std::max(0, static_cast(i * blockSize) - 1)); std::size_t endFrame = std::min(i * blockSize + blockSize, nbFrames); // If there is an extra thread with no new frames to process, skip it. // This might occur as a consequence of the "+1" when adjusting the number of threads. - if (startFrame >= nbFrames) { + if (startFrame >= nbFrames) + { break; } ALICEVISION_LOG_DEBUG("Starting thread to compute scores for frame " << startFrame << " to " << endFrame << "."); - threads.push_back(std::thread(&KeyframeSelector::computeScoresProc, this, startFrame, endFrame, nbFrames, - rescaledWidthSharpness, rescaledWidthFlow, sharpnessWindowSize, flowCellSize, + threads.push_back(std::thread(&KeyframeSelector::computeScoresProc, + this, + startFrame, + endFrame, + nbFrames, + rescaledWidthSharpness, + rescaledWidthFlow, + sharpnessWindowSize, + flowCellSize, skipSharpnessComputation)); } - for (auto &th : threads) { + for (auto& th : threads) + { th.join(); } return true; } -bool KeyframeSelector::computeScoresProc(const std::size_t startFrame, const std::size_t endFrame, - const std::size_t nbFrames, const std::size_t rescaledWidthSharpness, - const std::size_t rescaledWidthFlow, const std::size_t sharpnessWindowSize, - const std::size_t flowCellSize, const bool skipSharpnessComputation) +bool KeyframeSelector::computeScoresProc(const std::size_t startFrame, + const std::size_t endFrame, + const std::size_t nbFrames, + const std::size_t rescaledWidthSharpness, + const std::size_t rescaledWidthFlow, + const std::size_t sharpnessWindowSize, + const std::size_t flowCellSize, + const bool skipSharpnessComputation) { std::vector> feeds; std::vector> maskFeeds; const bool masksProvided = _maskPaths.size() > 0; - for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) { + for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) + { const auto& path = _mediaPaths.at(mediaIndex); // Create a feed provider per mediaPaths @@ -429,73 +483,83 @@ bool KeyframeSelector::computeScoresProc(const std::size_t startFrame, const std const auto& feed = *feeds.back(); // Check if feed is initialized - if (!feed.isInit()) { + if (!feed.isInit()) + { ALICEVISION_THROW(std::invalid_argument, "Cannot initialize the FeedProvider with " << path); } - if (masksProvided) { + if (masksProvided) + { const auto& maskPath = _maskPaths.at(mediaIndex); // Create a feed provider per mask directory maskFeeds.push_back(std::make_unique(maskPath)); const auto& maskFeed = *maskFeeds.back(); - if (!maskFeed.isInit()) { + if (!maskFeed.isInit()) + { ALICEVISION_THROW(std::invalid_argument, "Invalid path to masks: " << maskPath); } } } // Feed provider variables - camera::Pinhole queryIntrinsics; // image associated camera intrinsics - bool hasIntrinsics = false; // true if queryIntrinsics is valid + camera::Pinhole queryIntrinsics; // image associated camera intrinsics + bool hasIntrinsics = false; // true if queryIntrinsics is valid // Input feed provider variables - image::Image image; // original image - std::string currentImgName; // current image name + image::Image image; // original image + std::string currentImgName; // current image name // Mask feed provider variables - image::Image mask; // original mask - std::string currentMaskName; // current mask name - + image::Image mask; // original mask + std::string currentMaskName; // current mask name // Feed and metadata initialization - for (std::size_t mediaIndex = 0; mediaIndex < feeds.size(); ++mediaIndex) { + for (std::size_t mediaIndex = 0; mediaIndex < feeds.size(); ++mediaIndex) + { // First frame with offset feeds.at(mediaIndex)->goToFrame(startFrame); - if (!feeds.at(mediaIndex)->readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) { + if (!feeds.at(mediaIndex)->readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) + { ALICEVISION_THROW(std::invalid_argument, "Cannot read media first frame " << _mediaPaths[mediaIndex]); } - if (masksProvided) { + if (masksProvided) + { maskFeeds.at(mediaIndex)->goToFrame(startFrame); - if (!maskFeeds.at(mediaIndex)->readImage(mask, queryIntrinsics, currentMaskName, hasIntrinsics)) { + if (!maskFeeds.at(mediaIndex)->readImage(mask, queryIntrinsics, currentMaskName, hasIntrinsics)) + { ALICEVISION_THROW(std::invalid_argument, "Cannot read mask media first frame " << _maskPaths[mediaIndex]); } } } std::size_t currentFrame = startFrame; - cv::Mat currentMatSharpness; // OpenCV matrix for the sharpness computation + cv::Mat currentMatSharpness; // OpenCV matrix for the sharpness computation cv::Mat previousMatFlow, currentMatFlow; // OpenCV matrices for the optical flow computation auto ptrFlow = cv::optflow::createOptFlow_DeepFlow(); cv::Mat currentMatFlowMask, currentMatMask; // OpenCV matrices that will contain the masks - while (currentFrame < endFrame) { + while (currentFrame < endFrame) + { double minimalSharpness = skipSharpnessComputation ? 1.0f : std::numeric_limits::max(); double minimalFlow = std::numeric_limits::max(); - for (std::size_t mediaIndex = 0; mediaIndex < feeds.size(); ++mediaIndex) { + for (std::size_t mediaIndex = 0; mediaIndex < feeds.size(); ++mediaIndex) + { auto& feed = *feeds.at(mediaIndex); - if (currentFrame > startFrame) { // Get currentFrame - 1 for the optical flow computation + if (currentFrame > startFrame) + { // Get currentFrame - 1 for the optical flow computation previousMatFlow = readImage(feed, rescaledWidthFlow); feed.goToNextFrame(); - if (masksProvided) { - auto &maskFeed = *maskFeeds.at(mediaIndex); + if (masksProvided) + { + auto& maskFeed = *maskFeeds.at(mediaIndex); maskFeed.goToNextFrame(); } } @@ -508,21 +572,27 @@ bool KeyframeSelector::computeScoresProc(const std::size_t startFrame, const std * - otherwise (feed not correctly moved to the next frame), keep on going to the next frame until it is * valid or the end of the feed is reached */ - if (!skipSharpnessComputation) { - try { + if (!skipSharpnessComputation) + { + try + { // Read image for sharpness and rescale it if requested currentMatSharpness = readImage(feed, rescaledWidthSharpness); - if (masksProvided) { - auto &maskFeed = *maskFeeds.at(mediaIndex); + if (masksProvided) + { + auto& maskFeed = *maskFeeds.at(mediaIndex); currentMatMask = readImage(maskFeed, rescaledWidthSharpness); } - } catch (const std::invalid_argument& ex) { + } + catch (const std::invalid_argument& ex) + { bool success = false; - while (!success && currentFrame < nbFrames) { + while (!success && currentFrame < nbFrames) + { // currentFrame + 1 = currently evaluated frame with indexing starting at 1, for display reasons // currentFrame + 2 = next frame to evaluate with indexing starting at 1, for display reasons - ALICEVISION_LOG_WARNING("Invalid or missing frame " << currentFrame + 1 - << ", attempting to read frame " << currentFrame + 2 << "."); + ALICEVISION_LOG_WARNING("Invalid or missing frame " << currentFrame + 1 << ", attempting to read frame " << currentFrame + 2 + << "."); { // Push dummy scores for the frame that was skipped @@ -532,11 +602,13 @@ bool KeyframeSelector::computeScoresProc(const std::size_t startFrame, const std } success = feed.goToFrame(++currentFrame); - if (success) { + if (success) + { currentMatSharpness = readImage(feed, rescaledWidthSharpness); - if (masksProvided) { - auto &maskFeed = *maskFeeds.at(mediaIndex); + if (masksProvided) + { + auto& maskFeed = *maskFeeds.at(mediaIndex); maskFeed.goToFrame(currentFrame); currentMatMask = readImage(maskFeed, rescaledWidthSharpness); } @@ -545,30 +617,36 @@ bool KeyframeSelector::computeScoresProc(const std::size_t startFrame, const std } } - if (rescaledWidthSharpness == rescaledWidthFlow && !skipSharpnessComputation) { + if (rescaledWidthSharpness == rescaledWidthFlow && !skipSharpnessComputation) + { currentMatFlow = currentMatSharpness; - if (masksProvided) { - auto &maskFeed = *maskFeeds.at(mediaIndex); + if (masksProvided) + { + auto& maskFeed = *maskFeeds.at(mediaIndex); currentMatFlowMask = currentMatMask; } - } else { + } + else + { currentMatFlow = readImage(feed, rescaledWidthFlow); - if (masksProvided) { - auto &maskFeed = *maskFeeds.at(mediaIndex); + if (masksProvided) + { + auto& maskFeed = *maskFeeds.at(mediaIndex); currentMatFlowMask = readImage(maskFeed, rescaledWidthFlow); } } // Compute sharpness - if (!skipSharpnessComputation) { + if (!skipSharpnessComputation) + { const double sharpness = computeSharpness(currentMatSharpness, sharpnessWindowSize, currentMatMask); minimalSharpness = std::min(minimalSharpness, sharpness); } // Compute optical flow - if (currentFrame > startFrame) { - const double flow = estimateFlow(ptrFlow, currentMatFlow, previousMatFlow, flowCellSize, - currentMatFlowMask); + if (currentFrame > startFrame) + { + const double flow = estimateFlow(ptrFlow, currentMatFlow, previousMatFlow, flowCellSize, currentMatFlowMask); minimalFlow = std::min(minimalFlow, flow); } @@ -599,41 +677,50 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, bool hasIntrinsics = false; std::string currentImgName; - for (std::size_t id = 0; id < _mediaPaths.size(); ++id) { + for (std::size_t id = 0; id < _mediaPaths.size(); ++id) + { const auto& path = _mediaPaths.at(id); // Create a feed provider per mediaPaths dataio::FeedProvider feed(path); // Check if feed is initialized - if (!feed.isInit()) { + if (!feed.isInit()) + { ALICEVISION_LOG_ERROR("Cannot initialize the FeedProvider with " << path); return false; } // Ensure that we do want to write the keyframes on disk before going through this - if (outputExtension != "none") { + if (outputExtension != "none") + { std::string processedOutputFolder = _outputFolder; - if (_mediaPaths.size() > 1) { + if (_mediaPaths.size() > 1) + { const std::string rigFolder = _outputFolder + "/rig/"; - if (!fs::exists(rigFolder)) { + if (!fs::exists(rigFolder)) + { fs::create_directory(rigFolder); } processedOutputFolder = rigFolder + std::to_string(id); - if (!fs::exists(processedOutputFolder)) { + if (!fs::exists(processedOutputFolder)) + { fs::create_directory(processedOutputFolder); } } unsigned int outputKeyframeCnt = 0; // Used if the "renameKeyframes" option is enabled - for (const auto pos : _selectedKeyframes) { - if (!feed.goToFrame(pos)) { + for (const auto pos : _selectedKeyframes) + { + if (!feed.goToFrame(pos)) + { ALICEVISION_LOG_ERROR("Invalid frame position " << pos << ". Ignoring this frame."); continue; } - if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) { + if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) + { ALICEVISION_LOG_ERROR("Error reading image"); return false; } @@ -662,12 +749,15 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, image::ImageWriteOptions options; // If the feed is a video, frames are read as OpenCV RGB matrices before being converted to image::ImageRGB - if (feed.isVideo()) { + if (feed.isVideo()) + { options.fromColorSpace(image::EImageColorSpace::SRGB); options.toColorSpace(image::EImageColorSpace::AUTO); - } else { // Otherwise, the frames have been read without any conversion, they should be written as such + } + else + { // Otherwise, the frames have been read without any conversion, they should be written as such if (colorspace == "sRGB") - options.fromColorSpace(image::EImageColorSpace::SRGB); + options.fromColorSpace(image::EImageColorSpace::SRGB); if (outputExtension == "exr") options.toColorSpace(image::EImageColorSpace::NO_CONVERSION); @@ -675,7 +765,8 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, options.toColorSpace(image::EImageColorSpace::AUTO); } - if (storageDataType != image::EStorageDataType::Undefined && outputExtension == "exr") { + if (storageDataType != image::EStorageDataType::Undefined && outputExtension == "exr") + { options.storageDataType(storageDataType); } @@ -687,9 +778,11 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, } // If the current media is a video and there is no output keyframe, the corresponding SfMData file will not be written - if (feed.isVideo() && outputExtension == "none") { - ALICEVISION_THROW(std::invalid_argument, "The keyframes selected from the input video have not been " << - "written on disk. The keyframes' SfMData file cannot be written."); + if (feed.isVideo() && outputExtension == "none") + { + ALICEVISION_THROW(std::invalid_argument, + "The keyframes selected from the input video have not been " + << "written on disk. The keyframes' SfMData file cannot be written."); } if (!writeSfMData(path, feed, brands, models, mmFocals)) @@ -702,7 +795,8 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, bool KeyframeSelector::exportScoresToFile(const std::string& filename, const bool exportSelectedFrames) const { std::size_t sequenceSize = scoresMap.begin()->second->size(); - if (sequenceSize == 0) { + if (sequenceSize == 0) + { ALICEVISION_LOG_ERROR("Nothing to export, scores do not seem to have been computed!"); return false; } @@ -710,16 +804,17 @@ bool KeyframeSelector::exportScoresToFile(const std::string& filename, const boo std::ofstream os; os.open((fs::path(_outputFolder) / filename).string(), std::ios::app); - if (!os.is_open()) { + if (!os.is_open()) + { ALICEVISION_LOG_ERROR("Unable to open the scores file: " << filename << "."); return false; } - ALICEVISION_LOG_DEBUG("Exporting scores as CSV file: " << filename << " (export selected frames: " - << exportSelectedFrames << ")"); + ALICEVISION_LOG_DEBUG("Exporting scores as CSV file: " << filename << " (export selected frames: " << exportSelectedFrames << ")"); os.seekp(0, std::ios::end); // Put the cursor at the end of the file - if (os.tellp() == std::streampos(0)) { // 'tellp' returns the cursor's position + if (os.tellp() == std::streampos(0)) + { // 'tellp' returns the cursor's position // If the file does not exist yet, add a header std::string header = "FrameNb;"; for (const auto& mapIterator : scoresMap) @@ -731,7 +826,8 @@ bool KeyframeSelector::exportScoresToFile(const std::string& filename, const boo os << header << "\n"; } - for (std::size_t index = 0; index < sequenceSize; ++index) { + for (std::size_t index = 0; index < sequenceSize; ++index) + { os << index << ";"; // First column: frame index for (const auto& mapIterator : scoresMap) @@ -752,7 +848,8 @@ bool KeyframeSelector::exportFlowVisualisation(const std::size_t rescaledWidth) std::vector> feeds; std::vector outputFolders; - for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) { + for (std::size_t mediaIndex = 0; mediaIndex < _mediaPaths.size(); ++mediaIndex) + { const auto& path = _mediaPaths.at(mediaIndex); // Create a feed provider per mediaPaths @@ -760,7 +857,8 @@ bool KeyframeSelector::exportFlowVisualisation(const std::size_t rescaledWidth) auto& feed = *feeds.back(); // Check if feed is initialized - if (!feed.isInit()) { + if (!feed.isInit()) + { ALICEVISION_LOG_ERROR("Cannot initialize the FeedProvider with " << path); return false; } @@ -772,14 +870,17 @@ bool KeyframeSelector::exportFlowVisualisation(const std::size_t rescaledWidth) // If there is a rig, create the corresponding folders std::string processedOutputFolder = _outputFolder; - if (_mediaPaths.size() > 1) { + if (_mediaPaths.size() > 1) + { const std::string rigFolder = _outputFolder + "/rig/"; - if (!fs::exists(rigFolder)) { + if (!fs::exists(rigFolder)) + { fs::create_directory(rigFolder); } processedOutputFolder = rigFolder + std::to_string(mediaIndex); - if (!fs::exists(processedOutputFolder)) { + if (!fs::exists(processedOutputFolder)) + { fs::create_directory(processedOutputFolder); } } @@ -788,7 +889,8 @@ bool KeyframeSelector::exportFlowVisualisation(const std::size_t rescaledWidth) outputFolders.push_back(processedOutputFolder); } - if (nbFrames == 0) { + if (nbFrames == 0) + { ALICEVISION_LOG_ERROR("No frame to visualise optical flow from!"); return false; } @@ -800,30 +902,35 @@ bool KeyframeSelector::exportFlowVisualisation(const std::size_t rescaledWidth) /* To be able to handle the rigs and to avoid storing the optical flow results for all frames in case * we might want to export them, we need to recompute the optical flow for all the frames, even if it has already * been computed in computeScores(). */ - while (currentFrame < nbFrames) { - for (std::size_t mediaIndex = 0; mediaIndex < feeds.size(); ++mediaIndex) { + while (currentFrame < nbFrames) + { + for (std::size_t mediaIndex = 0; mediaIndex < feeds.size(); ++mediaIndex) + { auto& feed = *feeds.at(mediaIndex); - if (currentFrame > 0) { // Get currentFrame - 1 for the optical flow computation + if (currentFrame > 0) + { // Get currentFrame - 1 for the optical flow computation previousMat = readImage(feed, rescaledWidth); feed.goToNextFrame(); } // Handle invalid or missing frames - try { + try + { currentMat = readImage(feed, rescaledWidth); // Read image and rescale it if requested - } catch (const std::invalid_argument& ex) { - ALICEVISION_LOG_WARNING("Invalid or missing frame " << currentFrame + 1 - << ", attempting to read frame " << currentFrame + 2 << "."); + } + catch (const std::invalid_argument& ex) + { + ALICEVISION_LOG_WARNING("Invalid or missing frame " << currentFrame + 1 << ", attempting to read frame " << currentFrame + 2 << "."); bool success = feed.goToFrame(++currentFrame); if (success) currentMat = readImage(feed, rescaledWidth); else - ALICEVISION_THROW_ERROR("Could not go to frame " << currentFrame + 1 - << " either. The feed might be corrupted."); + ALICEVISION_THROW_ERROR("Could not go to frame " << currentFrame + 1 << " either. The feed might be corrupted."); } - if (currentFrame > 0) { + if (currentFrame > 0) + { cv::Mat flow; ptrFlow->calc(currentMat, previousMat, flow); @@ -854,14 +961,15 @@ bool KeyframeSelector::exportFlowVisualisation(const std::size_t rescaledWidth) return true; } -cv::Mat KeyframeSelector::readImage(dataio::FeedProvider &feed, std::size_t width) +cv::Mat KeyframeSelector::readImage(dataio::FeedProvider& feed, std::size_t width) { image::Image image; camera::Pinhole queryIntrinsics; bool hasIntrinsics = false; std::string currentImgName; - if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) { + if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) + { ALICEVISION_THROW(std::invalid_argument, "Cannot read frame '" << currentImgName << "'!"); } @@ -877,30 +985,29 @@ cv::Mat KeyframeSelector::readImage(dataio::FeedProvider &feed, std::size_t widt return cvGrayscale; cv::Mat cvRescaled; - if (cvGrayscale.cols > width && width > 0) { - cv::resize(cvGrayscale, cvRescaled, - cv::Size(width, double(cvGrayscale.rows) * double(width) / double(cvGrayscale.cols))); + if (cvGrayscale.cols > width && width > 0) + { + cv::resize(cvGrayscale, cvRescaled, cv::Size(width, double(cvGrayscale.rows) * double(width) / double(cvGrayscale.cols))); } return cvRescaled; } -double KeyframeSelector::computeSharpness(const cv::Mat& grayscaleImage, const std::size_t windowSize, - const cv::Mat& mask) +double KeyframeSelector::computeSharpness(const cv::Mat& grayscaleImage, const std::size_t windowSize, const cv::Mat& mask) { - if (windowSize > grayscaleImage.size().width || windowSize > grayscaleImage.size().height) { + if (windowSize > grayscaleImage.size().width || windowSize > grayscaleImage.size().height) + { ALICEVISION_THROW(std::invalid_argument, "Cannot use a sliding window bigger than the image (sliding window size: " - << windowSize << ", image size: " << grayscaleImage.size().width << "x" - << grayscaleImage.size().height << ")"); + << windowSize << ", image size: " << grayscaleImage.size().width << "x" << grayscaleImage.size().height << ")"); } - if (!mask.empty() && (mask.size().width != grayscaleImage.size().width || - mask.size().height != grayscaleImage.size().height)) { + if (!mask.empty() && (mask.size().width != grayscaleImage.size().width || mask.size().height != grayscaleImage.size().height)) + { ALICEVISION_THROW(std::invalid_argument, - "The sizes of the frame and the mask differ (image size: " << grayscaleImage.size().width - << "x" << grayscaleImage.size().height << ", mask size: " << mask.size().width << "x" - << mask.size().height << ")"); + "The sizes of the frame and the mask differ (image size: " + << grayscaleImage.size().width << "x" << grayscaleImage.size().height << ", mask size: " << mask.size().width << "x" + << mask.size().height << ")"); } cv::Mat sum, squaredSum, laplacian; @@ -913,7 +1020,8 @@ double KeyframeSelector::computeSharpness(const cv::Mat& grayscaleImage, const s // If the mask exists, apply it directly on the integral and squared integral images: // The sharpness information will still be retained but the masked pixels will now appear as 0s, // and they will be counted out during the standard deviation computation. - if (!mask.empty()) { + if (!mask.empty()) + { // The integral matrices are padded, so the mask needs it as well paddedMask = cv::Mat(sum.size(), CV_8UC1, 255); mask.copyTo(paddedMask(cv::Rect(1, 1, paddedMask.size().width - 1, paddedMask.size().height - 1))); @@ -925,14 +1033,17 @@ double KeyframeSelector::computeSharpness(const cv::Mat& grayscaleImage, const s int x, y; // Starts at 1 because the integral image is padded with 0s on the top and left borders - for (y = 1; y < sum.rows - windowSize; y += windowSize / 4) { - for (x = 1; x < sum.cols - windowSize; x += windowSize / 4) { + for (y = 1; y < sum.rows - windowSize; y += windowSize / 4) + { + for (x = 1; x < sum.cols - windowSize; x += windowSize / 4) + { maxstd = std::max(maxstd, computeSharpnessStd(maskedSum, maskedSquaredSum, x, y, windowSize, paddedMask)); } // Compute sharpness over the last part of the image for windowSize along the x-axis; // the overlap with the previous window might be greater than the previous ones - if (x >= sum.cols - windowSize) { + if (x >= sum.cols - windowSize) + { x = sum.cols - windowSize - 1; maxstd = std::max(maxstd, computeSharpnessStd(maskedSum, maskedSquaredSum, x, y, windowSize, paddedMask)); } @@ -940,7 +1051,8 @@ double KeyframeSelector::computeSharpness(const cv::Mat& grayscaleImage, const s // Compute sharpness over the last part of the image for windowSize along the y-axis; // the overlap with the previous window might be greater than the previous ones - if (y >= sum.rows - windowSize) { + if (y >= sum.rows - windowSize) + { y = sum.rows - windowSize - 1; maxstd = std::max(maxstd, computeSharpnessStd(maskedSum, maskedSquaredSum, x, y, windowSize, paddedMask)); } @@ -948,8 +1060,12 @@ double KeyframeSelector::computeSharpness(const cv::Mat& grayscaleImage, const s return maxstd; } -const double KeyframeSelector::computeSharpnessStd(const cv::Mat& sum, const cv::Mat& squaredSum, const int x, - const int y, const int windowSize, const cv::Mat &mask) +const double KeyframeSelector::computeSharpnessStd(const cv::Mat& sum, + const cv::Mat& squaredSum, + const int x, + const int y, + const int windowSize, + const cv::Mat& mask) { double totalCount = windowSize * windowSize; @@ -965,7 +1081,8 @@ const double KeyframeSelector::computeSharpnessStd(const cv::Mat& sum, const cv: br = squaredSum.at(y + windowSize, x + windowSize); const double s2 = br + tl - tr - bl; - if (!mask.empty()) { + if (!mask.empty()) + { // Count the number of pixels that are non-masked. Masked pixels are 0s. cv::Mat maskROI = mask(cv::Rect(x, y, windowSize, windowSize)); totalCount = cv::countNonZero(maskROI); @@ -974,37 +1091,41 @@ const double KeyframeSelector::computeSharpnessStd(const cv::Mat& sum, const cv: const double var = (s2 - (s1 * s1) / totalCount) / totalCount; // If the variance is negative or if less than 50% of the window is not covered by the mask, // return an invalid value. - if (var < 0.0 || totalCount < windowSize * windowSize * 0.5) { + if (var < 0.0 || totalCount < windowSize * windowSize * 0.5) + { return -1.0f; } return std::sqrt(var); } -double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFlow, const cv::Mat& grayscaleImage, - const cv::Mat& previousGrayscaleImage, const std::size_t cellSize, +double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFlow, + const cv::Mat& grayscaleImage, + const cv::Mat& previousGrayscaleImage, + const std::size_t cellSize, const cv::Mat& mask) { - if (cellSize > grayscaleImage.size().width) { // If the cell size is bigger than the height, it will be adjusted + if (cellSize > grayscaleImage.size().width) + { // If the cell size is bigger than the height, it will be adjusted ALICEVISION_THROW(std::invalid_argument, - "Cannot use a cell size bigger than the image's width (cell size: " << cellSize - << ", image's width: " << grayscaleImage.size().width << ")"); + "Cannot use a cell size bigger than the image's width (cell size: " << cellSize << ", image's width: " + << grayscaleImage.size().width << ")"); } - if (grayscaleImage.size() != previousGrayscaleImage.size()) { + if (grayscaleImage.size() != previousGrayscaleImage.size()) + { ALICEVISION_THROW(std::invalid_argument, "The images used for the optical flow computation have different sizes (" - << grayscaleImage.size().width << "x" << grayscaleImage.size().height << " and " - << previousGrayscaleImage.size().width << "x" << previousGrayscaleImage.size().height - << ")"); + << grayscaleImage.size().width << "x" << grayscaleImage.size().height << " and " << previousGrayscaleImage.size().width + << "x" << previousGrayscaleImage.size().height << ")"); } - if (!mask.empty() && (grayscaleImage.size().width != mask.size().width || - grayscaleImage.size().height != mask.size().height)) { + if (!mask.empty() && (grayscaleImage.size().width != mask.size().width || grayscaleImage.size().height != mask.size().height)) + { ALICEVISION_THROW(std::invalid_argument, - "The sizes of the framse and the masks differ (image size: " << grayscaleImage.size().width - << "x" << grayscaleImage.size().height << ", mask size: " << mask.size().width << "x" - << mask.size().height << ")"); + "The sizes of the framse and the masks differ (image size: " + << grayscaleImage.size().width << "x" << grayscaleImage.size().height << ", mask size: " << mask.size().width << "x" + << mask.size().height << ")"); } cv::Mat flow; @@ -1015,7 +1136,8 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl cv::Mat maskedSumflow = sumflow; cv::Mat paddedMask; - if (!mask.empty()) { + if (!mask.empty()) + { // The integral matrix is padded, so the mask needs it as well paddedMask = cv::Mat(sumflow.size(), CV_8UC1, 255); mask.copyTo(paddedMask(cv::Rect(1, 1, paddedMask.size().width - 1, paddedMask.size().height - 1))); @@ -1030,7 +1152,7 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl xyChannels[1].copyTo(yChannel, paddedMask); // Merge back the channels together - std::vector channels = { xyChannels[0], xyChannels[1] }; + std::vector channels = {xyChannels[0], xyChannels[1]}; cv::merge(channels, maskedSumflow); } @@ -1038,12 +1160,14 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl std::vector motionByCell; // Starts at 1 because the integral matrix is padded with 0s on the top and left borders - for (std::size_t y = 1; y < maskedSumflow.size().height; y += cellSize) { + for (std::size_t y = 1; y < maskedSumflow.size().height; y += cellSize) + { std::size_t maxCellSizeHeight = cellSize; if (std::min(maskedSumflow.size().height, int(y + cellSize)) == maskedSumflow.size().height) maxCellSizeHeight = maskedSumflow.size().height - y; - for (std::size_t x = 1; x < maskedSumflow.size().width; x += cellSize) { + for (std::size_t x = 1; x < maskedSumflow.size().width; x += cellSize) + { std::size_t maxCellSizeWidth = cellSize; if (std::min(maskedSumflow.size().width, int(x + cellSize)) == maskedSumflow.size().width) maxCellSizeWidth = maskedSumflow.size().width - x; @@ -1056,17 +1180,21 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl const double hypot = std::hypot(s.x, s.y); double totalCount = maxCellSizeWidth * maxCellSizeHeight; - if (!mask.empty()) { + if (!mask.empty()) + { // Count the number of pixels that are non-masked. Masked pixels are 0s. cv::Mat maskROI = paddedMask(cv::Rect(x, y, maxCellSizeWidth, maxCellSizeHeight)); totalCount = cv::countNonZero(maskROI); } - if (totalCount > maxCellSizeWidth * maxCellSizeHeight * 0.5) { + if (totalCount > maxCellSizeWidth * maxCellSizeHeight * 0.5) + { // If at least 50% of the cell is masked, then ignore it and skip to the next one norm = hypot / totalCount; motionByCell.push_back(norm); - } else { + } + else + { ALICEVISION_LOG_DEBUG("At least 50\% of the cell is covered by the mask. Skipping it."); } } @@ -1075,34 +1203,45 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl return findMedian(motionByCell); } -bool KeyframeSelector::writeSfMData(const std::string& mediaPath, dataio::FeedProvider &feed, - const std::vector& brands, const std::vector& models, +bool KeyframeSelector::writeSfMData(const std::string& mediaPath, + dataio::FeedProvider& feed, + const std::vector& brands, + const std::vector& models, const std::vector& mmFocals) { bool filledOutputs = false; - if (!feed.isSfMData()) { + if (!feed.isSfMData()) + { filledOutputs = writeSfMDataFromSequences(mediaPath, feed, brands, models, mmFocals); - } else { + } + else + { filledOutputs = writeSfMDataFromSfMData(mediaPath); } - if (!filledOutputs) { + if (!filledOutputs) + { ALICEVISION_LOG_ERROR("Error while filling the output SfMData files."); return false; } - if (!sfmDataIO::Save(_outputSfmKeyframes, _outputSfmKeyframesPath, sfmDataIO::ESfMData::ALL)) { + if (!sfmDataIO::Save(_outputSfmKeyframes, _outputSfmKeyframesPath, sfmDataIO::ESfMData::ALL)) + { ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmKeyframesPath << "' could not be written."); return false; } - if (!feed.isVideo()) { - if (!sfmDataIO::Save(_outputSfmFrames, _outputSfmFramesPath, sfmDataIO::ESfMData::ALL)) { + if (!feed.isVideo()) + { + if (!sfmDataIO::Save(_outputSfmFrames, _outputSfmFramesPath, sfmDataIO::ESfMData::ALL)) + { ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmFramesPath << "' could not be written."); return false; } - } else { + } + else + { ALICEVISION_LOG_DEBUG("The input feed is a video. The SfMData file containing the unselected frames will not" " be written."); } @@ -1123,7 +1262,8 @@ bool KeyframeSelector::writeSfMDataFromSfMData(const std::string& mediaPath) sfmData::SfMData inputSfm; std::vector> views; - if (!sfmDataIO::Load(inputSfm, mediaPath, sfmDataIO::ESfMData::ALL)) { + if (!sfmDataIO::Load(inputSfm, mediaPath, sfmDataIO::ESfMData::ALL)) + { ALICEVISION_LOG_ERROR("Could not open input SfMData file " << mediaPath << "."); return false; } @@ -1131,28 +1271,33 @@ bool KeyframeSelector::writeSfMDataFromSfMData(const std::string& mediaPath) // Order the views according to the frame ID and the intrinsics serial number std::map>> viewSequences; auto& intrinsics = inputSfm.getIntrinsics(); - for(auto it = inputSfm.getViews().begin(); it != inputSfm.getViews().end(); ++it) { + for (auto it = inputSfm.getViews().begin(); it != inputSfm.getViews().end(); ++it) + { auto view = it->second; auto serialNumber = intrinsics.at(view->getIntrinsicId())->serialNumber(); viewSequences[serialNumber].push_back(view); } // Sort the views with the same intrinsics together based on their frame ID and add them to the final global vector - for(auto& view : viewSequences) { - std::sort(view.second.begin(), view.second.end(), - [](std::shared_ptr v1, std::shared_ptr v2) { - return v1->getFrameId() < v2->getFrameId(); - }); + for (auto& view : viewSequences) + { + std::sort(view.second.begin(), view.second.end(), [](std::shared_ptr v1, std::shared_ptr v2) { + return v1->getFrameId() < v2->getFrameId(); + }); views.insert(views.end(), view.second.begin(), view.second.end()); } - for (int i = 0; i < views.size(); ++i) { + for (int i = 0; i < views.size(); ++i) + { viewId = views[i]->getViewId(); intrinsicId = views[i]->getIntrinsicId(); - if (_selectedFrames[i] == '1') { + if (_selectedFrames[i] == '1') + { keyframesViews.emplace(viewId, views[i]); keyframesIntrinsics.emplace(intrinsicId, inputSfm.getIntrinsics().at(intrinsicId)); - } else { + } + else + { framesViews.emplace(viewId, views[i]); framesIntrinsics.emplace(intrinsicId, inputSfm.getIntrinsics().at(intrinsicId)); } @@ -1161,7 +1306,8 @@ bool KeyframeSelector::writeSfMDataFromSfMData(const std::string& mediaPath) return true; } -bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, dataio::FeedProvider &feed, +bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, + dataio::FeedProvider& feed, const std::vector& brands, const std::vector& models, const std::vector& mmFocals) @@ -1178,8 +1324,8 @@ bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, d auto& keyframesRigs = _outputSfmKeyframes.getRigs(); auto& framesRigs = _outputSfmFrames.getRigs(); - const IndexT rigId = 0; // 0 by convention - IndexT viewId = 0; // Will be used to distinguish frames coming from videos + const IndexT rigId = 0; // 0 by convention + IndexT viewId = 0; // Will be used to distinguish frames coming from videos IndexT previousFrameId = UndefinedIndexT; feed.goToFrame(0); @@ -1195,22 +1341,28 @@ bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, d // Create rig structure if it is needed and does not already exist // A rig structure is needed when there is more than one input path - if (_mediaPaths.size() > 1 && keyframesRigs.size() == 0 && framesRigs.size() == 0) { + if (_mediaPaths.size() > 1 && keyframesRigs.size() == 0 && framesRigs.size() == 0) + { sfmData::Rig rig(_mediaPaths.size()); keyframesRigs[rigId] = rig; framesRigs[rigId] = rig; } - for (std::size_t i = 0; i < feed.nbFrames(); ++i) { + for (std::size_t i = 0; i < feed.nbFrames(); ++i) + { // Need to read the image to get its size and path - if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) { + if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) + { ALICEVISION_LOG_ERROR("Error reading image."); // Frames may be seldomly corrupted in the VideoFeeds, but this should not occur with other feeds - if (feed.isVideo()) { + if (feed.isVideo()) + { ALICEVISION_LOG_WARNING("Skipping to the next frame."); continue; - } else { + } + else + { return false; } } @@ -1220,7 +1372,8 @@ bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, d previousFrameId = view->getFrameId(); // If there is a rig, the view's rig and sub-pose IDs need to be set once it has been completed - if (keyframesRigs.size() > 0 && framesRigs.size() > 0) { + if (keyframesRigs.size() > 0 && framesRigs.size() > 0) + { view->setRigAndSubPoseId(rigId, mediaIndex); } @@ -1239,22 +1392,23 @@ bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, d double sensorWidth = -1.0; sensorDB::Datasheet datasheet; - if (_parsedSensorDb && !make.empty() && !model.empty() && - sensorDB::getInfo(make, model, _sensorDatabase, datasheet)) { + if (_parsedSensorDb && !make.empty() && !model.empty() && sensorDB::getInfo(make, model, _sensorDatabase, datasheet)) + { sensorWidth = datasheet._sensorWidth; } // Create the intrinsic for the view - auto intrinsic = createIntrinsic(*view, focalLength == -1.0 ? 0 : focalLength, sensorWidth, mediaIndex, - imageRatio); + auto intrinsic = createIntrinsic(*view, focalLength == -1.0 ? 0 : focalLength, sensorWidth, mediaIndex, imageRatio); // Update intrinsics ID if this is a new one if (previousIntrinsic != nullptr && *previousIntrinsic != *intrinsic) view->setIntrinsicId(++intrinsicId); // Fill the SfMData files - if (_selectedFrames[i] == '1') { - if (feed.isVideo()) { + if (_selectedFrames[i] == '1') + { + if (feed.isVideo()) + { // If the feed is a video, all views will have the same view ID by default, this needs to be fixed view->setViewId(view->getViewId() + viewId++); view->setPoseId(view->getViewId()); @@ -1263,9 +1417,12 @@ bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, d } keyframesViews.emplace(view->getViewId(), view); keyframesIntrinsics.emplace(intrinsicId, intrinsic); - } else { + } + else + { // No rejected frames if the feed is a video one, as they are not written on disk - if (!feed.isVideo()) { + if (!feed.isVideo()) + { framesViews.emplace(view->getViewId(), view); framesIntrinsics.emplace(intrinsicId, intrinsic); } @@ -1281,20 +1438,22 @@ bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, d return true; } -std::shared_ptr KeyframeSelector::createView(const std::string& imagePath, IndexT intrinsicId, - IndexT previousFrameId, std::size_t imageWidth, std::size_t imageHeight) +std::shared_ptr KeyframeSelector::createView(const std::string& imagePath, + IndexT intrinsicId, + IndexT previousFrameId, + std::size_t imageWidth, + std::size_t imageHeight) { // Create the View object: most attributes are set with default values and will be updated later on - auto view = std::make_shared( - imagePath, // filepath - UndefinedIndexT, // view ID - intrinsicId, // intrinsics ID - UndefinedIndexT, // pose ID - imageWidth, // image width - imageHeight, // image height - UndefinedIndexT, // rig ID - UndefinedIndexT, // sub-pose ID - std::map() // metadata + auto view = std::make_shared(imagePath, // filepath + UndefinedIndexT, // view ID + intrinsicId, // intrinsics ID + UndefinedIndexT, // pose ID + imageWidth, // image width + imageHeight, // image height + UndefinedIndexT, // rig ID + UndefinedIndexT, // sub-pose ID + std::map() // metadata ); // Complete the View attributes @@ -1307,28 +1466,38 @@ std::shared_ptr KeyframeSelector::createView(const std::string& i std::string prefix; std::string suffix; // Use the filename to determine the frame ID (if available) - if (sfmDataIO::extractNumberFromFileStem(fs::path(view->getImage().getImagePath()).stem().string(), frameId, prefix, suffix)) { + if (sfmDataIO::extractNumberFromFileStem(fs::path(view->getImage().getImagePath()).stem().string(), frameId, prefix, suffix)) + { view->setFrameId(frameId); } // Otherwise, set it fully manually - if (view->getFrameId() == 1 && previousFrameId != UndefinedIndexT) { + if (view->getFrameId() == 1 && previousFrameId != UndefinedIndexT) + { view->setFrameId(previousFrameId + 1); } return view; } -std::shared_ptr KeyframeSelector::createIntrinsic(const sfmData::View& view, const double focalLength, - const double sensorWidth, const double imageRatio, const std::size_t mediaIndex) +std::shared_ptr KeyframeSelector::createIntrinsic(const sfmData::View& view, + const double focalLength, + const double sensorWidth, + const double imageRatio, + const std::size_t mediaIndex) { auto intrinsic = sfmDataIO::getViewIntrinsic(view, focalLength, sensorWidth); - if (imageRatio > 1.0 && sensorWidth > -1.0) { + if (imageRatio > 1.0 && sensorWidth > -1.0) + { intrinsic->setSensorWidth(sensorWidth); intrinsic->setSensorHeight(sensorWidth / imageRatio); - } else if (imageRatio <= 1.0 && sensorWidth > -1.0) { + } + else if (imageRatio <= 1.0 && sensorWidth > -1.0) + { intrinsic->setSensorWidth(sensorWidth); intrinsic->setSensorHeight(sensorWidth * imageRatio); - } else { + } + else + { // Set default values for the sensor width and sensor height intrinsic->setSensorWidth(36.0); intrinsic->setSensorHeight(24.0); @@ -1340,5 +1509,5 @@ std::shared_ptr KeyframeSelector::createIntrinsic(const s return intrinsic; } -} // namespace keyframe -} // namespace aliceVision +} // namespace keyframe +} // namespace aliceVision diff --git a/src/aliceVision/keyframe/KeyframeSelector.hpp b/src/aliceVision/keyframe/KeyframeSelector.hpp index 4940b859d2..2eb0477bce 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.hpp +++ b/src/aliceVision/keyframe/KeyframeSelector.hpp @@ -29,7 +29,7 @@ namespace image { template class Image; -} // namespace image +} // namespace image namespace keyframe { @@ -37,7 +37,7 @@ namespace oiio = OIIO; class KeyframeSelector { -public: + public: /** * @brief KeyframeSelector constructor * @param[in] mediaPaths video file path, image sequence directory or SfMData file @@ -95,9 +95,12 @@ class KeyframeSelector * @param[in] skipSharpnessComputation if true, the sharpness score computations will not be performed and a fixed * sharpness score will be given to all the input frames */ - void processSmart(const float pxDisplacement, const std::size_t rescaledWidthSharpness, - const std::size_t rescaledWidthFlow, const std::size_t sharpnessWindowSize, - const std::size_t flowCellSize, const bool skipSharpnessComputation = false); + void processSmart(const float pxDisplacement, + const std::size_t rescaledWidthSharpness, + const std::size_t rescaledWidthFlow, + const std::size_t sharpnessWindowSize, + const std::size_t flowCellSize, + const bool skipSharpnessComputation = false); /** * @brief Compute the sharpness and optical flow scores for the input media paths @@ -112,8 +115,10 @@ class KeyframeSelector * sharpness score will be given to all the input frames * @return true if the scores have been successfully computed for all frames, false otherwise */ - bool computeScores(const std::size_t rescaledWidthSharpness, const std::size_t rescaledWidthFlow, - const std::size_t sharpnessWindowSize, const std::size_t flowCellSize, + bool computeScores(const std::size_t rescaledWidthSharpness, + const std::size_t rescaledWidthFlow, + const std::size_t sharpnessWindowSize, + const std::size_t flowCellSize, const bool skipSharpnessComputation); /** @@ -126,8 +131,10 @@ class KeyframeSelector * @param[in] storageDataType EXR storage data type for the output keyframes (ignored when the extension is not EXR) * @return true if all the selected keyframes were successfully written, false otherwise */ - bool writeSelection(const std::vector& brands, const std::vector& models, - const std::vector& mmFocals, const bool renameKeyframes, + bool writeSelection(const std::vector& brands, + const std::vector& models, + const std::vector& mmFocals, + const bool renameKeyframes, const std::string& outputExtension, const image::EStorageDataType storageDataType = image::EStorageDataType::Undefined); @@ -151,84 +158,57 @@ class KeyframeSelector * @brief Set the minimum frame step parameter for the processing algorithm * @param[in] frameStep minimum number of frames between two keyframes */ - void setMinFrameStep(unsigned int frameStep) - { - _minFrameStep = frameStep; - } + void setMinFrameStep(unsigned int frameStep) { _minFrameStep = frameStep; } /** * @brief Set the maximum frame step parameter for the processing algorithm * @param[in] frameStep maximum number of frames between two keyframes */ - void setMaxFrameStep(unsigned int frameStep) - { - _maxFrameStep = frameStep; - } + void setMaxFrameStep(unsigned int frameStep) { _maxFrameStep = frameStep; } /** * @brief Set the minimum output frame number parameter for the processing algorithm * @param[in] nbFrames minimum number of output frames */ - void setMinOutFrames(unsigned int nbFrames) - { - _minOutFrames = nbFrames; - } + void setMinOutFrames(unsigned int nbFrames) { _minOutFrames = nbFrames; } /** * @brief Set the maximum output frame number parameter for the processing algorithm * @param[in] nbFrames maximum number of output frames (if 0, no limit for the regular algorithm) */ - void setMaxOutFrames(unsigned int nbFrames) - { - _maxOutFrames = nbFrames; - } + void setMaxOutFrames(unsigned int nbFrames) { _maxOutFrames = nbFrames; } /** * @brief Set the minimum size of the blocks of frames for the multi-threading * @param[in] blockSize minimum number of frames in a block for a thread to be spawned */ - void setMinBlockSize(std::size_t blockSize) - { - _minBlockSize = blockSize; - } + void setMinBlockSize(std::size_t blockSize) { _minBlockSize = blockSize; } /** * @brief Get the minimum frame step parameter for the processing algorithm * @return minimum number of frames between two keyframes */ - unsigned int getMinFrameStep() const - { - return _minFrameStep; - } + unsigned int getMinFrameStep() const { return _minFrameStep; } /** * @brief Get the maximum output frame number parameter for the processing algorithm * @return maximum number of frames between two keyframes */ - unsigned int getMaxFrameStep() const - { - return _maxFrameStep; - } + unsigned int getMaxFrameStep() const { return _maxFrameStep; } /** * @brief Get the minimum output frame for the processing algorithm * @return minimum number of output frames */ - unsigned int getMinOutFrames() const - { - return _minOutFrames; - } + unsigned int getMinOutFrames() const { return _minOutFrames; } /** * @brief Get the maximum output frame number for the processing algorithm * @return maximum number of output frames (if 0, no limit for the regular algorithm) */ - unsigned int getMaxOutFrames() const - { - return _maxOutFrames; - } - -private: + unsigned int getMaxOutFrames() const { return _maxOutFrames; } + + private: /** * @brief Read an image from a feed provider into a grayscale OpenCV matrix, and rescale it if a size is provided * @param[in] feed The feed provider @@ -236,8 +216,7 @@ class KeyframeSelector * There will be no resizing if this parameter is set to 0 * @return An OpenCV Mat object containing the image */ - cv::Mat readImage(dataio::FeedProvider &feed, std::size_t width = 0); - + cv::Mat readImage(dataio::FeedProvider& feed, std::size_t width = 0); /** * @brief Compute the sharpness and optical flow scores for the input media paths for a given range of frames @@ -255,9 +234,13 @@ class KeyframeSelector * sharpness score will be given to all the input frames * @return true if the scores have been successfully computed for all frames, false otherwise */ - bool computeScoresProc(const std::size_t startFrame, const std::size_t endFrame, const std::size_t nbFrames, - const std::size_t rescaledWidthSharpness, const std::size_t rescaledWidthFlow, - const std::size_t sharpnessWindowSize, const std::size_t flowCellSize, + bool computeScoresProc(const std::size_t startFrame, + const std::size_t endFrame, + const std::size_t nbFrames, + const std::size_t rescaledWidthSharpness, + const std::size_t rescaledWidthFlow, + const std::size_t sharpnessWindowSize, + const std::size_t flowCellSize, const bool skipSharpnessComputation); /** @@ -279,8 +262,12 @@ class KeyframeSelector * @param[in] mask the mask associated to the frame the integral and integral images were calculated from * @return a const double value representating the local standard deviation of the Laplacian */ - const double computeSharpnessStd(const cv::Mat& sum, const cv::Mat& squaredSum, const int x, const int y, - const int windowSize, const cv::Mat& mask); + const double computeSharpnessStd(const cv::Mat& sum, + const cv::Mat& squaredSum, + const int x, + const int y, + const int windowSize, + const cv::Mat& mask); /** * @brief Estimate the optical flow score for an input grayscale frame based on its previous frame cell by cell @@ -291,8 +278,11 @@ class KeyframeSelector * @param[in] mask the mask associated to the current frame if it exists, an empty cv::Mat otherwise * @return a double value representing the median motion of all the image's cells */ - double estimateFlow(const cv::Ptr& ptrFlow, const cv::Mat& grayscaleImage, - const cv::Mat& previousGrayscaleImage, const std::size_t cellSize, const cv::Mat& mask); + double estimateFlow(const cv::Ptr& ptrFlow, + const cv::Mat& grayscaleImage, + const cv::Mat& previousGrayscaleImage, + const std::size_t cellSize, + const cv::Mat& mask); /** * @brief Write the output SfMData files with the selected and non-selected keyframes information @@ -303,8 +293,11 @@ class KeyframeSelector * @param[in] mmFocals focal in millimeters for each camera * @return true if the output SfMData files were written as expected, false otherwise */ - bool writeSfMData(const std::string& mediaPath, dataio::FeedProvider &feed, const std::vector& brands, - const std::vector& models, const std::vector& mmFocals); + bool writeSfMData(const std::string& mediaPath, + dataio::FeedProvider& feed, + const std::vector& brands, + const std::vector& models, + const std::vector& mmFocals); /** * @brief Copy the relevant information from the input SfMData file and fill the output SfMData files that will @@ -325,8 +318,10 @@ class KeyframeSelector * @param mmFocals focal in millimiters for each camera * @return true if the output SfMData files have successfully been filled, false otherwise */ - bool writeSfMDataFromSequences(const std::string& mediaPath, dataio::FeedProvider &feed, - const std::vector& brands, const std::vector& models, + bool writeSfMDataFromSequences(const std::string& mediaPath, + dataio::FeedProvider& feed, + const std::vector& brands, + const std::vector& models, const std::vector& mmFocals); /** @@ -338,8 +333,11 @@ class KeyframeSelector * @param imageHeight the height of the image corresponding to the view to create * @return a shared pointer to the created View */ - std::shared_ptr createView(const std::string& imagePath, IndexT intrinsicId, IndexT previousFrameId, - std::size_t imageWidth, std::size_t imageHeight); + std::shared_ptr createView(const std::string& imagePath, + IndexT intrinsicId, + IndexT previousFrameId, + std::size_t imageWidth, + std::size_t imageHeight); /** * @brief Create an Intrinsic object associated to a specific View @@ -350,8 +348,11 @@ class KeyframeSelector * @param mediaIndex the media index * @return a shared pointer to the created Intrinsic */ - std::shared_ptr createIntrinsic(const sfmData::View& view, const double focalLength, const double sensorWidth, - const double imageRatio, std::size_t mediaIndex); + std::shared_ptr createIntrinsic(const sfmData::View& view, + const double focalLength, + const double sensorWidth, + const double imageRatio, + std::size_t mediaIndex); /// Selected keyframes IDs std::vector _selectedKeyframes; @@ -416,5 +417,5 @@ class KeyframeSelector mutable std::mutex _mutex; }; -} // namespace keyframe -} // namespace aliceVision +} // namespace keyframe +} // namespace aliceVision diff --git a/src/aliceVision/lensCorrectionProfile/lcp.cpp b/src/aliceVision/lensCorrectionProfile/lcp.cpp index 236b7e6f73..1e2de6917b 100644 --- a/src/aliceVision/lensCorrectionProfile/lcp.cpp +++ b/src/aliceVision/lensCorrectionProfile/lcp.cpp @@ -97,7 +97,7 @@ void XMLCALL LCPinfo::XmlStartHandler(void* pLCPinfo, const char* el, const char LCPdata->_currText = value; LCPdata->setCommonSettings(key); LCPdata->setCameraSettings(key); - } + } } else { @@ -126,9 +126,9 @@ void XMLCALL LCPinfo::XmlStartHandler(void* pLCPinfo, const char* el, const char { LCPdata->_currReadingState = LCPReadingState::FillChromaticGreenModel; LCPdata->currLensParam.setChromaticParamsStatus(true); - if(attr[0]) + if (attr[0]) { - for(int i = 0; attr[i]; i += 2) + for (int i = 0; attr[i]; i += 2) { std::string key(attr[i]); std::string value(attr[i + 1]); @@ -141,9 +141,9 @@ void XMLCALL LCPinfo::XmlStartHandler(void* pLCPinfo, const char* el, const char else if (element == "stCamera:ChromaticRedGreenModel") { LCPdata->_currReadingState = LCPReadingState::FillChromaticRedGreenModel; - if(attr[0]) + if (attr[0]) { - for(int i = 0; attr[i]; i += 2) + for (int i = 0; attr[i]; i += 2) { std::string key(attr[i]); std::string value(attr[i + 1]); @@ -156,9 +156,9 @@ void XMLCALL LCPinfo::XmlStartHandler(void* pLCPinfo, const char* el, const char else if (element == "stCamera:ChromaticBlueGreenModel") { LCPdata->_currReadingState = LCPReadingState::FillChromaticBlueGreenModel; - if(attr[0]) + if (attr[0]) { - for(int i = 0; attr[i]; i += 2) + for (int i = 0; attr[i]; i += 2) { std::string key(attr[i]); std::string value(attr[i + 1]); @@ -207,11 +207,11 @@ void XMLCALL LCPinfo::XmlStartHandler(void* pLCPinfo, const char* el, const char LCPdata->_currText = value; LCPdata->setVignetteModel(key); } - } + } } else if ((LCPdata->_currReadingState == LCPReadingState::FillChromaticGreenModel) || - (LCPdata->_currReadingState == LCPReadingState::FillChromaticBlueGreenModel) || - (LCPdata->_currReadingState == LCPReadingState::FillChromaticRedGreenModel)) + (LCPdata->_currReadingState == LCPReadingState::FillChromaticBlueGreenModel) || + (LCPdata->_currReadingState == LCPReadingState::FillChromaticRedGreenModel)) { if (!attr[0]) { @@ -283,7 +283,7 @@ void XMLCALL LCPinfo::XmlStartHandler(void* pLCPinfo, const char* el, const char } } -} /* End of start handler */ +} /* End of start handler */ void XMLCALL LCPinfo::XmlEndHandler(void* pLCPinfo, const char* el) { @@ -326,7 +326,7 @@ void XMLCALL LCPinfo::XmlEndHandler(void* pLCPinfo, const char* el) else { LCPdata->setRectilinearModel(LCPdata->currLensParam.perspParams, element); - } + } LCPdata->_currText.clear(); } } @@ -379,7 +379,7 @@ void XMLCALL LCPinfo::XmlEndHandler(void* pLCPinfo, const char* el) } } -} /* End of end handler */ +} /* End of end handler */ void XMLCALL LCPinfo::XmlTextHandler(void* pLCPinfo, const char* s, int len) { @@ -401,7 +401,7 @@ void XMLCALL LCPinfo::XmlTextHandler(void* pLCPinfo, const char* s, int len) { LCPdata->addLensModel(localtextbuf.str()); } - else //if (LCPdata->_currReadingState == LCPReadingState::FillCommonAndCameraSettings) + else // if (LCPdata->_currReadingState == LCPReadingState::FillCommonAndCameraSettings) { LCPdata->_currText = localtextbuf.str(); } @@ -460,7 +460,7 @@ void XMLCALL LCPinfo::XmlStartHandlerCommonOnly(void* pLCPinfo, const char* el, } } -} /* End of start handler */ +} /* End of start handler */ void XMLCALL LCPinfo::XmlEndHandlerCommonOnly(void* pLCPinfo, const char* el) { @@ -476,12 +476,12 @@ void XMLCALL LCPinfo::XmlEndHandlerCommonOnly(void* pLCPinfo, const char* el) LCPdata->_currText.clear(); } } -} /* End of end handler */ +} /* End of end handler */ // LCPinfo class implementation -LCPinfo::LCPinfo(const std::string& filename, bool fullParsing) : - _isSeqOpened(false), +LCPinfo::LCPinfo(const std::string& filename, bool fullParsing) + : _isSeqOpened(false), _isCommonOK(false), _isCamDataOK(false), _inAlternateLensIDs(false), @@ -507,7 +507,7 @@ LCPinfo::LCPinfo(const std::string& filename, bool fullParsing) : void LCPinfo::load(const std::string& filename, bool fullParsing) { - if(fullParsing) + if (fullParsing) { ALICEVISION_LOG_INFO("Load LCP file: \"" << filename << "\""); } @@ -530,22 +530,25 @@ void LCPinfo::load(const std::string& filename, bool fullParsing) { XML_SetElementHandler(parser, XmlStartHandlerCommonOnly, XmlEndHandlerCommonOnly); } - + XML_SetCharacterDataHandler(parser, XmlTextHandler); XML_SetUserData(parser, static_cast(this)); FILE* const pFile = fopen(filename.c_str(), "rb"); - if (pFile) { + if (pFile) + { constexpr int BufferSize = 8192; char buf[BufferSize]; bool done; - do { + do + { int bytesRead = fread(buf, 1, BufferSize, pFile); done = feof(pFile); - if (XML_Parse(parser, buf, bytesRead, done) == XML_STATUS_ERROR) { + if (XML_Parse(parser, buf, bytesRead, done) == XML_STATUS_ERROR) + { XML_ParserFree(parser); throw std::runtime_error("Invalid XML in LCP file"); } @@ -566,8 +569,8 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, std::vector v_isChromaticValid; // Search the best focal lengths with respect to the target one in settings - // At the end of the loop, iLow is the index in v_lensParam of the lens parameter set with the closest focal length, lower or equal to the one in settings. - // iHigh is the index in v_lensParam of the lens parameter set with the closest focal length, greater or equal to the one in settings. + // At the end of the loop, iLow is the index in v_lensParam of the lens parameter set with the closest focal length, lower or equal to the one in + // settings. iHigh is the index in v_lensParam of the lens parameter set with the closest focal length, greater or equal to the one in settings. // In case of missing aperture value target and search mode is vignetting, if several parameter sets have the same ideal focal length, // iLow and iHigh point out the ones with the lowest aperture value. // If search mode is not vignetting and no focus distance target is given the highest focus value is targeted. @@ -579,8 +582,7 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, v_isDistortionValid.push_back(currParam.isFisheye() ? !currParam.fisheyeParams.isEmpty : !currParam.perspParams.isEmpty); v_isVignetteValid.push_back(currParam.hasVignetteParams() && !currParam.vignParams.isEmpty); v_isChromaticValid.push_back(currParam.hasChromaticParams() && !currParam.ChromaticGreenParams.isEmpty && - !currParam.ChromaticBlueGreenParams.isEmpty && - !currParam.ChromaticRedGreenParams.isEmpty); + !currParam.ChromaticBlueGreenParams.isEmpty && !currParam.ChromaticRedGreenParams.isEmpty); bool isCurrentValid = (mode == LCPCorrectionMode::DISTORTION && v_isDistortionValid.back()) || (mode == LCPCorrectionMode::VIGNETTE && v_isVignetteValid.back()) || @@ -588,38 +590,37 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, if (isCurrentValid) { - if ( - f <= settings.FocalLength // v_lensParams[iLow] focal length must always stay lower than or equal to the targeted focal length - && ( - iLow == -1 // iLow not yet initialized - || f > v_lensParams[iLow].camData.FocalLength // Better candidate than the stored one (higher than but still lower than or equal to the target value) - || (f == v_lensParams[iLow].camData.FocalLength && // Same focal lenth value as the stored one - (settings.FocusDistance == 0 && // No focus info - mode != LCPCorrectionMode::VIGNETTE && // search for other model than vignetting (geometry, chromatic aberation) - v_lensParams[iLow].camData.FocusDistance > currParam.camData.FocusDistance) // Higher focus distance value than the stored one - || (settings.ApertureValue == 0 && // No aperture info - mode == LCPCorrectionMode::VIGNETTE && // search for vignetting model - v_lensParams[iLow].camData.ApertureValue < currParam.camData.ApertureValue)) // Lower aperture value than the stored one - ) - ) + if (f <= settings.FocalLength // v_lensParams[iLow] focal length must always stay lower than or equal to the targeted focal length + && + (iLow == -1 // iLow not yet initialized + || + f > v_lensParams[iLow] + .camData.FocalLength // Better candidate than the stored one (higher than but still lower than or equal to the target value) + || + (f == v_lensParams[iLow].camData.FocalLength && // Same focal lenth value as the stored one + (settings.FocusDistance == 0 && // No focus info + mode != LCPCorrectionMode::VIGNETTE && // search for other model than vignetting (geometry, chromatic aberation) + v_lensParams[iLow].camData.FocusDistance > currParam.camData.FocusDistance) // Higher focus distance value than the stored one + || (settings.ApertureValue == 0 && // No aperture info + mode == LCPCorrectionMode::VIGNETTE && // search for vignetting model + v_lensParams[iLow].camData.ApertureValue < currParam.camData.ApertureValue)) // Lower aperture value than the stored one + )) { iLow = i; } - if ( - f >= settings.FocalLength // v_lensParams[iHigh] focal length must always stay greater than or equal to the targeted focal length - && ( - iHigh == -1 // iHigh not yet initialized - || f < v_lensParams[iHigh].camData.FocalLength // Better candidate than the stored one (lower than but still higher than or equal to the target value) - || (f == v_lensParams[iHigh].camData.FocalLength && // Same focal lenth value as the stored one - (settings.FocusDistance == 0 && // No focus info - mode != LCPCorrectionMode::VIGNETTE && // search for other model than vignetting (geometry, chromatic aberation) - v_lensParams[iHigh].camData.FocusDistance > currParam.camData.FocusDistance) - || (settings.ApertureValue == 0 && // No aperture info - mode == LCPCorrectionMode::VIGNETTE && // search for vignetting model - v_lensParams[iHigh].camData.ApertureValue < currParam.camData.ApertureValue)) // Lower aperture value than the stored one - ) - ) + if (f >= settings.FocalLength // v_lensParams[iHigh] focal length must always stay greater than or equal to the targeted focal length + && (iHigh == -1 // iHigh not yet initialized + || f < v_lensParams[iHigh].camData.FocalLength // Better candidate than the stored one (lower than but still higher than or equal + // to the target value) + || (f == v_lensParams[iHigh].camData.FocalLength && // Same focal lenth value as the stored one + (settings.FocusDistance == 0 && // No focus info + mode != LCPCorrectionMode::VIGNETTE && // search for other model than vignetting (geometry, chromatic aberation) + v_lensParams[iHigh].camData.FocusDistance > currParam.camData.FocusDistance) || + (settings.ApertureValue == 0 && // No aperture info + mode == LCPCorrectionMode::VIGNETTE && // search for vignetting model + v_lensParams[iHigh].camData.ApertureValue < currParam.camData.ApertureValue)) // Lower aperture value than the stored one + )) { iHigh = i; } @@ -635,8 +636,8 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, iHigh = iLow; } - bool settingsOK = (mode == LCPCorrectionMode::VIGNETTE && settings.ApertureValue > 0) || - (mode != LCPCorrectionMode::VIGNETTE && settings.FocusDistance > 0); + bool settingsOK = + (mode == LCPCorrectionMode::VIGNETTE && settings.ApertureValue > 0) || (mode != LCPCorrectionMode::VIGNETTE && settings.FocusDistance > 0); // Amongst all possible low and high candidates, the closest from the targeted focus or aperture value are selected if (iLow != -1 && iHigh != -1 && iLow != iHigh && settingsOK) @@ -673,7 +674,8 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, update = (candidateAperture >= settings.ApertureValue && candidateAperture < currAperture && currAperture > settings.ApertureValue) || (candidateAperture <= settings.ApertureValue && - (currAperture > settings.ApertureValue || std::fabs(settings.ApertureValue - candidateAperture) < std::fabs(settings.ApertureValue - currAperture))); + (currAperture > settings.ApertureValue || + std::fabs(settings.ApertureValue - candidateAperture) < std::fabs(settings.ApertureValue - currAperture))); } else { @@ -682,7 +684,8 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, update = (candidateFocus >= settings.FocusDistance && candidateFocus < currFocus && currFocus > settings.FocusDistance) || (candidateFocus <= settings.FocusDistance && - (currFocus > settings.FocusDistance || std::fabs(settings.FocusDistance - candidateFocus) < std::fabs(settings.FocusDistance - currFocus))); + (currFocus > settings.FocusDistance || + std::fabs(settings.FocusDistance - candidateFocus) < std::fabs(settings.FocusDistance - currFocus))); } if (update) @@ -701,7 +704,8 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, update = (candidateAperture <= settings.ApertureValue && candidateAperture > currAperture && currAperture < settings.ApertureValue) || (candidateAperture >= settings.ApertureValue && - (currAperture < settings.ApertureValue || std::fabs(settings.ApertureValue - candidateAperture) < std::fabs(settings.ApertureValue - currAperture))); + (currAperture < settings.ApertureValue || + std::fabs(settings.ApertureValue - candidateAperture) < std::fabs(settings.ApertureValue - currAperture))); } else { @@ -710,7 +714,8 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, update = (candidateFocus <= settings.FocusDistance && candidateFocus > currFocus && currFocus < settings.FocusDistance) || (candidateFocus >= settings.FocusDistance && - (currFocus < settings.FocusDistance || std::fabs(settings.FocusDistance - candidateFocus) < std::fabs(settings.FocusDistance - currFocus))); + (currFocus < settings.FocusDistance || + std::fabs(settings.FocusDistance - candidateFocus) < std::fabs(settings.FocusDistance - currFocus))); } if (update) @@ -795,7 +800,7 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, if (v_lensParams[iHigh].camData.FocalLength > v_lensParams[iLow].camData.FocalLength) { weightLow = (std::log(v_lensParams[iHigh].camData.FocalLength) - std::log(settings.FocalLength)) / - (std::log(v_lensParams[iHigh].camData.FocalLength) - std::log(v_lensParams[iLow].camData.FocalLength)); + (std::log(v_lensParams[iHigh].camData.FocalLength) - std::log(v_lensParams[iLow].camData.FocalLength)); return true; } else if (v_lensParams[iHigh].camData.FocalLength == v_lensParams[iLow].camData.FocalLength) @@ -811,7 +816,7 @@ bool LCPinfo::search(settingsInfo& settings, LCPCorrectionMode mode, int& iLow, if (v_lensParams[iHigh].camData.FocalLength > v_lensParams[iLow].camData.FocalLength) { weightLow = (std::log(v_lensParams[iHigh].camData.FocalLength) - std::log(settings.FocalLength)) / - (std::log(v_lensParams[iHigh].camData.FocalLength) - std::log(v_lensParams[iLow].camData.FocalLength)); + (std::log(v_lensParams[iHigh].camData.FocalLength) - std::log(v_lensParams[iLow].camData.FocalLength)); return true; } else if (v_lensParams[iHigh].camData.FocalLength == v_lensParams[iLow].camData.FocalLength) @@ -831,134 +836,140 @@ void LCPinfo::combine(size_t iLow, size_t iHigh, float weightLow, LCPCorrectionM const LensParam& p1 = v_lensParams[iLow]; const LensParam& p2 = v_lensParams[iHigh]; - switch (mode) { - case LCPCorrectionMode::VIGNETTE: { - if (p1.hasVignetteParams() && !p1.vignParams.isEmpty && p2.hasVignetteParams() && !p2.vignParams.isEmpty) - { - pOut.setVignetteParamsStatus(true); - pOut.vignParams.FocalLengthX = interpolate(weightLow, p1.vignParams.FocalLengthX, p2.vignParams.FocalLengthX); - pOut.vignParams.FocalLengthY = interpolate(weightLow, p1.vignParams.FocalLengthY, p2.vignParams.FocalLengthY); - pOut.vignParams.ImageXCenter = interpolate(weightLow, p1.vignParams.ImageXCenter, p2.vignParams.ImageXCenter); - pOut.vignParams.ImageYCenter = interpolate(weightLow, p1.vignParams.ImageYCenter, p2.vignParams.ImageYCenter); - pOut.vignParams.VignetteModelParam1 = interpolate(weightLow, p1.vignParams.VignetteModelParam1, p2.vignParams.VignetteModelParam1); - pOut.vignParams.VignetteModelParam2 = interpolate(weightLow, p1.vignParams.VignetteModelParam2, p2.vignParams.VignetteModelParam2); - pOut.vignParams.VignetteModelParam3 = interpolate(weightLow, p1.vignParams.VignetteModelParam3, p2.vignParams.VignetteModelParam3); - pOut.vignParams.isEmpty = false; - } - else + switch (mode) + { + case LCPCorrectionMode::VIGNETTE: { - pOut.setVignetteParamsStatus(false); - pOut.vignParams.isEmpty = true; + if (p1.hasVignetteParams() && !p1.vignParams.isEmpty && p2.hasVignetteParams() && !p2.vignParams.isEmpty) + { + pOut.setVignetteParamsStatus(true); + pOut.vignParams.FocalLengthX = interpolate(weightLow, p1.vignParams.FocalLengthX, p2.vignParams.FocalLengthX); + pOut.vignParams.FocalLengthY = interpolate(weightLow, p1.vignParams.FocalLengthY, p2.vignParams.FocalLengthY); + pOut.vignParams.ImageXCenter = interpolate(weightLow, p1.vignParams.ImageXCenter, p2.vignParams.ImageXCenter); + pOut.vignParams.ImageYCenter = interpolate(weightLow, p1.vignParams.ImageYCenter, p2.vignParams.ImageYCenter); + pOut.vignParams.VignetteModelParam1 = + interpolate(weightLow, p1.vignParams.VignetteModelParam1, p2.vignParams.VignetteModelParam1); + pOut.vignParams.VignetteModelParam2 = + interpolate(weightLow, p1.vignParams.VignetteModelParam2, p2.vignParams.VignetteModelParam2); + pOut.vignParams.VignetteModelParam3 = + interpolate(weightLow, p1.vignParams.VignetteModelParam3, p2.vignParams.VignetteModelParam3); + pOut.vignParams.isEmpty = false; + } + else + { + pOut.setVignetteParamsStatus(false); + pOut.vignParams.isEmpty = true; + } + break; } - break; - } - case LCPCorrectionMode::DISTORTION: { - pOut.setFisheyeStatus(p1.isFisheye() && p2.isFisheye() && !p1.fisheyeParams.isEmpty && !p2.fisheyeParams.isEmpty); - if (pOut.isFisheye()) - { - pOut.fisheyeParams.FocalLengthX = interpolate(weightLow, p1.fisheyeParams.FocalLengthX, p2.fisheyeParams.FocalLengthX); - pOut.fisheyeParams.FocalLengthY = interpolate(weightLow, p1.fisheyeParams.FocalLengthY, p2.fisheyeParams.FocalLengthY); - pOut.fisheyeParams.ImageXCenter = interpolate(weightLow, p1.fisheyeParams.ImageXCenter, p2.fisheyeParams.ImageXCenter); - pOut.fisheyeParams.ImageYCenter = interpolate(weightLow, p1.fisheyeParams.ImageYCenter, p2.fisheyeParams.ImageYCenter); - pOut.fisheyeParams.RadialDistortParam1 = interpolate(weightLow, p1.fisheyeParams.RadialDistortParam1, p2.fisheyeParams.RadialDistortParam1); - pOut.fisheyeParams.RadialDistortParam2 = interpolate(weightLow, p1.fisheyeParams.RadialDistortParam2, p2.fisheyeParams.RadialDistortParam2); - pOut.fisheyeParams.isEmpty = false; - } - else if (!p1.perspParams.isEmpty && !p2.perspParams.isEmpty) - { - pOut.perspParams.FocalLengthX = interpolate(weightLow, p1.perspParams.FocalLengthX, p2.perspParams.FocalLengthX); - pOut.perspParams.FocalLengthY = interpolate(weightLow, p1.perspParams.FocalLengthY, p2.perspParams.FocalLengthY); - pOut.perspParams.ImageXCenter = interpolate(weightLow, p1.perspParams.ImageXCenter, p2.perspParams.ImageXCenter); - pOut.perspParams.ImageYCenter = interpolate(weightLow, p1.perspParams.ImageYCenter, p2.perspParams.ImageYCenter); - pOut.perspParams.RadialDistortParam1 = interpolate(weightLow, p1.perspParams.RadialDistortParam1, p2.perspParams.RadialDistortParam1); - pOut.perspParams.RadialDistortParam2 = interpolate(weightLow, p1.perspParams.RadialDistortParam2, p2.perspParams.RadialDistortParam2); - pOut.perspParams.RadialDistortParam3 = interpolate(weightLow, p1.perspParams.RadialDistortParam3, p2.perspParams.RadialDistortParam3); - pOut.perspParams.TangentialDistortParam1 = interpolate( - weightLow, p1.perspParams.TangentialDistortParam1, p2.perspParams.TangentialDistortParam1); - pOut.perspParams.TangentialDistortParam2 = interpolate( - weightLow, p1.perspParams.TangentialDistortParam2, p2.perspParams.TangentialDistortParam2); - pOut.perspParams.isEmpty = false; - } - else + case LCPCorrectionMode::DISTORTION: { - pOut.fisheyeParams.isEmpty = true; - pOut.perspParams.isEmpty = true; + pOut.setFisheyeStatus(p1.isFisheye() && p2.isFisheye() && !p1.fisheyeParams.isEmpty && !p2.fisheyeParams.isEmpty); + if (pOut.isFisheye()) + { + pOut.fisheyeParams.FocalLengthX = interpolate(weightLow, p1.fisheyeParams.FocalLengthX, p2.fisheyeParams.FocalLengthX); + pOut.fisheyeParams.FocalLengthY = interpolate(weightLow, p1.fisheyeParams.FocalLengthY, p2.fisheyeParams.FocalLengthY); + pOut.fisheyeParams.ImageXCenter = interpolate(weightLow, p1.fisheyeParams.ImageXCenter, p2.fisheyeParams.ImageXCenter); + pOut.fisheyeParams.ImageYCenter = interpolate(weightLow, p1.fisheyeParams.ImageYCenter, p2.fisheyeParams.ImageYCenter); + pOut.fisheyeParams.RadialDistortParam1 = + interpolate(weightLow, p1.fisheyeParams.RadialDistortParam1, p2.fisheyeParams.RadialDistortParam1); + pOut.fisheyeParams.RadialDistortParam2 = + interpolate(weightLow, p1.fisheyeParams.RadialDistortParam2, p2.fisheyeParams.RadialDistortParam2); + pOut.fisheyeParams.isEmpty = false; + } + else if (!p1.perspParams.isEmpty && !p2.perspParams.isEmpty) + { + pOut.perspParams.FocalLengthX = interpolate(weightLow, p1.perspParams.FocalLengthX, p2.perspParams.FocalLengthX); + pOut.perspParams.FocalLengthY = interpolate(weightLow, p1.perspParams.FocalLengthY, p2.perspParams.FocalLengthY); + pOut.perspParams.ImageXCenter = interpolate(weightLow, p1.perspParams.ImageXCenter, p2.perspParams.ImageXCenter); + pOut.perspParams.ImageYCenter = interpolate(weightLow, p1.perspParams.ImageYCenter, p2.perspParams.ImageYCenter); + pOut.perspParams.RadialDistortParam1 = + interpolate(weightLow, p1.perspParams.RadialDistortParam1, p2.perspParams.RadialDistortParam1); + pOut.perspParams.RadialDistortParam2 = + interpolate(weightLow, p1.perspParams.RadialDistortParam2, p2.perspParams.RadialDistortParam2); + pOut.perspParams.RadialDistortParam3 = + interpolate(weightLow, p1.perspParams.RadialDistortParam3, p2.perspParams.RadialDistortParam3); + pOut.perspParams.TangentialDistortParam1 = + interpolate(weightLow, p1.perspParams.TangentialDistortParam1, p2.perspParams.TangentialDistortParam1); + pOut.perspParams.TangentialDistortParam2 = + interpolate(weightLow, p1.perspParams.TangentialDistortParam2, p2.perspParams.TangentialDistortParam2); + pOut.perspParams.isEmpty = false; + } + else + { + pOut.fisheyeParams.isEmpty = true; + pOut.perspParams.isEmpty = true; + } + break; } - break; - } - case LCPCorrectionMode::CA: { - if (p1.hasChromaticParams() && !p1.ChromaticGreenParams.isEmpty && p2.hasChromaticParams() && - !p2.ChromaticGreenParams.isEmpty) - { - pOut.setChromaticParamsStatus(true); - pOut.ChromaticGreenParams.FocalLengthX = interpolate(weightLow, p1.ChromaticGreenParams.FocalLengthX, - p2.ChromaticGreenParams.FocalLengthX); - pOut.ChromaticGreenParams.FocalLengthY = interpolate(weightLow, p1.ChromaticGreenParams.FocalLengthY, - p2.ChromaticGreenParams.FocalLengthY); - pOut.ChromaticGreenParams.ImageXCenter = interpolate(weightLow, p1.ChromaticGreenParams.ImageXCenter, - p2.ChromaticGreenParams.ImageXCenter); - pOut.ChromaticGreenParams.ImageYCenter = interpolate(weightLow, p1.ChromaticGreenParams.ImageYCenter, - p2.ChromaticGreenParams.ImageYCenter); - pOut.ChromaticGreenParams.ScaleFactor = interpolate(weightLow, p1.ChromaticGreenParams.ScaleFactor, p2.ChromaticGreenParams.ScaleFactor); - pOut.ChromaticGreenParams.RadialDistortParam1 = interpolate( - weightLow, p1.ChromaticGreenParams.RadialDistortParam1, p2.ChromaticGreenParams.RadialDistortParam1); - pOut.ChromaticGreenParams.RadialDistortParam2 = interpolate( - weightLow, p1.ChromaticGreenParams.RadialDistortParam2, p2.ChromaticGreenParams.RadialDistortParam2); - pOut.ChromaticGreenParams.RadialDistortParam3 = interpolate( - weightLow, p1.ChromaticGreenParams.RadialDistortParam3, p2.ChromaticGreenParams.RadialDistortParam3); - pOut.ChromaticGreenParams.isEmpty = false; - pOut.ChromaticBlueGreenParams.FocalLengthX = interpolate( - weightLow, p1.ChromaticBlueGreenParams.FocalLengthX, p2.ChromaticBlueGreenParams.FocalLengthX); - pOut.ChromaticBlueGreenParams.FocalLengthY = interpolate( - weightLow, p1.ChromaticBlueGreenParams.FocalLengthY, p2.ChromaticBlueGreenParams.FocalLengthY); - pOut.ChromaticBlueGreenParams.ImageXCenter = interpolate( - weightLow, p1.ChromaticBlueGreenParams.ImageXCenter, p2.ChromaticBlueGreenParams.ImageXCenter); - pOut.ChromaticBlueGreenParams.ImageYCenter = interpolate( - weightLow, p1.ChromaticBlueGreenParams.ImageYCenter, p2.ChromaticBlueGreenParams.ImageYCenter); - pOut.ChromaticBlueGreenParams.ScaleFactor = interpolate( - weightLow, p1.ChromaticBlueGreenParams.ScaleFactor, p2.ChromaticBlueGreenParams.ScaleFactor); - pOut.ChromaticBlueGreenParams.RadialDistortParam1 = - interpolate(weightLow, p1.ChromaticBlueGreenParams.RadialDistortParam1, - p2.ChromaticBlueGreenParams.RadialDistortParam1); - pOut.ChromaticBlueGreenParams.RadialDistortParam2 = - interpolate(weightLow, p1.ChromaticBlueGreenParams.RadialDistortParam2, - p2.ChromaticBlueGreenParams.RadialDistortParam2); - pOut.ChromaticBlueGreenParams.RadialDistortParam3 = - interpolate(weightLow, p1.ChromaticBlueGreenParams.RadialDistortParam3, - p2.ChromaticBlueGreenParams.RadialDistortParam3); - pOut.ChromaticBlueGreenParams.isEmpty = false; - pOut.ChromaticRedGreenParams.FocalLengthX = interpolate( - weightLow, p1.ChromaticRedGreenParams.FocalLengthX, p2.ChromaticRedGreenParams.FocalLengthX); - pOut.ChromaticRedGreenParams.FocalLengthY = interpolate( - weightLow, p1.ChromaticRedGreenParams.FocalLengthY, p2.ChromaticRedGreenParams.FocalLengthY); - pOut.ChromaticRedGreenParams.ImageXCenter = interpolate( - weightLow, p1.ChromaticRedGreenParams.ImageXCenter, p2.ChromaticRedGreenParams.ImageXCenter); - pOut.ChromaticRedGreenParams.ImageYCenter = interpolate( - weightLow, p1.ChromaticRedGreenParams.ImageYCenter, p2.ChromaticRedGreenParams.ImageYCenter); - pOut.ChromaticRedGreenParams.ScaleFactor = interpolate( - weightLow, p1.ChromaticRedGreenParams.ScaleFactor, p2.ChromaticRedGreenParams.ScaleFactor); - pOut.ChromaticRedGreenParams.RadialDistortParam1 = - interpolate(weightLow, p1.ChromaticRedGreenParams.RadialDistortParam1, - p2.ChromaticRedGreenParams.RadialDistortParam1); - pOut.ChromaticRedGreenParams.RadialDistortParam2 = - interpolate(weightLow, p1.ChromaticRedGreenParams.RadialDistortParam2, - p2.ChromaticRedGreenParams.RadialDistortParam2); - pOut.ChromaticRedGreenParams.RadialDistortParam3 = - interpolate(weightLow, p1.ChromaticRedGreenParams.RadialDistortParam3, - p2.ChromaticRedGreenParams.RadialDistortParam3); - pOut.ChromaticRedGreenParams.isEmpty = false; - } - else + case LCPCorrectionMode::CA: { - pOut.setChromaticParamsStatus(false); - pOut.ChromaticGreenParams.isEmpty = true; - pOut.ChromaticBlueGreenParams.isEmpty = true; - pOut.ChromaticRedGreenParams.isEmpty = true; + if (p1.hasChromaticParams() && !p1.ChromaticGreenParams.isEmpty && p2.hasChromaticParams() && !p2.ChromaticGreenParams.isEmpty) + { + pOut.setChromaticParamsStatus(true); + pOut.ChromaticGreenParams.FocalLengthX = + interpolate(weightLow, p1.ChromaticGreenParams.FocalLengthX, p2.ChromaticGreenParams.FocalLengthX); + pOut.ChromaticGreenParams.FocalLengthY = + interpolate(weightLow, p1.ChromaticGreenParams.FocalLengthY, p2.ChromaticGreenParams.FocalLengthY); + pOut.ChromaticGreenParams.ImageXCenter = + interpolate(weightLow, p1.ChromaticGreenParams.ImageXCenter, p2.ChromaticGreenParams.ImageXCenter); + pOut.ChromaticGreenParams.ImageYCenter = + interpolate(weightLow, p1.ChromaticGreenParams.ImageYCenter, p2.ChromaticGreenParams.ImageYCenter); + pOut.ChromaticGreenParams.ScaleFactor = + interpolate(weightLow, p1.ChromaticGreenParams.ScaleFactor, p2.ChromaticGreenParams.ScaleFactor); + pOut.ChromaticGreenParams.RadialDistortParam1 = + interpolate(weightLow, p1.ChromaticGreenParams.RadialDistortParam1, p2.ChromaticGreenParams.RadialDistortParam1); + pOut.ChromaticGreenParams.RadialDistortParam2 = + interpolate(weightLow, p1.ChromaticGreenParams.RadialDistortParam2, p2.ChromaticGreenParams.RadialDistortParam2); + pOut.ChromaticGreenParams.RadialDistortParam3 = + interpolate(weightLow, p1.ChromaticGreenParams.RadialDistortParam3, p2.ChromaticGreenParams.RadialDistortParam3); + pOut.ChromaticGreenParams.isEmpty = false; + pOut.ChromaticBlueGreenParams.FocalLengthX = + interpolate(weightLow, p1.ChromaticBlueGreenParams.FocalLengthX, p2.ChromaticBlueGreenParams.FocalLengthX); + pOut.ChromaticBlueGreenParams.FocalLengthY = + interpolate(weightLow, p1.ChromaticBlueGreenParams.FocalLengthY, p2.ChromaticBlueGreenParams.FocalLengthY); + pOut.ChromaticBlueGreenParams.ImageXCenter = + interpolate(weightLow, p1.ChromaticBlueGreenParams.ImageXCenter, p2.ChromaticBlueGreenParams.ImageXCenter); + pOut.ChromaticBlueGreenParams.ImageYCenter = + interpolate(weightLow, p1.ChromaticBlueGreenParams.ImageYCenter, p2.ChromaticBlueGreenParams.ImageYCenter); + pOut.ChromaticBlueGreenParams.ScaleFactor = + interpolate(weightLow, p1.ChromaticBlueGreenParams.ScaleFactor, p2.ChromaticBlueGreenParams.ScaleFactor); + pOut.ChromaticBlueGreenParams.RadialDistortParam1 = + interpolate(weightLow, p1.ChromaticBlueGreenParams.RadialDistortParam1, p2.ChromaticBlueGreenParams.RadialDistortParam1); + pOut.ChromaticBlueGreenParams.RadialDistortParam2 = + interpolate(weightLow, p1.ChromaticBlueGreenParams.RadialDistortParam2, p2.ChromaticBlueGreenParams.RadialDistortParam2); + pOut.ChromaticBlueGreenParams.RadialDistortParam3 = + interpolate(weightLow, p1.ChromaticBlueGreenParams.RadialDistortParam3, p2.ChromaticBlueGreenParams.RadialDistortParam3); + pOut.ChromaticBlueGreenParams.isEmpty = false; + pOut.ChromaticRedGreenParams.FocalLengthX = + interpolate(weightLow, p1.ChromaticRedGreenParams.FocalLengthX, p2.ChromaticRedGreenParams.FocalLengthX); + pOut.ChromaticRedGreenParams.FocalLengthY = + interpolate(weightLow, p1.ChromaticRedGreenParams.FocalLengthY, p2.ChromaticRedGreenParams.FocalLengthY); + pOut.ChromaticRedGreenParams.ImageXCenter = + interpolate(weightLow, p1.ChromaticRedGreenParams.ImageXCenter, p2.ChromaticRedGreenParams.ImageXCenter); + pOut.ChromaticRedGreenParams.ImageYCenter = + interpolate(weightLow, p1.ChromaticRedGreenParams.ImageYCenter, p2.ChromaticRedGreenParams.ImageYCenter); + pOut.ChromaticRedGreenParams.ScaleFactor = + interpolate(weightLow, p1.ChromaticRedGreenParams.ScaleFactor, p2.ChromaticRedGreenParams.ScaleFactor); + pOut.ChromaticRedGreenParams.RadialDistortParam1 = + interpolate(weightLow, p1.ChromaticRedGreenParams.RadialDistortParam1, p2.ChromaticRedGreenParams.RadialDistortParam1); + pOut.ChromaticRedGreenParams.RadialDistortParam2 = + interpolate(weightLow, p1.ChromaticRedGreenParams.RadialDistortParam2, p2.ChromaticRedGreenParams.RadialDistortParam2); + pOut.ChromaticRedGreenParams.RadialDistortParam3 = + interpolate(weightLow, p1.ChromaticRedGreenParams.RadialDistortParam3, p2.ChromaticRedGreenParams.RadialDistortParam3); + pOut.ChromaticRedGreenParams.isEmpty = false; + } + else + { + pOut.setChromaticParamsStatus(false); + pOut.ChromaticGreenParams.isEmpty = true; + pOut.ChromaticBlueGreenParams.isEmpty = true; + pOut.ChromaticRedGreenParams.isEmpty = true; + } } } - } } void LCPinfo::getDistortionParams(const float& focalLength, const float& focusDistance, LensParam& lparam) @@ -1000,7 +1011,7 @@ void LCPinfo::getChromaticParams(const float& focalLength, const float& focusDis int iLow, iHigh; float weightLow; - if(search(userSettings, LCPCorrectionMode::CA, iLow, iHigh, weightLow)) + if (search(userSettings, LCPCorrectionMode::CA, iLow, iHigh, weightLow)) { combine(iLow, iHigh, weightLow, LCPCorrectionMode::CA, lparam); } @@ -1126,7 +1137,7 @@ void LCPdatabase::loadDirectory(const boost::filesystem::path& p) for (auto&& x : boost::filesystem::directory_iterator(p)) sortedPaths.push_back(x.path()); std::sort(sortedPaths.begin(), sortedPaths.end()); - for(auto&& x : sortedPaths) + for (auto&& x : sortedPaths) loadDirectory(x); } else if (boost::filesystem::is_regular_file(p) && (boost::filesystem::extension(p) == ".lcp")) @@ -1166,10 +1177,10 @@ LCPinfo* LCPdatabase::retrieveLCP(const std::string& lcpFilepath) // Check if the LCP is already in the cache, or add it auto lcpCacheIt = _lcpCache.find(lcpFilepath); - if(lcpCacheIt == _lcpCache.end()) + if (lcpCacheIt == _lcpCache.end()) { - // If not already in the cache, add it. - #pragma omp critical +// If not already in the cache, add it. +#pragma omp critical _lcpCache[lcpFilepath] = LCPinfo(lcpFilepath, true); lcpCacheIt = _lcpCache.find(lcpFilepath); } @@ -1178,12 +1189,11 @@ LCPinfo* LCPdatabase::retrieveLCP(const std::string& lcpFilepath) return &lcpCacheIt->second; } -LCPinfo* LCPdatabase::findLCP( - const std::string& cameraMake, - const std::string& cameraModel, - const std::string& lensModel, - const int lensID, - int rawMode) +LCPinfo* LCPdatabase::findLCP(const std::string& cameraMake, + const std::string& cameraModel, + const std::string& lensModel, + const int lensID, + int rawMode) { const std::string reducedCameraMake = reduceString(cameraMake); const std::string reducedCameraModel = _omitCameraModel ? reducedCameraMake : reduceString(cameraModel); @@ -1191,19 +1201,19 @@ LCPinfo* LCPdatabase::findLCP( const std::string lensUidStr = reducedCameraMake + reducedCameraModel + reducedLensModel; const auto cachetoLcpPathIt = _lcpCameraMappingCache.find(lensUidStr); - if(cachetoLcpPathIt != _lcpCameraMappingCache.end()) + if (cachetoLcpPathIt != _lcpCameraMappingCache.end()) { return retrieveLCP(cachetoLcpPathIt->second); } - for(const LcpPath& lcpPath : _lcpFilepaths) + for (const LcpPath& lcpPath : _lcpFilepaths) { const bool filepathContainsMake = (lcpPath.reducedPath.find(reducedCameraMake) != std::string::npos); - if(!filepathContainsMake) + if (!filepathContainsMake) continue; auto headerIt = _lcpHeaderCache.find(lcpPath.reducedPath); - if(headerIt == _lcpHeaderCache.end()) + if (headerIt == _lcpHeaderCache.end()) { // If not already in the cache of LCP headers, add it. _lcpHeaderCache[lcpPath.path.string()] = LCPinfo(lcpPath.path.string(), false); @@ -1212,8 +1222,7 @@ LCPinfo* LCPdatabase::findLCP( const LCPinfo& lcpHeader = headerIt->second; - const std::string reducedCameraModelLCP = - reduceString(_omitCameraModel ? lcpHeader.getCameraMaker() : lcpHeader.getCameraModel()); + const std::string reducedCameraModelLCP = reduceString(_omitCameraModel ? lcpHeader.getCameraMaker() : lcpHeader.getCameraModel()); const std::string reducedCameraPrettyNameLCP = reduceString(lcpHeader.getCameraPrettyName()); const std::string reducedLensPrettyNameLCP = reduceString(lcpHeader.getLensPrettyName()); @@ -1224,16 +1233,14 @@ LCPinfo* LCPdatabase::findLCP( std::vector lensIDsLCP; lcpHeader.getLensIDs(lensIDsLCP); - const bool cameraOK = - ((reducedCameraModelLCP == reducedCameraModel) || (reducedCameraPrettyNameLCP == reducedCameraModel)); + const bool cameraOK = ((reducedCameraModelLCP == reducedCameraModel) || (reducedCameraPrettyNameLCP == reducedCameraModel)); const bool lensOK = ((reducedLensPrettyNameLCP.find(reducedLensModel) != std::string::npos) || - (std::find(reducedLensModelsLCP.begin(), reducedLensModelsLCP.end(), reducedLensModel) != reducedLensModelsLCP.end())); + (std::find(reducedLensModelsLCP.begin(), reducedLensModelsLCP.end(), reducedLensModel) != reducedLensModelsLCP.end())); const bool lensIDOK = (std::find(lensIDsLCP.begin(), lensIDsLCP.end(), lensID) != lensIDsLCP.end()); const bool isRaw = lcpHeader.isRawProfile(); - const bool lcpFound = - (cameraOK && lensOK && lensIDOK && ((isRaw && rawMode < 2) || (!isRaw && (rawMode % 2 == 0)))); - if(!lcpFound) + const bool lcpFound = (cameraOK && lensOK && lensIDOK && ((isRaw && rawMode < 2) || (!isRaw && (rawMode % 2 == 0)))); + if (!lcpFound) // The LCP does not match our image metadata continue; diff --git a/src/aliceVision/lensCorrectionProfile/lcp.hpp b/src/aliceVision/lensCorrectionProfile/lcp.hpp index 612906e0cb..de9d49463b 100644 --- a/src/aliceVision/lensCorrectionProfile/lcp.hpp +++ b/src/aliceVision/lensCorrectionProfile/lcp.hpp @@ -51,7 +51,6 @@ inline std::ostream& operator<<(std::ostream& os, const LCPReadingState& state) return os; } - struct settingsInfo { float FocalLength = 0.f; @@ -88,10 +87,7 @@ struct RectilinearModel float ScaleFactor = 1.f; bool isEmpty = true; - void reset() - { - *this = RectilinearModel(); - } + void reset() { *this = RectilinearModel(); } void distort(const float x, const float y, float& x_d, float& y_d) { @@ -104,7 +100,7 @@ struct RectilinearModel bool init3(const std::vector& params) { - if(params.size() < 7) + if (params.size() < 7) { reset(); return false; @@ -123,7 +119,7 @@ struct RectilinearModel bool init5(const std::vector& params) { - if(params.size() < 9) + if (params.size() < 9) { reset(); return false; @@ -147,10 +143,8 @@ inline std::ostream& operator<<(std::ostream& os, const RectilinearModel& model) { os << "Focal: (" << model.FocalLengthX << ", " << model.FocalLengthY << ")" << std::endl; os << "Center: (" << model.ImageXCenter << ", " << model.ImageYCenter << ")" << std::endl; - os << "Radial: (" << model.RadialDistortParam1 << ", " << model.RadialDistortParam2 << ", " - << model.RadialDistortParam3 << ")" << std::endl; - os << "Tangential: (" << model.TangentialDistortParam1 << ", " << model.TangentialDistortParam2 << ")" - << std::endl; + os << "Radial: (" << model.RadialDistortParam1 << ", " << model.RadialDistortParam2 << ", " << model.RadialDistortParam3 << ")" << std::endl; + os << "Tangential: (" << model.TangentialDistortParam1 << ", " << model.TangentialDistortParam2 << ")" << std::endl; os << "scale: " << model.ScaleFactor; } return os; @@ -172,10 +166,7 @@ struct VignetteModel float VignetteModelParam3 = 0.f; bool isEmpty = true; - void reset() - { - *this = VignetteModel(); - } + void reset() { *this = VignetteModel(); } }; /** @@ -196,10 +187,7 @@ struct FisheyeModel float RadialDistortParam2 = 0.f; bool isEmpty = true; - void reset() - { - *this = FisheyeModel(); - } + void reset() { *this = FisheyeModel(); } }; /** @@ -210,12 +198,12 @@ struct FisheyeModel */ class LensParam { -public: + public: LensParam() = default; /** - * @brief LensParam reset - */ + * @brief LensParam reset + */ void clear(); /** @@ -243,57 +231,56 @@ class LensParam bool hasChromaticParams() const { return _hasChromaticParams; } /** - * @brief Set fisheye status - * @param[in] fisheye status - */ + * @brief Set fisheye status + * @param[in] fisheye status + */ void setFisheyeStatus(bool s) { _isFisheye = s; } /** - * @brief Set vignetting availabilty status - * @param[in] vignetting availabilty status - */ + * @brief Set vignetting availabilty status + * @param[in] vignetting availabilty status + */ void setVignetteParamsStatus(bool s) { _hasVignetteParams = s; } /** - * @brief Set chromatic models availabilty status - * @param[in] chromatic models availabilty status - */ + * @brief Set chromatic models availabilty status + * @param[in] chromatic models availabilty status + */ void setChromaticParamsStatus(bool s) { _hasChromaticParams = s; } /** - * @brief Chromatic Green model parameters - */ + * @brief Chromatic Green model parameters + */ RectilinearModel ChromaticGreenParams; /** - * @brief Chromatic Red/Green model parameters - */ + * @brief Chromatic Red/Green model parameters + */ RectilinearModel ChromaticRedGreenParams; /** - * @brief Chromatic Blue/Green model parameters - */ + * @brief Chromatic Blue/Green model parameters + */ RectilinearModel ChromaticBlueGreenParams; /** - * @brief Fisheye model parameters - */ + * @brief Fisheye model parameters + */ FisheyeModel fisheyeParams; /** - * @brief Pinhole model parameters - */ - //PerspectiveModel perspParams; + * @brief Pinhole model parameters + */ + // PerspectiveModel perspParams; RectilinearModel perspParams; /** - * @brief Vignetting model parameters - */ + * @brief Vignetting model parameters + */ VignetteModel vignParams; /** - * @brief Camera settings - */ + * @brief Camera settings + */ settingsInfo camData; -private: - + private: bool _isFisheye = false; bool _hasVignetteParams = false; bool _hasChromaticParams = false; @@ -308,38 +295,38 @@ class LensParam */ class LCPinfo { -public: + public: LCPinfo() = default; /** - * @brief LCPinfo constructor - * @param[in] filename The lcp path on disk - * @param[in] fullParsing Load only common camera and lens info and skip all models when set to false (default = true) - */ - LCPinfo(const std::string& filename, bool fullParsing=true); + * @brief LCPinfo constructor + * @param[in] filename The lcp path on disk + * @param[in] fullParsing Load only common camera and lens info and skip all models when set to false (default = true) + */ + LCPinfo(const std::string& filename, bool fullParsing = true); ~LCPinfo() = default; /** - * @brief LCPinfo loader - * @param[in] filename The lcp path on disk - * @param[in] fullParsing Load only common camera and lens info and skip all models when set to false (default = true) - */ + * @brief LCPinfo loader + * @param[in] filename The lcp path on disk + * @param[in] fullParsing Load only common camera and lens info and skip all models when set to false (default = true) + */ void load(const std::string& filename, bool fullParsing = true); /** - * @brief Get distortion parameters for a given couple focal length, focus distance. Focus distance can set to zero. - * @param[in] focalLength Focal length in mm - * @param[in] focusDistance Focus distance in meters - * @param[out] lparam Lens parameters to be populated with the distortion model - */ + * @brief Get distortion parameters for a given couple focal length, focus distance. Focus distance can set to zero. + * @param[in] focalLength Focal length in mm + * @param[in] focusDistance Focus distance in meters + * @param[out] lparam Lens parameters to be populated with the distortion model + */ void getDistortionParams(const float& focalLength, const float& focusDistance, LensParam& lparam); /** - * @brief Get vignetting parameters for a given couple focal length, aperture value. Aperture value can set to zero. - * @param[in] focalLength Focal length in mm - * @param[in] aperture Aperture value - * @param[out] lparam Lens parameters to be populated with the vignetting model - */ + * @brief Get vignetting parameters for a given couple focal length, aperture value. Aperture value can set to zero. + * @param[in] focalLength Focal length in mm + * @param[in] aperture Aperture value + * @param[out] lparam Lens parameters to be populated with the vignetting model + */ void getVignettingParams(const float& focalLength, const float& aperture, LensParam& lparam); /** @@ -357,185 +344,185 @@ class LCPinfo inline bool isEmpty() const { return v_lensParams.empty(); } /** - * @brief Get profile author - * @return author - */ + * @brief Get profile author + * @return author + */ inline const std::string& getAuthor() const { return Author; } /** - * @brief Get profile name - * @return profile name - */ + * @brief Get profile name + * @return profile name + */ inline const std::string& getProfileName() const { return ProfileName; } /** - * @brief Get camera maker - * @return camera maker - */ + * @brief Get camera maker + * @return camera maker + */ inline const std::string& getCameraMaker() const { return Make; } /** - * @brief Get camera model - * @return camera model - */ + * @brief Get camera model + * @return camera model + */ inline const std::string& getCameraModel() const { return Model; } /** - * @brief Get unique camera model - * @return unique camera model - */ + * @brief Get unique camera model + * @return unique camera model + */ inline const std::string& getUniqueCameraModel() const { return UniqueCameraModel; } /** - * @brief Get camera pretty name - * @return camera pretty name - */ + * @brief Get camera pretty name + * @return camera pretty name + */ inline const std::string& getCameraPrettyName() const { return CameraPrettyName; } /** - * @brief Get lens pretty name - * @return lens pretty name - */ + * @brief Get lens pretty name + * @return lens pretty name + */ inline const std::string& getLensPrettyName() const { return LensPrettyName; } /** - * @brief Get lens information - * @return lens information - */ + * @brief Get lens information + * @return lens information + */ inline const std::string& getLensInfo() const { return LensInfo; } /** - * @brief Get all known IDs for the lens - * @return lens known IDs - */ + * @brief Get all known IDs for the lens + * @return lens known IDs + */ inline void getLensIDs(std::vector& lensIDs) const { lensIDs = LensID; } /** - * @brief Get all known model names for the lens - * @return lens model known names - */ + * @brief Get all known model names for the lens + * @return lens model known names + */ inline void getLensModels(std::vector& lensModels) const { lensModels = Lens; } /** - * @brief Get image width - * @return image width - */ + * @brief Get image width + * @return image width + */ inline int getImageWidth() const { return ImageWidth; } /** - * @brief Get image length - * @return image length - */ + * @brief Get image length + * @return image length + */ inline int getImageLength() const { return ImageLength; } /** - * @brief Get sensor format factor - * @return sensor format factor - */ + * @brief Get sensor format factor + * @return sensor format factor + */ inline float getSensorFormatFactor() const { return SensorFormatFactor; } /** - * @brief Get raw profile status - * @return true if profile is dedicated to a raw image - */ + * @brief Get raw profile status + * @return true if profile is dedicated to a raw image + */ inline bool isRawProfile() const { return CameraRawProfile; } /** - * @brief Get lens information - * @return lens information - */ + * @brief Get lens information + * @return lens information + */ inline int getModelNumber() const { return v_lensParams.size(); } /** - * @brief Set profile author - * @param[in] profile author - */ + * @brief Set profile author + * @param[in] profile author + */ inline void setAuthor(const std::string& str) { Author = str; } /** - * @brief Set profile author with current text value - */ + * @brief Set profile author with current text value + */ inline void setAuthor() { Author = _currText; } /** - * @brief Set profile name - * @param[in] profile name - */ + * @brief Set profile name + * @param[in] profile name + */ inline void setProfileName(const std::string& str) { ProfileName = str; } /** - * @brief Set camera maker - * @param[in] camera maker - */ + * @brief Set camera maker + * @param[in] camera maker + */ inline void setCameraMaker(const std::string& str) { Make = str; } /** - * @brief Set camera model - * @param[in] camera model - */ + * @brief Set camera model + * @param[in] camera model + */ inline void setCameraModel(const std::string& str) { Model = str; } /** - * @brief Set unique camera model - * @param[in] unique camera model - */ + * @brief Set unique camera model + * @param[in] unique camera model + */ inline void setUniqueCameraModel(const std::string& str) { UniqueCameraModel = str; } /** - * @brief Set camera pretty name - * @param[in] camera pretty name - */ + * @brief Set camera pretty name + * @param[in] camera pretty name + */ inline void setCameraPrettyName(const std::string& str) { CameraPrettyName = str; } /** - * @brief Set lens pretty name - * @param[in] lens pretty name - */ + * @brief Set lens pretty name + * @param[in] lens pretty name + */ inline void setLensPrettyName(const std::string& str) { LensPrettyName = str; } /** - * @brief Set lens information - * @param[in] lens information - */ - inline void setLensInfo(const std::string& str) { LensInfo = str; } + * @brief Set lens information + * @param[in] lens information + */ + inline void setLensInfo(const std::string& str) { LensInfo = str; } /** - * @brief Set an alernate lens ID for the lens - * @param[in] alternate lens ID - */ + * @brief Set an alernate lens ID for the lens + * @param[in] alternate lens ID + */ inline void addLensID(int lensID) { LensID.push_back(lensID); } /** - * @brief Set an alernate model name for the lens - * @param[in] alternate model name - */ + * @brief Set an alernate model name for the lens + * @param[in] alternate model name + */ inline void addLensModel(std::string lensModel) { Lens.push_back(lensModel); } /** - * @brief Set image width - * @param[in] image width - */ + * @brief Set image width + * @param[in] image width + */ inline void setImageWidth(int w) { ImageWidth = w; } /** - * @brief Set image length - * @param[in] image length - */ + * @brief Set image length + * @param[in] image length + */ inline void setImageLength(int l) { ImageLength = l; } /** - * @brief Set sensor format factor - * @param[in] sensor format factor - */ + * @brief Set sensor format factor + * @param[in] sensor format factor + */ inline void setSensorFormatFactor(float f) { SensorFormatFactor = f; } /** - * @brief Set raw profile status - * @param[in] raw profile status - */ + * @brief Set raw profile status + * @param[in] raw profile status + */ inline void setAsRawProfile() { CameraRawProfile = true; } -private: + private: // XML handlers static void XmlStartHandler(void* pLCPinfo, const char* el, const char** attr); static void XmlEndHandler(void* pLCPinfo, const char* el); @@ -631,14 +618,14 @@ std::vector reduceStrings(const std::vector& v_str); */ class LCPdatabase { -public: + public: LCPdatabase() = default; /** * @brief LCPdatabase constructor * @param[in] folder The folder containing all lcp files */ LCPdatabase(const std::string& folder, bool omitCameraModel = false) - : _omitCameraModel(omitCameraModel) + : _omitCameraModel(omitCameraModel) { loadDirectory(folder); } @@ -660,7 +647,7 @@ class LCPdatabase /** * @brief Try to find an appropriate LCP file for a set of camera and lens information amongst a set of files. If a * file is found, load its content. - * + * * @param[in] cameraMake Camera maker name * @param[in] cameraModel Camera model name * @param[in] lensModel Lens model name @@ -672,15 +659,14 @@ class LCPdatabase * @param[in] omitCameraModel cameraModelOrMaker contains only the camera maker (default is false) * @return pointer to the found LCPinfo */ - LCPinfo* findLCP(const std::string& cameraMake, const std::string& cameraModel, - const std::string& lensModel, const int lensID, int rawMode); + LCPinfo* findLCP(const std::string& cameraMake, const std::string& cameraModel, const std::string& lensModel, const int lensID, int rawMode); -private: + private: struct LcpPath { LcpPath(const boost::filesystem::path& p) - : path(p) - , reducedPath(reduceString(p.string())) + : path(p), + reducedPath(reduceString(p.string())) {} boost::filesystem::path path; std::string reducedPath; @@ -698,4 +684,3 @@ class LCPdatabase /// As we are looking for lens information, we can omit the CameraModel to get generic values valid for more lenses. bool _omitCameraModel = false; }; - diff --git a/src/aliceVision/lightingEstimation/augmentedNormals.cpp b/src/aliceVision/lightingEstimation/augmentedNormals.cpp index bd1bd52f09..5ec8c551c9 100644 --- a/src/aliceVision/lightingEstimation/augmentedNormals.cpp +++ b/src/aliceVision/lightingEstimation/augmentedNormals.cpp @@ -9,7 +9,5 @@ #include "augmentedNormals.hpp" namespace aliceVision { -namespace lightingEstimation { - -} -} +namespace lightingEstimation {} +} // namespace aliceVision diff --git a/src/aliceVision/lightingEstimation/augmentedNormals.hpp b/src/aliceVision/lightingEstimation/augmentedNormals.hpp index ce6b8cb889..e98bba686e 100644 --- a/src/aliceVision/lightingEstimation/augmentedNormals.hpp +++ b/src/aliceVision/lightingEstimation/augmentedNormals.hpp @@ -12,7 +12,6 @@ #include #include - namespace aliceVision { namespace lightingEstimation { @@ -29,8 +28,7 @@ class AugmentedNormal : public Eigen::Matrix typedef Eigen::Matrix BaseMatrix; typedef T TBase; -public: - + public: AugmentedNormal() = default; /** @@ -54,95 +52,39 @@ class AugmentedNormal : public Eigen::Matrix */ explicit inline AugmentedNormal(const BaseMatrix& m) : BaseMatrix(m) - { - } + {} explicit inline AugmentedNormal(const Eigen::Matrix& m) : AugmentedNormal(m(0), m(1), m(2)) - { - } + {} - inline const T& nx() const - { - return (*this)(0); - } - inline T& nx() - { - return (*this)(0); - } + inline const T& nx() const { return (*this)(0); } + inline T& nx() { return (*this)(0); } - inline const T& ny() const - { - return (*this)(1); - } - inline T& ny() - { - return (*this)(1); - } + inline const T& ny() const { return (*this)(1); } + inline T& ny() { return (*this)(1); } - inline const T& nz() const - { - return (*this)(2); - } - inline T& nz() - { - return (*this)(2); - } + inline const T& nz() const { return (*this)(2); } + inline T& nz() { return (*this)(2); } - inline const T& nambiant() const - { - return (*this)(3); - } - inline T& nambiant() - { - return (*this)(3); - } + inline const T& nambiant() const { return (*this)(3); } + inline T& nambiant() { return (*this)(3); } - inline const T& nx_ny() const - { - return (*this)(4); - } - inline T& nx_ny() - { - return (*this)(4); - } + inline const T& nx_ny() const { return (*this)(4); } + inline T& nx_ny() { return (*this)(4); } - inline const T& nx_nz() const - { - return (*this)(5); - } - inline T& nx_nz() - { - return (*this)(5); - } + inline const T& nx_nz() const { return (*this)(5); } + inline T& nx_nz() { return (*this)(5); } - inline const T& ny_nz() const - { - return (*this)(6); - } - inline T& ny_nz() - { - return (*this)(6); - } + inline const T& ny_nz() const { return (*this)(6); } + inline T& ny_nz() { return (*this)(6); } - inline const T& nx2_ny2() const - { - return (*this)(7); - } - inline T& nx2_ny2() - { - return (*this)(7); - } + inline const T& nx2_ny2() const { return (*this)(7); } + inline T& nx2_ny2() { return (*this)(7); } - inline const T& nz2() const - { - return (*this)(8); - } - inline T& nz2() - { - return (*this)(8); - } + inline const T& nz2() const { return (*this)(8); } + inline T& nz2() { return (*this)(8); } }; -} -} +} // namespace lightingEstimation +} // namespace aliceVision diff --git a/src/aliceVision/lightingEstimation/lightingCalibration.cpp b/src/aliceVision/lightingEstimation/lightingCalibration.cpp index 47adc0b4ff..0e2112f4ae 100644 --- a/src/aliceVision/lightingEstimation/lightingCalibration.cpp +++ b/src/aliceVision/lightingEstimation/lightingCalibration.cpp @@ -21,7 +21,6 @@ #include #include - // Eigen #include #include @@ -34,9 +33,11 @@ namespace bpt = boost::property_tree; namespace aliceVision { namespace lightingEstimation { - -void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJSON, const std::string& outputPath, - const std::string& method, const bool saveAsModel) +void lightCalibration(const sfmData::SfMData& sfmData, + const std::string& inputJSON, + const std::string& outputPath, + const std::string& method, + const bool saveAsModel) { std::vector imageList; std::vector> allSpheresParams; @@ -50,7 +51,7 @@ void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJ bpt::read_json(inputJSONFullName, fileTree); std::map viewMap; - for (auto& viewIt: sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { std::map currentMetadata = sfmData.getView(viewIt.first).getImage().getMetadata(); viewMap[currentMetadata.at("Exif:DateTimeDigitized")] = sfmData.getView(viewIt.first); @@ -67,12 +68,12 @@ void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJ auto sphereExists = (fileTree.get_child_optional(sphereName)).is_initialized(); if (sphereExists) { - ALICEVISION_LOG_INFO (" - " << imagePath.string ()); - imageList.push_back (imagePath.string ()); + ALICEVISION_LOG_INFO(" - " << imagePath.string()); + imageList.push_back(imagePath.string()); std::array currentSphereParams; - for (auto& currentSphere: fileTree.get_child(sphereName)) + for (auto& currentSphere : fileTree.get_child(sphereName)) { currentSphereParams[0] = currentSphere.second.get_child("").get("x", 0.0); currentSphereParams[1] = currentSphere.second.get_child("").get("y", 0.0); @@ -86,7 +87,7 @@ void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJ } else { - ALICEVISION_LOG_WARNING ("No detected sphere found for '" << imagePath << "'."); + ALICEVISION_LOG_WARNING("No detected sphere found for '" << imagePath << "'."); } } } @@ -97,7 +98,7 @@ void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJ for (size_t i = 0; i < imageList.size(); ++i) { std::string picturePath = imageList.at(i); - std::array sphereParam = allSpheresParams.at(i); + std::array sphereParam = allSpheresParams.at(i); float focal = focals.at(i); Eigen::Vector3f lightingDirection; @@ -110,8 +111,11 @@ void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJ writeJSON(outputPath, sfmData, imageList, lightMat, intList, saveAsModel); } -void lightCalibrationOneImage(const std::string& picturePath, const std::array& sphereParam, - const float focal, const std::string& method, Eigen::Vector3f& lightingDirection) +void lightCalibrationOneImage(const std::string& picturePath, + const std::array& sphereParam, + const float focal, + const std::string& method, + Eigen::Vector3f& lightingDirection) { // Read picture : image::Image imageFloat; @@ -162,16 +166,15 @@ void lightCalibrationOneImage(const std::string& picturePath, const std::array 0.3) && (patch(i, j) < 0.8)) + if ((distanceToCenter < (radius * radius - 0.05 * radius)) && (patch(i, j) > 0.3) && (patch(i, j) < 0.8)) { // imSphere = normalSphere.s imSphere(currentIndex) = patch(i, j); normalSphere(currentIndex, 0) = (float(j) - radius) / radius; normalSphere(currentIndex, 1) = (float(i) - radius) / radius; - normalSphere(currentIndex, 2) = - -sqrt(1 - normalSphere(currentIndex, 0) * normalSphere(currentIndex, 0) - - normalSphere(currentIndex, 1) * normalSphere(currentIndex, 1)); + normalSphere(currentIndex, 2) = -sqrt(1 - normalSphere(currentIndex, 0) * normalSphere(currentIndex, 0) - + normalSphere(currentIndex, 1) * normalSphere(currentIndex, 1)); ++currentIndex; } @@ -186,8 +189,7 @@ void lightCalibrationOneImage(const std::string& picturePath, const std::array& sphereParam, const image::Image& imageFloat, - Eigen::Vector2f& brigthestPoint) +void detectBrightestPoint(const std::array& sphereParam, const image::Image& imageFloat, Eigen::Vector2f& brigthestPoint) { image::Image patch; std::array patchOrigin; @@ -197,7 +199,7 @@ void detectBrightestPoint(const std::array& sphereParam, const image:: image::Image convolutedPatch2; // Create Kernel - size_t kernelSize = round(sphereParam[2] / 20); // arbitrary + size_t kernelSize = round(sphereParam[2] / 20); // arbitrary Eigen::VectorXf kernel(2 * kernelSize + 1); createTriangleKernel(kernelSize, kernel); @@ -226,19 +228,20 @@ void createTriangleKernel(const size_t kernelSize, Eigen::VectorXf& kernel) } } -void getNormalOnSphere(const float xPicture, const float yPicture, const std::array& sphereParam, - Eigen::Vector3f& currentNormal) +void getNormalOnSphere(const float xPicture, const float yPicture, const std::array& sphereParam, Eigen::Vector3f& currentNormal) { currentNormal(0) = (xPicture - sphereParam[0]) / sphereParam[2]; currentNormal(1) = (yPicture - sphereParam[1]) / sphereParam[2]; currentNormal(2) = -sqrt(1 - currentNormal(0) * currentNormal(0) - currentNormal(1) * currentNormal(1)); } -void cutImage(const image::Image& imageFloat, const std::array& sphereParam, - image::Image& patch, std::array& patchOrigin) +void cutImage(const image::Image& imageFloat, + const std::array& sphereParam, + image::Image& patch, + std::array& patchOrigin) { - const int minISphere = floor(sphereParam[1] - sphereParam[2] + imageFloat.rows()/2); - const int minJSphere = floor(sphereParam[0] - sphereParam[2] + imageFloat.cols()/2); + const int minISphere = floor(sphereParam[1] - sphereParam[2] + imageFloat.rows() / 2); + const int minJSphere = floor(sphereParam[0] - sphereParam[2] + imageFloat.cols() / 2); patchOrigin[0] = minJSphere; patchOrigin[1] = minISphere; @@ -251,8 +254,7 @@ void cutImage(const image::Image& imageFloat, const std::array& { for (int j = 0; j < patch.cols(); ++j) { - const float distanceToCenter = (i - patch.rows() / 2) * (i - patch.rows() / 2) + - (j - patch.cols() / 2) * (j - patch.cols() / 2); + const float distanceToCenter = (i - patch.rows() / 2) * (i - patch.rows() / 2) + (j - patch.cols() / 2) * (j - patch.cols() / 2); if (distanceToCenter > radius * radius + 2) { patch(i, j) = 0; @@ -261,21 +263,25 @@ void cutImage(const image::Image& imageFloat, const std::array& } } -void writeJSON(const std::string& fileName, const sfmData::SfMData& sfmData, const std::vector& imageList, - const Eigen::MatrixXf& lightMat, const std::vector& intList, const bool saveAsModel) +void writeJSON(const std::string& fileName, + const sfmData::SfMData& sfmData, + const std::vector& imageList, + const Eigen::MatrixXf& lightMat, + const std::vector& intList, + const bool saveAsModel) { bpt::ptree lightsTree; bpt::ptree fileTree; int imgCpt = 0; std::map viewMap; - for (auto& viewIt: sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { std::map currentMetadata = sfmData.getView(viewIt.first).getImage().getMetadata(); viewMap[currentMetadata.at("Exif:DateTimeDigitized")] = sfmData.getView(viewIt.first); } - for (const auto& [currentTime, viewId]: viewMap) + for (const auto& [currentTime, viewId] : viewMap) { const fs::path imagePath = fs::path(viewId.getImage().getImagePath()); @@ -293,7 +299,7 @@ void writeJSON(const std::string& fileName, const sfmData::SfMData& sfmData, con } else { - //const IndexT index = viewId.getViewId + // const IndexT index = viewId.getViewId lightTree.put("viewId", viewId.getViewId()); lightTree.put("type", "directional"); } @@ -323,8 +329,8 @@ void writeJSON(const std::string& fileName, const sfmData::SfMData& sfmData, con } else { - ALICEVISION_LOG_INFO("'" << imagePath << "' is in the input SfMData but has not been used for the lighting " << - "calibration or contains 'ambiant' in its filename."); + ALICEVISION_LOG_INFO("'" << imagePath << "' is in the input SfMData but has not been used for the lighting " + << "calibration or contains 'ambiant' in its filename."); } } @@ -332,5 +338,5 @@ void writeJSON(const std::string& fileName, const sfmData::SfMData& sfmData, con bpt::write_json(fileName, fileTree); } -} -} +} // namespace lightingEstimation +} // namespace aliceVision diff --git a/src/aliceVision/lightingEstimation/lightingCalibration.hpp b/src/aliceVision/lightingEstimation/lightingCalibration.hpp index 812f722a27..6dfd0a0044 100644 --- a/src/aliceVision/lightingEstimation/lightingCalibration.hpp +++ b/src/aliceVision/lightingEstimation/lightingCalibration.hpp @@ -13,7 +13,6 @@ #include #include - namespace aliceVision { namespace lightingEstimation { @@ -23,8 +22,11 @@ namespace lightingEstimation { * @param[in] inputJSON Path to the JSON file containing the spheres parameters (see sphereDetection) * @param[out] outputPath Path to the JSON file in which lights' directions are written */ -void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJSON, const std::string& outputPath, - const std::string& method, const bool saveAsModel); +void lightCalibration(const sfmData::SfMData& sfmData, + const std::string& inputJSON, + const std::string& outputPath, + const std::string& method, + const bool saveAsModel); /** * @brief Calibrate lighting direction of an image containing a sphere @@ -34,8 +36,10 @@ void lightCalibration(const sfmData::SfMData& sfmData, const std::string& inputJ * @param[in] method Method used for calibration (only "brightestPoint" for now) * @param[out] lightingDirection Output parameter for the estimated lighting direction */ -void lightCalibrationOneImage(const std::string& picturePath, const std::array& sphereParam, - const float focal, const std::string& method, +void lightCalibrationOneImage(const std::string& picturePath, + const std::array& sphereParam, + const float focal, + const std::string& method, Eigen::Vector3f& lightingDirection); /** @@ -45,8 +49,7 @@ void lightCalibrationOneImage(const std::string& picturePath, const std::array& sphereParam, const image::Image& imageFloat, - Eigen::Vector2f& brigthestPoint); +void detectBrightestPoint(const std::array& sphereParam, const image::Image& imageFloat, Eigen::Vector2f& brigthestPoint); /** * @brief Create a triangle kernel @@ -62,8 +65,7 @@ void createTriangleKernel(const size_t kernelSize, Eigen::VectorXf& kernel); * @param[in] sphereParam An array of 3 floating-point: the coordinates of the sphere center in the picture frame and the radius of the sphere * @param[out] currentNormal The normal vector */ -void getNormalOnSphere(const float xPicture, const float yPicture, const std::array& sphereParam, - Eigen::Vector3f& currentNormal); +void getNormalOnSphere(const float xPicture, const float yPicture, const std::array& sphereParam, Eigen::Vector3f& currentNormal); /** * @brief Cut a region around a sphere from a grayscale image @@ -72,8 +74,10 @@ void getNormalOnSphere(const float xPicture, const float yPicture, const std::ar * @param[out] patch The extracted region around the sphere. * @param[out] patchOrigin An array containing the x and y coordinates of the origin of the extracted region. */ -void cutImage(const image::Image& imageFloat, const std::array& sphereParam, - image::Image& patch, std::array& patchOrigin); +void cutImage(const image::Image& imageFloat, + const std::array& sphereParam, + image::Image& patch, + std::array& patchOrigin); /** * @brief Write a JSON file containing light information @@ -84,9 +88,12 @@ void cutImage(const image::Image& imageFloat, const std::array& * @param[in] intList A vector of arrays containing the intensity of the light sources * @param[in] saveAsModel True to save the light IDs instead of the view IDs, false otherwise */ -void writeJSON(const std::string& fileName, const sfmData::SfMData& sfmData, - const std::vector& imageList, const Eigen::MatrixXf& lightMat, - const std::vector& intList, const bool saveAsModel); +void writeJSON(const std::string& fileName, + const sfmData::SfMData& sfmData, + const std::vector& imageList, + const Eigen::MatrixXf& lightMat, + const std::vector& intList, + const bool saveAsModel); -} -} +} // namespace lightingEstimation +} // namespace aliceVision diff --git a/src/aliceVision/lightingEstimation/lightingEstimation.cpp b/src/aliceVision/lightingEstimation/lightingEstimation.cpp index b83ea71a8d..ae065bc313 100644 --- a/src/aliceVision/lightingEstimation/lightingEstimation.cpp +++ b/src/aliceVision/lightingEstimation/lightingEstimation.cpp @@ -13,15 +13,13 @@ #include - namespace aliceVision { namespace lightingEstimation { /** * @brief Evaluate albedo and normal product for one channel */ -void albedoNormalsProduct(MatrixXf& rhoTimesN, const MatrixXf& albedoChannel, - const image::Image& augmentedNormals) +void albedoNormalsProduct(MatrixXf& rhoTimesN, const MatrixXf& albedoChannel, const image::Image& augmentedNormals) { std::size_t validIndex = 0; for (int i = 0; i < augmentedNormals.size(); ++i) @@ -77,7 +75,8 @@ void prepareImageData(const MatrixXf& albedo, } } -void LighthingEstimator::addImage(const image::Image& albedo, const image::Image& picture, +void LighthingEstimator::addImage(const image::Image& albedo, + const image::Image& picture, const image::Image& normals) { using namespace Eigen; @@ -119,8 +118,8 @@ void LighthingEstimator::addImage(const image::Image& albedo, MatrixXf colors; // map albedo, image - Map> channelAlbedo(static_cast(&(albedo(0,0)(channel))), nbPixels, 1); - Map> channelPicture(static_cast(&(picture(0,0)(channel))), nbPixels, 1); + Map> channelAlbedo(static_cast(&(albedo(0, 0)(channel))), nbPixels, 1); + Map> channelPicture(static_cast(&(picture(0, 0)(channel))), nbPixels, 1); prepareImageData(channelAlbedo, channelPicture, augmentedNormals, rhoTimesN, colors); @@ -152,10 +151,10 @@ void LighthingEstimator::estimateLigthing(LightingVector& lighting) const // count and check matrices rows { - for(const MatrixXf& matrix : _all_rhoTimesN.at(channel)) + for (const MatrixXf& matrix : _all_rhoTimesN.at(channel)) nbRows_all_rhoTimesN += matrix.rows(); - for(const MatrixXf& matrix : _all_pictureChannel.at(channel)) + for (const MatrixXf& matrix : _all_pictureChannel.at(channel)) nbRows_all_pictureChannel += matrix.rows(); assert(nbRows_all_rhoTimesN == nbRows_all_pictureChannel); @@ -179,13 +178,13 @@ void LighthingEstimator::estimateLigthing(LightingVector& lighting) const std::size_t nbRow = 0; for (const MatrixXf& matrix : _all_pictureChannel.at(channel)) { - for(int matrixRow = 0; matrixRow < matrix.rows(); ++matrixRow) + for (int matrixRow = 0; matrixRow < matrix.rows(); ++matrixRow) pictureChannel.row(nbRow + matrixRow) = matrix.row(matrixRow); nbRow += matrix.rows(); } } - ALICEVISION_LOG_INFO("Estimate ligthing channel: rhoTimesN(" << rhoTimesN.rows() << "x" << rhoTimesN.cols()<< ")"); + ALICEVISION_LOG_INFO("Estimate ligthing channel: rhoTimesN(" << rhoTimesN.rows() << "x" << rhoTimesN.cols() << ")"); Eigen::Matrix lightingC = rhoTimesN.colPivHouseholderQr().solve(pictureChannel); // lighting vectors fusion @@ -206,5 +205,5 @@ void LighthingEstimator::clear() _all_pictureChannel = std::array, 3>(); } -} // namespace lightingEstimation -} // namespace aliceVision +} // namespace lightingEstimation +} // namespace aliceVision diff --git a/src/aliceVision/lightingEstimation/lightingEstimation.hpp b/src/aliceVision/lightingEstimation/lightingEstimation.hpp index 7196c9ebbc..f0b1bd30ab 100644 --- a/src/aliceVision/lightingEstimation/lightingEstimation.hpp +++ b/src/aliceVision/lightingEstimation/lightingEstimation.hpp @@ -23,7 +23,7 @@ using Eigen::MatrixXf; /** * @brief Augmented lighting vetor for augmented Lambert's law (using Spherical Harmonics model) * Composed of 9 coefficients - */ + */ using LightingVector = Eigen::Matrix; /** @@ -35,15 +35,14 @@ using LightingVector = Eigen::Matrix; */ class LighthingEstimator { -public: + public: /** * @brief Aggregate image data * @param[in] albedo the corresponding albedo image (float image) * @param[in] picture the corresponding picture (float image) * @param[in] normals the corresponding normals image */ - void addImage(const image::Image& albedo, const image::Image& picture, - const image::Image& normals); + void addImage(const image::Image& albedo, const image::Image& picture, const image::Image& normals); /** * @brief Aggregate image data @@ -51,7 +50,8 @@ class LighthingEstimator * @param[in] picture the corresponding picture (RGBf image) * @param[in] normals the corresponding normals image */ - void addImage(const image::Image& albedo, const image::Image& picture, + void addImage(const image::Image& albedo, + const image::Image& picture, const image::Image& normals); /** @@ -65,10 +65,10 @@ class LighthingEstimator */ void clear(); -private: + private: std::array, 3> _all_rhoTimesN; std::array, 3> _all_pictureChannel; }; -} // namespace lightingEstimation -} // namespace aliceVision +} // namespace lightingEstimation +} // namespace aliceVision diff --git a/src/aliceVision/lightingEstimation/lightingEstimation_test.cpp b/src/aliceVision/lightingEstimation/lightingEstimation_test.cpp index 36ffcf8193..971c25f37f 100644 --- a/src/aliceVision/lightingEstimation/lightingEstimation_test.cpp +++ b/src/aliceVision/lightingEstimation/lightingEstimation_test.cpp @@ -22,118 +22,109 @@ using namespace aliceVision; using namespace aliceVision::lightingEstimation; using namespace aliceVision::image; -inline float zeroOneRand() -{ - return float(rand()) / float(RAND_MAX); -} - +inline float zeroOneRand() { return float(rand()) / float(RAND_MAX); } BOOST_AUTO_TEST_CASE(LIGHTING_ESTIMATION_Lambertian_GT) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const std::size_t sx = 4; - const std::size_t sy = 3; + const std::size_t sx = 4; + const std::size_t sy = 3; - // Random initialization of lighting, albedo and normals - LightingVector lightingSynt = MatrixXf::Random(9, 3).cwiseAbs(); + // Random initialization of lighting, albedo and normals + LightingVector lightingSynt = MatrixXf::Random(9, 3).cwiseAbs(); - Image albedoSynt(sy, sx); - Image normalsSynt(sy, sx); + Image albedoSynt(sy, sx); + Image normalsSynt(sy, sx); - for(std::size_t y = 0; y < sy; ++y) - { - for(std::size_t x = 0; x < sx; ++x) + for (std::size_t y = 0; y < sy; ++y) { - albedoSynt(x, y) = RGBfColor(zeroOneRand(), zeroOneRand(), zeroOneRand()); - RGBfColor n(zeroOneRand(), zeroOneRand(), std::abs(zeroOneRand())); - n.normalize(); - normalsSynt(x, y) = n; + for (std::size_t x = 0; x < sx; ++x) + { + albedoSynt(x, y) = RGBfColor(zeroOneRand(), zeroOneRand(), zeroOneRand()); + RGBfColor n(zeroOneRand(), zeroOneRand(), std::abs(zeroOneRand())); + n.normalize(); + normalsSynt(x, y) = n; + } } - } - Image agmNormalsSynt(normalsSynt.cast()); + Image agmNormalsSynt(normalsSynt.cast()); - // Create simulated image - Image pictureGenerated(sy, sx); + // Create simulated image + Image pictureGenerated(sy, sx); - for(std::size_t y = 0; y < sy; ++y) - { - for(std::size_t x = 0; x < sx; ++x) + for (std::size_t y = 0; y < sy; ++y) { - for(std::size_t ch = 0; ch < 3; ++ch) - { - pictureGenerated(x,y)(ch) = albedoSynt(x,y)(ch) * agmNormalsSynt(x,y).dot(lightingSynt.col(ch)); - } + for (std::size_t x = 0; x < sx; ++x) + { + for (std::size_t ch = 0; ch < 3; ++ch) + { + pictureGenerated(x, y)(ch) = albedoSynt(x, y)(ch) * agmNormalsSynt(x, y).dot(lightingSynt.col(ch)); + } + } } - } - // Retrieve unknown lighting - LighthingEstimator estimator; - LightingVector lightingEst; + // Retrieve unknown lighting + LighthingEstimator estimator; + LightingVector lightingEst; - estimator.addImage(albedoSynt, pictureGenerated, normalsSynt); - estimator.estimateLigthing(lightingEst); + estimator.addImage(albedoSynt, pictureGenerated, normalsSynt); + estimator.estimateLigthing(lightingEst); - const float epsilon = 1e-3f; - EXPECT_MATRIX_NEAR(lightingEst, lightingSynt, epsilon); + const float epsilon = 1e-3f; + EXPECT_MATRIX_NEAR(lightingEst, lightingSynt, epsilon); } - BOOST_AUTO_TEST_CASE(LIGHTING_ESTIMATION_Lambertian_noise) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const std::size_t sx = 500; - const std::size_t sy = 300; + const std::size_t sx = 500; + const std::size_t sy = 300; - // Random initialization of lighting, albedo and normals - LightingVector lightingSynt = MatrixXf::Random(9, 3).cwiseAbs(); + // Random initialization of lighting, albedo and normals + LightingVector lightingSynt = MatrixXf::Random(9, 3).cwiseAbs(); - Image albedoSynt(sy, sx); - Image normalsSynt(sy, sx); + Image albedoSynt(sy, sx); + Image normalsSynt(sy, sx); - for(std::size_t y = 0; y < sy; ++y) - { - for(std::size_t x = 0; x < sx; ++x) + for (std::size_t y = 0; y < sy; ++y) { - albedoSynt(x, y) = RGBfColor(zeroOneRand(), zeroOneRand(), zeroOneRand()); - RGBfColor n(zeroOneRand(), zeroOneRand(), std::abs(zeroOneRand())); - n.normalize(); - normalsSynt(x, y) = n; + for (std::size_t x = 0; x < sx; ++x) + { + albedoSynt(x, y) = RGBfColor(zeroOneRand(), zeroOneRand(), zeroOneRand()); + RGBfColor n(zeroOneRand(), zeroOneRand(), std::abs(zeroOneRand())); + n.normalize(); + normalsSynt(x, y) = n; + } } - } - Image agmNormalsSynt(normalsSynt.cast()); + Image agmNormalsSynt(normalsSynt.cast()); - const float noiseLevel = 0.01f; + const float noiseLevel = 0.01f; - // Create simulated image - Image pictureGenerated(sy, sx); + // Create simulated image + Image pictureGenerated(sy, sx); - for(std::size_t y = 0; y < sy; ++y) - { - for(std::size_t x = 0; x < sx; ++x) + for (std::size_t y = 0; y < sy; ++y) { - for(std::size_t ch = 0; ch < 3; ++ch) - { - pictureGenerated(x,y)(ch) = albedoSynt(x,y)(ch) * agmNormalsSynt(x,y).dot(lightingSynt.col(ch)); - pictureGenerated(x,y)(ch) += zeroOneRand() * noiseLevel; - } + for (std::size_t x = 0; x < sx; ++x) + { + for (std::size_t ch = 0; ch < 3; ++ch) + { + pictureGenerated(x, y)(ch) = albedoSynt(x, y)(ch) * agmNormalsSynt(x, y).dot(lightingSynt.col(ch)); + pictureGenerated(x, y)(ch) += zeroOneRand() * noiseLevel; + } + } } - } - // Retrieve unknown lighting - LighthingEstimator estimator; - LightingVector lightingEst; + // Retrieve unknown lighting + LighthingEstimator estimator; + LightingVector lightingEst; - estimator.addImage(albedoSynt, pictureGenerated, normalsSynt); - estimator.estimateLigthing(lightingEst); + estimator.addImage(albedoSynt, pictureGenerated, normalsSynt); + estimator.estimateLigthing(lightingEst); - const float epsilon = 1e-2f; - EXPECT_MATRIX_NEAR(lightingEst, lightingSynt, epsilon); + const float epsilon = 1e-2f; + EXPECT_MATRIX_NEAR(lightingEst, lightingSynt, epsilon); } - - - - diff --git a/src/aliceVision/linearProgramming/ISolver.hpp b/src/aliceVision/linearProgramming/ISolver.hpp index d8ebf84c7f..cef5d68100 100644 --- a/src/aliceVision/linearProgramming/ISolver.hpp +++ b/src/aliceVision/linearProgramming/ISolver.hpp @@ -12,8 +12,8 @@ #include #include -namespace aliceVision { -namespace linearProgramming { +namespace aliceVision { +namespace linearProgramming { /// Generic container for LP (Linear Programming problems). /// Embed : @@ -24,26 +24,24 @@ namespace linearProgramming { /// struct LPConstraints { - enum eLP_SIGN - { - LP_LESS_OR_EQUAL = 1, // (<=) - LP_GREATER_OR_EQUAL = 2, // (>=) - LP_EQUAL = 3, // (=) - LP_FREE = 4 //only supported in MOSEK - }; - - LPConstraints() { - _bminimize = false; - } - - int _nbParams; // The number of parameter/variable in constraint. - Mat _constraintMat; // Constraint under Matrix form. - Vec _Cst_objective; // Constraint objective value. - std::vector _vec_sign; // Constraint sign. - std::vector< std::pair > _vec_bounds; // parameter/variable bounds. - - bool _bminimize; // minimize is true or maximize is false. - std::vector _vec_cost; // Objective function + enum eLP_SIGN + { + LP_LESS_OR_EQUAL = 1, // (<=) + LP_GREATER_OR_EQUAL = 2, // (>=) + LP_EQUAL = 3, // (=) + LP_FREE = 4 // only supported in MOSEK + }; + + LPConstraints() { _bminimize = false; } + + int _nbParams; // The number of parameter/variable in constraint. + Mat _constraintMat; // Constraint under Matrix form. + Vec _Cst_objective; // Constraint objective value. + std::vector _vec_sign; // Constraint sign. + std::vector> _vec_bounds; // parameter/variable bounds. + + bool _bminimize; // minimize is true or maximize is false. + std::vector _vec_cost; // Objective function }; /// Generic Sparse container for LP (Linear Programming problems). @@ -55,21 +53,19 @@ struct LPConstraints /// struct LPConstraintsSparse { - LPConstraintsSparse() { - _bminimize = false; - } + LPConstraintsSparse() { _bminimize = false; } - // Variable part - int _nbParams; // The number of parameter/variable in constraint. - std::vector< std::pair > _vec_bounds; // parameter/variable bounds. + // Variable part + int _nbParams; // The number of parameter/variable in constraint. + std::vector> _vec_bounds; // parameter/variable bounds. - // Constraint part - sRMat _constraintMat; // Constraint under Matrix form. - Vec _Cst_objective; // Constraint objective value. - std::vector _vec_sign; // Constraint sign. + // Constraint part + sRMat _constraintMat; // Constraint under Matrix form. + Vec _Cst_objective; // Constraint objective value. + std::vector _vec_sign; // Constraint sign. - bool _bminimize; // minimize is true or maximize is false. - std::vector _vec_cost; // Objective function + bool _bminimize; // minimize is true or maximize is false. + std::vector _vec_cost; // Objective function }; /// Generic LP solver (Linear Programming) @@ -77,23 +73,23 @@ struct LPConstraintsSparse /// Embed constraint setup, problem solving, and parameters getter. class ISolver { -public: + public: + ISolver(int nbParams) + : _nbParams(nbParams){}; - ISolver(int nbParams):_nbParams(nbParams){}; + /// Setup constraint for the given library. + virtual bool setup(const LPConstraints& constraints) = 0; + virtual bool setup(const LPConstraintsSparse& constraints) = 0; - /// Setup constraint for the given library. - virtual bool setup(const LPConstraints & constraints) = 0; - virtual bool setup(const LPConstraintsSparse & constraints) = 0; + /// Setup the feasibility and found the solution that best fit the constraint. + virtual bool solve() = 0; - /// Setup the feasibility and found the solution that best fit the constraint. - virtual bool solve() = 0; + /// Get back solution. Call it after solve. + virtual bool getSolution(std::vector& estimatedParams) = 0; - /// Get back solution. Call it after solve. - virtual bool getSolution(std::vector & estimatedParams) = 0; - -protected : - int _nbParams; // The number of parameter considered in constraint formulation. + protected: + int _nbParams; // The number of parameter considered in constraint formulation. }; -} // namespace linearProgramming -} // namespace aliceVision +} // namespace linearProgramming +} // namespace aliceVision diff --git a/src/aliceVision/linearProgramming/MOSEKSolver.cpp b/src/aliceVision/linearProgramming/MOSEKSolver.cpp index 8f874a98d5..0fba89f667 100644 --- a/src/aliceVision/linearProgramming/MOSEKSolver.cpp +++ b/src/aliceVision/linearProgramming/MOSEKSolver.cpp @@ -13,422 +13,427 @@ namespace aliceVision { namespace linearProgramming { // This function prints log output from MOSEK to the terminal. -inline void MSKAPI printstr(void *handle, - char str[]) -{ - ALICEVISION_LOG_DEBUG(str); -} +inline void MSKAPI printstr(void* handle, char str[]) { ALICEVISION_LOG_DEBUG(str); } -MSKenv_t env = nullptr; +MSKenv_t env = nullptr; -MOSEKSolver::MOSEKSolver(int nbParams) : ISolver(nbParams) +MOSEKSolver::MOSEKSolver(int nbParams) + : ISolver(nbParams) { - task = nullptr; - //env = nullptr; - - //-- Initialize MOSEK framework - - // Create the mosek environment. - /*MSKrescodee r = MSK_makeenv(&env,nullptr,nullptr,nullptr,nullptr); - if ( r!=MSK_RES_OK ) { - ALICEVISION_LOG_WARNING("Cannot create the MOSEK environment"); - }*/ - if (env == nullptr) - { - MSKrescodee r = MSK_makeenv(&env,nullptr,nullptr,nullptr,nullptr); - - // Directs the env log stream to the 'printstr' function. - if ( r==MSK_RES_OK ) - MSK_linkfunctoenvstream(env,MSK_STREAM_LOG,nullptr,printstr); - - // Initialize the environment. - if ( r==MSK_RES_OK ) - r = MSK_initenv(env); - else { + task = nullptr; + // env = nullptr; + + //-- Initialize MOSEK framework + + // Create the mosek environment. + /*MSKrescodee r = MSK_makeenv(&env,nullptr,nullptr,nullptr,nullptr); + if ( r!=MSK_RES_OK ) { ALICEVISION_LOG_WARNING("Cannot create the MOSEK environment"); + }*/ + if (env == nullptr) + { + MSKrescodee r = MSK_makeenv(&env, nullptr, nullptr, nullptr, nullptr); + + // Directs the env log stream to the 'printstr' function. + if (r == MSK_RES_OK) + MSK_linkfunctoenvstream(env, MSK_STREAM_LOG, nullptr, printstr); + + // Initialize the environment. + if (r == MSK_RES_OK) + r = MSK_initenv(env); + else + { + ALICEVISION_LOG_WARNING("Cannot create the MOSEK environment"); + } } - } - } MOSEKSolver::~MOSEKSolver() { - // Memory cleaning. - MSK_deletetask(&task); - //MSK_deleteenv(&env); + // Memory cleaning. + MSK_deletetask(&task); + // MSK_deleteenv(&env); } - -inline MSKboundkey_enum convertSign(LPConstraints::eLP_SIGN sign) { - - switch(sign) { - case LPConstraints::LP_LESS_OR_EQUAL: // = 1, // cst (<=) VAL - return MSK_BK_UP; - case LPConstraints::LP_GREATER_OR_EQUAL: // = 2, // cst (>=) VAL - return MSK_BK_LO; - case LPConstraints::LP_EQUAL: // = 3, // cst (=) VAL - return MSK_BK_FX; - case LPConstraints::LP_FREE: - return MSK_BK_FR; - } - throw std::out_of_range("Unknown constraint sign (LPConstraints enum): " + std::to_string(int(sign))); +inline MSKboundkey_enum convertSign(LPConstraints::eLP_SIGN sign) +{ + switch (sign) + { + case LPConstraints::LP_LESS_OR_EQUAL: // = 1, // cst (<=) VAL + return MSK_BK_UP; + case LPConstraints::LP_GREATER_OR_EQUAL: // = 2, // cst (>=) VAL + return MSK_BK_LO; + case LPConstraints::LP_EQUAL: // = 3, // cst (=) VAL + return MSK_BK_FX; + case LPConstraints::LP_FREE: + return MSK_BK_FR; + } + throw std::out_of_range("Unknown constraint sign (LPConstraints enum): " + std::to_string(int(sign))); } -bool MOSEKSolver::setup(const LPConstraints & cstraints) //cstraints <-> constraints +bool MOSEKSolver::setup(const LPConstraints& cstraints) // cstraints <-> constraints { - assert(_nbParams == cstraints._nbParams); + assert(_nbParams == cstraints._nbParams); - MSK_deletetask(&task); + MSK_deletetask(&task); - int NUMVAR = cstraints._constraintMat.cols(); - int NUMCON = cstraints._constraintMat.rows(); - int NUMANZ = cstraints._constraintMat.cols() * cstraints._constraintMat.rows(); //DENSE MATRIX + int NUMVAR = cstraints._constraintMat.cols(); + int NUMCON = cstraints._constraintMat.rows(); + int NUMANZ = cstraints._constraintMat.cols() * cstraints._constraintMat.rows(); // DENSE MATRIX - MSKrescodee r = MSK_RES_OK; - if ( r==MSK_RES_OK ) - { - // Create the optimization task. - r = MSK_maketask(env,NUMCON,NUMVAR,&task); + MSKrescodee r = MSK_RES_OK; + if (r == MSK_RES_OK) + { + // Create the optimization task. + r = MSK_maketask(env, NUMCON, NUMVAR, &task); - // Directs the log task stream to the 'printstr' function. - if ( r==MSK_RES_OK ) - MSK_linkfunctotaskstream(task,MSK_STREAM_LOG,nullptr,printstr); + // Directs the log task stream to the 'printstr' function. + if (r == MSK_RES_OK) + MSK_linkfunctotaskstream(task, MSK_STREAM_LOG, nullptr, printstr); - // Give MOSEK an estimate of the size of the input data. - //This is done to increase the speed of inputting data. - // However, it is optional. - if (r == MSK_RES_OK) - r = MSK_putmaxnumvar(task,NUMVAR); + // Give MOSEK an estimate of the size of the input data. + // This is done to increase the speed of inputting data. + // However, it is optional. + if (r == MSK_RES_OK) + r = MSK_putmaxnumvar(task, NUMVAR); - if (r == MSK_RES_OK) - r = MSK_putmaxnumcon(task,NUMCON); + if (r == MSK_RES_OK) + r = MSK_putmaxnumcon(task, NUMCON); + + if (r == MSK_RES_OK) + r = MSK_putmaxnumanz(task, NUMANZ); + // Append 'NUMCON' empty constraints. The constraints will initially have no bounds. + if (r == MSK_RES_OK) + r = MSK_append(task, MSK_ACC_CON, NUMCON); + + // Append 'NUMVAR' variables. The variables will initially be fixed at zero (x=0). + if (r == MSK_RES_OK) + r = MSK_append(task, MSK_ACC_VAR, NUMVAR); + } + + this->_nbParams = NUMVAR; + + if (cstraints._bminimize) + { + r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MINIMIZE); + } + else + { + r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MAXIMIZE); + } + + // Optionally add a constant term to the objective. if (r == MSK_RES_OK) - r = MSK_putmaxnumanz(task,NUMANZ); - - // Append 'NUMCON' empty constraints. The constraints will initially have no bounds. - if ( r == MSK_RES_OK ) - r = MSK_append(task,MSK_ACC_CON,NUMCON); - - // Append 'NUMVAR' variables. The variables will initially be fixed at zero (x=0). - if ( r == MSK_RES_OK ) - r = MSK_append(task,MSK_ACC_VAR,NUMVAR); - } - - this->_nbParams = NUMVAR; - - if (cstraints._bminimize) { - r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MINIMIZE); - } - else { - r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MAXIMIZE); - } - - // Optionally add a constant term to the objective. - if ( r ==MSK_RES_OK ) - r = MSK_putcfix(task,0.0); - - // Add the objective function if any - if (!cstraints._vec_cost.empty()) { - // Set objective - for (size_t i = 0; i < cstraints._vec_cost.size(); ++i) - MSK_putcj(task, i, cstraints._vec_cost[i]); - } - - //Add constraint row by row - const Mat & A = cstraints._constraintMat; - - for (int i=0; i vec_colno(A.cols(), 0); - for (int ii=0; ii vec_colno(A.cols(), 0); + for (int ii = 0; ii < A.cols(); ++ii) + { + vec_colno[ii] = ii; + } + + // Insert a row + Vec temp = A.row(i); + r = MSK_putavec(task, + MSK_ACC_CON, // Input row of A. + MSKidxt(i), // Variable row index. + vec_colno.size(), // Number of non-zeros in row(i) A. + &vec_colno[0], // Pointer to row indexes of row i. + (double*)temp.data()); // Pointer to Values of row i. + } + + // Add bound on variables: + if (cstraints._vec_bounds.size() == 1) + { + for (size_t i = 0; i < NUMVAR; ++i) + { + if (r == MSK_RES_OK) + r = MSK_putbound(task, + MSK_ACC_VAR, // Put bounds on variables. + i, // Index of variable. + convertSign(cstraints._vec_sign[0]), // Bound key. + cstraints._vec_bounds[0].first, // Numerical value of lower bound. + cstraints._vec_bounds[0].second); // Numerical value of upper bound. + } + } + else + { + for (size_t i = 0; i < NUMVAR; ++i) + { + // Set the bounds on variable j. + // lowerbound <= x_j <= upper bound + if (r == MSK_RES_OK) + r = MSK_putbound(task, + MSK_ACC_VAR, // Put bounds on variables. + i, // Index of variable. + convertSign(cstraints._vec_sign[i]), // Bound key. + cstraints._vec_bounds[i].first, // Numerical value of lower bound. + cstraints._vec_bounds[i].second); // Numerical value of upper bound. + // in order to add sparse bounds use: MSK_putboundlist + } } - } - else{ - for (size_t i = 0; i < NUMVAR; ++i) { - // Set the bounds on variable j. - // lowerbound <= x_j <= upper bound - if (r == MSK_RES_OK) + + // Add bounds on constraint + for (size_t i = 0; i < NUMCON && r == MSK_RES_OK; ++i) + { r = MSK_putbound(task, - MSK_ACC_VAR, // Put bounds on variables. - i, // Index of variable. - convertSign(cstraints._vec_sign[i]), // Bound key. - cstraints._vec_bounds[i].first, // Numerical value of lower bound. - cstraints._vec_bounds[i].second); // Numerical value of upper bound. - // in order to add sparse bounds use: MSK_putboundlist + MSK_ACC_CON, // Put bounds on constraints. + i, // Index of constraint. + convertSign(cstraints._vec_sign[i]), // Bound key. + -MSK_INFINITY, // Numerical value of lower bound. + cstraints._Cst_objective(i)); // Numerical value of upper bound. } - } - - // Add bounds on constraint - for (size_t i=0; i constraints +bool MOSEKSolver::setup(const LPConstraintsSparse& cstraints) // cstraints <-> constraints { - assert(_nbParams == cstraints._nbParams); + assert(_nbParams == cstraints._nbParams); - MSK_deletetask(&task); + MSK_deletetask(&task); - int NUMVAR = this->_nbParams; - int NUMCON = cstraints._constraintMat.rows(); - int NUMANZ = cstraints._constraintMat.nonZeros(); + int NUMVAR = this->_nbParams; + int NUMCON = cstraints._constraintMat.rows(); + int NUMANZ = cstraints._constraintMat.nonZeros(); - MSKrescodee r = MSK_RES_OK; - if ( r==MSK_RES_OK ) - { - // Directs the log task stream to the 'printstr' function. - if ( r==MSK_RES_OK ) - MSK_linkfunctotaskstream(task,MSK_STREAM_LOG,nullptr,printstr); + MSKrescodee r = MSK_RES_OK; + if (r == MSK_RES_OK) + { + // Directs the log task stream to the 'printstr' function. + if (r == MSK_RES_OK) + MSK_linkfunctotaskstream(task, MSK_STREAM_LOG, nullptr, printstr); - // Create the optimization task. - r = MSK_maketask(env,NUMCON,NUMVAR,&task); + // Create the optimization task. + r = MSK_maketask(env, NUMCON, NUMVAR, &task); - // Give MOSEK an estimate of the size of the input data. - //This is done to increase the speed of inputting data. - // However, it is optional. - if (r == MSK_RES_OK) - r = MSK_putmaxnumvar(task,NUMVAR); + // Give MOSEK an estimate of the size of the input data. + // This is done to increase the speed of inputting data. + // However, it is optional. + if (r == MSK_RES_OK) + r = MSK_putmaxnumvar(task, NUMVAR); - if (r == MSK_RES_OK) - r = MSK_putmaxnumcon(task,NUMCON); + if (r == MSK_RES_OK) + r = MSK_putmaxnumcon(task, NUMCON); + + if (r == MSK_RES_OK) + r = MSK_putmaxnumanz(task, NUMANZ); + + // Append 'NUMCON' empty constraints. The constraints will initially have no bounds. + if (r == MSK_RES_OK) + r = MSK_append(task, MSK_ACC_CON, NUMCON); + // Append 'NUMVAR' variables. The variables will initially be fixed at zero (x=0). + if (r == MSK_RES_OK) + r = MSK_append(task, MSK_ACC_VAR, NUMVAR); + } + + this->_nbParams = NUMVAR; + + if (cstraints._bminimize) + { + r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MINIMIZE); + } + else + { + r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MAXIMIZE); + } + + // Optionally add a constant term to the objective. if (r == MSK_RES_OK) - r = MSK_putmaxnumanz(task,NUMANZ); - - // Append 'NUMCON' empty constraints. The constraints will initially have no bounds. - if ( r == MSK_RES_OK ) - r = MSK_append(task,MSK_ACC_CON,NUMCON); - - // Append 'NUMVAR' variables. The variables will initially be fixed at zero (x=0). - if ( r == MSK_RES_OK ) - r = MSK_append(task,MSK_ACC_VAR,NUMVAR); - } - - this->_nbParams = NUMVAR; - - if (cstraints._bminimize) { - r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MINIMIZE); - } - else { - r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MAXIMIZE); - } - - // Optionally add a constant term to the objective. - if ( r ==MSK_RES_OK ) - r = MSK_putcfix(task,0.0); - - // Add the objective function if any - if (!cstraints._vec_cost.empty()) { - // Set objective - for (size_t i = 0; i < cstraints._vec_cost.size(); ++i) - r = MSK_putcj(task, i, cstraints._vec_cost[i]); - } - - //Add constraint row by row - const sRMat & A = cstraints._constraintMat; - std::vector vec_colno; - std::vector vec_value; - for (int i=0; i vec_colno; + std::vector vec_value; + for (int i = 0; i < A.rows(); ++i) + { + vec_colno.resize(0); + vec_value.resize(0); + for (sRMat::InnerIterator it(A, i); it; ++it) + { + vec_colno.push_back(it.col()); + vec_value.push_back(it.value()); + } + // Insert a row + r = MSK_putavec(task, + MSK_ACC_CON, // Input row of A. + MSKidxt(i), // Variable row index. + vec_colno.size(), // Number of non-zeros in row(i) A. + &vec_colno[0], // Pointer to row indexes of row i. + &vec_value[0]); // Pointer to Values of row i. } - } - else{ - for( size_t i = 0; i < NUMVAR; ++i) { - // Set the bounds on variable j. - // lower bound <= x_j <= upper bound + // Add bound on variables: + if (cstraints._vec_bounds.size() == 1) + { + for (size_t i = 0; i < NUMVAR; ++i) + { + if (r == MSK_RES_OK) + r = MSK_putbound(task, + MSK_ACC_VAR, // Put bounds on variables. + MSKidxt(i), // Index of variable. + MSK_BK_RA, // Bound key. + cstraints._vec_bounds[0].first, // Numerical value of lower bound. + cstraints._vec_bounds[0].second); // Numerical value of upper bound. + } + } + else + { + for (size_t i = 0; i < NUMVAR; ++i) + { + // Set the bounds on variable j. + // lower bound <= x_j <= upper bound + + if (r == MSK_RES_OK) + r = MSK_putbound(task, + MSK_ACC_VAR, // Put bounds on variables. + MSKidxt(i), // Index of variable. + MSK_BK_RA, // Bound key. + cstraints._vec_bounds[i].first, // Numerical value of lower bound. + cstraints._vec_bounds[i].second); // Numerical value of upper bound. + // in order to add sparse bounds use: MSK_putboundlist + } + } - if(r == MSK_RES_OK) - r = MSK_putbound(task, - MSK_ACC_VAR, // Put bounds on variables. - MSKidxt(i), // Index of variable. - MSK_BK_RA, // Bound key. - cstraints._vec_bounds[i].first, // Numerical value of lower bound. - cstraints._vec_bounds[i].second); // Numerical value of upper bound. - // in order to add sparse bounds use: MSK_putboundlist + // Add bounds on constraint + for (size_t i = 0; i < NUMCON && r == MSK_RES_OK; ++i) + { + MSKboundkey_enum cst = convertSign(cstraints._vec_sign[i]); + if (cst == MSK_BK_UP || cst == MSK_BK_RA) + r = MSK_putbound(task, + MSK_ACC_CON, // Put bounds on constraints. + MSKidxt(i), // Index of constraint. + cst, // Bound key. + -MSK_INFINITY, // Numerical value of lower bound. + cstraints._Cst_objective(i)); // upper bound. + else if (cst == MSK_BK_LO) + r = MSK_putbound(task, + MSK_ACC_CON, // Put bounds on constraints. + MSKidxt(i), // Index of constraint. + cst, // Bound key. + cstraints._Cst_objective(i), + +MSK_INFINITY); + else if (cst == MSK_BK_FX) + r = MSK_putbound(task, + MSK_ACC_CON, // Put bounds on constraints. + MSKidxt(i), // Index of constraint. + cst, // Bound key. + cstraints._Cst_objective(i), + cstraints._Cst_objective(i)); } - } - - // Add bounds on constraint - for(size_t i=0; i & estimatedParams) +bool MOSEKSolver::getSolution(std::vector& estimatedParams) { - bool bRet = false; - MSKsolstae solsta; - MSK_getsolutionstatus (task, - MSK_SOL_BAS, - nullptr, - &solsta); - switch(solsta) - { - case MSK_SOL_STA_OPTIMAL: - case MSK_SOL_STA_NEAR_OPTIMAL: - MSK_getsolutionslice(task, - MSK_SOL_BAS, // Request the basic solution. - MSK_SOL_ITEM_XX,// Which part of solution. - 0, // Index of first variable. - estimatedParams.size(), // Index of last variable+1. - &estimatedParams[0]); - bRet = true; - //MSK_writedata(task,"taskdump.opf"); - - /*printf("Optimal primal solution\n"); - for(size_t j=0; j -namespace aliceVision { -namespace linearProgramming { +namespace aliceVision { +namespace linearProgramming { /// MOSEK wrapper for the ISolver class MOSEKSolver : public ISolver { -public : - MOSEKSolver(int nbParams); + public: + MOSEKSolver(int nbParams); - ~MOSEKSolver(); + ~MOSEKSolver(); - //-- - // Inherited functions : - //-- + //-- + // Inherited functions : + //-- - bool setup(const LPConstraints & constraints); - bool setup(const LPConstraintsSparse & constraints); + bool setup(const LPConstraints& constraints); + bool setup(const LPConstraintsSparse& constraints); - bool solve(); + bool solve(); - bool getSolution(std::vector & estimatedParams); + bool getSolution(std::vector& estimatedParams); -private : - //MSKenv_t env; - MSKtask_t task; // Solver object. + private: + // MSKenv_t env; + MSKtask_t task; // Solver object. }; - -} // namespace linearProgramming -} // namespace aliceVision +} // namespace linearProgramming +} // namespace aliceVision diff --git a/src/aliceVision/linearProgramming/OSIXSolver.hpp b/src/aliceVision/linearProgramming/OSIXSolver.hpp index 01c3b75c21..8b43bad67e 100644 --- a/src/aliceVision/linearProgramming/OSIXSolver.hpp +++ b/src/aliceVision/linearProgramming/OSIXSolver.hpp @@ -11,7 +11,7 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "OsiMskSolverInterface.hpp" + #include "OsiMskSolverInterface.hpp" #endif #include "aliceVision/numeric/numeric.hpp" @@ -22,286 +22,269 @@ #include -namespace aliceVision { -namespace linearProgramming { +namespace aliceVision { +namespace linearProgramming { /// OSI_X wrapper for the ISolver template class OSIXSolver : public ISolver { -public : - OSIXSolver(int nbParams); + public: + OSIXSolver(int nbParams); - ~OSIXSolver(); + ~OSIXSolver(); - //-- - // Inherited functions : - //-- + //-- + // Inherited functions : + //-- - bool setup(const LPConstraints & constraints); - bool setup(const LPConstraintsSparse & constraints); + bool setup(const LPConstraints& constraints); + bool setup(const LPConstraintsSparse& constraints); - bool solve(); + bool solve(); - bool getSolution(std::vector & estimatedParams); + bool getSolution(std::vector& estimatedParams); -private : - SOLVERINTERFACE *si; + private: + SOLVERINTERFACE* si; }; - typedef OSIXSolver OSI_CISolverWrapper; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) typedef OSIXSolver OSI_MOSEK_SolverWrapper; -#endif // ALICEVISION_HAVE_MOSEK - - +#endif // ALICEVISION_HAVE_MOSEK template -OSIXSolver::OSIXSolver(int nbParams) : ISolver(nbParams) +OSIXSolver::OSIXSolver(int nbParams) + : ISolver(nbParams) { - si = new SOLVERINTERFACE; - si->setLogLevel(0); + si = new SOLVERINTERFACE; + si->setLogLevel(0); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) template<> -OSIXSolver::OSIXSolver(int nbParams) : ISolver(nbParams) +OSIXSolver::OSIXSolver(int nbParams) + : ISolver(nbParams) { - si = new OsiMskSolverInterface(); - //si->setLogLevel(0); + si = new OsiMskSolverInterface(); + // si->setLogLevel(0); } -#endif // ALICEVISION_HAVE_MOSEK +#endif // ALICEVISION_HAVE_MOSEK template OSIXSolver::~OSIXSolver() { - // Memory cleaning. - if ( si != nullptr ) - { - delete si; - si = nullptr; - } + // Memory cleaning. + if (si != nullptr) + { + delete si; + si = nullptr; + } } template -bool OSIXSolver::setup(const LPConstraints & cstraints) //cstraints <-> constraints +bool OSIXSolver::setup(const LPConstraints& cstraints) // cstraints <-> constraints { - bool bOk = true; - if ( si == nullptr ) - { - return false; - } - assert(_nbParams == cstraints._nbParams); - - const unsigned int NUMVAR = cstraints._constraintMat.cols(); - std::vector col_lb(NUMVAR);//the column lower bounds - std::vector col_ub(NUMVAR);//the column upper bounds + bool bOk = true; + if (si == nullptr) + { + return false; + } + assert(_nbParams == cstraints._nbParams); - this->_nbParams = NUMVAR; + const unsigned int NUMVAR = cstraints._constraintMat.cols(); + std::vector col_lb(NUMVAR); // the column lower bounds + std::vector col_ub(NUMVAR); // the column upper bounds - si->setObjSense( ((cstraints._bminimize) ? 1 : -1) ); + this->_nbParams = NUMVAR; - const Mat & A = cstraints._constraintMat; + si->setObjSense(((cstraints._bminimize) ? 1 : -1)); - //Equality constraint will be done by two constraints due to the API limitation ( >= & <=). - const size_t nbLine = A.rows() + - std::count(cstraints._vec_sign.begin(), cstraints._vec_sign.end(), LPConstraints::LP_EQUAL); + const Mat& A = cstraints._constraintMat; - std::vector row_lb(nbLine);//the row lower bounds - std::vector row_ub(nbLine);//the row upper bounds + // Equality constraint will be done by two constraints due to the API limitation ( >= & <=). + const size_t nbLine = A.rows() + std::count(cstraints._vec_sign.begin(), cstraints._vec_sign.end(), LPConstraints::LP_EQUAL); - CoinPackedMatrix * matrix = new CoinPackedMatrix(false,0,0); - matrix->setDimensions(0, NUMVAR); + std::vector row_lb(nbLine); // the row lower bounds + std::vector row_ub(nbLine); // the row upper bounds - //-- Add row-wise constraint - size_t indexRow = 0; - for (int i=0; i < A.rows(); ++i) - { - Vec temp = A.row(i); + CoinPackedMatrix* matrix = new CoinPackedMatrix(false, 0, 0); + matrix->setDimensions(0, NUMVAR); - CoinPackedVector row; - if ( cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_LESS_OR_EQUAL ) - { - int coef = 1; - for ( int j = 0; j < A.cols() ; j++ ) - { - row.insert(j, coef * temp.data()[j]); - } - row_lb[indexRow] = -1.0 * si->getInfinity(); - row_ub[indexRow] = coef * cstraints._Cst_objective(i); - matrix->appendRow(row); - indexRow++; - } - if ( cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_GREATER_OR_EQUAL ) + //-- Add row-wise constraint + size_t indexRow = 0; + for (int i = 0; i < A.rows(); ++i) { - int coef = -1; - for ( int j = 0; j < A.cols() ; j++ ) - { - row.insert(j, coef * temp.data()[j]); - } - row_lb[indexRow] = -1.0 * si->getInfinity(); - row_ub[indexRow] = coef * cstraints._Cst_objective(i); - matrix->appendRow(row); - indexRow++; + Vec temp = A.row(i); + + CoinPackedVector row; + if (cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_LESS_OR_EQUAL) + { + int coef = 1; + for (int j = 0; j < A.cols(); j++) + { + row.insert(j, coef * temp.data()[j]); + } + row_lb[indexRow] = -1.0 * si->getInfinity(); + row_ub[indexRow] = coef * cstraints._Cst_objective(i); + matrix->appendRow(row); + indexRow++; + } + if (cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_GREATER_OR_EQUAL) + { + int coef = -1; + for (int j = 0; j < A.cols(); j++) + { + row.insert(j, coef * temp.data()[j]); + } + row_lb[indexRow] = -1.0 * si->getInfinity(); + row_ub[indexRow] = coef * cstraints._Cst_objective(i); + matrix->appendRow(row); + indexRow++; + } } - } - //-- Setup bounds for all the parameters - if (cstraints._vec_bounds.size() == 1) - { - // Setup the same bound for all the parameters - for (int i=0; i < this->_nbParams; ++i) + //-- Setup bounds for all the parameters + if (cstraints._vec_bounds.size() == 1) { - col_lb[i] = cstraints._vec_bounds[0].first; - col_ub[i] = cstraints._vec_bounds[0].second; + // Setup the same bound for all the parameters + for (int i = 0; i < this->_nbParams; ++i) + { + col_lb[i] = cstraints._vec_bounds[0].first; + col_ub[i] = cstraints._vec_bounds[0].second; + } } - } - else // each parameter have it's own bounds - { - for (int i=0; i < this->_nbParams; ++i) + else // each parameter have it's own bounds { - col_lb[i] = cstraints._vec_bounds[i].first; - col_ub[i] = cstraints._vec_bounds[i].second; + for (int i = 0; i < this->_nbParams; ++i) + { + col_lb[i] = cstraints._vec_bounds[i].first; + col_ub[i] = cstraints._vec_bounds[i].second; + } } - } - si->loadProblem(*matrix, &col_lb[0], &col_ub[0], cstraints._vec_cost.empty() ? nullptr : &cstraints._vec_cost[0], &row_lb[0], &row_ub[0] ); + si->loadProblem(*matrix, &col_lb[0], &col_ub[0], cstraints._vec_cost.empty() ? nullptr : &cstraints._vec_cost[0], &row_lb[0], &row_ub[0]); - delete matrix; + delete matrix; - return bOk; + return bOk; } template -bool OSIXSolver::setup(const LPConstraintsSparse & cstraints) //cstraints <-> constraints +bool OSIXSolver::setup(const LPConstraintsSparse& cstraints) // cstraints <-> constraints { - bool bOk = true; - if ( si == nullptr ) - { - return false; - } - assert(_nbParams == cstraints._nbParams); - - const int NUMVAR = cstraints._constraintMat.cols(); - std::vector col_lb(NUMVAR);//the column lower bounds - std::vector col_ub(NUMVAR);//the column upper bounds - - this->_nbParams = NUMVAR; + bool bOk = true; + if (si == nullptr) + { + return false; + } + assert(_nbParams == cstraints._nbParams); - si->setObjSense( ((cstraints._bminimize) ? 1 : -1) ); + const int NUMVAR = cstraints._constraintMat.cols(); + std::vector col_lb(NUMVAR); // the column lower bounds + std::vector col_ub(NUMVAR); // the column upper bounds - const sRMat & A = cstraints._constraintMat; + this->_nbParams = NUMVAR; - //Equality constraint will be done by two constraints due to the API limitation (>= & <=) - const size_t nbLine = A.rows() + - std::count(cstraints._vec_sign.begin(), cstraints._vec_sign.end(), LPConstraints::LP_EQUAL); + si->setObjSense(((cstraints._bminimize) ? 1 : -1)); - std::vector row_lb(nbLine);//the row lower bounds - std::vector row_ub(nbLine);//the row upper bounds + const sRMat& A = cstraints._constraintMat; - CoinPackedMatrix * matrix = new CoinPackedMatrix(false,0,0); - matrix->setDimensions(0, NUMVAR); + // Equality constraint will be done by two constraints due to the API limitation (>= & <=) + const size_t nbLine = A.rows() + std::count(cstraints._vec_sign.begin(), cstraints._vec_sign.end(), LPConstraints::LP_EQUAL); - //-- Add row-wise constraint - size_t rowindex = 0; - for (int i=0; i < A.rows(); ++i) - { - std::vector vec_colno; - std::vector vec_value; - for (sRMat::InnerIterator it(A,i); it; ++it) - { - vec_colno.push_back(it.col()); - vec_value.push_back(it.value()); - } + std::vector row_lb(nbLine); // the row lower bounds + std::vector row_ub(nbLine); // the row upper bounds + CoinPackedMatrix* matrix = new CoinPackedMatrix(false, 0, 0); + matrix->setDimensions(0, NUMVAR); - if ( cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_LESS_OR_EQUAL ) - { - int coef = 1; - row_lb[rowindex] = -1.0 * si->getInfinity(); - row_ub[rowindex] = coef * cstraints._Cst_objective(i); - matrix->appendRow( vec_colno.size(), - &vec_colno[0], - &vec_value[0] ); - rowindex++; - } - - if ( cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_GREATER_OR_EQUAL ) + //-- Add row-wise constraint + size_t rowindex = 0; + for (int i = 0; i < A.rows(); ++i) { - int coef = -1; - for ( std::vector::iterator iter_val = vec_value.begin(); - iter_val != vec_value.end(); - iter_val++) - { - *iter_val *= coef; - } - row_lb[rowindex] = -1.0 * si->getInfinity(); - row_ub[rowindex] = coef * cstraints._Cst_objective(i); - matrix->appendRow( vec_colno.size(), - &vec_colno[0], - &vec_value[0] ); - rowindex++; + std::vector vec_colno; + std::vector vec_value; + for (sRMat::InnerIterator it(A, i); it; ++it) + { + vec_colno.push_back(it.col()); + vec_value.push_back(it.value()); + } + + if (cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_LESS_OR_EQUAL) + { + int coef = 1; + row_lb[rowindex] = -1.0 * si->getInfinity(); + row_ub[rowindex] = coef * cstraints._Cst_objective(i); + matrix->appendRow(vec_colno.size(), &vec_colno[0], &vec_value[0]); + rowindex++; + } + + if (cstraints._vec_sign[i] == LPConstraints::LP_EQUAL || cstraints._vec_sign[i] == LPConstraints::LP_GREATER_OR_EQUAL) + { + int coef = -1; + for (std::vector::iterator iter_val = vec_value.begin(); iter_val != vec_value.end(); iter_val++) + { + *iter_val *= coef; + } + row_lb[rowindex] = -1.0 * si->getInfinity(); + row_ub[rowindex] = coef * cstraints._Cst_objective(i); + matrix->appendRow(vec_colno.size(), &vec_colno[0], &vec_value[0]); + rowindex++; + } } - } - //-- Setup bounds for all the parameters - if (cstraints._vec_bounds.size() == 1) - { - // Setup the same bound for all the parameters - for (int i=0; i < this->_nbParams; ++i) + //-- Setup bounds for all the parameters + if (cstraints._vec_bounds.size() == 1) { - col_lb[i] = cstraints._vec_bounds[0].first; - col_ub[i] = cstraints._vec_bounds[0].second; + // Setup the same bound for all the parameters + for (int i = 0; i < this->_nbParams; ++i) + { + col_lb[i] = cstraints._vec_bounds[0].first; + col_ub[i] = cstraints._vec_bounds[0].second; + } } - } - else // each parameter have it's own bounds - { - for (int i=0; i < this->_nbParams; ++i) + else // each parameter have it's own bounds { - col_lb[i] = cstraints._vec_bounds[i].first; - col_ub[i] = cstraints._vec_bounds[i].second; + for (int i = 0; i < this->_nbParams; ++i) + { + col_lb[i] = cstraints._vec_bounds[i].first; + col_ub[i] = cstraints._vec_bounds[i].second; + } } - } - si->loadProblem( - *matrix, - &col_lb[0], - &col_ub[0], - cstraints._vec_cost.empty() ? nullptr : &cstraints._vec_cost[0], - &row_lb[0], - &row_ub[0]); + si->loadProblem(*matrix, &col_lb[0], &col_ub[0], cstraints._vec_cost.empty() ? nullptr : &cstraints._vec_cost[0], &row_lb[0], &row_ub[0]); - delete matrix; + delete matrix; - return bOk; + return bOk; } template bool OSIXSolver::solve() { - //-- Compute solution - if ( si != nullptr ) - { - si->getModelPtr()->setPerturbation(50); - si->initialSolve(); - return si->isProvenOptimal(); - } - return false; + //-- Compute solution + if (si != nullptr) + { + si->getModelPtr()->setPerturbation(50); + si->initialSolve(); + return si->isProvenOptimal(); + } + return false; } template -bool OSIXSolver::getSolution(std::vector & estimatedParams) +bool OSIXSolver::getSolution(std::vector& estimatedParams) { - if ( si != nullptr ) - { - const int n = si->getNumCols(); - memcpy(&estimatedParams[0], si->getColSolution(), n * sizeof(double)); - return true; - } - return false; + if (si != nullptr) + { + const int n = si->getNumCols(); + memcpy(&estimatedParams[0], si->getColSolution(), n * sizeof(double)); + return true; + } + return false; } -} // namespace linearProgramming -} // namespace aliceVision - +} // namespace linearProgramming +} // namespace aliceVision diff --git a/src/aliceVision/linearProgramming/bisectionLP.hpp b/src/aliceVision/linearProgramming/bisectionLP.hpp index abae4b03db..ee7ec88ee1 100644 --- a/src/aliceVision/linearProgramming/bisectionLP.hpp +++ b/src/aliceVision/linearProgramming/bisectionLP.hpp @@ -14,8 +14,8 @@ #include #include -namespace aliceVision { -namespace linearProgramming { +namespace aliceVision { +namespace linearProgramming { /// Generic Bisection algorithm via Linear Programming. /// Use dichotomy or mid-point best parameter that fit the solution. @@ -23,58 +23,55 @@ namespace linearProgramming { /// The bisection algorithm continue as long as /// precision or max iteration number is not reach. /// -template -bool BisectionLP( - ISolver & solver, - ConstraintBuilder & cstraintBuilder, - std::vector * parameters, - double gammaUp = 1.0, // Upper bound - double gammaLow = 0.0, // lower bound - double eps = 1e-8, // precision that stop dichotomy - const int maxIteration = 20, // max number of iteration - double * bestFeasibleGamma = nullptr, // value of best bisection found value - bool bVerbose = false) +template +bool BisectionLP(ISolver& solver, + ConstraintBuilder& cstraintBuilder, + std::vector* parameters, + double gammaUp = 1.0, // Upper bound + double gammaLow = 0.0, // lower bound + double eps = 1e-8, // precision that stop dichotomy + const int maxIteration = 20, // max number of iteration + double* bestFeasibleGamma = nullptr, // value of best bisection found value + bool bVerbose = false) { - int k = 0; - bool bModelFound = false; - ConstraintType constraint; - do - { - ++k; // One more iteration + int k = 0; + bool bModelFound = false; + ConstraintType constraint; + do + { + ++k; // One more iteration - double gamma = (gammaLow + gammaUp) / 2.0; + double gamma = (gammaLow + gammaUp) / 2.0; - //-- Setup constraint and solver - cstraintBuilder.Build(gamma, constraint); - solver.setup( constraint ); - //-- - // Solving - bool bFeasible = solver.solve(); - //-- + //-- Setup constraint and solver + cstraintBuilder.Build(gamma, constraint); + solver.setup(constraint); + //-- + // Solving + bool bFeasible = solver.solve(); + //-- - if (bFeasible) - { - gammaUp = gamma; - if (bestFeasibleGamma) - *bestFeasibleGamma = gamma; - solver.getSolution(*parameters); - bModelFound = true; + if (bFeasible) + { + gammaUp = gamma; + if (bestFeasibleGamma) + *bestFeasibleGamma = gamma; + solver.getSolution(*parameters); + bModelFound = true; - if(bVerbose) - ALICEVISION_LOG_DEBUG(k << "/" << maxIteration - << "\t gamma " << gamma - << "\t gammaUp-gammaLow " << gammaUp-gammaLow); - } - else - { - gammaLow = gamma; - if(bVerbose) - ALICEVISION_LOG_DEBUG("Not feasible with gamma: " << gamma); - } - } while (k < maxIteration && gammaUp - gammaLow > eps); + if (bVerbose) + ALICEVISION_LOG_DEBUG(k << "/" << maxIteration << "\t gamma " << gamma << "\t gammaUp-gammaLow " << gammaUp - gammaLow); + } + else + { + gammaLow = gamma; + if (bVerbose) + ALICEVISION_LOG_DEBUG("Not feasible with gamma: " << gamma); + } + } while (k < maxIteration && gammaUp - gammaLow > eps); - return bModelFound; + return bModelFound; } -} // namespace linearProgramming -} // namespace aliceVision +} // namespace linearProgramming +} // namespace aliceVision diff --git a/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTij.hpp b/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTij.hpp index 8ee4516a2f..81764af95c 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTij.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTij.hpp @@ -17,7 +17,7 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif //------------------ @@ -28,8 +28,8 @@ //- Date: December 2013. //- Conference: ICCV. -namespace aliceVision { -namespace lInfinityCV { +namespace aliceVision { +namespace lInfinityCV { using namespace linearProgramming; @@ -37,121 +37,120 @@ using namespace linearProgramming; // directions in a common global coordinate system. //- Implementation of the LINEAR PROGRAM (8) page 5 of [1]: //-- -inline void EncodeTi_from_tij( - const size_t nTranslation, - const std::vector & vec_relative, - sRMat & A, Vec & C, - std::vector & vec_sign, - std::vector & vec_costs, - std::vector< std::pair > & vec_bounds) +inline void EncodeTi_from_tij(const size_t nTranslation, + const std::vector& vec_relative, + sRMat& A, + Vec& C, + std::vector& vec_sign, + std::vector& vec_costs, + std::vector>& vec_bounds) { - // Build Constraint matrix. + // Build Constraint matrix. - const size_t Ncam = (size_t) nTranslation; - const size_t Nrelative = vec_relative.size(); + const size_t Ncam = (size_t)nTranslation; + const size_t Nrelative = vec_relative.size(); - const size_t transStart = 0; - const size_t lambdaStart = 3 * Ncam; - const size_t gammaStart = lambdaStart + Nrelative; + const size_t transStart = 0; + const size_t lambdaStart = 3 * Ncam; + const size_t gammaStart = lambdaStart + Nrelative; #undef TVAR #undef LAMBDAVAR #undef GAMMAVAR -# define TVAR(i, el) (transStart + 3*(i) + (el)) // translation (X,Y,Z) -# define LAMBDAVAR(j) (lambdaStart + (int)(j)) // One per relative translation -# define GAMMAVAR gammaStart - - const size_t Nconstraint = Nrelative * 6; - const size_t NVar = 3 * Ncam + Nrelative + 1; // { {X,Y,Z}; {Lambdas}; Gamma } - - A.resize(Nconstraint, NVar); - - C.resize(Nconstraint, 1); - C.fill(0.0); - vec_sign.resize(Nconstraint); - - // By default set free variable: - vec_bounds = std::vector< std::pair >(NVar); - fill( vec_bounds.begin(), vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); - // Make first camera at origin (translation ambiguity) - vec_bounds[TVAR(0,0)].first = vec_bounds[TVAR(0,0)].second = 0; - vec_bounds[TVAR(0,1)].first = vec_bounds[TVAR(0,1)].second = 0; - vec_bounds[TVAR(0,2)].first = vec_bounds[TVAR(0,2)].second = 0; - // Make lambda variables between 1 and large number => constraint that lambda_ij > 1 - for (size_t k = 0; k < Nrelative; ++k) - vec_bounds[lambdaStart + k].first = 1; - - // Setup gamma >= 0 - vec_bounds[vec_bounds.size()-1].first = 0.0; - - //-- Minimize gamma - vec_costs.resize(NVar); - std::fill(vec_costs.begin(), vec_costs.end(), 0.0); - vec_costs[GAMMAVAR] = 1.0; - //-- - - size_t rowPos = 0; - - for (size_t k = 0; k < Nrelative; ++k) - { - const size_t i = vec_relative[k].first.first; - const size_t j = vec_relative[k].first.second; - const Mat3 & Rij = vec_relative[k].second.first; - const Vec3 tij = vec_relative[k].second.second; - - // | T_j - R_ij T_i - Lambda_ij t_ij | < Gamma - // Absolute constraint transformed in two sign constraints - // T_j - R_ij T_i - Lambda_ij t_ij < Gamma - // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma - - // For X, Y, Z axis: - for (int l = 0; l < 3; ++l) +#define TVAR(i, el) (transStart + 3 * (i) + (el)) // translation (X,Y,Z) +#define LAMBDAVAR(j) (lambdaStart + (int)(j)) // One per relative translation +#define GAMMAVAR gammaStart + + const size_t Nconstraint = Nrelative * 6; + const size_t NVar = 3 * Ncam + Nrelative + 1; // { {X,Y,Z}; {Lambdas}; Gamma } + + A.resize(Nconstraint, NVar); + + C.resize(Nconstraint, 1); + C.fill(0.0); + vec_sign.resize(Nconstraint); + + // By default set free variable: + vec_bounds = std::vector>(NVar); + fill(vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); + // Make first camera at origin (translation ambiguity) + vec_bounds[TVAR(0, 0)].first = vec_bounds[TVAR(0, 0)].second = 0; + vec_bounds[TVAR(0, 1)].first = vec_bounds[TVAR(0, 1)].second = 0; + vec_bounds[TVAR(0, 2)].first = vec_bounds[TVAR(0, 2)].second = 0; + // Make lambda variables between 1 and large number => constraint that lambda_ij > 1 + for (size_t k = 0; k < Nrelative; ++k) + vec_bounds[lambdaStart + k].first = 1; + + // Setup gamma >= 0 + vec_bounds[vec_bounds.size() - 1].first = 0.0; + + //-- Minimize gamma + vec_costs.resize(NVar); + std::fill(vec_costs.begin(), vec_costs.end(), 0.0); + vec_costs[GAMMAVAR] = 1.0; + //-- + + size_t rowPos = 0; + + for (size_t k = 0; k < Nrelative; ++k) { - // T_j - A.coeffRef(rowPos, TVAR(j, l)) = 1; - - //- R_ij T_i - A.coeffRef(rowPos, TVAR(i, 0)) = - Rij(l, 0); - A.coeffRef(rowPos, TVAR(i, 1)) = - Rij(l, 1); - A.coeffRef(rowPos, TVAR(i, 2)) = - Rij(l, 2); - - // - Lambda_ij t_ij - A.coeffRef(rowPos, LAMBDAVAR(k)) = - tij(l); - - // - gamma - A.coeffRef(rowPos, GAMMAVAR) = -1; - - // < 0 - vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; - C(rowPos) = 0; - ++rowPos; - - // --------- - // Opposite constraint - // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma - // --------- - - // T_j - A.coeffRef(rowPos, TVAR(j, l)) = 1; - - //- R_ij T_i - A.coeffRef(rowPos, TVAR(i, 0)) = - Rij(l, 0); - A.coeffRef(rowPos, TVAR(i, 1)) = - Rij(l, 1); - A.coeffRef(rowPos, TVAR(i, 2)) = - Rij(l, 2); - - // - Lambda_ij t_ij - A.coeffRef(rowPos, LAMBDAVAR(k)) = - tij(l); - - // + gamma - A.coeffRef(rowPos, GAMMAVAR) = 1; - - // > 0 - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; - C(rowPos) = 0; - ++rowPos; - } - } // end for (k) + const size_t i = vec_relative[k].first.first; + const size_t j = vec_relative[k].first.second; + const Mat3& Rij = vec_relative[k].second.first; + const Vec3 tij = vec_relative[k].second.second; + + // | T_j - R_ij T_i - Lambda_ij t_ij | < Gamma + // Absolute constraint transformed in two sign constraints + // T_j - R_ij T_i - Lambda_ij t_ij < Gamma + // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma + + // For X, Y, Z axis: + for (int l = 0; l < 3; ++l) + { + // T_j + A.coeffRef(rowPos, TVAR(j, l)) = 1; + + //- R_ij T_i + A.coeffRef(rowPos, TVAR(i, 0)) = -Rij(l, 0); + A.coeffRef(rowPos, TVAR(i, 1)) = -Rij(l, 1); + A.coeffRef(rowPos, TVAR(i, 2)) = -Rij(l, 2); + + // - Lambda_ij t_ij + A.coeffRef(rowPos, LAMBDAVAR(k)) = -tij(l); + + // - gamma + A.coeffRef(rowPos, GAMMAVAR) = -1; + + // < 0 + vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + C(rowPos) = 0; + ++rowPos; + + // --------- + // Opposite constraint + // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma + // --------- + + // T_j + A.coeffRef(rowPos, TVAR(j, l)) = 1; + + //- R_ij T_i + A.coeffRef(rowPos, TVAR(i, 0)) = -Rij(l, 0); + A.coeffRef(rowPos, TVAR(i, 1)) = -Rij(l, 1); + A.coeffRef(rowPos, TVAR(i, 2)) = -Rij(l, 2); + + // - Lambda_ij t_ij + A.coeffRef(rowPos, LAMBDAVAR(k)) = -tij(l); + + // + gamma + A.coeffRef(rowPos, GAMMAVAR) = 1; + + // > 0 + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + C(rowPos) = 0; + ++rowPos; + } + } // end for (k) #undef TVAR #undef LAMBDAVAR #undef GAMMAVAR @@ -160,52 +159,49 @@ inline void EncodeTi_from_tij( //-- Estimate the translation from heading relative translations. struct Tifromtij_ConstraintBuilder { - Tifromtij_ConstraintBuilder( - const std::vector & vec_relative) - :_vec_relative(vec_relative) - { - //Count the number of camera that are represented - std::set countSet; - for (size_t i = 0; i < vec_relative.size(); ++i) + Tifromtij_ConstraintBuilder(const std::vector& vec_relative) + : _vec_relative(vec_relative) { - countSet.insert(vec_relative[i].first.first); - countSet.insert(vec_relative[i].first.second); + // Count the number of camera that are represented + std::set countSet; + for (size_t i = 0; i < vec_relative.size(); ++i) + { + countSet.insert(vec_relative[i].first.first); + countSet.insert(vec_relative[i].first.second); + } + _Ncam = countSet.size(); } - _Ncam = countSet.size(); -} - /// Setup constraints for the global translations problem, - /// in the LPConstraintsSparse object. - bool Build(LPConstraintsSparse & constraint) - { - EncodeTi_from_tij( - _Ncam, - _vec_relative, - constraint._constraintMat, - constraint._Cst_objective, - constraint._vec_sign, - constraint._vec_cost, - constraint._vec_bounds); - - // it's a minimization problem over the gamma variable - constraint._bminimize = true; - - //-- Setup additional information about the Linear Program constraint. - // We look for : - // - #translations parameters, - // - #relative lambda factors, - // - one gamma parameter. - constraint._nbParams = _Ncam * 3 + _vec_relative.size() + 1; - return true; - } - - // Internal data - size_t _Ncam; - const std::vector & _vec_relative; // /!\ memory Alias -}; + /// Setup constraints for the global translations problem, + /// in the LPConstraintsSparse object. + bool Build(LPConstraintsSparse& constraint) + { + EncodeTi_from_tij(_Ncam, + _vec_relative, + constraint._constraintMat, + constraint._Cst_objective, + constraint._vec_sign, + constraint._vec_cost, + constraint._vec_bounds); + + // it's a minimization problem over the gamma variable + constraint._bminimize = true; + + //-- Setup additional information about the Linear Program constraint. + // We look for : + // - #translations parameters, + // - #relative lambda factors, + // - one gamma parameter. + constraint._nbParams = _Ncam * 3 + _vec_relative.size() + 1; + return true; + } -} // namespace lInfinityCV -} // namespace aliceVision + // Internal data + size_t _Ncam; + const std::vector& _vec_relative; // /!\ memory Alias +}; -#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_GLOBAL_TRANSLATIONS_FROMTIJ_H_ +} // namespace lInfinityCV +} // namespace aliceVision +#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_GLOBAL_TRANSLATIONS_FROMTIJ_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTriplets.hpp b/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTriplets.hpp index b5e33cc2dc..e0459c4bfa 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTriplets.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/global_translations_fromTriplets.hpp @@ -17,7 +17,7 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif //------------------ @@ -28,8 +28,8 @@ //- Date: December 2013. //- Conference: ICCV. -namespace aliceVision { -namespace lInfinityCV { +namespace aliceVision { +namespace lInfinityCV { using namespace linearProgramming; @@ -37,121 +37,120 @@ using namespace linearProgramming; // directions in a common global coordinate system. //- Implementation of the LINEAR PROGRAM (9) page 5 of [1]: //-- -inline void EncodeTi_from_tij_OneLambdaPerTrif( - const size_t nTranslation, - const std::vector & vec_relative, - sRMat & A, Vec & C, - std::vector & vec_sign, - std::vector & vec_costs, - std::vector< std::pair > & vec_bounds) +inline void EncodeTi_from_tij_OneLambdaPerTrif(const size_t nTranslation, + const std::vector& vec_relative, + sRMat& A, + Vec& C, + std::vector& vec_sign, + std::vector& vec_costs, + std::vector>& vec_bounds) { - // Build Constraint matrix. + // Build Constraint matrix. - const size_t Ncam = (size_t) nTranslation; - const size_t Nrelative = vec_relative.size(); + const size_t Ncam = (size_t)nTranslation; + const size_t Nrelative = vec_relative.size(); - const size_t transStart = 0; - const size_t lambdaStart = 3 * Ncam; - const size_t gammaStart = lambdaStart + Nrelative/3; + const size_t transStart = 0; + const size_t lambdaStart = 3 * Ncam; + const size_t gammaStart = lambdaStart + Nrelative / 3; #undef TVAR #undef LAMBDAVAR #undef GAMMAVAR -# define TVAR(i, el) (transStart + 3*(i) + (el)) // translation (X,Y,Z) -# define LAMBDAVAR(j) (lambdaStart + (int)((j)/3)) // One per relative translation -# define GAMMAVAR gammaStart - - const size_t Nconstraint = Nrelative * 6; - const size_t NVar = 3 * Ncam + Nrelative/3 + 1; - - A.resize(Nconstraint, NVar); - - C.resize(Nconstraint, 1); - C.fill(0.0); - vec_sign.resize(Nconstraint); - - // By default set free variable: - vec_bounds = std::vector< std::pair >(NVar); - fill( vec_bounds.begin(), vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); - // Make first camera at origin (translation ambiguity) - vec_bounds[TVAR(0,0)].first = vec_bounds[TVAR(0,0)].second = 0; - vec_bounds[TVAR(0,1)].first = vec_bounds[TVAR(0,1)].second = 0; - vec_bounds[TVAR(0,2)].first = vec_bounds[TVAR(0,2)].second = 0; - // Make lambda variables between 1 and large number => constraint that lambda_ij > 1 - for (size_t k = 0; k < Nrelative/3; ++k) - vec_bounds[lambdaStart + k].first = 1; - - // Setup gamma >= 0 - vec_bounds[vec_bounds.size()-1].first = 0.0; - - //-- Minimize gamma - vec_costs.resize(NVar); - std::fill(vec_costs.begin(), vec_costs.end(), 0.0); - vec_costs[GAMMAVAR] = 1.0; - //-- - - size_t rowPos = 0; - - for (size_t k = 0; k < Nrelative; ++k) - { - const size_t i = vec_relative[k].first.first; - const size_t j = vec_relative[k].first.second; - const Mat3 & Rij = vec_relative[k].second.first; - const Vec3 & tij = vec_relative[k].second.second; - - // | T_j - R_ij T_i - Lambda_ij t_ij | < Gamma - // Absolute constraint transformed in two sign constraints - // T_j - R_ij T_i - Lambda_ij t_ij < Gamma - // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma - - // For X, Y, Z axis: - for (int l = 0; l < 3; ++l) +#define TVAR(i, el) (transStart + 3 * (i) + (el)) // translation (X,Y,Z) +#define LAMBDAVAR(j) (lambdaStart + (int)((j) / 3)) // One per relative translation +#define GAMMAVAR gammaStart + + const size_t Nconstraint = Nrelative * 6; + const size_t NVar = 3 * Ncam + Nrelative / 3 + 1; + + A.resize(Nconstraint, NVar); + + C.resize(Nconstraint, 1); + C.fill(0.0); + vec_sign.resize(Nconstraint); + + // By default set free variable: + vec_bounds = std::vector>(NVar); + fill(vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); + // Make first camera at origin (translation ambiguity) + vec_bounds[TVAR(0, 0)].first = vec_bounds[TVAR(0, 0)].second = 0; + vec_bounds[TVAR(0, 1)].first = vec_bounds[TVAR(0, 1)].second = 0; + vec_bounds[TVAR(0, 2)].first = vec_bounds[TVAR(0, 2)].second = 0; + // Make lambda variables between 1 and large number => constraint that lambda_ij > 1 + for (size_t k = 0; k < Nrelative / 3; ++k) + vec_bounds[lambdaStart + k].first = 1; + + // Setup gamma >= 0 + vec_bounds[vec_bounds.size() - 1].first = 0.0; + + //-- Minimize gamma + vec_costs.resize(NVar); + std::fill(vec_costs.begin(), vec_costs.end(), 0.0); + vec_costs[GAMMAVAR] = 1.0; + //-- + + size_t rowPos = 0; + + for (size_t k = 0; k < Nrelative; ++k) { - // T_j - A.coeffRef(rowPos, TVAR(j, l)) = 1; - - //- R_ij T_i - A.coeffRef(rowPos, TVAR(i, 0)) = - Rij(l, 0); - A.coeffRef(rowPos, TVAR(i, 1)) = - Rij(l, 1); - A.coeffRef(rowPos, TVAR(i, 2)) = - Rij(l, 2); - - // - Lambda_ij t_ij - A.coeffRef(rowPos, LAMBDAVAR(k)) = - tij(l); - - // - gamma - A.coeffRef(rowPos, GAMMAVAR) = -1; - - // < 0 - vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; - C(rowPos) = 0; - ++rowPos; - - // --------- - // Opposite constraint - // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma - // --------- - - // T_j - A.coeffRef(rowPos, TVAR(j, l)) = 1; - - //- R_ij T_i - A.coeffRef(rowPos, TVAR(i, 0)) = - Rij(l, 0); - A.coeffRef(rowPos, TVAR(i, 1)) = - Rij(l, 1); - A.coeffRef(rowPos, TVAR(i, 2)) = - Rij(l, 2); - - // - Lambda_ij t_ij - A.coeffRef(rowPos, LAMBDAVAR(k)) = - tij(l); - - // + gamma - A.coeffRef(rowPos, GAMMAVAR) = 1; - - // > 0 - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; - C(rowPos) = 0; - ++rowPos; - } - } // end for (k) + const size_t i = vec_relative[k].first.first; + const size_t j = vec_relative[k].first.second; + const Mat3& Rij = vec_relative[k].second.first; + const Vec3& tij = vec_relative[k].second.second; + + // | T_j - R_ij T_i - Lambda_ij t_ij | < Gamma + // Absolute constraint transformed in two sign constraints + // T_j - R_ij T_i - Lambda_ij t_ij < Gamma + // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma + + // For X, Y, Z axis: + for (int l = 0; l < 3; ++l) + { + // T_j + A.coeffRef(rowPos, TVAR(j, l)) = 1; + + //- R_ij T_i + A.coeffRef(rowPos, TVAR(i, 0)) = -Rij(l, 0); + A.coeffRef(rowPos, TVAR(i, 1)) = -Rij(l, 1); + A.coeffRef(rowPos, TVAR(i, 2)) = -Rij(l, 2); + + // - Lambda_ij t_ij + A.coeffRef(rowPos, LAMBDAVAR(k)) = -tij(l); + + // - gamma + A.coeffRef(rowPos, GAMMAVAR) = -1; + + // < 0 + vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + C(rowPos) = 0; + ++rowPos; + + // --------- + // Opposite constraint + // T_j - R_ij T_i - Lambda_ij t_ij > - Gamma + // --------- + + // T_j + A.coeffRef(rowPos, TVAR(j, l)) = 1; + + //- R_ij T_i + A.coeffRef(rowPos, TVAR(i, 0)) = -Rij(l, 0); + A.coeffRef(rowPos, TVAR(i, 1)) = -Rij(l, 1); + A.coeffRef(rowPos, TVAR(i, 2)) = -Rij(l, 2); + + // - Lambda_ij t_ij + A.coeffRef(rowPos, LAMBDAVAR(k)) = -tij(l); + + // + gamma + A.coeffRef(rowPos, GAMMAVAR) = 1; + + // > 0 + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + C(rowPos) = 0; + ++rowPos; + } + } // end for (k) #undef TVAR #undef LAMBDAVAR #undef GAMMAVAR @@ -162,52 +161,49 @@ inline void EncodeTi_from_tij_OneLambdaPerTrif( //- of relative motions is kept and colinear motion is supported). struct Tifromtij_ConstraintBuilder_OneLambdaPerTrif { - Tifromtij_ConstraintBuilder_OneLambdaPerTrif( - const std::vector & vec_relative) - :_vec_relative(vec_relative) - { - //Count the number of camera that are represented - std::set countSet; - for (size_t i = 0; i < vec_relative.size(); ++i) + Tifromtij_ConstraintBuilder_OneLambdaPerTrif(const std::vector& vec_relative) + : _vec_relative(vec_relative) { - countSet.insert(vec_relative[i].first.first); - countSet.insert(vec_relative[i].first.second); + // Count the number of camera that are represented + std::set countSet; + for (size_t i = 0; i < vec_relative.size(); ++i) + { + countSet.insert(vec_relative[i].first.first); + countSet.insert(vec_relative[i].first.second); + } + _Ncam = countSet.size(); } - _Ncam = countSet.size(); - } - - /// Setup constraints for the global translations problem, - /// in the LPConstraintsSparse object. - bool Build(LPConstraintsSparse & constraint) - { - EncodeTi_from_tij_OneLambdaPerTrif( - _Ncam, - _vec_relative, - constraint._constraintMat, - constraint._Cst_objective, - constraint._vec_sign, - constraint._vec_cost, - constraint._vec_bounds); - - // it's a minimization problem over the gamma variable - constraint._bminimize = true; - - //-- Setup additional information about the Linear Program constraint. - // We look for : - // - #translations parameters, - // - #relative lambda factors (one per triplet), - // - one gamma parameter. - constraint._nbParams = _Ncam * 3 + _vec_relative.size()/3 + 1; - return true; - } - - // Internal data - size_t _Ncam; - const std::vector & _vec_relative; // /!\ memory Alias -}; -} // namespace lInfinityCV -} // namespace aliceVision + /// Setup constraints for the global translations problem, + /// in the LPConstraintsSparse object. + bool Build(LPConstraintsSparse& constraint) + { + EncodeTi_from_tij_OneLambdaPerTrif(_Ncam, + _vec_relative, + constraint._constraintMat, + constraint._Cst_objective, + constraint._vec_sign, + constraint._vec_cost, + constraint._vec_bounds); + + // it's a minimization problem over the gamma variable + constraint._bminimize = true; + + //-- Setup additional information about the Linear Program constraint. + // We look for : + // - #translations parameters, + // - #relative lambda factors (one per triplet), + // - one gamma parameter. + constraint._nbParams = _Ncam * 3 + _vec_relative.size() / 3 + 1; + return true; + } + + // Internal data + size_t _Ncam; + const std::vector& _vec_relative; // /!\ memory Alias +}; -#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_GLOBAL_TRANSLATIONS_FROMTRIPLETS_H_ +} // namespace lInfinityCV +} // namespace aliceVision +#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_GLOBAL_TRANSLATIONS_FROMTRIPLETS_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInfinityCV.hpp b/src/aliceVision/linearProgramming/lInfinityCV/lInfinityCV.hpp index 24866674f3..5352d61eea 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInfinityCV.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInfinityCV.hpp @@ -27,4 +27,4 @@ // Compute from global translation by using 3-views relative translations guess #include "aliceVision/linearProgramming/lInfinityCV/global_translations_fromTriplets.hpp" -#endif // ALICEVISION_L_INFINITY_COMPUTER_VISION_H_ +#endif // ALICEVISION_L_INFINITY_COMPUTER_VISION_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTij_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTij_test.cpp index 226d0fd1c5..67611645d8 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTij_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTij_test.cpp @@ -21,86 +21,83 @@ using namespace aliceVision; using namespace aliceVision::linearProgramming; using namespace lInfinityCV; -BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs) { - - const int focal = 1000; - const int principal_Point = 500; - //-- Setup a circular camera rig or "cardiod". - const int iNviews = 12; - const int iNbPoints = 6; - - const bool bCardiod = true; - const bool bRelative_Translation_PerTriplet = true; - std::vector vec_relative_estimates; - - const NViewDataSet d = - Setup_RelativeTranslations_AndNviewDataset - ( - vec_relative_estimates, - focal, principal_Point, iNviews, iNbPoints, - bCardiod, bRelative_Translation_PerTriplet - ); - - d.exportToPLY("global_translations_from_Tij_GT.ply"); - visibleCamPosToSVGSurface(d._C, "global_translations_from_Tij_GT.svg"); - - //-- Compute the global translations from the translation heading directions - //- with the L_infinity optimization - // 3*NCam*[X,Y,Z] ; Ncam*[Lambda], [gamma] - std::vector vec_solution(iNviews*3 + vec_relative_estimates.size() + 1); - - //- a. Setup the LP solver, - //- b. Setup the constraints generator (for the dedicated L_inf problem), - //- c. Build constraints and solve the problem, - //- d. Get back the estimated parameters. - - //- a. Setup the LP solver, - OSI_CISolverWrapper solverLP(vec_solution.size()); - - //- b. Setup the constraints generator (for the dedicated L_inf problem), - Tifromtij_ConstraintBuilder cstBuilder(vec_relative_estimates); - - //- c. Build constraints and solve the problem (Setup constraints and solver) - LPConstraintsSparse constraint; - cstBuilder.Build(constraint); - solverLP.setup(constraint); - //-- Solving - BOOST_CHECK(solverLP.solve()); // the linear program must have a solution - - //- d. Get back the estimated parameters. - solverLP.getSolution(vec_solution); - const double gamma = vec_solution[vec_solution.size()-1]; - - //-- - //-- Unit test checking about the found solution - //-- - BOOST_CHECK_SMALL(gamma, 1e-6); // Gamma must be 0, no noise, perfect data have been sent - - ALICEVISION_LOG_DEBUG("Found solution with gamma = " << gamma); - - //-- Get back computed camera translations - std::vector vec_camTranslation(iNviews*3,0); - std::copy(&vec_solution[0], &vec_solution[iNviews*3], &vec_camTranslation[0]); - - //-- Get back computed lambda factors - std::vector vec_camRelLambdas(&vec_solution[iNviews*3], &vec_solution[iNviews*3 + vec_relative_estimates.size()]); - - // Check validity of the camera centers: - // Check the direction since solution if found up to a scale - for (size_t i = 0; i < iNviews; ++i) - { - const Vec3 t(vec_camTranslation[i*3], vec_camTranslation[i*3+1], vec_camTranslation[i*3+2]); - const Mat3 & Ri = d._R[i]; - const Vec3 C_computed = - Ri.transpose() * t; - - const Vec3 C_GT = d._C[i] - d._C[0]; - - //-- Check that found camera position is equal to GT value - if (i==0) { - EXPECT_MATRIX_NEAR(C_computed, C_GT, 1e-6); +BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs) +{ + const int focal = 1000; + const int principal_Point = 500; + //-- Setup a circular camera rig or "cardiod". + const int iNviews = 12; + const int iNbPoints = 6; + + const bool bCardiod = true; + const bool bRelative_Translation_PerTriplet = true; + std::vector vec_relative_estimates; + + const NViewDataSet d = Setup_RelativeTranslations_AndNviewDataset( + vec_relative_estimates, focal, principal_Point, iNviews, iNbPoints, bCardiod, bRelative_Translation_PerTriplet); + + d.exportToPLY("global_translations_from_Tij_GT.ply"); + visibleCamPosToSVGSurface(d._C, "global_translations_from_Tij_GT.svg"); + + //-- Compute the global translations from the translation heading directions + //- with the L_infinity optimization + // 3*NCam*[X,Y,Z] ; Ncam*[Lambda], [gamma] + std::vector vec_solution(iNviews * 3 + vec_relative_estimates.size() + 1); + + //- a. Setup the LP solver, + //- b. Setup the constraints generator (for the dedicated L_inf problem), + //- c. Build constraints and solve the problem, + //- d. Get back the estimated parameters. + + //- a. Setup the LP solver, + OSI_CISolverWrapper solverLP(vec_solution.size()); + + //- b. Setup the constraints generator (for the dedicated L_inf problem), + Tifromtij_ConstraintBuilder cstBuilder(vec_relative_estimates); + + //- c. Build constraints and solve the problem (Setup constraints and solver) + LPConstraintsSparse constraint; + cstBuilder.Build(constraint); + solverLP.setup(constraint); + //-- Solving + BOOST_CHECK(solverLP.solve()); // the linear program must have a solution + + //- d. Get back the estimated parameters. + solverLP.getSolution(vec_solution); + const double gamma = vec_solution[vec_solution.size() - 1]; + + //-- + //-- Unit test checking about the found solution + //-- + BOOST_CHECK_SMALL(gamma, 1e-6); // Gamma must be 0, no noise, perfect data have been sent + + ALICEVISION_LOG_DEBUG("Found solution with gamma = " << gamma); + + //-- Get back computed camera translations + std::vector vec_camTranslation(iNviews * 3, 0); + std::copy(&vec_solution[0], &vec_solution[iNviews * 3], &vec_camTranslation[0]); + + //-- Get back computed lambda factors + std::vector vec_camRelLambdas(&vec_solution[iNviews * 3], &vec_solution[iNviews * 3 + vec_relative_estimates.size()]); + + // Check validity of the camera centers: + // Check the direction since solution if found up to a scale + for (size_t i = 0; i < iNviews; ++i) + { + const Vec3 t(vec_camTranslation[i * 3], vec_camTranslation[i * 3 + 1], vec_camTranslation[i * 3 + 2]); + const Mat3& Ri = d._R[i]; + const Vec3 C_computed = -Ri.transpose() * t; + + const Vec3 C_GT = d._C[i] - d._C[0]; + + //-- Check that found camera position is equal to GT value + if (i == 0) + { + EXPECT_MATRIX_NEAR(C_computed, C_GT, 1e-6); + } + else + { + BOOST_CHECK_SMALL(DistanceLInfinity(C_computed.normalized(), C_GT.normalized()), 1e-6); + } } - else { - BOOST_CHECK_SMALL(DistanceLInfinity(C_computed.normalized(), C_GT.normalized()), 1e-6); - } - } } diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTriplets_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTriplets_test.cpp index 977780a7aa..d1ffcdb3a9 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTriplets_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_global_translations_fromTriplets_test.cpp @@ -21,84 +21,79 @@ using namespace aliceVision; using namespace aliceVision::linearProgramming; using namespace lInfinityCV; -BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_Triplets) { - - const int focal = 1000; - const int principal_Point = 500; - //-- Setup a circular camera rig or "cardiod". - const int iNviews = 12; - const int iNbPoints = 6; - - const bool bCardiod = true; - const bool bRelative_Translation_PerTriplet = true; - std::vector vec_relative_estimates; - - const NViewDataSet d = - Setup_RelativeTranslations_AndNviewDataset - ( - vec_relative_estimates, - focal, principal_Point, iNviews, iNbPoints, - bCardiod, bRelative_Translation_PerTriplet - ); - - d.exportToPLY("global_translations_from_triplets_GT.ply"); - visibleCamPosToSVGSurface(d._C, "global_translations_from_triplets_GT.svg"); - - //-- Compute the global translations from the triplets of heading directions - //- with the L_infinity optimization - - std::vector vec_solution(iNviews*3 + vec_relative_estimates.size()/3 + 1); - double gamma = -1.0; - - //- a. Setup the LP solver, - //- b. Setup the constraints generator (for the dedicated L_inf problem), - //- c. Build constraints and solve the problem, - //- d. Get back the estimated parameters. - - //- a. Setup the LP solver, - OSI_CISolverWrapper solverLP(vec_solution.size()); - - //- b. Setup the constraints generator (for the dedicated L_inf problem), - Tifromtij_ConstraintBuilder_OneLambdaPerTrif cstBuilder(vec_relative_estimates); - - //- c. Build constraints and solve the problem (Setup constraints and solver) - LPConstraintsSparse constraint; - cstBuilder.Build(constraint); - solverLP.setup(constraint); - //-- Solving - BOOST_CHECK(solverLP.solve()); // the linear program must have a solution - - //- d. Get back the estimated parameters. - solverLP.getSolution(vec_solution); - gamma = vec_solution[vec_solution.size()-1]; - - //-- - //-- Unit test checking about the found solution - //-- - BOOST_CHECK_SMALL(gamma, 1e-6); // Gamma must be 0, no noise, perfect data have been sent - - ALICEVISION_LOG_DEBUG("Found solution with gamma = " << gamma); - - //-- Get back computed camera translations - std::vector vec_camTranslation(iNviews*3,0); - std::copy(&vec_solution[0], &vec_solution[iNviews*3], &vec_camTranslation[0]); - - //-- Get back computed lambda factors - std::vector vec_camRelLambdas(&vec_solution[iNviews*3], &vec_solution[iNviews*3 + vec_relative_estimates.size()/3]); - // lambda factors must be equal to 1.0 (no compression, no dilation); - BOOST_CHECK_SMALL((vec_relative_estimates.size()/3)-std::accumulate (vec_camRelLambdas.begin(), vec_camRelLambdas.end(), 0.0), 1e-6); - - // Get back the camera translations in the global frame: - ALICEVISION_LOG_DEBUG(std::endl << "Camera centers (Computed): "); - for (size_t i = 0; i < iNviews; ++i) - { - const Vec3 C_GT = d._C[i] - d._C[0]; //First camera supposed to be at Identity - - const Vec3 t(vec_camTranslation[i*3], vec_camTranslation[i*3+1], vec_camTranslation[i*3+2]); - const Mat3 & Ri = d._R[i]; - const Vec3 C_computed = - Ri.transpose() * t; - - //-- Check that found camera position is equal to GT value - BOOST_CHECK_SMALL(DistanceLInfinity(C_computed, C_GT), 1e-6); - } +BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_Triplets) +{ + const int focal = 1000; + const int principal_Point = 500; + //-- Setup a circular camera rig or "cardiod". + const int iNviews = 12; + const int iNbPoints = 6; + + const bool bCardiod = true; + const bool bRelative_Translation_PerTriplet = true; + std::vector vec_relative_estimates; + + const NViewDataSet d = Setup_RelativeTranslations_AndNviewDataset( + vec_relative_estimates, focal, principal_Point, iNviews, iNbPoints, bCardiod, bRelative_Translation_PerTriplet); + + d.exportToPLY("global_translations_from_triplets_GT.ply"); + visibleCamPosToSVGSurface(d._C, "global_translations_from_triplets_GT.svg"); + + //-- Compute the global translations from the triplets of heading directions + //- with the L_infinity optimization + + std::vector vec_solution(iNviews * 3 + vec_relative_estimates.size() / 3 + 1); + double gamma = -1.0; + + //- a. Setup the LP solver, + //- b. Setup the constraints generator (for the dedicated L_inf problem), + //- c. Build constraints and solve the problem, + //- d. Get back the estimated parameters. + + //- a. Setup the LP solver, + OSI_CISolverWrapper solverLP(vec_solution.size()); + + //- b. Setup the constraints generator (for the dedicated L_inf problem), + Tifromtij_ConstraintBuilder_OneLambdaPerTrif cstBuilder(vec_relative_estimates); + + //- c. Build constraints and solve the problem (Setup constraints and solver) + LPConstraintsSparse constraint; + cstBuilder.Build(constraint); + solverLP.setup(constraint); + //-- Solving + BOOST_CHECK(solverLP.solve()); // the linear program must have a solution + + //- d. Get back the estimated parameters. + solverLP.getSolution(vec_solution); + gamma = vec_solution[vec_solution.size() - 1]; + + //-- + //-- Unit test checking about the found solution + //-- + BOOST_CHECK_SMALL(gamma, 1e-6); // Gamma must be 0, no noise, perfect data have been sent + + ALICEVISION_LOG_DEBUG("Found solution with gamma = " << gamma); + + //-- Get back computed camera translations + std::vector vec_camTranslation(iNviews * 3, 0); + std::copy(&vec_solution[0], &vec_solution[iNviews * 3], &vec_camTranslation[0]); + + //-- Get back computed lambda factors + std::vector vec_camRelLambdas(&vec_solution[iNviews * 3], &vec_solution[iNviews * 3 + vec_relative_estimates.size() / 3]); + // lambda factors must be equal to 1.0 (no compression, no dilation); + BOOST_CHECK_SMALL((vec_relative_estimates.size() / 3) - std::accumulate(vec_camRelLambdas.begin(), vec_camRelLambdas.end(), 0.0), 1e-6); + + // Get back the camera translations in the global frame: + ALICEVISION_LOG_DEBUG(std::endl << "Camera centers (Computed): "); + for (size_t i = 0; i < iNviews; ++i) + { + const Vec3 C_GT = d._C[i] - d._C[0]; // First camera supposed to be at Identity + + const Vec3 t(vec_camTranslation[i * 3], vec_camTranslation[i * 3 + 1], vec_camTranslation[i * 3 + 2]); + const Mat3& Ri = d._R[i]; + const Vec3 C_computed = -Ri.transpose() * t; + + //-- Check that found camera position is equal to GT value + BOOST_CHECK_SMALL(DistanceLInfinity(C_computed, C_GT), 1e-6); + } } diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_robust_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_robust_test.cpp index 4a4f59ffbe..91fb992883 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_robust_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_robust_test.cpp @@ -23,102 +23,101 @@ using namespace aliceVision; using namespace aliceVision::robustEstimation; -BOOST_AUTO_TEST_CASE(Resection_L_Infinity_Robust_OutlierFree) { - std::mt19937 randomNumberGenerator; - const int nViews = 3; - const int nbPoints = 10; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - //-- Modify a dataset (set to 0 and parse new value) (Assert good values) - NViewDataSet d2 = d; - - const int nResectionCameraIndex = 2; - //-- Set to 0 the future computed data to be sure of computation results : - d2._R[nResectionCameraIndex] = Mat3::Zero(); - d2._t[nResectionCameraIndex] = Vec3::Zero(); - - // Solve the problem and check that fitted value are good enough - { - typedef lInfinityCV::kernel::l1PoseResectionKernel KernelType; - const Mat& pt2D = d2._x[nResectionCameraIndex]; - const Mat& pt3D = d2._X; - - KernelType kernel(pt2D, pt3D); - ScoreEvaluator scorer(2*Square(0.6)); - robustEstimation::Mat34Model P = maxConsensus(kernel, scorer, randomNumberGenerator, nullptr, 128); - - // Check that Projection matrix is near to the GT : - Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() - / d.P(nResectionCameraIndex).norm(); - Mat34 COMPUTED_ProjectionMatrix = P.getMatrix().array() / P.getMatrix().norm(); - - // Extract K[R|t] - Mat3 R,K; - Vec3 t; - KRt_from_P(P.getMatrix(), K, R, t); - - d2._R[nResectionCameraIndex] = R; - d2._t[nResectionCameraIndex] = t; - - //CHeck matrix to GT, and residual - BOOST_CHECK_SMALL(FrobeniusDistance(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix), 1e-1 ); - BOOST_CHECK_SMALL(reprojectionErrorRMSE(pt2D, pt3D.colwise().homogeneous(), COMPUTED_ProjectionMatrix), 1e-1); - } +BOOST_AUTO_TEST_CASE(Resection_L_Infinity_Robust_OutlierFree) +{ + std::mt19937 randomNumberGenerator; + const int nViews = 3; + const int nbPoints = 10; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K + + //-- Modify a dataset (set to 0 and parse new value) (Assert good values) + NViewDataSet d2 = d; + + const int nResectionCameraIndex = 2; + //-- Set to 0 the future computed data to be sure of computation results : + d2._R[nResectionCameraIndex] = Mat3::Zero(); + d2._t[nResectionCameraIndex] = Vec3::Zero(); + + // Solve the problem and check that fitted value are good enough + { + typedef lInfinityCV::kernel::l1PoseResectionKernel KernelType; + const Mat& pt2D = d2._x[nResectionCameraIndex]; + const Mat& pt3D = d2._X; + + KernelType kernel(pt2D, pt3D); + ScoreEvaluator scorer(2 * Square(0.6)); + robustEstimation::Mat34Model P = maxConsensus(kernel, scorer, randomNumberGenerator, nullptr, 128); + + // Check that Projection matrix is near to the GT : + Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() / d.P(nResectionCameraIndex).norm(); + Mat34 COMPUTED_ProjectionMatrix = P.getMatrix().array() / P.getMatrix().norm(); + + // Extract K[R|t] + Mat3 R, K; + Vec3 t; + KRt_from_P(P.getMatrix(), K, R, t); + + d2._R[nResectionCameraIndex] = R; + d2._t[nResectionCameraIndex] = t; + + // CHeck matrix to GT, and residual + BOOST_CHECK_SMALL(FrobeniusDistance(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix), 1e-1); + BOOST_CHECK_SMALL(reprojectionErrorRMSE(pt2D, pt3D.colwise().homogeneous(), COMPUTED_ProjectionMatrix), 1e-1); + } } BOOST_AUTO_TEST_CASE(Resection_L_Infinity_Robust_OneOutlier) { - std::mt19937 randomNumberGenerator; - - const int nViews = 3; - const int nbPoints = 20; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - d.exportToPLY("test_Before_Infinity.ply"); - //-- Modify a dataset (set to 0 and parse new value) (Assert good values) - NViewDataSet d2 = d; - - const int nResectionCameraIndex = 2; - //-- Set to 0 the future computed data to be sure of computation results : - d2._R[nResectionCameraIndex] = Mat3::Zero(); - d2._t[nResectionCameraIndex] = Vec3::Zero(); - - // Set 20% of the 3D point as outlier - const int nbOutlier = nbPoints*0.2; - for (int i=0; i < nbOutlier; ++i) - { - d2._X.col(i)(0) += 120.0; - d2._X.col(i)(1) += -60.0; - d2._X.col(i)(2) += 80.0; - } - - // Solve the problem and check that fitted value are good enough - { - typedef lInfinityCV::kernel::l1PoseResectionKernel KernelType; - const Mat & pt2D = d2._x[nResectionCameraIndex]; - const Mat & pt3D = d2._X; - KernelType kernel(pt2D, pt3D); - ScoreEvaluator scorer(Square(0.1)); //Highly intolerant for the test - robustEstimation::Mat34Model P = maxConsensus(kernel, scorer, randomNumberGenerator, nullptr, 128); - - // Check that Projection matrix is near to the GT : - Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() - / d.P(nResectionCameraIndex).norm(); - Mat34 estimatedProjectionMatrix = P.getMatrix().array() / P.getMatrix().norm(); - - // Extract K[R|t] - Mat3 R,K; - Vec3 t; - KRt_from_P(P.getMatrix(), K, R, t); - - d2._R[nResectionCameraIndex] = R; - d2._t[nResectionCameraIndex] = t; - - //CHeck matrix to GT, and residual - BOOST_CHECK_SMALL(FrobeniusDistance(GT_ProjectionMatrix, estimatedProjectionMatrix), 1e-1 ); - BOOST_CHECK_SMALL(reprojectionErrorRMSE(pt2D, pt3D.colwise().homogeneous(), estimatedProjectionMatrix), 0.75); - } - d2.exportToPLY("test_After_Infinity.ply"); + std::mt19937 randomNumberGenerator; + + const int nViews = 3; + const int nbPoints = 20; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K + + d.exportToPLY("test_Before_Infinity.ply"); + //-- Modify a dataset (set to 0 and parse new value) (Assert good values) + NViewDataSet d2 = d; + + const int nResectionCameraIndex = 2; + //-- Set to 0 the future computed data to be sure of computation results : + d2._R[nResectionCameraIndex] = Mat3::Zero(); + d2._t[nResectionCameraIndex] = Vec3::Zero(); + + // Set 20% of the 3D point as outlier + const int nbOutlier = nbPoints * 0.2; + for (int i = 0; i < nbOutlier; ++i) + { + d2._X.col(i)(0) += 120.0; + d2._X.col(i)(1) += -60.0; + d2._X.col(i)(2) += 80.0; + } + + // Solve the problem and check that fitted value are good enough + { + typedef lInfinityCV::kernel::l1PoseResectionKernel KernelType; + const Mat& pt2D = d2._x[nResectionCameraIndex]; + const Mat& pt3D = d2._X; + KernelType kernel(pt2D, pt3D); + ScoreEvaluator scorer(Square(0.1)); // Highly intolerant for the test + robustEstimation::Mat34Model P = maxConsensus(kernel, scorer, randomNumberGenerator, nullptr, 128); + + // Check that Projection matrix is near to the GT : + Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() / d.P(nResectionCameraIndex).norm(); + Mat34 estimatedProjectionMatrix = P.getMatrix().array() / P.getMatrix().norm(); + + // Extract K[R|t] + Mat3 R, K; + Vec3 t; + KRt_from_P(P.getMatrix(), K, R, t); + + d2._R[nResectionCameraIndex] = R; + d2._t[nResectionCameraIndex] = t; + + // CHeck matrix to GT, and residual + BOOST_CHECK_SMALL(FrobeniusDistance(GT_ProjectionMatrix, estimatedProjectionMatrix), 1e-1); + BOOST_CHECK_SMALL(reprojectionErrorRMSE(pt2D, pt3D.colwise().homogeneous(), estimatedProjectionMatrix), 0.75); + } + d2.exportToPLY("test_After_Infinity.ply"); } diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_test.cpp index 310e0ba0f4..eb2a64b138 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_resection_test.cpp @@ -15,12 +15,11 @@ #include "aliceVision/linearProgramming/ISolver.hpp" #include "aliceVision/linearProgramming/OSIXSolver.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" -#endif // ALICEVISION_HAVE_MOSEK + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" +#endif // ALICEVISION_HAVE_MOSEK #include "aliceVision/linearProgramming/bisectionLP.hpp" #include "aliceVision/linearProgramming/lInfinityCV/resection.hpp" - #define BOOST_TEST_MODULE ResectionLInfinity #include @@ -32,126 +31,103 @@ using namespace aliceVision; using namespace linearProgramming; using namespace lInfinityCV; -void translate(const Mat3X & X, const Vec3 & vecTranslation, - Mat3X * XPoints) { +void translate(const Mat3X& X, const Vec3& vecTranslation, Mat3X* XPoints) +{ XPoints->resize(X.rows(), X.cols()); - for (size_t i=0; i<(size_t)X.cols(); ++i) { - XPoints->col(i) = X.col(i) + vecTranslation; + for (size_t i = 0; i < (size_t)X.cols(); ++i) + { + XPoints->col(i) = X.col(i) + vecTranslation; } } -BOOST_AUTO_TEST_CASE(Resection_L_Infinity_OSICLP) { - - const int nViews = 3; - const int nbPoints = 10; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - d.exportToPLY("test_Before_Infinity.ply"); - //-- Modify a dataset (set to 0 and parse new value) (Assert good values) - NViewDataSet d2 = d; - - const int nResectionCameraIndex = 2; - //-- Set to 0 the future computed data to be sure of computation results : - d2._R[nResectionCameraIndex] = Mat3::Zero(); - d2._t[nResectionCameraIndex] = Vec3::Zero(); - - // Solve the problem and check that fitted value are good enough - { - std::vector vec_solution(11); - - //-- Translate 3D point in order to have X0 = (0,0,0,1). - Vec3 vecTranslation = - d2._X.col(0); - Mat4 translationMatrix = Mat4::Identity(); - translationMatrix << 1, 0, 0, vecTranslation(0), - 0, 1, 0, vecTranslation(1), - 0, 0, 1, vecTranslation(2), - 0, 0, 0, 1; - Mat3X XPoints; - translate(d2._X, vecTranslation, &XPoints); - - OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); - Resection_L1_ConstraintBuilder cstBuilder(d2._x[nResectionCameraIndex], XPoints); - BOOST_CHECK( - (BisectionLP( - wrapperOSICLPSolver, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - // Move computed value to dataset for residual estimation. - Mat34 P; - P << vec_solution[0], vec_solution[1], vec_solution[2], vec_solution[3], - vec_solution[4], vec_solution[5], vec_solution[6], vec_solution[7], - vec_solution[8], vec_solution[9], vec_solution[10], 1.0; - P = P * translationMatrix; - - // Check that Projection matrix is near to the GT : - Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() - / d.P(nResectionCameraIndex).norm(); - Mat34 COMPUTED_ProjectionMatrix = P.array() / P.norm(); - EXPECT_MATRIX_NEAR(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix, 1e-4); - } - d2.exportToPLY("test_After_Infinity.ply"); +BOOST_AUTO_TEST_CASE(Resection_L_Infinity_OSICLP) +{ + const int nViews = 3; + const int nbPoints = 10; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K + + d.exportToPLY("test_Before_Infinity.ply"); + //-- Modify a dataset (set to 0 and parse new value) (Assert good values) + NViewDataSet d2 = d; + + const int nResectionCameraIndex = 2; + //-- Set to 0 the future computed data to be sure of computation results : + d2._R[nResectionCameraIndex] = Mat3::Zero(); + d2._t[nResectionCameraIndex] = Vec3::Zero(); + + // Solve the problem and check that fitted value are good enough + { + std::vector vec_solution(11); + + //-- Translate 3D point in order to have X0 = (0,0,0,1). + Vec3 vecTranslation = -d2._X.col(0); + Mat4 translationMatrix = Mat4::Identity(); + translationMatrix << 1, 0, 0, vecTranslation(0), 0, 1, 0, vecTranslation(1), 0, 0, 1, vecTranslation(2), 0, 0, 0, 1; + Mat3X XPoints; + translate(d2._X, vecTranslation, &XPoints); + + OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); + Resection_L1_ConstraintBuilder cstBuilder(d2._x[nResectionCameraIndex], XPoints); + BOOST_CHECK((BisectionLP(wrapperOSICLPSolver, cstBuilder, &vec_solution, 1.0, 0.0))); + + // Move computed value to dataset for residual estimation. + Mat34 P; + P << vec_solution[0], vec_solution[1], vec_solution[2], vec_solution[3], vec_solution[4], vec_solution[5], vec_solution[6], vec_solution[7], + vec_solution[8], vec_solution[9], vec_solution[10], 1.0; + P = P * translationMatrix; + + // Check that Projection matrix is near to the GT : + Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() / d.P(nResectionCameraIndex).norm(); + Mat34 COMPUTED_ProjectionMatrix = P.array() / P.norm(); + EXPECT_MATRIX_NEAR(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix, 1e-4); + } + d2.exportToPLY("test_After_Infinity.ply"); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -BOOST_AUTO_TEST_CASE(Resection_L_Infinity_MOSEK) { - - const int nViews = 3; - const int nbPoints = 10; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - d.exportToPLY("test_Before_Infinity.ply"); - //-- Modify a dataset (set to 0 and parse new value) (Assert good values) - NViewDataSet d2 = d; - - const int nResectionCameraIndex = 2; - //-- Set to 0 the future computed data to be sure of computation results : - d2._R[nResectionCameraIndex] = Mat3::Zero(); - d2._t[nResectionCameraIndex] = Vec3::Zero(); - - // Solve the problem and check that fitted value are good enough - { - std::vector vec_solution(11); - - //-- Translate 3D point in order to have X0 = (0,0,0,1). - Vec3 vecTranslation = - d2._X.col(0); - Mat4 translationMatrix = Mat4::Identity(); - translationMatrix << 1, 0, 0, vecTranslation(0), - 0, 1, 0, vecTranslation(1), - 0, 0, 1, vecTranslation(2), - 0, 0, 0, 1; - Mat3X XPoints; - translate(d2._X, vecTranslation, &XPoints); - - MOSEKSolver wrapperMosek(vec_solution.size()); - Resection_L1_ConstraintBuilder cstBuilder(d2._x[nResectionCameraIndex], XPoints); - BOOST_CHECK( - (BisectionLP( - wrapperMosek, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - // Move computed value to dataset for residual estimation. - Mat34 P; - P << vec_solution[0], vec_solution[1], vec_solution[2], vec_solution[3], - vec_solution[4], vec_solution[5], vec_solution[6], vec_solution[7], - vec_solution[8], vec_solution[9], vec_solution[10], 1.0; - P = P * translationMatrix; - - // Check that Projection matrix is near to the GT : - Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() - / d.P(nResectionCameraIndex).norm(); - Mat34 COMPUTED_ProjectionMatrix = P.array() / P.norm(); - EXPECT_MATRIX_NEAR(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix, 1e-4); - } - d2.exportToPLY("test_After_Infinity.ply"); +BOOST_AUTO_TEST_CASE(Resection_L_Infinity_MOSEK) +{ + const int nViews = 3; + const int nbPoints = 10; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K + + d.exportToPLY("test_Before_Infinity.ply"); + //-- Modify a dataset (set to 0 and parse new value) (Assert good values) + NViewDataSet d2 = d; + + const int nResectionCameraIndex = 2; + //-- Set to 0 the future computed data to be sure of computation results : + d2._R[nResectionCameraIndex] = Mat3::Zero(); + d2._t[nResectionCameraIndex] = Vec3::Zero(); + + // Solve the problem and check that fitted value are good enough + { + std::vector vec_solution(11); + + //-- Translate 3D point in order to have X0 = (0,0,0,1). + Vec3 vecTranslation = -d2._X.col(0); + Mat4 translationMatrix = Mat4::Identity(); + translationMatrix << 1, 0, 0, vecTranslation(0), 0, 1, 0, vecTranslation(1), 0, 0, 1, vecTranslation(2), 0, 0, 0, 1; + Mat3X XPoints; + translate(d2._X, vecTranslation, &XPoints); + + MOSEKSolver wrapperMosek(vec_solution.size()); + Resection_L1_ConstraintBuilder cstBuilder(d2._x[nResectionCameraIndex], XPoints); + BOOST_CHECK((BisectionLP(wrapperMosek, cstBuilder, &vec_solution, 1.0, 0.0))); + + // Move computed value to dataset for residual estimation. + Mat34 P; + P << vec_solution[0], vec_solution[1], vec_solution[2], vec_solution[3], vec_solution[4], vec_solution[5], vec_solution[6], vec_solution[7], + vec_solution[8], vec_solution[9], vec_solution[10], 1.0; + P = P * translationMatrix; + + // Check that Projection matrix is near to the GT : + Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() / d.P(nResectionCameraIndex).norm(); + Mat34 COMPUTED_ProjectionMatrix = P.array() / P.norm(); + EXPECT_MATRIX_NEAR(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix, 1e-4); + } + d2.exportToPLY("test_After_Infinity.ply"); } -#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) +#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_outlier_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_outlier_test.cpp index f2409ae49f..f1d44fa2f8 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_outlier_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_outlier_test.cpp @@ -15,7 +15,7 @@ #include "aliceVision/linearProgramming/ISolver.hpp" #include "aliceVision/linearProgramming/OSIXSolver.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" #endif #include "aliceVision/linearProgramming/bisectionLP.hpp" @@ -34,178 +34,174 @@ using namespace aliceVision; using namespace linearProgramming; using namespace lInfinityCV; -BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_OSICLP_SOLVER) { - - const int nViews = 5; - const int nbPoints = 5; - const double focalValue = 1000; - const double cx = 500, cy = 500; - - const NViewDataSet d = - //NRealisticCamerasRing(nViews, nbPoints, - NRealisticCamerasCardioid(nViews, nbPoints, - NViewDatasetConfigurator(focalValue,focalValue,cx,cy,5,0)); - - d.exportToPLY("test_Before_Infinity.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; - - //-- Set to 0 the future computed data to be sure of computation results : - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - fill(d2._t.begin(), d2._t.end(), Vec3(0.0,0.0,0.0)); - - { - // Prepare Rotation matrix (premultiplied by K) - // Apply the K matrix to the rotation - Mat3 K; - K << focalValue, 0, cx, - 0, focalValue, cy, - 0, 0, 1; - std::vector vec_KRotation(nViews); - for (size_t i = 0; i < nViews; ++i) { - vec_KRotation[i] = K * d._R[i]; - } +BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_OSICLP_SOLVER) +{ + const int nViews = 5; + const int nbPoints = 5; + const double focalValue = 1000; + const double cx = 500, cy = 500; + + const NViewDataSet d = + // NRealisticCamerasRing(nViews, nbPoints, + NRealisticCamerasCardioid(nViews, nbPoints, NViewDatasetConfigurator(focalValue, focalValue, cx, cy, 5, 0)); + + d.exportToPLY("test_Before_Infinity.ply"); + //-- Test triangulation of all the point + NViewDataSet d2 = d; - //set two point as outlier - d2._x[0].col(0)(0) +=10; //Camera 0, measurement 0, noise on X coord - d2._x[3].col(3)(1) -=8; //Camera 3, measurement 3, noise on Y coord + //-- Set to 0 the future computed data to be sure of computation results : + d2._X.fill(0); // Set _Xi of dataset 2 to 0 to be sure of new data computation + fill(d2._t.begin(), d2._t.end(), Vec3(0.0, 0.0, 0.0)); - //Create the mega matrix - Mat megaMat(4, d._n*d._x[0].cols()); { - int cpt = 0; - for (int i=0; i vec_KRotation(nViews); + for (size_t i = 0; i < nViews; ++i) { - megaMat(0,cpt) = d2._x[camIndex].col(j)(0); - megaMat(1,cpt) = d2._x[camIndex].col(j)(1); - megaMat(2,cpt) = j; - megaMat(3,cpt) = camIndex; - cpt++; + vec_KRotation[i] = K * d._R[i]; } - } - } - double admissibleResidual = 1.0/focalValue; - std::vector vec_solution((nViews + nbPoints + megaMat.cols())*3); - OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); - TiXi_withNoise_L1_ConstraintBuilder cstBuilder( vec_KRotation, megaMat); - const bool bisectionRes = BisectionLP( - wrapperOSICLPSolver, - cstBuilder, - &vec_solution, - admissibleResidual, - 0.0, 1e-8); - ALICEVISION_LOG_DEBUG("Bisection returns : " << bisectionRes); + // set two point as outlier + d2._x[0].col(0)(0) += 10; // Camera 0, measurement 0, noise on X coord + d2._x[3].col(3)(1) -= 8; // Camera 3, measurement 3, noise on Y coord - ALICEVISION_LOG_DEBUG("Found solution:" << vec_solution); + // Create the mega matrix + Mat megaMat(4, d._n * d._x[0].cols()); + { + int cpt = 0; + for (int i = 0; i < d._n; ++i) + { + const int camIndex = i; + for (int j = 0; j < d._x[0].cols(); ++j) + { + megaMat(0, cpt) = d2._x[camIndex].col(j)(0); + megaMat(1, cpt) = d2._x[camIndex].col(j)(1); + megaMat(2, cpt) = j; + megaMat(3, cpt) = camIndex; + cpt++; + } + } + } - //-- First the ti and after the Xi : + double admissibleResidual = 1.0 / focalValue; + std::vector vec_solution((nViews + nbPoints + megaMat.cols()) * 3); + OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); + TiXi_withNoise_L1_ConstraintBuilder cstBuilder(vec_KRotation, megaMat); + const bool bisectionRes = BisectionLP( + wrapperOSICLPSolver, cstBuilder, &vec_solution, admissibleResidual, 0.0, 1e-8); + ALICEVISION_LOG_DEBUG("Bisection returns : " << bisectionRes); - //-- Fill the ti - for (int i=0; i < nViews; ++i) { - d2._t[i] = K.inverse() * Vec3(vec_solution[3*i], vec_solution[3*i+1], vec_solution[3*i+2]); - // Change Ci to -Ri*Ci - d2._C[i] = -d2._R[i].inverse() * d2._t[i]; - } + ALICEVISION_LOG_DEBUG("Found solution:" << vec_solution); - for (int i=0; i < nbPoints; ++i) { - size_t index = 3*nViews; - d2._X.col(i) = Vec3(vec_solution[index+i*3], vec_solution[index+i*3+1], vec_solution[index+i*3+2]); - } + //-- First the ti and after the Xi : - // Compute residuals L2 from estimated parameter values : - ALICEVISION_LOG_DEBUG("Residual : "); - Vec2 xk; - double xsum = 0.0; - for (int i = 0; i < d2._n; ++i) { - ALICEVISION_LOG_DEBUG_OBJ << "Camera : " << i << " \t:"; - for(int k = 0; k < d._x[0].cols(); ++k) + //-- Fill the ti + for (int i = 0; i < nViews; ++i) { - xk = project(d2.P(i), Vec3(d2._X.col(k))); - double residual = (xk - d2._x[i].col(k)).norm(); - ALICEVISION_LOG_DEBUG_OBJ << Vec2(( xk - d2._x[i].col(k)).array().pow(2)).array().sqrt().mean() <<"\t"; - //-- Check that were measurement are not noisy the residual is small - // check were the measurement are noisy, the residual is important - //if ((i != 0 && k != 0) || (i!=3 && k !=3)) - if ((i != 0 && k != 0) && (i!=3 && k !=3)) { - BOOST_CHECK_SMALL(residual, 1e-5); - xsum += residual; - } + d2._t[i] = K.inverse() * Vec3(vec_solution[3 * i], vec_solution[3 * i + 1], vec_solution[3 * i + 2]); + // Change Ci to -Ri*Ci + d2._C[i] = -d2._R[i].inverse() * d2._t[i]; + } + + for (int i = 0; i < nbPoints; ++i) + { + size_t index = 3 * nViews; + d2._X.col(i) = Vec3(vec_solution[index + i * 3], vec_solution[index + i * 3 + 1], vec_solution[index + i * 3 + 2]); } - ALICEVISION_LOG_DEBUG_OBJ << std::endl; + + // Compute residuals L2 from estimated parameter values : + ALICEVISION_LOG_DEBUG("Residual : "); + Vec2 xk; + double xsum = 0.0; + for (int i = 0; i < d2._n; ++i) + { + ALICEVISION_LOG_DEBUG_OBJ << "Camera : " << i << " \t:"; + for (int k = 0; k < d._x[0].cols(); ++k) + { + xk = project(d2.P(i), Vec3(d2._X.col(k))); + double residual = (xk - d2._x[i].col(k)).norm(); + ALICEVISION_LOG_DEBUG_OBJ << Vec2((xk - d2._x[i].col(k)).array().pow(2)).array().sqrt().mean() << "\t"; + //-- Check that were measurement are not noisy the residual is small + // check were the measurement are noisy, the residual is important + // if ((i != 0 && k != 0) || (i!=3 && k !=3)) + if ((i != 0 && k != 0) && (i != 3 && k != 3)) + { + BOOST_CHECK_SMALL(residual, 1e-5); + xsum += residual; + } + } + ALICEVISION_LOG_DEBUG_OBJ << std::endl; + } + double dResidual = xsum / (d2._n * d._x[0].cols()); + ALICEVISION_LOG_DEBUG(std::endl << "Residual mean in not noisy measurement: " << dResidual); + // Check that 2D re-projection and 3D point are near to GT. + BOOST_CHECK_SMALL(dResidual, 1e-1); } - double dResidual = xsum / (d2._n*d._x[0].cols()); - ALICEVISION_LOG_DEBUG(std::endl << "Residual mean in not noisy measurement: " << dResidual); - // Check that 2D re-projection and 3D point are near to GT. - BOOST_CHECK_SMALL(dResidual, 1e-1); - } - d2.exportToPLY("test_After_Infinity.ply"); + d2.exportToPLY("test_After_Infinity.ply"); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_MOSEK) { - - const int nViews = 5; - const int nbPoints = 5; - const double focalValue = 1000; - const double cx = 500, cy = 500; - - const NViewDataSet d = - //NRealisticCamerasRing(nViews, nbPoints, - NRealisticCamerasCardioid(nViews, nbPoints, - NViewDatasetConfigurator(focalValue,focalValue,cx,cy,5,0)); - - d.exportToPLY("test_Before_Infinity.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; - - //-- Set to 0 the future computed data to be sure of computation results : - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - fill(d2._t.begin(), d2._t.end(), Vec3(0.0,0.0,0.0)); - - { - // Prepare Rotation matrix (premultiplied by K) - // Apply the K matrix to the rotation - Mat3 K; - K << focalValue, 0, cx, - 0, focalValue, cy, - 0, 0, 1; - std::vector vec_KRotation(nViews); - for (size_t i = 0; i < nViews; ++i) { - vec_KRotation[i] = K * d._R[i]; - } +BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_MOSEK) +{ + const int nViews = 5; + const int nbPoints = 5; + const double focalValue = 1000; + const double cx = 500, cy = 500; - //set two point as outlier - d2._x[0].col(0)(0) +=10; //Camera 0, measurement 0, noise on X coord - d2._x[3].col(3)(1) -=8; //Camera 3, measurement 3, noise on Y coord + const NViewDataSet d = + // NRealisticCamerasRing(nViews, nbPoints, + NRealisticCamerasCardioid(nViews, nbPoints, NViewDatasetConfigurator(focalValue, focalValue, cx, cy, 5, 0)); + + d.exportToPLY("test_Before_Infinity.ply"); + //-- Test triangulation of all the point + NViewDataSet d2 = d; + + //-- Set to 0 the future computed data to be sure of computation results : + d2._X.fill(0); // Set _Xi of dataset 2 to 0 to be sure of new data computation + fill(d2._t.begin(), d2._t.end(), Vec3(0.0, 0.0, 0.0)); - //Create the mega matrix - Mat megaMat(4, d._n*d._x[0].cols()); { - int cpt = 0; - for (int i=0; i vec_KRotation(nViews); + for (size_t i = 0; i < nViews; ++i) { - megaMat(0,cpt) = d2._x[camIndex].col(j)(0); - megaMat(1,cpt) = d2._x[camIndex].col(j)(1); - megaMat(2,cpt) = j; - megaMat(3,cpt) = camIndex; - cpt++; + vec_KRotation[i] = K * d._R[i]; } - } - } - double admissibleResidual = 1.0/focalValue; - std::vector vec_solution((nViews + nbPoints + megaMat.cols())*3); - MOSEKSolver wrapperMosek(vec_solution.size()); - TiXi_withNoise_L1_ConstraintBuilder cstBuilder( vec_KRotation, megaMat); - ALICEVISION_LOG_DEBUG(std::endl << "Bisection returns : "); + // set two point as outlier + d2._x[0].col(0)(0) += 10; // Camera 0, measurement 0, noise on X coord + d2._x[3].col(3)(1) -= 8; // Camera 3, measurement 3, noise on Y coord + + // Create the mega matrix + Mat megaMat(4, d._n * d._x[0].cols()); + { + int cpt = 0; + for (int i = 0; i < d._n; ++i) + { + const int camIndex = i; + for (int j = 0; j < d._x[0].cols(); ++j) + { + megaMat(0, cpt) = d2._x[camIndex].col(j)(0); + megaMat(1, cpt) = d2._x[camIndex].col(j)(1); + megaMat(2, cpt) = j; + megaMat(3, cpt) = camIndex; + cpt++; + } + } + } + + double admissibleResidual = 1.0 / focalValue; + std::vector vec_solution((nViews + nbPoints + megaMat.cols()) * 3); + MOSEKSolver wrapperMosek(vec_solution.size()); + TiXi_withNoise_L1_ConstraintBuilder cstBuilder(vec_KRotation, megaMat); + ALICEVISION_LOG_DEBUG(std::endl << "Bisection returns : "); ALICEVISION_LOG_DEBUG( BisectionLP( wrapperMosek, cstBuilder, @@ -220,14 +216,14 @@ BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_MOSEK) { //-- Fill the ti for (int i=0; i < nViews; ++i) { - d2._t[i] = K.inverse() * Vec3(vec_solution[3*i], vec_solution[3*i+1], vec_solution[3*i+2]); - // Change Ci to -Ri*Ci - d2._C[i] = -d2._R[i].inverse() * d2._t[i]; + d2._t[i] = K.inverse() * Vec3(vec_solution[3 * i], vec_solution[3 * i + 1], vec_solution[3 * i + 2]); + // Change Ci to -Ri*Ci + d2._C[i] = -d2._R[i].inverse() * d2._t[i]; } for (int i=0; i < nbPoints; ++i) { - size_t index = 3*nViews; - d2._X.col(i) = Vec3(vec_solution[index+i*3], vec_solution[index+i*3+1], vec_solution[index+i*3+2]); + size_t index = 3 * nViews; + d2._X.col(i) = Vec3(vec_solution[index + i * 3], vec_solution[index + i * 3 + 1], vec_solution[index + i * 3 + 2]); } // Compute residuals L2 from estimated parameter values : @@ -238,15 +234,15 @@ BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_MOSEK) { ALICEVISION_LOG_DEBUG("\nCamera : " << i << " \t:"; for(int k = 0; k < d._x[0].cols(); ++k) { - xk = project(d2.P(i), Vec3(d2._X.col(k))); - double residual = (xk - d2._x[i].col(k)).norm(); + xk = project(d2.P(i), Vec3(d2._X.col(k))); + double residual = (xk - d2._x[i].col(k)).norm(); ALICEVISION_LOG_DEBUG(Vec2(( xk - d2._x[i].col(k)).array().pow(2)).array().sqrt().mean() <<"\t"; //-- Check that were measurement are not noisy the residual is small // check were the measurement are noisy, the residual is important //if ((i != 0 && k != 0) || (i!=3 && k !=3)) if ((i != 0 && k != 0) && (i!=3 && k !=3)) { - BOOST_CHECK_SMALL(residual, 1e-6); - xsum += residual; + BOOST_CHECK_SMALL(residual, 1e-6); + xsum += residual; } } ALICEVISION_LOG_DEBUG(std::endl; @@ -255,8 +251,8 @@ BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_Noisy_Outlier_MOSEK) { ALICEVISION_LOG_DEBUG(std::endl << "Residual mean in not noisy measurement: " << dResidual); // Check that 2D re-projection and 3D point are near to GT. BOOST_CHECK_SMALL(dResidual, 1e-1); - } + } - d2.exportToPLY("test_After_Infinity.ply"); + d2.exportToPLY("test_After_Infinity.ply"); } -#endif // ALICEVISION_HAVE_MOSEK +#endif // ALICEVISION_HAVE_MOSEK diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_test.cpp index 410ebeb0a1..cb2d519d4b 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_tijsAndXis_test.cpp @@ -12,7 +12,7 @@ #include "aliceVision/linearProgramming/ISolver.hpp" #include "aliceVision/linearProgramming/OSIXSolver.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" #endif #include "aliceVision/linearProgramming/bisectionLP.hpp" @@ -31,260 +31,248 @@ using namespace aliceVision; using namespace linearProgramming; using namespace lInfinityCV; -BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_OSICLP_SOLVER) { +BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_OSICLP_SOLVER) +{ + const size_t nViews = 3; + const size_t nbPoints = 6; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K - const size_t nViews = 3; - const size_t nbPoints = 6; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K + d.exportToPLY("test_Before_Infinity.ply"); + //-- Test triangulation of all the point + NViewDataSet d2 = d; - d.exportToPLY("test_Before_Infinity.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; + //-- Set to 0 the future computed data to be sure of computation results : + d2._X.fill(0); // Set _Xi of dataset 2 to 0 to be sure of new data computation + fill(d2._t.begin(), d2._t.end(), Vec3(0.0, 0.0, 0.0)); - //-- Set to 0 the future computed data to be sure of computation results : - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - fill(d2._t.begin(), d2._t.end(), Vec3(0.0,0.0,0.0)); - - //Create the mega matrix - Mat megaMat(4, d._n*d._x[0].cols()); - { - size_t cpt = 0; - for (size_t i=0; i vec_solution((nViews + nbPoints)*3); - - OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); - Translation_Structure_L1_ConstraintBuilder cstBuilder( d._R, megaMat); - BOOST_CHECK( - (BisectionLP( - wrapperOSICLPSolver, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - // Move computed value to dataset for residual estimation. + // Create the mega matrix + Mat megaMat(4, d._n * d._x[0].cols()); { - //-- Fill the ti - for (size_t i=0; i < nViews; ++i) - { - size_t index = i*3; - d2._t[i] = Vec3(vec_solution[index], vec_solution[index+1], vec_solution[index+2]); - // Change Ci to -Ri*Ci - d2._C[i] = -d2._R[i] * d2._t[i]; - } - - //-- Now the Xi : - for (size_t i=0; i < nbPoints; ++i) { - size_t index = 3*nViews; - d2._X.col(i) = Vec3(vec_solution[index+i*3], vec_solution[index+i*3+1], vec_solution[index+i*3+2]); - } + size_t cpt = 0; + for (size_t i = 0; i < d._n; ++i) + { + const size_t camIndex = i; + for (size_t j = 0; j < d._x[0].cols(); ++j) + { + megaMat(0, cpt) = d._x[camIndex].col(j)(0); + megaMat(1, cpt) = d._x[camIndex].col(j)(1); + megaMat(2, cpt) = j; + megaMat(3, cpt) = camIndex; + cpt++; + } + } } - // Compute residuals L2 from estimated parameter values : - Vec2 xk, xsum(0.0,0.0); - for (size_t i = 0; i < d2._n; ++i) { - for(size_t k = 0; k < d._x[0].cols(); ++k) - { - xk = project(d2.P(i), Vec3(d2._X.col(k))); - xsum += Vec2(( xk - d2._x[i].col(k)).array().pow(2)); - } + // Solve the problem and check that fitted value are good enough + { + std::vector vec_solution((nViews + nbPoints) * 3); + + OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); + Translation_Structure_L1_ConstraintBuilder cstBuilder(d._R, megaMat); + BOOST_CHECK( + (BisectionLP(wrapperOSICLPSolver, cstBuilder, &vec_solution, 1.0, 0.0))); + + // Move computed value to dataset for residual estimation. + { + //-- Fill the ti + for (size_t i = 0; i < nViews; ++i) + { + size_t index = i * 3; + d2._t[i] = Vec3(vec_solution[index], vec_solution[index + 1], vec_solution[index + 2]); + // Change Ci to -Ri*Ci + d2._C[i] = -d2._R[i] * d2._t[i]; + } + + //-- Now the Xi : + for (size_t i = 0; i < nbPoints; ++i) + { + size_t index = 3 * nViews; + d2._X.col(i) = Vec3(vec_solution[index + i * 3], vec_solution[index + i * 3 + 1], vec_solution[index + i * 3 + 2]); + } + } + + // Compute residuals L2 from estimated parameter values : + Vec2 xk, xsum(0.0, 0.0); + for (size_t i = 0; i < d2._n; ++i) + { + for (size_t k = 0; k < d._x[0].cols(); ++k) + { + xk = project(d2.P(i), Vec3(d2._X.col(k))); + xsum += Vec2((xk - d2._x[i].col(k)).array().pow(2)); + } + } + double dResidual2D = (xsum.array().sqrt().sum()); + + // Check that 2D re-projection and 3D point are near to GT. + BOOST_CHECK_SMALL(dResidual2D, 1e-4); } - double dResidual2D = (xsum.array().sqrt().sum()); - // Check that 2D re-projection and 3D point are near to GT. - BOOST_CHECK_SMALL(dResidual2D, 1e-4); - } - - d2.exportToPLY("test_After_Infinity.ply"); + d2.exportToPLY("test_After_Infinity.ply"); } -BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_OSICLP_SOLVER_K) { - - const size_t nViews = 3; - const size_t nbPoints = 6; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1000,1000,500,500,5,0)); // Suppose a camera with Unit matrix as K +BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_OSICLP_SOLVER_K) +{ + const size_t nViews = 3; + const size_t nbPoints = 6; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1000, 1000, 500, 500, 5, 0)); // Suppose a camera with Unit matrix as K - d.exportToPLY("test_Before_Infinity.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; + d.exportToPLY("test_Before_Infinity.ply"); + //-- Test triangulation of all the point + NViewDataSet d2 = d; - //-- Set to 0 the future computed data to be sure of computation results : - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - fill(d2._t.begin(), d2._t.end(), Vec3(0.0,0.0,0.0)); + //-- Set to 0 the future computed data to be sure of computation results : + d2._X.fill(0); // Set _Xi of dataset 2 to 0 to be sure of new data computation + fill(d2._t.begin(), d2._t.end(), Vec3(0.0, 0.0, 0.0)); - //Create the mega matrix - Mat megaMat(4, d._n*d._x[0].cols()); - { - size_t cpt = 0; - for (size_t i=0; i < d._n;++i) - { - const size_t camIndex = i; - for (size_t j=0; j < (size_t)d._x[0].cols(); ++j) - { - megaMat(0,cpt) = d._x[camIndex].col(j)(0); - megaMat(1,cpt) = d._x[camIndex].col(j)(1); - megaMat(2,cpt) = j; - megaMat(3,cpt) = camIndex; - cpt++; - } - } - } - - // Solve the problem and check that fitted value are good enough - { - std::vector vec_solution((nViews + nbPoints)*3); - - std::vector vec_KR(d._R); - for(int i=0;i < nViews; ++i) - vec_KR[i] = d._K[0] * d._R[i]; - - OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); - Translation_Structure_L1_ConstraintBuilder cstBuilder( vec_KR, megaMat); - BOOST_CHECK( - (BisectionLP( - wrapperOSICLPSolver, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - // Move computed value to dataset for residual estimation. + // Create the mega matrix + Mat megaMat(4, d._n * d._x[0].cols()); { - //-- Fill the ti - for (size_t i=0; i < nViews; ++i) - { - size_t index = i*3; - d2._t[i] = d._K[0].inverse() * Vec3(vec_solution[index], vec_solution[index+1], vec_solution[index+2]); - // Change Ci to -Ri*Ci - d2._C[i] = -d2._R[i] * d2._t[i]; - } - - //-- Now the Xi : - for (size_t i=0; i < nbPoints; ++i) { - size_t index = 3*nViews; - d2._X.col(i) = Vec3(vec_solution[index+i*3], vec_solution[index+i*3+1], vec_solution[index+i*3+2]); - } + size_t cpt = 0; + for (size_t i = 0; i < d._n; ++i) + { + const size_t camIndex = i; + for (size_t j = 0; j < (size_t)d._x[0].cols(); ++j) + { + megaMat(0, cpt) = d._x[camIndex].col(j)(0); + megaMat(1, cpt) = d._x[camIndex].col(j)(1); + megaMat(2, cpt) = j; + megaMat(3, cpt) = camIndex; + cpt++; + } + } } - // Compute residuals L2 from estimated parameter values : - Vec2 xk, xsum(0.0,0.0); - for (size_t i = 0; i < d2._n; ++i) { - for(size_t k = 0; k < (size_t)d._x[0].cols(); ++k) - { - xk = project(d2.P(i), Vec3(d2._X.col(k))); - xsum += Vec2(( xk - d2._x[i].col(k)).array().pow(2)); - } + // Solve the problem and check that fitted value are good enough + { + std::vector vec_solution((nViews + nbPoints) * 3); + + std::vector vec_KR(d._R); + for (int i = 0; i < nViews; ++i) + vec_KR[i] = d._K[0] * d._R[i]; + + OSI_CISolverWrapper wrapperOSICLPSolver(vec_solution.size()); + Translation_Structure_L1_ConstraintBuilder cstBuilder(vec_KR, megaMat); + BOOST_CHECK( + (BisectionLP(wrapperOSICLPSolver, cstBuilder, &vec_solution, 1.0, 0.0))); + + // Move computed value to dataset for residual estimation. + { + //-- Fill the ti + for (size_t i = 0; i < nViews; ++i) + { + size_t index = i * 3; + d2._t[i] = d._K[0].inverse() * Vec3(vec_solution[index], vec_solution[index + 1], vec_solution[index + 2]); + // Change Ci to -Ri*Ci + d2._C[i] = -d2._R[i] * d2._t[i]; + } + + //-- Now the Xi : + for (size_t i = 0; i < nbPoints; ++i) + { + size_t index = 3 * nViews; + d2._X.col(i) = Vec3(vec_solution[index + i * 3], vec_solution[index + i * 3 + 1], vec_solution[index + i * 3 + 2]); + } + } + + // Compute residuals L2 from estimated parameter values : + Vec2 xk, xsum(0.0, 0.0); + for (size_t i = 0; i < d2._n; ++i) + { + for (size_t k = 0; k < (size_t)d._x[0].cols(); ++k) + { + xk = project(d2.P(i), Vec3(d2._X.col(k))); + xsum += Vec2((xk - d2._x[i].col(k)).array().pow(2)); + } + } + double dResidual2D = (xsum.array().sqrt().sum()); + + // Check that 2D re-projection and 3D point are near to GT. + BOOST_CHECK_SMALL(dResidual2D, 1e-4); } - double dResidual2D = (xsum.array().sqrt().sum()); - // Check that 2D re-projection and 3D point are near to GT. - BOOST_CHECK_SMALL(dResidual2D, 1e-4); - } - - d2.exportToPLY("test_After_Infinity.ply"); + d2.exportToPLY("test_After_Infinity.ply"); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -BOOST_AUTO_TEST_CASE(Translation_Structure_L_Infinity_MOSEK) { - - const size_t nViews = 3; - const size_t nbPoints = 6; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - d.exportToPLY("test_Before_Infinity.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; - - //-- Set to 0 the future computed data to be sure of computation results : - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - fill(d2._t.begin(), d2._t.end(), Vec3(0.0,0.0,0.0)); - - //Create the mega matrix - Mat megaMat(4, d._n*d._x[0].cols()); - { - size_t cpt = 0; - for (size_t i=0; i vec_solution((nViews + nbPoints)*3); - - MOSEKSolver wrapperMosek(vec_solution.size()); - Translation_Structure_L1_ConstraintBuilder cstBuilder( d._R, megaMat); - BOOST_CHECK( - (BisectionLP( - wrapperMosek, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - // Move computed value to dataset for residual estimation. - { - //-- Fill the ti - for (size_t i=0; i < nViews; ++i) - { - size_t index = i*3; - d2._t[i] = Vec3(vec_solution[index], vec_solution[index+1], vec_solution[index+2]); - // Change Ci to -Ri*Ci - d2._C[i] = -d2._R[i] * d2._t[i]; - } - - //-- Now the Xi : - for (size_t i=0; i < nbPoints; ++i) { - size_t index = 3*nViews; - d2._X.col(i) = Vec3(vec_solution[index+i*3], vec_solution[index+i*3+1], vec_solution[index+i*3+2]); - } + size_t cpt = 0; + for (size_t i = 0; i < d._n; ++i) + { + const size_t camIndex = i; + for (size_t j = 0; j < d._x[0].cols(); ++j) + { + megaMat(0, cpt) = d._x[camIndex].col(j)(0); + megaMat(1, cpt) = d._x[camIndex].col(j)(1); + megaMat(2, cpt) = j; + megaMat(3, cpt) = camIndex; + cpt++; + } + } } - // Compute residuals L2 from estimated parameter values : - Vec2 xk, xsum(0.0,0.0); - for (size_t i = 0; i < d2._n; ++i) { - for(size_t k = 0; k < d._x[0].cols(); ++k) - { - xk = project(d2.P(i), Vec3(d2._X.col(k))); - xsum += Vec2(( xk - d2._x[i].col(k)).array().pow(2)); - } + // Solve the problem and check that fitted value are good enough + { + std::vector vec_solution((nViews + nbPoints) * 3); + + MOSEKSolver wrapperMosek(vec_solution.size()); + Translation_Structure_L1_ConstraintBuilder cstBuilder(d._R, megaMat); + BOOST_CHECK( + (BisectionLP(wrapperMosek, cstBuilder, &vec_solution, 1.0, 0.0))); + + // Move computed value to dataset for residual estimation. + { + //-- Fill the ti + for (size_t i = 0; i < nViews; ++i) + { + size_t index = i * 3; + d2._t[i] = Vec3(vec_solution[index], vec_solution[index + 1], vec_solution[index + 2]); + // Change Ci to -Ri*Ci + d2._C[i] = -d2._R[i] * d2._t[i]; + } + + //-- Now the Xi : + for (size_t i = 0; i < nbPoints; ++i) + { + size_t index = 3 * nViews; + d2._X.col(i) = Vec3(vec_solution[index + i * 3], vec_solution[index + i * 3 + 1], vec_solution[index + i * 3 + 2]); + } + } + + // Compute residuals L2 from estimated parameter values : + Vec2 xk, xsum(0.0, 0.0); + for (size_t i = 0; i < d2._n; ++i) + { + for (size_t k = 0; k < d._x[0].cols(); ++k) + { + xk = project(d2.P(i), Vec3(d2._X.col(k))); + xsum += Vec2((xk - d2._x[i].col(k)).array().pow(2)); + } + } + double dResidual2D = (xsum.array().sqrt().sum()); + + // Check that 2D re-projection and 3D point are near to GT. + BOOST_CHECK_SMALL(dResidual2D, 1e-4); } - double dResidual2D = (xsum.array().sqrt().sum()); - - // Check that 2D re-projection and 3D point are near to GT. - BOOST_CHECK_SMALL(dResidual2D, 1e-4); - } - d2.exportToPLY("test_After_Infinity.ply"); + d2.exportToPLY("test_After_Infinity.ply"); } -#endif // ALICEVISION_HAVE_MOSEK +#endif // ALICEVISION_HAVE_MOSEK diff --git a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_triangulation_test.cpp b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_triangulation_test.cpp index 75c5dc52db..0a2c022da1 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_triangulation_test.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/lInftyCV_triangulation_test.cpp @@ -17,7 +17,7 @@ #include "aliceVision/linearProgramming/ISolver.hpp" #include "aliceVision/linearProgramming/OSIXSolver.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" #endif #include "aliceVision/linearProgramming/bisectionLP.hpp" @@ -32,122 +32,108 @@ using namespace aliceVision; using namespace linearProgramming; using namespace lInfinityCV; -BOOST_AUTO_TEST_CASE(lInfinityCV_Triangulation_OSICLPSOLVER) { +BOOST_AUTO_TEST_CASE(lInfinityCV_Triangulation_OSICLPSOLVER) +{ + NViewDataSet d = NRealisticCamerasRing(6, 10, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K - NViewDataSet d = NRealisticCamerasRing(6, 10, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K + std::vector vec_Pi; - std::vector vec_Pi; + d.exportToPLY("test_Before_Infinity_Triangulation_OSICLP.ply"); + //-- Test triangulation of all the point + NViewDataSet d2 = d; + d2._X.fill(0); // Set _Xi of dataset 2 to 0 to be sure of new data computation - d.exportToPLY("test_Before_Infinity_Triangulation_OSICLP.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - - for (int i = 0; i < d._n; ++i) - vec_Pi.push_back(d.P(i)); - - for (int k = 0; k < d._x[0].cols(); ++k) - { - Mat2X x_ij; - x_ij.resize(2,d._n); for (int i = 0; i < d._n; ++i) - x_ij.col(i) = d._x[i].col(k); - - std::vector vec_solution(3); - - OSI_CISolverWrapper wrapperOSICLPSolver(3); - Triangulation_L1_ConstraintBuilder cstBuilder(vec_Pi, x_ij); - // Use bisection in order to find the global optimum and so find the - // best triangulated point under the L_infinity norm - BOOST_CHECK( - (BisectionLP( - wrapperOSICLPSolver, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - Vec3 XSolution(vec_solution[0], vec_solution[1], vec_solution[2]); - d2._X.col(k) = XSolution.transpose(); - - // Compute residuals L2 from estimated parameter values : - const Vec3 & X = XSolution; - Vec2 x1, xsum(0.0,0.0); - for (int i = 0; i < d2._n; ++i) { - x1 = project(d2.P(i), X); - xsum += Vec2((x1-x_ij.col(i)).array().pow(2)); + vec_Pi.push_back(d.P(i)); + + for (int k = 0; k < d._x[0].cols(); ++k) + { + Mat2X x_ij; + x_ij.resize(2, d._n); + for (int i = 0; i < d._n; ++i) + x_ij.col(i) = d._x[i].col(k); + + std::vector vec_solution(3); + + OSI_CISolverWrapper wrapperOSICLPSolver(3); + Triangulation_L1_ConstraintBuilder cstBuilder(vec_Pi, x_ij); + // Use bisection in order to find the global optimum and so find the + // best triangulated point under the L_infinity norm + BOOST_CHECK((BisectionLP(wrapperOSICLPSolver, cstBuilder, &vec_solution, 1.0, 0.0))); + + Vec3 XSolution(vec_solution[0], vec_solution[1], vec_solution[2]); + d2._X.col(k) = XSolution.transpose(); + + // Compute residuals L2 from estimated parameter values : + const Vec3& X = XSolution; + Vec2 x1, xsum(0.0, 0.0); + for (int i = 0; i < d2._n; ++i) + { + x1 = project(d2.P(i), X); + xsum += Vec2((x1 - x_ij.col(i)).array().pow(2)); + } + double dResidual2D = (xsum.array().sqrt().sum()); + + // Residual LInfinity between GT 3D point and found one + double dResidual3D = DistanceLInfinity(XSolution, Vec3(d._X.col(k))); + + // Check that 2D re-projection and 3D point are near to GT. + BOOST_CHECK_SMALL(dResidual2D, 1e-5); + BOOST_CHECK_SMALL(dResidual3D, 1e-5); } - double dResidual2D = (xsum.array().sqrt().sum()); - - // Residual LInfinity between GT 3D point and found one - double dResidual3D = DistanceLInfinity(XSolution, Vec3(d._X.col(k))); - - // Check that 2D re-projection and 3D point are near to GT. - BOOST_CHECK_SMALL(dResidual2D, 1e-5); - BOOST_CHECK_SMALL(dResidual3D, 1e-5); - } - d2.exportToPLY("test_After_Infinity_Triangulation_OSICLP.ply"); + d2.exportToPLY("test_After_Infinity_Triangulation_OSICLP.ply"); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -BOOST_AUTO_TEST_CASE(computervision_Triangulation_MOSEK) { +BOOST_AUTO_TEST_CASE(computervision_Triangulation_MOSEK) +{ + NViewDataSet d = NRealisticCamerasRing(6, 10, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K - NViewDataSet d = NRealisticCamerasRing(6, 10, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K + std::vector vec_Pi; - std::vector vec_Pi; + d.exportToPLY("test_Before_Infinity_Triangulation_MOSEK.ply"); + //-- Test triangulation of all the point + NViewDataSet d2 = d; + d2._X.fill(0); // Set _Xi of dataset 2 to 0 to be sure of new data computation - d.exportToPLY("test_Before_Infinity_Triangulation_MOSEK.ply"); - //-- Test triangulation of all the point - NViewDataSet d2 = d; - d2._X.fill(0); //Set _Xi of dataset 2 to 0 to be sure of new data computation - - for (int i = 0; i < d._n; ++i) - vec_Pi.push_back(d.P(i)); - - for (int k = 0; k < d._x[0].cols(); ++k) - { - Mat2X x_ij; - x_ij.resize(2,d._n); for (int i = 0; i < d._n; ++i) - x_ij.col(i) = d._x[i].col(k); - - std::vector vec_solution(3); - - MOSEKSolver wrapperLpSolve(3); - Triangulation_L1_ConstraintBuilder cstBuilder(vec_Pi, x_ij); - // Use bisection in order to find the global optimum and so find the - // best triangulated point under the L_infinity norm - BOOST_CHECK( - (BisectionLP( - wrapperLpSolve, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ); - - Vec3 XSolution(vec_solution[0], vec_solution[1], vec_solution[2]); - d2._X.col(k) = XSolution.transpose(); - - // Compute residuals L2 from estimated parameter values : - const Vec3 & X = XSolution; - Vec2 x1, xsum(0.0,0.0); - for (int i = 0; i < d2._n; ++i) { - x1 = project(d2.P(i), X); - xsum += Vec2((x1-x_ij.col(i)).array().pow(2)); + vec_Pi.push_back(d.P(i)); + + for (int k = 0; k < d._x[0].cols(); ++k) + { + Mat2X x_ij; + x_ij.resize(2, d._n); + for (int i = 0; i < d._n; ++i) + x_ij.col(i) = d._x[i].col(k); + + std::vector vec_solution(3); + + MOSEKSolver wrapperLpSolve(3); + Triangulation_L1_ConstraintBuilder cstBuilder(vec_Pi, x_ij); + // Use bisection in order to find the global optimum and so find the + // best triangulated point under the L_infinity norm + BOOST_CHECK((BisectionLP(wrapperLpSolve, cstBuilder, &vec_solution, 1.0, 0.0))); + + Vec3 XSolution(vec_solution[0], vec_solution[1], vec_solution[2]); + d2._X.col(k) = XSolution.transpose(); + + // Compute residuals L2 from estimated parameter values : + const Vec3& X = XSolution; + Vec2 x1, xsum(0.0, 0.0); + for (int i = 0; i < d2._n; ++i) + { + x1 = project(d2.P(i), X); + xsum += Vec2((x1 - x_ij.col(i)).array().pow(2)); + } + double dResidual2D = (xsum.array().sqrt().sum()); + + // Residual LInfinity between GT 3D point and found one + double dResidual3D = DistanceLInfinity(XSolution, Vec3(d._X.col(k))); + + // Check that 2D re-projection and 3D point are near to GT. + BOOST_CHECK_SMALL(dResidual2D, 1e-5); + BOOST_CHECK_SMALL(dResidual3D, 1e-5); } - double dResidual2D = (xsum.array().sqrt().sum()); - - // Residual LInfinity between GT 3D point and found one - double dResidual3D = DistanceLInfinity(XSolution, Vec3(d._X.col(k))); - - // Check that 2D re-projection and 3D point are near to GT. - BOOST_CHECK_SMALL(dResidual2D, 1e-5); - BOOST_CHECK_SMALL(dResidual3D, 1e-5); - } - d2.exportToPLY("test_After_Infinity_Triangulation_MOSEK.ply"); + d2.exportToPLY("test_After_Infinity_Triangulation_MOSEK.ply"); } -#endif // ALICEVISION_HAVE_MOSEK +#endif // ALICEVISION_HAVE_MOSEK diff --git a/src/aliceVision/linearProgramming/lInfinityCV/resection.hpp b/src/aliceVision/linearProgramming/lInfinityCV/resection.hpp index 3860596956..fec5ce9cac 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/resection.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/resection.hpp @@ -25,8 +25,8 @@ //- ICCV 2005. //-- -namespace aliceVision { -namespace lInfinityCV { +namespace aliceVision { +namespace lInfinityCV { using namespace linearProgramming; @@ -44,96 +44,96 @@ using namespace linearProgramming; // (simplex) instead of using SOCP (second order cone programming). // Implementation by Pierre Moulon // -inline void EncodeResection(const Mat2X & Pt2D, - const Mat3X & Pt3D, - double gamma, // Start upper bound - sRMat & A, Vec & C) +inline void EncodeResection(const Mat2X& Pt2D, + const Mat3X& Pt3D, + double gamma, // Start upper bound + sRMat& A, + Vec& C) { - // Build Constraint matrix. - - const int Nobs = Pt2D.cols(); - - assert(Pt2D.cols() == Pt3D.cols()); - assert(Pt2D.cols() >= 6 && "The problem requires at least 6 points"); - - A.resize(Nobs * 5, 11); - - C.resize(Nobs * 5, 1); - C.fill(0.0); - - for (int p=0; p 0 - // - R^3 X - t < 0 => // - R^3 X < 1.0 because t3 = 1.0 by default - int cpti = 8; - int cptj = 5 * p; - A.coeffRef(cptj, cpti) = -pt3d(0); - A.coeffRef(cptj, cpti+1) = -pt3d(1); - A.coeffRef(cptj, cpti+2) = -pt3d(2); - C(cptj) = 1.0; - - // First constraint <= s - // R1 * X + t1 + R3 * X *(-xij -s) <= xij t3 + s t3 - cpti = 0; - cptj += 1; - A.coeffRef(cptj, cpti) = pt3d(0); - A.coeffRef(cptj, cpti+1) = pt3d(1); - A.coeffRef(cptj, cpti+2) = pt3d(2); - A.coeffRef(cptj, cpti+3) = 1.0; - cpti = 4; - A.coeffRef(cptj+1, cpti) = pt3d(0); - A.coeffRef(cptj+1, cpti+1) = pt3d(1); - A.coeffRef(cptj+1, cpti+2) = pt3d(2); - A.coeffRef(cptj+1, cpti+3) = 1.0; - - cpti = 8; - Mat temp; - temp = Vec2((-pt2d).array() - gamma) * pt3d.transpose(); - A.coeffRef(cptj, cpti) = temp(0,0); - A.coeffRef(cptj, cpti+1) = temp(0,1); - A.coeffRef(cptj, cpti+2) = temp(0,2); - - A.coeffRef(cptj+1, cpti) = temp(1,0); - A.coeffRef(cptj+1, cpti+1) = temp(1,1); - A.coeffRef(cptj+1, cpti+2) = temp(1,2); - - C(cptj) = gamma + pt2d(0); - C(cptj+1) = gamma + pt2d(1); - - - // Second constraint >= s - // -R1 * X - t1 + R3 * X *(xij -s) <= - xij t3 + s t3 - cpti = 0; - cptj += 2; - A.coeffRef(cptj, cpti) = - pt3d(0); - A.coeffRef(cptj, cpti+1) = - pt3d(1); - A.coeffRef(cptj, cpti+2) = - pt3d(2); - A.coeffRef(cptj, cpti+3) = - 1.0; - cpti = 4; - A.coeffRef(cptj+1, cpti) = - pt3d(0); - A.coeffRef(cptj+1, cpti+1) = - pt3d(1); - A.coeffRef(cptj+1, cpti+2) = - pt3d(2); - A.coeffRef(cptj+1, cpti+3) = - 1.0; - - cpti = 8; - temp = Vec2(pt2d.array() - gamma) * pt3d.transpose(); - A.coeffRef(cptj, cpti) = temp(0,0); - A.coeffRef(cptj, cpti+1) = temp(0,1); - A.coeffRef(cptj, cpti+2) = temp(0,2); - - A.coeffRef(cptj+1, cpti) = temp(1,0); - A.coeffRef(cptj+1, cpti+1) = temp(1,1); - A.coeffRef(cptj+1, cpti+2) = temp(1,2); - - C(cptj) = gamma - pt2d(0); - C(cptj+1) = gamma - pt2d(1); - } + // Build Constraint matrix. + + const int Nobs = Pt2D.cols(); + + assert(Pt2D.cols() == Pt3D.cols()); + assert(Pt2D.cols() >= 6 && "The problem requires at least 6 points"); + + A.resize(Nobs * 5, 11); + + C.resize(Nobs * 5, 1); + C.fill(0.0); + + for (int p = 0; p < Nobs; ++p) + { + const Vec2 pt2d = Pt2D.col(p); + const Vec3 pt3d = Pt3D.col(p); + + // Compute and setup constraint : + + // Cheirality + // R^3 X + t >0 + // - R^3 X - t < 0 => // - R^3 X < 1.0 because t3 = 1.0 by default + int cpti = 8; + int cptj = 5 * p; + A.coeffRef(cptj, cpti) = -pt3d(0); + A.coeffRef(cptj, cpti + 1) = -pt3d(1); + A.coeffRef(cptj, cpti + 2) = -pt3d(2); + C(cptj) = 1.0; + + // First constraint <= s + // R1 * X + t1 + R3 * X *(-xij -s) <= xij t3 + s t3 + cpti = 0; + cptj += 1; + A.coeffRef(cptj, cpti) = pt3d(0); + A.coeffRef(cptj, cpti + 1) = pt3d(1); + A.coeffRef(cptj, cpti + 2) = pt3d(2); + A.coeffRef(cptj, cpti + 3) = 1.0; + cpti = 4; + A.coeffRef(cptj + 1, cpti) = pt3d(0); + A.coeffRef(cptj + 1, cpti + 1) = pt3d(1); + A.coeffRef(cptj + 1, cpti + 2) = pt3d(2); + A.coeffRef(cptj + 1, cpti + 3) = 1.0; + + cpti = 8; + Mat temp; + temp = Vec2((-pt2d).array() - gamma) * pt3d.transpose(); + A.coeffRef(cptj, cpti) = temp(0, 0); + A.coeffRef(cptj, cpti + 1) = temp(0, 1); + A.coeffRef(cptj, cpti + 2) = temp(0, 2); + + A.coeffRef(cptj + 1, cpti) = temp(1, 0); + A.coeffRef(cptj + 1, cpti + 1) = temp(1, 1); + A.coeffRef(cptj + 1, cpti + 2) = temp(1, 2); + + C(cptj) = gamma + pt2d(0); + C(cptj + 1) = gamma + pt2d(1); + + // Second constraint >= s + // -R1 * X - t1 + R3 * X *(xij -s) <= - xij t3 + s t3 + cpti = 0; + cptj += 2; + A.coeffRef(cptj, cpti) = -pt3d(0); + A.coeffRef(cptj, cpti + 1) = -pt3d(1); + A.coeffRef(cptj, cpti + 2) = -pt3d(2); + A.coeffRef(cptj, cpti + 3) = -1.0; + cpti = 4; + A.coeffRef(cptj + 1, cpti) = -pt3d(0); + A.coeffRef(cptj + 1, cpti + 1) = -pt3d(1); + A.coeffRef(cptj + 1, cpti + 2) = -pt3d(2); + A.coeffRef(cptj + 1, cpti + 3) = -1.0; + + cpti = 8; + temp = Vec2(pt2d.array() - gamma) * pt3d.transpose(); + A.coeffRef(cptj, cpti) = temp(0, 0); + A.coeffRef(cptj, cpti + 1) = temp(0, 1); + A.coeffRef(cptj, cpti + 2) = temp(0, 2); + + A.coeffRef(cptj + 1, cpti) = temp(1, 0); + A.coeffRef(cptj + 1, cpti + 1) = temp(1, 1); + A.coeffRef(cptj + 1, cpti + 2) = temp(1, 2); + + C(cptj) = gamma - pt2d(0); + C(cptj + 1) = gamma - pt2d(1); + } } /// Kernel that set Linear constraints for the @@ -146,47 +146,42 @@ inline void EncodeResection(const Mat2X & Pt2D, /// struct Resection_L1_ConstraintBuilder { - Resection_L1_ConstraintBuilder( - const Mat2X & Pt2D, - const Mat3X & Pt3D) - { - _2DPt = Pt2D; - _3DPt = Pt3D; - } - - /// Setup constraints for the Resection problem, - /// in the LPConstraints object. - bool Build(double gamma, LPConstraintsSparse & constraint) - { - EncodeResection(_2DPt, _3DPt, - gamma, - constraint._constraintMat, - constraint._Cst_objective); - - //-- Setup additional information about the Linear Program constraint - // We look for: - // P = [P00 P01 P02 P03; - // P10 P11 P12 P13; - // P20 P21 P22 1.0]; - const int NParams = 4 * 2 + 3; - - constraint._nbParams = NParams; - constraint._vec_bounds = std::vector< std::pair >(1); - fill(constraint._vec_bounds.begin(),constraint._vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); // lp_solve => getInfinite => DEF_INFINITE - // Constraint sign are all LESS or equal (<=) - constraint._vec_sign.resize(constraint._constraintMat.rows()); - fill(constraint._vec_sign.begin(), constraint._vec_sign.end(), - LPConstraints::LP_LESS_OR_EQUAL); - - return true; - } - - Mat2X _2DPt; - Mat3X _3DPt; + Resection_L1_ConstraintBuilder(const Mat2X& Pt2D, const Mat3X& Pt3D) + { + _2DPt = Pt2D; + _3DPt = Pt3D; + } + + /// Setup constraints for the Resection problem, + /// in the LPConstraints object. + bool Build(double gamma, LPConstraintsSparse& constraint) + { + EncodeResection(_2DPt, _3DPt, gamma, constraint._constraintMat, constraint._Cst_objective); + + //-- Setup additional information about the Linear Program constraint + // We look for: + // P = [P00 P01 P02 P03; + // P10 P11 P12 P13; + // P20 P21 P22 1.0]; + const int NParams = 4 * 2 + 3; + + constraint._nbParams = NParams; + constraint._vec_bounds = std::vector>(1); + fill(constraint._vec_bounds.begin(), + constraint._vec_bounds.end(), + std::make_pair((double)-1e+30, (double)1e+30)); // lp_solve => getInfinite => DEF_INFINITE + // Constraint sign are all LESS or equal (<=) + constraint._vec_sign.resize(constraint._constraintMat.rows()); + fill(constraint._vec_sign.begin(), constraint._vec_sign.end(), LPConstraints::LP_LESS_OR_EQUAL); + + return true; + } + + Mat2X _2DPt; + Mat3X _3DPt; }; -} // namespace lInfinityCV -} // namespace aliceVision +} // namespace lInfinityCV +} // namespace aliceVision -#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_RESECTION_H_ +#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_RESECTION_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.cpp b/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.cpp index f3a176881d..d975f31601 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.cpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.cpp @@ -18,49 +18,42 @@ namespace aliceVision { namespace lInfinityCV { namespace kernel { -void translate(const Mat3X & X, const Vec3 & vecTranslation, - Mat3X * XPoints) { - XPoints->resize(X.rows(), X.cols()); - for (size_t i=0; icol(i) = X.col(i) + vecTranslation; - } +void translate(const Mat3X& X, const Vec3& vecTranslation, Mat3X* XPoints) +{ + XPoints->resize(X.rows(), X.cols()); + for (size_t i = 0; i < X.cols(); ++i) + { + XPoints->col(i) = X.col(i) + vecTranslation; + } } -void l1SixPointResectionSolver::solve(const Mat &pt2D, const Mat &pt3d, std::vector& Ps) const +void l1SixPointResectionSolver::solve(const Mat& pt2D, const Mat& pt3d, std::vector& Ps) const { - assert(2 == pt2D.rows()); - assert(3 == pt3d.rows()); - assert(6 <= pt2D.cols()); - assert(pt2D.cols() == pt3d.cols()); - - //-- Translate 3D points in order to have X0 = (0,0,0,1). - Vec3 vecTranslation = - pt3d.col(0); - Mat4 translationMatrix = Mat4::Identity(); - translationMatrix.block<3,1>(0,3) = vecTranslation; - - Mat3X XPoints; - translate(pt3d, vecTranslation, &XPoints); - - std::vector vec_solution(11); - OSI_CISolverWrapper wrapperLpSolve(vec_solution.size()); - Resection_L1_ConstraintBuilder cstBuilder(pt2D, XPoints); - if( - (BisectionLP( - wrapperLpSolve, - cstBuilder, - &vec_solution, - 1.0, - 0.0)) - ) - { - // Move computed value to dataset for residual estimation. - Mat34 P; - P << vec_solution[0], vec_solution[1], vec_solution[2], vec_solution[3], - vec_solution[4], vec_solution[5], vec_solution[6], vec_solution[7], - vec_solution[8], vec_solution[9], vec_solution[10], 1.0; - P = P * translationMatrix; - Ps.emplace_back(P); - } + assert(2 == pt2D.rows()); + assert(3 == pt3d.rows()); + assert(6 <= pt2D.cols()); + assert(pt2D.cols() == pt3d.cols()); + + //-- Translate 3D points in order to have X0 = (0,0,0,1). + Vec3 vecTranslation = -pt3d.col(0); + Mat4 translationMatrix = Mat4::Identity(); + translationMatrix.block<3, 1>(0, 3) = vecTranslation; + + Mat3X XPoints; + translate(pt3d, vecTranslation, &XPoints); + + std::vector vec_solution(11); + OSI_CISolverWrapper wrapperLpSolve(vec_solution.size()); + Resection_L1_ConstraintBuilder cstBuilder(pt2D, XPoints); + if ((BisectionLP(wrapperLpSolve, cstBuilder, &vec_solution, 1.0, 0.0))) + { + // Move computed value to dataset for residual estimation. + Mat34 P; + P << vec_solution[0], vec_solution[1], vec_solution[2], vec_solution[3], vec_solution[4], vec_solution[5], vec_solution[6], vec_solution[7], + vec_solution[8], vec_solution[9], vec_solution[10], 1.0; + P = P * translationMatrix; + Ps.emplace_back(P); + } } } // namespace kernel diff --git a/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.hpp b/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.hpp index c3a2e0fa9f..dbaa42322a 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/resection_kernel.hpp @@ -26,48 +26,48 @@ namespace kernel { */ struct l1SixPointResectionSolver : public robustEstimation::ISolver { - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 6; - } + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override + { + return 6; + } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override + { + return 1; + } - /** - * @brief Solve the problem of camera pose. - * @note First 3d point will be translated in order to have X0 = (0,0,0,1) - */ - void solve(const Mat& pt2D, const Mat& pt3D, std::vector& P) const; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("l1SixPointResectionSolver does not support problem solving with weights."); - } + /** + * @brief Solve the problem of camera pose. + * @note First 3d point will be translated in order to have X0 = (0,0,0,1) + */ + void solve(const Mat& pt2D, const Mat& pt3D, std::vector& P) const; + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("l1SixPointResectionSolver does not support problem solving with weights."); + } }; /** * @brief Usable solver for the l1 6pt Resection Estimation */ -using l1PoseResectionKernel = robustEstimation::PointFittingKernel; +using l1PoseResectionKernel = + robustEstimation::PointFittingKernel; } // namespace kernel } // namespace lInfinityCV diff --git a/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri.hpp b/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri.hpp index 0b0c1db5ba..390d981490 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri.hpp @@ -15,7 +15,7 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif //-- @@ -29,8 +29,8 @@ //- ICCV 2005. //-- -namespace aliceVision { -namespace lInfinityCV { +namespace aliceVision { +namespace lInfinityCV { using namespace linearProgramming; @@ -46,108 +46,108 @@ using namespace linearProgramming; // /// Encode translation and structure linear program -inline void EncodeTiXi(const Mat & M, //Scene representation - const std::vector & Ri, - double sigma, // Start upper bound - sRMat & A, Vec & C, - std::vector & vec_sign, - std::vector & vec_costs, - std::vector< std::pair > & vec_bounds) +inline void EncodeTiXi(const Mat& M, // Scene representation + const std::vector& Ri, + double sigma, // Start upper bound + sRMat& A, + Vec& C, + std::vector& vec_sign, + std::vector& vec_costs, + std::vector>& vec_bounds) { - // Build Constraint matrix. - const size_t Ncam = (size_t) M.row(3).maxCoeff()+1; - const size_t N3D = (size_t) M.row(2).maxCoeff()+1; - const size_t Nobs = M.cols(); - - assert(Ncam == Ri.size()); - - A.resize(5 * Nobs, 3 * (N3D + Ncam)); - - C.resize(5 * Nobs, 1); - C.fill(0.0); - vec_sign.resize(5 * Nobs + 3); - - const size_t transStart = 0; - const size_t pointStart = transStart + 3*Ncam; -# define TVAR(i, el) (0 + 3*(i) + (el)) -# define XVAR(j, el) (pointStart + 3*(j) + (el)) - - // By default set free variable: - vec_bounds = std::vector< std::pair >(3 * (N3D + Ncam)); - fill( vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); - // Fix the translation ambiguity. (set first cam at (0,0,0)) - vec_bounds[0] = vec_bounds[1] = vec_bounds[2] = std::make_pair(0,0); - - size_t rowPos = 0; - // Add the cheirality conditions (R_i*X_j + T_i)_3 >= 1 - for (size_t k = 0; k < Nobs; ++k) - { - const size_t indexPt3D = M(2,k); - const size_t indexCam = M(3,k); - const Mat3 & R = Ri[indexCam]; - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 2)) = 1.0; - C(rowPos) = 1.0; - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; - ++rowPos; - - const Vec2 pt = M.block<2,1>(0,k); - const double u = pt(0); - const double v = pt(1); - - // x-residual => - // (R_i*X_j + T_i)_1 / (R_i*X_j + T_i)_3 - u >= -sigma - // (R_i*X_j + T_i)_1 - u * (R_i*X_j + T_i)_3 + sigma (R_i*X_j + T_i)_3 >= 0.0 - // R_i_3 * (sigma-u) + R_i_1 + t_i_1 + t_i_3 * (sigma-u) >= 0 - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0,0) + (sigma-u) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0,1) + (sigma-u) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0,2) + (sigma-u) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma-u; - C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; - ++rowPos; - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0,0) - (sigma+u) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0,1) - (sigma+u) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0,2) - (sigma+u) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + u); - C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; - ++rowPos; - - // y-residual => - // (R_i*X_j + T_i)_2 / (R_i*X_j + T_i)_3 - v >= -sigma - // (R_i*X_j + T_i)_2 - v * (R_i*X_j + T_i)_3 + sigma (R_i*X_j + T_i)_3 >= 0.0 - // R_i_3 * (sigma-v) + R_i_2 + t_i_2 + t_i_3 * (sigma-v) >= 0 - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1,0) + (sigma-v) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1,1) + (sigma-v) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1,2) + (sigma-v) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma-v; - C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; - ++rowPos; - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1,0) - (sigma+v) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1,1) - (sigma+v) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1,2) - (sigma+v) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + v); - C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; - ++rowPos; - } -# undef TVAR -# undef XVAR + // Build Constraint matrix. + const size_t Ncam = (size_t)M.row(3).maxCoeff() + 1; + const size_t N3D = (size_t)M.row(2).maxCoeff() + 1; + const size_t Nobs = M.cols(); + + assert(Ncam == Ri.size()); + + A.resize(5 * Nobs, 3 * (N3D + Ncam)); + + C.resize(5 * Nobs, 1); + C.fill(0.0); + vec_sign.resize(5 * Nobs + 3); + + const size_t transStart = 0; + const size_t pointStart = transStart + 3 * Ncam; +#define TVAR(i, el) (0 + 3 * (i) + (el)) +#define XVAR(j, el) (pointStart + 3 * (j) + (el)) + + // By default set free variable: + vec_bounds = std::vector>(3 * (N3D + Ncam)); + fill(vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); + // Fix the translation ambiguity. (set first cam at (0,0,0)) + vec_bounds[0] = vec_bounds[1] = vec_bounds[2] = std::make_pair(0, 0); + + size_t rowPos = 0; + // Add the cheirality conditions (R_i*X_j + T_i)_3 >= 1 + for (size_t k = 0; k < Nobs; ++k) + { + const size_t indexPt3D = M(2, k); + const size_t indexCam = M(3, k); + const Mat3& R = Ri[indexCam]; + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 2)) = 1.0; + C(rowPos) = 1.0; + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + ++rowPos; + + const Vec2 pt = M.block<2, 1>(0, k); + const double u = pt(0); + const double v = pt(1); + + // x-residual => + // (R_i*X_j + T_i)_1 / (R_i*X_j + T_i)_3 - u >= -sigma + // (R_i*X_j + T_i)_1 - u * (R_i*X_j + T_i)_3 + sigma (R_i*X_j + T_i)_3 >= 0.0 + // R_i_3 * (sigma-u) + R_i_1 + t_i_1 + t_i_3 * (sigma-u) >= 0 + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0, 0) + (sigma - u) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0, 1) + (sigma - u) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0, 2) + (sigma - u) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma - u; + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + ++rowPos; + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0, 0) - (sigma + u) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0, 1) - (sigma + u) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0, 2) - (sigma + u) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + u); + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + ++rowPos; + + // y-residual => + // (R_i*X_j + T_i)_2 / (R_i*X_j + T_i)_3 - v >= -sigma + // (R_i*X_j + T_i)_2 - v * (R_i*X_j + T_i)_3 + sigma (R_i*X_j + T_i)_3 >= 0.0 + // R_i_3 * (sigma-v) + R_i_2 + t_i_2 + t_i_3 * (sigma-v) >= 0 + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1, 0) + (sigma - v) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1, 1) + (sigma - v) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1, 2) + (sigma - v) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma - v; + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + ++rowPos; + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1, 0) - (sigma + v) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1, 1) - (sigma + v) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1, 2) - (sigma + v) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + v); + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + ++rowPos; + } +#undef TVAR +#undef XVAR } - /// Kernel that set Linear constraints for the /// - Translation Registration and Structure Problem. /// Designed to be used with bisectionLP and ISolver interface. @@ -157,41 +157,40 @@ inline void EncodeTiXi(const Mat & M, //Scene representation struct Translation_Structure_L1_ConstraintBuilder { - Translation_Structure_L1_ConstraintBuilder( - const std::vector & vec_Ri, - const Mat & M) - { - _M = M; - _vec_Ri = vec_Ri; - } - - /// Setup constraints for the translation and structure problem, - /// in the LPConstraints object. - bool Build(double gamma, LPConstraintsSparse & constraint) - { - EncodeTiXi(_M, _vec_Ri, - gamma, - constraint._constraintMat, - constraint._Cst_objective, - constraint._vec_sign, - constraint._vec_cost, - constraint._vec_bounds); - - //-- Setup additional information about the Linear Program constraint - // We look for nb translations and nb 3D points. - const size_t N3D = (size_t) _M.row(2).maxCoeff() + 1; - const size_t Ncam = (size_t) _M.row(3).maxCoeff() + 1; - - constraint._nbParams = (Ncam + N3D) * 3; - - return true; - } - - std::vector _vec_Ri; // Rotation matrix - Mat _M; // M contains (X,Y,index3dPoint, indexCam)^T + Translation_Structure_L1_ConstraintBuilder(const std::vector& vec_Ri, const Mat& M) + { + _M = M; + _vec_Ri = vec_Ri; + } + + /// Setup constraints for the translation and structure problem, + /// in the LPConstraints object. + bool Build(double gamma, LPConstraintsSparse& constraint) + { + EncodeTiXi(_M, + _vec_Ri, + gamma, + constraint._constraintMat, + constraint._Cst_objective, + constraint._vec_sign, + constraint._vec_cost, + constraint._vec_bounds); + + //-- Setup additional information about the Linear Program constraint + // We look for nb translations and nb 3D points. + const size_t N3D = (size_t)_M.row(2).maxCoeff() + 1; + const size_t Ncam = (size_t)_M.row(3).maxCoeff() + 1; + + constraint._nbParams = (Ncam + N3D) * 3; + + return true; + } + + std::vector _vec_Ri; // Rotation matrix + Mat _M; // M contains (X,Y,index3dPoint, indexCam)^T }; -} // namespace lInfinityCV -} // namespace aliceVision +} // namespace lInfinityCV +} // namespace aliceVision -#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_TRANSLATIONANDSTRUCTUREFrom_xi_RI_H_ +#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_TRANSLATIONANDSTRUCTUREFrom_xi_RI_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri_noise.hpp b/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri_noise.hpp index 2b7099c66d..d994bedbd7 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri_noise.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/tijsAndXis_From_xi_Ri_noise.hpp @@ -24,8 +24,8 @@ //- ICCV 2005. //-- -namespace aliceVision { -namespace lInfinityCV { +namespace aliceVision { +namespace lInfinityCV { using namespace linearProgramming; @@ -49,178 +49,187 @@ using namespace linearProgramming; /// Encode translation and structure linear program with slack variables /// in order to handle noisy measurements. -inline void EncodeTiXi_withNoise(const Mat & M, //Scene representation - const std::vector & Ri, - double sigma, // Start upper bound - sRMat & A, Vec & C, - std::vector & vec_sign, - std::vector & vec_costs, - std::vector< std::pair > & vec_bounds) +inline void EncodeTiXi_withNoise(const Mat& M, // Scene representation + const std::vector& Ri, + double sigma, // Start upper bound + sRMat& A, + Vec& C, + std::vector& vec_sign, + std::vector& vec_costs, + std::vector>& vec_bounds) { - // Build Constraint matrix. - - const size_t Ncam = (size_t) M.row(3).maxCoeff()+1; - const size_t N3D = (size_t) M.row(2).maxCoeff()+1; - const size_t Nobs = M.cols(); - - assert(Ncam == Ri.size()); - - A.resize(5*Nobs+3, 3 * (Ncam + N3D + Nobs)); - - C.resize(5 * Nobs + 3, 1); - C.fill(0.0); - vec_sign.resize(5*Nobs+3); - - const size_t transStart = 0; - const size_t pointStart = transStart + 3*Ncam; - const size_t offsetStart = pointStart + 3*N3D; -# define TVAR(i, el) (0 + 3*(i) + (el)) -# define XVAR(j, el) (pointStart + 3*(j) + (el)) -# define OFSVAR(k, el) (offsetStart + 3*(k) + (el)) - - // Set objective coefficients. - vec_costs = std::vector(3 * (Ncam + N3D + Nobs), 0.0); - for (size_t k = 0; k < Nobs; ++k) - { - vec_costs[OFSVAR(k, 0)] = 1.0; - vec_costs[OFSVAR(k, 1)] = 1.0; - vec_costs[OFSVAR(k, 2)] = 1.0; - } - - // By default set free variable: - vec_bounds = std::vector< std::pair >(3 * (Ncam + N3D + Nobs)); - fill( vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); - // Change the offset to be positive - for (size_t k = 0; k < 3*Nobs; ++k) - vec_bounds[offsetStart + k].first = 0; - - size_t rowPos = 0; - // Add the cheirality conditions (R_i*X_j + T_i)_3 >= 1 - for (size_t k = 0; k < Nobs; ++k) - { - const size_t indexPt3D = M(2,k); - const size_t indexCam = M(3,k); - const Mat3 & R = Ri[indexCam]; - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 2)) = 1.0; - A.coeffRef(rowPos, OFSVAR(k, 2)) = 1.0; - C(rowPos) = 1.0; - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; - ++rowPos; - } // end for (k) - - // Add conic constraint: - for (size_t k = 0; k < Nobs; ++k) - { - const Vec2 pt = M.block<2,1>(0,k); - const double u = pt(0); - const double v = pt(1); - const size_t indexPt3D = M(2,k); - const size_t indexCam = M(3,k); - const Mat3 & R = Ri[indexCam]; - - // x-residual => - // (R_i*X_j + T_i)_1 / (R_i*X_j + T_i)_3 - u >= -sigma - // (R_i*X_j + T_i)_1 - u * (R_i*X_j + T_i)_3 + sigma (R_i*X_j + T_i)_3 >= 0.0 - // R_i_3 * (sigma-u) + R_i_1 + t_i_1 + t_i_3 * (sigma-u) >= 0 - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0,0) + (sigma-u) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0,1) + (sigma-u) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0,2) + (sigma-u) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma-u; - A.coeffRef(rowPos, OFSVAR(k, 0)) = 1.0; + // Build Constraint matrix. + + const size_t Ncam = (size_t)M.row(3).maxCoeff() + 1; + const size_t N3D = (size_t)M.row(2).maxCoeff() + 1; + const size_t Nobs = M.cols(); + + assert(Ncam == Ri.size()); + + A.resize(5 * Nobs + 3, 3 * (Ncam + N3D + Nobs)); + + C.resize(5 * Nobs + 3, 1); + C.fill(0.0); + vec_sign.resize(5 * Nobs + 3); + + const size_t transStart = 0; + const size_t pointStart = transStart + 3 * Ncam; + const size_t offsetStart = pointStart + 3 * N3D; +#define TVAR(i, el) (0 + 3 * (i) + (el)) +#define XVAR(j, el) (pointStart + 3 * (j) + (el)) +#define OFSVAR(k, el) (offsetStart + 3 * (k) + (el)) + + // Set objective coefficients. + vec_costs = std::vector(3 * (Ncam + N3D + Nobs), 0.0); + for (size_t k = 0; k < Nobs; ++k) + { + vec_costs[OFSVAR(k, 0)] = 1.0; + vec_costs[OFSVAR(k, 1)] = 1.0; + vec_costs[OFSVAR(k, 2)] = 1.0; + } + + // By default set free variable: + vec_bounds = std::vector>(3 * (Ncam + N3D + Nobs)); + fill(vec_bounds.begin(), vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); + // Change the offset to be positive + for (size_t k = 0; k < 3 * Nobs; ++k) + vec_bounds[offsetStart + k].first = 0; + + size_t rowPos = 0; + // Add the cheirality conditions (R_i*X_j + T_i)_3 >= 1 + for (size_t k = 0; k < Nobs; ++k) + { + const size_t indexPt3D = M(2, k); + const size_t indexCam = M(3, k); + const Mat3& R = Ri[indexCam]; + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 2)) = 1.0; + A.coeffRef(rowPos, OFSVAR(k, 2)) = 1.0; + C(rowPos) = 1.0; + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + ++rowPos; + } // end for (k) + + // Add conic constraint: + for (size_t k = 0; k < Nobs; ++k) + { + const Vec2 pt = M.block<2, 1>(0, k); + const double u = pt(0); + const double v = pt(1); + const size_t indexPt3D = M(2, k); + const size_t indexCam = M(3, k); + const Mat3& R = Ri[indexCam]; + + // x-residual => + // (R_i*X_j + T_i)_1 / (R_i*X_j + T_i)_3 - u >= -sigma + // (R_i*X_j + T_i)_1 - u * (R_i*X_j + T_i)_3 + sigma (R_i*X_j + T_i)_3 >= 0.0 + // R_i_3 * (sigma-u) + R_i_1 + t_i_1 + t_i_3 * (sigma-u) >= 0 + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0, 0) + (sigma - u) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0, 1) + (sigma - u) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0, 2) + (sigma - u) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma - u; + A.coeffRef(rowPos, OFSVAR(k, 0)) = 1.0; + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + ++rowPos; + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0, 0) - (sigma + u) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0, 1) - (sigma + u) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0, 2) - (sigma + u) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + u); + A.coeffRef(rowPos, OFSVAR(k, 0)) = -1.0; + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + ++rowPos; + + // y-residual => + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1, 0) + (sigma - v) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1, 1) + (sigma - v) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1, 2) + (sigma - v) * R(2, 2); + A.coeffRef(rowPos, OFSVAR(k, 1)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma - v; + + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + ++rowPos; + + A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1, 0) - (sigma + v) * R(2, 0); + A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1, 1) - (sigma + v) * R(2, 1); + A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1, 2) - (sigma + v) * R(2, 2); + A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; + A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + v); + A.coeffRef(rowPos, OFSVAR(k, 1)) = -1.0; + C(rowPos) = 0.0; + vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + ++rowPos; + } + // Fix the translation ambiguity. (set first cam at (0,0,0) + // LP_EQUAL + A.coeffRef(rowPos, TVAR(0, 0)) = 1.0; C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + vec_sign[rowPos] = LPConstraints::LP_EQUAL; ++rowPos; - - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(0,0) - (sigma+u) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(0,1) - (sigma+u) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(0,2) - (sigma+u) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 0)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + u); - A.coeffRef(rowPos, OFSVAR(k, 0)) = -1.0; + A.coeffRef(rowPos, TVAR(0, 1)) = 1.0; C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; + vec_sign[rowPos] = LPConstraints::LP_EQUAL; ++rowPos; - - // y-residual => - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1,0) + (sigma-v) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1,1) + (sigma-v) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1,2) + (sigma-v) * R(2,2); - A.coeffRef(rowPos, OFSVAR(k, 1)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = sigma-v; - + A.coeffRef(rowPos, TVAR(0, 2)) = 1.0; C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_GREATER_OR_EQUAL; + vec_sign[rowPos] = LPConstraints::LP_EQUAL; ++rowPos; - A.coeffRef(rowPos, XVAR(indexPt3D, 0)) = R(1,0) - (sigma+v) * R(2,0); - A.coeffRef(rowPos, XVAR(indexPt3D, 1)) = R(1,1) - (sigma+v) * R(2,1); - A.coeffRef(rowPos, XVAR(indexPt3D, 2)) = R(1,2) - (sigma+v) * R(2,2); - A.coeffRef(rowPos, TVAR(indexCam, 1)) = 1.0; - A.coeffRef(rowPos, TVAR(indexCam, 2)) = -(sigma + v); - A.coeffRef(rowPos, OFSVAR(k, 1)) = -1.0; - C(rowPos) = 0.0; - vec_sign[rowPos] = LPConstraints::LP_LESS_OR_EQUAL; - ++rowPos; - } - // Fix the translation ambiguity. (set first cam at (0,0,0) - //LP_EQUAL - A.coeffRef(rowPos, TVAR(0, 0)) = 1.0; C(rowPos) = 0.0; vec_sign[rowPos] = LPConstraints::LP_EQUAL; ++rowPos; - A.coeffRef(rowPos, TVAR(0, 1)) = 1.0; C(rowPos) = 0.0; vec_sign[rowPos] = LPConstraints::LP_EQUAL; ++rowPos; - A.coeffRef(rowPos, TVAR(0, 2)) = 1.0; C(rowPos) = 0.0; vec_sign[rowPos] = LPConstraints::LP_EQUAL; ++rowPos; - -# undef TVAR -# undef XVAR -# undef OFSVAR +#undef TVAR +#undef XVAR +#undef OFSVAR } struct TiXi_withNoise_L1_ConstraintBuilder { - TiXi_withNoise_L1_ConstraintBuilder( - const std::vector & vec_Ri, - const Mat & M) - { - _M = M; - _vec_Ri = vec_Ri; - } - - /// Setup constraints for the translation and structure problem, - /// in the LPConstraints object. - bool Build(double gamma, LPConstraintsSparse & constraint) - { - EncodeTiXi_withNoise(_M, _vec_Ri, - gamma, - constraint._constraintMat, - constraint._Cst_objective, - constraint._vec_sign, - constraint._vec_cost, - constraint._vec_bounds); - - constraint._bminimize = true; - - //-- Setup additional information about the Linear Program constraint - // We look for nb translations and nb 3D points. - const size_t N3D = (size_t) _M.row(2).maxCoeff() + 1; - const size_t Nobs = _M.cols(); - const size_t NNoiseVar = Nobs; // noise over Zdepth, (x,y) residual - const size_t Ncam = (size_t) _M.row(3).maxCoeff() + 1; - - constraint._nbParams = (Ncam + N3D + NNoiseVar)*3; - - return true; - } - - std::vector _vec_Ri; // Rotation matrix - Mat _M; // M contains (X,Y,index3dPoint, indexCam)^T + TiXi_withNoise_L1_ConstraintBuilder(const std::vector& vec_Ri, const Mat& M) + { + _M = M; + _vec_Ri = vec_Ri; + } + + /// Setup constraints for the translation and structure problem, + /// in the LPConstraints object. + bool Build(double gamma, LPConstraintsSparse& constraint) + { + EncodeTiXi_withNoise(_M, + _vec_Ri, + gamma, + constraint._constraintMat, + constraint._Cst_objective, + constraint._vec_sign, + constraint._vec_cost, + constraint._vec_bounds); + + constraint._bminimize = true; + + //-- Setup additional information about the Linear Program constraint + // We look for nb translations and nb 3D points. + const size_t N3D = (size_t)_M.row(2).maxCoeff() + 1; + const size_t Nobs = _M.cols(); + const size_t NNoiseVar = Nobs; // noise over Zdepth, (x,y) residual + const size_t Ncam = (size_t)_M.row(3).maxCoeff() + 1; + + constraint._nbParams = (Ncam + N3D + NNoiseVar) * 3; + + return true; + } + + std::vector _vec_Ri; // Rotation matrix + Mat _M; // M contains (X,Y,index3dPoint, indexCam)^T }; -} // namespace lInfinityCV -} // namespace aliceVision +} // namespace lInfinityCV +} // namespace aliceVision -#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_TRANSLATIONANDSTRUCTUREFrom_xi_RI_NOISE_H_ +#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_TRANSLATIONANDSTRUCTUREFrom_xi_RI_NOISE_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/triangulation.hpp b/src/aliceVision/linearProgramming/lInfinityCV/triangulation.hpp index 478fed35c2..696b4e4a89 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/triangulation.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/triangulation.hpp @@ -24,8 +24,8 @@ //- ICCV 2005. //-- -namespace aliceVision { -namespace lInfinityCV { +namespace aliceVision { +namespace lInfinityCV { using namespace linearProgramming; @@ -39,42 +39,42 @@ using namespace linearProgramming; // Implementation by Pierre Moulon // -inline void EncodeTriangulation( - const std::vector & Pi, // Projection matrices - const Mat2X & x_ij, // corresponding observations - double gamma, // Start upper bound - Mat & A, Vec & C) +inline void EncodeTriangulation(const std::vector& Pi, // Projection matrices + const Mat2X& x_ij, // corresponding observations + double gamma, // Start upper bound + Mat& A, + Vec& C) { - // Build A, C matrix. - - const size_t nbCamera = Pi.size(); - A.resize(5*nbCamera,3); - C.resize(5*nbCamera,1); - - int cpt = 0; - for (int i = 0; i< nbCamera; ++i) - { - const Mat3 R = Pi[i].block<3,3>(0,0); - const Vec3 t = Pi[i].block<3,1>(0,3); - const Mat2X pt = x_ij.col(i); - - // A (Rotational part): - A.block<1,3>(cpt,0) = R.row(0) - pt(0) * R.row(2) - gamma * R.row(2); - A.block<1,3>(cpt+1,0) = R.row(1) - pt(1) * R.row(2) - gamma * R.row(2); - A.block<1,3>(cpt+2,0) = - R.row(2); - A.block<1,3>(cpt+3,0) = - R.row(0) + pt(0) * R.row(2) - gamma * R.row(2); - A.block<1,3>(cpt+4,0) = - R.row(1) + pt(1) * R.row(2) - gamma * R.row(2); - - // C (translation part): - C(cpt) = - t(0) + pt(0) * t(2) + gamma * t(2); - C(cpt+1) = - t(1) + pt(1) * t(2) + gamma * t(2); - C(cpt+2) = t(2); - C(cpt+3) = t(0) - pt(0) * t(2) + gamma * t(2); - C(cpt+4) = t(1) - pt(1) * t(2) + gamma * t(2); - - //- Next entry - cpt += 5; - } + // Build A, C matrix. + + const size_t nbCamera = Pi.size(); + A.resize(5 * nbCamera, 3); + C.resize(5 * nbCamera, 1); + + int cpt = 0; + for (int i = 0; i < nbCamera; ++i) + { + const Mat3 R = Pi[i].block<3, 3>(0, 0); + const Vec3 t = Pi[i].block<3, 1>(0, 3); + const Mat2X pt = x_ij.col(i); + + // A (Rotational part): + A.block<1, 3>(cpt, 0) = R.row(0) - pt(0) * R.row(2) - gamma * R.row(2); + A.block<1, 3>(cpt + 1, 0) = R.row(1) - pt(1) * R.row(2) - gamma * R.row(2); + A.block<1, 3>(cpt + 2, 0) = -R.row(2); + A.block<1, 3>(cpt + 3, 0) = -R.row(0) + pt(0) * R.row(2) - gamma * R.row(2); + A.block<1, 3>(cpt + 4, 0) = -R.row(1) + pt(1) * R.row(2) - gamma * R.row(2); + + // C (translation part): + C(cpt) = -t(0) + pt(0) * t(2) + gamma * t(2); + C(cpt + 1) = -t(1) + pt(1) * t(2) + gamma * t(2); + C(cpt + 2) = t(2); + C(cpt + 3) = t(0) - pt(0) * t(2) + gamma * t(2); + C(cpt + 4) = t(1) - pt(1) * t(2) + gamma * t(2); + + //- Next entry + cpt += 5; + } } /// Kernel that set Linear constraints for the Triangulation Problem. @@ -86,43 +86,35 @@ inline void EncodeTriangulation( /// under a Linear Program form. struct Triangulation_L1_ConstraintBuilder { - Triangulation_L1_ConstraintBuilder( - const std::vector & vec_Pi, - const Mat2X & x_ij) - { - _vec_Pi = vec_Pi; - _x_ij = x_ij; - } - - /// Setup constraints of the triangulation problem as a Linear program - bool Build(double gamma, LPConstraints & constraint) - { - EncodeTriangulation(_vec_Pi, _x_ij, - gamma, - constraint._constraintMat, - constraint._Cst_objective); - //-- Setup additional information about the Linear Program constraint - // We look for 3 variables [X,Y,Z] with no bounds. - // Constraint sign are all less or equal (<=) - constraint._nbParams = 3; - constraint._vec_bounds = std::vector< std::pair >(1); - fill(constraint._vec_bounds.begin(),constraint._vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); - // Setup constraint sign - constraint._vec_sign.resize(constraint._constraintMat.rows()); - fill(constraint._vec_sign.begin(), constraint._vec_sign.end(), - LPConstraints::LP_LESS_OR_EQUAL); - - return true; - } - - //-- Data required for triangulation : - std::vector _vec_Pi; // Projection matrix - Mat2X _x_ij; // 2d projection : xij = Pj*Xi + Triangulation_L1_ConstraintBuilder(const std::vector& vec_Pi, const Mat2X& x_ij) + { + _vec_Pi = vec_Pi; + _x_ij = x_ij; + } + + /// Setup constraints of the triangulation problem as a Linear program + bool Build(double gamma, LPConstraints& constraint) + { + EncodeTriangulation(_vec_Pi, _x_ij, gamma, constraint._constraintMat, constraint._Cst_objective); + //-- Setup additional information about the Linear Program constraint + // We look for 3 variables [X,Y,Z] with no bounds. + // Constraint sign are all less or equal (<=) + constraint._nbParams = 3; + constraint._vec_bounds = std::vector>(1); + fill(constraint._vec_bounds.begin(), constraint._vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); + // Setup constraint sign + constraint._vec_sign.resize(constraint._constraintMat.rows()); + fill(constraint._vec_sign.begin(), constraint._vec_sign.end(), LPConstraints::LP_LESS_OR_EQUAL); + + return true; + } + + //-- Data required for triangulation : + std::vector _vec_Pi; // Projection matrix + Mat2X _x_ij; // 2d projection : xij = Pj*Xi }; +} // namespace lInfinityCV +} // namespace aliceVision -} // namespace lInfinityCV -} // namespace aliceVision - -#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_TRIANGULATION_H_ +#endif // ALICEVISION_LINFINITY_COMPUTER_VISION_TRIANGULATION_H_ diff --git a/src/aliceVision/linearProgramming/lInfinityCV/triplet_tijsAndXis_kernel.hpp b/src/aliceVision/linearProgramming/lInfinityCV/triplet_tijsAndXis_kernel.hpp index edc9e5656f..8ca1d082d0 100644 --- a/src/aliceVision/linearProgramming/lInfinityCV/triplet_tijsAndXis_kernel.hpp +++ b/src/aliceVision/linearProgramming/lInfinityCV/triplet_tijsAndXis_kernel.hpp @@ -18,7 +18,7 @@ #include "aliceVision/linearProgramming/ISolver.hpp" #include "aliceVision/linearProgramming/OSIXSolver.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" #endif #include "aliceVision/linearProgramming/bisectionLP.hpp" @@ -29,116 +29,111 @@ namespace trifocal { namespace kernel { /// A trifocal tensor seen as 3 projective cameras -struct TrifocalTensorModel { - Mat34 P1, P2, P3; - - static double Error(const TrifocalTensorModel & t, const Vec2 & pt1, const Vec2 & pt2, const Vec2 & pt3) - { - // Triangulate - multiview::Triangulation triangulationObj; - triangulationObj.add(t.P1, pt1); - triangulationObj.add(t.P2, pt2); - triangulationObj.add(t.P3, pt3); - const Vec3 X = triangulationObj.compute(); - - // Return the maximum observed reprojection error - const double pt1ReProj = (project(t.P1, X) - pt1).squaredNorm(); - const double pt2ReProj = (project(t.P2, X) - pt2).squaredNorm(); - const double pt3ReProj = (project(t.P3, X) - pt3).squaredNorm(); - - return std::max(pt1ReProj, std::max(pt2ReProj,pt3ReProj)); - } +struct TrifocalTensorModel +{ + Mat34 P1, P2, P3; + + static double Error(const TrifocalTensorModel& t, const Vec2& pt1, const Vec2& pt2, const Vec2& pt3) + { + // Triangulate + multiview::Triangulation triangulationObj; + triangulationObj.add(t.P1, pt1); + triangulationObj.add(t.P2, pt2); + triangulationObj.add(t.P3, pt3); + const Vec3 X = triangulationObj.compute(); + + // Return the maximum observed reprojection error + const double pt1ReProj = (project(t.P1, X) - pt1).squaredNorm(); + const double pt2ReProj = (project(t.P2, X) - pt2).squaredNorm(); + const double pt3ReProj = (project(t.P3, X) - pt3).squaredNorm(); + + return std::max(pt1ReProj, std::max(pt2ReProj, pt3ReProj)); + } }; } // namespace kernel } // namespace trifocal } // namespace aliceVision -namespace aliceVision{ +namespace aliceVision { using namespace aliceVision::trifocal::kernel; /// Solve the translations and the structure of a view-triplet that have known rotations struct translations_Triplet_Solver { - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - std::size_t getMinimumNbRequiredSamples() const - { - return 4; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - std::size_t getMaximumNbModels() const - { - return 1; - } - - /// Solve the computation of the "tensor". - void solve( - const Mat &pt0, const Mat & pt1, const Mat & pt2, - const std::vector & vec_KR, std::vector &P, - const double ThresholdUpperBound) const - { - //Build the megaMatMatrix - const int n_obs = pt0.cols(); - Mat4X megaMat(4, n_obs*3); + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + std::size_t getMinimumNbRequiredSamples() const { - size_t cpt = 0; - for (size_t i = 0; i < n_obs; ++i) - { - megaMat.col(cpt++) << pt0.col(i)(0), pt0.col(i)(1), (double)i, 0.0; - megaMat.col(cpt++) << pt1.col(i)(0), pt1.col(i)(1), (double)i, 1.0; - megaMat.col(cpt++) << pt2.col(i)(0), pt2.col(i)(1), (double)i, 2.0; - } + return 4; } - //-- Solve the LInfinity translation and structure from Rotation and points data. - std::vector vec_solution((3 + getMinimumNbRequiredSamples())*3); - using namespace aliceVision::lInfinityCV; + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + std::size_t getMaximumNbModels() const + { + return 1; + } + + /// Solve the computation of the "tensor". + void solve(const Mat& pt0, + const Mat& pt1, + const Mat& pt2, + const std::vector& vec_KR, + std::vector& P, + const double ThresholdUpperBound) const + { + // Build the megaMatMatrix + const int n_obs = pt0.cols(); + Mat4X megaMat(4, n_obs * 3); + { + size_t cpt = 0; + for (size_t i = 0; i < n_obs; ++i) + { + megaMat.col(cpt++) << pt0.col(i)(0), pt0.col(i)(1), (double)i, 0.0; + megaMat.col(cpt++) << pt1.col(i)(0), pt1.col(i)(1), (double)i, 1.0; + megaMat.col(cpt++) << pt2.col(i)(0), pt2.col(i)(1), (double)i, 2.0; + } + } + //-- Solve the LInfinity translation and structure from Rotation and points data. + std::vector vec_solution((3 + getMinimumNbRequiredSamples()) * 3); + + using namespace aliceVision::lInfinityCV; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) - MOSEKSolver LPsolver(static_cast(vec_solution.size())); + MOSEKSolver LPsolver(static_cast(vec_solution.size())); #else - OSI_CISolverWrapper LPsolver(static_cast(vec_solution.size())); + OSI_CISolverWrapper LPsolver(static_cast(vec_solution.size())); #endif - Translation_Structure_L1_ConstraintBuilder cstBuilder(vec_KR, megaMat); - double gamma; - if (BisectionLP( - LPsolver, - cstBuilder, - &vec_solution, - ThresholdUpperBound, - 0.0, 1e-8, 2, &gamma, false)) - { - const std::vector vec_tis = { - Vec3(vec_solution[0], vec_solution[1], vec_solution[2]), - Vec3(vec_solution[3], vec_solution[4], vec_solution[5]), - Vec3(vec_solution[6], vec_solution[7], vec_solution[8])}; - - TrifocalTensorModel PTemp; - PTemp.P1 = HStack(vec_KR[0], vec_tis[0]); - PTemp.P2 = HStack(vec_KR[1], vec_tis[1]); - PTemp.P3 = HStack(vec_KR[2], vec_tis[2]); + Translation_Structure_L1_ConstraintBuilder cstBuilder(vec_KR, megaMat); + double gamma; + if (BisectionLP( + LPsolver, cstBuilder, &vec_solution, ThresholdUpperBound, 0.0, 1e-8, 2, &gamma, false)) + { + const std::vector vec_tis = {Vec3(vec_solution[0], vec_solution[1], vec_solution[2]), + Vec3(vec_solution[3], vec_solution[4], vec_solution[5]), + Vec3(vec_solution[6], vec_solution[7], vec_solution[8])}; + + TrifocalTensorModel PTemp; + PTemp.P1 = HStack(vec_KR[0], vec_tis[0]); + PTemp.P2 = HStack(vec_KR[1], vec_tis[1]); + PTemp.P3 = HStack(vec_KR[2], vec_tis[2]); + + P.push_back(PTemp); + } + } - P.push_back(PTemp); + // Compute the residual of reprojections + static double error(const TrifocalTensorModel& Tensor, const Vec2& pt0, const Vec2& pt1, const Vec2& pt2) + { + return TrifocalTensorModel::Error(Tensor, pt0, pt1, pt2); } - } - - // Compute the residual of reprojections - static double error( - const TrifocalTensorModel & Tensor, - const Vec2 & pt0, const Vec2 & pt1, const Vec2 & pt2) - { - return TrifocalTensorModel::Error(Tensor, pt0, pt1, pt2); - } }; -} // namespace aliceVision - +} // namespace aliceVision diff --git a/src/aliceVision/linearProgramming/linearProgramming.hpp b/src/aliceVision/linearProgramming/linearProgramming.hpp index 67ce533802..e4824336b4 100644 --- a/src/aliceVision/linearProgramming/linearProgramming.hpp +++ b/src/aliceVision/linearProgramming/linearProgramming.hpp @@ -13,9 +13,8 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include + #include #endif // Multiple View Geometry solver that rely on Linear programming formulations #include - diff --git a/src/aliceVision/linearProgramming/linearProgramming_test.cpp b/src/aliceVision/linearProgramming/linearProgramming_test.cpp index 20102b2bb1..0f5a4209a2 100644 --- a/src/aliceVision/linearProgramming/linearProgramming_test.cpp +++ b/src/aliceVision/linearProgramming/linearProgramming_test.cpp @@ -11,7 +11,7 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -#include "aliceVision/linearProgramming/MOSEKSolver.hpp" + #include "aliceVision/linearProgramming/MOSEKSolver.hpp" #endif #include "aliceVision/linearProgramming/OSIXSolver.hpp" @@ -31,72 +31,66 @@ using namespace aliceVision::linearProgramming; // x + y <= 75 // x >= 0 // y >= 0 -void BuildLinearProblem(LPConstraints & cstraint) +void BuildLinearProblem(LPConstraints& cstraint) { - cstraint._nbParams = 2; - cstraint._bminimize = false; - - //Configure objective - cstraint._vec_cost.push_back(143); - cstraint._vec_cost.push_back(60); - - cstraint._constraintMat = Mat(5,2); - cstraint._constraintMat << - 120, 210, - 110, 30, - 1, 1, - 1, 0, - 0, 1; - - cstraint._Cst_objective = Vec(5); - cstraint._Cst_objective << 15000, 4000, 75, 0, 0; - - cstraint._vec_sign.resize(5); - std::fill_n(cstraint._vec_sign.begin(), 3, LPConstraints::LP_LESS_OR_EQUAL); - std::fill_n(cstraint._vec_sign.begin()+3, 2, LPConstraints::LP_GREATER_OR_EQUAL); - - cstraint._vec_bounds = std::vector< std::pair >(cstraint._nbParams); - fill(cstraint._vec_bounds.begin(),cstraint._vec_bounds.end(), - std::make_pair((double)-1e+30, (double)1e+30)); + cstraint._nbParams = 2; + cstraint._bminimize = false; + + // Configure objective + cstraint._vec_cost.push_back(143); + cstraint._vec_cost.push_back(60); + + cstraint._constraintMat = Mat(5, 2); + cstraint._constraintMat << 120, 210, 110, 30, 1, 1, 1, 0, 0, 1; + + cstraint._Cst_objective = Vec(5); + cstraint._Cst_objective << 15000, 4000, 75, 0, 0; + + cstraint._vec_sign.resize(5); + std::fill_n(cstraint._vec_sign.begin(), 3, LPConstraints::LP_LESS_OR_EQUAL); + std::fill_n(cstraint._vec_sign.begin() + 3, 2, LPConstraints::LP_GREATER_OR_EQUAL); + + cstraint._vec_bounds = std::vector>(cstraint._nbParams); + fill(cstraint._vec_bounds.begin(), cstraint._vec_bounds.end(), std::make_pair((double)-1e+30, (double)1e+30)); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) // LP_Solve website example solving with the HighLevelFramework -BOOST_AUTO_TEST_CASE(linearProgramming_MOSEK_dense_sample) { - - LPConstraints cstraint; - BuildLinearProblem(cstraint); +BOOST_AUTO_TEST_CASE(linearProgramming_MOSEK_dense_sample) +{ + LPConstraints cstraint; + BuildLinearProblem(cstraint); - //Solve - std::vector vec_solution(2); - MOSEKSolver solver(2); - solver.setup(cstraint); + // Solve + std::vector vec_solution(2); + MOSEKSolver solver(2); + solver.setup(cstraint); - BOOST_CHECK(solver.solve()); - solver.getSolution(vec_solution); + BOOST_CHECK(solver.solve()); + solver.getSolution(vec_solution); - BOOST_CHECK_SMALL( 21.875000-vec_solution[0], 1e-6); - BOOST_CHECK_SMALL( 53.125000-vec_solution[1], 1e-6); + BOOST_CHECK_SMALL(21.875000 - vec_solution[0], 1e-6); + BOOST_CHECK_SMALL(53.125000 - vec_solution[1], 1e-6); - ALICEVISION_LOG_DEBUG("Solution : " << vec_solution[0] << " " << vec_solution[1]); + ALICEVISION_LOG_DEBUG("Solution : " << vec_solution[0] << " " << vec_solution[1]); } -#endif // ALICEVISION_HAVE_MOSEK - -BOOST_AUTO_TEST_CASE(linearProgramming_osiclp_dense_sample) { +#endif // ALICEVISION_HAVE_MOSEK - LPConstraints cstraint; - BuildLinearProblem(cstraint); +BOOST_AUTO_TEST_CASE(linearProgramming_osiclp_dense_sample) +{ + LPConstraints cstraint; + BuildLinearProblem(cstraint); - //Solve - std::vector vec_solution(2); - OSI_CISolverWrapper solver(2); - solver.setup(cstraint); + // Solve + std::vector vec_solution(2); + OSI_CISolverWrapper solver(2); + solver.setup(cstraint); - BOOST_CHECK(solver.solve()); - solver.getSolution(vec_solution); + BOOST_CHECK(solver.solve()); + solver.getSolution(vec_solution); - BOOST_CHECK_SMALL( 21.875000-vec_solution[0], 1e-6); - BOOST_CHECK_SMALL( 53.125000-vec_solution[1], 1e-6); + BOOST_CHECK_SMALL(21.875000 - vec_solution[0], 1e-6); + BOOST_CHECK_SMALL(53.125000 - vec_solution[1], 1e-6); } // Setup example from MOSEK API @@ -109,113 +103,112 @@ BOOST_AUTO_TEST_CASE(linearProgramming_osiclp_dense_sample) { // bounds // 0 <= x0, x2, x3 < infinity // 0 <= x1 <= 10 -void BuildSparseLinearProblem(LPConstraintsSparse & cstraint) +void BuildSparseLinearProblem(LPConstraintsSparse& cstraint) { - // Number of variable we are looking for - cstraint._nbParams = 4; // {x0, x1, x2, x3} - - // Constraint coefficient - sRMat & A = cstraint._constraintMat; - A.resize(3,4); - A.coeffRef(0,0) = 3; - A.coeffRef(0,1) = 1; - A.coeffRef(0,2) = 2; - - A.coeffRef(1,0) = 2; - A.coeffRef(1,1) = 1; - A.coeffRef(1,2) = 3; - A.coeffRef(1,3) = 1; - - A.coeffRef(2,1) = 2; - A.coeffRef(2,3) = 3; - - // Constraint objective - Vec & C = cstraint._Cst_objective; - C.resize(3, 1); - C[0] = 30; - C[1] = 15; - C[2] = 25; - - // Constraint sign - std::vector & vec_sign = cstraint._vec_sign; - vec_sign.resize(3); - vec_sign[0] = LPConstraints::LP_EQUAL; - vec_sign[1] = LPConstraints::LP_GREATER_OR_EQUAL; - vec_sign[2] = LPConstraints::LP_LESS_OR_EQUAL; - - // Variable bounds - cstraint._vec_bounds = std::vector< std::pair >(4); - fill(cstraint._vec_bounds.begin(),cstraint._vec_bounds.end(), - std::make_pair(0.0, (double)1e+30)); - cstraint._vec_bounds[1].second = 10; - - // Objective to maximize - cstraint._bminimize = false; - cstraint._vec_cost.resize(4); - cstraint._vec_cost[0] = 3; - cstraint._vec_cost[1] = 1; - cstraint._vec_cost[2] = 5; - cstraint._vec_cost[3] = 1; + // Number of variable we are looking for + cstraint._nbParams = 4; // {x0, x1, x2, x3} + + // Constraint coefficient + sRMat& A = cstraint._constraintMat; + A.resize(3, 4); + A.coeffRef(0, 0) = 3; + A.coeffRef(0, 1) = 1; + A.coeffRef(0, 2) = 2; + + A.coeffRef(1, 0) = 2; + A.coeffRef(1, 1) = 1; + A.coeffRef(1, 2) = 3; + A.coeffRef(1, 3) = 1; + + A.coeffRef(2, 1) = 2; + A.coeffRef(2, 3) = 3; + + // Constraint objective + Vec& C = cstraint._Cst_objective; + C.resize(3, 1); + C[0] = 30; + C[1] = 15; + C[2] = 25; + + // Constraint sign + std::vector& vec_sign = cstraint._vec_sign; + vec_sign.resize(3); + vec_sign[0] = LPConstraints::LP_EQUAL; + vec_sign[1] = LPConstraints::LP_GREATER_OR_EQUAL; + vec_sign[2] = LPConstraints::LP_LESS_OR_EQUAL; + + // Variable bounds + cstraint._vec_bounds = std::vector>(4); + fill(cstraint._vec_bounds.begin(), cstraint._vec_bounds.end(), std::make_pair(0.0, (double)1e+30)); + cstraint._vec_bounds[1].second = 10; + + // Objective to maximize + cstraint._bminimize = false; + cstraint._vec_cost.resize(4); + cstraint._vec_cost[0] = 3; + cstraint._vec_cost[1] = 1; + cstraint._vec_cost[2] = 5; + cstraint._vec_cost[3] = 1; } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) // Unit test on mosek Sparse constraint -BOOST_AUTO_TEST_CASE(linearProgramming_mosek_sparse_sample) { - - LPConstraintsSparse cstraint; - BuildSparseLinearProblem(cstraint); +BOOST_AUTO_TEST_CASE(linearProgramming_mosek_sparse_sample) +{ + LPConstraintsSparse cstraint; + BuildSparseLinearProblem(cstraint); - //Solve - std::vector vec_solution(4); - MOSEKSolver solver(4); - solver.setup(cstraint); + // Solve + std::vector vec_solution(4); + MOSEKSolver solver(4); + solver.setup(cstraint); - BOOST_CHECK(solver.solve()); - solver.getSolution(vec_solution); + BOOST_CHECK(solver.solve()); + solver.getSolution(vec_solution); - BOOST_CHECK_SMALL(vec_solution[0], 1e-2); - BOOST_CHECK_SMALL(vec_solution[1], 1e-2); - BOOST_CHECK_SMALL( 15-vec_solution[2], 1e-2); - BOOST_CHECK_SMALL( 8.33-vec_solution[3], 1e-2); + BOOST_CHECK_SMALL(vec_solution[0], 1e-2); + BOOST_CHECK_SMALL(vec_solution[1], 1e-2); + BOOST_CHECK_SMALL(15 - vec_solution[2], 1e-2); + BOOST_CHECK_SMALL(8.33 - vec_solution[3], 1e-2); } -#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) - -BOOST_AUTO_TEST_CASE(linearProgramming_osiclp_sparse_sample) { +#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) - LPConstraintsSparse cstraint; - BuildSparseLinearProblem(cstraint); +BOOST_AUTO_TEST_CASE(linearProgramming_osiclp_sparse_sample) +{ + LPConstraintsSparse cstraint; + BuildSparseLinearProblem(cstraint); - //Solve - std::vector vec_solution(4); - OSI_CISolverWrapper solver(4); - solver.setup(cstraint); + // Solve + std::vector vec_solution(4); + OSI_CISolverWrapper solver(4); + solver.setup(cstraint); - BOOST_CHECK(solver.solve()); - solver.getSolution(vec_solution); + BOOST_CHECK(solver.solve()); + solver.getSolution(vec_solution); - BOOST_CHECK_SMALL(vec_solution[0], 1e-2); - BOOST_CHECK_SMALL(vec_solution[1], 1e-2); - BOOST_CHECK_SMALL( 15-vec_solution[2], 1e-2); - BOOST_CHECK_SMALL( 8.33-vec_solution[3], 1e-2); + BOOST_CHECK_SMALL(vec_solution[0], 1e-2); + BOOST_CHECK_SMALL(vec_solution[1], 1e-2); + BOOST_CHECK_SMALL(15 - vec_solution[2], 1e-2); + BOOST_CHECK_SMALL(8.33 - vec_solution[3], 1e-2); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) -BOOST_AUTO_TEST_CASE(linearProgramming_osi_mosek_sparse_sample) { - - LPConstraintsSparse cstraint; - BuildSparseLinearProblem(cstraint); +BOOST_AUTO_TEST_CASE(linearProgramming_osi_mosek_sparse_sample) +{ + LPConstraintsSparse cstraint; + BuildSparseLinearProblem(cstraint); - //Solve - std::vector vec_solution(4); - OSI_MOSEK_SolverWrapper solver(4); - solver.setup(cstraint); + // Solve + std::vector vec_solution(4); + OSI_MOSEK_SolverWrapper solver(4); + solver.setup(cstraint); - BOOST_CHECK(solver.solve()); - solver.getSolution(vec_solution); + BOOST_CHECK(solver.solve()); + solver.getSolution(vec_solution); - BOOST_CHECK_SMALL(vec_solution[0], 1e-2); - BOOST_CHECK_SMALL(vec_solution[1], 1e-2); - BOOST_CHECK_SMALL( 15-vec_solution[2], 1e-2); - BOOST_CHECK_SMALL( 8.33-vec_solution[3], 1e-2); + BOOST_CHECK_SMALL(vec_solution[0], 1e-2); + BOOST_CHECK_SMALL(vec_solution[1], 1e-2); + BOOST_CHECK_SMALL(15 - vec_solution[2], 1e-2); + BOOST_CHECK_SMALL(8.33 - vec_solution[3], 1e-2); } -#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) +#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) diff --git a/src/aliceVision/localization/BoundedBuffer.hpp b/src/aliceVision/localization/BoundedBuffer.hpp index 90bac5651c..a217346c56 100644 --- a/src/aliceVision/localization/BoundedBuffer.hpp +++ b/src/aliceVision/localization/BoundedBuffer.hpp @@ -21,75 +21,73 @@ namespace localization { template class BoundedBuffer { - -private: - - typedef std::deque Buffer; - - Buffer _buffer; + private: + typedef std::deque Buffer; - /// The fixed maximum size for the buffer - std::size_t _maxSize; - -public: - - /** - * @brief Build a bounded buffer of the given size. - * @param[in] maxSize The maximum size of the buffer. Whenever a new element is - * pushed into the buffer, if the buffer is full, the first element is removed - * in a FIFO strategy in order to make place for the new element. - */ - BoundedBuffer(std::size_t maxSize) : _maxSize(maxSize) { } - - typedef typename Buffer::iterator iterator; - typedef typename Buffer::const_iterator const_iterator; + Buffer _buffer; - /** - * @brief Returns an iterator pointing to the first element of the buffer. - * - * @return the iterator pointing to the first element of the buffer. - */ - iterator begin() { return _buffer.begin(); } + /// The fixed maximum size for the buffer + std::size_t _maxSize; - /** - * @brief Returns an iterator pointing to the last element of the buffer. - * - * @return the iterator pointing to the last element of the buffer. - */ - iterator end() { return _buffer.end(); } - - /** - * @brief Returns a const iterator pointing to the first element of the buffer. - * - * @return the const iterator pointing to the first element of the buffer. - */ - const_iterator begin() const { return _buffer.begin(); } + public: + /** + * @brief Build a bounded buffer of the given size. + * @param[in] maxSize The maximum size of the buffer. Whenever a new element is + * pushed into the buffer, if the buffer is full, the first element is removed + * in a FIFO strategy in order to make place for the new element. + */ + BoundedBuffer(std::size_t maxSize) + : _maxSize(maxSize) + {} - /** - * @brief Returns an iterator pointing to the last element of the buffer. - * - * @return the iterator pointing to the last element of the buffer. - */ - const_iterator end() const { return _buffer.end(); } - - /** - * @brief It add a new element at the end of the buffer. If the buffer is full - * the first element is removed before the insertion (FIFO strategy). - * @param[in] fm The element to add. - */ - template - void emplace_back(Args&&... args) - { - assert(_buffer.size() <= _maxSize); + typedef typename Buffer::iterator iterator; + typedef typename Buffer::const_iterator const_iterator; - if(_buffer.size() == _maxSize) + /** + * @brief Returns an iterator pointing to the first element of the buffer. + * + * @return the iterator pointing to the first element of the buffer. + */ + iterator begin() { return _buffer.begin(); } + + /** + * @brief Returns an iterator pointing to the last element of the buffer. + * + * @return the iterator pointing to the last element of the buffer. + */ + iterator end() { return _buffer.end(); } + + /** + * @brief Returns a const iterator pointing to the first element of the buffer. + * + * @return the const iterator pointing to the first element of the buffer. + */ + const_iterator begin() const { return _buffer.begin(); } + + /** + * @brief Returns an iterator pointing to the last element of the buffer. + * + * @return the iterator pointing to the last element of the buffer. + */ + const_iterator end() const { return _buffer.end(); } + + /** + * @brief It add a new element at the end of the buffer. If the buffer is full + * the first element is removed before the insertion (FIFO strategy). + * @param[in] fm The element to add. + */ + template + void emplace_back(Args&&... args) { - _buffer.pop_front(); - } - _buffer.emplace_back(args...); - } + assert(_buffer.size() <= _maxSize); + if (_buffer.size() == _maxSize) + { + _buffer.pop_front(); + } + _buffer.emplace_back(args...); + } }; -} -} +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/CCTagLocalizer.cpp b/src/aliceVision/localization/CCTagLocalizer.cpp index aa974560cc..8ecfc2e0bf 100644 --- a/src/aliceVision/localization/CCTagLocalizer.cpp +++ b/src/aliceVision/localization/CCTagLocalizer.cpp @@ -29,985 +29,919 @@ namespace aliceVision { namespace localization { -CCTagLocalizer::CCTagLocalizer(const sfmData::SfMData &sfmData, - const std::string &descriptorsFolder) - : _cudaPipe( 0 ) +CCTagLocalizer::CCTagLocalizer(const sfmData::SfMData& sfmData, const std::string& descriptorsFolder) + : _cudaPipe(0) { - _sfm_data = sfmData; - - bool loadSuccessful = loadReconstructionDescriptors(_sfm_data, descriptorsFolder); - - if(!loadSuccessful) - { - ALICEVISION_LOG_ERROR("Unable to load the descriptors"); - throw std::invalid_argument("Unable to load the descriptors from "+descriptorsFolder); - } - -// for(const auto & landmark : landmarks) -// { -// // Use the first observation to retrieve the associated descriptor. -// const auto & firstObservation = *landmark.second.obs.begin(); -// -// // Retrieve the Regions of the first observation -// auto & reconstructedRegions = _regions_per_view[firstObservation.first]; -// -// // Get the feature id: remap the index as we only load the reconstructed regions -// const auto localFeatureId = reconstructedRegions._mapFullToLocal[firstObservation.second.id_feat]; -// -// const auto & desc = reconstructedRegions._regions.Descriptors()[localFeatureId]; -// IndexT idCCTag = getCCTagId(desc); -// -// // Insert into a map. -// if (idCCTag!=UndefinedIndexT) -// { -// _cctagDatabase.emplace(idCCTag, landmark.second.X); -// } -// } - - _isInit = true; -} + _sfm_data = sfmData; + + bool loadSuccessful = loadReconstructionDescriptors(_sfm_data, descriptorsFolder); + + if (!loadSuccessful) + { + ALICEVISION_LOG_ERROR("Unable to load the descriptors"); + throw std::invalid_argument("Unable to load the descriptors from " + descriptorsFolder); + } + // for(const auto & landmark : landmarks) + // { + // // Use the first observation to retrieve the associated descriptor. + // const auto & firstObservation = *landmark.second.obs.begin(); + // + // // Retrieve the Regions of the first observation + // auto & reconstructedRegions = _regions_per_view[firstObservation.first]; + // + // // Get the feature id: remap the index as we only load the reconstructed regions + // const auto localFeatureId = reconstructedRegions._mapFullToLocal[firstObservation.second.id_feat]; + // + // const auto & desc = reconstructedRegions._regions.Descriptors()[localFeatureId]; + // IndexT idCCTag = getCCTagId(desc); + // + // // Insert into a map. + // if (idCCTag!=UndefinedIndexT) + // { + // _cctagDatabase.emplace(idCCTag, landmark.second.X); + // } + // } + + _isInit = true; +} -bool CCTagLocalizer::loadReconstructionDescriptors(const sfmData::SfMData & sfm_data, - const std::string & feat_directory) +bool CCTagLocalizer::loadReconstructionDescriptors(const sfmData::SfMData& sfm_data, const std::string& feat_directory) { - ALICEVISION_LOG_DEBUG("Build observations per view"); - - // Build observations per view - std::map>> observationsPerView; - for(const auto& landmarkValue : _sfm_data.getLandmarks()) - { - IndexT trackId = landmarkValue.first; - const sfmData::Landmark& landmark = landmarkValue.second; - - for(const auto& obs : landmark.observations) - { - const IndexT viewId = obs.first; - const sfmData::Observation& obs2d = obs.second; - observationsPerView[viewId][landmark.descType].emplace_back(obs2d.id_feat, trackId); - } - } - for(auto& featuresPerTypeInImage : observationsPerView) - { - for(auto& featuresInImage : featuresPerTypeInImage.second) - { - std::sort(featuresInImage.second.begin(), featuresInImage.second.end()); - } - } - - ALICEVISION_LOG_DEBUG("Load Features and Descriptors per view"); - - std::vector featuresFolders = _sfm_data.getFeaturesFolders(); - featuresFolders.emplace_back(feat_directory); - - // Read for each view the corresponding Regions and store them - for(const auto &iter : _sfm_data.getViews()) - { - const IndexT id_view = iter.second->getViewId(); - if(observationsPerView.count(id_view) == 0) - continue; - const auto& observations = observationsPerView.at(id_view); - { - const feature::EImageDescriberType descType = _imageDescriber.getDescriberType(); - - if(observations.count(descType) == 0) - { - // no descriptor of this type reconstructed in this View - _imageDescriber.allocate(_regionsPerView.getData()[id_view][descType]); - continue; - } - - // Load from files - std::unique_ptr currRegions = sfm::loadRegions(featuresFolders, id_view, _imageDescriber); - - // Filter descriptors to keep only the 3D reconstructed points - _regionsPerView.getData()[id_view][descType] = createFilteredRegions(*currRegions, observations.at(descType), _reconstructedRegionsMappingPerView[id_view][descType]); - } - } - - - { - std::set presentCCtagIds; - std::vector counterCCtagsInImage = {0, 0, 0, 0, 0, 0}; - // just debugging stuff -- print for each image the visible reconstructed cctag - // and create an histogram of cctags per image - for(const auto &iter : sfm_data.getViews()) - { - const IndexT id_view = iter.second->getViewId(); - auto itViewRegions = _regionsPerView.getData().find(id_view); - if(itViewRegions == _regionsPerView.getData().end()) - continue; - auto itViewRegion = itViewRegions->second.find(_cctagDescType); - if(itViewRegion == itViewRegions->second.end()) - continue; - const feature::Regions& regions = *itViewRegion->second; - const std::string &sImageName = iter.second.get()->getImage().getImagePath(); - std::stringstream ss; - - ss << "Image " << sImageName; - if(regions.RegionCount() == 0 ) - { - counterCCtagsInImage[0] += 1; - ss << " does not contain any cctag!!!"; - } - else - { - ss << " contains CCTag Id: "; - const feature::CCTAG_Regions& cctagRegions = dynamic_cast(regions); - for(const auto &desc : cctagRegions.Descriptors()) + ALICEVISION_LOG_DEBUG("Build observations per view"); + + // Build observations per view + std::map>> observationsPerView; + for (const auto& landmarkValue : _sfm_data.getLandmarks()) + { + IndexT trackId = landmarkValue.first; + const sfmData::Landmark& landmark = landmarkValue.second; + + for (const auto& obs : landmark.observations) { - const IndexT cctagIdA = feature::getCCTagId(desc); - if(cctagIdA != UndefinedIndexT) - { - presentCCtagIds.insert(cctagIdA); - ss << cctagIdA << " "; - } + const IndexT viewId = obs.first; + const sfmData::Observation& obs2d = obs.second; + observationsPerView[viewId][landmark.descType].emplace_back(obs2d.id_feat, trackId); + } + } + for (auto& featuresPerTypeInImage : observationsPerView) + { + for (auto& featuresInImage : featuresPerTypeInImage.second) + { + std::sort(featuresInImage.second.begin(), featuresInImage.second.end()); + } + } + + ALICEVISION_LOG_DEBUG("Load Features and Descriptors per view"); + + std::vector featuresFolders = _sfm_data.getFeaturesFolders(); + featuresFolders.emplace_back(feat_directory); + + // Read for each view the corresponding Regions and store them + for (const auto& iter : _sfm_data.getViews()) + { + const IndexT id_view = iter.second->getViewId(); + if (observationsPerView.count(id_view) == 0) + continue; + const auto& observations = observationsPerView.at(id_view); + { + const feature::EImageDescriberType descType = _imageDescriber.getDescriberType(); + + if (observations.count(descType) == 0) + { + // no descriptor of this type reconstructed in this View + _imageDescriber.allocate(_regionsPerView.getData()[id_view][descType]); + continue; + } + + // Load from files + std::unique_ptr currRegions = sfm::loadRegions(featuresFolders, id_view, _imageDescriber); + + // Filter descriptors to keep only the 3D reconstructed points + _regionsPerView.getData()[id_view][descType] = + createFilteredRegions(*currRegions, observations.at(descType), _reconstructedRegionsMappingPerView[id_view][descType]); } - // Update histogram - int countcctag = cctagRegions.Descriptors().size(); - if(countcctag >= 5) - counterCCtagsInImage[5] +=1; - else - counterCCtagsInImage[countcctag] += 1; - } - ALICEVISION_LOG_DEBUG(ss.str()); } - - // Display histogram - ALICEVISION_LOG_DEBUG("Histogram of number of cctags in images :"); - for(std::size_t i = 0; i < 5; i++) - ALICEVISION_LOG_DEBUG("Images with " << i << " CCTags : " << counterCCtagsInImage[i]); - ALICEVISION_LOG_DEBUG("Images with 5+ CCTags : " << counterCCtagsInImage[5]); - // Display the cctag ids over all cctag landmarks present in the database - ALICEVISION_LOG_DEBUG("Found " << presentCCtagIds.size() << " different CCTag ids in the database with " << _sfm_data.getLandmarks().size() << " associated 3D points\n" - "The CCTag ids in the database are: "); - for(int cctagId: presentCCtagIds) { - ALICEVISION_LOG_DEBUG(cctagId); + std::set presentCCtagIds; + std::vector counterCCtagsInImage = {0, 0, 0, 0, 0, 0}; + // just debugging stuff -- print for each image the visible reconstructed cctag + // and create an histogram of cctags per image + for (const auto& iter : sfm_data.getViews()) + { + const IndexT id_view = iter.second->getViewId(); + auto itViewRegions = _regionsPerView.getData().find(id_view); + if (itViewRegions == _regionsPerView.getData().end()) + continue; + auto itViewRegion = itViewRegions->second.find(_cctagDescType); + if (itViewRegion == itViewRegions->second.end()) + continue; + const feature::Regions& regions = *itViewRegion->second; + const std::string& sImageName = iter.second.get()->getImage().getImagePath(); + std::stringstream ss; + + ss << "Image " << sImageName; + if (regions.RegionCount() == 0) + { + counterCCtagsInImage[0] += 1; + ss << " does not contain any cctag!!!"; + } + else + { + ss << " contains CCTag Id: "; + const feature::CCTAG_Regions& cctagRegions = dynamic_cast(regions); + for (const auto& desc : cctagRegions.Descriptors()) + { + const IndexT cctagIdA = feature::getCCTagId(desc); + if (cctagIdA != UndefinedIndexT) + { + presentCCtagIds.insert(cctagIdA); + ss << cctagIdA << " "; + } + } + // Update histogram + int countcctag = cctagRegions.Descriptors().size(); + if (countcctag >= 5) + counterCCtagsInImage[5] += 1; + else + counterCCtagsInImage[countcctag] += 1; + } + ALICEVISION_LOG_DEBUG(ss.str()); + } + + // Display histogram + ALICEVISION_LOG_DEBUG("Histogram of number of cctags in images :"); + for (std::size_t i = 0; i < 5; i++) + ALICEVISION_LOG_DEBUG("Images with " << i << " CCTags : " << counterCCtagsInImage[i]); + ALICEVISION_LOG_DEBUG("Images with 5+ CCTags : " << counterCCtagsInImage[5]); + + // Display the cctag ids over all cctag landmarks present in the database + ALICEVISION_LOG_DEBUG("Found " << presentCCtagIds.size() << " different CCTag ids in the database with " << _sfm_data.getLandmarks().size() + << " associated 3D points\n" + "The CCTag ids in the database are: "); + for (int cctagId : presentCCtagIds) + { + ALICEVISION_LOG_DEBUG(cctagId); + } } - } - return true; + return true; } -bool CCTagLocalizer::localize(const image::Image & imageGrey, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, +bool CCTagLocalizer::localize(const image::Image& imageGrey, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, const std::string& imagePath) { - namespace bfs = boost::filesystem; - - const CCTagLocalizer::Parameters *param = static_cast(parameters); - if(!param) - { - throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); - } - // extract descriptors and features from image - ALICEVISION_LOG_DEBUG("[features]\tExtract CCTag from query image"); - - image::Image imageGrayUChar; // cctag image describer don't support float image - imageGrayUChar = (imageGrey.GetMat() * 255.f).cast(); - - feature::MapRegionsPerDesc tmpQueryRegions; - - _imageDescriber.setCudaPipe( _cudaPipe ); - _imageDescriber.setConfigurationPreset(param->_featurePreset); - _imageDescriber.describe(imageGrayUChar, tmpQueryRegions[_cctagDescType]); - ALICEVISION_LOG_DEBUG("[features]\tExtract CCTAG done: found " << tmpQueryRegions.at(_cctagDescType)->RegionCount() << " features"); - - std::pair imageSize = std::make_pair(imageGrey.Width(),imageGrey.Height()); - - if(!param->_visualDebug.empty() && !imagePath.empty()) - { - // it automatically throws an exception if the cast does not work - const feature::CCTAG_Regions & cctagQueryRegions = tmpQueryRegions.getRegions(_cctagDescType); - - // just debugging -- save the svg image with detected cctag - matching::saveCCTag2SVG(imagePath, - imageSize, - cctagQueryRegions, - param->_visualDebug+"/"+bfs::path(imagePath).stem().string()+".svg"); - } - return localize(tmpQueryRegions, - imageSize, - parameters, - randomNumberGenerator, - useInputIntrinsics, - queryIntrinsics, - localizationResult, - imagePath); -} + namespace bfs = boost::filesystem; -void CCTagLocalizer::setCudaPipe( int i ) -{ - _cudaPipe = i; + const CCTagLocalizer::Parameters* param = static_cast(parameters); + if (!param) + { + throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); + } + // extract descriptors and features from image + ALICEVISION_LOG_DEBUG("[features]\tExtract CCTag from query image"); + + image::Image imageGrayUChar; // cctag image describer don't support float image + imageGrayUChar = (imageGrey.GetMat() * 255.f).cast(); + + feature::MapRegionsPerDesc tmpQueryRegions; + + _imageDescriber.setCudaPipe(_cudaPipe); + _imageDescriber.setConfigurationPreset(param->_featurePreset); + _imageDescriber.describe(imageGrayUChar, tmpQueryRegions[_cctagDescType]); + ALICEVISION_LOG_DEBUG("[features]\tExtract CCTAG done: found " << tmpQueryRegions.at(_cctagDescType)->RegionCount() << " features"); + + std::pair imageSize = std::make_pair(imageGrey.Width(), imageGrey.Height()); + + if (!param->_visualDebug.empty() && !imagePath.empty()) + { + // it automatically throws an exception if the cast does not work + const feature::CCTAG_Regions& cctagQueryRegions = tmpQueryRegions.getRegions(_cctagDescType); + + // just debugging -- save the svg image with detected cctag + matching::saveCCTag2SVG(imagePath, imageSize, cctagQueryRegions, param->_visualDebug + "/" + bfs::path(imagePath).stem().string() + ".svg"); + } + return localize( + tmpQueryRegions, imageSize, parameters, randomNumberGenerator, useInputIntrinsics, queryIntrinsics, localizationResult, imagePath); } -bool CCTagLocalizer::localize(const feature::MapRegionsPerDesc & genQueryRegions, - const std::pair &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, +void CCTagLocalizer::setCudaPipe(int i) { _cudaPipe = i; } + +bool CCTagLocalizer::localize(const feature::MapRegionsPerDesc& genQueryRegions, + const std::pair& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, const std::string& imagePath) { - namespace bfs = boost::filesystem; - - const CCTagLocalizer::Parameters *param = dynamic_cast(parameters); - if(!param) - { - throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); - } - - // it automatically throws an exception if the cast does not work - const feature::CCTAG_Regions &queryRegions = genQueryRegions.getRegions(_cctagDescType); - - // a map containing for each pair the number of times that - // the association has been seen - std::map occurences; - sfm::ImageLocalizerMatchData resectionData; - - - std::vector matchedImages; - system::Timer timer; - getAllAssociations(queryRegions, imageSize, *param, randomNumberGenerator, occurences, resectionData.pt2D, resectionData.pt3D, matchedImages, imagePath); - - resectionData.vec_descType.resize(resectionData.pt2D.cols(), _cctagDescType); - ALICEVISION_LOG_DEBUG("[Matching]\tRetrieving associations took " << timer.elapsedMs() << "ms"); - - const std::size_t numCollectedPts = occurences.size(); - - // create an vector of - std::vector associationIDs; - associationIDs.reserve(numCollectedPts); - - for(const auto &ass : occurences) - { - // recopy the associations IDs in the vector - associationIDs.push_back(ass.first); - } - - assert(associationIDs.size() == numCollectedPts); - assert(resectionData.pt2D.cols() == numCollectedPts); - assert(resectionData.pt3D.cols() == numCollectedPts); - - geometry::Pose3 pose; - - timer.reset(); - // estimate the pose - resectionData.error_max = param->_errorMax; - ALICEVISION_LOG_DEBUG("[poseEstimation]\tEstimating camera pose..."); - const bool bResection = sfm::SfMLocalizer::Localize(imageSize, - // pass the input intrinsic if they are valid, null otherwise - (useInputIntrinsics) ? &queryIntrinsics : nullptr, - randomNumberGenerator, - resectionData, - pose, - param->_resectionEstimator); - - if(!bResection) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection failed"); - if(!param->_visualDebug.empty() && !imagePath.empty()) - { -// namespace bfs = boost::filesystem; -// matching::saveFeatures2SVG(imagePath, -// imageSize, -// resectionData.pt2D, -// param._visualDebug + "/" + bfs::path(imagePath).stem().string() + ".associations.svg"); - } - localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, bResection); + namespace bfs = boost::filesystem; + + const CCTagLocalizer::Parameters* param = dynamic_cast(parameters); + if (!param) + { + throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); + } + + // it automatically throws an exception if the cast does not work + const feature::CCTAG_Regions& queryRegions = genQueryRegions.getRegions(_cctagDescType); + + // a map containing for each pair the number of times that + // the association has been seen + std::map occurences; + sfm::ImageLocalizerMatchData resectionData; + + std::vector matchedImages; + system::Timer timer; + getAllAssociations( + queryRegions, imageSize, *param, randomNumberGenerator, occurences, resectionData.pt2D, resectionData.pt3D, matchedImages, imagePath); + + resectionData.vec_descType.resize(resectionData.pt2D.cols(), _cctagDescType); + ALICEVISION_LOG_DEBUG("[Matching]\tRetrieving associations took " << timer.elapsedMs() << "ms"); + + const std::size_t numCollectedPts = occurences.size(); + + // create an vector of + std::vector associationIDs; + associationIDs.reserve(numCollectedPts); + + for (const auto& ass : occurences) + { + // recopy the associations IDs in the vector + associationIDs.push_back(ass.first); + } + + assert(associationIDs.size() == numCollectedPts); + assert(resectionData.pt2D.cols() == numCollectedPts); + assert(resectionData.pt3D.cols() == numCollectedPts); + + geometry::Pose3 pose; + + timer.reset(); + // estimate the pose + resectionData.error_max = param->_errorMax; + ALICEVISION_LOG_DEBUG("[poseEstimation]\tEstimating camera pose..."); + const bool bResection = sfm::SfMLocalizer::Localize(imageSize, + // pass the input intrinsic if they are valid, null otherwise + (useInputIntrinsics) ? &queryIntrinsics : nullptr, + randomNumberGenerator, + resectionData, + pose, + param->_resectionEstimator); + + if (!bResection) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection failed"); + if (!param->_visualDebug.empty() && !imagePath.empty()) + { + // namespace bfs = boost::filesystem; + // matching::saveFeatures2SVG(imagePath, + // imageSize, + // resectionData.pt2D, + // param._visualDebug + "/" + bfs::path(imagePath).stem().string() + ".associations.svg"); + } + localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, bResection); + return localizationResult.isValid(); + } + ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection SUCCEDED"); + + ALICEVISION_LOG_DEBUG("R est\n" << pose.rotation()); + ALICEVISION_LOG_DEBUG("t est\n" << pose.translation()); + + // if we didn't use the provided intrinsics, estimate K from the projection + // matrix estimated by the localizer and initialize the queryIntrinsics with + // it and the image size. This will provide a first guess for the refine function + if (!useInputIntrinsics) + { + // Decompose P matrix + Mat3 K_, R_; + Vec3 t_; + // Decompose the projection matrix to get K, R and t using + // RQ decomposition + KRt_from_P(resectionData.projection_matrix, K_, R_, t_); + queryIntrinsics.setK(K_); + ALICEVISION_LOG_DEBUG("K estimated\n" << K_); + queryIntrinsics.setWidth(imageSize.first); + queryIntrinsics.setHeight(imageSize.second); + } + + // refine the estimated pose + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefining estimated pose"); + const bool b_refine_pose = true; + const bool refineStatus = sfm::SfMLocalizer::RefinePose(&queryIntrinsics, pose, resectionData, b_refine_pose, param->_refineIntrinsics); + if (!refineStatus) + ALICEVISION_LOG_DEBUG("Refine pose failed."); + + if (!param->_visualDebug.empty() && !imagePath.empty()) + { + //@todo save image with cctag with different code color for inliers + } + + ALICEVISION_LOG_DEBUG("[poseEstimation]\tPose estimation took " << timer.elapsedMs() << "ms."); + + localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, refineStatus); + + { + // just debugging this block can be safely removed or commented out + ALICEVISION_LOG_DEBUG("R refined\n" << pose.rotation()); + ALICEVISION_LOG_DEBUG("t refined\n" << pose.translation()); + ALICEVISION_LOG_DEBUG("K refined\n" << queryIntrinsics.K()); + + const Mat2X residuals = localizationResult.computeInliersResiduals(); + + const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + ALICEVISION_LOG_DEBUG("RMSE = " << std::sqrt(sqrErrors.mean()) << " min = " << std::sqrt(sqrErrors.minCoeff()) + << " max = " << std::sqrt(sqrErrors.maxCoeff())); + } + return localizationResult.isValid(); - } - ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection SUCCEDED"); - - ALICEVISION_LOG_DEBUG("R est\n" << pose.rotation()); - ALICEVISION_LOG_DEBUG("t est\n" << pose.translation()); - - // if we didn't use the provided intrinsics, estimate K from the projection - // matrix estimated by the localizer and initialize the queryIntrinsics with - // it and the image size. This will provide a first guess for the refine function - if(!useInputIntrinsics) - { - // Decompose P matrix - Mat3 K_, R_; - Vec3 t_; - // Decompose the projection matrix to get K, R and t using - // RQ decomposition - KRt_from_P(resectionData.projection_matrix, K_, R_, t_); - queryIntrinsics.setK(K_); - ALICEVISION_LOG_DEBUG("K estimated\n" << K_); - queryIntrinsics.setWidth(imageSize.first); - queryIntrinsics.setHeight(imageSize.second); - } - - // refine the estimated pose - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefining estimated pose"); - const bool b_refine_pose = true; - const bool refineStatus = sfm::SfMLocalizer::RefinePose(&queryIntrinsics, - pose, - resectionData, - b_refine_pose, - param->_refineIntrinsics); - if(!refineStatus) - ALICEVISION_LOG_DEBUG("Refine pose failed."); - - if(!param->_visualDebug.empty() && !imagePath.empty()) - { - //@todo save image with cctag with different code color for inliers - } - - ALICEVISION_LOG_DEBUG("[poseEstimation]\tPose estimation took " << timer.elapsedMs() << "ms."); - - localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, refineStatus); - - { - // just debugging this block can be safely removed or commented out - ALICEVISION_LOG_DEBUG("R refined\n" << pose.rotation()); - ALICEVISION_LOG_DEBUG("t refined\n" << pose.translation()); - ALICEVISION_LOG_DEBUG("K refined\n" << queryIntrinsics.K()); - - const Mat2X residuals = localizationResult.computeInliersResiduals(); - - const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - ALICEVISION_LOG_DEBUG("RMSE = " << std::sqrt(sqrErrors.mean()) - << " min = " << std::sqrt(sqrErrors.minCoeff()) - << " max = " << std::sqrt(sqrErrors.maxCoeff())); - } - - return localizationResult.isValid(); - - } -CCTagLocalizer::~CCTagLocalizer() -{ -} +CCTagLocalizer::~CCTagLocalizer() {} -// subposes is n-1 as we consider the first camera as the main camera and the +// subposes is n-1 as we consider the first camera as the main camera and the // reference frame of the grid -bool CCTagLocalizer::localizeRig(const std::vector> & vec_imageGrey, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector & vec_locResults) +bool CCTagLocalizer::localizeRig(const std::vector>& vec_imageGrey, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) { - const CCTagLocalizer::Parameters *param = static_cast(parameters); - if(!param) - { - throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); - } - const size_t numCams = vec_imageGrey.size(); - assert(numCams == vec_queryIntrinsics.size()); - assert(numCams == vec_subPoses.size() + 1); - - std::vector vec_queryRegions(numCams); - std::vector > vec_imageSize; - - //@todo parallelize? - for(size_t i = 0; i < numCams; ++i) - { - image::Image imageGrayUChar; // cctag image describer don't support float image - imageGrayUChar = (vec_imageGrey.at(i).GetMat() * 255.f).cast(); - - // extract descriptors and features from each image - ALICEVISION_LOG_DEBUG("[features]\tExtract CCTag from query image..."); - _imageDescriber.setConfigurationPreset(param->_featurePreset); - _imageDescriber.describe(imageGrayUChar, vec_queryRegions[i][_imageDescriber.getDescriberType()]); - ALICEVISION_LOG_DEBUG("[features]\tExtract CCTAG done: found " << vec_queryRegions[i].at(_imageDescriber.getDescriberType())->RegionCount() << " features"); - // add the image size for this image - vec_imageSize.emplace_back(vec_imageGrey[i].Width(), vec_imageGrey[i].Height()); - } - assert(vec_imageSize.size() == vec_queryRegions.size()); - - return localizeRig(vec_queryRegions, - vec_imageSize, - parameters, - randomNumberGenerator, - vec_queryIntrinsics, - vec_subPoses, - rigPose, - vec_locResults); + const CCTagLocalizer::Parameters* param = static_cast(parameters); + if (!param) + { + throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); + } + const size_t numCams = vec_imageGrey.size(); + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); + + std::vector vec_queryRegions(numCams); + std::vector> vec_imageSize; + + //@todo parallelize? + for (size_t i = 0; i < numCams; ++i) + { + image::Image imageGrayUChar; // cctag image describer don't support float image + imageGrayUChar = (vec_imageGrey.at(i).GetMat() * 255.f).cast(); + + // extract descriptors and features from each image + ALICEVISION_LOG_DEBUG("[features]\tExtract CCTag from query image..."); + _imageDescriber.setConfigurationPreset(param->_featurePreset); + _imageDescriber.describe(imageGrayUChar, vec_queryRegions[i][_imageDescriber.getDescriberType()]); + ALICEVISION_LOG_DEBUG("[features]\tExtract CCTAG done: found " << vec_queryRegions[i].at(_imageDescriber.getDescriberType())->RegionCount() + << " features"); + // add the image size for this image + vec_imageSize.emplace_back(vec_imageGrey[i].Width(), vec_imageGrey[i].Height()); + } + assert(vec_imageSize.size() == vec_queryRegions.size()); + + return localizeRig( + vec_queryRegions, vec_imageSize, parameters, randomNumberGenerator, vec_queryIntrinsics, vec_subPoses, rigPose, vec_locResults); } -bool CCTagLocalizer::localizeRig(const std::vector & vec_queryRegions, - const std::vector > &vec_imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, +bool CCTagLocalizer::localizeRig(const std::vector& vec_queryRegions, + const std::vector>& vec_imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, std::vector& vec_locResults) { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) - if(!parameters->_useLocalizeRigNaive) - { - ALICEVISION_LOG_DEBUG("Using localizeRig_opengv()"); - return localizeRig_opengv(vec_queryRegions, - vec_imageSize, - parameters, - randomNumberGenerator, - vec_queryIntrinsics, - vec_subPoses, - rigPose, - vec_locResults); - } - else + if (!parameters->_useLocalizeRigNaive) + { + ALICEVISION_LOG_DEBUG("Using localizeRig_opengv()"); + return localizeRig_opengv( + vec_queryRegions, vec_imageSize, parameters, randomNumberGenerator, vec_queryIntrinsics, vec_subPoses, rigPose, vec_locResults); + } + else #endif - { - if(!parameters->_useLocalizeRigNaive) - ALICEVISION_LOG_DEBUG("OpenGV is not available. Fallback to localizeRig_naive()."); - return localizeRig_naive(vec_queryRegions, - vec_imageSize, - parameters, - randomNumberGenerator, - vec_queryIntrinsics, - vec_subPoses, - rigPose, - vec_locResults); - } + { + if (!parameters->_useLocalizeRigNaive) + ALICEVISION_LOG_DEBUG("OpenGV is not available. Fallback to localizeRig_naive()."); + return localizeRig_naive( + vec_queryRegions, vec_imageSize, parameters, randomNumberGenerator, vec_queryIntrinsics, vec_subPoses, rigPose, vec_locResults); + } } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) -bool CCTagLocalizer::localizeRig_opengv(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults) +bool CCTagLocalizer::localizeRig_opengv(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) { - const size_t numCams = vec_queryRegions.size(); - assert(numCams == vec_queryIntrinsics.size()); - assert(numCams == vec_subPoses.size() + 1); - - vec_locResults.clear(); - vec_locResults.reserve(numCams); - - const CCTagLocalizer::Parameters *param = static_cast(parameters); - if(!param) - { - throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); - } - - // each element of the vector is a map containing for each pair - // the number of times that the association has been seen. One element fo the - // vector for each camera. - std::vector vec_occurrences(numCams); - std::vector vec_pts3D(numCams); - std::vector vec_pts2D(numCams); - std::vector > vec_matchedImages(numCams); - - // for each camera retrieve the associations - //@todo parallelize? - size_t numAssociations = 0; - for( size_t i = 0; i < numCams; ++i ) - { - // this map is used to collect the 2d-3d associations as we go through the images - // the key is a pair - // the element is the pair 3D point - 2D point - auto &occurrences = vec_occurrences[i]; - auto &matchedImages = vec_matchedImages[i]; - Mat &pts3D = vec_pts3D[i]; - Mat &pts2D = vec_pts2D[i]; - const feature::CCTAG_Regions &queryRegions = vec_queryRegions[i].getRegions(_cctagDescType); - getAllAssociations(queryRegions, imageSize[i],*param, randomNumberGenerator, occurrences, pts2D, pts3D, matchedImages); - numAssociations += occurrences.size(); - } - - // @todo Here it could be possible to filter the associations according to their - // occurrences, eg giving priority to those associations that are more frequent - - const size_t minNumAssociations = 4; //possible parameter? - if(numAssociations < minNumAssociations) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tonly " << numAssociations << " have been found, not enough to do the resection!"); - for(std::size_t cam = 0; cam < numCams; ++cam) - { - // empty result with isValid set to false - vec_locResults.emplace_back(); - } - return false; - } - - std::vector > vec_inliers; - const EstimationStatus status = rigResection(vec_pts2D, - vec_pts3D, - vec_queryIntrinsics, - vec_subPoses, - nullptr, - rigPose, - vec_inliers, - param->_angularThreshold); - - if(!status.isValid) - { - for(std::size_t cam = 0; cam < numCams; ++cam) - { - // empty result with isValid set to false - vec_locResults.emplace_back(); - } - return false; - } - - { - if(vec_inliers.size() < numCams) - { - // in general the inlier should be spread among different cameras - ALICEVISION_CERR("WARNING: RIG Voctree Localizer: Inliers in " - << vec_inliers.size() << " cameras on a RIG of " - << numCams << " cameras."); - } - - for(std::size_t camID = 0; camID < vec_inliers.size(); ++camID) - ALICEVISION_LOG_DEBUG("#inliers for cam " << camID << ": " << vec_inliers[camID].size()); - - ALICEVISION_LOG_DEBUG("Pose after resection:"); - ALICEVISION_LOG_DEBUG("Rotation: " << rigPose.rotation()); - ALICEVISION_LOG_DEBUG("Centre: " << rigPose.center()); - - // debugging stats - // print the reprojection error for inliers (just debugging purposes) - printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); - } - -// const bool refineOk = refineRigPose(vec_pts2D, -// vec_pts3D, -// vec_inliers, -// vec_queryIntrinsics, -// vec_subPoses, -// rigPose); - aliceVision::system::Timer timer; - const std::size_t minNumPoints = 4; - const bool refineOk = iterativeRefineRigPose(vec_pts2D, - vec_pts3D, - vec_queryIntrinsics, - vec_subPoses, - param->_errorMax, - minNumPoints, - vec_inliers, - rigPose); - ALICEVISION_LOG_DEBUG("Iterative refinement took " << timer.elapsedMs() << "ms"); - - { - // debugging stats - // print the reprojection error for inliers (just debugging purposes) - printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); - } - - // create localization results - for(std::size_t cam = 0; cam < numCams; ++cam) - { - - const auto &intrinsics = vec_queryIntrinsics[cam]; - - // compute the (absolute) pose of each camera: for the main camera it's the - // rig pose, for the others, combine the subpose with the rig pose - geometry::Pose3 pose; - if(cam == 0) + const size_t numCams = vec_queryRegions.size(); + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); + + vec_locResults.clear(); + vec_locResults.reserve(numCams); + + const CCTagLocalizer::Parameters* param = static_cast(parameters); + if (!param) { - pose = rigPose; + throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); } - else + + // each element of the vector is a map containing for each pair + // the number of times that the association has been seen. One element fo the + // vector for each camera. + std::vector vec_occurrences(numCams); + std::vector vec_pts3D(numCams); + std::vector vec_pts2D(numCams); + std::vector> vec_matchedImages(numCams); + + // for each camera retrieve the associations + //@todo parallelize? + size_t numAssociations = 0; + for (size_t i = 0; i < numCams; ++i) + { + // this map is used to collect the 2d-3d associations as we go through the images + // the key is a pair + // the element is the pair 3D point - 2D point + auto& occurrences = vec_occurrences[i]; + auto& matchedImages = vec_matchedImages[i]; + Mat& pts3D = vec_pts3D[i]; + Mat& pts2D = vec_pts2D[i]; + const feature::CCTAG_Regions& queryRegions = vec_queryRegions[i].getRegions(_cctagDescType); + getAllAssociations(queryRegions, imageSize[i], *param, randomNumberGenerator, occurrences, pts2D, pts3D, matchedImages); + numAssociations += occurrences.size(); + } + + // @todo Here it could be possible to filter the associations according to their + // occurrences, eg giving priority to those associations that are more frequent + + const size_t minNumAssociations = 4; // possible parameter? + if (numAssociations < minNumAssociations) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tonly " << numAssociations << " have been found, not enough to do the resection!"); + for (std::size_t cam = 0; cam < numCams; ++cam) + { + // empty result with isValid set to false + vec_locResults.emplace_back(); + } + return false; + } + + std::vector> vec_inliers; + const EstimationStatus status = + rigResection(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, nullptr, rigPose, vec_inliers, param->_angularThreshold); + + if (!status.isValid) { - // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q - // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A - // and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) - // With rigResection() we compute [R1 t1] (aka rigPose), hence: - // subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) and we need [R2 t2], ie the absolute pose - // => [R1 t1] * subPose12 = [R2 t2] - // => rigPose * subPose12 = [R2 t2] - pose = vec_subPoses[cam-1] * rigPose; - } - - // create matchData - sfm::ImageLocalizerMatchData matchData; - matchData.vec_inliers = vec_inliers[cam]; - matchData.error_max = param->_errorMax; - matchData.projection_matrix = intrinsics.getProjectiveEquivalent(pose); - matchData.pt2D = vec_pts2D[cam]; - matchData.pt3D = vec_pts3D[cam]; - - // create indMatch3D2D - std::vector indMatch3D2D; - indMatch3D2D.reserve(matchData.pt2D.cols()); - const auto &occurrences = vec_occurrences[cam]; - for(const auto &ass : occurrences) - { - // recopy the associations IDs in the vector - indMatch3D2D.push_back(ass.first); - } - - vec_locResults.emplace_back(matchData, indMatch3D2D, pose, intrinsics, vec_matchedImages[cam], refineOk); - } - - if(!refineOk) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefine failed."); - return false; - } - - return true; - + for (std::size_t cam = 0; cam < numCams; ++cam) + { + // empty result with isValid set to false + vec_locResults.emplace_back(); + } + return false; + } + + { + if (vec_inliers.size() < numCams) + { + // in general the inlier should be spread among different cameras + ALICEVISION_CERR("WARNING: RIG Voctree Localizer: Inliers in " << vec_inliers.size() << " cameras on a RIG of " << numCams + << " cameras."); + } + + for (std::size_t camID = 0; camID < vec_inliers.size(); ++camID) + ALICEVISION_LOG_DEBUG("#inliers for cam " << camID << ": " << vec_inliers[camID].size()); + + ALICEVISION_LOG_DEBUG("Pose after resection:"); + ALICEVISION_LOG_DEBUG("Rotation: " << rigPose.rotation()); + ALICEVISION_LOG_DEBUG("Centre: " << rigPose.center()); + + // debugging stats + // print the reprojection error for inliers (just debugging purposes) + printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); + } + + // const bool refineOk = refineRigPose(vec_pts2D, + // vec_pts3D, + // vec_inliers, + // vec_queryIntrinsics, + // vec_subPoses, + // rigPose); + aliceVision::system::Timer timer; + const std::size_t minNumPoints = 4; + const bool refineOk = + iterativeRefineRigPose(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, param->_errorMax, minNumPoints, vec_inliers, rigPose); + ALICEVISION_LOG_DEBUG("Iterative refinement took " << timer.elapsedMs() << "ms"); + + { + // debugging stats + // print the reprojection error for inliers (just debugging purposes) + printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); + } + + // create localization results + for (std::size_t cam = 0; cam < numCams; ++cam) + { + const auto& intrinsics = vec_queryIntrinsics[cam]; + + // compute the (absolute) pose of each camera: for the main camera it's the + // rig pose, for the others, combine the subpose with the rig pose + geometry::Pose3 pose; + if (cam == 0) + { + pose = rigPose; + } + else + { + // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q + // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A + // and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) + // With rigResection() we compute [R1 t1] (aka rigPose), hence: + // subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) and we need [R2 t2], ie the absolute pose + // => [R1 t1] * subPose12 = [R2 t2] + // => rigPose * subPose12 = [R2 t2] + pose = vec_subPoses[cam - 1] * rigPose; + } + + // create matchData + sfm::ImageLocalizerMatchData matchData; + matchData.vec_inliers = vec_inliers[cam]; + matchData.error_max = param->_errorMax; + matchData.projection_matrix = intrinsics.getProjectiveEquivalent(pose); + matchData.pt2D = vec_pts2D[cam]; + matchData.pt3D = vec_pts3D[cam]; + + // create indMatch3D2D + std::vector indMatch3D2D; + indMatch3D2D.reserve(matchData.pt2D.cols()); + const auto& occurrences = vec_occurrences[cam]; + for (const auto& ass : occurrences) + { + // recopy the associations IDs in the vector + indMatch3D2D.push_back(ass.first); + } + + vec_locResults.emplace_back(matchData, indMatch3D2D, pose, intrinsics, vec_matchedImages[cam], refineOk); + } + + if (!refineOk) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefine failed."); + return false; + } + + return true; } - -#endif //ALICEVISION_HAVE_OPENGV -// subposes is n-1 as we consider the first camera as the main camera and the +#endif // ALICEVISION_HAVE_OPENGV + +// subposes is n-1 as we consider the first camera as the main camera and the // reference frame of the grid -bool CCTagLocalizer::localizeRig_naive(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_localizationResults) +bool CCTagLocalizer::localizeRig_naive(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_localizationResults) { - const CCTagLocalizer::Parameters *param = dynamic_cast(parameters); - if(!param) - { - throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); - } - - const size_t numCams = vec_queryRegions.size(); - - assert(numCams==vec_queryIntrinsics.size()); - assert(numCams==vec_subPoses.size()+1); - assert(numCams==imageSize.size()); - - vec_localizationResults.resize(numCams); - - // this is basic, just localize each camera alone - //@todo parallelize? - std::vector isLocalized(numCams, false); - for(size_t i = 0; i < numCams; ++i) - { - isLocalized[i] = localize(vec_queryRegions[i], imageSize[i], param, randomNumberGenerator, true /*useInputIntrinsics*/, vec_queryIntrinsics[i], vec_localizationResults[i]); - if(!isLocalized[i]) - { - ALICEVISION_CERR("Could not localize camera " << i); - // even if it is not localize we can try to go on and do with the cameras we have - } - } - - // ** 'easy' cases in which we don't need further processing ** - - const std::size_t numLocalizedCam = std::count(isLocalized.begin(), isLocalized.end(), true); - - // no camera has be localized - if(numLocalizedCam == 0) - { - ALICEVISION_LOG_DEBUG("No camera has been localized!!!"); - return false; - } - - ALICEVISION_LOG_DEBUG("Localized cameras: " << numLocalizedCam << "/" << numCams); - - // if there is only one camera (the main one) - if(numCams==1) - { - // there is only the main camera, not much else to do, the position is already - // refined by the call to localize - //set the pose - rigPose = vec_localizationResults[0].getPose(); - } - else - { - - // find the index of the first localized camera - const std::size_t idx = std::distance(isLocalized.begin(), - std::find(isLocalized.begin(), isLocalized.end(), true)); - - // useless safeguard as there should be at least 1 element at this point but - // better safe than sorry - assert(idx < isLocalized.size()); - - ALICEVISION_LOG_DEBUG("Index of the first localized camera: " << idx); - - // if the only localized camera is the main camera - if(idx==0) - { - // just give its pose - rigPose = vec_localizationResults[0].getPose(); + const CCTagLocalizer::Parameters* param = dynamic_cast(parameters); + if (!param) + { + throw std::invalid_argument("The CCTag localizer parameters are not in the right format."); + } + + const size_t numCams = vec_queryRegions.size(); + + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); + assert(numCams == imageSize.size()); + + vec_localizationResults.resize(numCams); + + // this is basic, just localize each camera alone + //@todo parallelize? + std::vector isLocalized(numCams, false); + for (size_t i = 0; i < numCams; ++i) + { + isLocalized[i] = localize(vec_queryRegions[i], + imageSize[i], + param, + randomNumberGenerator, + true /*useInputIntrinsics*/, + vec_queryIntrinsics[i], + vec_localizationResults[i]); + if (!isLocalized[i]) + { + ALICEVISION_CERR("Could not localize camera " << i); + // even if it is not localize we can try to go on and do with the cameras we have + } + } + + // ** 'easy' cases in which we don't need further processing ** + + const std::size_t numLocalizedCam = std::count(isLocalized.begin(), isLocalized.end(), true); + + // no camera has be localized + if (numLocalizedCam == 0) + { + ALICEVISION_LOG_DEBUG("No camera has been localized!!!"); + return false; + } + + ALICEVISION_LOG_DEBUG("Localized cameras: " << numLocalizedCam << "/" << numCams); + + // if there is only one camera (the main one) + if (numCams == 1) + { + // there is only the main camera, not much else to do, the position is already + // refined by the call to localize + // set the pose + rigPose = vec_localizationResults[0].getPose(); } else { - // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q - // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) - // with the localization localize() we have computed [R2 t2], hence: - // q2 ~ [R2 t2] Q = [R12 t12]*inv([R12 t12]) * [R2 t2] Q - // and inv([R12 t12]) * [R2 t2] is the pose of the main camera - - // recover the rig pose using the subposes - rigPose = vec_subPoses[idx-1].inverse() * vec_localizationResults[idx].getPose(); - } - } - - // ** otherwise run a BA with the localized cameras - const bool refineOk = refineRigPose(vec_subPoses, vec_localizationResults, rigPose); - - if(!refineOk) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRig pose refinement failed."); - return false; - } - - updateRigPoses(vec_localizationResults, rigPose, vec_subPoses); - - return true; -} + // find the index of the first localized camera + const std::size_t idx = std::distance(isLocalized.begin(), std::find(isLocalized.begin(), isLocalized.end(), true)); + // useless safeguard as there should be at least 1 element at this point but + // better safe than sorry + assert(idx < isLocalized.size()); -void CCTagLocalizer::getAllAssociations(const feature::CCTAG_Regions &queryRegions, - const std::pair &imageSize, - const CCTagLocalizer::Parameters ¶m, - std::mt19937 & randomNumberGenerator, - OccurenceMap & out_occurences, - Mat &out_pt2D, - Mat &out_pt3D, + ALICEVISION_LOG_DEBUG("Index of the first localized camera: " << idx); + + // if the only localized camera is the main camera + if (idx == 0) + { + // just give its pose + rigPose = vec_localizationResults[0].getPose(); + } + else + { + // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q + // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) + // with the localization localize() we have computed [R2 t2], hence: + // q2 ~ [R2 t2] Q = [R12 t12]*inv([R12 t12]) * [R2 t2] Q + // and inv([R12 t12]) * [R2 t2] is the pose of the main camera + + // recover the rig pose using the subposes + rigPose = vec_subPoses[idx - 1].inverse() * vec_localizationResults[idx].getPose(); + } + } + + // ** otherwise run a BA with the localized cameras + const bool refineOk = refineRigPose(vec_subPoses, vec_localizationResults, rigPose); + + if (!refineOk) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRig pose refinement failed."); + return false; + } + + updateRigPoses(vec_localizationResults, rigPose, vec_subPoses); + + return true; +} + +void CCTagLocalizer::getAllAssociations(const feature::CCTAG_Regions& queryRegions, + const std::pair& imageSize, + const CCTagLocalizer::Parameters& param, + std::mt19937& randomNumberGenerator, + OccurenceMap& out_occurences, + Mat& out_pt2D, + Mat& out_pt3D, std::vector& out_matchedImages, const std::string& imagePath) const { - std::vector nearestKeyFrames; - nearestKeyFrames.reserve(param._nNearestKeyFrames); - - kNearestKeyFrames(queryRegions, - _cctagDescType, - _regionsPerView, - param._nNearestKeyFrames, - nearestKeyFrames); - - out_matchedImages.clear(); - out_matchedImages.reserve(nearestKeyFrames.size()); - - ALICEVISION_LOG_DEBUG("nearestKeyFrames.size() = " << nearestKeyFrames.size()); - for(const IndexT keyframeId : nearestKeyFrames) - { - ALICEVISION_LOG_DEBUG(keyframeId); - ALICEVISION_LOG_DEBUG(_sfm_data.getViews().at(keyframeId)->getImage().getImagePath()); - const feature::Regions& matchedRegions = _regionsPerView.getRegions(keyframeId, _cctagDescType); - const ReconstructedRegionsMapping& regionsMapping = _reconstructedRegionsMappingPerView.at(keyframeId).at(_cctagDescType); - const feature::CCTAG_Regions & matchedCCtagRegions = dynamic_cast(matchedRegions); - - // Matching - std::vector vec_featureMatches; - viewMatching(queryRegions, matchedCCtagRegions, vec_featureMatches); - ALICEVISION_LOG_DEBUG("[matching]\tFound "<< vec_featureMatches.size() <<" matches."); - - out_matchedImages.emplace_back(keyframeId, vec_featureMatches.size()); - - if(!param._visualDebug.empty() && !imagePath.empty()) - { - namespace bfs = boost::filesystem; - const sfmData::View *mview = _sfm_data.getViews().at(keyframeId).get(); - const std::string queryImage = bfs::path(imagePath).stem().string(); - const std::string matchedImage = bfs::path(mview->getImage().getImagePath()).stem().string(); - const std::string matchedPath = mview->getImage().getImagePath(); - - // the directory where to save the feature matches - const auto baseDir = bfs::path(param._visualDebug) / queryImage; - if((!bfs::exists(baseDir))) - { - ALICEVISION_LOG_DEBUG("created " << baseDir.string()); - bfs::create_directories(baseDir); - } - - // the final filename for the output svg file as a composition of the query - // image and the matched image - auto outputName = baseDir / queryImage; - outputName += "_"; - outputName += matchedImage; - outputName += ".svg"; - - const bool showNotMatched = true; - matching::saveCCTagMatches2SVG(imagePath, - imageSize, - queryRegions, - matchedPath, - std::make_pair(mview->getImage().getWidth(), mview->getImage().getHeight()), - matchedCCtagRegions, - vec_featureMatches, - outputName.string(), - showNotMatched ); - } - - // Recover the 2D-3D associations from the matches - // Each matched feature in the current similar image is associated to a 3D point, - // hence we can recover the 2D-3D associations to estimate the pose - - // Get the 3D points associated to each matched feature - for(const matching::IndMatch& featureMatch : vec_featureMatches) - { - // the ID of the 3D point - const IndexT pt3D_id = regionsMapping._associated3dPoint[featureMatch._j]; - const IndexT pt2D_id = featureMatch._i; - - const IndMatch3D2D key(pt3D_id, _cctagDescType, pt2D_id); - if(out_occurences.count(key)) - { - out_occurences[key]++; - } - else - { - out_occurences[key] = 1; - } - } - } - - const size_t numCollectedPts = out_occurences.size(); - ALICEVISION_LOG_DEBUG("[matching]\tCollected "<< numCollectedPts <<" associations."); - - { - // just debugging statistics, this block can be safely removed - std::size_t maxOcc = 0; - for(const auto &idx : out_occurences) - { - const auto &key = idx.first; - const auto &value = idx.second; - ALICEVISION_LOG_DEBUG("[matching]\tAssociations " - << feature::EImageDescriberType_enumToString(key.descType) << " " - << key.landmarkId << "," << key.featId <<" found " - << value << " times."); - if(value > maxOcc) - maxOcc = value; - } - - std::size_t numOccTreated = 0; - for(std::size_t value = 1; value < maxOcc; ++value) - { - std::size_t counter = 0; - for(const auto &idx : out_occurences) - { - if(idx.second == value) + std::vector nearestKeyFrames; + nearestKeyFrames.reserve(param._nNearestKeyFrames); + + kNearestKeyFrames(queryRegions, _cctagDescType, _regionsPerView, param._nNearestKeyFrames, nearestKeyFrames); + + out_matchedImages.clear(); + out_matchedImages.reserve(nearestKeyFrames.size()); + + ALICEVISION_LOG_DEBUG("nearestKeyFrames.size() = " << nearestKeyFrames.size()); + for (const IndexT keyframeId : nearestKeyFrames) + { + ALICEVISION_LOG_DEBUG(keyframeId); + ALICEVISION_LOG_DEBUG(_sfm_data.getViews().at(keyframeId)->getImage().getImagePath()); + const feature::Regions& matchedRegions = _regionsPerView.getRegions(keyframeId, _cctagDescType); + const ReconstructedRegionsMapping& regionsMapping = _reconstructedRegionsMappingPerView.at(keyframeId).at(_cctagDescType); + const feature::CCTAG_Regions& matchedCCtagRegions = dynamic_cast(matchedRegions); + + // Matching + std::vector vec_featureMatches; + viewMatching(queryRegions, matchedCCtagRegions, vec_featureMatches); + ALICEVISION_LOG_DEBUG("[matching]\tFound " << vec_featureMatches.size() << " matches."); + + out_matchedImages.emplace_back(keyframeId, vec_featureMatches.size()); + + if (!param._visualDebug.empty() && !imagePath.empty()) { - ++counter; + namespace bfs = boost::filesystem; + const sfmData::View* mview = _sfm_data.getViews().at(keyframeId).get(); + const std::string queryImage = bfs::path(imagePath).stem().string(); + const std::string matchedImage = bfs::path(mview->getImage().getImagePath()).stem().string(); + const std::string matchedPath = mview->getImage().getImagePath(); + + // the directory where to save the feature matches + const auto baseDir = bfs::path(param._visualDebug) / queryImage; + if ((!bfs::exists(baseDir))) + { + ALICEVISION_LOG_DEBUG("created " << baseDir.string()); + bfs::create_directories(baseDir); + } + + // the final filename for the output svg file as a composition of the query + // image and the matched image + auto outputName = baseDir / queryImage; + outputName += "_"; + outputName += matchedImage; + outputName += ".svg"; + + const bool showNotMatched = true; + matching::saveCCTagMatches2SVG(imagePath, + imageSize, + queryRegions, + matchedPath, + std::make_pair(mview->getImage().getWidth(), mview->getImage().getHeight()), + matchedCCtagRegions, + vec_featureMatches, + outputName.string(), + showNotMatched); } - } - if(counter>0) - ALICEVISION_LOG_DEBUG("[matching]\tThere are " << counter - << " associations occurred " << value << " times (" - << 100.0 * counter / (double) numCollectedPts << "%)"); - numOccTreated += counter; - if(numOccTreated >= numCollectedPts) - break; - } - } - - out_pt2D = Mat2X(2, numCollectedPts); - out_pt3D = Mat3X(3, numCollectedPts); - - size_t index = 0; - for(const auto &idx : out_occurences) - { - // recopy all the points in the matching structure - const IndexT pt3D_id = idx.first.landmarkId; - const IndexT pt2D_id = idx.first.featId; - - out_pt2D.col(index) = queryRegions.GetRegionPosition(pt2D_id); - out_pt3D.col(index) = _sfm_data.getLandmarks().at(pt3D_id).X; - ++index; - } -} + // Recover the 2D-3D associations from the matches + // Each matched feature in the current similar image is associated to a 3D point, + // hence we can recover the 2D-3D associations to estimate the pose + + // Get the 3D points associated to each matched feature + for (const matching::IndMatch& featureMatch : vec_featureMatches) + { + // the ID of the 3D point + const IndexT pt3D_id = regionsMapping._associated3dPoint[featureMatch._j]; + const IndexT pt2D_id = featureMatch._i; + + const IndMatch3D2D key(pt3D_id, _cctagDescType, pt2D_id); + if (out_occurences.count(key)) + { + out_occurences[key]++; + } + else + { + out_occurences[key] = 1; + } + } + } + + const size_t numCollectedPts = out_occurences.size(); + ALICEVISION_LOG_DEBUG("[matching]\tCollected " << numCollectedPts << " associations."); + + { + // just debugging statistics, this block can be safely removed + std::size_t maxOcc = 0; + for (const auto& idx : out_occurences) + { + const auto& key = idx.first; + const auto& value = idx.second; + ALICEVISION_LOG_DEBUG("[matching]\tAssociations " << feature::EImageDescriberType_enumToString(key.descType) << " " << key.landmarkId + << "," << key.featId << " found " << value << " times."); + if (value > maxOcc) + maxOcc = value; + } + + std::size_t numOccTreated = 0; + for (std::size_t value = 1; value < maxOcc; ++value) + { + std::size_t counter = 0; + for (const auto& idx : out_occurences) + { + if (idx.second == value) + { + ++counter; + } + } + if (counter > 0) + ALICEVISION_LOG_DEBUG("[matching]\tThere are " << counter << " associations occurred " << value << " times (" + << 100.0 * counter / (double)numCollectedPts << "%)"); + numOccTreated += counter; + if (numOccTreated >= numCollectedPts) + break; + } + } + + out_pt2D = Mat2X(2, numCollectedPts); + out_pt3D = Mat3X(3, numCollectedPts); + + size_t index = 0; + for (const auto& idx : out_occurences) + { + // recopy all the points in the matching structure + const IndexT pt3D_id = idx.first.landmarkId; + const IndexT pt2D_id = idx.first.featId; + + out_pt2D.col(index) = queryRegions.GetRegionPosition(pt2D_id); + out_pt3D.col(index) = _sfm_data.getLandmarks().at(pt3D_id).X; + ++index; + } +} -void kNearestKeyFrames(const feature::CCTAG_Regions & queryRegions, +void kNearestKeyFrames(const feature::CCTAG_Regions& queryRegions, feature::EImageDescriberType cctagDescType, - const feature::RegionsPerView & regionsPerView, + const feature::RegionsPerView& regionsPerView, std::size_t nNearestKeyFrames, - std::vector & out_kNearestFrames, + std::vector& out_kNearestFrames, float similarityThreshold /*=.0f*/) { - out_kNearestFrames.clear(); - - // A std::multimap is used instead of a std::map because is very likely that the - // similarity measure is equal for a subset of views in the CCTag regions case. - std::multimap sortedViewSimilarities; - - for(const auto & keyFrame : regionsPerView.getData()) - { - const feature::CCTAG_Regions& keyFrameCCTagRegions = keyFrame.second.getRegions(cctagDescType); - const float similarity = viewSimilarity(queryRegions, keyFrameCCTagRegions); - sortedViewSimilarities.emplace(similarity, keyFrame.first); - } - - std::size_t counter = 0; - out_kNearestFrames.reserve(nNearestKeyFrames); - for (auto rit = sortedViewSimilarities.crbegin(); rit != sortedViewSimilarities.crend(); ++rit) - { - if(rit->first < similarityThreshold) - // since it is ordered, the first having smaller similarity guarantees that - // there won't be other useful kframes - break; - - out_kNearestFrames.push_back(rit->second); - ++counter; - - if (counter == nNearestKeyFrames) - break; - } + out_kNearestFrames.clear(); + + // A std::multimap is used instead of a std::map because is very likely that the + // similarity measure is equal for a subset of views in the CCTag regions case. + std::multimap sortedViewSimilarities; + + for (const auto& keyFrame : regionsPerView.getData()) + { + const feature::CCTAG_Regions& keyFrameCCTagRegions = keyFrame.second.getRegions(cctagDescType); + const float similarity = viewSimilarity(queryRegions, keyFrameCCTagRegions); + sortedViewSimilarities.emplace(similarity, keyFrame.first); + } + + std::size_t counter = 0; + out_kNearestFrames.reserve(nNearestKeyFrames); + for (auto rit = sortedViewSimilarities.crbegin(); rit != sortedViewSimilarities.crend(); ++rit) + { + if (rit->first < similarityThreshold) + // since it is ordered, the first having smaller similarity guarantees that + // there won't be other useful kframes + break; + + out_kNearestFrames.push_back(rit->second); + ++counter; + + if (counter == nNearestKeyFrames) + break; + } } - -void viewMatching(const feature::CCTAG_Regions & regionsA, - const feature::CCTAG_Regions & regionsB, - std::vector & out_featureMatches) + +void viewMatching(const feature::CCTAG_Regions& regionsA, const feature::CCTAG_Regions& regionsB, std::vector& out_featureMatches) { - out_featureMatches.clear(); - - for(std::size_t i=0 ; i < regionsA.Descriptors().size() ; ++i) - { - const IndexT cctagIdA = feature::getCCTagId(regionsA.Descriptors()[i]); - // todo: Should be change to: Find in regionsB.Descriptors() the nearest - // descriptor to descriptorA. Currently, a cctag descriptor encode directly - // the cctag id, then the id equality is tested. - for(std::size_t j=0 ; j < regionsB.Descriptors().size() ; ++j) - { - const IndexT cctagIdB = feature::getCCTagId(regionsB.Descriptors()[j]); - if ( cctagIdA == cctagIdB ) - { - out_featureMatches.emplace_back(i,j); - break; - } - } - } + out_featureMatches.clear(); + + for (std::size_t i = 0; i < regionsA.Descriptors().size(); ++i) + { + const IndexT cctagIdA = feature::getCCTagId(regionsA.Descriptors()[i]); + // todo: Should be change to: Find in regionsB.Descriptors() the nearest + // descriptor to descriptorA. Currently, a cctag descriptor encode directly + // the cctag id, then the id equality is tested. + for (std::size_t j = 0; j < regionsB.Descriptors().size(); ++j) + { + const IndexT cctagIdB = feature::getCCTagId(regionsB.Descriptors()[j]); + if (cctagIdA == cctagIdB) + { + out_featureMatches.emplace_back(i, j); + break; + } + } + } } - - - -float viewSimilarity(const feature::CCTAG_Regions & regionsA, - const feature::CCTAG_Regions & regionsB) + +float viewSimilarity(const feature::CCTAG_Regions& regionsA, const feature::CCTAG_Regions& regionsB) { - assert(regionsA.DescriptorLength() == regionsB.DescriptorLength()); - - const std::bitset<128> descriptorViewA = constructCCTagViewDescriptor(regionsA.Descriptors()); - const std::bitset<128> descriptorViewB = constructCCTagViewDescriptor(regionsB.Descriptors()); - - // The similarity is the sum of all the cctags sharing the same id visible in both views. - return (descriptorViewA & descriptorViewB).count(); + assert(regionsA.DescriptorLength() == regionsB.DescriptorLength()); + + const std::bitset<128> descriptorViewA = constructCCTagViewDescriptor(regionsA.Descriptors()); + const std::bitset<128> descriptorViewB = constructCCTagViewDescriptor(regionsB.Descriptors()); + + // The similarity is the sum of all the cctags sharing the same id visible in both views. + return (descriptorViewA & descriptorViewB).count(); } -std::bitset<128> constructCCTagViewDescriptor(const std::vector & vCCTagDescriptors) +std::bitset<128> constructCCTagViewDescriptor(const std::vector& vCCTagDescriptors) { - std::bitset<128> descriptorView; - for(const auto & cctagDescriptor : vCCTagDescriptors ) - { - const IndexT cctagId = feature::getCCTagId(cctagDescriptor); - if ( cctagId != UndefinedIndexT) + std::bitset<128> descriptorView; + for (const auto& cctagDescriptor : vCCTagDescriptors) { - descriptorView.set(cctagId, true); + const IndexT cctagId = feature::getCCTagId(cctagDescriptor); + if (cctagId != UndefinedIndexT) + { + descriptorView.set(cctagId, true); + } } - } - return descriptorView; + return descriptorView; } -} // localization -} // aliceVision - +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/CCTagLocalizer.hpp b/src/aliceVision/localization/CCTagLocalizer.hpp index f081f7dd50..ce14cb22d9 100644 --- a/src/aliceVision/localization/CCTagLocalizer.hpp +++ b/src/aliceVision/localization/CCTagLocalizer.hpp @@ -23,203 +23,189 @@ namespace aliceVision { namespace localization { - class CCTagLocalizer : public ILocalizer { public: + struct Parameters : public LocalizerParameters + { + Parameters() + : LocalizerParameters(), + _nNearestKeyFrames(4) + {} + + /// number of best matching images to retrieve from the database + std::size_t _nNearestKeyFrames; + }; + + public: + CCTagLocalizer(const sfmData::SfMData& sfmData, const std::string& descriptorsFolder); + + void setCudaPipe(int i) override; + + /** + * @brief Just a wrapper around the different localization algorithm, the algorith + * used to localized is chosen using \p param._algorithm + * + * @param[in] imageGrey The input greyscale image. + * @param[in] param The parameters for the localization. + * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. + * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the + * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. + * @param[out] localizationResult The localization result containing the pose and the associations. + * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. + * @return true if the image has been successfully localized. + */ + bool localize(const image::Image& imageGrey, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()) override; + + bool localize(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()) override; + + /** + * @brief Naive implementation of the localizer using the rig. Each image from + * the rig is localized and then a bundle adjustment is run for optimizing the + * global pose. + * + * @param[in] vec_imageGrey A vector containing all the images from the rig + * @param[in] parameters The parameters for the localization. + * @param[in,out] vec_queryIntrinsics Vector containing the intrinsic parameters of the cameras + * @param[in] vec_subPoses A vector containing the N-1 subposes of each camera wrt the main camera + * @param[out] rigPose The pose of the rig expressed as the pose of the main camera + * @return true if the rig has been successfully localized. + */ + bool localizeRig(const std::vector>& vec_imageGrey, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) override; + + bool localizeRig(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) override; - struct Parameters : public LocalizerParameters - { - - Parameters() - : LocalizerParameters() - , _nNearestKeyFrames(4) - {} - - /// number of best matching images to retrieve from the database - std::size_t _nNearestKeyFrames; - }; - -public: - - CCTagLocalizer(const sfmData::SfMData &sfmData, - const std::string &descriptorsFolder); - - void setCudaPipe(int i) override; - - /** - * @brief Just a wrapper around the different localization algorithm, the algorith - * used to localized is chosen using \p param._algorithm - * - * @param[in] imageGrey The input greyscale image. - * @param[in] param The parameters for the localization. - * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. - * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the - * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. - * @param[out] localizationResult The localization result containing the pose and the associations. - * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. - * @return true if the image has been successfully localized. - */ - bool localize(const image::Image & imageGrey, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, const std::string& imagePath = std::string()) override; - - bool localize(const feature::MapRegionsPerDesc &queryRegions, - const std::pair &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, - const std::string& imagePath = std::string()) override; - - /** - * @brief Naive implementation of the localizer using the rig. Each image from - * the rig is localized and then a bundle adjustment is run for optimizing the - * global pose. - * - * @param[in] vec_imageGrey A vector containing all the images from the rig - * @param[in] parameters The parameters for the localization. - * @param[in,out] vec_queryIntrinsics Vector containing the intrinsic parameters of the cameras - * @param[in] vec_subPoses A vector containing the N-1 subposes of each camera wrt the main camera - * @param[out] rigPose The pose of the rig expressed as the pose of the main camera - * @return true if the rig has been successfully localized. - */ - bool localizeRig(const std::vector> & vec_imageGrey, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector & vec_locResults) override; - - - bool localizeRig(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults) override; - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) - bool localizeRig_opengv(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults); + bool localizeRig_opengv(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults); #endif - - bool localizeRig_naive(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults); - - /** - * @brief Given the input Regions, it retrieves all the 2D-3D associations from - * the nearest k-frames in the database. The associations are retrieved in terms - * of region index and 3D point index along with the number of times (\p occurrences) that the - * pair has been found. \p pt2D and \p pt3D contains the coordinates of the corresponding - * points of the associations, in the same order as in \p occurences. - * - * @param[in] queryRegions The input query regions containing the extracted - * markers from the query image. - * @param[in] imageSize The size of the query image. - * @param[in] param The parameters to use. - * @param[out] occurences A map containing for each pair - * the number of times that the association has been seen - * @param[out] pt2D The set of 2D points of the associations as they are given in \p queryRegions. - * @param[out] pt3D The set of 3D points of the associations. - * @param[in] The optional path to the query image file, used for debugging. - */ - void getAllAssociations(const feature::CCTAG_Regions &queryRegions, - const std::pair &imageSize, - const CCTagLocalizer::Parameters ¶m, - std::mt19937 & randomNumberGenerator, - OccurenceMap & out_occurences, - Mat &out_pt2D, - Mat &out_pt3D, - std::vector& out_matchedImages, - const std::string& imagePath = std::string()) const; - - virtual ~CCTagLocalizer(); - -private: - - bool loadReconstructionDescriptors( - const sfmData::SfMData & sfm_data, - const std::string & feat_directory); - - // for each view index, it contains the cctag features and descriptors that have an - // associated 3D point - feature::RegionsPerView _regionsPerView; - ReconstructedRegionsMappingPerView _reconstructedRegionsMappingPerView; - - // the feature extractor - feature::ImageDescriber_CCTAG _imageDescriber; - /// @warning: descType needs to be a CCTAG_Regions - feature::EImageDescriberType _cctagDescType = feature::EImageDescriberType::CCTAG3; - - // CUDA CCTag supports several parallel pipelines, where each one can - // processing different image dimensions. - int _cudaPipe = 0; - - // - //std::map _cctagDatabase; + + bool localizeRig_naive(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults); + + /** + * @brief Given the input Regions, it retrieves all the 2D-3D associations from + * the nearest k-frames in the database. The associations are retrieved in terms + * of region index and 3D point index along with the number of times (\p occurrences) that the + * pair has been found. \p pt2D and \p pt3D contains the coordinates of the corresponding + * points of the associations, in the same order as in \p occurences. + * + * @param[in] queryRegions The input query regions containing the extracted + * markers from the query image. + * @param[in] imageSize The size of the query image. + * @param[in] param The parameters to use. + * @param[out] occurences A map containing for each pair + * the number of times that the association has been seen + * @param[out] pt2D The set of 2D points of the associations as they are given in \p queryRegions. + * @param[out] pt3D The set of 3D points of the associations. + * @param[in] The optional path to the query image file, used for debugging. + */ + void getAllAssociations(const feature::CCTAG_Regions& queryRegions, + const std::pair& imageSize, + const CCTagLocalizer::Parameters& param, + std::mt19937& randomNumberGenerator, + OccurenceMap& out_occurences, + Mat& out_pt2D, + Mat& out_pt3D, + std::vector& out_matchedImages, + const std::string& imagePath = std::string()) const; + + virtual ~CCTagLocalizer(); + + private: + bool loadReconstructionDescriptors(const sfmData::SfMData& sfm_data, const std::string& feat_directory); + + // for each view index, it contains the cctag features and descriptors that have an + // associated 3D point + feature::RegionsPerView _regionsPerView; + ReconstructedRegionsMappingPerView _reconstructedRegionsMappingPerView; + + // the feature extractor + feature::ImageDescriber_CCTAG _imageDescriber; + /// @warning: descType needs to be a CCTAG_Regions + feature::EImageDescriberType _cctagDescType = feature::EImageDescriberType::CCTAG3; + + // CUDA CCTag supports several parallel pipelines, where each one can + // processing different image dimensions. + int _cudaPipe = 0; + + // + // std::map _cctagDatabase; }; - /** - * @brief Retrieve the k nearest views in a collection of views based on a query - * consisting in a set of CCTag regions. - * - * @param[in] queryRegions Set of CCTag regions in the query. - * @param[in] regionsPerView Collection of views containing a set of cctag regions. - * @param[in] nNearestKeyFrames Number of nearest neighbours to return. - * @param[out] out_kNearestFrames Set of computed indices associated to the k nearest views. - * @param[in] similarityThreshold A threshold to retrieve only the kframes having - * at least \p similarityThreshold similarity score. - */ -void kNearestKeyFrames( - const feature::CCTAG_Regions & queryRegions, - feature::EImageDescriberType descType, - const feature::RegionsPerView & regionsPerView, - std::size_t nNearestKeyFrames, - std::vector & out_kNearestFrames, - float similarityThreshold = 1.0f); +/** + * @brief Retrieve the k nearest views in a collection of views based on a query + * consisting in a set of CCTag regions. + * + * @param[in] queryRegions Set of CCTag regions in the query. + * @param[in] regionsPerView Collection of views containing a set of cctag regions. + * @param[in] nNearestKeyFrames Number of nearest neighbours to return. + * @param[out] out_kNearestFrames Set of computed indices associated to the k nearest views. + * @param[in] similarityThreshold A threshold to retrieve only the kframes having + * at least \p similarityThreshold similarity score. + */ +void kNearestKeyFrames(const feature::CCTAG_Regions& queryRegions, + feature::EImageDescriberType descType, + const feature::RegionsPerView& regionsPerView, + std::size_t nNearestKeyFrames, + std::vector& out_kNearestFrames, + float similarityThreshold = 1.0f); /** * @brief Given a set of CCTag descriptors seen in a view, it creates a descriptor for the view: the - * view descriptor is a 128 bit array (ie the number of possible markers) whose - * bits are 0 or 1 whether the corresponding marker is seen or not. E.g. if the + * view descriptor is a 128 bit array (ie the number of possible markers) whose + * bits are 0 or 1 whether the corresponding marker is seen or not. E.g. if the * bit in position 8 is 1 it means that the marker with ID 8 has been seen by the view. - * + * * @param[in] vCCTagDescriptors The input descriptors associated to the view. * @return The view descriptor as a set of bit representing the visibility of * each possible marker for that view. */ -std::bitset<128> constructCCTagViewDescriptor( - const std::vector & vCCTagDescriptors); - -float viewSimilarity( - const feature::CCTAG_Regions & regionsA, - const feature::CCTAG_Regions & regionsB); +std::bitset<128> constructCCTagViewDescriptor(const std::vector& vCCTagDescriptors); -void viewMatching( - const feature::CCTAG_Regions & regionsA, - const feature::CCTAG_Regions & regionsB, - std::vector & out_featureMatches); +float viewSimilarity(const feature::CCTAG_Regions& regionsA, const feature::CCTAG_Regions& regionsB); -} // namespace localization -} // aliceVision +void viewMatching(const feature::CCTAG_Regions& regionsA, + const feature::CCTAG_Regions& regionsB, + std::vector& out_featureMatches); +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/ILocalizer.hpp b/src/aliceVision/localization/ILocalizer.hpp index 6203b944cb..19135e252a 100644 --- a/src/aliceVision/localization/ILocalizer.hpp +++ b/src/aliceVision/localization/ILocalizer.hpp @@ -20,39 +20,39 @@ namespace localization { struct LocalizerParameters { - LocalizerParameters() - : _visualDebug("") - , _refineIntrinsics(false) - , _fDistRatio(0.8) - , _errorMax(std::numeric_limits::infinity()) - , _resectionEstimator(robustEstimation::ERobustEstimator::ACRANSAC) - , _matchingEstimator(robustEstimation::ERobustEstimator::ACRANSAC) - , _useLocalizeRigNaive(false) - , _angularThreshold(degreeToRadian(0.1)) - { - _featurePreset.setDescPreset(feature::EImageDescriberPreset::ULTRA); - } - - virtual ~LocalizerParameters() = 0; - - /// enable visual debugging options - std::string _visualDebug; - /// whether or not the Intrinsics of the query camera has to be refined - bool _refineIntrinsics; - /// the distance ratio to use when matching feature with the ratio test - float _fDistRatio; - /// the preset to use for feature extraction of the query image - feature::ConfigurationPreset _featurePreset; - /// maximum reprojection error allowed for resectioning - double _errorMax; - /// the type of *sac framework to use for resection - robustEstimation::ERobustEstimator _resectionEstimator; - /// the type of *sac framework to use for matching - robustEstimation::ERobustEstimator _matchingEstimator; - /// force the use of the rig localization without openGV - bool _useLocalizeRigNaive; - /// in rad, it is the maximum angular error for the opengv rig resection - double _angularThreshold; + LocalizerParameters() + : _visualDebug(""), + _refineIntrinsics(false), + _fDistRatio(0.8), + _errorMax(std::numeric_limits::infinity()), + _resectionEstimator(robustEstimation::ERobustEstimator::ACRANSAC), + _matchingEstimator(robustEstimation::ERobustEstimator::ACRANSAC), + _useLocalizeRigNaive(false), + _angularThreshold(degreeToRadian(0.1)) + { + _featurePreset.setDescPreset(feature::EImageDescriberPreset::ULTRA); + } + + virtual ~LocalizerParameters() = 0; + + /// enable visual debugging options + std::string _visualDebug; + /// whether or not the Intrinsics of the query camera has to be refined + bool _refineIntrinsics; + /// the distance ratio to use when matching feature with the ratio test + float _fDistRatio; + /// the preset to use for feature extraction of the query image + feature::ConfigurationPreset _featurePreset; + /// maximum reprojection error allowed for resectioning + double _errorMax; + /// the type of *sac framework to use for resection + robustEstimation::ERobustEstimator _resectionEstimator; + /// the type of *sac framework to use for matching + robustEstimation::ERobustEstimator _matchingEstimator; + /// force the use of the rig localization without openGV + bool _useLocalizeRigNaive; + /// in rad, it is the maximum angular error for the opengv rig resection + double _angularThreshold; }; inline LocalizerParameters::~LocalizerParameters() {} @@ -62,71 +62,70 @@ using OccurenceMap = std::map; class ILocalizer { -public: - ILocalizer() : _isInit(false) { }; + public: + ILocalizer() + : _isInit(false){}; // Only relevant for CCTagLocalizer - virtual void setCudaPipe(int) { } - - bool isInit() const {return _isInit;} - - const sfmData::SfMData& getSfMData() const {return _sfm_data; } - - /** - * @brief Localize one image - * - * @param[in] gen random seed. - * @param[in] imageGrey The input greyscale image. - * @param[in] param The parameters for the localization. - * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. - * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the - * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. - * @param[out] localizationResult The localization result containing the pose and the associations. - * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. - * @return true if the image has been successfully localized. - */ - virtual bool localize(const image::Image & imageGrey, - const LocalizerParameters *param, - std::mt19937 & gen, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, - const std::string& imagePath = std::string()) = 0; - - virtual bool localize(const feature::MapRegionsPerDesc &queryRegions, - const std::pair &imageSize, - const LocalizerParameters *param, - std::mt19937 & gen, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, - const std::string& imagePath = std::string()) = 0; - - virtual bool localizeRig(const std::vector> & vec_imageGrey, - const LocalizerParameters *param, - std::mt19937 & gen, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults)=0; - - virtual bool localizeRig(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *param, - std::mt19937 & gen, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults)=0; - - virtual ~ILocalizer( ) {} - -protected: - bool _isInit; - sfmData::SfMData _sfm_data; + virtual void setCudaPipe(int) {} -}; + bool isInit() const { return _isInit; } + + const sfmData::SfMData& getSfMData() const { return _sfm_data; } -} //namespace aliceVision -} //namespace localization + /** + * @brief Localize one image + * + * @param[in] gen random seed. + * @param[in] imageGrey The input greyscale image. + * @param[in] param The parameters for the localization. + * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. + * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the + * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. + * @param[out] localizationResult The localization result containing the pose and the associations. + * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. + * @return true if the image has been successfully localized. + */ + virtual bool localize(const image::Image& imageGrey, + const LocalizerParameters* param, + std::mt19937& gen, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()) = 0; + + virtual bool localize(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const LocalizerParameters* param, + std::mt19937& gen, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()) = 0; + + virtual bool localizeRig(const std::vector>& vec_imageGrey, + const LocalizerParameters* param, + std::mt19937& gen, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) = 0; + + virtual bool localizeRig(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* param, + std::mt19937& gen, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) = 0; + + virtual ~ILocalizer() {} + + protected: + bool _isInit; + sfmData::SfMData _sfm_data; +}; +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/LocalizationResult.cpp b/src/aliceVision/localization/LocalizationResult.cpp index 89c0fae74e..f5ee821dea 100644 --- a/src/aliceVision/localization/LocalizationResult.cpp +++ b/src/aliceVision/localization/LocalizationResult.cpp @@ -18,356 +18,356 @@ namespace localization { namespace bpt = boost::property_tree; -LocalizationResult::LocalizationResult() : _isValid(false) {} -LocalizationResult::LocalizationResult( - const sfm::ImageLocalizerMatchData& matchData, - const std::vector& indMatch3D2D, - const geometry::Pose3& pose, - const camera::Pinhole& intrinsics, - const std::vector& matchedImages, - bool isValid) : - _matchData(matchData), - _indMatch3D2D(indMatch3D2D), - _pose(pose), - _intrinsics(intrinsics), - _matchedImages(matchedImages), - _isValid(isValid) +LocalizationResult::LocalizationResult() + : _isValid(false) +{} +LocalizationResult::LocalizationResult(const sfm::ImageLocalizerMatchData& matchData, + const std::vector& indMatch3D2D, + const geometry::Pose3& pose, + const camera::Pinhole& intrinsics, + const std::vector& matchedImages, + bool isValid) + : _matchData(matchData), + _indMatch3D2D(indMatch3D2D), + _pose(pose), + _intrinsics(intrinsics), + _matchedImages(matchedImages), + _isValid(isValid) { - // verify data consistency - assert(_matchData.pt2D.cols() == _matchData.pt3D.cols()); - assert(_matchData.pt2D.cols() == _indMatch3D2D.size()); + // verify data consistency + assert(_matchData.pt2D.cols() == _matchData.pt3D.cols()); + assert(_matchData.pt2D.cols() == _indMatch3D2D.size()); } - + LocalizationResult::~LocalizationResult() {} const Mat LocalizationResult::retrieveUndistortedPt2D() const { - const auto& intrinsics = getIntrinsics(); - const auto& distorted = getPt2D(); - if(!intrinsics.hasDistortion() || !intrinsics.isValid()) - { - return getPt2D(); - } - const std::size_t numPts = distorted.cols(); - Mat pt2Dundistorted = Mat2X(2, numPts); - for(std::size_t iPoint = 0; iPoint < numPts; ++iPoint) - { - pt2Dundistorted.col(iPoint) = intrinsics.get_ud_pixel(distorted.col(iPoint)); - } - return pt2Dundistorted; + const auto& intrinsics = getIntrinsics(); + const auto& distorted = getPt2D(); + if (!intrinsics.hasDistortion() || !intrinsics.isValid()) + { + return getPt2D(); + } + const std::size_t numPts = distorted.cols(); + Mat pt2Dundistorted = Mat2X(2, numPts); + for (std::size_t iPoint = 0; iPoint < numPts; ++iPoint) + { + pt2Dundistorted.col(iPoint) = intrinsics.get_ud_pixel(distorted.col(iPoint)); + } + return pt2Dundistorted; } -Mat2X LocalizationResult::computeAllResiduals() const +Mat2X LocalizationResult::computeAllResiduals() const { - const Mat2X& orig2d = getPt2D(); - const Mat3X& orig3d = getPt3D(); - assert(orig2d.cols()==orig3d.cols()); - - const auto& intrinsics = getIntrinsics(); - return intrinsics.residuals(getPose(), orig3d, orig2d); + const Mat2X& orig2d = getPt2D(); + const Mat3X& orig3d = getPt3D(); + assert(orig2d.cols() == orig3d.cols()); + + const auto& intrinsics = getIntrinsics(); + return intrinsics.residuals(getPose(), orig3d, orig2d); } -Mat2X LocalizationResult::computeInliersResiduals() const +Mat2X LocalizationResult::computeInliersResiduals() const { - // get the inliers. - const auto& currInliers = getInliers(); - const std::size_t numInliers = currInliers.size(); - - const Mat2X& orig2d = getPt2D(); - const Mat3X& orig3d = getPt3D(); - Mat2X inliers2d = Mat2X(2, numInliers); - Mat3X inliers3d = Mat3X(3, numInliers); - - for(std::size_t i = 0; i < numInliers; ++i) - { - const std::size_t idx = currInliers[i]; - inliers2d.col(i) = orig2d.col(idx); - inliers3d.col(i) = orig3d.col(idx); - } - - const auto &intrinsics = getIntrinsics(); - return intrinsics.residuals(getPose(), inliers3d, inliers2d); + // get the inliers. + const auto& currInliers = getInliers(); + const std::size_t numInliers = currInliers.size(); + + const Mat2X& orig2d = getPt2D(); + const Mat3X& orig3d = getPt3D(); + Mat2X inliers2d = Mat2X(2, numInliers); + Mat3X inliers3d = Mat3X(3, numInliers); + + for (std::size_t i = 0; i < numInliers; ++i) + { + const std::size_t idx = currInliers[i]; + inliers2d.col(i) = orig2d.col(idx); + inliers3d.col(i) = orig3d.col(idx); + } + + const auto& intrinsics = getIntrinsics(); + return intrinsics.residuals(getPose(), inliers3d, inliers2d); } -double LocalizationResult::computeInliersRMSE() const +double LocalizationResult::computeInliersRMSE() const { - const auto& residuals = computeInliersResiduals(); - // squared residual for each point - const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - //RMSE - return std::sqrt(sqrErrors.mean()); + const auto& residuals = computeInliersResiduals(); + // squared residual for each point + const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + // RMSE + return std::sqrt(sqrErrors.mean()); } -double LocalizationResult::computeAllRMSE() const +double LocalizationResult::computeAllRMSE() const { - const auto& residuals = computeAllResiduals(); - // squared residual for each point - const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - //RMSE - return std::sqrt(sqrErrors.mean()); + const auto& residuals = computeAllResiduals(); + // squared residual for each point + const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + // RMSE + return std::sqrt(sqrErrors.mean()); } -Vec LocalizationResult::computeReprojectionErrorPerInlier() const +Vec LocalizationResult::computeReprojectionErrorPerInlier() const { - const auto& residuals = computeInliersResiduals(); - // squared residual for each point - const Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - //RMSE - return sqrErrors.array().sqrt(); + const auto& residuals = computeInliersResiduals(); + // squared residual for each point + const Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + // RMSE + return sqrErrors.array().sqrt(); } Vec LocalizationResult::computeReprojectionErrorPerPoint() const { - const auto& residuals = computeAllResiduals(); - // squared residual for each point - const Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - //RMSE - return sqrErrors.array().sqrt(); + const auto& residuals = computeAllResiduals(); + // squared residual for each point + const Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + // RMSE + return sqrErrors.array().sqrt(); } std::size_t LocalizationResult::selectBestInliers(double maxReprojectionError) { - const auto& residuals = computeReprojectionErrorPerPoint(); - auto& inliers = _matchData.vec_inliers; - ALICEVISION_LOG_DEBUG("Inliers before: " << inliers.size()); - inliers.clear(); - // at worst they could all be inliers - inliers.reserve(getPt2D().size()); - - for(std::size_t i = 0; i < residuals.size(); ++i) - { - if(residuals[i] < maxReprojectionError) - { - inliers.push_back(i); - } - } - ALICEVISION_LOG_DEBUG(" After: " << inliers.size()); - _matchData.error_max = maxReprojectionError; - return inliers.size(); + const auto& residuals = computeReprojectionErrorPerPoint(); + auto& inliers = _matchData.vec_inliers; + ALICEVISION_LOG_DEBUG("Inliers before: " << inliers.size()); + inliers.clear(); + // at worst they could all be inliers + inliers.reserve(getPt2D().size()); + + for (std::size_t i = 0; i < residuals.size(); ++i) + { + if (residuals[i] < maxReprojectionError) + { + inliers.push_back(i); + } + } + ALICEVISION_LOG_DEBUG(" After: " << inliers.size()); + _matchData.error_max = maxReprojectionError; + return inliers.size(); } std::size_t LocalizationResult::selectBestInliers() -{ - const auto threshold = _matchData.error_max; - return selectBestInliers(threshold); +{ + const auto threshold = _matchData.error_max; + return selectBestInliers(threshold); } - void LocalizationResult::load(std::vector& localizationResults, const std::string& filename) { - using namespace aliceVision::sfm; - - // empty output vector - localizationResults.clear(); + using namespace aliceVision::sfm; - Version version; + // empty output vector + localizationResults.clear(); - // main tree - bpt::ptree fileTree; + Version version; - // read the json file and initialize the tree - bpt::read_json(filename, fileTree); + // main tree + bpt::ptree fileTree; - // version - { - Vec3i v; - sfmDataIO::loadMatrix("version", v, fileTree); - version = v; - } + // read the json file and initialize the tree + bpt::read_json(filename, fileTree); - if(fileTree.count("localizationResults")) - { - for(bpt::ptree::value_type& lrNode : fileTree.get_child("localizationResults")) + // version { - // localization result tree - bpt::ptree lrTree = lrNode.second; - - LocalizationResult lr; - - lr._isValid = lrTree.get("isValid"); - sfmDataIO::loadPose3("pose", lr._pose, lrTree); - - // indMatch3D2D - if(lrTree.count("indMatch3D2D")) - { - for(bpt::ptree::value_type& itNode : lrTree.get_child("indMatch3D2D")) - { - bpt::ptree& itTree = itNode.second; - - IndMatch3D2D indMatch; - - indMatch.landmarkId = itTree.get("landmarkId"); - indMatch.featId = itTree.get("featureId"); - indMatch.descType = feature::EImageDescriberType_stringToEnum(itTree.get("descType")); + Vec3i v; + sfmDataIO::loadMatrix("version", v, fileTree); + version = v; + } - lr._indMatch3D2D.emplace_back(indMatch); - } - } - - // intrinsic - { - IndexT intrinsicId; - std::shared_ptr intrinsicPtr; - sfmDataIO::loadIntrinsic(version, intrinsicId, intrinsicPtr, lrTree.get_child("intrinsic")); - lr._intrinsics = *(dynamic_cast(intrinsicPtr.get())); - } - - // inliers - if(lrTree.count("inliers")) - { - for(bpt::ptree::value_type& itNode : lrTree.get_child("inliers")) - lr._matchData.vec_inliers.emplace_back(itNode.second.get_value()); - } - - const std::size_t nbPts = lrTree.get("nbPts"); - - lr._matchData.pt3D = Mat(3, nbPts); - lr._matchData.pt2D = Mat(2, nbPts); - - sfmDataIO::loadMatrix("pt3D", lr._matchData.pt3D, lrTree); - sfmDataIO::loadMatrix("pt2D", lr._matchData.pt2D, lrTree); - sfmDataIO::loadMatrix("projectionMatrix", lr._matchData.projection_matrix, lrTree); - - lr._matchData.error_max = std::stod(lrTree.get("errorMax")); - lr._matchData.max_iteration = lrTree.get("maxIteration"); - - // matchedImages; - if(lrTree.count("matchedImages")) - { - for(bpt::ptree::value_type& itNode : lrTree.get_child("matchedImages")) + if (fileTree.count("localizationResults")) + { + for (bpt::ptree::value_type& lrNode : fileTree.get_child("localizationResults")) { - bpt::ptree& itTree = itNode.second; - - voctree::DocMatch docMatch; - - docMatch.id = itTree.get("id"); - docMatch.score = itTree.get("score"); - - lr._matchedImages.emplace_back(docMatch); + // localization result tree + bpt::ptree lrTree = lrNode.second; + + LocalizationResult lr; + + lr._isValid = lrTree.get("isValid"); + sfmDataIO::loadPose3("pose", lr._pose, lrTree); + + // indMatch3D2D + if (lrTree.count("indMatch3D2D")) + { + for (bpt::ptree::value_type& itNode : lrTree.get_child("indMatch3D2D")) + { + bpt::ptree& itTree = itNode.second; + + IndMatch3D2D indMatch; + + indMatch.landmarkId = itTree.get("landmarkId"); + indMatch.featId = itTree.get("featureId"); + indMatch.descType = feature::EImageDescriberType_stringToEnum(itTree.get("descType")); + + lr._indMatch3D2D.emplace_back(indMatch); + } + } + + // intrinsic + { + IndexT intrinsicId; + std::shared_ptr intrinsicPtr; + sfmDataIO::loadIntrinsic(version, intrinsicId, intrinsicPtr, lrTree.get_child("intrinsic")); + lr._intrinsics = *(dynamic_cast(intrinsicPtr.get())); + } + + // inliers + if (lrTree.count("inliers")) + { + for (bpt::ptree::value_type& itNode : lrTree.get_child("inliers")) + lr._matchData.vec_inliers.emplace_back(itNode.second.get_value()); + } + + const std::size_t nbPts = lrTree.get("nbPts"); + + lr._matchData.pt3D = Mat(3, nbPts); + lr._matchData.pt2D = Mat(2, nbPts); + + sfmDataIO::loadMatrix("pt3D", lr._matchData.pt3D, lrTree); + sfmDataIO::loadMatrix("pt2D", lr._matchData.pt2D, lrTree); + sfmDataIO::loadMatrix("projectionMatrix", lr._matchData.projection_matrix, lrTree); + + lr._matchData.error_max = std::stod(lrTree.get("errorMax")); + lr._matchData.max_iteration = lrTree.get("maxIteration"); + + // matchedImages; + if (lrTree.count("matchedImages")) + { + for (bpt::ptree::value_type& itNode : lrTree.get_child("matchedImages")) + { + bpt::ptree& itTree = itNode.second; + + voctree::DocMatch docMatch; + + docMatch.id = itTree.get("id"); + docMatch.score = itTree.get("score"); + + lr._matchedImages.emplace_back(docMatch); + } + } + localizationResults.emplace_back(lr); } - } - localizationResults.emplace_back(lr); } - } } void LocalizationResult::save(const std::vector& localizationResults, const std::string& filename) { - using namespace aliceVision::sfm; + using namespace aliceVision::sfm; - const Vec3i version = {ALICEVISION_SFMDATAIO_VERSION_MAJOR, ALICEVISION_SFMDATAIO_VERSION_MINOR, ALICEVISION_SFMDATAIO_VERSION_REVISION}; + const Vec3i version = {ALICEVISION_SFMDATAIO_VERSION_MAJOR, ALICEVISION_SFMDATAIO_VERSION_MINOR, ALICEVISION_SFMDATAIO_VERSION_REVISION}; - // main tree - bpt::ptree fileTree; + // main tree + bpt::ptree fileTree; - // file version - sfmDataIO::saveMatrix("version", version, fileTree); + // file version + sfmDataIO::saveMatrix("version", version, fileTree); - // localizationResults tree - bpt::ptree localizationResultsTree; + // localizationResults tree + bpt::ptree localizationResultsTree; - for(const LocalizationResult& lr : localizationResults) - { - bpt::ptree lrTree; + for (const LocalizationResult& lr : localizationResults) + { + bpt::ptree lrTree; - lrTree.put("isValid", lr._isValid); - sfmDataIO::savePose3("pose", lr._pose, lrTree); + lrTree.put("isValid", lr._isValid); + sfmDataIO::savePose3("pose", lr._pose, lrTree); - // indMatch3D2D - { - bpt::ptree indMatch3D2DTree; - - for(const IndMatch3D2D& indMatch3D2D : lr._indMatch3D2D) - { - bpt::ptree itTree; - itTree.put("landmarkId", indMatch3D2D.landmarkId); - itTree.put("featureId", indMatch3D2D.featId); - itTree.put("descType", feature::EImageDescriberType_enumToString(indMatch3D2D.descType)); - indMatch3D2DTree.push_back(std::make_pair("", itTree)); - } - lrTree.add_child("indMatch3D2D", indMatch3D2DTree); - } + // indMatch3D2D + { + bpt::ptree indMatch3D2DTree; + + for (const IndMatch3D2D& indMatch3D2D : lr._indMatch3D2D) + { + bpt::ptree itTree; + itTree.put("landmarkId", indMatch3D2D.landmarkId); + itTree.put("featureId", indMatch3D2D.featId); + itTree.put("descType", feature::EImageDescriberType_enumToString(indMatch3D2D.descType)); + indMatch3D2DTree.push_back(std::make_pair("", itTree)); + } + lrTree.add_child("indMatch3D2D", indMatch3D2DTree); + } - //intrinsic - { - std::shared_ptr intrinsicPtr = camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA); - *intrinsicPtr = lr._intrinsics; - sfmDataIO::saveIntrinsic("intrinsic", UndefinedIndexT, std::dynamic_pointer_cast(intrinsicPtr), lrTree); - } + // intrinsic + { + std::shared_ptr intrinsicPtr = camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA); + *intrinsicPtr = lr._intrinsics; + sfmDataIO::saveIntrinsic("intrinsic", UndefinedIndexT, std::dynamic_pointer_cast(intrinsicPtr), lrTree); + } - // inliers - { - bpt::ptree inliersTree; - for(std::size_t index : lr._matchData.vec_inliers) - { - bpt::ptree inlierTree; - inlierTree.put("",index); - inliersTree.push_back(std::make_pair("", inlierTree)); - } - lrTree.add_child("inliers", inliersTree); - } + // inliers + { + bpt::ptree inliersTree; + for (std::size_t index : lr._matchData.vec_inliers) + { + bpt::ptree inlierTree; + inlierTree.put("", index); + inliersTree.push_back(std::make_pair("", inlierTree)); + } + lrTree.add_child("inliers", inliersTree); + } - // needed for loading - lrTree.put("nbPts", lr._matchData.pt3D.cols()); + // needed for loading + lrTree.put("nbPts", lr._matchData.pt3D.cols()); - sfmDataIO::saveMatrix("pt3D", lr._matchData.pt3D, lrTree); - sfmDataIO::saveMatrix("pt2D", lr._matchData.pt2D, lrTree); - sfmDataIO::saveMatrix("projectionMatrix", lr._matchData.projection_matrix, lrTree); + sfmDataIO::saveMatrix("pt3D", lr._matchData.pt3D, lrTree); + sfmDataIO::saveMatrix("pt2D", lr._matchData.pt2D, lrTree); + sfmDataIO::saveMatrix("projectionMatrix", lr._matchData.projection_matrix, lrTree); - lrTree.put("errorMax", lr._matchData.error_max); - lrTree.put("maxIteration", lr._matchData.max_iteration); + lrTree.put("errorMax", lr._matchData.error_max); + lrTree.put("maxIteration", lr._matchData.max_iteration); - // matchedImages - { - bpt::ptree matchedImagesTree; - - for(const voctree::DocMatch& docMatch : lr._matchedImages) - { - bpt::ptree itTree; - itTree.put("id", docMatch.id); - itTree.put("score", docMatch.score); - matchedImagesTree.push_back(std::make_pair("", itTree)); - } - lrTree.add_child("matchedImages", matchedImagesTree); + // matchedImages + { + bpt::ptree matchedImagesTree; + + for (const voctree::DocMatch& docMatch : lr._matchedImages) + { + bpt::ptree itTree; + itTree.put("id", docMatch.id); + itTree.put("score", docMatch.score); + matchedImagesTree.push_back(std::make_pair("", itTree)); + } + lrTree.add_child("matchedImages", matchedImagesTree); + } + localizationResultsTree.push_back(std::make_pair("", lrTree)); } - localizationResultsTree.push_back(std::make_pair("", lrTree)); - } - fileTree.add_child("localizationResults", localizationResultsTree); + fileTree.add_child("localizationResults", localizationResultsTree); - // write the json file with the tree - bpt::write_json(filename, fileTree); + // write the json file with the tree + bpt::write_json(filename, fileTree); } void updateRigPoses(std::vector& vec_localizationResults, - const geometry::Pose3 &rigPose, - const std::vector &vec_subPoses) + const geometry::Pose3& rigPose, + const std::vector& vec_subPoses) { - const std::size_t numCams = vec_localizationResults.size(); - assert(numCams==vec_subPoses.size()+1); - - // update localization result poses - for(std::size_t camID = 0; camID < numCams; ++camID) - { - geometry::Pose3 pose; - if(camID == 0) - { - pose = rigPose; - } - else + const std::size_t numCams = vec_localizationResults.size(); + assert(numCams == vec_subPoses.size() + 1); + + // update localization result poses + for (std::size_t camID = 0; camID < numCams; ++camID) { - // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q - // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A - // and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) - // With rigResection() we compute [R1 t1] (aka rigPose), hence: - // subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) and we need [R2 t2], ie the absolute pose - // => [R1 t1] * subPose12 = [R2 t2] - // => rigPose * subPose12 = [R2 t2] - pose = vec_subPoses[camID-1] * rigPose; + geometry::Pose3 pose; + if (camID == 0) + { + pose = rigPose; + } + else + { + // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q + // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A + // and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) + // With rigResection() we compute [R1 t1] (aka rigPose), hence: + // subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) and we need [R2 t2], ie the absolute pose + // => [R1 t1] * subPose12 = [R2 t2] + // => rigPose * subPose12 = [R2 t2] + pose = vec_subPoses[camID - 1] * rigPose; + } + + vec_localizationResults[camID].setPose(pose); } - - vec_localizationResults[camID].setPose(pose); - } } -} // localization -} // aliceVision +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/LocalizationResult.hpp b/src/aliceVision/localization/LocalizationResult.hpp index a2f1be0710..bdedfa8b82 100644 --- a/src/aliceVision/localization/LocalizationResult.hpp +++ b/src/aliceVision/localization/LocalizationResult.hpp @@ -18,199 +18,151 @@ namespace localization { struct IndMatch3D2D { - IndMatch3D2D() {} - IndMatch3D2D(IndexT landmarkId, - feature::EImageDescriberType descType, - IndexT featId) - : landmarkId(landmarkId) - , descType(descType) - , featId(featId) - {} - - bool operator<(const IndMatch3D2D& other) const - { - return landmarkId < other.landmarkId; - } - - IndexT landmarkId = UndefinedIndexT; - feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; - IndexT featId = UndefinedIndexT; + IndMatch3D2D() {} + IndMatch3D2D(IndexT landmarkId, feature::EImageDescriberType descType, IndexT featId) + : landmarkId(landmarkId), + descType(descType), + featId(featId) + {} + + bool operator<(const IndMatch3D2D& other) const { return landmarkId < other.landmarkId; } + + IndexT landmarkId = UndefinedIndexT; + feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; + IndexT featId = UndefinedIndexT; }; - class LocalizationResult { -public: - - LocalizationResult(); - - LocalizationResult(const sfm::ImageLocalizerMatchData& matchData, - const std::vector& indMatch3D2D, - const geometry::Pose3& pose, - const camera::Pinhole& intrinsics, - const std::vector& matchedImages, - bool isValid = true); - - virtual ~LocalizationResult(); - - const std::vector& getInliers() const - { - return _matchData.vec_inliers; - } - - const Mat34& getProjection() const - { - return _matchData.projection_matrix; - } - - const Mat& getPt2D() const - { - return _matchData.pt2D; - } - - const Mat& getPt3D() const - { - return _matchData.pt3D; - } - - const Mat retrieveUndistortedPt2D() const; - - const sfm::ImageLocalizerMatchData& getMatchData() const - { - return _matchData; - } - - const std::vector& getIndMatch3D2D() const - { - return _indMatch3D2D; - } - - const std::vector& getMatchedImages() const - { - return _matchedImages; - } - - const geometry::Pose3& getPose() const - { - return _pose; - } - - void setPose(const geometry::Pose3 & pose) - { - _pose = pose; - } - - const camera::Pinhole& getIntrinsics() const - { - return _intrinsics; - } - - camera::Pinhole& getIntrinsics() - { - return _intrinsics; - } - - void updateIntrinsics(const std::vector& params) - { - _intrinsics.updateFromParams(params); - } - - bool isValid() const - { - return _isValid; - } - - /** - * @brief Compute the residual for each 2D-3D association. - * @return A 2xN matrix containing the x-y residual for each point. - */ - Mat2X computeAllResiduals() const; - - /** - * @brief Compute the residual for the inlier 2D-3D association. - * @return A 2xNumInliers containing the x-y residual for each inlier point. - */ - Mat2X computeInliersResiduals() const ; - - /** - * @brief Compute the reprojection error for the inliers. - * @return A 1xNumInliers vector containing the reprojection error for each inlier point. - */ - Vec computeReprojectionErrorPerInlier() const; - - /** - * @brief Compute the reprojection error for the all the points. - * @return A 1xNumInliers vector containing the reprojection error for each point. - */ - Vec computeReprojectionErrorPerPoint() const; - - /** - * @brief Compute the RMSE for the inlier association. - * @return The RMSE for the inlier associations. - */ - double computeInliersRMSE() const ; - - /** - * @brief Compute the RMSE for the all the associations. - * @return The RMSE for the inlier associations. - */ - double computeAllRMSE() const ; - - /** - * @brief Select the best inliers according to the given reprojection error threshold. - * @param[in] threshold The threshold for the reprojection error in pixels. - * @return The number of inliers detected with the new threshold. - */ - std::size_t selectBestInliers(double maxReprojectionError); - - /** - * @brief Select the best inliers according to the reprojection error threshold - * used/computed during the resection. - * @return The number of inliers detected. - */ - std::size_t selectBestInliers(); - - double getMaxReprojectionError() const { return _matchData.error_max;} - - static void save(const std::vector& localizationResults, const std::string& filename); - static void load(std::vector& localizationResults, const std::string& filename); - -private: - - /// Hold all the imaged points, their associated 3D points and the inlier indices - /// (w.r.t. the pose robust estimation) - sfm::ImageLocalizerMatchData _matchData; - - /// 3D to 2D index matches in the global index system, - /// i.e. the set of pair (landmark id, index of the associated 2D point). - /// It must have the same size of _matchData.pt3D (and pt2D) - std::vector _indMatch3D2D; - - /// Computed camera pose - geometry::Pose3 _pose; - - /// The camera intrinsics associated - camera::Pinhole _intrinsics; - - /// the database images that have been used for matching - std::vector _matchedImages; - - /// True if the localization succeeded, false otherwise - bool _isValid; + public: + LocalizationResult(); + + LocalizationResult(const sfm::ImageLocalizerMatchData& matchData, + const std::vector& indMatch3D2D, + const geometry::Pose3& pose, + const camera::Pinhole& intrinsics, + const std::vector& matchedImages, + bool isValid = true); + + virtual ~LocalizationResult(); + + const std::vector& getInliers() const { return _matchData.vec_inliers; } + + const Mat34& getProjection() const { return _matchData.projection_matrix; } + + const Mat& getPt2D() const { return _matchData.pt2D; } + + const Mat& getPt3D() const { return _matchData.pt3D; } + + const Mat retrieveUndistortedPt2D() const; + + const sfm::ImageLocalizerMatchData& getMatchData() const { return _matchData; } + + const std::vector& getIndMatch3D2D() const { return _indMatch3D2D; } + + const std::vector& getMatchedImages() const { return _matchedImages; } + + const geometry::Pose3& getPose() const { return _pose; } + + void setPose(const geometry::Pose3& pose) { _pose = pose; } + + const camera::Pinhole& getIntrinsics() const { return _intrinsics; } + + camera::Pinhole& getIntrinsics() { return _intrinsics; } + + void updateIntrinsics(const std::vector& params) { _intrinsics.updateFromParams(params); } + + bool isValid() const { return _isValid; } + + /** + * @brief Compute the residual for each 2D-3D association. + * @return A 2xN matrix containing the x-y residual for each point. + */ + Mat2X computeAllResiduals() const; + + /** + * @brief Compute the residual for the inlier 2D-3D association. + * @return A 2xNumInliers containing the x-y residual for each inlier point. + */ + Mat2X computeInliersResiduals() const; + + /** + * @brief Compute the reprojection error for the inliers. + * @return A 1xNumInliers vector containing the reprojection error for each inlier point. + */ + Vec computeReprojectionErrorPerInlier() const; + + /** + * @brief Compute the reprojection error for the all the points. + * @return A 1xNumInliers vector containing the reprojection error for each point. + */ + Vec computeReprojectionErrorPerPoint() const; + + /** + * @brief Compute the RMSE for the inlier association. + * @return The RMSE for the inlier associations. + */ + double computeInliersRMSE() const; + + /** + * @brief Compute the RMSE for the all the associations. + * @return The RMSE for the inlier associations. + */ + double computeAllRMSE() const; + + /** + * @brief Select the best inliers according to the given reprojection error threshold. + * @param[in] threshold The threshold for the reprojection error in pixels. + * @return The number of inliers detected with the new threshold. + */ + std::size_t selectBestInliers(double maxReprojectionError); + + /** + * @brief Select the best inliers according to the reprojection error threshold + * used/computed during the resection. + * @return The number of inliers detected. + */ + std::size_t selectBestInliers(); + + double getMaxReprojectionError() const { return _matchData.error_max; } + + static void save(const std::vector& localizationResults, const std::string& filename); + static void load(std::vector& localizationResults, const std::string& filename); + + private: + /// Hold all the imaged points, their associated 3D points and the inlier indices + /// (w.r.t. the pose robust estimation) + sfm::ImageLocalizerMatchData _matchData; + + /// 3D to 2D index matches in the global index system, + /// i.e. the set of pair (landmark id, index of the associated 2D point). + /// It must have the same size of _matchData.pt3D (and pt2D) + std::vector _indMatch3D2D; + + /// Computed camera pose + geometry::Pose3 _pose; + + /// The camera intrinsics associated + camera::Pinhole _intrinsics; + + /// the database images that have been used for matching + std::vector _matchedImages; + + /// True if the localization succeeded, false otherwise + bool _isValid; }; /** * @brief It recompute the pose of each camera in Localization results according - * to the rigPose given as input. The camera in position 0 is supposed to be the + * to the rigPose given as input. The camera in position 0 is supposed to be the * main camera and it is set to the pose of the rig. * @param[out] vec_localizationResults * @param[in] rigPose * @param[in] vec_subPoses (N-1) vector */ void updateRigPoses(std::vector& vec_localizationResults, - const geometry::Pose3 &rigPose, - const std::vector &vec_subPoses); - -} // localization -} // aliceVision + const geometry::Pose3& rigPose, + const std::vector& vec_subPoses); +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/LocalizationResult_test.cpp b/src/aliceVision/localization/LocalizationResult_test.cpp index b497378b8f..d6e3e82edb 100644 --- a/src/aliceVision/localization/LocalizationResult_test.cpp +++ b/src/aliceVision/localization/LocalizationResult_test.cpp @@ -26,136 +26,132 @@ using namespace aliceVision; sfm::ImageLocalizerMatchData generateRandomMatch_Data(std::size_t numPts) { - sfm::ImageLocalizerMatchData data; - data.projection_matrix = Mat34::Random(); - data.pt3D = Mat::Random(3, numPts); - data.pt2D = Mat::Random(2, numPts); - const std::size_t numInliers = (std::size_t) numPts*0.7; - for(std::size_t i = 0; i < numInliers; ++i) - { - data.vec_inliers.push_back(i); - } - return data; + sfm::ImageLocalizerMatchData data; + data.projection_matrix = Mat34::Random(); + data.pt3D = Mat::Random(3, numPts); + data.pt2D = Mat::Random(2, numPts); + const std::size_t numInliers = (std::size_t)numPts * 0.7; + for (std::size_t i = 0; i < numInliers; ++i) + { + data.vec_inliers.push_back(i); + } + return data; } localization::LocalizationResult generateRandomResult(std::size_t numPts) { - // random matchData - const sfm::ImageLocalizerMatchData &data = generateRandomMatch_Data(numPts); - - // random indMatch3D2D - std::vector indMatch3D2D; - indMatch3D2D.reserve(numPts); - for(std::size_t i = 0; i < numPts; ++i) - { - indMatch3D2D.emplace_back(i, feature::EImageDescriberType::UNKNOWN, i); - } - - // random pose - geometry::Pose3 pose = geometry::randomPose(); - - // random intrinsics - camera::Pinhole intrinsics = camera::Pinhole( - 640, 480, - 1400, 1400, - 320.5, 240.5, - std::make_shared(0.001, -0.05, 0.00003)); - - // random valid - const bool valid = (numPts % 2 == 0); - - std::vector matchedImages; - matchedImages.push_back(voctree::DocMatch(2, 0.5)); - matchedImages.push_back(voctree::DocMatch(3, 0.8)); - - return localization::LocalizationResult(data, indMatch3D2D, pose, intrinsics, matchedImages, valid); + // random matchData + const sfm::ImageLocalizerMatchData& data = generateRandomMatch_Data(numPts); + + // random indMatch3D2D + std::vector indMatch3D2D; + indMatch3D2D.reserve(numPts); + for (std::size_t i = 0; i < numPts; ++i) + { + indMatch3D2D.emplace_back(i, feature::EImageDescriberType::UNKNOWN, i); + } + + // random pose + geometry::Pose3 pose = geometry::randomPose(); + + // random intrinsics + camera::Pinhole intrinsics = + camera::Pinhole(640, 480, 1400, 1400, 320.5, 240.5, std::make_shared(0.001, -0.05, 0.00003)); + + // random valid + const bool valid = (numPts % 2 == 0); + + std::vector matchedImages; + matchedImages.push_back(voctree::DocMatch(2, 0.5)); + matchedImages.push_back(voctree::DocMatch(3, 0.8)); + + return localization::LocalizationResult(data, indMatch3D2D, pose, intrinsics, matchedImages, valid); } BOOST_AUTO_TEST_CASE(LocalizationResult_LoadSaveVector) { - makeRandomOperationsReproducible(); - - const double threshold = 1e-10; - const std::size_t numResults = 10; - const std::string filename = "test_localizationResults.json"; - const unsigned seed1 = std::chrono::system_clock::now().time_since_epoch().count(); - std::random_device rd; - std::mt19937 gen(seed1); - std::uniform_int_distribution<> numpts(1, 20); - - std::vector resGT; - std::vector resCheck; - resGT.reserve(numResults); - resCheck.reserve(numResults); - - for(std::size_t i = 0; i < numResults; ++i) - { - resGT.push_back(generateRandomResult(numpts(gen))); - } - - - BOOST_CHECK_NO_THROW(localization::LocalizationResult::save(resGT, filename)); - BOOST_CHECK_NO_THROW(localization::LocalizationResult::load(resCheck, filename)); - BOOST_CHECK(resCheck.size() == resGT.size()); - - // check each element - for(std::size_t i = 0; i < numResults; ++i) - { - const auto res = resGT[i]; - const auto check = resCheck[i]; - - // same validity - BOOST_CHECK(res.isValid() == check.isValid()); - - // same pose - const Mat3 rotGT = res.getPose().rotation(); - const Mat3 rot = check.getPose().rotation(); - for(std::size_t i = 0; i < 3; ++i) - { - for(std::size_t j = 0; j < 3; ++j) - { - BOOST_CHECK_SMALL(rotGT(i, j)- rot(i, j), threshold); - } - } - const Vec3 centerGT = res.getPose().center(); - const Vec3 center = check.getPose().center(); - BOOST_CHECK_SMALL(centerGT(0)- center(0), threshold); - BOOST_CHECK_SMALL(centerGT(1)- center(1), threshold); - BOOST_CHECK_SMALL(centerGT(2)- center(2), threshold); - - // same _indMatch3D2D - const auto idxGT = res.getIndMatch3D2D(); - const auto idx = check.getIndMatch3D2D(); - BOOST_CHECK(idxGT.size() == idx.size()); - const std::size_t numpts = idxGT.size(); - for(std::size_t j = 0; j < numpts; ++j) + makeRandomOperationsReproducible(); + + const double threshold = 1e-10; + const std::size_t numResults = 10; + const std::string filename = "test_localizationResults.json"; + const unsigned seed1 = std::chrono::system_clock::now().time_since_epoch().count(); + std::random_device rd; + std::mt19937 gen(seed1); + std::uniform_int_distribution<> numpts(1, 20); + + std::vector resGT; + std::vector resCheck; + resGT.reserve(numResults); + resCheck.reserve(numResults); + + for (std::size_t i = 0; i < numResults; ++i) { - BOOST_CHECK(idxGT[j].landmarkId == idx[j].landmarkId); - BOOST_CHECK(idxGT[j].featId == idx[j].featId); + resGT.push_back(generateRandomResult(numpts(gen))); } - // same _matchData - BOOST_CHECK(res.getInliers().size() == check.getInliers().size()); - const auto inliersGT = res.getInliers(); - const auto inliers = check.getInliers(); - for(std::size_t j = 0; j < res.getInliers().size(); ++j) - { - BOOST_CHECK(inliersGT[j] == inliers[j]); - } + BOOST_CHECK_NO_THROW(localization::LocalizationResult::save(resGT, filename)); + BOOST_CHECK_NO_THROW(localization::LocalizationResult::load(resCheck, filename)); + BOOST_CHECK(resCheck.size() == resGT.size()); - EXPECT_MATRIX_NEAR(res.getPt3D(), check.getPt3D(), threshold); - EXPECT_MATRIX_NEAR(res.getPt2D(), check.getPt2D(), threshold); - EXPECT_MATRIX_NEAR(res.getProjection(), check.getProjection(), threshold); - - // same matchedImages - BOOST_CHECK(res.getMatchedImages().size() == check.getMatchedImages().size()); - const auto matchedImagesGT = res.getMatchedImages(); - const auto matchedImages = check.getMatchedImages(); - for(std::size_t j = 0; j < res.getMatchedImages().size(); ++j) + // check each element + for (std::size_t i = 0; i < numResults; ++i) { - BOOST_CHECK(matchedImagesGT[j] == matchedImages[j]); + const auto res = resGT[i]; + const auto check = resCheck[i]; + + // same validity + BOOST_CHECK(res.isValid() == check.isValid()); + + // same pose + const Mat3 rotGT = res.getPose().rotation(); + const Mat3 rot = check.getPose().rotation(); + for (std::size_t i = 0; i < 3; ++i) + { + for (std::size_t j = 0; j < 3; ++j) + { + BOOST_CHECK_SMALL(rotGT(i, j) - rot(i, j), threshold); + } + } + const Vec3 centerGT = res.getPose().center(); + const Vec3 center = check.getPose().center(); + BOOST_CHECK_SMALL(centerGT(0) - center(0), threshold); + BOOST_CHECK_SMALL(centerGT(1) - center(1), threshold); + BOOST_CHECK_SMALL(centerGT(2) - center(2), threshold); + + // same _indMatch3D2D + const auto idxGT = res.getIndMatch3D2D(); + const auto idx = check.getIndMatch3D2D(); + BOOST_CHECK(idxGT.size() == idx.size()); + const std::size_t numpts = idxGT.size(); + for (std::size_t j = 0; j < numpts; ++j) + { + BOOST_CHECK(idxGT[j].landmarkId == idx[j].landmarkId); + BOOST_CHECK(idxGT[j].featId == idx[j].featId); + } + + // same _matchData + BOOST_CHECK(res.getInliers().size() == check.getInliers().size()); + const auto inliersGT = res.getInliers(); + const auto inliers = check.getInliers(); + for (std::size_t j = 0; j < res.getInliers().size(); ++j) + { + BOOST_CHECK(inliersGT[j] == inliers[j]); + } + + EXPECT_MATRIX_NEAR(res.getPt3D(), check.getPt3D(), threshold); + EXPECT_MATRIX_NEAR(res.getPt2D(), check.getPt2D(), threshold); + EXPECT_MATRIX_NEAR(res.getProjection(), check.getProjection(), threshold); + + // same matchedImages + BOOST_CHECK(res.getMatchedImages().size() == check.getMatchedImages().size()); + const auto matchedImagesGT = res.getMatchedImages(); + const auto matchedImages = check.getMatchedImages(); + for (std::size_t j = 0; j < res.getMatchedImages().size(); ++j) + { + BOOST_CHECK(matchedImagesGT[j] == matchedImages[j]); + } + + fs::remove(filename); } - - fs::remove(filename); - } } diff --git a/src/aliceVision/localization/VoctreeLocalizer.cpp b/src/aliceVision/localization/VoctreeLocalizer.cpp index bb1d1d3f87..952a62e819 100644 --- a/src/aliceVision/localization/VoctreeLocalizer.cpp +++ b/src/aliceVision/localization/VoctreeLocalizer.cpp @@ -39,1581 +39,1516 @@ namespace localization { std::ostream& operator<<(std::ostream& os, VoctreeLocalizer::Algorithm a) { - switch(a) - { - case VoctreeLocalizer::Algorithm::FirstBest: os << "FirstBest"; - break; - case VoctreeLocalizer::Algorithm::BestResult: os << "BestResult"; - break; - case VoctreeLocalizer::Algorithm::AllResults: os << "AllResults"; - break; - case VoctreeLocalizer::Algorithm::Cluster: os << "Cluster"; - break; - default: - os << "Unknown algorithm!"; - throw std::invalid_argument("Unrecognized algorithm!"); - } - return os; + switch (a) + { + case VoctreeLocalizer::Algorithm::FirstBest: + os << "FirstBest"; + break; + case VoctreeLocalizer::Algorithm::BestResult: + os << "BestResult"; + break; + case VoctreeLocalizer::Algorithm::AllResults: + os << "AllResults"; + break; + case VoctreeLocalizer::Algorithm::Cluster: + os << "Cluster"; + break; + default: + os << "Unknown algorithm!"; + throw std::invalid_argument("Unrecognized algorithm!"); + } + return os; } -std::istream& operator>>(std::istream &in, VoctreeLocalizer::Algorithm &a) +std::istream& operator>>(std::istream& in, VoctreeLocalizer::Algorithm& a) { - int i; - in >> i; - a = static_cast (i); - return in; -} + int i; + in >> i; + a = static_cast(i); + return in; +} -FrameData::FrameData(const LocalizationResult &locResult, const feature::MapRegionsPerDesc& regionsPerDesc) +FrameData::FrameData(const LocalizationResult& locResult, const feature::MapRegionsPerDesc& regionsPerDesc) : _locResult(locResult) { - // now we need to filter out and keep only the regions with 3D data associated - const auto &associationIDs = _locResult.getIndMatch3D2D(); - const auto &inliers = _locResult.getInliers(); - - // Regions for each describer type - for(const auto& regionsPerDescIt : regionsPerDesc) - { - // feature in image are associations - std::vector featuresInImage; - featuresInImage.reserve(inliers.size()); - - assert(inliers.size() <= associationIDs.size()); - for(const auto &idx : inliers) + // now we need to filter out and keep only the regions with 3D data associated + const auto& associationIDs = _locResult.getIndMatch3D2D(); + const auto& inliers = _locResult.getInliers(); + + // Regions for each describer type + for (const auto& regionsPerDescIt : regionsPerDesc) { - assert(idx < associationIDs.size()); - const auto &association = associationIDs[idx]; - assert(association.descType != feature::EImageDescriberType::UNINITIALIZED); - if(association.descType != regionsPerDescIt.first) - continue; - - // add it to featuresInImage - featuresInImage.emplace_back(association.featId, association.landmarkId); - } + // feature in image are associations + std::vector featuresInImage; + featuresInImage.reserve(inliers.size()); - _regions[regionsPerDescIt.first] = createFilteredRegions(*regionsPerDescIt.second, featuresInImage, _regionsWith3D[regionsPerDescIt.first]); - } + assert(inliers.size() <= associationIDs.size()); + for (const auto& idx : inliers) + { + assert(idx < associationIDs.size()); + const auto& association = associationIDs[idx]; + assert(association.descType != feature::EImageDescriberType::UNINITIALIZED); + if (association.descType != regionsPerDescIt.first) + continue; + + // add it to featuresInImage + featuresInImage.emplace_back(association.featId, association.landmarkId); + } + + _regions[regionsPerDescIt.first] = createFilteredRegions(*regionsPerDescIt.second, featuresInImage, _regionsWith3D[regionsPerDescIt.first]); + } } -VoctreeLocalizer::Algorithm VoctreeLocalizer::initFromString(const std::string &value) +VoctreeLocalizer::Algorithm VoctreeLocalizer::initFromString(const std::string& value) { - if(value=="FirstBest") - return VoctreeLocalizer::Algorithm::FirstBest; - else if(value=="AllResults") - return VoctreeLocalizer::Algorithm::AllResults; - else if(value=="BestResult") - throw std::invalid_argument("BestResult not yet implemented"); - else if(value=="Cluster") - throw std::invalid_argument("Cluster not yet implemented"); - else - throw std::invalid_argument("Unrecognized algorithm \"" + value + "\"!"); + if (value == "FirstBest") + return VoctreeLocalizer::Algorithm::FirstBest; + else if (value == "AllResults") + return VoctreeLocalizer::Algorithm::AllResults; + else if (value == "BestResult") + throw std::invalid_argument("BestResult not yet implemented"); + else if (value == "Cluster") + throw std::invalid_argument("Cluster not yet implemented"); + else + throw std::invalid_argument("Unrecognized algorithm \"" + value + "\"!"); } - -VoctreeLocalizer::VoctreeLocalizer(const sfmData::SfMData &sfmData, - const std::string &descriptorsFolder, - const std::string &vocTreeFilepath, - const std::string &weightsFilepath, +VoctreeLocalizer::VoctreeLocalizer(const sfmData::SfMData& sfmData, + const std::string& descriptorsFolder, + const std::string& vocTreeFilepath, + const std::string& weightsFilepath, const std::vector& matchingDescTypes) - : ILocalizer() - , _frameBuffer(5) + : ILocalizer(), + _frameBuffer(5) { - using namespace aliceVision::feature; + using namespace aliceVision::feature; - // init the feature extractor - _imageDescribers.reserve(matchingDescTypes.size()); - for(feature::EImageDescriberType matchingDescType: matchingDescTypes) - { - _imageDescribers.push_back(createImageDescriber(matchingDescType)); - } + // init the feature extractor + _imageDescribers.reserve(matchingDescTypes.size()); + for (feature::EImageDescriberType matchingDescType : matchingDescTypes) + { + _imageDescribers.push_back(createImageDescriber(matchingDescType)); + } - _sfm_data = sfmData; + _sfm_data = sfmData; - // load the features and descriptors - // initially we need all the feature in order to create the database - // then we can store only those associated to 3D points - //? can we use Feature_Provider to load the features and filter them later? + // load the features and descriptors + // initially we need all the feature in order to create the database + // then we can store only those associated to 3D points + //? can we use Feature_Provider to load the features and filter them later? - _isInit = initDatabase(vocTreeFilepath, weightsFilepath, descriptorsFolder); + _isInit = initDatabase(vocTreeFilepath, weightsFilepath, descriptorsFolder); } -bool VoctreeLocalizer::localize(const feature::MapRegionsPerDesc & queryRegions, - const std::pair &imageSize, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, +bool VoctreeLocalizer::localize(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, const std::string& imagePath) { - const Parameters *voctreeParam = static_cast(param); - if(!voctreeParam) - { - // error! - throw std::invalid_argument("The parameters are not in the right format!!"); - } - - switch(voctreeParam->_algorithm) - { - case Algorithm::FirstBest: - return localizeFirstBestResult(queryRegions, - imageSize, - *voctreeParam, - randomNumberGenerator, - useInputIntrinsics, - queryIntrinsics, - localizationResult, - imagePath); - case Algorithm::BestResult: throw std::invalid_argument("BestResult not yet implemented"); - case Algorithm::AllResults: - return localizeAllResults(queryRegions, - imageSize, - *voctreeParam, - randomNumberGenerator, - useInputIntrinsics, - queryIntrinsics, - localizationResult, - imagePath); - case Algorithm::Cluster: throw std::invalid_argument("Cluster not yet implemented"); - default: throw std::invalid_argument("Unknown algorithm type"); - } + const Parameters* voctreeParam = static_cast(param); + if (!voctreeParam) + { + // error! + throw std::invalid_argument("The parameters are not in the right format!!"); + } + + switch (voctreeParam->_algorithm) + { + case Algorithm::FirstBest: + return localizeFirstBestResult( + queryRegions, imageSize, *voctreeParam, randomNumberGenerator, useInputIntrinsics, queryIntrinsics, localizationResult, imagePath); + case Algorithm::BestResult: + throw std::invalid_argument("BestResult not yet implemented"); + case Algorithm::AllResults: + return localizeAllResults( + queryRegions, imageSize, *voctreeParam, randomNumberGenerator, useInputIntrinsics, queryIntrinsics, localizationResult, imagePath); + case Algorithm::Cluster: + throw std::invalid_argument("Cluster not yet implemented"); + default: + throw std::invalid_argument("Unknown algorithm type"); + } } bool VoctreeLocalizer::localize(const image::Image& imageGrey, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult &localizationResult, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, const std::string& imagePath /* = std::string() */) { - // A. extract descriptors and features from image - ALICEVISION_LOG_DEBUG("[features]\tExtract Regions from query image"); - feature::MapRegionsPerDesc queryRegionsPerDesc; + // A. extract descriptors and features from image + ALICEVISION_LOG_DEBUG("[features]\tExtract Regions from query image"); + feature::MapRegionsPerDesc queryRegionsPerDesc; - image::Image imageGrayUChar; // uchar image copy for uchar image describer + image::Image imageGrayUChar; // uchar image copy for uchar image describer - for(const auto& imageDescriber : _imageDescribers) - { - const auto descType = imageDescriber->getDescriberType(); - auto & queryRegions = queryRegionsPerDesc[descType]; + for (const auto& imageDescriber : _imageDescribers) + { + const auto descType = imageDescriber->getDescriberType(); + auto& queryRegions = queryRegionsPerDesc[descType]; - imageDescriber->allocate(queryRegions); + imageDescriber->allocate(queryRegions); - system::Timer timer; - imageDescriber->setCudaPipe(_cudaPipe); - imageDescriber->setConfigurationPreset(param->_featurePreset); + system::Timer timer; + imageDescriber->setCudaPipe(_cudaPipe); + imageDescriber->setConfigurationPreset(param->_featurePreset); - if(imageDescriber->useFloatImage()) - { - imageDescriber->describe(imageGrey, queryRegions, nullptr); - } - else - { - // image descriptor can't use float image - if(imageGrayUChar.Width() == 0) // the first time, convert the float buffer to uchar - imageGrayUChar = (imageGrey.GetMat() * 255.f).cast(); - imageDescriber->describe(imageGrayUChar, queryRegions, nullptr); + if (imageDescriber->useFloatImage()) + { + imageDescriber->describe(imageGrey, queryRegions, nullptr); + } + else + { + // image descriptor can't use float image + if (imageGrayUChar.Width() == 0) // the first time, convert the float buffer to uchar + imageGrayUChar = (imageGrey.GetMat() * 255.f).cast(); + imageDescriber->describe(imageGrayUChar, queryRegions, nullptr); + } + + ALICEVISION_LOG_DEBUG("[features]\tExtract " << feature::EImageDescriberType_enumToString(descType) << " done: found " + << queryRegions->RegionCount() << " features in " << timer.elapsedMs() << " [ms]"); } - ALICEVISION_LOG_DEBUG("[features]\tExtract " << feature::EImageDescriberType_enumToString(descType) << " done: found " << queryRegions->RegionCount() << " features in " << timer.elapsedMs() << " [ms]"); - } + const std::pair queryImageSize = std::make_pair(imageGrey.Width(), imageGrey.Height()); - const std::pair queryImageSize = std::make_pair(imageGrey.Width(), imageGrey.Height()); + // if debugging is enable save the svg image with the extracted features + if (!param->_visualDebug.empty() && !imagePath.empty()) + { + feature::MapFeaturesPerDesc extractedFeatures; - // if debugging is enable save the svg image with the extracted features - if(!param->_visualDebug.empty() && !imagePath.empty()) - { - feature::MapFeaturesPerDesc extractedFeatures; + for (const auto& imageDescriber : _imageDescribers) + { + const auto descType = imageDescriber->getDescriberType(); + extractedFeatures[descType] = queryRegionsPerDesc.at(descType)->GetRegionsPositions(); + } - for(const auto& imageDescriber : _imageDescribers) - { - const auto descType = imageDescriber->getDescriberType(); - extractedFeatures[descType] = queryRegionsPerDesc.at(descType)->GetRegionsPositions(); + namespace bfs = boost::filesystem; + matching::saveFeatures2SVG( + imagePath, queryImageSize, extractedFeatures, param->_visualDebug + "/" + bfs::path(imagePath).stem().string() + ".svg"); } - namespace bfs = boost::filesystem; - matching::saveFeatures2SVG(imagePath, - queryImageSize, - extractedFeatures, - param->_visualDebug + "/" + bfs::path(imagePath).stem().string() + ".svg"); - } - - return localize(queryRegionsPerDesc, - queryImageSize, - param, - randomNumberGenerator, - useInputIntrinsics, - queryIntrinsics, - localizationResult, - imagePath); + return localize( + queryRegionsPerDesc, queryImageSize, param, randomNumberGenerator, useInputIntrinsics, queryIntrinsics, localizationResult, imagePath); } -bool VoctreeLocalizer::loadReconstructionDescriptors(const sfmData::SfMData & sfm_data, - const std::string & feat_directory) +bool VoctreeLocalizer::loadReconstructionDescriptors(const sfmData::SfMData& sfm_data, const std::string& feat_directory) { - //@fixme deprecated: now inside initDatabase - throw std::logic_error("loadReconstructionDescriptors is deprecated, use initDatabase instead."); - return true; + //@fixme deprecated: now inside initDatabase + throw std::logic_error("loadReconstructionDescriptors is deprecated, use initDatabase instead."); + return true; } /** * @brief Initialize the database: load features & descriptors for reconstructed landmarks, * and create voctree image desc. */ -bool VoctreeLocalizer::initDatabase(const std::string & vocTreeFilepath, - const std::string & weightsFilepath, - const std::string & featFolder) +bool VoctreeLocalizer::initDatabase(const std::string& vocTreeFilepath, const std::string& weightsFilepath, const std::string& featFolder) { + bool withWeights = !weightsFilepath.empty(); + + // Load vocabulary tree + ALICEVISION_LOG_DEBUG("Loading vocabulary tree..."); - bool withWeights = !weightsFilepath.empty(); - - // Load vocabulary tree - ALICEVISION_LOG_DEBUG("Loading vocabulary tree..."); - - voctree::load(_voctree, _voctreeDescType, vocTreeFilepath); - - ALICEVISION_LOG_DEBUG("tree loaded with " << _voctree->levels() << " levels and " - << _voctree->splits() << " branching factors"); - - ALICEVISION_LOG_DEBUG("Creating the database..."); - // Add each object (document) to the database - _database = voctree::Database(_voctree->words()); - if(withWeights) - { - ALICEVISION_LOG_DEBUG("Loading weights..."); - _database.loadWeights(weightsFilepath); - } - else - { - ALICEVISION_LOG_DEBUG("No weights specified, skipping..."); - } - - // Load the descriptors and the features related to the images - // for every image, pass the descriptors through the vocabulary tree and - // add its visual words to the database. - // then only store the feature and descriptors that have a 3D point associated - ALICEVISION_LOG_DEBUG("Build observations per view"); - auto progressDisplay = - system::createConsoleProgressDisplay(_sfm_data.getViews().size(), std::cout, - "\n- Load Features and Descriptors per view -\n"); - - // Build observations per view - std::map>> observationsPerView; - for(const auto& landmarkValue : _sfm_data.getLandmarks()) - { - IndexT trackId = landmarkValue.first; - const sfmData::Landmark& landmark = landmarkValue.second; - - for(const auto& obs : landmark.observations) + voctree::load(_voctree, _voctreeDescType, vocTreeFilepath); + + ALICEVISION_LOG_DEBUG("tree loaded with " << _voctree->levels() << " levels and " << _voctree->splits() << " branching factors"); + + ALICEVISION_LOG_DEBUG("Creating the database..."); + // Add each object (document) to the database + _database = voctree::Database(_voctree->words()); + if (withWeights) { - const IndexT viewId = obs.first; - const sfmData::Observation& obs2d = obs.second; - observationsPerView[viewId][landmark.descType].emplace_back(obs2d.id_feat, trackId); + ALICEVISION_LOG_DEBUG("Loading weights..."); + _database.loadWeights(weightsFilepath); } - } - for(auto& featuresPerTypeInImage : observationsPerView) - { - for(auto& featuresInImage : featuresPerTypeInImage.second) + else { - std::sort(featuresInImage.second.begin(), featuresInImage.second.end()); + ALICEVISION_LOG_DEBUG("No weights specified, skipping..."); } - } - - std::vector featuresFolders = _sfm_data.getFeaturesFolders(); - if(!featFolder.empty()) - featuresFolders.emplace_back(featFolder); - // Read for each view the corresponding Regions and store them -#pragma omp parallel for num_threads(3) - for(int i = 0; i < _sfm_data.getViews().size(); ++i) - { - auto iter = _sfm_data.getViews().cbegin(); - std::advance(iter, i); - const IndexT id_view = iter->second->getViewId(); - if(observationsPerView.count(id_view) == 0) - continue; - const auto& observations = observationsPerView.at(id_view); - for(const auto& imageDescriber: _imageDescribers) + // Load the descriptors and the features related to the images + // for every image, pass the descriptors through the vocabulary tree and + // add its visual words to the database. + // then only store the feature and descriptors that have a 3D point associated + ALICEVISION_LOG_DEBUG("Build observations per view"); + auto progressDisplay = + system::createConsoleProgressDisplay(_sfm_data.getViews().size(), std::cout, "\n- Load Features and Descriptors per view -\n"); + + // Build observations per view + std::map>> observationsPerView; + for (const auto& landmarkValue : _sfm_data.getLandmarks()) { - const feature::EImageDescriberType descType = imageDescriber->getDescriberType(); + IndexT trackId = landmarkValue.first; + const sfmData::Landmark& landmark = landmarkValue.second; - ReconstructedRegionsMapping mapping; - if(observations.count(descType) == 0) - { - // no descriptor of this type in this View -#pragma omp critical + for (const auto& obs : landmark.observations) + { + const IndexT viewId = obs.first; + const sfmData::Observation& obs2d = obs.second; + observationsPerView[viewId][landmark.descType].emplace_back(obs2d.id_feat, trackId); + } + } + for (auto& featuresPerTypeInImage : observationsPerView) + { + for (auto& featuresInImage : featuresPerTypeInImage.second) { - // We always initialize objects with empty structures, - // so all views and descTypes always exist in the map. - // It simplifies the code based on these data structures, - // so you have a data structure with 0 element and you don't need to add - // special cases everywhere for empty elements. - _reconstructedRegionsMappingPerView[id_view][descType] = std::move(mapping); - imageDescriber->allocate(_regionsPerView.getData()[id_view][descType]); + std::sort(featuresInImage.second.begin(), featuresInImage.second.end()); } - continue; - } + } - // Load from files - std::unique_ptr currRegions = sfm::loadRegions(featuresFolders, id_view, *imageDescriber); + std::vector featuresFolders = _sfm_data.getFeaturesFolders(); + if (!featFolder.empty()) + featuresFolders.emplace_back(featFolder); - if(descType == _voctreeDescType) - { - voctree::SparseHistogram histo = _voctree->quantizeToSparse(currRegions->blindDescriptors()); -#pragma omp critical + // Read for each view the corresponding Regions and store them +#pragma omp parallel for num_threads(3) + for (int i = 0; i < _sfm_data.getViews().size(); ++i) + { + auto iter = _sfm_data.getViews().cbegin(); + std::advance(iter, i); + const IndexT id_view = iter->second->getViewId(); + if (observationsPerView.count(id_view) == 0) + continue; + const auto& observations = observationsPerView.at(id_view); + for (const auto& imageDescriber : _imageDescribers) { - _database.insert(id_view, histo); - } - } + const feature::EImageDescriberType descType = imageDescriber->getDescriberType(); + ReconstructedRegionsMapping mapping; + if (observations.count(descType) == 0) + { + // no descriptor of this type in this View +#pragma omp critical + { + // We always initialize objects with empty structures, + // so all views and descTypes always exist in the map. + // It simplifies the code based on these data structures, + // so you have a data structure with 0 element and you don't need to add + // special cases everywhere for empty elements. + _reconstructedRegionsMappingPerView[id_view][descType] = std::move(mapping); + imageDescriber->allocate(_regionsPerView.getData()[id_view][descType]); + } + continue; + } + + // Load from files + std::unique_ptr currRegions = sfm::loadRegions(featuresFolders, id_view, *imageDescriber); + + if (descType == _voctreeDescType) + { + voctree::SparseHistogram histo = _voctree->quantizeToSparse(currRegions->blindDescriptors()); +#pragma omp critical + { + _database.insert(id_view, histo); + } + } - // Filter descriptors to keep only the 3D reconstructed points - std::unique_ptr filteredRegions = createFilteredRegions(*currRegions, observations.at(descType), mapping); + // Filter descriptors to keep only the 3D reconstructed points + std::unique_ptr filteredRegions = createFilteredRegions(*currRegions, observations.at(descType), mapping); #pragma omp critical - { - _reconstructedRegionsMappingPerView[id_view][descType] = std::move(mapping); - _regionsPerView.getData()[id_view][descType] = std::move(filteredRegions); - } + { + _reconstructedRegionsMappingPerView[id_view][descType] = std::move(mapping); + _regionsPerView.getData()[id_view][descType] = std::move(filteredRegions); + } + } + ++progressDisplay; } - ++progressDisplay; - } - return true; + return true; } -bool VoctreeLocalizer::localizeFirstBestResult(const feature::MapRegionsPerDesc &queryRegions, - const std::pair &queryImageSize, - const Parameters ¶m, - std::mt19937 & randomNumberGenerator, +bool VoctreeLocalizer::localizeFirstBestResult(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& queryImageSize, + const Parameters& param, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult &localizationResult, - const std::string &imagePath) + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath) { - // A. Find the (visually) similar images in the database - ALICEVISION_LOG_DEBUG("[database]\tRequest closest images from voctree"); - // pass the descriptors through the vocabulary tree to get the visual words - // associated to each feature - voctree::SparseHistogram requestImageWords = _voctree->quantizeToSparse(queryRegions.at(_voctreeDescType)->blindDescriptors()); - - // Request closest images from voctree - std::vector matchedImages; - _database.find(requestImageWords, param._numResults, matchedImages); - -// // Debugging log -// // for each similar image found print score and number of features -// for(const voctree::DocMatch & currMatch : matchedImages ) -// { -// // get the corresponding index of the view -// const IndexT matchedViewIndex = currMatch.id; -// // get the view handle -// const std::shared_ptr matchedView = _sfm_data.getViews()[matchedViewIndex]; -// ALICEVISION_LOG_DEBUG( "[database]\t\t match " << matchedView->getImage().getImagePath() -// << " [docid: "<< currMatch.id << "]" -// << " with score " << currMatch.score -// << " and it has " << _regions_per_view[matchedViewIndex]._regions.RegionCount() -// << " features with 3D points"); -// } - - ALICEVISION_LOG_DEBUG("[matching]\tBuilding the matcher"); - matching::RegionsDatabaseMatcherPerDesc matchers(randomNumberGenerator, _matcherType, queryRegions); - - sfm::ImageLocalizerMatchData resectionData; - std::vector associationIDs; - geometry::Pose3 pose; - - // B. for each found similar image, try to find the correspondences between the - // query image and the similar image - for(const voctree::DocMatch& matchedImage : matchedImages) - { - // minimum number of points that allows a reliable 3D reconstruction - const size_t minNum3DPoints = 5; - - // the view index of the current matched image - const IndexT matchedViewId = matchedImage.id; - // the handler to the current view - const std::shared_ptr matchedView = _sfm_data.getViews().at(matchedViewId); - - // safeguard: we should match the query image with an image that has at least - // some 3D points visible --> if it has 0 3d points it is likely that it is an - // image of the dataset that was not reconstructed - if(_regionsPerView.getRegionsPerDesc(matchedViewId).getNbAllRegions() < minNum3DPoints) + // A. Find the (visually) similar images in the database + ALICEVISION_LOG_DEBUG("[database]\tRequest closest images from voctree"); + // pass the descriptors through the vocabulary tree to get the visual words + // associated to each feature + voctree::SparseHistogram requestImageWords = _voctree->quantizeToSparse(queryRegions.at(_voctreeDescType)->blindDescriptors()); + + // Request closest images from voctree + std::vector matchedImages; + _database.find(requestImageWords, param._numResults, matchedImages); + + // // Debugging log + // // for each similar image found print score and number of features + // for(const voctree::DocMatch & currMatch : matchedImages ) + // { + // // get the corresponding index of the view + // const IndexT matchedViewIndex = currMatch.id; + // // get the view handle + // const std::shared_ptr matchedView = _sfm_data.getViews()[matchedViewIndex]; + // ALICEVISION_LOG_DEBUG( "[database]\t\t match " << matchedView->getImage().getImagePath() + // << " [docid: "<< currMatch.id << "]" + // << " with score " << currMatch.score + // << " and it has " << _regions_per_view[matchedViewIndex]._regions.RegionCount() + // << " features with 3D points"); + // } + + ALICEVISION_LOG_DEBUG("[matching]\tBuilding the matcher"); + matching::RegionsDatabaseMatcherPerDesc matchers(randomNumberGenerator, _matcherType, queryRegions); + + sfm::ImageLocalizerMatchData resectionData; + std::vector associationIDs; + geometry::Pose3 pose; + + // B. for each found similar image, try to find the correspondences between the + // query image and the similar image + for (const voctree::DocMatch& matchedImage : matchedImages) { - ALICEVISION_LOG_DEBUG("[matching]\tSkipping matching with " << matchedView->getImage().getImagePath() << " as it has too few visible 3D points (" << _regionsPerView.getRegionsPerDesc(matchedViewId).getNbAllRegions() << ")"); - continue; - } - ALICEVISION_LOG_DEBUG("[matching]\tTrying to match the query image with " << matchedView->getImage().getImagePath()); - - // its associated intrinsics - const camera::IntrinsicBase *matchedIntrinsicsBase = _sfm_data.getIntrinsicPtr(matchedView->getIntrinsicId()); - if ( !isPinhole(matchedIntrinsicsBase->getType()) ) - throw std::logic_error("Unsupported intrinsic: " + EINTRINSIC_enumToString(matchedIntrinsicsBase->getType()) + " (only Pinhole cameras are supported for localization)."); - - const camera::Pinhole *matchedIntrinsics = (const camera::Pinhole*)(matchedIntrinsicsBase); - - matching::MatchesPerDescType featureMatches; - bool matchWorked = robustMatching(matchers, - // pass the input intrinsic if they are valid, null otherwise - (useInputIntrinsics) ? &queryIntrinsics : nullptr, - _regionsPerView.getRegionsPerDesc(matchedViewId), - matchedIntrinsics, - param._fDistRatio, - param._matchingError, - param._useRobustMatching, - param._useGuidedMatching, + // minimum number of points that allows a reliable 3D reconstruction + const size_t minNum3DPoints = 5; + + // the view index of the current matched image + const IndexT matchedViewId = matchedImage.id; + // the handler to the current view + const std::shared_ptr matchedView = _sfm_data.getViews().at(matchedViewId); + + // safeguard: we should match the query image with an image that has at least + // some 3D points visible --> if it has 0 3d points it is likely that it is an + // image of the dataset that was not reconstructed + if (_regionsPerView.getRegionsPerDesc(matchedViewId).getNbAllRegions() < minNum3DPoints) + { + ALICEVISION_LOG_DEBUG("[matching]\tSkipping matching with " << matchedView->getImage().getImagePath() + << " as it has too few visible 3D points (" + << _regionsPerView.getRegionsPerDesc(matchedViewId).getNbAllRegions() << ")"); + continue; + } + ALICEVISION_LOG_DEBUG("[matching]\tTrying to match the query image with " << matchedView->getImage().getImagePath()); + + // its associated intrinsics + const camera::IntrinsicBase* matchedIntrinsicsBase = _sfm_data.getIntrinsicPtr(matchedView->getIntrinsicId()); + if (!isPinhole(matchedIntrinsicsBase->getType())) + throw std::logic_error("Unsupported intrinsic: " + EINTRINSIC_enumToString(matchedIntrinsicsBase->getType()) + + " (only Pinhole cameras are supported for localization)."); + + const camera::Pinhole* matchedIntrinsics = (const camera::Pinhole*)(matchedIntrinsicsBase); + + matching::MatchesPerDescType featureMatches; + bool matchWorked = robustMatching(matchers, + // pass the input intrinsic if they are valid, null otherwise + (useInputIntrinsics) ? &queryIntrinsics : nullptr, + _regionsPerView.getRegionsPerDesc(matchedViewId), + matchedIntrinsics, + param._fDistRatio, + param._matchingError, + param._useRobustMatching, + param._useGuidedMatching, + queryImageSize, + std::make_pair(matchedView->getImage().getWidth(), matchedView->getImage().getHeight()), + randomNumberGenerator, + featureMatches, + param._matchingEstimator); + if (!matchWorked) + { + ALICEVISION_LOG_DEBUG("[matching]\tMatching with " << matchedView->getImage().getImagePath() << " failed! Skipping image"); + continue; + } + + std::size_t nbAllMatches = featureMatches.getNbAllMatches(); + ALICEVISION_LOG_DEBUG("[matching]\tFound " << nbAllMatches << " geometrically validated matches"); + assert(nbAllMatches > 0); + + if (!param._visualDebug.empty() && !imagePath.empty()) + { + namespace bfs = boost::filesystem; + const sfmData::View* mview = _sfm_data.getViews().at(matchedViewId).get(); + const std::string queryimage = bfs::path(imagePath).stem().string(); + const std::string matchedImage = bfs::path(mview->getImage().getImagePath()).stem().string(); + const std::string matchedPath = mview->getImage().getImagePath(); + + matching::saveMatches2SVG(imagePath, queryImageSize, - std::make_pair(matchedView->getImage().getWidth(), matchedView->getImage().getHeight()), - randomNumberGenerator, + queryRegions, + matchedPath, + std::make_pair(mview->getImage().getWidth(), mview->getImage().getHeight()), + _regionsPerView.getRegionsPerDesc(matchedViewId), featureMatches, - param._matchingEstimator); - if (!matchWorked) - { - ALICEVISION_LOG_DEBUG("[matching]\tMatching with " << matchedView->getImage().getImagePath() << " failed! Skipping image"); - continue; - } + param._visualDebug + "/" + queryimage + "_" + matchedImage + ".svg"); + } - std::size_t nbAllMatches = featureMatches.getNbAllMatches(); - ALICEVISION_LOG_DEBUG("[matching]\tFound " << nbAllMatches << " geometrically validated matches"); - assert(nbAllMatches > 0); - - if(!param._visualDebug.empty() && !imagePath.empty()) - { - namespace bfs = boost::filesystem; - const sfmData::View *mview = _sfm_data.getViews().at(matchedViewId).get(); - const std::string queryimage = bfs::path(imagePath).stem().string(); - const std::string matchedImage = bfs::path(mview->getImage().getImagePath()).stem().string(); - const std::string matchedPath = mview->getImage().getImagePath(); - - matching::saveMatches2SVG(imagePath, - queryImageSize, - queryRegions, - matchedPath, - std::make_pair(mview->getImage().getWidth(), mview->getImage().getHeight()), - _regionsPerView.getRegionsPerDesc(matchedViewId), - featureMatches, - param._visualDebug + "/" + queryimage + "_" + matchedImage + ".svg"); - } - - // C. recover the 2D-3D associations from the matches - // Each matched feature in the current similar image is associated to a 3D point, - // hence we can recover the 2D-3D associations to estimate the pose - // Prepare data for resection - resectionData = sfm::ImageLocalizerMatchData(); - resectionData.pt2D = Mat2X(2, nbAllMatches); - resectionData.pt3D = Mat3X(3, nbAllMatches); - associationIDs.clear(); - associationIDs.reserve(nbAllMatches); - - // Get the 3D points associated to each matched feature - std::size_t index = 0; - for(const auto& featureMatchesIt : featureMatches) - { - const feature::EImageDescriberType descType = featureMatchesIt.first; - const auto& matchedRegions = _reconstructedRegionsMappingPerView.at(matchedViewId).at(descType); + // C. recover the 2D-3D associations from the matches + // Each matched feature in the current similar image is associated to a 3D point, + // hence we can recover the 2D-3D associations to estimate the pose + // Prepare data for resection + resectionData = sfm::ImageLocalizerMatchData(); + resectionData.pt2D = Mat2X(2, nbAllMatches); + resectionData.pt3D = Mat3X(3, nbAllMatches); + associationIDs.clear(); + associationIDs.reserve(nbAllMatches); + + // Get the 3D points associated to each matched feature + std::size_t index = 0; + for (const auto& featureMatchesIt : featureMatches) + { + const feature::EImageDescriberType descType = featureMatchesIt.first; + const auto& matchedRegions = _reconstructedRegionsMappingPerView.at(matchedViewId).at(descType); - for(const matching::IndMatch& featureMatch : featureMatchesIt.second) - { - // the ID of the 3D point - const IndexT trackId3D = matchedRegions._associated3dPoint[featureMatch._j]; + for (const matching::IndMatch& featureMatch : featureMatchesIt.second) + { + // the ID of the 3D point + const IndexT trackId3D = matchedRegions._associated3dPoint[featureMatch._j]; - // prepare data for resectioning - resectionData.pt3D.col(index) = _sfm_data.getLandmarks().at(trackId3D).X; + // prepare data for resectioning + resectionData.pt3D.col(index) = _sfm_data.getLandmarks().at(trackId3D).X; - const Vec2 feat = queryRegions.at(descType)->GetRegionPosition(featureMatch._i); - resectionData.pt2D.col(index) = feat; + const Vec2 feat = queryRegions.at(descType)->GetRegionPosition(featureMatch._i); + resectionData.pt2D.col(index) = feat; - associationIDs.emplace_back(trackId3D, descType, featureMatch._i); + associationIDs.emplace_back(trackId3D, descType, featureMatch._i); - ++index; - } + ++index; + } + } + assert(index == nbAllMatches); + + // estimate the pose + // Do the resectioning: compute the camera pose. + resectionData.error_max = param._errorMax; + ALICEVISION_LOG_DEBUG("[poseEstimation]\tEstimating camera pose..."); + bool bResection = sfm::SfMLocalizer::Localize(queryImageSize, + // pass the input intrinsic if they are valid, null otherwise + (useInputIntrinsics) ? &queryIntrinsics : nullptr, + randomNumberGenerator, + resectionData, + pose, + param._resectionEstimator); + + if (!bResection) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection failed"); + // try next one + continue; + } + ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection SUCCEDED"); + + ALICEVISION_LOG_DEBUG("R est\n" << pose.rotation()); + ALICEVISION_LOG_DEBUG("t est\n" << pose.translation()); + + // if we didn't use the provided intrinsics, estimate K from the projection + // matrix estimated by the localizer and initialize the queryIntrinsics with + // it and the image size. This will provide a first guess for the refine function + if (!useInputIntrinsics) + { + // Decompose P matrix + Mat3 K_, R_; + Vec3 t_; + // Decompose the projection matrix to get K, R and t using + // RQ decomposition + KRt_from_P(resectionData.projection_matrix, K_, R_, t_); + ALICEVISION_LOG_DEBUG("K estimated\n" << K_); + queryIntrinsics.setK(K_); + queryIntrinsics.setWidth(queryImageSize.first); + queryIntrinsics.setHeight(queryImageSize.second); + } + + // D. refine the estimated pose + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefining estimated pose"); + bool refineStatus = sfm::SfMLocalizer::RefinePose( + &queryIntrinsics, pose, resectionData, true /*b_refine_pose*/, param._refineIntrinsics /*b_refine_intrinsic*/); + if (!refineStatus) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefine pose failed."); + // try next one + continue; + } + + { + // just temporary code to evaluate the estimated pose @todo remove it + const geometry::Pose3 referencePose = _sfm_data.getPose(*_sfm_data.getViews().at(matchedViewId)).getTransform(); + ALICEVISION_LOG_DEBUG("R refined\n" << pose.rotation()); + ALICEVISION_LOG_DEBUG("t refined\n" << pose.translation()); + ALICEVISION_LOG_DEBUG("K refined\n" << queryIntrinsics.K()); + ALICEVISION_LOG_DEBUG("R_gt\n" << referencePose.rotation()); + ALICEVISION_LOG_DEBUG("t_gt\n" << referencePose.translation()); + ALICEVISION_LOG_DEBUG("angular difference: " << radianToDegree(getRotationMagnitude(pose.rotation() * referencePose.rotation().inverse())) + << "deg"); + ALICEVISION_LOG_DEBUG("center difference: " << (pose.center() - referencePose.center()).norm()); + ALICEVISION_LOG_DEBUG("err = [err; " << radianToDegree(getRotationMagnitude(pose.rotation() * referencePose.rotation().inverse())) << ", " + << (pose.center() - referencePose.center()).norm() << "];"); + } + localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, refineStatus); + break; } - assert(index == nbAllMatches); + //@todo deal with unsuccesful case... + return localizationResult.isValid(); +} + +bool VoctreeLocalizer::localizeAllResults(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& queryImageSize, + const Parameters& param, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath) +{ + sfm::ImageLocalizerMatchData resectionData; + // a map containing for each pair the number of times that + // the association has been seen + OccurenceMap occurences; + + // get all the association from the database images + std::vector matchedImages; + getAllAssociations(queryRegions, + queryImageSize, + param, + randomNumberGenerator, + useInputIntrinsics, + queryIntrinsics, + occurences, + resectionData.pt2D, + resectionData.pt3D, + resectionData.vec_descType, + matchedImages, + imagePath); + + const std::size_t numCollectedPts = occurences.size(); + std::vector associationIDs; + associationIDs.reserve(numCollectedPts); + + for (const auto& ass : occurences) + { + // recopy the associations IDs in the vector + associationIDs.push_back(ass.first); + } + + assert(associationIDs.size() == numCollectedPts); + assert(resectionData.pt2D.cols() == numCollectedPts); + assert(resectionData.pt3D.cols() == numCollectedPts); + + geometry::Pose3 pose; // estimate the pose // Do the resectioning: compute the camera pose. resectionData.error_max = param._errorMax; ALICEVISION_LOG_DEBUG("[poseEstimation]\tEstimating camera pose..."); - bool bResection = sfm::SfMLocalizer::Localize(queryImageSize, - // pass the input intrinsic if they are valid, null otherwise - (useInputIntrinsics) ? &queryIntrinsics : nullptr, - randomNumberGenerator, - resectionData, - pose, - param._resectionEstimator); - - if(!bResection) + const bool bResection = sfm::SfMLocalizer::Localize(queryImageSize, + // pass the input intrinsic if they are valid, null otherwise + (useInputIntrinsics) ? &queryIntrinsics : nullptr, + randomNumberGenerator, + resectionData, + pose, + param._resectionEstimator); + + if (!bResection) { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection failed"); - // try next one - continue; + ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection failed"); + if (!param._visualDebug.empty() && !imagePath.empty()) + { + namespace bfs = boost::filesystem; + matching::saveFeatures2SVG( + imagePath, queryImageSize, resectionData.pt2D, param._visualDebug + "/" + bfs::path(imagePath).stem().string() + ".associations.svg"); + } + localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, bResection); + return localizationResult.isValid(); } ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection SUCCEDED"); - + ALICEVISION_LOG_DEBUG("R est\n" << pose.rotation()); ALICEVISION_LOG_DEBUG("t est\n" << pose.translation()); // if we didn't use the provided intrinsics, estimate K from the projection // matrix estimated by the localizer and initialize the queryIntrinsics with // it and the image size. This will provide a first guess for the refine function - if(!useInputIntrinsics) + if (!useInputIntrinsics) { - // Decompose P matrix - Mat3 K_, R_; - Vec3 t_; - // Decompose the projection matrix to get K, R and t using - // RQ decomposition - KRt_from_P(resectionData.projection_matrix, K_, R_, t_); - ALICEVISION_LOG_DEBUG("K estimated\n" << K_); - queryIntrinsics.setK(K_); - queryIntrinsics.setWidth(queryImageSize.first); - queryIntrinsics.setHeight(queryImageSize.second); + // Decompose P matrix + Mat3 K_, R_; + Vec3 t_; + // Decompose the projection matrix to get K, R and t using + // RQ decomposition + KRt_from_P(resectionData.projection_matrix, K_, R_, t_); + queryIntrinsics.setK(K_); + ALICEVISION_LOG_DEBUG("K estimated\n" << K_); + queryIntrinsics.setWidth(queryImageSize.first); + queryIntrinsics.setHeight(queryImageSize.second); } - // D. refine the estimated pose + // E. refine the estimated pose ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefining estimated pose"); - bool refineStatus = sfm::SfMLocalizer::RefinePose(&queryIntrinsics, - pose, - resectionData, - true /*b_refine_pose*/, - param._refineIntrinsics /*b_refine_intrinsic*/); - if(!refineStatus) + bool refineStatus = + sfm::SfMLocalizer::RefinePose(&queryIntrinsics, pose, resectionData, true /*b_refine_pose*/, param._refineIntrinsics /*b_refine_intrinsic*/); + if (!refineStatus) + ALICEVISION_LOG_DEBUG("Refine pose failed."); + + if (!param._visualDebug.empty() && !imagePath.empty()) { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefine pose failed."); - // try next one - continue; + namespace bfs = boost::filesystem; + matching::saveFeatures2SVG(imagePath, + queryImageSize, + resectionData.pt2D, + param._visualDebug + "/" + bfs::path(imagePath).stem().string() + ".associations.svg", + &resectionData.vec_inliers); } - + + localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, refineStatus); + { - // just temporary code to evaluate the estimated pose @todo remove it - const geometry::Pose3 referencePose = _sfm_data.getPose(*_sfm_data.getViews().at(matchedViewId)).getTransform(); - ALICEVISION_LOG_DEBUG("R refined\n" << pose.rotation()); - ALICEVISION_LOG_DEBUG("t refined\n" << pose.translation()); - ALICEVISION_LOG_DEBUG("K refined\n" << queryIntrinsics.K()); - ALICEVISION_LOG_DEBUG("R_gt\n" << referencePose.rotation()); - ALICEVISION_LOG_DEBUG("t_gt\n" << referencePose.translation()); - ALICEVISION_LOG_DEBUG("angular difference: " << radianToDegree(getRotationMagnitude(pose.rotation()*referencePose.rotation().inverse())) << "deg"); - ALICEVISION_LOG_DEBUG("center difference: " << (pose.center()-referencePose.center()).norm()); - ALICEVISION_LOG_DEBUG("err = [err; " << radianToDegree(getRotationMagnitude(pose.rotation()*referencePose.rotation().inverse())) << ", "<< (pose.center()-referencePose.center()).norm() << "];"); + // just debugging this block can be safely removed or commented out + ALICEVISION_LOG_DEBUG("R refined\n" << pose.rotation()); + ALICEVISION_LOG_DEBUG("t refined\n" << pose.translation()); + ALICEVISION_LOG_DEBUG("K refined\n" << queryIntrinsics.K()); + + const Mat2X residuals = localizationResult.computeInliersResiduals(); + + const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + ALICEVISION_LOG_DEBUG("RMSE = " << std::sqrt(sqrErrors.mean()) << " min = " << std::sqrt(sqrErrors.minCoeff()) + << " max = " << std::sqrt(sqrErrors.maxCoeff())); } - localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, refineStatus); - break; - } - //@todo deal with unsuccesful case... - return localizationResult.isValid(); -} - -bool VoctreeLocalizer::localizeAllResults(const feature::MapRegionsPerDesc &queryRegions, - const std::pair & queryImageSize, - const Parameters ¶m, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult &localizationResult, - const std::string& imagePath) -{ - - sfm::ImageLocalizerMatchData resectionData; - // a map containing for each pair the number of times that - // the association has been seen - OccurenceMap occurences; - - // get all the association from the database images - std::vector matchedImages; - getAllAssociations(queryRegions, - queryImageSize, - param, - randomNumberGenerator, - useInputIntrinsics, - queryIntrinsics, - occurences, - resectionData.pt2D, - resectionData.pt3D, - resectionData.vec_descType, - matchedImages, - imagePath); - - const std::size_t numCollectedPts = occurences.size(); - std::vector associationIDs; - associationIDs.reserve(numCollectedPts); - - for(const auto &ass : occurences) - { - // recopy the associations IDs in the vector - associationIDs.push_back(ass.first); - } - - assert(associationIDs.size() == numCollectedPts); - assert(resectionData.pt2D.cols() == numCollectedPts); - assert(resectionData.pt3D.cols() == numCollectedPts); - - geometry::Pose3 pose; - - // estimate the pose - // Do the resectioning: compute the camera pose. - resectionData.error_max = param._errorMax; - ALICEVISION_LOG_DEBUG("[poseEstimation]\tEstimating camera pose..."); - const bool bResection = sfm::SfMLocalizer::Localize(queryImageSize, - // pass the input intrinsic if they are valid, null otherwise - (useInputIntrinsics) ? &queryIntrinsics : nullptr, - randomNumberGenerator, - resectionData, - pose, - param._resectionEstimator); - if(!bResection) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection failed"); - if(!param._visualDebug.empty() && !imagePath.empty()) + if (param._nbFrameBufferMatching > 0) { - namespace bfs = boost::filesystem; - matching::saveFeatures2SVG(imagePath, - queryImageSize, - resectionData.pt2D, - param._visualDebug + "/" + bfs::path(imagePath).stem().string() + ".associations.svg"); + // add everything to the buffer + _frameBuffer.emplace_back(localizationResult, queryRegions); } - localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, bResection); + return localizationResult.isValid(); - } - ALICEVISION_LOG_DEBUG("[poseEstimation]\tResection SUCCEDED"); - - ALICEVISION_LOG_DEBUG("R est\n" << pose.rotation()); - ALICEVISION_LOG_DEBUG("t est\n" << pose.translation()); - - // if we didn't use the provided intrinsics, estimate K from the projection - // matrix estimated by the localizer and initialize the queryIntrinsics with - // it and the image size. This will provide a first guess for the refine function - if(!useInputIntrinsics) - { - // Decompose P matrix - Mat3 K_, R_; - Vec3 t_; - // Decompose the projection matrix to get K, R and t using - // RQ decomposition - KRt_from_P(resectionData.projection_matrix, K_, R_, t_); - queryIntrinsics.setK(K_); - ALICEVISION_LOG_DEBUG("K estimated\n" << K_); - queryIntrinsics.setWidth(queryImageSize.first); - queryIntrinsics.setHeight(queryImageSize.second); - } - - // E. refine the estimated pose - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefining estimated pose"); - bool refineStatus = sfm::SfMLocalizer::RefinePose(&queryIntrinsics, - pose, - resectionData, - true /*b_refine_pose*/, - param._refineIntrinsics /*b_refine_intrinsic*/); - if(!refineStatus) - ALICEVISION_LOG_DEBUG("Refine pose failed."); - - if(!param._visualDebug.empty() && !imagePath.empty()) - { - namespace bfs = boost::filesystem; - matching::saveFeatures2SVG(imagePath, - queryImageSize, - resectionData.pt2D, - param._visualDebug + "/" + bfs::path(imagePath).stem().string() + ".associations.svg", - &resectionData.vec_inliers); - } - - localizationResult = LocalizationResult(resectionData, associationIDs, pose, queryIntrinsics, matchedImages, refineStatus); - - { - // just debugging this block can be safely removed or commented out - ALICEVISION_LOG_DEBUG("R refined\n" << pose.rotation()); - ALICEVISION_LOG_DEBUG("t refined\n" << pose.translation()); - ALICEVISION_LOG_DEBUG("K refined\n" << queryIntrinsics.K()); - - const Mat2X residuals = localizationResult.computeInliersResiduals(); - - const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - ALICEVISION_LOG_DEBUG("RMSE = " << std::sqrt(sqrErrors.mean()) - << " min = " << std::sqrt(sqrErrors.minCoeff()) - << " max = " << std::sqrt(sqrErrors.maxCoeff())); - } - - if(param._nbFrameBufferMatching > 0) - { - // add everything to the buffer - _frameBuffer.emplace_back(localizationResult, queryRegions); - } - - return localizationResult.isValid(); } -void VoctreeLocalizer::getAllAssociations(const feature::MapRegionsPerDesc &queryRegions, - const std::pair &imageSize, - const Parameters ¶m, - std::mt19937 & randomNumberGenerator, +void VoctreeLocalizer::getAllAssociations(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const Parameters& param, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - const camera::Pinhole &queryIntrinsics, - OccurenceMap &out_occurences, - Mat &out_pt2D, - Mat &out_pt3D, + const camera::Pinhole& queryIntrinsics, + OccurenceMap& out_occurences, + Mat& out_pt2D, + Mat& out_pt3D, std::vector& out_descTypes, std::vector& out_matchedImages, const std::string& imagePath) const { - assert(out_descTypes.empty()); - - // A. Find the (visually) similar images in the database - // pass the descriptors through the vocabulary tree to get the visual words - // associated to each feature - ALICEVISION_LOG_DEBUG("[database]\tRequest closest images from voctree"); - if(queryRegions.count(_voctreeDescType) == 0) - { - ALICEVISION_LOG_WARNING("[database]\t No feature type " << feature::EImageDescriberType_enumToString(_voctreeDescType) << " in query region."); - return; - } - voctree::SparseHistogram requestImageWords = _voctree->quantizeToSparse(queryRegions.at(_voctreeDescType)->blindDescriptors()); - - // Request closest images from voctree - _database.find(requestImageWords, (param._numResults==0) ? (_database.size()) : (param._numResults) , out_matchedImages); - -// // Debugging log -// // for each similar image found print score and number of features -// for(const voctree::DocMatch& currMatch : matchedImages ) -// { -// // get the view handle -// const std::shared_ptr matchedView = _sfm_data.getViews()[currMatch.id]; -// ALICEVISION_LOG_DEBUG( "[database]\t\t match " << matchedView->getImage().getImagePath() -// << " [docid: "<< currMatch.id << "]" -// << " with score " << currMatch.score -// << " and it has " << _regions_per_view[currMatch.id]._regions.RegionCount() -// << " features with 3D points"); -// } - - ALICEVISION_LOG_DEBUG("[matching]\tBuilding the matcher"); - matching::RegionsDatabaseMatcherPerDesc matchers(randomNumberGenerator, _matcherType, queryRegions); - - std::map< std::pair, std::size_t > repeated; - - // B. for each found similar image, try to find the correspondences between the - // query image adn the similar image - // stop when param._maxResults successful matches have been found - std::size_t goodMatches = 0; - for(const voctree::DocMatch& matchedImage : out_matchedImages) - { - // minimum number of points that allows a reliable 3D reconstruction - const size_t minNum3DPoints = 5; - - const auto matchedViewId = matchedImage.id; - // the handler to the current view - const std::shared_ptr matchedView = _sfm_data.getViews().at(matchedViewId); - // its associated reconstructed regions - const feature::MapRegionsPerDesc& matchedRegions = _regionsPerView.getRegionsPerDesc(matchedViewId); - - // safeguard: we should match the query image with an image that has at least - // some 3D points visible --> if this is not true it is likely that it is an - // image of the dataset that was not reconstructed - if(matchedRegions.getNbAllRegions() < minNum3DPoints) - { - ALICEVISION_LOG_DEBUG("[matching]\tSkipping matching with " << matchedView->getImage().getImagePath() << " as it has too few visible 3D points"); - continue; - } - ALICEVISION_LOG_TRACE("[matching]\tTrying to match the query image with " << matchedView->getImage().getImagePath()); - ALICEVISION_LOG_TRACE("[matching]\tIt has " << matchedRegions.getNbAllRegions() << " available features to match"); - - // its associated intrinsics - // this is just ugly! - const camera::IntrinsicBase *matchedIntrinsicsBase = _sfm_data.getIntrinsics().at(matchedView->getIntrinsicId()).get(); - if ( !isPinhole(matchedIntrinsicsBase->getType()) ) + assert(out_descTypes.empty()); + + // A. Find the (visually) similar images in the database + // pass the descriptors through the vocabulary tree to get the visual words + // associated to each feature + ALICEVISION_LOG_DEBUG("[database]\tRequest closest images from voctree"); + if (queryRegions.count(_voctreeDescType) == 0) { - //@fixme maybe better to throw something here - ALICEVISION_CERR("Only Pinhole cameras are supported!"); - return; + ALICEVISION_LOG_WARNING("[database]\t No feature type " << feature::EImageDescriberType_enumToString(_voctreeDescType) + << " in query region."); + return; } - const camera::Pinhole *matchedIntrinsics = (const camera::Pinhole*)(matchedIntrinsicsBase); - - matching::MatchesPerDescType featureMatches; - const bool matchWorked = robustMatching(matchers, - // pass the input intrinsic if they are valid, null otherwise - (useInputIntrinsics) ? &queryIntrinsics : nullptr, - matchedRegions, - matchedIntrinsics, - param._fDistRatio, - param._matchingError, - param._useRobustMatching, - param._useGuidedMatching, - imageSize, - std::make_pair(matchedView->getImage().getWidth(), matchedView->getImage().getHeight()), - randomNumberGenerator, - featureMatches, - param._matchingEstimator); - if (!matchWorked) + voctree::SparseHistogram requestImageWords = _voctree->quantizeToSparse(queryRegions.at(_voctreeDescType)->blindDescriptors()); + + // Request closest images from voctree + _database.find(requestImageWords, (param._numResults == 0) ? (_database.size()) : (param._numResults), out_matchedImages); + + // // Debugging log + // // for each similar image found print score and number of features + // for(const voctree::DocMatch& currMatch : matchedImages ) + // { + // // get the view handle + // const std::shared_ptr matchedView = _sfm_data.getViews()[currMatch.id]; + // ALICEVISION_LOG_DEBUG( "[database]\t\t match " << matchedView->getImage().getImagePath() + // << " [docid: "<< currMatch.id << "]" + // << " with score " << currMatch.score + // << " and it has " << _regions_per_view[currMatch.id]._regions.RegionCount() + // << " features with 3D points"); + // } + + ALICEVISION_LOG_DEBUG("[matching]\tBuilding the matcher"); + matching::RegionsDatabaseMatcherPerDesc matchers(randomNumberGenerator, _matcherType, queryRegions); + + std::map, std::size_t> repeated; + + // B. for each found similar image, try to find the correspondences between the + // query image adn the similar image + // stop when param._maxResults successful matches have been found + std::size_t goodMatches = 0; + for (const voctree::DocMatch& matchedImage : out_matchedImages) { -// ALICEVISION_LOG_DEBUG("[matching]\tMatching with " << matchedView->getImage().getImagePath() << " failed! Skipping image"); - continue; - } + // minimum number of points that allows a reliable 3D reconstruction + const size_t minNum3DPoints = 5; + + const auto matchedViewId = matchedImage.id; + // the handler to the current view + const std::shared_ptr matchedView = _sfm_data.getViews().at(matchedViewId); + // its associated reconstructed regions + const feature::MapRegionsPerDesc& matchedRegions = _regionsPerView.getRegionsPerDesc(matchedViewId); + + // safeguard: we should match the query image with an image that has at least + // some 3D points visible --> if this is not true it is likely that it is an + // image of the dataset that was not reconstructed + if (matchedRegions.getNbAllRegions() < minNum3DPoints) + { + ALICEVISION_LOG_DEBUG("[matching]\tSkipping matching with " << matchedView->getImage().getImagePath() + << " as it has too few visible 3D points"); + continue; + } + ALICEVISION_LOG_TRACE("[matching]\tTrying to match the query image with " << matchedView->getImage().getImagePath()); + ALICEVISION_LOG_TRACE("[matching]\tIt has " << matchedRegions.getNbAllRegions() << " available features to match"); - ALICEVISION_LOG_DEBUG("[matching]\tFound " << featureMatches.getNbAllMatches() << " geometrically validated matches"); - assert(featureMatches.getNbAllMatches() > 0); + // its associated intrinsics + // this is just ugly! + const camera::IntrinsicBase* matchedIntrinsicsBase = _sfm_data.getIntrinsics().at(matchedView->getIntrinsicId()).get(); + if (!isPinhole(matchedIntrinsicsBase->getType())) + { + //@fixme maybe better to throw something here + ALICEVISION_CERR("Only Pinhole cameras are supported!"); + return; + } + const camera::Pinhole* matchedIntrinsics = (const camera::Pinhole*)(matchedIntrinsicsBase); + + matching::MatchesPerDescType featureMatches; + const bool matchWorked = robustMatching(matchers, + // pass the input intrinsic if they are valid, null otherwise + (useInputIntrinsics) ? &queryIntrinsics : nullptr, + matchedRegions, + matchedIntrinsics, + param._fDistRatio, + param._matchingError, + param._useRobustMatching, + param._useGuidedMatching, + imageSize, + std::make_pair(matchedView->getImage().getWidth(), matchedView->getImage().getHeight()), + randomNumberGenerator, + featureMatches, + param._matchingEstimator); + if (!matchWorked) + { + // ALICEVISION_LOG_DEBUG("[matching]\tMatching with " << matchedView->getImage().getImagePath() << " failed! Skipping image"); + continue; + } - // if debug is enable save the matches between the query image and the current matching image - // It saves the feature matches in a folder with the same name as the query - // image, if it does not exist it will create it. The final svg file will have - // a name like this: queryImage_matchedImage.svg placed in the following directory: - // param._visualDebug/queryImage/ - if(!param._visualDebug.empty() && !imagePath.empty()) - { - namespace bfs = boost::filesystem; - const sfmData::View *mview = _sfm_data.getViews().at(matchedViewId).get(); - // the current query image without extension - const auto queryImage = bfs::path(imagePath).stem(); - // the matching image without extension - const auto matchedImage = bfs::path(mview->getImage().getImagePath()).stem(); - // the full path of the matching image - const auto matchedPath = mview->getImage().getImagePath(); - - // the directory where to save the feature matches - const auto baseDir = bfs::path(param._visualDebug) / queryImage; - if((!bfs::exists(baseDir))) - { - ALICEVISION_LOG_DEBUG("created " << baseDir.string()); - bfs::create_directories(baseDir); - } - - // damn you, boost, what does it take to make the operator "+"? - // the final filename for the output svg file as a composition of the query - // image and the matched image - auto outputName = baseDir / queryImage; - outputName += "_"; - outputName += matchedImage; - outputName += ".svg"; - - matching::saveMatches2SVG(imagePath, - imageSize, - queryRegions, - matchedPath, - std::make_pair(mview->getImage().getWidth(), mview->getImage().getHeight()), - _regionsPerView.getRegionsPerDesc(matchedViewId), - featureMatches, - outputName.string()); - } + ALICEVISION_LOG_DEBUG("[matching]\tFound " << featureMatches.getNbAllMatches() << " geometrically validated matches"); + assert(featureMatches.getNbAllMatches() > 0); - const auto& matchedRegionsMapping = _reconstructedRegionsMappingPerView.at(matchedViewId); + // if debug is enable save the matches between the query image and the current matching image + // It saves the feature matches in a folder with the same name as the query + // image, if it does not exist it will create it. The final svg file will have + // a name like this: queryImage_matchedImage.svg placed in the following directory: + // param._visualDebug/queryImage/ + if (!param._visualDebug.empty() && !imagePath.empty()) + { + namespace bfs = boost::filesystem; + const sfmData::View* mview = _sfm_data.getViews().at(matchedViewId).get(); + // the current query image without extension + const auto queryImage = bfs::path(imagePath).stem(); + // the matching image without extension + const auto matchedImage = bfs::path(mview->getImage().getImagePath()).stem(); + // the full path of the matching image + const auto matchedPath = mview->getImage().getImagePath(); + + // the directory where to save the feature matches + const auto baseDir = bfs::path(param._visualDebug) / queryImage; + if ((!bfs::exists(baseDir))) + { + ALICEVISION_LOG_DEBUG("created " << baseDir.string()); + bfs::create_directories(baseDir); + } + + // damn you, boost, what does it take to make the operator "+"? + // the final filename for the output svg file as a composition of the query + // image and the matched image + auto outputName = baseDir / queryImage; + outputName += "_"; + outputName += matchedImage; + outputName += ".svg"; + + matching::saveMatches2SVG(imagePath, + imageSize, + queryRegions, + matchedPath, + std::make_pair(mview->getImage().getWidth(), mview->getImage().getHeight()), + _regionsPerView.getRegionsPerDesc(matchedViewId), + featureMatches, + outputName.string()); + } - // C. recover the 2D-3D associations from the matches - // Each matched feature in the current similar image is associated to a 3D point - for(const auto& featureMatchesIt : featureMatches) - { - feature::EImageDescriberType descType = featureMatchesIt.first; - const auto& matchedRegionsMappingType = matchedRegionsMapping.at(descType); - for(const matching::IndMatch& featureMatch : featureMatchesIt.second) - { - // the ID of the 3D point - const IndexT pt3D_id = matchedRegionsMappingType._associated3dPoint[featureMatch._j]; - const IndexT pt2D_id = featureMatch._i; - - const OccurenceKey key(pt3D_id, descType, pt2D_id); - if(out_occurences.count(key)) + const auto& matchedRegionsMapping = _reconstructedRegionsMappingPerView.at(matchedViewId); + + // C. recover the 2D-3D associations from the matches + // Each matched feature in the current similar image is associated to a 3D point + for (const auto& featureMatchesIt : featureMatches) { - out_occurences[key]++; + feature::EImageDescriberType descType = featureMatchesIt.first; + const auto& matchedRegionsMappingType = matchedRegionsMapping.at(descType); + for (const matching::IndMatch& featureMatch : featureMatchesIt.second) + { + // the ID of the 3D point + const IndexT pt3D_id = matchedRegionsMappingType._associated3dPoint[featureMatch._j]; + const IndexT pt2D_id = featureMatch._i; + + const OccurenceKey key(pt3D_id, descType, pt2D_id); + if (out_occurences.count(key)) + { + out_occurences[key]++; + } + else + { + out_occurences[key] = 1; + } + } } - else + ++goodMatches; + if ((param._maxResults != 0) && (goodMatches == param._maxResults)) { - out_occurences[key] = 1; + // let's say we have enough features + ALICEVISION_LOG_DEBUG("[matching]\tgot enough point from " << param._maxResults << " images"); + break; } - } } - ++goodMatches; - if((param._maxResults !=0) && (goodMatches == param._maxResults)) - { - // let's say we have enough features - ALICEVISION_LOG_DEBUG("[matching]\tgot enough point from " << param._maxResults << " images"); - break; + + if (param._nbFrameBufferMatching > 0) + { + ALICEVISION_LOG_DEBUG("[matching]\tUsing frameBuffer matching: matching with the past " << param._nbFrameBufferMatching << " frames"); + getAssociationsFromBuffer(matchers, imageSize, param, useInputIntrinsics, queryIntrinsics, out_occurences, randomNumberGenerator); } - } - - if(param._nbFrameBufferMatching > 0) - { - ALICEVISION_LOG_DEBUG("[matching]\tUsing frameBuffer matching: matching with the past " - << param._nbFrameBufferMatching << " frames" ); - getAssociationsFromBuffer(matchers, imageSize, param, useInputIntrinsics, queryIntrinsics, out_occurences, randomNumberGenerator); - } - - const std::size_t numCollectedPts = out_occurences.size(); - - { - // just debugging statistics, this block can be safely removed - std::size_t numOccTreated = 0; - for(std::size_t value = 1; value < numCollectedPts; ++value) + + const std::size_t numCollectedPts = out_occurences.size(); + { - std::size_t counter = 0; - for(const auto &idx : out_occurences) - { - if(idx.second == value) + // just debugging statistics, this block can be safely removed + std::size_t numOccTreated = 0; + for (std::size_t value = 1; value < numCollectedPts; ++value) { - ++counter; + std::size_t counter = 0; + for (const auto& idx : out_occurences) + { + if (idx.second == value) + { + ++counter; + } + } + ALICEVISION_LOG_DEBUG("[matching]\tThere are " << counter << " associations occurred " << value << " times (" + << 100.0 * counter / (double)numCollectedPts << "%)"); + numOccTreated += counter; + if (numOccTreated >= numCollectedPts) + break; } - } - ALICEVISION_LOG_DEBUG("[matching]\tThere are " << counter - << " associations occurred " << value << " times (" - << 100.0*counter/(double)numCollectedPts << "%)"); - numOccTreated += counter; - if(numOccTreated >= numCollectedPts) - break; } - } - - out_pt2D = Mat2X(2, numCollectedPts); - out_pt3D = Mat3X(3, numCollectedPts); - - - out_descTypes.resize(out_occurences.size()); - - std::size_t index = 0; - for(const auto &idx : out_occurences) - { - // recopy all the points in the matching structure - const IndexT pt2D_id = idx.first.featId; - const sfmData::Landmark& landmark = _sfm_data.getLandmarks().at(idx.first.landmarkId); - -// ALICEVISION_LOG_DEBUG("[matching]\tAssociation [" << idx.first.landmarkId << "," << pt2D_id << "] occurred " << idx.second << " times"); - - out_pt2D.col(index) = queryRegions.at(idx.first.descType)->GetRegionPosition(pt2D_id); - out_pt3D.col(index) = landmark.X; - out_descTypes.at(index) = landmark.descType; - ++index; - } - + + out_pt2D = Mat2X(2, numCollectedPts); + out_pt3D = Mat3X(3, numCollectedPts); + + out_descTypes.resize(out_occurences.size()); + + std::size_t index = 0; + for (const auto& idx : out_occurences) + { + // recopy all the points in the matching structure + const IndexT pt2D_id = idx.first.featId; + const sfmData::Landmark& landmark = _sfm_data.getLandmarks().at(idx.first.landmarkId); + + // ALICEVISION_LOG_DEBUG("[matching]\tAssociation [" << idx.first.landmarkId << "," << pt2D_id << "] occurred " << idx.second << " times"); + + out_pt2D.col(index) = queryRegions.at(idx.first.descType)->GetRegionPosition(pt2D_id); + out_pt3D.col(index) = landmark.X; + out_descTypes.at(index) = landmark.descType; + ++index; + } } -void VoctreeLocalizer::getAssociationsFromBuffer(matching::RegionsDatabaseMatcherPerDesc & matchers, - const std::pair & queryImageSize, - const Parameters ¶m, +void VoctreeLocalizer::getAssociationsFromBuffer(matching::RegionsDatabaseMatcherPerDesc& matchers, + const std::pair& queryImageSize, + const Parameters& param, bool useInputIntrinsics, - const camera::Pinhole &queryIntrinsics, - OccurenceMap & out_occurences, - std::mt19937 & randomNumberGenerator, + const camera::Pinhole& queryIntrinsics, + OccurenceMap& out_occurences, + std::mt19937& randomNumberGenerator, const std::string& imagePath) const { - std::size_t frameCounter = 0; - // for all the past frames - for(const auto& frame : _frameBuffer) - { - // gather the data - const auto &frameReconstructedRegions = frame._regionsWith3D; - const auto &frameRegions = frame._regions; - const auto &frameIntrinsics = frame._locResult.getIntrinsics(); - const auto frameImageSize = std::make_pair(frameIntrinsics.w(), frameIntrinsics.h()); - matching::MatchesPerDescType featureMatches; - - // match the query image with the current frame - bool matchWorked = robustMatching(matchers, - // pass the input intrinsic if they are valid, null otherwise - (useInputIntrinsics) ? &queryIntrinsics : nullptr, - frameRegions, - &frameIntrinsics, - param._fDistRatio, - param._matchingError, - param._useRobustMatching, - param._useGuidedMatching, - queryImageSize, - frameImageSize, - randomNumberGenerator, - featureMatches, - param._matchingEstimator); - if (!matchWorked) - { - continue; - } - - ALICEVISION_LOG_DEBUG("[matching]\tFound " << featureMatches.getNbAllMatches() << " matches from frame " << frameCounter); - assert(featureMatches.getNbAllMatches() > 0); - - // recover the 2D-3D associations from the matches - // Each matched feature in the current similar image is associated to a 3D point - - std::size_t newOccurences = 0; - for(const auto& featureMatchesIt : featureMatches) + std::size_t frameCounter = 0; + // for all the past frames + for (const auto& frame : _frameBuffer) { - feature::EImageDescriberType descType = featureMatchesIt.first; - for(const matching::IndMatch& featureMatch : featureMatchesIt.second) - { - // the ID of the 3D point - const IndexT pt3D_id = frameReconstructedRegions.at(descType)._associated3dPoint.at(featureMatch._j); - const IndexT pt2D_id = featureMatch._i; - const OccurenceKey key(pt3D_id, descType, pt2D_id); - - if(out_occurences.count(key)) + // gather the data + const auto& frameReconstructedRegions = frame._regionsWith3D; + const auto& frameRegions = frame._regions; + const auto& frameIntrinsics = frame._locResult.getIntrinsics(); + const auto frameImageSize = std::make_pair(frameIntrinsics.w(), frameIntrinsics.h()); + matching::MatchesPerDescType featureMatches; + + // match the query image with the current frame + bool matchWorked = robustMatching(matchers, + // pass the input intrinsic if they are valid, null otherwise + (useInputIntrinsics) ? &queryIntrinsics : nullptr, + frameRegions, + &frameIntrinsics, + param._fDistRatio, + param._matchingError, + param._useRobustMatching, + param._useGuidedMatching, + queryImageSize, + frameImageSize, + randomNumberGenerator, + featureMatches, + param._matchingEstimator); + if (!matchWorked) { - out_occurences[key]++; + continue; } - else + + ALICEVISION_LOG_DEBUG("[matching]\tFound " << featureMatches.getNbAllMatches() << " matches from frame " << frameCounter); + assert(featureMatches.getNbAllMatches() > 0); + + // recover the 2D-3D associations from the matches + // Each matched feature in the current similar image is associated to a 3D point + + std::size_t newOccurences = 0; + for (const auto& featureMatchesIt : featureMatches) { - ALICEVISION_LOG_DEBUG("[matching]\tnew association found: [" << pt3D_id << "," << pt2D_id << "]"); - out_occurences[key] = 1; - ++newOccurences; + feature::EImageDescriberType descType = featureMatchesIt.first; + for (const matching::IndMatch& featureMatch : featureMatchesIt.second) + { + // the ID of the 3D point + const IndexT pt3D_id = frameReconstructedRegions.at(descType)._associated3dPoint.at(featureMatch._j); + const IndexT pt2D_id = featureMatch._i; + const OccurenceKey key(pt3D_id, descType, pt2D_id); + + if (out_occurences.count(key)) + { + out_occurences[key]++; + } + else + { + ALICEVISION_LOG_DEBUG("[matching]\tnew association found: [" << pt3D_id << "," << pt2D_id << "]"); + out_occurences[key] = 1; + ++newOccurences; + } + } } - } + ALICEVISION_LOG_DEBUG("[matching]\tFound " << newOccurences << " new associations with the frameBufferMatching"); + ++frameCounter; } - ALICEVISION_LOG_DEBUG("[matching]\tFound " << newOccurences << " new associations with the frameBufferMatching"); - ++frameCounter; - } } -bool VoctreeLocalizer::robustMatching(matching::RegionsDatabaseMatcherPerDesc & matchers, - const camera::IntrinsicBase * queryIntrinsicsBase, // the intrinsics of the image we are using as reference - const feature::MapRegionsPerDesc & matchedRegions, - const camera::IntrinsicBase * matchedIntrinsicsBase, - float fDistRatio, - double matchingError, - bool useGeometricFiltering, - bool useGuidedMatching, - const std::pair & imageSizeI, // size of the first image @fixme change the API of the kernel!! - const std::pair & imageSizeJ, // size of the second image - std::mt19937 & randomNumberGenerator, - matching::MatchesPerDescType & out_featureMatches, - robustEstimation::ERobustEstimator estimator) const +bool VoctreeLocalizer::robustMatching( + matching::RegionsDatabaseMatcherPerDesc& matchers, + const camera::IntrinsicBase* queryIntrinsicsBase, // the intrinsics of the image we are using as reference + const feature::MapRegionsPerDesc& matchedRegions, + const camera::IntrinsicBase* matchedIntrinsicsBase, + float fDistRatio, + double matchingError, + bool useGeometricFiltering, + bool useGuidedMatching, + const std::pair& imageSizeI, // size of the first image @fixme change the API of the kernel!! + const std::pair& imageSizeJ, // size of the second image + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_featureMatches, + robustEstimation::ERobustEstimator estimator) const { - // get the intrinsics of the query camera - if ((queryIntrinsicsBase != nullptr) && !isPinhole(queryIntrinsicsBase->getType())) - { - //@fixme maybe better to throw something here - ALICEVISION_CERR("[matching]\tOnly Pinhole cameras are supported!"); - return false; - } - const camera::Pinhole *queryIntrinsics = (const camera::Pinhole*)(queryIntrinsicsBase); - - // get the intrinsics of the matched view - if ((matchedIntrinsicsBase != nullptr) && !isPinhole(matchedIntrinsicsBase->getType()) ) - { - //@fixme maybe better to throw something here - ALICEVISION_CERR("[matching]\tOnly Pinhole cameras are supported!"); - return false; - } - const camera::Pinhole *matchedIntrinsics = (const camera::Pinhole*)(matchedIntrinsicsBase); - - const bool canBeUndistorted = (queryIntrinsicsBase != nullptr) && (matchedIntrinsicsBase != nullptr); - - // A. Putative Features Matching - matching::MatchesPerDescType putativeFeatureMatches; - const bool matchWorked = matchers.Match(fDistRatio, matchedRegions, putativeFeatureMatches); - if (!matchWorked) - { - ALICEVISION_LOG_DEBUG("[matching]\tPutative matching failed."); - return false; - } - assert(!putativeFeatureMatches.empty()); - - if(!useGeometricFiltering) - { - // nothing else to do - out_featureMatches.swap(putativeFeatureMatches); - return true; - } - - // perform the geometric filtering - matchingImageCollection::GeometricFilterMatrix_F_AC geometricFilter(matchingError, 5000, estimator); - - matching::MatchesPerDescType geometricInliersPerType; - EstimationStatus estimationState = geometricFilter.geometricEstimation( - matchers.getDatabaseRegionsPerDesc(), - matchedRegions, - queryIntrinsics, - matchedIntrinsics, - imageSizeI, - imageSizeJ, - putativeFeatureMatches, - randomNumberGenerator, - geometricInliersPerType); - - if(!estimationState.isValid) - { - ALICEVISION_LOG_DEBUG("[matching]\tGeometric validation failed."); - return false; - } - - if(!estimationState.hasStrongSupport) - { - ALICEVISION_LOG_DEBUG("[matching]\tGeometric validation hasn't strong support."); - return false; - } - - if(!useGuidedMatching) - { - out_featureMatches.swap(geometricInliersPerType); + // get the intrinsics of the query camera + if ((queryIntrinsicsBase != nullptr) && !isPinhole(queryIntrinsicsBase->getType())) + { + //@fixme maybe better to throw something here + ALICEVISION_CERR("[matching]\tOnly Pinhole cameras are supported!"); + return false; + } + const camera::Pinhole* queryIntrinsics = (const camera::Pinhole*)(queryIntrinsicsBase); + + // get the intrinsics of the matched view + if ((matchedIntrinsicsBase != nullptr) && !isPinhole(matchedIntrinsicsBase->getType())) + { + //@fixme maybe better to throw something here + ALICEVISION_CERR("[matching]\tOnly Pinhole cameras are supported!"); + return false; + } + const camera::Pinhole* matchedIntrinsics = (const camera::Pinhole*)(matchedIntrinsicsBase); + + const bool canBeUndistorted = (queryIntrinsicsBase != nullptr) && (matchedIntrinsicsBase != nullptr); + + // A. Putative Features Matching + matching::MatchesPerDescType putativeFeatureMatches; + const bool matchWorked = matchers.Match(fDistRatio, matchedRegions, putativeFeatureMatches); + if (!matchWorked) + { + ALICEVISION_LOG_DEBUG("[matching]\tPutative matching failed."); + return false; + } + assert(!putativeFeatureMatches.empty()); + + if (!useGeometricFiltering) + { + // nothing else to do + out_featureMatches.swap(putativeFeatureMatches); + return true; + } + + // perform the geometric filtering + matchingImageCollection::GeometricFilterMatrix_F_AC geometricFilter(matchingError, 5000, estimator); + + matching::MatchesPerDescType geometricInliersPerType; + EstimationStatus estimationState = geometricFilter.geometricEstimation(matchers.getDatabaseRegionsPerDesc(), + matchedRegions, + queryIntrinsics, + matchedIntrinsics, + imageSizeI, + imageSizeJ, + putativeFeatureMatches, + randomNumberGenerator, + geometricInliersPerType); + + if (!estimationState.isValid) + { + ALICEVISION_LOG_DEBUG("[matching]\tGeometric validation failed."); + return false; + } + + if (!estimationState.hasStrongSupport) + { + ALICEVISION_LOG_DEBUG("[matching]\tGeometric validation hasn't strong support."); + return false; + } + + if (!useGuidedMatching) + { + out_featureMatches.swap(geometricInliersPerType); + return true; + } + + // Use the Fundamental Matrix estimated by the robust estimation to + // perform guided matching. + // So we ignore the previous matches and recompute all matches. + out_featureMatches.clear(); + + robustEstimation::Mat3Model model(geometricFilter.m_F); + + matching::guidedMatching( + model, + queryIntrinsicsBase, // camera::IntrinsicBase of the matched image + matchers.getDatabaseRegionsPerDesc(), // feature::Regions + matchedIntrinsicsBase, // camera::IntrinsicBase of the query image + matchedRegions, // feature::Regions + Square(geometricFilter.m_dPrecision_robust), + Square(fDistRatio), + out_featureMatches); // output + return true; - } - - // Use the Fundamental Matrix estimated by the robust estimation to - // perform guided matching. - // So we ignore the previous matches and recompute all matches. - out_featureMatches.clear(); - - robustEstimation::Mat3Model model(geometricFilter.m_F); - - matching::guidedMatching( - model, - queryIntrinsicsBase, // camera::IntrinsicBase of the matched image - matchers.getDatabaseRegionsPerDesc(), // feature::Regions - matchedIntrinsicsBase, // camera::IntrinsicBase of the query image - matchedRegions, // feature::Regions - Square(geometricFilter.m_dPrecision_robust), - Square(fDistRatio), - out_featureMatches); // output - - return true; } -bool VoctreeLocalizer::localizeRig(const std::vector> & vec_imageGrey, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector & vec_locResults) +bool VoctreeLocalizer::localizeRig(const std::vector>& vec_imageGrey, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) { - const size_t numCams = vec_imageGrey.size(); - assert(numCams == vec_queryIntrinsics.size()); - assert(numCams == vec_subPoses.size() + 1); + const size_t numCams = vec_imageGrey.size(); + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); - std::vector vec_queryRegions(numCams); - std::vector > vec_imageSize; + std::vector vec_queryRegions(numCams); + std::vector> vec_imageSize; - //@todo parallelize? - for(size_t i = 0; i < numCams; ++i) - { - // add the image size for this image - vec_imageSize.emplace_back(vec_imageGrey[i].Width(), vec_imageGrey[i].Height()); + //@todo parallelize? + for (size_t i = 0; i < numCams; ++i) + { + // add the image size for this image + vec_imageSize.emplace_back(vec_imageGrey[i].Width(), vec_imageGrey[i].Height()); - image::Image imageGrayUChar; // uchar image copy for uchar image describer + image::Image imageGrayUChar; // uchar image copy for uchar image describer - // extract descriptors and features from each image - for(auto& imageDescriber: _imageDescribers) - { - ALICEVISION_LOG_DEBUG("[features]\tExtract " << feature::EImageDescriberType_enumToString(imageDescriber->getDescriberType()) << " from query image..."); - imageDescriber->describe(vec_imageGrey[i], vec_queryRegions[i][imageDescriber->getDescriberType()]); - - if(imageDescriber->useFloatImage()) - { - imageDescriber->describe(vec_imageGrey[i], vec_queryRegions[i][imageDescriber->getDescriberType()]); - } - else - { - // image descriptor can't use float image - if(imageGrayUChar.Width() == 0) // the first time, convert the float buffer to uchar - imageGrayUChar = (vec_imageGrey.at(i).GetMat() * 255.f).cast(); - imageDescriber->describe(imageGrayUChar, vec_queryRegions[i][imageDescriber->getDescriberType()]); - } - ALICEVISION_LOG_DEBUG("[features]\tExtract done: found " << vec_queryRegions[i][imageDescriber->getDescriberType()]->RegionCount() << " features"); + // extract descriptors and features from each image + for (auto& imageDescriber : _imageDescribers) + { + ALICEVISION_LOG_DEBUG("[features]\tExtract " << feature::EImageDescriberType_enumToString(imageDescriber->getDescriberType()) + << " from query image..."); + imageDescriber->describe(vec_imageGrey[i], vec_queryRegions[i][imageDescriber->getDescriberType()]); + + if (imageDescriber->useFloatImage()) + { + imageDescriber->describe(vec_imageGrey[i], vec_queryRegions[i][imageDescriber->getDescriberType()]); + } + else + { + // image descriptor can't use float image + if (imageGrayUChar.Width() == 0) // the first time, convert the float buffer to uchar + imageGrayUChar = (vec_imageGrey.at(i).GetMat() * 255.f).cast(); + imageDescriber->describe(imageGrayUChar, vec_queryRegions[i][imageDescriber->getDescriberType()]); + } + ALICEVISION_LOG_DEBUG("[features]\tExtract done: found " << vec_queryRegions[i][imageDescriber->getDescriberType()]->RegionCount() + << " features"); + } + ALICEVISION_LOG_DEBUG("[features]\tAll descriptors extracted. Found " << vec_queryRegions[i].getNbAllRegions() << " features"); } - ALICEVISION_LOG_DEBUG("[features]\tAll descriptors extracted. Found " << vec_queryRegions[i].getNbAllRegions() << " features"); - } - assert(vec_imageSize.size() == vec_queryRegions.size()); - - return localizeRig(vec_queryRegions, - vec_imageSize, - parameters, - randomNumberGenerator, - vec_queryIntrinsics, - vec_subPoses, - rigPose, - vec_locResults); -} + assert(vec_imageSize.size() == vec_queryRegions.size()); + return localizeRig( + vec_queryRegions, vec_imageSize, parameters, randomNumberGenerator, vec_queryIntrinsics, vec_subPoses, rigPose, vec_locResults); +} -bool VoctreeLocalizer::localizeRig(const std::vector & vec_queryRegions, - const std::vector > &vec_imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, +bool VoctreeLocalizer::localizeRig(const std::vector& vec_queryRegions, + const std::vector>& vec_imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, std::vector& vec_locResults) { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) - if(!parameters->_useLocalizeRigNaive) - { - ALICEVISION_LOG_DEBUG("Using localizeRig_opengv()"); - return localizeRig_opengv(vec_queryRegions, - vec_imageSize, - parameters, - randomNumberGenerator, - vec_queryIntrinsics, - vec_subPoses, - rigPose, - vec_locResults); - } - else + if (!parameters->_useLocalizeRigNaive) + { + ALICEVISION_LOG_DEBUG("Using localizeRig_opengv()"); + return localizeRig_opengv( + vec_queryRegions, vec_imageSize, parameters, randomNumberGenerator, vec_queryIntrinsics, vec_subPoses, rigPose, vec_locResults); + } + else #endif - { - if(!parameters->_useLocalizeRigNaive) - ALICEVISION_LOG_DEBUG("OpenGV is not available. Fallback to localizeRig_naive()."); - return localizeRig_naive(vec_queryRegions, - vec_imageSize, - parameters, - randomNumberGenerator, - vec_queryIntrinsics, - vec_subPoses, - rigPose, - vec_locResults); - } + { + if (!parameters->_useLocalizeRigNaive) + ALICEVISION_LOG_DEBUG("OpenGV is not available. Fallback to localizeRig_naive()."); + return localizeRig_naive( + vec_queryRegions, vec_imageSize, parameters, randomNumberGenerator, vec_queryIntrinsics, vec_subPoses, rigPose, vec_locResults); + } } - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) -bool VoctreeLocalizer::localizeRig_opengv(const std::vector & vec_queryRegions, - const std::vector > &vec_imageSize, - const LocalizerParameters *parameters, - std::mt19937 &randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, +bool VoctreeLocalizer::localizeRig_opengv(const std::vector& vec_queryRegions, + const std::vector>& vec_imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, std::vector& vec_locResults) { - const std::size_t numCams = vec_queryRegions.size(); - assert(numCams == vec_queryIntrinsics.size()); - assert(numCams == vec_subPoses.size() + 1); - - vec_locResults.clear(); - vec_locResults.reserve(numCams); - - const VoctreeLocalizer::Parameters *param = static_cast(parameters); - if(!param) - { - // error! - throw std::invalid_argument("The parameters are not in the right format!!"); - } - - std::vector vec_occurrences(numCams); - std::vector > vec_matchedImages(numCams); - std::vector< std::vector > descTypesPerCamera(numCams); - std::vector vec_pts3D(numCams); - std::vector vec_pts2D(numCams); - - // for each camera retrieve the associations - //@todo parallelize? - size_t numAssociations = 0; - for(std::size_t camID = 0; camID < numCams; ++camID) - { - - // this map is used to collect the 2d-3d associations as we go through the images - // the key is a pair - // the element is the pair 3D point - 2D point - auto &occurrences = vec_occurrences[camID]; - auto &matchedImages = vec_matchedImages[camID]; - auto &descTypes = descTypesPerCamera[camID]; - auto &imageSize = vec_imageSize[camID]; - Mat &pts3D = vec_pts3D[camID]; - Mat &pts2D = vec_pts2D[camID]; - camera::Pinhole &queryIntrinsics = vec_queryIntrinsics[camID]; - const bool useInputIntrinsics = true; - getAllAssociations(vec_queryRegions[camID], - imageSize, - *param, - randomNumberGenerator, - useInputIntrinsics, - queryIntrinsics, - occurrences, - pts2D, - pts3D, - descTypes, - matchedImages); - numAssociations += occurrences.size(); - } - - // @todo Here it could be possible to filter the associations according to their - // occurrences, eg giving priority to those associations that are more frequent - - const std::size_t minNumAssociations = 5; //possible parameter? - if(numAssociations < minNumAssociations) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tonly " << numAssociations << " have been found, not enough to do the resection!"); - for(std::size_t cam = 0; cam < numCams; ++cam) + const std::size_t numCams = vec_queryRegions.size(); + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); + + vec_locResults.clear(); + vec_locResults.reserve(numCams); + + const VoctreeLocalizer::Parameters* param = static_cast(parameters); + if (!param) + { + // error! + throw std::invalid_argument("The parameters are not in the right format!!"); + } + + std::vector vec_occurrences(numCams); + std::vector> vec_matchedImages(numCams); + std::vector> descTypesPerCamera(numCams); + std::vector vec_pts3D(numCams); + std::vector vec_pts2D(numCams); + + // for each camera retrieve the associations + //@todo parallelize? + size_t numAssociations = 0; + for (std::size_t camID = 0; camID < numCams; ++camID) { - // empty result with isValid set to false - vec_locResults.emplace_back(); + // this map is used to collect the 2d-3d associations as we go through the images + // the key is a pair + // the element is the pair 3D point - 2D point + auto& occurrences = vec_occurrences[camID]; + auto& matchedImages = vec_matchedImages[camID]; + auto& descTypes = descTypesPerCamera[camID]; + auto& imageSize = vec_imageSize[camID]; + Mat& pts3D = vec_pts3D[camID]; + Mat& pts2D = vec_pts2D[camID]; + camera::Pinhole& queryIntrinsics = vec_queryIntrinsics[camID]; + const bool useInputIntrinsics = true; + getAllAssociations(vec_queryRegions[camID], + imageSize, + *param, + randomNumberGenerator, + useInputIntrinsics, + queryIntrinsics, + occurrences, + pts2D, + pts3D, + descTypes, + matchedImages); + numAssociations += occurrences.size(); } - return false; - } - - { - for(std::size_t camID = 0; camID < vec_subPoses.size(); ++camID) + + // @todo Here it could be possible to filter the associations according to their + // occurrences, eg giving priority to those associations that are more frequent + + const std::size_t minNumAssociations = 5; // possible parameter? + if (numAssociations < minNumAssociations) { - ALICEVISION_LOG_DEBUG("Rotation: " << vec_subPoses[camID].rotation()); - ALICEVISION_LOG_DEBUG("Centre: " << vec_subPoses[camID].center()); + ALICEVISION_LOG_DEBUG("[poseEstimation]\tonly " << numAssociations << " have been found, not enough to do the resection!"); + for (std::size_t cam = 0; cam < numCams; ++cam) + { + // empty result with isValid set to false + vec_locResults.emplace_back(); + } + return false; } - } - - std::vector > vec_inliers; - const EstimationStatus resectionEstimation = rigResection(vec_pts2D, - vec_pts3D, - vec_queryIntrinsics, - vec_subPoses, - &descTypesPerCamera, - rigPose, - vec_inliers, - param->_angularThreshold); - - if(!resectionEstimation.isValid) - { - for(std::size_t camID = 0; camID < numCams; ++camID) + { - // empty result with isValid set to false - vec_locResults.emplace_back(); + for (std::size_t camID = 0; camID < vec_subPoses.size(); ++camID) + { + ALICEVISION_LOG_DEBUG("Rotation: " << vec_subPoses[camID].rotation()); + ALICEVISION_LOG_DEBUG("Centre: " << vec_subPoses[camID].center()); + } } - ALICEVISION_LOG_DEBUG("Resection failed."); - return false; - } - - if(!resectionEstimation.hasStrongSupport) - { - ALICEVISION_LOG_DEBUG("Resection hasn't a strong support."); - } - - { // just debugging stuff, this block can be removed - - if(vec_inliers.size() < numCams) + + std::vector> vec_inliers; + const EstimationStatus resectionEstimation = + rigResection(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, &descTypesPerCamera, rigPose, vec_inliers, param->_angularThreshold); + + if (!resectionEstimation.isValid) { - // in general the inlier should be spread among different cameras - ALICEVISION_CERR("WARNING: RIG Voctree Localizer: Inliers in " - << vec_inliers.size() << " cameras on a RIG of " - << numCams << " cameras."); + for (std::size_t camID = 0; camID < numCams; ++camID) + { + // empty result with isValid set to false + vec_locResults.emplace_back(); + } + ALICEVISION_LOG_DEBUG("Resection failed."); + return false; } - for(std::size_t camID = 0; camID < vec_inliers.size(); ++camID) - ALICEVISION_LOG_DEBUG("#inliers for cam " << camID << ": " << vec_inliers[camID].size()); - - ALICEVISION_LOG_DEBUG("Pose after resection:"); - ALICEVISION_LOG_DEBUG("Rotation: " << rigPose.rotation()); - ALICEVISION_LOG_DEBUG("Centre: " << rigPose.center()); - - // print the reprojection error for inliers (just debugging purposes) - printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); - } - -// const bool refineOk = refineRigPose(vec_pts2D, -// vec_pts3D, -// vec_inliers, -// vec_queryIntrinsics, -// vec_subPoses, -// rigPose); - - const auto& resInl = computeInliers(vec_pts2D, - vec_pts3D, - vec_queryIntrinsics, - vec_subPoses, - param->_errorMax, - rigPose, - vec_inliers); - - ALICEVISION_LOG_DEBUG("After first recomputation of inliers with a threshold of " - << param->_errorMax << " the RMSE is: " << resInl.first); - - aliceVision::system::Timer timer; - const std::size_t minNumPoints = 4; - const bool refineOk = iterativeRefineRigPose(vec_pts2D, - vec_pts3D, - vec_queryIntrinsics, - vec_subPoses, - param->_errorMax, - minNumPoints, - vec_inliers, - rigPose); - ALICEVISION_LOG_DEBUG("Iterative refinement took " << timer.elapsedMs() << "ms"); - - { - // debugging stats - printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); - } - - // create localization results - for(std::size_t camID = 0; camID < numCams; ++camID) - { - - const auto &intrinsics = vec_queryIntrinsics[camID]; - - // compute the (absolute) pose of each camera: for the main camera it's the - // rig pose, for the others, combine the subpose with the rig pose - geometry::Pose3 pose; - if(camID == 0) + if (!resectionEstimation.hasStrongSupport) { - pose = rigPose; + ALICEVISION_LOG_DEBUG("Resection hasn't a strong support."); } - else + + { // just debugging stuff, this block can be removed + + if (vec_inliers.size() < numCams) + { + // in general the inlier should be spread among different cameras + ALICEVISION_CERR("WARNING: RIG Voctree Localizer: Inliers in " << vec_inliers.size() << " cameras on a RIG of " << numCams + << " cameras."); + } + + for (std::size_t camID = 0; camID < vec_inliers.size(); ++camID) + ALICEVISION_LOG_DEBUG("#inliers for cam " << camID << ": " << vec_inliers[camID].size()); + + ALICEVISION_LOG_DEBUG("Pose after resection:"); + ALICEVISION_LOG_DEBUG("Rotation: " << rigPose.rotation()); + ALICEVISION_LOG_DEBUG("Centre: " << rigPose.center()); + + // print the reprojection error for inliers (just debugging purposes) + printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); + } + + // const bool refineOk = refineRigPose(vec_pts2D, + // vec_pts3D, + // vec_inliers, + // vec_queryIntrinsics, + // vec_subPoses, + // rigPose); + + const auto& resInl = computeInliers(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, param->_errorMax, rigPose, vec_inliers); + + ALICEVISION_LOG_DEBUG("After first recomputation of inliers with a threshold of " << param->_errorMax << " the RMSE is: " << resInl.first); + + aliceVision::system::Timer timer; + const std::size_t minNumPoints = 4; + const bool refineOk = + iterativeRefineRigPose(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, param->_errorMax, minNumPoints, vec_inliers, rigPose); + ALICEVISION_LOG_DEBUG("Iterative refinement took " << timer.elapsedMs() << "ms"); + { - // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q - // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A - // and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) - // With rigResection() we compute [R1 t1] (aka rigPose), hence: - // subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) and we need [R2 t2], ie the absolute pose - // => [R1 t1] * subPose12 = [R2 t2] - // => rigPose * subPose12 = [R2 t2] - pose = vec_subPoses[camID-1] * rigPose; + // debugging stats + printRigRMSEStats(vec_pts2D, vec_pts3D, vec_queryIntrinsics, vec_subPoses, rigPose, vec_inliers); } - - // create matchData - sfm::ImageLocalizerMatchData matchData; - matchData.vec_inliers = vec_inliers[camID]; - matchData.error_max = param->_errorMax; - matchData.projection_matrix = intrinsics.getProjectiveEquivalent(pose); - matchData.pt2D = vec_pts2D[camID]; - matchData.pt3D = vec_pts3D[camID]; - - // create indMatch3D2D - std::vector indMatch3D2D; - indMatch3D2D.reserve(matchData.pt2D.cols()); - const auto &occurrences = vec_occurrences[camID]; - for(const auto &ass : occurrences) + + // create localization results + for (std::size_t camID = 0; camID < numCams; ++camID) { - // recopy the associations IDs in the vector - indMatch3D2D.push_back(ass.first); + const auto& intrinsics = vec_queryIntrinsics[camID]; + + // compute the (absolute) pose of each camera: for the main camera it's the + // rig pose, for the others, combine the subpose with the rig pose + geometry::Pose3 pose; + if (camID == 0) + { + pose = rigPose; + } + else + { + // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q + // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A + // and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) + // With rigResection() we compute [R1 t1] (aka rigPose), hence: + // subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) and we need [R2 t2], ie the absolute pose + // => [R1 t1] * subPose12 = [R2 t2] + // => rigPose * subPose12 = [R2 t2] + pose = vec_subPoses[camID - 1] * rigPose; + } + + // create matchData + sfm::ImageLocalizerMatchData matchData; + matchData.vec_inliers = vec_inliers[camID]; + matchData.error_max = param->_errorMax; + matchData.projection_matrix = intrinsics.getProjectiveEquivalent(pose); + matchData.pt2D = vec_pts2D[camID]; + matchData.pt3D = vec_pts3D[camID]; + + // create indMatch3D2D + std::vector indMatch3D2D; + indMatch3D2D.reserve(matchData.pt2D.cols()); + const auto& occurrences = vec_occurrences[camID]; + for (const auto& ass : occurrences) + { + // recopy the associations IDs in the vector + indMatch3D2D.push_back(ass.first); + } + + const auto& matchedImages = vec_matchedImages[camID]; + + vec_locResults.emplace_back(matchData, indMatch3D2D, pose, intrinsics, matchedImages, refineOk); } - - const auto &matchedImages = vec_matchedImages[camID]; - - vec_locResults.emplace_back(matchData, indMatch3D2D, pose, intrinsics, matchedImages, refineOk); - } - - - if(!refineOk) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefine failed."); - return false; - } - - { // just debugging stuff, this block can be removed - - ALICEVISION_LOG_DEBUG("Pose after BA:"); - ALICEVISION_LOG_DEBUG("Rotation: " << rigPose.rotation()); - ALICEVISION_LOG_DEBUG("Centre: " << rigPose.center()); - - // compute the reprojection error for inliers (just debugging purposes) - for(std::size_t camID = 0; camID < numCams; ++camID) + + if (!refineOk) { - const camera::Pinhole &currCamera = vec_queryIntrinsics[camID]; - Mat2X residuals; - if(camID!=0) - residuals = currCamera.residuals(vec_subPoses[camID-1]*rigPose, vec_pts3D[camID], vec_pts2D[camID]); - else - residuals = currCamera.residuals(geometry::Pose3()*rigPose, vec_pts3D[camID], vec_pts2D[camID]); - - const Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - -// ALICEVISION_LOG_DEBUG("Camera " << camID << " all reprojection errors after BA:"); -// ALICEVISION_LOG_DEBUG(sqrErrors); - -// ALICEVISION_LOG_DEBUG("Camera " << camID << " inliers reprojection errors after BA:"); - const auto &currInliers = vec_inliers[camID]; - - double rmse = 0; - for(std::size_t j = 0; j < currInliers.size(); ++j) - { -// ALICEVISION_LOG_DEBUG(sqrErrors(currInliers[j])); - rmse += sqrErrors(currInliers[j]); - } - if(!currInliers.empty()) - ALICEVISION_LOG_DEBUG("\n\nRMSE inliers cam " << camID << ": " << std::sqrt(rmse/currInliers.size())); + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRefine failed."); + return false; + } + + { // just debugging stuff, this block can be removed + + ALICEVISION_LOG_DEBUG("Pose after BA:"); + ALICEVISION_LOG_DEBUG("Rotation: " << rigPose.rotation()); + ALICEVISION_LOG_DEBUG("Centre: " << rigPose.center()); + + // compute the reprojection error for inliers (just debugging purposes) + for (std::size_t camID = 0; camID < numCams; ++camID) + { + const camera::Pinhole& currCamera = vec_queryIntrinsics[camID]; + Mat2X residuals; + if (camID != 0) + residuals = currCamera.residuals(vec_subPoses[camID - 1] * rigPose, vec_pts3D[camID], vec_pts2D[camID]); + else + residuals = currCamera.residuals(geometry::Pose3() * rigPose, vec_pts3D[camID], vec_pts2D[camID]); + + const Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + + // ALICEVISION_LOG_DEBUG("Camera " << camID << " all reprojection errors after BA:"); + // ALICEVISION_LOG_DEBUG(sqrErrors); + + // ALICEVISION_LOG_DEBUG("Camera " << camID << " inliers reprojection errors after BA:"); + const auto& currInliers = vec_inliers[camID]; + + double rmse = 0; + for (std::size_t j = 0; j < currInliers.size(); ++j) + { + // ALICEVISION_LOG_DEBUG(sqrErrors(currInliers[j])); + rmse += sqrErrors(currInliers[j]); + } + if (!currInliers.empty()) + ALICEVISION_LOG_DEBUG("\n\nRMSE inliers cam " << camID << ": " << std::sqrt(rmse / currInliers.size())); + } } - } - - return true; + + return true; } -#endif // ALICEVISION_HAVE_OPENGV +#endif // ALICEVISION_HAVE_OPENGV -// subposes is n-1 as we consider the first camera as the main camera and the +// subposes is n-1 as we consider the first camera as the main camera and the // reference frame of the grid -bool VoctreeLocalizer::localizeRig_naive(const std::vector & vec_queryRegions, - const std::vector > &vec_imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_localizationResults) +bool VoctreeLocalizer::localizeRig_naive(const std::vector& vec_queryRegions, + const std::vector>& vec_imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_localizationResults) { - const size_t numCams = vec_queryRegions.size(); - - assert(numCams==vec_queryIntrinsics.size()); - assert(numCams==vec_subPoses.size()+1); - assert(numCams==vec_imageSize.size()); - - vec_localizationResults.resize(numCams); - - // this is basic, just localize each camera alone - //@todo parallelize? - std::vector isLocalized(numCams, false); - for(size_t i = 0; i < numCams; ++i) - { - isLocalized[i] = localize(vec_queryRegions[i], vec_imageSize[i], parameters, randomNumberGenerator, true /*useInputIntrinsics*/, vec_queryIntrinsics[i], vec_localizationResults[i]); - assert(isLocalized[i] == vec_localizationResults[i].isValid()); - if(!isLocalized[i]) + const size_t numCams = vec_queryRegions.size(); + + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); + assert(numCams == vec_imageSize.size()); + + vec_localizationResults.resize(numCams); + + // this is basic, just localize each camera alone + //@todo parallelize? + std::vector isLocalized(numCams, false); + for (size_t i = 0; i < numCams; ++i) + { + isLocalized[i] = localize(vec_queryRegions[i], + vec_imageSize[i], + parameters, + randomNumberGenerator, + true /*useInputIntrinsics*/, + vec_queryIntrinsics[i], + vec_localizationResults[i]); + assert(isLocalized[i] == vec_localizationResults[i].isValid()); + if (!isLocalized[i]) + { + ALICEVISION_CERR("Could not localize camera " << i); + // even if it is not localize we can try to go on and do with the cameras we have + } + } + + // ** 'easy' cases in which we don't need further processing ** + const std::size_t numLocalizedCam = std::count(isLocalized.begin(), isLocalized.end(), true); + + // no camera has be localized + if (numLocalizedCam == 0) { - ALICEVISION_CERR("Could not localize camera " << i); - // even if it is not localize we can try to go on and do with the cameras we have + ALICEVISION_LOG_DEBUG("No camera has been localized!!!"); + return false; } - } - - // ** 'easy' cases in which we don't need further processing ** - const std::size_t numLocalizedCam = std::count(isLocalized.begin(), isLocalized.end(), true); - - // no camera has be localized - if(numLocalizedCam == 0) - { - ALICEVISION_LOG_DEBUG("No camera has been localized!!!"); - return false; - } - - ALICEVISION_LOG_DEBUG("Localized cameras: " << numLocalizedCam << "/" << numCams); - - // if there is only one camera (the main one) - if(numCams==1) - { - // there is only the main camera, not much else to do, the position is already - // refined by the call to localize - //set the pose - rigPose = vec_localizationResults[0].getPose(); - } - else - { - // find the index of the first localized camera - const std::size_t idx = std::distance(isLocalized.begin(), - std::find(isLocalized.begin(), isLocalized.end(), true)); - - // useless safeguard as there should be at least 1 element at this point but - // better safe than sorry - assert(idx < isLocalized.size()); - - ALICEVISION_LOG_DEBUG("Index of the first localized camera: " << idx); - - // if the only localized camera is the main camera - if(idx==0) + + ALICEVISION_LOG_DEBUG("Localized cameras: " << numLocalizedCam << "/" << numCams); + + // if there is only one camera (the main one) + if (numCams == 1) { - // just give its pose - rigPose = vec_localizationResults[0].getPose(); + // there is only the main camera, not much else to do, the position is already + // refined by the call to localize + // set the pose + rigPose = vec_localizationResults[0].getPose(); } else { - // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q - // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) - // with the localization localize() we have computed [R2 t2], hence: - // q2 ~ [R2 t2] Q = [R12 t12]*inv([R12 t12]) * [R2 t2] Q - // and inv([R12 t12]) * [R2 t2] is the pose of the main camera - - // recover the rig pose using the subposes - rigPose = vec_subPoses[idx-1].inverse() * vec_localizationResults[idx].getPose(); + // find the index of the first localized camera + const std::size_t idx = std::distance(isLocalized.begin(), std::find(isLocalized.begin(), isLocalized.end(), true)); + + // useless safeguard as there should be at least 1 element at this point but + // better safe than sorry + assert(idx < isLocalized.size()); + + ALICEVISION_LOG_DEBUG("Index of the first localized camera: " << idx); + + // if the only localized camera is the main camera + if (idx == 0) + { + // just give its pose + rigPose = vec_localizationResults[0].getPose(); + } + else + { + // main camera: q1 ~ [R1 t1] Q = [I 0] A where A = [R1 t1] Q + // another camera: q2 ~ [R2 t2] Q = [R2 t2]*inv([R1 t1]) A and subPose12 = [R12 t12] = [R2 t2]*inv([R1 t1]) + // with the localization localize() we have computed [R2 t2], hence: + // q2 ~ [R2 t2] Q = [R12 t12]*inv([R12 t12]) * [R2 t2] Q + // and inv([R12 t12]) * [R2 t2] is the pose of the main camera + + // recover the rig pose using the subposes + rigPose = vec_subPoses[idx - 1].inverse() * vec_localizationResults[idx].getPose(); + } } - } - - // ** otherwise run a BA with the localized cameras - const bool refineOk = refineRigPose(vec_subPoses, vec_localizationResults, rigPose); - - if(!refineOk) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tRig pose refinement failed."); - return false; - } - - updateRigPoses(vec_localizationResults, rigPose, vec_subPoses); - - return true; -} + // ** otherwise run a BA with the localized cameras + const bool refineOk = refineRigPose(vec_subPoses, vec_localizationResults, rigPose); + if (!refineOk) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tRig pose refinement failed."); + return false; + } + + updateRigPoses(vec_localizationResults, rigPose, vec_subPoses); + + return true; +} -} // localization -} // aliceVision +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/VoctreeLocalizer.hpp b/src/aliceVision/localization/VoctreeLocalizer.hpp index 0e40064c63..bb220a0b4a 100644 --- a/src/aliceVision/localization/VoctreeLocalizer.hpp +++ b/src/aliceVision/localization/VoctreeLocalizer.hpp @@ -24,349 +24,339 @@ namespace localization { struct FrameData { - FrameData(const LocalizationResult &locResult, const feature::MapRegionsPerDesc& regionsPerDesc); - - LocalizationResult _locResult; - ReconstructedRegionsMappingPerDesc _regionsWith3D; - feature::MapRegionsPerDesc _regions; + FrameData(const LocalizationResult& locResult, const feature::MapRegionsPerDesc& regionsPerDesc); + + LocalizationResult _locResult; + ReconstructedRegionsMappingPerDesc _regionsWith3D; + feature::MapRegionsPerDesc _regions; }; class VoctreeLocalizer : public ILocalizer { -public: - enum Algorithm : int {FirstBest=0, BestResult=1, AllResults=2, Cluster=3}; - static Algorithm initFromString(const std::string &value); - -public: - struct Parameters : public LocalizerParameters - { - - Parameters() - : LocalizerParameters() - , _useGuidedMatching(false) - , _useRobustMatching(true) - , _algorithm(Algorithm::AllResults) - , _numResults(4) - , _maxResults(10) - , _numCommonViews(3) - , _ccTagUseCuda(true) - , _matchingError(std::numeric_limits::infinity()) - , _nbFrameBufferMatching(10) - {} - - /// Enable/disable guided matching when matching images - bool _useGuidedMatching; - /// Enable/disable robust feature matching (geometric validation) - bool _useRobustMatching; - /// algorithm to use for localization - Algorithm _algorithm; - /// number of best matching images to retrieve from the database - std::size_t _numResults; - /// for algorithm AllResults, it stops the image matching when this number of matched images is reached - std::size_t _maxResults; - /// number minimum common images in which a point must be seen to be used in cluster tracking - std::size_t _numCommonViews; - /// ccTag-CUDA cannot process frames at different resolutions ATM, so set to false if localizer is used on images of differing sizes - bool _ccTagUseCuda; - /// maximum reprojection error allowed for image matching with geometric validation - double _matchingError; - /// maximum capacity of the frame buffer - std::size_t _nbFrameBufferMatching; - }; - -public: - - /** - * @brief Initialize a localizer based on a vocabulary tree - * - * @param[in] sfmData The sfmdata containing the scene - * reconstruction. - * @param[in] descriptorsFolder The path to the directory containing the features - * of the scene (.desc and .feat files). - * @param[in] vocTreeFilepath The path to the vocabulary tree (usually a .tree file). - * @param[in] weightsFilepath Optional path to the weights of the vocabulary - * tree (usually a .weights file), if not provided the weights will be recomputed - * when all the documents are added. - * @param[in] matchingDescTypes List of descriptor types to use for feature matching. - * @param[in] voctreeDescType Descriptor type used for image matching with voctree. - * - * It enable the use of combined SIFT and CCTAG features. - */ - VoctreeLocalizer(const sfmData::SfMData &sfmData, - const std::string &descriptorsFolder, - const std::string &vocTreeFilepath, - const std::string &weightsFilepath, - const std::vector& matchingDescTypes - ); - - void setCudaPipe( int i ) override - { - _cudaPipe = i; - } - - /** - * @brief Just a wrapper around the different localization algorithm, the algorithm - * used to localized is chosen using \p param._algorithm. This version extract the - * sift features from the query image. - * @param[in] randomNumberGenerator, The random seed - * @param[in] imageGrey The input greyscale image. - * @param[in] param The parameters for the localization. - * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. - * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the - * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. - * @param[out] localizationResult The localization result containing the pose and the associations. - * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. - * @return true if the image has been successfully localized. - */ - bool localize(const image::Image & imageGrey, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult &localizationResult, - const std::string& imagePath = std::string()) override; - - /** - * @brief Just a wrapper around the different localization algorithm, the algorithm - * used to localized is chosen using \p param._algorithm. This version takes as - * input the sift feature already extracted. - * @param[in] randomNumberGenerator, The random seed - * @param[in] queryRegions The input features of the query image - * @param[in] imageSize The size of the input image - * @param[in] param The parameters for the localization. - * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. - * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the - * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. - * @param[out] localizationResult The localization result containing the pose and the associations. - * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. - * @return true if the image has been successfully localized. - */ - bool localize(const feature::MapRegionsPerDesc & queryRegions, - const std::pair &imageSize, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult & localizationResult, - const std::string& imagePath = std::string()) override; - - - bool localizeRig(const std::vector> & vec_imageGrey, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector & vec_locResults) override; - - bool localizeRig(const std::vector & vec_queryRegions, - const std::vector > &vec_imageSize, - const LocalizerParameters *param, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults) override; + public: + enum Algorithm : int + { + FirstBest = 0, + BestResult = 1, + AllResults = 2, + Cluster = 3 + }; + static Algorithm initFromString(const std::string& value); + + public: + struct Parameters : public LocalizerParameters + { + Parameters() + : LocalizerParameters(), + _useGuidedMatching(false), + _useRobustMatching(true), + _algorithm(Algorithm::AllResults), + _numResults(4), + _maxResults(10), + _numCommonViews(3), + _ccTagUseCuda(true), + _matchingError(std::numeric_limits::infinity()), + _nbFrameBufferMatching(10) + {} + + /// Enable/disable guided matching when matching images + bool _useGuidedMatching; + /// Enable/disable robust feature matching (geometric validation) + bool _useRobustMatching; + /// algorithm to use for localization + Algorithm _algorithm; + /// number of best matching images to retrieve from the database + std::size_t _numResults; + /// for algorithm AllResults, it stops the image matching when this number of matched images is reached + std::size_t _maxResults; + /// number minimum common images in which a point must be seen to be used in cluster tracking + std::size_t _numCommonViews; + /// ccTag-CUDA cannot process frames at different resolutions ATM, so set to false if localizer is used on images of differing sizes + bool _ccTagUseCuda; + /// maximum reprojection error allowed for image matching with geometric validation + double _matchingError; + /// maximum capacity of the frame buffer + std::size_t _nbFrameBufferMatching; + }; + + public: + /** + * @brief Initialize a localizer based on a vocabulary tree + * + * @param[in] sfmData The sfmdata containing the scene + * reconstruction. + * @param[in] descriptorsFolder The path to the directory containing the features + * of the scene (.desc and .feat files). + * @param[in] vocTreeFilepath The path to the vocabulary tree (usually a .tree file). + * @param[in] weightsFilepath Optional path to the weights of the vocabulary + * tree (usually a .weights file), if not provided the weights will be recomputed + * when all the documents are added. + * @param[in] matchingDescTypes List of descriptor types to use for feature matching. + * @param[in] voctreeDescType Descriptor type used for image matching with voctree. + * + * It enable the use of combined SIFT and CCTAG features. + */ + VoctreeLocalizer(const sfmData::SfMData& sfmData, + const std::string& descriptorsFolder, + const std::string& vocTreeFilepath, + const std::string& weightsFilepath, + const std::vector& matchingDescTypes); + + void setCudaPipe(int i) override { _cudaPipe = i; } + + /** + * @brief Just a wrapper around the different localization algorithm, the algorithm + * used to localized is chosen using \p param._algorithm. This version extract the + * sift features from the query image. + * @param[in] randomNumberGenerator, The random seed + * @param[in] imageGrey The input greyscale image. + * @param[in] param The parameters for the localization. + * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. + * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the + * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. + * @param[out] localizationResult The localization result containing the pose and the associations. + * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. + * @return true if the image has been successfully localized. + */ + bool localize(const image::Image& imageGrey, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()) override; + + /** + * @brief Just a wrapper around the different localization algorithm, the algorithm + * used to localized is chosen using \p param._algorithm. This version takes as + * input the sift feature already extracted. + * @param[in] randomNumberGenerator, The random seed + * @param[in] queryRegions The input features of the query image + * @param[in] imageSize The size of the input image + * @param[in] param The parameters for the localization. + * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration. + * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the + * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. + * @param[out] localizationResult The localization result containing the pose and the associations. + * @param[in] imagePath Optional complete path to the image, used only for debugging purposes. + * @return true if the image has been successfully localized. + */ + bool localize(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()) override; + bool localizeRig(const std::vector>& vec_imageGrey, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) override; + + bool localizeRig(const std::vector& vec_queryRegions, + const std::vector>& vec_imageSize, + const LocalizerParameters* param, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults) override; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) - bool localizeRig_opengv(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults); + bool localizeRig_opengv(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults); #endif - bool localizeRig_naive(const std::vector & vec_queryRegions, - const std::vector > &imageSize, - const LocalizerParameters *parameters, - std::mt19937 & randomNumberGenerator, - std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose, - std::vector& vec_locResults); - - - /** - * @brief Try to localize an image in the database: it queries the database to - * retrieve \p numResults matching images and it tries to localize the query image - * wrt the retrieve images in order of their score taking the first best result. - * - - * @param[in] queryRegions The input features of the query image - * @param[in] imageSize The size of the input image - * @param[in] param The parameters for the localization - * @param[in] randomNumberGenerator The random seed - * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration - * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the - * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. - * @param[out] pose The camera pose - * @param[out] resection_data the 2D-3D correspondences used to compute the pose - * @param[out] associationIDs the ids of the 2D-3D correspondences used to compute the pose - * @return true if the localization is successful - */ - bool localizeFirstBestResult(const feature::MapRegionsPerDesc &queryRegions, - const std::pair &imageSize, - const Parameters ¶m, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult &localizationResult, - const std::string &imagePath = std::string()); - - /** - * @brief Try to localize an image in the database: it queries the database to - * retrieve \p numResults matching images and it tries to localize the query image - * wrt the retrieve images in order of their score, collecting all the 2d-3d correspondences - * and performing the resection with all these correspondences - * - * @param[in] queryRegions The input features of the query image - * @param[in] imageSize The size of the input image - * @param[in] param The parameters for the localization - * @param[in] randomNumberGenerator The random seed - * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration - * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the - * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. - * @param[out] pose The camera pose - * @param[out] resection_data the 2D-3D correspondences used to compute the pose - * @param[out] associationIDs the ids of the 2D-3D correspondences used to compute the pose - * @return true if the localization is successful - */ - bool localizeAllResults(const feature::MapRegionsPerDesc & queryRegions, - const std::pair & imageSize, - const Parameters ¶m, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - camera::Pinhole &queryIntrinsics, - LocalizationResult &localizationResult, - const std::string& imagePath = std::string()); - - - /** - * @brief Retrieve matches to all images of the database. - * - * @param[in] queryRegions - * @param[in] imageSize - * @param[in] param - * @param[in] randomNumberGenerator, - * @param[in] useInputIntrinsics - * @param[in] queryIntrinsics - * @param[out] out_occurences - * @param[out] out_pt2D output matrix of 2D points - * @param[out] out_pt3D output matrix of 3D points - * @param[out] out_descTypes output vector of describerType - * @param[out] out_matchedImages image matches output - * @param[in] imagePath - */ - void getAllAssociations(const feature::MapRegionsPerDesc & queryRegions, - const std::pair &imageSize, - const Parameters ¶m, - std::mt19937 & randomNumberGenerator, - bool useInputIntrinsics, - const camera::Pinhole &queryIntrinsics, - OccurenceMap & out_occurences, - Mat &out_pt2D, - Mat &out_pt3D, - std::vector& out_descTypes, - std::vector& out_matchedImages, - const std::string& imagePath = std::string()) const; - -private: - /** - * @brief Load the vocabulary tree. - - * @param[in] vocTreeFilepath The path to the directory containing the features - * of the scene (.desc and .feat files). - * @param[in] weightsFilepath weightsFilepath Optional path to the weights of the vocabulary - * tree (usually a .weights file), if not provided the weights will be recomputed - * when all the documents are added. - * @param[in] feat_directory The path to the directory containing the features - * of the scene (.desc and .feat files). - * @return true if everything went ok - */ - bool initDatabase(const std::string & vocTreeFilepath, - const std::string & weightsFilepath, - const std::string & featFolder); - - /** - * @brief robustMatching - * - * @param[?] matchers - * @param[in] queryIntrinsics - * @param[in] regionsToMatch - * @param[in] matchedIntrinsics - * @param[in] fDistRatio - * @param[in] matchingError - * @param[in] useGeometricFiltering - * @param[in] useGuidedMatching - * @param[in] imageSizeI - * @param[in] imageSizeJ - * @param[out] vec_featureMatches - * @param[in] estimator - * @return - */ - bool robustMatching(matching::RegionsDatabaseMatcherPerDesc & matchers, - const camera::IntrinsicBase * queryIntrinsics,// the intrinsics of the image we are using as reference - const feature::MapRegionsPerDesc & regionsToMatch, - const camera::IntrinsicBase * matchedIntrinsics, - float fDistRatio, - double matchingError, - bool useGeometricFiltering, - bool useGuidedMatching, - const std::pair & imageSizeI, // size of the image in matcher - const std::pair & imageSizeJ, // size of the query image - std::mt19937 & randomNumberGenerator, - matching::MatchesPerDescType & out_featureMatches, - robustEstimation::ERobustEstimator estimator = robustEstimation::ERobustEstimator::ACRANSAC) const; - - void getAssociationsFromBuffer(matching::RegionsDatabaseMatcherPerDesc& matchers, - const std::pair & imageSize, - const Parameters ¶m, + bool localizeRig_naive(const std::vector& vec_queryRegions, + const std::vector>& imageSize, + const LocalizerParameters* parameters, + std::mt19937& randomNumberGenerator, + std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose, + std::vector& vec_locResults); + + /** + * @brief Try to localize an image in the database: it queries the database to + * retrieve \p numResults matching images and it tries to localize the query image + * wrt the retrieve images in order of their score taking the first best result. + * + + * @param[in] queryRegions The input features of the query image + * @param[in] imageSize The size of the input image + * @param[in] param The parameters for the localization + * @param[in] randomNumberGenerator The random seed + * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration + * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the + * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. + * @param[out] pose The camera pose + * @param[out] resection_data the 2D-3D correspondences used to compute the pose + * @param[out] associationIDs the ids of the 2D-3D correspondences used to compute the pose + * @return true if the localization is successful + */ + bool localizeFirstBestResult(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const Parameters& param, + std::mt19937& randomNumberGenerator, bool useInputIntrinsics, - const camera::Pinhole &queryIntrinsics, - OccurenceMap &out_occurences, - std::mt19937 & randomNumberGenerator, - const std::string& imagePath = std::string()) const; - - /** - * @brief Load all the Descriptors who have contributed to the reconstruction. - * deprecated.. now inside initDatabase - */ - bool loadReconstructionDescriptors( - const sfmData::SfMData & sfm_data, - const std::string & feat_directory); - - -public: - - /// for each view index, it contains the features and descriptors that have an - /// associated 3D point - feature::RegionsPerView _regionsPerView; - ReconstructedRegionsMappingPerView _reconstructedRegionsMappingPerView; - - /// the feature extractor - std::vector> _imageDescribers; - - // CUDA CCTag supports several parallel pipelines, where each one can - // processing different image dimensions. - int _cudaPipe = 0; - - /// the vocabulary tree used to generate the database and the visual images for - /// the query images - std::unique_ptr _voctree; - feature::EImageDescriberType _voctreeDescType = feature::EImageDescriberType::UNINITIALIZED; - - /// the database that stores the visual word representation of each image of - /// the original dataset - voctree::Database _database; - - /// Last frames buffer - BoundedBuffer _frameBuffer; - - matching::EMatcherType _matcherType = matching::ANN_L2; + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()); + + /** + * @brief Try to localize an image in the database: it queries the database to + * retrieve \p numResults matching images and it tries to localize the query image + * wrt the retrieve images in order of their score, collecting all the 2d-3d correspondences + * and performing the resection with all these correspondences + * + * @param[in] queryRegions The input features of the query image + * @param[in] imageSize The size of the input image + * @param[in] param The parameters for the localization + * @param[in] randomNumberGenerator The random seed + * @param[in] useInputIntrinsics Uses the \p queryIntrinsics as known calibration + * @param[in,out] queryIntrinsics Intrinsic parameters of the camera, they are used if the + * flag useInputIntrinsics is set to true, otherwise they are estimated from the correspondences. + * @param[out] pose The camera pose + * @param[out] resection_data the 2D-3D correspondences used to compute the pose + * @param[out] associationIDs the ids of the 2D-3D correspondences used to compute the pose + * @return true if the localization is successful + */ + bool localizeAllResults(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const Parameters& param, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + camera::Pinhole& queryIntrinsics, + LocalizationResult& localizationResult, + const std::string& imagePath = std::string()); + + /** + * @brief Retrieve matches to all images of the database. + * + * @param[in] queryRegions + * @param[in] imageSize + * @param[in] param + * @param[in] randomNumberGenerator, + * @param[in] useInputIntrinsics + * @param[in] queryIntrinsics + * @param[out] out_occurences + * @param[out] out_pt2D output matrix of 2D points + * @param[out] out_pt3D output matrix of 3D points + * @param[out] out_descTypes output vector of describerType + * @param[out] out_matchedImages image matches output + * @param[in] imagePath + */ + void getAllAssociations(const feature::MapRegionsPerDesc& queryRegions, + const std::pair& imageSize, + const Parameters& param, + std::mt19937& randomNumberGenerator, + bool useInputIntrinsics, + const camera::Pinhole& queryIntrinsics, + OccurenceMap& out_occurences, + Mat& out_pt2D, + Mat& out_pt3D, + std::vector& out_descTypes, + std::vector& out_matchedImages, + const std::string& imagePath = std::string()) const; + + private: + /** + * @brief Load the vocabulary tree. + + * @param[in] vocTreeFilepath The path to the directory containing the features + * of the scene (.desc and .feat files). + * @param[in] weightsFilepath weightsFilepath Optional path to the weights of the vocabulary + * tree (usually a .weights file), if not provided the weights will be recomputed + * when all the documents are added. + * @param[in] feat_directory The path to the directory containing the features + * of the scene (.desc and .feat files). + * @return true if everything went ok + */ + bool initDatabase(const std::string& vocTreeFilepath, const std::string& weightsFilepath, const std::string& featFolder); + + /** + * @brief robustMatching + * + * @param[?] matchers + * @param[in] queryIntrinsics + * @param[in] regionsToMatch + * @param[in] matchedIntrinsics + * @param[in] fDistRatio + * @param[in] matchingError + * @param[in] useGeometricFiltering + * @param[in] useGuidedMatching + * @param[in] imageSizeI + * @param[in] imageSizeJ + * @param[out] vec_featureMatches + * @param[in] estimator + * @return + */ + bool robustMatching(matching::RegionsDatabaseMatcherPerDesc& matchers, + const camera::IntrinsicBase* queryIntrinsics, // the intrinsics of the image we are using as reference + const feature::MapRegionsPerDesc& regionsToMatch, + const camera::IntrinsicBase* matchedIntrinsics, + float fDistRatio, + double matchingError, + bool useGeometricFiltering, + bool useGuidedMatching, + const std::pair& imageSizeI, // size of the image in matcher + const std::pair& imageSizeJ, // size of the query image + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_featureMatches, + robustEstimation::ERobustEstimator estimator = robustEstimation::ERobustEstimator::ACRANSAC) const; + + void getAssociationsFromBuffer(matching::RegionsDatabaseMatcherPerDesc& matchers, + const std::pair& imageSize, + const Parameters& param, + bool useInputIntrinsics, + const camera::Pinhole& queryIntrinsics, + OccurenceMap& out_occurences, + std::mt19937& randomNumberGenerator, + const std::string& imagePath = std::string()) const; + + /** + * @brief Load all the Descriptors who have contributed to the reconstruction. + * deprecated.. now inside initDatabase + */ + bool loadReconstructionDescriptors(const sfmData::SfMData& sfm_data, const std::string& feat_directory); + + public: + /// for each view index, it contains the features and descriptors that have an + /// associated 3D point + feature::RegionsPerView _regionsPerView; + ReconstructedRegionsMappingPerView _reconstructedRegionsMappingPerView; + + /// the feature extractor + std::vector> _imageDescribers; + + // CUDA CCTag supports several parallel pipelines, where each one can + // processing different image dimensions. + int _cudaPipe = 0; + + /// the vocabulary tree used to generate the database and the visual images for + /// the query images + std::unique_ptr _voctree; + feature::EImageDescriberType _voctreeDescType = feature::EImageDescriberType::UNINITIALIZED; + + /// the database that stores the visual word representation of each image of + /// the original dataset + voctree::Database _database; + + /// Last frames buffer + BoundedBuffer _frameBuffer; + + matching::EMatcherType _matcherType = matching::ANN_L2; }; /** @@ -377,8 +367,7 @@ std::ostream& operator<<(std::ostream& os, VoctreeLocalizer::Algorithm a); /** * @brief Get the type of algorithm from an integer */ -std::istream& operator>>(std::istream &in, VoctreeLocalizer::Algorithm &a); - +std::istream& operator>>(std::istream& in, VoctreeLocalizer::Algorithm& a); -} // localization -} // aliceVision +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/optimization.cpp b/src/aliceVision/localization/optimization.cpp index 237a8a00ca..c1bc7dc797 100644 --- a/src/aliceVision/localization/optimization.cpp +++ b/src/aliceVision/localization/optimization.cpp @@ -20,828 +20,798 @@ #include #include -namespace aliceVision{ -namespace localization{ +namespace aliceVision { +namespace localization { -bool refineSequence(std::vector & vec_localizationResult, +bool refineSequence(std::vector& vec_localizationResult, bool allTheSameIntrinsics /*= true*/, bool b_refine_intrinsic /*= true*/, bool b_no_distortion /*= false*/, bool b_refine_pose /*= true*/, bool b_refine_structure /*= false*/, - const std::string & outputFilename /*= ""*/, + const std::string& outputFilename /*= ""*/, std::size_t minPointVisibility /*=0*/) { - - const std::size_t numViews = vec_localizationResult.size(); - assert(numViews > 0 ); - - // the id for the instrinsic group - IndexT intrinsicID = 0; - - // Setup a tiny SfM scene with the corresponding 2D-3D data - sfmData::SfMData tinyScene; - - // if we have only one camera just set the intrinsics group once for all - if(allTheSameIntrinsics) - { - // find the first valid localization result and use its intrinsics - std::size_t intrinsicIndex = 0; - for(std::size_t viewID = 0; viewID < numViews; ++viewID, ++intrinsicIndex) - { - if(vec_localizationResult[viewID].isValid()) - break; - } - // it may be the case that all the localization result are invalid... - if(!vec_localizationResult[intrinsicIndex].isValid()) - { - ALICEVISION_CERR("Apparently all the vec_localizationResult are invalid! Aborting..."); - return false; - } - - ALICEVISION_CERR("allTheSameIntrinsics mode: using the intrinsics of the " << intrinsicIndex << " result"); - - camera::Pinhole* currIntrinsics = &vec_localizationResult.at(intrinsicIndex).getIntrinsics(); - - if(b_no_distortion) - { - // no distortion refinement - ALICEVISION_LOG_DEBUG("Optical distortion won't be considered"); - // just add a simple pinhole camera with the same K as the input camera - Vec2 pp = currIntrinsics->getPrincipalPoint(); - tinyScene.getIntrinsics().emplace(intrinsicID, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - currIntrinsics->w(), currIntrinsics->h(), - currIntrinsics->getFocalLengthPixX(), currIntrinsics->getFocalLengthPixY(), - pp(0), pp(1))); - } - else + const std::size_t numViews = vec_localizationResult.size(); + assert(numViews > 0); + + // the id for the instrinsic group + IndexT intrinsicID = 0; + + // Setup a tiny SfM scene with the corresponding 2D-3D data + sfmData::SfMData tinyScene; + + // if we have only one camera just set the intrinsics group once for all + if (allTheSameIntrinsics) { - // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) - tinyScene.getIntrinsics().emplace(intrinsicID, std::shared_ptr(currIntrinsics, [](camera::Pinhole*){})); - ALICEVISION_LOG_DEBUG("Type of intrinsics " <getType()); + // find the first valid localization result and use its intrinsics + std::size_t intrinsicIndex = 0; + for (std::size_t viewID = 0; viewID < numViews; ++viewID, ++intrinsicIndex) + { + if (vec_localizationResult[viewID].isValid()) + break; + } + // it may be the case that all the localization result are invalid... + if (!vec_localizationResult[intrinsicIndex].isValid()) + { + ALICEVISION_CERR("Apparently all the vec_localizationResult are invalid! Aborting..."); + return false; + } + + ALICEVISION_CERR("allTheSameIntrinsics mode: using the intrinsics of the " << intrinsicIndex << " result"); + + camera::Pinhole* currIntrinsics = &vec_localizationResult.at(intrinsicIndex).getIntrinsics(); + + if (b_no_distortion) + { + // no distortion refinement + ALICEVISION_LOG_DEBUG("Optical distortion won't be considered"); + // just add a simple pinhole camera with the same K as the input camera + Vec2 pp = currIntrinsics->getPrincipalPoint(); + tinyScene.getIntrinsics().emplace(intrinsicID, + camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, + currIntrinsics->w(), + currIntrinsics->h(), + currIntrinsics->getFocalLengthPixX(), + currIntrinsics->getFocalLengthPixY(), + pp(0), + pp(1))); + } + else + { + // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) + tinyScene.getIntrinsics().emplace(intrinsicID, std::shared_ptr(currIntrinsics, [](camera::Pinhole*) {})); + ALICEVISION_LOG_DEBUG("Type of intrinsics " << tinyScene.getIntrinsics().at(0).get()->getType()); + } } - } - - { - // just debugging -- print reprojection errors -- this block can be safely removed/commented out - for(size_t viewID = 0; viewID < numViews; ++viewID) + { - const LocalizationResult &currResult = vec_localizationResult[viewID]; - if(!currResult.isValid()) - { - continue; - } - - const Mat2X residuals = currResult.computeInliersResiduals(); - - const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - ALICEVISION_LOG_DEBUG("View " << viewID << " RMSE = " << std::sqrt(sqrErrors.mean()) - << " min = " << std::sqrt(sqrErrors.minCoeff()) - << " mean = " << std::sqrt(sqrErrors.mean()) - << " max = " << std::sqrt(sqrErrors.maxCoeff()) - << " threshold = " << currResult.getMaxReprojectionError()); + // just debugging -- print reprojection errors -- this block can be safely removed/commented out + for (size_t viewID = 0; viewID < numViews; ++viewID) + { + const LocalizationResult& currResult = vec_localizationResult[viewID]; + if (!currResult.isValid()) + { + continue; + } + + const Mat2X residuals = currResult.computeInliersResiduals(); + + const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + ALICEVISION_LOG_DEBUG("View " << viewID << " RMSE = " << std::sqrt(sqrErrors.mean()) << " min = " << std::sqrt(sqrErrors.minCoeff()) + << " mean = " << std::sqrt(sqrErrors.mean()) << " max = " << std::sqrt(sqrErrors.maxCoeff()) + << " threshold = " << currResult.getMaxReprojectionError()); + } } - } - - const double unknownScale = 0.0; - for(size_t viewID = 0; viewID < numViews; ++viewID) - { - LocalizationResult &currResult = vec_localizationResult[viewID]; - // skip invalid poses - if(!currResult.isValid()) + + const double unknownScale = 0.0; + for (size_t viewID = 0; viewID < numViews; ++viewID) { - ALICEVISION_LOG_DEBUG("\n*****\nskipping invalid View " << viewID); - continue; + LocalizationResult& currResult = vec_localizationResult[viewID]; + // skip invalid poses + if (!currResult.isValid()) + { + ALICEVISION_LOG_DEBUG("\n*****\nskipping invalid View " << viewID); + continue; + } + + // ALICEVISION_LOG_DEBUG("\n*****\nView " << viewID); + // view + std::shared_ptr view = std::make_shared("", viewID, intrinsicID, viewID); + tinyScene.getViews().insert(std::make_pair(viewID, view)); + // pose + tinyScene.setPose(*view, sfmData::CameraPose(currResult.getPose())); + + if (!allTheSameIntrinsics) + { + camera::Pinhole* currIntrinsics = &currResult.getIntrinsics(); + // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) + tinyScene.getIntrinsics().emplace(intrinsicID, std::shared_ptr(currIntrinsics, [](camera::Pinhole*) {})); + ++intrinsicID; + } + + // structure data (2D-3D correspondences) + const std::vector& matches = currResult.getIndMatch3D2D(); + + for (const size_t idx : currResult.getInliers()) + { + // the idx should be in the size range of the data + assert(idx < currResult.getPt3D().cols()); + const IndMatch3D2D& match = matches[idx]; + // ALICEVISION_LOG_DEBUG("inlier " << idx << " is land " << landmarkID << " and feat " << featID); + // get the corresponding feature + const Vec2& feature = currResult.getPt2D().col(idx); + // check if the point exists already + if (tinyScene.getLandmarks().count(match.landmarkId)) + { + sfmData::Landmark& landmark = tinyScene.getLandmarks().at(match.landmarkId); + assert(landmark.descType == match.descType); + // normally there should be no other features already associated to this + // 3D point in this view + if (landmark.observations.count(viewID) != 0) + { + // this is weird but it could happen when two features are really close to each other (?) + ALICEVISION_LOG_DEBUG("Point 3D " << match.landmarkId << " has multiple features " + << "in the same view " << viewID << ", current size of obs: " << landmark.observations.size()); + ALICEVISION_LOG_DEBUG("its associated features are: "); + for (std::size_t i = 0; i < matches.size(); ++i) + { + auto const& p = matches[i]; + if (p.landmarkId == match.landmarkId) + { + assert(p.descType == match.descType); + const Vec2& fff = currResult.getPt2D().col(i); + ALICEVISION_LOG_DEBUG("\tfeatID " << feature::EImageDescriberType_enumToString(p.descType) << " " << p.featId << " " + << fff.transpose()); + } + } + continue; + } + + // the 3D point exists already, add the observation + landmark.observations[viewID] = sfmData::Observation(feature, match.featId, unknownScale); + } + else + { + // create a new 3D point + sfmData::Landmark newLandmark; + newLandmark.descType = match.descType; + newLandmark.X = currResult.getPt3D().col(idx); + newLandmark.observations[viewID] = sfmData::Observation(feature, match.featId, unknownScale); + tinyScene.getLandmarks()[match.landmarkId] = std::move(newLandmark); + } + } } - -// ALICEVISION_LOG_DEBUG("\n*****\nView " << viewID); - // view - std::shared_ptr view = std::make_shared("",viewID, intrinsicID, viewID); - tinyScene.getViews().insert( std::make_pair(viewID, view)); - // pose - tinyScene.setPose(*view, sfmData::CameraPose(currResult.getPose())); - - - if(!allTheSameIntrinsics) + + // { + // ALICEVISION_LOG_DEBUG("Number of 3D-2D associations before filtering " << tinyScene.getLandmarks().size()); + // sfmData::Landmarks &landmarks = tinyScene.getLandmarks(); + // for(sfmData::Landmarks::iterator it = landmarks.begin(), ite = landmarks.end(); it != ite;) + // { + // if(it->second.observations.size() < 5) + // { + // it = landmarks.erase(it); + // } + // else + // ++it; + // } + // } + { - camera::Pinhole* currIntrinsics = &currResult.getIntrinsics(); - // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) - tinyScene.getIntrinsics().emplace(intrinsicID, std::shared_ptr(currIntrinsics, [](camera::Pinhole*){})); - ++intrinsicID; + // just debugging some stats -- this block can be safely removed/commented out + + ALICEVISION_LOG_DEBUG("Number of 3D-2D associations " << tinyScene.getLandmarks().size()); + + std::size_t maxObs = 0; + for (const auto landmark : tinyScene.getLandmarks()) + { + if (landmark.second.observations.size() > maxObs) + maxObs = landmark.second.observations.size(); + } + namespace bacc = boost::accumulators; + bacc::accumulator_set> stats; + std::vector hist(maxObs + 1, 0); + for (const auto landmark : tinyScene.getLandmarks()) + { + const std::size_t nobs = landmark.second.observations.size(); + assert(nobs < hist.size()); + stats(nobs); + hist[nobs]++; + } + ALICEVISION_LOG_DEBUG("Min number of observations per point: " << bacc::min(stats)); + ALICEVISION_LOG_DEBUG("Mean number of observations per point: " << bacc::mean(stats)); + ALICEVISION_LOG_DEBUG("Max number of observations per point: " << bacc::max(stats)); + + std::size_t cumulative = 0; + const std::size_t num3DPoints = tinyScene.getLandmarks().size(); + for (std::size_t i = 0; i < hist.size(); i++) + { + ALICEVISION_LOG_DEBUG("Points with " << i << " observations: " << hist[i] + << " (cumulative in %: " << 100 * (num3DPoints - cumulative) / float(num3DPoints) << ")"); + cumulative += hist[i]; + } + + // just debugging stuff + if (allTheSameIntrinsics) + { + std::vector params = tinyScene.getIntrinsics().at(0).get()->getParams(); + ALICEVISION_LOG_DEBUG("K before bundle: " << params[0] << " " << params[1] << " " << params[2]); + if (params.size() == 6) + ALICEVISION_LOG_DEBUG("Distortion before bundle: " << params[3] << " " << params[4] << " " << params[5]); + } } - - // structure data (2D-3D correspondences) - const std::vector &matches = currResult.getIndMatch3D2D(); - - for(const size_t idx : currResult.getInliers() ) + + // filter out the 3D points having too few observations. + if (minPointVisibility > 0) { - // the idx should be in the size range of the data - assert(idx < currResult.getPt3D().cols()); - const IndMatch3D2D& match = matches[idx]; -// ALICEVISION_LOG_DEBUG("inlier " << idx << " is land " << landmarkID << " and feat " << featID); - // get the corresponding feature - const Vec2 &feature = currResult.getPt2D().col(idx); - // check if the point exists already - if(tinyScene.getLandmarks().count(match.landmarkId)) - { - sfmData::Landmark& landmark = tinyScene.getLandmarks().at(match.landmarkId); - assert(landmark.descType == match.descType); - // normally there should be no other features already associated to this - // 3D point in this view - if(landmark.observations.count(viewID) != 0) + auto& landmarks = tinyScene.getLandmarks(); + auto iter = landmarks.begin(); + auto endIter = landmarks.end(); + + for (; iter != endIter;) { - // this is weird but it could happen when two features are really close to each other (?) - ALICEVISION_LOG_DEBUG("Point 3D " << match.landmarkId << " has multiple features " - << "in the same view " << viewID << ", current size of obs: " - << landmark.observations.size() ); - ALICEVISION_LOG_DEBUG("its associated features are: "); - for(std::size_t i = 0; i < matches.size(); ++i) - { - auto const &p = matches[i]; - if(p.landmarkId == match.landmarkId) + if (iter->second.observations.size() < minPointVisibility) { - assert(p.descType == match.descType); - const Vec2 &fff = currResult.getPt2D().col(i); - ALICEVISION_LOG_DEBUG("\tfeatID " << feature::EImageDescriberType_enumToString(p.descType) << " " << p.featId << " " << fff.transpose()); + iter = landmarks.erase(iter); + } + else + { + ++iter; } - } - continue; } - - // the 3D point exists already, add the observation - landmark.observations[viewID] = sfmData::Observation(feature, match.featId, unknownScale); - } - else - { - // create a new 3D point - sfmData::Landmark newLandmark; - newLandmark.descType = match.descType; - newLandmark.X = currResult.getPt3D().col(idx); - newLandmark.observations[viewID] = sfmData::Observation(feature, match.featId, unknownScale); - tinyScene.getLandmarks()[match.landmarkId] = std::move(newLandmark); - } } - } - -// { -// ALICEVISION_LOG_DEBUG("Number of 3D-2D associations before filtering " << tinyScene.getLandmarks().size()); -// sfmData::Landmarks &landmarks = tinyScene.getLandmarks(); -// for(sfmData::Landmarks::iterator it = landmarks.begin(), ite = landmarks.end(); it != ite;) -// { -// if(it->second.observations.size() < 5) -// { -// it = landmarks.erase(it); -// } -// else -// ++it; -// } -// } - - { - // just debugging some stats -- this block can be safely removed/commented out - - ALICEVISION_LOG_DEBUG("Number of 3D-2D associations " << tinyScene.getLandmarks().size()); - - std::size_t maxObs = 0; - for(const auto landmark : tinyScene.getLandmarks() ) - { - if(landmark.second.observations.size() > maxObs) - maxObs = landmark.second.observations.size(); - } - namespace bacc = boost::accumulators; - bacc::accumulator_set > stats; - std::vector hist(maxObs+1, 0); - for(const auto landmark : tinyScene.getLandmarks() ) + + if (!outputFilename.empty()) { - const std::size_t nobs = landmark.second.observations.size(); - assert(nobs < hist.size()); - stats(nobs); - hist[nobs]++; + const std::string outfile = outputFilename + ".BEFORE.json"; + if (!sfmDataIO::Save(tinyScene, outfile, sfmDataIO::ESfMData::ALL)) + ALICEVISION_CERR("Could not save " << outfile); } - ALICEVISION_LOG_DEBUG("Min number of observations per point: " << bacc::min(stats) ); - ALICEVISION_LOG_DEBUG("Mean number of observations per point: " << bacc::mean(stats) ); - ALICEVISION_LOG_DEBUG("Max number of observations per point: " << bacc::max(stats) ); - - std::size_t cumulative = 0; - const std::size_t num3DPoints = tinyScene.getLandmarks().size(); - for(std::size_t i = 0; i < hist.size(); i++ ) + + sfm::BundleAdjustmentCeres bundle_adjustment_obj; + sfm::BundleAdjustment::ERefineOptions refineOptions = sfm::BundleAdjustment::REFINE_NONE; + if (b_refine_pose) + refineOptions |= sfm::BundleAdjustment::REFINE_ROTATION | sfm::BundleAdjustment::REFINE_TRANSLATION; + if (b_refine_intrinsic) + refineOptions |= sfm::BundleAdjustment::REFINE_INTRINSICS_ALL; + if (b_refine_structure) + refineOptions |= sfm::BundleAdjustment::REFINE_STRUCTURE; + + const bool b_BA_Status = bundle_adjustment_obj.adjust(tinyScene, refineOptions); + if (b_BA_Status) { - ALICEVISION_LOG_DEBUG("Points with " << i << " observations: " << hist[i] - << " (cumulative in %: " << 100*(num3DPoints-cumulative)/float(num3DPoints) << ")"); - cumulative += hist[i]; + // get back the results and update the localization result with the refined pose + for (const auto& pose : tinyScene.getPoses()) + { + const IndexT idPose = pose.first; + vec_localizationResult[idPose].setPose(pose.second.getTransform()); + } + + if (!outputFilename.empty()) + { + const std::string outfile = outputFilename + ".AFTER.json"; + if (!sfmDataIO::Save(tinyScene, outfile, sfmDataIO::ESfMData::ALL)) + ALICEVISION_CERR("Could not save " << outfile); + } } - // just debugging stuff - if(allTheSameIntrinsics) + if (allTheSameIntrinsics) { - std::vector params = tinyScene.getIntrinsics().at(0).get()->getParams(); - ALICEVISION_LOG_DEBUG("K before bundle: " << params[0] << " " << params[1] << " "<< params[2]); - if(params.size() == 6) - ALICEVISION_LOG_DEBUG("Distortion before bundle: " << params[3] << " " << params[4] << " "<< params[5]); - } - } + // if we used the same intrinsics for all the localization results we need to + // update the intrinsics of each localization result - // filter out the 3D points having too few observations. - if(minPointVisibility > 0) - { - auto &landmarks = tinyScene.getLandmarks(); - auto iter = landmarks.begin(); - auto endIter = landmarks.end(); + // get its optimized parameters + std::vector params = tinyScene.getIntrinsics().at(0).get()->getParams(); + ALICEVISION_LOG_DEBUG("Type of intrinsics " << tinyScene.getIntrinsics().at(0).get()->getType()); + if (params.size() == 4) + { + // this means that the b_no_distortion has been passed + // set distortion to 0 + params.push_back(0); + params.push_back(0); + params.push_back(0); + } + assert(params.size() == 6); + ALICEVISION_LOG_DEBUG("K after bundle: " << params[0] << " " << params[1] << " " << params[2] << " " << params[3]); + ALICEVISION_LOG_DEBUG("Distortion after bundle " << params[4] << " " << params[5] << " " << params[6]); - for(; iter != endIter;) - { - if(iter->second.observations.size() < minPointVisibility) - { - iter = landmarks.erase(iter); - } - else - { - ++iter; - } - } - } - - if(!outputFilename.empty()) - { - const std::string outfile = outputFilename+".BEFORE.json"; - if(!sfmDataIO::Save(tinyScene, outfile, sfmDataIO::ESfMData::ALL)) - ALICEVISION_CERR("Could not save " << outfile); - } - - sfm::BundleAdjustmentCeres bundle_adjustment_obj; - sfm::BundleAdjustment::ERefineOptions refineOptions = sfm::BundleAdjustment::REFINE_NONE; - if(b_refine_pose) - refineOptions |= sfm::BundleAdjustment::REFINE_ROTATION | sfm::BundleAdjustment::REFINE_TRANSLATION; - if(b_refine_intrinsic) - refineOptions |= sfm::BundleAdjustment::REFINE_INTRINSICS_ALL; - if(b_refine_structure) - refineOptions |= sfm::BundleAdjustment::REFINE_STRUCTURE; - - const bool b_BA_Status = bundle_adjustment_obj.adjust(tinyScene, refineOptions); - if(b_BA_Status) - { - // get back the results and update the localization result with the refined pose - for(const auto &pose : tinyScene.getPoses()) - { - const IndexT idPose = pose.first; - vec_localizationResult[idPose].setPose(pose.second.getTransform()); + // update the intrinsics of the each localization result + for (size_t viewID = 0; viewID < numViews; ++viewID) + { + LocalizationResult& currResult = vec_localizationResult[viewID]; + if (!currResult.isValid()) + { + continue; + } + currResult.updateIntrinsics(params); + + // just debugging -- print reprojection errors + const Mat2X residuals = currResult.computeInliersResiduals(); + + const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + ALICEVISION_LOG_DEBUG("View " << viewID << " RMSE = " << std::sqrt(sqrErrors.mean()) << " min = " << std::sqrt(sqrErrors.minCoeff()) + << " mean = " << std::sqrt(sqrErrors.mean()) << " max = " << std::sqrt(sqrErrors.maxCoeff()) + << " threshold = " << currResult.getMaxReprojectionError()); + } } - if(!outputFilename.empty()) + return b_BA_Status; +} + +bool refineRigPose(const std::vector& vec_subPoses, + const std::vector& vec_localizationResults, + geometry::Pose3& rigPose) +{ + const std::size_t numCameras = vec_localizationResults.size(); + assert(vec_subPoses.size() == numCameras - 1); + + ceres::Problem problem; + + const aliceVision::Mat3& R = rigPose.rotation(); + const aliceVision::Vec3& t = rigPose.translation(); + + double mainPose[6]; + ceres::RotationMatrixToAngleAxis((const double*)R.data(), mainPose); + + mainPose[3] = t(0); + mainPose[4] = t(1); + mainPose[5] = t(2); + problem.AddParameterBlock(mainPose, 6); + + // Set a LossFunction to be less penalized by false measurements + // - set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* p_LossFunction = nullptr; // new ceres::HuberLoss(Square(4.0)); + // todo: make the LOSS function and the parameter an option + + // For all visibility add reprojections errors: + for (std::size_t iLocalizer = 0; iLocalizer < numCameras; ++iLocalizer) { - const std::string outfile = outputFilename+".AFTER.json"; - if(!sfmDataIO::Save(tinyScene, outfile, sfmDataIO::ESfMData::ALL)) - ALICEVISION_CERR("Could not save " << outfile); + const localization::LocalizationResult& localizationResult = vec_localizationResults[iLocalizer]; + + if (!localizationResult.isValid()) + { + ALICEVISION_LOG_DEBUG("Skipping camera " << iLocalizer << " as it has not been localized"); + continue; + } + // Get the inliers 3D points + const Mat& points3D = localizationResult.getPt3D(); + // Get their image locations (also referred as observations) + const Mat& points2D = localizationResult.getPt2D(); + + // Add a residual block for all inliers + for (const IndexT iPoint : localizationResult.getInliers()) + { + assert(iPoint < points2D.cols()); + assert(iPoint < points3D.cols()); + + // Each Residual block takes a point and a camera as input and outputs a 2 + // dimensional residual. Internally, the cost function stores the observations + // and the 3D point and compares the reprojection against the observation. + ceres::CostFunction* cost_function; + + // Vector-2 residual, pose of the rig parameterized by 6 parameters + // + relative pose of the secondary camera parameterized by 6 parameters + + geometry::Pose3 subPose; + // if it is not the main camera (whose subpose is the identity) + if (iLocalizer != 0) + { + subPose = vec_subPoses[iLocalizer - 1]; + } + + cost_function = new ceres::AutoDiffCostFunction( + new rig::ResidualErrorSecondaryCameraFixedRelativeFunctor( + localizationResult.getIntrinsics(), points2D.col(iPoint), points3D.col(iPoint), subPose)); + + if (cost_function) + { + problem.AddResidualBlock(cost_function, p_LossFunction, mainPose); + } + else + { + ALICEVISION_CERR("Fail in adding residual block for the " << iLocalizer << " camera while adding point id " << iPoint); + } + } } - } - - if(allTheSameIntrinsics) - { - // if we used the same intrinsics for all the localization results we need to - // update the intrinsics of each localization result - - // get its optimized parameters - std::vector params = tinyScene.getIntrinsics().at(0).get()->getParams(); - ALICEVISION_LOG_DEBUG("Type of intrinsics " <getType()); - if(params.size() == 4) + + // Configure a BA engine and run it + // todo: Set the most appropriate options + aliceVision::sfm::BundleAdjustmentCeres::CeresOptions aliceVision_options; // Set all + // the options field in our owm struct - unnecessary dependancy to aliceVision here + + ceres::Solver::Options options; + + options.preconditioner_type = aliceVision_options.preconditionerType; + options.linear_solver_type = aliceVision_options.linearSolverType; + options.sparse_linear_algebra_library_type = aliceVision_options.sparseLinearAlgebraLibraryType; + options.minimizer_progress_to_stdout = aliceVision_options.verbose; + options.logging_type = ceres::SILENT; + options.num_threads = 1; // aliceVision_options._nbThreads; +#if CERES_VERSION_MAJOR < 2 + options.num_linear_solver_threads = 1; // aliceVision_options._nbThreads; +#endif + + // Solve BA + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + if (aliceVision_options.summary) + ALICEVISION_LOG_DEBUG(summary.FullReport()); + + // If no error, get back refined parameters + if (!summary.IsSolutionUsable()) { - // this means that the b_no_distortion has been passed - // set distortion to 0 - params.push_back(0); - params.push_back(0); - params.push_back(0); + if (aliceVision_options.verbose) + ALICEVISION_CERR("Bundle Adjustment failed."); + return false; } - assert(params.size() == 6); - ALICEVISION_LOG_DEBUG("K after bundle: " << params[0] << " " << params[1] << " "<< params[2] << " "<< params[3]); - ALICEVISION_LOG_DEBUG("Distortion after bundle " << params[4] << " " << params[5] << " "<< params[6]); - // update the intrinsics of the each localization result - for(size_t viewID = 0; viewID < numViews; ++viewID) + if (aliceVision_options.verbose) { - LocalizationResult &currResult = vec_localizationResult[viewID]; - if(!currResult.isValid()) - { - continue; - } - currResult.updateIntrinsics(params); - - // just debugging -- print reprojection errors - const Mat2X residuals = currResult.computeInliersResiduals(); - - const auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - ALICEVISION_LOG_DEBUG("View " << viewID << " RMSE = " << std::sqrt(sqrErrors.mean()) - << " min = " << std::sqrt(sqrErrors.minCoeff()) - << " mean = " << std::sqrt(sqrErrors.mean()) - << " max = " << std::sqrt(sqrErrors.maxCoeff()) - << " threshold = " << currResult.getMaxReprojectionError()); + // Display statistics about the minimization + ALICEVISION_LOG_DEBUG("Bundle Adjustment statistics (approximated RMSE"); + ALICEVISION_LOG_DEBUG(" #localizers: " << vec_localizationResults.size()); + ALICEVISION_LOG_DEBUG(" #residuals: " << summary.num_residuals); + ALICEVISION_LOG_DEBUG(" Initial RMSE: " << std::sqrt(summary.initial_cost / summary.num_residuals)); + ALICEVISION_LOG_DEBUG(" Final RMSE: " << std::sqrt(summary.final_cost / summary.num_residuals)); } - - } - - return b_BA_Status; + + // update the rigPose + aliceVision::Mat3 R_refined; + ceres::AngleAxisToRotationMatrix(mainPose, R_refined.data()); + aliceVision::Vec3 t_refined(mainPose[3], mainPose[4], mainPose[5]); + // Push the optimized pose + rigPose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); + + // displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); + + // @todo do we want to update pose inside the LocalizationResults + + return true; } -bool refineRigPose(const std::vector &vec_subPoses, - const std::vector & vec_localizationResults, - geometry::Pose3 & rigPose) +bool refineRigPose(const std::vector& pts2d, + const std::vector& pts3d, + const std::vector>& inliers, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose) { - const std::size_t numCameras = vec_localizationResults.size(); - assert(vec_subPoses.size() == numCameras - 1); - - ceres::Problem problem; - - const aliceVision::Mat3 & R = rigPose.rotation(); - const aliceVision::Vec3 & t = rigPose.translation(); - - double mainPose[6]; - ceres::RotationMatrixToAngleAxis((const double*)R.data(), mainPose); - - mainPose[3] = t(0); - mainPose[4] = t(1); - mainPose[5] = t(2); - problem.AddParameterBlock(mainPose, 6); - - - // Set a LossFunction to be less penalized by false measurements - // - set it to NULL if you don't want use a lossFunction. - ceres::LossFunction * p_LossFunction = nullptr;//new ceres::HuberLoss(Square(4.0)); - // todo: make the LOSS function and the parameter an option - - // For all visibility add reprojections errors: - for(std::size_t iLocalizer = 0; iLocalizer < numCameras; ++iLocalizer) - { - const localization::LocalizationResult & localizationResult = vec_localizationResults[iLocalizer]; - - if(!localizationResult.isValid()) - { - ALICEVISION_LOG_DEBUG("Skipping camera " << iLocalizer << " as it has not been localized"); - continue; - } - // Get the inliers 3D points - const Mat & points3D = localizationResult.getPt3D(); - // Get their image locations (also referred as observations) - const Mat & points2D = localizationResult.getPt2D(); + const size_t numCameras = pts2d.size(); + assert(pts3d.size() == numCameras); + assert(vec_queryIntrinsics.size() == numCameras); + assert(inliers.size() == numCameras); + assert(vec_subPoses.size() == numCameras - 1); + + ceres::Problem problem; + + const aliceVision::Mat3& R = rigPose.rotation(); + const aliceVision::Vec3& t = rigPose.translation(); + + double mainPose[6]; + ceres::RotationMatrixToAngleAxis((const double*)R.data(), mainPose); + + mainPose[3] = t(0); + mainPose[4] = t(1); + mainPose[5] = t(2); + problem.AddParameterBlock(mainPose, 6); + + // Set a LossFunction to be less penalized by false measurements + // - set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* p_LossFunction = nullptr; // new ceres::HuberLoss(Square(4.0)); + // todo: make the LOSS function and the parameter an option - // Add a residual block for all inliers - for(const IndexT iPoint : localizationResult.getInliers()) + // For all visibility add reprojections errors: + for (size_t cam = 0; cam < numCameras; ++cam) { - assert(iPoint < points2D.cols()); - assert(iPoint < points3D.cols()); - - // Each Residual block takes a point and a camera as input and outputs a 2 - // dimensional residual. Internally, the cost function stores the observations - // and the 3D point and compares the reprojection against the observation. - ceres::CostFunction* cost_function; - - // Vector-2 residual, pose of the rig parameterized by 6 parameters - // + relative pose of the secondary camera parameterized by 6 parameters - - geometry::Pose3 subPose; - // if it is not the main camera (whose subpose is the identity) - if(iLocalizer != 0) - { - subPose = vec_subPoses[iLocalizer - 1]; - } - - cost_function = new ceres::AutoDiffCostFunction( - new rig::ResidualErrorSecondaryCameraFixedRelativeFunctor(localizationResult.getIntrinsics(), - points2D.col(iPoint), - points3D.col(iPoint), - subPose)); - - if(cost_function) - { - problem.AddResidualBlock(cost_function, - p_LossFunction, - mainPose); - } - else - { - ALICEVISION_CERR("Fail in adding residual block for the " << iLocalizer - << " camera while adding point id " << iPoint); - } + // Get the inliers 3D points + const Mat& points3D = pts3d[cam]; + assert(points3D.rows() == 3); + // Get their image locations (also referred as observations) + const Mat& points2D = pts2d[cam]; + assert(points2D.rows() == 2); + + if (inliers[cam].empty()) + { + ALICEVISION_LOG_DEBUG("Skipping cam " << cam << " as it has no inliers"); + continue; + } + // Add a residual block for all inliers + for (const IndexT iPoint : inliers[cam]) + { + assert(iPoint < points2D.cols()); + assert(iPoint < points3D.cols()); + + // Each Residual block takes a point and a camera as input and outputs a 2 + // dimensional residual. Internally, the cost function stores the observations + // and the 3D point and compares the reprojection against the observation. + ceres::CostFunction* cost_function; + + // Vector-2 residual, pose of the rig parameterized by 6 parameters + // + relative pose of the secondary camera parameterized by 6 parameters + + geometry::Pose3 subPose; + // if it is not the main camera (whose subpose is the identity) + if (cam != 0) + { + subPose = vec_subPoses[cam - 1]; + } + + cost_function = new ceres::AutoDiffCostFunction( + new rig::ResidualErrorSecondaryCameraFixedRelativeFunctor( + vec_queryIntrinsics[cam], points2D.col(iPoint), points3D.col(iPoint), subPose)); + + if (cost_function) + { + problem.AddResidualBlock(cost_function, p_LossFunction, mainPose); + } + else + { + ALICEVISION_CERR("Fail in adding residual block for the " << cam << " camera while adding point id " << iPoint); + } + } } - } - - // Configure a BA engine and run it - // todo: Set the most appropriate options - aliceVision::sfm::BundleAdjustmentCeres::CeresOptions aliceVision_options; // Set all - // the options field in our owm struct - unnecessary dependancy to aliceVision here - - ceres::Solver::Options options; - - options.preconditioner_type = aliceVision_options.preconditionerType; - options.linear_solver_type = aliceVision_options.linearSolverType; - options.sparse_linear_algebra_library_type = aliceVision_options.sparseLinearAlgebraLibraryType; - options.minimizer_progress_to_stdout = aliceVision_options.verbose; - options.logging_type = ceres::SILENT; - options.num_threads = 1;//aliceVision_options._nbThreads; + + // Configure a BA engine and run it + // todo: Set the most appropriate options + aliceVision::sfm::BundleAdjustmentCeres::CeresOptions aliceVision_options; // Set all + // the options field in our owm struct - unnecessary dependancy to aliceVision here + + ceres::Solver::Options options; + + options.preconditioner_type = aliceVision_options.preconditionerType; + options.linear_solver_type = aliceVision_options.linearSolverType; + options.sparse_linear_algebra_library_type = aliceVision_options.sparseLinearAlgebraLibraryType; + options.minimizer_progress_to_stdout = true; + // options.logging_type = ceres::SILENT; + options.num_threads = 1; // aliceVision_options._nbThreads; #if CERES_VERSION_MAJOR < 2 - options.num_linear_solver_threads = 1;//aliceVision_options._nbThreads; + options.num_linear_solver_threads = 1; // aliceVision_options._nbThreads; #endif - - // Solve BA - ceres::Solver::Summary summary; - ceres::Solve(options, &problem, &summary); - - if (aliceVision_options.summary) - ALICEVISION_LOG_DEBUG(summary.FullReport()); - - // If no error, get back refined parameters - if (!summary.IsSolutionUsable()) - { - if (aliceVision_options.verbose) - ALICEVISION_CERR("Bundle Adjustment failed."); - return false; - } - - if(aliceVision_options.verbose) - { - // Display statistics about the minimization - ALICEVISION_LOG_DEBUG("Bundle Adjustment statistics (approximated RMSE"); - ALICEVISION_LOG_DEBUG(" #localizers: " << vec_localizationResults.size()); - ALICEVISION_LOG_DEBUG(" #residuals: " << summary.num_residuals); - ALICEVISION_LOG_DEBUG(" Initial RMSE: " << std::sqrt(summary.initial_cost / summary.num_residuals)); - ALICEVISION_LOG_DEBUG(" Final RMSE: " << std::sqrt(summary.final_cost / summary.num_residuals)); - } - - // update the rigPose - aliceVision::Mat3 R_refined; - ceres::AngleAxisToRotationMatrix(mainPose, R_refined.data()); - aliceVision::Vec3 t_refined(mainPose[3], mainPose[4], mainPose[5]); - // Push the optimized pose - rigPose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); - -// displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); - - // @todo do we want to update pose inside the LocalizationResults - - return true; -} -bool refineRigPose(const std::vector &pts2d, - const std::vector &pts3d, - const std::vector > &inliers, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose) -{ - const size_t numCameras = pts2d.size(); - assert(pts3d.size() == numCameras); - assert(vec_queryIntrinsics.size() == numCameras); - assert(inliers.size() == numCameras); - assert(vec_subPoses.size() == numCameras - 1); - - ceres::Problem problem; - - const aliceVision::Mat3 & R = rigPose.rotation(); - const aliceVision::Vec3 & t = rigPose.translation(); - - double mainPose[6]; - ceres::RotationMatrixToAngleAxis((const double*)R.data(), mainPose); - - mainPose[3] = t(0); - mainPose[4] = t(1); - mainPose[5] = t(2); - problem.AddParameterBlock(mainPose, 6); - - - // Set a LossFunction to be less penalized by false measurements - // - set it to NULL if you don't want use a lossFunction. - ceres::LossFunction * p_LossFunction = nullptr;//new ceres::HuberLoss(Square(4.0)); - // todo: make the LOSS function and the parameter an option - - // For all visibility add reprojections errors: - for(size_t cam = 0; cam < numCameras; ++cam) - { - - // Get the inliers 3D points - const Mat & points3D = pts3d[cam]; - assert(points3D.rows() == 3); - // Get their image locations (also referred as observations) - const Mat & points2D = pts2d[cam]; - assert(points2D.rows() == 2); - - if(inliers[cam].empty()) + // Solve BA + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + if (aliceVision_options.summary) + ALICEVISION_LOG_DEBUG(summary.FullReport()); + + // If no error, get back refined parameters + if (!summary.IsSolutionUsable()) { - ALICEVISION_LOG_DEBUG("Skipping cam " << cam << " as it has no inliers"); - continue; + if (aliceVision_options.verbose) + ALICEVISION_LOG_DEBUG("Bundle Adjustment failed."); + return false; } - // Add a residual block for all inliers - for(const IndexT iPoint : inliers[cam]) + + if (aliceVision_options.verbose) { - assert(iPoint < points2D.cols()); - assert(iPoint < points3D.cols()); - - // Each Residual block takes a point and a camera as input and outputs a 2 - // dimensional residual. Internally, the cost function stores the observations - // and the 3D point and compares the reprojection against the observation. - ceres::CostFunction* cost_function; - - // Vector-2 residual, pose of the rig parameterized by 6 parameters - // + relative pose of the secondary camera parameterized by 6 parameters - - geometry::Pose3 subPose; - // if it is not the main camera (whose subpose is the identity) - if(cam != 0) - { - subPose = vec_subPoses[cam - 1]; - } - - cost_function = new ceres::AutoDiffCostFunction( - new rig::ResidualErrorSecondaryCameraFixedRelativeFunctor(vec_queryIntrinsics[cam], - points2D.col(iPoint), - points3D.col(iPoint), - subPose)); - - if(cost_function) - { - problem.AddResidualBlock(cost_function, - p_LossFunction, - mainPose); - } - else - { - ALICEVISION_CERR("Fail in adding residual block for the " << cam - << " camera while adding point id " << iPoint); - } + // Display statistics about the minimization + ALICEVISION_LOG_DEBUG("Bundle Adjustment statistics (approximated RMSE):\n" + " #cameras: " + << numCameras + << "\n" + " #residuals: " + << summary.num_residuals + << "\n" + " Initial RMSE: " + << std::sqrt(summary.initial_cost / summary.num_residuals) + << "\n" + " Final RMSE: " + << std::sqrt(summary.final_cost / summary.num_residuals)); } - } - - // Configure a BA engine and run it - // todo: Set the most appropriate options - aliceVision::sfm::BundleAdjustmentCeres::CeresOptions aliceVision_options; // Set all - // the options field in our owm struct - unnecessary dependancy to aliceVision here - - ceres::Solver::Options options; - - options.preconditioner_type = aliceVision_options.preconditionerType; - options.linear_solver_type = aliceVision_options.linearSolverType; - options.sparse_linear_algebra_library_type = aliceVision_options.sparseLinearAlgebraLibraryType; - options.minimizer_progress_to_stdout = true; - //options.logging_type = ceres::SILENT; - options.num_threads = 1;//aliceVision_options._nbThreads; -#if CERES_VERSION_MAJOR < 2 - options.num_linear_solver_threads = 1;//aliceVision_options._nbThreads; -#endif - - // Solve BA - ceres::Solver::Summary summary; - ceres::Solve(options, &problem, &summary); - - if (aliceVision_options.summary) - ALICEVISION_LOG_DEBUG(summary.FullReport()); - - // If no error, get back refined parameters - if (!summary.IsSolutionUsable()) - { - if (aliceVision_options.verbose) - ALICEVISION_LOG_DEBUG("Bundle Adjustment failed."); - return false; - } - - if(aliceVision_options.verbose) - { - // Display statistics about the minimization - ALICEVISION_LOG_DEBUG( - "Bundle Adjustment statistics (approximated RMSE):\n" - " #cameras: " << numCameras << "\n" - " #residuals: " << summary.num_residuals << "\n" - " Initial RMSE: " << std::sqrt(summary.initial_cost / summary.num_residuals) << "\n" - " Final RMSE: " << std::sqrt(summary.final_cost / summary.num_residuals) - ); - } - - // update the rigPose - aliceVision::Mat3 R_refined; - ceres::AngleAxisToRotationMatrix(mainPose, R_refined.data()); - aliceVision::Vec3 t_refined(mainPose[3], mainPose[4], mainPose[5]); - // Push the optimized pose - rigPose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); - -// displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); - - // @todo do we want to update pose inside the LocalizationResults - - return true; + + // update the rigPose + aliceVision::Mat3 R_refined; + ceres::AngleAxisToRotationMatrix(mainPose, R_refined.data()); + aliceVision::Vec3 t_refined(mainPose[3], mainPose[4], mainPose[5]); + // Push the optimized pose + rigPose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); + + // displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); + + // @todo do we want to update pose inside the LocalizationResults + + return true; } // rmse, min, max -std::tuple computeStatistics(const Mat &pts2D, - const Mat &pts3D, - const camera::Pinhole &currCamera, - const std::vector &currInliers, - const geometry::Pose3 &subPoses, - const geometry::Pose3 &rigPose) +std::tuple computeStatistics(const Mat& pts2D, + const Mat& pts3D, + const camera::Pinhole& currCamera, + const std::vector& currInliers, + const geometry::Pose3& subPoses, + const geometry::Pose3& rigPose) { - if(currInliers.empty()) - return std::make_tuple(0., 0., 0.); - - Mat2X residuals = currCamera.residuals(subPoses*rigPose, pts3D, pts2D); - - Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); - - // ALICEVISION_LOG_DEBUG("Camera " << camID << " all reprojection errors:"); - // ALICEVISION_LOG_DEBUG(sqrErrors); - // - // ALICEVISION_LOG_DEBUG("Camera " << camID << " inliers reprojection errors:"); - - double rmse = 0; - double rmseMin = std::numeric_limits::max(); - double rmseMax = 0; - for(std::size_t j = 0; j < currInliers.size(); ++j) - { - // ALICEVISION_LOG_DEBUG(sqrErrors(currInliers[j])); - const double err = sqrErrors(currInliers[j]); - rmse += err; - if(err > rmseMax) - rmseMax = err; - if(err < rmseMin) - rmseMin = err; - } - - return std::make_tuple(std::sqrt(rmse / currInliers.size()), std::sqrt(rmseMin), std::sqrt(rmseMax)); + if (currInliers.empty()) + return std::make_tuple(0., 0., 0.); + + Mat2X residuals = currCamera.residuals(subPoses * rigPose, pts3D, pts2D); + + Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + + // ALICEVISION_LOG_DEBUG("Camera " << camID << " all reprojection errors:"); + // ALICEVISION_LOG_DEBUG(sqrErrors); + // + // ALICEVISION_LOG_DEBUG("Camera " << camID << " inliers reprojection errors:"); + + double rmse = 0; + double rmseMin = std::numeric_limits::max(); + double rmseMax = 0; + for (std::size_t j = 0; j < currInliers.size(); ++j) + { + // ALICEVISION_LOG_DEBUG(sqrErrors(currInliers[j])); + const double err = sqrErrors(currInliers[j]); + rmse += err; + if (err > rmseMax) + rmseMax = err; + if (err < rmseMin) + rmseMin = err; + } + + return std::make_tuple(std::sqrt(rmse / currInliers.size()), std::sqrt(rmseMin), std::sqrt(rmseMax)); } -void printRigRMSEStats(const std::vector &vec_pts2D, - const std::vector &vec_pts3D, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - const geometry::Pose3 &rigPose, - const std::vector > &vec_inliers) +void printRigRMSEStats(const std::vector& vec_pts2D, + const std::vector& vec_pts3D, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + const geometry::Pose3& rigPose, + const std::vector>& vec_inliers) { - const std::size_t numCams = vec_pts2D.size(); - assert(numCams == vec_pts3D.size()); - assert(numCams == vec_queryIntrinsics.size()); - assert(numCams == vec_subPoses.size() + 1); - - // compute the reprojection error for inliers (just debugging purposes) - double totalRMSE = 0; - std::size_t totalInliers = 0; - for(std::size_t camID = 0; camID < numCams; ++camID) - { - if(!vec_inliers[camID].empty()) + const std::size_t numCams = vec_pts2D.size(); + assert(numCams == vec_pts3D.size()); + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); + + // compute the reprojection error for inliers (just debugging purposes) + double totalRMSE = 0; + std::size_t totalInliers = 0; + for (std::size_t camID = 0; camID < numCams; ++camID) { - const auto& stats = computeStatistics(vec_pts2D[camID], - vec_pts3D[camID], - vec_queryIntrinsics[camID], - vec_inliers[camID], - (camID != 0 ) ? vec_subPoses[camID-1] : geometry::Pose3(), - rigPose); - ALICEVISION_LOG_DEBUG("\nCam #" << camID - << " RMSE inliers: " << std::get<0>(stats) - << " min: " << std::get<1>(stats) - << " max: " << std::get<2>(stats)); - - totalRMSE += Square(std::get<0>(stats))*vec_inliers[camID].size(); - totalInliers += vec_inliers[camID].size(); + if (!vec_inliers[camID].empty()) + { + const auto& stats = computeStatistics(vec_pts2D[camID], + vec_pts3D[camID], + vec_queryIntrinsics[camID], + vec_inliers[camID], + (camID != 0) ? vec_subPoses[camID - 1] : geometry::Pose3(), + rigPose); + ALICEVISION_LOG_DEBUG("\nCam #" << camID << " RMSE inliers: " << std::get<0>(stats) << " min: " << std::get<1>(stats) + << " max: " << std::get<2>(stats)); + + totalRMSE += Square(std::get<0>(stats)) * vec_inliers[camID].size(); + totalInliers += vec_inliers[camID].size(); + } } - } - ALICEVISION_LOG_DEBUG("Overall RMSE: " << std::sqrt(totalRMSE/totalInliers)); + ALICEVISION_LOG_DEBUG("Overall RMSE: " << std::sqrt(totalRMSE / totalInliers)); } -std::pair computeInliers(const std::vector &vec_pts2d, - const std::vector &vec_pts3d, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, +std::pair computeInliers(const std::vector& vec_pts2d, + const std::vector& vec_pts3d, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, double maxReprojectionError, - const geometry::Pose3 &rigPose, - std::vector > &vec_inliers) + const geometry::Pose3& rigPose, + std::vector>& vec_inliers) { - const std::size_t numCams = vec_pts2d.size(); - assert(numCams == vec_pts3d.size()); - assert(numCams == vec_queryIntrinsics.size()); - assert(numCams == vec_subPoses.size() + 1); - - const double squareThreshold = Square(maxReprojectionError); - - double rmse = 0; - std::size_t numInliers = 0; - // number of inlier removed - std::size_t numAdded = 0; - // number of point that were not inliers and now they are - std::size_t numRemoved = 0; - - std::vector > vec_newInliers(numCams); - - // compute the reprojection error for inliers (just debugging purposes) - for(std::size_t camID = 0; camID < numCams; ++camID) - { - const std::size_t numPts = vec_pts2d[camID].cols(); - - const camera::Pinhole &currCamera = vec_queryIntrinsics[camID]; - - Mat2X residuals; - if(camID != 0) - residuals = currCamera.residuals(vec_subPoses[camID - 1] * rigPose, vec_pts3d[camID], vec_pts2d[camID]); - else - residuals = currCamera.residuals(geometry::Pose3() * rigPose, vec_pts3d[camID], vec_pts2d[camID]); + const std::size_t numCams = vec_pts2d.size(); + assert(numCams == vec_pts3d.size()); + assert(numCams == vec_queryIntrinsics.size()); + assert(numCams == vec_subPoses.size() + 1); - Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + const double squareThreshold = Square(maxReprojectionError); + + double rmse = 0; + std::size_t numInliers = 0; + // number of inlier removed + std::size_t numAdded = 0; + // number of point that were not inliers and now they are + std::size_t numRemoved = 0; + + std::vector> vec_newInliers(numCams); - auto &currInliers = vec_newInliers[camID]; - const auto &oldInliers = vec_inliers[camID]; - currInliers.reserve(numPts); - - for(std::size_t i = 0; i < numPts; ++i) + // compute the reprojection error for inliers (just debugging purposes) + for (std::size_t camID = 0; camID < numCams; ++camID) { - // check whether the current point was an inlier - const auto occ = std::count(oldInliers.begin(), oldInliers.end(), i); - assert(occ == 0 || occ == 1); - - if(sqrErrors(i) < squareThreshold) - { - currInliers.push_back(i); - - // if it was not an inlier mark it as added one - if(occ == 0) - ++numAdded; - - rmse += sqrErrors(i); - ++numInliers; - } - else - { - // if it was an inlier mark it as removed one - if(occ == 1) - ++numRemoved; - } + const std::size_t numPts = vec_pts2d[camID].cols(); + + const camera::Pinhole& currCamera = vec_queryIntrinsics[camID]; + + Mat2X residuals; + if (camID != 0) + residuals = currCamera.residuals(vec_subPoses[camID - 1] * rigPose, vec_pts3d[camID], vec_pts2d[camID]); + else + residuals = currCamera.residuals(geometry::Pose3() * rigPose, vec_pts3d[camID], vec_pts2d[camID]); + + Vec sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + + auto& currInliers = vec_newInliers[camID]; + const auto& oldInliers = vec_inliers[camID]; + currInliers.reserve(numPts); + + for (std::size_t i = 0; i < numPts; ++i) + { + // check whether the current point was an inlier + const auto occ = std::count(oldInliers.begin(), oldInliers.end(), i); + assert(occ == 0 || occ == 1); + + if (sqrErrors(i) < squareThreshold) + { + currInliers.push_back(i); + + // if it was not an inlier mark it as added one + if (occ == 0) + ++numAdded; + + rmse += sqrErrors(i); + ++numInliers; + } + else + { + // if it was an inlier mark it as removed one + if (occ == 1) + ++numRemoved; + } + } } - } - ALICEVISION_LOG_DEBUG("Removed " << numRemoved << " inliers, added new " << numAdded << " point"); - - // swap - vec_inliers.swap(vec_newInliers); - return std::make_pair(std::sqrt(rmse/numInliers), (numRemoved > 0 || numAdded > 0) ); -} + ALICEVISION_LOG_DEBUG("Removed " << numRemoved << " inliers, added new " << numAdded << " point"); + // swap + vec_inliers.swap(vec_newInliers); + return std::make_pair(std::sqrt(rmse / numInliers), (numRemoved > 0 || numAdded > 0)); +} -bool iterativeRefineRigPose(const std::vector &pts2d, - const std::vector &pts3d, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, +bool iterativeRefineRigPose(const std::vector& pts2d, + const std::vector& pts3d, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, double maxReprojectionError, std::size_t minNumPoints, - std::vector > &vec_inliers, - geometry::Pose3 &rigPose, + std::vector>& vec_inliers, + geometry::Pose3& rigPose, std::size_t maxIterationNumber) { - geometry::Pose3 optimalPose = rigPose; - std::size_t iterationNumber = 1; - bool hasChanged = false; - - do - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tIteration " << iterationNumber); - const bool refineOk = refineRigPose(pts2d, - pts3d, - vec_inliers, - vec_queryIntrinsics, - vec_subPoses, - optimalPose); - if(!refineOk) - { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tIterative refine rig pose failed"); - return false; - } - - // recompute the inliers and the RMSE - const auto& result = computeInliers(pts2d, - pts3d, - vec_queryIntrinsics, - vec_subPoses, - maxReprojectionError, - optimalPose, - vec_inliers); - // if there have been changes in the inlier sets - hasChanged = result.second; - - // if not enough inliers stop? - std::size_t numInliers = 0; - for(const auto& inliers : vec_inliers) - numInliers += inliers.size(); - - if(numInliers <= minNumPoints) + geometry::Pose3 optimalPose = rigPose; + std::size_t iterationNumber = 1; + bool hasChanged = false; + + do { - ALICEVISION_LOG_DEBUG("[poseEstimation]\tIterative refine rig pose has reached the minimum number of points"); - return false; - } - - ++iterationNumber; - if(iterationNumber > maxIterationNumber) - ALICEVISION_LOG_DEBUG("Terminating refine because the max number of iterations has been reached"); - } - while(hasChanged && iterationNumber <= maxIterationNumber); + ALICEVISION_LOG_DEBUG("[poseEstimation]\tIteration " << iterationNumber); + const bool refineOk = refineRigPose(pts2d, pts3d, vec_inliers, vec_queryIntrinsics, vec_subPoses, optimalPose); + if (!refineOk) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tIterative refine rig pose failed"); + return false; + } - rigPose = optimalPose; + // recompute the inliers and the RMSE + const auto& result = computeInliers(pts2d, pts3d, vec_queryIntrinsics, vec_subPoses, maxReprojectionError, optimalPose, vec_inliers); + // if there have been changes in the inlier sets + hasChanged = result.second; - return true; -} + // if not enough inliers stop? + std::size_t numInliers = 0; + for (const auto& inliers : vec_inliers) + numInliers += inliers.size(); + + if (numInliers <= minNumPoints) + { + ALICEVISION_LOG_DEBUG("[poseEstimation]\tIterative refine rig pose has reached the minimum number of points"); + return false; + } + + ++iterationNumber; + if (iterationNumber > maxIterationNumber) + ALICEVISION_LOG_DEBUG("Terminating refine because the max number of iterations has been reached"); + } while (hasChanged && iterationNumber <= maxIterationNumber); + rigPose = optimalPose; -} //namespace localization -} //namespace aliceVision + return true; +} +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/optimization.hpp b/src/aliceVision/localization/optimization.hpp index 55634ece5d..f93c6512af 100644 --- a/src/aliceVision/localization/optimization.hpp +++ b/src/aliceVision/localization/optimization.hpp @@ -14,45 +14,45 @@ #include #include -namespace aliceVision{ -namespace localization{ +namespace aliceVision { +namespace localization { /** * @brief Refine a sequence of camera positions and their camera intrinsics. * The camera parameters can change during the sequence and they are refined * in terms of K (principal point and focal length) and the distortion coefficients. - * The camera poses are refined as well. If \p allTheSameIntrinsics is provided + * The camera poses are refined as well. If \p allTheSameIntrinsics is provided * a single camera having constant internal parameters during the whole sequence is assumed. - * - * @param[in,out] vec_localizationResult The series of camera poses and point correspondences. + * + * @param[in,out] vec_localizationResult The series of camera poses and point correspondences. * @param[in] allTheSameIntrinsics If true all the intrinsics of the sequence are * assumed to be the same, ie a sequence in which the camrera parameters do not change. * @param[in] b_refine_intrinsic Whether to refine the camera parameters. * @param[in] b_no_distortion If b_refine_intrinsic is true, this allow to not consider - * the optical distortion, setting it to 0. + * the optical distortion, setting it to 0. * @param[in] b_refine_pose Whether to refine the camera poses. * @param[in] b_refine_structure Whether to refine the 3D points. - * @param[in] outputFilename If not empty, a filename (possibly with path) without - * extension where to save the scene before and after the bundle adjustment. The + * @param[in] outputFilename If not empty, a filename (possibly with path) without + * extension where to save the scene before and after the bundle adjustment. The * file will be named outputFilename.BEFORE.json and outputFilename.AFTER.json. - * @param[in] minPointVisibility if > 0 it allows to use only the 3D points that - * are seen in at least \p minPointVisibility views/frames, all the other + * @param[in] minPointVisibility if > 0 it allows to use only the 3D points that + * are seen in at least \p minPointVisibility views/frames, all the other * points (and associated 2D features) will be discarded. * @return true if the bundle adjustment has success. */ -bool refineSequence(std::vector & vec_localizationResult, +bool refineSequence(std::vector& vec_localizationResult, bool allTheSameIntrinsics = true, bool b_refine_intrinsic = true, bool b_no_distortion = false, bool b_refine_pose = true, bool b_refine_structure = false, - const std::string & outputFilename = "", + const std::string& outputFilename = "", std::size_t minPointVisibility = 0); /** * @brief refine the pose of a camera rig by minimizing the reprojection error in * each camera with the bundle adjustment. - * + * * @param[in] vec_subPoses The rig calibration, ie the pose of the cameras wrt the main * camera; for a rig of N cameras the size of the vector is N-1 as the main camera has * identity as pose. @@ -62,17 +62,17 @@ bool refineSequence(std::vector & vec_localizationResult, * adjustment succeeds. * @return true if the bundle adjustment succeeds. */ -bool refineRigPose(const std::vector &vec_subPoses, - const std::vector & vec_localizationResults, - geometry::Pose3 & rigPose); +bool refineRigPose(const std::vector& vec_subPoses, + const std::vector& vec_localizationResults, + geometry::Pose3& rigPose); /** * @brief refine the pose of a camera rig of N cameras by minimizing the reprojection error in * each camera with the bundle adjustment. - * - * @param[in] pts2d A vector of N 2xM matrices containing the image points of the + * + * @param[in] pts2d A vector of N 2xM matrices containing the image points of the * 2D-3D associations. - * @param[in] pts3d A vector of N 3xM matrices containing the image points of the + * @param[in] pts3d A vector of N 3xM matrices containing the image points of the * 2D-3D associations. * @param[in] inliers A vector of N vectors, each contining the inliers for the 2D-3D * associations. @@ -84,15 +84,15 @@ bool refineRigPose(const std::vector &vec_subPoses, * adjustment succeeds. * @return true if the bundle adjustment succeeds. */ -bool refineRigPose(const std::vector &pts2d, - const std::vector &pts3d, - const std::vector > &inliers, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - geometry::Pose3 &rigPose); +bool refineRigPose(const std::vector& pts2d, + const std::vector& pts3d, + const std::vector>& inliers, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + geometry::Pose3& rigPose); /** - * + * * @param pts2d * @param pts3d * @param vec_queryIntrinsics @@ -102,49 +102,49 @@ bool refineRigPose(const std::vector &pts2d, * @param vec_inliers * @param rigPose * @param maxIterationNumber - * @return + * @return */ -bool iterativeRefineRigPose(const std::vector &pts2d, - const std::vector &pts3d, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, +bool iterativeRefineRigPose(const std::vector& pts2d, + const std::vector& pts3d, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, double maxReprojectionError, std::size_t minNumPoints, - std::vector > &vec_inliers, - geometry::Pose3 &rigPose, + std::vector>& vec_inliers, + geometry::Pose3& rigPose, std::size_t maxIterationNumber = 10); -std::pair computeInliers(const std::vector &vec_pts2d, - const std::vector &vec_pts3d, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, +std::pair computeInliers(const std::vector& vec_pts2d, + const std::vector& vec_pts3d, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, double maxReprojectionError, - const geometry::Pose3 &rigPose, - std::vector > &vec_inliers); + const geometry::Pose3& rigPose, + std::vector>& vec_inliers); -void printRigRMSEStats(const std::vector &vec_pts2D, - const std::vector &vec_pts3D, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - const geometry::Pose3 &rigPose, - const std::vector > &vec_inliers); +void printRigRMSEStats(const std::vector& vec_pts2D, + const std::vector& vec_pts3D, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + const geometry::Pose3& rigPose, + const std::vector>& vec_inliers); /** - * + * * @param pts2d * @param pts3d * @param currCamera * @param currInliers * @param subPoses * @param rigPose - * @return + * @return */ -std::tuple computeStatistics(const Mat &pts2d, - const Mat &pts3d, - const camera::Pinhole &currCamera, - const std::vector &currInliers, - const geometry::Pose3 &subPoses, - const geometry::Pose3 &rigPose); +std::tuple computeStatistics(const Mat& pts2d, + const Mat& pts3d, + const camera::Pinhole& currCamera, + const std::vector& currInliers, + const geometry::Pose3& subPoses, + const geometry::Pose3& rigPose); -} //namespace localization -} //namespace aliceVision \ No newline at end of file +} // namespace localization +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/localization/reconstructed_regions.hpp b/src/aliceVision/localization/reconstructed_regions.hpp index e6f8deb102..bf39d6848c 100644 --- a/src/aliceVision/localization/reconstructed_regions.hpp +++ b/src/aliceVision/localization/reconstructed_regions.hpp @@ -19,23 +19,22 @@ namespace aliceVision { namespace localization { - struct ReconstructedRegionsMapping { - std::vector _associated3dPoint; - std::map _mapFullToLocal; + std::vector _associated3dPoint; + std::map _mapFullToLocal; }; - -inline std::unique_ptr createFilteredRegions(const feature::Regions& regions, const std::vector& featuresInImage, ReconstructedRegionsMapping& out_mapping) +inline std::unique_ptr createFilteredRegions(const feature::Regions& regions, + const std::vector& featuresInImage, + ReconstructedRegionsMapping& out_mapping) { - return regions.createFilteredRegions(featuresInImage, out_mapping._associated3dPoint, out_mapping._mapFullToLocal); + return regions.createFilteredRegions(featuresInImage, out_mapping._associated3dPoint, out_mapping._mapFullToLocal); } using ReconstructedRegionsMappingPerDesc = std::map; using ReconstructedRegionsMappingPerView = std::map; - -} // namespace feature -} // namespace aliceVision +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/rigResection.cpp b/src/aliceVision/localization/rigResection.cpp index ae5d2e885b..37e59c437c 100644 --- a/src/aliceVision/localization/rigResection.cpp +++ b/src/aliceVision/localization/rigResection.cpp @@ -9,11 +9,11 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include #endif #include @@ -21,192 +21,196 @@ #include #include -namespace aliceVision{ -namespace localization{ +namespace aliceVision { +namespace localization { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) -EstimationStatus rigResection(const std::vector &pts2d, - const std::vector &pts3d, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - const std::vector< std::vector > * descTypesPerCamera, - geometry::Pose3 &rigPose, - std::vector > &inliers, - double angularThreshold, - std::size_t maxIterations /*= 100*/, - bool verbosity /*= true*/) +EstimationStatus rigResection(const std::vector& pts2d, + const std::vector& pts3d, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + const std::vector>* descTypesPerCamera, + geometry::Pose3& rigPose, + std::vector>& inliers, + double angularThreshold, + std::size_t maxIterations /*= 100*/, + bool verbosity /*= true*/) { - const std::size_t numCameras = pts2d.size(); - // Note that the threshold to provide ot the ransac is expressed in cos(angle), - // more specifically it's the maximum angle allowed between the 3D direction of - // the feature point and the 3D direction of the associated 3D point. The reprojection - // error computed in the ransac is 1-cos(angle), where angle is the angle between - // the two directions. - const double threshold = 1-std::cos(angularThreshold); - - assert(pts3d.size() == numCameras); - assert(vec_queryIntrinsics.size() == numCameras); - // the subposes are always wrt the main camera - assert(vec_subPoses.size() == numCameras-1); - - - // Copy the subposes into separate vectors for rotations and centers - opengv::translations_t camOffsets(numCameras); //centers - opengv::rotations_t camRotations(numCameras); - - // the first is the identity (the main camera) - camOffsets[0] = Vec3::Zero(); - camRotations[0] = Mat3::Identity(); - // now copy the others - for(std::size_t i = 1; i < numCameras; ++i) - { - camOffsets[i] = vec_subPoses[i-1].center(); - // it takes as input a real pose, ie the rotation wrt the world frame - camRotations[i] = vec_subPoses[i-1].rotation().inverse(); - } - - // count the total number of associations - std::size_t numTotalPoints = 0; - for(std::size_t i = 0; i < numCameras; ++i) - { - const std::size_t numPts = pts2d[i].cols(); - assert(pts3d[i].cols() == numPts); - assert(pts3d[i].rows() == 3); - assert(pts2d[i].rows() == 2); - numTotalPoints += numPts; - } - // transform the points into bearingVectors - opengv::bearingVectors_t bearingVectors; // this contains the bearing vector associated to each image feature - opengv::points_t points; // this contains the 3d points - points.reserve(numTotalPoints); - std::vector camCorrespondences; // this is to keep track, for each bearing vector, whose camera it belongs - - // this map is used to remap the indices of the flatten structure containing - // the points (points, bearingvector) to the original local index of each pts2d/pts3d: - // eg - // localIdx = absoluteToLocal[absoluteID]; such that - // pts3d[localIdx.first].col(localIdx.second) == points[absoluteID]; - std::map> absoluteToLocal; - - - for(std::size_t cam = 0; cam < numCameras; ++cam) - { - const std::size_t numPts = pts2d[cam].cols(); - const camera::Pinhole &currCamera = vec_queryIntrinsics[cam]; - - for(std::size_t i = 0; i < numPts; ++i) + const std::size_t numCameras = pts2d.size(); + // Note that the threshold to provide ot the ransac is expressed in cos(angle), + // more specifically it's the maximum angle allowed between the 3D direction of + // the feature point and the 3D direction of the associated 3D point. The reprojection + // error computed in the ransac is 1-cos(angle), where angle is the angle between + // the two directions. + const double threshold = 1 - std::cos(angularThreshold); + + assert(pts3d.size() == numCameras); + assert(vec_queryIntrinsics.size() == numCameras); + // the subposes are always wrt the main camera + assert(vec_subPoses.size() == numCameras - 1); + + // Copy the subposes into separate vectors for rotations and centers + opengv::translations_t camOffsets(numCameras); // centers + opengv::rotations_t camRotations(numCameras); + + // the first is the identity (the main camera) + camOffsets[0] = Vec3::Zero(); + camRotations[0] = Mat3::Identity(); + // now copy the others + for (std::size_t i = 1; i < numCameras; ++i) { - // store the 3D point - points.push_back(pts3d[cam].col(i)); - - // we first remove the distortion and then we transform the undistorted point in - // normalized camera coordinates (inv(K)*undistortedPoint) - auto pt = currCamera.ima2cam(currCamera.get_ud_pixel(pts2d[cam].col(i))); - - opengv::bearingVector_t bearing(pt(0), pt(1), 1.0); - - // normalize the bearing-vector to 1 - bearing = bearing / bearing.norm(); - - bearingVectors.push_back(bearing); - - camCorrespondences.push_back(cam); - - // fill the indices map - absoluteToLocal[points.size()-1] = std::make_pair(cam, i); + camOffsets[i] = vec_subPoses[i - 1].center(); + // it takes as input a real pose, ie the rotation wrt the world frame + camRotations[i] = vec_subPoses[i - 1].rotation().inverse(); } - } - - assert(points.size() == numTotalPoints); - assert(bearingVectors.size() == numTotalPoints); - assert(camCorrespondences.size() == numTotalPoints); - - using namespace opengv; - //create a non-central absolute adapter - absolute_pose::NoncentralAbsoluteAdapter adapter(bearingVectors, - camCorrespondences, - points, - camOffsets, - camRotations); - - //Create a AbsolutePoseSacProblem and Ransac - //The method is set to GP3P - sac::Ransac ransac; - std::shared_ptr< - sac_problems::absolute_pose::AbsolutePoseSacProblem> absposeproblem_ptr( - new sac_problems::absolute_pose::AbsolutePoseSacProblem( - adapter, - sac_problems::absolute_pose::AbsolutePoseSacProblem::GP3P)); - ransac.sac_model_ = absposeproblem_ptr; - ransac.threshold_ = threshold; - ransac.max_iterations_ = maxIterations; - - auto detect_start = std::chrono::steady_clock::now(); - const bool success = ransac.computeModel(); - auto detect_end = std::chrono::steady_clock::now(); - const auto detect_elapsed = std::chrono::duration_cast(detect_end - detect_start); - - if(!success) - { - if(verbosity) - ALICEVISION_LOG_DEBUG("Resection Failed"); - - return EstimationStatus(false, false); - } - - const auto &sol = ransac.model_coefficients_; - // again, the output is a real pose, with the center and the rotation expressed - // wrt the world frame, ie we need to take the transpose/inverse of the rotation - rigPose = geometry::Pose3(sol.block<3,3>(0,0).transpose(), sol.col(3)); - - // copy the inliers - const std::size_t numInliers = ransac.inliers_.size(); - assert(numInliers >= 3); - - inliers.clear(); - inliers.resize(numCameras); - - if(verbosity) - { - ALICEVISION_LOG_DEBUG( - "-------------------------------" << "\n" - "-- Robust Resection " << "\n" - "-- Resection status: " << success << "\n" - "-- #Points used for Resection: " << numTotalPoints << "\n" - "-- #Points validated by robust Resection: " << numInliers << "\n" - "-- #Iterations needed: " << ransac.iterations_ << "\n" - "-- #Thresehold used: " << ransac.threshold_ << " (" << radianToDegree(angularThreshold) << "deg)\n" - "-- Time spent in ransac [ms]: " << detect_elapsed.count() << "\n" - "-------------------------------"); - } - - // remap the inliers - for(std::size_t i = 0; i < numInliers; i++) - { - const auto idx = absoluteToLocal.at(ransac.inliers_[i]); - inliers[idx.first].emplace_back(idx.second); - } -// for(size_t i = 0; i < ransac.inliers_.size(); i++) -// ALICEVISION_LOG_DEBUG(ransac.inliers_[i]); -// -// for(size_t i = 0; i < inliers.size(); ++i) -// { -// ALICEVISION_LOG_DEBUG("Inliers cam " << i << ":"); -// for(size_t j = 0; j < inliers[i].size(); ++j) -// ALICEVISION_LOG_DEBUG(inliers[i][j]); -// } - - bool hasStrongSupport = true; - if(descTypesPerCamera) - { - // Check if estimation has strong support - hasStrongSupport = matching::hasStrongSupport(inliers, *descTypesPerCamera, 3); - } - return EstimationStatus(true, hasStrongSupport); -} -#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) + // count the total number of associations + std::size_t numTotalPoints = 0; + for (std::size_t i = 0; i < numCameras; ++i) + { + const std::size_t numPts = pts2d[i].cols(); + assert(pts3d[i].cols() == numPts); + assert(pts3d[i].rows() == 3); + assert(pts2d[i].rows() == 2); + numTotalPoints += numPts; + } + // transform the points into bearingVectors + opengv::bearingVectors_t bearingVectors; // this contains the bearing vector associated to each image feature + opengv::points_t points; // this contains the 3d points + points.reserve(numTotalPoints); + std::vector camCorrespondences; // this is to keep track, for each bearing vector, whose camera it belongs + + // this map is used to remap the indices of the flatten structure containing + // the points (points, bearingvector) to the original local index of each pts2d/pts3d: + // eg + // localIdx = absoluteToLocal[absoluteID]; such that + // pts3d[localIdx.first].col(localIdx.second) == points[absoluteID]; + std::map> absoluteToLocal; + + for (std::size_t cam = 0; cam < numCameras; ++cam) + { + const std::size_t numPts = pts2d[cam].cols(); + const camera::Pinhole& currCamera = vec_queryIntrinsics[cam]; + + for (std::size_t i = 0; i < numPts; ++i) + { + // store the 3D point + points.push_back(pts3d[cam].col(i)); + // we first remove the distortion and then we transform the undistorted point in + // normalized camera coordinates (inv(K)*undistortedPoint) + auto pt = currCamera.ima2cam(currCamera.get_ud_pixel(pts2d[cam].col(i))); + + opengv::bearingVector_t bearing(pt(0), pt(1), 1.0); + + // normalize the bearing-vector to 1 + bearing = bearing / bearing.norm(); + + bearingVectors.push_back(bearing); + + camCorrespondences.push_back(cam); + + // fill the indices map + absoluteToLocal[points.size() - 1] = std::make_pair(cam, i); + } + } + + assert(points.size() == numTotalPoints); + assert(bearingVectors.size() == numTotalPoints); + assert(camCorrespondences.size() == numTotalPoints); + + using namespace opengv; + // create a non-central absolute adapter + absolute_pose::NoncentralAbsoluteAdapter adapter(bearingVectors, camCorrespondences, points, camOffsets, camRotations); + + // Create a AbsolutePoseSacProblem and Ransac + // The method is set to GP3P + sac::Ransac ransac; + std::shared_ptr absposeproblem_ptr( + new sac_problems::absolute_pose::AbsolutePoseSacProblem(adapter, sac_problems::absolute_pose::AbsolutePoseSacProblem::GP3P)); + ransac.sac_model_ = absposeproblem_ptr; + ransac.threshold_ = threshold; + ransac.max_iterations_ = maxIterations; + + auto detect_start = std::chrono::steady_clock::now(); + const bool success = ransac.computeModel(); + auto detect_end = std::chrono::steady_clock::now(); + const auto detect_elapsed = std::chrono::duration_cast(detect_end - detect_start); + + if (!success) + { + if (verbosity) + ALICEVISION_LOG_DEBUG("Resection Failed"); + + return EstimationStatus(false, false); + } + + const auto& sol = ransac.model_coefficients_; + // again, the output is a real pose, with the center and the rotation expressed + // wrt the world frame, ie we need to take the transpose/inverse of the rotation + rigPose = geometry::Pose3(sol.block<3, 3>(0, 0).transpose(), sol.col(3)); + + // copy the inliers + const std::size_t numInliers = ransac.inliers_.size(); + assert(numInliers >= 3); + + inliers.clear(); + inliers.resize(numCameras); + + if (verbosity) + { + ALICEVISION_LOG_DEBUG("-------------------------------" + << "\n" + "-- Robust Resection " + << "\n" + "-- Resection status: " + << success + << "\n" + "-- #Points used for Resection: " + << numTotalPoints + << "\n" + "-- #Points validated by robust Resection: " + << numInliers + << "\n" + "-- #Iterations needed: " + << ransac.iterations_ + << "\n" + "-- #Thresehold used: " + << ransac.threshold_ << " (" << radianToDegree(angularThreshold) + << "deg)\n" + "-- Time spent in ransac [ms]: " + << detect_elapsed.count() + << "\n" + "-------------------------------"); + } + + // remap the inliers + for (std::size_t i = 0; i < numInliers; i++) + { + const auto idx = absoluteToLocal.at(ransac.inliers_[i]); + inliers[idx.first].emplace_back(idx.second); + } + // for(size_t i = 0; i < ransac.inliers_.size(); i++) + // ALICEVISION_LOG_DEBUG(ransac.inliers_[i]); + // + // for(size_t i = 0; i < inliers.size(); ++i) + // { + // ALICEVISION_LOG_DEBUG("Inliers cam " << i << ":"); + // for(size_t j = 0; j < inliers[i].size(); ++j) + // ALICEVISION_LOG_DEBUG(inliers[i][j]); + // } + + bool hasStrongSupport = true; + if (descTypesPerCamera) + { + // Check if estimation has strong support + hasStrongSupport = matching::hasStrongSupport(inliers, *descTypesPerCamera, 3); + } + return EstimationStatus(true, hasStrongSupport); } -} + +#endif // #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) + +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/rigResection.hpp b/src/aliceVision/localization/rigResection.hpp index e5e8ac47ec..1786f7b061 100644 --- a/src/aliceVision/localization/rigResection.hpp +++ b/src/aliceVision/localization/rigResection.hpp @@ -15,24 +15,24 @@ #include namespace aliceVision { -namespace localization{ +namespace localization { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENGV) /** - * @brief It computes the pose of a camera rig given the 2d-3d associations of - * each camera along with the internal calibration of each camera and the external + * @brief It computes the pose of a camera rig given the 2d-3d associations of + * each camera along with the internal calibration of each camera and the external * calibration of the cameras wrt the main one. - * - * @param[in] vec_pts2d A vector of the same size as the number of the camera in - * the rig, each element of the vector contains the 2d points of the associations + * + * @param[in] vec_pts2d A vector of the same size as the number of the camera in + * the rig, each element of the vector contains the 2d points of the associations * for each camera. - * @param[in] vec_pts3d A vector of the same size as the number of the camera in - * the rig, each element of the vector contains the 3d points of the associations + * @param[in] vec_pts3d A vector of the same size as the number of the camera in + * the rig, each element of the vector contains the 3d points of the associations * for each camera. A 2d-3d association is represented by (vec_pts2d[i].col(j), vec_pts3d[i].col(j)). - * @param[in] vec_queryIntrinsics A vector containing the intrinsics for each + * @param[in] vec_queryIntrinsics A vector containing the intrinsics for each * camera of the rig. - * @param[in] vec_subPoses A vector containing the subposes of the cameras wrt + * @param[in] vec_subPoses A vector containing the subposes of the cameras wrt * the main one, ie the camera 0. This vector has numCameras-1 elements. * @param[in] descTypesPerCamera optional vector of describer types per camera. * It is used in the weighting stategy to decide if the resection is strongly supported. @@ -41,24 +41,24 @@ namespace localization{ * ontaining the indices of inliers. * @param[in] threshold The threshold in radians to use in the ransac process. It * represents the maximum angular error between the direction of the 3D point in - * space and the bearing vector of the feature (i.e. the direction of the + * space and the bearing vector of the feature (i.e. the direction of the * re-projection ray). * @param[in] maxIterations Maximum number of iteration for the ransac process. * @param[in] verbosity Mute/unmute the debugging messages. * @return true if the ransac has success. */ -EstimationStatus rigResection(const std::vector &vec_pts2d, - const std::vector &vec_pts3d, - const std::vector &vec_queryIntrinsics, - const std::vector &vec_subPoses, - const std::vector< std::vector > * descTypesPerCamera, - geometry::Pose3 &rigPose, - std::vector > &inliers, - double threshold = degreeToRadian(0.1), - std::size_t maxIterations = 100, - bool verbosity = true); +EstimationStatus rigResection(const std::vector& vec_pts2d, + const std::vector& vec_pts3d, + const std::vector& vec_queryIntrinsics, + const std::vector& vec_subPoses, + const std::vector>* descTypesPerCamera, + geometry::Pose3& rigPose, + std::vector>& inliers, + double threshold = degreeToRadian(0.1), + std::size_t maxIterations = 100, + bool verbosity = true); #endif -} -} +} // namespace localization +} // namespace aliceVision diff --git a/src/aliceVision/localization/rigResection_test.cpp b/src/aliceVision/localization/rigResection_test.cpp index 9ad8ca9324..6b8e464630 100644 --- a/src/aliceVision/localization/rigResection_test.cpp +++ b/src/aliceVision/localization/rigResection_test.cpp @@ -25,99 +25,94 @@ using namespace aliceVision; Mat3 generateRotation(double x, double y, double z) { -//ALICEVISION_LOG_DEBUG("generateRotation"); - Mat3 R1 = Mat3::Identity(); - R1(1,1) = cos(x); - R1(1,2) = -sin(x); - R1(2,1) = -R1(1,2); - R1(2,2) = R1(1,1); - - Mat3 R2 = Mat3::Identity(); - R2(0,0) = cos(y); - R2(0,2) = sin(y); - R2(2,0) = -R2(0,2); - R2(2,2) = R2(0,0); - - Mat3 R3 = Mat3::Identity(); - R3(0,0) = cos(z); - R3(0,1) = -sin(z); - R3(1,0) =-R3(0,1); - R3(1,1) = R3(0,0); - return R3 * R2 * R1; + // ALICEVISION_LOG_DEBUG("generateRotation"); + Mat3 R1 = Mat3::Identity(); + R1(1, 1) = cos(x); + R1(1, 2) = -sin(x); + R1(2, 1) = -R1(1, 2); + R1(2, 2) = R1(1, 1); + + Mat3 R2 = Mat3::Identity(); + R2(0, 0) = cos(y); + R2(0, 2) = sin(y); + R2(2, 0) = -R2(0, 2); + R2(2, 2) = R2(0, 0); + + Mat3 R3 = Mat3::Identity(); + R3(0, 0) = cos(z); + R3(0, 1) = -sin(z); + R3(1, 0) = -R3(0, 1); + R3(1, 1) = R3(0, 0); + return R3 * R2 * R1; } -Mat3 generateRotation(const Vec3 &angles) -{ - return generateRotation(angles(0), angles(1), angles(2)); -} +Mat3 generateRotation(const Vec3& angles) { return generateRotation(angles(0), angles(1), angles(2)); } -Mat3 generateRandomRotation(const Vec3 &maxAngles = Vec3::Constant(2*M_PI)) +Mat3 generateRandomRotation(const Vec3& maxAngles = Vec3::Constant(2 * M_PI)) { - const Vec3 angles = Vec3::Random().cwiseProduct(maxAngles); -// ALICEVISION_LOG_DEBUG("generateRandomRotation"); - return generateRotation(angles); + const Vec3 angles = Vec3::Random().cwiseProduct(maxAngles); + // ALICEVISION_LOG_DEBUG("generateRandomRotation"); + return generateRotation(angles); } Vec3 generateRandomTranslation(double maxNorm) { -// ALICEVISION_LOG_DEBUG("generateRandomTranslation"); - Vec3 translation = Vec3::Random(); - return maxNorm * (translation / translation.norm()); + // ALICEVISION_LOG_DEBUG("generateRandomTranslation"); + Vec3 translation = Vec3::Random(); + return maxNorm * (translation / translation.norm()); } Vec3 generateRandomPoint(double thetaMin, double thetaMax, double depthMax, double depthMin) { - // variables contains the spherical parameters (r, theta, phi) for the point to generate its - // spherical coordinatrs - Vec3 variables = Vec3::Random(); - variables(0) = (variables(0)+1)/2; // put the random in [0,1] - variables(1) = (variables(1)+1)/2; // put the random in [0,1] - - // get random value for r - const double r = variables(0) = depthMin*variables(0) + (1-variables(0))*depthMax; - // get random value for theta - const double theta = variables(1) = thetaMin*variables(1) + (1-variables(1))*thetaMax; - // get random value for phi - const double phi = variables(2) = M_PI*variables(2); - - return Vec3(r*std::sin(theta)*std::cos(phi), - r*std::sin(theta)*std::sin(phi), - r*std::cos(theta)); + // variables contains the spherical parameters (r, theta, phi) for the point to generate its + // spherical coordinatrs + Vec3 variables = Vec3::Random(); + variables(0) = (variables(0) + 1) / 2; // put the random in [0,1] + variables(1) = (variables(1) + 1) / 2; // put the random in [0,1] + + // get random value for r + const double r = variables(0) = depthMin * variables(0) + (1 - variables(0)) * depthMax; + // get random value for theta + const double theta = variables(1) = thetaMin * variables(1) + (1 - variables(1)) * thetaMax; + // get random value for phi + const double phi = variables(2) = M_PI * variables(2); + + return Vec3(r * std::sin(theta) * std::cos(phi), r * std::sin(theta) * std::sin(phi), r * std::cos(theta)); } Mat3X generateRandomPoints(std::size_t numPts, double thetaMin, double thetaMax, double depthMax, double depthMin) { - Mat3X points = Mat(3, numPts); - for(std::size_t i = 0; i < numPts; ++i) - { - points.col(i) = generateRandomPoint(thetaMin, thetaMax, depthMax, depthMin); - } - return points; + Mat3X points = Mat(3, numPts); + for (std::size_t i = 0; i < numPts; ++i) + { + points.col(i) = generateRandomPoint(thetaMin, thetaMax, depthMax, depthMin); + } + return points; } -geometry::Pose3 generateRandomPose(const Vec3 &maxAngles = Vec3::Constant(2*M_PI), double maxNorm = 1) +geometry::Pose3 generateRandomPose(const Vec3& maxAngles = Vec3::Constant(2 * M_PI), double maxNorm = 1) { -// ALICEVISION_LOG_DEBUG("generateRandomPose"); - return geometry::Pose3(generateRandomRotation(maxAngles), generateRandomTranslation(maxNorm)); + // ALICEVISION_LOG_DEBUG("generateRandomPose"); + return geometry::Pose3(generateRandomRotation(maxAngles), generateRandomTranslation(maxNorm)); } -void generateRandomExperiment(std::size_t numCameras, +void generateRandomExperiment(std::size_t numCameras, std::size_t numPoints, double outliersRatio, double noise, - geometry::Pose3 &rigPoseGT, - Mat3X &pointsGT, - std::vector &vec_queryIntrinsics, - std::vector &vec_subPoses, - std::vector &vec_pts3d, - std::vector &vec_pts2d) + geometry::Pose3& rigPoseGT, + Mat3X& pointsGT, + std::vector& vec_queryIntrinsics, + std::vector& vec_subPoses, + std::vector& vec_pts3d, + std::vector& vec_pts2d) { - // generate random pose for the rig - rigPoseGT = generateRandomPose(Vec3::Constant(M_PI/10), 5); + // generate random pose for the rig + rigPoseGT = generateRandomPose(Vec3::Constant(M_PI / 10), 5); // generate random 3D points const double thetaMin = 0; - const double thetaMax = M_PI/3; + const double thetaMax = M_PI / 3; const double depthMax = 50; const double depthMin = 10; pointsGT = generateRandomPoints(numPoints, thetaMin, thetaMax, depthMax, depthMin); @@ -127,291 +122,248 @@ void generateRandomExperiment(std::size_t numCameras, ALICEVISION_LOG_DEBUG("rigPoseGT\n" << rigPoseGT.rotation() << "\n" << rigPoseGT.center()); // generate numCameras random poses and intrinsics - for(std::size_t cam = 0; cam < numCameras; ++cam) + for (std::size_t cam = 0; cam < numCameras; ++cam) { - // first camera is in I 0 - if(cam != 0) - vec_subPoses.push_back(generateRandomPose(Vec3::Constant(M_PI/10), 1.5)); + // first camera is in I 0 + if (cam != 0) + vec_subPoses.push_back(generateRandomPose(Vec3::Constant(M_PI / 10), 1.5)); - // let's keep it simple - vec_queryIntrinsics.push_back( - camera::Pinhole(640, 480, 500, 500, 320, 240, std::make_shared())); + // let's keep it simple + vec_queryIntrinsics.push_back(camera::Pinhole(640, 480, 500, 500, 320, 240, std::make_shared())); } - assert(vec_subPoses.size() == numCameras-1); - // for(std::size_t i = 0; i < vec_subPoses.size(); ++i) - // ALICEVISION_LOG_DEBUG("vec_subPoses\n" << vec_subPoses[i].rotation() << "\n" << vec_subPoses[i].center()); + assert(vec_subPoses.size() == numCameras - 1); + // for(std::size_t i = 0; i < vec_subPoses.size(); ++i) + // ALICEVISION_LOG_DEBUG("vec_subPoses\n" << vec_subPoses[i].rotation() << "\n" << vec_subPoses[i].center()); // for each camera generate the features (if 3D point is "in front" of the camera) vec_pts3d.reserve(numCameras); vec_pts2d.reserve(numCameras); - - const std::size_t numOutliers = (std::size_t)numPoints*outliersRatio; - for(std::size_t cam = 0; cam < numCameras; ++cam) + const std::size_t numOutliers = (std::size_t)numPoints * outliersRatio; + + for (std::size_t cam = 0; cam < numCameras; ++cam) { - Mat3X localPts; - if(cam != 0) - localPts = vec_subPoses[cam-1](points); - else - localPts = points; - - // count the number of points in front of the camera - // ie take the 3rd coordinate and check it is > 0 - const std::size_t validPoints = (localPts.row(2).array() > 0).count(); - Mat3X pts3d = Mat(3, validPoints + numOutliers); - Mat2X pts2d = Mat(2, validPoints + numOutliers); - - // for each 3D point - std::size_t idx = 0; - for(std::size_t i = 0; i < numPoints; ++i) - { - // if it is in front of the camera - if(localPts(2,i) > 0) + Mat3X localPts; + if (cam != 0) + localPts = vec_subPoses[cam - 1](points); + else + localPts = points; + + // count the number of points in front of the camera + // ie take the 3rd coordinate and check it is > 0 + const std::size_t validPoints = (localPts.row(2).array() > 0).count(); + Mat3X pts3d = Mat(3, validPoints + numOutliers); + Mat2X pts2d = Mat(2, validPoints + numOutliers); + + // for each 3D point + std::size_t idx = 0; + for (std::size_t i = 0; i < numPoints; ++i) { - // project it - Vec2 feat = vec_queryIntrinsics[cam].project(geometry::Pose3(), localPts.col(i).homogeneous()); - - if(noise > std::numeric_limits::epsilon()) - { - feat = feat + noise*Vec2::Random(); - } - - // add the 3d and 2d point - pts3d.col(idx) = pointsGT.col(i); - pts2d.col(idx) = feat; - ++idx; + // if it is in front of the camera + if (localPts(2, i) > 0) + { + // project it + Vec2 feat = vec_queryIntrinsics[cam].project(geometry::Pose3(), localPts.col(i).homogeneous()); + + if (noise > std::numeric_limits::epsilon()) + { + feat = feat + noise * Vec2::Random(); + } + + // add the 3d and 2d point + pts3d.col(idx) = pointsGT.col(i); + pts2d.col(idx) = feat; + ++idx; + } } - } - assert(idx == validPoints); - - if(numOutliers) - { - //add some other random associations - for(std::size_t i = 0; i < numOutliers; ++i) + assert(idx == validPoints); + + if (numOutliers) { - pts3d.col(idx) = 10*Vec3::Random(); - pts2d.col(idx) = 100*Vec2::Random(); - ++idx; + // add some other random associations + for (std::size_t i = 0; i < numOutliers; ++i) + { + pts3d.col(idx) = 10 * Vec3::Random(); + pts2d.col(idx) = 100 * Vec2::Random(); + ++idx; + } } - } - -// ALICEVISION_LOG_DEBUG("Cam " << cam); -// ALICEVISION_LOG_DEBUG("pts2d\n" << pts2d); -// ALICEVISION_LOG_DEBUG("pts3d\n" << pts3d); -// ALICEVISION_LOG_DEBUG("pts3dTRA\n" << localPts); -// -// auto residuals = vec_queryIntrinsics[cam].residuals(geometry::Pose3(), localPts, pts2d); -// auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); -// -// ALICEVISION_LOG_DEBUG("residuals\n" << sqrErrors); - - // if(cam!=0) - // residuals = vec_queryIntrinsics[cam].residuals(vec_subPoses[cam-1], points, pts2d); - // else - // residuals = vec_queryIntrinsics[cam].residuals(geometry::Pose3(), points, pts2d); - // - // auto sqrErrors2 = (residuals.cwiseProduct(residuals)).colwise().sum(); - // - // ALICEVISION_LOG_DEBUG("residuals2\n" << sqrErrors2); - - vec_pts3d.push_back(pts3d); - vec_pts2d.push_back(pts2d); + + // ALICEVISION_LOG_DEBUG("Cam " << cam); + // ALICEVISION_LOG_DEBUG("pts2d\n" << pts2d); + // ALICEVISION_LOG_DEBUG("pts3d\n" << pts3d); + // ALICEVISION_LOG_DEBUG("pts3dTRA\n" << localPts); + // + // auto residuals = vec_queryIntrinsics[cam].residuals(geometry::Pose3(), localPts, pts2d); + // auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + // + // ALICEVISION_LOG_DEBUG("residuals\n" << sqrErrors); + + // if(cam!=0) + // residuals = vec_queryIntrinsics[cam].residuals(vec_subPoses[cam-1], points, pts2d); + // else + // residuals = vec_queryIntrinsics[cam].residuals(geometry::Pose3(), points, pts2d); + // + // auto sqrErrors2 = (residuals.cwiseProduct(residuals)).colwise().sum(); + // + // ALICEVISION_LOG_DEBUG("residuals2\n" << sqrErrors2); + + vec_pts3d.push_back(pts3d); + vec_pts2d.push_back(pts2d); } } - BOOST_AUTO_TEST_CASE(rigResection_simpleNoNoiseNoOutliers) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const std::size_t numCameras = 3; - const std::size_t numPoints = 10; - const std::size_t numTrials = 10; - const double threshold = 0.1; - - for(std::size_t trial = 0; trial < numTrials; ++trial) - { - - // generate random pose for the rig - geometry::Pose3 rigPoseGT; - std::vector vec_queryIntrinsics; - std::vector vec_subPoses; - std::vector vec_pts3d; - std::vector vec_pts2d; - Mat3X pointsGT; + const std::size_t numCameras = 3; + const std::size_t numPoints = 10; + const std::size_t numTrials = 10; + const double threshold = 0.1; - generateRandomExperiment(numCameras, - numPoints, - 0, - 0, - rigPoseGT, - pointsGT, - vec_queryIntrinsics, - vec_subPoses, - vec_pts3d, - vec_pts2d); - - // call the GPNP - std::vector > inliers; - - geometry::Pose3 rigPose; - const EstimationStatus status = localization::rigResection(vec_pts2d, - vec_pts3d, - vec_queryIntrinsics, - vec_subPoses, - nullptr, - rigPose, - inliers); - BOOST_CHECK(status.isValid); - - ALICEVISION_LOG_DEBUG("rigPose\n" << rigPose.rotation() << "\n" << rigPose.center()); - - // check result for the pose - const auto poseDiff = rigPose*rigPoseGT.inverse(); - ALICEVISION_LOG_DEBUG("diffR\n" << poseDiff.rotation()); - ALICEVISION_LOG_DEBUG("diffC\n" << poseDiff.center().norm()); - - // check result for the posediff, how close it is to the identity - const Mat3 &rot = poseDiff.rotation(); - for(std::size_t i = 0; i < 3; ++i) + for (std::size_t trial = 0; trial < numTrials; ++trial) { - for(std::size_t j = 0; j < 3; ++j) - { - if(i == j) + // generate random pose for the rig + geometry::Pose3 rigPoseGT; + std::vector vec_queryIntrinsics; + std::vector vec_subPoses; + std::vector vec_pts3d; + std::vector vec_pts2d; + Mat3X pointsGT; + + generateRandomExperiment(numCameras, numPoints, 0, 0, rigPoseGT, pointsGT, vec_queryIntrinsics, vec_subPoses, vec_pts3d, vec_pts2d); + + // call the GPNP + std::vector> inliers; + + geometry::Pose3 rigPose; + const EstimationStatus status = + localization::rigResection(vec_pts2d, vec_pts3d, vec_queryIntrinsics, vec_subPoses, nullptr, rigPose, inliers); + BOOST_CHECK(status.isValid); + + ALICEVISION_LOG_DEBUG("rigPose\n" << rigPose.rotation() << "\n" << rigPose.center()); + + // check result for the pose + const auto poseDiff = rigPose * rigPoseGT.inverse(); + ALICEVISION_LOG_DEBUG("diffR\n" << poseDiff.rotation()); + ALICEVISION_LOG_DEBUG("diffC\n" << poseDiff.center().norm()); + + // check result for the posediff, how close it is to the identity + const Mat3& rot = poseDiff.rotation(); + for (std::size_t i = 0; i < 3; ++i) { - BOOST_CHECK_SMALL(rot(i,j)- 1.0, threshold); + for (std::size_t j = 0; j < 3; ++j) + { + if (i == j) + { + BOOST_CHECK_SMALL(rot(i, j) - 1.0, threshold); + } + else + { + BOOST_CHECK_SMALL(rot(i, j), threshold); + } + } } - else + + const Vec3& center = poseDiff.center(); + for (std::size_t i = 0; i < 3; ++i) { - BOOST_CHECK_SMALL(rot(i,j), threshold); + BOOST_CHECK_SMALL(center(i), threshold); } - } - } - const Vec3 ¢er = poseDiff.center(); - for(std::size_t i = 0; i < 3; ++i) - { - BOOST_CHECK_SMALL(center(i), threshold); - } + // check inliers + BOOST_CHECK(inliers.size() == numCameras); + for (std::size_t i = 0; i < numCameras; ++i) + { + BOOST_CHECK(inliers[i].size() == vec_pts2d[i].cols()); + } - // check inliers - BOOST_CHECK(inliers.size() == numCameras); - for(std::size_t i = 0; i < numCameras; ++i) - { - BOOST_CHECK(inliers[i].size() == vec_pts2d[i].cols()); + BOOST_CHECK(localization::refineRigPose(vec_pts2d, vec_pts3d, inliers, vec_queryIntrinsics, vec_subPoses, rigPose)); + + // THIS BOOST_AUTO_TEST_CASE CAN FAIL AS THE REPROJECTION ERROR USED INSIDE THE GP3P IS BASED + // ON THE ANGLE DIFFERENCE RATHER THAN THE REPROJECTED POINT DIFFERENCE + // check reprojection errors + // for(std::size_t cam = 0; cam < numCameras; ++cam) + // { + // const std::size_t numPts = vec_pts2d[cam].cols(); + // const camera::Pinhole &currCamera = vec_queryIntrinsics[cam]; + // Mat2X residuals; + // if(cam!=0) + // residuals = currCamera.residuals(vec_subPoses[cam-1]*rigPose, vec_pts3d[cam], vec_pts2d[cam]); + // else + // residuals = currCamera.residuals(geometry::Pose3()*rigPose, vec_pts3d[cam], vec_pts2d[cam]); + // + // auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); + // ALICEVISION_LOG_DEBUG(sqrErrors); + // for(std::size_t j = 0; j < numPts; ++j) + // { + // BOOST_CHECK(sqrErrors(j) <= threshold); + // } + // } } - - BOOST_CHECK(localization::refineRigPose(vec_pts2d, - vec_pts3d, - inliers, - vec_queryIntrinsics, - vec_subPoses, - rigPose)); - - // THIS BOOST_AUTO_TEST_CASE CAN FAIL AS THE REPROJECTION ERROR USED INSIDE THE GP3P IS BASED - // ON THE ANGLE DIFFERENCE RATHER THAN THE REPROJECTED POINT DIFFERENCE - // check reprojection errors -// for(std::size_t cam = 0; cam < numCameras; ++cam) -// { -// const std::size_t numPts = vec_pts2d[cam].cols(); -// const camera::Pinhole &currCamera = vec_queryIntrinsics[cam]; -// Mat2X residuals; -// if(cam!=0) -// residuals = currCamera.residuals(vec_subPoses[cam-1]*rigPose, vec_pts3d[cam], vec_pts2d[cam]); -// else -// residuals = currCamera.residuals(geometry::Pose3()*rigPose, vec_pts3d[cam], vec_pts2d[cam]); -// -// auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); -// ALICEVISION_LOG_DEBUG(sqrErrors); -// for(std::size_t j = 0; j < numPts; ++j) -// { -// BOOST_CHECK(sqrErrors(j) <= threshold); -// } -// } - } } BOOST_AUTO_TEST_CASE(rigResection_simpleWithNoiseNoOutliers) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const std::size_t numCameras = 3; - const std::size_t numPoints = 10; - const std::size_t numTrials = 10; - const double noise = 0.1; - const double threshold = 0.1; - - for(std::size_t trial = 0; trial < numTrials; ++trial) - { + const std::size_t numCameras = 3; + const std::size_t numPoints = 10; + const std::size_t numTrials = 10; + const double noise = 0.1; + const double threshold = 0.1; - // generate random pose for the rig - geometry::Pose3 rigPoseGT; - std::vector vec_queryIntrinsics; - std::vector vec_subPoses; - std::vector vec_pts3d; - std::vector vec_pts2d; - Mat3X pointsGT; - - generateRandomExperiment(numCameras, - numPoints, - 0, - noise, - rigPoseGT, - pointsGT, - vec_queryIntrinsics, - vec_subPoses, - vec_pts3d, - vec_pts2d); - - // call the GPNP - std::vector > inliers; - geometry::Pose3 rigPose; - const EstimationStatus status = localization::rigResection(vec_pts2d, - vec_pts3d, - vec_queryIntrinsics, - vec_subPoses, - nullptr, - rigPose, - inliers, - degreeToRadian(0.008)); - BOOST_CHECK(status.isValid); - - ALICEVISION_LOG_DEBUG("rigPose\n" << rigPose.rotation() << "\n" << rigPose.center()); - - const auto poseDiff = rigPose*rigPoseGT.inverse(); - ALICEVISION_LOG_DEBUG("diffR\n" << poseDiff.rotation()); - ALICEVISION_LOG_DEBUG("diffC\n" << poseDiff.center().norm()); - - // check result for the posediff, how close it is to the identity - const Mat3 &rot = poseDiff.rotation(); - for(std::size_t i = 0; i < 3; ++i) + for (std::size_t trial = 0; trial < numTrials; ++trial) { - for(std::size_t j = 0; j < 3; ++j) - { - if(i == j) + // generate random pose for the rig + geometry::Pose3 rigPoseGT; + std::vector vec_queryIntrinsics; + std::vector vec_subPoses; + std::vector vec_pts3d; + std::vector vec_pts2d; + Mat3X pointsGT; + + generateRandomExperiment(numCameras, numPoints, 0, noise, rigPoseGT, pointsGT, vec_queryIntrinsics, vec_subPoses, vec_pts3d, vec_pts2d); + + // call the GPNP + std::vector> inliers; + geometry::Pose3 rigPose; + const EstimationStatus status = + localization::rigResection(vec_pts2d, vec_pts3d, vec_queryIntrinsics, vec_subPoses, nullptr, rigPose, inliers, degreeToRadian(0.008)); + BOOST_CHECK(status.isValid); + + ALICEVISION_LOG_DEBUG("rigPose\n" << rigPose.rotation() << "\n" << rigPose.center()); + + const auto poseDiff = rigPose * rigPoseGT.inverse(); + ALICEVISION_LOG_DEBUG("diffR\n" << poseDiff.rotation()); + ALICEVISION_LOG_DEBUG("diffC\n" << poseDiff.center().norm()); + + // check result for the posediff, how close it is to the identity + const Mat3& rot = poseDiff.rotation(); + for (std::size_t i = 0; i < 3; ++i) { - BOOST_CHECK_SMALL(rot(i,j)- 1.0, threshold); + for (std::size_t j = 0; j < 3; ++j) + { + if (i == j) + { + BOOST_CHECK_SMALL(rot(i, j) - 1.0, threshold); + } + else + { + BOOST_CHECK_SMALL(rot(i, j), threshold); + } + } } - else + + const Vec3& center = poseDiff.center(); + for (std::size_t i = 0; i < 3; ++i) { - BOOST_CHECK_SMALL(rot(i,j), threshold); + BOOST_CHECK_SMALL(center(i), threshold); } - } - } - const Vec3 ¢er = poseDiff.center(); - for(std::size_t i = 0; i < 3; ++i) - { - BOOST_CHECK_SMALL(center(i), threshold); + BOOST_CHECK(localization::refineRigPose(vec_pts2d, vec_pts3d, inliers, vec_queryIntrinsics, vec_subPoses, rigPose)); } - - BOOST_CHECK(localization::refineRigPose(vec_pts2d, - vec_pts3d, - inliers, - vec_queryIntrinsics, - vec_subPoses, - rigPose)); - } } /* @@ -427,7 +379,7 @@ BOOST_AUTO_TEST_CASE(rigResection_simpleNoNoiseWithOutliers) std::mt19937 gen; std::uniform_real_distribution<> dis(0, 0.4); - + for(std::size_t trial = 0; trial < numTrials; ++trial) { @@ -440,7 +392,7 @@ BOOST_AUTO_TEST_CASE(rigResection_simpleNoNoiseWithOutliers) std::vector vec_pts2d; Mat3X pointsGT; - generateRandomExperiment(numCameras, + generateRandomExperiment(numCameras, numPoints, outlierPercentage, noise, @@ -485,7 +437,7 @@ BOOST_AUTO_TEST_CASE(rigResection_simpleNoNoiseWithOutliers) // residuals = currCamera.residuals(vec_subPoses[cam-1]*rigPose, vec_pts3d[cam], vec_pts2d[cam]); // else // residuals = currCamera.residuals(geometry::Pose3()*rigPose, vec_pts3d[cam], vec_pts2d[cam]); -// +// // auto sqrErrors = (residuals.cwiseProduct(residuals)).colwise().sum(); // //// ALICEVISION_LOG_DEBUG(sqrErrors); diff --git a/src/aliceVision/matching/ArrayMatcher.hpp b/src/aliceVision/matching/ArrayMatcher.hpp index 16b356851e..a5b0c40fd3 100644 --- a/src/aliceVision/matching/ArrayMatcher.hpp +++ b/src/aliceVision/matching/ArrayMatcher.hpp @@ -16,59 +16,53 @@ namespace aliceVision { namespace matching { - -template < typename Scalar, typename Metric > +template class ArrayMatcher { public: - typedef Scalar ScalarT; - typedef typename Metric::ResultType DistanceType; - typedef Metric MetricT; - - ArrayMatcher() {} - virtual ~ArrayMatcher() {}; + typedef Scalar ScalarT; + typedef typename Metric::ResultType DistanceType; + typedef Metric MetricT; - /** - * Build the matching structure - * - * \param[in] dataset Input data. - * \param[in] nbRows The number of component. - * \param[in] dimension Length of the data contained in the dataset. - * - * \return True if success. - */ - virtual bool Build(std::mt19937 & randomNumberGenerator,const Scalar * dataset, int nbRows, int dimension)=0; + ArrayMatcher() {} + virtual ~ArrayMatcher(){}; - /** - * Search the nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[out] indice The indice of array in the dataset that - * have been computed as the nearest array. - * \param[out] distance The distance between the two arrays. - * - * \return True if success. - */ - virtual bool SearchNeighbour( const Scalar * query, - int * indice, DistanceType * distance)=0; + /** + * Build the matching structure + * + * \param[in] dataset Input data. + * \param[in] nbRows The number of component. + * \param[in] dimension Length of the data contained in the dataset. + * + * \return True if success. + */ + virtual bool Build(std::mt19937& randomNumberGenerator, const Scalar* dataset, int nbRows, int dimension) = 0; + /** + * Search the nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[out] indice The indice of array in the dataset that + * have been computed as the nearest array. + * \param[out] distance The distance between the two arrays. + * + * \return True if success. + */ + virtual bool SearchNeighbour(const Scalar* query, int* indice, DistanceType* distance) = 0; -/** - * Search the N nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[in] nbQuery The number of query rows - * \param[out] indices The corresponding (query, neighbor) indices - * \param[out] distances The distances between the matched arrays. - * \param[out] NN The number of maximal neighbor that could - * will be searched. - * - * \return True if success. - */ - virtual bool SearchNeighbours( const Scalar * query, int nbQuery, - IndMatches * indices, - std::vector * distances, - size_t NN)=0; + /** + * Search the N nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[in] nbQuery The number of query rows + * \param[out] indices The corresponding (query, neighbor) indices + * \param[out] distances The distances between the matched arrays. + * \param[out] NN The number of maximal neighbor that could + * will be searched. + * + * \return True if success. + */ + virtual bool SearchNeighbours(const Scalar* query, int nbQuery, IndMatches* indices, std::vector* distances, size_t NN) = 0; }; } // namespace matching diff --git a/src/aliceVision/matching/ArrayMatcher_bruteForce.hpp b/src/aliceVision/matching/ArrayMatcher_bruteForce.hpp index 578a5d3d61..9e90da8c02 100644 --- a/src/aliceVision/matching/ArrayMatcher_bruteForce.hpp +++ b/src/aliceVision/matching/ArrayMatcher_bruteForce.hpp @@ -17,145 +17,134 @@ #include #include - namespace aliceVision { namespace matching { // By default compute square(L2 distance). -template < typename Scalar = float, typename Metric = feature::L2_Simple > -class ArrayMatcher_bruteForce : public ArrayMatcher +template> +class ArrayMatcher_bruteForce : public ArrayMatcher { public: - typedef typename Metric::ResultType DistanceType; - - ArrayMatcher_bruteForce() {} - virtual ~ArrayMatcher_bruteForce() { - memMapping.reset(); - } - - /** - * Build the matching structure - * - * \param[in] dataset Input data. - * \param[in] nbRows The number of component. - * \param[in] dimension Length of the data contained in the dataset. - * - * \return True if success. - */ - bool Build(std::mt19937 & randomNumberGenerator,const Scalar * dataset, int nbRows, int dimension) { - if (nbRows < 1) { - memMapping.reset(nullptr); - return false; - } - memMapping.reset(new Eigen::Map( (Scalar*)dataset, nbRows, dimension) ); - return true; - } - - /** - * Search the nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[out] indice The indice of array in the dataset that - * have been computed as the nearest array. - * \param[out] distance The distance between the two arrays. - * - * \return True if success. - */ - bool SearchNeighbour( const Scalar * query, - int * indice, DistanceType * distance) - { - if (memMapping.get() == nullptr) - return false; - - //matrix representation of the input data; - Eigen::Map mat_query((Scalar*)query, 1, (*memMapping).cols() ); - Metric metric; - std::vector vec_dist((*memMapping).rows(), 0.0); - for (int i = 0; i < (*memMapping).rows(); ++i) + typedef typename Metric::ResultType DistanceType; + + ArrayMatcher_bruteForce() {} + virtual ~ArrayMatcher_bruteForce() { memMapping.reset(); } + + /** + * Build the matching structure + * + * \param[in] dataset Input data. + * \param[in] nbRows The number of component. + * \param[in] dimension Length of the data contained in the dataset. + * + * \return True if success. + */ + bool Build(std::mt19937& randomNumberGenerator, const Scalar* dataset, int nbRows, int dimension) { - // Compute Distance Metric - vec_dist[i] = metric( (Scalar*)query, (*memMapping).row(i).data(), (*memMapping).cols() ); - } - if (!vec_dist.empty()) - { - // Find the minimum distance : - typename std::vector::const_iterator min_iter = - min_element( vec_dist.begin(), vec_dist.end()); - *indice =std::distance( - typename std::vector::const_iterator(vec_dist.begin()), - min_iter); - *distance = static_cast(*min_iter); - } - return true; - } - - -/** - * Search the N nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[in] nbQuery The number of query rows - * \param[out] indices The corresponding (query, neighbor) indices - * \param[out] distances The distances between the matched arrays. - * \param[out] NN The number of maximal neighbor that will be searched. - * - * \return True if success. - */ - bool SearchNeighbours - ( - const Scalar * query, int nbQuery, - IndMatches * pvec_indices, - std::vector * pvec_distances, - size_t NN - ) - { - if (memMapping.get() == nullptr) { - return false; - } - - if (NN > (*memMapping).rows() || nbQuery < 1) { - return false; + if (nbRows < 1) + { + memMapping.reset(nullptr); + return false; + } + memMapping.reset(new Eigen::Map((Scalar*)dataset, nbRows, dimension)); + return true; } - //matrix representation of the input data; - Eigen::Map mat_query((Scalar*)query, nbQuery, (*memMapping).cols()); - Metric metric; - - pvec_distances->resize(nbQuery * NN); - pvec_indices->resize(nbQuery * NN); - - #pragma omp parallel for schedule(dynamic) - for (int queryIndex=0; queryIndex < nbQuery; ++queryIndex) + /** + * Search the nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[out] indice The indice of array in the dataset that + * have been computed as the nearest array. + * \param[out] distance The distance between the two arrays. + * + * \return True if success. + */ + bool SearchNeighbour(const Scalar* query, int* indice, DistanceType* distance) { - std::vector vec_distance((*memMapping).rows(), 0.0); - const Scalar * queryPtr = mat_query.row(queryIndex).data(); - const Scalar * rowPtr = (*memMapping).data(); - for (int i = 0; i < (*memMapping).rows(); ++i) - { - vec_distance[i] = metric( queryPtr, - rowPtr, (*memMapping).cols() ); - rowPtr += (*memMapping).cols(); - } - - // Find the N minimum distances: - const int maxMinFound = (int) std::min( size_t(NN), vec_distance.size()); - using namespace stl::indexed_sort; - std::vector< sort_index_packet_ascend< DistanceType, int> > packet_vec(vec_distance.size()); - sort_index_helper(packet_vec, &vec_distance[0], maxMinFound); - - for (int i = 0; i < maxMinFound; ++i) - { - (*pvec_distances)[queryIndex*NN+i] = packet_vec[i].val; - (*pvec_indices)[queryIndex*NN+i] = IndMatch(queryIndex, packet_vec[i].index); - } + if (memMapping.get() == nullptr) + return false; + + // matrix representation of the input data; + Eigen::Map mat_query((Scalar*)query, 1, (*memMapping).cols()); + Metric metric; + std::vector vec_dist((*memMapping).rows(), 0.0); + for (int i = 0; i < (*memMapping).rows(); ++i) + { + // Compute Distance Metric + vec_dist[i] = metric((Scalar*)query, (*memMapping).row(i).data(), (*memMapping).cols()); + } + if (!vec_dist.empty()) + { + // Find the minimum distance : + typename std::vector::const_iterator min_iter = min_element(vec_dist.begin(), vec_dist.end()); + *indice = std::distance(typename std::vector::const_iterator(vec_dist.begin()), min_iter); + *distance = static_cast(*min_iter); + } + return true; } - return true; - }; -private: - typedef Eigen::Matrix BaseMat; - /// Use a memory mapping in order to avoid memory re-allocation - std::unique_ptr< Eigen::Map > memMapping; + /** + * Search the N nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[in] nbQuery The number of query rows + * \param[out] indices The corresponding (query, neighbor) indices + * \param[out] distances The distances between the matched arrays. + * \param[out] NN The number of maximal neighbor that will be searched. + * + * \return True if success. + */ + bool SearchNeighbours(const Scalar* query, int nbQuery, IndMatches* pvec_indices, std::vector* pvec_distances, size_t NN) + { + if (memMapping.get() == nullptr) + { + return false; + } + + if (NN > (*memMapping).rows() || nbQuery < 1) + { + return false; + } + + // matrix representation of the input data; + Eigen::Map mat_query((Scalar*)query, nbQuery, (*memMapping).cols()); + Metric metric; + + pvec_distances->resize(nbQuery * NN); + pvec_indices->resize(nbQuery * NN); + +#pragma omp parallel for schedule(dynamic) + for (int queryIndex = 0; queryIndex < nbQuery; ++queryIndex) + { + std::vector vec_distance((*memMapping).rows(), 0.0); + const Scalar* queryPtr = mat_query.row(queryIndex).data(); + const Scalar* rowPtr = (*memMapping).data(); + for (int i = 0; i < (*memMapping).rows(); ++i) + { + vec_distance[i] = metric(queryPtr, rowPtr, (*memMapping).cols()); + rowPtr += (*memMapping).cols(); + } + + // Find the N minimum distances: + const int maxMinFound = (int)std::min(size_t(NN), vec_distance.size()); + using namespace stl::indexed_sort; + std::vector> packet_vec(vec_distance.size()); + sort_index_helper(packet_vec, &vec_distance[0], maxMinFound); + + for (int i = 0; i < maxMinFound; ++i) + { + (*pvec_distances)[queryIndex * NN + i] = packet_vec[i].val; + (*pvec_indices)[queryIndex * NN + i] = IndMatch(queryIndex, packet_vec[i].index); + } + } + return true; + }; + + private: + typedef Eigen::Matrix BaseMat; + /// Use a memory mapping in order to avoid memory re-allocation + std::unique_ptr> memMapping; }; } // namespace matching diff --git a/src/aliceVision/matching/ArrayMatcher_cascadeHashing.hpp b/src/aliceVision/matching/ArrayMatcher_cascadeHashing.hpp index bc31e74d40..65fc2d2310 100644 --- a/src/aliceVision/matching/ArrayMatcher_cascadeHashing.hpp +++ b/src/aliceVision/matching/ArrayMatcher_cascadeHashing.hpp @@ -33,115 +33,102 @@ namespace matching { // Implementation of descriptor matching using the cascade hashing method of [1]. // If you use this matcher, please cite the paper. // template Metric parameter is ignored (by default compute square(L2 distance)). -template < typename Scalar = float, typename Metric = feature::L2_Simple > -class ArrayMatcher_cascadeHashing : public ArrayMatcher +template> +class ArrayMatcher_cascadeHashing : public ArrayMatcher { public: - typedef typename Metric::ResultType DistanceType; - - ArrayMatcher_cascadeHashing() {} - virtual ~ArrayMatcher_cascadeHashing() { - memMapping.reset(); - } - - /** - * Build the matching structure - * - * \param[in] dataset Input data. - * \param[in] nbRows The number of component. - * \param[in] dimension Length of the data contained in the dataset. - * - * \return True if success. - */ - bool Build(std::mt19937 & randomNumberGenerator,const Scalar * dataset, int nbRows, int dimension) { - if (nbRows < 1) { - memMapping.reset(nullptr); - return false; - } - memMapping.reset(new Eigen::Map( (Scalar*)dataset, nbRows, dimension) ); - - // Init the cascade hasher (hashing projection matrices) - cascade_hasher_.Init(randomNumberGenerator, dimension); - // Index the input descriptors - zero_mean_descriptor_ = CascadeHasher::GetZeroMeanDescriptor(*memMapping); - hashed_base_ = cascade_hasher_.CreateHashedDescriptions( - *memMapping, zero_mean_descriptor_); - - return true; - }; - - /** - * Search the nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[out] indice The indice of array in the dataset that - * have been computed as the nearest array. - * \param[out] distance The distance between the two arrays. - * - * \return True if success. - */ - bool SearchNeighbour( const Scalar * query, - int * indice, DistanceType * distance) - { - ALICEVISION_LOG_WARNING("This matcher is not made to match a single query"); - return false; - } - - -/** - * Search the N nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[in] nbQuery The number of query rows - * \param[out] indices The corresponding (query, neighbor) indices - * \param[out] distances The distances between the matched arrays. - * \param[out] NN The number of maximal neighbor that will be searched. - * - * \return True if success. - */ - bool SearchNeighbours - ( - const Scalar * query, int nbQuery, - IndMatches * pvec_indices, - std::vector * pvec_distances, - size_t NN - ) - { - if (memMapping.get() == nullptr) { - return false; - } - - if (NN > (*memMapping).rows() || nbQuery < 1) { - return false; + typedef typename Metric::ResultType DistanceType; + + ArrayMatcher_cascadeHashing() {} + virtual ~ArrayMatcher_cascadeHashing() { memMapping.reset(); } + + /** + * Build the matching structure + * + * \param[in] dataset Input data. + * \param[in] nbRows The number of component. + * \param[in] dimension Length of the data contained in the dataset. + * + * \return True if success. + */ + bool Build(std::mt19937& randomNumberGenerator, const Scalar* dataset, int nbRows, int dimension) + { + if (nbRows < 1) + { + memMapping.reset(nullptr); + return false; + } + memMapping.reset(new Eigen::Map((Scalar*)dataset, nbRows, dimension)); + + // Init the cascade hasher (hashing projection matrices) + cascade_hasher_.Init(randomNumberGenerator, dimension); + // Index the input descriptors + zero_mean_descriptor_ = CascadeHasher::GetZeroMeanDescriptor(*memMapping); + hashed_base_ = cascade_hasher_.CreateHashedDescriptions(*memMapping, zero_mean_descriptor_); + + return true; + }; + + /** + * Search the nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[out] indice The indice of array in the dataset that + * have been computed as the nearest array. + * \param[out] distance The distance between the two arrays. + * + * \return True if success. + */ + bool SearchNeighbour(const Scalar* query, int* indice, DistanceType* distance) + { + ALICEVISION_LOG_WARNING("This matcher is not made to match a single query"); + return false; } - // Matrix representation of the input data; - Eigen::Map mat_query((Scalar*)query, nbQuery, (*memMapping).cols()); - - pvec_distances->reserve(nbQuery * NN); - pvec_indices->reserve(nbQuery * NN); - - // Index the query descriptors - const HashedDescriptions hashed_query = cascade_hasher_.CreateHashedDescriptions( - mat_query, - zero_mean_descriptor_); - // Match the query descriptors to the database - cascade_hasher_.Match_HashedDescriptions( - hashed_query, mat_query, - hashed_base_, *memMapping, - pvec_indices, pvec_distances, - NN); - - return true; - }; - -private: - typedef Eigen::Matrix BaseMat; - /// Use a memory mapping in order to avoid memory re-allocation - std::unique_ptr< Eigen::Map > memMapping; - CascadeHasher cascade_hasher_; - HashedDescriptions hashed_base_; - Eigen::VectorXf zero_mean_descriptor_; + /** + * Search the N nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[in] nbQuery The number of query rows + * \param[out] indices The corresponding (query, neighbor) indices + * \param[out] distances The distances between the matched arrays. + * \param[out] NN The number of maximal neighbor that will be searched. + * + * \return True if success. + */ + bool SearchNeighbours(const Scalar* query, int nbQuery, IndMatches* pvec_indices, std::vector* pvec_distances, size_t NN) + { + if (memMapping.get() == nullptr) + { + return false; + } + + if (NN > (*memMapping).rows() || nbQuery < 1) + { + return false; + } + + // Matrix representation of the input data; + Eigen::Map mat_query((Scalar*)query, nbQuery, (*memMapping).cols()); + + pvec_distances->reserve(nbQuery * NN); + pvec_indices->reserve(nbQuery * NN); + + // Index the query descriptors + const HashedDescriptions hashed_query = cascade_hasher_.CreateHashedDescriptions(mat_query, zero_mean_descriptor_); + // Match the query descriptors to the database + cascade_hasher_.Match_HashedDescriptions(hashed_query, mat_query, hashed_base_, *memMapping, pvec_indices, pvec_distances, NN); + + return true; + }; + + private: + typedef Eigen::Matrix BaseMat; + /// Use a memory mapping in order to avoid memory re-allocation + std::unique_ptr> memMapping; + CascadeHasher cascade_hasher_; + HashedDescriptions hashed_base_; + Eigen::VectorXf zero_mean_descriptor_; }; } // namespace matching diff --git a/src/aliceVision/matching/ArrayMatcher_kdtreeFlann.hpp b/src/aliceVision/matching/ArrayMatcher_kdtreeFlann.hpp index 0ebf5a8482..1dff2332cf 100644 --- a/src/aliceVision/matching/ArrayMatcher_kdtreeFlann.hpp +++ b/src/aliceVision/matching/ArrayMatcher_kdtreeFlann.hpp @@ -16,7 +16,7 @@ #include namespace aliceVision { -namespace matching { +namespace matching { /// Implement ArrayMatcher as a FLANN KDtree matcher. // http://www.cs.ubc.ca/~mariusm/index.php/FLANN/FLANN @@ -25,128 +25,120 @@ namespace matching { // By default use squared L2 metric (flann::L2) // sqrt is monotonic so for performance reason we do not compute it. -template < typename Scalar = float, typename Metric = flann::L2 > +template> class ArrayMatcher_kdtreeFlann : public ArrayMatcher { public: - typedef typename Metric::ResultType DistanceType; - - ArrayMatcher_kdtreeFlann() = default; - - virtual ~ArrayMatcher_kdtreeFlann() = default; - - /** - * Build the matching structure - * - * \param[in] dataset Input data. - * \param[in] nbRows The number of component. - * \param[in] dimension Length of the data contained in the each - * row of the dataset. - * - * \return True if success. - */ - bool Build(std::mt19937 & randomNumberGenerator,const Scalar * dataset, int nbRows, int dimension) - { - if (nbRows <= 0) - return false; - - _dimension = dimension; - //-- Build Flann Matrix container (map to already allocated memory) - _datasetM.reset( - new flann::Matrix((Scalar*)dataset, nbRows, dimension)); - - //-- Build FLANN index - flann::KDTreeIndexParams params(4); - params["random_seed"] = 1; - _index.reset(new flann::Index (*_datasetM, params)); - _index->buildIndex(); - - return true; - } - - /** - * Search the nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[out] indice The indice of array in the dataset that - * have been computed as the nearest array. - * \param[out] distance The distance between the two arrays. - * - * \return True if success. - */ - bool SearchNeighbour( const Scalar * query, int * indice, DistanceType * distance) - { - if (_index.get() == nullptr) - return false; - - int * indicePTR = indice; - DistanceType * distancePTR = distance; - flann::Matrix queries((Scalar*)query, 1, _dimension); - - flann::Matrix indices(indicePTR, 1, 1); - flann::Matrix dists(distancePTR, 1, 1); - // do a knn search, using 128 checks - return (_index->knnSearch(queries, indices, dists, 1, flann::SearchParams(128)) > 0); - } - - /** - * Search the N nearest Neighbor of the scalar array query. - * - * \param[in] query The query array - * \param[in] nbQuery The number of query rows - * \param[out] indices The corresponding (query, neighbor) indices - * \param[out] pvec_distances The distances between the matched arrays. - * \param[out] NN The number of maximal neighbor that will be searched. - * - * \return True if success. - */ - bool SearchNeighbours - ( - const Scalar * query, int nbQuery, - IndMatches * pvec_indices, - std::vector * pvec_distances, - size_t NN - ) - { - if (_index.get() == nullptr || NN > _datasetM->rows) - return false; - - std::vector vec_distances(nbQuery * NN); - DistanceType * distancePTR = &(vec_distances[0]); - flann::Matrix dists(distancePTR, nbQuery, NN); - - std::vector vec_indices(nbQuery * NN); - int * indicePTR = &(vec_indices[0]); - flann::Matrix indices(indicePTR, nbQuery, NN); - - flann::Matrix queries((Scalar*)query, nbQuery, _dimension); - // do a knn search, using 128 checks - flann::SearchParams params(128); - params.cores = omp_get_max_threads(); - - if (_index->knnSearch(queries, indices, dists, NN, params) <= 0) - return false; - - // Save the resulting found indices - pvec_indices->reserve(nbQuery * NN); - pvec_distances->reserve(nbQuery * NN); - for (size_t i = 0; i < nbQuery; ++i) + typedef typename Metric::ResultType DistanceType; + + ArrayMatcher_kdtreeFlann() = default; + + virtual ~ArrayMatcher_kdtreeFlann() = default; + + /** + * Build the matching structure + * + * \param[in] dataset Input data. + * \param[in] nbRows The number of component. + * \param[in] dimension Length of the data contained in the each + * row of the dataset. + * + * \return True if success. + */ + bool Build(std::mt19937& randomNumberGenerator, const Scalar* dataset, int nbRows, int dimension) { - for (size_t j = 0; j < NN; ++j) - { - pvec_indices->emplace_back(i, vec_indices[i*NN+j]); - pvec_distances->emplace_back(vec_distances[i*NN+j]); - } + if (nbRows <= 0) + return false; + + _dimension = dimension; + //-- Build Flann Matrix container (map to already allocated memory) + _datasetM.reset(new flann::Matrix((Scalar*)dataset, nbRows, dimension)); + + //-- Build FLANN index + flann::KDTreeIndexParams params(4); + params["random_seed"] = 1; + _index.reset(new flann::Index(*_datasetM, params)); + _index->buildIndex(); + + return true; } - return true; - } - private: + /** + * Search the nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[out] indice The indice of array in the dataset that + * have been computed as the nearest array. + * \param[out] distance The distance between the two arrays. + * + * \return True if success. + */ + bool SearchNeighbour(const Scalar* query, int* indice, DistanceType* distance) + { + if (_index.get() == nullptr) + return false; + + int* indicePTR = indice; + DistanceType* distancePTR = distance; + flann::Matrix queries((Scalar*)query, 1, _dimension); - std::unique_ptr< flann::Matrix > _datasetM; - std::unique_ptr< flann::Index > _index; - std::size_t _dimension; + flann::Matrix indices(indicePTR, 1, 1); + flann::Matrix dists(distancePTR, 1, 1); + // do a knn search, using 128 checks + return (_index->knnSearch(queries, indices, dists, 1, flann::SearchParams(128)) > 0); + } + + /** + * Search the N nearest Neighbor of the scalar array query. + * + * \param[in] query The query array + * \param[in] nbQuery The number of query rows + * \param[out] indices The corresponding (query, neighbor) indices + * \param[out] pvec_distances The distances between the matched arrays. + * \param[out] NN The number of maximal neighbor that will be searched. + * + * \return True if success. + */ + bool SearchNeighbours(const Scalar* query, int nbQuery, IndMatches* pvec_indices, std::vector* pvec_distances, size_t NN) + { + if (_index.get() == nullptr || NN > _datasetM->rows) + return false; + + std::vector vec_distances(nbQuery * NN); + DistanceType* distancePTR = &(vec_distances[0]); + flann::Matrix dists(distancePTR, nbQuery, NN); + + std::vector vec_indices(nbQuery * NN); + int* indicePTR = &(vec_indices[0]); + flann::Matrix indices(indicePTR, nbQuery, NN); + + flann::Matrix queries((Scalar*)query, nbQuery, _dimension); + // do a knn search, using 128 checks + flann::SearchParams params(128); + params.cores = omp_get_max_threads(); + + if (_index->knnSearch(queries, indices, dists, NN, params) <= 0) + return false; + + // Save the resulting found indices + pvec_indices->reserve(nbQuery * NN); + pvec_distances->reserve(nbQuery * NN); + for (size_t i = 0; i < nbQuery; ++i) + { + for (size_t j = 0; j < NN; ++j) + { + pvec_indices->emplace_back(i, vec_indices[i * NN + j]); + pvec_distances->emplace_back(vec_distances[i * NN + j]); + } + } + return true; + } + + private: + std::unique_ptr> _datasetM; + std::unique_ptr> _index; + std::size_t _dimension; }; -} // namespace matching -} // namespace aliceVision +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/CascadeHasher.hpp b/src/aliceVision/matching/CascadeHasher.hpp index 72ad30b23e..34090fd57d 100644 --- a/src/aliceVision/matching/CascadeHasher.hpp +++ b/src/aliceVision/matching/CascadeHasher.hpp @@ -19,22 +19,24 @@ namespace aliceVision { namespace matching { -struct HashedDescription { - // Hash code generated by the primary hashing function. - stl::dynamic_bitset hash_code; - - // Each bucket_ids[x] = y means the descriptor belongs to bucket y in bucket - // group x. - std::vector bucket_ids; +struct HashedDescription +{ + // Hash code generated by the primary hashing function. + stl::dynamic_bitset hash_code; + + // Each bucket_ids[x] = y means the descriptor belongs to bucket y in bucket + // group x. + std::vector bucket_ids; }; -struct HashedDescriptions{ - // The hash information. - std::vector hashed_desc; +struct HashedDescriptions +{ + // The hash information. + std::vector hashed_desc; - typedef std::vector Bucket; - // buckets[bucket_group][bucket_id] = bucket (container of description ids). - std::vector > buckets; + typedef std::vector Bucket; + // buckets[bucket_group][bucket_id] = bucket (container of description ids). + std::vector> buckets; }; /** @@ -59,291 +61,266 @@ struct HashedDescriptions{ * - this implementation can support various descriptor length and internal type * SIFT, SURF, ... all scalar based descriptor */ -class CascadeHasher { -private: - - // The number of bucket bits. - int nb_bits_per_bucket_; - // The number of dimensions of the Hash code. - int nb_hash_code_; - // The number of bucket groups. - int nb_bucket_groups_; - // The number of buckets in each group. - int nb_buckets_per_group_; - -public: - CascadeHasher() {} - - // Creates the hashing projections (cascade of two level of hash codes) - bool Init - ( - std::mt19937 & generator, - const uint8_t nb_hash_code = 128, - const uint8_t nb_bucket_groups = 6, - const uint8_t nb_bits_per_bucket = 10) - { - nb_bucket_groups_= nb_bucket_groups; - nb_hash_code_ = nb_hash_code; - nb_bits_per_bucket_ = nb_bits_per_bucket; - nb_buckets_per_group_= 1 << nb_bits_per_bucket; - - // - // Box Muller transform is used in the original paper to get fast random number - // from a normal distribution with and . - // Here we use C++11 normal distribution random number generator - std::normal_distribution<> d(0,1); - - primary_hash_projection_.resize(nb_hash_code, nb_hash_code); - - // Initialize primary hash projection. - for (int i = 0; i < nb_hash_code; ++i) +class CascadeHasher +{ + private: + // The number of bucket bits. + int nb_bits_per_bucket_; + // The number of dimensions of the Hash code. + int nb_hash_code_; + // The number of bucket groups. + int nb_bucket_groups_; + // The number of buckets in each group. + int nb_buckets_per_group_; + + public: + CascadeHasher() {} + + // Creates the hashing projections (cascade of two level of hash codes) + bool Init(std::mt19937& generator, const uint8_t nb_hash_code = 128, const uint8_t nb_bucket_groups = 6, const uint8_t nb_bits_per_bucket = 10) { - for (int j = 0; j < nb_hash_code; ++j) - primary_hash_projection_(i, j) = d(generator); - } + nb_bucket_groups_ = nb_bucket_groups; + nb_hash_code_ = nb_hash_code; + nb_bits_per_bucket_ = nb_bits_per_bucket; + nb_buckets_per_group_ = 1 << nb_bits_per_bucket; - // Initialize secondary hash projection. - secondary_hash_projection_.resize(nb_bucket_groups); - for (int i = 0; i < nb_bucket_groups; ++i) - { - secondary_hash_projection_[i].resize(nb_bits_per_bucket_, - nb_hash_code); - for (int j = 0; j < nb_bits_per_bucket_; ++j) - { - for (int k = 0; k < nb_hash_code; ++k) - secondary_hash_projection_[i](j, k) = d(generator); - } - } - return true; - } - - template - static Eigen::VectorXf GetZeroMeanDescriptor - ( - const MatrixT & descriptions - ) - { - Eigen::VectorXf zero_mean_descriptor; - if (descriptions.rows() == 0) { - return zero_mean_descriptor; - } - // Compute the ZeroMean descriptor - zero_mean_descriptor.setZero(descriptions.cols()); - const typename MatrixT::Index nbDescriptions = descriptions.rows(); - for (int i = 0; i < nbDescriptions; ++i) - { - for (int j = 0; j < descriptions.cols(); ++j) - zero_mean_descriptor(j) += descriptions(i,j); - } - return zero_mean_descriptor / static_cast(nbDescriptions); - } - - - template - HashedDescriptions CreateHashedDescriptions - ( - const MatrixT & descriptions, - const Eigen::VectorXf & zero_mean_descriptor - ) const - { - // Steps: - // 1) Compute hash code and hash buckets (based on the zero_mean_descriptor). - // 2) Construct buckets. - - HashedDescriptions hashed_descriptions; - if (descriptions.rows() == 0) { - return hashed_descriptions; - } + // + // Box Muller transform is used in the original paper to get fast random number + // from a normal distribution with and . + // Here we use C++11 normal distribution random number generator + std::normal_distribution<> d(0, 1); - // Create hash codes for each description. - { - // Allocate space for hash codes. - const typename MatrixT::Index nbDescriptions = descriptions.rows(); - hashed_descriptions.hashed_desc.resize(nbDescriptions); - Eigen::VectorXf descriptor(descriptions.cols()); - for (int i = 0; i < nbDescriptions; ++i) - { - // Allocate space for each bucket id. - hashed_descriptions.hashed_desc[i].bucket_ids.resize(nb_bucket_groups_); - - for (int k = 0; k < descriptions.cols(); ++k) - { - descriptor(k) = descriptions(i,k); - } - descriptor -= zero_mean_descriptor; + primary_hash_projection_.resize(nb_hash_code, nb_hash_code); - auto& hash_code = hashed_descriptions.hashed_desc[i].hash_code; - hash_code = stl::dynamic_bitset(descriptions.cols()); - - // Compute hash code. - const Eigen::VectorXf primary_projection = primary_hash_projection_ * descriptor; - for (int j = 0; j < nb_hash_code_; ++j) + // Initialize primary hash projection. + for (int i = 0; i < nb_hash_code; ++i) { - hash_code[j] = primary_projection(j) > 0; + for (int j = 0; j < nb_hash_code; ++j) + primary_hash_projection_(i, j) = d(generator); } - // Determine the bucket index for each group. - Eigen::VectorXf secondary_projection; - for (int j = 0; j < nb_bucket_groups_; ++j) + // Initialize secondary hash projection. + secondary_hash_projection_.resize(nb_bucket_groups); + for (int i = 0; i < nb_bucket_groups; ++i) { - uint16_t bucket_id = 0; - secondary_projection = secondary_hash_projection_[j] * descriptor; - - for (int k = 0; k < nb_bits_per_bucket_; ++k) - { - bucket_id = (bucket_id << 1) + (secondary_projection(k) > 0 ? 1 : 0); - } - hashed_descriptions.hashed_desc[i].bucket_ids[j] = bucket_id; + secondary_hash_projection_[i].resize(nb_bits_per_bucket_, nb_hash_code); + for (int j = 0; j < nb_bits_per_bucket_; ++j) + { + for (int k = 0; k < nb_hash_code; ++k) + secondary_hash_projection_[i](j, k) = d(generator); + } } - } + return true; } - // Build the Buckets - { - hashed_descriptions.buckets.resize(nb_bucket_groups_); - for (int i = 0; i < nb_bucket_groups_; ++i) - { - hashed_descriptions.buckets[i].resize(nb_buckets_per_group_); - // Add the descriptor ID to the proper bucket group and id. - for (int j = 0; j < hashed_descriptions.hashed_desc.size(); ++j) + template + static Eigen::VectorXf GetZeroMeanDescriptor(const MatrixT& descriptions) + { + Eigen::VectorXf zero_mean_descriptor; + if (descriptions.rows() == 0) { - const uint16_t bucket_id = hashed_descriptions.hashed_desc[j].bucket_ids[i]; - hashed_descriptions.buckets[i][bucket_id].push_back(j); + return zero_mean_descriptor; } - } + // Compute the ZeroMean descriptor + zero_mean_descriptor.setZero(descriptions.cols()); + const typename MatrixT::Index nbDescriptions = descriptions.rows(); + for (int i = 0; i < nbDescriptions; ++i) + { + for (int j = 0; j < descriptions.cols(); ++j) + zero_mean_descriptor(j) += descriptions(i, j); + } + return zero_mean_descriptor / static_cast(nbDescriptions); } - return hashed_descriptions; - } - - // Matches two collection of hashed descriptions with a fast matching scheme - // based on the hash codes previously generated. - template - void Match_HashedDescriptions - ( - const HashedDescriptions& hashed_descriptions1, - const MatrixT & descriptions1, - const HashedDescriptions& hashed_descriptions2, - const MatrixT & descriptions2, - IndMatches * pvec_indices, - std::vector * pvec_distances, - const int NN = 2 - ) const - { - typedef feature::L2_Vectorized MetricT; - MetricT metric; - - static const int kNumTopCandidates = 10; - - // Preallocate the candidate descriptors container. - std::vector candidate_descriptors; - candidate_descriptors.reserve(hashed_descriptions2.hashed_desc.size()); - - // Preallocated hamming distances. Each column indicates the hamming distance - // and the rows collect the descriptor ids with that - // distance. num_descriptors_with_hamming_distance keeps track of how many - // descriptors have that distance. - Eigen::MatrixXi candidate_hamming_distances( - hashed_descriptions2.hashed_desc.size(), nb_hash_code_ + 1); - Eigen::VectorXi num_descriptors_with_hamming_distance(nb_hash_code_ + 1); - - // Preallocate the container for keeping euclidean distances. - std::vector > candidate_euclidean_distances; - candidate_euclidean_distances.reserve(kNumTopCandidates); - - // A preallocated vector to determine if we have already used a particular - // feature for matching (i.e., prevents duplicates). - std::vector used_descriptor(hashed_descriptions2.hashed_desc.size()); - - typedef feature::Hamming HammingMetricType; - static const HammingMetricType metricH = {}; - for (int i = 0; i < hashed_descriptions1.hashed_desc.size(); ++i) + + template + HashedDescriptions CreateHashedDescriptions(const MatrixT& descriptions, const Eigen::VectorXf& zero_mean_descriptor) const { - candidate_descriptors.clear(); - num_descriptors_with_hamming_distance.setZero(); - candidate_euclidean_distances.clear(); - - const auto& hashed_desc = hashed_descriptions1.hashed_desc[i]; - - // Accumulate all descriptors in each bucket group that are in the same - // bucket id as the query descriptor. - for (int j = 0; j < nb_bucket_groups_; ++j) - { - const uint16_t bucket_id = hashed_desc.bucket_ids[j]; - for (const auto& feature_id : hashed_descriptions2.buckets[j][bucket_id]) + // Steps: + // 1) Compute hash code and hash buckets (based on the zero_mean_descriptor). + // 2) Construct buckets. + + HashedDescriptions hashed_descriptions; + if (descriptions.rows() == 0) { - candidate_descriptors.emplace_back(feature_id); - used_descriptor[feature_id] = false; + return hashed_descriptions; } - } - - // Skip matching this descriptor if there are not at least NN candidates. - if (candidate_descriptors.size() <= NN) - { - continue; - } - - // Compute the hamming distance of all candidates based on the comp hash - // code. Put the descriptors into buckets corresponding to their hamming - // distance. - for (const int candidate_id : candidate_descriptors) - { - if (!used_descriptor[candidate_id]) // avoid selecting the same candidate multiple times + + // Create hash codes for each description. { - used_descriptor[candidate_id] = true; - - const HammingMetricType::ResultType hamming_distance = metricH( - hashed_desc.hash_code.data(), - hashed_descriptions2.hashed_desc[candidate_id].hash_code.data(), - hashed_desc.hash_code.num_blocks()); - candidate_hamming_distances( - num_descriptors_with_hamming_distance(hamming_distance)++, - hamming_distance) = candidate_id; + // Allocate space for hash codes. + const typename MatrixT::Index nbDescriptions = descriptions.rows(); + hashed_descriptions.hashed_desc.resize(nbDescriptions); + Eigen::VectorXf descriptor(descriptions.cols()); + for (int i = 0; i < nbDescriptions; ++i) + { + // Allocate space for each bucket id. + hashed_descriptions.hashed_desc[i].bucket_ids.resize(nb_bucket_groups_); + + for (int k = 0; k < descriptions.cols(); ++k) + { + descriptor(k) = descriptions(i, k); + } + descriptor -= zero_mean_descriptor; + + auto& hash_code = hashed_descriptions.hashed_desc[i].hash_code; + hash_code = stl::dynamic_bitset(descriptions.cols()); + + // Compute hash code. + const Eigen::VectorXf primary_projection = primary_hash_projection_ * descriptor; + for (int j = 0; j < nb_hash_code_; ++j) + { + hash_code[j] = primary_projection(j) > 0; + } + + // Determine the bucket index for each group. + Eigen::VectorXf secondary_projection; + for (int j = 0; j < nb_bucket_groups_; ++j) + { + uint16_t bucket_id = 0; + secondary_projection = secondary_hash_projection_[j] * descriptor; + + for (int k = 0; k < nb_bits_per_bucket_; ++k) + { + bucket_id = (bucket_id << 1) + (secondary_projection(k) > 0 ? 1 : 0); + } + hashed_descriptions.hashed_desc[i].bucket_ids[j] = bucket_id; + } + } } - } - - // Compute the euclidean distance of the k descriptors with the best hamming - // distance. - candidate_euclidean_distances.reserve(kNumTopCandidates); - for (int j = 0; j < candidate_hamming_distances.cols() && - (candidate_euclidean_distances.size() < kNumTopCandidates); ++j) - { - for(int k = 0; k < num_descriptors_with_hamming_distance(j) && - (candidate_euclidean_distances.size() < kNumTopCandidates); ++k) + // Build the Buckets { - const int candidate_id = candidate_hamming_distances(k, j); - const DistanceType distance = metric( - descriptions2.row(candidate_id).data(), - descriptions1.row(i).data(), - descriptions1.cols()); - - candidate_euclidean_distances.emplace_back(distance, candidate_id); + hashed_descriptions.buckets.resize(nb_bucket_groups_); + for (int i = 0; i < nb_bucket_groups_; ++i) + { + hashed_descriptions.buckets[i].resize(nb_buckets_per_group_); + + // Add the descriptor ID to the proper bucket group and id. + for (int j = 0; j < hashed_descriptions.hashed_desc.size(); ++j) + { + const uint16_t bucket_id = hashed_descriptions.hashed_desc[j].bucket_ids[i]; + hashed_descriptions.buckets[i][bucket_id].push_back(j); + } + } } - } - - // Assert that each query is having at least NN retrieved neighbors - if (candidate_euclidean_distances.size() >= NN) - { - // Find the top NN candidates based on euclidean distance. - std::partial_sort(candidate_euclidean_distances.begin(), - candidate_euclidean_distances.begin() + NN, - candidate_euclidean_distances.end()); - // save resulting neighbors - for (int l = 0; l < NN; ++l) + return hashed_descriptions; + } + + // Matches two collection of hashed descriptions with a fast matching scheme + // based on the hash codes previously generated. + template + void Match_HashedDescriptions(const HashedDescriptions& hashed_descriptions1, + const MatrixT& descriptions1, + const HashedDescriptions& hashed_descriptions2, + const MatrixT& descriptions2, + IndMatches* pvec_indices, + std::vector* pvec_distances, + const int NN = 2) const + { + typedef feature::L2_Vectorized MetricT; + MetricT metric; + + static const int kNumTopCandidates = 10; + + // Preallocate the candidate descriptors container. + std::vector candidate_descriptors; + candidate_descriptors.reserve(hashed_descriptions2.hashed_desc.size()); + + // Preallocated hamming distances. Each column indicates the hamming distance + // and the rows collect the descriptor ids with that + // distance. num_descriptors_with_hamming_distance keeps track of how many + // descriptors have that distance. + Eigen::MatrixXi candidate_hamming_distances(hashed_descriptions2.hashed_desc.size(), nb_hash_code_ + 1); + Eigen::VectorXi num_descriptors_with_hamming_distance(nb_hash_code_ + 1); + + // Preallocate the container for keeping euclidean distances. + std::vector> candidate_euclidean_distances; + candidate_euclidean_distances.reserve(kNumTopCandidates); + + // A preallocated vector to determine if we have already used a particular + // feature for matching (i.e., prevents duplicates). + std::vector used_descriptor(hashed_descriptions2.hashed_desc.size()); + + typedef feature::Hamming HammingMetricType; + static const HammingMetricType metricH = {}; + for (int i = 0; i < hashed_descriptions1.hashed_desc.size(); ++i) { - pvec_distances->emplace_back(candidate_euclidean_distances[l].first); - pvec_indices->emplace_back(IndMatch(i,candidate_euclidean_distances[l].second)); + candidate_descriptors.clear(); + num_descriptors_with_hamming_distance.setZero(); + candidate_euclidean_distances.clear(); + + const auto& hashed_desc = hashed_descriptions1.hashed_desc[i]; + + // Accumulate all descriptors in each bucket group that are in the same + // bucket id as the query descriptor. + for (int j = 0; j < nb_bucket_groups_; ++j) + { + const uint16_t bucket_id = hashed_desc.bucket_ids[j]; + for (const auto& feature_id : hashed_descriptions2.buckets[j][bucket_id]) + { + candidate_descriptors.emplace_back(feature_id); + used_descriptor[feature_id] = false; + } + } + + // Skip matching this descriptor if there are not at least NN candidates. + if (candidate_descriptors.size() <= NN) + { + continue; + } + + // Compute the hamming distance of all candidates based on the comp hash + // code. Put the descriptors into buckets corresponding to their hamming + // distance. + for (const int candidate_id : candidate_descriptors) + { + if (!used_descriptor[candidate_id]) // avoid selecting the same candidate multiple times + { + used_descriptor[candidate_id] = true; + + const HammingMetricType::ResultType hamming_distance = metricH(hashed_desc.hash_code.data(), + hashed_descriptions2.hashed_desc[candidate_id].hash_code.data(), + hashed_desc.hash_code.num_blocks()); + candidate_hamming_distances(num_descriptors_with_hamming_distance(hamming_distance)++, hamming_distance) = candidate_id; + } + } + + // Compute the euclidean distance of the k descriptors with the best hamming + // distance. + candidate_euclidean_distances.reserve(kNumTopCandidates); + for (int j = 0; j < candidate_hamming_distances.cols() && (candidate_euclidean_distances.size() < kNumTopCandidates); ++j) + { + for (int k = 0; k < num_descriptors_with_hamming_distance(j) && (candidate_euclidean_distances.size() < kNumTopCandidates); ++k) + { + const int candidate_id = candidate_hamming_distances(k, j); + const DistanceType distance = metric(descriptions2.row(candidate_id).data(), descriptions1.row(i).data(), descriptions1.cols()); + + candidate_euclidean_distances.emplace_back(distance, candidate_id); + } + } + + // Assert that each query is having at least NN retrieved neighbors + if (candidate_euclidean_distances.size() >= NN) + { + // Find the top NN candidates based on euclidean distance. + std::partial_sort( + candidate_euclidean_distances.begin(), candidate_euclidean_distances.begin() + NN, candidate_euclidean_distances.end()); + // save resulting neighbors + for (int l = 0; l < NN; ++l) + { + pvec_distances->emplace_back(candidate_euclidean_distances[l].first); + pvec_indices->emplace_back(IndMatch(i, candidate_euclidean_distances[l].second)); + } + } + // else -> too few candidates... (save no one) } - } - //else -> too few candidates... (save no one) } - } private: - // Primary hashing function. - Eigen::MatrixXf primary_hash_projection_; + // Primary hashing function. + Eigen::MatrixXf primary_hash_projection_; - // Secondary hashing function. - std::vector secondary_hash_projection_; + // Secondary hashing function. + std::vector secondary_hash_projection_; }; } // namespace matching diff --git a/src/aliceVision/matching/IndMatch.hpp b/src/aliceVision/matching/IndMatch.hpp index e7e46574d8..7692edeccf 100644 --- a/src/aliceVision/matching/IndMatch.hpp +++ b/src/aliceVision/matching/IndMatch.hpp @@ -24,74 +24,65 @@ namespace matching { /// A sort operator exist in order to remove duplicates of IndMatch series. struct IndMatch { - IndMatch( - IndexT i = 0, IndexT j = 0 - , float distanceRatio = 0.0 + IndMatch(IndexT i = 0, + IndexT j = 0, + float distanceRatio = 0.0 #ifdef ALICEVISION_DEBUG_MATCHING - , float distance = 0.0 + , + float distance = 0.0 #endif - ) - { - _i = i; - _j = j; - _distanceRatio = distanceRatio; + ) + { + _i = i; + _j = j; + _distanceRatio = distanceRatio; #ifdef ALICEVISION_DEBUG_MATCHING - _distance = distance; + _distance = distance; #endif - } - - friend bool operator==(const IndMatch& m1, const IndMatch& m2) { - return (m1._i == m2._i && m1._j == m2._j); - } - - friend bool operator!=(const IndMatch& m1, const IndMatch& m2) { - return !(m1 == m2); - } - - // Lexicographical ordering of matches. Used to remove duplicates. - friend bool operator<(const IndMatch& m1, const IndMatch& m2) { - return (m1._i < m2._i || (m1._i == m2._i && m1._j < m2._j)); - } - - /// Remove duplicates ((_i, _j) that appears multiple times) - static bool getDeduplicated(std::vector & vec_match) - { - const size_t sizeBefore = vec_match.size(); - std::set set_deduplicated( vec_match.begin(), vec_match.end()); - vec_match.assign(set_deduplicated.begin(), set_deduplicated.end()); - return sizeBefore != vec_match.size(); - } - - IndexT _i, _j; // Left, right index - float _distanceRatio; // Ratio between the best and second best matches + } + + friend bool operator==(const IndMatch& m1, const IndMatch& m2) { return (m1._i == m2._i && m1._j == m2._j); } + + friend bool operator!=(const IndMatch& m1, const IndMatch& m2) { return !(m1 == m2); } + + // Lexicographical ordering of matches. Used to remove duplicates. + friend bool operator<(const IndMatch& m1, const IndMatch& m2) { return (m1._i < m2._i || (m1._i == m2._i && m1._j < m2._j)); } + + /// Remove duplicates ((_i, _j) that appears multiple times) + static bool getDeduplicated(std::vector& vec_match) + { + const size_t sizeBefore = vec_match.size(); + std::set set_deduplicated(vec_match.begin(), vec_match.end()); + vec_match.assign(set_deduplicated.begin(), set_deduplicated.end()); + return sizeBefore != vec_match.size(); + } + + IndexT _i, _j; // Left, right index + float _distanceRatio; // Ratio between the best and second best matches #ifdef ALICEVISION_DEBUG_MATCHING - float _distance; + float _distance; #endif }; -inline std::ostream& operator<<(std::ostream & out, const IndMatch & obj) { - return out << obj._i << " " << obj._j; -} +inline std::ostream& operator<<(std::ostream& out, const IndMatch& obj) { return out << obj._i << " " << obj._j; } -inline std::istream& operator>>(std::istream & in, IndMatch & obj) { - return in >> obj._i >> obj._j; -} +inline std::istream& operator>>(std::istream& in, IndMatch& obj) { return in >> obj._i >> obj._j; } typedef std::vector IndMatches; -struct MatchesPerDescType: public std::map +struct MatchesPerDescType : public std::map { int getNbMatches(feature::EImageDescriberType descType) const { const auto& it = this->find(descType); - if(it == this->end()) + if (it == this->end()) return 0; return it->second.size(); } int getNbAllMatches() const { int nbMatches = 0; - for(const auto& matches: *this) + for (const auto& matches : *this) { nbMatches += matches.second.size(); } @@ -106,12 +97,12 @@ typedef std::map PairwiseMatches; typedef std::map PairwiseSimpleMatches; -inline PairSet getImagePairs(const PairwiseMatches & matches) +inline PairSet getImagePairs(const PairwiseMatches& matches) { - PairSet pairs; - for(PairwiseMatches::const_iterator it = matches.begin(); it != matches.end(); ++it) - pairs.insert(it->first); - return pairs; + PairSet pairs; + for (PairwiseMatches::const_iterator it = matches.begin(); it != matches.end(); ++it) + pairs.insert(it->first); + return pairs; } } // namespace matching diff --git a/src/aliceVision/matching/IndMatchDecorator.hpp b/src/aliceVision/matching/IndMatchDecorator.hpp index 76ca414dcf..e91d94ef34 100644 --- a/src/aliceVision/matching/IndMatchDecorator.hpp +++ b/src/aliceVision/matching/IndMatchDecorator.hpp @@ -19,113 +19,108 @@ namespace matching { template class IndMatchDecorator { - struct IndMatchDecoratorStruct - { - IndMatchDecoratorStruct( - T xa, T ya, - T xb, T yb, - const IndMatch & ind) { - - x1 = xa; y1 = ya; - x2 = xb; y2 = yb; - index = ind; - } - - /// Lexicographical ordering of matches. Used to remove duplicates. - friend bool operator<(const IndMatchDecoratorStruct& m1, - const IndMatchDecoratorStruct& m2) { - - if (m1 == m2) return false; - - if (m1.x1 < m2.x1) - return m1.y1 < m2.y1; - else - if (m1.x1 > m2.x1) - return m1.y1 < m2.y1; - return m1.x1 < m2.x1; + struct IndMatchDecoratorStruct + { + IndMatchDecoratorStruct(T xa, T ya, T xb, T yb, const IndMatch& ind) + { + x1 = xa; + y1 = ya; + x2 = xb; + y2 = yb; + index = ind; + } + + /// Lexicographical ordering of matches. Used to remove duplicates. + friend bool operator<(const IndMatchDecoratorStruct& m1, const IndMatchDecoratorStruct& m2) + { + if (m1 == m2) + return false; + + if (m1.x1 < m2.x1) + return m1.y1 < m2.y1; + else if (m1.x1 > m2.x1) + return m1.y1 < m2.y1; + return m1.x1 < m2.x1; + } + + /// Comparison Operator + friend bool operator==(const IndMatchDecoratorStruct& m1, const IndMatchDecoratorStruct& m2) + { + return (m1.x1 == m2.x1 && m1.y1 == m2.y1 && m1.x2 == m2.x2 && m1.y2 == m2.y2); + } + + T x1, y1, x2, y2; + IndMatch index; + }; + + public: + IndMatchDecorator(const std::vector& vec_matches, + const std::vector& leftFeat, + const std::vector& rightFeat) + : _vec_matches(vec_matches) + { + for (size_t i = 0; i < vec_matches.size(); ++i) + { + const size_t I = vec_matches[i]._i; + const size_t J = vec_matches[i]._j; + _vecDecoredMatches.push_back( + IndMatchDecoratorStruct(leftFeat[I].x(), leftFeat[I].y(), rightFeat[J].x(), rightFeat[J].y(), vec_matches[i])); + } } - /// Comparison Operator - friend bool operator==(const IndMatchDecoratorStruct& m1, - const IndMatchDecoratorStruct& m2) { - - return (m1.x1==m2.x1 && m1.y1==m2.y1 && - m1.x2==m2.x2 && m1.y2==m2.y2); + IndMatchDecorator(const std::vector& vec_matches, const Mat& leftFeat, const Mat& rightFeat) + : _vec_matches(vec_matches) + { + for (size_t i = 0; i < vec_matches.size(); ++i) + { + const size_t I = vec_matches[i]._i; + const size_t J = vec_matches[i]._j; + _vecDecoredMatches.push_back( + IndMatchDecoratorStruct(leftFeat.col(I)(0), leftFeat.col(I)(1), rightFeat.col(J)(0), rightFeat.col(J)(1), vec_matches[i])); + } } - T x1,y1, x2,y2; - IndMatch index; - }; -public: - - IndMatchDecorator(const std::vector & vec_matches, - const std::vector & leftFeat, - const std::vector & rightFeat) - :_vec_matches(vec_matches) - { - for (size_t i = 0; i < vec_matches.size(); ++i) { - const size_t I = vec_matches[i]._i; - const size_t J = vec_matches[i]._j; - _vecDecoredMatches.push_back( - IndMatchDecoratorStruct(leftFeat[I].x(),leftFeat[I].y(), - rightFeat[J].x(), rightFeat[J].y(), vec_matches[i])); - } - } - - IndMatchDecorator(const std::vector & vec_matches, - const Mat & leftFeat, - const Mat & rightFeat) - :_vec_matches(vec_matches) - { - for (size_t i = 0; i < vec_matches.size(); ++i) { - const size_t I = vec_matches[i]._i; - const size_t J = vec_matches[i]._j; - _vecDecoredMatches.push_back( - IndMatchDecoratorStruct(leftFeat.col(I)(0),leftFeat.col(I)(1), - rightFeat.col(J)(0), rightFeat.col(J)(1), vec_matches[i])); - } - } - - /// Remove duplicates (same (x1,y1) coords that appears multiple times) - size_t getDeduplicated(std::vector & vec_matches) - { - size_t sizeBefore = _vecDecoredMatches.size(); - std::set set_deduplicated( - _vecDecoredMatches.begin(),_vecDecoredMatches.end()); - _vecDecoredMatches.assign(set_deduplicated.begin(), set_deduplicated.end()); - - vec_matches.resize(_vecDecoredMatches.size()); - for (size_t i = 0; i < _vecDecoredMatches.size(); ++i) { - const IndMatch & idxM = _vecDecoredMatches[i].index; - vec_matches[i] = idxM; + /// Remove duplicates (same (x1,y1) coords that appears multiple times) + size_t getDeduplicated(std::vector& vec_matches) + { + size_t sizeBefore = _vecDecoredMatches.size(); + std::set set_deduplicated(_vecDecoredMatches.begin(), _vecDecoredMatches.end()); + _vecDecoredMatches.assign(set_deduplicated.begin(), set_deduplicated.end()); + + vec_matches.resize(_vecDecoredMatches.size()); + for (size_t i = 0; i < _vecDecoredMatches.size(); ++i) + { + const IndMatch& idxM = _vecDecoredMatches[i].index; + vec_matches[i] = idxM; + } + + return sizeBefore != vec_matches.size(); } - return sizeBefore != vec_matches.size(); - } - - /** - * Save the corresponding matches to file. - * \param nameFile The file where matches will be saved. - * \param vec_match The matches that we want to save. - * \return bool True if everything was ok, otherwise false. - */ - bool saveMatch(const char* nameFile) const { - std::ofstream f(nameFile); - if( f.is_open() ) { - std::copy(_vecDecoredMatches.begin(), _vecDecoredMatches.end(), - std::ostream_iterator(f, "")); - } - return f.is_open(); + /** + * Save the corresponding matches to file. + * \param nameFile The file where matches will be saved. + * \param vec_match The matches that we want to save. + * \return bool True if everything was ok, otherwise false. + */ + bool saveMatch(const char* nameFile) const + { + std::ofstream f(nameFile); + if (f.is_open()) + { + std::copy(_vecDecoredMatches.begin(), _vecDecoredMatches.end(), std::ostream_iterator(f, "")); + } + return f.is_open(); } - friend std::ostream& operator<<(std::ostream& os, const IndMatchDecoratorStruct & m) + friend std::ostream& operator<<(std::ostream& os, const IndMatchDecoratorStruct& m) { - return os << m.x1 << " " << m.y1 << " " << m.x2 << " " << m.y2 << "\n"; + return os << m.x1 << " " << m.y1 << " " << m.x2 << " " << m.y2 << "\n"; } -private : - std::vector _vec_matches; - std::vector _vecDecoredMatches; + private: + std::vector _vec_matches; + std::vector _vecDecoredMatches; }; } // namespace matching diff --git a/src/aliceVision/matching/RegionsMatcher.cpp b/src/aliceVision/matching/RegionsMatcher.cpp index 38eb2f5512..00d1c755c8 100644 --- a/src/aliceVision/matching/RegionsMatcher.cpp +++ b/src/aliceVision/matching/RegionsMatcher.cpp @@ -16,168 +16,163 @@ namespace aliceVision { namespace matching { -void DistanceRatioMatch( - std::mt19937 & randomNumberGenerator, - float f_dist_ratio, - matching::EMatcherType eMatcherType, - const feature::Regions & regions_I, // database - const feature::Regions & regions_J, // query - matching::IndMatches & matches) +void DistanceRatioMatch(std::mt19937& randomNumberGenerator, + float f_dist_ratio, + matching::EMatcherType eMatcherType, + const feature::Regions& regions_I, // database + const feature::Regions& regions_J, // query + matching::IndMatches& matches) { - RegionsDatabaseMatcher matcher(randomNumberGenerator, eMatcherType, regions_I); - matcher.Match(f_dist_ratio, regions_J, matches); + RegionsDatabaseMatcher matcher(randomNumberGenerator, eMatcherType, regions_I); + matcher.Match(f_dist_ratio, regions_J, matches); } -bool RegionsDatabaseMatcher::Match( - float distRatio, - const feature::Regions & queryRegions, - matching::IndMatches & matches) const +bool RegionsDatabaseMatcher::Match(float distRatio, const feature::Regions& queryRegions, matching::IndMatches& matches) const { - if (queryRegions.RegionCount() == 0) - return false; + if (queryRegions.RegionCount() == 0) + return false; - if (!_regionsMatcher) - return false; + if (!_regionsMatcher) + return false; - return _regionsMatcher->Match(distRatio, queryRegions, matches); + return _regionsMatcher->Match(distRatio, queryRegions, matches); } -RegionsDatabaseMatcher::RegionsDatabaseMatcher(): - _matcherType(BRUTE_FORCE_L2), - _regionsMatcher(nullptr) +RegionsDatabaseMatcher::RegionsDatabaseMatcher() + : _matcherType(BRUTE_FORCE_L2), + _regionsMatcher(nullptr) {} - -RegionsDatabaseMatcher::RegionsDatabaseMatcher( - std::mt19937 & randomNumberGenerator, - matching::EMatcherType matcherType, - const feature::Regions & databaseRegions) +RegionsDatabaseMatcher::RegionsDatabaseMatcher(std::mt19937& randomNumberGenerator, + matching::EMatcherType matcherType, + const feature::Regions& databaseRegions) : _matcherType(matcherType) { - _regionsMatcher = createRegionsMatcher(randomNumberGenerator, databaseRegions, matcherType); + _regionsMatcher = createRegionsMatcher(randomNumberGenerator, databaseRegions, matcherType); } - -std::unique_ptr createRegionsMatcher(std::mt19937 & randomNumberGenerator,const feature::Regions & regions, matching::EMatcherType matcherType) +std::unique_ptr createRegionsMatcher(std::mt19937& randomNumberGenerator, + const feature::Regions& regions, + matching::EMatcherType matcherType) { - std::unique_ptr out; + std::unique_ptr out; - // Handle invalid request - if (regions.IsScalar() && matcherType == BRUTE_FORCE_HAMMING) - return out; - if (regions.IsBinary() && matcherType != BRUTE_FORCE_HAMMING) - return out; + // Handle invalid request + if (regions.IsScalar() && matcherType == BRUTE_FORCE_HAMMING) + return out; + if (regions.IsBinary() && matcherType != BRUTE_FORCE_HAMMING) + return out; - // Switch regions type ID, matcher & Metric: initialize the Matcher interface - if (regions.IsScalar()) - { - if (regions.Type_id() == typeid(unsigned char).name()) + // Switch regions type ID, matcher & Metric: initialize the Matcher interface + if (regions.IsScalar()) { - // Build on the fly unsigned char based Matcher - switch (matcherType) - { - case BRUTE_FORCE_L2: + if (regions.Type_id() == typeid(unsigned char).name()) { - typedef feature::L2_Vectorized MetricT; - typedef ArrayMatcher_bruteForce MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + // Build on the fly unsigned char based Matcher + switch (matcherType) + { + case BRUTE_FORCE_L2: + { + typedef feature::L2_Vectorized MetricT; + typedef ArrayMatcher_bruteForce MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + case ANN_L2: + { + typedef ArrayMatcher_kdtreeFlann MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + case CASCADE_HASHING_L2: + { + typedef feature::L2_Vectorized MetricT; + typedef ArrayMatcher_cascadeHashing MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + default: + ALICEVISION_LOG_WARNING("Using unknown matcher type"); + } } - break; - case ANN_L2: + else if (regions.Type_id() == typeid(float).name()) { - typedef ArrayMatcher_kdtreeFlann MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + // Build on the fly float based Matcher + switch (matcherType) + { + case BRUTE_FORCE_L2: + { + typedef feature::L2_Vectorized MetricT; + typedef ArrayMatcher_bruteForce MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + case ANN_L2: + { + typedef ArrayMatcher_kdtreeFlann MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + case CASCADE_HASHING_L2: + { + typedef feature::L2_Vectorized MetricT; + typedef ArrayMatcher_cascadeHashing MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + default: + ALICEVISION_LOG_WARNING("Using unknown matcher type"); + } } - break; - case CASCADE_HASHING_L2: + else if (regions.Type_id() == typeid(double).name()) { - typedef feature::L2_Vectorized MetricT; - typedef ArrayMatcher_cascadeHashing MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + // Build on the fly double based Matcher + switch (matcherType) + { + case BRUTE_FORCE_L2: + { + typedef feature::L2_Vectorized MetricT; + typedef ArrayMatcher_bruteForce MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + case ANN_L2: + { + typedef ArrayMatcher_kdtreeFlann MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + } + break; + case CASCADE_HASHING_L2: + { + ALICEVISION_LOG_WARNING("Not yet implemented"); + } + break; + default: + ALICEVISION_LOG_WARNING("Using unknown matcher type"); + } } - break; - default: - ALICEVISION_LOG_WARNING("Using unknown matcher type"); - } } - else if (regions.Type_id() == typeid(float).name()) + else if (regions.IsBinary() && regions.Type_id() == typeid(unsigned char).name()) { - // Build on the fly float based Matcher - switch (matcherType) - { - case BRUTE_FORCE_L2: + switch (matcherType) { - typedef feature::L2_Vectorized MetricT; - typedef ArrayMatcher_bruteForce MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); + case BRUTE_FORCE_HAMMING: + { + typedef feature::Hamming Metric; + typedef ArrayMatcher_bruteForce MatcherT; + out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, false)); + } + break; + default: + ALICEVISION_LOG_WARNING("Using unknown matcher type"); } - break; - case ANN_L2: - { - typedef ArrayMatcher_kdtreeFlann MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); - } - break; - case CASCADE_HASHING_L2: - { - typedef feature::L2_Vectorized MetricT; - typedef ArrayMatcher_cascadeHashing MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); - } - break; - default: - ALICEVISION_LOG_WARNING("Using unknown matcher type"); - } } - else if (regions.Type_id() == typeid(double).name()) + else { - // Build on the fly double based Matcher - switch (matcherType) - { - case BRUTE_FORCE_L2: - { - typedef feature::L2_Vectorized MetricT; - typedef ArrayMatcher_bruteForce MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); - } - break; - case ANN_L2: - { - typedef ArrayMatcher_kdtreeFlann MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, true)); - } - break; - case CASCADE_HASHING_L2: - { - ALICEVISION_LOG_WARNING("Not yet implemented"); - } - break; - default: - ALICEVISION_LOG_WARNING("Using unknown matcher type"); - } - } - } - else if (regions.IsBinary() && regions.Type_id() == typeid(unsigned char).name()) - { - switch (matcherType) - { - case BRUTE_FORCE_HAMMING: - { - typedef feature::Hamming Metric; - typedef ArrayMatcher_bruteForce MatcherT; - out.reset(new matching::RegionsMatcher(randomNumberGenerator, regions, false)); - } - break; - default: - ALICEVISION_LOG_WARNING("Using unknown matcher type"); + ALICEVISION_LOG_WARNING("Please consider add this region type_id to Matcher_Regions_Database::Match(...)\n" + << "typeid: " << regions.Type_id()); } - } - else - { - ALICEVISION_LOG_WARNING("Please consider add this region type_id to Matcher_Regions_Database::Match(...)\n" - << "typeid: " << regions.Type_id()); - } - return out; + return out; } } // namespace matching diff --git a/src/aliceVision/matching/RegionsMatcher.hpp b/src/aliceVision/matching/RegionsMatcher.hpp index c57c8117bb..eeb80746d4 100644 --- a/src/aliceVision/matching/RegionsMatcher.hpp +++ b/src/aliceVision/matching/RegionsMatcher.hpp @@ -33,17 +33,14 @@ namespace matching { * @param[in] regions_J The second Region to match * @param[out] matches It contains the indices of the matching features */ -void DistanceRatioMatch -( - std::mt19937 & randomNumberGenerator, - float dist_ratio, // Distance ratio - matching::EMatcherType eMatcherType, // Matcher - const feature::Regions & regions_I, // database - const feature::Regions & regions_J, // query - matching::IndMatches & matches // corresponding points +void DistanceRatioMatch(std::mt19937& randomNumberGenerator, + float dist_ratio, // Distance ratio + matching::EMatcherType eMatcherType, // Matcher + const feature::Regions& regions_I, // database + const feature::Regions& regions_J, // query + matching::IndMatches& matches // corresponding points ); - /** * @brief Interface for a matcher of Regions that keep on of the Regions stored * as a "database". The other region is then a query that is matched with respect @@ -51,144 +48,134 @@ void DistanceRatioMatch */ class IRegionsMatcher { -public: - const feature::Regions& regions_; - - /** - * @brief The destructor. - */ - virtual ~IRegionsMatcher() = 0; - - IRegionsMatcher(const feature::Regions& regions) - : regions_(regions) - { - - } - - /** - * @brief Match a Regions to the internal database using the test ratio to improve - * the robustness of the match. - * - * @param[in] f_dist_ratio The threshold for the ratio test. - * @param[in] query_regions The Regions to match. - * @param[out] vec_putative_matches It contains the indices of the matching features - * of the database and the query Regions. - * @return True if everything went well. - */ - virtual bool Match( - const float f_dist_ratio, - const feature::Regions& query_regions, - matching::IndMatches & vec_putative_matches - ) = 0; - - const feature::Regions& getDatabaseRegions() const { return regions_; } + public: + const feature::Regions& regions_; + + /** + * @brief The destructor. + */ + virtual ~IRegionsMatcher() = 0; + + IRegionsMatcher(const feature::Regions& regions) + : regions_(regions) + {} + + /** + * @brief Match a Regions to the internal database using the test ratio to improve + * the robustness of the match. + * + * @param[in] f_dist_ratio The threshold for the ratio test. + * @param[in] query_regions The Regions to match. + * @param[out] vec_putative_matches It contains the indices of the matching features + * of the database and the query Regions. + * @return True if everything went well. + */ + virtual bool Match(const float f_dist_ratio, const feature::Regions& query_regions, matching::IndMatches& vec_putative_matches) = 0; + + const feature::Regions& getDatabaseRegions() const { return regions_; } }; -inline IRegionsMatcher::~IRegionsMatcher() -{} +inline IRegionsMatcher::~IRegionsMatcher() {} /** * Match two Regions with one stored as a "database" according a Template ArrayMatcher. */ -template < class ArrayMatcherT > +template class RegionsMatcher : public IRegionsMatcher { -private: - ArrayMatcherT matcher_; - bool b_squared_metric_; // Store if the metric is squared or not -public: - typedef typename ArrayMatcherT::ScalarT Scalar; - typedef typename ArrayMatcherT::DistanceType DistanceType; - - /** - * @brief Empty constructor, by default it initializes the database to an empty database. - */ - RegionsMatcher() {} - - /** - * @brief Initialize the matcher with a Regions that will be used as database - * - * @param regions The Regions to be used as database. - * @param b_squared_metric Whether to use a squared metric for the ratio test - * when matching two Regions. - */ - RegionsMatcher(std::mt19937 & randomNumberGenerator,const feature::Regions& regions, bool b_squared_metric = false) - : IRegionsMatcher(regions), b_squared_metric_(b_squared_metric) - { - if (regions_.RegionCount() == 0) - return; - - const Scalar * tab = reinterpret_cast(regions_.DescriptorRawData()); - matcher_.Build(randomNumberGenerator, tab, regions_.RegionCount(), regions_.DescriptorLength()); - } - - /** - * @brief Match a Regions to the internal database using the test ratio to improve - * the robustness of the match. - * - * @param[in] f_dist_ratio The threshold for the ratio test. - * @param[in] query_regions The Regions to match. - * @param[out] vec_putative_matches It contains the indices of the matching features - * of the database and the query Regions. - * @return True if everything went well. - */ - bool Match(const float f_dist_ratio, - const feature::Regions& queryregions_, - matching::IndMatches & vec_putative_matches) - { - - const Scalar * queries = reinterpret_cast(queryregions_.DescriptorRawData()); - - const size_t NNN__ = 2; - matching::IndMatches vec_nIndice; - std::vector vec_fDistance; - - // Search the 2 closest features neighbours for each query descriptor - if (!matcher_.SearchNeighbours(queries, queryregions_.RegionCount(), &vec_nIndice, &vec_fDistance, NNN__)) - return false; - - assert(vec_nIndice.size() == vec_fDistance.size()); - - std::vector vec_nn_ratio_idx; - std::vector vec_distanceRatio; - // Filter the matches using a distance ratio test: - // The probability that a match is correct is determined by taking - // the ratio of distance from the closest neighbor to the distance - // of the second closest. - matching::NNdistanceRatio( - vec_fDistance.begin(), // distance start - vec_fDistance.end(), // distance end - NNN__, // Number of neighbor in iterator sequence (minimum required 2) - vec_nn_ratio_idx, // output (indices that respect the distance Ratio) - b_squared_metric_ ? Square(f_dist_ratio) : f_dist_ratio, - &vec_distanceRatio); - - vec_putative_matches.reserve(vec_nn_ratio_idx.size()); - for (size_t k=0; k < vec_nn_ratio_idx.size(); ++k) - { - const size_t index = vec_nn_ratio_idx[k] * NNN__; - vec_putative_matches.emplace_back(vec_nIndice[index]._j, vec_nIndice[index]._i - , vec_distanceRatio[k] - #ifdef ALICEVISION_DEBUG_MATCHING - , (float) vec_fDistance[index] - #endif - ); - } + private: + ArrayMatcherT matcher_; + bool b_squared_metric_; // Store if the metric is squared or not + public: + typedef typename ArrayMatcherT::ScalarT Scalar; + typedef typename ArrayMatcherT::DistanceType DistanceType; - // Remove duplicates - matching::IndMatch::getDeduplicated(vec_putative_matches); + /** + * @brief Empty constructor, by default it initializes the database to an empty database. + */ + RegionsMatcher() {} - // Remove matches that have the same (X,Y) coordinates - matching::IndMatchDecorator matchDeduplicator(vec_putative_matches, - regions_.GetRegionsPositions(), queryregions_.GetRegionsPositions()); - matchDeduplicator.getDeduplicated(vec_putative_matches); + /** + * @brief Initialize the matcher with a Regions that will be used as database + * + * @param regions The Regions to be used as database. + * @param b_squared_metric Whether to use a squared metric for the ratio test + * when matching two Regions. + */ + RegionsMatcher(std::mt19937& randomNumberGenerator, const feature::Regions& regions, bool b_squared_metric = false) + : IRegionsMatcher(regions), + b_squared_metric_(b_squared_metric) + { + if (regions_.RegionCount() == 0) + return; - return (!vec_putative_matches.empty()); - } + const Scalar* tab = reinterpret_cast(regions_.DescriptorRawData()); + matcher_.Build(randomNumberGenerator, tab, regions_.RegionCount(), regions_.DescriptorLength()); + } + /** + * @brief Match a Regions to the internal database using the test ratio to improve + * the robustness of the match. + * + * @param[in] f_dist_ratio The threshold for the ratio test. + * @param[in] query_regions The Regions to match. + * @param[out] vec_putative_matches It contains the indices of the matching features + * of the database and the query Regions. + * @return True if everything went well. + */ + bool Match(const float f_dist_ratio, const feature::Regions& queryregions_, matching::IndMatches& vec_putative_matches) + { + const Scalar* queries = reinterpret_cast(queryregions_.DescriptorRawData()); + + const size_t NNN__ = 2; + matching::IndMatches vec_nIndice; + std::vector vec_fDistance; + + // Search the 2 closest features neighbours for each query descriptor + if (!matcher_.SearchNeighbours(queries, queryregions_.RegionCount(), &vec_nIndice, &vec_fDistance, NNN__)) + return false; + + assert(vec_nIndice.size() == vec_fDistance.size()); + + std::vector vec_nn_ratio_idx; + std::vector vec_distanceRatio; + // Filter the matches using a distance ratio test: + // The probability that a match is correct is determined by taking + // the ratio of distance from the closest neighbor to the distance + // of the second closest. + matching::NNdistanceRatio(vec_fDistance.begin(), // distance start + vec_fDistance.end(), // distance end + NNN__, // Number of neighbor in iterator sequence (minimum required 2) + vec_nn_ratio_idx, // output (indices that respect the distance Ratio) + b_squared_metric_ ? Square(f_dist_ratio) : f_dist_ratio, + &vec_distanceRatio); + + vec_putative_matches.reserve(vec_nn_ratio_idx.size()); + for (size_t k = 0; k < vec_nn_ratio_idx.size(); ++k) + { + const size_t index = vec_nn_ratio_idx[k] * NNN__; + vec_putative_matches.emplace_back(vec_nIndice[index]._j, + vec_nIndice[index]._i, + vec_distanceRatio[k] +#ifdef ALICEVISION_DEBUG_MATCHING + , + (float)vec_fDistance[index] +#endif + ); + } + + // Remove duplicates + matching::IndMatch::getDeduplicated(vec_putative_matches); + + // Remove matches that have the same (X,Y) coordinates + matching::IndMatchDecorator matchDeduplicator( + vec_putative_matches, regions_.GetRegionsPositions(), queryregions_.GetRegionsPositions()); + matchDeduplicator.getDeduplicated(vec_putative_matches); + + return (!vec_putative_matches.empty()); + } }; - /** * @brief A simple class that allows to match Regions with respect a given Regions * used as "database". @@ -209,10 +196,7 @@ class RegionsDatabaseMatcher * @param[in] database_regions The Regions that will be used as database to * match other Regions (query). */ - RegionsDatabaseMatcher( - std::mt19937 & randomNumberGenerator, - matching::EMatcherType matcherType, - const feature::Regions & database_regions); + RegionsDatabaseMatcher(std::mt19937& randomNumberGenerator, matching::EMatcherType matcherType, const feature::Regions& database_regions); /** * @brief Find corresponding points between the query Regions and the database one @@ -223,64 +207,56 @@ class RegionsDatabaseMatcher * of the database and the query Regions. * @return True if everything went well. */ - bool Match( - float distRatio, - const feature::Regions & queryRegions, - matching::IndMatches & matches) const; + bool Match(float distRatio, const feature::Regions& queryRegions, matching::IndMatches& matches) const; const feature::Regions& getDatabaseRegions() const { return _regionsMatcher->getDatabaseRegions(); } private: - // Matcher Type - matching::EMatcherType _matcherType; - // The matching interface - std::unique_ptr _regionsMatcher; + // Matcher Type + matching::EMatcherType _matcherType; + // The matching interface + std::unique_ptr _regionsMatcher; }; class RegionsDatabaseMatcherPerDesc { -public: - RegionsDatabaseMatcherPerDesc( - std::mt19937 & randomNumberGenerator, - matching::EMatcherType matcherType, - const feature::MapRegionsPerDesc & queryRegions) - : _databaseRegions(queryRegions) - { - for(const auto& queryRegionsIt: queryRegions) + public: + RegionsDatabaseMatcherPerDesc(std::mt19937& randomNumberGenerator, + matching::EMatcherType matcherType, + const feature::MapRegionsPerDesc& queryRegions) + : _databaseRegions(queryRegions) { - _mapMatchers[queryRegionsIt.first] = RegionsDatabaseMatcher(randomNumberGenerator, matcherType, *queryRegionsIt.second); + for (const auto& queryRegionsIt : queryRegions) + { + _mapMatchers[queryRegionsIt.first] = RegionsDatabaseMatcher(randomNumberGenerator, matcherType, *queryRegionsIt.second); + } } - } - - bool Match( - float distRatio, - const feature::MapRegionsPerDesc & matchedRegions, - matching::MatchesPerDescType & out_putativeFeatureMatches) - { - bool res = false; - for(auto& matcherIt: _mapMatchers) + + bool Match(float distRatio, const feature::MapRegionsPerDesc& matchedRegions, matching::MatchesPerDescType& out_putativeFeatureMatches) { - const feature::EImageDescriberType descType = matcherIt.first; - res |= matcherIt.second.Match( - distRatio, - *matchedRegions.at(descType), - out_putativeFeatureMatches[descType]); + bool res = false; + for (auto& matcherIt : _mapMatchers) + { + const feature::EImageDescriberType descType = matcherIt.first; + res |= matcherIt.second.Match(distRatio, *matchedRegions.at(descType), out_putativeFeatureMatches[descType]); + } + return res; } - return res; - } - const feature::MapRegionsPerDesc & getDatabaseRegionsPerDesc() const { return _databaseRegions; } + const feature::MapRegionsPerDesc& getDatabaseRegionsPerDesc() const { return _databaseRegions; } - const feature::Regions & getDatabaseRegions(feature::EImageDescriberType descType) const { return _mapMatchers.at(descType).getDatabaseRegions(); } + const feature::Regions& getDatabaseRegions(feature::EImageDescriberType descType) const { return _mapMatchers.at(descType).getDatabaseRegions(); } - std::map & getData() { return _mapMatchers; } + std::map& getData() { return _mapMatchers; } -private: - const feature::MapRegionsPerDesc & _databaseRegions; - std::map _mapMatchers; + private: + const feature::MapRegionsPerDesc& _databaseRegions; + std::map _mapMatchers; }; -std::unique_ptr createRegionsMatcher(std::mt19937 & randomNumberGenerator,const feature::Regions & regions, matching::EMatcherType matcherType); +std::unique_ptr createRegionsMatcher(std::mt19937& randomNumberGenerator, + const feature::Regions& regions, + matching::EMatcherType matcherType); } // namespace matching } // namespace aliceVision diff --git a/src/aliceVision/matching/filters.hpp b/src/aliceVision/matching/filters.hpp index 56dc017f38..661332a2a5 100644 --- a/src/aliceVision/matching/filters.hpp +++ b/src/aliceVision/matching/filters.hpp @@ -18,197 +18,185 @@ namespace aliceVision { namespace matching { /** - * Nearest neighbor distance ratio filtering ( a < fratio * b) : - * Ratio between best and second best matches must be superior to - * given threshold. It avoid matches that have similar descriptor, - * (so it removes potential ambiguities). - * - * \param[in] first Iterator on the sequence of distance. - * \param[in] last Iterator of the end of the sequence. - * \param[in] NN Number of neighbor in iterator - * sequence (minimum required 2). - * \param[out] vec_ratioOkIndex Indexes that respect NN dist Ratio) - * \param[in] fratio Ratio value (default value 0.6f) - * - * \return void. - */ -template -inline void NNdistanceRatio -( - DataInputIterator first, // distance start - DataInputIterator last, // distance end - int NN, // Number of neighbor in iterator sequence (minimum required 2) - std::vector & vec_ratioOkIndex, // output (index that respect NN dist Ratio) - float fratio = 0.6f, // ratio value - std::vector * vec_distanceRatio = nullptr // output (ratio between best and second best matches: valid ratio (< fratio) only) -) + * Nearest neighbor distance ratio filtering ( a < fratio * b) : + * Ratio between best and second best matches must be superior to + * given threshold. It avoid matches that have similar descriptor, + * (so it removes potential ambiguities). + * + * \param[in] first Iterator on the sequence of distance. + * \param[in] last Iterator of the end of the sequence. + * \param[in] NN Number of neighbor in iterator + * sequence (minimum required 2). + * \param[out] vec_ratioOkIndex Indexes that respect NN dist Ratio) + * \param[in] fratio Ratio value (default value 0.6f) + * + * \return void. + */ +template +inline void NNdistanceRatio( + DataInputIterator first, // distance start + DataInputIterator last, // distance end + int NN, // Number of neighbor in iterator sequence (minimum required 2) + std::vector& vec_ratioOkIndex, // output (index that respect NN dist Ratio) + float fratio = 0.6f, // ratio value + std::vector* vec_distanceRatio = nullptr // output (ratio between best and second best matches: valid ratio (< fratio) only) +) { - assert( NN >= 2); - - const size_t n = std::distance(first,last); - vec_ratioOkIndex.clear(); - vec_ratioOkIndex.reserve(n/NN); - if (vec_distanceRatio != nullptr) - { - vec_distanceRatio->clear(); - vec_distanceRatio->reserve(n/NN); - } - DataInputIterator iter = first; - for(size_t i=0; i < n/NN; ++i, std::advance(iter, NN)) - { - DataInputIterator iter2 = iter; - std::advance(iter2, 1); - if ( (*iter) < fratio * (*iter2)) + assert(NN >= 2); + + const size_t n = std::distance(first, last); + vec_ratioOkIndex.clear(); + vec_ratioOkIndex.reserve(n / NN); + if (vec_distanceRatio != nullptr) + { + vec_distanceRatio->clear(); + vec_distanceRatio->reserve(n / NN); + } + DataInputIterator iter = first; + for (size_t i = 0; i < n / NN; ++i, std::advance(iter, NN)) { - vec_ratioOkIndex.push_back(static_cast(i)); - if (vec_distanceRatio != nullptr) - vec_distanceRatio->push_back((*iter)/(*iter2)); + DataInputIterator iter2 = iter; + std::advance(iter2, 1); + if ((*iter) < fratio * (*iter2)) + { + vec_ratioOkIndex.push_back(static_cast(i)); + if (vec_distanceRatio != nullptr) + vec_distanceRatio->push_back((*iter) / (*iter2)); + } } - } } /** - * Symmetric matches filtering : - * Suppose matches from dataset A to B stored in vec_matches - * Suppose matches from dataset B to A stored in vec_reversematches - * A matches is kept if (i == vec_reversematches[vec_matches[i]]) - * If NN > 1 => Only the major matches are considered. - * - * \param[in] first matches from A to B. - * \param[in] last matches from B to A. - * \param[in] NN Number of neighbor matches. - * \param[out] vec_goodIndex Indexes that respect Symmetric matches - * - * \return void. - */ -inline void SymmetricMatches(const std::vector & vec_matches, - const std::vector & vec_reversematches, - int NN, - std::vector & vec_goodIndex) + * Symmetric matches filtering : + * Suppose matches from dataset A to B stored in vec_matches + * Suppose matches from dataset B to A stored in vec_reversematches + * A matches is kept if (i == vec_reversematches[vec_matches[i]]) + * If NN > 1 => Only the major matches are considered. + * + * \param[in] first matches from A to B. + * \param[in] last matches from B to A. + * \param[in] NN Number of neighbor matches. + * \param[out] vec_goodIndex Indexes that respect Symmetric matches + * + * \return void. + */ +inline void SymmetricMatches(const std::vector& vec_matches, const std::vector& vec_reversematches, int NN, std::vector& vec_goodIndex) { - assert (NN >= 1); - - int index = 0; - for (size_t i=0; i= 1); + + int index = 0; + for (size_t i = 0; i < vec_matches.size(); i += NN, ++index) + { + // Add the match only if we have a symmetric result. + if (index == vec_reversematches[vec_matches[i] * NN]) + { + vec_goodIndex.push_back(index); + } } - } } /** - * Intersect two container via Iterator. - * Keep element that exist in the two sequence of data. - * - * \param[in] aStart Begin iterator on the sequence A. - * \param[in] aEnd End iterator on sequence A. - * \param[in] bStart Begin iterator on the sequence B. - * \param[in] bEnd End iterator on sequence B. - * \param[out] vec_out Merged output indexes. - * - * \return void. - */ -template -inline void IntersectMatches( Iterator aStart, Iterator aEnd, - Iterator bStart, Iterator bEnd, - std::vector & vec_out) + * Intersect two container via Iterator. + * Keep element that exist in the two sequence of data. + * + * \param[in] aStart Begin iterator on the sequence A. + * \param[in] aEnd End iterator on sequence A. + * \param[in] bStart Begin iterator on the sequence B. + * \param[in] bEnd End iterator on sequence B. + * \param[out] vec_out Merged output indexes. + * + * \return void. + */ +template +inline void IntersectMatches(Iterator aStart, Iterator aEnd, Iterator bStart, Iterator bEnd, std::vector& vec_out) { - //-- Compute the intersection of the two vector - //--- Use STL to perform it. Require that the input vectors are sorted. - std::set intersect; - std::set_intersection( aStart, aEnd, - bStart, bEnd, - std::inserter( intersect, intersect.begin() ) ); - - vec_out = std::vector(intersect.begin(), intersect.end()); + //-- Compute the intersection of the two vector + //--- Use STL to perform it. Require that the input vectors are sorted. + std::set intersect; + std::set_intersection(aStart, aEnd, bStart, bEnd, std::inserter(intersect, intersect.begin())); + + vec_out = std::vector(intersect.begin(), intersect.end()); } enum eMatchFilter { - MATCHFILTER_SYMMETRIC = 1, - MATCHFILTER_NNDISTANCERATIO = 2, - MATCHFILER_SYM_AND_NNDISTANCERATIO = MATCHFILTER_SYMMETRIC | MATCHFILTER_NNDISTANCERATIO + MATCHFILTER_SYMMETRIC = 1, + MATCHFILTER_NNDISTANCERATIO = 2, + MATCHFILER_SYM_AND_NNDISTANCERATIO = MATCHFILTER_SYMMETRIC | MATCHFILTER_NNDISTANCERATIO }; -inline void Filter( int NN, - const std::vector & vec_Matches01, - const std::vector & vec_distance01, - const std::vector & vec_Matches10, - const std::vector & vec_distance10, - std::vector & vec_outIndex, - eMatchFilter matchFilter, - float fNNDistanceRatio = 0.6f) +inline void Filter(int NN, + const std::vector& vec_Matches01, + const std::vector& vec_distance01, + const std::vector& vec_Matches10, + const std::vector& vec_distance10, + std::vector& vec_outIndex, + eMatchFilter matchFilter, + float fNNDistanceRatio = 0.6f) { - std::vector vec_symmetricIndex, vec_NNDistRatioIndexes; - - if (matchFilter == MATCHFILTER_SYMMETRIC || - matchFilter == MATCHFILER_SYM_AND_NNDISTANCERATIO) - { - SymmetricMatches(vec_Matches01, - vec_Matches10, - NN, - vec_symmetricIndex); - } - - if (matchFilter == MATCHFILTER_NNDISTANCERATIO || - matchFilter == MATCHFILER_SYM_AND_NNDISTANCERATIO) - { - if ( NN == 1) + std::vector vec_symmetricIndex, vec_NNDistRatioIndexes; + + if (matchFilter == MATCHFILTER_SYMMETRIC || matchFilter == MATCHFILER_SYM_AND_NNDISTANCERATIO) { - vec_NNDistRatioIndexes = vec_Matches01; + SymmetricMatches(vec_Matches01, vec_Matches10, NN, vec_symmetricIndex); } - NNdistanceRatio( vec_distance01.begin(), // distance start - vec_distance01.end(), // distance end - NN, // Number of neighbor in iterator sequence (minimum required 2) - vec_NNDistRatioIndexes, // output (index that respect Lowe Ratio) - fNNDistanceRatio); - } - - switch (matchFilter) - { - case MATCHFILTER_NNDISTANCERATIO: - - for (size_t i=0; i < vec_NNDistRatioIndexes.size()-1&& vec_NNDistRatioIndexes.size()>0; ++i) + if (matchFilter == MATCHFILTER_NNDISTANCERATIO || matchFilter == MATCHFILER_SYM_AND_NNDISTANCERATIO) { - vec_outIndex.push_back( IndMatch(vec_NNDistRatioIndexes[i], vec_Matches01[vec_NNDistRatioIndexes[i]*NN]) ); + if (NN == 1) + { + vec_NNDistRatioIndexes = vec_Matches01; + } + + NNdistanceRatio(vec_distance01.begin(), // distance start + vec_distance01.end(), // distance end + NN, // Number of neighbor in iterator sequence (minimum required 2) + vec_NNDistRatioIndexes, // output (index that respect Lowe Ratio) + fNNDistanceRatio); } - break; + switch (matchFilter) + { + case MATCHFILTER_NNDISTANCERATIO: - case MATCHFILTER_SYMMETRIC: + for (size_t i = 0; i < vec_NNDistRatioIndexes.size() - 1 && vec_NNDistRatioIndexes.size() > 0; ++i) + { + vec_outIndex.push_back(IndMatch(vec_NNDistRatioIndexes[i], vec_Matches01[vec_NNDistRatioIndexes[i] * NN])); + } - for (size_t i=0; i < vec_symmetricIndex.size()-1&& vec_symmetricIndex.size()>0; ++i) - { - vec_outIndex.push_back( IndMatch(vec_symmetricIndex[i], vec_Matches01[vec_symmetricIndex[i]*NN]) ); - } + break; - break; + case MATCHFILTER_SYMMETRIC: - case MATCHFILER_SYM_AND_NNDISTANCERATIO: + for (size_t i = 0; i < vec_symmetricIndex.size() - 1 && vec_symmetricIndex.size() > 0; ++i) + { + vec_outIndex.push_back(IndMatch(vec_symmetricIndex[i], vec_Matches01[vec_symmetricIndex[i] * NN])); + } - std::vector vec_indexes; - //-- Compute the intersection of the two vector - IntersectMatches(vec_symmetricIndex.begin(), vec_symmetricIndex.end(), - vec_NNDistRatioIndexes.begin(), vec_NNDistRatioIndexes.end(), - vec_indexes); - - for (size_t i=0; i < vec_indexes.size()-1 && vec_indexes.size()>0; ++i) - vec_outIndex.push_back( IndMatch(vec_indexes[i], vec_Matches01[vec_indexes[i]*NN]) ); - - break; - } - - // Remove multi-index - { - std::sort(vec_outIndex.begin(), vec_outIndex.end()); - std::vector::iterator end = std::unique(vec_outIndex.begin(), vec_outIndex.end()); - if(end != vec_outIndex.end()) { - vec_outIndex.erase(end, vec_outIndex.end()); + break; + + case MATCHFILER_SYM_AND_NNDISTANCERATIO: + + std::vector vec_indexes; + //-- Compute the intersection of the two vector + IntersectMatches( + vec_symmetricIndex.begin(), vec_symmetricIndex.end(), vec_NNDistRatioIndexes.begin(), vec_NNDistRatioIndexes.end(), vec_indexes); + + for (size_t i = 0; i < vec_indexes.size() - 1 && vec_indexes.size() > 0; ++i) + vec_outIndex.push_back(IndMatch(vec_indexes[i], vec_Matches01[vec_indexes[i] * NN])); + + break; + } + + // Remove multi-index + { + std::sort(vec_outIndex.begin(), vec_outIndex.end()); + std::vector::iterator end = std::unique(vec_outIndex.begin(), vec_outIndex.end()); + if (end != vec_outIndex.end()) + { + vec_outIndex.erase(end, vec_outIndex.end()); + } } - } } } // namespace matching diff --git a/src/aliceVision/matching/filters_test.cpp b/src/aliceVision/matching/filters_test.cpp index f1bce718af..9d743675d9 100644 --- a/src/aliceVision/matching/filters_test.cpp +++ b/src/aliceVision/matching/filters_test.cpp @@ -17,22 +17,20 @@ using namespace aliceVision::matching; /// Sorted vector intersection (increasing order) BOOST_AUTO_TEST_CASE(matching_setIntersection) { - int tab0[] = {0, 1, 2, 3, 4, 5, 6, 7}; - int tab1[] = {0, 1, 8, 3, 4, 9, 6, 7}; - std::set vec_0(tab0, tab0+8); - std::set vec_1(tab1, tab1+8); - /// Array must be sorted + int tab0[] = {0, 1, 2, 3, 4, 5, 6, 7}; + int tab1[] = {0, 1, 8, 3, 4, 9, 6, 7}; + std::set vec_0(tab0, tab0 + 8); + std::set vec_1(tab1, tab1 + 8); + /// Array must be sorted - std::vector vec_intersect; - IntersectMatches(vec_0.begin(), vec_0.end(), - vec_1.begin(), vec_1.end(), - vec_intersect); + std::vector vec_intersect; + IntersectMatches(vec_0.begin(), vec_0.end(), vec_1.begin(), vec_1.end(), vec_intersect); - BOOST_CHECK_EQUAL(6, vec_intersect.size()); - BOOST_CHECK_EQUAL(0, vec_intersect[0]); - BOOST_CHECK_EQUAL(1, vec_intersect[1]); - BOOST_CHECK_EQUAL(3, vec_intersect[2]); - BOOST_CHECK_EQUAL(4, vec_intersect[3]); - BOOST_CHECK_EQUAL(6, vec_intersect[4]); - BOOST_CHECK_EQUAL(7, vec_intersect[5]); + BOOST_CHECK_EQUAL(6, vec_intersect.size()); + BOOST_CHECK_EQUAL(0, vec_intersect[0]); + BOOST_CHECK_EQUAL(1, vec_intersect[1]); + BOOST_CHECK_EQUAL(3, vec_intersect[2]); + BOOST_CHECK_EQUAL(4, vec_intersect[3]); + BOOST_CHECK_EQUAL(6, vec_intersect[4]); + BOOST_CHECK_EQUAL(7, vec_intersect[5]); } diff --git a/src/aliceVision/matching/guidedMatching.cpp b/src/aliceVision/matching/guidedMatching.cpp index 948c4d81c8..314410eccb 100644 --- a/src/aliceVision/matching/guidedMatching.cpp +++ b/src/aliceVision/matching/guidedMatching.cpp @@ -12,13 +12,13 @@ namespace matching { unsigned int pix_to_bucket(const Vec2i& x, int W, int H) { - if(x(1) == 0) - return x(0); // Top border - if(x(0) == W - 1) - return W - 1 + x(1); // Right border - if(x(1) == H - 1) - return 2 * W + H - 3 - x(0); // Bottom border - return 2 * (W + H - 2) - x(1); // Left border + if (x(1) == 0) + return x(0); // Top border + if (x(0) == W - 1) + return W - 1 + x(1); // Right border + if (x(1) == H - 1) + return 2 * W + H - 3 - x(0); // Bottom border + return 2 * (W + H - 2) - x(1); // Left border } bool line_to_endPoints(const Vec3& line, int W, int H, Vec2& x0, Vec2& x1) @@ -29,13 +29,13 @@ bool line_to_endPoints(const Vec3& line, int W, int H, Vec2& x0, Vec2& x1) float r1, r2; // Intersection with Y axis (0 or W-1) - if(b != 0) + if (b != 0) { const double x = (b < 0) ? 0 : W - 1; double y = -(a * x + c) / b; - if(y < 0) + if (y < 0) y = 0.; - else if(y >= H) + else if (y >= H) y = H - 1; r1 = fabs(a * x + b * y + c); x0 << x, y; @@ -46,13 +46,13 @@ bool line_to_endPoints(const Vec3& line, int W, int H, Vec2& x0, Vec2& x1) } // Intersection with X axis (0 or H-1) - if(a != 0) + if (a != 0) { const double y = (a < 0) ? H - 1 : 0; double x = -(b * y + c) / a; - if(x < 0) + if (x < 0) x = 0.; - else if(x >= W) + else if (x >= W) x = W - 1; r2 = fabs(a * x + b * y + c); x1 << x, y; @@ -63,11 +63,11 @@ bool line_to_endPoints(const Vec3& line, int W, int H, Vec2& x0, Vec2& x1) } // Choose x0 to be as close as the intersection axis - if(r1 > r2) + if (r1 > r2) std::swap(x0, x1); return true; } -} -} \ No newline at end of file +} // namespace matching +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/matching/guidedMatching.hpp b/src/aliceVision/matching/guidedMatching.hpp index 94dbc17c91..c29bf430ee 100644 --- a/src/aliceVision/matching/guidedMatching.hpp +++ b/src/aliceVision/matching/guidedMatching.hpp @@ -33,95 +33,93 @@ namespace matching { * @param[in] errorTh Maximal authorized error threshold * @param[out] out_validMatches Ouput corresponding index */ -template +template void guidedMatching(const ModelT& mod, const Mat& xLeft, const Mat& xRight, double errorTh, matching::IndMatches& out_validMatches) { - assert(xLeft.rows() == xRight.rows()); + assert(xLeft.rows() == xRight.rows()); - const ErrorT errorEstimator = ErrorT(); + const ErrorT errorEstimator = ErrorT(); - // looking for the corresponding points that have - // the smallest distance (smaller than the provided Threshold) - for(Mat::Index i = 0; i < xLeft.cols(); ++i) - { - double min = std::numeric_limits::max(); - matching::IndMatch match; - for(Mat::Index j = 0; j < xRight.cols(); ++j) - { - // compute the geometric error: error to the model - const double err = errorEstimator.error(mod, xLeft.col(i), xRight.col(j)); - - // if smaller error update corresponding index - if(err < errorTh && err < min) - { - min = err; - match = matching::IndMatch(i, j); - } - } - if(min < errorTh) + // looking for the corresponding points that have + // the smallest distance (smaller than the provided Threshold) + for (Mat::Index i = 0; i < xLeft.cols(); ++i) { - // save the best corresponding index - out_validMatches.push_back(match); + double min = std::numeric_limits::max(); + matching::IndMatch match; + for (Mat::Index j = 0; j < xRight.cols(); ++j) + { + // compute the geometric error: error to the model + const double err = errorEstimator.error(mod, xLeft.col(i), xRight.col(j)); + + // if smaller error update corresponding index + if (err < errorTh && err < min) + { + min = err; + match = matching::IndMatch(i, j); + } + } + if (min < errorTh) + { + // save the best corresponding index + out_validMatches.push_back(match); + } } - } - // remove duplicates (when multiple points at same position exist) - matching::IndMatch::getDeduplicated(out_validMatches); + // remove duplicates (when multiple points at same position exist) + matching::IndMatch::getDeduplicated(out_validMatches); } /** * @brief Struct to help filtering of correspondence according update of two smallest distance. * @note useful for descriptor distance ratio filtering */ -template +template struct distanceRatio { - /// best and second best distance - DistT bd, sbd; - /// best corresponding index - std::size_t idx; - - distanceRatio() - : bd(std::numeric_limits::max()) - , sbd(std::numeric_limits::max()) - , idx(0) - {} - - /** - * @brief Update match according the provided distance - */ - inline bool update(std::size_t index, DistT dist) - { - if(dist < bd) // best than any previous + /// best and second best distance + DistT bd, sbd; + /// best corresponding index + std::size_t idx; + + distanceRatio() + : bd(std::numeric_limits::max()), + sbd(std::numeric_limits::max()), + idx(0) + {} + + /** + * @brief Update match according the provided distance + */ + inline bool update(std::size_t index, DistT dist) { - idx = index; - // update and swap - sbd = dist; - std::swap(bd, sbd); - return true; + if (dist < bd) // best than any previous + { + idx = index; + // update and swap + sbd = dist; + std::swap(bd, sbd); + return true; + } + else if (dist < sbd) + { + sbd = dist; + return true; + } + return false; } - else if(dist < sbd) + + /** + * @brief Return if the ratio of distance is ok or not + */ + inline bool isValid(const double distRatio) const { - sbd = dist; - return true; + // check: + // - that two best distance have been found + // - the distance ratio + return (sbd != std::numeric_limits::max() && bd < distRatio * sbd); } - return false; - } - - /** - * @brief Return if the ratio of distance is ok or not - */ - inline bool isValid(const double distRatio) const - { - // check: - // - that two best distance have been found - // - the distance ratio - return (sbd != std::numeric_limits::max() - && bd < distRatio * sbd); - } }; - /** * @brief Guided Matching (features + descriptors with distance ratio): * Use a model to find valid correspondences: @@ -152,44 +150,42 @@ void guidedMatching(const ModelT& mod, double distRatio, matching::IndMatches& vec_corresponding_index) { - assert(xLeft.rows() == xRight.rows()); - assert(xLeft.cols() == lDescriptors.size()); - assert(xRight.cols() == rDescriptors.size()); - - MetricT metric; + assert(xLeft.rows() == xRight.rows()); + assert(xLeft.cols() == lDescriptors.size()); + assert(xRight.cols() == rDescriptors.size()); - // Looking for the corresponding points that have to satisfy: - // 1. a geometric distance below the provided Threshold - // 2. a distance ratio between descriptors of valid geometric correspondencess + MetricT metric; - for(Mat::Index i = 0; i < xLeft.cols(); ++i) - { + // Looking for the corresponding points that have to satisfy: + // 1. a geometric distance below the provided Threshold + // 2. a distance ratio between descriptors of valid geometric correspondencess - distanceRatio dR; - for(Mat::Index j = 0; j < xRight.cols(); ++j) + for (Mat::Index i = 0; i < xLeft.cols(); ++i) { - // Compute the geometric error: error to the model - const double geomErr = ErrorT::Error(mod, // The model - xLeft.col(i), - xRight.col(j)); // The corresponding points - if(geomErr < errorTh) - { - const typename MetricT::ResultType descDist = - metric(lDescriptors[i].getData(), rDescriptors[j].getData(), DescriptorT::static_size); - // Update the corresponding points & distance (if required) - dR.update(j, descDist); - } - } - // Add correspondence only iff the distance ratio is valid - if(dR.isValid(distRatio)) - { - // save the best corresponding index - vec_corresponding_index.emplace_back(i, dR.idx); + distanceRatio dR; + for (Mat::Index j = 0; j < xRight.cols(); ++j) + { + // Compute the geometric error: error to the model + const double geomErr = ErrorT::Error(mod, // The model + xLeft.col(i), + xRight.col(j)); // The corresponding points + if (geomErr < errorTh) + { + const typename MetricT::ResultType descDist = metric(lDescriptors[i].getData(), rDescriptors[j].getData(), DescriptorT::static_size); + // Update the corresponding points & distance (if required) + dR.update(j, descDist); + } + } + // Add correspondence only iff the distance ratio is valid + if (dR.isValid(distRatio)) + { + // save the best corresponding index + vec_corresponding_index.emplace_back(i, dR.idx); + } } - } - // Remove duplicates (when multiple points at same position exist) - matching::IndMatch::getDeduplicated(vec_corresponding_index); + // Remove duplicates (when multiple points at same position exist) + matching::IndMatch::getDeduplicated(vec_corresponding_index); } /** @@ -220,61 +216,61 @@ void guidedMatching(const ModelT& mod, double distRatio, matching::IndMatches& out_matches) { - // looking for the corresponding points that have to satisfy: - // 1. a geometric distance below the provided Threshold - // 2. a distance ratio between descriptors of valid geometric correspondencess - - const ErrorT errorEstimator = ErrorT(); - - // build region positions arrays (in order to un-distord on-demand point position once) - std::vector lRegionsPos(lRegions.RegionCount()); - std::vector rRegionsPos(rRegions.RegionCount()); - - if(camL && camL->isValid()) - { - for(std::size_t i = 0; i < lRegions.RegionCount(); ++i) - lRegionsPos[i] = camL->get_ud_pixel(lRegions.GetRegionPosition(i)); - } - else - { - for(std::size_t i = 0; i < lRegions.RegionCount(); ++i) - lRegionsPos[i] = lRegions.GetRegionPosition(i); - } - if(camR && camR->isValid()) - { - for(std::size_t i = 0; i < rRegions.RegionCount(); ++i) - rRegionsPos[i] = camR->get_ud_pixel(rRegions.GetRegionPosition(i)); - } - else - { - for(std::size_t i = 0; i < rRegions.RegionCount(); ++i) - rRegionsPos[i] = rRegions.GetRegionPosition(i); - } - - for(std::size_t i = 0; i < lRegions.RegionCount(); ++i) - { - distanceRatio dR; - for(std::size_t j = 0; j < rRegions.RegionCount(); ++j) + // looking for the corresponding points that have to satisfy: + // 1. a geometric distance below the provided Threshold + // 2. a distance ratio between descriptors of valid geometric correspondencess + + const ErrorT errorEstimator = ErrorT(); + + // build region positions arrays (in order to un-distord on-demand point position once) + std::vector lRegionsPos(lRegions.RegionCount()); + std::vector rRegionsPos(rRegions.RegionCount()); + + if (camL && camL->isValid()) + { + for (std::size_t i = 0; i < lRegions.RegionCount(); ++i) + lRegionsPos[i] = camL->get_ud_pixel(lRegions.GetRegionPosition(i)); + } + else { - // compute the geometric error: error to the model - const double geomErr = errorEstimator.error(mod, lRegionsPos[i], rRegionsPos[j]); - - if(geomErr < errorTh) - { - // update the corresponding points & distance (if required) - dR.update(j, lRegions.SquaredDescriptorDistance(i, &rRegions, j)); - } + for (std::size_t i = 0; i < lRegions.RegionCount(); ++i) + lRegionsPos[i] = lRegions.GetRegionPosition(i); } - // add correspondence only iff the distance ratio is valid - if(dR.isValid(distRatio)) + if (camR && camR->isValid()) { - // save the best corresponding index - out_matches.emplace_back(i, dR.idx); + for (std::size_t i = 0; i < rRegions.RegionCount(); ++i) + rRegionsPos[i] = camR->get_ud_pixel(rRegions.GetRegionPosition(i)); + } + else + { + for (std::size_t i = 0; i < rRegions.RegionCount(); ++i) + rRegionsPos[i] = rRegions.GetRegionPosition(i); } - } - // remove duplicates (when multiple points at same position exist) - matching::IndMatch::getDeduplicated(out_matches); + for (std::size_t i = 0; i < lRegions.RegionCount(); ++i) + { + distanceRatio dR; + for (std::size_t j = 0; j < rRegions.RegionCount(); ++j) + { + // compute the geometric error: error to the model + const double geomErr = errorEstimator.error(mod, lRegionsPos[i], rRegionsPos[j]); + + if (geomErr < errorTh) + { + // update the corresponding points & distance (if required) + dR.update(j, lRegions.SquaredDescriptorDistance(i, &rRegions, j)); + } + } + // add correspondence only iff the distance ratio is valid + if (dR.isValid(distRatio)) + { + // save the best corresponding index + out_matches.emplace_back(i, dR.idx); + } + } + + // remove duplicates (when multiple points at same position exist) + matching::IndMatch::getDeduplicated(out_matches); } /** @@ -305,21 +301,22 @@ void guidedMatching(const ModelT& mod, double distRatio, matching::MatchesPerDescType& out_matchesPerDesc) { - const std::vector descTypes = getCommonDescTypes(lRegions, rRegions); - if(descTypes.empty()) - return; - - for(const feature::EImageDescriberType descType: descTypes) - { - guidedMatching(mod, camL, *lRegions.at(descType), camR, *rRegions.at(descType), errorTh, distRatio, out_matchesPerDesc[descType]); - } + const std::vector descTypes = getCommonDescTypes(lRegions, rRegions); + if (descTypes.empty()) + return; + + for (const feature::EImageDescriberType descType : descTypes) + { + guidedMatching( + mod, camL, *lRegions.at(descType), camR, *rRegions.at(descType), errorTh, distRatio, out_matchesPerDesc[descType]); + } } /** * @brief Compute a bucket index from an epipolar point * (the one that is closer to image border intersection) */ -unsigned int pix_to_bucket(const Vec2i &x, int W, int H); +unsigned int pix_to_bucket(const Vec2i& x, int W, int H); /** * @brief Compute intersection of the epipolar line with the image border @@ -366,102 +363,101 @@ void guidedMatchingFundamentalFast(const Mat3& FMat, double distRatio, matching::IndMatches& vec_corresponding_index) { - // Looking for the corresponding points that have to satisfy: - // 1. a geometric distance below the provided Threshold - // 2. a distance ratio between descriptors of valid geometric correspondencess - // - // - Cluster left point according their epipolar line border intersection. - // - For each right point, compute threshold limited bandwidth and compare only - // points that belong to this range (limited buckets). - - // Normalize F and epipole for (ep2->p2) line adequation - Mat3 F = FMat; - Vec3 ep2 = epipole2; - if(ep2(2) > 0.0) - { - F = -F; - ep2 = -ep2; - } - ep2 = ep2 / ep2(2); - - //-- - //-- Store point in the corresponding epipolar line bucket - //-- - using Bucket_vec = std::vector; - using Buckets_vec = std::vector; - const int nb_buckets = 2 * (widthR + heightR - 2); - - Buckets_vec buckets(nb_buckets); - for(std::size_t i = 0; i < lRegions.RegionCount(); ++i) - { - // Compute epipolar line - const Vec2 l_pt = (camL && camL->isValid()) ? camL->get_ud_pixel(lRegions.GetRegionPosition(i)) : lRegions.GetRegionPosition(i); - const Vec3 line = F * Vec3(l_pt(0), l_pt(1), 1.); - // If the epipolar line exists in Right image - Vec2 x0, x1; - if(line_to_endPoints(line, widthR, heightR, x0, x1)) + // Looking for the corresponding points that have to satisfy: + // 1. a geometric distance below the provided Threshold + // 2. a distance ratio between descriptors of valid geometric correspondencess + // + // - Cluster left point according their epipolar line border intersection. + // - For each right point, compute threshold limited bandwidth and compare only + // points that belong to this range (limited buckets). + + // Normalize F and epipole for (ep2->p2) line adequation + Mat3 F = FMat; + Vec3 ep2 = epipole2; + if (ep2(2) > 0.0) { - // Find in which cluster the point belongs - const auto bucket = pix_to_bucket(x0.cast(), widthR, heightR); - buckets[bucket].push_back(i); + F = -F; + ep2 = -ep2; } - } - - // For each point in right image, find if there is good candidates. - std::vector > dR(lRegions.RegionCount()); - for(std::size_t j = 0; j < rRegions.RegionCount(); ++j) - { - // According the point: - // - Compute it's epipolar line from the epipole - // - compute the range of possible bucket by computing - // the epipolar line gauge limitation introduced by the tolerated pixel error - - const Vec2 xR = (camR && camR->isValid()) ? camR->get_ud_pixel(rRegions.GetRegionPosition(j)) : rRegions.GetRegionPosition(j); - const Vec3 l2 = ep2.cross(Vec3(xR(0), xR(1), 1.)); - const Vec2 n = l2.head<2>() * (sqrt(errorTh) / l2.head<2>().norm()); - - const Vec3 l2min = ep2.cross(Vec3(xR(0) - n(0), xR(1) - n(1), 1.)); - const Vec3 l2max = ep2.cross(Vec3(xR(0) + n(0), xR(1) + n(1), 1.)); - - // Compute corresponding buckets - Vec2 x0, x1; - if(!line_to_endPoints(l2min, widthR, heightR, x0, x1)) - continue; - - const auto bucket_start = pix_to_bucket(x0.cast(), widthR, heightR); - - if(!line_to_endPoints(l2max, widthR, heightR, x0, x1)) - continue; - - const auto bucket_stop = pix_to_bucket(x0.cast(), widthR, heightR); - - if(bucket_stop > bucket_start) + ep2 = ep2 / ep2(2); + + //-- + //-- Store point in the corresponding epipolar line bucket + //-- + using Bucket_vec = std::vector; + using Buckets_vec = std::vector; + const int nb_buckets = 2 * (widthR + heightR - 2); + + Buckets_vec buckets(nb_buckets); + for (std::size_t i = 0; i < lRegions.RegionCount(); ++i) { - // test candidate buckets - for(Buckets_vec::const_iterator itBs = buckets.begin() + bucket_start; - itBs != buckets.begin() + bucket_stop; ++itBs) - { - const Bucket_vec & bucket = *itBs; - for(unsigned int i : bucket) + // Compute epipolar line + const Vec2 l_pt = (camL && camL->isValid()) ? camL->get_ud_pixel(lRegions.GetRegionPosition(i)) : lRegions.GetRegionPosition(i); + const Vec3 line = F * Vec3(l_pt(0), l_pt(1), 1.); + // If the epipolar line exists in Right image + Vec2 x0, x1; + if (line_to_endPoints(line, widthR, heightR, x0, x1)) { - // Compute descriptor distance - const double descDist = lRegions.SquaredDescriptorDistance(i, &rRegions, j); - // Update the corresponding points & distance (if required) - dR[i].update(j, descDist); + // Find in which cluster the point belongs + const auto bucket = pix_to_bucket(x0.cast(), widthR, heightR); + buckets[bucket].push_back(i); } - } } - } - // Check distance ratio validity - for(std::size_t i = 0; i < dR.size(); ++i) - { - if(dR[i].isValid(distRatio)) + + // For each point in right image, find if there is good candidates. + std::vector> dR(lRegions.RegionCount()); + for (std::size_t j = 0; j < rRegions.RegionCount(); ++j) { - // save the best corresponding index - vec_corresponding_index.emplace_back(i, dR[i].idx); + // According the point: + // - Compute it's epipolar line from the epipole + // - compute the range of possible bucket by computing + // the epipolar line gauge limitation introduced by the tolerated pixel error + + const Vec2 xR = (camR && camR->isValid()) ? camR->get_ud_pixel(rRegions.GetRegionPosition(j)) : rRegions.GetRegionPosition(j); + const Vec3 l2 = ep2.cross(Vec3(xR(0), xR(1), 1.)); + const Vec2 n = l2.head<2>() * (sqrt(errorTh) / l2.head<2>().norm()); + + const Vec3 l2min = ep2.cross(Vec3(xR(0) - n(0), xR(1) - n(1), 1.)); + const Vec3 l2max = ep2.cross(Vec3(xR(0) + n(0), xR(1) + n(1), 1.)); + + // Compute corresponding buckets + Vec2 x0, x1; + if (!line_to_endPoints(l2min, widthR, heightR, x0, x1)) + continue; + + const auto bucket_start = pix_to_bucket(x0.cast(), widthR, heightR); + + if (!line_to_endPoints(l2max, widthR, heightR, x0, x1)) + continue; + + const auto bucket_stop = pix_to_bucket(x0.cast(), widthR, heightR); + + if (bucket_stop > bucket_start) + { + // test candidate buckets + for (Buckets_vec::const_iterator itBs = buckets.begin() + bucket_start; itBs != buckets.begin() + bucket_stop; ++itBs) + { + const Bucket_vec& bucket = *itBs; + for (unsigned int i : bucket) + { + // Compute descriptor distance + const double descDist = lRegions.SquaredDescriptorDistance(i, &rRegions, j); + // Update the corresponding points & distance (if required) + dR[i].update(j, descDist); + } + } + } + } + // Check distance ratio validity + for (std::size_t i = 0; i < dR.size(); ++i) + { + if (dR[i].isValid(distRatio)) + { + // save the best corresponding index + vec_corresponding_index.emplace_back(i, dR[i].idx); + } } - } } -} -} +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/indMatch_test.cpp b/src/aliceVision/matching/indMatch_test.cpp index 1cd8f00ceb..15869cdd89 100644 --- a/src/aliceVision/matching/indMatch_test.cpp +++ b/src/aliceVision/matching/indMatch_test.cpp @@ -24,141 +24,141 @@ namespace fs = boost::filesystem; BOOST_AUTO_TEST_CASE(IndMatch_IO) { - const std::string testFolder = "matchingTest"; - boost::filesystem::create_directory(testFolder); - { - std::set viewsKeys; - PairwiseMatches matches; - - // Test save + load of empty data - BOOST_CHECK(Save(matches, testFolder, "txt", false)); - BOOST_CHECK(Load(matches, viewsKeys, {testFolder}, {})); - BOOST_CHECK_EQUAL(0, matches.size()); - fs::remove_all("./1/"); - } - boost::filesystem::remove_all(testFolder); - boost::filesystem::create_directory(testFolder); - { - std::set viewsKeys; - PairwiseMatches matches; - - // Test save + load of empty data - BOOST_CHECK(Save(matches, testFolder, "txt", true)); - BOOST_CHECK(!Load(matches, viewsKeys, {testFolder}, {})); - BOOST_CHECK_EQUAL(0, matches.size()); - fs::remove_all("./2/"); - } - boost::filesystem::remove_all(testFolder); - boost::filesystem::create_directory(testFolder); - { - std::set viewsKeys = {0, 1, 2}; - PairwiseMatches matches; - // Test export with not empty data - matches[std::make_pair(0,1)][EImageDescriberType::UNKNOWN] = {{0,0},{1,1}}; - matches[std::make_pair(1,2)][EImageDescriberType::UNKNOWN] = {{0,0},{1,1}, {2,2}}; - - BOOST_CHECK(Save(matches, testFolder, "txt", false)); - matches.clear(); - BOOST_CHECK(Load(matches, viewsKeys, {testFolder}, {EImageDescriberType::UNKNOWN})); - BOOST_CHECK_EQUAL(2, matches.size()); - BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(0,1))); - BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(1,2))); - BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0,1)).at(EImageDescriberType::UNKNOWN).size()); - BOOST_CHECK_EQUAL(3, matches.at(std::make_pair(1,2)).at(EImageDescriberType::UNKNOWN).size()); - fs::remove_all("./3/"); - } - boost::filesystem::remove_all(testFolder); - boost::filesystem::create_directory(testFolder); - { - std::set viewsKeys = {0, 1, 2}; - PairwiseMatches matches; - // Test export with not empty data - matches[std::make_pair(0,1)][EImageDescriberType::UNKNOWN] = {{0,0},{1,1}}; - matches[std::make_pair(1,2)][EImageDescriberType::UNKNOWN] = {{0,0},{1,1}, {2,2}}; - - BOOST_CHECK(Save(matches, testFolder, "txt", true)); - // Create an additional pair-wise matches entry - matches[std::make_pair(0,2)][EImageDescriberType::UNKNOWN] = {{3,3},{4,4}}; - - // Don't clear matches and reload saved file - BOOST_CHECK(Load(matches, viewsKeys, {testFolder}, {EImageDescriberType::UNKNOWN})); - BOOST_CHECK_EQUAL(3, matches.size()); - BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(0,1))); - BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(1,2))); - BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(0,2))); - // 'matches' was not cleared, pair-wise matches are accumulated ({0, 1} and {1,2} contains duplicates) - BOOST_CHECK_EQUAL(4, matches.at(std::make_pair(0,1)).at(EImageDescriberType::UNKNOWN).size()); - BOOST_CHECK_EQUAL(6, matches.at(std::make_pair(1,2)).at(EImageDescriberType::UNKNOWN).size()); - BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0,2)).at(EImageDescriberType::UNKNOWN).size()); - - // Deduplicate matches - // - {0, 1} has duplicates - BOOST_CHECK(IndMatch::getDeduplicated(matches.at(std::make_pair(0,1)).at(EImageDescriberType::UNKNOWN))); - // - {1, 2} has duplicates - BOOST_CHECK(IndMatch::getDeduplicated(matches.at(std::make_pair(1,2)).at(EImageDescriberType::UNKNOWN))); - // - {0, 2} does not have duplicates - BOOST_CHECK(!IndMatch::getDeduplicated(matches.at(std::make_pair(0,2)).at(EImageDescriberType::UNKNOWN))); - - // Check matches count after deduplication - BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0,1)).at(EImageDescriberType::UNKNOWN).size()); - BOOST_CHECK_EQUAL(3, matches.at(std::make_pair(1,2)).at(EImageDescriberType::UNKNOWN).size()); - BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0,2)).at(EImageDescriberType::UNKNOWN).size()); - fs::remove_all("./4/"); - } - boost::filesystem::remove_all(testFolder); + const std::string testFolder = "matchingTest"; + boost::filesystem::create_directory(testFolder); + { + std::set viewsKeys; + PairwiseMatches matches; + + // Test save + load of empty data + BOOST_CHECK(Save(matches, testFolder, "txt", false)); + BOOST_CHECK(Load(matches, viewsKeys, {testFolder}, {})); + BOOST_CHECK_EQUAL(0, matches.size()); + fs::remove_all("./1/"); + } + boost::filesystem::remove_all(testFolder); + boost::filesystem::create_directory(testFolder); + { + std::set viewsKeys; + PairwiseMatches matches; + + // Test save + load of empty data + BOOST_CHECK(Save(matches, testFolder, "txt", true)); + BOOST_CHECK(!Load(matches, viewsKeys, {testFolder}, {})); + BOOST_CHECK_EQUAL(0, matches.size()); + fs::remove_all("./2/"); + } + boost::filesystem::remove_all(testFolder); + boost::filesystem::create_directory(testFolder); + { + std::set viewsKeys = {0, 1, 2}; + PairwiseMatches matches; + // Test export with not empty data + matches[std::make_pair(0, 1)][EImageDescriberType::UNKNOWN] = {{0, 0}, {1, 1}}; + matches[std::make_pair(1, 2)][EImageDescriberType::UNKNOWN] = {{0, 0}, {1, 1}, {2, 2}}; + + BOOST_CHECK(Save(matches, testFolder, "txt", false)); + matches.clear(); + BOOST_CHECK(Load(matches, viewsKeys, {testFolder}, {EImageDescriberType::UNKNOWN})); + BOOST_CHECK_EQUAL(2, matches.size()); + BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(0, 1))); + BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(1, 2))); + BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0, 1)).at(EImageDescriberType::UNKNOWN).size()); + BOOST_CHECK_EQUAL(3, matches.at(std::make_pair(1, 2)).at(EImageDescriberType::UNKNOWN).size()); + fs::remove_all("./3/"); + } + boost::filesystem::remove_all(testFolder); + boost::filesystem::create_directory(testFolder); + { + std::set viewsKeys = {0, 1, 2}; + PairwiseMatches matches; + // Test export with not empty data + matches[std::make_pair(0, 1)][EImageDescriberType::UNKNOWN] = {{0, 0}, {1, 1}}; + matches[std::make_pair(1, 2)][EImageDescriberType::UNKNOWN] = {{0, 0}, {1, 1}, {2, 2}}; + + BOOST_CHECK(Save(matches, testFolder, "txt", true)); + // Create an additional pair-wise matches entry + matches[std::make_pair(0, 2)][EImageDescriberType::UNKNOWN] = {{3, 3}, {4, 4}}; + + // Don't clear matches and reload saved file + BOOST_CHECK(Load(matches, viewsKeys, {testFolder}, {EImageDescriberType::UNKNOWN})); + BOOST_CHECK_EQUAL(3, matches.size()); + BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(0, 1))); + BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(1, 2))); + BOOST_CHECK_EQUAL(1, matches.count(std::make_pair(0, 2))); + // 'matches' was not cleared, pair-wise matches are accumulated ({0, 1} and {1,2} contains duplicates) + BOOST_CHECK_EQUAL(4, matches.at(std::make_pair(0, 1)).at(EImageDescriberType::UNKNOWN).size()); + BOOST_CHECK_EQUAL(6, matches.at(std::make_pair(1, 2)).at(EImageDescriberType::UNKNOWN).size()); + BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0, 2)).at(EImageDescriberType::UNKNOWN).size()); + + // Deduplicate matches + // - {0, 1} has duplicates + BOOST_CHECK(IndMatch::getDeduplicated(matches.at(std::make_pair(0, 1)).at(EImageDescriberType::UNKNOWN))); + // - {1, 2} has duplicates + BOOST_CHECK(IndMatch::getDeduplicated(matches.at(std::make_pair(1, 2)).at(EImageDescriberType::UNKNOWN))); + // - {0, 2} does not have duplicates + BOOST_CHECK(!IndMatch::getDeduplicated(matches.at(std::make_pair(0, 2)).at(EImageDescriberType::UNKNOWN))); + + // Check matches count after deduplication + BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0, 1)).at(EImageDescriberType::UNKNOWN).size()); + BOOST_CHECK_EQUAL(3, matches.at(std::make_pair(1, 2)).at(EImageDescriberType::UNKNOWN).size()); + BOOST_CHECK_EQUAL(2, matches.at(std::make_pair(0, 2)).at(EImageDescriberType::UNKNOWN).size()); + fs::remove_all("./4/"); + } + boost::filesystem::remove_all(testFolder); } BOOST_AUTO_TEST_CASE(IndMatch_DuplicateRemoval_NoRemoval) { - std::vector vec_indMatch; + std::vector vec_indMatch; - vec_indMatch.push_back(IndMatch(2,3)); // 0 - vec_indMatch.push_back(IndMatch(0,1)); // 1 + vec_indMatch.push_back(IndMatch(2, 3)); // 0 + vec_indMatch.push_back(IndMatch(0, 1)); // 1 - // Check no removal - BOOST_CHECK(!IndMatch::getDeduplicated(vec_indMatch)); + // Check no removal + BOOST_CHECK(!IndMatch::getDeduplicated(vec_indMatch)); - // Check lexigraphical sorting - // Due to lexigraphical sorting (0,1) must appears first - BOOST_CHECK_EQUAL(IndMatch(0,1), vec_indMatch[0]); - BOOST_CHECK_EQUAL(IndMatch(2,3), vec_indMatch[1]); + // Check lexigraphical sorting + // Due to lexigraphical sorting (0,1) must appears first + BOOST_CHECK_EQUAL(IndMatch(0, 1), vec_indMatch[0]); + BOOST_CHECK_EQUAL(IndMatch(2, 3), vec_indMatch[1]); } BOOST_AUTO_TEST_CASE(IndMatch_DuplicateRemoval_Simple) { - std::vector vec_indMatch; + std::vector vec_indMatch; - vec_indMatch.push_back(IndMatch(0,1)); // 0 - vec_indMatch.push_back(IndMatch(0,1)); // 1: error with addition 0 + vec_indMatch.push_back(IndMatch(0, 1)); // 0 + vec_indMatch.push_back(IndMatch(0, 1)); // 1: error with addition 0 - vec_indMatch.push_back(IndMatch(1,2)); // 2 - vec_indMatch.push_back(IndMatch(1,2)); // 3: error with addition 2 + vec_indMatch.push_back(IndMatch(1, 2)); // 2 + vec_indMatch.push_back(IndMatch(1, 2)); // 3: error with addition 2 - BOOST_CHECK(IndMatch::getDeduplicated(vec_indMatch)); - // Two matches must remain (line 0 and 2) - BOOST_CHECK_EQUAL(2, vec_indMatch.size()); + BOOST_CHECK(IndMatch::getDeduplicated(vec_indMatch)); + // Two matches must remain (line 0 and 2) + BOOST_CHECK_EQUAL(2, vec_indMatch.size()); } BOOST_AUTO_TEST_CASE(IndMatch_DuplicateRemoval) { - std::vector vec_indMatch; - - vec_indMatch.push_back(IndMatch(0,1)); - vec_indMatch.push_back(IndMatch(0,1)); // Error defined before - - // Add some other matches - vec_indMatch.push_back(IndMatch(0,2)); - vec_indMatch.push_back(IndMatch(1,1)); - vec_indMatch.push_back(IndMatch(2,3)); - vec_indMatch.push_back(IndMatch(3,3)); - - BOOST_CHECK(IndMatch::getDeduplicated(vec_indMatch)); - // Five matches must remain (one (0,1) must disappear) - BOOST_CHECK_EQUAL(5, vec_indMatch.size()); - - BOOST_CHECK_EQUAL(IndMatch(0,1), vec_indMatch[0]); - BOOST_CHECK_EQUAL(IndMatch(0,2), vec_indMatch[1]); - BOOST_CHECK_EQUAL(IndMatch(1,1), vec_indMatch[2]); - BOOST_CHECK_EQUAL(IndMatch(2,3), vec_indMatch[3]); - BOOST_CHECK_EQUAL(IndMatch(3,3), vec_indMatch[4]); + std::vector vec_indMatch; + + vec_indMatch.push_back(IndMatch(0, 1)); + vec_indMatch.push_back(IndMatch(0, 1)); // Error defined before + + // Add some other matches + vec_indMatch.push_back(IndMatch(0, 2)); + vec_indMatch.push_back(IndMatch(1, 1)); + vec_indMatch.push_back(IndMatch(2, 3)); + vec_indMatch.push_back(IndMatch(3, 3)); + + BOOST_CHECK(IndMatch::getDeduplicated(vec_indMatch)); + // Five matches must remain (one (0,1) must disappear) + BOOST_CHECK_EQUAL(5, vec_indMatch.size()); + + BOOST_CHECK_EQUAL(IndMatch(0, 1), vec_indMatch[0]); + BOOST_CHECK_EQUAL(IndMatch(0, 2), vec_indMatch[1]); + BOOST_CHECK_EQUAL(IndMatch(1, 1), vec_indMatch[2]); + BOOST_CHECK_EQUAL(IndMatch(2, 3), vec_indMatch[3]); + BOOST_CHECK_EQUAL(IndMatch(3, 3), vec_indMatch[4]); } diff --git a/src/aliceVision/matching/io.cpp b/src/aliceVision/matching/io.cpp index 7ed417a75f..d1365698de 100644 --- a/src/aliceVision/matching/io.cpp +++ b/src/aliceVision/matching/io.cpp @@ -26,108 +26,105 @@ namespace matching { bool LoadMatchFile(PairwiseMatches& matches, const std::string& filepath) { - const std::string ext = fs::extension(filepath); + const std::string ext = fs::extension(filepath); - if(!fs::exists(filepath)) - return false; + if (!fs::exists(filepath)) + return false; - if(ext == ".txt") - { - std::ifstream stream(filepath); - if (!stream.is_open()) - return false; - - // Read from the text file - // I J - // nbDescType - // descType matchesCount - // idx idx - // ... - // descType matchesCount - // idx idx - // ... - std::size_t I = 0; - std::size_t J = 0; - std::size_t nbDescType = 0; - while(stream >> I >> J >> nbDescType) + if (ext == ".txt") { - for(std::size_t i = 0; i < nbDescType; ++i) - { - std::string descTypeStr; - std::size_t nbMatches = 0; - // Read descType and number of matches - stream >> descTypeStr >> nbMatches; - - feature::EImageDescriberType descType = feature::EImageDescriberType_stringToEnum(descTypeStr); - std::vector matchesPerDesc(nbMatches); - // Read all matches - for(std::size_t i = 0; i < nbMatches; ++i) + std::ifstream stream(filepath); + if (!stream.is_open()) + return false; + + // Read from the text file + // I J + // nbDescType + // descType matchesCount + // idx idx + // ... + // descType matchesCount + // idx idx + // ... + std::size_t I = 0; + std::size_t J = 0; + std::size_t nbDescType = 0; + while (stream >> I >> J >> nbDescType) { - stream >> matchesPerDesc[i]; + for (std::size_t i = 0; i < nbDescType; ++i) + { + std::string descTypeStr; + std::size_t nbMatches = 0; + // Read descType and number of matches + stream >> descTypeStr >> nbMatches; + + feature::EImageDescriberType descType = feature::EImageDescriberType_stringToEnum(descTypeStr); + std::vector matchesPerDesc(nbMatches); + // Read all matches + for (std::size_t i = 0; i < nbMatches; ++i) + { + stream >> matchesPerDesc[i]; + } + matches[std::make_pair(I, J)][descType] = std::move(matchesPerDesc); + } } - matches[std::make_pair(I,J)][descType] = std::move(matchesPerDesc); - } + stream.close(); + return true; } - stream.close(); - return true; - } - else - { - ALICEVISION_LOG_WARNING("Unknown matching file format: " << ext); - } - return false; + else + { + ALICEVISION_LOG_WARNING("Unknown matching file format: " << ext); + } + return false; } void filterMatchesByViews(PairwiseMatches& matches, const std::set& viewsKeys) { - matching::PairwiseMatches filteredMatches; - for (matching::PairwiseMatches::const_iterator iter = matches.begin(); - iter != matches.end(); - ++iter) - { - if(viewsKeys.find(iter->first.first) != viewsKeys.end() && - viewsKeys.find(iter->first.second) != viewsKeys.end()) + matching::PairwiseMatches filteredMatches; + for (matching::PairwiseMatches::const_iterator iter = matches.begin(); iter != matches.end(); ++iter) { - filteredMatches.insert(*iter); + if (viewsKeys.find(iter->first.first) != viewsKeys.end() && viewsKeys.find(iter->first.second) != viewsKeys.end()) + { + filteredMatches.insert(*iter); + } } - } - matches.swap(filteredMatches); + matches.swap(filteredMatches); } -void filterTopMatches(PairwiseMatches& allMatches, int maxNum, int minNum) +void filterTopMatches(PairwiseMatches& allMatches, int maxNum, int minNum) { - if (maxNum <= 0 && minNum <=0) - return; - if (maxNum > 0 && minNum > maxNum) - throw std::runtime_error("The minimum number of matches is higher than the maximum."); - - for(auto& matchesPerDesc: allMatches) - { - for(auto& matches: matchesPerDesc.second) + if (maxNum <= 0 && minNum <= 0) + return; + if (maxNum > 0 && minNum > maxNum) + throw std::runtime_error("The minimum number of matches is higher than the maximum."); + + for (auto& matchesPerDesc : allMatches) { - IndMatches& m = matches.second; - if (minNum > 0 && m.size() < minNum) - m.clear(); - else if (maxNum > 0 && m.size() > maxNum) - m.erase(m.begin()+ maxNum, m.end()); + for (auto& matches : matchesPerDesc.second) + { + IndMatches& m = matches.second; + if (minNum > 0 && m.size() < minNum) + m.clear(); + else if (maxNum > 0 && m.size() > maxNum) + m.erase(m.begin() + maxNum, m.end()); + } } - } } void filterMatchesByDesc(PairwiseMatches& allMatches, const std::vector& descTypesFilter) { - matching::PairwiseMatches filteredMatches; - for(const auto& matchesPerDesc: allMatches) - { - for(const auto& matches: matchesPerDesc.second) + matching::PairwiseMatches filteredMatches; + for (const auto& matchesPerDesc : allMatches) { - const IndMatches& m = matches.second; - // if current descType in descTypesFilter - if(std::find(descTypesFilter.begin(), descTypesFilter.end(), matches.first) != descTypesFilter.end()) - filteredMatches[matchesPerDesc.first][matches.first] = m; + for (const auto& matches : matchesPerDesc.second) + { + const IndMatches& m = matches.second; + // if current descType in descTypesFilter + if (std::find(descTypesFilter.begin(), descTypesFilter.end(), matches.first) != descTypesFilter.end()) + filteredMatches[matchesPerDesc.first][matches.first] = m; + } } - } - allMatches.swap(filteredMatches); + allMatches.swap(filteredMatches); } std::size_t LoadMatchFilePerImage(PairwiseMatches& matches, @@ -135,35 +132,35 @@ std::size_t LoadMatchFilePerImage(PairwiseMatches& matches, const std::string& folder, const std::string& extension) { - int nbLoadedMatchFiles = 0; - // Load one match file per image - #pragma omp parallel for num_threads(3) - for(ptrdiff_t i = 0; i < static_cast(viewsKeys.size()); ++i) - { - std::set::const_iterator it = viewsKeys.begin(); - std::advance(it, i); - const IndexT idView = *it; - const std::string matchFilename = std::to_string(idView) + "." + extension; - PairwiseMatches fileMatches; - if(!LoadMatchFile(fileMatches, (fs::path(folder) / matchFilename).string() )) - { - #pragma omp critical - { - ALICEVISION_LOG_DEBUG("Unable to load match file: " << matchFilename << " in: " << folder); - } - continue; - } - #pragma omp critical + int nbLoadedMatchFiles = 0; +// Load one match file per image +#pragma omp parallel for num_threads(3) + for (ptrdiff_t i = 0; i < static_cast(viewsKeys.size()); ++i) { - ++nbLoadedMatchFiles; - // merge the loaded matches into the output - for(const auto& v: fileMatches) - { - matches[v.first] = v.second; - } + std::set::const_iterator it = viewsKeys.begin(); + std::advance(it, i); + const IndexT idView = *it; + const std::string matchFilename = std::to_string(idView) + "." + extension; + PairwiseMatches fileMatches; + if (!LoadMatchFile(fileMatches, (fs::path(folder) / matchFilename).string())) + { +#pragma omp critical + { + ALICEVISION_LOG_DEBUG("Unable to load match file: " << matchFilename << " in: " << folder); + } + continue; + } +#pragma omp critical + { + ++nbLoadedMatchFiles; + // merge the loaded matches into the output + for (const auto& v : fileMatches) + { + matches[v.first] = v.second; + } + } } - } - return nbLoadedMatchFiles; + return nbLoadedMatchFiles; } /** @@ -174,52 +171,50 @@ std::size_t LoadMatchFilePerImage(PairwiseMatches& matches, */ std::size_t loadMatchesFromFolder(PairwiseMatches& matches, const std::string& folder, const std::string& pattern) { - std::size_t nbLoadedMatchFiles = 0; - std::vector matchFiles; - // list all matches files in 'folder' matching (i.e containing) 'pattern' - for(const auto& entry : boost::make_iterator_range(fs::directory_iterator(folder), {})) - { - if(entry.path().string().find(pattern) != std::string::npos) - { - matchFiles.push_back(entry.path().string()); - } - } - - #pragma omp parallel for num_threads(3) - for(int i = 0; i < matchFiles.size(); ++i) - { - const std::string& matchFile = matchFiles[i]; - PairwiseMatches fileMatches; - ALICEVISION_LOG_DEBUG("Loading match file: " << matchFile); - if(!LoadMatchFile(fileMatches, matchFile)) + std::size_t nbLoadedMatchFiles = 0; + std::vector matchFiles; + // list all matches files in 'folder' matching (i.e containing) 'pattern' + for (const auto& entry : boost::make_iterator_range(fs::directory_iterator(folder), {})) { - ALICEVISION_LOG_WARNING("Unable to load match file: " << matchFile); - continue; + if (entry.path().string().find(pattern) != std::string::npos) + { + matchFiles.push_back(entry.path().string()); + } } - #pragma omp critical - { - for(const auto& matchesPerView: fileMatches) + +#pragma omp parallel for num_threads(3) + for (int i = 0; i < matchFiles.size(); ++i) { - const Pair& pair = matchesPerView.first; - const MatchesPerDescType& pairMatches = matchesPerView.second; - for(const auto& matchesPerDescType : pairMatches) - { - const feature::EImageDescriberType& descType = matchesPerDescType.first; - const auto& pairMatches = matchesPerDescType.second; - // merge in global map - std::copy( - std::make_move_iterator(pairMatches.begin()), - std::make_move_iterator(pairMatches.end()), - std::back_inserter(matches[pair][descType]) - ); - } + const std::string& matchFile = matchFiles[i]; + PairwiseMatches fileMatches; + ALICEVISION_LOG_DEBUG("Loading match file: " << matchFile); + if (!LoadMatchFile(fileMatches, matchFile)) + { + ALICEVISION_LOG_WARNING("Unable to load match file: " << matchFile); + continue; + } +#pragma omp critical + { + for (const auto& matchesPerView : fileMatches) + { + const Pair& pair = matchesPerView.first; + const MatchesPerDescType& pairMatches = matchesPerView.second; + for (const auto& matchesPerDescType : pairMatches) + { + const feature::EImageDescriberType& descType = matchesPerDescType.first; + const auto& pairMatches = matchesPerDescType.second; + // merge in global map + std::copy(std::make_move_iterator(pairMatches.begin()), + std::make_move_iterator(pairMatches.end()), + std::back_inserter(matches[pair][descType])); + } + } + ++nbLoadedMatchFiles; + } } - ++nbLoadedMatchFiles; - } - } - if(!nbLoadedMatchFiles) - ALICEVISION_LOG_WARNING("No matches file loaded in: " << folder); - return nbLoadedMatchFiles; + if (!nbLoadedMatchFiles) + ALICEVISION_LOG_WARNING("No matches file loaded in: " << folder); + return nbLoadedMatchFiles; } bool Load(PairwiseMatches& matches, @@ -229,165 +224,148 @@ bool Load(PairwiseMatches& matches, int maxNbMatches, int minNbMatches) { - std::size_t nbLoadedMatchFiles = 0; - const std::string pattern = "matches.txt"; - - // build up a set with normalized paths to remove duplicates - std::set foldersSet; - for(const auto& folder : folders) - { - if(fs::exists(folder)) + std::size_t nbLoadedMatchFiles = 0; + const std::string pattern = "matches.txt"; + + // build up a set with normalized paths to remove duplicates + std::set foldersSet; + for (const auto& folder : folders) { - foldersSet.insert(fs::canonical(folder).string()); + if (fs::exists(folder)) + { + foldersSet.insert(fs::canonical(folder).string()); + } } - } - - for(const auto& folder : foldersSet) - { - nbLoadedMatchFiles += loadMatchesFromFolder(matches, folder, pattern); - } - - if(!nbLoadedMatchFiles) - return false; - const auto logMatches = [](const PairwiseMatches& m) - { - for(const auto& imagePairIt: m) + for (const auto& folder : foldersSet) { - std::stringstream ss; - ss << " * " << imagePairIt.first.first << "-" << imagePairIt.first.second << ": " << imagePairIt.second.getNbAllMatches() << " "; - for(const auto& matchesPerDeskIt: imagePairIt.second) - ss << " [" << feature::EImageDescriberType_enumToString(matchesPerDeskIt.first) << ": " << matchesPerDeskIt.second.size() << "]"; - ALICEVISION_LOG_TRACE(ss.str()); + nbLoadedMatchFiles += loadMatchesFromFolder(matches, folder, pattern); } - }; - ALICEVISION_LOG_TRACE("Matches per image pair (before filtering):"); - logMatches(matches); + if (!nbLoadedMatchFiles) + return false; - if(!viewsKeysFilter.empty()) - filterMatchesByViews(matches, viewsKeysFilter); + const auto logMatches = [](const PairwiseMatches& m) { + for (const auto& imagePairIt : m) + { + std::stringstream ss; + ss << " * " << imagePairIt.first.first << "-" << imagePairIt.first.second << ": " << imagePairIt.second.getNbAllMatches() << " "; + for (const auto& matchesPerDeskIt : imagePairIt.second) + ss << " [" << feature::EImageDescriberType_enumToString(matchesPerDeskIt.first) << ": " << matchesPerDeskIt.second.size() << "]"; + ALICEVISION_LOG_TRACE(ss.str()); + } + }; - if(!descTypesFilter.empty()) - filterMatchesByDesc(matches, descTypesFilter); + ALICEVISION_LOG_TRACE("Matches per image pair (before filtering):"); + logMatches(matches); - filterTopMatches(matches, maxNbMatches, minNbMatches); + if (!viewsKeysFilter.empty()) + filterMatchesByViews(matches, viewsKeysFilter); - ALICEVISION_LOG_TRACE("Matches per image pair (after filtering):"); - logMatches(matches); + if (!descTypesFilter.empty()) + filterMatchesByDesc(matches, descTypesFilter); - return true; -} + filterTopMatches(matches, maxNbMatches, minNbMatches); + ALICEVISION_LOG_TRACE("Matches per image pair (after filtering):"); + logMatches(matches); + + return true; +} class MatchExporter { -private: - void saveTxt( - const std::string& filepath, - const PairwiseMatches::const_iterator& matchBegin, - const PairwiseMatches::const_iterator& matchEnd) - { - const fs::path bPath = fs::path(filepath); - const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + bPath.extension().string(); - - // write temporary file + private: + void saveTxt(const std::string& filepath, const PairwiseMatches::const_iterator& matchBegin, const PairwiseMatches::const_iterator& matchEnd) { - std::ofstream stream(tmpPath, std::ios::out); - for(PairwiseMatches::const_iterator match = matchBegin; - match != matchEnd; - ++match) - { - const std::size_t I = match->first.first; - const std::size_t J = match->first.second; - const MatchesPerDescType & matchesPerDesc = match->second; - stream << I << " " << J << '\n' - << matchesPerDesc.size() << '\n'; - for(const auto& m: matchesPerDesc) + const fs::path bPath = fs::path(filepath); + const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + bPath.extension().string(); + + // write temporary file { - stream << feature::EImageDescriberType_enumToString(m.first) << " " << m.second.size() << '\n'; - copy(m.second.begin(), m.second.end(), std::ostream_iterator(stream, "\n")); + std::ofstream stream(tmpPath, std::ios::out); + for (PairwiseMatches::const_iterator match = matchBegin; match != matchEnd; ++match) + { + const std::size_t I = match->first.first; + const std::size_t J = match->first.second; + const MatchesPerDescType& matchesPerDesc = match->second; + stream << I << " " << J << '\n' << matchesPerDesc.size() << '\n'; + for (const auto& m : matchesPerDesc) + { + stream << feature::EImageDescriberType_enumToString(m.first) << " " << m.second.size() << '\n'; + copy(m.second.begin(), m.second.end(), std::ostream_iterator(stream, "\n")); + } + } } - } + + // rename temporary file + fs::rename(tmpPath, filepath); } - // rename temporary file - fs::rename(tmpPath, filepath); - } - -public: - MatchExporter( - const PairwiseMatches& matches, - const std::string& folder, - const std::string& filename) - : m_matches(matches) - , m_directory(folder) - , m_filename(filename) - , m_ext(fs::extension(filename)) - {} - - ~MatchExporter() = default; - - void saveGlobalFile() - { - const std::string filepath = (fs::path(m_directory) / m_filename).string(); - - if(m_ext == ".txt") - saveTxt(filepath, m_matches.begin(), m_matches.end()); - else - throw std::runtime_error(std::string("Unknown matching file format: ") + m_ext); - } - - /// Export matches into separate files, one for each image. - void saveOneFilePerImage() - { - std::set keys; - std::transform( - m_matches.begin(), m_matches.end(), - std::inserter(keys, keys.begin()), - [](const PairwiseMatches::value_type &v) { return v.first.first; }); - - PairwiseMatches::const_iterator matchBegin = m_matches.begin(); - PairwiseMatches::const_iterator matchEnd = m_matches.end(); - for(IndexT key: keys) + public: + MatchExporter(const PairwiseMatches& matches, const std::string& folder, const std::string& filename) + : m_matches(matches), + m_directory(folder), + m_filename(filename), + m_ext(fs::extension(filename)) + {} + + ~MatchExporter() = default; + + void saveGlobalFile() { - PairwiseMatches::const_iterator match = matchBegin; - while(match != matchEnd && match->first.first == key) - ++match; - const std::string filepath = (fs::path(m_directory) / (std::to_string(key) + "." + m_filename)).string(); - ALICEVISION_LOG_DEBUG("Export Matches in: " << filepath); - - if(m_ext == ".txt") - saveTxt(filepath, matchBegin, match); - else - throw std::runtime_error(std::string("Unknown matching file format: ") + m_ext); - - matchBegin = match; + const std::string filepath = (fs::path(m_directory) / m_filename).string(); + + if (m_ext == ".txt") + saveTxt(filepath, m_matches.begin(), m_matches.end()); + else + throw std::runtime_error(std::string("Unknown matching file format: ") + m_ext); } - } -public: - const PairwiseMatches& m_matches; - const std::string m_ext; - std::string m_directory; - std::string m_filename; + /// Export matches into separate files, one for each image. + void saveOneFilePerImage() + { + std::set keys; + std::transform( + m_matches.begin(), m_matches.end(), std::inserter(keys, keys.begin()), [](const PairwiseMatches::value_type& v) { return v.first.first; }); + + PairwiseMatches::const_iterator matchBegin = m_matches.begin(); + PairwiseMatches::const_iterator matchEnd = m_matches.end(); + for (IndexT key : keys) + { + PairwiseMatches::const_iterator match = matchBegin; + while (match != matchEnd && match->first.first == key) + ++match; + const std::string filepath = (fs::path(m_directory) / (std::to_string(key) + "." + m_filename)).string(); + ALICEVISION_LOG_DEBUG("Export Matches in: " << filepath); + + if (m_ext == ".txt") + saveTxt(filepath, matchBegin, match); + else + throw std::runtime_error(std::string("Unknown matching file format: ") + m_ext); + + matchBegin = match; + } + } + + public: + const PairwiseMatches& m_matches; + const std::string m_ext; + std::string m_directory; + std::string m_filename; }; -bool Save(const PairwiseMatches& matches, - const std::string& folder, - const std::string& extension, - bool matchFilePerImage, - const std::string& prefix) +bool Save(const PairwiseMatches& matches, const std::string& folder, const std::string& extension, bool matchFilePerImage, const std::string& prefix) { - const std::string filename = prefix + "matches." + extension; - MatchExporter exporter(matches, folder, filename); + const std::string filename = prefix + "matches." + extension; + MatchExporter exporter(matches, folder, filename); - if(matchFilePerImage) - exporter.saveOneFilePerImage(); - else - exporter.saveGlobalFile(); + if (matchFilePerImage) + exporter.saveOneFilePerImage(); + else + exporter.saveGlobalFile(); - return true; + return true; } } // namespace matching diff --git a/src/aliceVision/matching/io.hpp b/src/aliceVision/matching/io.hpp index 7fbfd848e8..2a64928c2b 100644 --- a/src/aliceVision/matching/io.hpp +++ b/src/aliceVision/matching/io.hpp @@ -14,7 +14,6 @@ namespace aliceVision { namespace matching { - /** * @brief Load a match file. * diff --git a/src/aliceVision/matching/kvld/algorithm.cpp b/src/aliceVision/matching/kvld/algorithm.cpp index 231332c6ee..dd817b3991 100644 --- a/src/aliceVision/matching/kvld/algorithm.cpp +++ b/src/aliceVision/matching/kvld/algorithm.cpp @@ -15,77 +15,70 @@ #include -IntegralImages::IntegralImages(const aliceVision::image::Image< float >& I) +IntegralImages::IntegralImages(const aliceVision::image::Image& I) { - map.resize( I.Width() + 1, I.Height() + 1 ); - map.fill( 0 ); - for( int y = 0; y < I.Height(); y++ ) - { - for( int x = 0; x < I.Width(); x++ ) + map.resize(I.Width() + 1, I.Height() + 1); + map.fill(0); + for (int y = 0; y < I.Height(); y++) { - map( y + 1, x + 1 ) = double( I( y, x ) ) + map( y, x + 1 ) + map( y + 1, x ) - map( y, x ); + for (int x = 0; x < I.Width(); x++) + { + map(y + 1, x + 1) = double(I(y, x)) + map(y, x + 1) + map(y + 1, x) - map(y, x); + } } - } } -float getRange( const aliceVision::image::Image< float >& I, int a, const float p ) +float getRange(const aliceVision::image::Image& I, int a, const float p) { - float range = sqrt(float(3.f * I.Height() * I.Width()) / (p * a * boost::math::constants::pi())); - return range; + float range = sqrt(float(3.f * I.Height() * I.Width()) / (p * a * boost::math::constants::pi())); + return range; } - //=============================IO interface======================// -std::ofstream& writeDetector( std::ofstream& out, const aliceVision::feature::PointFeature& feature ) +std::ofstream& writeDetector(std::ofstream& out, const aliceVision::feature::PointFeature& feature) { - out << feature.x() << " " - << feature.y() << " " - << feature.scale() << " " - << feature.orientation() << std::endl; - return out; + out << feature.x() << " " << feature.y() << " " << feature.scale() << " " << feature.orientation() << std::endl; + return out; } -std::ifstream& readDetector( std::ifstream& in, aliceVision::feature::PointFeature& point ) +std::ifstream& readDetector(std::ifstream& in, aliceVision::feature::PointFeature& point) { - in >> point.x() - >> point.y() - >> point.scale() - >> point.orientation(); - return in; + in >> point.x() >> point.y() >> point.scale() >> point.orientation(); + return in; } -bool anglefrom( float x, float y, float& angle ) +bool anglefrom(float x, float y, float& angle) { using namespace boost::math; - if(x != 0) + if (x != 0) angle = std::atan(y / x); - else if(y > 0) + else if (y > 0) angle = constants::pi() / 2; - else if(y < 0) + else if (y < 0) angle = -constants::pi() / 2; else return false; - if(x < 0) + if (x < 0) angle += constants::pi(); - while(angle < 0) + while (angle < 0) angle += 2 * constants::pi(); - while(angle >= 2 * constants::pi()) + while (angle >= 2 * constants::pi()) angle -= 2 * constants::pi(); assert(angle >= 0 && angle < 2 * constants::pi()); return true; } -double angle_difference( double angle1, double angle2 ) +double angle_difference(double angle1, double angle2) { using namespace boost::math; double angle = angle1 - angle2; - while(angle < 0) + while (angle < 0) angle += 2 * constants::pi(); - while(angle >= 2 * constants::pi()) + while (angle >= 2 * constants::pi()) angle -= 2 * constants::pi(); assert(angle <= 2 * constants::pi() && angle >= 0); diff --git a/src/aliceVision/matching/kvld/algorithm.h b/src/aliceVision/matching/kvld/algorithm.h index 8bd9784a0e..1d60192673 100644 --- a/src/aliceVision/matching/kvld/algorithm.h +++ b/src/aliceVision/matching/kvld/algorithm.h @@ -29,147 +29,148 @@ #include #include - - //============================== simplified structure of a point=============================// -//if you set KVLD geometry verification to false, you only need to fill x and y in a point structure +// if you set KVLD geometry verification to false, you only need to fill x and y in a point structure struct PointS { - float x, y; // point position - float scale; // point scale - float angle; // point orientation - - PointS( float x = (0.f), float y = (0.f)): - x( x ), y( y ), scale(0.f), angle(0.f){} - PointS( const float& x, const float& y,const float& angle,const float& scale): - x( x ), y( y ), angle( angle ), scale( scale ){} + float x, y; // point position + float scale; // point scale + float angle; // point orientation + + PointS(float x = (0.f), float y = (0.f)) + : x(x), + y(y), + scale(0.f), + angle(0.f) + {} + PointS(const float& x, const float& y, const float& angle, const float& scale) + : x(x), + y(y), + angle(angle), + scale(scale) + {} }; //===================================== integral image ====================================// -//It is used to efficiently construct the pyramid of scale images in KVLD +// It is used to efficiently construct the pyramid of scale images in KVLD struct IntegralImages { - aliceVision::image::Image< double > map; - - IntegralImages(const aliceVision::image::Image< float >& I); - - inline double operator()( double x1, double y1, double x2, double y2 )const - { - return get( x2, y2 ) - get( x1, y2 ) - get( x2, y1 ) + get( x1, y1 ); - } - inline double operator()( double x, double y, double size ) const - { - double window = 0.5 * size; - return ( get( x + window, y + window ) - get( x - window, y + window ) - get( x + window, y - window ) + get( x - window, y - window ) ) / ( 4 * window * window ); - } -private : - inline double get( double x, double y )const - { - int ix = int( x ); - int iy = int( y ); - double dx = x - ix; - double dy = y - iy; - if( dx == 0 && dy == 0 ) - return map( iy, ix ); - if( dx == 0 ) - return map( iy, ix ) * ( 1 - dy ) + map( iy + 1, ix ) * dy; - if( dy == 0 ) - return map( iy, ix ) * ( 1 - dx ) + map( iy, ix + 1 ) * dx; - - return map( iy, ix ) * ( 1 - dx ) * ( 1 - dy ) + - map( iy + 1, ix ) * dy * ( 1 - dx ) + - map( iy, ix + 1 ) * ( 1 - dy ) * dx + - map( iy + 1, ix + 1 ) * dx * dy; - } + aliceVision::image::Image map; + + IntegralImages(const aliceVision::image::Image& I); + + inline double operator()(double x1, double y1, double x2, double y2) const { return get(x2, y2) - get(x1, y2) - get(x2, y1) + get(x1, y1); } + inline double operator()(double x, double y, double size) const + { + double window = 0.5 * size; + return (get(x + window, y + window) - get(x - window, y + window) - get(x + window, y - window) + get(x - window, y - window)) / + (4 * window * window); + } + + private: + inline double get(double x, double y) const + { + int ix = int(x); + int iy = int(y); + double dx = x - ix; + double dy = y - iy; + if (dx == 0 && dy == 0) + return map(iy, ix); + if (dx == 0) + return map(iy, ix) * (1 - dy) + map(iy + 1, ix) * dy; + if (dy == 0) + return map(iy, ix) * (1 - dx) + map(iy, ix + 1) * dx; + + return map(iy, ix) * (1 - dx) * (1 - dy) + map(iy + 1, ix) * dy * (1 - dx) + map(iy, ix + 1) * (1 - dy) * dx + map(iy + 1, ix + 1) * dx * dy; + } }; //=============================IO interface ======================// -std::ofstream& writeDetector( std::ofstream& out, const aliceVision::feature::PointFeature& vect ); -std::ifstream& readDetector( std::ifstream& in, aliceVision::feature::PointFeature& point ); +std::ofstream& writeDetector(std::ofstream& out, const aliceVision::feature::PointFeature& vect); +std::ifstream& readDetector(std::ifstream& in, aliceVision::feature::PointFeature& point); //======================================elemetuary operations================================// -template < typename T > -inline T point_distance( const T x1, const T y1, const T x2, const T y2 ) -{//distance of points - float a = x1 - x2; - float b = y1 - y2; - return std::hypot(a, b); +template +inline T point_distance(const T x1, const T y1, const T x2, const T y2) +{ // distance of points + float a = x1 - x2; + float b = y1 - y2; + return std::hypot(a, b); } -template < typename T > -inline float point_distance( const T& P1, const T& P2 ) -{//distance of points - return point_distance< float >( P1.x(), P1.y(), P2.x(), P2.y() ); +template +inline float point_distance(const T& P1, const T& P2) +{ // distance of points + return point_distance(P1.x(), P1.y(), P2.x(), P2.y()); } -inline bool inside( int w, int h, int x,int y, double radius ) -{ - return( x - radius >= 0 && y - radius >= 0 && x + radius < w && y + radius < h ); -} +inline bool inside(int w, int h, int x, int y, double radius) { return (x - radius >= 0 && y - radius >= 0 && x + radius < w && y + radius < h); } bool anglefrom(float x, float y, float& angle); double angle_difference(double angle1, double angle2); -inline void max( double* list,double& weight, int size, int& index, int& second_index ) +inline void max(double* list, double& weight, int size, int& index, int& second_index) { - index = 0; - second_index = -1; - double best = list[ index ] - list[ index + size / 2 ]; - - for( int i = 0; i < size; i++ ) - { - double value; - if( i < size / 2) value = list[ i ] - list[ i + size / 2 ]; - else value = list[ i ] - list[ i - size / 2 ]; - - if( value > best ) - { - best = value; - second_index = index; - index = i; - } - } - weight = best; + index = 0; + second_index = -1; + double best = list[index] - list[index + size / 2]; + + for (int i = 0; i < size; i++) + { + double value; + if (i < size / 2) + value = list[i] - list[i + size / 2]; + else + value = list[i] - list[i - size / 2]; + + if (value > best) + { + best = value; + second_index = index; + index = i; + } + } + weight = best; } -template< typename ARRAY > -inline void normalize_weight( ARRAY & weight ) +template +inline void normalize_weight(ARRAY& weight) { - double total = weight.array().sum(); - if( total != 0 ) - for( int i = 0; i < weight.size(); i++ ) - weight[ i ] /= total; + double total = weight.array().sum(); + if (total != 0) + for (int i = 0; i < weight.size(); i++) + weight[i] /= total; } -template< typename T > -inline float consistent( const T& a1, const T& a2, const T& b1, const T& b2 ) +template +inline float consistent(const T& a1, const T& a2, const T& b1, const T& b2) { - float ax = float( a1.x() - a2.x() ); - float ay = float( a1.y() - a2.y() ); - float bx = float( b1.x() - b2.x() ); - float by = float( b1.y() - b2.y() ); - - float angle1 = float( b1.orientation() - a1.orientation() ); - float angle2 = float( b2.orientation() - a2.orientation() ); - - float ax1 = cos( angle1 ) * ax - sin( angle1 ) * ay; - ax1 *= float( b1.scale() / a1.scale() ); - float ay1 = sin( angle1 ) * ax + cos( angle1 ) * ay; - ay1 *= float( b1.scale() / a1.scale() ); - float d1 = std::hypot(ax1, ay1); - float d1_error = std::hypot( ( ax1 - bx ), ( ay1 - by ) ); - - float ax2 = float( cos( angle2 ) * ax - sin( angle2 ) * ay ); - ax2 *= float( b2.scale() / a2.scale() ); - - float ay2 = float( sin( angle2 ) * ax + cos( angle2 ) * ay ); - ay2 *= float( b2.scale() / a2.scale() ); - float d2 = std::hypot( ax2, ay2 ); - float d2_error = std::hypot( ( ax2 - bx ), + ( ay2 - by ) ); - float d = std::min( d1_error / std::min( d1, point_distance( b1, b2 ) ), d2_error / std::min( d2, point_distance( b1, b2 ) ) ); - return d; + float ax = float(a1.x() - a2.x()); + float ay = float(a1.y() - a2.y()); + float bx = float(b1.x() - b2.x()); + float by = float(b1.y() - b2.y()); + + float angle1 = float(b1.orientation() - a1.orientation()); + float angle2 = float(b2.orientation() - a2.orientation()); + + float ax1 = cos(angle1) * ax - sin(angle1) * ay; + ax1 *= float(b1.scale() / a1.scale()); + float ay1 = sin(angle1) * ax + cos(angle1) * ay; + ay1 *= float(b1.scale() / a1.scale()); + float d1 = std::hypot(ax1, ay1); + float d1_error = std::hypot((ax1 - bx), (ay1 - by)); + + float ax2 = float(cos(angle2) * ax - sin(angle2) * ay); + ax2 *= float(b2.scale() / a2.scale()); + + float ay2 = float(sin(angle2) * ax + cos(angle2) * ay); + ay2 *= float(b2.scale() / a2.scale()); + float d2 = std::hypot(ax2, ay2); + float d2_error = std::hypot((ax2 - bx), +(ay2 - by)); + float d = std::min(d1_error / std::min(d1, point_distance(b1, b2)), d2_error / std::min(d2, point_distance(b1, b2))); + return d; } -float getRange(const aliceVision::image::Image< float >& I, int a, const float p); +float getRange(const aliceVision::image::Image& I, int a, const float p); -#endif //KVLD_ALGORITHM_H +#endif // KVLD_ALGORITHM_H diff --git a/src/aliceVision/matching/kvld/kvld.cpp b/src/aliceVision/matching/kvld/kvld.cpp index e6216725b3..bdffa3fc2f 100644 --- a/src/aliceVision/matching/kvld/kvld.cpp +++ b/src/aliceVision/matching/kvld/kvld.cpp @@ -12,7 +12,6 @@ ** @author Zhe Liu **/ - #include "kvld.h" #include "algorithm.h" #include @@ -23,413 +22,414 @@ using namespace aliceVision; using namespace aliceVision::image; -ImageScale::ImageScale( const Image< float >& I, double r ) +ImageScale::ImageScale(const Image& I, double r) { - IntegralImages inter( I ); - radius_size = r; - step = std::sqrt( 2.0 ); - int size = std::max( I.Width(),I.Height() ); - - int number= int( log( size / r ) / log( 2.0 ) ) + 1; - angles.resize( number ); - magnitudes.resize( number ); - ratios.resize( number ); - - GradAndNorm( I, angles[ 0 ], magnitudes[ 0 ] ); - ratios[ 0 ] = 1; - - #pragma omp parallel for - for( int k = 1; k < number; k++ ) - { - Image< float > I2; - double ratio = 1 * pow( step, k ); - I2.resize( int( I.Width() / ratio ), int( I.Height() / ratio ) ); - angles[ k ].resize( int( I.Width() / ratio ), int( I.Height() / ratio ) ); - magnitudes[ k ].resize( int( I.Width() / ratio ), int( I.Height() / ratio ) ); - - for( int i = 0; i < I2.Width(); i++ ) + IntegralImages inter(I); + radius_size = r; + step = std::sqrt(2.0); + int size = std::max(I.Width(), I.Height()); + + int number = int(log(size / r) / log(2.0)) + 1; + angles.resize(number); + magnitudes.resize(number); + ratios.resize(number); + + GradAndNorm(I, angles[0], magnitudes[0]); + ratios[0] = 1; + +#pragma omp parallel for + for (int k = 1; k < number; k++) { - for( int j = 0; j < I2.Height(); j++ ) - { - I2( j, i ) = inter( double( i + 0.5 ) * ratio, double( j + 0.5 ) * ratio, ratio ); - } + Image I2; + double ratio = 1 * pow(step, k); + I2.resize(int(I.Width() / ratio), int(I.Height() / ratio)); + angles[k].resize(int(I.Width() / ratio), int(I.Height() / ratio)); + magnitudes[k].resize(int(I.Width() / ratio), int(I.Height() / ratio)); + + for (int i = 0; i < I2.Width(); i++) + { + for (int j = 0; j < I2.Height(); j++) + { + I2(j, i) = inter(double(i + 0.5) * ratio, double(j + 0.5) * ratio, ratio); + } + } + GradAndNorm(I2, angles[k], magnitudes[k]); + ratios[k] = ratio; } - GradAndNorm( I2,angles[ k ], magnitudes[ k ] ); - ratios[ k ] = ratio; - } } -void ImageScale::GradAndNorm( const Image< float >& I, Image< float >& angle, Image< float >& m ) +void ImageScale::GradAndNorm(const Image& I, Image& angle, Image& m) { - angle = Image< float >( I.Width(), I.Height() ); - m = Image< float >( I.Width(), I.Height() ); - angle.fill( 0 ); - m.fill( 0 ); - - #pragma omp parallel for - for( int y = 1; y < I.Height() - 1; y++ ) - { - for( int x = 1; x < I.Width() - 1; x++ ) + angle = Image(I.Width(), I.Height()); + m = Image(I.Width(), I.Height()); + angle.fill(0); + m.fill(0); + +#pragma omp parallel for + for (int y = 1; y < I.Height() - 1; y++) { - const float gx = I( y, x + 1 ) - I( y, x - 1 ); - const float gy = I( y + 1, x ) - I( y - 1, x ); + for (int x = 1; x < I.Width() - 1; x++) + { + const float gx = I(y, x + 1) - I(y, x - 1); + const float gy = I(y + 1, x) - I(y - 1, x); - if( !anglefrom( gx, gy, angle( y, x ) ) ) - angle( y, x ) = -1; - m( y, x ) = std::hypot(gx, gy); + if (!anglefrom(gx, gy, angle(y, x))) + angle(y, x) = -1; + m(y, x) = std::hypot(gx, gy); + } } - } } -int ImageScale::getIndex( const double r )const +int ImageScale::getIndex(const double r) const { - const double step = sqrt( 2.0 ); - - if( r <= radius_size ) return 0; - else - { - double range_low = radius_size; - int index = 0; - while( r > range_low * step ) + const double step = sqrt(2.0); + + if (r <= radius_size) + return 0; + else { - ++index; - range_low *= step; + double range_low = radius_size; + int index = 0; + while (r > range_low * step) + { + ++index; + range_low *= step; + } + return std::min(int(angles.size() - 1), index); } - return std::min(int(angles.size()-1), index); - } } -template< typename T > -VLD::VLD( const ImageScale& series, T const& P1, T const& P2 ) : contrast( 0.0 ) +template +VLD::VLD(const ImageScale& series, T const& P1, T const& P2) + : contrast(0.0) { - using namespace boost::math; + using namespace boost::math; - //============== initializing============// - principleAngle.fill( 0 ); - descriptor.fill( 0 ); - weight.fill( 0 ); + //============== initializing============// + principleAngle.fill(0); + descriptor.fill(0); + weight.fill(0); - begin_point[ 0 ] = P1.x(); - begin_point[ 1 ] = P1.y(); - end_point[ 0 ] = P2.x(); - end_point[ 1 ] = P2.y(); + begin_point[0] = P1.x(); + begin_point[1] = P1.y(); + end_point[0] = P2.x(); + end_point[1] = P2.y(); - const float dy = float( end_point[ 1 ] - begin_point[ 1 ] ); - const float dx = float( end_point[ 0 ] - begin_point[ 0 ] ); - distance = std::hypot( dy, dx ); + const float dy = float(end_point[1] - begin_point[1]); + const float dx = float(end_point[0] - begin_point[0]); + distance = std::hypot(dy, dx); - if( distance == 0 ) - std::cerr << "Two SIFT points have the same coordinate" << std::endl; + if (distance == 0) + std::cerr << "Two SIFT points have the same coordinate" << std::endl; - const float radius = std::max( distance / float( dimension + 1 ), 2.0f );//at least 2 + const float radius = std::max(distance / float(dimension + 1), 2.0f); // at least 2 - const double mainAngle = get_orientation();//absolute angle + const double mainAngle = get_orientation(); // absolute angle - const int image_index = series.getIndex( radius ); + const int image_index = series.getIndex(radius); - const Image< float > & ang = series.angles[ image_index ]; - const Image< float > & m = series.magnitudes[ image_index ]; - const double ratio = series.ratios[ image_index ]; + const Image& ang = series.angles[image_index]; + const Image& m = series.magnitudes[image_index]; + const double ratio = series.ratios[image_index]; - const int w = m.Width(); - const int h = m.Height(); - const float r = float( radius / ratio ); - const float sigma2 = r * r; - //======calculating the descriptor=====// + const int w = m.Width(); + const int h = m.Height(); + const float r = float(radius / ratio); + const float sigma2 = r * r; + //======calculating the descriptor=====// - double statistic[ binNum ]; - for( int i = 0; i < dimension; i++ ) - { - std::fill_n( statistic, binNum, 0.0); + double statistic[binNum]; + for (int i = 0; i < dimension; i++) + { + std::fill_n(statistic, binNum, 0.0); - float xi = float( begin_point[ 0 ] + float( i + 1 ) / ( dimension + 1 ) * ( dx ) ); - float yi = float( begin_point[ 1 ] + float( i + 1 ) / ( dimension + 1 ) * ( dy ) ); - xi /= float( ratio ); - yi /= float( ratio ); + float xi = float(begin_point[0] + float(i + 1) / (dimension + 1) * (dx)); + float yi = float(begin_point[1] + float(i + 1) / (dimension + 1) * (dy)); + xi /= float(ratio); + yi /= float(ratio); - for( int y = int( yi - r ); y <= int( yi + r + 0.5 ); y++ ) - { - for( int x = int( xi - r ); x <= int( xi + r + 0.5 ); x++ ) - { - float d = point_distance( xi, yi, float( x ), float( y ) ); - if( d <= r && inside( w, h, x, y, 1 ) ) + for (int y = int(yi - r); y <= int(yi + r + 0.5); y++) { - //================angle and magnitude==========================// - double angle; - if( ang( y, x ) >= 0 ) - angle = ang( y, x ) - mainAngle;//relative angle - else angle = 0.0; - - //cout<(); - while(angle >= 2 * constants::pi()) - angle -= 2 * constants::pi(); - - //===============principle angle==============================// - const int index = int(angle * binNum / (2 * constants::pi()) + 0.5); - - double Gweight = exp( -d * d / 4.5 / sigma2 ) * ( m( y, x ) ); - if( index < binNum ) - statistic[ index ] += Gweight; - else // possible since the 0.5 - statistic[ 0 ] += Gweight; - - //==============the descriptor===============================// - const int index2 = int(angle * subdirection / (2 * constants::pi()) + 0.5); - assert( index2 >= 0 && index2 <= subdirection ); - - if( index2 < subdirection ) - descriptor[ subdirection * i + index2 ] += Gweight; - else descriptor[ subdirection * i ] += Gweight;// possible since the 0.5 + for (int x = int(xi - r); x <= int(xi + r + 0.5); x++) + { + float d = point_distance(xi, yi, float(x), float(y)); + if (d <= r && inside(w, h, x, y, 1)) + { + //================angle and magnitude==========================// + double angle; + if (ang(y, x) >= 0) + angle = ang(y, x) - mainAngle; // relative angle + else + angle = 0.0; + + // cout<(); + while (angle >= 2 * constants::pi()) + angle -= 2 * constants::pi(); + + //===============principle angle==============================// + const int index = int(angle * binNum / (2 * constants::pi()) + 0.5); + + double Gweight = exp(-d * d / 4.5 / sigma2) * (m(y, x)); + if (index < binNum) + statistic[index] += Gweight; + else // possible since the 0.5 + statistic[0] += Gweight; + + //==============the descriptor===============================// + const int index2 = int(angle * subdirection / (2 * constants::pi()) + 0.5); + assert(index2 >= 0 && index2 <= subdirection); + + if (index2 < subdirection) + descriptor[subdirection * i + index2] += Gweight; + else + descriptor[subdirection * i] += Gweight; // possible since the 0.5 + } + } } - } + //=====================find the biggest angle of ith SIFT==================// + int index; + int second_index; + max(statistic, weight[i], binNum, index, second_index); + principleAngle[i] = index; } - //=====================find the biggest angle of ith SIFT==================// - int index; - int second_index; - max( statistic, weight[ i ], binNum, index, second_index ); - principleAngle[ i ] = index; - } - - normalize_weight( descriptor ); - - contrast = weight.array().sum(); - contrast /= distance / ratio; - normalize_weight( weight ); -} -float KVLD( const Image< float >& I1, - const Image< float >& I2, - const std::vector & F1, - const std::vector & F2, - const std::vector< Pair >& matches, - std::vector< Pair >& matchesFiltered, - std::vector< double >& score, - aliceVision::Mat& E, - std::vector< bool >& valide, - KvldParameters& kvldParameters ) -{ - matchesFiltered.clear(); - score.clear(); - ImageScale Chaine1( I1 ); - ImageScale Chaine2( I2 ); + normalize_weight(descriptor); - std::cout << "Image scale-space complete..." << std::endl; + contrast = weight.array().sum(); + contrast /= distance / ratio; + normalize_weight(weight); +} - const float range1 = getRange( I1, std::min( F1.size(), matches.size() ), kvldParameters.inlierRate ); - const float range2 = getRange( I2, std::min( F2.size(), matches.size() ), kvldParameters.inlierRate ); +float KVLD(const Image& I1, + const Image& I2, + const std::vector& F1, + const std::vector& F2, + const std::vector& matches, + std::vector& matchesFiltered, + std::vector& score, + aliceVision::Mat& E, + std::vector& valide, + KvldParameters& kvldParameters) +{ + matchesFiltered.clear(); + score.clear(); + ImageScale Chaine1(I1); + ImageScale Chaine2(I2); - const size_t size = matches.size(); + std::cout << "Image scale-space complete..." << std::endl; - //================distance map construction, foruse of selecting neighbors===============// - std::cout << "computing distance maps" << std::endl; + const float range1 = getRange(I1, std::min(F1.size(), matches.size()), kvldParameters.inlierRate); + const float range2 = getRange(I2, std::min(F2.size(), matches.size()), kvldParameters.inlierRate); - bool bPrecomputedDist = false; + const size_t size = matches.size(); - aliceVision::Matf dist1, dist2; - if( bPrecomputedDist ) - { - dist1 = aliceVision::Matf::Zero( F1.size(), F1.size() ); - dist2 = aliceVision::Matf::Zero( F2.size(), F2.size() ); + //================distance map construction, foruse of selecting neighbors===============// + std::cout << "computing distance maps" << std::endl; - for( int a1 = 0; a1 < F1.size(); ++a1 ) - for( int a2 = a1; a2 < F1.size(); ++a2 ) - dist1( a1, a2 ) = dist1( a2, a1 ) = point_distance( F1[ a1 ], F1[ a2 ] ); + bool bPrecomputedDist = false; - for( int b1 = 0; b1 < F2.size(); ++b1 ) - for( int b2 = b1; b2 < F2.size(); ++b2 ) - dist2( b1, b2 ) = dist2( b2, b1 ) = point_distance( F2[ b1 ], F2[ b2 ] ); - } + aliceVision::Matf dist1, dist2; + if (bPrecomputedDist) + { + dist1 = aliceVision::Matf::Zero(F1.size(), F1.size()); + dist2 = aliceVision::Matf::Zero(F2.size(), F2.size()); - std::fill( valide.begin(), valide.end(), true ); - std::vector< double > scoretable( size, 0.0 ); - std::vector< size_t > result( size, 0 ); + for (int a1 = 0; a1 < F1.size(); ++a1) + for (int a2 = a1; a2 < F1.size(); ++a2) + dist1(a1, a2) = dist1(a2, a1) = point_distance(F1[a1], F1[a2]); + for (int b1 = 0; b1 < F2.size(); ++b1) + for (int b2 = b1; b2 < F2.size(); ++b2) + dist2(b1, b2) = dist2(b2, b1) = point_distance(F2[b1], F2[b2]); + } -//============main iteration formatch verification==========// -// cout<<"main iteration"; - bool change = true; + std::fill(valide.begin(), valide.end(), true); + std::vector scoretable(size, 0.0); + std::vector result(size, 0); - while( change ) - { - change = false; + //============main iteration formatch verification==========// + // cout<<"main iteration"; + bool change = true; - std::fill( scoretable.begin(), scoretable.end(), 0.0 ); - std::fill( result.begin(), result.end(), 0 ); - //========substep 1: search foreach match its neighbors and verify if they are gvld-consistent ============// - for( int it1 = 0; it1 < size - 1; it1++ ) + while (change) { - if( valide[ it1 ] ) - { - size_t a1 = matches[ it1 ].first, b1 = matches[ it1 ].second; - - for( int it2 = it1 + 1; it2 < size; it2++ ) - if(valide[ it2 ]) - { - size_t a2 = matches[ it2 ].first, b2 = matches[ it2 ].second; - - bool bOk = false; - if( bPrecomputedDist ) - bOk = ( dist1( a1, a2 ) > min_dist && dist2( b1, b2 ) > min_dist - && ( dist1( a1, a2 ) < range1 || dist2( b1, b2 ) < range2 ) ); - else - bOk = ( point_distance( F1[ a1 ], F1[ a2 ] ) > min_dist && point_distance( F2[ b1 ], F2[ b2 ] ) > min_dist && - ( point_distance( F1[ a1 ], F1[ a2 ] ) < range1 || point_distance( F2[ b1 ], F2[ b2 ] ) < range2 ) ); - if( bOk ) - { - if( E( it1, it2 ) == -1 ) - { //update E ifunknow - E( it1, it2 ) = -2; - E( it2, it1 ) = -2; + change = false; - if( !kvldParameters.geometry || consistent( F1[ a1 ], F1[ a2 ], F2[ b1 ], F2[ b2 ] ) < distance_thres ) - { - VLD vld1( Chaine1, F1[ a1 ], F1[ a2 ] ); - VLD vld2( Chaine2, F2[ b1 ], F2[ b2 ] ); - //vld1.test(); - double error = vld1.difference( vld2 ); - //cout<= 0 ) - { - result[ it1 ] += 1; - result[ it2 ] += 1; - scoretable[ it1 ] += double( E( it1, it2 ) ); - scoretable[ it2 ] += double( E( it1, it2 ) ); - if( result[ it1 ] >= max_connection ) - break; - } + std::fill(scoretable.begin(), scoretable.end(), 0.0); + std::fill(result.begin(), result.end(), 0); + //========substep 1: search foreach match its neighbors and verify if they are gvld-consistent ============// + for (int it1 = 0; it1 < size - 1; it1++) + { + if (valide[it1]) + { + size_t a1 = matches[it1].first, b1 = matches[it1].second; + + for (int it2 = it1 + 1; it2 < size; it2++) + if (valide[it2]) + { + size_t a2 = matches[it2].first, b2 = matches[it2].second; + + bool bOk = false; + if (bPrecomputedDist) + bOk = (dist1(a1, a2) > min_dist && dist2(b1, b2) > min_dist && (dist1(a1, a2) < range1 || dist2(b1, b2) < range2)); + else + bOk = (point_distance(F1[a1], F1[a2]) > min_dist && point_distance(F2[b1], F2[b2]) > min_dist && + (point_distance(F1[a1], F1[a2]) < range1 || point_distance(F2[b1], F2[b2]) < range2)); + if (bOk) + { + if (E(it1, it2) == -1) + { // update E ifunknow + E(it1, it2) = -2; + E(it2, it1) = -2; + + if (!kvldParameters.geometry || consistent(F1[a1], F1[a2], F2[b1], F2[b2]) < distance_thres) + { + VLD vld1(Chaine1, F1[a1], F1[a2]); + VLD vld2(Chaine2, F2[b1], F2[b2]); + // vld1.test(); + double error = vld1.difference(vld2); + // cout<= 0) + { + result[it1] += 1; + result[it2] += 1; + scoretable[it1] += double(E(it1, it2)); + scoretable[it2] += double(E(it1, it2)); + if (result[it1] >= max_connection) + break; + } + } + } } - } - } - } + } - //========substep 2: remove false matches by K gvld-consistency criteria ============// - for( int it = 0; it < size; it++ ) - { - if( valide[ it ] && result[ it ] < kvldParameters.K ) - { - valide[ it ] = false; - change = true; - } - } - //========substep 3: remove multiple matches to a same point by keeping the one with the best average gvld-consistency score ============// - if( uniqueMatch ) - for( int it1 = 0; it1 < size - 1; it1++ ) - if( valide[ it1 ]) { - size_t a1 = matches[ it1 ].first; - size_t b1 = matches[ it1 ].second; - - for( int it2 = it1 + 1; it2 < size; it2++ ) - if( valide[ it2 ] ) + //========substep 2: remove false matches by K gvld-consistency criteria ============// + for (int it = 0; it < size; it++) + { + if (valide[it] && result[it] < kvldParameters.K) { - size_t a2 = matches[ it2 ].first; - size_t b2 = matches[ it2 ].second; - - if( a1 == a2 || b1 == b2 - || ( F1[ a1 ].x() == F1[ a2 ].x() && F1[ a1 ].y() == F1[ a2 ].y() && - ( F2[ b1 ].x() != F2[ b2 ].x() || F2[ b1 ].y() != F2[ b2 ].y() ) ) - || ( ( F1[ a1 ].x() != F1[ a2 ].x() || F1[ a1 ].y() != F1[ a2 ].y() ) && - F2[ b1 ].x() == F2[ b2 ].x() && F2[ b1 ].y() == F2[ b2 ].y() ) ) - { - //cardinal comparison - if( result[ it1 ] > result[ it2 ] ) - { - valide[ it2 ] = false; - change = true; - } - else if( result[ it1 ] < result[ it2 ] ) - { - valide[ it1 ] = false; - change = true; - } - else if( result[ it1 ] == result[ it2 ] ) - { - //score comparison - if( scoretable[ it1 ] > scoretable[ it2 ] ) - { - valide[ it1 ] = false; - change = true; - } - else if( scoretable[ it1 ] < scoretable[ it2 ] ) - { - valide[ it2 ] = false; - change = true; - } - } - } + valide[it] = false; + change = true; } } - //========substep 4: ifgeometric verification is set, re-score matches by geometric-consistency, and remove poorly scored ones ============================// - if( uniqueMatch && kvldParameters.geometry ) - { - for( int i = 0; i < size; i++ ) - scoretable[ i ]=0; + //========substep 3: remove multiple matches to a same point by keeping the one with the best average gvld-consistency score ============// + if (uniqueMatch) + for (int it1 = 0; it1 < size - 1; it1++) + if (valide[it1]) + { + size_t a1 = matches[it1].first; + size_t b1 = matches[it1].second; + + for (int it2 = it1 + 1; it2 < size; it2++) + if (valide[it2]) + { + size_t a2 = matches[it2].first; + size_t b2 = matches[it2].second; + + if (a1 == a2 || b1 == b2 || + (F1[a1].x() == F1[a2].x() && F1[a1].y() == F1[a2].y() && (F2[b1].x() != F2[b2].x() || F2[b1].y() != F2[b2].y())) || + ((F1[a1].x() != F1[a2].x() || F1[a1].y() != F1[a2].y()) && F2[b1].x() == F2[b2].x() && F2[b1].y() == F2[b2].y())) + { + // cardinal comparison + if (result[it1] > result[it2]) + { + valide[it2] = false; + change = true; + } + else if (result[it1] < result[it2]) + { + valide[it1] = false; + change = true; + } + else if (result[it1] == result[it2]) + { + // score comparison + if (scoretable[it1] > scoretable[it2]) + { + valide[it1] = false; + change = true; + } + else if (scoretable[it1] < scoretable[it2]) + { + valide[it2] = false; + change = true; + } + } + } + } + } + //========substep 4: ifgeometric verification is set, re-score matches by geometric-consistency, and remove poorly scored ones + //============================// + if (uniqueMatch && kvldParameters.geometry) + { + for (int i = 0; i < size; i++) + scoretable[i] = 0; - std::vector< bool > switching; - for( int i = 0; i < size; i++ ) - switching.push_back( false ); + std::vector switching; + for (int i = 0; i < size; i++) + switching.push_back(false); - for( int it1 = 0; it1 < size; it1++ ) - { - if( valide[ it1 ] ) - { - size_t a1 = matches[ it1 ].first, b1 = matches[ it1 ].second; - float index = 0.0f; - int good_index = 0; - for( int it2 = 0; it2 < size; it2++ ) - { - if( it1 != it2 && valide[ it2 ] ) + for (int it1 = 0; it1 < size; it1++) { - size_t a2 = matches[ it2 ].first; - size_t b2 = matches[ it2 ].second; - - bool bOk = false; - if( bPrecomputedDist ) - bOk = ( dist1( a1, a2 ) > min_dist && dist2( b1, b2 ) > min_dist && - ( dist1( a1, a2 ) < range1 || dist2( b1, b2 ) < range2 ) ); - else - bOk = ( point_distance( F1[ a1 ], F1[ a2 ] ) > min_dist && point_distance( F2[ b1 ], F2[ b2 ] ) > min_dist && - ( point_distance( F1[ a1 ], F1[ a2 ] ) < range1 || point_distance( F2[ b1 ], F2[ b2 ] ) < range2 ) ); - if( bOk ) - { - float d = consistent( F1[ a1 ], F1[ a2 ], F2[ b1 ], F2[ b2 ] ); - scoretable[ it1 ] += d; - index += 1; - if( d < distance_thres ) - good_index++; - } + if (valide[it1]) + { + size_t a1 = matches[it1].first, b1 = matches[it1].second; + float index = 0.0f; + int good_index = 0; + for (int it2 = 0; it2 < size; it2++) + { + if (it1 != it2 && valide[it2]) + { + size_t a2 = matches[it2].first; + size_t b2 = matches[it2].second; + + bool bOk = false; + if (bPrecomputedDist) + bOk = (dist1(a1, a2) > min_dist && dist2(b1, b2) > min_dist && (dist1(a1, a2) < range1 || dist2(b1, b2) < range2)); + else + bOk = (point_distance(F1[a1], F1[a2]) > min_dist && point_distance(F2[b1], F2[b2]) > min_dist && + (point_distance(F1[a1], F1[a2]) < range1 || point_distance(F2[b1], F2[b2]) < range2)); + if (bOk) + { + float d = consistent(F1[a1], F1[a2], F2[b1], F2[b2]); + scoretable[it1] += d; + index += 1; + if (d < distance_thres) + good_index++; + } + } + } + scoretable[it1] /= index; + if (good_index < 0.3f * float(index) && scoretable[it1] > 1.2) + { + switching[it1] = true; + change = true; + } + } } - } - scoretable[ it1 ] /= index; - if( good_index < 0.3f * float( index ) && scoretable[ it1 ] > 1.2 ) - { - switching[ it1 ] = true; - change = true; - } + for (int it1 = 0; it1 < size; it1++) + if (switching[it1]) + valide[it1] = false; } - } - for( int it1 = 0; it1 < size; it1++ ) - if( switching[ it1 ] ) - valide[ it1 ] = false; - } - } - //=============== generating output list ===================// - for( int it = 0; it < size; it++ ) - if( valide[ it ] ) - { - matchesFiltered.push_back( matches[ it ] ); - score.push_back( scoretable[ it ] ); } - return float( matchesFiltered.size() ) / matches.size(); + //=============== generating output list ===================// + for (int it = 0; it < size; it++) + if (valide[it]) + { + matchesFiltered.push_back(matches[it]); + score.push_back(scoretable[it]); + } + return float(matchesFiltered.size()) / matches.size(); } double VLD::get_orientation() const diff --git a/src/aliceVision/matching/kvld/kvld.h b/src/aliceVision/matching/kvld/kvld.h index 3242465262..4e9e74e65f 100644 --- a/src/aliceVision/matching/kvld/kvld.h +++ b/src/aliceVision/matching/kvld/kvld.h @@ -26,18 +26,19 @@ #include #include -//Parameters concerning speed and performance - const bool uniqueMatch = true;//if activated, a point can be matched to only one point in the other image. Note: if false, it also desactivate partially geometric verification - const double juge = 0.35; - const size_t max_connection = 20; - const double distance_thres = 0.5; - const float min_dist = 10; - const float maxContrast = 300.0f; +// Parameters concerning speed and performance +const bool uniqueMatch = true; // if activated, a point can be matched to only one point in the other image. Note: if false, it also desactivate + // partially geometric verification +const double juge = 0.35; +const size_t max_connection = 20; +const double distance_thres = 0.5; +const float min_dist = 10; +const float maxContrast = 300.0f; //===inner parameters of VLD, usually not to change - const int dimension = 10; //number of simplified SIFT-like chain used in a single vld - const int subdirection = 8; // number of bins in a SIFT-like chain histogram - const int binNum = 24;//number of bins for SIFT-like chain main direction. Must be a pair number +const int dimension = 10; // number of simplified SIFT-like chain used in a single vld +const int subdirection = 8; // number of bins in a SIFT-like chain histogram +const int binNum = 24; // number of bins for SIFT-like chain main direction. Must be a pair number //===== initialize parameters for a KVLD process ====// // inlierRate: the minimum rate down to which the KVLD should be reprocessed with a lower number of inlierRate, initially set to 0.04 @@ -46,10 +47,13 @@ // if false, KVLD execute a pure photometric verification struct KvldParameters { - float inlierRate; - size_t K; - bool geometry; - KvldParameters(): inlierRate( 0.04 ), K( 3 ), geometry( true ){}; + float inlierRate; + size_t K; + bool geometry; + KvldParameters() + : inlierRate(0.04), + K(3), + geometry(true){}; }; //====== Pyramid of scale images ======// @@ -60,112 +64,115 @@ struct KvldParameters // magnitudes: store gradient norms of pixels of each scale image into a vector of images struct ImageScale { - std::vector< aliceVision::image::Image< float > > angles; - std::vector< aliceVision::image::Image< float > > magnitudes; - std::vector< double > ratios; - double radius_size; - double step; - - ImageScale(const aliceVision::image::Image< float >& I, double r = 5.0); - int getIndex( const double r )const; - -private: - void GradAndNorm( - const aliceVision::image::Image< float >& I, - aliceVision::image::Image< float >& angle, - aliceVision::image::Image< float >& m); + std::vector> angles; + std::vector> magnitudes; + std::vector ratios; + double radius_size; + double step; + + ImageScale(const aliceVision::image::Image& I, double r = 5.0); + int getIndex(const double r) const; + + private: + void GradAndNorm(const aliceVision::image::Image& I, aliceVision::image::Image& angle, aliceVision::image::Image& m); }; //====== VLD structures ======// class VLD { - double contrast; - float distance; - - float begin_point[ 2 ]; - float end_point[ 2 ]; - - Eigen::Matrix< int, dimension, 1 > principleAngle; //relative angle - Eigen::Matrix< double, dimension, 1 > weight; - Eigen::Matrix< double, dimension * subdirection, 1 > descriptor;//relative angle - -public: - inline double get_contrast()const{ return contrast; } - //====================constructors=====================// - template< typename T > - VLD( const ImageScale& series, T const& P1, T const& P2 ); -//=========================================class functions==============================================// + double contrast; + float distance; + + float begin_point[2]; + float end_point[2]; + + Eigen::Matrix principleAngle; // relative angle + Eigen::Matrix weight; + Eigen::Matrix descriptor; // relative angle + + public: + inline double get_contrast() const { return contrast; } + //====================constructors=====================// + template + VLD(const ImageScale& series, T const& P1, T const& P2); + //=========================================class functions==============================================// double get_orientation() const; - - inline double difference( const VLD& vld2 )const - { - double diff[ 2 ]; - diff[ 0 ] = 0; - diff[ 1 ] = 0; - if( contrast > 300 || vld2.contrast > 300 || contrast <= 0 || vld2.contrast <=0 ) - return 128; - - for( int i = 0; i < dimension; i++ ) + inline double difference(const VLD& vld2) const { - for( int j = 0; j < subdirection; j++ ) - {// term of descriptor - diff[ 0 ] += std::abs( descriptor[ i * subdirection + j ] - vld2.descriptor[ i * subdirection + j ] ); - } - //term of main SIFT like orientation - diff[ 1 ] += std::min( std::abs( principleAngle[ i ] - vld2.principleAngle[ i ] ), - binNum - std::abs( principleAngle[ i ] - vld2.principleAngle[ i ] ) ) * ( weight[ i ] + vld2.weight[ i ] );// orientation term + double diff[2]; + diff[0] = 0; + diff[1] = 0; + + if (contrast > 300 || vld2.contrast > 300 || contrast <= 0 || vld2.contrast <= 0) + return 128; + + for (int i = 0; i < dimension; i++) + { + for (int j = 0; j < subdirection; j++) + { // term of descriptor + diff[0] += std::abs(descriptor[i * subdirection + j] - vld2.descriptor[i * subdirection + j]); + } + // term of main SIFT like orientation + diff[1] += std::min(std::abs(principleAngle[i] - vld2.principleAngle[i]), + binNum - std::abs(principleAngle[i] - vld2.principleAngle[i])) * + (weight[i] + vld2.weight[i]); // orientation term + } + + diff[0] *= 0.36; + diff[1] *= 0.64 / (binNum); + // ALICEVISION_LOG_DEBUG("diff = "< valide(size, true); // -//kvldParameters: container of minimum inlier rate, the value of K (=3 initially) and geometric verification flag (true initially) - -float KVLD(const aliceVision::image::Image< float >& I1, - const aliceVision::image::Image< float >& I2, - const std::vector & F1, - const std::vector & F2, - const std::vector< aliceVision::Pair >& matches, - std::vector< aliceVision::Pair >& matchesFiltered, - std::vector< double >& score, - aliceVision::Mat& E, - std::vector< bool >& valide, - KvldParameters& kvldParameters ); - -#endif //KVLD_H +// kvldParameters: container of minimum inlier rate, the value of K (=3 initially) and geometric verification flag (true initially) + +float KVLD(const aliceVision::image::Image& I1, + const aliceVision::image::Image& I2, + const std::vector& F1, + const std::vector& F2, + const std::vector& matches, + std::vector& matchesFiltered, + std::vector& score, + aliceVision::Mat& E, + std::vector& valide, + KvldParameters& kvldParameters); + +#endif // KVLD_H diff --git a/src/aliceVision/matching/kvld/kvld_draw.h b/src/aliceVision/matching/kvld/kvld_draw.h index 606443df93..7704afa1f1 100644 --- a/src/aliceVision/matching/kvld/kvld_draw.h +++ b/src/aliceVision/matching/kvld/kvld_draw.h @@ -14,37 +14,36 @@ namespace aliceVision { //-- A slow but accurate way to draw K-VLD lines -inline void getKVLDMask( - image::Image< unsigned char > *maskL, - image::Image< unsigned char > *maskR, - const std::vector< feature::PointFeature > &vec_F1, - const std::vector< feature::PointFeature > &vec_F2, - const std::vector< Pair >& vec_matches, - const std::vector< bool >& vec_valide, - const aliceVision::Mat& mat_E) +inline void getKVLDMask(image::Image* maskL, + image::Image* maskR, + const std::vector& vec_F1, + const std::vector& vec_F2, + const std::vector& vec_matches, + const std::vector& vec_valide, + const aliceVision::Mat& mat_E) { - for( int it1 = 0; it1 < vec_matches.size() - 1; it1++ ) - { - for( int it2 = it1 + 1; it2 < vec_matches.size(); it2++ ) + for (int it1 = 0; it1 < vec_matches.size() - 1; it1++) { - if( vec_valide[ it1 ] && vec_valide[ it2 ] && mat_E( it1, it2 ) >= 0 ) - { - const feature::PointFeature & l1 = vec_F1[ vec_matches[ it1 ].first ]; - const feature::PointFeature & l2 = vec_F1[ vec_matches[ it2 ].first ]; - float l = ( l1.coords() - l2.coords() ).norm(); - int widthL = std::max( 1.f, l / ( dimension + 1.f ) ); - - image::DrawLineThickness(l1.x(), l1.y(), l2.x(), l2.y(), 255, widthL, maskL); - - const feature::PointFeature & r1 = vec_F2[ vec_matches[ it1 ].second ]; - const feature::PointFeature & r2 = vec_F2[ vec_matches[ it2 ].second ]; - float r = ( r1.coords() - r2.coords() ).norm(); - int widthR = std::max( 1.f, r / ( dimension + 1.f ) ); - - image::DrawLineThickness(r1.x(), r1.y(), r2.x(), r2.y(), 255, widthR, maskR); - } + for (int it2 = it1 + 1; it2 < vec_matches.size(); it2++) + { + if (vec_valide[it1] && vec_valide[it2] && mat_E(it1, it2) >= 0) + { + const feature::PointFeature& l1 = vec_F1[vec_matches[it1].first]; + const feature::PointFeature& l2 = vec_F1[vec_matches[it2].first]; + float l = (l1.coords() - l2.coords()).norm(); + int widthL = std::max(1.f, l / (dimension + 1.f)); + + image::DrawLineThickness(l1.x(), l1.y(), l2.x(), l2.y(), 255, widthL, maskL); + + const feature::PointFeature& r1 = vec_F2[vec_matches[it1].second]; + const feature::PointFeature& r2 = vec_F2[vec_matches[it2].second]; + float r = (r1.coords() - r2.coords()).norm(); + int widthR = std::max(1.f, r / (dimension + 1.f)); + + image::DrawLineThickness(r1.x(), r1.y(), r2.x(), r2.y(), 255, widthR, maskR); + } + } } - } } -}; // namespace aliceVision +}; // namespace aliceVision diff --git a/src/aliceVision/matching/matcherType.cpp b/src/aliceVision/matching/matcherType.cpp index f4b7fb895e..8925e1e9c2 100644 --- a/src/aliceVision/matching/matcherType.cpp +++ b/src/aliceVision/matching/matcherType.cpp @@ -11,29 +11,39 @@ namespace aliceVision { namespace matching { - + std::string EMatcherType_enumToString(EMatcherType matcherType) { - switch(matcherType) - { - case EMatcherType::BRUTE_FORCE_L2: return "BRUTE_FORCE_L2"; - case EMatcherType::ANN_L2: return "ANN_L2"; - case EMatcherType::CASCADE_HASHING_L2: return "CASCADE_HASHING_L2"; - case EMatcherType::FAST_CASCADE_HASHING_L2: return "FAST_CASCADE_HASHING_L2"; - case EMatcherType::BRUTE_FORCE_HAMMING: return "BRUTE_FORCE_HAMMING"; - } - throw std::out_of_range("Invalid matcherType enum"); + switch (matcherType) + { + case EMatcherType::BRUTE_FORCE_L2: + return "BRUTE_FORCE_L2"; + case EMatcherType::ANN_L2: + return "ANN_L2"; + case EMatcherType::CASCADE_HASHING_L2: + return "CASCADE_HASHING_L2"; + case EMatcherType::FAST_CASCADE_HASHING_L2: + return "FAST_CASCADE_HASHING_L2"; + case EMatcherType::BRUTE_FORCE_HAMMING: + return "BRUTE_FORCE_HAMMING"; + } + throw std::out_of_range("Invalid matcherType enum"); } EMatcherType EMatcherType_stringToEnum(const std::string& matcherType) { - if(matcherType == "BRUTE_FORCE_L2") return EMatcherType::BRUTE_FORCE_L2; - if(matcherType == "ANN_L2") return EMatcherType::ANN_L2; - if(matcherType == "CASCADE_HASHING_L2") return EMatcherType::CASCADE_HASHING_L2; - if(matcherType == "FAST_CASCADE_HASHING_L2") return EMatcherType::FAST_CASCADE_HASHING_L2; - if(matcherType == "BRUTE_FORCE_HAMMING") return EMatcherType::BRUTE_FORCE_HAMMING; - throw std::out_of_range("Invalid matcherType : " + matcherType); + if (matcherType == "BRUTE_FORCE_L2") + return EMatcherType::BRUTE_FORCE_L2; + if (matcherType == "ANN_L2") + return EMatcherType::ANN_L2; + if (matcherType == "CASCADE_HASHING_L2") + return EMatcherType::CASCADE_HASHING_L2; + if (matcherType == "FAST_CASCADE_HASHING_L2") + return EMatcherType::FAST_CASCADE_HASHING_L2; + if (matcherType == "BRUTE_FORCE_HAMMING") + return EMatcherType::BRUTE_FORCE_HAMMING; + throw std::out_of_range("Invalid matcherType : " + matcherType); } -} // namespace matching -} // namespace aliceVision \ No newline at end of file +} // namespace matching +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/matching/matcherType.hpp b/src/aliceVision/matching/matcherType.hpp index a5239d8d83..9a29ad630c 100644 --- a/src/aliceVision/matching/matcherType.hpp +++ b/src/aliceVision/matching/matcherType.hpp @@ -14,11 +14,11 @@ namespace matching { enum EMatcherType { - BRUTE_FORCE_L2, - ANN_L2, - CASCADE_HASHING_L2, - FAST_CASCADE_HASHING_L2, - BRUTE_FORCE_HAMMING + BRUTE_FORCE_L2, + ANN_L2, + CASCADE_HASHING_L2, + FAST_CASCADE_HASHING_L2, + BRUTE_FORCE_HAMMING }; /** @@ -28,14 +28,12 @@ enum EMatcherType */ std::string EMatcherType_enumToString(EMatcherType matcherType); - /** * @brief convert a string matcherType to it's corresponding enum EMatcherType * @param matcherType * @return EMatcherType */ - EMatcherType EMatcherType_stringToEnum(const std::string& matcherType); - +EMatcherType EMatcherType_stringToEnum(const std::string& matcherType); -} // namespace matching -} // namespace aliceVision +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/matchesFiltering.cpp b/src/aliceVision/matching/matchesFiltering.cpp index 6507a94334..be66c7810a 100644 --- a/src/aliceVision/matching/matchesFiltering.cpp +++ b/src/aliceVision/matching/matchesFiltering.cpp @@ -9,7 +9,6 @@ namespace aliceVision { namespace matching { - void sortMatches_byFeaturesScale(const aliceVision::matching::IndMatches& inputMatches, const aliceVision::feature::Regions& regionsI, const aliceVision::feature::Regions& regionsJ, @@ -25,7 +24,7 @@ void sortMatches_byFeaturesScale(const aliceVision::matching::IndMatches& inputM // It will be used to retrieve the correct matches after the sort. std::vector> vecFeatureScale; - for(size_t i = 0; i < inputMatches.size(); i++) + for (size_t i = 0; i < inputMatches.size(); i++) { float scale1 = vecFeatureI[inputMatches[i]._i].scale(); float scale2 = vecFeatureJ[inputMatches[i]._j].scale(); @@ -35,7 +34,7 @@ void sortMatches_byFeaturesScale(const aliceVision::matching::IndMatches& inputM std::sort(vecFeatureScale.begin(), vecFeatureScale.end(), matchCompare); // The sorted match vector is filled according to the result of the sorting above. - for(size_t i = 0; i < vecFeatureScale.size(); i++) + for (size_t i = 0; i < vecFeatureScale.size(); i++) { outputMatches.push_back(inputMatches[vecFeatureScale[i].second]); } @@ -45,10 +44,7 @@ void sortMatches_byDistanceRatio(aliceVision::matching::IndMatches& matches) { struct { - bool operator()(const matching::IndMatch& m1, const matching::IndMatch& m2) const - { - return m1._distanceRatio < m2._distanceRatio; - } + bool operator()(const matching::IndMatch& m1, const matching::IndMatch& m2) const { return m1._distanceRatio < m2._distanceRatio; } } increasingLoweRatio; std::sort(matches.begin(), matches.end(), increasingLoweRatio); @@ -61,7 +57,7 @@ bool matchCompare(const std::pair& firstElem, const std::pair uNumMatchesToKeep) + if (outputMatches.size() > uNumMatchesToKeep) { outputMatches.resize(uNumMatchesToKeep); } @@ -72,7 +68,8 @@ void matchesGridFiltering(const aliceVision::feature::Regions& lRegions, const aliceVision::feature::Regions& rRegions, const std::pair& rImgSize, const aliceVision::Pair& indexImagePair, - aliceVision::matching::IndMatches& outMatches, size_t gridSize) + aliceVision::matching::IndMatches& outMatches, + size_t gridSize) { const size_t leftCellWidth = divideRoundUp(lImgSize.first, gridSize); const size_t leftCellHeight = divideRoundUp(lImgSize.second, gridSize); @@ -81,20 +78,19 @@ void matchesGridFiltering(const aliceVision::feature::Regions& lRegions, std::vector completeGrid(gridSize * gridSize * 2); // Reserve all cells - for(aliceVision::matching::IndMatches& cell : completeGrid) + for (aliceVision::matching::IndMatches& cell : completeGrid) { cell.reserve(outMatches.size() / completeGrid.size()); } // Split matches in grid cells - for(const auto& match : outMatches) + for (const auto& match : outMatches) { const aliceVision::feature::PointFeature& leftPoint = lRegions.Features()[match._i]; const aliceVision::feature::PointFeature& rightPoint = rRegions.Features()[match._j]; - const float leftGridIndex_f = std::floor(leftPoint.x() / (float)leftCellWidth) + - std::floor(leftPoint.y() / (float)leftCellHeight) * gridSize; - const float rightGridIndex_f = std::floor(rightPoint.x() / (float)rightCellWidth) + - std::floor(rightPoint.y() / (float)rightCellHeight) * gridSize; + const float leftGridIndex_f = std::floor(leftPoint.x() / (float)leftCellWidth) + std::floor(leftPoint.y() / (float)leftCellHeight) * gridSize; + const float rightGridIndex_f = + std::floor(rightPoint.x() / (float)rightCellWidth) + std::floor(rightPoint.y() / (float)rightCellHeight) * gridSize; // clamp the values if we have feature/marker centers outside the image size. const std::size_t leftGridIndex = clamp(leftGridIndex_f, 0.f, float(gridSize - 1)); const std::size_t rightGridIndex = clamp(rightGridIndex_f, 0.f, float(gridSize - 1)); @@ -102,7 +98,7 @@ void matchesGridFiltering(const aliceVision::feature::Regions& lRegions, aliceVision::matching::IndMatches& currentCaseL = completeGrid[leftGridIndex]; aliceVision::matching::IndMatches& currentCaseR = completeGrid[rightGridIndex + gridSize * gridSize]; - if(currentCaseL.size() <= currentCaseR.size()) + if (currentCaseL.size() <= currentCaseR.size()) { currentCaseL.push_back(match); } @@ -114,9 +110,9 @@ void matchesGridFiltering(const aliceVision::feature::Regions& lRegions, // max Size of the cells: int maxSize = 0; - for(const auto& cell : completeGrid) + for (const auto& cell : completeGrid) { - if(cell.size() > maxSize) + if (cell.size() > maxSize) { maxSize = cell.size(); } @@ -126,11 +122,11 @@ void matchesGridFiltering(const aliceVision::feature::Regions& lRegions, finalMatches.reserve(outMatches.size()); // Combine all cells into a global ordered vector - for(int cmpt = 0; cmpt < maxSize; ++cmpt) + for (int cmpt = 0; cmpt < maxSize; ++cmpt) { - for(const auto& cell : completeGrid) + for (const auto& cell : completeGrid) { - if(cmpt < cell.size()) + if (cmpt < cell.size()) { finalMatches.push_back(cell[cmpt]); } @@ -146,13 +142,13 @@ void matchesGridFilteringForAllPairs(const PairwiseMatches& geometricMatches, std::size_t numMatchesToKeep, PairwiseMatches& outPairwiseMatches) { - for (const auto& geometricMatch: geometricMatches) + for (const auto& geometricMatch : geometricMatches) { - //Get the image pair and their matches. + // Get the image pair and their matches. const Pair& indexImagePair = geometricMatch.first; const MatchesPerDescType& matchesPerDesc = geometricMatch.second; - for (const auto& match: matchesPerDesc) + for (const auto& match : matchesPerDesc) { const feature::EImageDescriberType descType = match.first; assert(descType != feature::EImageDescriberType::UNINITIALIZED); @@ -162,18 +158,21 @@ void matchesGridFilteringForAllPairs(const PairwiseMatches& geometricMatches, const feature::Regions* lRegions = ®ionPerView.getRegions(indexImagePair.first, descType); // get the regions for the current view pair: - if(rRegions && lRegions) + if (rRegions && lRegions) { // sorting function: aliceVision::matching::IndMatches outMatches; sortMatches_byFeaturesScale(inputMatches, *lRegions, *rRegions, outMatches); - if(useGridSort) + if (useGridSort) { // TODO: rename as matchesGridOrdering - matchesGridFiltering(*lRegions, sfmData.getView(indexImagePair.first).getImage().getImgSize(), - *rRegions, sfmData.getView(indexImagePair.second).getImage().getImgSize(), - indexImagePair, outMatches); + matchesGridFiltering(*lRegions, + sfmData.getView(indexImagePair.first).getImage().getImgSize(), + *rRegions, + sfmData.getView(indexImagePair.second).getImage().getImgSize(), + indexImagePair, + outMatches); } if (numMatchesToKeep > 0) @@ -182,46 +181,45 @@ void matchesGridFilteringForAllPairs(const PairwiseMatches& geometricMatches, outMatches.resize(finalSize); } - // std::cout << "Left features: " << lRegions->Features().size() << ", right features: " << rRegions->Features().size() << ", num matches: " << inputMatches.size() << ", num filtered matches: " << outMatches.size() << std::endl; + // std::cout << "Left features: " << lRegions->Features().size() << ", right features: " << rRegions->Features().size() << ", num + // matches: " << inputMatches.size() << ", num filtered matches: " << outMatches.size() << std::endl; outPairwiseMatches[indexImagePair].insert(std::make_pair(descType, outMatches)); } else { - ALICEVISION_LOG_INFO("You cannot perform the grid filtering with these regions"); + ALICEVISION_LOG_INFO("You cannot perform the grid filtering with these regions"); } } } } -void filterMatchesByMin2DMotion(PairwiseMatches& mapPutativesMatches, - const feature::RegionsPerView& regionPerView, - double minRequired2DMotion) +void filterMatchesByMin2DMotion(PairwiseMatches& mapPutativesMatches, const feature::RegionsPerView& regionPerView, double minRequired2DMotion) { if (minRequired2DMotion < 0.0f) return; - //For each image pair - for (auto& imgPair: mapPutativesMatches) + // For each image pair + for (auto& imgPair : mapPutativesMatches) { const Pair viewPair = imgPair.first; IndexT viewI = viewPair.first; IndexT viewJ = viewPair.second; - //For each descriptors in this image - for (auto& descType: imgPair.second) + // For each descriptors in this image + for (auto& descType : imgPair.second) { const feature::EImageDescriberType type = descType.first; - const feature::Regions & regions_I = regionPerView.getRegions(viewI, type); - const feature::Regions & regions_J = regionPerView.getRegions(viewJ, type); + const feature::Regions& regions_I = regionPerView.getRegions(viewI, type); + const feature::Regions& regions_J = regionPerView.getRegions(viewJ, type); - const auto & features_I = regions_I.Features(); - const auto & features_J = regions_J.Features(); + const auto& features_I = regions_I.Features(); + const auto& features_J = regions_J.Features(); - IndMatches & matches = descType.second; + IndMatches& matches = descType.second; IndMatches updated_matches; - for (auto & match : matches) + for (auto& match : matches) { Vec2f pi = features_I[match._i].coords(); Vec2f pj = features_J[match._j].coords(); @@ -242,5 +240,5 @@ void filterMatchesByMin2DMotion(PairwiseMatches& mapPutativesMatches, } } -} // namespace sfm -} // namespace aliceVision +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/matchesFiltering.hpp b/src/aliceVision/matching/matchesFiltering.hpp index 3d9671f07f..4a205ff9b3 100644 --- a/src/aliceVision/matching/matchesFiltering.hpp +++ b/src/aliceVision/matching/matchesFiltering.hpp @@ -15,7 +15,6 @@ namespace aliceVision { namespace matching { - /** * @brief Compute the n best matches ('best' = mean of features' scale) * @param[in] inputMatches Set of indices for (putative) matches. @@ -65,18 +64,17 @@ void matchesGridFiltering(const aliceVision::feature::Regions& lRegions, const aliceVision::feature::Regions& rRegions, const std::pair& rImgSize, const aliceVision::Pair& indexImagePair, - aliceVision::matching::IndMatches& outMatches, size_t gridSize = 3); + aliceVision::matching::IndMatches& outMatches, + size_t gridSize = 3); void matchesGridFilteringForAllPairs(const PairwiseMatches& geometricMatches, const sfmData::SfMData& sfmData, const feature::RegionsPerView& regionPerView, - bool useGridSort, std::size_t numMatchesToKeep, + bool useGridSort, + std::size_t numMatchesToKeep, PairwiseMatches& outPairwiseMatches); +void filterMatchesByMin2DMotion(PairwiseMatches& mapPutativesMatches, const feature::RegionsPerView& regionPerView, double minRequired2DMotion); -void filterMatchesByMin2DMotion(PairwiseMatches& mapPutativesMatches, - const feature::RegionsPerView& regionPerView, - double minRequired2DMotion); - -} // namespace sfm -} // namespace aliceVision +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/matching_test.cpp b/src/aliceVision/matching/matching_test.cpp index 836d16678c..6371ab77a0 100644 --- a/src/aliceVision/matching/matching_test.cpp +++ b/src/aliceVision/matching/matching_test.cpp @@ -21,151 +21,148 @@ using namespace matching; BOOST_AUTO_TEST_CASE(Matching_ArrayMatcher_bruteForce_Simple_Dim1) { - std::random_device rd; - std::mt19937 gen(rd()); + std::random_device rd; + std::mt19937 gen(rd()); - const float array[] = {0, 1, 2, 3, 4}; - ArrayMatcher_bruteForce matcher; - BOOST_CHECK( matcher.Build(gen, array, 5, 1) ); + const float array[] = {0, 1, 2, 3, 4}; + ArrayMatcher_bruteForce matcher; + BOOST_CHECK(matcher.Build(gen, array, 5, 1)); - const float query[] = {2}; - int nIndice = -1; - float fDistance = -1.0f; - BOOST_CHECK( matcher.SearchNeighbour( query, &nIndice, &fDistance) ); + const float query[] = {2}; + int nIndice = -1; + float fDistance = -1.0f; + BOOST_CHECK(matcher.SearchNeighbour(query, &nIndice, &fDistance)); - BOOST_CHECK_EQUAL( 2, nIndice); // index of the found nearest neighbor - BOOST_CHECK_SMALL(static_cast(fDistance), 1e-8); //distance + BOOST_CHECK_EQUAL(2, nIndice); // index of the found nearest neighbor + BOOST_CHECK_SMALL(static_cast(fDistance), 1e-8); // distance } BOOST_AUTO_TEST_CASE(Matching_ArrayMatcher_bruteForce_NN) { - std::random_device rd; - std::mt19937 gen(rd()); - - const float array[] = {0, 1, 2, 5, 6}; - // no 3, because it involve the same dist as 1,1 - ArrayMatcher_bruteForce matcher; - BOOST_CHECK( matcher.Build(gen, array, 5, 1) ); - - const float query[] = {2}; - IndMatches vec_nIndice; - std::vector vec_fDistance; - BOOST_CHECK( matcher.SearchNeighbours(query,1, &vec_nIndice, &vec_fDistance, 5) ); - - BOOST_CHECK_EQUAL( 5, vec_nIndice.size()); - BOOST_CHECK_EQUAL( 5, vec_fDistance.size()); - - // Check distances: - BOOST_CHECK_SMALL(static_cast(vec_fDistance[0]- Square(2.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[1]- Square(1.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[2]- Square(0.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[3]- Square(5.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[4]- Square(6.0f-2.0f)), 1e-6); - - // Check indexes: - BOOST_CHECK_EQUAL(IndMatch(0,2), vec_nIndice[0]); - BOOST_CHECK_EQUAL(IndMatch(0,1), vec_nIndice[1]); - BOOST_CHECK_EQUAL(IndMatch(0,0), vec_nIndice[2]); - BOOST_CHECK_EQUAL(IndMatch(0,3), vec_nIndice[3]); - BOOST_CHECK_EQUAL(IndMatch(0,4), vec_nIndice[4]); + std::random_device rd; + std::mt19937 gen(rd()); + + const float array[] = {0, 1, 2, 5, 6}; + // no 3, because it involve the same dist as 1,1 + ArrayMatcher_bruteForce matcher; + BOOST_CHECK(matcher.Build(gen, array, 5, 1)); + + const float query[] = {2}; + IndMatches vec_nIndice; + std::vector vec_fDistance; + BOOST_CHECK(matcher.SearchNeighbours(query, 1, &vec_nIndice, &vec_fDistance, 5)); + + BOOST_CHECK_EQUAL(5, vec_nIndice.size()); + BOOST_CHECK_EQUAL(5, vec_fDistance.size()); + + // Check distances: + BOOST_CHECK_SMALL(static_cast(vec_fDistance[0] - Square(2.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[1] - Square(1.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[2] - Square(0.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[3] - Square(5.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[4] - Square(6.0f - 2.0f)), 1e-6); + + // Check indexes: + BOOST_CHECK_EQUAL(IndMatch(0, 2), vec_nIndice[0]); + BOOST_CHECK_EQUAL(IndMatch(0, 1), vec_nIndice[1]); + BOOST_CHECK_EQUAL(IndMatch(0, 0), vec_nIndice[2]); + BOOST_CHECK_EQUAL(IndMatch(0, 3), vec_nIndice[3]); + BOOST_CHECK_EQUAL(IndMatch(0, 4), vec_nIndice[4]); } BOOST_AUTO_TEST_CASE(Matching_ArrayMatcher_bruteForce_Simple_Dim4) { - std::random_device rd; - std::mt19937 gen(rd()); - - const float array[] = { - 0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, 10, 11}; - ArrayMatcher_bruteForce matcher; - BOOST_CHECK( matcher.Build(gen, array, 3, 4) ); - - const float query[] = {4, 5, 6, 7}; - int nIndice = -1; - float fDistance = -1.0f; - BOOST_CHECK( matcher.SearchNeighbour( query, &nIndice, &fDistance) ); - - BOOST_CHECK_EQUAL( 1, nIndice); // index of the found nearest neighbor - BOOST_CHECK_SMALL(static_cast(fDistance), 1e-8); //distance + std::random_device rd; + std::mt19937 gen(rd()); + + const float array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + ArrayMatcher_bruteForce matcher; + BOOST_CHECK(matcher.Build(gen, array, 3, 4)); + + const float query[] = {4, 5, 6, 7}; + int nIndice = -1; + float fDistance = -1.0f; + BOOST_CHECK(matcher.SearchNeighbour(query, &nIndice, &fDistance)); + + BOOST_CHECK_EQUAL(1, nIndice); // index of the found nearest neighbor + BOOST_CHECK_SMALL(static_cast(fDistance), 1e-8); // distance } BOOST_AUTO_TEST_CASE(Matching_ArrayMatcher_kdtreeFlann_Simple__NN) { - std::random_device rd; - std::mt19937 gen(rd()); - - const float array[] = {0, 1, 2, 5, 6}; - // no 3, because it involve the same dist as 1,1 - - ArrayMatcher_kdtreeFlann matcher; - BOOST_CHECK( matcher.Build(gen, array, 5, 1) ); - - const float query[] = {2}; - IndMatches vec_nIndice; - std::vector vec_fDistance; - const int NN = 5; - BOOST_CHECK( matcher.SearchNeighbours(query, 1, &vec_nIndice, &vec_fDistance, NN) ); - - BOOST_CHECK_EQUAL( 5, vec_nIndice.size()); - BOOST_CHECK_EQUAL( 5, vec_fDistance.size()); - - // Check distances: - BOOST_CHECK_SMALL(static_cast(vec_fDistance[0]- Square(2.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[1]- Square(1.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[2]- Square(0.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[3]- Square(5.0f-2.0f)), 1e-6); - BOOST_CHECK_SMALL(static_cast(vec_fDistance[4]- Square(6.0f-2.0f)), 1e-6); - - // Check indexes: - BOOST_CHECK_EQUAL(IndMatch(0,2), vec_nIndice[0]); - BOOST_CHECK_EQUAL(IndMatch(0,1), vec_nIndice[1]); - BOOST_CHECK_EQUAL(IndMatch(0,0), vec_nIndice[2]); - BOOST_CHECK_EQUAL(IndMatch(0,3), vec_nIndice[3]); - BOOST_CHECK_EQUAL(IndMatch(0,4), vec_nIndice[4]); + std::random_device rd; + std::mt19937 gen(rd()); + + const float array[] = {0, 1, 2, 5, 6}; + // no 3, because it involve the same dist as 1,1 + + ArrayMatcher_kdtreeFlann matcher; + BOOST_CHECK(matcher.Build(gen, array, 5, 1)); + + const float query[] = {2}; + IndMatches vec_nIndice; + std::vector vec_fDistance; + const int NN = 5; + BOOST_CHECK(matcher.SearchNeighbours(query, 1, &vec_nIndice, &vec_fDistance, NN)); + + BOOST_CHECK_EQUAL(5, vec_nIndice.size()); + BOOST_CHECK_EQUAL(5, vec_fDistance.size()); + + // Check distances: + BOOST_CHECK_SMALL(static_cast(vec_fDistance[0] - Square(2.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[1] - Square(1.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[2] - Square(0.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[3] - Square(5.0f - 2.0f)), 1e-6); + BOOST_CHECK_SMALL(static_cast(vec_fDistance[4] - Square(6.0f - 2.0f)), 1e-6); + + // Check indexes: + BOOST_CHECK_EQUAL(IndMatch(0, 2), vec_nIndice[0]); + BOOST_CHECK_EQUAL(IndMatch(0, 1), vec_nIndice[1]); + BOOST_CHECK_EQUAL(IndMatch(0, 0), vec_nIndice[2]); + BOOST_CHECK_EQUAL(IndMatch(0, 3), vec_nIndice[3]); + BOOST_CHECK_EQUAL(IndMatch(0, 4), vec_nIndice[4]); } //-- Test LIMIT case (empty arrays) BOOST_AUTO_TEST_CASE(Matching_ArrayMatcher_bruteForce_Simple_EmptyArrays) { - std::random_device rd; - std::mt19937 gen(rd()); + std::random_device rd; + std::mt19937 gen(rd()); - std::vector array; - ArrayMatcher_bruteForce matcher; - BOOST_CHECK(! matcher.Build(gen, &array[0], 0, 4) ); + std::vector array; + ArrayMatcher_bruteForce matcher; + BOOST_CHECK(!matcher.Build(gen, &array[0], 0, 4)); - int nIndice = -1; - float fDistance = -1.0f; - BOOST_CHECK(! matcher.SearchNeighbour( &array[0], &nIndice, &fDistance) ); + int nIndice = -1; + float fDistance = -1.0f; + BOOST_CHECK(!matcher.SearchNeighbour(&array[0], &nIndice, &fDistance)); } BOOST_AUTO_TEST_CASE(Matching_ArrayMatcher_kdtreeFlann_Simple_EmptyArrays) { - std::random_device rd; - std::mt19937 gen(rd()); + std::random_device rd; + std::mt19937 gen(rd()); - std::vector array; - ArrayMatcher_kdtreeFlann matcher; - BOOST_CHECK(! matcher.Build(gen, &array[0], 0, 4) ); + std::vector array; + ArrayMatcher_kdtreeFlann matcher; + BOOST_CHECK(!matcher.Build(gen, &array[0], 0, 4)); - int nIndice = -1; - float fDistance = -1.0f; - BOOST_CHECK(! matcher.SearchNeighbour( &array[0], &nIndice, &fDistance) ); + int nIndice = -1; + float fDistance = -1.0f; + BOOST_CHECK(!matcher.SearchNeighbour(&array[0], &nIndice, &fDistance)); } BOOST_AUTO_TEST_CASE(Matching_Cascade_Hashing_Simple_EmptyArrays) { - std::random_device rd; - std::mt19937 gen(rd()); + std::random_device rd; + std::mt19937 gen(rd()); - std::vector array; - ArrayMatcher_cascadeHashing matcher; - BOOST_CHECK(! matcher.Build(gen, &array[0], 0, 4) ); + std::vector array; + ArrayMatcher_cascadeHashing matcher; + BOOST_CHECK(!matcher.Build(gen, &array[0], 0, 4)); - int nIndice = -1; - float fDistance = -1.0f; - BOOST_CHECK(! matcher.SearchNeighbour( &array[0], &nIndice, &fDistance) ); + int nIndice = -1; + float fDistance = -1.0f; + BOOST_CHECK(!matcher.SearchNeighbour(&array[0], &nIndice, &fDistance)); } diff --git a/src/aliceVision/matching/pairwiseAdjacencyDisplay.hpp b/src/aliceVision/matching/pairwiseAdjacencyDisplay.hpp index 208b15c43c..5a3ff805e8 100644 --- a/src/aliceVision/matching/pairwiseAdjacencyDisplay.hpp +++ b/src/aliceVision/matching/pairwiseAdjacencyDisplay.hpp @@ -10,54 +10,55 @@ #include "dependencies/vectorGraphics/svgDrawer.hpp" #include "aliceVision/matching/IndMatch.hpp" -namespace aliceVision { +namespace aliceVision { namespace matching { /// Display pair wises matches as an Adjacency matrix in svg format -inline void PairwiseMatchingToAdjacencyMatrixSVG(const size_t NbImages, - const matching::PairwiseMatches & map_Matches, - const std::string & sOutName) +inline void PairwiseMatchingToAdjacencyMatrixSVG(const size_t NbImages, const matching::PairwiseMatches& map_Matches, const std::string& sOutName) { - if ( !map_Matches.empty()) - { - float scaleFactor = 5.0f; - svg::svgDrawer svgStream((NbImages+3)*5, (NbImages+3)*5); - // Go along all possible pair - for (size_t I = 0; I < NbImages; ++I) { - for (size_t J = 0; J < NbImages; ++J) { - // If the pair have matches display a blue boxes at I,J position. - matching::PairwiseMatches::const_iterator iterSearch = map_Matches.find(std::make_pair(I,J)); - if (iterSearch != map_Matches.end() && !iterSearch->second.empty()) + if (!map_Matches.empty()) + { + float scaleFactor = 5.0f; + svg::svgDrawer svgStream((NbImages + 3) * 5, (NbImages + 3) * 5); + // Go along all possible pair + for (size_t I = 0; I < NbImages; ++I) { - // Display as a tooltip: (IndexI, IndexJ NbMatches) - std::ostringstream os; - os << "(" << J << "," << I << " " << iterSearch->second.getNbAllMatches() <<")"; - svgStream.drawSquare(J*scaleFactor, I*scaleFactor, scaleFactor/2.0f, - svg::svgStyle().fill("blue").noStroke()); - } // HINT : THINK ABOUT OPACITY [0.4 -> 1.0] TO EXPRESS MATCH COUNT - } - } - // Display axes with 0 -> NbImages annotation : _| - std::ostringstream osNbImages; - osNbImages << NbImages; - svgStream.drawText((NbImages+1)*scaleFactor, scaleFactor, scaleFactor, "0", "black"); - svgStream.drawText((NbImages+1)*scaleFactor, - (NbImages)*scaleFactor - scaleFactor, scaleFactor, osNbImages.str(), "black"); - svgStream.drawLine((NbImages+1)*scaleFactor, 2*scaleFactor, - (NbImages+1)*scaleFactor, (NbImages)*scaleFactor - 2*scaleFactor, - svg::svgStyle().stroke("black", 1.0)); + for (size_t J = 0; J < NbImages; ++J) + { + // If the pair have matches display a blue boxes at I,J position. + matching::PairwiseMatches::const_iterator iterSearch = map_Matches.find(std::make_pair(I, J)); + if (iterSearch != map_Matches.end() && !iterSearch->second.empty()) + { + // Display as a tooltip: (IndexI, IndexJ NbMatches) + std::ostringstream os; + os << "(" << J << "," << I << " " << iterSearch->second.getNbAllMatches() << ")"; + svgStream.drawSquare(J * scaleFactor, I * scaleFactor, scaleFactor / 2.0f, svg::svgStyle().fill("blue").noStroke()); + } // HINT : THINK ABOUT OPACITY [0.4 -> 1.0] TO EXPRESS MATCH COUNT + } + } + // Display axes with 0 -> NbImages annotation : _| + std::ostringstream osNbImages; + osNbImages << NbImages; + svgStream.drawText((NbImages + 1) * scaleFactor, scaleFactor, scaleFactor, "0", "black"); + svgStream.drawText((NbImages + 1) * scaleFactor, (NbImages)*scaleFactor - scaleFactor, scaleFactor, osNbImages.str(), "black"); + svgStream.drawLine((NbImages + 1) * scaleFactor, + 2 * scaleFactor, + (NbImages + 1) * scaleFactor, + (NbImages)*scaleFactor - 2 * scaleFactor, + svg::svgStyle().stroke("black", 1.0)); - svgStream.drawText(scaleFactor, (NbImages+1)*scaleFactor, scaleFactor, "0", "black"); - svgStream.drawText((NbImages)*scaleFactor - scaleFactor, - (NbImages+1)*scaleFactor, scaleFactor, osNbImages.str(), "black"); - svgStream.drawLine(2*scaleFactor, (NbImages+1)*scaleFactor, - (NbImages)*scaleFactor - 2*scaleFactor, (NbImages+1)*scaleFactor, - svg::svgStyle().stroke("black", 1.0)); + svgStream.drawText(scaleFactor, (NbImages + 1) * scaleFactor, scaleFactor, "0", "black"); + svgStream.drawText((NbImages)*scaleFactor - scaleFactor, (NbImages + 1) * scaleFactor, scaleFactor, osNbImages.str(), "black"); + svgStream.drawLine(2 * scaleFactor, + (NbImages + 1) * scaleFactor, + (NbImages)*scaleFactor - 2 * scaleFactor, + (NbImages + 1) * scaleFactor, + svg::svgStyle().stroke("black", 1.0)); - std::ofstream svgFileStream( sOutName.c_str()); - svgFileStream << svgStream.closeSvgFile().str(); - } + std::ofstream svgFileStream(sOutName.c_str()); + svgFileStream << svgStream.closeSvgFile().str(); + } } -} // namespace matching -} // namespace aliceVision +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/supportEstimation.cpp b/src/aliceVision/matching/supportEstimation.cpp index 7f9b64c463..bfebc2f0ba 100644 --- a/src/aliceVision/matching/supportEstimation.cpp +++ b/src/aliceVision/matching/supportEstimation.cpp @@ -7,16 +7,15 @@ #include "supportEstimation.hpp" -namespace aliceVision{ -namespace matching{ +namespace aliceVision { +namespace matching { -bool hasStrongSupport(const std::vector& inliers, - const std::vector& descTypes, std::size_t minimumSamples) +bool hasStrongSupport(const std::vector& inliers, const std::vector& descTypes, std::size_t minimumSamples) { assert(inliers.size() <= descTypes.size()); float score = 0; - for(const std::size_t inlier : inliers) + for (const std::size_t inlier : inliers) { score += feature::getStrongSupportCoeff(descTypes[inlier]); } @@ -27,18 +26,18 @@ bool hasStrongSupport(const std::vector>& inliersPerCam const std::vector>& descTypesPerCamera, std::size_t minimumSamples) { - assert(inliersPerCamera.size() == descTypesPerCamera.size()); //same number of cameras + assert(inliersPerCamera.size() == descTypesPerCamera.size()); // same number of cameras float score = 0; - for(std::size_t camIdx = 0; camIdx < inliersPerCamera.size(); ++camIdx) + for (std::size_t camIdx = 0; camIdx < inliersPerCamera.size(); ++camIdx) { const auto& inliers = inliersPerCamera.at(camIdx); const auto& descTypes = descTypesPerCamera.at(camIdx); assert(inliers.size() <= descTypes.size()); - for(const std::size_t inlier : inliers) + for (const std::size_t inlier : inliers) { score += feature::getStrongSupportCoeff(descTypes[inlier]); } @@ -49,7 +48,7 @@ bool hasStrongSupport(const std::vector>& inliersPerCam bool hasStrongSupport(const aliceVision::matching::MatchesPerDescType& matchesPerDesc, std::size_t minimumSamples) { float score = 0; - for(const auto& matchesIt : matchesPerDesc) + for (const auto& matchesIt : matchesPerDesc) { const feature::EImageDescriberType descType = matchesIt.first; const matching::IndMatches& descMatches = matchesIt.second; @@ -59,5 +58,5 @@ bool hasStrongSupport(const aliceVision::matching::MatchesPerDescType& matchesPe return (score > minimumSamples); } -} -} \ No newline at end of file +} // namespace matching +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/matching/supportEstimation.hpp b/src/aliceVision/matching/supportEstimation.hpp index efc991a591..8a971b841e 100644 --- a/src/aliceVision/matching/supportEstimation.hpp +++ b/src/aliceVision/matching/supportEstimation.hpp @@ -14,7 +14,7 @@ namespace aliceVision { namespace matching { -#define ALICEVISION_MINIMUM_SAMPLES_COEF 7 // TODO: TO REMOVE +#define ALICEVISION_MINIMUM_SAMPLES_COEF 7 // TODO: TO REMOVE bool hasStrongSupport(const std::vector& inliers, const std::vector& descTypes, @@ -26,5 +26,5 @@ bool hasStrongSupport(const std::vector>& inliersPerCam bool hasStrongSupport(const matching::MatchesPerDescType& matchesPerDesc, std::size_t minimumSamples); -} -} +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/svgVisualization.cpp b/src/aliceVision/matching/svgVisualization.cpp index d9f9cb9f66..1f448d2d6d 100644 --- a/src/aliceVision/matching/svgVisualization.cpp +++ b/src/aliceVision/matching/svgVisualization.cpp @@ -10,72 +10,82 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) -#include + #include #endif #include namespace aliceVision { namespace matching { - -std::string describerTypeColor(feature::EImageDescriberType descType ) +std::string describerTypeColor(feature::EImageDescriberType descType) { - switch(descType) - { - case feature::EImageDescriberType::SIFT: return "yellow"; - case feature::EImageDescriberType::SIFT_FLOAT: return "yellow"; - case feature::EImageDescriberType::SIFT_UPRIGHT: return "yellow"; - case feature::EImageDescriberType::DSPSIFT: return "yellow"; - - case feature::EImageDescriberType::AKAZE: return "purple"; - case feature::EImageDescriberType::AKAZE_LIOP: return "purple"; - case feature::EImageDescriberType::AKAZE_MLDB: return "purple"; + switch (descType) + { + case feature::EImageDescriberType::SIFT: + return "yellow"; + case feature::EImageDescriberType::SIFT_FLOAT: + return "yellow"; + case feature::EImageDescriberType::SIFT_UPRIGHT: + return "yellow"; + case feature::EImageDescriberType::DSPSIFT: + return "yellow"; + + case feature::EImageDescriberType::AKAZE: + return "purple"; + case feature::EImageDescriberType::AKAZE_LIOP: + return "purple"; + case feature::EImageDescriberType::AKAZE_MLDB: + return "purple"; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case feature::EImageDescriberType::CCTAG3: return "blue"; - case feature::EImageDescriberType::CCTAG4: return "blue"; + case feature::EImageDescriberType::CCTAG3: + return "blue"; + case feature::EImageDescriberType::CCTAG4: + return "blue"; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - case feature::EImageDescriberType::APRILTAG16H5: return "blue"; + case feature::EImageDescriberType::APRILTAG16H5: + return "blue"; #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - case feature::EImageDescriberType::SIFT_OCV: return "orange"; -#endif - case feature::EImageDescriberType::AKAZE_OCV: return "indigo"; + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + case feature::EImageDescriberType::SIFT_OCV: + return "orange"; + #endif + case feature::EImageDescriberType::AKAZE_OCV: + return "indigo"; #endif - case feature::EImageDescriberType::UNKNOWN: return "red"; - case feature::EImageDescriberType::UNINITIALIZED: return "fuchsia"; - } - return "magenta"; + case feature::EImageDescriberType::UNKNOWN: + return "red"; + case feature::EImageDescriberType::UNINITIALIZED: + return "fuchsia"; + } + return "magenta"; } -float getRadiusEstimate(const std::pair & imgSize) +float getRadiusEstimate(const std::pair& imgSize) { - // heuristic for the radius of the feature according to the size of the image - // the larger the distance the larger the minimum radius should be in order to be visible - // We consider a minimum of 2 pixel and we increment it linearly according to - // the image size - return std::max(std::max(imgSize.first, imgSize.second) / float(600), 2.0f); + // heuristic for the radius of the feature according to the size of the image + // the larger the distance the larger the minimum radius should be in order to be visible + // We consider a minimum of 2 pixel and we increment it linearly according to + // the image size + return std::max(std::max(imgSize.first, imgSize.second) / float(600), 2.0f); } -float getStrokeEstimate(const std::pair & imgSize) -{ - return std::max(std::max(imgSize.first, imgSize.second) / float(2200), 2.0f); -} +float getStrokeEstimate(const std::pair& imgSize) { return std::max(std::max(imgSize.first, imgSize.second) / float(2200), 2.0f); } inline void drawMatchesSideBySide(svg::svgDrawer& svgStream, - const feature::PointFeature &L, - const feature::PointFeature &R, - std::size_t offset, - float radiusLeft, - float radiusRight, - float strokeLeft, - float strokeRight, - const svg::svgStyle& lineStyle, - const svg::svgStyle& leftStyle, - const svg::svgStyle& rightStyle) + const feature::PointFeature& L, + const feature::PointFeature& R, + std::size_t offset, + float radiusLeft, + float radiusRight, + float strokeLeft, + float strokeRight, + const svg::svgStyle& lineStyle, + const svg::svgStyle& leftStyle, + const svg::svgStyle& rightStyle) { const float xRight = R.x() + offset; svgStream.drawLine(L.x(), L.y(), xRight, R.y(), lineStyle); @@ -84,250 +94,243 @@ inline void drawMatchesSideBySide(svg::svgDrawer& svgStream, } inline void drawMatchesSideBySide(svg::svgDrawer& svgStream, - const std::vector& keypointsLeft, - const std::vector& keypointsRight, - std::size_t offset, - float radiusLeft, - float radiusRight, - float strokeLeft, - float strokeRight, - const svg::svgStyle& lineStyle, - const svg::svgStyle& leftStyle, - const svg::svgStyle& rightStyle, - const matching::IndMatches& matches) + const std::vector& keypointsLeft, + const std::vector& keypointsRight, + std::size_t offset, + float radiusLeft, + float radiusRight, + float strokeLeft, + float strokeRight, + const svg::svgStyle& lineStyle, + const svg::svgStyle& leftStyle, + const svg::svgStyle& rightStyle, + const matching::IndMatches& matches) { - for (const matching::IndMatch &m : matches) + for (const matching::IndMatch& m : matches) { - const feature::PointFeature &L = keypointsLeft[m._i]; - const feature::PointFeature &R = keypointsRight[m._j]; - drawMatchesSideBySide(svgStream, L, R, offset, radiusLeft, radiusRight, strokeLeft, strokeRight, lineStyle, leftStyle, rightStyle); + const feature::PointFeature& L = keypointsLeft[m._i]; + const feature::PointFeature& R = keypointsRight[m._j]; + drawMatchesSideBySide(svgStream, L, R, offset, radiusLeft, radiusRight, strokeLeft, strokeRight, lineStyle, leftStyle, rightStyle); } } inline void drawInliersSideBySide(svg::svgDrawer& svgStream, - const std::vector& keypointsLeft, - const std::vector& keypointsRight, - std::size_t offset, - float radiusLeft, - float radiusRight, - float strokeLeft, - float strokeRight, - const svg::svgStyle& lineStyle, - const svg::svgStyle& leftStyle, - const svg::svgStyle& rightStyle, - const matching::IndMatches& matches, - std::vector& inliers) + const std::vector& keypointsLeft, + const std::vector& keypointsRight, + std::size_t offset, + float radiusLeft, + float radiusRight, + float strokeLeft, + float strokeRight, + const svg::svgStyle& lineStyle, + const svg::svgStyle& leftStyle, + const svg::svgStyle& rightStyle, + const matching::IndMatches& matches, + std::vector& inliers) { - for (const auto &idx : inliers) + for (const auto& idx : inliers) { const auto currMatch = matches[idx]; - const feature::PointFeature &L = keypointsLeft[currMatch._i]; - const feature::PointFeature &R = keypointsRight[currMatch._j]; + const feature::PointFeature& L = keypointsLeft[currMatch._i]; + const feature::PointFeature& R = keypointsRight[currMatch._j]; - drawMatchesSideBySide(svgStream, L, R, offset, radiusLeft, radiusRight, strokeLeft, strokeRight, lineStyle, leftStyle, rightStyle); + drawMatchesSideBySide(svgStream, L, R, offset, radiusLeft, radiusRight, strokeLeft, strokeRight, lineStyle, leftStyle, rightStyle); } } void drawMatchesSideBySide(const std::string& imagePathLeft, - const std::pair& imageSizeLeft, + const std::pair& imageSizeLeft, const std::vector& keypointsLeft, const std::string& imagePathRight, - const std::pair& imageSizeRight, + const std::pair& imageSizeRight, const std::vector& keypointsRight, const matching::IndMatches& matches, - const std::string &outputSVGPath) + const std::string& outputSVGPath) { - svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, - std::max(imageSizeLeft.second, imageSizeRight.second)); - const std::size_t offset = imageSizeLeft.first; - svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); - svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, offset); - - const float radiusLeft = getRadiusEstimate(imageSizeLeft); - const float radiusRight = getRadiusEstimate(imageSizeRight); - const float strokeLeft = getStrokeEstimate(imageSizeLeft); - const float strokeRight = getStrokeEstimate(imageSizeRight); - const std::string color = "green"; - const svg::svgStyle lineStyle = svg::svgStyle().stroke(color, std::min(strokeRight, strokeLeft)); - const svg::svgStyle leftStyle = svg::svgStyle().stroke(color, strokeLeft); - const svg::svgStyle rightStyle = svg::svgStyle().stroke(color, strokeRight); - - - for (const matching::IndMatch &m : matches) - { - //Get back linked feature, draw a circle and link them by a line - const feature::PointFeature &L = keypointsLeft[m._i]; - const feature::PointFeature &R = keypointsRight[m._j]; - const float xRight = R.x() + offset; + svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); + const std::size_t offset = imageSizeLeft.first; + svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); + svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, offset); + + const float radiusLeft = getRadiusEstimate(imageSizeLeft); + const float radiusRight = getRadiusEstimate(imageSizeRight); + const float strokeLeft = getStrokeEstimate(imageSizeLeft); + const float strokeRight = getStrokeEstimate(imageSizeRight); + const std::string color = "green"; + const svg::svgStyle lineStyle = svg::svgStyle().stroke(color, std::min(strokeRight, strokeLeft)); + const svg::svgStyle leftStyle = svg::svgStyle().stroke(color, strokeLeft); + const svg::svgStyle rightStyle = svg::svgStyle().stroke(color, strokeRight); + + for (const matching::IndMatch& m : matches) + { + // Get back linked feature, draw a circle and link them by a line + const feature::PointFeature& L = keypointsLeft[m._i]; + const feature::PointFeature& R = keypointsRight[m._j]; + const float xRight = R.x() + offset; + + svgStream.drawLine(L.x(), L.y(), xRight, R.y(), lineStyle); + svgStream.drawCircle(L.x(), L.y(), radiusLeft, leftStyle); + svgStream.drawCircle(xRight, R.y(), radiusRight, rightStyle); + } - svgStream.drawLine(L.x(), L.y(), xRight, R.y(), lineStyle); - svgStream.drawCircle(L.x(), L.y(), radiusLeft, leftStyle); - svgStream.drawCircle(xRight, R.y(), radiusRight, rightStyle); - } - - - std::ofstream svgFile(outputSVGPath); - if (!svgFile.is_open()) - { - ALICEVISION_CERR("Unable to open file " + outputSVGPath); - return; - } - svgFile << svgStream.closeSvgFile().str(); - if (!svgFile.good()) - { - ALICEVISION_CERR("Something wrong while writing file " + outputSVGPath); - return; - } - svgFile.close(); + std::ofstream svgFile(outputSVGPath); + if (!svgFile.is_open()) + { + ALICEVISION_CERR("Unable to open file " + outputSVGPath); + return; + } + svgFile << svgStream.closeSvgFile().str(); + if (!svgFile.good()) + { + ALICEVISION_CERR("Something wrong while writing file " + outputSVGPath); + return; + } + svgFile.close(); } void drawHomographyMatches(const std::string& imagePathLeft, - const std::pair& imageSizeLeft, + const std::pair& imageSizeLeft, const std::vector& features_I, const std::string& imagePathRight, - const std::pair& imageSizeRight, + const std::pair& imageSizeRight, const std::vector& features_J, const std::vector>& homographiesAndMatches, const matching::IndMatches& putativeMatches, const std::string& outFilename) { - const auto& colors = sixteenColors; - - svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, - std::max(imageSizeLeft.second, imageSizeRight.second)); - const std::size_t offset = imageSizeLeft.first; - - svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); - svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, offset); - - // draw little white dots representing putative matches - for (const auto& match : putativeMatches) - { - const float radius{1.f}; - const float strokeSize{2.f}; - const feature::PointFeature &fI = features_I.at(match._i); - const feature::PointFeature &fJ = features_J.at(match._j); - const svg::svgStyle style = svg::svgStyle().stroke("white", strokeSize); - - svgStream.drawCircle(fI.x(), fI.y(), radius, style); - svgStream.drawCircle(fJ.x() + offset, fJ.y(), radius, style); - } - - { - // for each homography draw the associated matches in a different color - std::size_t iH{0}; - const float radius{5.f}; - const float strokeSize{5.f}; - for (const auto &currRes : homographiesAndMatches) - { - const auto &bestMatchesId = currRes.second; - // 0 < iH <= 8: colored; iH > 8 are white (not enough colors) - const std::string color = (iH < colors.size()) ? colors.at(iH) : "grey"; + const auto& colors = sixteenColors; - for (const auto &match : bestMatchesId) - { - const feature::PointFeature &fI = features_I.at(match._i); - const feature::PointFeature &fJ = features_J.at(match._j); + svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); + const std::size_t offset = imageSizeLeft.first; - const svg::svgStyle style = svg::svgStyle().stroke(color, strokeSize); + svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); + svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, offset); + + // draw little white dots representing putative matches + for (const auto& match : putativeMatches) + { + const float radius{1.f}; + const float strokeSize{2.f}; + const feature::PointFeature& fI = features_I.at(match._i); + const feature::PointFeature& fJ = features_J.at(match._j); + const svg::svgStyle style = svg::svgStyle().stroke("white", strokeSize); svgStream.drawCircle(fI.x(), fI.y(), radius, style); svgStream.drawCircle(fJ.x() + offset, fJ.y(), radius, style); - } - ++iH; } - } - - std::ofstream svgFile(outFilename); - if(!svgFile.is_open()) - { - ALICEVISION_CERR("Unable to open file "+outFilename); - return; - } - svgFile << svgStream.closeSvgFile().str(); - if(!svgFile.good()) - { - ALICEVISION_CERR("Something wrong happened while writing file "+outFilename); - return; - } - svgFile.close(); -} -void saveMatches2SVG(const std::string &imagePathLeft, - const std::pair & imageSizeLeft, - const feature::MapRegionsPerDesc &keypointsLeft, - const std::string &imagePathRight, - const std::pair & imageSizeRight, - const feature::MapRegionsPerDesc &keypointsRight, - const matching::MatchesPerDescType & matches, - const std::string &outputSVGPath) -{ - svg::svgDrawer svgStream( imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); - svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); - svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, imageSizeLeft.first); - - // heuristic for the radius of the feature according to the size of the image - // the larger the distance the larger the minimum radius should be in order to be visible - // We consider a minimum of 2 pixel and we increment it linearly according to - // the image size - const float radiusLeft = getRadiusEstimate(imageSizeLeft); - const float radiusRight = getRadiusEstimate(imageSizeRight); - const float strokeLeft = getStrokeEstimate(imageSizeLeft); - const float strokeRight = getStrokeEstimate(imageSizeRight); - - for(const auto& descMatches : matches) - { - feature::EImageDescriberType descType = descMatches.first; - const std::string descColor = describerTypeColor(descType); - for(const matching::IndMatch &m : descMatches.second) { - //Get back linked feature, draw a circle and link them by a line - const feature::PointFeature & L = keypointsLeft.at(descType)->GetRegionsPositions()[m._i]; - const feature::PointFeature & R = keypointsRight.at(descType)->GetRegionsPositions()[m._j]; + // for each homography draw the associated matches in a different color + std::size_t iH{0}; + const float radius{5.f}; + const float strokeSize{5.f}; + for (const auto& currRes : homographiesAndMatches) + { + const auto& bestMatchesId = currRes.second; + // 0 < iH <= 8: colored; iH > 8 are white (not enough colors) + const std::string color = (iH < colors.size()) ? colors.at(iH) : "grey"; + + for (const auto& match : bestMatchesId) + { + const feature::PointFeature& fI = features_I.at(match._i); + const feature::PointFeature& fJ = features_J.at(match._j); + + const svg::svgStyle style = svg::svgStyle().stroke(color, strokeSize); + + svgStream.drawCircle(fI.x(), fI.y(), radius, style); + svgStream.drawCircle(fJ.x() + offset, fJ.y(), radius, style); + } + ++iH; + } + } - svgStream.drawLine(L.x(), L.y(), R.x()+imageSizeLeft.first, R.y(), svg::svgStyle().stroke("green", std::min(strokeRight,strokeLeft))); - svgStream.drawCircle(L.x(), L.y(), radiusLeft, svg::svgStyle().stroke(descColor, strokeLeft)); - svgStream.drawCircle(R.x()+imageSizeLeft.first, R.y(), radiusRight, svg::svgStyle().stroke(descColor, strokeRight)); + std::ofstream svgFile(outFilename); + if (!svgFile.is_open()) + { + ALICEVISION_CERR("Unable to open file " + outFilename); + return; } - } - - std::ofstream svgFile(outputSVGPath); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + svgFile << svgStream.closeSvgFile().str(); + if (!svgFile.good()) + { + ALICEVISION_CERR("Something wrong happened while writing file " + outFilename); + return; + } + svgFile.close(); } +void saveMatches2SVG(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, + const feature::MapRegionsPerDesc& keypointsLeft, + const std::string& imagePathRight, + const std::pair& imageSizeRight, + const feature::MapRegionsPerDesc& keypointsRight, + const matching::MatchesPerDescType& matches, + const std::string& outputSVGPath) +{ + svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); + svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); + svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, imageSizeLeft.first); + + // heuristic for the radius of the feature according to the size of the image + // the larger the distance the larger the minimum radius should be in order to be visible + // We consider a minimum of 2 pixel and we increment it linearly according to + // the image size + const float radiusLeft = getRadiusEstimate(imageSizeLeft); + const float radiusRight = getRadiusEstimate(imageSizeRight); + const float strokeLeft = getStrokeEstimate(imageSizeLeft); + const float strokeRight = getStrokeEstimate(imageSizeRight); + + for (const auto& descMatches : matches) + { + feature::EImageDescriberType descType = descMatches.first; + const std::string descColor = describerTypeColor(descType); + for (const matching::IndMatch& m : descMatches.second) + { + // Get back linked feature, draw a circle and link them by a line + const feature::PointFeature& L = keypointsLeft.at(descType)->GetRegionsPositions()[m._i]; + const feature::PointFeature& R = keypointsRight.at(descType)->GetRegionsPositions()[m._j]; + + svgStream.drawLine(L.x(), L.y(), R.x() + imageSizeLeft.first, R.y(), svg::svgStyle().stroke("green", std::min(strokeRight, strokeLeft))); + svgStream.drawCircle(L.x(), L.y(), radiusLeft, svg::svgStyle().stroke(descColor, strokeLeft)); + svgStream.drawCircle(R.x() + imageSizeLeft.first, R.y(), radiusRight, svg::svgStyle().stroke(descColor, strokeRight)); + } + } -void saveKeypoints2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::string &outputSVGPath, + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); +} + +void saveKeypoints2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::string& outputSVGPath, bool richKeypoint /*= true*/) { - svg::svgDrawer svgStream( imageSize.first, imageSize.second); - svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); - // heuristic for the radius of the feature according to the size of the image - // the larger the distance the larger the minimum radius should be in order to be visible - // We consider a minimum of 2 pixel and we increment it linearly according to - // the image size - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); - - for(const feature::PointFeature &kpt : keypoints) - { - svgStream.drawCircle(kpt.x(), kpt.y(), (richKeypoint) ? kpt.scale()*radius : radius, svg::svgStyle().stroke("yellow", strokeWidth)); - if(richKeypoint) + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); + // heuristic for the radius of the feature according to the size of the image + // the larger the distance the larger the minimum radius should be in order to be visible + // We consider a minimum of 2 pixel and we increment it linearly according to + // the image size + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); + + for (const feature::PointFeature& kpt : keypoints) { - // compute the coordinate of the point on the circle used to draw the orientation line - const Vec2f point = kpt.coords() + kpt.getScaledOrientationVector() * radius; - svgStream.drawLine(kpt.x(), kpt.y(), - point(0), point(1), - svg::svgStyle().stroke("yellow", strokeWidth)); + svgStream.drawCircle(kpt.x(), kpt.y(), (richKeypoint) ? kpt.scale() * radius : radius, svg::svgStyle().stroke("yellow", strokeWidth)); + if (richKeypoint) + { + // compute the coordinate of the point on the circle used to draw the orientation line + const Vec2f point = kpt.coords() + kpt.getScaledOrientationVector() * radius; + svgStream.drawLine(kpt.x(), kpt.y(), point(0), point(1), svg::svgStyle().stroke("yellow", strokeWidth)); + } } - } - - std::ofstream svgFile( outputSVGPath ); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } /** @@ -345,535 +348,533 @@ void saveKeypoints2SVG(const std::string &inputImagePath, * @param[in] richKeypoint Draw rich keypoints with a circle proportional to the * octave in which the point has been detected. */ -void drawKeypointsSideBySide(const std::string&imagePathLeft, - const std::pair& imageSizeLeft, +void drawKeypointsSideBySide(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, const std::vector& keypointsLeft, - const std::string &imagePathRight, - const std::pair& imageSizeRight, + const std::string& imagePathRight, + const std::pair& imageSizeRight, const std::vector& keypointsRight, - const std::string &outputSVGPath, + const std::string& outputSVGPath, bool richKeypoint) { - svg::svgDrawer svgStream( imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); - const std::size_t offset = imageSizeLeft.first; - svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); - svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, offset); - - const float radiusLeft = getRadiusEstimate(imageSizeLeft); - const float radiusRight = getRadiusEstimate(imageSizeRight); - const float strokeLeft = getStrokeEstimate(imageSizeLeft); - const float strokeRight = getStrokeEstimate(imageSizeRight); - const svg::svgStyle styleLeft = svg::svgStyle().stroke("green", strokeLeft); - const svg::svgStyle styleRight = svg::svgStyle().stroke("green", strokeRight); - - for(const auto& kpt : keypointsLeft) - { - svgStream.drawCircle(kpt.x(), kpt.y(), (richKeypoint) ? kpt.scale()*radiusLeft : radiusLeft, styleLeft); - } - for(const auto& kpt : keypointsRight) - { - svgStream.drawCircle(kpt.x()+offset, kpt.y(), (richKeypoint) ? kpt.scale()*radiusRight : radiusRight, styleRight); - } - - std::ofstream svgFile(outputSVGPath); - if(!svgFile.is_open()) - { - ALICEVISION_CERR("Unable to open file " + outputSVGPath); - return; - } - svgFile << svgStream.closeSvgFile().str(); - if(!svgFile.good()) - { - ALICEVISION_CERR("Something wrong while writing file " + outputSVGPath); - return; - } - svgFile.close(); + svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); + const std::size_t offset = imageSizeLeft.first; + svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); + svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, offset); + + const float radiusLeft = getRadiusEstimate(imageSizeLeft); + const float radiusRight = getRadiusEstimate(imageSizeRight); + const float strokeLeft = getStrokeEstimate(imageSizeLeft); + const float strokeRight = getStrokeEstimate(imageSizeRight); + const svg::svgStyle styleLeft = svg::svgStyle().stroke("green", strokeLeft); + const svg::svgStyle styleRight = svg::svgStyle().stroke("green", strokeRight); + + for (const auto& kpt : keypointsLeft) + { + svgStream.drawCircle(kpt.x(), kpt.y(), (richKeypoint) ? kpt.scale() * radiusLeft : radiusLeft, styleLeft); + } + for (const auto& kpt : keypointsRight) + { + svgStream.drawCircle(kpt.x() + offset, kpt.y(), (richKeypoint) ? kpt.scale() * radiusRight : radiusRight, styleRight); + } + + std::ofstream svgFile(outputSVGPath); + if (!svgFile.is_open()) + { + ALICEVISION_CERR("Unable to open file " + outputSVGPath); + return; + } + svgFile << svgStream.closeSvgFile().str(); + if (!svgFile.good()) + { + ALICEVISION_CERR("Something wrong while writing file " + outputSVGPath); + return; + } + svgFile.close(); } -void saveFeatures2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const feature::MapFeaturesPerDesc & keypoints, - const std::string & outputSVGPath) +void saveFeatures2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const feature::MapFeaturesPerDesc& keypoints, + const std::string& outputSVGPath) { - svg::svgDrawer svgStream( imageSize.first, imageSize.second); - svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); - // heuristic for the radius of the feature according to the size of the image - // the larger the distance the larger the minimum radius should be in order to be visible - // We consider a minimum of 2 pixel and we increment it linearly according to - // the image size - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); - - for(const auto& keypointPerDesc: keypoints) - { - const std::string featColor = describerTypeColor(keypointPerDesc.first); - for(const feature::PointFeature &kpt : keypointPerDesc.second) + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); + // heuristic for the radius of the feature according to the size of the image + // the larger the distance the larger the minimum radius should be in order to be visible + // We consider a minimum of 2 pixel and we increment it linearly according to + // the image size + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); + + for (const auto& keypointPerDesc : keypoints) { - svgStream.drawCircle(kpt.x(), kpt.y(), radius, svg::svgStyle().stroke(featColor, strokeWidth)); + const std::string featColor = describerTypeColor(keypointPerDesc.first); + for (const feature::PointFeature& kpt : keypointPerDesc.second) + { + svgStream.drawCircle(kpt.x(), kpt.y(), radius, svg::svgStyle().stroke(featColor, strokeWidth)); + } } - } - - std::ofstream svgFile( outputSVGPath ); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } /** * @brief - * - * @param[in] inputImagePath The full path to the image file. The image is only + * + * @param[in] inputImagePath The full path to the image file. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSize The size of the image . * @param[in] points A vector containing the points to draw. * @param[in] outputSVGPath The name of the svg file to generate. */ -void saveFeatures2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const Mat &points, - const std::string &outputSVGPath, - const std::vector *inliers /*=nullptr*/) +void saveFeatures2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const Mat& points, + const std::string& outputSVGPath, + const std::vector* inliers /*=nullptr*/) { - assert(points.rows()>=2); - svg::svgDrawer svgStream( imageSize.first, imageSize.second); - svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); - // heuristic for the radius of the feature according to the size of the image - // the larger the distance the larger the minimum radius should be in order to be visible - // We consider a minimum of 2 pixel and we increment it linearly according to - // the image size - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); - - if(!inliers) - { - for(std::size_t i=0; i < points.cols(); ++i) + assert(points.rows() >= 2); + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); + // heuristic for the radius of the feature according to the size of the image + // the larger the distance the larger the minimum radius should be in order to be visible + // We consider a minimum of 2 pixel and we increment it linearly according to + // the image size + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); + + if (!inliers) { - svgStream.drawCircle(points(0,i), points(1,i), radius, svg::svgStyle().stroke("yellow", strokeWidth)); + for (std::size_t i = 0; i < points.cols(); ++i) + { + svgStream.drawCircle(points(0, i), points(1, i), radius, svg::svgStyle().stroke("yellow", strokeWidth)); + } } - } - else - { - for(std::size_t i=0; i < points.cols(); ++i) + else { - if(std::find(inliers->begin(), inliers->end(), i) != inliers->end()) - svgStream.drawCircle(points(0,i), points(1,i), radius, svg::svgStyle().stroke("green", strokeWidth)); - else - svgStream.drawCircle(points(0,i), points(1,i), radius, svg::svgStyle().stroke("yellow", strokeWidth)); - } - } - - std::ofstream svgFile( outputSVGPath ); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + for (std::size_t i = 0; i < points.cols(); ++i) + { + if (std::find(inliers->begin(), inliers->end(), i) != inliers->end()) + svgStream.drawCircle(points(0, i), points(1, i), radius, svg::svgStyle().stroke("green", strokeWidth)); + else + svgStream.drawCircle(points(0, i), points(1, i), radius, svg::svgStyle().stroke("yellow", strokeWidth)); + } + } + + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } -bool lineToBorderPoints(const Vec3 &epiLine, const std::size_t imgW, const std::size_t imgH, std::vector &intersectionPts) +bool lineToBorderPoints(const Vec3& epiLine, const std::size_t imgW, const std::size_t imgH, std::vector& intersectionPts) { - intersectionPts.clear(); - intersectionPts.reserve(2); - // @TODO check special case of epiline coincident with the border lines - - // intersect epiline with x=0 - //y = -(a*0+c)/b - double p = - epiLine(2)/epiLine(1); - if(p >= 0 && p <= imgH) - intersectionPts.emplace_back(0, p); - - // intersect epiline with x=imgW - //y = -(a*imgW+c)/b - p = - (imgW*epiLine(0) + epiLine(2))/epiLine(1); - if(p >= 0 && p <= imgH) - intersectionPts.emplace_back(imgW, p); - - if(intersectionPts.size()==2) - return true; - - // intersect epiline with y=0 - //x = -(b*0+c)/a - p = - epiLine(2)/epiLine(0); - if(p >= 0 && p <= imgW) - intersectionPts.emplace_back(p, 0); - - if(intersectionPts.size()==2) - return true; - - // intersect epiline with y=imgH - //y = -(a*imgW+c)/b - p = - (imgH*epiLine(1) + epiLine(2))/epiLine(0); - if(p >= 0 && p <= imgW) - intersectionPts.emplace_back(p, imgH); - - return (intersectionPts.size()==2); - + intersectionPts.clear(); + intersectionPts.reserve(2); + // @TODO check special case of epiline coincident with the border lines + + // intersect epiline with x=0 + // y = -(a*0+c)/b + double p = -epiLine(2) / epiLine(1); + if (p >= 0 && p <= imgH) + intersectionPts.emplace_back(0, p); + + // intersect epiline with x=imgW + // y = -(a*imgW+c)/b + p = -(imgW * epiLine(0) + epiLine(2)) / epiLine(1); + if (p >= 0 && p <= imgH) + intersectionPts.emplace_back(imgW, p); + + if (intersectionPts.size() == 2) + return true; + + // intersect epiline with y=0 + // x = -(b*0+c)/a + p = -epiLine(2) / epiLine(0); + if (p >= 0 && p <= imgW) + intersectionPts.emplace_back(p, 0); + + if (intersectionPts.size() == 2) + return true; + + // intersect epiline with y=imgH + // y = -(a*imgW+c)/b + p = -(imgH * epiLine(1) + epiLine(2)) / epiLine(0); + if (p >= 0 && p <= imgW) + intersectionPts.emplace_back(p, imgH); + + return (intersectionPts.size() == 2); } -void saveEpipolarGeometry2SVG(const std::string &imagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::vector &otherKeypoints, - const matching::IndMatches &matches, - const Mat3 &Fmat, - const std::string &outputSVGPath, +void saveEpipolarGeometry2SVG(const std::string& imagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::vector& otherKeypoints, + const matching::IndMatches& matches, + const Mat3& Fmat, + const std::string& outputSVGPath, bool left) { - svg::svgDrawer svgStream(imageSize.first, imageSize.second); - svgStream.drawImage(imagePath, imageSize.first, imageSize.second); - std::size_t count = 0; - // heuristic for the radius of the feature according to the size of the image - // the larger the distance the larger the minimum radius should be in order to be visible - // We consider a minimum of 2 pixel and we increment it linearly according to - // the image size - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); - for(const matching::IndMatch &m : matches) - { - //Get back linked feature, draw a circle and link them by a line - feature::PointFeature p; - feature::PointFeature other; - if(left) + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(imagePath, imageSize.first, imageSize.second); + std::size_t count = 0; + // heuristic for the radius of the feature according to the size of the image + // the larger the distance the larger the minimum radius should be in order to be visible + // We consider a minimum of 2 pixel and we increment it linearly according to + // the image size + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); + for (const matching::IndMatch& m : matches) { - p = keypoints[m._i]; - other = otherKeypoints[m._j]; + // Get back linked feature, draw a circle and link them by a line + feature::PointFeature p; + feature::PointFeature other; + if (left) + { + p = keypoints[m._i]; + other = otherKeypoints[m._j]; + } + else + { + p = keypoints[m._j]; + other = otherKeypoints[m._i]; + } + if (count > 7) + svgStream.drawCircle(p.x(), p.y(), radius, svg::svgStyle().stroke("yellow", strokeWidth)); + else + svgStream.drawCircle(p.x(), p.y(), radius, svg::svgStyle().stroke("red", strokeWidth).fill("red")); + + Vec3 epiLine; + if (left) + { + epiLine = Fmat.transpose() * Vec3(other.x(), other.y(), 1.0); + } + else + { + epiLine = Fmat * Vec3(other.x(), other.y(), 1.0); + } + + // ALICEVISION_LOG_DEBUG("test 1 o*F*p " << (Fmat*Vec3(p.x(), p.y(), 1.0)).transpose()*Vec3(other.x(), other.y(), 1.0)); + // ALICEVISION_LOG_DEBUG("test 2 p*F*o " << (Fmat.transpose()*Vec3(p.x(), p.y(), 1.0)).transpose()*Vec3(other.x(), other.y(), 1.0)); + // ALICEVISION_LOG_DEBUG("epiline\n" << epiLine << " dotprod " << (epiLine.dot(Vec3(p.x(), p.y(), 1.0)))); + std::vector pts; + if (lineToBorderPoints(epiLine, imageSize.first, imageSize.second, pts)) + { + // ALICEVISION_LOG_DEBUG("pt1*epiline " << (epiLine.transpose()*Vec3(pts[0](0), pts[0](1), 1))); + // ALICEVISION_LOG_DEBUG("pt1 " << pts[0]); + // ALICEVISION_LOG_DEBUG("pt2*epiline " << (epiLine.transpose()*Vec3(pts[1](0), pts[1](1), 1))); + // ALICEVISION_LOG_DEBUG("pt2 " << pts[1]); + if (count > 7) + svgStream.drawLine(pts[0](0), pts[0](1), pts[1](0), pts[1](1), svg::svgStyle().stroke("green", strokeWidth)); + else + svgStream.drawLine(pts[0](0), pts[0](1), pts[1](0), pts[1](1), svg::svgStyle().stroke("red", strokeWidth)); + } + else + { + ALICEVISION_LOG_DEBUG("********** pts size: " << pts.size() << " epiline " << epiLine << " out of image"); + if (pts.size() > 0) + { + svgStream.drawLine(pts[0](0), pts[0](1), p.x(), p.y(), svg::svgStyle().stroke("red", 10 * strokeWidth)); + ALICEVISION_LOG_DEBUG("********** pts: " << pts[0].transpose()); + } + } + ++count; + // if(count > 7) break; } - else - { - p = keypoints[m._j]; - other = otherKeypoints[m._i]; - } - if(count > 7) - svgStream.drawCircle(p.x(), p.y(), radius, svg::svgStyle().stroke("yellow", strokeWidth)); - else - svgStream.drawCircle(p.x(), p.y(), radius, svg::svgStyle().stroke("red", strokeWidth).fill("red")); - Vec3 epiLine; - if(left) - { - epiLine = Fmat.transpose() * Vec3(other.x(), other.y(), 1.0); - } + // draw the epipole + Mat epipole; + if (left) + epipole = Fmat.fullPivLu().kernel(); else - { - epiLine = Fmat * Vec3(other.x(), other.y(), 1.0); - } + epipole = Fmat.transpose().fullPivLu().kernel(); - // ALICEVISION_LOG_DEBUG("test 1 o*F*p " << (Fmat*Vec3(p.x(), p.y(), 1.0)).transpose()*Vec3(other.x(), other.y(), 1.0)); - // ALICEVISION_LOG_DEBUG("test 2 p*F*o " << (Fmat.transpose()*Vec3(p.x(), p.y(), 1.0)).transpose()*Vec3(other.x(), other.y(), 1.0)); - // ALICEVISION_LOG_DEBUG("epiline\n" << epiLine << " dotprod " << (epiLine.dot(Vec3(p.x(), p.y(), 1.0)))); - std::vector pts; - if(lineToBorderPoints(epiLine, imageSize.first, imageSize.second, pts)) + if (epipole.cols() > 1) { - // ALICEVISION_LOG_DEBUG("pt1*epiline " << (epiLine.transpose()*Vec3(pts[0](0), pts[0](1), 1))); - // ALICEVISION_LOG_DEBUG("pt1 " << pts[0]); - // ALICEVISION_LOG_DEBUG("pt2*epiline " << (epiLine.transpose()*Vec3(pts[1](0), pts[1](1), 1))); - // ALICEVISION_LOG_DEBUG("pt2 " << pts[1]); - if(count > 7) - svgStream.drawLine(pts[0](0), pts[0](1), pts[1](0), pts[1](1), svg::svgStyle().stroke("green", strokeWidth)); - else - svgStream.drawLine(pts[0](0), pts[0](1), pts[1](0), pts[1](1), svg::svgStyle().stroke("red", strokeWidth)); + ALICEVISION_LOG_WARNING("F has kernel of size " << epipole.cols() << ":\n" << epipole); } else { - ALICEVISION_LOG_DEBUG("********** pts size: " << pts.size() << " epiline " << epiLine << " out of image"); - if(pts.size() > 0) - { - svgStream.drawLine(pts[0](0), pts[0](1), p.x(), p.y(), svg::svgStyle().stroke("red", 10 * strokeWidth)); - ALICEVISION_LOG_DEBUG("********** pts: " << pts[0].transpose()); - } - } - ++count; - // if(count > 7) break; - - } - - //draw the epipole - Mat epipole; - if(left) - epipole = Fmat.fullPivLu().kernel(); - else - epipole = Fmat.transpose().fullPivLu().kernel(); - - if(epipole.cols() > 1) - { - ALICEVISION_LOG_WARNING("F has kernel of size " << epipole.cols() << ":\n" << epipole); - } - else - { - // normalize coordinates - Vec3 point = epipole.col(0); - ALICEVISION_LOG_DEBUG("epipole:\n" << point); - //@todo check 0 - point /= point(2); - ALICEVISION_LOG_DEBUG("epipole normalized:\n" << point); - // check if the point is inside the image - if(!((point(0) > 0 && point(0) < imageSize.first) && - (point(1) > 0 && point(1) < imageSize.second))) - { - ALICEVISION_LOG_DEBUG("epipole outside the image:\n" << point); - // point outside the image, clamp it to the borders - if(point(0) < 0) point(0) = 0; - if(point(0) > imageSize.first) point(0) = imageSize.first; - if(point(1) < 0) point(1) = 0; - if(point(1) > imageSize.second) point(0) = imageSize.second; - ALICEVISION_LOG_DEBUG("clamped epipole:\n" << point); + // normalize coordinates + Vec3 point = epipole.col(0); + ALICEVISION_LOG_DEBUG("epipole:\n" << point); + //@todo check 0 + point /= point(2); + ALICEVISION_LOG_DEBUG("epipole normalized:\n" << point); + // check if the point is inside the image + if (!((point(0) > 0 && point(0) < imageSize.first) && (point(1) > 0 && point(1) < imageSize.second))) + { + ALICEVISION_LOG_DEBUG("epipole outside the image:\n" << point); + // point outside the image, clamp it to the borders + if (point(0) < 0) + point(0) = 0; + if (point(0) > imageSize.first) + point(0) = imageSize.first; + if (point(1) < 0) + point(1) = 0; + if (point(1) > imageSize.second) + point(0) = imageSize.second; + ALICEVISION_LOG_DEBUG("clamped epipole:\n" << point); + } + svgStream.drawCircle(point(0), point(1), 3 * radius, svg::svgStyle().stroke("red", strokeWidth).fill("red")); } - svgStream.drawCircle(point(0), point(1), 3 * radius, svg::svgStyle().stroke("red", strokeWidth).fill("red")); - } - std::ofstream svgFile(outputSVGPath); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } -void saveMatchesAsMotion(const std::string &imagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::vector &otherKeypoints, - const matching::IndMatches &matches, - const std::string &outputSVGPath, +void saveMatchesAsMotion(const std::string& imagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::vector& otherKeypoints, + const matching::IndMatches& matches, + const std::string& outputSVGPath, bool left, bool richKeypoint) { - svg::svgDrawer svgStream(imageSize.first, imageSize.second); - svgStream.drawImage(imagePath, imageSize.first, imageSize.second); - - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); - for(size_t i = 0; i < matches.size(); ++i) - { - //Get back linked feature, draw a circle and link them by a line - const auto L = keypoints[matches[i]._i]; - const auto R = otherKeypoints[matches[i]._j]; - if(left) - { - svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), svg::svgStyle().stroke("green", strokeWidth)); - svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale()*radius : radius, svg::svgStyle().stroke("yellow", 2.0)); - svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale()*radius : radius, svg::svgStyle().stroke("red", strokeWidth)); - } - else - { - svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), svg::svgStyle().stroke("green", strokeWidth)); - svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale()*radius : radius, svg::svgStyle().stroke("yellow", 2.0)); - svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale()*radius : radius, svg::svgStyle().stroke("red", strokeWidth)); + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(imagePath, imageSize.first, imageSize.second); + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); + for (size_t i = 0; i < matches.size(); ++i) + { + // Get back linked feature, draw a circle and link them by a line + const auto L = keypoints[matches[i]._i]; + const auto R = otherKeypoints[matches[i]._j]; + if (left) + { + svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), svg::svgStyle().stroke("green", strokeWidth)); + svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale() * radius : radius, svg::svgStyle().stroke("yellow", 2.0)); + svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale() * radius : radius, svg::svgStyle().stroke("red", strokeWidth)); + } + else + { + svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), svg::svgStyle().stroke("green", strokeWidth)); + svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale() * radius : radius, svg::svgStyle().stroke("yellow", 2.0)); + svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale() * radius : radius, svg::svgStyle().stroke("red", strokeWidth)); + } } - } - std::ofstream svgFile(outputSVGPath); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } -void saveMatchesAsMotion(const std::string &imagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::vector &otherKeypoints, +void saveMatchesAsMotion(const std::string& imagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::vector& otherKeypoints, const std::vector>& homographiesAndMatches, - const std::string &outputSVGPath, + const std::string& outputSVGPath, bool left, bool richKeypoint) { - svg::svgDrawer svgStream(imageSize.first, imageSize.second); - svgStream.drawImage(imagePath, imageSize.first, imageSize.second); - - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(imagePath, imageSize.first, imageSize.second); - std::size_t count{0}; - const std::size_t numMaxColor{sixteenColors.size()}; + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); - for(const auto& h : homographiesAndMatches) - { - const auto& matches = h.second; - const auto strokeColor = sixteenColors[count++ % numMaxColor]; - const svg::svgStyle lineStyle = svg::svgStyle().stroke(strokeColor, strokeWidth); - const svg::svgStyle otherStyle = svg::svgStyle().stroke(strokeColor, 2.f); + std::size_t count{0}; + const std::size_t numMaxColor{sixteenColors.size()}; - for (const auto& match : matches) + for (const auto& h : homographiesAndMatches) + { + const auto& matches = h.second; + const auto strokeColor = sixteenColors[count++ % numMaxColor]; + const svg::svgStyle lineStyle = svg::svgStyle().stroke(strokeColor, strokeWidth); + const svg::svgStyle otherStyle = svg::svgStyle().stroke(strokeColor, 2.f); + + for (const auto& match : matches) + { + // Get back linked feature, draw a circle and link them by a line + const auto L = keypoints[match._i]; + const auto R = otherKeypoints[match._j]; + if (left) + { + svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), lineStyle); + svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale() * radius : radius, otherStyle); + svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale() * radius : radius, lineStyle); + } + else + { + svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), lineStyle); + svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale() * radius : radius, otherStyle); + svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale() * radius : radius, lineStyle); + } + } + } + std::ofstream svgFile(outputSVGPath); + if (!svgFile.is_open()) + { + ALICEVISION_CERR("Unable to open file " + outputSVGPath); + return; + } + svgFile << svgStream.closeSvgFile().str(); + if (!svgFile.good()) { - //Get back linked feature, draw a circle and link them by a line - const auto L = keypoints[match._i]; - const auto R = otherKeypoints[match._j]; - if(left) - { - svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), lineStyle); - svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale()*radius : radius, otherStyle); - svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale()*radius : radius, lineStyle); - } - else - { - svgStream.drawLine(L.x(), L.y(), R.x(), R.y(), lineStyle); - svgStream.drawCircle(R.x(), R.y(), (richKeypoint) ? R.scale()*radius : radius, otherStyle); - svgStream.drawCircle(L.x(), L.y(), (richKeypoint) ? L.scale()*radius : radius, lineStyle); - - } + ALICEVISION_CERR("Something wrong while writing file " + outputSVGPath); + return; } - } - std::ofstream svgFile(outputSVGPath); - if(!svgFile.is_open()) - { - ALICEVISION_CERR("Unable to open file " + outputSVGPath); - return; - } - svgFile << svgStream.closeSvgFile().str(); - if(!svgFile.good()) - { - ALICEVISION_CERR("Something wrong while writing file " + outputSVGPath); - return; - } - svgFile.close(); + svgFile.close(); } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) -void saveCCTag2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const feature::CCTAG_Regions &cctags, - const std::string &outputSVGPath) +void saveCCTag2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const feature::CCTAG_Regions& cctags, + const std::string& outputSVGPath) { - // set the text size to 5% if the image heigth - const float textSize = 0.05*imageSize.second; - - svg::svgDrawer svgStream( imageSize.first, imageSize.second); - svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); - - const float radius = getRadiusEstimate(imageSize); - const float strokeWidth = getStrokeEstimate(imageSize); - - const auto &feat = cctags.Features(); - const auto &desc = cctags.Descriptors(); - - for(std::size_t i = 0; i < desc.size(); ++i) - { - const IndexT cctagId = feature::getCCTagId(desc[i]); - if ( cctagId == UndefinedIndexT) + // set the text size to 5% if the image heigth + const float textSize = 0.05 * imageSize.second; + + svg::svgDrawer svgStream(imageSize.first, imageSize.second); + svgStream.drawImage(inputImagePath, imageSize.first, imageSize.second); + + const float radius = getRadiusEstimate(imageSize); + const float strokeWidth = getStrokeEstimate(imageSize); + + const auto& feat = cctags.Features(); + const auto& desc = cctags.Descriptors(); + + for (std::size_t i = 0; i < desc.size(); ++i) { - continue; + const IndexT cctagId = feature::getCCTagId(desc[i]); + if (cctagId == UndefinedIndexT) + { + continue; + } + const auto& kpt = feat[i]; + svgStream.drawCircle(kpt.x(), kpt.y(), radius, svg::svgStyle().stroke("yellow", strokeWidth)); + svgStream.drawText(kpt.x(), kpt.y(), textSize, std::to_string(cctagId), "yellow"); } - const auto &kpt = feat[i]; - svgStream.drawCircle(kpt.x(), kpt.y(), radius, svg::svgStyle().stroke("yellow", strokeWidth)); - svgStream.drawText(kpt.x(), kpt.y(), textSize, std::to_string(cctagId), "yellow"); - } - - std::ofstream svgFile( outputSVGPath ); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } -void saveCCTagMatches2SVG(const std::string &imagePathLeft, - const std::pair & imageSizeLeft, - const feature::CCTAG_Regions &cctagLeft, - const std::string &imagePathRight, - const std::pair & imageSizeRight, - const feature::CCTAG_Regions &cctagRight, - const matching::IndMatches &matches, - const std::string &outputSVGPath, - bool showNotMatched) +void saveCCTagMatches2SVG(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, + const feature::CCTAG_Regions& cctagLeft, + const std::string& imagePathRight, + const std::pair& imageSizeRight, + const feature::CCTAG_Regions& cctagRight, + const matching::IndMatches& matches, + const std::string& outputSVGPath, + bool showNotMatched) { - // set the text size to 5% if the image heigth - const float textSize = 0.05*std::min(imageSizeRight.second, imageSizeLeft.second); - - svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); - svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); - svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, imageSizeLeft.first); - - const auto &keypointsLeft = cctagLeft.Features(); - const auto &keypointsRight = cctagRight.Features(); - const auto &descLeft = cctagLeft.Descriptors(); - const auto &descRight = cctagRight.Descriptors(); - - //just to be sure... - assert(keypointsLeft.size() == descLeft.size()); - assert(keypointsRight.size() == descRight.size()); - - const float radiusLeft = getRadiusEstimate(imageSizeLeft); - const float radiusRight = getRadiusEstimate(imageSizeRight); - const float strokeLeft = getStrokeEstimate(imageSizeLeft); - const float strokeRight = getStrokeEstimate(imageSizeRight); - - for(const matching::IndMatch &m : matches) - { - //Get back linked feature, draw a circle and link them by a line - const feature::PointFeature & L = keypointsLeft[m._i]; - const feature::PointFeature & R = keypointsRight[m._j]; - const IndexT cctagIdLeft = feature::getCCTagId(descLeft[m._i]); - const IndexT cctagIdRight = feature::getCCTagId(descRight[m._j]); - if ( cctagIdLeft == UndefinedIndexT || cctagIdRight == UndefinedIndexT ) - { - ALICEVISION_LOG_WARNING("[svg]\tWarning! cctagIdLeft " << cctagIdLeft << " " << "cctagIdRight " << cctagIdRight); - continue; - } - - svgStream.drawLine(L.x(), L.y(), R.x() + imageSizeLeft.first, R.y(), svg::svgStyle().stroke("green", std::min(strokeLeft, strokeRight))); - svgStream.drawCircle(L.x(), L.y(), radiusLeft, svg::svgStyle().stroke("yellow", strokeLeft)); - svgStream.drawCircle(R.x() + imageSizeLeft.first, R.y(), radiusRight, svg::svgStyle().stroke("yellow", std::min(strokeLeft, strokeRight))); - - svgStream.drawText(L.x(), L.y(), textSize, std::to_string(cctagIdLeft), "yellow"); - svgStream.drawText(R.x() + imageSizeLeft.first, R.y(), textSize, std::to_string(cctagIdRight), "yellow"); - } - - if(showNotMatched) - { - const float textSizeSmaller = 0.75*textSize; - - // for the left side - for(std::size_t i = 0; i < keypointsLeft.size(); ++i) + // set the text size to 5% if the image heigth + const float textSize = 0.05 * std::min(imageSizeRight.second, imageSizeLeft.second); + + svg::svgDrawer svgStream(imageSizeLeft.first + imageSizeRight.first, std::max(imageSizeLeft.second, imageSizeRight.second)); + svgStream.drawImage(imagePathLeft, imageSizeLeft.first, imageSizeLeft.second); + svgStream.drawImage(imagePathRight, imageSizeRight.first, imageSizeRight.second, imageSizeLeft.first); + + const auto& keypointsLeft = cctagLeft.Features(); + const auto& keypointsRight = cctagRight.Features(); + const auto& descLeft = cctagLeft.Descriptors(); + const auto& descRight = cctagRight.Descriptors(); + + // just to be sure... + assert(keypointsLeft.size() == descLeft.size()); + assert(keypointsRight.size() == descRight.size()); + + const float radiusLeft = getRadiusEstimate(imageSizeLeft); + const float radiusRight = getRadiusEstimate(imageSizeRight); + const float strokeLeft = getStrokeEstimate(imageSizeLeft); + const float strokeRight = getStrokeEstimate(imageSizeRight); + + for (const matching::IndMatch& m : matches) { - // look if the index is not already in matches - bool found = false; - for(const matching::IndMatch &m : matches) - { - found = m._i == i; - if(found) - break; - } - if(found) - continue; - - assert(i < descLeft.size()); - // find the cctag id - const IndexT cctagIdLeft = feature::getCCTagId(descLeft[i]); - if(cctagIdLeft == UndefinedIndexT) - continue; - - // draw the center - const feature::PointFeature & L = keypointsLeft[i]; - svgStream.drawCircle(L.x(), L.y(), radiusLeft, svg::svgStyle().stroke("red", strokeLeft)); - // print the id - svgStream.drawText(L.x(), L.y(), textSizeSmaller, std::to_string(cctagIdLeft), "red"); + // Get back linked feature, draw a circle and link them by a line + const feature::PointFeature& L = keypointsLeft[m._i]; + const feature::PointFeature& R = keypointsRight[m._j]; + const IndexT cctagIdLeft = feature::getCCTagId(descLeft[m._i]); + const IndexT cctagIdRight = feature::getCCTagId(descRight[m._j]); + if (cctagIdLeft == UndefinedIndexT || cctagIdRight == UndefinedIndexT) + { + ALICEVISION_LOG_WARNING("[svg]\tWarning! cctagIdLeft " << cctagIdLeft << " " + << "cctagIdRight " << cctagIdRight); + continue; + } + + svgStream.drawLine(L.x(), L.y(), R.x() + imageSizeLeft.first, R.y(), svg::svgStyle().stroke("green", std::min(strokeLeft, strokeRight))); + svgStream.drawCircle(L.x(), L.y(), radiusLeft, svg::svgStyle().stroke("yellow", strokeLeft)); + svgStream.drawCircle(R.x() + imageSizeLeft.first, R.y(), radiusRight, svg::svgStyle().stroke("yellow", std::min(strokeLeft, strokeRight))); + + svgStream.drawText(L.x(), L.y(), textSize, std::to_string(cctagIdLeft), "yellow"); + svgStream.drawText(R.x() + imageSizeLeft.first, R.y(), textSize, std::to_string(cctagIdRight), "yellow"); } - - // for the right side - for(std::size_t i = 0; i < keypointsRight.size(); ++i) - { - // look if the index is not already in matches - bool found = false; - for(const matching::IndMatch &m : matches) - { - found = m._j == i; - if(found) - break; - } - if(found) - continue; - - assert(i < descRight.size()); - // find the cctag id - const IndexT cctagIdRight = feature::getCCTagId(descRight[i]); - if(cctagIdRight == UndefinedIndexT) - continue; - - // draw the center - const feature::PointFeature & R = keypointsRight[i]; - svgStream.drawCircle(R.x() + imageSizeLeft.first, R.y(), radiusRight, svg::svgStyle().stroke("red", strokeRight)); - // print the id - svgStream.drawText(R.x() + imageSizeLeft.first, R.y(), textSizeSmaller, std::to_string(cctagIdRight), "red"); + if (showNotMatched) + { + const float textSizeSmaller = 0.75 * textSize; + + // for the left side + for (std::size_t i = 0; i < keypointsLeft.size(); ++i) + { + // look if the index is not already in matches + bool found = false; + for (const matching::IndMatch& m : matches) + { + found = m._i == i; + if (found) + break; + } + if (found) + continue; + + assert(i < descLeft.size()); + // find the cctag id + const IndexT cctagIdLeft = feature::getCCTagId(descLeft[i]); + if (cctagIdLeft == UndefinedIndexT) + continue; + + // draw the center + const feature::PointFeature& L = keypointsLeft[i]; + svgStream.drawCircle(L.x(), L.y(), radiusLeft, svg::svgStyle().stroke("red", strokeLeft)); + // print the id + svgStream.drawText(L.x(), L.y(), textSizeSmaller, std::to_string(cctagIdLeft), "red"); + } + + // for the right side + for (std::size_t i = 0; i < keypointsRight.size(); ++i) + { + // look if the index is not already in matches + bool found = false; + for (const matching::IndMatch& m : matches) + { + found = m._j == i; + if (found) + break; + } + if (found) + continue; + + assert(i < descRight.size()); + // find the cctag id + const IndexT cctagIdRight = feature::getCCTagId(descRight[i]); + if (cctagIdRight == UndefinedIndexT) + continue; + + // draw the center + const feature::PointFeature& R = keypointsRight[i]; + svgStream.drawCircle(R.x() + imageSizeLeft.first, R.y(), radiusRight, svg::svgStyle().stroke("red", strokeRight)); + // print the id + svgStream.drawText(R.x() + imageSizeLeft.first, R.y(), textSizeSmaller, std::to_string(cctagIdRight), "red"); + } } - } - std::ofstream svgFile(outputSVGPath); - svgFile << svgStream.closeSvgFile().str(); - svgFile.close(); + std::ofstream svgFile(outputSVGPath); + svgFile << svgStream.closeSvgFile().str(); + svgFile.close(); } #endif -} // namespace feature -} // namespace aliceVision - +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matching/svgVisualization.hpp b/src/aliceVision/matching/svgVisualization.hpp index 38fc884ba0..e510f82225 100644 --- a/src/aliceVision/matching/svgVisualization.hpp +++ b/src/aliceVision/matching/svgVisualization.hpp @@ -10,7 +10,7 @@ #include #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) -#include + #include #endif #include #include @@ -28,10 +28,22 @@ namespace matching { in HTML and now in Cascading Style Sheets. https://www.w3schools.com/colors/colors_names.asp */ -static const std::array sixteenColors {"aqua","blue","fuchsia","yellow", - "green","red","purple","lime", - "teal","maroon","navy","olive", - "gray","silver","white","black"}; +static const std::array sixteenColors{"aqua", + "blue", + "fuchsia", + "yellow", + "green", + "red", + "purple", + "lime", + "teal", + "maroon", + "navy", + "olive", + "gray", + "silver", + "white", + "black"}; std::string describerTypeColor(feature::EImageDescriberType descType); @@ -53,13 +65,13 @@ std::string describerTypeColor(feature::EImageDescriberType descType); * @param[in] outputSVGPath The name of the svg file to generate. */ void drawMatchesSideBySide(const std::string& imagePathLeft, - const std::pair& imageSizeLeft, + const std::pair& imageSizeLeft, const std::vector& keypointsLeft, const std::string& imagePathRight, - const std::pair& imageSizeRight, + const std::pair& imageSizeRight, const std::vector& keypointsRight, const matching::IndMatches& matches, - const std::string &outputSVGPath); + const std::string& outputSVGPath); /** * @brief It saves a svg file containing two images (as linked images) and their @@ -80,10 +92,10 @@ void drawMatchesSideBySide(const std::string& imagePathLeft, * @param[in] outFilename The name of the svg file to generate. */ void drawHomographyMatches(const std::string& imagePathLeft, - const std::pair& imageSizeLeft, + const std::pair& imageSizeLeft, const std::vector& siofeatures_I, const std::string& imagePathRight, - const std::pair& imageSizeRight, + const std::pair& imageSizeRight, const std::vector& siofeatures_J, const std::vector>& homographiesAndMatches, const matching::IndMatches& putativeMatches, @@ -92,9 +104,9 @@ void drawHomographyMatches(const std::string& imagePathLeft, /** * @brief It saves a svg file containing two images (as linked images) and their * feature matches: the two images are showed side by side and each feature in each - * image (depicted as a circle) is connected to the corresponding feature on the + * image (depicted as a circle) is connected to the corresponding feature on the * other image through a line. - * + * * @param[in] imagePathLeft The full path to the left image. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSizeLeft The size of the image . @@ -106,14 +118,14 @@ void drawHomographyMatches(const std::string& imagePathLeft, * @param[in] matches The vector containing the indices of matching features for each descriptor type. * @param[in] outputSVGPath The name of the svg file to generate. */ -void saveMatches2SVG(const std::string &imagePathLeft, - const std::pair & imageSizeLeft, - const feature::MapRegionsPerDesc &keypointsLeft, - const std::string &imagePathRight, - const std::pair & imageSizeRight, - const feature::MapRegionsPerDesc &keypointsRight, - const matching::MatchesPerDescType & matches, - const std::string &outputSVGPath); +void saveMatches2SVG(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, + const feature::MapRegionsPerDesc& keypointsLeft, + const std::string& imagePathRight, + const std::pair& imageSizeRight, + const feature::MapRegionsPerDesc& keypointsRight, + const matching::MatchesPerDescType& matches, + const std::string& outputSVGPath); /** * @brief It saves a svg file containing two images (as linked images) and their @@ -132,14 +144,14 @@ void saveMatches2SVG(const std::string &imagePathLeft, * @param[in] matches The vector containing the indices of matching features for each descriptor type. * @param[in] outputSVGPath The name of the svg file to generate. */ -void saveMatches2SVG(const std::string &imagePathLeft, - const std::pair & imageSizeLeft, - const feature::MapRegionsPerDesc &keypointsLeft, - const std::string &imagePathRight, - const std::pair & imageSizeRight, - const feature::MapRegionsPerDesc &keypointsRight, - const matching::MatchesPerDescType & matches, - const std::string &outputSVGPath); +void saveMatches2SVG(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, + const feature::MapRegionsPerDesc& keypointsLeft, + const std::string& imagePathRight, + const std::pair& imageSizeRight, + const feature::MapRegionsPerDesc& keypointsRight, + const matching::MatchesPerDescType& matches, + const std::string& outputSVGPath); /** * @brief It saves a svg file containing an image (as linked image) and its detected @@ -156,97 +168,97 @@ void saveMatches2SVG(const std::string &imagePathLeft, * @param[in] richKeypoint Draw rich keypoints with a circle proportional to the * octave in which the point has been detected. */ -void drawKeypointsSideBySide(const std::string&imagePathLeft, - const std::pair& imageSizeLeft, +void drawKeypointsSideBySide(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, const std::vector& keypointsLeft, - const std::string &imagePathRight, - const std::pair& imageSizeRight, + const std::string& imagePathRight, + const std::pair& imageSizeRight, const std::vector& keypointsRight, - const std::string &outputSVGPath, + const std::string& outputSVGPath, bool richKeypoint = true); /** * @brief It saves a svg file containing an image (as linked image) and its detected * features. - * - * @param[in] inputImagePath The full path to the image file. The image is only + * + * @param[in] inputImagePath The full path to the image file. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSize The size of the image . * @param[in] keypoints The keypoints of the right image. * @param[in] outputSVGPath The name of the svg file to generate. - * @param[in] richKeypoint Draw rich keypoints with a circle proportional to the - * octave in which the point has been detected. + * @param[in] richKeypoint Draw rich keypoints with a circle proportional to the + * octave in which the point has been detected. */ -void saveKeypoints2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::string &outputSVGPath, +void saveKeypoints2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::string& outputSVGPath, bool richKeypoint = true); /** * @brief It saves a svg file containing an image (as linked image) and its detected * features. - * - * @param[in] inputImagePath The full path to the image file. The image is only + * + * @param[in] inputImagePath The full path to the image file. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSize The size of the image . * @param[in] keypoints The points of the right image. * @param[in] outputSVGPath The name of the svg file to generate. **/ -void saveFeatures2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const feature::MapFeaturesPerDesc & keypoints, - const std::string & outputSVGPath); +void saveFeatures2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const feature::MapFeaturesPerDesc& keypoints, + const std::string& outputSVGPath); /** * @brief It saves a svg file containing an image (as linked image) and its detected * features. - * - * @param[in] inputImagePath The full path to the image file. The image is only + * + * @param[in] inputImagePath The full path to the image file. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSize The size of the image . * @param[in] points A vector containing the points to draw. * @param[in] outputSVGPath The name of the svg file to generate. * @param[in] inliers [optional] The indices of the features to draw. */ -void saveFeatures2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const Mat &points, - const std::string &outputSVGPath, - const std::vector *inliers = nullptr); +void saveFeatures2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const Mat& points, + const std::string& outputSVGPath, + const std::vector* inliers = nullptr); /** - * @brief It saves an image as a svg file (linked image) with an overlay of the + * @brief It saves an image as a svg file (linked image) with an overlay of the * epipolar geometry. For each feature of the image it displays the features itself * as a circle and the corresponding epipolar line. The epipolar line is computed * by using the provided F and match of a second image. - * + * * @param[in] imagePath The full path to the image file to display. * @param[in] imageSize The size of the image . * @param[in] keypoints The list of keypoints associated to the image. - * @param[in] otherKeypoints The list of keypoints associated to the other image, + * @param[in] otherKeypoints The list of keypoints associated to the other image, * they are used to draw the epipolar lines. - * @param[in] matches The correspondences between the keypoints of the current + * @param[in] matches The correspondences between the keypoints of the current * image and the other image. - * @param[in] Fmat The fundamental matrix between the current image and the other + * @param[in] Fmat The fundamental matrix between the current image and the other * image, in particular it is assumed the following right' * F * left * @param[in] outputSVGPath The filename for the svg file to generate * @param[in] left If true it will consider the current image as the left image */ -void saveEpipolarGeometry2SVG(const std::string &imagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::vector &otherKeypoints, - const matching::IndMatches &matches, - const Mat3 &Fmat, - const std::string &outputSVGPath, +void saveEpipolarGeometry2SVG(const std::string& imagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::vector& otherKeypoints, + const matching::IndMatches& matches, + const Mat3& Fmat, + const std::string& outputSVGPath, bool left); /** * @brief It saves a svg file containing an image (as linked image) and its - * feature matches: the matches are depicted as trails, ie both feature and its - * corresponding match are drawn on the same image and connected by a line. The + * feature matches: the matches are depicted as trails, ie both feature and its + * corresponding match are drawn on the same image and connected by a line. The * keypoint of the current image is drawn in yellow, the other in red. - * + * * @param[in] imagePath The full path to the image file to display. * @param[in] imageSize The size of the image . * @param[in] keypoints The list of keypoints associated to the image. @@ -254,29 +266,29 @@ void saveEpipolarGeometry2SVG(const std::string &imagePath, * @param[in] matches The correspondences between the keypoints of the current image and the other image. * @param[in] outputSVGPath The filename for the svg file to generate. * @param[in] left If true it will consider the current image as the left image. - * @param[in] richKeypoint Draw rich keypoints with a circle proportional to the + * @param[in] richKeypoint Draw rich keypoints with a circle proportional to the * octave in which the point has been detected. */ -void saveMatchesAsMotion(const std::string &imagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::vector &otherKeypoints, - const matching::IndMatches &matches, - const std::string &outputSVGPath, +void saveMatchesAsMotion(const std::string& imagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::vector& otherKeypoints, + const matching::IndMatches& matches, + const std::string& outputSVGPath, bool left, bool richKeypoint = true); -void saveMatchesAsMotion(const std::string &imagePath, - const std::pair & imageSize, - const std::vector &keypoints, - const std::vector &otherKeypoints, +void saveMatchesAsMotion(const std::string& imagePath, + const std::pair& imageSize, + const std::vector& keypoints, + const std::vector& otherKeypoints, const std::vector>& homographiesAndMatches, - const std::string &outputSVGPath, + const std::string& outputSVGPath, bool left, bool richKeypoint); /** - * @brief Given a 2d line and an image size it returns the intersection points + * @brief Given a 2d line and an image size it returns the intersection points * of the line with the image borders. It returns false if there is no intersection * points * @param[in] epiLine The 2D line parameters, [a, b, c] for ax+by+c=0. @@ -285,10 +297,7 @@ void saveMatchesAsMotion(const std::string &imagePath, * @param[out] intersectionPts A list of intersection points * @return False if there is no intersection points. */ -bool lineToBorderPoints(const Vec3 &epiLine, - const std::size_t imgW, - const std::size_t imgH, - std::vector &intersectionPts); +bool lineToBorderPoints(const Vec3& epiLine, const std::size_t imgW, const std::size_t imgH, std::vector& intersectionPts); #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) @@ -296,30 +305,30 @@ bool lineToBorderPoints(const Vec3 &epiLine, * @brief It generates a svg file containing the image and its extracted cctags. * The center of the cctag is marked with a small circle and the id of the cctag * is rendered as text close to the center. - * - * @param[in] inputImagePath The full path to the image file. The image is only + * + * @param[in] inputImagePath The full path to the image file. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSize The size of the image . * @param[in] cctags The CCtag regions (keypoints+descriptors). * @param[in] outputSVGPath The name of the svg file to generate. */ -void saveCCTag2SVG(const std::string &inputImagePath, - const std::pair & imageSize, - const feature::CCTAG_Regions &cctags, - const std::string &outputSVGPath); +void saveCCTag2SVG(const std::string& inputImagePath, + const std::pair& imageSize, + const feature::CCTAG_Regions& cctags, + const std::string& outputSVGPath); /** - * @brief It generates a svg file containing the two images, its extracted cctags, + * @brief It generates a svg file containing the two images, its extracted cctags, * and their correspondences. * Each correspondence is drawn as a line connecting the two centers drawn as a small circle. * The ids of the cctags are rendered as text close to their center. * If \p showNotMatched is enable also the non matching cctags are drawn. - * - * @param[in] imagePathLeft The full path to the left iamge. The image is only + * + * @param[in] imagePathLeft The full path to the left iamge. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSizeLeft The size of the image . * @param[in] cctagLeft The CCtag regions (keypoints+descriptors). - * @param[in] imagePathRight The full path to the left iamge. The image is only + * @param[in] imagePathRight The full path to the left iamge. The image is only * saved as a link, no image data is stored in the svg. * @param[in] imageSizeRight The size of the image . * @param[in] cctagRight The CCtag regions (keypoints+descriptors). @@ -327,17 +336,16 @@ void saveCCTag2SVG(const std::string &inputImagePath, * @param[in] outputSVGPath The name of the svg file to generate. * @param[in] showNotMatched If enabled, even the non matched cctags are drawn. */ -void saveCCTagMatches2SVG(const std::string &imagePathLeft, - const std::pair & imageSizeLeft, - const feature::CCTAG_Regions &cctagLeft, - const std::string &imagePathRight, - const std::pair & imageSizeRight, - const feature::CCTAG_Regions &cctagRight, - const matching::IndMatches &matches, - const std::string &outputSVGPath, - bool showNotMatched); +void saveCCTagMatches2SVG(const std::string& imagePathLeft, + const std::pair& imageSizeLeft, + const feature::CCTAG_Regions& cctagLeft, + const std::string& imagePathRight, + const std::pair& imageSizeRight, + const feature::CCTAG_Regions& cctagRight, + const matching::IndMatches& matches, + const std::string& outputSVGPath, + bool showNotMatched); #endif -} // namespace feature -} // namespace aliceVision - +} // namespace matching +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilter.cpp b/src/aliceVision/matchingImageCollection/GeometricFilter.cpp index ebefa6ae17..1bb6382b29 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilter.cpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilter.cpp @@ -20,8 +20,9 @@ void removePoorlyOverlappingImagePairs(PairwiseMatches& geometricMatches, const size_t photometricCount = putativeMatches.find(match.first)->second.getNbAllMatches(); const size_t geometricCount = match.second.getNbAllMatches(); const float ratio = geometricCount / (float)photometricCount; - if (geometricCount < minimumGeometricCount || ratio < minimumRatio) { - toRemoveVec.push_back(match.first); // the image pair will be removed + if (geometricCount < minimumGeometricCount || ratio < minimumRatio) + { + toRemoveVec.push_back(match.first); // the image pair will be removed } } @@ -32,5 +33,5 @@ void removePoorlyOverlappingImagePairs(PairwiseMatches& geometricMatches, } } -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilter.hpp b/src/aliceVision/matchingImageCollection/GeometricFilter.hpp index 855940207c..57d9d021bd 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilter.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilter.hpp @@ -38,57 +38,53 @@ using namespace aliceVision::matching; * @param[in] randomNumberGenerator */ template -void robustModelEstimation( - PairwiseMatches& out_geometricMatches, - const sfmData::SfMData* sfmData, - const feature::RegionsPerView& regionsPerView, - const GeometryFunctor& functor, - const PairwiseMatches& putativeMatches, - std::mt19937 & randomNumberGenerator, - const bool guidedMatching = false, - const double distanceRatio = 0.6 - ) +void robustModelEstimation(PairwiseMatches& out_geometricMatches, + const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const GeometryFunctor& functor, + const PairwiseMatches& putativeMatches, + std::mt19937& randomNumberGenerator, + const bool guidedMatching = false, + const double distanceRatio = 0.6) { - out_geometricMatches.clear(); + out_geometricMatches.clear(); + + auto progressDisplay = system::createConsoleProgressDisplay(putativeMatches.size(), std::cout, "Robust Model Estimation\n"); - auto progressDisplay = - system::createConsoleProgressDisplay(putativeMatches.size(), std::cout, - "Robust Model Estimation\n"); - #pragma omp parallel for schedule(dynamic) - for (int i = 0; i < (int)putativeMatches.size(); ++i) - { - PairwiseMatches::const_iterator iter = putativeMatches.begin(); - std::advance(iter, i); + for (int i = 0; i < (int)putativeMatches.size(); ++i) + { + PairwiseMatches::const_iterator iter = putativeMatches.begin(); + std::advance(iter, i); - const Pair currentPair = iter->first; - const MatchesPerDescType& putativeMatchesPerType = iter->second; - const Pair& imagePair = iter->first; + const Pair currentPair = iter->first; + const MatchesPerDescType& putativeMatchesPerType = iter->second; + const Pair& imagePair = iter->first; - // apply the geometric filter (robust model estimation) - { - MatchesPerDescType inliers; - GeometryFunctor geometricFilter = functor; // use a copy since we are in a multi-thread context - const EstimationStatus state = geometricFilter.geometricEstimation(sfmData, regionsPerView, imagePair, putativeMatchesPerType, randomNumberGenerator, inliers); - if(state.hasStrongSupport) - { - if(guidedMatching) + // apply the geometric filter (robust model estimation) { - MatchesPerDescType guidedGeometricInliers; - geometricFilter.Geometry_guided_matching(sfmData, regionsPerView, imagePair, distanceRatio, guidedGeometricInliers); - //ALICEVISION_LOG_DEBUG("#before/#after: " << putative_inliers.size() << "/" << guided_geometric_inliers.size()); - std::swap(inliers, guidedGeometricInliers); - } + MatchesPerDescType inliers; + GeometryFunctor geometricFilter = functor; // use a copy since we are in a multi-thread context + const EstimationStatus state = + geometricFilter.geometricEstimation(sfmData, regionsPerView, imagePair, putativeMatchesPerType, randomNumberGenerator, inliers); + if (state.hasStrongSupport) + { + if (guidedMatching) + { + MatchesPerDescType guidedGeometricInliers; + geometricFilter.Geometry_guided_matching(sfmData, regionsPerView, imagePair, distanceRatio, guidedGeometricInliers); + // ALICEVISION_LOG_DEBUG("#before/#after: " << putative_inliers.size() << "/" << guided_geometric_inliers.size()); + std::swap(inliers, guidedGeometricInliers); + } #pragma omp critical - { - out_geometricMatches.emplace(currentPair, std::move(inliers)); + { + out_geometricMatches.emplace(currentPair, std::move(inliers)); + } + } } - - } + ++progressDisplay; } - ++progressDisplay; - } } /** @@ -104,7 +100,5 @@ void removePoorlyOverlappingImagePairs(PairwiseMatches& geometricMatches, float minimumRatio, std::size_t minimumGeometricCount); -} // namespace matchingImageCollection -} // namespace aliceVision - - +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix.hpp b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix.hpp index b202605859..f92bab3ce5 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix.hpp @@ -9,7 +9,6 @@ namespace aliceVision { - namespace feature { class RegionsPerView; } @@ -22,44 +21,35 @@ namespace sfmData { class SfMData; } - namespace matchingImageCollection { - struct GeometricFilterMatrix { - GeometricFilterMatrix(double precision, - double precisionRobust, - std::size_t stIteration) - : m_dPrecision(precision) - , m_dPrecision_robust(precisionRobust) - , m_stIteration(stIteration) - {} - - /** - * @brief Geometry_guided_matching - * @param sfm_data - * @param regionsPerView - * @param pairIndex - * @param dDistanceRatio - * @param matches - * @return - */ - virtual bool Geometry_guided_matching - ( - const sfmData::SfMData * sfmData, - const feature::RegionsPerView& regionsPerView, - const Pair imageIdsPair, - const double dDistanceRatio, - matching::MatchesPerDescType & matches - ) = 0; - - - double m_dPrecision; //upper_bound precision used for robust estimation - double m_dPrecision_robust; - std::size_t m_stIteration; //maximal number of iteration for robust estimation + GeometricFilterMatrix(double precision, double precisionRobust, std::size_t stIteration) + : m_dPrecision(precision), + m_dPrecision_robust(precisionRobust), + m_stIteration(stIteration) + {} + + /** + * @brief Geometry_guided_matching + * @param sfm_data + * @param regionsPerView + * @param pairIndex + * @param dDistanceRatio + * @param matches + * @return + */ + virtual bool Geometry_guided_matching(const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const Pair imageIdsPair, + const double dDistanceRatio, + matching::MatchesPerDescType& matches) = 0; + + double m_dPrecision; // upper_bound precision used for robust estimation + double m_dPrecision_robust; + std::size_t m_stIteration; // maximal number of iteration for robust estimation }; - -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_E_AC.hpp b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_E_AC.hpp index 8621fb8b5a..d221537c1d 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_E_AC.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_E_AC.hpp @@ -30,152 +30,153 @@ namespace matchingImageCollection { */ struct GeometricFilterMatrix_E_AC : public GeometricFilterMatrix { - GeometricFilterMatrix_E_AC(double dPrecision = std::numeric_limits::infinity(), - std::size_t iteration = 1024) - : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration) - , m_E(Mat3::Identity()) - {} - - /** - * @brief Given two sets of image points, it estimates the essential matrix - * relating them using a robust method (like A Contrario Ransac). - */ - template - EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, - const Regions_or_Features_ProviderT& regionsPerView, - const Pair& pairIndex, - const matching::MatchesPerDescType& putativeMatchesPerType, - std::mt19937 & randomNumberGenerator, - matching::MatchesPerDescType& out_geometricInliersPerType) - { - out_geometricInliersPerType.clear(); - - // get back corresponding view index - const IndexT I = pairIndex.first; - const IndexT J = pairIndex.second; - - const std::vector descTypes = regionsPerView.getCommonDescTypes(pairIndex); - - if(descTypes.empty()) - return EstimationStatus(false, false); - - // reject pair with missing Intrinsic information - const sfmData::View& viewI = sfmData->getView(I); - const sfmData::View& viewJ = sfmData->getView(J); - - // Check that valid cameras can be retrieved for the pair of views - std::shared_ptr cam_I = sfmData->getIntrinsicsharedPtr(viewI.getIntrinsicId()); - std::shared_ptr cam_J = sfmData->getIntrinsicsharedPtr(viewJ.getIntrinsicId()); - - if(!cam_I || !cam_J) - return EstimationStatus(false, false); - - std::shared_ptr castedCam_I = std::dynamic_pointer_cast(cam_I); - std::shared_ptr castedCam_J = std::dynamic_pointer_cast(cam_J); - if (castedCam_I == nullptr || castedCam_J == nullptr) + GeometricFilterMatrix_E_AC(double dPrecision = std::numeric_limits::infinity(), std::size_t iteration = 1024) + : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration), + m_E(Mat3::Identity()) + {} + + /** + * @brief Given two sets of image points, it estimates the essential matrix + * relating them using a robust method (like A Contrario Ransac). + */ + template + EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, + const Regions_or_Features_ProviderT& regionsPerView, + const Pair& pairIndex, + const matching::MatchesPerDescType& putativeMatchesPerType, + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_geometricInliersPerType) { - return EstimationStatus(false, false); + out_geometricInliersPerType.clear(); + + // get back corresponding view index + const IndexT I = pairIndex.first; + const IndexT J = pairIndex.second; + + const std::vector descTypes = regionsPerView.getCommonDescTypes(pairIndex); + + if (descTypes.empty()) + return EstimationStatus(false, false); + + // reject pair with missing Intrinsic information + const sfmData::View& viewI = sfmData->getView(I); + const sfmData::View& viewJ = sfmData->getView(J); + + // Check that valid cameras can be retrieved for the pair of views + std::shared_ptr cam_I = sfmData->getIntrinsicsharedPtr(viewI.getIntrinsicId()); + std::shared_ptr cam_J = sfmData->getIntrinsicsharedPtr(viewJ.getIntrinsicId()); + + if (!cam_I || !cam_J) + return EstimationStatus(false, false); + + std::shared_ptr castedCam_I = std::dynamic_pointer_cast(cam_I); + std::shared_ptr castedCam_J = std::dynamic_pointer_cast(cam_J); + if (castedCam_I == nullptr || castedCam_J == nullptr) + { + return EstimationStatus(false, false); + } + + // get corresponding point regions arrays + Mat xI, xJ; + fillMatricesWithUndistortFeaturesMatches(pairIndex, putativeMatchesPerType, sfmData, regionsPerView, descTypes, xI, xJ); + + // define the AContrario adapted Essential matrix solver + using KernelT = multiview::RelativePoseKernel_K; + + KernelT kernel(xI, + viewI.getImage().getWidth(), + viewI.getImage().getHeight(), + xJ, + viewJ.getImage().getWidth(), + viewJ.getImage().getHeight(), + castedCam_I->K(), + castedCam_J->K()); + + // robustly estimate the Essential matrix with A Contrario ransac + const double upperBoundPrecision = Square(m_dPrecision); + + std::vector inliers; + robustEstimation::Mat3Model model; + const std::pair ACRansacOut = + robustEstimation::ACRANSAC(kernel, randomNumberGenerator, inliers, m_stIteration, &model, upperBoundPrecision); + m_E = model.getMatrix(); + + if (inliers.empty()) + return EstimationStatus(false, false); + + m_dPrecision_robust = ACRansacOut.first; + + // fill geometricInliersPerType with inliers from putativeMatchesPerType + copyInlierMatches(inliers, putativeMatchesPerType, descTypes, out_geometricInliersPerType); + + // check if resection has strong support + const bool hasStrongSupport = matching::hasStrongSupport(out_geometricInliersPerType, kernel.getMinimumNbRequiredSamples()); + + return EstimationStatus(true, hasStrongSupport); } - // get corresponding point regions arrays - Mat xI,xJ; - fillMatricesWithUndistortFeaturesMatches(pairIndex, putativeMatchesPerType, sfmData, regionsPerView, descTypes, xI, xJ); - - // define the AContrario adapted Essential matrix solver - using KernelT = multiview::RelativePoseKernel_K< - multiview::relativePose::Essential5PSolver, - multiview::relativePose::FundamentalEpipolarDistanceError, - robustEstimation::Mat3Model>; - - KernelT kernel( - xI, viewI.getImage().getWidth(), viewI.getImage().getHeight(), - xJ, viewJ.getImage().getWidth(), viewJ.getImage().getHeight(), - castedCam_I->K(), castedCam_J->K()); - - // robustly estimate the Essential matrix with A Contrario ransac - const double upperBoundPrecision = Square(m_dPrecision); - - std::vector inliers; - robustEstimation::Mat3Model model; - const std::pair ACRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, inliers, m_stIteration, &model, upperBoundPrecision); - m_E = model.getMatrix(); - - if (inliers.empty()) - return EstimationStatus(false, false); - - m_dPrecision_robust = ACRansacOut.first; - - // fill geometricInliersPerType with inliers from putativeMatchesPerType - copyInlierMatches(inliers, - putativeMatchesPerType, - descTypes, - out_geometricInliersPerType); - - // check if resection has strong support - const bool hasStrongSupport = matching::hasStrongSupport(out_geometricInliersPerType, kernel.getMinimumNbRequiredSamples()); - - return EstimationStatus(true, hasStrongSupport); - } - - /** - * @brief Geometry_guided_matching - * @param sfmData - * @param regionsPerView - * @param imageIdsPair - * @param dDistanceRatio - * @param matches - * @return - */ - bool Geometry_guided_matching(const sfmData::SfMData* sfmData, - const feature::RegionsPerView& regionsPerView, - const Pair imageIdsPair, - const double dDistanceRatio, - matching::MatchesPerDescType& matches) override - { - if(!std::isinf(m_dPrecision_robust)) + /** + * @brief Geometry_guided_matching + * @param sfmData + * @param regionsPerView + * @param imageIdsPair + * @param dDistanceRatio + * @param matches + * @return + */ + bool Geometry_guided_matching(const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const Pair imageIdsPair, + const double dDistanceRatio, + matching::MatchesPerDescType& matches) override { - // get back corresponding view index - const IndexT I = imageIdsPair.first; - const IndexT J = imageIdsPair.second; - - const sfmData::View& viewI = sfmData->getView(I); - const sfmData::View& viewJ = sfmData->getView(J); - - // check that valid cameras can be retrieved for the pair of views - const camera::IntrinsicBase* camI = sfmData->getIntrinsics().count(viewI.getIntrinsicId()) ? - sfmData->getIntrinsics().at(viewI.getIntrinsicId()).get() : nullptr; - const camera::IntrinsicBase* camJ = sfmData->getIntrinsics().count(viewJ.getIntrinsicId()) ? - sfmData->getIntrinsics().at(viewJ.getIntrinsicId()).get() : nullptr; - if(!camI || !camJ) - return false; - - if(!isPinhole(camI->getType()) || !isPinhole(camJ->getType())) - return false; - - const camera::Pinhole* ptrPinholeI = (const camera::Pinhole*)(camI); - const camera::Pinhole* ptrPinholeJ = (const camera::Pinhole*)(camJ); - - Mat3 F; - fundamentalFromEssential(m_E, ptrPinholeI->K(), ptrPinholeJ->K(), &F); - - robustEstimation::Mat3Model model(F); - // multiview::relativePose::FundamentalSymmetricEpipolarDistanceError - matching::guidedMatching( - model, - camI, regionsPerView.getAllRegions(I), - camJ, regionsPerView.getAllRegions(J), - Square(m_dPrecision_robust), Square(dDistanceRatio), - matches); + if (!std::isinf(m_dPrecision_robust)) + { + // get back corresponding view index + const IndexT I = imageIdsPair.first; + const IndexT J = imageIdsPair.second; + + const sfmData::View& viewI = sfmData->getView(I); + const sfmData::View& viewJ = sfmData->getView(J); + + // check that valid cameras can be retrieved for the pair of views + const camera::IntrinsicBase* camI = + sfmData->getIntrinsics().count(viewI.getIntrinsicId()) ? sfmData->getIntrinsics().at(viewI.getIntrinsicId()).get() : nullptr; + const camera::IntrinsicBase* camJ = + sfmData->getIntrinsics().count(viewJ.getIntrinsicId()) ? sfmData->getIntrinsics().at(viewJ.getIntrinsicId()).get() : nullptr; + if (!camI || !camJ) + return false; + + if (!isPinhole(camI->getType()) || !isPinhole(camJ->getType())) + return false; + + const camera::Pinhole* ptrPinholeI = (const camera::Pinhole*)(camI); + const camera::Pinhole* ptrPinholeJ = (const camera::Pinhole*)(camJ); + + Mat3 F; + fundamentalFromEssential(m_E, ptrPinholeI->K(), ptrPinholeJ->K(), &F); + + robustEstimation::Mat3Model model(F); + // multiview::relativePose::FundamentalSymmetricEpipolarDistanceError + matching::guidedMatching( + model, + camI, + regionsPerView.getAllRegions(I), + camJ, + regionsPerView.getAllRegions(J), + Square(m_dPrecision_robust), + Square(dDistanceRatio), + matches); + } + + return matches.getNbAllMatches() != 0; } - return matches.getNbAllMatches() != 0; - } - - // stored data - Mat3 m_E; + // stored data + Mat3 m_E; }; -} // namespace matchingImageCollection -} // namespace aliceVision - - +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_F_AC.hpp b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_F_AC.hpp index 8a41594f14..ebf44c65e8 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_F_AC.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_F_AC.hpp @@ -34,362 +34,368 @@ namespace matchingImageCollection { */ struct GeometricFilterMatrix_F_AC : public GeometricFilterMatrix { - GeometricFilterMatrix_F_AC(double dPrecision = std::numeric_limits::infinity(), - std::size_t iteration = 1024, - robustEstimation::ERobustEstimator estimator = robustEstimation::ERobustEstimator::ACRANSAC, - bool estimateDistortion = false) - : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration) - , m_F(Mat3::Identity()) - , m_estimator(estimator) - , m_estimateDistortion(estimateDistortion) - {} - - /** - * @brief Given two sets of image points, it estimates the fundamental matrix - * relating them using a robust method (like A Contrario Ransac). - */ - template - EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, - const Regions_or_Features_ProviderT& regionsPerView, - const Pair& pairIndex, - const matching::MatchesPerDescType& putativeMatchesPerType, - std::mt19937 & randomNumberGenerator, - matching::MatchesPerDescType& out_geometricInliersPerType) - { - out_geometricInliersPerType.clear(); - - // get back corresponding view index - const IndexT I = pairIndex.first; - const IndexT J = pairIndex.second; - - const sfmData::View& viewI = sfmData->getView(I); - const sfmData::View& viewJ = sfmData->getView(J); - - const camera::IntrinsicBase* camI = sfmData->getIntrinsicPtr(viewI.getIntrinsicId()); - const camera::IntrinsicBase* camJ = sfmData->getIntrinsicPtr(viewJ.getIntrinsicId()); - - const std::pair imageSizeI(viewI.getImage().getWidth(), viewI.getImage().getHeight()); - const std::pair imageSizeJ(viewJ.getImage().getWidth(), viewJ.getImage().getHeight()); - - return geometricEstimation(regionsPerView.getDataPerDesc(pairIndex.first), - regionsPerView.getDataPerDesc(pairIndex.second), - camI, camJ, - imageSizeI, imageSizeJ, - putativeMatchesPerType, - randomNumberGenerator, - out_geometricInliersPerType); - } - - /** - * @brief Given two sets of image points, it estimates the fundamental matrix - * relating them using a robust method (like A Contrario Ransac). - */ - template - EstimationStatus geometricEstimation(const MapFeatOrRegionsPerDesc& regionI, - const MapFeatOrRegionsPerDesc& regionJ, - const camera::IntrinsicBase* camI, - const camera::IntrinsicBase* camJ, - const std::pair& imageSizeI, // size of the first image - const std::pair& imageSizeJ, // size of the second image - const matching::MatchesPerDescType& putativeMatchesPerType, - std::mt19937 & randomNumberGenerator, - matching::MatchesPerDescType& out_geometricInliersPerType) - { - out_geometricInliersPerType.clear(); - - const std::vector descTypes = getCommonDescTypes(regionI, regionJ); - - if(descTypes.empty()) - return EstimationStatus(false, false); - - // retrieve all 2D features as undistorted positions into flat arrays - Mat xI, xJ; - fillMatricesWithUndistortFeaturesMatches(putativeMatchesPerType, camI, camJ, - regionI, regionJ, - descTypes, xI, xJ); - - std::vector inliers; - const camera::Equidistant * cam_I_equidistant = dynamic_cast(camI); - const camera::Equidistant * cam_J_equidistant = dynamic_cast(camJ); - std::pair estimationPair; - - switch(m_estimator) + GeometricFilterMatrix_F_AC(double dPrecision = std::numeric_limits::infinity(), + std::size_t iteration = 1024, + robustEstimation::ERobustEstimator estimator = robustEstimation::ERobustEstimator::ACRANSAC, + bool estimateDistortion = false) + : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration), + m_F(Mat3::Identity()), + m_estimator(estimator), + m_estimateDistortion(estimateDistortion) + {} + + /** + * @brief Given two sets of image points, it estimates the fundamental matrix + * relating them using a robust method (like A Contrario Ransac). + */ + template + EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, + const Regions_or_Features_ProviderT& regionsPerView, + const Pair& pairIndex, + const matching::MatchesPerDescType& putativeMatchesPerType, + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_geometricInliersPerType) { - case robustEstimation::ERobustEstimator::ACRANSAC: - { - if (cam_I_equidistant && cam_J_equidistant) + out_geometricInliersPerType.clear(); + + // get back corresponding view index + const IndexT I = pairIndex.first; + const IndexT J = pairIndex.second; + + const sfmData::View& viewI = sfmData->getView(I); + const sfmData::View& viewJ = sfmData->getView(J); + + const camera::IntrinsicBase* camI = sfmData->getIntrinsicPtr(viewI.getIntrinsicId()); + const camera::IntrinsicBase* camJ = sfmData->getIntrinsicPtr(viewJ.getIntrinsicId()); + + const std::pair imageSizeI(viewI.getImage().getWidth(), viewI.getImage().getHeight()); + const std::pair imageSizeJ(viewJ.getImage().getWidth(), viewJ.getImage().getHeight()); + + return geometricEstimation(regionsPerView.getDataPerDesc(pairIndex.first), + regionsPerView.getDataPerDesc(pairIndex.second), + camI, + camJ, + imageSizeI, + imageSizeJ, + putativeMatchesPerType, + randomNumberGenerator, + out_geometricInliersPerType); + } + + /** + * @brief Given two sets of image points, it estimates the fundamental matrix + * relating them using a robust method (like A Contrario Ransac). + */ + template + EstimationStatus geometricEstimation(const MapFeatOrRegionsPerDesc& regionI, + const MapFeatOrRegionsPerDesc& regionJ, + const camera::IntrinsicBase* camI, + const camera::IntrinsicBase* camJ, + const std::pair& imageSizeI, // size of the first image + const std::pair& imageSizeJ, // size of the second image + const matching::MatchesPerDescType& putativeMatchesPerType, + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_geometricInliersPerType) + { + out_geometricInliersPerType.clear(); + + const std::vector descTypes = getCommonDescTypes(regionI, regionJ); + + if (descTypes.empty()) + return EstimationStatus(false, false); + + // retrieve all 2D features as undistorted positions into flat arrays + Mat xI, xJ; + fillMatricesWithUndistortFeaturesMatches(putativeMatchesPerType, camI, camJ, regionI, regionJ, descTypes, xI, xJ); + + std::vector inliers; + const camera::Equidistant* cam_I_equidistant = dynamic_cast(camI); + const camera::Equidistant* cam_J_equidistant = dynamic_cast(camJ); + std::pair estimationPair; + + switch (m_estimator) { - estimationPair = geometricEstimation_Spherical_Mat(xI, xJ, cam_I_equidistant, cam_J_equidistant, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + case robustEstimation::ERobustEstimator::ACRANSAC: + { + if (cam_I_equidistant && cam_J_equidistant) + { + estimationPair = geometricEstimation_Spherical_Mat( + xI, xJ, cam_I_equidistant, cam_J_equidistant, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + } + else if (m_estimateDistortion) + { + estimationPair = + geometricEstimation_Mat_ACRANSAC( + xI, xJ, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + } + else + { + estimationPair = geometricEstimation_Mat_ACRANSAC( + xI, xJ, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + } + } + break; + case robustEstimation::ERobustEstimator::LORANSAC: + { + if (m_estimateDistortion) + { + throw std::invalid_argument("[" + std::string(__func__) + + "] Using fundamental matrix and f10 solver with LO_RANSAC is not yet implemented"); + } + if (cam_I_equidistant && cam_J_equidistant) + { + throw std::invalid_argument("[" + std::string(__func__) + + "] Using fundamental matrix and equidistant cameras solver with LO_RANSAC is not yet implemented"); + } + + estimationPair = + geometricEstimation_Mat_LORANSAC( + xI, xJ, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + } + break; + + default: + throw std::runtime_error("[" + std::string(__func__) + "] only ACRansac and LORansac are supported!"); } - else if(m_estimateDistortion) + + if (!estimationPair.first) // estimation is not valid { - estimationPair = geometricEstimation_Mat_ACRANSAC(xI, xJ, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + assert(inliers.empty()); + return EstimationStatus(false, false); } - else + + // fill geometricInliersPerType with inliers from putativeMatchesPerType + copyInlierMatches(inliers, putativeMatchesPerType, descTypes, out_geometricInliersPerType); + + // have matches has strong support + const bool hasStrongSupport = matching::hasStrongSupport(out_geometricInliersPerType, estimationPair.second); + + return EstimationStatus(true, hasStrongSupport); + } + + /** + * @brief Given two sets of image points, it estimates the fundamental matrix + * For ACRANSAC estimator + * @param[in] xI The first set of points + * @param[in] xJ The second set of points + * @param[in] imageSizeI The size of the first image (used for normalizing the points) + * @param[in] imageSizeJ The size of the second image + * @param[out] geometric_inliers A vector containing the indices of the inliers + * @return true if geometric_inliers is not empty + */ + std::pair geometricEstimation_Spherical_Mat(const Mat& xI, // points of the first image + const Mat& xJ, // points of the second image + const camera::Equidistant* cam_I, + const camera::Equidistant* cam_J, + const std::pair& imageSizeI, // size of the first image + const std::pair& imageSizeJ, // size of the first image + std::mt19937& randomNumberGenerator, + std::vector& out_inliers) + { + using namespace aliceVision; + using namespace aliceVision::robustEstimation; + out_inliers.clear(); + + if (m_estimator != ERobustEstimator::ACRANSAC) { - estimationPair = geometricEstimation_Mat_ACRANSAC(xI, xJ, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); + throw std::runtime_error("[GeometricFilterMatrix_F_AC_AC::geometricEstimation_Spherical_Mat] only ACRansac " + "and LORansac are supported!"); } - } - break; - case robustEstimation::ERobustEstimator::LORANSAC: - { - if(m_estimateDistortion) + + // define the AContrario adapted Fundamental matrix solver + using KernelT = multiview::RelativePoseSphericalKernel; + + // TODO FACA: move normalization into the kernel? + + // Lift points + Mat xI_lifted(3, xI.cols()); + for (int i = 0; i < xI.cols(); ++i) { - throw std::invalid_argument("["+std::string(__func__)+"] Using fundamental matrix and f10 solver with LO_RANSAC is not yet implemented"); + Vec2 src; + src(0) = xI(0, i); + src(1) = xI(1, i); + Vec3 dst = cam_I->toUnitSphere(cam_I->removeDistortion(cam_I->ima2cam(src))); + xI_lifted(0, i) = dst(0); + xI_lifted(1, i) = dst(1); + xI_lifted(2, i) = dst(2); } - if (cam_I_equidistant && cam_J_equidistant) + Mat xJ_lifted(3, xJ.cols()); + for (int i = 0; i < xJ.cols(); ++i) { - throw std::invalid_argument("["+std::string(__func__)+"] Using fundamental matrix and equidistant cameras solver with LO_RANSAC is not yet implemented"); + Vec2 src; + src(0) = xJ(0, i); + src(1) = xJ(1, i); + Vec3 dst = cam_J->toUnitSphere(cam_J->removeDistortion(cam_J->ima2cam(src))); + xJ_lifted(0, i) = dst(0); + xJ_lifted(1, i) = dst(1); + xJ_lifted(2, i) = dst(2); } - estimationPair = geometricEstimation_Mat_LORANSAC(xI, xJ, imageSizeI, imageSizeJ, randomNumberGenerator, inliers); - } - break; + const KernelT kernel(xI_lifted, xJ_lifted); + + // Robustly estimate the Fundamental matrix with A Contrario ransac + const double upper_bound_precision = m_dPrecision; + + robustEstimation::Mat3Model model; + const std::pair ACRansacOut = + ACRANSAC(kernel, randomNumberGenerator, out_inliers, m_stIteration, &model, upper_bound_precision); + + m_F = model.getMatrix(); + + if (out_inliers.empty()) + return std::make_pair(false, kernel.getMinimumNbRequiredSamples()); - default: - throw std::runtime_error("["+std::string(__func__)+"] only ACRansac and LORansac are supported!"); + m_dPrecision_robust = ACRansacOut.first; + + return std::make_pair(true, kernel.getMinimumNbRequiredSamples()); + } + + /** + * @brief Given two sets of image points, it estimates the fundamental matrix + * relating them using a robust method (like A Contrario Ransac). + * + * @param[in] xI The first set of points + * @param[in] xJ The second set of points + * @param[in] imageSizeI The size of the first image (used for normalizing the points) + * @param[in] imageSizeJ The size of the second image + * @param[out] geometric_inliers A vector containing the indices of the inliers + * @return true if geometric_inliers is not empty + */ + template + std::pair geometricEstimation_Mat_ACRANSAC(const Mat& xI, // points of the first image + const Mat& xJ, // points of the second image + const std::pair& imageSizeI, // size of the first image + const std::pair& imageSizeJ, // size of the first image + std::mt19937 randomNumberGenerator, + std::vector& out_inliers) + { + out_inliers.clear(); + + // define the AContrario adapted Fundamental matrix solver + using KernelT = multiview::RelativePoseKernel; + + const KernelT kernel(xI, imageSizeI.first, imageSizeI.second, xJ, imageSizeJ.first, imageSizeJ.second, true); + + // robustly estimate the Fundamental matrix with A Contrario ransac + const double upperBoundPrecision = m_dPrecision; + + ModelT_ model; + const std::pair ACRansacOut = + robustEstimation::ACRANSAC(kernel, randomNumberGenerator, out_inliers, m_stIteration, &model, upperBoundPrecision); + m_F = model.getMatrix(); + + if (out_inliers.empty()) + return std::make_pair(false, kernel.getMinimumNbRequiredSamples()); + + m_dPrecision_robust = ACRansacOut.first; + + return std::make_pair(true, kernel.getMinimumNbRequiredSamples()); } - if(!estimationPair.first) // estimation is not valid + /** + * @brief Given two sets of image points, it estimates the fundamental matrix + * For LORANSAC estimator + * @param[in] xI The first set of points + * @param[in] xJ The second set of points + * @param[in] imageSizeI The size of the first image (used for normalizing the points) + * @param[in] imageSizeJ The size of the second image + * @param[out] geometric_inliers A vector containing the indices of the inliers + * @return true if geometric_inliers is not empty + */ + template + std::pair geometricEstimation_Mat_LORANSAC(const Mat& xI, // points of the first image + const Mat& xJ, // points of the second image + const std::pair& imageSizeI, // size of the first image + const std::pair& imageSizeJ, // size of the first image + std::mt19937& randomNumberGenerator, + std::vector& out_inliers) { - assert(inliers.empty()); - return EstimationStatus(false, false); + out_inliers.clear(); + + // just a safeguard + if (m_dPrecision == std::numeric_limits::infinity()) + throw std::invalid_argument("[" + std::string(__func__) + "] the threshold of the LORANSAC is set to infinity!"); + + using KernelT = multiview::RelativePoseKernel; + + const KernelT kernel(xI, imageSizeI.first, imageSizeI.second, xJ, imageSizeJ.first, imageSizeJ.second, true); + + //@fixme scorer should be using the pixel error, not the squared version, refactoring needed + const double normalizedThreshold = Square(m_dPrecision * kernel.thresholdNormalizer()); + robustEstimation::ScoreEvaluator scorer(normalizedThreshold); + + robustEstimation::Mat3Model model = robustEstimation::LO_RANSAC(kernel, scorer, randomNumberGenerator, &out_inliers); + m_F = model.getMatrix(); + + if (out_inliers.empty()) + return std::make_pair(false, kernel.getMinimumNbRequiredSamples()); + + m_dPrecision_robust = m_dPrecision; + + return std::make_pair(true, kernel.getMinimumNbRequiredSamples()); } - // fill geometricInliersPerType with inliers from putativeMatchesPerType - copyInlierMatches(inliers, putativeMatchesPerType, descTypes, out_geometricInliersPerType); - - // have matches has strong support - const bool hasStrongSupport = matching::hasStrongSupport(out_geometricInliersPerType, estimationPair.second); - - return EstimationStatus(true, hasStrongSupport); - } - - /** - * @brief Given two sets of image points, it estimates the fundamental matrix - * For ACRANSAC estimator - * @param[in] xI The first set of points - * @param[in] xJ The second set of points - * @param[in] imageSizeI The size of the first image (used for normalizing the points) - * @param[in] imageSizeJ The size of the second image - * @param[out] geometric_inliers A vector containing the indices of the inliers - * @return true if geometric_inliers is not empty - */ - std::pair - geometricEstimation_Spherical_Mat(const Mat& xI, // points of the first image - const Mat& xJ, // points of the second image - const camera::Equidistant* cam_I, const camera::Equidistant* cam_J, - const std::pair& imageSizeI, // size of the first image - const std::pair& imageSizeJ, // size of the first image - std::mt19937 &randomNumberGenerator, - std::vector& out_inliers) - { - using namespace aliceVision; - using namespace aliceVision::robustEstimation; - out_inliers.clear(); - - if(m_estimator != ERobustEstimator::ACRANSAC) - { - throw std::runtime_error("[GeometricFilterMatrix_F_AC_AC::geometricEstimation_Spherical_Mat] only ACRansac " - "and LORansac are supported!"); - } - - // define the AContrario adapted Fundamental matrix solver - using KernelT = multiview::RelativePoseSphericalKernel< - multiview::relativePose::Fundamental7PSphericalSolver, - multiview::relativePose::EpipolarSphericalDistanceError, - robustEstimation::Mat3Model>; - - // TODO FACA: move normalization into the kernel? - - // Lift points - Mat xI_lifted(3, xI.cols()); - for(int i = 0; i < xI.cols(); ++i) - { - Vec2 src; - src(0) = xI(0, i); - src(1) = xI(1, i); - Vec3 dst = cam_I->toUnitSphere(cam_I->removeDistortion(cam_I->ima2cam(src))); - xI_lifted(0, i) = dst(0); - xI_lifted(1, i) = dst(1); - xI_lifted(2, i) = dst(2); - } - Mat xJ_lifted(3, xJ.cols()); - for(int i = 0; i < xJ.cols(); ++i) - { - Vec2 src; - src(0) = xJ(0, i); - src(1) = xJ(1, i); - Vec3 dst = cam_J->toUnitSphere(cam_J->removeDistortion(cam_J->ima2cam(src))); - xJ_lifted(0, i) = dst(0); - xJ_lifted(1, i) = dst(1); - xJ_lifted(2, i) = dst(2); - } - - const KernelT kernel(xI_lifted, xJ_lifted); - - // Robustly estimate the Fundamental matrix with A Contrario ransac - const double upper_bound_precision = m_dPrecision; - - robustEstimation::Mat3Model model; - const std::pair ACRansacOut = ACRANSAC(kernel, randomNumberGenerator, out_inliers, m_stIteration, &model, upper_bound_precision); - - m_F = model.getMatrix(); - - if(out_inliers.empty()) - return std::make_pair(false, kernel.getMinimumNbRequiredSamples()); - - m_dPrecision_robust = ACRansacOut.first; - - return std::make_pair(true, kernel.getMinimumNbRequiredSamples()); - } - - /** - * @brief Given two sets of image points, it estimates the fundamental matrix - * relating them using a robust method (like A Contrario Ransac). - * - * @param[in] xI The first set of points - * @param[in] xJ The second set of points - * @param[in] imageSizeI The size of the first image (used for normalizing the points) - * @param[in] imageSizeJ The size of the second image - * @param[out] geometric_inliers A vector containing the indices of the inliers - * @return true if geometric_inliers is not empty - */ - template - std::pair geometricEstimation_Mat_ACRANSAC(const Mat& xI, // points of the first image - const Mat& xJ, // points of the second image - const std::pair& imageSizeI, // size of the first image - const std::pair& imageSizeJ, // size of the first image - std::mt19937 randomNumberGenerator, - std::vector& out_inliers) - { - out_inliers.clear(); - - // define the AContrario adapted Fundamental matrix solver - using KernelT = multiview::RelativePoseKernel< - SolverT_, - multiview::relativePose::FundamentalEpipolarDistanceError, - //multiview::relativePose::FundamentalSymmetricEpipolarDistanceError, - multiview::UnnormalizerT, - ModelT_>; - - const KernelT kernel(xI, imageSizeI.first, imageSizeI.second, - xJ, imageSizeJ.first, imageSizeJ.second, true); - - // robustly estimate the Fundamental matrix with A Contrario ransac - const double upperBoundPrecision = m_dPrecision; - - ModelT_ model; - const std::pair ACRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, out_inliers, m_stIteration, &model, upperBoundPrecision); - m_F = model.getMatrix(); - - if(out_inliers.empty()) - return std::make_pair(false, kernel.getMinimumNbRequiredSamples()); - - m_dPrecision_robust = ACRansacOut.first; - - return std::make_pair(true, kernel.getMinimumNbRequiredSamples()); - } - - /** - * @brief Given two sets of image points, it estimates the fundamental matrix - * For LORANSAC estimator - * @param[in] xI The first set of points - * @param[in] xJ The second set of points - * @param[in] imageSizeI The size of the first image (used for normalizing the points) - * @param[in] imageSizeJ The size of the second image - * @param[out] geometric_inliers A vector containing the indices of the inliers - * @return true if geometric_inliers is not empty - */ - template - std::pair geometricEstimation_Mat_LORANSAC(const Mat& xI, // points of the first image - const Mat& xJ, // points of the second image - const std::pair& imageSizeI, // size of the first image - const std::pair& imageSizeJ, // size of the first image - std::mt19937 &randomNumberGenerator, - std::vector& out_inliers) - { - out_inliers.clear(); - - // just a safeguard - if(m_dPrecision == std::numeric_limits::infinity()) - throw std::invalid_argument("["+std::string(__func__)+"] the threshold of the LORANSAC is set to infinity!"); - - using KernelT = multiview::RelativePoseKernel< - SolverT_, - multiview::relativePose::FundamentalSymmetricEpipolarDistanceError, - multiview::UnnormalizerT, - robustEstimation::Mat3Model, - SolverLsT_>; - - const KernelT kernel(xI, imageSizeI.first, imageSizeI.second, - xJ, imageSizeJ.first, imageSizeJ.second, true); - - //@fixme scorer should be using the pixel error, not the squared version, refactoring needed - const double normalizedThreshold = Square(m_dPrecision * kernel.thresholdNormalizer()); - robustEstimation::ScoreEvaluator scorer(normalizedThreshold); - - robustEstimation::Mat3Model model = robustEstimation::LO_RANSAC(kernel, scorer, randomNumberGenerator, &out_inliers); - m_F = model.getMatrix(); - - if(out_inliers.empty()) - return std::make_pair(false, kernel.getMinimumNbRequiredSamples()); - - m_dPrecision_robust = m_dPrecision; - - return std::make_pair(true, kernel.getMinimumNbRequiredSamples()); - } - - /** - * @brief Geometry_guided_matching - * @param sfmData - * @param regionsPerView - * @param imagesPair - * @param dDistanceRatio - * @param matches - * @return - */ - bool Geometry_guided_matching(const sfmData::SfMData* sfmData, - const feature::RegionsPerView& regionsPerView, - const Pair imageIdsPair, - const double dDistanceRatio, - matching::MatchesPerDescType& matches) override - { - if (m_dPrecision_robust != std::numeric_limits::infinity()) + /** + * @brief Geometry_guided_matching + * @param sfmData + * @param regionsPerView + * @param imagesPair + * @param dDistanceRatio + * @param matches + * @return + */ + bool Geometry_guided_matching(const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const Pair imageIdsPair, + const double dDistanceRatio, + matching::MatchesPerDescType& matches) override { - // get back corresponding view index - const IndexT I = imageIdsPair.first; - const IndexT J = imageIdsPair.second; - - const sfmData::View& viewI = sfmData->getView(I); - const sfmData::View& viewJ = sfmData->getView(J); - - // retrieve corresponding pair camera intrinsic if any - const camera::IntrinsicBase* camI = sfmData->getIntrinsics().count(viewI.getIntrinsicId()) ? - sfmData->getIntrinsics().at(viewI.getIntrinsicId()).get() : nullptr; - const camera::IntrinsicBase* camJ = sfmData->getIntrinsics().count(viewJ.getIntrinsicId()) ? - sfmData->getIntrinsics().at(viewJ.getIntrinsicId()).get() : nullptr; - - robustEstimation::Mat3Model model(m_F); - - // check the features correspondences that agree in the geometric and photometric domain - matching::guidedMatching( - model, - camI, // camera::IntrinsicBase - regionsPerView.getAllRegions(I), // feature::Regions - camJ, // camera::IntrinsicBase - regionsPerView.getAllRegions(J), // feature::Regions - Square(m_dPrecision_robust), Square(dDistanceRatio), - matches); + if (m_dPrecision_robust != std::numeric_limits::infinity()) + { + // get back corresponding view index + const IndexT I = imageIdsPair.first; + const IndexT J = imageIdsPair.second; + + const sfmData::View& viewI = sfmData->getView(I); + const sfmData::View& viewJ = sfmData->getView(J); + + // retrieve corresponding pair camera intrinsic if any + const camera::IntrinsicBase* camI = + sfmData->getIntrinsics().count(viewI.getIntrinsicId()) ? sfmData->getIntrinsics().at(viewI.getIntrinsicId()).get() : nullptr; + const camera::IntrinsicBase* camJ = + sfmData->getIntrinsics().count(viewJ.getIntrinsicId()) ? sfmData->getIntrinsics().at(viewJ.getIntrinsicId()).get() : nullptr; + + robustEstimation::Mat3Model model(m_F); + + // check the features correspondences that agree in the geometric and photometric domain + matching::guidedMatching( + model, + camI, // camera::IntrinsicBase + regionsPerView.getAllRegions(I), // feature::Regions + camJ, // camera::IntrinsicBase + regionsPerView.getAllRegions(J), // feature::Regions + Square(m_dPrecision_robust), + Square(dDistanceRatio), + matches); + } + return matches.getNbAllMatches() != 0; } - return matches.getNbAllMatches() != 0; - } - - // Stored data - Mat3 m_F; - robustEstimation::ERobustEstimator m_estimator; - bool m_estimateDistortion; + + // Stored data + Mat3 m_F; + robustEstimation::ERobustEstimator m_estimator; + bool m_estimateDistortion; }; -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.cpp b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.cpp index 2b2f75efff..4f8408498a 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.cpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.cpp @@ -10,107 +10,101 @@ namespace aliceVision { namespace matchingImageCollection { - -bool GeometricFilterMatrix_HGrowing::getMatches(const feature::EImageDescriberType &descType, - const IndexT homographyId, matching::IndMatches &matches) const +bool GeometricFilterMatrix_HGrowing::getMatches(const feature::EImageDescriberType& descType, + const IndexT homographyId, + matching::IndMatches& matches) const { - matches.clear(); - - if (_HsAndMatchesPerDesc.find(descType) == _HsAndMatchesPerDesc.end()) - return false; - if (homographyId > _HsAndMatchesPerDesc.at(descType).size() - 1) - return false; + matches.clear(); - matches = _HsAndMatchesPerDesc.at(descType).at(homographyId).second; + if (_HsAndMatchesPerDesc.find(descType) == _HsAndMatchesPerDesc.end()) + return false; + if (homographyId > _HsAndMatchesPerDesc.at(descType).size() - 1) + return false; - return !matches.empty(); + matches = _HsAndMatchesPerDesc.at(descType).at(homographyId).second; + return !matches.empty(); } -std::size_t GeometricFilterMatrix_HGrowing::getNbHomographies(const feature::EImageDescriberType &descType) const +std::size_t GeometricFilterMatrix_HGrowing::getNbHomographies(const feature::EImageDescriberType& descType) const { - if (_HsAndMatchesPerDesc.find(descType) == _HsAndMatchesPerDesc.end()) - return 0; - return _HsAndMatchesPerDesc.at(descType).size(); + if (_HsAndMatchesPerDesc.find(descType) == _HsAndMatchesPerDesc.end()) + return 0; + return _HsAndMatchesPerDesc.at(descType).size(); } - -std::size_t GeometricFilterMatrix_HGrowing::getNbVerifiedMatches(const feature::EImageDescriberType &descType, - const IndexT homographyId) const +std::size_t GeometricFilterMatrix_HGrowing::getNbVerifiedMatches(const feature::EImageDescriberType& descType, const IndexT homographyId) const { - if (_HsAndMatchesPerDesc.find(descType) == _HsAndMatchesPerDesc.end()) - return 0; - if (homographyId > _HsAndMatchesPerDesc.at(descType).size() - 1) - return 0; + if (_HsAndMatchesPerDesc.find(descType) == _HsAndMatchesPerDesc.end()) + return 0; + if (homographyId > _HsAndMatchesPerDesc.at(descType).size() - 1) + return 0; - return _HsAndMatchesPerDesc.at(descType).at(homographyId).second.size(); + return _HsAndMatchesPerDesc.at(descType).at(homographyId).second.size(); } std::size_t GeometricFilterMatrix_HGrowing::getNbAllVerifiedMatches() const { - std::size_t counter = 0; - for (const auto & HnMs : _HsAndMatchesPerDesc) - { - for (const std::pair & HnM : HnMs.second) + std::size_t counter = 0; + for (const auto& HnMs : _HsAndMatchesPerDesc) { - counter += HnM.second.size(); + for (const std::pair& HnM : HnMs.second) + { + counter += HnM.second.size(); + } } - } - return counter; + return counter; } - - -bool growHomography(const std::vector &featuresI, - const std::vector &featuresJ, - const matching::IndMatches &matches, - const IndexT &seedMatchId, - std::set &planarMatchesIndices, Mat3 &transformation, +bool growHomography(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + const IndexT& seedMatchId, + std::set& planarMatchesIndices, + Mat3& transformation, const GrowParameters& param) { - assert(seedMatchId <= matches.size()); + assert(seedMatchId <= matches.size()); - planarMatchesIndices.clear(); - transformation = Mat3::Identity(); + planarMatchesIndices.clear(); + transformation = Mat3::Identity(); - const matching::IndMatch & seedMatch = matches.at(seedMatchId); - const feature::PointFeature & seedFeatureI = featuresI.at(seedMatch._i); - const feature::PointFeature & seedFeatureJ = featuresJ.at(seedMatch._j); + const matching::IndMatch& seedMatch = matches.at(seedMatchId); + const feature::PointFeature& seedFeatureI = featuresI.at(seedMatch._i); + const feature::PointFeature& seedFeatureJ = featuresJ.at(seedMatch._j); - double currTolerance; + double currTolerance; - for (IndexT iRefineStep = 0; iRefineStep < param._nbRefiningIterations; ++iRefineStep) - { - if (iRefineStep == 0) - { - computeSimilarity(seedFeatureI, seedFeatureJ, transformation); - currTolerance = param._similarityTolerance; - } - else if (iRefineStep <= 4) + for (IndexT iRefineStep = 0; iRefineStep < param._nbRefiningIterations; ++iRefineStep) { - estimateAffinity(featuresI, featuresJ, matches, transformation, planarMatchesIndices); - currTolerance = param._affinityTolerance; - } - else - { - estimateHomography(featuresI, featuresJ, matches, transformation, planarMatchesIndices); - currTolerance = param._homographyTolerance; - } + if (iRefineStep == 0) + { + computeSimilarity(seedFeatureI, seedFeatureJ, transformation); + currTolerance = param._similarityTolerance; + } + else if (iRefineStep <= 4) + { + estimateAffinity(featuresI, featuresJ, matches, transformation, planarMatchesIndices); + currTolerance = param._affinityTolerance; + } + else + { + estimateHomography(featuresI, featuresJ, matches, transformation, planarMatchesIndices); + currTolerance = param._homographyTolerance; + } - findTransformationInliers(featuresI, featuresJ, matches, transformation, currTolerance, planarMatchesIndices); + findTransformationInliers(featuresI, featuresJ, matches, transformation, currTolerance, planarMatchesIndices); - if (planarMatchesIndices.size() < param._minInliersToRefine) - return false; + if (planarMatchesIndices.size() < param._minInliersToRefine) + return false; - // Note: the following statement is present in the MATLAB code but not implemented in YASM -// if (planarMatchesIndices.size() >= param._maxFractionPlanarMatches * matches.size()) -// break; - } - return !transformation.isIdentity(); + // Note: the following statement is present in the MATLAB code but not implemented in YASM + // if (planarMatchesIndices.size() >= param._maxFractionPlanarMatches * matches.size()) + // break; + } + return !transformation.isIdentity(); } - - void filterMatchesByHGrowing(const std::vector& siofeatures_I, const std::vector& siofeatures_J, const matching::IndMatches& putativeMatches, @@ -118,120 +112,110 @@ void filterMatchesByHGrowing(const std::vector& siofeatur matching::IndMatches& outGeometricInliers, const HGrowingFilteringParam& param) { + using namespace aliceVision::feature; + using namespace aliceVision::matching; - using namespace aliceVision::feature; - using namespace aliceVision::matching; - - IndMatches remainingMatches = putativeMatches; - GeometricFilterMatrix_HGrowing dummy; - - for(IndexT iH = 0; iH < param._maxNbHomographies; ++iH) - { - std::set usedMatchesId, bestMatchesId; - Mat3 bestHomography; + IndMatches remainingMatches = putativeMatches; + GeometricFilterMatrix_HGrowing dummy; - // -- Estimate H using homography-growing approach - #pragma omp parallel for // (huge optimization but modify results a little) - for(int iMatch = 0; iMatch < remainingMatches.size(); ++iMatch) + for (IndexT iH = 0; iH < param._maxNbHomographies; ++iH) { - // Growing a homography from one match ([F.Srajer, 2016] algo. 1, p. 20) - // each match is used once only per homography estimation (increases computation time) [1st improvement ([F.Srajer, 2016] p. 20) ] - if (usedMatchesId.find(iMatch) != usedMatchesId.end()) - continue; - std::set planarMatchesId; // be careful: it contains the id. in the 'remainingMatches' vector not 'putativeMatches' vector. - Mat3 homography; - - if (!growHomography(siofeatures_I, - siofeatures_J, - remainingMatches, - iMatch, - planarMatchesId, - homography, - param._growParam)) - { - continue; - } - - #pragma omp critical - usedMatchesId.insert(planarMatchesId.begin(), planarMatchesId.end()); - - if (planarMatchesId.size() > bestMatchesId.size()) - { - #pragma omp critical + std::set usedMatchesId, bestMatchesId; + Mat3 bestHomography; + +// -- Estimate H using homography-growing approach +#pragma omp parallel for // (huge optimization but modify results a little) + for (int iMatch = 0; iMatch < remainingMatches.size(); ++iMatch) + { + // Growing a homography from one match ([F.Srajer, 2016] algo. 1, p. 20) + // each match is used once only per homography estimation (increases computation time) [1st improvement ([F.Srajer, 2016] p. 20) ] + if (usedMatchesId.find(iMatch) != usedMatchesId.end()) + continue; + std::set planarMatchesId; // be careful: it contains the id. in the 'remainingMatches' vector not 'putativeMatches' vector. + Mat3 homography; + + if (!growHomography(siofeatures_I, siofeatures_J, remainingMatches, iMatch, planarMatchesId, homography, param._growParam)) + { + continue; + } + +#pragma omp critical + usedMatchesId.insert(planarMatchesId.begin(), planarMatchesId.end()); + + if (planarMatchesId.size() > bestMatchesId.size()) + { +#pragma omp critical + { + // if(iH == 3) { std::cout << "best iMatch" << iMatch << std::endl; } + bestMatchesId = + planarMatchesId; // be careful: it contains the id. in the 'remainingMatches' vector not 'putativeMatches' vector. + bestHomography = homography; + } + } + } // 'iMatch' + + // -- Refine H using Ceres minimizer + refineHomography(siofeatures_I, siofeatures_J, remainingMatches, bestHomography, bestMatchesId, param._growParam._homographyTolerance); + + // stop when the models get too small + if (bestMatchesId.size() < param._minNbMatchesPerH) + break; + + // Store validated results: { - // if(iH == 3) { std::cout << "best iMatch" << iMatch << std::endl; } - bestMatchesId = planarMatchesId; // be careful: it contains the id. in the 'remainingMatches' vector not 'putativeMatches' vector. - bestHomography = homography; + IndMatches matches; + Mat3 H = bestHomography; + for (IndexT id : bestMatchesId) + { + matches.push_back(remainingMatches.at(id)); + } + homographiesAndMatches.emplace_back(H, matches); } - } - } // 'iMatch' - - // -- Refine H using Ceres minimizer - refineHomography(siofeatures_I, siofeatures_J, remainingMatches, bestHomography, bestMatchesId, param._growParam._homographyTolerance); - - // stop when the models get too small - if (bestMatchesId.size() < param._minNbMatchesPerH) - break; - - // Store validated results: - { - IndMatches matches; - Mat3 H = bestHomography; - for (IndexT id : bestMatchesId) - { - matches.push_back(remainingMatches.at(id)); - } - homographiesAndMatches.emplace_back(H, matches); - } - - // -- Update not used matches & Save geometrically verified matches - for (IndexT id : bestMatchesId) - { - outGeometricInliers.push_back(remainingMatches.at(id)); - } - // update remaining matches (/!\ Keep ordering) - std::size_t cpt = 0; - for (IndexT id : bestMatchesId) - { - remainingMatches.erase(remainingMatches.begin() + id - cpt); - ++cpt; - } + // -- Update not used matches & Save geometrically verified matches + for (IndexT id : bestMatchesId) + { + outGeometricInliers.push_back(remainingMatches.at(id)); + } - // stop when the number of remaining matches is too small - if (remainingMatches.size() < param._minNbMatchesPerH) - break; + // update remaining matches (/!\ Keep ordering) + std::size_t cpt = 0; + for (IndexT id : bestMatchesId) + { + remainingMatches.erase(remainingMatches.begin() + id - cpt); + ++cpt; + } - } // 'iH' + // stop when the number of remaining matches is too small + if (remainingMatches.size() < param._minNbMatchesPerH) + break; + } // 'iH' } - - -void drawHomographyMatches(const sfmData::View &viewI, - const sfmData::View &viewJ, - const std::vector &siofeatures_I, - const std::vector &siofeatures_J, - const std::vector> &homographiesAndMatches, - const matching::IndMatches &putativeMatches, - const std::string &outFilename) +void drawHomographyMatches(const sfmData::View& viewI, + const sfmData::View& viewJ, + const std::vector& siofeatures_I, + const std::vector& siofeatures_J, + const std::vector>& homographiesAndMatches, + const matching::IndMatches& putativeMatches, + const std::string& outFilename) { - - const std::string& imagePathLeft = viewI.getImage().getImagePath(); - const std::string& imagePathRight = viewJ.getImage().getImagePath(); - const auto imageSizeLeft = std::make_pair(viewI.getImage().getWidth(), viewI.getImage().getHeight()); - const auto imageSizeRight = std::make_pair(viewJ.getImage().getWidth(), viewJ.getImage().getHeight()); - - drawHomographyMatches(imagePathLeft, - imageSizeLeft, - siofeatures_I, - imagePathRight, - imageSizeRight, - siofeatures_J, - homographiesAndMatches, - putativeMatches, - outFilename); + const std::string& imagePathLeft = viewI.getImage().getImagePath(); + const std::string& imagePathRight = viewJ.getImage().getImagePath(); + const auto imageSizeLeft = std::make_pair(viewI.getImage().getWidth(), viewI.getImage().getHeight()); + const auto imageSizeRight = std::make_pair(viewJ.getImage().getWidth(), viewJ.getImage().getHeight()); + + drawHomographyMatches(imagePathLeft, + imageSizeLeft, + siofeatures_I, + imagePathRight, + imageSizeRight, + siofeatures_J, + homographiesAndMatches, + putativeMatches, + outFilename); } -} -} +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.hpp b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.hpp index a97a617aeb..c8097de81c 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_HGrowing.hpp @@ -16,13 +16,9 @@ #include - namespace aliceVision { namespace matchingImageCollection { - - - struct GrowParameters { GrowParameters() = default; @@ -33,14 +29,14 @@ struct GrowParameters std::size_t minInliersToRefine, std::size_t nbRefiningIterations, double maxFractionPlanarMatches) - : _similarityTolerance(similarityTolerance), - _affinityTolerance(affinityTolerance), - _homographyTolerance(homographyTolerance), - _minInliersToRefine(minInliersToRefine), - _nbRefiningIterations(nbRefiningIterations), - _maxFractionPlanarMatches(maxFractionPlanarMatches) + : _similarityTolerance(similarityTolerance), + _affinityTolerance(affinityTolerance), + _homographyTolerance(homographyTolerance), + _minInliersToRefine(minInliersToRefine), + _nbRefiningIterations(nbRefiningIterations), + _maxFractionPlanarMatches(maxFractionPlanarMatches) { - assert(_maxFractionPlanarMatches >= 0 && _maxFractionPlanarMatches <= 1); + assert(_maxFractionPlanarMatches >= 0 && _maxFractionPlanarMatches <= 1); } /// The maximal reprojection (pixel) error for matches estimated @@ -81,21 +77,21 @@ struct GrowParameters * @param[in] param The parameters of the algorihm. * @return true if the \c transformation is different than the identity matrix. */ -bool growHomography(const std::vector &featuresI, - const std::vector &featuresJ, - const matching::IndMatches &matches, - const IndexT &seedMatchId, - std::set &planarMatchesIndices, - Mat3 &transformation, +bool growHomography(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + const IndexT& seedMatchId, + std::set& planarMatchesIndices, + Mat3& transformation, const GrowParameters& param); struct HGrowingFilteringParam { HGrowingFilteringParam() = default; HGrowingFilteringParam(std::size_t maxNbHomographies, std::size_t minNbMatchesPerH, const GrowParameters& growParam) - : _maxNbHomographies(maxNbHomographies), - _minNbMatchesPerH(minNbMatchesPerH), - _growParam(growParam) {} ; + : _maxNbHomographies(maxNbHomographies), + _minNbMatchesPerH(minNbMatchesPerH), + _growParam(growParam){}; /// Max. number of homographies to estimate. std::size_t _maxNbHomographies{10}; @@ -120,8 +116,7 @@ struct HGrowingFilteringParam void filterMatchesByHGrowing(const std::vector& siofeatures_I, const std::vector& siofeatures_J, const matching::IndMatches& putativeMatches, - std::vector>& homographiesAndMatches, + std::vector>& homographiesAndMatches, matching::IndMatches& outGeometricInliers, const HGrowingFilteringParam& param); @@ -136,223 +131,208 @@ void filterMatchesByHGrowing(const std::vector& siofeatur * @param[in] homographiesAndMatches Contains each found homography and the relevant supporting matches. * @param[in] putativeMatches The putative matches. */ -void drawHomographyMatches(const sfmData::View &viewI, - const sfmData::View &viewJ, - const std::vector &siofeatures_I, - const std::vector &siofeatures_J, - const std::vector> &homographiesAndMatches, - const matching::IndMatches &putativeMatches, - const std::string &outFilename); +void drawHomographyMatches(const sfmData::View& viewI, + const sfmData::View& viewJ, + const std::vector& siofeatures_I, + const std::vector& siofeatures_J, + const std::vector>& homographiesAndMatches, + const matching::IndMatches& putativeMatches, + const std::string& outFilename); //-- Multiple homography matrices estimation template functor, based on homography growing, used for filter pair of putative correspondences struct GeometricFilterMatrix_HGrowing : public GeometricFilterMatrix { - explicit GeometricFilterMatrix_HGrowing( - double dPrecision = std::numeric_limits::infinity(), - size_t iteration = 1024) - : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration) - { } - - /** - * @brief Given two sets of image points, it estimates the homography matrix - * relating them using a robust method (like A Contrario Ransac). - * @details It return matches grouped by estimated homographies. - * Example: - * - Putative matches id: [0 1 2 3 4 5 6 7] - * - Estimated homographies: [0(h0) 1(h2) 2(h1) 3(nan) 4(h2) 5(h0) 6(h1) 7(nan)] - * - Sorted matches [0(h0) 5(h0) 2(h1) 6(h1) 1(h2) 4(h2)] - * - out_geometricInliersPerType [0 5 2 6 1 4] - * - * To draw & save matches groups into .svg images: - * enter an existing folder in the following variable ('outputSvgDir'). - * File format: hmatches___.svg - * Little white dots = putative matches - * Colored dots = geometrically verified matches (1 color per estimated plane) - * - * - * @tparam Regions_or_Features_ProviderT The Region provider. - * @param sfmData The sfmData containing the info about the scene. - * @param regionsPerView The region provider. - * @param pairIndex The pair of view for which the geometric validation is computed. - * @param putativeMatchesPerType The putative matches for the considerec views. - * @param out_geometricInliersPerType The filtered matches validated by the growing homography approach. - * @param[in] outputSvgDir An existing folder where to save the images. - * @return The estimation status. - */ - template - EstimationStatus geometricEstimation(const sfmData::SfMData * sfmData, - const Regions_or_Features_ProviderT ®ionsPerView, - const Pair &pairIndex, - const matching::MatchesPerDescType &putativeMatchesPerType, - std::mt19937 &randomNumberGenerator, - matching::MatchesPerDescType &out_geometricInliersPerType, - const std::string& outputSvgDir = "") - { - using namespace aliceVision::feature; - using namespace aliceVision::matching; - - out_geometricInliersPerType.clear(); - - const std::vector descTypes = regionsPerView.getCommonDescTypes(pairIndex); - if(descTypes.empty()) - return EstimationStatus(false, false); - -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - if (std::find(descTypes.begin(), descTypes.end(),feature::EImageDescriberType::CCTAG3) != descTypes.end() || - std::find(descTypes.begin(), descTypes.end(),feature::EImageDescriberType::CCTAG4) != descTypes.end()) + explicit GeometricFilterMatrix_HGrowing(double dPrecision = std::numeric_limits::infinity(), size_t iteration = 1024) + : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration) + {} + + /** + * @brief Given two sets of image points, it estimates the homography matrix + * relating them using a robust method (like A Contrario Ransac). + * @details It return matches grouped by estimated homographies. + * Example: + * - Putative matches id: [0 1 2 3 4 5 6 7] + * - Estimated homographies: [0(h0) 1(h2) 2(h1) 3(nan) 4(h2) 5(h0) 6(h1) 7(nan)] + * - Sorted matches [0(h0) 5(h0) 2(h1) 6(h1) 1(h2) 4(h2)] + * - out_geometricInliersPerType [0 5 2 6 1 4] + * + * To draw & save matches groups into .svg images: + * enter an existing folder in the following variable ('outputSvgDir'). + * File format: hmatches___.svg + * Little white dots = putative matches + * Colored dots = geometrically verified matches (1 color per estimated plane) + * + * + * @tparam Regions_or_Features_ProviderT The Region provider. + * @param sfmData The sfmData containing the info about the scene. + * @param regionsPerView The region provider. + * @param pairIndex The pair of view for which the geometric validation is computed. + * @param putativeMatchesPerType The putative matches for the considerec views. + * @param out_geometricInliersPerType The filtered matches validated by the growing homography approach. + * @param[in] outputSvgDir An existing folder where to save the images. + * @return The estimation status. + */ + template + EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, + const Regions_or_Features_ProviderT& regionsPerView, + const Pair& pairIndex, + const matching::MatchesPerDescType& putativeMatchesPerType, + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_geometricInliersPerType, + const std::string& outputSvgDir = "") { - ALICEVISION_LOG_ERROR("Geometric filtering by Homography Growing cannot handle CCTAG descriptors."); - return EstimationStatus(false, false); - } + using namespace aliceVision::feature; + using namespace aliceVision::matching; + + out_geometricInliersPerType.clear(); + + const std::vector descTypes = regionsPerView.getCommonDescTypes(pairIndex); + if (descTypes.empty()) + return EstimationStatus(false, false); + +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) + if (std::find(descTypes.begin(), descTypes.end(), feature::EImageDescriberType::CCTAG3) != descTypes.end() || + std::find(descTypes.begin(), descTypes.end(), feature::EImageDescriberType::CCTAG4) != descTypes.end()) + { + ALICEVISION_LOG_ERROR("Geometric filtering by Homography Growing cannot handle CCTAG descriptors."); + return EstimationStatus(false, false); + } #endif #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_APRILTAG) - if (std::find(descTypes.begin(), descTypes.end(),feature::EImageDescriberType::APRILTAG16H5) != descTypes.end()) - { - ALICEVISION_LOG_ERROR("Geometric filtering by Homography Growing cannot handle AprilTag descriptors."); - return EstimationStatus(false, false); - } + if (std::find(descTypes.begin(), descTypes.end(), feature::EImageDescriberType::APRILTAG16H5) != descTypes.end()) + { + ALICEVISION_LOG_ERROR("Geometric filtering by Homography Growing cannot handle AprilTag descriptors."); + return EstimationStatus(false, false); + } #endif - - // Get back corresponding view index - const IndexT viewId_I = pairIndex.first; - const IndexT viewId_J = pairIndex.second; - - const sfmData::View & viewI = *(sfmData->getViews().at(viewId_I)); - const sfmData::View & viewJ = *(sfmData->getViews().at(viewId_J)); + // Get back corresponding view index + const IndexT viewId_I = pairIndex.first; + const IndexT viewId_J = pairIndex.second; + + const sfmData::View& viewI = *(sfmData->getViews().at(viewId_I)); + const sfmData::View& viewJ = *(sfmData->getViews().at(viewId_J)); + + for (const EImageDescriberType& descType : descTypes) + { + if (!putativeMatchesPerType.count(descType)) + { + continue; + } // we may have 0 feature for some descriptor types + + const Regions& regions_I = regionsPerView.getRegions(viewId_I, descType); + const Regions& regions_J = regionsPerView.getRegions(viewId_J, descType); + + std::vector> homographiesAndMatches; + matching::IndMatches outGeometricInliers; + filterMatchesByHGrowing(regions_I.Features(), + regions_J.Features(), + putativeMatchesPerType.at(descType), + homographiesAndMatches, + outGeometricInliers, + _parameters); + + out_geometricInliersPerType.emplace(descType, outGeometricInliers); + _HsAndMatchesPerDesc.emplace(descType, homographiesAndMatches); + + if (outputSvgDir.empty()) + { + continue; + } + + if (boost::filesystem::exists(outputSvgDir)) + { + const std::size_t nbMatches = outGeometricInliers.size(); + const std::string name = std::to_string(nbMatches) + "hmatches_" + std::to_string(viewI.getViewId()) + "_" + + std::to_string(viewJ.getViewId()) + "_" + EImageDescriberType_enumToString(descType) + ".svg"; + // @FIXME not worth it having boost::filesystem in a header + const std::string outFilename = (boost::filesystem::path(outputSvgDir) / boost::filesystem::path(name)).string(); + drawHomographyMatches( + viewI, viewJ, regions_I.Features(), regions_J.Features(), homographiesAndMatches, putativeMatchesPerType.at(descType), outFilename); + } + else + { + ALICEVISION_LOG_WARNING("Cannot save homography-growing matches into '" << outputSvgDir << "': folder does not exist."); + } + + } // 'descriptor' + + // Check if resection has strong support + if (out_geometricInliersPerType.empty()) + return EstimationStatus(true, false); + else + return EstimationStatus(true, true); + } - for (const EImageDescriberType& descType : descTypes) + /** + * @brief Geometry_guided_matching + * @param sfm_data + * @param regionsPerView + * @param pairIndex + * @param dDistanceRatio + * @param matches + * @return + */ + bool Geometry_guided_matching(const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const Pair imageIdsPair, + const double dDistanceRatio, + matching::MatchesPerDescType& matches) override { - if(!putativeMatchesPerType.count(descType)) - { - continue; - } // we may have 0 feature for some descriptor types - - const Regions & regions_I = regionsPerView.getRegions(viewId_I, descType); - const Regions & regions_J = regionsPerView.getRegions(viewId_J, descType); - - std::vector> homographiesAndMatches; - matching::IndMatches outGeometricInliers; - filterMatchesByHGrowing(regions_I.Features(), - regions_J.Features(), - putativeMatchesPerType.at(descType), - homographiesAndMatches, - outGeometricInliers, - _parameters); - - out_geometricInliersPerType.emplace(descType, outGeometricInliers); - _HsAndMatchesPerDesc.emplace(descType, homographiesAndMatches); - - if (outputSvgDir.empty()) - { - continue; - } - - if (boost::filesystem::exists(outputSvgDir)) - { - const std::size_t nbMatches = outGeometricInliers.size(); - const std::string name = std::to_string(nbMatches) + "hmatches_" + std::to_string(viewI.getViewId()) + "_" + - std::to_string(viewJ.getViewId()) + - "_" + EImageDescriberType_enumToString(descType) + ".svg"; - // @FIXME not worth it having boost::filesystem in a header - const std::string outFilename = (boost::filesystem::path(outputSvgDir) / boost::filesystem::path(name)).string(); - drawHomographyMatches(viewI, - viewJ, - regions_I.Features(), - regions_J.Features(), - homographiesAndMatches, - putativeMatchesPerType.at(descType), - outFilename); - } - else - { - ALICEVISION_LOG_WARNING("Cannot save homography-growing matches into '" << outputSvgDir << "': folder does not exist."); - } - - } // 'descriptor' - - // Check if resection has strong support - if (out_geometricInliersPerType.empty()) - return EstimationStatus(true, false); - else - return EstimationStatus(true, true); - } - - /** - * @brief Geometry_guided_matching - * @param sfm_data - * @param regionsPerView - * @param pairIndex - * @param dDistanceRatio - * @param matches - * @return - */ - bool Geometry_guided_matching(const sfmData::SfMData *sfmData, - const feature::RegionsPerView ®ionsPerView, - const Pair imageIdsPair, - const double dDistanceRatio, - matching::MatchesPerDescType &matches) override - { - - /* ... */ - return matches.getNbAllMatches() != 0; - } - - /** - * @brief Get the number of descriptor types including verified matches by the - * homography growing method. - * @return The number of descriptor types - */ - inline std::size_t getNbDescTypes() const {return _HsAndMatchesPerDesc.size();} - - /** - * @brief The number of estimated homographies for the given descriptor. - * @param[in] descType The wished descriptor type - * @return The number of estimated homographies (= planes). Return 0 if there is - * no homography or if the descriptor type does not exist. - */ - std::size_t getNbHomographies(const feature::EImageDescriberType & descType) const; - - /** - * @brief Get the number of matches associated to the given descriptor type & homography index. - * @param[in] descType Descriptor type. - * @param[in] homographyId The id. of the wished homography / plane. - * @return The number of matches. Return 0 if the descriptor type or the homography - * index do not exist in the result. - */ - std::size_t getNbVerifiedMatches(const feature::EImageDescriberType & descType, IndexT homographyId) const; - - /** - * @brief Get the number of verified matches for every descriptor and associated homographies. - * @return The nb of verified matches - */ - std::size_t getNbAllVerifiedMatches() const; - - /** - * @brief Get a copy of the matches for a given desc. type & homography index. - * @param[in] descType The descriptor type. - * @param[in] homographyId The id. of the wished homography / plane. - * @param[in] matches Contains the matches. - * @return true the number of matches is up to 0. - */ - bool getMatches(const feature::EImageDescriberType & descType, const IndexT homographyId, matching::IndMatches & matches) const; - -private: - - // -- Results container - - /// The estimated homographies and their associated planar matches, - /// for each descriptor type. - std::map>> - _HsAndMatchesPerDesc; - - //-- Parameters - HGrowingFilteringParam _parameters{}; - -}; // struct GeometricFilterMatrix_HGrowing - - -} // namespace matchingImageCollection -} // namespace aliceVision - + /* ... */ + return matches.getNbAllMatches() != 0; + } + /** + * @brief Get the number of descriptor types including verified matches by the + * homography growing method. + * @return The number of descriptor types + */ + inline std::size_t getNbDescTypes() const { return _HsAndMatchesPerDesc.size(); } + + /** + * @brief The number of estimated homographies for the given descriptor. + * @param[in] descType The wished descriptor type + * @return The number of estimated homographies (= planes). Return 0 if there is + * no homography or if the descriptor type does not exist. + */ + std::size_t getNbHomographies(const feature::EImageDescriberType& descType) const; + + /** + * @brief Get the number of matches associated to the given descriptor type & homography index. + * @param[in] descType Descriptor type. + * @param[in] homographyId The id. of the wished homography / plane. + * @return The number of matches. Return 0 if the descriptor type or the homography + * index do not exist in the result. + */ + std::size_t getNbVerifiedMatches(const feature::EImageDescriberType& descType, IndexT homographyId) const; + + /** + * @brief Get the number of verified matches for every descriptor and associated homographies. + * @return The nb of verified matches + */ + std::size_t getNbAllVerifiedMatches() const; + + /** + * @brief Get a copy of the matches for a given desc. type & homography index. + * @param[in] descType The descriptor type. + * @param[in] homographyId The id. of the wished homography / plane. + * @param[in] matches Contains the matches. + * @return true the number of matches is up to 0. + */ + bool getMatches(const feature::EImageDescriberType& descType, const IndexT homographyId, matching::IndMatches& matches) const; + + private: + // -- Results container + + /// The estimated homographies and their associated planar matches, + /// for each descriptor type. + std::map>> _HsAndMatchesPerDesc; + + //-- Parameters + HGrowingFilteringParam _parameters{}; + +}; // struct GeometricFilterMatrix_HGrowing + +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_H_AC.hpp b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_H_AC.hpp index e033796d11..cfae09783c 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_H_AC.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterMatrix_H_AC.hpp @@ -27,211 +27,211 @@ namespace matchingImageCollection { */ struct GeometricFilterMatrix_H_AC : public GeometricFilterMatrix { - GeometricFilterMatrix_H_AC(double dPrecision = std::numeric_limits::infinity(), - std::size_t iteration = 1024) - : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration) - , m_H(Mat3::Identity()) - {} - - /** - * @brief Given two sets of image points, it estimates the homography matrix - * relating them using a robust method (like A Contrario Ransac). - */ - template - EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, - const Regions_or_Features_ProviderT& regionsPerView, - const Pair& pairIndex, - const matching::MatchesPerDescType& putativeMatchesPerType, - std::mt19937 &randomNumberGenerator, - matching::MatchesPerDescType& out_geometricInliersPerType) - { - out_geometricInliersPerType.clear(); - - // get back corresponding view index - const IndexT I = pairIndex.first; - const IndexT J = pairIndex.second; - - const sfmData::View& viewI = sfmData->getView(I); - const sfmData::View& viewJ = sfmData->getView(J); - - const std::vector descTypes = regionsPerView.getCommonDescTypes(pairIndex); - - if(descTypes.empty()) - return EstimationStatus(false, false); - - // retrieve all 2D features as undistorted positions into flat arrays - Mat xI, xJ; - fillMatricesWithUndistortFeaturesMatches(pairIndex, putativeMatchesPerType, sfmData, regionsPerView, descTypes, xI, xJ); - - // define the AContrario adapted Homography matrix solver - using KernelT = multiview::RelativePoseKernel< - multiview::relativePose::Homography4PSolver, - multiview::relativePose::HomographyAsymmetricError, - multiview::UnnormalizerI, - robustEstimation::Mat3Model>; - - const KernelT kernel(xI, viewI.getImage().getWidth(), viewI.getImage().getHeight(), - xJ, viewJ.getImage().getWidth(), viewJ.getImage().getHeight(), false); // configure as point to point error model. - - // robustly estimate the Homography matrix with A Contrario ransac - const double upperBoundPrecision = m_dPrecision; - - std::vector inliers; - robustEstimation::Mat3Model model; - const std::pair ACRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, inliers, m_stIteration, &model, upperBoundPrecision); - m_H = model.getMatrix(); - - if (inliers.empty()) - return EstimationStatus(false, false); - - m_dPrecision_robust = ACRansacOut.first; - - // fill geometricInliersPerType with inliers from putativeMatchesPerType - copyInlierMatches(inliers, - putativeMatchesPerType, - descTypes, - out_geometricInliersPerType); - - // check if resection has strong support - const bool hasStrongSupport = matching::hasStrongSupport(out_geometricInliersPerType, kernel.getMinimumNbRequiredSamples()); - - return EstimationStatus(true, hasStrongSupport); - } - - /** - * @brief Export point feature based vector to a matrix [(x,y)'T, (x,y)'T]. - * Use the camera intrinsics in order to get undistorted pixel coordinates - */ - template - static void fillMatricesWithUndistortFeatures(const camera::IntrinsicBase* cam, - const feature::PointFeatures& vec_feats, - MatT& m) - { - using Scalar = typename MatT::Scalar; // output matrix type - - const bool hasValidIntrinsics = cam && cam->isValid(); - std::size_t i = 0; - - if(hasValidIntrinsics) + GeometricFilterMatrix_H_AC(double dPrecision = std::numeric_limits::infinity(), std::size_t iteration = 1024) + : GeometricFilterMatrix(dPrecision, std::numeric_limits::infinity(), iteration), + m_H(Mat3::Identity()) + {} + + /** + * @brief Given two sets of image points, it estimates the homography matrix + * relating them using a robust method (like A Contrario Ransac). + */ + template + EstimationStatus geometricEstimation(const sfmData::SfMData* sfmData, + const Regions_or_Features_ProviderT& regionsPerView, + const Pair& pairIndex, + const matching::MatchesPerDescType& putativeMatchesPerType, + std::mt19937& randomNumberGenerator, + matching::MatchesPerDescType& out_geometricInliersPerType) { - for(feature::PointFeatures::const_iterator iter = vec_feats.begin(); iter != vec_feats.end(); ++iter, ++i) - m.col(i) = cam->get_ud_pixel(Vec2(iter->x(), iter->y())); - } - else - { - for(feature::PointFeatures::const_iterator iter = vec_feats.begin(); iter != vec_feats.end(); ++iter, ++i) - m.col(i) = iter->coords().cast(); - } - } + out_geometricInliersPerType.clear(); - template - static void createMatricesWithUndistortFeatures(const camera::IntrinsicBase* cam, const feature::MapRegionsPerDesc& regionsPerDesc, MatT& m) - { - std::size_t nbRegions = 0; - for(const auto& regions: regionsPerDesc) - nbRegions += regions.second->RegionCount(); + // get back corresponding view index + const IndexT I = pairIndex.first; + const IndexT J = pairIndex.second; - m.resize(2, nbRegions); + const sfmData::View& viewI = sfmData->getView(I); + const sfmData::View& viewJ = sfmData->getView(J); - std::size_t y = 0; - for(const auto& regions: regionsPerDesc) - { - fillMatricesWithUndistortFeatures(cam, regions.second, m.block(0, y, 2, regions.second->RegionCount())); - y += regions.second->RegionCount(); - } - } - - template - static void createMatricesWithUndistortFeatures(const camera::IntrinsicBase* cam, - const feature::PointFeatures& vec_feats, - MatT& m) - { - std::size_t nbRegions = vec_feats.size(); - m.resize(2, nbRegions); - fillMatricesWithUndistortFeatures(cam, vec_feats, m); - } - - /** - * @brief Geometry_guided_matching - * @param sfm_data - * @param regionsPerView - * @param pairIndex - * @param dDistanceRatio - * @param matches - * @return - */ - bool Geometry_guided_matching(const sfmData::SfMData* sfmData, - const feature::RegionsPerView& regionsPerView, - const Pair imageIdsPair, - const double dDistanceRatio, - matching::MatchesPerDescType& matches) override - { - if(m_dPrecision_robust != std::numeric_limits::infinity()) - { - const std::vector descTypes = regionsPerView.getCommonDescTypes(imageIdsPair); + const std::vector descTypes = regionsPerView.getCommonDescTypes(pairIndex); - if(descTypes.empty()) - return false; + if (descTypes.empty()) + return EstimationStatus(false, false); - // get back corresponding view index - const IndexT I = imageIdsPair.first; - const IndexT J = imageIdsPair.second; + // retrieve all 2D features as undistorted positions into flat arrays + Mat xI, xJ; + fillMatricesWithUndistortFeaturesMatches(pairIndex, putativeMatchesPerType, sfmData, regionsPerView, descTypes, xI, xJ); - const sfmData::View& viewI = sfmData->getView(I); - const sfmData::View& viewJ = sfmData->getView(J); + // define the AContrario adapted Homography matrix solver + using KernelT = multiview::RelativePoseKernel; - // retrieve corresponding pair camera intrinsic if any - const camera::IntrinsicBase* camI = sfmData->getIntrinsics().count(viewI.getIntrinsicId()) ? - sfmData->getIntrinsics().at(viewI.getIntrinsicId()).get() : nullptr; - const camera::IntrinsicBase* camJ = sfmData->getIntrinsics().count(viewJ.getIntrinsicId()) ? - sfmData->getIntrinsics().at(viewJ.getIntrinsicId()).get() : nullptr; + const KernelT kernel(xI, + viewI.getImage().getWidth(), + viewI.getImage().getHeight(), + xJ, + viewJ.getImage().getWidth(), + viewJ.getImage().getHeight(), + false); // configure as point to point error model. - robustEstimation::Mat3Model model(m_H); + // robustly estimate the Homography matrix with A Contrario ransac + const double upperBoundPrecision = m_dPrecision; - if(dDistanceRatio < 0) - { - for(const feature::EImageDescriberType descType : descTypes) - { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - matching::IndMatches localMatches; + std::vector inliers; + robustEstimation::Mat3Model model; + const std::pair ACRansacOut = + robustEstimation::ACRANSAC(kernel, randomNumberGenerator, inliers, m_stIteration, &model, upperBoundPrecision); + m_H = model.getMatrix(); + + if (inliers.empty()) + return EstimationStatus(false, false); - const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); - const feature::Regions& regionsJ = regionsPerView.getRegions(J, descType); - const feature::PointFeatures pointsFeaturesI = regionsI.GetRegionsPositions(); - const feature::PointFeatures pointsFeaturesJ = regionsJ.GetRegionsPositions(); + m_dPrecision_robust = ACRansacOut.first; - // filtering based only on region positions - Mat xI, xJ; - createMatricesWithUndistortFeatures(camI, pointsFeaturesI, xI); - createMatricesWithUndistortFeatures(camJ, pointsFeaturesJ, xJ); + // fill geometricInliersPerType with inliers from putativeMatchesPerType + copyInlierMatches(inliers, putativeMatchesPerType, descTypes, out_geometricInliersPerType); - matching::guidedMatching(model, xI, xJ, Square(m_dPrecision_robust), localMatches); + // check if resection has strong support + const bool hasStrongSupport = matching::hasStrongSupport(out_geometricInliersPerType, kernel.getMinimumNbRequiredSamples()); - // remove matches that have the same (X,Y) coordinates - matching::IndMatchDecorator matchDeduplicator(localMatches, pointsFeaturesI, pointsFeaturesJ); - matchDeduplicator.getDeduplicated(localMatches); - matches[descType] = localMatches; + return EstimationStatus(true, hasStrongSupport); + } + + /** + * @brief Export point feature based vector to a matrix [(x,y)'T, (x,y)'T]. + * Use the camera intrinsics in order to get undistorted pixel coordinates + */ + template + static void fillMatricesWithUndistortFeatures(const camera::IntrinsicBase* cam, const feature::PointFeatures& vec_feats, MatT& m) + { + using Scalar = typename MatT::Scalar; // output matrix type + + const bool hasValidIntrinsics = cam && cam->isValid(); + std::size_t i = 0; + + if (hasValidIntrinsics) + { + for (feature::PointFeatures::const_iterator iter = vec_feats.begin(); iter != vec_feats.end(); ++iter, ++i) + m.col(i) = cam->get_ud_pixel(Vec2(iter->x(), iter->y())); + } + else + { + for (feature::PointFeatures::const_iterator iter = vec_feats.begin(); iter != vec_feats.end(); ++iter, ++i) + m.col(i) = iter->coords().cast(); } - } - else - { - // filtering based on region positions and regions descriptors - matching::guidedMatching(model, - camI, regionsPerView.getAllRegions(I), - camJ, regionsPerView.getAllRegions(J), - Square(m_dPrecision_robust), Square(dDistanceRatio), - matches); - } } - return matches.getNbAllMatches() != 0; - } + template + static void createMatricesWithUndistortFeatures(const camera::IntrinsicBase* cam, const feature::MapRegionsPerDesc& regionsPerDesc, MatT& m) + { + std::size_t nbRegions = 0; + for (const auto& regions : regionsPerDesc) + nbRegions += regions.second->RegionCount(); - // stored data - Mat3 m_H; -}; + m.resize(2, nbRegions); + + std::size_t y = 0; + for (const auto& regions : regionsPerDesc) + { + fillMatricesWithUndistortFeatures(cam, regions.second, m.block(0, y, 2, regions.second->RegionCount())); + y += regions.second->RegionCount(); + } + } -} // namespace matchingImageCollection -} // namespace aliceVision + template + static void createMatricesWithUndistortFeatures(const camera::IntrinsicBase* cam, const feature::PointFeatures& vec_feats, MatT& m) + { + std::size_t nbRegions = vec_feats.size(); + m.resize(2, nbRegions); + fillMatricesWithUndistortFeatures(cam, vec_feats, m); + } + /** + * @brief Geometry_guided_matching + * @param sfm_data + * @param regionsPerView + * @param pairIndex + * @param dDistanceRatio + * @param matches + * @return + */ + bool Geometry_guided_matching(const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const Pair imageIdsPair, + const double dDistanceRatio, + matching::MatchesPerDescType& matches) override + { + if (m_dPrecision_robust != std::numeric_limits::infinity()) + { + const std::vector descTypes = regionsPerView.getCommonDescTypes(imageIdsPair); + + if (descTypes.empty()) + return false; + + // get back corresponding view index + const IndexT I = imageIdsPair.first; + const IndexT J = imageIdsPair.second; + + const sfmData::View& viewI = sfmData->getView(I); + const sfmData::View& viewJ = sfmData->getView(J); + + // retrieve corresponding pair camera intrinsic if any + const camera::IntrinsicBase* camI = + sfmData->getIntrinsics().count(viewI.getIntrinsicId()) ? sfmData->getIntrinsics().at(viewI.getIntrinsicId()).get() : nullptr; + const camera::IntrinsicBase* camJ = + sfmData->getIntrinsics().count(viewJ.getIntrinsicId()) ? sfmData->getIntrinsics().at(viewJ.getIntrinsicId()).get() : nullptr; + + robustEstimation::Mat3Model model(m_H); + + if (dDistanceRatio < 0) + { + for (const feature::EImageDescriberType descType : descTypes) + { + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + matching::IndMatches localMatches; + + const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); + const feature::Regions& regionsJ = regionsPerView.getRegions(J, descType); + const feature::PointFeatures pointsFeaturesI = regionsI.GetRegionsPositions(); + const feature::PointFeatures pointsFeaturesJ = regionsJ.GetRegionsPositions(); + + // filtering based only on region positions + Mat xI, xJ; + createMatricesWithUndistortFeatures(camI, pointsFeaturesI, xI); + createMatricesWithUndistortFeatures(camJ, pointsFeaturesJ, xJ); + + matching::guidedMatching( + model, xI, xJ, Square(m_dPrecision_robust), localMatches); + + // remove matches that have the same (X,Y) coordinates + matching::IndMatchDecorator matchDeduplicator(localMatches, pointsFeaturesI, pointsFeaturesJ); + matchDeduplicator.getDeduplicated(localMatches); + matches[descType] = localMatches; + } + } + else + { + // filtering based on region positions and regions descriptors + matching::guidedMatching( + model, + camI, + regionsPerView.getAllRegions(I), + camJ, + regionsPerView.getAllRegions(J), + Square(m_dPrecision_robust), + Square(dDistanceRatio), + matches); + } + } + + return matches.getNbAllMatches() != 0; + } + + // stored data + Mat3 m_H; +}; +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/GeometricFilterType.hpp b/src/aliceVision/matchingImageCollection/GeometricFilterType.hpp index dcb1ddc6a1..bd9c791e6a 100644 --- a/src/aliceVision/matchingImageCollection/GeometricFilterType.hpp +++ b/src/aliceVision/matchingImageCollection/GeometricFilterType.hpp @@ -16,12 +16,12 @@ namespace matchingImageCollection { enum class EGeometricFilterType { - NO_FILTERING - , FUNDAMENTAL_MATRIX - , FUNDAMENTAL_WITH_DISTORTION - , ESSENTIAL_MATRIX - , HOMOGRAPHY_MATRIX - , HOMOGRAPHY_GROWING + NO_FILTERING, + FUNDAMENTAL_MATRIX, + FUNDAMENTAL_WITH_DISTORTION, + ESSENTIAL_MATRIX, + HOMOGRAPHY_MATRIX, + HOMOGRAPHY_GROWING }; /** @@ -30,13 +30,13 @@ enum class EGeometricFilterType */ inline std::string EGeometricFilterType_informations() { - return "Geometric filter type: Pairwise correspondences filtering thanks to robust model estimation:\n" - "* no_filtering: no geometric filtering\n" - "* fundamental_matrix: fundamental matrix\n" - "* fundamental_with_distortion: fundamental matrix with F10 solver [Z.Kukelova, 2015]\n" - "* essential_matrix: essential matrix\n" - "* homography_matrix: homography matrix\n" - "* homography_growing: multiple homography matrices [F.Srajer, 2016]\n"; + return "Geometric filter type: Pairwise correspondences filtering thanks to robust model estimation:\n" + "* no_filtering: no geometric filtering\n" + "* fundamental_matrix: fundamental matrix\n" + "* fundamental_with_distortion: fundamental matrix with F10 solver [Z.Kukelova, 2015]\n" + "* essential_matrix: essential matrix\n" + "* homography_matrix: homography matrix\n" + "* homography_growing: multiple homography matrices [F.Srajer, 2016]\n"; } /** @@ -46,16 +46,22 @@ inline std::string EGeometricFilterType_informations() */ inline std::string EGeometricFilterType_enumToString(EGeometricFilterType geometricFilterType) { - switch(geometricFilterType) - { - case EGeometricFilterType::NO_FILTERING: return "no_filtering"; - case EGeometricFilterType::FUNDAMENTAL_MATRIX: return "fundamental_matrix"; - case EGeometricFilterType::FUNDAMENTAL_WITH_DISTORTION: return "fundamental_with_distortion"; - case EGeometricFilterType::ESSENTIAL_MATRIX: return "essential_matrix"; - case EGeometricFilterType::HOMOGRAPHY_MATRIX: return "homography_matrix"; - case EGeometricFilterType::HOMOGRAPHY_GROWING: return "homography_growing"; - } - throw std::out_of_range("Invalid geometricFilterType enum"); + switch (geometricFilterType) + { + case EGeometricFilterType::NO_FILTERING: + return "no_filtering"; + case EGeometricFilterType::FUNDAMENTAL_MATRIX: + return "fundamental_matrix"; + case EGeometricFilterType::FUNDAMENTAL_WITH_DISTORTION: + return "fundamental_with_distortion"; + case EGeometricFilterType::ESSENTIAL_MATRIX: + return "essential_matrix"; + case EGeometricFilterType::HOMOGRAPHY_MATRIX: + return "homography_matrix"; + case EGeometricFilterType::HOMOGRAPHY_GROWING: + return "homography_growing"; + } + throw std::out_of_range("Invalid geometricFilterType enum"); } /** @@ -65,32 +71,38 @@ inline std::string EGeometricFilterType_enumToString(EGeometricFilterType geomet */ inline EGeometricFilterType EGeometricFilterType_stringToEnum(const std::string& geometricFilterType) { - std::string model = geometricFilterType; - std::transform(model.begin(), model.end(), model.begin(), ::tolower); //tolower + std::string model = geometricFilterType; + std::transform(model.begin(), model.end(), model.begin(), ::tolower); // tolower - if(model == "no_filtering") return EGeometricFilterType::NO_FILTERING; - if(model == "fundamental_matrix") return EGeometricFilterType::FUNDAMENTAL_MATRIX; - if(model == "fundamental_with_distortion") return EGeometricFilterType::FUNDAMENTAL_WITH_DISTORTION; - if(model == "essential_matrix") return EGeometricFilterType::ESSENTIAL_MATRIX; - if(model == "homography_matrix") return EGeometricFilterType::HOMOGRAPHY_MATRIX; - if(model == "homography_growing") return EGeometricFilterType::HOMOGRAPHY_GROWING; + if (model == "no_filtering") + return EGeometricFilterType::NO_FILTERING; + if (model == "fundamental_matrix") + return EGeometricFilterType::FUNDAMENTAL_MATRIX; + if (model == "fundamental_with_distortion") + return EGeometricFilterType::FUNDAMENTAL_WITH_DISTORTION; + if (model == "essential_matrix") + return EGeometricFilterType::ESSENTIAL_MATRIX; + if (model == "homography_matrix") + return EGeometricFilterType::HOMOGRAPHY_MATRIX; + if (model == "homography_growing") + return EGeometricFilterType::HOMOGRAPHY_GROWING; - throw std::out_of_range("Invalid geometricFilterType: " + geometricFilterType); + throw std::out_of_range("Invalid geometricFilterType: " + geometricFilterType); } inline std::ostream& operator<<(std::ostream& os, const EGeometricFilterType geometricFilterType) { - os << EGeometricFilterType_enumToString(geometricFilterType); - return os; + os << EGeometricFilterType_enumToString(geometricFilterType); + return os; } inline std::istream& operator>>(std::istream& in, EGeometricFilterType& geometricFilterType) { - std::string token; - in >> token; - geometricFilterType = EGeometricFilterType_stringToEnum(token); - return in; + std::string token; + in >> token; + geometricFilterType = EGeometricFilterType_stringToEnum(token); + return in; } -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/IImageCollectionMatcher.hpp b/src/aliceVision/matchingImageCollection/IImageCollectionMatcher.hpp index 3dd1d8662e..00fdf1cb96 100644 --- a/src/aliceVision/matchingImageCollection/IImageCollectionMatcher.hpp +++ b/src/aliceVision/matchingImageCollection/IImageCollectionMatcher.hpp @@ -28,19 +28,18 @@ namespace matchingImageCollection { class IImageCollectionMatcher { public: - IImageCollectionMatcher() = default; + IImageCollectionMatcher() = default; - virtual ~IImageCollectionMatcher() = default; + virtual ~IImageCollectionMatcher() = default; - /// Find corresponding points between some pair of view Ids - virtual void Match( - std::mt19937 & randomNumberGenerator, - const feature::RegionsPerView& regionsPerView, - const PairSet & pairs, // list of pair to consider for matching - feature::EImageDescriberType descType, - matching::PairwiseMatches & map_putatives_matches // the output pairwise photometric corresponding points + /// Find corresponding points between some pair of view Ids + virtual void Match(std::mt19937& randomNumberGenerator, + const feature::RegionsPerView& regionsPerView, + const PairSet& pairs, // list of pair to consider for matching + feature::EImageDescriberType descType, + matching::PairwiseMatches& map_putatives_matches // the output pairwise photometric corresponding points ) const = 0; }; -} // namespace aliceVision -} // namespace matchingImageCollection +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.cpp b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.cpp index 43a38b1dbc..c363fe9240 100644 --- a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.cpp +++ b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.cpp @@ -18,242 +18,210 @@ namespace matchingImageCollection { using namespace aliceVision::matching; using namespace aliceVision::feature; -ImageCollectionMatcher_cascadeHashing -::ImageCollectionMatcher_cascadeHashing -( - float distRatio -):IImageCollectionMatcher(), f_dist_ratio_(distRatio) -{ -} - -namespace impl -{ -template -void Match -( - std::mt19937 & gen, - const feature::RegionsPerView& regionsPerView, - const PairSet & pairs, - EImageDescriberType descType, - float fDistRatio, - PairwiseMatches & map_PutativesMatches // the pairwise photometric corresponding points +ImageCollectionMatcher_cascadeHashing ::ImageCollectionMatcher_cascadeHashing(float distRatio) + : IImageCollectionMatcher(), + f_dist_ratio_(distRatio) +{} + +namespace impl { +template +void Match(std::mt19937& gen, + const feature::RegionsPerView& regionsPerView, + const PairSet& pairs, + EImageDescriberType descType, + float fDistRatio, + PairwiseMatches& map_PutativesMatches // the pairwise photometric corresponding points ) { - auto progressDisplay = system::createConsoleProgressDisplay(pairs.size(), std::cout); - - // Collect used view indexes - std::set used_index; - // Sort pairs according the first index to minimize later memory swapping - typedef std::map > Map_vectorT; - Map_vectorT map_Pairs; - for (PairSet::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) - { - map_Pairs[iter->first].push_back(iter->second); - used_index.insert(iter->first); - used_index.insert(iter->second); - } - - typedef Eigen::Matrix BaseMat; - - // Init the cascade hasher - CascadeHasher cascade_hasher; - if (!used_index.empty()) - { - const IndexT I = *used_index.begin(); - const feature::Regions ®ionsI = regionsPerView.getRegions(I, descType); - const size_t dimension = regionsI.DescriptorLength(); - cascade_hasher.Init(gen, dimension); - } - - std::map hashed_base_; - - // Compute the zero mean descriptor that will be used for hashing (one for all the image regions) - Eigen::VectorXf zero_mean_descriptor; - { - Eigen::MatrixXf matForZeroMean; - for (int i =0; i < used_index.size(); ++i) + auto progressDisplay = system::createConsoleProgressDisplay(pairs.size(), std::cout); + + // Collect used view indexes + std::set used_index; + // Sort pairs according the first index to minimize later memory swapping + typedef std::map> Map_vectorT; + Map_vectorT map_Pairs; + for (PairSet::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) { - std::set::const_iterator iter = used_index.begin(); - std::advance(iter, i); - const IndexT I = *iter; - const feature::Regions ®ionsI = regionsPerView.getRegions(I, descType); - const ScalarT * tabI = - reinterpret_cast(regionsI.DescriptorRawData()); - const size_t dimension = regionsI.DescriptorLength(); - if (i==0) - { - matForZeroMean.resize(used_index.size(), dimension); - matForZeroMean.fill(0.0f); - } - if (regionsI.RegionCount() > 0) - { - Eigen::Map mat_I( (ScalarT*)tabI, regionsI.RegionCount(), dimension); - matForZeroMean.row(i) = CascadeHasher::GetZeroMeanDescriptor(mat_I); - } + map_Pairs[iter->first].push_back(iter->second); + used_index.insert(iter->first); + used_index.insert(iter->second); } - zero_mean_descriptor = CascadeHasher::GetZeroMeanDescriptor(matForZeroMean); - } - // Index the input regions - #pragma omp parallel for schedule(dynamic) - for (int i =0; i < used_index.size(); ++i) - { - std::set::const_iterator iter = used_index.begin(); - std::advance(iter, i); - const IndexT I = *iter; - const feature::Regions ®ionsI = regionsPerView.getRegions(I, descType); - const ScalarT * tabI = - reinterpret_cast(regionsI.DescriptorRawData()); - const size_t dimension = regionsI.DescriptorLength(); + typedef Eigen::Matrix BaseMat; - Eigen::Map mat_I( (ScalarT*)tabI, regionsI.RegionCount(), dimension); - HashedDescriptions hashed_description = cascade_hasher.CreateHashedDescriptions(mat_I, - zero_mean_descriptor); - #pragma omp critical + // Init the cascade hasher + CascadeHasher cascade_hasher; + if (!used_index.empty()) { - hashed_base_[I] = std::move(hashed_description); + const IndexT I = *used_index.begin(); + const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); + const size_t dimension = regionsI.DescriptorLength(); + cascade_hasher.Init(gen, dimension); } - } - // Perform matching between all the pairs - for (Map_vectorT::const_iterator iter = map_Pairs.begin(); - iter != map_Pairs.end(); ++iter) - { - const IndexT I = iter->first; - const std::vector & indexToCompare = iter->second; + std::map hashed_base_; - const feature::Regions ®ionsI = regionsPerView.getRegions(I, descType); - if (regionsI.RegionCount() == 0) + // Compute the zero mean descriptor that will be used for hashing (one for all the image regions) + Eigen::VectorXf zero_mean_descriptor; { - progressDisplay += indexToCompare.size(); - continue; + Eigen::MatrixXf matForZeroMean; + for (int i = 0; i < used_index.size(); ++i) + { + std::set::const_iterator iter = used_index.begin(); + std::advance(iter, i); + const IndexT I = *iter; + const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); + const ScalarT* tabI = reinterpret_cast(regionsI.DescriptorRawData()); + const size_t dimension = regionsI.DescriptorLength(); + if (i == 0) + { + matForZeroMean.resize(used_index.size(), dimension); + matForZeroMean.fill(0.0f); + } + if (regionsI.RegionCount() > 0) + { + Eigen::Map mat_I((ScalarT*)tabI, regionsI.RegionCount(), dimension); + matForZeroMean.row(i) = CascadeHasher::GetZeroMeanDescriptor(mat_I); + } + } + zero_mean_descriptor = CascadeHasher::GetZeroMeanDescriptor(matForZeroMean); } - const std::vector pointFeaturesI = regionsI.GetRegionsPositions(); - const ScalarT * tabI = - reinterpret_cast(regionsI.DescriptorRawData()); - const size_t dimension = regionsI.DescriptorLength(); - Eigen::Map mat_I( (ScalarT*)tabI, regionsI.RegionCount(), dimension); - #pragma omp parallel for schedule(dynamic) - for (int j = 0; j < (int)indexToCompare.size(); ++j) +// Index the input regions +#pragma omp parallel for schedule(dynamic) + for (int i = 0; i < used_index.size(); ++i) { - size_t J = indexToCompare[j]; - const feature::Regions ®ionsJ = regionsPerView.getRegions(I, descType); - - if (!regionsPerView.viewExist(J) - || regionsI.Type_id() != regionsJ.Type_id()) - { - ++progressDisplay; - continue; - } - - // Matrix representation of the query input data; - const ScalarT * tabJ = reinterpret_cast(regionsJ.DescriptorRawData()); - Eigen::Map mat_J( (ScalarT*)tabJ, regionsJ.RegionCount(), dimension); - - IndMatches pvec_indices; - typedef typename Accumulator::Type ResultType; - std::vector pvec_distances; - pvec_distances.reserve(regionsJ.RegionCount() * 2); - pvec_indices.reserve(regionsJ.RegionCount() * 2); - - // Match the query descriptors to the database - cascade_hasher.Match_HashedDescriptions( - hashed_base_[J], mat_J, - hashed_base_[I], mat_I, - &pvec_indices, &pvec_distances); - - std::vector vec_nn_ratio_idx; - // Filter the matches using a distance ratio test: - // The probability that a match is correct is determined by taking - // the ratio of distance from the closest neighbor to the distance - // of the second closest. - matching::NNdistanceRatio( - pvec_distances.begin(), // distance start - pvec_distances.end(), // distance end - 2, // Number of neighbor in iterator sequence (minimum required 2) - vec_nn_ratio_idx, // output (indices that respect the distance Ratio) - Square(fDistRatio)); - - matching::IndMatches vec_putative_matches; - vec_putative_matches.reserve(vec_nn_ratio_idx.size()); - for (size_t k=0; k < vec_nn_ratio_idx.size(); ++k) - { - const size_t index = vec_nn_ratio_idx[k]; - vec_putative_matches.emplace_back(pvec_indices[index*2]._j, pvec_indices[index*2]._i); - } + std::set::const_iterator iter = used_index.begin(); + std::advance(iter, i); + const IndexT I = *iter; + const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); + const ScalarT* tabI = reinterpret_cast(regionsI.DescriptorRawData()); + const size_t dimension = regionsI.DescriptorLength(); + + Eigen::Map mat_I((ScalarT*)tabI, regionsI.RegionCount(), dimension); + HashedDescriptions hashed_description = cascade_hasher.CreateHashedDescriptions(mat_I, zero_mean_descriptor); +#pragma omp critical + { + hashed_base_[I] = std::move(hashed_description); + } + } - // Remove duplicates - matching::IndMatch::getDeduplicated(vec_putative_matches); + // Perform matching between all the pairs + for (Map_vectorT::const_iterator iter = map_Pairs.begin(); iter != map_Pairs.end(); ++iter) + { + const IndexT I = iter->first; + const std::vector& indexToCompare = iter->second; - // Remove matches that have the same (X,Y) coordinates - const std::vector pointFeaturesJ = regionsJ.GetRegionsPositions(); - matching::IndMatchDecorator matchDeduplicator(vec_putative_matches, - pointFeaturesI, pointFeaturesJ); - matchDeduplicator.getDeduplicated(vec_putative_matches); + const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); + if (regionsI.RegionCount() == 0) + { + progressDisplay += indexToCompare.size(); + continue; + } - #pragma omp critical - { - ++progressDisplay; - if (!vec_putative_matches.empty()) + const std::vector pointFeaturesI = regionsI.GetRegionsPositions(); + const ScalarT* tabI = reinterpret_cast(regionsI.DescriptorRawData()); + const size_t dimension = regionsI.DescriptorLength(); + Eigen::Map mat_I((ScalarT*)tabI, regionsI.RegionCount(), dimension); +#pragma omp parallel for schedule(dynamic) + for (int j = 0; j < (int)indexToCompare.size(); ++j) { - assert(map_PutativesMatches.count(std::make_pair(I,J)) == 0); - map_PutativesMatches[std::make_pair(I,J)].emplace(descType, std::move(vec_putative_matches)); + size_t J = indexToCompare[j]; + const feature::Regions& regionsJ = regionsPerView.getRegions(I, descType); + + if (!regionsPerView.viewExist(J) || regionsI.Type_id() != regionsJ.Type_id()) + { + ++progressDisplay; + continue; + } + + // Matrix representation of the query input data; + const ScalarT* tabJ = reinterpret_cast(regionsJ.DescriptorRawData()); + Eigen::Map mat_J((ScalarT*)tabJ, regionsJ.RegionCount(), dimension); + + IndMatches pvec_indices; + typedef typename Accumulator::Type ResultType; + std::vector pvec_distances; + pvec_distances.reserve(regionsJ.RegionCount() * 2); + pvec_indices.reserve(regionsJ.RegionCount() * 2); + + // Match the query descriptors to the database + cascade_hasher.Match_HashedDescriptions( + hashed_base_[J], mat_J, hashed_base_[I], mat_I, &pvec_indices, &pvec_distances); + + std::vector vec_nn_ratio_idx; + // Filter the matches using a distance ratio test: + // The probability that a match is correct is determined by taking + // the ratio of distance from the closest neighbor to the distance + // of the second closest. + matching::NNdistanceRatio(pvec_distances.begin(), // distance start + pvec_distances.end(), // distance end + 2, // Number of neighbor in iterator sequence (minimum required 2) + vec_nn_ratio_idx, // output (indices that respect the distance Ratio) + Square(fDistRatio)); + + matching::IndMatches vec_putative_matches; + vec_putative_matches.reserve(vec_nn_ratio_idx.size()); + for (size_t k = 0; k < vec_nn_ratio_idx.size(); ++k) + { + const size_t index = vec_nn_ratio_idx[k]; + vec_putative_matches.emplace_back(pvec_indices[index * 2]._j, pvec_indices[index * 2]._i); + } + + // Remove duplicates + matching::IndMatch::getDeduplicated(vec_putative_matches); + + // Remove matches that have the same (X,Y) coordinates + const std::vector pointFeaturesJ = regionsJ.GetRegionsPositions(); + matching::IndMatchDecorator matchDeduplicator(vec_putative_matches, pointFeaturesI, pointFeaturesJ); + matchDeduplicator.getDeduplicated(vec_putative_matches); + +#pragma omp critical + { + ++progressDisplay; + if (!vec_putative_matches.empty()) + { + assert(map_PutativesMatches.count(std::make_pair(I, J)) == 0); + map_PutativesMatches[std::make_pair(I, J)].emplace(descType, std::move(vec_putative_matches)); + } + } } - } } - } } -} // namespace impl +} // namespace impl -void ImageCollectionMatcher_cascadeHashing::Match -( - std::mt19937 & gen, - const feature::RegionsPerView& regionsPerView, - const PairSet & pairs, - feature::EImageDescriberType descType, - PairwiseMatches & map_PutativesMatches // the pairwise photometric corresponding points +void ImageCollectionMatcher_cascadeHashing::Match(std::mt19937& gen, + const feature::RegionsPerView& regionsPerView, + const PairSet& pairs, + feature::EImageDescriberType descType, + PairwiseMatches& map_PutativesMatches // the pairwise photometric corresponding points ) const { - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENMP) - ALICEVISION_LOG_DEBUG("Using the OPENMP thread interface"); + ALICEVISION_LOG_DEBUG("Using the OPENMP thread interface"); #endif - if (regionsPerView.isEmpty()) - return; + if (regionsPerView.isEmpty()) + return; - const feature::Regions& regions = regionsPerView.getFirstViewRegions(descType); + const feature::Regions& regions = regionsPerView.getFirstViewRegions(descType); - if (regions.IsBinary()) - return; + if (regions.IsBinary()) + return; - if(regions.Type_id() == typeid(unsigned char).name()) - { - impl::Match( - gen, - regionsPerView, - pairs, - descType, - f_dist_ratio_, - map_PutativesMatches); - } - else - if(regions.Type_id() == typeid(float).name()) - { - impl::Match( - gen, - regionsPerView, - pairs, - descType, - f_dist_ratio_, - map_PutativesMatches); - } - else - { - ALICEVISION_LOG_WARNING("Matcher not implemented for this region type"); - } + if (regions.Type_id() == typeid(unsigned char).name()) + { + impl::Match(gen, regionsPerView, pairs, descType, f_dist_ratio_, map_PutativesMatches); + } + else if (regions.Type_id() == typeid(float).name()) + { + impl::Match(gen, regionsPerView, pairs, descType, f_dist_ratio_, map_PutativesMatches); + } + else + { + ALICEVISION_LOG_WARNING("Matcher not implemented for this region type"); + } } -} // namespace aliceVision -} // namespace matchingImageCollection +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.hpp b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.hpp index b70b842002..92c9abf2d6 100644 --- a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.hpp +++ b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_cascadeHashing.hpp @@ -24,24 +24,20 @@ namespace matchingImageCollection { class ImageCollectionMatcher_cascadeHashing : public IImageCollectionMatcher { public: - ImageCollectionMatcher_cascadeHashing - ( - float dist_ratio - ); + ImageCollectionMatcher_cascadeHashing(float dist_ratio); - /// Find corresponding points between some pair of view Ids - void Match( - std::mt19937 & randomNumberGenerator, - const feature::RegionsPerView& regionsPerView, - const PairSet & pairs, - feature::EImageDescriberType descType, - matching::PairwiseMatches & map_PutativesMatches // the pairwise photometric corresponding points - ) const; + /// Find corresponding points between some pair of view Ids + void Match(std::mt19937& randomNumberGenerator, + const feature::RegionsPerView& regionsPerView, + const PairSet& pairs, + feature::EImageDescriberType descType, + matching::PairwiseMatches& map_PutativesMatches // the pairwise photometric corresponding points + ) const; private: - // Distance ratio used to discard spurious correspondence - float f_dist_ratio_; + // Distance ratio used to discard spurious correspondence + float f_dist_ratio_; }; -} // namespace aliceVision -} // namespace matchingImageCollection +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.cpp b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.cpp index f54d755497..cd1455bb31 100644 --- a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.cpp +++ b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.cpp @@ -20,112 +20,107 @@ namespace matchingImageCollection { using namespace aliceVision::matching; using namespace aliceVision::feature; -ImageCollectionMatcher_generic::ImageCollectionMatcher_generic( - float distRatio, bool crossMatching, EMatcherType matcherType) - : IImageCollectionMatcher() - , _f_dist_ratio(distRatio) - , _useCrossMatching(crossMatching) - , _matcherType(matcherType) -{ -} - -void ImageCollectionMatcher_generic::Match( - std::mt19937 & randomNumberGenerator, - const feature::RegionsPerView& regionsPerView, - const PairSet & pairs, - feature::EImageDescriberType descType, - matching::PairwiseMatches & map_PutativesMatches)const // the pairwise photometric corresponding points +ImageCollectionMatcher_generic::ImageCollectionMatcher_generic(float distRatio, bool crossMatching, EMatcherType matcherType) + : IImageCollectionMatcher(), + _f_dist_ratio(distRatio), + _useCrossMatching(crossMatching), + _matcherType(matcherType) +{} + +void ImageCollectionMatcher_generic::Match(std::mt19937& randomNumberGenerator, + const feature::RegionsPerView& regionsPerView, + const PairSet& pairs, + feature::EImageDescriberType descType, + matching::PairwiseMatches& map_PutativesMatches) const // the pairwise photometric corresponding points { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENMP) - ALICEVISION_LOG_DEBUG("Using the OPENMP thread interface"); + ALICEVISION_LOG_DEBUG("Using the OPENMP thread interface"); #endif - const bool b_multithreaded_pair_search = (_matcherType == CASCADE_HASHING_L2); - // -> set to true for CASCADE_HASHING_L2, since OpenMP instructions are not used in this matcher - - auto progressDisplay = system::createConsoleProgressDisplay(pairs.size(), std::cout); - - // Sort pairs according the first index to minimize the MatcherT build operations - typedef std::map > Map_vectorT; - Map_vectorT map_Pairs; - for (PairSet::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) - { - map_Pairs[iter->first].push_back(iter->second); - } - - // Perform matching between all the pairs - for (Map_vectorT::const_iterator iter = map_Pairs.begin(); - iter != map_Pairs.end(); ++iter) - { - const size_t I = iter->first; - const std::vector & indexToCompare = iter->second; - - const feature::Regions & regionsI = regionsPerView.getRegions(I, descType); - if (regionsI.RegionCount() == 0) - { - progressDisplay += indexToCompare.size(); - continue; - } + const bool b_multithreaded_pair_search = (_matcherType == CASCADE_HASHING_L2); + // -> set to true for CASCADE_HASHING_L2, since OpenMP instructions are not used in this matcher - // Initialize the matching interface - matching::RegionsDatabaseMatcher matcher(randomNumberGenerator, _matcherType, regionsI); + auto progressDisplay = system::createConsoleProgressDisplay(pairs.size(), std::cout); - #pragma omp parallel for schedule(dynamic) if(b_multithreaded_pair_search) - for (int j = 0; j < (int)indexToCompare.size(); ++j) + // Sort pairs according the first index to minimize the MatcherT build operations + typedef std::map> Map_vectorT; + Map_vectorT map_Pairs; + for (PairSet::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) { - const size_t J = indexToCompare[j]; - - const feature::Regions ®ionsJ = regionsPerView.getRegions(J, descType); - if (regionsJ.RegionCount() == 0 - || regionsI.Type_id() != regionsJ.Type_id()) - { - ++progressDisplay; - continue; - } - - IndMatches vec_putatives_matches; - matcher.Match(_f_dist_ratio, regionsJ, vec_putatives_matches); - - if (_useCrossMatching) - { - // Initialize the matching interface - matching::RegionsDatabaseMatcher matcherCross(randomNumberGenerator, _matcherType, regionsJ); - - IndMatches vec_putatives_matches_cross; - matcherCross.Match(_f_dist_ratio, regionsI, vec_putatives_matches_cross); + map_Pairs[iter->first].push_back(iter->second); + } - //Create a dictionnary of matches indexed by their pair of indexes - std::map, IndMatch> check_matches; - for (IndMatch & m : vec_putatives_matches_cross) - { - std::pair key = std::make_pair(m._i, m._j); - check_matches[key] = m; - } + // Perform matching between all the pairs + for (Map_vectorT::const_iterator iter = map_Pairs.begin(); iter != map_Pairs.end(); ++iter) + { + const size_t I = iter->first; + const std::vector& indexToCompare = iter->second; - IndMatches vec_putatives_matches_checked; - for (IndMatch & m : vec_putatives_matches) + const feature::Regions& regionsI = regionsPerView.getRegions(I, descType); + if (regionsI.RegionCount() == 0) { - //Check with reversed key (images are swapped) - std::pair key = std::make_pair(m._j, m._i); - if (check_matches.find(key) != check_matches.end()) - { - vec_putatives_matches_checked.push_back(m); - } + progressDisplay += indexToCompare.size(); + continue; } - std::swap(vec_putatives_matches, vec_putatives_matches_checked); - } + // Initialize the matching interface + matching::RegionsDatabaseMatcher matcher(randomNumberGenerator, _matcherType, regionsI); - #pragma omp critical - { - ++progressDisplay; - if (!vec_putatives_matches.empty()) +#pragma omp parallel for schedule(dynamic) if (b_multithreaded_pair_search) + for (int j = 0; j < (int)indexToCompare.size(); ++j) { - map_PutativesMatches[std::make_pair(I,J)].emplace(descType, std::move(vec_putatives_matches)); + const size_t J = indexToCompare[j]; + + const feature::Regions& regionsJ = regionsPerView.getRegions(J, descType); + if (regionsJ.RegionCount() == 0 || regionsI.Type_id() != regionsJ.Type_id()) + { + ++progressDisplay; + continue; + } + + IndMatches vec_putatives_matches; + matcher.Match(_f_dist_ratio, regionsJ, vec_putatives_matches); + + if (_useCrossMatching) + { + // Initialize the matching interface + matching::RegionsDatabaseMatcher matcherCross(randomNumberGenerator, _matcherType, regionsJ); + + IndMatches vec_putatives_matches_cross; + matcherCross.Match(_f_dist_ratio, regionsI, vec_putatives_matches_cross); + + // Create a dictionnary of matches indexed by their pair of indexes + std::map, IndMatch> check_matches; + for (IndMatch& m : vec_putatives_matches_cross) + { + std::pair key = std::make_pair(m._i, m._j); + check_matches[key] = m; + } + + IndMatches vec_putatives_matches_checked; + for (IndMatch& m : vec_putatives_matches) + { + // Check with reversed key (images are swapped) + std::pair key = std::make_pair(m._j, m._i); + if (check_matches.find(key) != check_matches.end()) + { + vec_putatives_matches_checked.push_back(m); + } + } + + std::swap(vec_putatives_matches, vec_putatives_matches_checked); + } + +#pragma omp critical + { + ++progressDisplay; + if (!vec_putatives_matches.empty()) + { + map_PutativesMatches[std::make_pair(I, J)].emplace(descType, std::move(vec_putatives_matches)); + } + } } - } } - } } -} // namespace aliceVision -} // namespace matchingImageCollection +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.hpp b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.hpp index 5a2d1af87e..b0b7093223 100644 --- a/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.hpp +++ b/src/aliceVision/matchingImageCollection/ImageCollectionMatcher_generic.hpp @@ -23,29 +23,24 @@ namespace matchingImageCollection { class ImageCollectionMatcher_generic : public IImageCollectionMatcher { public: - ImageCollectionMatcher_generic( - float dist_ratio, - bool crossMatching, - matching::EMatcherType matcherType - ); + ImageCollectionMatcher_generic(float dist_ratio, bool crossMatching, matching::EMatcherType matcherType); - /// Find corresponding points between some pair of view Ids - void Match( - std::mt19937 & randomNumberGenerator, - const feature::RegionsPerView& regionsPerView, - const PairSet & pairs, - feature::EImageDescriberType descType, - matching::PairwiseMatches & map_PutativesMatches // the pairwise photometric corresponding points + /// Find corresponding points between some pair of view Ids + void Match(std::mt19937& randomNumberGenerator, + const feature::RegionsPerView& regionsPerView, + const PairSet& pairs, + feature::EImageDescriberType descType, + matching::PairwiseMatches& map_PutativesMatches // the pairwise photometric corresponding points ) const; private: - // Distance ratio used to discard spurious correspondence - float _f_dist_ratio; - // Do we use cross matching (Symmetric matching test) ? - bool _useCrossMatching; - // Matcher Type - matching::EMatcherType _matcherType; + // Distance ratio used to discard spurious correspondence + float _f_dist_ratio; + // Do we use cross matching (Symmetric matching test) ? + bool _useCrossMatching; + // Matcher Type + matching::EMatcherType _matcherType; }; -} // namespace aliceVision -} // namespace matchingImageCollection +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp b/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp index fa8aa77e2f..9cd15baf39 100644 --- a/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp +++ b/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp @@ -14,10 +14,7 @@ namespace aliceVision { namespace matchingImageCollection { -bool loadPairs(std::istream& stream, - PairSet & pairs, - int rangeStart, - int rangeSize) +bool loadPairs(std::istream& stream, PairSet& pairs, int rangeStart, int rangeSize) { std::size_t nbLine = 0; std::string sValue; @@ -43,26 +40,26 @@ bool loadPairs(std::istream& stream, return false; } std::stringstream oss; - oss.clear(); oss.str(vec_str[0]); + oss.clear(); + oss.str(vec_str[0]); size_t I, J; oss >> I; - for (size_t i=1; i> J; - if( I == J ) + if (I == J) { - ALICEVISION_LOG_WARNING("loadPairs: Invalid input file. Image " << I - << " sees itself."); + ALICEVISION_LOG_WARNING("loadPairs: Invalid input file. Image " << I << " sees itself."); return false; } Pair pairToInsert = (I < J) ? std::make_pair(I, J) : std::make_pair(J, I); - if(pairs.find(pairToInsert) != pairs.end()) + if (pairs.find(pairToInsert) != pairs.end()) { // There is no reason to have the same image pair twice in the list of image pairs - //to match. - ALICEVISION_LOG_WARNING("loadPairs: image pair (" << I << ", " << J - << ") already added."); + // to match. + ALICEVISION_LOG_WARNING("loadPairs: image pair (" << I << ", " << J << ") already added."); } ALICEVISION_LOG_INFO("loadPairs: image pair (" << I << ", " << J << ") added."); pairs.insert(pairToInsert); @@ -71,7 +68,7 @@ bool loadPairs(std::istream& stream, return true; } -void savePairs(std::ostream& stream, const PairSet & pairs) +void savePairs(std::ostream& stream, const PairSet& pairs) { if (pairs.empty()) { @@ -97,7 +94,7 @@ void savePairs(std::ostream& stream, const PairSet & pairs) stream << "\n"; } -bool loadPairsFromFile(const std::string& sFileName, // filename of the list file, +bool loadPairsFromFile(const std::string& sFileName, // filename of the list file, PairSet& pairs, int rangeStart, int rangeSize) @@ -105,8 +102,7 @@ bool loadPairsFromFile(const std::string& sFileName, // filename of the list fil std::ifstream in(sFileName); if (!in.is_open()) { - ALICEVISION_LOG_WARNING("loadPairsFromFile: Impossible to read the specified file: \"" - << sFileName << "\"."); + ALICEVISION_LOG_WARNING("loadPairsFromFile: Impossible to read the specified file: \"" << sFileName << "\"."); return false; } @@ -123,8 +119,7 @@ bool savePairsToFile(const std::string& sFileName, const PairSet& pairs) std::ofstream outStream(sFileName); if (!outStream.is_open()) { - ALICEVISION_LOG_WARNING("savePairsToFile: Impossible to open the output specified file: \"" - << sFileName << "\"."); + ALICEVISION_LOG_WARNING("savePairsToFile: Impossible to open the output specified file: \"" << sFileName << "\"."); return false; } @@ -133,5 +128,5 @@ bool savePairsToFile(const std::string& sFileName, const PairSet& pairs) return !outStream.bad(); } -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp b/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp index 612847cb77..c9762de0af 100644 --- a/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp +++ b/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp @@ -13,10 +13,7 @@ namespace matchingImageCollection { /// Load a set of PairSet from a stream /// I J K L (pair that link I) -bool loadPairs(std::istream& stream, - PairSet& pairs, - int rangeStart=-1, - int rangeSize=0); +bool loadPairs(std::istream& stream, PairSet& pairs, int rangeStart = -1, int rangeSize = 0); /// Save a set of PairSet to a stream (one pair per line) /// I J @@ -24,7 +21,7 @@ bool loadPairs(std::istream& stream, void savePairs(std::ostream& stream, const PairSet& pairs); /// Same as loadPairs, but loads from a given file -bool loadPairsFromFile(const std::string& sFileName, // filename of the list file, +bool loadPairsFromFile(const std::string& sFileName, // filename of the list file, PairSet& pairs, int rangeStart = -1, int rangeSize = 0); @@ -32,5 +29,5 @@ bool loadPairsFromFile(const std::string& sFileName, // filename of the list fil /// Same as savePairs, but saves to a given file bool savePairsToFile(const std::string& sFileName, const PairSet& pairs); -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp b/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp index 331744b880..b3c77b039b 100644 --- a/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp +++ b/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp @@ -18,32 +18,26 @@ using namespace aliceVision::matchingImageCollection; BOOST_AUTO_TEST_CASE(read_write_pairs_to_file) { PairSet pairSetGT; - pairSetGT.insert( std::make_pair(0,1) ); - pairSetGT.insert( std::make_pair(1,2) ); - pairSetGT.insert( std::make_pair(2,0) ); + pairSetGT.insert(std::make_pair(0, 1)); + pairSetGT.insert(std::make_pair(1, 2)); + pairSetGT.insert(std::make_pair(2, 0)); PairSet pairSetGTsorted; - pairSetGTsorted.insert( std::make_pair(0,1) ); - pairSetGTsorted.insert( std::make_pair(0,2) ); - pairSetGTsorted.insert( std::make_pair(1,2) ); + pairSetGTsorted.insert(std::make_pair(0, 1)); + pairSetGTsorted.insert(std::make_pair(0, 2)); + pairSetGTsorted.insert(std::make_pair(1, 2)); BOOST_CHECK(savePairsToFile("pairsT_IO.txt", pairSetGT)); PairSet loaded_Pairs; BOOST_CHECK(loadPairsFromFile("pairsT_IO.txt", loaded_Pairs)); - BOOST_CHECK( std::equal(loaded_Pairs.begin(), loaded_Pairs.end(), pairSetGTsorted.begin()) ); + BOOST_CHECK(std::equal(loaded_Pairs.begin(), loaded_Pairs.end(), pairSetGTsorted.begin())); std::remove("pairsT_IO.txt"); } - BOOST_AUTO_TEST_CASE(save_pairs) { - PairSet pairs = { - {0, 2}, {0, 4}, {0, 5}, - {8, 2}, - {0, 1}, - {5, 9} - }; + PairSet pairs = {{0, 2}, {0, 4}, {0, 5}, {8, 2}, {0, 1}, {5, 9}}; std::stringstream output; savePairs(output, pairs); @@ -61,11 +55,6 @@ BOOST_AUTO_TEST_CASE(load_multiple_pairs_per_line) PairSet loadedPairs; BOOST_CHECK(loadPairs(input, loadedPairs)); - PairSet expectedPairs = { - {0, 2}, {0, 4}, {0, 5}, - {0, 1}, - {5, 9} - }; + PairSet expectedPairs = {{0, 2}, {0, 4}, {0, 5}, {0, 1}, {5, 9}}; BOOST_CHECK(loadedPairs == expectedPairs); } - diff --git a/src/aliceVision/matchingImageCollection/geometricFilterUtils.cpp b/src/aliceVision/matchingImageCollection/geometricFilterUtils.cpp index 7958dd05d7..075bdc0952 100644 --- a/src/aliceVision/matchingImageCollection/geometricFilterUtils.cpp +++ b/src/aliceVision/matchingImageCollection/geometricFilterUtils.cpp @@ -10,328 +10,319 @@ namespace aliceVision { namespace matchingImageCollection { -void copyInlierMatches(const std::vector &inliers, - const matching::MatchesPerDescType &putativeMatchesPerType, - const std::vector &descTypes, - matching::MatchesPerDescType &out_geometricInliersPerType) +void copyInlierMatches(const std::vector& inliers, + const matching::MatchesPerDescType& putativeMatchesPerType, + const std::vector& descTypes, + matching::MatchesPerDescType& out_geometricInliersPerType) { - std::vector orderedInliers = inliers; - std::sort(orderedInliers.begin(), orderedInliers.end()); + std::vector orderedInliers = inliers; + std::sort(orderedInliers.begin(), orderedInliers.end()); - size_t currentDescType = 0; - size_t currentDescTypeStartIndex = 0; - auto currentDescTypeMaxLength = putativeMatchesPerType.getNbMatches(descTypes[currentDescType]); + size_t currentDescType = 0; + size_t currentDescTypeStartIndex = 0; + auto currentDescTypeMaxLength = putativeMatchesPerType.getNbMatches(descTypes[currentDescType]); - for(const size_t globalInlierIndex : orderedInliers) - { - while (globalInlierIndex >= currentDescTypeMaxLength) + for (const size_t globalInlierIndex : orderedInliers) { - ++currentDescType; - currentDescTypeStartIndex = currentDescTypeMaxLength; - currentDescTypeMaxLength += putativeMatchesPerType.getNbMatches(descTypes[currentDescType]); + while (globalInlierIndex >= currentDescTypeMaxLength) + { + ++currentDescType; + currentDescTypeStartIndex = currentDescTypeMaxLength; + currentDescTypeMaxLength += putativeMatchesPerType.getNbMatches(descTypes[currentDescType]); + } + const size_t localIndex = globalInlierIndex - currentDescTypeStartIndex; + const auto descType = descTypes[currentDescType]; + out_geometricInliersPerType[descType].push_back(putativeMatchesPerType.at(descType)[localIndex]); } - const size_t localIndex = globalInlierIndex - currentDescTypeStartIndex; - const auto descType = descTypes[currentDescType]; - out_geometricInliersPerType[descType].push_back(putativeMatchesPerType.at(descType)[localIndex]); - } } -void centerMatrix(const Eigen::Matrix2Xf & points2d, Mat3 & t) +void centerMatrix(const Eigen::Matrix2Xf& points2d, Mat3& t) { - t = Mat3::Identity(); + t = Mat3::Identity(); - const Vec2f mean = points2d.rowwise().mean(); - const auto nbPoints = points2d.cols(); + const Vec2f mean = points2d.rowwise().mean(); + const auto nbPoints = points2d.cols(); - Vec2f stdDev = ((points2d.colwise() - mean).cwiseAbs2().rowwise().sum()/(nbPoints - 1)).cwiseSqrt(); + Vec2f stdDev = ((points2d.colwise() - mean).cwiseAbs2().rowwise().sum() / (nbPoints - 1)).cwiseSqrt(); - if(stdDev(0) < 0.1) - stdDev(0) = 0.1; - if(stdDev(1) < 0.1) - stdDev(1) = 0.1; + if (stdDev(0) < 0.1) + stdDev(0) = 0.1; + if (stdDev(1) < 0.1) + stdDev(1) = 0.1; - t << 1./stdDev(0), 0., -mean(0)/stdDev(0), - 0., 1./stdDev(1), -mean(1)/stdDev(1), - 0., 0., 1.; + t << 1. / stdDev(0), 0., -mean(0) / stdDev(0), 0., 1. / stdDev(1), -mean(1) / stdDev(1), 0., 0., 1.; } - -void centeringMatrices(const std::vector & featuresI, - const std::vector & featuresJ, - const matching::IndMatches & matches, - Mat3 & cI, - Mat3 & cJ, - const std::set & usefulMatchesId) +void centeringMatrices(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + Mat3& cI, + Mat3& cJ, + const std::set& usefulMatchesId) { - assert(!featuresI.empty()); - assert(!featuresJ.empty()); - assert(!matches.empty()); - assert(*std::max_element(usefulMatchesId.begin(), usefulMatchesId.end()) <= matches.size()); // prevent segfault - - std::set matchesId = usefulMatchesId; // duplicate - std::size_t nbMatches = usefulMatchesId.size(); - - if (usefulMatchesId.empty()) - { - nbMatches = matches.size(); - // set every match as useful for estimation - for (IndexT i = 0; i < nbMatches; ++i) - matchesId.insert(i); - } - - int iMatch = 0; - Matf ptsI(2, nbMatches); - Matf ptsJ(2, nbMatches); - - for (IndexT matchId : matchesId) - { - ptsI.col(iMatch) = featuresI.at(matches.at(matchId)._i).coords(); - ptsJ.col(iMatch) = featuresJ.at(matches.at(matchId)._j).coords(); - ++iMatch; - } - - centerMatrix(ptsI, cI); - centerMatrix(ptsJ, cJ); + assert(!featuresI.empty()); + assert(!featuresJ.empty()); + assert(!matches.empty()); + assert(*std::max_element(usefulMatchesId.begin(), usefulMatchesId.end()) <= matches.size()); // prevent segfault + + std::set matchesId = usefulMatchesId; // duplicate + std::size_t nbMatches = usefulMatchesId.size(); + + if (usefulMatchesId.empty()) + { + nbMatches = matches.size(); + // set every match as useful for estimation + for (IndexT i = 0; i < nbMatches; ++i) + matchesId.insert(i); + } + + int iMatch = 0; + Matf ptsI(2, nbMatches); + Matf ptsJ(2, nbMatches); + + for (IndexT matchId : matchesId) + { + ptsI.col(iMatch) = featuresI.at(matches.at(matchId)._i).coords(); + ptsJ.col(iMatch) = featuresJ.at(matches.at(matchId)._j).coords(); + ++iMatch; + } + + centerMatrix(ptsI, cI); + centerMatrix(ptsJ, cJ); } -void computeSimilarity(const feature::PointFeature & feat1, - const feature::PointFeature & feat2, - Mat3 & S) +void computeSimilarity(const feature::PointFeature& feat1, const feature::PointFeature& feat2, Mat3& S) { - S = Mat3::Identity(); - - const Vec2f & coord1 = feat1.coords(); - const double scale1 = feat1.scale(); - const double orientation1 = feat1.orientation(); - - const Vec2f & coord2 = feat2.coords(); - const double scale2 = feat2.scale(); - const double orientation2 = feat2.orientation(); - - const double c1 = cos(orientation1); - const double s1 = sin(orientation1); - const double c2 = cos(orientation2); - const double s2 = sin(orientation2); - - Mat3 A1; - A1 << scale1 * c1, scale1 * (-s1), coord1(0), - scale1 * s1, scale1 * c1, coord1(1), - 0, 0, 1; - Mat3 A2; - A2 << scale2 * c2, scale2 * (-s2), coord2(0), - scale2 * s2, scale2 * c2, coord2(1), - 0, 0, 1; - - S = A2 * A1.inverse(); + S = Mat3::Identity(); + + const Vec2f& coord1 = feat1.coords(); + const double scale1 = feat1.scale(); + const double orientation1 = feat1.orientation(); + + const Vec2f& coord2 = feat2.coords(); + const double scale2 = feat2.scale(); + const double orientation2 = feat2.orientation(); + + const double c1 = cos(orientation1); + const double s1 = sin(orientation1); + const double c2 = cos(orientation2); + const double s2 = sin(orientation2); + + Mat3 A1; + A1 << scale1 * c1, scale1 * (-s1), coord1(0), scale1 * s1, scale1 * c1, coord1(1), 0, 0, 1; + Mat3 A2; + A2 << scale2 * c2, scale2 * (-s2), coord2(0), scale2 * s2, scale2 * c2, coord2(1), 0, 0, 1; + + S = A2 * A1.inverse(); } -void estimateAffinity(const std::vector & featuresI, - const std::vector & featuresJ, - const matching::IndMatches & matches, - Mat3 & affineTransformation, - const std::set & usefulMatchesId) +void estimateAffinity(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + Mat3& affineTransformation, + const std::set& usefulMatchesId) { - assert(!featuresI.empty()); - assert(!featuresJ.empty()); - assert(!matches.empty()); - assert(*std::max_element(usefulMatchesId.begin(), usefulMatchesId.end()) <= matches.size()); // prevent segfault - - affineTransformation = Mat3::Identity(); - - std::set matchesId = usefulMatchesId; // duplicate - - std::size_t nbMatches = usefulMatchesId.size(); - - if (usefulMatchesId.empty()) - { - nbMatches = matches.size(); - // set every match as useful for estimation - for (IndexT i = 0; i < nbMatches; ++i) - matchesId.insert(i); - } - - Mat M(Mat::Zero(2*nbMatches,6)); - Vec b(2*nbMatches); - int iMatch = 0; - for (IndexT matchId : matchesId) - { - const feature::PointFeature & featI = featuresI.at(matches.at(matchId)._i); - const feature::PointFeature & featJ = featuresJ.at(matches.at(matchId)._j); - const Vec2 featICoords (featI.x(), featI.y()); - - M.block(iMatch,0,1,3) = featICoords.homogeneous().transpose(); - M.block(iMatch+nbMatches,3,1,3) = featICoords.homogeneous().transpose(); - b(iMatch) = featJ.x(); - b(iMatch+nbMatches) = featJ.y(); - - ++iMatch; - } - - const Vec a = M.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); - affineTransformation.row(0) = a.topRows(3).transpose(); - affineTransformation.row(1) = a.bottomRows(3).transpose(); - affineTransformation(2,0) = 0.; - affineTransformation(2,1) = 0.; - affineTransformation(2,2) = 1.; + assert(!featuresI.empty()); + assert(!featuresJ.empty()); + assert(!matches.empty()); + assert(*std::max_element(usefulMatchesId.begin(), usefulMatchesId.end()) <= matches.size()); // prevent segfault + + affineTransformation = Mat3::Identity(); + + std::set matchesId = usefulMatchesId; // duplicate + + std::size_t nbMatches = usefulMatchesId.size(); + + if (usefulMatchesId.empty()) + { + nbMatches = matches.size(); + // set every match as useful for estimation + for (IndexT i = 0; i < nbMatches; ++i) + matchesId.insert(i); + } + + Mat M(Mat::Zero(2 * nbMatches, 6)); + Vec b(2 * nbMatches); + int iMatch = 0; + for (IndexT matchId : matchesId) + { + const feature::PointFeature& featI = featuresI.at(matches.at(matchId)._i); + const feature::PointFeature& featJ = featuresJ.at(matches.at(matchId)._j); + const Vec2 featICoords(featI.x(), featI.y()); + + M.block(iMatch, 0, 1, 3) = featICoords.homogeneous().transpose(); + M.block(iMatch + nbMatches, 3, 1, 3) = featICoords.homogeneous().transpose(); + b(iMatch) = featJ.x(); + b(iMatch + nbMatches) = featJ.y(); + + ++iMatch; + } + + const Vec a = M.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); + affineTransformation.row(0) = a.topRows(3).transpose(); + affineTransformation.row(1) = a.bottomRows(3).transpose(); + affineTransformation(2, 0) = 0.; + affineTransformation(2, 1) = 0.; + affineTransformation(2, 2) = 1.; } -void estimateHomography(const std::vector &featuresI, - const std::vector &featuresJ, - const matching::IndMatches &matches, - Mat3 &H, - const std::set &usefulMatchesId) +void estimateHomography(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + Mat3& H, + const std::set& usefulMatchesId) { - assert(!featuresI.empty()); - assert(!featuresJ.empty()); - assert(!matches.empty()); - assert(*std::max_element(usefulMatchesId.begin(), usefulMatchesId.end()) <= matches.size()); // prevent segfault - - H = Mat3::Identity(); - - std::size_t nbMatches = usefulMatchesId.size(); - - std::set matchesId = usefulMatchesId; // duplicate - - if (usefulMatchesId.empty()) - { - nbMatches = matches.size(); - // set every match as useful for estimation - for (IndexT i = 0; i < nbMatches; ++i) - matchesId.insert(i); - } - - Mat3 CI, CJ; - centeringMatrices(featuresI, featuresJ, matches, CI, CJ, matchesId); - - Mat A(Mat::Zero(2*nbMatches,9)); - - IndexT iMatch = 0; - for(IndexT matchId : matchesId) - { - const feature::PointFeature & featI = featuresI.at(matches.at(matchId)._i); - const feature::PointFeature & featJ = featuresJ.at(matches.at(matchId)._j); - Vec2 fI(featI.x(), featI.y()); - Vec2 fJ(featJ.x(), featJ.y()); - Vec3 ptI = CI * fI.homogeneous(); - Vec3 ptJ = CJ * fJ.homogeneous(); - - A.block(iMatch,0,1,3) = ptI.transpose(); - A.block(iMatch,6,1,3) = -ptJ(0) * ptI.transpose(); - A.block(iMatch+nbMatches,3,1,3) = ptI.transpose(); - A.block(iMatch+nbMatches,6,1,3) = -ptJ(1) * ptI.transpose(); - ++iMatch; - } - - Eigen::JacobiSVD svd(A, Eigen::ComputeThinU | Eigen::ComputeFullV); - Vec h = svd.matrixV().rightCols(1); - Mat3 H0; - H0.row(0) = h.topRows(3).transpose(); - H0.row(1) = h.middleRows(3,3).transpose(); - H0.row(2) = h.bottomRows(3).transpose(); - - H = CJ.inverse() * H0 * CI; - if(std::fabs(H(2, 2)) > std::numeric_limits::epsilon()) - H /= H(2,2); + assert(!featuresI.empty()); + assert(!featuresJ.empty()); + assert(!matches.empty()); + assert(*std::max_element(usefulMatchesId.begin(), usefulMatchesId.end()) <= matches.size()); // prevent segfault + + H = Mat3::Identity(); + + std::size_t nbMatches = usefulMatchesId.size(); + + std::set matchesId = usefulMatchesId; // duplicate + + if (usefulMatchesId.empty()) + { + nbMatches = matches.size(); + // set every match as useful for estimation + for (IndexT i = 0; i < nbMatches; ++i) + matchesId.insert(i); + } + + Mat3 CI, CJ; + centeringMatrices(featuresI, featuresJ, matches, CI, CJ, matchesId); + + Mat A(Mat::Zero(2 * nbMatches, 9)); + + IndexT iMatch = 0; + for (IndexT matchId : matchesId) + { + const feature::PointFeature& featI = featuresI.at(matches.at(matchId)._i); + const feature::PointFeature& featJ = featuresJ.at(matches.at(matchId)._j); + Vec2 fI(featI.x(), featI.y()); + Vec2 fJ(featJ.x(), featJ.y()); + Vec3 ptI = CI * fI.homogeneous(); + Vec3 ptJ = CJ * fJ.homogeneous(); + + A.block(iMatch, 0, 1, 3) = ptI.transpose(); + A.block(iMatch, 6, 1, 3) = -ptJ(0) * ptI.transpose(); + A.block(iMatch + nbMatches, 3, 1, 3) = ptI.transpose(); + A.block(iMatch + nbMatches, 6, 1, 3) = -ptJ(1) * ptI.transpose(); + ++iMatch; + } + + Eigen::JacobiSVD svd(A, Eigen::ComputeThinU | Eigen::ComputeFullV); + Vec h = svd.matrixV().rightCols(1); + Mat3 H0; + H0.row(0) = h.topRows(3).transpose(); + H0.row(1) = h.middleRows(3, 3).transpose(); + H0.row(2) = h.bottomRows(3).transpose(); + + H = CJ.inverse() * H0 * CI; + if (std::fabs(H(2, 2)) > std::numeric_limits::epsilon()) + H /= H(2, 2); } -void findTransformationInliers(const std::vector &featuresI, - const std::vector &featuresJ, - const matching::IndMatches &matches, - const Mat3 &transformation, +void findTransformationInliers(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + const Mat3& transformation, double tolerance, - std::set &inliersId) + std::set& inliersId) { - inliersId.clear(); - const double squaredTolerance = Square(tolerance); + inliersId.clear(); + const double squaredTolerance = Square(tolerance); #pragma omp parallel for - for (int iMatch = 0; iMatch < matches.size(); ++iMatch) - { - const feature::PointFeature & featI = featuresI.at(matches.at(iMatch)._i); - const feature::PointFeature & featJ = featuresJ.at(matches.at(iMatch)._j); + for (int iMatch = 0; iMatch < matches.size(); ++iMatch) + { + const feature::PointFeature& featI = featuresI.at(matches.at(iMatch)._i); + const feature::PointFeature& featJ = featuresJ.at(matches.at(iMatch)._j); - const Vec2 ptI(featI.x(), featI.y()); - const Vec2 ptJ(featJ.x(), featJ.y()); + const Vec2 ptI(featI.x(), featI.y()); + const Vec2 ptJ(featJ.x(), featJ.y()); - const Vec3 ptIp_hom = transformation * ptI.homogeneous(); + const Vec3 ptIp_hom = transformation * ptI.homogeneous(); - const double dist = (ptJ - ptIp_hom.hnormalized()).squaredNorm(); + const double dist = (ptJ - ptIp_hom.hnormalized()).squaredNorm(); - if (dist < squaredTolerance) - { + if (dist < squaredTolerance) + { #pragma omp critical - inliersId.insert(iMatch); + inliersId.insert(iMatch); + } } - } } void findTransformationInliers(const Mat2X& featuresI, const Mat2X& featuresJ, - const matching::IndMatches &matches, - const Mat3 &transformation, + const matching::IndMatches& matches, + const Mat3& transformation, double tolerance, - std::set &inliersId) + std::set& inliersId) { - inliersId.clear(); - const double squaredTolerance = Square(tolerance); + inliersId.clear(); + const double squaredTolerance = Square(tolerance); #pragma omp parallel for - for (int iMatch = 0; iMatch < matches.size(); ++iMatch) - { - const matching::IndMatch& match = matches.at(iMatch); - const Vec2 & ptI = featuresI.col(match._i); - const Vec2 & ptJ = featuresJ.col(match._j); + for (int iMatch = 0; iMatch < matches.size(); ++iMatch) + { + const matching::IndMatch& match = matches.at(iMatch); + const Vec2& ptI = featuresI.col(match._i); + const Vec2& ptJ = featuresJ.col(match._j); - const Vec3 ptIp_hom = transformation * ptI.homogeneous(); + const Vec3 ptIp_hom = transformation * ptI.homogeneous(); - const double dist = (ptJ - ptIp_hom.hnormalized()).squaredNorm(); + const double dist = (ptJ - ptIp_hom.hnormalized()).squaredNorm(); - if (dist < squaredTolerance) - { + if (dist < squaredTolerance) + { #pragma omp critical - inliersId.insert(iMatch); + inliersId.insert(iMatch); + } } - } } /** * @brief This functor allows to optimize an Homography. - * @details It is based on [F.Srajer, 2016] p.20, 21 and its C++ implementation: https://github.com/fsrajer/yasfm/blob/master/YASFM/relative_pose.cpp#L992 - * "The optimization takes into account points with error close to the threshold and does not care about high-error ones." + * @details It is based on [F.Srajer, 2016] p.20, 21 and its C++ implementation: + * https://github.com/fsrajer/yasfm/blob/master/YASFM/relative_pose.cpp#L992 "The optimization takes into account points with error close to the + * threshold and does not care about high-error ones." */ class RefineHRobustCostFunctor { -public: - - RefineHRobustCostFunctor(const Vec2& x1, const Vec2& x2, - double softThresh) - : x1(x1),x2(x2),_softThresh(softThresh) - { - } + public: + RefineHRobustCostFunctor(const Vec2& x1, const Vec2& x2, double softThresh) + : x1(x1), + x2(x2), + _softThresh(softThresh) + {} template bool operator()(const T* const parameters, T* residuals) const { - using Mat3T = Eigen::Matrix; - using Vec3T = Eigen::Matrix; - using Vec2T = Eigen::Matrix; + using Mat3T = Eigen::Matrix; + using Vec3T = Eigen::Matrix; + using Vec2T = Eigen::Matrix; - const Vec2T x(T(x1(0)), T(x1(1))); - const Vec2T y(T(x2(0)), T(x2(1))); + const Vec2T x(T(x1(0)), T(x1(1))); + const Vec2T y(T(x2(0)), T(x2(1))); - const Mat3T H(parameters); + const Mat3T H(parameters); - const Vec3T xp = H * x.homogeneous(); + const Vec3T xp = H * x.homogeneous(); - const T errX = y(0) - xp(0)/xp(2); - const T errY = y(1) - xp(1)/xp(2); - const T errSq = errX*errX + errY*errY; + const T errX = y(0) - xp(0) / xp(2); + const T errY = y(1) - xp(1) / xp(2); + const T errSq = errX * errX + errY * errY; - // Avoid division by zero in derivatives computation - const T err = (errSq==0.) ? T(errSq) : T(sqrt(errSq)); - residuals[0] = robustify(_softThresh, err); + // Avoid division by zero in derivatives computation + const T err = (errSq == 0.) ? T(errSq) : T(sqrt(errSq)); + residuals[0] = robustify(_softThresh, err); - return true; + return true; } template @@ -344,33 +335,28 @@ class RefineHRobustCostFunctor */ static T robustify(double softThresh, T x) { - const double t = 0.25; - const double sigma = softThresh / std::sqrt(-std::log(t * t)); + const double t = 0.25; + const double sigma = softThresh / std::sqrt(-std::log(t * t)); - return -ceres::log(ceres::exp(-(x * x) / T(2.0 * sigma * sigma)) + T(t)) + T(std::log(1.0 + t)); + return -ceres::log(ceres::exp(-(x * x) / T(2.0 * sigma * sigma)) + T(t)) + T(std::log(1.0 + t)); } Vec2 x1, x2; double _softThresh; }; -bool refineHomography(const std::vector &featuresI, - const std::vector &featuresJ, +bool refineHomography(const std::vector& featuresI, + const std::vector& featuresJ, const matching::IndMatches& remainingMatches, Mat3& homography, std::set& bestMatchesId, double homographyTolerance) { - Mat2X pointsI; - Mat2X pointsJ; - feature::PointsToMat(featuresI, pointsI); - feature::PointsToMat(featuresJ, pointsJ); - return refineHomography(pointsI, - pointsJ, - remainingMatches, - homography, - bestMatchesId, - homographyTolerance); + Mat2X pointsI; + Mat2X pointsJ; + feature::PointsToMat(featuresI, pointsI); + feature::PointsToMat(featuresJ, pointsJ); + return refineHomography(pointsI, pointsJ, remainingMatches, homography, bestMatchesId, homographyTolerance); } bool refineHomography(const Mat2X& features_I, @@ -380,48 +366,41 @@ bool refineHomography(const Mat2X& features_I, std::set& bestMatchesId, double homographyTolerance) { - ceres::Problem problem; - // use a copy for the optimization to avoid changes in the input one - Mat3 tempHomography = homography; + ceres::Problem problem; + // use a copy for the optimization to avoid changes in the input one + Mat3 tempHomography = homography; - for(IndexT matchId : bestMatchesId) - { - const matching::IndMatch& match = remainingMatches.at(matchId); + for (IndexT matchId : bestMatchesId) + { + const matching::IndMatch& match = remainingMatches.at(matchId); - const Vec2& x1 = features_I.col(match._i); - const Vec2& x2 = features_J.col(match._j); + const Vec2& x1 = features_I.col(match._i); + const Vec2& x2 = features_J.col(match._j); - RefineHRobustCostFunctor* costFun = - new RefineHRobustCostFunctor(x1, x2, homographyTolerance); + RefineHRobustCostFunctor* costFun = new RefineHRobustCostFunctor(x1, x2, homographyTolerance); - problem.AddResidualBlock( - new ceres::AutoDiffCostFunction< - RefineHRobustCostFunctor, - 1, - 9>(costFun), - nullptr, - tempHomography.data()); - } + problem.AddResidualBlock(new ceres::AutoDiffCostFunction(costFun), nullptr, tempHomography.data()); + } - ceres::Solver::Options solverOpt; - solverOpt.max_num_iterations = 10; + ceres::Solver::Options solverOpt; + solverOpt.max_num_iterations = 10; - ceres::Solver::Summary summary; - ceres::Solve(solverOpt,&problem,&summary); + ceres::Solver::Summary summary; + ceres::Solve(solverOpt, &problem, &summary); - // if the optimization did not succeed return without changing - if(!summary.IsSolutionUsable()) - return false; + // if the optimization did not succeed return without changing + if (!summary.IsSolutionUsable()) + return false; - homography = tempHomography; + homography = tempHomography; - // normalize the homography - if(std::fabs(homography(2, 2)) > std::numeric_limits::epsilon()) - homography /= homography(2,2); + // normalize the homography + if (std::fabs(homography(2, 2)) > std::numeric_limits::epsilon()) + homography /= homography(2, 2); - findTransformationInliers(features_I, features_J, remainingMatches, homography, homographyTolerance, bestMatchesId); + findTransformationInliers(features_I, features_J, remainingMatches, homography, homographyTolerance, bestMatchesId); - return true; -} -} + return true; } +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/geometricFilterUtils.hpp b/src/aliceVision/matchingImageCollection/geometricFilterUtils.hpp index eca37eefe8..cb63abf887 100644 --- a/src/aliceVision/matchingImageCollection/geometricFilterUtils.hpp +++ b/src/aliceVision/matchingImageCollection/geometricFilterUtils.hpp @@ -17,15 +17,9 @@ namespace aliceVision { namespace matchingImageCollection { // TODO: remove PointFeature to avoid this hack -inline Vec2 getFeaturePosition(const std::unique_ptr& regions, std::size_t i) -{ - return regions->GetRegionPosition(i); -} +inline Vec2 getFeaturePosition(const std::unique_ptr& regions, std::size_t i) { return regions->GetRegionPosition(i); } -inline Vec2 getFeaturePosition(const feature::PointFeatures& features, std::size_t i) -{ - return features[i].coords().cast(); -} +inline Vec2 getFeaturePosition(const feature::PointFeatures& features, std::size_t i) { return features[i].coords().cast(); } /** * @brief Fill matrices with un-distorted feature positions @@ -39,36 +33,36 @@ inline Vec2 getFeaturePosition(const feature::PointFeatures& features, std::size * @param[out] x_J Pixel perfect features from the Jnth image putativeMatches matches */ template -void fillMatricesWithUndistortFeaturesMatches(const matching::IndMatches &putativeMatches, - const camera::IntrinsicBase *cam_I, - const FeatOrRegions &feature_I, - const camera::IntrinsicBase *cam_J, - const FeatOrRegions &feature_J, - MatT & x_I, MatT & x_J) +void fillMatricesWithUndistortFeaturesMatches(const matching::IndMatches& putativeMatches, + const camera::IntrinsicBase* cam_I, + const FeatOrRegions& feature_I, + const camera::IntrinsicBase* cam_J, + const FeatOrRegions& feature_J, + MatT& x_I, + MatT& x_J) { - typedef typename MatT::Scalar Scalar; // Output matrix type + typedef typename MatT::Scalar Scalar; // Output matrix type - const bool I_hasValidIntrinsics = cam_I && cam_I->isValid() && cam_I->hasDistortion(); - const bool J_hasValidIntrinsics = cam_J && cam_J->isValid() && cam_J->hasDistortion(); + const bool I_hasValidIntrinsics = cam_I && cam_I->isValid() && cam_I->hasDistortion(); + const bool J_hasValidIntrinsics = cam_J && cam_J->isValid() && cam_J->hasDistortion(); - for (size_t i = 0; i < putativeMatches.size(); ++i) - { - const Vec2 pt_I = getFeaturePosition(feature_I, putativeMatches[i]._i); - if (I_hasValidIntrinsics) - x_I.col(i) = cam_I->get_ud_pixel(pt_I); - else - x_I.col(i) = pt_I; - } - - for (size_t i = 0; i < putativeMatches.size(); ++i) - { - const Vec2 pt_J = getFeaturePosition(feature_J, putativeMatches[i]._j); - if (J_hasValidIntrinsics) - x_J.col(i) = cam_J->get_ud_pixel(pt_J); - else - x_J.col(i) = pt_J; - } + for (size_t i = 0; i < putativeMatches.size(); ++i) + { + const Vec2 pt_I = getFeaturePosition(feature_I, putativeMatches[i]._i); + if (I_hasValidIntrinsics) + x_I.col(i) = cam_I->get_ud_pixel(pt_I); + else + x_I.col(i) = pt_I; + } + for (size_t i = 0; i < putativeMatches.size(); ++i) + { + const Vec2 pt_J = getFeaturePosition(feature_J, putativeMatches[i]._j); + if (J_hasValidIntrinsics) + x_J.col(i) = cam_J->get_ud_pixel(pt_J); + else + x_J.col(i) = pt_J; + } } /** @@ -83,48 +77,44 @@ void fillMatricesWithUndistortFeaturesMatches(const matching::IndMatches &putati * @param[out] x_J Pixel perfect features from the Jnth image putativeMatches matches */ template -void fillMatricesWithUndistortFeaturesMatches(const matching::MatchesPerDescType &putativeMatchesPerType, - const camera::IntrinsicBase *cam_I, - const camera::IntrinsicBase *cam_J, - const MapFeatOrRegionPerDesc &features_I, - const MapFeatOrRegionPerDesc &features_J, - const std::vector &descTypes, - MatT &x_I, MatT &x_J) +void fillMatricesWithUndistortFeaturesMatches(const matching::MatchesPerDescType& putativeMatchesPerType, + const camera::IntrinsicBase* cam_I, + const camera::IntrinsicBase* cam_J, + const MapFeatOrRegionPerDesc& features_I, + const MapFeatOrRegionPerDesc& features_J, + const std::vector& descTypes, + MatT& x_I, + MatT& x_J) { - // Create the output matrices with all matched features for images I and J - const size_t n = putativeMatchesPerType.getNbAllMatches(); - x_I.resize(2, n); - x_J.resize(2, n); + // Create the output matrices with all matched features for images I and J + const size_t n = putativeMatchesPerType.getNbAllMatches(); + x_I.resize(2, n); + x_J.resize(2, n); - size_t y = 0; - for (const auto& descType : descTypes) - { - if(!putativeMatchesPerType.count(descType)) + size_t y = 0; + for (const auto& descType : descTypes) { - // we may have 0 feature for some descriptor types - continue; - } + if (!putativeMatchesPerType.count(descType)) + { + // we may have 0 feature for some descriptor types + continue; + } - const matching::IndMatches& putativeMatches = putativeMatchesPerType.at(descType); + const matching::IndMatches& putativeMatches = putativeMatchesPerType.at(descType); - const auto& feature_I = features_I.at(descType); - const auto& feature_J = features_J.at(descType); + const auto& feature_I = features_I.at(descType); + const auto& feature_J = features_J.at(descType); - // auto b_I = x_I.block(y, 0, putativeMatches.size(), 2); - // auto b_J = x_J.block(y, 0, putativeMatches.size(), 2); - auto subpart_I = x_I.block(0, y, 2, putativeMatches.size()); - auto subpart_J = x_J.block(0, y, 2, putativeMatches.size()); + // auto b_I = x_I.block(y, 0, putativeMatches.size(), 2); + // auto b_J = x_J.block(y, 0, putativeMatches.size(), 2); + auto subpart_I = x_I.block(0, y, 2, putativeMatches.size()); + auto subpart_J = x_J.block(0, y, 2, putativeMatches.size()); - // fill subpart of the matrices with undistorted features - fillMatricesWithUndistortFeaturesMatches( - putativeMatches, - cam_I, feature_I, - cam_J, feature_J, - subpart_I, - subpart_J); + // fill subpart of the matrices with undistorted features + fillMatricesWithUndistortFeaturesMatches(putativeMatches, cam_I, feature_I, cam_J, feature_J, subpart_I, subpart_J); - y += putativeMatches.size(); - } + y += putativeMatches.size(); + } } /** @@ -136,42 +126,43 @@ void fillMatricesWithUndistortFeaturesMatches(const matching::MatchesPerDescType * @param[out] x_I Pixel perfect features from the Inth image putativeMatches matches * @param[out] x_J Pixel perfect features from the Jnth image putativeMatches matches */ -template -void fillMatricesWithUndistortFeaturesMatches(const Pair &pairIndex, - const matching::MatchesPerDescType &putativeMatchesPerType, - const sfmData::SfMData *sfmData, - const feature::RegionsPerView ®ionsPerView, - const std::vector &descTypes, - MatT &x_I, MatT &x_J) +template +void fillMatricesWithUndistortFeaturesMatches(const Pair& pairIndex, + const matching::MatchesPerDescType& putativeMatchesPerType, + const sfmData::SfMData* sfmData, + const feature::RegionsPerView& regionsPerView, + const std::vector& descTypes, + MatT& x_I, + MatT& x_J) { - const sfmData::View * view_I = sfmData->getViews().at(pairIndex.first).get(); - const sfmData::View * view_J = sfmData->getViews().at(pairIndex.second).get(); + const sfmData::View* view_I = sfmData->getViews().at(pairIndex.first).get(); + const sfmData::View* view_J = sfmData->getViews().at(pairIndex.second).get(); - // Retrieve corresponding pair camera intrinsic if any - const camera::IntrinsicBase * cam_I = sfmData->getIntrinsicPtr(view_I->getIntrinsicId()); - const camera::IntrinsicBase * cam_J = sfmData->getIntrinsicPtr(view_J->getIntrinsicId()); + // Retrieve corresponding pair camera intrinsic if any + const camera::IntrinsicBase* cam_I = sfmData->getIntrinsicPtr(view_I->getIntrinsicId()); + const camera::IntrinsicBase* cam_J = sfmData->getIntrinsicPtr(view_J->getIntrinsicId()); - fillMatricesWithUndistortFeaturesMatches( - putativeMatchesPerType, - cam_I, - cam_J, - regionsPerView.getRegionsPerDesc(pairIndex.first), - regionsPerView.getRegionsPerDesc(pairIndex.second), - descTypes, - x_I, x_J); + fillMatricesWithUndistortFeaturesMatches(putativeMatchesPerType, + cam_I, + cam_J, + regionsPerView.getRegionsPerDesc(pairIndex.first), + regionsPerView.getRegionsPerDesc(pairIndex.second), + descTypes, + x_I, + x_J); } /** -* @brief copyInlierMatches -* @param[in] inliers -* @param[in] putativeMatchesPerType -* @param[in] descTypes -* @param[out] out_geometricInliersPerType -*/ -void copyInlierMatches(const std::vector &inliers, - const matching::MatchesPerDescType &putativeMatchesPerType, - const std::vector &descTypes, - matching::MatchesPerDescType &out_geometricInliersPerType); + * @brief copyInlierMatches + * @param[in] inliers + * @param[in] putativeMatchesPerType + * @param[in] descTypes + * @param[out] out_geometricInliersPerType + */ +void copyInlierMatches(const std::vector& inliers, + const matching::MatchesPerDescType& putativeMatchesPerType, + const std::vector& descTypes, + matching::MatchesPerDescType& out_geometricInliersPerType); /** * @brief Compute the transformation that standardize the input points so that @@ -179,7 +170,7 @@ void copyInlierMatches(const std::vector &inliers, * @param[in] points2d The 2D inputs points. * @param[out] t the transformation that standardize the points. */ -void centerMatrix(const Eigen::Matrix2Xf & points2d, Mat3 & t); +void centerMatrix(const Eigen::Matrix2Xf& points2d, Mat3& t); /** * @brief Compute the standardizing tranformation for the input features. @@ -191,12 +182,12 @@ void centerMatrix(const Eigen::Matrix2Xf & points2d, Mat3 & t); * @param[out] cJ The standardizing matrix to apply to (the subpart of) \c featuresJ * @param[in] usefulMatchesId To consider a subpart of \c matches only. */ -void centeringMatrices(const std::vector & featuresI, - const std::vector & featuresJ, - const matching::IndMatches & matches, - Mat3 & cI, - Mat3 & cJ, - const std::set & usefulMatchesId = std::set()); +void centeringMatrices(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + Mat3& cI, + Mat3& cJ, + const std::set& usefulMatchesId = std::set()); /** * @brief Compute the similarity transformation between 2 features (using their scale & orientation). * Based on: https://github.com/fsrajer/yasfm/blob/3a09bc0ee69b7021910d646386cd92deab504a2c/YASFM/relative_pose.cpp#L1649 @@ -204,9 +195,7 @@ void centeringMatrices(const std::vector & featuresI, * @param[in] feat2 The second feature with known scale & orientation. * @param[out] S The similarity transformation between f1 et f2 so that f2 = S * f1. */ -void computeSimilarity(const feature::PointFeature & feat1, - const feature::PointFeature & feat2, - Mat3 & S); +void computeSimilarity(const feature::PointFeature& feat1, const feature::PointFeature& feat2, Mat3& S); /** * @brief Estimate (using SVD) the Affinity transformation from a set of matches. @@ -217,11 +206,11 @@ void computeSimilarity(const feature::PointFeature & feat1, * @param[out] affineTransformation The estimated Affine transformation. * @param[in] usefulMatchesId To consider a subpart of \c matches only. */ -void estimateAffinity(const std::vector & featuresI, - const std::vector & featuresJ, - const matching::IndMatches & matches, - Mat3 & affineTransformation, - const std::set & usefulMatchesId = std::set()); +void estimateAffinity(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + Mat3& affineTransformation, + const std::set& usefulMatchesId = std::set()); /** * @brief estimateHomography Estimate (using SVD) the Homography transformation from a set of matches. @@ -232,11 +221,11 @@ void estimateAffinity(const std::vector & featuresI, * @param[out] H The estimated Homography transformation. * @param[in] usefulMatchesId To consider a subpart of \c matches only. */ -void estimateHomography(const std::vector & featuresI, - const std::vector & featuresJ, - const matching::IndMatches & matches, - Mat3 &H, - const std::set & usefulMatchesId = std::set()); +void estimateHomography(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + Mat3& H, + const std::set& usefulMatchesId = std::set()); /** * @brief Return the id. of the matches with a reprojection error < to the desirered \c tolerance. @@ -247,12 +236,12 @@ void estimateHomography(const std::vector & featuresI, * @param[in] tolerance The tolerated pixel error. * @param[in] inliersId The index in the \c matches vector. */ -void findTransformationInliers(const std::vector & featuresI, - const std::vector & featuresJ, - const matching::IndMatches & matches, - const Mat3 & transformation, +void findTransformationInliers(const std::vector& featuresI, + const std::vector& featuresJ, + const matching::IndMatches& matches, + const Mat3& transformation, double tolerance, - std::set & inliersId); + std::set& inliersId); /** * @brief Return the id. of the matches with a reprojection error < to the desirered \c tolerance. * @param[in] featuresI @@ -264,14 +253,13 @@ void findTransformationInliers(const std::vector & featur */ void findTransformationInliers(const Mat2X& featuresI, const Mat2X& featuresJ, - const matching::IndMatches &matches, - const Mat3 &transformation, + const matching::IndMatches& matches, + const Mat3& transformation, double tolerance, - std::set &inliersId); - + std::set& inliersId); -bool refineHomography(const std::vector &featuresI, - const std::vector &featuresJ, +bool refineHomography(const std::vector& featuresI, + const std::vector& featuresJ, const matching::IndMatches& remainingMatches, Mat3& homography, std::set& bestMatchesId, @@ -284,5 +272,5 @@ bool refineHomography(const Mat2X& features_I, std::set& bestMatchesId, double homographyTolerance); -} // namespace aliceVision -} // namespace matchingImageCollection +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/geometricFilterUtils_test.cpp b/src/aliceVision/matchingImageCollection/geometricFilterUtils_test.cpp index da119f2263..b5f99ebd29 100644 --- a/src/aliceVision/matchingImageCollection/geometricFilterUtils_test.cpp +++ b/src/aliceVision/matchingImageCollection/geometricFilterUtils_test.cpp @@ -15,68 +15,68 @@ using namespace aliceVision; void meanAndStd(const Eigen::Matrix2Xf& points2d, Vec2f& mean, Vec2f& stdDev) { - mean = points2d.rowwise().mean(); - const auto nbPoints = points2d.cols(); - stdDev = ((points2d.colwise() - mean).cwiseAbs2().rowwise().sum() / (nbPoints - 1)).cwiseSqrt(); + mean = points2d.rowwise().mean(); + const auto nbPoints = points2d.cols(); + stdDev = ((points2d.colwise() - mean).cwiseAbs2().rowwise().sum() / (nbPoints - 1)).cwiseSqrt(); } BOOST_AUTO_TEST_CASE(matchingImageCollection_centerMatrix) { - makeRandomOperationsReproducible(); - - const std::size_t numPoints{100}; - const std::size_t numTrials{100}; - const float threshold{0.0001f}; - for(std::size_t i = 0; i < numTrials; ++i) - { - Eigen::Matrix2Xf points(2, numPoints); - const Vec2f coeff = 100*Vec2f::Random(); - points = ((coeff(0) * points.setRandom()).array() + coeff(1)).matrix(); - - Mat3 transformation; - matchingImageCollection::centerMatrix(points, transformation); - - Eigen::Matrix2Xf points2d = (transformation.cast() * points.colwise().homogeneous()).colwise().hnormalized(); - Vec2f mean; - Vec2f stdDev; - meanAndStd(points2d, mean, stdDev); - std::cout << "points2d\n" << points2d << std::endl; -// std::cout << "Mean " << mean << "\nStd dev " << stdDev << std::endl; -// std::cout << "Mean " << mean.isZero() << "\nStd dev " << stdDev.isOnes() << std::endl; - BOOST_CHECK(mean.isZero(threshold)); - BOOST_CHECK(stdDev.isOnes()); - } + makeRandomOperationsReproducible(); + + const std::size_t numPoints{100}; + const std::size_t numTrials{100}; + const float threshold{0.0001f}; + for (std::size_t i = 0; i < numTrials; ++i) + { + Eigen::Matrix2Xf points(2, numPoints); + const Vec2f coeff = 100 * Vec2f::Random(); + points = ((coeff(0) * points.setRandom()).array() + coeff(1)).matrix(); + + Mat3 transformation; + matchingImageCollection::centerMatrix(points, transformation); + + Eigen::Matrix2Xf points2d = (transformation.cast() * points.colwise().homogeneous()).colwise().hnormalized(); + Vec2f mean; + Vec2f stdDev; + meanAndStd(points2d, mean, stdDev); + std::cout << "points2d\n" << points2d << std::endl; + // std::cout << "Mean " << mean << "\nStd dev " << stdDev << std::endl; + // std::cout << "Mean " << mean.isZero() << "\nStd dev " << stdDev.isOnes() << std::endl; + BOOST_CHECK(mean.isZero(threshold)); + BOOST_CHECK(stdDev.isOnes()); + } } BOOST_AUTO_TEST_CASE(matchingImageCollection_similarityEstimation) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - // same point should give the identity matrix - const feature::PointFeature feat1 {1.0f, 5.0f, 1.0f, 0.1f}; - Mat3 S; - matchingImageCollection::computeSimilarity(feat1, feat1, S); - - // up to eigen's default tolerance - BOOST_CHECK(S.isIdentity()); - - const std::size_t numTrials{100}; - for(std::size_t i = 0; i < numTrials; ++i) - { - // generate a random point with a random scale and orientation - const Vec2f point = Vec2f::Random(); - const Vec2f param = Vec2f::Random(); - const feature::PointFeature feat2 {point(0), point(1), param(0), param(1)}; - - // estimate the similarity - matchingImageCollection::computeSimilarity(feat1, feat2, S); - - const Vec3 ptIp_hom = S * feat1.coords().cast().homogeneous(); - const Vec2 result = point.cast() - ptIp_hom.hnormalized(); + // same point should give the identity matrix + const feature::PointFeature feat1{1.0f, 5.0f, 1.0f, 0.1f}; + Mat3 S; + matchingImageCollection::computeSimilarity(feat1, feat1, S); // up to eigen's default tolerance - BOOST_CHECK(result.isZero()); -// std::cout << result.isZero() << std::endl; -// std::cout << point.cast() - ptIp_hom.hnormalized() << std::endl; - } + BOOST_CHECK(S.isIdentity()); + + const std::size_t numTrials{100}; + for (std::size_t i = 0; i < numTrials; ++i) + { + // generate a random point with a random scale and orientation + const Vec2f point = Vec2f::Random(); + const Vec2f param = Vec2f::Random(); + const feature::PointFeature feat2{point(0), point(1), param(0), param(1)}; + + // estimate the similarity + matchingImageCollection::computeSimilarity(feat1, feat2, S); + + const Vec3 ptIp_hom = S * feat1.coords().cast().homogeneous(); + const Vec2 result = point.cast() - ptIp_hom.hnormalized(); + + // up to eigen's default tolerance + BOOST_CHECK(result.isZero()); + // std::cout << result.isZero() << std::endl; + // std::cout << point.cast() - ptIp_hom.hnormalized() << std::endl; + } } diff --git a/src/aliceVision/matchingImageCollection/matchingCommon.cpp b/src/aliceVision/matchingImageCollection/matchingCommon.cpp index d9caa72a83..6ea9d6caa9 100644 --- a/src/aliceVision/matchingImageCollection/matchingCommon.cpp +++ b/src/aliceVision/matchingImageCollection/matchingCommon.cpp @@ -15,26 +15,36 @@ namespace aliceVision { namespace matchingImageCollection { - std::unique_ptr createImageCollectionMatcher(matching::EMatcherType matcherType, float distRatio, bool crossMatching) { - std::unique_ptr matcherPtr; - - switch(matcherType) - { - case matching::BRUTE_FORCE_L2: matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::BRUTE_FORCE_L2)); break; - case matching::ANN_L2: matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::ANN_L2)); break; - case matching::CASCADE_HASHING_L2: matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::CASCADE_HASHING_L2)); break; - case matching::FAST_CASCADE_HASHING_L2: matcherPtr.reset(new ImageCollectionMatcher_cascadeHashing(distRatio)); break; - case matching::BRUTE_FORCE_HAMMING: matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::BRUTE_FORCE_HAMMING)); break; - - default: throw std::out_of_range("Invalid matcherType enum"); - } - assert(matcherPtr != nullptr); - - return matcherPtr; + std::unique_ptr matcherPtr; + + switch (matcherType) + { + case matching::BRUTE_FORCE_L2: + matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::BRUTE_FORCE_L2)); + break; + case matching::ANN_L2: + matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::ANN_L2)); + break; + case matching::CASCADE_HASHING_L2: + matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::CASCADE_HASHING_L2)); + break; + case matching::FAST_CASCADE_HASHING_L2: + matcherPtr.reset(new ImageCollectionMatcher_cascadeHashing(distRatio)); + break; + case matching::BRUTE_FORCE_HAMMING: + matcherPtr.reset(new ImageCollectionMatcher_generic(distRatio, crossMatching, matching::BRUTE_FORCE_HAMMING)); + break; + + default: + throw std::out_of_range("Invalid matcherType enum"); + } + assert(matcherPtr != nullptr); + + return matcherPtr; } -} // namespace matchingImageCollection -} // namespace aliceVision +} // namespace matchingImageCollection +} // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/matchingCommon.hpp b/src/aliceVision/matchingImageCollection/matchingCommon.hpp index 19383c1938..7d6a8661be 100644 --- a/src/aliceVision/matchingImageCollection/matchingCommon.hpp +++ b/src/aliceVision/matchingImageCollection/matchingCommon.hpp @@ -12,14 +12,13 @@ namespace aliceVision { namespace matchingImageCollection { - + /** - * + * * @param matcherType - * @return + * @return */ std::unique_ptr createImageCollectionMatcher(matching::EMatcherType matcherType, float distRatio, bool crossMatching); - -} // namespace matching -} // namespace aliceVision \ No newline at end of file +} // namespace matchingImageCollection +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/matchingImageCollection/pairBuilder.cpp b/src/aliceVision/matchingImageCollection/pairBuilder.cpp index ee6e92f471..01bd528fec 100644 --- a/src/aliceVision/matchingImageCollection/pairBuilder.cpp +++ b/src/aliceVision/matchingImageCollection/pairBuilder.cpp @@ -21,28 +21,28 @@ namespace aliceVision { /// Generate all the (I,J) pairs of the upper diagonal of the NxN matrix PairSet exhaustivePairs(const sfmData::Views& views, int rangeStart, int rangeSize) { - PairSet pairs; - sfmData::Views::const_iterator itA = views.begin(); - sfmData::Views::const_iterator itAEnd = views.end(); - - // If we have a rangeStart, only compute the matching for (rangeStart, X). - if(rangeStart != -1 && rangeSize != 0) - { - if(rangeStart >= views.size()) - return pairs; - std::advance(itA, rangeStart); - itAEnd = views.begin(); - std::advance(itAEnd, std::min(std::size_t(rangeStart+rangeSize), views.size())); - } - - for(; itA != itAEnd; ++itA) - { - sfmData::Views::const_iterator itB = itA; - std::advance(itB, 1); - for(; itB != views.end(); ++itB) - pairs.insert(std::make_pair(itA->first, itB->first)); - } - return pairs; + PairSet pairs; + sfmData::Views::const_iterator itA = views.begin(); + sfmData::Views::const_iterator itAEnd = views.end(); + + // If we have a rangeStart, only compute the matching for (rangeStart, X). + if (rangeStart != -1 && rangeSize != 0) + { + if (rangeStart >= views.size()) + return pairs; + std::advance(itA, rangeStart); + itAEnd = views.begin(); + std::advance(itAEnd, std::min(std::size_t(rangeStart + rangeSize), views.size())); + } + + for (; itA != itAEnd; ++itA) + { + sfmData::Views::const_iterator itB = itA; + std::advance(itB, 1); + for (; itB != views.end(); ++itB) + pairs.insert(std::make_pair(itA->first, itB->first)); + } + return pairs; } -}; // namespace aliceVision +}; // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/pairBuilder.hpp b/src/aliceVision/matchingImageCollection/pairBuilder.hpp index 91a57ef20d..50f3b23f8c 100644 --- a/src/aliceVision/matchingImageCollection/pairBuilder.hpp +++ b/src/aliceVision/matchingImageCollection/pairBuilder.hpp @@ -14,6 +14,6 @@ namespace aliceVision { /// Generate all the (I,J) pairs of the upper diagonal of the NxN matrix -PairSet exhaustivePairs(const sfmData::Views& views, int rangeStart=-1, int rangeSize=0); +PairSet exhaustivePairs(const sfmData::Views& views, int rangeStart = -1, int rangeSize = 0); -}; // namespace aliceVision +}; // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp b/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp index cd2d602751..73c0de23c8 100644 --- a/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp +++ b/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp @@ -22,41 +22,39 @@ using namespace aliceVision; // Check pairs follow a weak ordering pair.first < pair.second template -bool checkPairOrder(const IterablePairs & pairs) +bool checkPairOrder(const IterablePairs& pairs) { - for (typename IterablePairs::const_iterator iterP = pairs.begin(); iterP != pairs.end(); - ++iterP) - { - if (iterP->first >= iterP->second) - return false; - } - return true; + for (typename IterablePairs::const_iterator iterP = pairs.begin(); iterP != pairs.end(); ++iterP) + { + if (iterP->first >= iterP->second) + return false; + } + return true; } BOOST_AUTO_TEST_CASE(matchingImageCollection_exhaustivePairs) { - sfmData::Views views; - { - // Empty - PairSet pairSet = exhaustivePairs(views); - BOOST_CHECK_EQUAL( 0, pairSet.size()); - } - { - std::vector indexes = {{ 12, 54, 89, 65 }}; - for( IndexT i: indexes ) + sfmData::Views views; { - views.emplace(i, std::make_shared("filepath", i)); + // Empty + PairSet pairSet = exhaustivePairs(views); + BOOST_CHECK_EQUAL(0, pairSet.size()); + } + { + std::vector indexes = {{12, 54, 89, 65}}; + for (IndexT i : indexes) + { + views.emplace(i, std::make_shared("filepath", i)); + } + + PairSet pairSet = exhaustivePairs(views); + BOOST_CHECK(checkPairOrder(pairSet)); + BOOST_CHECK_EQUAL(6, pairSet.size()); + BOOST_CHECK(pairSet.find(std::make_pair(12, 54)) != pairSet.end()); + BOOST_CHECK(pairSet.find(std::make_pair(12, 89)) != pairSet.end()); + BOOST_CHECK(pairSet.find(std::make_pair(12, 65)) != pairSet.end()); + BOOST_CHECK(pairSet.find(std::make_pair(54, 89)) != pairSet.end()); + BOOST_CHECK(pairSet.find(std::make_pair(54, 65)) != pairSet.end()); + BOOST_CHECK(pairSet.find(std::make_pair(65, 89)) != pairSet.end()); } - - - PairSet pairSet = exhaustivePairs(views); - BOOST_CHECK( checkPairOrder(pairSet) ); - BOOST_CHECK_EQUAL( 6, pairSet.size()); - BOOST_CHECK( pairSet.find(std::make_pair(12,54)) != pairSet.end() ); - BOOST_CHECK( pairSet.find(std::make_pair(12,89)) != pairSet.end() ); - BOOST_CHECK( pairSet.find(std::make_pair(12,65)) != pairSet.end() ); - BOOST_CHECK( pairSet.find(std::make_pair(54,89)) != pairSet.end() ); - BOOST_CHECK( pairSet.find(std::make_pair(54,65)) != pairSet.end() ); - BOOST_CHECK( pairSet.find(std::make_pair(65,89)) != pairSet.end() ); - } } diff --git a/src/aliceVision/mesh/Material.cpp b/src/aliceVision/mesh/Material.cpp index 9ddd98327e..dca4ecd3b6 100644 --- a/src/aliceVision/mesh/Material.cpp +++ b/src/aliceVision/mesh/Material.cpp @@ -53,8 +53,7 @@ void Material::addTexture(TextureType type, const std::string& textureName) displacementPrefix = prefix; displacementType = image::EImageFileType_stringToEnum(extension); } - else if (displacementPrefix != prefix || - displacementType != image::EImageFileType_stringToEnum(extension)) + else if (displacementPrefix != prefix || displacementType != image::EImageFileType_stringToEnum(extension)) { throw std::runtime_error("All texture tiles must have the same prefix and extension!"); } @@ -97,10 +96,7 @@ const StaticVector& Material::getTextures(TextureType type) const StaticVector Material::getAllTextures() const { StaticVector textures; - textures.resize(_bumpTextures.size() - + _diffuseTextures.size() - + _displacementTextures.size() - + _normalTextures.size()); + textures.resize(_bumpTextures.size() + _diffuseTextures.size() + _displacementTextures.size() + _normalTextures.size()); auto last = std::copy(_bumpTextures.begin(), _bumpTextures.end(), textures.begin()); last = std::copy(_diffuseTextures.begin(), _diffuseTextures.end(), last); @@ -131,9 +127,8 @@ int Material::numAtlases() const { const int num = _diffuseTextures.size(); - if ((!_displacementTextures.empty() && _displacementTextures.size() != num) || - (!_normalTextures.empty() && _normalTextures.size() != num) || - (!_bumpTextures.empty() && _bumpTextures.size() != num)) + if ((!_displacementTextures.empty() && _displacementTextures.size() != num) || (!_normalTextures.empty() && _normalTextures.size() != num) || + (!_bumpTextures.empty() && _bumpTextures.size() != num)) { throw std::runtime_error("All texture maps must have same number of atlases!"); } @@ -179,5 +174,5 @@ std::string Material::textureName(TextureType type, int index) const return prefix + textureId(index) + "." + image::EImageFileType_enumToString(fileType); } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/Material.hpp b/src/aliceVision/mesh/Material.hpp index bc9e4cdc84..91cc19843b 100644 --- a/src/aliceVision/mesh/Material.hpp +++ b/src/aliceVision/mesh/Material.hpp @@ -15,7 +15,7 @@ namespace mesh { /// Simple material type that supports only the data written out from Texturing pipeline class Material { -public: + public: struct Color { float r = 0.0; @@ -77,12 +77,12 @@ class Material return index < 0 ? "" : std::to_string(1001 + index); } -private: + private: StaticVector _diffuseTextures; StaticVector _normalTextures; StaticVector _bumpTextures; StaticVector _displacementTextures; }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/Mesh.cpp b/src/aliceVision/mesh/Mesh.cpp index 3f9277e60b..538ea0a0d9 100644 --- a/src/aliceVision/mesh/Mesh.cpp +++ b/src/aliceVision/mesh/Mesh.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -30,19 +30,13 @@ namespace aliceVision { namespace mesh { +Mesh::Mesh() {} -Mesh::Mesh() -{ -} - -Mesh::~Mesh() -{ -} - +Mesh::~Mesh() {} std::string EFileType_enumToString(const EFileType meshFileType) { - switch(meshFileType) + switch (meshFileType) { case EFileType::OBJ: return "obj"; @@ -61,21 +55,18 @@ EFileType EFileType_stringToEnum(const std::string& meshFileType) std::string m = meshFileType; boost::to_lower(m); - if(m == "obj") + if (m == "obj") return EFileType::OBJ; - if(m == "fbx") + if (m == "fbx") return EFileType::FBX; - if(m == "stl") + if (m == "stl") return EFileType::STL; - if(m == "gltf") + if (m == "gltf") return EFileType::GLTF; throw std::out_of_range("Invalid mesh file type " + meshFileType); } -std::ostream& operator<<(std::ostream& os, EFileType meshFileType) -{ - return os << EFileType_enumToString(meshFileType); -} +std::ostream& operator<<(std::ostream& os, EFileType meshFileType) { return os << EFileType_enumToString(meshFileType); } std::istream& operator>>(std::istream& in, EFileType& meshFileType) { std::string token; @@ -106,14 +97,14 @@ void Mesh::save(const std::string& filepath) scene.mRootNode->mMeshes[0] = 0; scene.mMeshes[0] = new aiMesh; - aiMesh * aimesh = scene.mMeshes[0]; + aiMesh* aimesh = scene.mMeshes[0]; aimesh->mMaterialIndex = 0; aimesh->mNumVertices = pts.size(); aimesh->mVertices = new aiVector3D[pts.size()]; int index = 0; - for (const auto & p : pts) + for (const auto& p : pts) { aimesh->mVertices[index].x = p.x; aimesh->mVertices[index].y = -p.y; @@ -125,7 +116,7 @@ void Mesh::save(const std::string& filepath) aimesh->mNumFaces = tris.size(); aimesh->mFaces = new aiFace[tris.size()]; - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { aimesh->mFaces[i].mNumIndices = 3; aimesh->mFaces[i].mIndices = new unsigned int[3]; @@ -159,7 +150,7 @@ void Mesh::save(const std::string& filepath) ALICEVISION_LOG_DEBUG("Vertices: " << pts.size()); ALICEVISION_LOG_DEBUG("Triangles: " << tris.size()); - ALICEVISION_LOG_DEBUG("UVs: " << uvCoords.size()); + ALICEVISION_LOG_DEBUG("UVs: " << uvCoords.size()); ALICEVISION_LOG_DEBUG("Normals: " << normals.size()); } @@ -167,7 +158,7 @@ bool Mesh::loadFromBin(const std::string& binFilepath) { FILE* f = fopen(binFilepath.c_str(), "rb"); - if(f == nullptr) + if (f == nullptr) return false; int npts; @@ -222,13 +213,11 @@ void Mesh::addMesh(const Mesh& mesh) std::copy(mesh._colors.begin(), mesh._colors.end(), std::back_inserter(_colors)); tris.reserveAdd(mesh.tris.size()); - for(int i = 0; i < mesh.tris.size(); ++i) + for (int i = 0; i < mesh.tris.size(); ++i) { Mesh::triangle t = mesh.tris[i]; // check triangles indices validity - if( (t.v[0] >= 0 && t.v[0] < mesh.pts.size()) - && (t.v[1] >= 0 && t.v[1] < mesh.pts.size()) - && (t.v[2] >= 0 && t.v[2] < mesh.pts.size())) + if ((t.v[0] >= 0 && t.v[0] < mesh.pts.size()) && (t.v[1] >= 0 && t.v[1] < mesh.pts.size()) && (t.v[2] >= 0 && t.v[2] < mesh.pts.size())) { t.v[0] += npts; t.v[1] += npts; @@ -241,12 +230,12 @@ void Mesh::addMesh(const Mesh& mesh) } } - if(!mesh.uvCoords.empty()) + if (!mesh.uvCoords.empty()) { uvCoords.reserve(mesh.uvCoords.size()); std::copy(mesh.uvCoords.begin(), mesh.uvCoords.end(), std::back_inserter(uvCoords.getDataWritable())); } - if(!mesh.trisUvIds.empty()) + if (!mesh.trisUvIds.empty()) { trisUvIds.reserve(mesh.trisUvIds.size()); std::copy(mesh.trisUvIds.begin(), mesh.trisUvIds.end(), std::back_inserter(trisUvIds.getDataWritable())); @@ -259,7 +248,7 @@ Mesh::triangle_proj Mesh::getTriangleProjection(int triid, const mvsUtils::Multi int oh = mp.getHeight(rc); triangle_proj tp; - for(int j = 0; j < 3; ++j) + for (int j = 0; j < 3; ++j) { mp.getPixelFor3DPoint(&tp.tp2ds[j], pts[tris[triid].v[j]], rc); tp.tp2ds[j].x = (tp.tp2ds[j].x / (float)ow) * (float)w; @@ -272,21 +261,21 @@ Mesh::triangle_proj Mesh::getTriangleProjection(int triid, const mvsUtils::Multi tp.lu.y = h; tp.rd.x = 0; tp.rd.y = 0; - for(int j = 0; j < 3; j++) + for (int j = 0; j < 3; j++) { - if((float)tp.lu.x > tp.tp2ds[j].x) + if ((float)tp.lu.x > tp.tp2ds[j].x) { tp.lu.x = (int)tp.tp2ds[j].x; } - if((float)tp.lu.y > tp.tp2ds[j].y) + if ((float)tp.lu.y > tp.tp2ds[j].y) { tp.lu.y = (int)tp.tp2ds[j].y; } - if((float)tp.rd.x < tp.tp2ds[j].x) + if ((float)tp.rd.x < tp.tp2ds[j].x) { tp.rd.x = (int)tp.tp2ds[j].x; } - if((float)tp.rd.y < tp.tp2ds[j].y) + if ((float)tp.rd.y < tp.tp2ds[j].y) { tp.rd.y = (int)tp.tp2ds[j].y; } @@ -305,7 +294,7 @@ int Mesh::getTriangleNbVertexInImage(const mvsUtils::MultiViewParams& mp, const int nbVertexInImage = 0; for (int j = 0; j < 3; ++j) { - if(mp.isPixelInImage(tp.tpixs[j], camId, margin) && mp.isPixelInSourceImage(tp.tpixs[j], camId, margin)) + if (mp.isPixelInImage(tp.tpixs[j], camId, margin) && mp.isPixelInSourceImage(tp.tpixs[j], camId, margin)) { ++nbVertexInImage; } @@ -317,115 +306,112 @@ void Mesh::getTrianglePixelIntersectionsAndInternalPoints(Mesh::triangle_proj& t { out.reserve(20); - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[0])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[0])) { out.push_back(re.P[0]); } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[1])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[1])) { out.push_back(re.P[1]); } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[2])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[2])) { out.push_back(re.P[2]); } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[3])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[3])) { out.push_back(re.P[3]); } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[0])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[0]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[0])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[0]))) { out.push_back(tp.tp2ds[0]); } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[1])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[1]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[1])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[1]))) { out.push_back(tp.tp2ds[1]); } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[2])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[2]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[2])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[2]))) { out.push_back(tp.tp2ds[2]); } Point2d lli; - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[0], re.P[1])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[0], re.P[1])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[0], re.P[1])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[0], re.P[1])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[0], re.P[1])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[0], re.P[1])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[1], re.P[2])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[1], re.P[2])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[1], re.P[2])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[1], re.P[2])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[1], re.P[2])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[1], re.P[2])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[2], re.P[3])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[2], re.P[3])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[2], re.P[3])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[2], re.P[3])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[2], re.P[3])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[2], re.P[3])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[3], re.P[0])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[0], tp.tp2ds[1], re.P[3], re.P[0])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[3], re.P[0])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[1], tp.tp2ds[2], re.P[3], re.P[0])) { out.push_back(lli); } - if(lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[3], re.P[0])) + if (lineSegmentsIntersect2DTest(lli, tp.tp2ds[2], tp.tp2ds[0], re.P[3], re.P[0])) { out.push_back(lli); } } -void Mesh::getTrianglePixelIntersectionsAndInternalPoints(const mvsUtils::MultiViewParams& mp, int idTri, Pixel& /*pix*/, - int rc, Mesh::triangle_proj& tp, Mesh::rectangle& re, - StaticVector& out) +void Mesh::getTrianglePixelIntersectionsAndInternalPoints(const mvsUtils::MultiViewParams& mp, + int idTri, + Pixel& /*pix*/, + int rc, + Mesh::triangle_proj& tp, + Mesh::rectangle& re, + StaticVector& out) { - Point3d A = pts[tris[idTri].v[0]]; Point3d B = pts[tris[idTri].v[1]]; Point3d C = pts[tris[idTri].v[2]]; triangleRectangleIntersection(A, B, C, mp, rc, re.P, out); - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[0])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[0]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[0])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[0]))) { out.push_back(A); } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[1])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[1]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[1])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[1]))) { out.push_back(B); } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[2])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[2]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[2])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[2]))) { out.push_back(C); } @@ -433,36 +419,32 @@ void Mesh::getTrianglePixelIntersectionsAndInternalPoints(const mvsUtils::MultiV Point2d Mesh::getTrianglePixelInternalPoint(Mesh::triangle_proj& tp, Mesh::rectangle& re) { - - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[0])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[0]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[0])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[0]))) { return tp.tp2ds[0]; } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[1])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[1]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[1])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[1]))) { return tp.tp2ds[1]; } - if((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[2])) || - (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[2]))) + if ((isPointInTriangle(re.P[0], re.P[1], re.P[2], tp.tp2ds[2])) || (isPointInTriangle(re.P[2], re.P[3], re.P[0], tp.tp2ds[2]))) { return tp.tp2ds[2]; } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[0])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[0])) { return re.P[0]; } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[1])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[1])) { return re.P[1]; } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[2])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[2])) { return re.P[2]; } - if(isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[3])) + if (isPointInTriangle(tp.tp2ds[0], tp.tp2ds[1], tp.tp2ds[2], re.P[3])) { return re.P[3]; } @@ -471,7 +453,6 @@ Point2d Mesh::getTrianglePixelInternalPoint(Mesh::triangle_proj& tp, Mesh::recta bool Mesh::doesTriangleIntersectsRectangle(Mesh::triangle_proj& tp, Mesh::rectangle& re) { - Point2d p1[3]; Point2d p2[3]; p1[0] = re.P[0]; @@ -481,12 +462,9 @@ bool Mesh::doesTriangleIntersectsRectangle(Mesh::triangle_proj& tp, Mesh::rectan p2[1] = re.P[3]; p2[2] = re.P[0]; - return ((TrianglesOverlap(tp.tp2ds, p1)) || (TrianglesOverlap(tp.tp2ds, p2)) || - (isPointInTriangle(p1[0], p1[1], p1[2], tp.tp2ds[0])) || - (isPointInTriangle(p1[0], p1[1], p1[2], tp.tp2ds[1])) || - (isPointInTriangle(p1[0], p1[1], p1[2], tp.tp2ds[2])) || - (isPointInTriangle(p2[0], p2[1], p2[2], tp.tp2ds[0])) || - (isPointInTriangle(p2[0], p2[1], p2[2], tp.tp2ds[1])) || + return ((TrianglesOverlap(tp.tp2ds, p1)) || (TrianglesOverlap(tp.tp2ds, p2)) || (isPointInTriangle(p1[0], p1[1], p1[2], tp.tp2ds[0])) || + (isPointInTriangle(p1[0], p1[1], p1[2], tp.tp2ds[1])) || (isPointInTriangle(p1[0], p1[1], p1[2], tp.tp2ds[2])) || + (isPointInTriangle(p2[0], p2[1], p2[2], tp.tp2ds[0])) || (isPointInTriangle(p2[0], p2[1], p2[2], tp.tp2ds[1])) || (isPointInTriangle(p2[0], p2[1], p2[2], tp.tp2ds[2]))); /* @@ -563,7 +541,7 @@ void Mesh::getPtsNeighborTriangles(StaticVector>& out_ptsNeigh // array of tuples StaticVector vertexNeighborhoodPairs; vertexNeighborhoodPairs.reserve(tris.size() * 3); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { vertexNeighborhoodPairs.push_back(Voxel(tris[i].v[0], i, 0)); vertexNeighborhoodPairs.push_back(Voxel(tris[i].v[1], i, 0)); @@ -571,17 +549,17 @@ void Mesh::getPtsNeighborTriangles(StaticVector>& out_ptsNeigh } qsort(&vertexNeighborhoodPairs[0], vertexNeighborhoodPairs.size(), sizeof(Voxel), qSortCompareVoxelByXAsc); - int i = 0; // index of the unique pair of - int j = 0; // index of the vertex - int k = 0; // number of neighbors + int i = 0; // index of the unique pair of + int j = 0; // index of the vertex + int k = 0; // number of neighbors int firstid = 0; - while(i < vertexNeighborhoodPairs.size()) + while (i < vertexNeighborhoodPairs.size()) { ++k; // (*vertexNeighborhoodPairs)[i].z = j; - if((i == vertexNeighborhoodPairs.size() - 1) || (vertexNeighborhoodPairs[i].x != vertexNeighborhoodPairs[i + 1].x)) + if ((i == vertexNeighborhoodPairs.size() - 1) || (vertexNeighborhoodPairs[i].x != vertexNeighborhoodPairs[i + 1].x)) { - vertexNeighborhoodPairs[firstid].z = k; // store the number of neighbors + vertexNeighborhoodPairs[firstid].z = k; // store the number of neighbors ++j; firstid = i + 1; k = 0; @@ -594,7 +572,7 @@ void Mesh::getPtsNeighborTriangles(StaticVector>& out_ptsNeigh out_ptsNeighTris.resize(pts.size()); i = 0; - for(j = 0; j < npts; ++j) + for (j = 0; j < npts; ++j) { int middlePtId = vertexNeighborhoodPairs[i].x; int nbNeighbors = vertexNeighborhoodPairs[i].z; @@ -604,9 +582,9 @@ void Mesh::getPtsNeighborTriangles(StaticVector>& out_ptsNeigh StaticVector& triTmp = out_ptsNeighTris[middlePtId]; triTmp.reserve(nbNeighbors); - for(int l = i0; l < i1; ++l) + for (int l = i0; l < i1; ++l) { - triTmp.push_back(vertexNeighborhoodPairs[l].y); // index of triangle + triTmp.push_back(vertexNeighborhoodPairs[l].y); // index of triangle } } } @@ -614,22 +592,21 @@ void Mesh::getPtsNeighborTriangles(StaticVector>& out_ptsNeigh void Mesh::getPtsNeighbors(std::vector>& out_ptsNeigh) const { out_ptsNeigh.resize(pts.size()); - for(int triangleId = 0; triangleId < tris.size(); ++triangleId) + for (int triangleId = 0; triangleId < tris.size(); ++triangleId) { const Mesh::triangle& triangle = tris[triangleId]; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { int ptId = triangle.v[k]; std::vector& ptNeigh = out_ptsNeigh[ptId]; - if(std::find(ptNeigh.begin(), ptNeigh.end(), triangle.v[(k+1)%3]) == ptNeigh.end()) - ptNeigh.push_back(triangle.v[(k+1)%3]); - if(std::find(ptNeigh.begin(), ptNeigh.end(), triangle.v[(k+2)%3]) == ptNeigh.end()) - ptNeigh.push_back(triangle.v[(k+2)%3]); + if (std::find(ptNeigh.begin(), ptNeigh.end(), triangle.v[(k + 1) % 3]) == ptNeigh.end()) + ptNeigh.push_back(triangle.v[(k + 1) % 3]); + if (std::find(ptNeigh.begin(), ptNeigh.end(), triangle.v[(k + 2) % 3]) == ptNeigh.end()) + ptNeigh.push_back(triangle.v[(k + 2) % 3]); } } } - void Mesh::getPtsNeighPtsOrdered(StaticVector>& out_ptsNeighPts) const { StaticVector> ptsNeighborTriangles; @@ -637,10 +614,10 @@ void Mesh::getPtsNeighPtsOrdered(StaticVector>& out_ptsNeighPt out_ptsNeighPts.resize(pts.size()); - for(int middlePtId = 0; middlePtId < pts.size(); ++middlePtId) + for (int middlePtId = 0; middlePtId < pts.size(); ++middlePtId) { StaticVector& neighborTriangles = ptsNeighborTriangles[middlePtId]; - if(neighborTriangles.empty()) + if (neighborTriangles.empty()) continue; StaticVector vhid; @@ -650,58 +627,58 @@ void Mesh::getPtsNeighPtsOrdered(StaticVector>& out_ptsNeighPt vhid.push_back(currentTriPtId); bool isThereTWithCurrentTriPtId = true; - while(!neighborTriangles.empty() && isThereTWithCurrentTriPtId) + while (!neighborTriangles.empty() && isThereTWithCurrentTriPtId) { isThereTWithCurrentTriPtId = false; // find triangle with middlePtId and currentTriPtId and get remaining point id - for(int n = 0; n < neighborTriangles.size(); ++n) + for (int n = 0; n < neighborTriangles.size(); ++n) { bool ok_middlePtId = false; bool ok_actTriPtId = false; - int remainingPtId = -1; // remaining pt id - for(int k = 0; k < 3; ++k) + int remainingPtId = -1; // remaining pt id + for (int k = 0; k < 3; ++k) { int triPtId = tris[neighborTriangles[n]].v[k]; double length = (pts[middlePtId] - pts[triPtId]).size(); - if((triPtId != middlePtId) && (triPtId != currentTriPtId) && (length > 0.0) && (!std::isnan(length))) + if ((triPtId != middlePtId) && (triPtId != currentTriPtId) && (length > 0.0) && (!std::isnan(length))) { remainingPtId = triPtId; } - if(triPtId == middlePtId) + if (triPtId == middlePtId) { ok_middlePtId = true; } - if(triPtId == currentTriPtId) + if (triPtId == currentTriPtId) { ok_actTriPtId = true; } } - if(ok_middlePtId && ok_actTriPtId && (remainingPtId > -1)) + if (ok_middlePtId && ok_actTriPtId && (remainingPtId > -1)) { currentTriPtId = remainingPtId; neighborTriangles.remove(n); vhid.push_back(currentTriPtId); - isThereTWithCurrentTriPtId = true; // we removed one, so we try again + isThereTWithCurrentTriPtId = true; // we removed one, so we try again break; } } } - if(!vhid.empty()) + if (!vhid.empty()) { - if(currentTriPtId == firstTriPtId) + if (currentTriPtId == firstTriPtId) { - vhid.pop(); // remove last ... which is first + vhid.pop(); // remove last ... which is first } // remove duplicates StaticVector& vhid1 = out_ptsNeighPts[middlePtId]; vhid1.reserve(vhid.size()); - for(int k1 = 0; k1 < vhid.size(); ++k1) + for (int k1 = 0; k1 < vhid.size(); ++k1) { - if(vhid1.indexOf(vhid[k1]) == -1) + if (vhid1.indexOf(vhid[k1]) == -1) { vhid1.push_back(vhid[k1]); } @@ -710,7 +687,7 @@ void Mesh::getPtsNeighPtsOrdered(StaticVector>& out_ptsNeighPt } } -void Mesh::getTrisMap(StaticVector>& out, const mvsUtils::MultiViewParams& mp, int rc, int /*scale*/, int w, int h) +void Mesh::getTrisMap(StaticVector>& out, const mvsUtils::MultiViewParams& mp, int rc, int /*scale*/, int w, int h) { long tstart = clock(); @@ -720,34 +697,34 @@ void Mesh::getTrisMap(StaticVector>& out, const mvsUtils::Mult nmap.resize_with(w * h, 0); long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { triangle_proj tp = getTriangleProjection(i, mp, rc, w, h); - if((isTriangleProjectionInImage(mp, tp, rc, 0))) + if ((isTriangleProjectionInImage(mp, tp, rc, 0))) { Pixel pix; - for(pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) + for (pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) { - for(pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) + for (pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) { Mesh::rectangle re = Mesh::rectangle(pix, 1); - if(doesTriangleIntersectsRectangle(tp, re)) + if (doesTriangleIntersectsRectangle(tp, re)) { nmap[pix.x * h + pix.y] += 1; } - } // for y - } // for x - } // isthere + } // for y + } // for x + } // isthere mvsUtils::printfEstimate(i, tris.size(), t1); - } // for i ntris + } // for i ntris mvsUtils::finishEstimate(); // allocate out.reserve(w * h); out.resize(w * h); - for(int i = 0; i < w * h; ++i) + for (int i = 0; i < w * h; ++i) { - if(nmap[i] > 0) + if (nmap[i] > 0) { out[i].reserve(nmap[i]); } @@ -755,33 +732,38 @@ void Mesh::getTrisMap(StaticVector>& out, const mvsUtils::Mult // fill t1 = mvsUtils::initEstimate(); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { triangle_proj tp = getTriangleProjection(i, mp, rc, w, h); - if((isTriangleProjectionInImage(mp, tp, rc, 0))) + if ((isTriangleProjectionInImage(mp, tp, rc, 0))) { Pixel pix; - for(pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) + for (pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) { - for(pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) + for (pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) { Mesh::rectangle re = Mesh::rectangle(pix, 1); - if(doesTriangleIntersectsRectangle(tp, re)) + if (doesTriangleIntersectsRectangle(tp, re)) { out[pix.x * h + pix.y].push_back(i); } - } // for y - } // for x - } // isthere + } // for y + } // for x + } // isthere mvsUtils::printfEstimate(i, tris.size(), t1); - } // for i ntris + } // for i ntris mvsUtils::finishEstimate(); mvsUtils::printfElapsedTime(tstart); } -void Mesh::getTrisMap(StaticVector>& out, StaticVector& visTris, const mvsUtils::MultiViewParams& mp, int rc, - int /*scale*/, int w, int h) +void Mesh::getTrisMap(StaticVector>& out, + StaticVector& visTris, + const mvsUtils::MultiViewParams& mp, + int rc, + int /*scale*/, + int w, + int h) { long tstart = clock(); @@ -791,34 +773,34 @@ void Mesh::getTrisMap(StaticVector>& out, StaticVector& v nmap.resize_with(w * h, 0); long t1 = mvsUtils::initEstimate(); - for(int m = 0; m < visTris.size(); ++m) + for (int m = 0; m < visTris.size(); ++m) { int i = visTris[m]; triangle_proj tp = getTriangleProjection(i, mp, rc, w, h); - if((isTriangleProjectionInImage(mp, tp, rc, 0))) + if ((isTriangleProjectionInImage(mp, tp, rc, 0))) { Pixel pix; - for(pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) + for (pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) { - for(pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) + for (pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) { Mesh::rectangle re = Mesh::rectangle(pix, 1); - if(doesTriangleIntersectsRectangle(tp, re)) + if (doesTriangleIntersectsRectangle(tp, re)) { nmap[pix.x * h + pix.y] += 1; } - } // for y - } // for x - } // isthere + } // for y + } // for x + } // isthere mvsUtils::printfEstimate(i, tris.size(), t1); - } // for i ntris + } // for i ntris mvsUtils::finishEstimate(); // allocate out.resize(w * h); - for(int i = 0; i < w * h; ++i) + for (int i = 0; i < w * h; ++i) { - if(nmap[i] > 0) + if (nmap[i] > 0) { out[i].reserve(nmap[i]); } @@ -826,27 +808,27 @@ void Mesh::getTrisMap(StaticVector>& out, StaticVector& v // fill t1 = mvsUtils::initEstimate(); - for(int m = 0; m < visTris.size(); ++m) + for (int m = 0; m < visTris.size(); ++m) { int i = visTris[m]; triangle_proj tp = getTriangleProjection(i, mp, rc, w, h); - if((isTriangleProjectionInImage(mp, tp, rc, 0))) + if ((isTriangleProjectionInImage(mp, tp, rc, 0))) { Pixel pix; - for(pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) + for (pix.x = tp.lu.x; pix.x <= tp.rd.x; ++pix.x) { - for(pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) + for (pix.y = tp.lu.y; pix.y <= tp.rd.y; ++pix.y) { Mesh::rectangle re = Mesh::rectangle(pix, 1); - if(doesTriangleIntersectsRectangle(tp, re)) + if (doesTriangleIntersectsRectangle(tp, re)) { out[pix.x * h + pix.y].push_back(i); } - } // for y - } // for x - } // isthere + } // for y + } // for x + } // isthere mvsUtils::printfEstimate(i, tris.size(), t1); - } // for i ntris + } // for i ntris mvsUtils::finishEstimate(); mvsUtils::printfElapsedTime(tstart); @@ -859,19 +841,23 @@ void Mesh::getDepthMap(StaticVector& depthMap, const mvsUtils::MultiViewP getDepthMap(depthMap, tmp, mp, rc, scale, w, h); } -void Mesh::getDepthMap(StaticVector& depthMap, StaticVector>& tmp, const mvsUtils::MultiViewParams& mp, - int rc, int scale, int w, int h) +void Mesh::getDepthMap(StaticVector& depthMap, + StaticVector>& tmp, + const mvsUtils::MultiViewParams& mp, + int rc, + int scale, + int w, + int h) { depthMap.resize_with(w * h, -1.0f); Pixel pix; - for(pix.x = 0; pix.x < w; ++pix.x) + for (pix.x = 0; pix.x < w; ++pix.x) { - for(pix.y = 0; pix.y < h; ++pix.y) + for (pix.y = 0; pix.y < h; ++pix.y) { - StaticVector& ti = tmp[pix.x * h + pix.y]; - if(!ti.empty()) + if (!ti.empty()) { Point2d p; p.x = (double)pix.x; @@ -879,7 +865,7 @@ void Mesh::getDepthMap(StaticVector& depthMap, StaticVector& depthMap, StaticVector& depthMap, StaticVector& out_visTri, const std::string& depthMapFilepath, const std::string& trisMapFilepath, - const mvsUtils::MultiViewParams& mp, int rc, int w, int h) +void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, + const std::string& depthMapFilepath, + const std::string& trisMapFilepath, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h) { StaticVector depthMap; loadArrayFromFile(depthMap, depthMapFilepath); @@ -968,7 +958,12 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, const std:: getVisibleTrianglesIndexes(out_visTri, trisMap, depthMap, mp, rc, w, h); } -void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, const std::string& tmpDir, const mvsUtils::MultiViewParams& mp, int rc, int w, int h) +void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, + const std::string& tmpDir, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h) { std::string depthMapFilepath = tmpDir + "depthMap" + std::to_string(mp.getViewId(rc)) + ".bin"; std::string trisMapFilepath = tmpDir + "trisMap" + std::to_string(mp.getViewId(rc)) + ".bin"; @@ -981,27 +976,31 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, const std:: getVisibleTrianglesIndexes(out_visTri, trisMap, depthMap, mp, rc, w, h); } -void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVector& depthMap, const mvsUtils::MultiViewParams& mp, int rc, - int w, int h) +void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, + StaticVector& depthMap, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h) { int ow = mp.getWidth(rc); int oh = mp.getHeight(rc); out_visTri.reserve(tris.size()); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { Point3d cg = computeTriangleCenterOfGravity(i); Pixel pix; mp.getPixelFor3DPoint(&pix, cg, rc); - if(mp.isPixelInImage(pix, rc, 1)) + if (mp.isPixelInImage(pix, rc, 1)) { pix.x = (int)(((float)pix.x / (float)ow) * (float)w); pix.y = (int)(((float)pix.y / (float)oh) * (float)h); float depth = depthMap[pix.x * h + pix.y]; float pixSize = mp.getCamPixelSize(cg, rc) * 6.0f; // if (depth-pixSize<(mp->CArr[rc]-cg).size()) { - if(fabs(depth - (mp.CArr[rc] - cg).size()) < pixSize) + if (fabs(depth - (mp.CArr[rc] - cg).size()) < pixSize) { out_visTri.push_back(i); } @@ -1009,9 +1008,13 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVecto } } -void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVector>& trisMap, - StaticVector& depthMap, const mvsUtils::MultiViewParams& mp, int rc, - int w, int h) +void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, + StaticVector>& trisMap, + StaticVector& depthMap, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h) { int ow = mp.getWidth(rc); int oh = mp.getHeight(rc); @@ -1021,25 +1024,25 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVecto btris.resize_with(tris.size(), false); Pixel pix; - for(pix.x = 0; pix.x < w; ++pix.x) + for (pix.x = 0; pix.x < w; ++pix.x) { - for(pix.y = 0; pix.y < h; ++pix.y) + for (pix.y = 0; pix.y < h; ++pix.y) { StaticVector& ti = trisMap[pix.x * h + pix.y]; - if(!ti.empty()) + if (!ti.empty()) { Point2d p; p.x = (float)pix.x; p.y = (float)pix.y; float depth = depthMap[pix.x * h + pix.y]; - for(int i = 0; i < ti.size(); ++i) + for (int i = 0; i < ti.size(); ++i) { int idTri = ti[i]; OrientedPoint tri; tri.p = pts[tris[idTri].v[0]]; - tri.n = cross((pts[tris[idTri].v[1]] - pts[tris[idTri].v[0]]).normalize(), - (pts[tris[idTri].v[2]] - pts[tris[idTri].v[0]]).normalize()); + tri.n = + cross((pts[tris[idTri].v[1]] - pts[tris[idTri].v[0]]).normalize(), (pts[tris[idTri].v[2]] - pts[tris[idTri].v[0]]).normalize()); Mesh::rectangle re = Mesh::rectangle(pix, 1); triangle_proj tp = getTriangleProjection(idTri, mp, rc, w, h); @@ -1070,19 +1073,19 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVecto Point3d lpi = linePlaneIntersect(mp.CArr[rc], (mp.iCamArr[rc] * tpip).normalize(), tri.p, tri.n); float lpidepth = (mp.CArr[rc] - lpi).size(); float pixSize = mp.getCamPixelSize(lpi, rc) * 2.0f; - if(fabs(depth - lpidepth) < pixSize) + if (fabs(depth - lpidepth) < pixSize) { btris[idTri] = true; } } } - } // for pix.y - } // for pix.x + } // for pix.y + } // for pix.x int nvistris = 0; - for(int i = 0; i < btris.size(); ++i) + for (int i = 0; i < btris.size(); ++i) { - if(btris[i]) + if (btris[i]) { ++nvistris; } @@ -1090,9 +1093,9 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVecto out_visTri.reserve(nvistris); - for(int i = 0; i < btris.size(); ++i) + for (int i = 0; i < btris.size(); ++i) { - if(btris[i]) + if (btris[i]) { out_visTri.push_back(i); } @@ -1101,19 +1104,19 @@ void Mesh::getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVecto void Mesh::generateMeshFromTrianglesSubset(const StaticVector& visTris, Mesh& outMesh, StaticVector& out_ptIdToNewPtId) const { - out_ptIdToNewPtId.resize_with(pts.size(), -1); // -1 means unused - for(int i = 0; i < visTris.size(); ++i) + out_ptIdToNewPtId.resize_with(pts.size(), -1); // -1 means unused + for (int i = 0; i < visTris.size(); ++i) { int idTri = visTris[i]; - out_ptIdToNewPtId[tris[idTri].v[0]] = 0; // 0 means used + out_ptIdToNewPtId[tris[idTri].v[0]] = 0; // 0 means used out_ptIdToNewPtId[tris[idTri].v[1]] = 0; out_ptIdToNewPtId[tris[idTri].v[2]] = 0; } int j = 0; - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { - if(out_ptIdToNewPtId[i] == 0) // if input point used + if (out_ptIdToNewPtId[i] == 0) // if input point used { out_ptIdToNewPtId[i] = j; ++j; @@ -1121,24 +1124,24 @@ void Mesh::generateMeshFromTrianglesSubset(const StaticVector& visTris, Mes } outMesh.pts.reserve(j); - + // also update vertex color data if any const bool updateColors = !_colors.empty(); auto& outColors = outMesh.colors(); outColors.reserve(_colors.size()); - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { - if(out_ptIdToNewPtId[i] > -1) + if (out_ptIdToNewPtId[i] > -1) { outMesh.pts.push_back(pts[i]); - if(updateColors) + if (updateColors) outColors.push_back(_colors[i]); } } outMesh.tris.reserve(visTris.size()); - for(int i = 0; i < visTris.size(); ++i) + for (int i = 0; i < visTris.size(); ++i) { int idTri = visTris[i]; Mesh::triangle t; @@ -1155,7 +1158,7 @@ void Mesh::getNotOrientedEdges(StaticVector>& edgesNeighTris, StaticVector edges; edges.reserve(tris.size() * 3); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { int a = tris[i].v[0]; int b = tris[i].v[1]; @@ -1172,25 +1175,25 @@ void Mesh::getNotOrientedEdges(StaticVector>& edgesNeighTris, // remove duplicities int i0 = 0; long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < edges.size(); ++i) + for (int i = 0; i < edges.size(); ++i) { - if((i == edges.size() - 1) || (edges[i].x != edges[i + 1].x)) + if ((i == edges.size() - 1) || (edges[i].x != edges[i + 1].x)) { StaticVector edges1; edges1.reserve(i - i0 + 1); - edges1.getDataWritable().insert(edges1.begin(), edges.begin()+i0, edges.begin()+i+1); + edges1.getDataWritable().insert(edges1.begin(), edges.begin() + i0, edges.begin() + i + 1); qsort(&edges1[0], edges1.size(), sizeof(Voxel), qSortCompareVoxelByYAsc); int j0 = 0; - for(int j = 0; j < edges1.size(); ++j) + for (int j = 0; j < edges1.size(); ++j) { - if((j == edges1.size() - 1) || (edges1[j].y != edges1[j + 1].y)) + if ((j == edges1.size() - 1) || (edges1[j].y != edges1[j + 1].y)) { edgesPointsPairs.push_back(Pixel(edges1[j].x, edges1[j].y)); edgesNeighTris.resize(edgesNeighTris.size() + 1); StaticVector& neighTris = edgesNeighTris.back(); neighTris.reserve(j - j0 + 1); - for(int k = j0; k <= j; ++k) + for (int k = j0; k <= j; ++k) { neighTris.push_back(edges1[k].z); } @@ -1204,22 +1207,21 @@ void Mesh::getNotOrientedEdges(StaticVector>& edgesNeighTris, mvsUtils::finishEstimate(); } -void Mesh::getLaplacianSmoothingVectors(StaticVector>& ptsNeighPts, StaticVector& out_nms, - double maximalNeighDist) +void Mesh::getLaplacianSmoothingVectors(StaticVector>& ptsNeighPts, StaticVector& out_nms, double maximalNeighDist) { out_nms.reserve(pts.size()); - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { Point3d& p = pts[i]; StaticVector& nei = ptsNeighPts[i]; int nneighs = 0; - if(!nei.empty()) + if (!nei.empty()) { nneighs = nei.size(); } - if(nneighs == 0) + if (nneighs == 0) { out_nms.push_back(Point3d(0.0, 0.0, 0.0)); } @@ -1228,7 +1230,7 @@ void Mesh::getLaplacianSmoothingVectors(StaticVector>& ptsNeig double maxNeighDist = 0.0f; // laplacian smoothing vector Point3d n = Point3d(0.0, 0.0, 0.0); - for(int j = 0; j < nneighs; ++j) + for (int j = 0; j < nneighs; ++j) { n = n + pts[nei[j]]; maxNeighDist = std::max(maxNeighDist, (p - pts[nei[j]]).size()); @@ -1238,7 +1240,7 @@ void Mesh::getLaplacianSmoothingVectors(StaticVector>& ptsNeig float d = n.size(); n = n.normalize(); - if(std::isnan(d) || std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN + if (std::isnan(d) || std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN { n = Point3d(0.0, 0.0, 0.0); } @@ -1247,12 +1249,12 @@ void Mesh::getLaplacianSmoothingVectors(StaticVector>& ptsNeig n = n * d; } - if(std::isnan(d) || std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN + if (std::isnan(d) || std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN { n = Point3d(0.0, 0.0, 0.0); } - if((maximalNeighDist > 0.0f) && (maxNeighDist > maximalNeighDist)) + if ((maximalNeighDist > 0.0f) && (maxNeighDist > maximalNeighDist)) { n = Point3d(0.0, 0.0, 0.0); } @@ -1275,7 +1277,7 @@ void Mesh::laplacianSmoothPts(StaticVector>& ptsNeighPts, doub getLaplacianSmoothingVectors(ptsNeighPts, nms, maximalNeighDist); // smooth - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { pts[i] = pts[i] + nms[i]; } @@ -1284,9 +1286,7 @@ void Mesh::laplacianSmoothPts(StaticVector>& ptsNeighPts, doub Point3d Mesh::computeTriangleNormal(int idTri) const { const Mesh::triangle& t = tris[idTri]; - return cross((pts[t.v[1]] - pts[t.v[0]]).normalize(), - (pts[t.v[2]] - pts[t.v[0]]).normalize()) - .normalize(); + return cross((pts[t.v[1]] - pts[t.v[0]]).normalize(), (pts[t.v[2]] - pts[t.v[0]]).normalize()).normalize(); } Point3d Mesh::computeTriangleCenterOfGravity(int idTri) const @@ -1298,17 +1298,13 @@ Point3d Mesh::computeTriangleCenterOfGravity(int idTri) const double Mesh::computeTriangleMaxEdgeLength(int idTri) const { const Mesh::triangle& t = tris[idTri]; - return std::max({(pts[t.v[0]] - pts[t.v[1]]).size(), - (pts[t.v[1]] - pts[t.v[2]]).size(), - (pts[t.v[2]] - pts[t.v[0]]).size()}); + return std::max({(pts[t.v[0]] - pts[t.v[1]]).size(), (pts[t.v[1]] - pts[t.v[2]]).size(), (pts[t.v[2]] - pts[t.v[0]]).size()}); } double Mesh::computeTriangleMinEdgeLength(int idTri) const { const Mesh::triangle& t = tris[idTri]; - return std::min({(pts[t.v[0]] - pts[t.v[1]]).size(), - (pts[t.v[1]] - pts[t.v[2]]).size(), - (pts[t.v[2]] - pts[t.v[0]]).size()}); + return std::min({(pts[t.v[0]] - pts[t.v[1]]).size(), (pts[t.v[1]] - pts[t.v[2]]).size(), (pts[t.v[2]] - pts[t.v[0]]).size()}); } void Mesh::computeNormalsForPts(StaticVector& out_nms) const @@ -1323,18 +1319,18 @@ void Mesh::computeNormalsForPts(StaticVector>& ptsNeighTris, S out_nms.reserve(pts.size()); out_nms.resize_with(pts.size(), Point3d(0.0f, 0.0f, 0.0f)); - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { StaticVector& triTmp = ptsNeighTris[i]; - if(!triTmp.empty()) + if (!triTmp.empty()) { Point3d n = Point3d(0.0f, 0.0f, 0.0f); float nn = 0.0f; - for(int j = 0; j < triTmp.size(); ++j) + for (int j = 0; j < triTmp.size(); ++j) { Point3d n1 = computeTriangleNormal(triTmp[j]); n1 = n1.normalize(); - if(!std::isnan(n1.x) && !std::isnan(n1.y) && !std::isnan(n1.z)) // check if is not NaN + if (!std::isnan(n1.x) && !std::isnan(n1.y) && !std::isnan(n1.z)) // check if is not NaN { n = n + computeTriangleNormal(triTmp[j]); nn += 1.0f; @@ -1343,7 +1339,7 @@ void Mesh::computeNormalsForPts(StaticVector>& ptsNeighTris, S n = n / nn; n = n.normalize(); - if(std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN + if (std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN { n = Point3d(0.0f, 0.0f, 0.0f); } @@ -1355,19 +1351,19 @@ void Mesh::computeNormalsForPts(StaticVector>& ptsNeighTris, S void Mesh::smoothNormals(StaticVector& nms, StaticVector>& ptsNeighPts) { - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { Point3d& n = nms[i]; - for(int j = 0; j < sizeOfStaticVector(ptsNeighPts[i]); ++j) + for (int j = 0; j < sizeOfStaticVector(ptsNeighPts[i]); ++j) { n = n + nms[ptsNeighPts[i][j]]; } - if(sizeOfStaticVector(ptsNeighPts[i]) > 0) + if (sizeOfStaticVector(ptsNeighPts[i]) > 0) { n = n / (float)sizeOfStaticVector(ptsNeighPts[i]); } n = n.normalize(); - if(std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) + if (std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) { n = Point3d(0.0f, 0.0f, 0.0f); } @@ -1381,7 +1377,7 @@ void Mesh::removeFreePointsFromMesh(StaticVector& out_ptIdToNewPtId) // declare all triangles as used StaticVector visTris; visTris.reserve(tris.size()); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { visTris.push_back(i); } @@ -1431,25 +1427,25 @@ void Mesh::getTrianglesEdgesIds(const StaticVector>& edgesNeig out.reserve(tris.size()); out.resize_with(tris.size(), Voxel(-1, -1, -1)); - for(int i = 0; i < edgesNeighTris.size(); ++i) + for (int i = 0; i < edgesNeighTris.size(); ++i) { - for(int j = 0; j < edgesNeighTris[i].size(); ++j) + for (int j = 0; j < edgesNeighTris[i].size(); ++j) { int idTri = edgesNeighTris[i][j]; - if(out[idTri].x == -1) + if (out[idTri].x == -1) { out[idTri].x = i; } else { - if(out[idTri].y == -1) + if (out[idTri].y == -1) { out[idTri].y = i; } else { - if(out[idTri].z == -1) + if (out[idTri].z == -1) { out[idTri].z = i; } @@ -1460,20 +1456,19 @@ void Mesh::getTrianglesEdgesIds(const StaticVector>& edgesNeig } } - } // for j - } // for i + } // for j + } // for i // check ... each triangle has to have three edge ids - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { - if((out[i].x == -1) || (out[i].y == -1) || (out[i].z == -1)) + if ((out[i].x == -1) || (out[i].y == -1) || (out[i].z == -1)) { ALICEVISION_LOG_ERROR("triangle " << i << " has to have three edge ids."); } } } - namespace subdiv { struct edge { @@ -1508,7 +1503,7 @@ struct edge // (A,B) = (0,1) or (1,2) or (2,0) void orient() { - if(localIdB != (localIdA + 1) % 3) + if (localIdB != (localIdA + 1) % 3) { localIdA = localIdB; localIdB = (localIdA + 1) % 3; @@ -1516,9 +1511,13 @@ struct edge } }; - -void subdivideTriangle(const Mesh& mesh, int triangleId, std::vector& edgesToSubdivide, StaticVector& new_tris, - StaticVector& new_trisUvIds, StaticVector& new_uvCoords, std::vector& new_trisMtlIds) +void subdivideTriangle(const Mesh& mesh, + int triangleId, + std::vector& edgesToSubdivide, + StaticVector& new_tris, + StaticVector& new_trisUvIds, + StaticVector& new_uvCoords, + std::vector& new_trisMtlIds) { int nEdgesToSubdivide = edgesToSubdivide.size(); // Triangle (A,B,C) @@ -1549,7 +1548,7 @@ void subdivideTriangle(const Mesh& mesh, int triangleId, std::vector& edge */ // Subdivide into 2 new triangles - if(nEdgesToSubdivide == 1) + if (nEdgesToSubdivide == 1) { // New point int new_id = edgesToSubdivide[0].new_pointId; @@ -1578,7 +1577,7 @@ void subdivideTriangle(const Mesh& mesh, int triangleId, std::vector& edge */ // Subdivide into 3 new triangles - else if(nEdgesToSubdivide == 2) + else if (nEdgesToSubdivide == 2) { // New point 1 int new_id1 = edgesToSubdivide[0].new_pointId; @@ -1618,7 +1617,7 @@ void subdivideTriangle(const Mesh& mesh, int triangleId, std::vector& edge */ // Subdivide into 4 new triangles - else if(nEdgesToSubdivide == 3) + else if (nEdgesToSubdivide == 3) { // New point 1 int new_id1 = edgesToSubdivide[0].new_pointId; @@ -1656,7 +1655,7 @@ void subdivideTriangle(const Mesh& mesh, int triangleId, std::vector& edge } } -} +} // namespace subdiv int Mesh::subdivideMesh(const Mesh& refMesh, float ratioSubdiv, bool remapVisibilities) { @@ -1673,7 +1672,7 @@ int Mesh::subdivideMesh(const Mesh& refMesh, float ratioSubdiv, bool remapVisibi int nbAllSubdiv = 0; int nsubd = 0; - while(pts.size() < targetNbPts) + while (pts.size() < targetNbPts) { // lengthRatio value is 0.5 with a margin to ensure that we will not generate more points than the reference mesh/pointCloud const float lengthRatio = 0.45f; @@ -1683,10 +1682,10 @@ int Mesh::subdivideMesh(const Mesh& refMesh, float ratioSubdiv, bool remapVisibi ALICEVISION_LOG_DEBUG(" - nb pts: " << pts.size()); // Stop iteration if we dont have enough subdivisions - if(nsubd <= 10) + if (nsubd <= 10) break; } - if(nbAllSubdiv == 0) + if (nbAllSubdiv == 0) { ALICEVISION_LOG_INFO("No subdivision needed."); return 0; @@ -1736,12 +1735,12 @@ int Mesh::subdivideMeshOnce(const Mesh& refMesh, const GEO::AdaptiveKdTree& refM int nEdgesToSubdivide = 0; // find which edges to subdivide - for(int i = 0; i < edgesPointsPairs.size(); ++i) + for (int i = 0; i < edgesPointsPairs.size(); ++i) { int idA = edgesPointsPairs[i].x; int idB = edgesPointsPairs[i].y; - double refLocalEdgeLength = 0; // rough estimation of points distances around point A and B + double refLocalEdgeLength = 0; // rough estimation of points distances around point A and B { int j = 0; GEO::index_t neighborsId[8]; @@ -1774,10 +1773,10 @@ int Mesh::subdivideMeshOnce(const Mesh& refMesh, const GEO::AdaptiveKdTree& refM Point3d& pointB = pts[idB]; const double edgeLength = dist(pointA, pointB); -// ALICEVISION_LOG_INFO("edge length: " << edgeLength); -// ALICEVISION_LOG_INFO("refLocalEdgeLength: " << refLocalEdgeLength); + // ALICEVISION_LOG_INFO("edge length: " << edgeLength); + // ALICEVISION_LOG_INFO("refLocalEdgeLength: " << refLocalEdgeLength); - if(refLocalEdgeLength > 0 && edgeLength * lengthRatio > refLocalEdgeLength) + if (refLocalEdgeLength > 0 && edgeLength * lengthRatio > refLocalEdgeLength) { // add new point Point3d newPoint = (pointA + pointB) * 0.5; @@ -1785,7 +1784,7 @@ int Mesh::subdivideMeshOnce(const Mesh& refMesh, const GEO::AdaptiveKdTree& refM new_pts.push_back(newPoint); // which triangles to subdivide (= edge neighbors triangles) - for(int triangleId : edgesNeighTris[i]) + for (int triangleId : edgesNeighTris[i]) { const Mesh::triangle& triangle = tris[triangleId]; @@ -1815,14 +1814,14 @@ int Mesh::subdivideMeshOnce(const Mesh& refMesh, const GEO::AdaptiveKdTree& refM new_trisUvIds.reserve(trisUvIds.size() - nTrianglesToSubdivide + 4 * nTrianglesToSubdivide); new_trisMtlIds.reserve(_trisMtlIds.size() - nTrianglesToSubdivide + 4 * nTrianglesToSubdivide); - for(int triangleId = 0; triangleId < tris.size(); ++triangleId) + for (int triangleId = 0; triangleId < tris.size(); ++triangleId) { - if(trianglesToSubdivide.find(triangleId) != trianglesToSubdivide.end()) + if (trianglesToSubdivide.find(triangleId) != trianglesToSubdivide.end()) { // sort edges in ascending & adjacent order in the triangle coordinate system - std::sort(trianglesToSubdivide[triangleId].begin(), trianglesToSubdivide[triangleId].end(), [](const subdiv::edge& a, const subdiv::edge& b) { - return (a.localIdB == b.localIdA) ; - }); + std::sort(trianglesToSubdivide[triangleId].begin(), + trianglesToSubdivide[triangleId].end(), + [](const subdiv::edge& a, const subdiv::edge& b) { return (a.localIdB == b.localIdA); }); subdiv::subdivideTriangle(*this, triangleId, trianglesToSubdivide.at(triangleId), new_tris, new_trisUvIds, new_uvCoords, new_trisMtlIds); } else @@ -1852,12 +1851,12 @@ double Mesh::computeAverageEdgeLength() const { double s = 0.0; double n = 0.0; - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { s += computeTriangleMaxEdgeLength(i); n += 1.0; } - if(n == 0.0) + if (n == 0.0) { return 0.0; } @@ -1873,10 +1872,10 @@ double Mesh::computeLocalAverageEdgeLength(const std::vector>& const std::vector& ptNeighbors = ptsNeighbors[ptId]; const int nbNeighbors = ptNeighbors.size(); - if(nbNeighbors == 0) + if (nbNeighbors == 0) return -1; - for(int i = 0; i < nbNeighbors; ++i) + for (int i = 0; i < nbNeighbors; ++i) { const Point3d& pointNeighbor = pts[ptNeighbors[i]]; localAverageEdgeLength += dist(point, pointNeighbor); @@ -1891,7 +1890,7 @@ void Mesh::letJustTringlesIdsInMesh(StaticVector& trisIdsToStay) StaticVector trisTmp; trisTmp.reserve(trisIdsToStay.size()); - for(int i = 0; i < trisIdsToStay.size(); ++i) + for (int i = 0; i < trisIdsToStay.size(); ++i) { trisTmp.push_back(tris[trisIdsToStay[i]]); } @@ -1901,15 +1900,15 @@ void Mesh::letJustTringlesIdsInMesh(StaticVector& trisIdsToStay) void Mesh::letJustTringlesIdsInMesh(const StaticVectorBool& trisToStay) { int nbTris = 0; - for(int i = 0; i < trisToStay.size(); ++i) - if(trisToStay[i]) + for (int i = 0; i < trisToStay.size(); ++i) + if (trisToStay[i]) ++nbTris; StaticVector trisTmp; trisTmp.reserve(nbTris); - for(int i = 0; i < trisToStay.size(); ++i) - if(trisToStay[i]) + for (int i = 0; i < trisToStay.size(); ++i) + if (trisToStay[i]) trisTmp.push_back(tris[i]); tris.swap(trisTmp); @@ -1917,7 +1916,7 @@ void Mesh::letJustTringlesIdsInMesh(const StaticVectorBool& trisToStay) void Mesh::computeTrisCams(StaticVector>& trisCams, const mvsUtils::MultiViewParams& mp, const std::string tmpDir) { - if(mp.verbose) + if (mp.verbose) ALICEVISION_LOG_DEBUG("Computing tris cams."); StaticVector ntrisCams; @@ -1925,14 +1924,14 @@ void Mesh::computeTrisCams(StaticVector>& trisCams, const mvsU ntrisCams.resize_with(tris.size(), 0); long t1 = mvsUtils::initEstimate(); - for(int rc = 0; rc < mp.ncams; ++rc) + for (int rc = 0; rc < mp.ncams; ++rc) { std::string visTrisFilepath = tmpDir + "visTris" + std::to_string(mp.getViewId(rc)) + ".bin"; StaticVector visTris; loadArrayFromFile(visTris, visTrisFilepath); - if(!visTris.empty()) + if (!visTris.empty()) { - for(int i = 0; i < visTris.size(); ++i) + for (int i = 0; i < visTris.size(); ++i) { int idTri = visTris[i]; ++ntrisCams[idTri]; @@ -1944,23 +1943,23 @@ void Mesh::computeTrisCams(StaticVector>& trisCams, const mvsU trisCams.reserve(tris.size()); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { - if(ntrisCams[i] > 0) + if (ntrisCams[i] > 0) { trisCams[i].reserve(ntrisCams[i]); } } t1 = mvsUtils::initEstimate(); - for(int rc = 0; rc < mp.ncams; ++rc) + for (int rc = 0; rc < mp.ncams; ++rc) { std::string visTrisFilepath = tmpDir + "visTris" + std::to_string(mp.getViewId(rc)) + ".bin"; StaticVector visTris; loadArrayFromFile(visTris, visTrisFilepath); - if(!visTris.empty()) + if (!visTris.empty()) { - for(int i = 0; i < visTris.size(); ++i) + for (int i = 0; i < visTris.size(); ++i) { int idTri = visTris[i]; trisCams[idTri].push_back(rc); @@ -1976,18 +1975,17 @@ void Mesh::computeTrisCamsFromPtsCams(StaticVector>& trisCams) // TODO: try intersection trisCams.reserve(tris.size()); - for(int idTri = 0; idTri < tris.size(); ++idTri) + for (int idTri = 0; idTri < tris.size(); ++idTri) { const Mesh::triangle& t = tris[idTri]; StaticVector cams; - int maxcams = sizeOfStaticVector(pointsVisibilities[t.v[0]]) + - sizeOfStaticVector(pointsVisibilities[t.v[1]]) + + int maxcams = sizeOfStaticVector(pointsVisibilities[t.v[0]]) + sizeOfStaticVector(pointsVisibilities[t.v[1]]) + sizeOfStaticVector(pointsVisibilities[t.v[2]]); cams.reserve(maxcams); - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { - for(int i = 0; i < sizeOfStaticVector(pointsVisibilities[t.v[k]]); ++i) + for (int i = 0; i < sizeOfStaticVector(pointsVisibilities[t.v[k]]); ++i) { cams.push_back_distinct(pointsVisibilities[t.v[k]][i]); } @@ -2006,8 +2004,7 @@ void Mesh::initFromDepthMap(const mvsUtils::MultiViewParams& mp, float* depthMap initFromDepthMap(1, mp, depthMap, rc, scale, step, alpha); } -void Mesh::initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, float* depthMap, int rc, int scale, int step, - float alpha) +void Mesh::initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, float* depthMap, int rc, int scale, int step, float alpha) { int w = mp.getWidth(rc) / (scale * step); int h = mp.getHeight(rc) / (scale * step); @@ -2016,16 +2013,15 @@ void Mesh::initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, pts.reserve(w * h); StaticVectorBool usedMap; usedMap.reserve(w * h); - for(int i = 0; i < w * h; ++i) + for (int i = 0; i < w * h; ++i) { int x = i / h; int y = i % h; float depth = depthMap[i]; - if(depth > 0.0f) + if (depth > 0.0f) { - Point3d p = mp.CArr[rc] + - (mp.iCamArr[rc] * Point2d((float)x * (float)(scale * step), (float)y * (float)(scale * step))) - .normalize() * depth; + Point3d p = + mp.CArr[rc] + (mp.iCamArr[rc] * Point2d((float)x * (float)(scale * step), (float)y * (float)(scale * step))).normalize() * depth; pts.push_back(p); usedMap.push_back(true); } @@ -2038,21 +2034,20 @@ void Mesh::initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, tris = StaticVector(); tris.reserve(w * h * 2); - for(int x = 0; x < w - 1 - stepDetail; x += stepDetail) + for (int x = 0; x < w - 1 - stepDetail; x += stepDetail) { - for(int y = 0; y < h - 1 - stepDetail; y += stepDetail) + for (int y = 0; y < h - 1 - stepDetail; y += stepDetail) { Point3d p1 = pts[x * h + y]; Point3d p2 = pts[(x + stepDetail) * h + y]; Point3d p3 = pts[(x + stepDetail) * h + y + stepDetail]; Point3d p4 = pts[x * h + y + stepDetail]; - if(usedMap[x * h + y] && usedMap[(x + stepDetail) * h + y] && - usedMap[(x + stepDetail) * h + y + stepDetail] && usedMap[x * h + y + stepDetail]) + if (usedMap[x * h + y] && usedMap[(x + stepDetail) * h + y] && usedMap[(x + stepDetail) * h + y + stepDetail] && + usedMap[x * h + y + stepDetail]) { float d = mp.getCamPixelSize(p1, rc, alpha); - if(((p1 - p2).size() < d) && ((p1 - p3).size() < d) && ((p1 - p4).size() < d) && - ((p2 - p3).size() < d) && ((p3 - p4).size() < d)) + if (((p1 - p2).size() < d) && ((p1 - p3).size() < d) && ((p1 - p4).size() < d) && ((p2 - p3).size() < d) && ((p3 - p4).size() < d)) { Mesh::triangle t; t.alive = true; @@ -2077,33 +2072,34 @@ void Mesh::initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, void Mesh::removeTrianglesInHexahedrons(StaticVector* hexahsToExcludeFromResultingMesh) { - if(hexahsToExcludeFromResultingMesh != nullptr) + if (hexahsToExcludeFromResultingMesh != nullptr) { - ALICEVISION_LOG_INFO("Remove triangles in hexahedrons: " << tris.size() << " " << static_cast(hexahsToExcludeFromResultingMesh->size() / 8)); + ALICEVISION_LOG_INFO("Remove triangles in hexahedrons: " << tris.size() << " " + << static_cast(hexahsToExcludeFromResultingMesh->size() / 8)); StaticVector trisIdsToStay; trisIdsToStay.reserve(tris.size()); long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { int nin = 0; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { Point3d p = pts[tris[i].v[k]]; bool isThere = false; - for(int j = 0; j < (int)(hexahsToExcludeFromResultingMesh->size() / 8); ++j) + for (int j = 0; j < (int)(hexahsToExcludeFromResultingMesh->size() / 8); ++j) { - if(mvsUtils::isPointInHexahedron(p, &(*hexahsToExcludeFromResultingMesh)[j * 8])) + if (mvsUtils::isPointInHexahedron(p, &(*hexahsToExcludeFromResultingMesh)[j * 8])) { isThere = true; } } - if(isThere) + if (isThere) { ++nin; } } - if(nin < 3) + if (nin < 3) { trisIdsToStay.push_back(i); } @@ -2122,23 +2118,23 @@ void Mesh::removeTrianglesOutsideHexahedron(Point3d* hexah) trisIdsToStay.reserve(tris.size()); long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { int nout = 0; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { Point3d p = pts[tris[i].v[k]]; bool isThere = false; - if(!mvsUtils::isPointInHexahedron(p, hexah)) + if (!mvsUtils::isPointInHexahedron(p, hexah)) { isThere = true; } - if(isThere) + if (isThere) { ++nout; } } - if(nout < 1) + if (nout < 1) { trisIdsToStay.push_back(i); } @@ -2156,15 +2152,15 @@ void Mesh::filterLargeEdgeTriangles(double cutAverageEdgeLengthFactor, const Sta const double averageEdgeLength = computeAverageEdgeLength(); const double avelthr = averageEdgeLength * cutAverageEdgeLengthFactor; - #pragma omp parallel for - for(int i = 0; i < tris.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < tris.size(); ++i) { - if(trisToConsider.empty() || trisToConsider[i]) + if (trisToConsider.empty() || trisToConsider[i]) { - const double triMaxEdgelength = computeTriangleMaxEdgeLength(i); + const double triMaxEdgelength = computeTriangleMaxEdgeLength(i); - if(triMaxEdgelength >= avelthr) - trisToStay[i] = false; + if (triMaxEdgelength >= avelthr) + trisToStay[i] = false; } } @@ -2175,16 +2171,16 @@ void Mesh::filterTrianglesByRatio(double ratio, const StaticVectorBool& trisToCo { ALICEVISION_LOG_INFO("Filtering triangles by ratio " << ratio << "."); - #pragma omp parallel for - for(int i = 0; i < tris.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < tris.size(); ++i) { - if(trisToConsider.empty() || trisToConsider[i]) + if (trisToConsider.empty() || trisToConsider[i]) { - const double minEdge = computeTriangleMinEdgeLength(i); - const double maxEdge = computeTriangleMaxEdgeLength(i); - - if((minEdge == 0) || ((maxEdge/minEdge) > ratio)) - trisToStay[i] = false; + const double minEdge = computeTriangleMinEdgeLength(i); + const double maxEdge = computeTriangleMaxEdgeLength(i); + + if ((minEdge == 0) || ((maxEdge / minEdge) > ratio)) + trisToStay[i] = false; } } @@ -2194,7 +2190,7 @@ void Mesh::filterTrianglesByRatio(double ratio, const StaticVectorBool& trisToCo void Mesh::invertTriangleOrientations() { ALICEVISION_LOG_INFO("Invert triangle orientations."); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { Mesh::triangle& t = tris[i]; std::swap(t.v[1], t.v[2]); @@ -2203,9 +2199,9 @@ void Mesh::invertTriangleOrientations() void Mesh::changeTriPtId(int triId, int oldPtId, int newPtId) { - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { - if(oldPtId == tris[triId].v[k]) + if (oldPtId == tris[triId].v[k]) { tris[triId].v[k] = newPtId; } @@ -2214,15 +2210,15 @@ void Mesh::changeTriPtId(int triId, int oldPtId, int newPtId) int Mesh::getTriPtIndex(int triId, int ptId, bool failIfDoesNotExists) const { - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { - if(ptId == tris[triId].v[k]) + if (ptId == tris[triId].v[k]) { return k; } } - if(failIfDoesNotExists) + if (failIfDoesNotExists) { throw std::runtime_error("Mesh::getTriPtIndex: ptId does not exist: " + std::to_string(ptId)); } @@ -2233,20 +2229,20 @@ Pixel Mesh::getTriOtherPtsIds(int triId, int _ptId) const { int others[3]; int nothers = 0; - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { - if(_ptId != tris[triId].v[k]) + if (_ptId != tris[triId].v[k]) { others[nothers] = tris[triId].v[k]; ++nothers; } } - if(nothers != 2) + if (nothers != 2) { ALICEVISION_THROW_ERROR("Mesh::getTriOtherPtsIds: pt X neighbouring tringle without pt X. (" - << "triangle Id: " << triId - << ", triangle vertices: " << tris[triId].v[0] << ", " << tris[triId].v[1] << ", " << tris[triId].v[2] << ")"); + << "triangle Id: " << triId << ", triangle vertices: " << tris[triId].v[0] << ", " << tris[triId].v[1] << ", " + << tris[triId].v[2] << ")"); } return Pixel(others[0], others[1]); @@ -2257,8 +2253,8 @@ bool Mesh::areTwoTrisSameOriented(int triId1, int triId2, int edgePtId1, int edg int t1ep2Index = getTriPtIndex(triId1, edgePtId2, true); int t2ep1Index = getTriPtIndex(triId2, edgePtId1, true); int t2ep2Index = getTriPtIndex(triId2, edgePtId2, true); - int t1Orientation = (t1ep2Index + (3 - t1ep1Index)) % 3; // can be 1 or 2; - int t2Orientation = (t2ep2Index + (3 - t2ep1Index)) % 3; // can be 1 or 2; + int t1Orientation = (t1ep2Index + (3 - t1ep1Index)) % 3; // can be 1 or 2; + int t2Orientation = (t2ep2Index + (3 - t2ep1Index)) % 3; // can be 1 or 2; return (t1Orientation != t2Orientation); } @@ -2273,8 +2269,7 @@ bool Mesh::isTriangleAngleAtVetexObtuse(int vertexIdInTriangle, int triId) const bool Mesh::isTriangleObtuse(int triId) const { - return (isTriangleAngleAtVetexObtuse(0, triId)) || (isTriangleAngleAtVetexObtuse(1, triId)) || - (isTriangleAngleAtVetexObtuse(2, triId)); + return (isTriangleAngleAtVetexObtuse(0, triId)) || (isTriangleAngleAtVetexObtuse(1, triId)) || (isTriangleAngleAtVetexObtuse(2, triId)); } void Mesh::getLargestConnectedComponentTrisIds(StaticVector& out) const @@ -2292,35 +2287,35 @@ void Mesh::getLargestConnectedComponentTrisIds(StaticVector& out) const int col = 0; int maxNptsOfCol = -1; int bestCol = -1; - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { - if(colors[i] != -1) // already labelled with a color id + if (colors[i] != -1) // already labelled with a color id continue; buff.resize(0); buff.push_back(i); int nptsOfCol = 0; - while(buff.size() > 0) + while (buff.size() > 0) { int ptid = buff.pop(); - if(colors[ptid] == -1) + if (colors[ptid] == -1) { colors[ptid] = col; ++nptsOfCol; } else { - if(colors[ptid] != col) + if (colors[ptid] != col) { throw std::runtime_error("getLargestConnectedComponentTrisIds: bad condition."); } } - for(int j = 0; j < sizeOfStaticVector(ptsNeighPtsOrdered[ptid]); ++j) + for (int j = 0; j < sizeOfStaticVector(ptsNeighPtsOrdered[ptid]); ++j) { int nptid = ptsNeighPtsOrdered[ptid][j]; - if((nptid > -1) && (colors[nptid] == -1)) + if ((nptid > -1) && (colors[nptid] == -1)) { - if(buff.size() >= buff.capacity()) // should not happen but no problem + if (buff.size() >= buff.capacity()) // should not happen but no problem { ALICEVISION_LOG_WARNING("getLargestConnectedComponentTrisIds: bad condition."); buff.reserveAdd(pts.size()); @@ -2330,7 +2325,7 @@ void Mesh::getLargestConnectedComponentTrisIds(StaticVector& out) const } } - if(maxNptsOfCol < nptsOfCol) + if (maxNptsOfCol < nptsOfCol) { maxNptsOfCol = nptsOfCol; bestCol = col; @@ -2339,12 +2334,9 @@ void Mesh::getLargestConnectedComponentTrisIds(StaticVector& out) const } out.reserve(tris.size()); - for(int i = 0; i < tris.size(); ++i) + for (int i = 0; i < tris.size(); ++i) { - if((tris[i].alive) && - (colors[tris[i].v[0]] == bestCol) && - (colors[tris[i].v[1]] == bestCol) && - (colors[tris[i].v[2]] == bestCol)) + if ((tris[i].alive) && (colors[tris[i].v[0]] == bestCol) && (colors[tris[i].v[1]] == bestCol) && (colors[tris[i].v[2]] == bestCol)) { out.push_back(i); } @@ -2366,45 +2358,41 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material normals.clear(); pointsVisibilities.clear(); - if(!boost::filesystem::exists(filepath)) + if (!boost::filesystem::exists(filepath)) { ALICEVISION_THROW_ERROR("Mesh::load: no such file: " << filepath); } // see https://github.com/assimp/assimp/blob/master/include/assimp/postprocess.h#L85 const unsigned int pFlags = - // If this flag is not specified, no vertices are referenced by more than one face - aiProcess_JoinIdenticalVertices | - // if a face contain more than 3 vertices, split it in triangles - aiProcess_Triangulate | - // Removes the node graph and pre-transforms all vertices with the local transformation matrices of their nodes. - //aiProcess_PreTransformVertices | - // This is intended to get rid of some common exporter errors - //// aiProcess_FindInvalidData | - // Face normals are shared between all points of a single face, - // so a single point can have multiple normals, which forces the library to duplicate vertices in some cases. - aiProcess_DropNormals | - // This makes sure that all indices are valid - //aiProcess_ValidateDataStructure | - aiProcess_RemoveComponent | - // This step searches all meshes for degenerate primitives and converts them to proper lines or points. - // A face is 'degenerate' if one or more of its points are identical. - aiProcess_FindDegenerates | - // aiProcess_SortByPType needed for aiProcess_FindDegenerates. - // This step splits meshes with more than one primitive type in homogeneous sub-meshes - // (point and line in different meshes, so we can remove them with AI_CONFIG_PP_SBP_REMOVE). - aiProcess_SortByPType | - 0; - importer.SetPropertyInteger( - AI_CONFIG_PP_RVC_FLAGS, - aiComponent_NORMALS | - aiComponent_TANGENTS_AND_BITANGENTS - // We do not remove texture coords as we need it for texturing, - // but it causes vertices duplicates with assimp. This is problematic for mesh post-processing - // but we should face this problem only for mesh coming from retopology, - // in which case we will only do texturing. - //aiComponent_TEXCOORDS - ); + // If this flag is not specified, no vertices are referenced by more than one face + aiProcess_JoinIdenticalVertices | + // if a face contain more than 3 vertices, split it in triangles + aiProcess_Triangulate | + // Removes the node graph and pre-transforms all vertices with the local transformation matrices of their nodes. + // aiProcess_PreTransformVertices | + // This is intended to get rid of some common exporter errors + //// aiProcess_FindInvalidData | + // Face normals are shared between all points of a single face, + // so a single point can have multiple normals, which forces the library to duplicate vertices in some cases. + aiProcess_DropNormals | + // This makes sure that all indices are valid + // aiProcess_ValidateDataStructure | + aiProcess_RemoveComponent | + // This step searches all meshes for degenerate primitives and converts them to proper lines or points. + // A face is 'degenerate' if one or more of its points are identical. + aiProcess_FindDegenerates | + // aiProcess_SortByPType needed for aiProcess_FindDegenerates. + // This step splits meshes with more than one primitive type in homogeneous sub-meshes + // (point and line in different meshes, so we can remove them with AI_CONFIG_PP_SBP_REMOVE). + aiProcess_SortByPType | 0; + importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, aiComponent_NORMALS | aiComponent_TANGENTS_AND_BITANGENTS + // We do not remove texture coords as we need it for texturing, + // but it causes vertices duplicates with assimp. This is problematic for mesh post-processing + // but we should face this problem only for mesh coming from retopology, + // in which case we will only do texturing. + // aiComponent_TEXCOORDS + ); // aiProcess_FindDegenerates will convert degenerate triangles. // As we don't want lines and points, we set the AI_CONFIG_PP_SBP_REMOVE. @@ -2425,15 +2413,14 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material const aiString defMatMame(AI_DEFAULT_MATERIAL_NAME); const int materialIdOffset = scene->mNumMaterials > 1 && scene->mMaterials[0]->GetName() == defMatMame ? 1 : 0; - std::list nodes; + std::list nodes; nodes.push_back(scene->mRootNode); std::map map_indices; - while (!nodes.empty()) - { - aiNode * node = nodes.back(); + { + aiNode* node = nodes.back(); nodes.pop_back(); Eigen::Matrix4d T; @@ -2453,7 +2440,7 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material if (!mesh->HasFaces()) { - //Why should we have a mesh without faces ? + // Why should we have a mesh without faces ? continue; } @@ -2505,14 +2492,14 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material { const unsigned int index = face.mIndices[idVertex]; const unsigned int global_index = map_indices[index]; - + triangle.v[idVertex] = global_index; if (mesh->HasTextureCoords(0)) { uvids[idVertex] = global_index; } - + if (mesh->HasNormals()) { nid[idVertex] = global_index; @@ -2542,11 +2529,11 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material { std::map oldToNewMap; StaticVector uniquePoints; - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { const Point3d& p = pts[i]; const auto it = std::find(uniquePoints.begin(), uniquePoints.end(), p); - if(it == uniquePoints.end()) + if (it == uniquePoints.end()) { oldToNewMap[i] = uniquePoints.size(); uniquePoints.push_back(p); @@ -2558,7 +2545,7 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material } pts = uniquePoints; - for(triangle& f : tris) + for (triangle& f : tris) { f.v[0] = oldToNewMap[f.v[0]]; f.v[1] = oldToNewMap[f.v[1]]; @@ -2613,18 +2600,17 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material ALICEVISION_LOG_DEBUG("Num Materials: " + std::to_string(nmtls)); } -bool Mesh::getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector& edgesXStat, - StaticVector& edgesXYStat) +bool Mesh::getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector& edgesXStat, StaticVector& edgesXYStat) { int ptId1 = std::max(edge.x, edge.y); int ptId2 = std::min(edge.x, edge.y); itr = Pixel(-1, -1); int i1 = indexOfSortedVoxelArrByX(ptId1, edgesXStat, 0, edgesXStat.size() - 1); - if(i1 > -1) + if (i1 > -1) { int i2 = indexOfSortedVoxelArrByX(ptId2, edgesXYStat, edgesXStat[i1].y, edgesXStat[i1].z); - if(i2 > -1) + if (i2 > -1) { itr = Pixel(edgesXYStat[i2].y, edgesXYStat[i2].z); } @@ -2644,36 +2630,35 @@ bool Mesh::getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector bool Mesh::lockSurfaceBoundaries(int neighbourIterations, StaticVectorBool& out_ptsCanMove, bool invert) const { using Edge = std::pair; // min vertex index, max vertex index - + // qSort lambda for Edge structure - auto qSortCompareEdgeAsc = [] (const void* ia, const void* ib) - { + auto qSortCompareEdgeAsc = [](const void* ia, const void* ib) { const Edge a = *(Edge*)ia; const Edge b = *(Edge*)ib; - if(a.first < b.first) + if (a.first < b.first) return -1; - else if(a.first == b.first) + else if (a.first == b.first) return (a.second < b.second) ? -1 : 1; return 1; }; - ALICEVISION_LOG_INFO("Lock surface " << (invert? "inner part" : "boundaries") << "."); + ALICEVISION_LOG_INFO("Lock surface " << (invert ? "inner part" : "boundaries") << "."); StaticVectorBool boundariesVertices(pts.size(), false); // Get all edges StaticVector edges(tris.size() * 3); - #pragma omp parallel for - for(int i = 0; i < tris.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < tris.size(); ++i) { - const int edgeStartIndex = i * 3; - const Mesh::triangle& t = tris[i]; + const int edgeStartIndex = i * 3; + const Mesh::triangle& t = tris[i]; - edges[edgeStartIndex] = std::make_pair(std::min(t.v[0], t.v[1]), std::max(t.v[0], t.v[1])); - edges[edgeStartIndex + 1] = std::make_pair(std::min(t.v[1], t.v[2]), std::max(t.v[1], t.v[2])); - edges[edgeStartIndex + 2] = std::make_pair(std::min(t.v[2], t.v[0]), std::max(t.v[2], t.v[0])); + edges[edgeStartIndex] = std::make_pair(std::min(t.v[0], t.v[1]), std::max(t.v[0], t.v[1])); + edges[edgeStartIndex + 1] = std::make_pair(std::min(t.v[1], t.v[2]), std::max(t.v[1], t.v[2])); + edges[edgeStartIndex + 2] = std::make_pair(std::min(t.v[2], t.v[0]), std::max(t.v[2], t.v[0])); } // Sort edges by vertex indexes @@ -2685,56 +2670,55 @@ bool Mesh::lockSurfaceBoundaries(int neighbourIterations, StaticVectorBool& out_ int edgeCount = 0; bool boundary = false; - for(int i = 0; i < edges.size(); ++i) + for (int i = 0; i < edges.size(); ++i) { - const Edge& edge = edges[i]; + const Edge& edge = edges[i]; - if((edge.first == lastEdgeFirst) && (edge.second == lastEdgeSecond)) - { - ++edgeCount; - } - else - { - if(edgeCount < 2) - { - boundariesVertices[lastEdgeFirst] = true; - boundariesVertices[lastEdgeSecond] = true; - boundary = true; - } + if ((edge.first == lastEdgeFirst) && (edge.second == lastEdgeSecond)) + { + ++edgeCount; + } + else + { + if (edgeCount < 2) + { + boundariesVertices[lastEdgeFirst] = true; + boundariesVertices[lastEdgeSecond] = true; + boundary = true; + } - lastEdgeFirst = edge.first; - lastEdgeSecond = edge.second; - edgeCount = 1; - } + lastEdgeFirst = edge.first; + lastEdgeSecond = edge.second; + edgeCount = 1; + } } // Return false if no boundary - if(!boundary) + if (!boundary) { - ALICEVISION_LOG_INFO("No vertex on surface boundary, done."); - return false; + ALICEVISION_LOG_INFO("No vertex on surface boundary, done."); + return false; } // Set is on boundary for neighbours - for(int n = 0; n < neighbourIterations; ++n) + for (int n = 0; n < neighbourIterations; ++n) { StaticVectorBool boundariesVerticesCurrent = boundariesVertices; - - #pragma omp parallel for - for(int i = 0; i < edges.size(); ++i) + +#pragma omp parallel for + for (int i = 0; i < edges.size(); ++i) { Edge& edge = edges[i]; - if(boundariesVertices[edge.first] && - boundariesVertices[edge.second]) // 2 vertices on boundary, skip + if (boundariesVertices[edge.first] && boundariesVertices[edge.second]) // 2 vertices on boundary, skip continue; - if(boundariesVertices[edge.first]) + if (boundariesVertices[edge.first]) { boost::atomic_ref{boundariesVerticesCurrent[edge.second]} = true; } - if(boundariesVertices[edge.second]) + if (boundariesVertices[edge.second]) { boost::atomic_ref{boundariesVerticesCurrent[edge.first]} = true; } @@ -2743,17 +2727,17 @@ bool Mesh::lockSurfaceBoundaries(int neighbourIterations, StaticVectorBool& out_ } // Create output vectors - if(out_ptsCanMove.empty()) - out_ptsCanMove.resize(pts.size(), true); - - #pragma omp parallel for - for(int i = 0; i < boundariesVertices.size(); ++i) + if (out_ptsCanMove.empty()) + out_ptsCanMove.resize(pts.size(), true); + +#pragma omp parallel for + for (int i = 0; i < boundariesVertices.size(); ++i) { - if(boundariesVertices[i] == !invert) + if (boundariesVertices[i] == !invert) out_ptsCanMove[i] = false; } - - ALICEVISION_LOG_INFO("Lock surface " << (invert? "inner part" : "boundaries") << ", done."); + + ALICEVISION_LOG_INFO("Lock surface " << (invert ? "inner part" : "boundaries") << ", done."); return true; } @@ -2762,49 +2746,52 @@ bool Mesh::getSurfaceBoundaries(StaticVectorBool& out_trisToConsider, bool inver // Edge struct struct Edge { - int first; // min vertex index - int second; // max vertex index - int triId; // triangle index + int first; // min vertex index + int second; // max vertex index + int triId; // triangle index - Edge() : first(0), second(0), triId(0) - {} + Edge() + : first(0), + second(0), + triId(0) + {} - Edge(int vertexId1, int vertexId2, int triangleId) : triId(triangleId) - { - first = std::min(vertexId1, vertexId2); - second = std::max(vertexId1, vertexId2); - } + Edge(int vertexId1, int vertexId2, int triangleId) + : triId(triangleId) + { + first = std::min(vertexId1, vertexId2); + second = std::max(vertexId1, vertexId2); + } }; // qSort lambda for Edge structure - auto qSortCompareEdgeAsc = [] (const void* ia, const void* ib) - { + auto qSortCompareEdgeAsc = [](const void* ia, const void* ib) { const Edge a = *(Edge*)ia; const Edge b = *(Edge*)ib; - if(a.first < b.first) + if (a.first < b.first) return -1; - else if(a.first == b.first) + else if (a.first == b.first) return (a.second < b.second) ? -1 : 1; return 1; }; - ALICEVISION_LOG_INFO("Get surface " << (invert? "inner part" : "boundaries") << "."); + ALICEVISION_LOG_INFO("Get surface " << (invert ? "inner part" : "boundaries") << "."); StaticVectorBool boundariesEdges(tris.size() * 3, false); // Get all edges StaticVector edges(tris.size() * 3); - #pragma omp parallel for - for(int i = 0; i < tris.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < tris.size(); ++i) { - const int edgeStartIndex = i * 3; - const Mesh::triangle& t = tris[i]; + const int edgeStartIndex = i * 3; + const Mesh::triangle& t = tris[i]; - edges[edgeStartIndex] = Edge(t.v[0], t.v[1], i); - edges[edgeStartIndex + 1] = Edge(t.v[1], t.v[2], i); - edges[edgeStartIndex + 2] = Edge(t.v[2], t.v[0], i); + edges[edgeStartIndex] = Edge(t.v[0], t.v[1], i); + edges[edgeStartIndex + 1] = Edge(t.v[1], t.v[2], i); + edges[edgeStartIndex + 2] = Edge(t.v[2], t.v[0], i); } // Sort edges by vertex indexes @@ -2816,51 +2803,51 @@ bool Mesh::getSurfaceBoundaries(StaticVectorBool& out_trisToConsider, bool inver int edgeCount = 0; bool boundary = false; - for(int i = 0; i < edges.size(); ++i) + for (int i = 0; i < edges.size(); ++i) { - const Edge& edge = edges[i]; + const Edge& edge = edges[i]; - if((edge.first == lastEdgeFirst) && (edge.second == lastEdgeSecond)) - { - ++edgeCount; - } - else - { - if(edgeCount < 2) - { - boundariesEdges[i-1] = true; - boundary = true; - } + if ((edge.first == lastEdgeFirst) && (edge.second == lastEdgeSecond)) + { + ++edgeCount; + } + else + { + if (edgeCount < 2) + { + boundariesEdges[i - 1] = true; + boundary = true; + } - lastEdgeFirst = edge.first; - lastEdgeSecond = edge.second; - edgeCount = 1; - } + lastEdgeFirst = edge.first; + lastEdgeSecond = edge.second; + edgeCount = 1; + } } // Return false if no boundary - if(!boundary) + if (!boundary) { - ALICEVISION_LOG_INFO("No vertex on surface boundary, done."); - return false; + ALICEVISION_LOG_INFO("No vertex on surface boundary, done."); + return false; } // Create output vectors out_trisToConsider.resize(tris.size(), false); - // Surface triangles - #pragma omp parallel for - for(int i = 0; i < edges.size(); ++i) +// Surface triangles +#pragma omp parallel for + for (int i = 0; i < edges.size(); ++i) { Edge& edge = edges[i]; - if(boundariesEdges[i] == !invert) + if (boundariesEdges[i] == !invert) { boost::atomic_ref{out_trisToConsider[edge.triId]} = true; } } - ALICEVISION_LOG_INFO("Get surface " << (invert? "inner part" : "boundaries") << ", done."); + ALICEVISION_LOG_INFO("Get surface " << (invert ? "inner part" : "boundaries") << ", done."); return true; } @@ -2878,5 +2865,5 @@ void Mesh::remapVisibilities(EVisibilityRemappingMethod remappingMethod, const M throw std::runtime_error("No visibility after visibility remapping."); } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/Mesh.hpp b/src/aliceVision/mesh/Mesh.hpp index bbac93bb37..35738ae9ec 100644 --- a/src/aliceVision/mesh/Mesh.hpp +++ b/src/aliceVision/mesh/Mesh.hpp @@ -7,7 +7,7 @@ #pragma once #include -#include +#include #include #include #include @@ -17,7 +17,7 @@ #include namespace GEO { - class AdaptiveKdTree; +class AdaptiveKdTree; } namespace aliceVision { @@ -31,10 +31,10 @@ using PointsVisibility = StaticVector; */ enum EVisibilityRemappingMethod { - Pull = 1, //< For each vertex of the input mesh, pull the visibilities from the closest vertex in the reconstruction. - Push = 2, //< For each vertex of the reconstruction, push the visibilities to the closest triangle in the input mesh. - MeshItself = 4, //< For each vertex of the mesh, test the reprojection in each camera - PullPush = Pull | Push //< Combine results from Pull and Push results. + Pull = 1, //< For each vertex of the input mesh, pull the visibilities from the closest vertex in the reconstruction. + Push = 2, //< For each vertex of the reconstruction, push the visibilities to the closest triangle in the input mesh. + MeshItself = 4, //< For each vertex of the mesh, test the reprojection in each camera + PullPush = Pull | Push //< Combine results from Pull and Push results. }; ALICEVISION_BITMASK(EVisibilityRemappingMethod); @@ -58,14 +58,12 @@ std::string EFileType_enumToString(const EFileType filetype); std::istream& operator>>(std::istream& in, EFileType& meshFileType); std::ostream& operator<<(std::ostream& os, EFileType meshFileType); - class Mesh { -public: - + public: struct triangle { - int v[3]; ///< vertex indexes + int v[3]; ///< vertex indexes bool alive; triangle() @@ -135,7 +133,7 @@ class Mesh rectangle& operator=(const rectangle param) { - for(int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { P[i] = param.P[i]; }; @@ -145,13 +143,13 @@ class Mesh } }; -protected: + protected: /// Per-vertex color data std::vector _colors; /// Per triangle material id std::vector _trisMtlIds; -public: + public: StaticVector pts; StaticVector tris; @@ -169,13 +167,18 @@ class Mesh bool loadFromBin(const std::string& binFilepath); void saveToBin(const std::string& binFilepath); - void load(const std::string& filepath, bool mergeCoincidentVerts=false, Material* material=nullptr); + void load(const std::string& filepath, bool mergeCoincidentVerts = false, Material* material = nullptr); void addMesh(const Mesh& mesh); void getTrisMap(StaticVector>& out, const mvsUtils::MultiViewParams& mp, int rc, int scale, int w, int h); - void getTrisMap(StaticVector>& out, StaticVector& visTris, const mvsUtils::MultiViewParams& mp, int rc, int scale, - int w, int h); + void getTrisMap(StaticVector>& out, + StaticVector& visTris, + const mvsUtils::MultiViewParams& mp, + int rc, + int scale, + int w, + int h); /// Per-vertex color data const accessor const std::vector& colors() const { return _colors; } /// Per-vertex color data accessor @@ -186,29 +189,51 @@ class Mesh std::vector& trisMtlIds() { return _trisMtlIds; } void getDepthMap(StaticVector& depthMap, const mvsUtils::MultiViewParams& mp, int rc, int scale, int w, int h); - void getDepthMap(StaticVector& depthMap, StaticVector>& tmp, const mvsUtils::MultiViewParams& mp, int rc, - int scale, int w, int h); + void getDepthMap(StaticVector& depthMap, + StaticVector>& tmp, + const mvsUtils::MultiViewParams& mp, + int rc, + int scale, + int w, + int h); void getPtsNeighbors(std::vector>& out_ptsNeighTris) const; void getPtsNeighborTriangles(StaticVector>& out_ptsNeighTris) const; void getPtsNeighPtsOrdered(StaticVector>& out_ptsNeighTris) const; - void getVisibleTrianglesIndexes(StaticVector& out_visTri, const std::string& tmpDir, const mvsUtils::MultiViewParams& mp, int rc, int w, int h); - void getVisibleTrianglesIndexes(StaticVector& out_visTri, const std::string& depthMapFilepath, const std::string& trisMapFilepath, - const mvsUtils::MultiViewParams& mp, int rc, int w, int h); - void getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVector>& trisMap, - StaticVector& depthMap, const mvsUtils::MultiViewParams& mp, int rc, int w, - int h); - void getVisibleTrianglesIndexes(StaticVector& out_visTri, StaticVector& depthMap, const mvsUtils::MultiViewParams& mp, int rc, int w, - int h); + void getVisibleTrianglesIndexes(StaticVector& out_visTri, + const std::string& tmpDir, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h); + void getVisibleTrianglesIndexes(StaticVector& out_visTri, + const std::string& depthMapFilepath, + const std::string& trisMapFilepath, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h); + void getVisibleTrianglesIndexes(StaticVector& out_visTri, + StaticVector>& trisMap, + StaticVector& depthMap, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h); + void getVisibleTrianglesIndexes(StaticVector& out_visTri, + StaticVector& depthMap, + const mvsUtils::MultiViewParams& mp, + int rc, + int w, + int h); void generateMeshFromTrianglesSubset(const StaticVector& visTris, Mesh& outMesh, StaticVector& out_ptIdToNewPtId) const; void getNotOrientedEdges(StaticVector>& edgesNeighTris, StaticVector& edgesPointsPairs); void getTrianglesEdgesIds(const StaticVector>& edgesNeighTris, StaticVector& out) const; - void getLaplacianSmoothingVectors(StaticVector>& ptsNeighPts, StaticVector& out_nms, - double maximalNeighDist = -1.0f); + void getLaplacianSmoothingVectors(StaticVector>& ptsNeighPts, StaticVector& out_nms, double maximalNeighDist = -1.0f); void laplacianSmoothPts(float maximalNeighDist = -1.0f); void laplacianSmoothPts(StaticVector>& ptsNeighPts, double maximalNeighDist = -1.0f); void computeNormalsForPts(StaticVector& out_nms) const; @@ -230,7 +255,7 @@ class Mesh bool isTriangleAngleAtVetexObtuse(int vertexIdInTriangle, int triId) const; bool isTriangleObtuse(int triId) const; -public: + public: double computeTriangleProjectionArea(const triangle_proj& tp) const; double computeTriangleArea(int idTri) const; Mesh::triangle_proj getTriangleProjection(int triid, const mvsUtils::MultiViewParams& mp, int rc, int w, int h) const; @@ -238,9 +263,13 @@ class Mesh int getTriangleNbVertexInImage(const mvsUtils::MultiViewParams& mp, const Mesh::triangle_proj& tp, int camId, int margin) const; bool doesTriangleIntersectsRectangle(Mesh::triangle_proj& tp, Mesh::rectangle& re); void getTrianglePixelIntersectionsAndInternalPoints(Mesh::triangle_proj& tp, Mesh::rectangle& re, StaticVector& out); - void getTrianglePixelIntersectionsAndInternalPoints(const mvsUtils::MultiViewParams& mp, int idTri, Pixel& pix, - int rc, Mesh::triangle_proj& tp, Mesh::rectangle& re, - StaticVector& out); + void getTrianglePixelIntersectionsAndInternalPoints(const mvsUtils::MultiViewParams& mp, + int idTri, + Pixel& pix, + int rc, + Mesh::triangle_proj& tp, + Mesh::rectangle& re, + StaticVector& out); Point2d getTrianglePixelInternalPoint(Mesh::triangle_proj& tp, Mesh::rectangle& re); @@ -252,27 +281,26 @@ class Mesh void initFromDepthMap(const mvsUtils::MultiViewParams& mp, float* depthMap, int rc, int scale, int step, float alpha); void initFromDepthMap(const mvsUtils::MultiViewParams& mp, StaticVector& depthMap, int rc, int scale, float alpha); - void initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, float* depthMap, int rc, int scale, int step, - float alpha); + void initFromDepthMap(int stepDetail, const mvsUtils::MultiViewParams& mp, float* depthMap, int rc, int scale, int step, float alpha); void removeTrianglesInHexahedrons(StaticVector* hexahsToExcludeFromResultingMesh); void removeTrianglesOutsideHexahedron(Point3d* hexah); - /** - * @brief Find all triangles with an edge length higher than the average in order to be removed. - * @param[in] cutAverageEdgeLengthFactor The average edge length filtering factor. - * @param[in] trisToConsider The input triangle group. if empty, all triangles of the mesh. - * @param[out] trisIdsToStay For each triangle set to true if the triangle should stay. - * @return false if no boundaries. - */ + /** + * @brief Find all triangles with an edge length higher than the average in order to be removed. + * @param[in] cutAverageEdgeLengthFactor The average edge length filtering factor. + * @param[in] trisToConsider The input triangle group. if empty, all triangles of the mesh. + * @param[out] trisIdsToStay For each triangle set to true if the triangle should stay. + * @return false if no boundaries. + */ void filterLargeEdgeTriangles(double cutAverageEdgeLengthFactor, const StaticVectorBool& trisToConsider, StaticVectorBool& trisIdsToStay) const; - /** - * @brief Find all triangles with [maxEdge/minEdge > ratio] in order to be removed. - * @param[in] ratio The filtering ratio. - * @param[in] trisToConsider The input triangle group. if empty, all triangles of the mesh. - * @param[out] trisIdsToStay For each triangle set to true if the triangle should stay. - * @return false if no boundaries. - */ + /** + * @brief Find all triangles with [maxEdge/minEdge > ratio] in order to be removed. + * @param[in] ratio The filtering ratio. + * @param[in] trisToConsider The input triangle group. if empty, all triangles of the mesh. + * @param[out] trisIdsToStay For each triangle set to true if the triangle should stay. + * @return false if no boundaries. + */ void filterTrianglesByRatio(double ratio, const StaticVectorBool& trisToConsider, StaticVectorBool& trisIdsToStay) const; void invertTriangleOrientations(); @@ -282,26 +310,24 @@ class Mesh bool areTwoTrisSameOriented(int triId1, int triId2, int edgePtId1, int edgePtId2) const; void getLargestConnectedComponentTrisIds(StaticVector& out) const; - bool getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector& edgesXStat, - StaticVector& edgesXYStat); + bool getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector& edgesXStat, StaticVector& edgesXYStat); - /** - * @brief Lock mesh vertices on the surface boundaries. - * @param[in] neighbourIterations Number of boudary neighbours. - * @param[out] out_ptsCanMove For each mesh vertices set to true if locked. Initialized if empty. - * @param[in] invert if true lock all vertices not on the surface boundaries. - * @return false if no boundaries. - */ + /** + * @brief Lock mesh vertices on the surface boundaries. + * @param[in] neighbourIterations Number of boudary neighbours. + * @param[out] out_ptsCanMove For each mesh vertices set to true if locked. Initialized if empty. + * @param[in] invert if true lock all vertices not on the surface boundaries. + * @return false if no boundaries. + */ bool lockSurfaceBoundaries(int neighbourIterations, StaticVectorBool& out_ptsCanMove, bool invert = false) const; - /** - * @brief Get mesh triangles on the surface boundaries. - * @param[out] out_trisToConsider For each mesh triangle set to true if on surface. - * @param[in] invert If true get all triangles not on the surface boundaries. - * @return false if no boundaries. - */ + /** + * @brief Get mesh triangles on the surface boundaries. + * @param[out] out_trisToConsider For each mesh triangle set to true if on surface. + * @param[in] invert If true get all triangles not on the surface boundaries. + * @return false if no boundaries. + */ bool getSurfaceBoundaries(StaticVectorBool& out_trisToConsider, bool invert = false) const; - /** * @brief Remap visibilities @@ -313,5 +339,5 @@ class Mesh void remapVisibilities(EVisibilityRemappingMethod remappingMethod, const Mesh& refMesh); }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/MeshAnalyze.cpp b/src/aliceVision/mesh/MeshAnalyze.cpp index 6bfc573cd0..2740a59676 100644 --- a/src/aliceVision/mesh/MeshAnalyze.cpp +++ b/src/aliceVision/mesh/MeshAnalyze.cpp @@ -11,7 +11,7 @@ namespace aliceVision { namespace mesh { MeshAnalyze::MeshAnalyze(mvsUtils::MultiViewParams* _mp) - : MeshClean(_mp) + : MeshClean(_mp) {} MeshAnalyze::~MeshAnalyze() = default; @@ -28,7 +28,7 @@ double MeshAnalyze::getCotanOfAngle(Point3d& vo, Point3d& v1, Point3d& v2) /* denom can be zero if u==v. Returning 0 is acceptable, based on * the callers of this function below. */ - if(denom == 0.0) + if (denom == 0.0) return (0.0); return (udotv / denom); @@ -38,12 +38,12 @@ double MeshAnalyze::getRegionArea(int vertexIdInTriangle, int triId) { /* cf. Section 3.3 of [Meyer et al 2002] */ double triArea = computeTriangleArea(triId); - if(triArea == 0.0) + if (triArea == 0.0) return (0.0); - if(isTriangleObtuse(triId)) + if (isTriangleObtuse(triId)) { - if(isTriangleAngleAtVetexObtuse(vertexIdInTriangle, triId)) + if (isTriangleAngleAtVetexObtuse(vertexIdInTriangle, triId)) return (triArea / 2.0); return (triArea / 4.0); @@ -59,9 +59,9 @@ double MeshAnalyze::getRegionArea(int vertexIdInTriangle, int triId) int MeshAnalyze::getVertexIdInTriangleForPtId(int ptId, int triId) { - for(int i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { - if(tris[triId].v[i] == ptId) + if (tris[triId].v[i] == ptId) { return i; } @@ -73,13 +73,13 @@ bool MeshAnalyze::getVertexSurfaceNormal(int ptId, Point3d& N) { StaticVector& ptNeighPtsOrdered = ptsNeighPtsOrdered[ptId]; StaticVector& ptNeighTris = ptsNeighTrisSortedAsc[ptId]; - if((isIsBoundaryPt(ptId)) || ptNeighPtsOrdered.empty() || ptNeighTris.empty() ) + if ((isIsBoundaryPt(ptId)) || ptNeighPtsOrdered.empty() || ptNeighTris.empty()) { return false; } N = Point3d(); - for(int i = 0; i < ptNeighTris.size(); i++) + for (int i = 0; i < ptNeighTris.size(); i++) { int triId = ptNeighTris[i]; N = N + computeTriangleNormal(triId); @@ -94,13 +94,13 @@ bool MeshAnalyze::getVertexMeanCurvatureNormal(int ptId, Point3d& Kh) { StaticVector& ptNeighPtsOrdered = ptsNeighPtsOrdered[ptId]; StaticVector& ptNeighTris = ptsNeighTrisSortedAsc[ptId]; - if((isIsBoundaryPt(ptId)) || ptNeighPtsOrdered.empty() || ptNeighTris.empty()) + if ((isIsBoundaryPt(ptId)) || ptNeighPtsOrdered.empty() || ptNeighTris.empty()) { return false; } double area = 0.0; - for(int i = 0; i < ptNeighTris.size(); i++) + for (int i = 0; i < ptNeighTris.size(); i++) { int triId = ptNeighTris[i]; int vertexIdInTriangle = getVertexIdInTriangleForPtId(ptId, triId); @@ -109,10 +109,10 @@ bool MeshAnalyze::getVertexMeanCurvatureNormal(int ptId, Point3d& Kh) Kh = Point3d(0.0f, 0.0f, 0.0f); - for(int i = 0; i < ptNeighPtsOrdered.size(); i++) + for (int i = 0; i < ptNeighPtsOrdered.size(); i++) { int ip1 = i + 1; - if(ip1 >= ptNeighPtsOrdered.size()) + if (ip1 >= ptNeighPtsOrdered.size()) { ip1 = 0; } @@ -127,7 +127,7 @@ bool MeshAnalyze::getVertexMeanCurvatureNormal(int ptId, Point3d& Kh) Kh = Kh + (v1 - v) * temp; } - if(area > 0.0) + if (area > 0.0) { Kh = Kh / (2.0f * area); } @@ -143,7 +143,7 @@ bool MeshAnalyze::getVertexMeanCurvatureNormal(int ptId, Point3d& Kh) void MeshAnalyze::getVertexPrincipalCurvatures(double Kh, double Kg, double& K1, double& K2) { double temp = Kh * Kh - Kg; - if(temp < 0.0) + if (temp < 0.0) temp = 0.0; temp = sqrt(temp); K1 = Kh + temp; @@ -153,17 +153,17 @@ void MeshAnalyze::getVertexPrincipalCurvatures(double Kh, double Kg, double& K1, bool MeshAnalyze::applyLaplacianOperator(int ptId, const StaticVector& ptsToApplyLaplacianOp, Point3d& ln) { StaticVector& ptNeighPtsOrdered = ptsNeighPtsOrdered[ptId]; - if(ptNeighPtsOrdered.empty()) + if (ptNeighPtsOrdered.empty()) { return false; } ln = Point3d(0.0f, 0.0f, 0.0f); - for(int i = 0; i < ptNeighPtsOrdered.size(); i++) + for (int i = 0; i < ptNeighPtsOrdered.size(); i++) { Point3d npt = ptsToApplyLaplacianOp[ptNeighPtsOrdered[i]]; - if((npt.x == 0.0f) && (npt.y == 0.0f) && (npt.z == 0.0f)) + if ((npt.x == 0.0f) && (npt.y == 0.0f) && (npt.z == 0.0f)) { ALICEVISION_LOG_WARNING("MeshAnalyze::applyLaplacianOperator: zero neighb pt"); return false; @@ -175,7 +175,7 @@ bool MeshAnalyze::applyLaplacianOperator(int ptId, const StaticVector& Point3d n = ln; float d = n.size(); n = n.normalize(); - if(std::isnan(d) || std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN + if (std::isnan(d) || std::isnan(n.x) || std::isnan(n.y) || std::isnan(n.z)) // check if is not NaN { ALICEVISION_LOG_WARNING("MeshAnalyze::applyLaplacianOperator: nan"); return false; @@ -186,10 +186,7 @@ bool MeshAnalyze::applyLaplacianOperator(int ptId, const StaticVector& // othake et al 00 Polyhedral Surface Smoothing with Simultaneous Mesh Regularization // page 3 eq (3) -bool MeshAnalyze::getLaplacianSmoothingVector(int ptId, Point3d& ln) -{ - return applyLaplacianOperator(ptId, pts, ln); -} +bool MeshAnalyze::getLaplacianSmoothingVector(int ptId, Point3d& ln) { return applyLaplacianOperator(ptId, pts, ln); } // kobbelt kampagna 98 Interactive Multi-Resolution Modeling on Arbitrary Meshes // page 5: @@ -197,20 +194,20 @@ bool MeshAnalyze::getLaplacianSmoothingVector(int ptId, Point3d& ln) // U2 - bi-laplacian is obtained when apply to laplacian points bool MeshAnalyze::getBiLaplacianSmoothingVector(int ptId, const StaticVector& ptsLaplacian, Point3d& tp) { - if(applyLaplacianOperator(ptId, ptsLaplacian, tp)) + if (applyLaplacianOperator(ptId, ptsLaplacian, tp)) { StaticVector& ptNeighPtsOrdered = ptsNeighPtsOrdered[ptId]; StaticVector& ptNeighTris = ptsNeighTrisSortedAsc[ptId]; - if(ptNeighPtsOrdered.empty() || ptNeighTris.empty() ) + if (ptNeighPtsOrdered.empty() || ptNeighTris.empty()) { return false; } float sum = 0.0f; - for(int i = 0; i < sizeOfStaticVector(ptNeighPtsOrdered); i++) + for (int i = 0; i < sizeOfStaticVector(ptNeighPtsOrdered); i++) { int neighValence = sizeOfStaticVector(ptsNeighPtsOrdered[ptNeighPtsOrdered[i]]); - if(neighValence > 0) + if (neighValence > 0) { sum += 1.0f / (float)neighValence; } @@ -222,7 +219,7 @@ bool MeshAnalyze::getBiLaplacianSmoothingVector(int ptId, const StaticVector epsilon) + if (cosTheta > epsilon) { F = (m * absH) / cosTheta; return true; } - if(cosTheta < -epsilon) + if (cosTheta < -epsilon) { F = Hn * 2.0f - (m * absH) / cosTheta; return true; } - if(fabs(cosTheta) <= epsilon) + if (fabs(cosTheta) <= epsilon) { F = Point3d(0.0f, 0.0f, 0.0f); return true; @@ -277,5 +274,5 @@ bool MeshAnalyze::getMeanCurvAndLaplacianSmoothing(int ptId, Point3d& F, float e return false; } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/MeshAnalyze.hpp b/src/aliceVision/mesh/MeshAnalyze.hpp index 941dafce5b..86259d40e0 100644 --- a/src/aliceVision/mesh/MeshAnalyze.hpp +++ b/src/aliceVision/mesh/MeshAnalyze.hpp @@ -16,7 +16,7 @@ namespace mesh { class MeshAnalyze : public MeshClean { -public: + public: MeshAnalyze(mvsUtils::MultiViewParams* _mp); ~MeshAnalyze(); @@ -32,5 +32,5 @@ class MeshAnalyze : public MeshClean bool getVertexSurfaceNormal(int ptId, Point3d& N); }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/MeshClean.cpp b/src/aliceVision/mesh/MeshClean.cpp index c3817a740a..b0b5910cc1 100644 --- a/src/aliceVision/mesh/MeshClean.cpp +++ b/src/aliceVision/mesh/MeshClean.cpp @@ -11,10 +11,9 @@ namespace aliceVision { namespace mesh { MeshClean::path::path(MeshClean* mesh, int ptId) -: meshClean(mesh) -, _ptId(ptId) -{ -} + : meshClean(mesh), + _ptId(ptId) +{} MeshClean::path::~path() = default; @@ -27,20 +26,21 @@ bool MeshClean::path::addNextTriIdToPathBack(int nextTriId, StaticVectorgetTriOtherPtsIds(nextTriId, _ptId); - if((firstTriId == nextTriId) || ((ptId1 == others2.x) && (ptId1 == others2.y))) + if ((firstTriId == nextTriId) || ((ptId1 == others2.x) && (ptId1 == others2.y))) { std::stringstream s; - s << "MeshClean::path::addNextTriIdToPathFront: firstTriId=" << firstTriId << ", nextTriId=" << nextTriId << ", ptId1=" << ptId1 << ", other2=" << others2.x << "," << others2.y; + s << "MeshClean::path::addNextTriIdToPathFront: firstTriId=" << firstTriId << ", nextTriId=" << nextTriId << ", ptId1=" << ptId1 + << ", other2=" << others2.x << "," << others2.y; throw std::runtime_error(s.str()); } - if(ptId1 == others2.x) + if (ptId1 == others2.x) { path.push_front(pathPart(nextTriId, others2.y, others2.x)); return true; } - if(ptId1 == others2.y) + if (ptId1 == others2.y) { path.push_front(pathPart(nextTriId, others2.x, others2.y)); return true; @@ -81,26 +82,26 @@ bool MeshClean::path::addNextTriIdToPathFront(int nextTriId, StaticVector& ptNeighTrisSortedAscToProcess, - StaticVector& out_path) + StaticVector& out_path) { int lastTriId = out_path[out_path.size() - 1].triId; int ptId2 = out_path[out_path.size() - 1].ptsIds[1]; int id = ptNeighTrisSortedAscToProcess.indexOfSorted(lastTriId); - if(id > -1) + if (id > -1) { ptNeighTrisSortedAscToProcess.remove(id); } Pixel itr; - if(meshClean->getEdgeNeighTrisInterval(itr, _ptId, ptId2)) + if (meshClean->getEdgeNeighTrisInterval(itr, _ptId, ptId2)) { - for(int j = itr.x; j <= itr.y; j++) + for (int j = itr.x; j <= itr.y; j++) { - if(meshClean->edgesNeigTrisAlive[j]) + if (meshClean->edgesNeigTrisAlive[j]) { int nextTriId = meshClean->edgesNeigTris[j].z; - if((ptNeighTrisSortedAscToProcess.indexOfSorted(nextTriId) > -1) && - (meshClean->areTwoTrisSameOriented(lastTriId, nextTriId, _ptId, ptId2))) + if ((ptNeighTrisSortedAscToProcess.indexOfSorted(nextTriId) > -1) && + (meshClean->areTwoTrisSameOriented(lastTriId, nextTriId, _ptId, ptId2))) { return nextTriId; } @@ -112,12 +113,12 @@ int MeshClean::path::getNextNeighBouringUnprocessedLast(StaticVector& ptNei } int MeshClean::path::getNextNeighBouringUnprocessedFirst(StaticVector& ptNeighTrisSortedAscToProcess, - StaticVector& out_path) + StaticVector& out_path) { int firstTriId = out_path[0].triId; int ptId1 = out_path[0].ptsIds[0]; int id = ptNeighTrisSortedAscToProcess.indexOfSorted(firstTriId); - if(id > -1) + if (id > -1) { ptNeighTrisSortedAscToProcess.remove(id); } @@ -126,15 +127,15 @@ int MeshClean::path::getNextNeighBouringUnprocessedFirst(StaticVector& ptNe // ",(*ptNeighTrisSortedAscToProcess)[i]); };printf("\n"); Pixel itr; - if(meshClean->getEdgeNeighTrisInterval(itr, _ptId, ptId1)) + if (meshClean->getEdgeNeighTrisInterval(itr, _ptId, ptId1)) { - for(int j = itr.x; j <= itr.y; j++) + for (int j = itr.x; j <= itr.y; j++) { - if(meshClean->edgesNeigTrisAlive[j]) + if (meshClean->edgesNeigTrisAlive[j]) { int nextTriId = meshClean->edgesNeigTris[j].z; - if((ptNeighTrisSortedAscToProcess.indexOfSorted(nextTriId) > -1) && - (meshClean->areTwoTrisSameOriented(firstTriId, nextTriId, _ptId, ptId1))) + if ((ptNeighTrisSortedAscToProcess.indexOfSorted(nextTriId) > -1) && + (meshClean->areTwoTrisSameOriented(firstTriId, nextTriId, _ptId, ptId1))) { return nextTriId; } @@ -149,12 +150,12 @@ void MeshClean::path::printfState(StaticVector& path) { ALICEVISION_LOG_DEBUG("ptid: " << _ptId); ALICEVISION_LOG_DEBUG("tris in path:"); - for(int i = 0; i < path.size(); i++) + for (int i = 0; i < path.size(); i++) { ALICEVISION_LOG_DEBUG("\t- " << path[i].triId); } ALICEVISION_LOG_DEBUG("pts in path:"); - for(int i = 0; i < path.size(); i++) + for (int i = 0; i < path.size(); i++) { ALICEVISION_LOG_DEBUG("\t- (" << path[i].ptsIds[0] << " " << path[i].ptsIds[1] << ")"); } @@ -164,7 +165,7 @@ int MeshClean::path::nCrossings(StaticVector& path) { int n = 0; - if(path.size() > 1) + if (path.size() > 1) { StaticVector ptsOfPathSorted; ptsOfPathSorted.reserve(path.size() + 1); @@ -172,10 +173,10 @@ int MeshClean::path::nCrossings(StaticVector& path) ptsOfPathSorted.push_back(path[0].ptsIds[1]); qsort(&ptsOfPathSorted[0], ptsOfPathSorted.size(), sizeof(int), qSortCompareIntAsc); - for(int i = 1; i < path.size(); i++) + for (int i = 1; i < path.size(); i++) { int id = ptsOfPathSorted.indexOfSorted(path[i].ptsIds[1]); - if(id == -1) + if (id == -1) { ptsOfPathSorted.push_back(id); qsort(&ptsOfPathSorted[0], ptsOfPathSorted.size(), sizeof(int), qSortCompareIntAsc); @@ -194,34 +195,34 @@ void MeshClean::path::removeCycleFromPath(StaticVector= 1) + if (inPath.size() >= 1) { outPath.push_back(inPath[0]); int i = 1; int idPrev = -1; - while((i < inPath.size()) && (idPrev == -1)) + while ((i < inPath.size()) && (idPrev == -1)) { outPath.push_back(inPath[i]); - for(int j = 0; j < outPath.size() - 1; j++) + for (int j = 0; j < outPath.size() - 1; j++) { - if(outPath[j].ptsIds[0] == outPath[outPath.size() - 1].ptsIds[1]) + if (outPath[j].ptsIds[0] == outPath[outPath.size() - 1].ptsIds[1]) { idPrev = j; } } - if(idPrev == -1) + if (idPrev == -1) { i++; } } - if(idPrev > -1) + if (idPrev > -1) { - for(int j = 0; j < idPrev; j++) + for (int j = 0; j < idPrev; j++) { outPath.remove(0); } - for(int j = idPrev; j <= i; j++) + for (int j = idPrev; j <= i; j++) { inPath.remove(idPrev); } @@ -236,17 +237,17 @@ void MeshClean::path::deployTriangle(int triId) { // printf("triId %i\n"); Pixel others = meshClean->getTriOtherPtsIds(triId, _ptId); - for(int i = 0; i < 2; i++) + for (int i = 0; i < 2; i++) { Pixel itr; - if(meshClean->getEdgeNeighTrisInterval(itr, _ptId, others[i])) + if (meshClean->getEdgeNeighTrisInterval(itr, _ptId, others[i])) { - for(int j = itr.x; j <= itr.y; j++) + for (int j = itr.x; j <= itr.y; j++) { - if(meshClean->edgesNeigTrisAlive[j]) + if (meshClean->edgesNeigTrisAlive[j]) { int nextTriId = meshClean->edgesNeigTris[j].z; - if(triId == nextTriId) + if (triId == nextTriId) { meshClean->edgesNeigTrisAlive[j] = false; } @@ -264,7 +265,7 @@ int MeshClean::path::deployTriangles(StaticVector& trisIds, bool isBoundary int newPtId = meshClean->pts.size() - 1; int origPtId = _ptId; - while(origPtId >= meshClean->nPtsInit) + while (origPtId >= meshClean->nPtsInit) { origPtId = meshClean->newPtsOldPtId[origPtId - meshClean->nPtsInit]; } @@ -277,7 +278,7 @@ int MeshClean::path::deployTriangles(StaticVector& trisIds, bool isBoundary meshClean->ptsNeighTrisSortedAsc.reserveAddIfNeeded(trisIds.size(), 1000); StaticVector newPtNeighTrisSortedAsc; newPtNeighTrisSortedAsc.reserve(trisIds.size()); - for(int i = 0; i < trisIds.size(); i++) + for (int i = 0; i < trisIds.size(); i++) { newPtNeighTrisSortedAsc.push_back(trisIds[i]); } @@ -291,13 +292,13 @@ int MeshClean::path::deployTriangles(StaticVector& trisIds, bool isBoundary //}; // update edgesNeigTris - for(int i = 0; i < trisIds.size(); i++) + for (int i = 0; i < trisIds.size(); i++) { deployTriangle(trisIds[i]); } // change actual tris to new pt - for(int i = 0; i < trisIds.size(); i++) + for (int i = 0; i < trisIds.size(); i++) { meshClean->changeTriPtId(trisIds[i], _ptId, newPtId); } @@ -310,7 +311,7 @@ int MeshClean::path::deployTriangles(StaticVector& trisIds, bool isBoundary int i0 = meshClean->edgesNeigTris.size(); // in the case when the apth is not cycle - for(int i = 0; i < trisIds.size(); i++) + for (int i = 0; i < trisIds.size(); i++) { Pixel others = meshClean->getTriOtherPtsIds(trisIds[i], newPtId); meshClean->edgesNeigTris.push_back(Voxel(newPtId, others[0], trisIds[i])); @@ -322,17 +323,17 @@ int MeshClean::path::deployTriangles(StaticVector& trisIds, bool isBoundary { int i = meshClean->edgesNeigTris.size() - 1; - if(i - i0 + 1 > 1) + if (i - i0 + 1 > 1) qsort(&meshClean->edgesNeigTris[i0], i - i0 + 1, sizeof(Voxel), qSortCompareVoxelByYAsc); int xyI0 = meshClean->edgesXYStat.size(); int j0 = i0; - for(int j = i0; j <= i; j++) + for (int j = i0; j <= i; j++) { - if((j == i) || (meshClean->edgesNeigTris[j].y != meshClean->edgesNeigTris[j + 1].y)) + if ((j == i) || (meshClean->edgesNeigTris[j].y != meshClean->edgesNeigTris[j + 1].y)) { - if(j - j0 + 1 > 1) + if (j - j0 + 1 > 1) qsort(&meshClean->edgesNeigTris[j0], j - j0 + 1, sizeof(Voxel), qSortCompareVoxelByZAsc); meshClean->edgesXYStat.push_back(Voxel(meshClean->edgesNeigTris[j].y, j0, j)); @@ -349,7 +350,7 @@ int MeshClean::path::deployTriangles(StaticVector& trisIds, bool isBoundary bool MeshClean::path::isClodePath(StaticVector& path) { - if(path.size() < 3) + if (path.size() < 3) { return false; } @@ -360,7 +361,7 @@ void MeshClean::path::deployPath(StaticVector& path) { StaticVector trisIds; trisIds.reserve(path.size()); - for(int i = 0; i < path.size(); i++) + for (int i = 0; i < path.size(); i++) { trisIds.push_back(path[i].triId); } @@ -375,12 +376,14 @@ void MeshClean::path::clearPointNeighbors(int ptId) { if (ptId < 0 || ptId >= meshClean->ptsNeighPtsOrdered.size()) { - ALICEVISION_LOG_ERROR("MeshClean::path::clearPointNeighbors ptId: " << ptId << ", meshClean->ptsNeighPtsOrdered.size(): " << meshClean->ptsNeighPtsOrdered.size()); - throw std::runtime_error("MeshClean::path::clearPointNeighbors ptId: " + std::to_string(ptId) + ", meshClean->ptsNeighPtsOrdered.size(): " + std::to_string(meshClean->ptsNeighPtsOrdered.size())); + ALICEVISION_LOG_ERROR("MeshClean::path::clearPointNeighbors ptId: " << ptId << ", meshClean->ptsNeighPtsOrdered.size(): " + << meshClean->ptsNeighPtsOrdered.size()); + throw std::runtime_error("MeshClean::path::clearPointNeighbors ptId: " + std::to_string(ptId) + + ", meshClean->ptsNeighPtsOrdered.size(): " + std::to_string(meshClean->ptsNeighPtsOrdered.size())); } StaticVector& ptNeighPtsOrderedByPath = meshClean->ptsNeighPtsOrdered[ptId]; - if(!ptNeighPtsOrderedByPath.empty()) + if (!ptNeighPtsOrderedByPath.empty()) { ptNeighPtsOrderedByPath.clear(); } @@ -390,16 +393,16 @@ void MeshClean::path::updatePtNeighPtsOrderedByPath(int ptId, StaticVector& ptNeighPtsOrderedByPath = meshClean->ptsNeighPtsOrdered[ptId]; ptNeighPtsOrderedByPath.reserve(path.size() + 1); - if(!isClodePath(path)) + if (!isClodePath(path)) { ptNeighPtsOrderedByPath.push_back(path[0].ptsIds[0]); } - for(int i = 0; i < path.size(); i++) + for (int i = 0; i < path.size(); i++) { ptNeighPtsOrderedByPath.push_back(path[i].ptsIds[1]); } @@ -411,7 +414,7 @@ void MeshClean::path::createPath(StaticVector& ptNeighTrisSortedAscToProces out_path.clear(); out_path.reserve(sizeOfStaticVector(ptNeighTrisSortedAscToProcess)); - if(sizeOfStaticVector(ptNeighTrisSortedAscToProcess) == 0) + if (sizeOfStaticVector(ptNeighTrisSortedAscToProcess) == 0) { return; } @@ -424,20 +427,20 @@ void MeshClean::path::createPath(StaticVector& ptNeighTrisSortedAscToProces int nextTriId; nextTriId = out_path[0].triId; - while(nextTriId > -1) + while (nextTriId > -1) { nextTriId = getNextNeighBouringUnprocessedLast(ptNeighTrisSortedAscToProcess, out_path); - if(nextTriId > -1) + if (nextTriId > -1) { addNextTriIdToPathBack(nextTriId, out_path); } } nextTriId = out_path[0].triId; - while(nextTriId > -1) + while (nextTriId > -1) { nextTriId = getNextNeighBouringUnprocessedFirst(ptNeighTrisSortedAscToProcess, out_path); - if(nextTriId > -1) + if (nextTriId > -1) { addNextTriIdToPathFront(nextTriId, out_path); } @@ -450,22 +453,22 @@ int MeshClean::path::deployAll() StaticVector path; { - StaticVector& ptsNeighTrisSortedAsc = meshClean->ptsNeighTrisSortedAsc[_ptId]; - if(sizeOfStaticVector(ptsNeighTrisSortedAsc) == 0) - { - return 0; - } + StaticVector& ptsNeighTrisSortedAsc = meshClean->ptsNeighTrisSortedAsc[_ptId]; + if (sizeOfStaticVector(ptsNeighTrisSortedAsc) == 0) + { + return 0; + } - ptNeighTrisSortedAscToProcess.reserve(sizeOfStaticVector(ptsNeighTrisSortedAsc)); - ptNeighTrisSortedAscToProcess.push_back_arr(ptsNeighTrisSortedAsc); + ptNeighTrisSortedAscToProcess.reserve(sizeOfStaticVector(ptsNeighTrisSortedAsc)); + ptNeighTrisSortedAscToProcess.push_back_arr(ptsNeighTrisSortedAsc); - createPath(ptNeighTrisSortedAscToProcess, path); + createPath(ptNeighTrisSortedAscToProcess, path); } int nNewPts = 0; // if there are some not connected triangles then deploy them - if(ptNeighTrisSortedAscToProcess.size() > 0) + if (ptNeighTrisSortedAscToProcess.size() > 0) { int newPtId = deployTriangles(ptNeighTrisSortedAscToProcess, true); meshClean->ptsNeighPtsOrdered.reserveAddIfNeeded(1, 1000); @@ -476,12 +479,12 @@ int MeshClean::path::deployAll() } // extract from path all cycles and last (cycle or path) remains - while(path.size() > 0) + while (path.size() > 0) { StaticVector pathNew; removeCycleFromPath(path, pathNew); - if(path.size() > 0) + if (path.size() > 0) { deployPath(pathNew); nNewPts++; @@ -491,14 +494,14 @@ int MeshClean::path::deployAll() // get an up-to-date pointer to data since me->ptsNeighTrisSortedAsc might have been // modified inside the while loop by 'deployPath' StaticVector& toUpdate = meshClean->ptsNeighTrisSortedAsc[_ptId]; - if(toUpdate.empty()) + if (toUpdate.empty()) { printfState(path); printfState(pathNew); throw std::runtime_error("deployAll: bad condition, pthNew size: " + std::to_string(pathNew.size())); } - if(toUpdate.capacity() < pathNew.size()) + if (toUpdate.capacity() < pathNew.size()) { printfState(path); printfState(pathNew); @@ -506,11 +509,11 @@ int MeshClean::path::deployAll() } toUpdate.resize(0); - for(int i = 0; i < pathNew.size(); i++) + for (int i = 0; i < pathNew.size(); i++) { toUpdate.push_back(pathNew[i].triId); } - if(pathNew.size() > 0) + if (pathNew.size() > 0) { qsort(&toUpdate[0], toUpdate.size(), sizeof(int), qSortCompareIntAsc); } @@ -534,70 +537,65 @@ bool MeshClean::path::isWrongPt() createPath(ptNeighTrisSortedAscToProcess, path); // if there are some not connected triangles then deploy them - if(ptNeighTrisSortedAscToProcess.size() > 0) + if (ptNeighTrisSortedAscToProcess.size() > 0) { nNewPtsNeededToAdd++; } // extract from path all cycles and last (cycle or path) remains - while(path.size() > 0) + while (path.size() > 0) { StaticVector pathNew; removeCycleFromPath(path, pathNew); - if(path.size() > 0) + if (path.size() > 0) { nNewPtsNeededToAdd++; } - else - { - } + else {} } return (nNewPtsNeededToAdd > 0); } MeshClean::MeshClean(mvsUtils::MultiViewParams* _mp) - : Mesh() + : Mesh() { mp = _mp; } -MeshClean::~MeshClean() -{ - deallocateCleaningAttributes(); -} +MeshClean::~MeshClean() { deallocateCleaningAttributes(); } void MeshClean::deallocateCleaningAttributes() { - if(!edgesNeigTris.empty()) + if (!edgesNeigTris.empty()) { edgesNeigTris.clear(); } - if(!edgesNeigTrisAlive.empty()) + if (!edgesNeigTrisAlive.empty()) { edgesNeigTrisAlive.clear(); } - if(!edgesXStat.empty()) + if (!edgesXStat.empty()) { edgesXStat.clear(); } - if(!edgesXYStat.empty()) + if (!edgesXYStat.empty()) { edgesXYStat.clear(); } - if(!ptsBoundary.empty()) + if (!ptsBoundary.empty()) { ptsBoundary.clear(); } - if(!ptsNeighTrisSortedAsc.empty()) + if (!ptsNeighTrisSortedAsc.empty()) { ptsNeighTrisSortedAsc.clear(); } - if(!ptsNeighPtsOrdered.empty()) + if (!ptsNeighPtsOrdered.empty()) { ptsNeighPtsOrdered.clear(); } - if(!newPtsOldPtId.empty()) + if (!newPtsOldPtId.empty()) { newPtsOldPtId.clear(); } @@ -612,10 +610,10 @@ bool MeshClean::getEdgeNeighTrisInterval(Pixel& itr, int _ptId1, int _ptId2) itr = Pixel(-1, -1); int i1 = indexOfSortedVoxelArrByX(ptId1, edgesXStat, 0, edgesXStat.size() - 1); - if(i1 > -1) + if (i1 > -1) { int i2 = indexOfSortedVoxelArrByX(ptId2, edgesXYStat, edgesXStat[i1].y, edgesXStat[i1].z); - if(i2 > -1) + if (i2 > -1) { itr = Pixel(edgesXYStat[i2].y, edgesXYStat[i2].z); } @@ -636,10 +634,10 @@ void MeshClean::init() deallocateCleaningAttributes(); getPtsNeighborTriangles(ptsNeighTrisSortedAsc); - for(int i = 0; i < pts.size(); i++) + for (int i = 0; i < pts.size(); i++) { StaticVector& ptNeigTris = ptsNeighTrisSortedAsc[i]; - if(sizeOfStaticVector(ptNeigTris) > 1) + if (sizeOfStaticVector(ptNeigTris) > 1) { qsort(&ptNeigTris[0], ptNeigTris.size(), sizeof(int), qSortCompareIntAsc); } @@ -659,7 +657,7 @@ void MeshClean::init() edgesXStat.reserve(pts.size()); edgesXYStat.reserve(tris.size() * 3); - for(int i = 0; i < tris.size(); i++) + for (int i = 0; i < tris.size(); i++) { int a = tris[i].v[0]; int b = tris[i].v[1]; @@ -678,22 +676,21 @@ void MeshClean::init() // sort int i0 = 0; long t1 = mvsUtils::initEstimate(); - for(int i = 0; i < edgesNeigTris.size(); i++) + for (int i = 0; i < edgesNeigTris.size(); i++) { - - if((i == edgesNeigTris.size() - 1) || (edgesNeigTris[i].x != edgesNeigTris[i + 1].x)) + if ((i == edgesNeigTris.size() - 1) || (edgesNeigTris[i].x != edgesNeigTris[i + 1].x)) { - if(i - i0 + 1 > 1) + if (i - i0 + 1 > 1) qsort(&edgesNeigTris[i0], i - i0 + 1, sizeof(Voxel), qSortCompareVoxelByYAsc); int xyI0 = edgesXYStat.size(); int j0 = i0; - for(int j = i0; j <= i; j++) + for (int j = i0; j <= i; j++) { - if((j == i) || (edgesNeigTris[j].y != edgesNeigTris[j + 1].y)) + if ((j == i) || (edgesNeigTris[j].y != edgesNeigTris[j + 1].y)) { - if(j - j0 + 1 > 1) + if (j - j0 + 1 > 1) qsort(&edgesNeigTris[j0], j - j0 + 1, sizeof(Voxel), qSortCompareVoxelByZAsc); edgesXYStat.push_back(Voxel(edgesNeigTris[j].y, j0, j)); @@ -718,19 +715,19 @@ void MeshClean::testPtsNeighTrisSortedAsc() { ALICEVISION_LOG_DEBUG("Testing if each point of each triangle has the triangleid in ptsNeighTris array."); int n = 0; - for(int i = 0; i < tris.size(); i++) + for (int i = 0; i < tris.size(); i++) { - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { int ptId = tris[i].v[k]; - if(ptsNeighTrisSortedAsc[ptId].indexOf(i) == -1) + if (ptsNeighTrisSortedAsc[ptId].indexOf(i) == -1) { n++; - ALICEVISION_LOG_DEBUG("\t- ptid: " << ptId << "triid: " << i); + ALICEVISION_LOG_DEBUG("\t- ptid: " << ptId << "triid: " << i); } } } - if(n == 0) + if (n == 0) { ALICEVISION_LOG_DEBUG("test ok"); } @@ -741,20 +738,20 @@ void MeshClean::testPtsNeighTrisSortedAsc() ALICEVISION_LOG_DEBUG("Testing for each pt if all neigh triangles are sorted by id in asc"); n = 0; - for(int i = 0; i < pts.size(); i++) + for (int i = 0; i < pts.size(); i++) { StaticVector& ptNeighTris = ptsNeighTrisSortedAsc[i]; int lastid = -1; - for(int k = 0; k < sizeOfStaticVector(ptNeighTris); k++) + for (int k = 0; k < sizeOfStaticVector(ptNeighTris); k++) { - if(lastid > ptNeighTris[k]) + if (lastid > ptNeighTris[k]) { n++; } lastid = ptNeighTris[k]; } } - if(n == 0) + if (n == 0) { ALICEVISION_LOG_DEBUG("test ok"); } @@ -768,20 +765,20 @@ void MeshClean::testEdgesNeighTris() { ALICEVISION_LOG_DEBUG("Testing if each edge of each triangle has the triangleid in edgeNeighTris array"); int n = 0; - for(int i = 0; i < tris.size(); i++) + for (int i = 0; i < tris.size(); i++) { - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { int k1 = (k + 1) % 3; int ptId1 = tris[i].v[k]; int ptId2 = tris[i].v[k1]; Pixel itr; - if(getEdgeNeighTrisInterval(itr, ptId1, ptId2)) + if (getEdgeNeighTrisInterval(itr, ptId1, ptId2)) { bool isNotThere = true; - for(int y = itr.x; y <= itr.y; y++) + for (int y = itr.x; y <= itr.y; y++) { - if(edgesNeigTris[y].z == i) + if (edgesNeigTris[y].z == i) { isNotThere = false; } @@ -794,7 +791,7 @@ void MeshClean::testEdgesNeighTris() } } } - if(n == 0) + if (n == 0) { ALICEVISION_LOG_DEBUG("test ok"); } @@ -808,15 +805,15 @@ void MeshClean::testPtsNeighPtsOrdered() { ALICEVISION_LOG_DEBUG("Testing if each edge of each triangle has both pts in ptsNeighPtsOrdered"); int n = 0; - for(int i = 0; i < tris.size(); i++) + for (int i = 0; i < tris.size(); i++) { - for(int k = 0; k < 3; k++) + for (int k = 0; k < 3; k++) { int k1 = (k + 1) % 3; int ptId1 = tris[i].v[k]; int ptId2 = tris[i].v[k1]; - if(sizeOfStaticVector(ptsNeighPtsOrdered[ptId1]) == 0) + if (sizeOfStaticVector(ptsNeighPtsOrdered[ptId1]) == 0) { n++; } @@ -825,7 +822,7 @@ void MeshClean::testPtsNeighPtsOrdered() n += static_cast(ptsNeighPtsOrdered[ptId1].indexOf(ptId2) == -1); } - if(sizeOfStaticVector(ptsNeighPtsOrdered[ptId2]) == 0) + if (sizeOfStaticVector(ptsNeighPtsOrdered[ptId2]) == 0) { n++; } @@ -835,7 +832,7 @@ void MeshClean::testPtsNeighPtsOrdered() } } } - if(n == 0) + if (n == 0) { ALICEVISION_LOG_DEBUG("test ok"); } @@ -849,16 +846,16 @@ int MeshClean::cleanMesh() { int nWrongPts = 0; int nv = pts.size(); - for(int i = 0; i < nv; i++) + for (int i = 0; i < nv; i++) { path pth(this, i); nWrongPts += static_cast(pth.deployAll() > 0); } // update vertex color data (if any) if points were modified - if(!_colors.empty() && !newPtsOldPtId.empty()) + if (!_colors.empty() && !newPtsOldPtId.empty()) { std::vector newColors(pts.size(), {0, 0, 0}); - for(std::size_t newId = 0; newId < newPtsOldPtId.size(); ++newId) + for (std::size_t newId = 0; newId < newPtsOldPtId.size(); ++newId) { const std::size_t oldId = newPtsOldPtId[newId]; newColors[newId] = _colors[oldId]; @@ -866,9 +863,7 @@ int MeshClean::cleanMesh() std::swap(_colors, newColors); } - ALICEVISION_LOG_INFO("cleanMesh:" << std::endl - << "\t- # wrong points: " << nWrongPts << std::endl - << "\t- # new points: " << (pts.size() - nv)); + ALICEVISION_LOG_INFO("cleanMesh:" << std::endl << "\t- # wrong points: " << nWrongPts << std::endl << "\t- # new points: " << (pts.size() - nv)); return pts.size() - nv; } @@ -878,7 +873,7 @@ int MeshClean::cleanMesh(int maxIters) testPtsNeighTrisSortedAsc(); testEdgesNeighTris(); int nupd = 1; - for(int iter = 0; (iter < maxIters) && (nupd > 0); ++iter) + for (int iter = 0; (iter < maxIters) && (nupd > 0); ++iter) { nupd = cleanMesh(); testPtsNeighTrisSortedAsc(); @@ -889,10 +884,7 @@ int MeshClean::cleanMesh(int maxIters) return nupd; } -bool MeshClean::isIsBoundaryPt(int ptId) -{ - return ptsBoundary[ptId]; -} +bool MeshClean::isIsBoundaryPt(int ptId) { return ptsBoundary[ptId]; } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/MeshClean.hpp b/src/aliceVision/mesh/MeshClean.hpp index 76a0730586..ad60066e17 100644 --- a/src/aliceVision/mesh/MeshClean.hpp +++ b/src/aliceVision/mesh/MeshClean.hpp @@ -15,10 +15,10 @@ namespace mesh { class MeshClean : public Mesh { -public: + public: class path { - public: + public: struct pathPart { int triId; @@ -53,10 +53,8 @@ class MeshClean : public Mesh void printfState(StaticVector& path); bool addNextTriIdToPathBack(int nextTriId, StaticVector& path); bool addNextTriIdToPathFront(int nextTriId, StaticVector& path); - int getNextNeighBouringUnprocessedLast(StaticVector& ptNeighTrisSortedAscToProcess, - StaticVector& out_path); - int getNextNeighBouringUnprocessedFirst(StaticVector& ptNeighTrisSortedAscToProcess, - StaticVector& out_path); + int getNextNeighBouringUnprocessedLast(StaticVector& ptNeighTrisSortedAscToProcess, StaticVector& out_path); + int getNextNeighBouringUnprocessedFirst(StaticVector& ptNeighTrisSortedAscToProcess, StaticVector& out_path); int nCrossings(StaticVector& path); void removeCycleFromPath(StaticVector& inPath, StaticVector& outPath); void deployTriangle(int triId); @@ -101,5 +99,5 @@ class MeshClean : public Mesh int cleanMesh(int maxIters); }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/MeshEnergyOpt.cpp b/src/aliceVision/mesh/MeshEnergyOpt.cpp index 0acb35a02a..6a1080f81a 100644 --- a/src/aliceVision/mesh/MeshEnergyOpt.cpp +++ b/src/aliceVision/mesh/MeshEnergyOpt.cpp @@ -15,10 +15,10 @@ namespace mesh { namespace bfs = boost::filesystem; MeshEnergyOpt::MeshEnergyOpt(mvsUtils::MultiViewParams* _mp) - : MeshAnalyze(_mp) + : MeshAnalyze(_mp) { -// tmpDir = mp->mvDir + "meshEnergyOpt/"; -// bfs::create_directory(tmpDir); + // tmpDir = mp->mvDir + "meshEnergyOpt/"; + // bfs::create_directory(tmpDir); } MeshEnergyOpt::~MeshEnergyOpt() = default; @@ -30,10 +30,10 @@ void MeshEnergyOpt::computeLaplacianPtsParallel(StaticVector& out_lapPt int nlabpts = 0; #pragma omp parallel for - for(int i = 0; i < pts.size(); i++) + for (int i = 0; i < pts.size(); i++) { Point3d lapPt; - if(getLaplacianSmoothingVector(i, lapPt)) + if (getLaplacianSmoothingVector(i, lapPt)) { out_lapPts[i] = lapPt; nlabpts++; @@ -41,8 +41,7 @@ void MeshEnergyOpt::computeLaplacianPtsParallel(StaticVector& out_lapPt } } -void MeshEnergyOpt::updateGradientParallel(float lambda, const Point3d& LU, - const Point3d& RD, StaticVectorBool& ptsCanMove) +void MeshEnergyOpt::updateGradientParallel(float lambda, const Point3d& LU, const Point3d& RD, StaticVectorBool& ptsCanMove) { // printf("nlabpts %i of %i\n",nlabpts,pts->size()); StaticVector lapPts; @@ -53,16 +52,16 @@ void MeshEnergyOpt::updateGradientParallel(float lambda, const Point3d& LU, newPts.push_back_arr(&pts); #pragma omp parallel for - for(int i = 0; i < pts.size(); ++i) + for (int i = 0; i < pts.size(); ++i) { - if( ptsCanMove.empty() || ptsCanMove[i] ) + if (ptsCanMove.empty() || ptsCanMove[i]) { Point3d n; - if(getBiLaplacianSmoothingVector(i, lapPts, n)) + if (getBiLaplacianSmoothingVector(i, lapPts, n)) { Point3d p = newPts[i] + n * lambda; - if((p.x > LU.x) && (p.y > LU.y) && (p.z > LU.z) && (p.x < RD.x) && (p.y < RD.y) && (p.z < RD.z)) + if ((p.x > LU.x) && (p.y > LU.y) && (p.z > LU.z) && (p.x < RD.x) && (p.y < RD.y) && (p.z < RD.z)) { newPts[i] = p; } @@ -76,17 +75,17 @@ void MeshEnergyOpt::updateGradientParallel(float lambda, const Point3d& LU, bool MeshEnergyOpt::optimizeSmooth(float lambda, int niter, StaticVectorBool& ptsCanMove) { - if(pts.size() <= 4) + if (pts.size() <= 4) { return false; } - // bool saveDebug = mp ? mp->userParams.get("meshEnergyOpt.saveAllIterations", false) : false; + // bool saveDebug = mp ? mp->userParams.get("meshEnergyOpt.saveAllIterations", false) : false; Point3d LU, RD; LU = pts[0]; RD = pts[0]; - for(int i = 0; i < pts.size(); i++) + for (int i = 0; i < pts.size(); i++) { LU.x = std::min(LU.x, pts[i].x); LU.y = std::min(LU.y, pts[i].y); @@ -96,20 +95,18 @@ bool MeshEnergyOpt::optimizeSmooth(float lambda, int niter, StaticVectorBool& pt RD.z = std::max(RD.z, pts[i].z); } - ALICEVISION_LOG_INFO("Optimizing mesh smooth: " << std::endl - << "\t- lamda: " << lambda << std::endl - << "\t- niters: " << niter << std::endl); + ALICEVISION_LOG_INFO("Optimizing mesh smooth: " << std::endl << "\t- lamda: " << lambda << std::endl << "\t- niters: " << niter << std::endl); - for(int i = 0; i < niter; i++) + for (int i = 0; i < niter; i++) { ALICEVISION_LOG_INFO("Optimizing mesh smooth: iteration " << i); updateGradientParallel(lambda, LU, RD, ptsCanMove); - //if(saveDebug) - // save(folder + "mesh_smoothed_" + std::to_string(i)); + // if(saveDebug) + // save(folder + "mesh_smoothed_" + std::to_string(i)); } return true; } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/MeshEnergyOpt.hpp b/src/aliceVision/mesh/MeshEnergyOpt.hpp index d765631ea6..92636a3b5b 100644 --- a/src/aliceVision/mesh/MeshEnergyOpt.hpp +++ b/src/aliceVision/mesh/MeshEnergyOpt.hpp @@ -15,16 +15,16 @@ namespace mesh { class MeshEnergyOpt : public MeshAnalyze { -public: + public: explicit MeshEnergyOpt(mvsUtils::MultiViewParams* _mp); ~MeshEnergyOpt(); bool optimizeSmooth(float lambda, int niter, StaticVectorBool& ptsCanMove); -private: + private: void computeLaplacianPtsParallel(StaticVector& out_lapPts); void updateGradientParallel(float lambda, const Point3d& LU, const Point3d& RD, StaticVectorBool& ptsCanMove); }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/Texturing.cpp b/src/aliceVision/mesh/Texturing.cpp index 6fb2fe5209..527db149b3 100644 --- a/src/aliceVision/mesh/Texturing.cpp +++ b/src/aliceVision/mesh/Texturing.cpp @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include @@ -51,25 +51,25 @@ EUnwrapMethod EUnwrapMethod_stringToEnum(const std::string& method) std::string m = method; boost::to_lower(m); - if(m == "basic") + if (m == "basic") return EUnwrapMethod::Basic; - if(m == "abf") + if (m == "abf") return EUnwrapMethod::ABF; - if(m == "lscm") + if (m == "lscm") return EUnwrapMethod::LSCM; throw std::out_of_range("Invalid unwrap method " + method); } std::string EUnwrapMethod_enumToString(EUnwrapMethod method) { - switch(method) + switch (method) { - case EUnwrapMethod::Basic: - return "Basic"; - case EUnwrapMethod::ABF: - return "ABF"; - case EUnwrapMethod::LSCM: - return "LSCM"; + case EUnwrapMethod::Basic: + return "Basic"; + case EUnwrapMethod::ABF: + return "ABF"; + case EUnwrapMethod::LSCM: + return "LSCM"; } throw std::out_of_range("Unrecognized EUnwrapMethod"); } @@ -82,7 +82,7 @@ EVisibilityRemappingMethod EVisibilityRemappingMethod_stringToEnum(const std::st return EVisibilityRemappingMethod::Push; if (method == "PullPush") return EVisibilityRemappingMethod::PullPush; - if(method == "MeshItself") + if (method == "MeshItself") return EVisibilityRemappingMethod::MeshItself; throw std::out_of_range("Invalid visibilities remapping method " + method); } @@ -91,29 +91,29 @@ std::string EVisibilityRemappingMethod_enumToString(EVisibilityRemappingMethod m { switch (method) { - case EVisibilityRemappingMethod::Pull: - return "Push"; - case EVisibilityRemappingMethod::Push: - return "Pull"; - case EVisibilityRemappingMethod::PullPush: - return "PullPush"; - case EVisibilityRemappingMethod::MeshItself: - return "MeshItself"; + case EVisibilityRemappingMethod::Pull: + return "Push"; + case EVisibilityRemappingMethod::Push: + return "Pull"; + case EVisibilityRemappingMethod::PullPush: + return "PullPush"; + case EVisibilityRemappingMethod::MeshItself: + return "MeshItself"; } throw std::out_of_range("Unrecognized EVisibilityRemappingMethod"); } -EBumpMappingType EBumpMappingType_stringToEnum(const std::string& type) +EBumpMappingType EBumpMappingType_stringToEnum(const std::string& type) { - if(type == "Height") + if (type == "Height") return EBumpMappingType::Height; - if(type == "Normal") + if (type == "Normal") return EBumpMappingType::Normal; throw std::out_of_range("Invalid bump mapping type " + type); } -std::string EBumpMappingType_enumToString(EBumpMappingType type) +std::string EBumpMappingType_enumToString(EBumpMappingType type) { - switch(type) + switch (type) { case EBumpMappingType::Height: return "Height"; @@ -122,10 +122,7 @@ std::string EBumpMappingType_enumToString(EBumpMappingType type) } throw std::out_of_range("Invalid bump mapping type enum"); } -std::ostream& operator<<(std::ostream& os, EBumpMappingType bumpMappingType) -{ - return os << EBumpMappingType_enumToString(bumpMappingType); -} +std::ostream& operator<<(std::ostream& os, EBumpMappingType bumpMappingType) { return os << EBumpMappingType_enumToString(bumpMappingType); } std::istream& operator>>(std::istream& in, EBumpMappingType& bumpMappingType) { std::string token; @@ -171,9 +168,9 @@ Point3d barycentricToCartesian(const Point3d* triangle, const Point2d& coords) void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) { - if(!mesh) + if (!mesh) throw std::runtime_error("Can't generate UVs without a mesh"); - if(mesh->pointsVisibilities.empty()) + if (mesh->pointsVisibilities.empty()) throw std::runtime_error("Points visibilities are required for basic unwrap method."); // automatic uv atlasing @@ -182,7 +179,7 @@ void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) // create a new mesh to store data mesh->trisUvIds.reserve(mesh->tris.size()); - mesh->uvCoords.reserve(mesh->pts.size()); // not sufficient + mesh->uvCoords.reserve(mesh->pts.size()); // not sufficient _atlases.clear(); _atlases.resize(mua.atlases().size()); mesh->nmtls = mua.atlases().size(); @@ -193,11 +190,11 @@ void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) int atlasId = 0; - for(auto& charts : mua.atlases()) + for (auto& charts : mua.atlases()) { - for(auto& chart : charts) + for (auto& chart : charts) { - if(chart.refCameraID == -1) + if (chart.refCameraID == -1) continue; std::map uvCache; @@ -206,7 +203,7 @@ void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) Point2d targetLU(chart.targetLU.x, chart.targetLU.y); // for each triangle in this chart - for(size_t i = 0; i < chart.triangleIDs.size(); ++i) + for (size_t i = 0; i < chart.triangleIDs.size(); ++i) { int triangleID = chart.triangleIDs[i]; // register triangle in corresponding atlas @@ -215,21 +212,21 @@ void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) Voxel& triUvIds = mesh->trisUvIds[triangleID]; // for each point - for(int k = 0; k < 3; ++k) + for (int k = 0; k < 3; ++k) { int pointId = mesh->tris[triangleID].v[k]; int uvIdx; auto uvCacheIt = uvCache.find(pointId); // if uv coords for this point had not been computed in this chart yet - if(uvCacheIt == uvCache.end()) + if (uvCacheIt == uvCache.end()) { const Point3d& p = mesh->pts[pointId]; Point2d uvPix; Point2d pix; mp.getPixelFor3DPoint(&pix, p, chart.refCameraID); - if(mp.isPixelInImage(pix, chart.refCameraID)) + if (mp.isPixelInImage(pix, chart.refCameraID)) { // compute the final pixel coordinates // get pixel offset in reference camera space with applied downscale @@ -239,14 +236,13 @@ void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) uvPix.y = 1.0 - uvPix.y; // sanity check: discard invalid UVs - if( uvPix.x < 0 || uvPix.x > 1.0 - || uvPix.y < 0 || uvPix.x > 1.0 ) + if (uvPix.x < 0 || uvPix.x > 1.0 || uvPix.y < 0 || uvPix.x > 1.0) { ALICEVISION_LOG_WARNING("Discarding invalid UV: " + std::to_string(uvPix.x) + ", " + std::to_string(uvPix.y)); uvPix = Point2d(); } - if(texParams.useUDIM) + if (texParams.useUDIM) { uvPix.x += atlasId % 10; uvPix.y += atlasId / 10; @@ -256,13 +252,13 @@ void Texturing::generateUVsBasicMethod(mvsUtils::MultiViewParams& mp) uvIdx = mesh->uvCoords.size() - 1; uvCache[pointId] = uvIdx; } - else { + else + { uvIdx = uvCacheIt->second; } triUvIds.m[k] = uvIdx; } } - } ++atlasId; } @@ -274,10 +270,10 @@ void Texturing::updateAtlases() // Fill atlases (1 atlas per material) with triangles issued from mesh subdivision _atlases.clear(); _atlases.resize(std::max(1, mesh->nmtls)); - for(int triangleID = 0; triangleID < mesh->trisMtlIds().size(); ++triangleID) + for (int triangleID = 0; triangleID < mesh->trisMtlIds().size(); ++triangleID) { unsigned int atlasID = mesh->nmtls ? mesh->trisMtlIds()[triangleID] : 0; - if(mesh->trisMtlIds()[triangleID] != -1) + if (mesh->trisMtlIds()[triangleID] != -1) _atlases[atlasID].push_back(triangleID); } } @@ -292,41 +288,40 @@ void Texturing::generateTextures(const mvsUtils::MultiViewParams& mp, m.erase(std::remove(std::begin(m), std::end(m), 0), std::end(m)); texParams.nbBand = m.size(); - if(!std::is_sorted(std::begin(m), std::end(m))) + if (!std::is_sorted(std::begin(m), std::end(m))) { ALICEVISION_LOG_INFO("Sorting contributions per band (necessary)."); std::sort(std::begin(m), std::end(m)); } ALICEVISION_LOG_INFO("Texturing: Use multiband blending with the following contributions per band:"); - for(int c: texParams.multiBandNbContrib) + for (int c : texParams.multiBandNbContrib) { ALICEVISION_LOG_INFO(" - " << c); } std::partial_sum(m.begin(), m.end(), m.begin()); ALICEVISION_LOG_INFO("Texturing in " + image::EImageColorSpace_enumToString(texParams.workingColorSpace) + " colorspace."); - mvsUtils::ImagesCache> imageCache( - mp, texParams.workingColorSpace, texParams.correctEV); + mvsUtils::ImagesCache> imageCache(mp, texParams.workingColorSpace, texParams.correctEV); imageCache.setCacheSize(2); ALICEVISION_LOG_INFO("Images loaded from cache with: " + ECorrectEV_enumToString(texParams.correctEV)); - //calculate the maximum number of atlases in memory in MB - const std::size_t imageMaxMemSize = - mp.getMaxImageWidth() * mp.getMaxImageHeight() * sizeof(image::RGBfColor) / std::pow(2,20); //MB + // calculate the maximum number of atlases in memory in MB + const std::size_t imageMaxMemSize = mp.getMaxImageWidth() * mp.getMaxImageHeight() * sizeof(image::RGBfColor) / std::pow(2, 20); // MB const std::size_t imagePyramidMaxMemSize = texParams.nbBand * imageMaxMemSize; const std::size_t atlasContribMemSize = - texParams.textureSide * texParams.textureSide * (sizeof(image::RGBfColor)+sizeof(float)) / std::pow(2,20); //MB + texParams.textureSide * texParams.textureSide * (sizeof(image::RGBfColor) + sizeof(float)) / std::pow(2, 20); // MB const std::size_t atlasPyramidMaxMemSize = texParams.nbBand * atlasContribMemSize; - const int availableRam = int(memoryAvailable / std::pow(2,20)); - const int availableMem = availableRam - 2 * (imagePyramidMaxMemSize + imageMaxMemSize); // keep some memory for the 2 input images in cache and one laplacian pyramid + const int availableRam = int(memoryAvailable / std::pow(2, 20)); + const int availableMem = + availableRam - 2 * (imagePyramidMaxMemSize + imageMaxMemSize); // keep some memory for the 2 input images in cache and one laplacian pyramid const int nbAtlas = _atlases.size(); // Memory needed to process each attlas = input + input pyramid + output atlas pyramid const int memoryPerAtlas = (imageMaxMemSize + imagePyramidMaxMemSize) + atlasPyramidMaxMemSize; - int nbAtlasMax = std::floor(availableMem / double(memoryPerAtlas)); //maximum number of textures laplacian pyramid in RAM - nbAtlasMax = std::min(nbAtlas, nbAtlasMax); //if enough memory, do it with all atlases + int nbAtlasMax = std::floor(availableMem / double(memoryPerAtlas)); // maximum number of textures laplacian pyramid in RAM + nbAtlasMax = std::min(nbAtlas, nbAtlasMax); // if enough memory, do it with all atlases ALICEVISION_LOG_INFO("nbAtlas: " << nbAtlas); ALICEVISION_LOG_INFO("availableRam: " << availableRam); @@ -341,9 +336,9 @@ void Texturing::generateTextures(const mvsUtils::MultiViewParams& mp, ALICEVISION_LOG_DEBUG("nChunks: " << nChunks); ALICEVISION_LOG_INFO("nbAtlasMax (after rounding): " << nbAtlasMax); - if (availableMem - nbAtlasMax*atlasPyramidMaxMemSize < 1000) //keep 1 GB margin in memory + if (availableMem - nbAtlasMax * atlasPyramidMaxMemSize < 1000) // keep 1 GB margin in memory nbAtlasMax -= 1; - nbAtlasMax = std::max(1, nbAtlasMax); //if not enough memory, do it one by one + nbAtlasMax = std::max(1, nbAtlasMax); // if not enough memory, do it one by one ALICEVISION_LOG_INFO("Total amount of available RAM: " << availableRam << " MB."); ALICEVISION_LOG_INFO("Total amount of memory remaining for the computation: " << availableMem << " MB."); @@ -351,22 +346,22 @@ void Texturing::generateTextures(const mvsUtils::MultiViewParams& mp, ALICEVISION_LOG_INFO("Total amount of an atlas pyramid in memory: " << atlasPyramidMaxMemSize << " MB."); ALICEVISION_LOG_INFO("Processing " << nbAtlas << " atlases by chunks of " << nbAtlasMax); - //generateTexture for the maximum number of atlases, and iterate + // generateTexture for the maximum number of atlases, and iterate const std::div_t divresult = div(nbAtlas, nbAtlasMax); std::vector atlasIDs; atlasIDs.reserve(nbAtlasMax); - for(int n = 0; n <= divresult.quot; ++n) + for (int n = 0; n <= divresult.quot; ++n) { atlasIDs.clear(); int imax = (n < divresult.quot ? nbAtlasMax : divresult.rem); - if(!imax) + if (!imax) continue; - for(int i = 0; i < imax; ++i) + for (int i = 0; i < imax; ++i) { - size_t atlasID = size_t(n*nbAtlasMax + i); + size_t atlasID = size_t(n * nbAtlasMax + i); atlasIDs.push_back(atlasID); } - ALICEVISION_LOG_INFO("Generating texture for atlases " << n*nbAtlasMax + 1 << " to " << n*nbAtlasMax+imax ); + ALICEVISION_LOG_INFO("Generating texture for atlases " << n * nbAtlasMax + 1 << " to " << n * nbAtlasMax + imax); generateTexturesSubSet(mp, atlasIDs, imageCache, outPath, textureFileType); } } @@ -377,7 +372,7 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, const bfs::path& outPath, image::EImageFileType textureFileType) { - if(atlasIDs.size() > _atlases.size()) + if (atlasIDs.size() > _atlases.size()) throw std::runtime_error("Invalid atlas IDs "); unsigned int textureSize = texParams.textureSide * texParams.textureSide; @@ -385,17 +380,17 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, // We select the best cameras for each triangle and store it per camera for each output texture files. // Triangles contributions are stored per frequency bands for multi-band blending. using AtlasIndex = size_t; - using ScorePerTriangle = std::vector>; //list of + using ScorePerTriangle = std::vector>; // list of std::vector>> contributionsPerCamera(mp.ncams); - //for each atlasID, calculate contributionPerCamera - for(const size_t atlasID : atlasIDs) + // for each atlasID, calculate contributionPerCamera + for (const size_t atlasID : atlasIDs) { - ALICEVISION_LOG_INFO("Generating texture for atlas " << atlasID + 1 << "/" << _atlases.size() - << " (" << _atlases[atlasID].size() << " triangles)."); + ALICEVISION_LOG_INFO("Generating texture for atlas " << atlasID + 1 << "/" << _atlases.size() << " (" << _atlases[atlasID].size() + << " triangles)."); // iterate over atlas' triangles - for(size_t i = 0; i < _atlases[atlasID].size(); ++i) + for (size_t i = 0; i < _atlases[atlasID].size(); ++i) { int triangleID = _atlases[atlasID][i]; @@ -418,12 +413,12 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, } std::sort(allTriCams.begin(), allTriCams.end()); - std::vector> selectedTriCams; // + std::vector> selectedTriCams; // selectedTriCams.emplace_back(allTriCams.front(), 1); for (int j = 1; j < allTriCams.size(); ++j) { const unsigned int camId = allTriCams[j]; - if(selectedTriCams.back().first == camId) + if (selectedTriCams.back().first == camId) { ++selectedTriCams.back().second; } @@ -443,20 +438,20 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, triangleNormal = mesh->computeTriangleNormal(triangleID); triangleCenter = mesh->computeTriangleCenterOfGravity(triangleID); } - using ScoreCamId = std::tuple; // + using ScoreCamId = std::tuple; // std::vector scorePerCamId; - for (const auto& itCamVis: selectedTriCams) + for (const auto& itCamVis : selectedTriCams) { const int camId = itCamVis.first; const int verticesSupport = itCamVis.second; - if(texParams.forceVisibleByAllVertices && verticesSupport < 3) + if (texParams.forceVisibleByAllVertices && verticesSupport < 3) continue; if (texParams.angleHardThreshold != 0.0) { const Point3d vecPointToCam = (mp.CArr[camId] - triangleCenter).normalize(); const double angle = angleBetwV1andV2(triangleNormal, vecPointToCam); - if(angle > texParams.angleHardThreshold) + if (angle > texParams.angleHardThreshold) continue; } @@ -465,7 +460,7 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, const Mesh::triangle_proj tProj = mesh->getTriangleProjection(triangleID, mp, camId, w, h); const int nbVertex = mesh->getTriangleNbVertexInImage(mp, tProj, camId, 20); - if(nbVertex == 0) + if (nbVertex == 0) // No triangle vertex in the image continue; @@ -481,33 +476,33 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, } std::sort(scorePerCamId.begin(), scorePerCamId.end(), std::greater()); - const double minScore = texParams.bestScoreThreshold * std::get<1>(scorePerCamId.front()); // bestScoreThreshold * bestScore + const double minScore = texParams.bestScoreThreshold * std::get<1>(scorePerCamId.front()); // bestScoreThreshold * bestScore const bool bestIsPartial = (std::get<0>(scorePerCamId.front()) < 3); int nbContribMax = std::min(texParams.multiBandNbContrib.back(), static_cast(scorePerCamId.size())); int nbCumulatedVertices = 0; int band = 0; - for(int contrib = 0; nbCumulatedVertices < 3 * nbContribMax && contrib < nbContribMax; ++contrib) + for (int contrib = 0; nbCumulatedVertices < 3 * nbContribMax && contrib < nbContribMax; ++contrib) { nbCumulatedVertices += std::get<0>(scorePerCamId[contrib]); if (!bestIsPartial && contrib != 0) { - if(std::get<1>(scorePerCamId[contrib]) < minScore) + if (std::get<1>(scorePerCamId[contrib]) < minScore) { // The best image fully see the triangle and has a much better score, so only rely on the first ones break; } } - //for the camera camId : add triangle score to the corresponding texture, at the right frequency band + // for the camera camId : add triangle score to the corresponding texture, at the right frequency band const int camId = std::get<2>(scorePerCamId[contrib]); const int triangleScore = std::get<1>(scorePerCamId[contrib]); auto& camContribution = contributionsPerCamera[camId]; - if(camContribution.find(atlasID) == camContribution.end()) + if (camContribution.find(atlasID) == camContribution.end()) camContribution[atlasID].resize(texParams.nbBand); camContribution.at(atlasID)[band].emplace_back(triangleID, triangleScore); - if(contrib + 1 == texParams.multiBandNbContrib[band]) + if (contrib + 1 == texParams.multiBandNbContrib[band]) { ++band; } @@ -517,45 +512,46 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, ALICEVISION_LOG_INFO("Reading pixel color."); - //pyramid of atlases frequency bands + // pyramid of atlases frequency bands std::map accuPyramids; - for(std::size_t atlasID: atlasIDs) + for (std::size_t atlasID : atlasIDs) accuPyramids[atlasID].init(texParams.nbBand, texParams.textureSide, texParams.textureSide); - //for each camera, for each texture, iterate over triangles and fill the accuPyramids map - for(int camId = 0; camId < contributionsPerCamera.size(); ++camId) + // for each camera, for each texture, iterate over triangles and fill the accuPyramids map + for (int camId = 0; camId < contributionsPerCamera.size(); ++camId) { const std::map>& cameraContributions = contributionsPerCamera[camId]; - if(cameraContributions.empty()) + if (cameraContributions.empty()) { ALICEVISION_LOG_INFO("- camera " << mp.getViewId(camId) << " (" << camId + 1 << "/" << mp.ncams << ") unused."); continue; } - ALICEVISION_LOG_INFO("- camera " << mp.getViewId(camId) << " (" << camId + 1 << "/" << mp.ncams << ") with contributions to " << cameraContributions.size() << " texture files:"); + ALICEVISION_LOG_INFO("- camera " << mp.getViewId(camId) << " (" << camId + 1 << "/" << mp.ncams << ") with contributions to " + << cameraContributions.size() << " texture files:"); // Load camera image from cache auto imgPtr = imageCache.getImg_sync(camId); const image::Image& camImg = *imgPtr; // Calculate laplacianPyramid - std::vector> pyramidL; //laplacian pyramid + std::vector> pyramidL; // laplacian pyramid imageAlgo::laplacianPyramid(pyramidL, camImg, texParams.nbBand, texParams.multiBandDownscale); // for each output texture file - for(const auto& c : cameraContributions) + for (const auto& c : cameraContributions) { AtlasIndex atlasID = c.first; ALICEVISION_LOG_INFO(" - Texture file: " << atlasID + 1); - //for each frequency band - for(int band = 0; band < c.second.size(); ++band) + // for each frequency band + for (int band = 0; band < c.second.size(); ++band) { const ScorePerTriangle& trianglesId = c.second[band]; ALICEVISION_LOG_INFO(" - band " << band + 1 << ": " << trianglesId.size() << " triangles."); - // for each triangle - #pragma omp parallel for - for(int ti = 0; ti < trianglesId.size(); ++ti) +// for each triangle +#pragma omp parallel for + for (int ti = 0; ti < trianglesId.size(); ++ti) { const unsigned int triangleId = std::get<0>(trianglesId[ti]); const float triangleScore = texParams.useScore ? std::get<1>(trianglesId[ti]) : 1.0f; @@ -566,23 +562,19 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, // compute the Bottom-Left minima of the current UDIM for [0,1] range remapping Point2d udimBL; StaticVector& uvCoords = mesh->uvCoords; - udimBL.x = std::floor(std::min({uvCoords[triangleUvIds[0]].x, - uvCoords[triangleUvIds[1]].x, - uvCoords[triangleUvIds[2]].x})); - udimBL.y = std::floor(std::min({uvCoords[triangleUvIds[0]].y, - uvCoords[triangleUvIds[1]].y, - uvCoords[triangleUvIds[2]].y})); - - for(int k = 0; k < 3; ++k) + udimBL.x = std::floor(std::min({uvCoords[triangleUvIds[0]].x, uvCoords[triangleUvIds[1]].x, uvCoords[triangleUvIds[2]].x})); + udimBL.y = std::floor(std::min({uvCoords[triangleUvIds[0]].y, uvCoords[triangleUvIds[1]].y, uvCoords[triangleUvIds[2]].y})); + + for (int k = 0; k < 3; ++k) { - const int pointIndex = mesh->tris[triangleId].v[k]; - triPts[k] = mesh->pts[pointIndex]; // 3D coordinates - const int uvPointIndex = triangleUvIds.m[k]; - Point2d uv = uvCoords[uvPointIndex]; - // UDIM: remap coordinates between [0,1] - uv = uv - udimBL; - - triPixs[k] = uv * texParams.textureSide; // UV coordinates + const int pointIndex = mesh->tris[triangleId].v[k]; + triPts[k] = mesh->pts[pointIndex]; // 3D coordinates + const int uvPointIndex = triangleUvIds.m[k]; + Point2d uv = uvCoords[uvPointIndex]; + // UDIM: remap coordinates between [0,1] + uv = uv - udimBL; + + triPixs[k] = uv * texParams.textureSide; // UV coordinates } // compute triangle bounding box in pixel indexes @@ -602,60 +594,61 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, RD.y = clamp(RD.y, 0, texSide); // iterate over pixels of the triangle's bounding box - for(int y = LU.y; y < RD.y; ++y) + for (int y = LU.y; y < RD.y; ++y) { - for(int x = LU.x; x < RD.x; ++x) - { - Pixel pix(x, y); // top-left corner of the pixel - Point2d barycCoords; - - // test if the pixel is inside triangle - // and retrieve its barycentric coordinates - if(!isPixelInTriangle(triPixs, pix, barycCoords)) - { - continue; - } - - // remap 'y' to image coordinates system (inverted Y axis) - const unsigned int y_ = (texParams.textureSide - 1) - y; - // 1D pixel index - unsigned int xyoffset = y_ * texParams.textureSide + x; - // get 3D coordinates - Point3d pt3d = barycentricToCartesian(triPts, barycCoords); - // get 2D coordinates in source image - Point2d pixRC; - mp.getPixelFor3DPoint(&pixRC, pt3d, camId); - // exclude out of bounds pixels - if(!mp.isPixelInImage(pixRC, camId)) - continue; - - // If the color is pure zero (ie. no contributions), we consider it as an invalid pixel. - if (getInterpolateColor(camImg, pixRC.y, pixRC.x) == image::RGBfColor(0.f, 0.f, 0.f)) - continue; - - // Fill the accumulated pyramid for this pixel - // each frequency band also contributes to lower frequencies (higher band indexes) - AccuPyramid& accuPyramid = accuPyramids.at(atlasID); - for(std::size_t bandContrib = band; bandContrib < pyramidL.size(); ++bandContrib) - { - int downscaleCoef = std::pow(texParams.multiBandDownscale, bandContrib); - AccuImage& accuImage = accuPyramid.pyramid[bandContrib]; - - // fill the accumulated color map for this pixel - const auto pixDownscaled = pixRC / downscaleCoef; - accuImage.img(xyoffset) += getInterpolateColor(pyramidL[bandContrib], pixDownscaled.y, pixDownscaled.x) * triangleScore; - accuImage.imgCount[xyoffset] += triangleScore; - } - } + for (int x = LU.x; x < RD.x; ++x) + { + Pixel pix(x, y); // top-left corner of the pixel + Point2d barycCoords; + + // test if the pixel is inside triangle + // and retrieve its barycentric coordinates + if (!isPixelInTriangle(triPixs, pix, barycCoords)) + { + continue; + } + + // remap 'y' to image coordinates system (inverted Y axis) + const unsigned int y_ = (texParams.textureSide - 1) - y; + // 1D pixel index + unsigned int xyoffset = y_ * texParams.textureSide + x; + // get 3D coordinates + Point3d pt3d = barycentricToCartesian(triPts, barycCoords); + // get 2D coordinates in source image + Point2d pixRC; + mp.getPixelFor3DPoint(&pixRC, pt3d, camId); + // exclude out of bounds pixels + if (!mp.isPixelInImage(pixRC, camId)) + continue; + + // If the color is pure zero (ie. no contributions), we consider it as an invalid pixel. + if (getInterpolateColor(camImg, pixRC.y, pixRC.x) == image::RGBfColor(0.f, 0.f, 0.f)) + continue; + + // Fill the accumulated pyramid for this pixel + // each frequency band also contributes to lower frequencies (higher band indexes) + AccuPyramid& accuPyramid = accuPyramids.at(atlasID); + for (std::size_t bandContrib = band; bandContrib < pyramidL.size(); ++bandContrib) + { + int downscaleCoef = std::pow(texParams.multiBandDownscale, bandContrib); + AccuImage& accuImage = accuPyramid.pyramid[bandContrib]; + + // fill the accumulated color map for this pixel + const auto pixDownscaled = pixRC / downscaleCoef; + accuImage.img(xyoffset) += + getInterpolateColor(pyramidL[bandContrib], pixDownscaled.y, pixDownscaled.x) * triangleScore; + accuImage.imgCount[xyoffset] += triangleScore; + } + } } } } } } - //calculate atlas texture in the first level of the pyramid (avoid creating a new buffer) - //debug mode : write all the frequencies levels for each texture - for(std::size_t atlasID : atlasIDs) + // calculate atlas texture in the first level of the pyramid (avoid creating a new buffer) + // debug mode : write all the frequencies levels for each texture + for (std::size_t atlasID : atlasIDs) { AccuPyramid& accuPyramid = accuPyramids.at(atlasID); AccuImage& atlasTexture = accuPyramid.pyramid[0]; @@ -664,31 +657,33 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, #if TEXTURING_MBB_DEBUG { // write the number of contribution per atlas frequency bands - if(!texParams.useScore) + if (!texParams.useScore) { - for(std::size_t level = 0; level < accuPyramid.pyramid.size(); ++level) + for (std::size_t level = 0; level < accuPyramid.pyramid.size(); ++level) { - AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; + AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; - //write the number of contributions for each texture + // write the number of contributions for each texture std::vector imgContrib(textureSize); - for(unsigned int yp = 0; yp < texParams.textureSide; ++yp) + for (unsigned int yp = 0; yp < texParams.textureSide; ++yp) { unsigned int yoffset = yp * texParams.textureSide; - for(unsigned int xp = 0; xp < texParams.textureSide; ++xp) + for (unsigned int xp = 0; xp < texParams.textureSide; ++xp) { unsigned int xyoffset = yoffset + xp; imgContrib[xyoffset] = atlasLevelTexture.imgCount[xyoffset]; } } - const std::string textureName = "contrib_" + std::to_string(1001 + atlasID) + std::string("_") + std::to_string(level) + std::string(".") + EImageFileType_enumToString(textureFileType); // starts at '1001' for UDIM compatibility + const std::string textureName = "contrib_" + std::to_string(1001 + atlasID) + std::string("_") + std::to_string(level) + + std::string(".") + + EImageFileType_enumToString(textureFileType); // starts at '1001' for UDIM compatibility bfs::path texturePath = outPath / textureName; using namespace imageIO; OutputFileColorSpace colorspace(EImageColorSpace::SRGB, EImageColorSpace::AUTO); - if(texParams.convertLAB) + if (texParams.convertLAB) colorspace.from = EImageColorSpace::LAB; writeImage(texturePath.string(), texParams.textureSide, texParams.textureSide, imgContrib, EImageQuality::OPTIMIZED, colorspace); } @@ -697,23 +692,23 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, #endif ALICEVISION_LOG_INFO(" - Computing final (average) color."); - for(unsigned int yp = 0; yp < texParams.textureSide; ++yp) + for (unsigned int yp = 0; yp < texParams.textureSide; ++yp) { unsigned int yoffset = yp * texParams.textureSide; - for(unsigned int xp = 0; xp < texParams.textureSide; ++xp) + for (unsigned int xp = 0; xp < texParams.textureSide; ++xp) { unsigned int xyoffset = yoffset + xp; // If the imgCount is valid on the first band, it will be valid on all the other bands - if(atlasTexture.imgCount[xyoffset] == 0) + if (atlasTexture.imgCount[xyoffset] == 0) continue; atlasTexture.img(xyoffset) /= atlasTexture.imgCount[xyoffset]; atlasTexture.imgCount[xyoffset] = 1; - for(std::size_t level = 1; level < accuPyramid.pyramid.size(); ++level) + for (std::size_t level = 1; level < accuPyramid.pyramid.size(); ++level) { - AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; + AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; atlasLevelTexture.img(xyoffset) /= atlasLevelTexture.imgCount[xyoffset]; } } @@ -721,26 +716,25 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, #if TEXTURING_MBB_DEBUG { - //write each frequency band, for each texture - for(std::size_t level = 0; level < accuPyramid.pyramid.size(); ++level) + // write each frequency band, for each texture + for (std::size_t level = 0; level < accuPyramid.pyramid.size(); ++level) { - AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; + AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; writeTexture(atlasLevelTexture, atlasID, outPath, textureFileType, level); } - } #endif // Fuse frequency bands into the first buffer, calculate final texture - for(unsigned int yp = 0; yp < texParams.textureSide; ++yp) + for (unsigned int yp = 0; yp < texParams.textureSide; ++yp) { unsigned int yoffset = yp * texParams.textureSide; - for(unsigned int xp = 0; xp < texParams.textureSide; ++xp) + for (unsigned int xp = 0; xp < texParams.textureSide; ++xp) { unsigned int xyoffset = yoffset + xp; - for(std::size_t level = 1; level < accuPyramid.pyramid.size(); ++level) + for (std::size_t level = 1; level < accuPyramid.pyramid.size(); ++level) { - AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; + AccuImage& atlasLevelTexture = accuPyramid.pyramid[level]; atlasTexture.img(xyoffset) += atlasLevelTexture.img(xyoffset); } } @@ -749,79 +743,82 @@ void Texturing::generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, } } -void Texturing::generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, const Mesh& denseMesh, - const bfs::path& outPath, const mesh::BumpMappingParams& bumpMappingParams) +void Texturing::generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, + const Mesh& denseMesh, + const bfs::path& outPath, + const mesh::BumpMappingParams& bumpMappingParams) { GEO::Mesh geoDenseMesh; toGeoMesh(denseMesh, geoDenseMesh); GEO::compute_normals(geoDenseMesh); - GEO::MeshFacetsAABB denseMeshAABB(geoDenseMesh); // warning: mesh_reorder called inside + GEO::MeshFacetsAABB denseMeshAABB(geoDenseMesh); // warning: mesh_reorder called inside GEO::Mesh geoSparseMesh; toGeoMesh(*mesh, geoSparseMesh); GEO::compute_normals(geoSparseMesh); mvsUtils::ImagesCache> imageCache(mp, image::EImageColorSpace::NO_CONVERSION); - - for(size_t atlasID = 0; atlasID < _atlases.size(); ++atlasID) + + for (size_t atlasID = 0; atlasID < _atlases.size(); ++atlasID) _generateNormalAndHeightMaps(mp, denseMeshAABB, geoSparseMesh, atlasID, imageCache, outPath, bumpMappingParams); } - - -void Texturing::writeTexture(AccuImage& atlasTexture, const std::size_t atlasID, const boost::filesystem::path &outPath, - image::EImageFileType textureFileType, const int level) +void Texturing::writeTexture(AccuImage& atlasTexture, + const std::size_t atlasID, + const boost::filesystem::path& outPath, + image::EImageFileType textureFileType, + const int level) { unsigned int outTextureSide = texParams.textureSide; // WARNING: we modify the "imgCount" to apply the padding (to avoid the creation of a new buffer) // edge padding (dilate gutter) - if(!texParams.fillHoles && texParams.padding > 0 && level < 0) + if (!texParams.fillHoles && texParams.padding > 0 && level < 0) { const unsigned int padding = texParams.padding * 3; ALICEVISION_LOG_INFO(" - Edge padding (" << padding << " pixels)."); // Init valid values to 1 - for(unsigned int y = 0; y < outTextureSide; ++y) + for (unsigned int y = 0; y < outTextureSide; ++y) { unsigned int yoffset = y * outTextureSide; - for(unsigned int x = 0; x < outTextureSide; ++x) + for (unsigned int x = 0; x < outTextureSide; ++x) { unsigned int xyoffset = yoffset + x; - if(atlasTexture.imgCount[xyoffset] > 0) + if (atlasTexture.imgCount[xyoffset] > 0) atlasTexture.imgCount[xyoffset] = 1; } } - //up-left to bottom-right - for(unsigned int y = 1; y < outTextureSide-1; ++y) + // up-left to bottom-right + for (unsigned int y = 1; y < outTextureSide - 1; ++y) { unsigned int yoffset = y * outTextureSide; - for(unsigned int x = 1; x < outTextureSide-1; ++x) + for (unsigned int x = 1; x < outTextureSide - 1; ++x) { unsigned int xyoffset = yoffset + x; - if(atlasTexture.imgCount[xyoffset] > 0) + if (atlasTexture.imgCount[xyoffset] > 0) continue; const int upCount = atlasTexture.imgCount[xyoffset - outTextureSide]; const int leftCount = atlasTexture.imgCount[xyoffset - 1]; - //if pixel on the edge of a chart - if(leftCount > 0) + // if pixel on the edge of a chart + if (leftCount > 0) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset - 1); - atlasTexture.imgCount[xyoffset] = - 1; + atlasTexture.imgCount[xyoffset] = -1; } - else if(upCount > 0) + else if (upCount > 0) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset - outTextureSide); - atlasTexture.imgCount[xyoffset] = - 1; + atlasTexture.imgCount[xyoffset] = -1; } // - else if (leftCount < 0 && - leftCount < padding && (upCount == 0 || leftCount > upCount)) + else if (leftCount < 0 && -leftCount < padding && (upCount == 0 || leftCount > upCount)) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset - 1); atlasTexture.imgCount[xyoffset] = leftCount - 1; } - else if (upCount < 0 && - upCount < padding) + else if (upCount < 0 && -upCount < padding) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset - outTextureSide); atlasTexture.imgCount[xyoffset] = upCount - 1; @@ -829,41 +826,37 @@ void Texturing::writeTexture(AccuImage& atlasTexture, const std::size_t atlasID, } } - //bottom-right to up-left - for(unsigned int y = 1; y < outTextureSide-1; ++y) + // bottom-right to up-left + for (unsigned int y = 1; y < outTextureSide - 1; ++y) { unsigned int yoffset = (outTextureSide - 1 - y) * outTextureSide; - for(unsigned int x = 1; x < outTextureSide-1; ++x) + for (unsigned int x = 1; x < outTextureSide - 1; ++x) { unsigned int xyoffset = yoffset + (outTextureSide - 1 - x); - if(atlasTexture.imgCount[xyoffset] > 0) + if (atlasTexture.imgCount[xyoffset] > 0) continue; const int upCount = atlasTexture.imgCount[xyoffset - outTextureSide]; const int downCount = atlasTexture.imgCount[xyoffset + outTextureSide]; const int rightCount = atlasTexture.imgCount[xyoffset + 1]; const int leftCount = atlasTexture.imgCount[xyoffset - 1]; - if(rightCount > 0) + if (rightCount > 0) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset + 1); - atlasTexture.imgCount[xyoffset] = - 1; + atlasTexture.imgCount[xyoffset] = -1; } - else if(downCount > 0) + else if (downCount > 0) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset + outTextureSide); - atlasTexture.imgCount[xyoffset] = - 1; + atlasTexture.imgCount[xyoffset] = -1; } - else if ((rightCount < 0 && - rightCount < padding) && - (leftCount == 0 || rightCount > leftCount) && - (downCount == 0 || rightCount >= downCount) - ) + else if ((rightCount < 0 && -rightCount < padding) && (leftCount == 0 || rightCount > leftCount) && + (downCount == 0 || rightCount >= downCount)) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset + 1); atlasTexture.imgCount[xyoffset] = rightCount - 1; } - else if ((downCount < 0 && - downCount < padding) && - (upCount == 0 || downCount > upCount) - ) + else if ((downCount < 0 && -downCount < padding) && (upCount == 0 || downCount > upCount)) { atlasTexture.img(xyoffset) = atlasTexture.img(xyoffset + outTextureSide); atlasTexture.imgCount[xyoffset] = downCount - 1; @@ -873,14 +866,14 @@ void Texturing::writeTexture(AccuImage& atlasTexture, const std::size_t atlasID, } // texture holes filling - if(texParams.fillHoles) + if (texParams.fillHoles) { ALICEVISION_LOG_INFO(" - Filling texture holes."); std::vector alphaBuffer(atlasTexture.img.size()); - for(unsigned int yp = 0; yp < texParams.textureSide; ++yp) + for (unsigned int yp = 0; yp < texParams.textureSide; ++yp) { unsigned int yoffset = yp * texParams.textureSide; - for(unsigned int xp = 0; xp < texParams.textureSide; ++xp) + for (unsigned int xp = 0; xp < texParams.textureSide; ++xp) { unsigned int xyoffset = yoffset + xp; alphaBuffer[xyoffset] = atlasTexture.imgCount[xyoffset] ? 1 : 0; @@ -891,7 +884,7 @@ void Texturing::writeTexture(AccuImage& atlasTexture, const std::size_t atlasID, } // downscale texture if required - if(texParams.downscale > 1) + if (texParams.downscale > 1) { image::Image resizedColorBuffer; @@ -907,13 +900,14 @@ void Texturing::writeTexture(AccuImage& atlasTexture, const std::size_t atlasID, bfs::path texturePath = outPath / textureName; ALICEVISION_LOG_INFO(" - Writing texture file: " << texturePath.string()); - image::writeImage(texturePath.string(), atlasTexture.img, - image::ImageWriteOptions().fromColorSpace(texParams.workingColorSpace) - .toColorSpace(texParams.outputColorSpace) - .storageDataType(image::EStorageDataType::Half)); + image::writeImage(texturePath.string(), + atlasTexture.img, + image::ImageWriteOptions() + .fromColorSpace(texParams.workingColorSpace) + .toColorSpace(texParams.outputColorSpace) + .storageDataType(image::EStorageDataType::Half)); } - void Texturing::clear() { delete mesh; @@ -930,13 +924,13 @@ void Texturing::loadWithAtlas(const std::string& filepath, bool flipNormals) mesh->load(filepath); // Handle normals flipping - if(flipNormals) + if (flipNormals) mesh->invertTriangleOrientations(); // Fill atlases (1 atlas per material) with corresponding rectangles // if no material, create only one atlas with all triangles _atlases.resize(std::max(1, mesh->nmtls)); - for(int triangleID = 0; triangleID < mesh->trisMtlIds().size(); ++triangleID) + for (int triangleID = 0; triangleID < mesh->trisMtlIds().size(); ++triangleID) { unsigned int atlasID = mesh->nmtls ? mesh->trisMtlIds()[triangleID] : 0; _atlases[atlasID].push_back(triangleID); @@ -963,30 +957,28 @@ void Texturing::loadWithMaterial(const std::string& filepath, bool flipNormals) } } -void Texturing::remapVisibilities(EVisibilityRemappingMethod remappingMethod, - const mvsUtils::MultiViewParams& mp, const Mesh& refMesh) +void Texturing::remapVisibilities(EVisibilityRemappingMethod remappingMethod, const mvsUtils::MultiViewParams& mp, const Mesh& refMesh) { - if(refMesh.pointsVisibilities.empty() && - (remappingMethod & mesh::EVisibilityRemappingMethod::Pull || - remappingMethod & mesh::EVisibilityRemappingMethod::Push)) + if (refMesh.pointsVisibilities.empty() && + (remappingMethod & mesh::EVisibilityRemappingMethod::Pull || remappingMethod & mesh::EVisibilityRemappingMethod::Push)) { throw std::runtime_error("Texturing: Cannot remap visibilities as there is no reference points."); } // remap visibilities from the reference onto the mesh - if(remappingMethod & mesh::EVisibilityRemappingMethod::Pull) + if (remappingMethod & mesh::EVisibilityRemappingMethod::Pull) { remapMeshVisibilities_pullVerticesVisibility(refMesh, *mesh); } - if(remappingMethod & mesh::EVisibilityRemappingMethod::Push) + if (remappingMethod & mesh::EVisibilityRemappingMethod::Push) { remapMeshVisibilities_pushVerticesVisibilityToTriangles(refMesh, *mesh); } - if(remappingMethod & EVisibilityRemappingMethod::MeshItself) + if (remappingMethod & EVisibilityRemappingMethod::MeshItself) { remapMeshVisibilities_meshItself(mp, *mesh); } - if(mesh->pointsVisibilities.empty()) + if (mesh->pointsVisibilities.empty()) { throw std::runtime_error("No visibility after visibility remapping."); } @@ -999,17 +991,17 @@ void Texturing::replaceMesh(const std::string& otherMeshPath, bool flipNormals) // set pointers to null to avoid deallocation by 'loadFromObj' mesh->pointsVisibilities.resize(0); mesh = nullptr; - + // load input obj file loadWithAtlas(otherMeshPath, flipNormals); // allocate pointsVisibilities for new internal mesh mesh->pointsVisibilities = PointsVisibility(); // remap visibilities from reconstruction onto input mesh - if(texParams.visibilityRemappingMethod & EVisibilityRemappingMethod::Pull) + if (texParams.visibilityRemappingMethod & EVisibilityRemappingMethod::Pull) remapMeshVisibilities_pullVerticesVisibility(*refMesh, *mesh); if (texParams.visibilityRemappingMethod & EVisibilityRemappingMethod::Push) remapMeshVisibilities_pushVerticesVisibilityToTriangles(*refMesh, *mesh); - if(mesh->pointsVisibilities.empty()) + if (mesh->pointsVisibilities.empty()) throw std::runtime_error("No visibility after visibility remapping."); // delete ref mesh and visibilities @@ -1018,7 +1010,7 @@ void Texturing::replaceMesh(const std::string& otherMeshPath, bool flipNormals) void Texturing::unwrap(mvsUtils::MultiViewParams& mp, EUnwrapMethod method) { - if(method == mesh::EUnwrapMethod::Basic) + if (method == mesh::EUnwrapMethod::Basic) { // generate UV coordinates based on automatic uv atlas generateUVsBasicMethod(mp); @@ -1091,9 +1083,9 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT const StaticVector& displacementTextures = material.getTextures(Material::TextureType::DISPLACEMENT); // write faces per texture atlas - for(int atlasId = 0; atlasId < _atlases.size(); ++atlasId) + for (int atlasId = 0; atlasId < _atlases.size(); ++atlasId) { - //Set material for this atlas + // Set material for this atlas const aiString texName(Material::textureId(atlasId)); scene.mMaterials[atlasId] = new aiMaterial; @@ -1104,28 +1096,28 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT scene.mMaterials[atlasId]->AddProperty(&texName, AI_MATKEY_NAME); // Color Mapping - if(diffuseTextures.size() == _atlases.size()) + if (diffuseTextures.size() == _atlases.size()) { const aiString texFile(diffuseTextures[atlasId]); scene.mMaterials[atlasId]->AddProperty(&texFile, AI_MATKEY_TEXTURE_DIFFUSE(0)); } // Displacement Mapping - if(displacementTextures.size() == _atlases.size()) + if (displacementTextures.size() == _atlases.size()) { const aiString texFileHeightMap(displacementTextures[atlasId]); scene.mMaterials[atlasId]->AddProperty(&texFileHeightMap, AI_MATKEY_TEXTURE_DISPLACEMENT(0)); } - + // Normal Mapping - if(normalTextures.size() == _atlases.size()) + if (normalTextures.size() == _atlases.size()) { const aiString texFileNormalMap(normalTextures[atlasId]); scene.mMaterials[atlasId]->AddProperty(&texFileNormalMap, AI_MATKEY_TEXTURE_NORMALS(0)); } // Bump Mapping - if(bumpTextures.size() == _atlases.size()) + if (bumpTextures.size() == _atlases.size()) { const aiString texFileHeightMap(bumpTextures[atlasId]); scene.mMaterials[atlasId]->AddProperty(&texFileHeightMap, AI_MATKEY_TEXTURE_HEIGHT(0)); @@ -1133,14 +1125,14 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT scene.mRootNode->mMeshes[atlasId] = atlasId; scene.mMeshes[atlasId] = new aiMesh; - aiMesh * aimesh = scene.mMeshes[atlasId]; + aiMesh* aimesh = scene.mMeshes[atlasId]; aimesh->mMaterialIndex = atlasId; aimesh->mNumUVComponents[0] = 2; - //Assimp does not allow vertex indices different from uv indices - //So we need to group and duplicate + // Assimp does not allow vertex indices different from uv indices + // So we need to group and duplicate std::map, int> unique_pairs; - for(const auto triangleID : _atlases[atlasId]) + for (const auto triangleID : _atlases[atlasId]) { for (int k = 0; k < 3; ++k) { @@ -1157,7 +1149,7 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT aimesh->mTextureCoords[0] = new aiVector3D[unique_pairs.size()]; int index = 0; - for (auto & p : unique_pairs) + for (auto& p : unique_pairs) { int vertexId = p.first.first; int uvId = p.first.second; @@ -1178,7 +1170,7 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT aimesh->mNumFaces = _atlases[atlasId].size(); aimesh->mFaces = new aiFace[aimesh->mNumFaces]; - for(int i = 0; i < _atlases[atlasId].size(); ++i) + for (int i = 0; i < _atlases[atlasId].size(); ++i) { const int triangleId = _atlases[atlasId][i]; @@ -1199,7 +1191,7 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT std::string formatId = meshFileTypeStr; unsigned int pPreprocessing = 0u; // If gltf, use gltf 2.0 - if(meshFileType == EFileType::GLTF) + if (meshFileType == EFileType::GLTF) { formatId = "gltf2"; // Flip UVs when exporting (issue with UV origin for gltf2) @@ -1213,7 +1205,6 @@ void Texturing::saveAs(const bfs::path& dir, const std::string& basename, EFileT ALICEVISION_LOG_INFO("Save mesh to " << meshFileTypeStr << " done."); } - inline GEO::vec3 mesh_facet_interpolate_normal_at_point(const GEO::Mesh& mesh, GEO::index_t f, const GEO::vec3& p) { const GEO::index_t v0 = mesh.facets.vertex(f, 0); @@ -1230,40 +1221,37 @@ inline GEO::vec3 mesh_facet_interpolate_normal_at_point(const GEO::Mesh& mesh, G GEO::vec3 barycCoords; GEO::vec3 closestPoint; - GEO::Geom::point_triangle_squared_distance(p, p0, p1, p2, closestPoint, barycCoords.x, barycCoords.y, - barycCoords.z); + GEO::Geom::point_triangle_squared_distance(p, p0, p1, p2, closestPoint, barycCoords.x, barycCoords.y, barycCoords.z); const GEO::vec3 n = barycCoords.x * n0 + barycCoords.y * n1 + barycCoords.z * n2; return GEO::normalize(n); } -inline GEO::vec3 mesh_facet_interpolate_normal_at_point(const StaticVector& ptsNormals, const Mesh& mesh, - GEO::index_t f, const GEO::vec3& p) +inline GEO::vec3 mesh_facet_interpolate_normal_at_point(const StaticVector& ptsNormals, const Mesh& mesh, GEO::index_t f, const GEO::vec3& p) { const GEO::index_t v0 = (mesh.tris)[f].v[0]; const GEO::index_t v1 = (mesh.tris)[f].v[1]; const GEO::index_t v2 = (mesh.tris)[f].v[2]; - const GEO::vec3 p0 ((mesh.pts)[v0].x, (mesh.pts)[v0].y, (mesh.pts)[v0].z); - const GEO::vec3 p1 ((mesh.pts)[v1].x, (mesh.pts)[v1].y, (mesh.pts)[v1].z); - const GEO::vec3 p2 ((mesh.pts)[v2].x, (mesh.pts)[v2].y, (mesh.pts)[v2].z); + const GEO::vec3 p0((mesh.pts)[v0].x, (mesh.pts)[v0].y, (mesh.pts)[v0].z); + const GEO::vec3 p1((mesh.pts)[v1].x, (mesh.pts)[v1].y, (mesh.pts)[v1].z); + const GEO::vec3 p2((mesh.pts)[v2].x, (mesh.pts)[v2].y, (mesh.pts)[v2].z); - const GEO::vec3 n0 (ptsNormals[v0].x, ptsNormals[v0].y, ptsNormals[v0].z); - const GEO::vec3 n1 (ptsNormals[v1].x, ptsNormals[v1].y, ptsNormals[v1].z); - const GEO::vec3 n2 (ptsNormals[v2].x, ptsNormals[v2].y, ptsNormals[v2].z); + const GEO::vec3 n0(ptsNormals[v0].x, ptsNormals[v0].y, ptsNormals[v0].z); + const GEO::vec3 n1(ptsNormals[v1].x, ptsNormals[v1].y, ptsNormals[v1].z); + const GEO::vec3 n2(ptsNormals[v2].x, ptsNormals[v2].y, ptsNormals[v2].z); GEO::vec3 barycCoords; GEO::vec3 closestPoint; - GEO::Geom::point_triangle_squared_distance(p, p0, p1, p2, closestPoint, barycCoords.x, barycCoords.y, - barycCoords.z); + GEO::Geom::point_triangle_squared_distance(p, p0, p1, p2, closestPoint, barycCoords.x, barycCoords.y, barycCoords.z); const GEO::vec3 n = barycCoords.x * n0 + barycCoords.y * n1 + barycCoords.z * n2; return GEO::normalize(n); } -template +template inline Eigen::Matrix toEigen(const GEO::vecng& v) { return Eigen::Matrix(v.data()); @@ -1285,20 +1273,20 @@ inline Eigen::Matrix3d computeTriangleTransform(const Mesh& mesh, int f, const P const Eigen::Vector3d p1 = toEigen((mesh.pts)[(mesh.tris)[f].v[1]]); const Eigen::Vector3d p2 = toEigen((mesh.pts)[(mesh.tris)[f].v[2]]); - const Eigen::Vector3d tX = (p1 - p0).normalized(); // edge0 => local triangle X-axis - const Eigen::Vector3d N = tX.cross((p2 - p0).normalized()).normalized(); // cross(edge0, edge1) => Z-axis + const Eigen::Vector3d tX = (p1 - p0).normalized(); // edge0 => local triangle X-axis + const Eigen::Vector3d N = tX.cross((p2 - p0).normalized()).normalized(); // cross(edge0, edge1) => Z-axis // Correct triangle X-axis to be align with X-axis in the texture const GEO::vec2 t0 = GEO::vec2(triPts[0].m); const GEO::vec2 t1 = GEO::vec2(triPts[1].m); const GEO::vec2 tV = GEO::normalize(t1 - t0); - const GEO::vec2 origNormal(1.0, 0.0); // X-axis in the texture + const GEO::vec2 origNormal(1.0, 0.0); // X-axis in the texture const double tAngle = GEO::Geom::angle(tV, origNormal); Eigen::Matrix3d transform(Eigen::AngleAxisd(tAngle, N).toRotationMatrix()); // Rotate triangle v0v1 axis around Z-axis, to get a X axis aligned with the 2d texture Eigen::Vector3d X = (transform * tX).normalized(); - const Eigen::Vector3d Y = N.cross(X).normalized(); // Y-axis + const Eigen::Vector3d Y = N.cross(X).normalized(); // Y-axis Eigen::Matrix3d m; m.col(0) = X; @@ -1310,9 +1298,16 @@ inline Eigen::Matrix3d computeTriangleTransform(const Mesh& mesh, int f, const P return mT; } -inline void computeNormalHeight(const GEO::Mesh& mesh, double orientation, double t, GEO::index_t f, - const Eigen::Matrix3d& m, const GEO::vec3& q, const GEO::vec3& qA, const GEO::vec3& qB, - float& out_height, image::RGBfColor& out_normal) +inline void computeNormalHeight(const GEO::Mesh& mesh, + double orientation, + double t, + GEO::index_t f, + const Eigen::Matrix3d& m, + const GEO::vec3& q, + const GEO::vec3& qA, + const GEO::vec3& qB, + float& out_height, + image::RGBfColor& out_normal) { GEO::vec3 intersectionPoint = t * qB + (1.0 - t) * qA; out_height = q.distance(intersectionPoint) * orientation; @@ -1328,13 +1323,15 @@ inline void computeNormalHeight(const GEO::Mesh& mesh, double orientation, doubl } void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, - const GEO::MeshFacetsAABB& denseMeshAABB, const GEO::Mesh& sparseMesh, + const GEO::MeshFacetsAABB& denseMeshAABB, + const GEO::Mesh& sparseMesh, size_t atlasID, mvsUtils::ImagesCache>& imageCache, - const bfs::path& outPath, const mesh::BumpMappingParams& bumpMappingParams) + const bfs::path& outPath, + const mesh::BumpMappingParams& bumpMappingParams) { - ALICEVISION_LOG_INFO("Generating Height and Normal Maps for atlas " << atlasID + 1 << "/" << _atlases.size() << " (" - << _atlases[atlasID].size() << " triangles)."); + ALICEVISION_LOG_INFO("Generating Height and Normal Maps for atlas " << atlasID + 1 << "/" << _atlases.size() << " (" << _atlases[atlasID].size() + << " triangles)."); image::Image normalMap(texParams.textureSide, texParams.textureSide); image::Image heightMap(texParams.textureSide, texParams.textureSide); @@ -1342,7 +1339,7 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp // iterate over atlas' triangles #pragma omp parallel for - for(int ti = 0; ti < triangles.size(); ++ti) + for (int ti = 0; ti < triangles.size(); ++ti) { const unsigned int triangleId = triangles[ti]; // const Point3d __triangleNormal_ = me->computeTriangleNormal(triangleId).normalize(); @@ -1358,24 +1355,20 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp // compute the Bottom-Left minima of the current UDIM for [0,1] range remapping Point2d udimBL; StaticVector& uvCoords = mesh->uvCoords; - udimBL.x = std::floor(std::min({uvCoords[triangleUvIds[0]].x, - uvCoords[triangleUvIds[1]].x, - uvCoords[triangleUvIds[2]].x})); - udimBL.y = std::floor(std::min({uvCoords[triangleUvIds[0]].y, - uvCoords[triangleUvIds[1]].y, - uvCoords[triangleUvIds[2]].y})); - - for(int k = 0; k < 3; k++) + udimBL.x = std::floor(std::min({uvCoords[triangleUvIds[0]].x, uvCoords[triangleUvIds[1]].x, uvCoords[triangleUvIds[2]].x})); + udimBL.y = std::floor(std::min({uvCoords[triangleUvIds[0]].y, uvCoords[triangleUvIds[1]].y, uvCoords[triangleUvIds[2]].y})); + + for (int k = 0; k < 3; k++) { const int pointIndex = (mesh->tris)[triangleId].v[k]; - triPts[k] = (mesh->pts)[pointIndex]; // 3D coordinates + triPts[k] = (mesh->pts)[pointIndex]; // 3D coordinates const int uvPointIndex = triangleUvIds.m[k]; Point2d uv = uvCoords[uvPointIndex]; // UDIM: remap coordinates between [0,1] uv = uv - udimBL; - triPixs[k] = uv * texParams.textureSide; // UV coordinates + triPixs[k] = uv * texParams.textureSide; // UV coordinates } // compute triangle bounding box in pixel indexes @@ -1398,16 +1391,16 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp // const Point3d triangleNormal = me->computeTriangleNormal(triangleId); // iterate over bounding box's pixels - for(int y = LU.y; y < RD.y; ++y) + for (int y = LU.y; y < RD.y; ++y) { - for(int x = LU.x; x < RD.x; ++x) + for (int x = LU.x; x < RD.x; ++x) { - Pixel pix(x, y); // top-left corner of the pixel + Pixel pix(x, y); // top-left corner of the pixel Point2d barycCoords; // test if the pixel is inside triangle // and retrieve its barycentric coordinates - if(!isPixelInTriangle(triPixs, pix, barycCoords)) + if (!isPixelInTriangle(triPixs, pix, barycCoords)) { continue; } @@ -1433,20 +1426,20 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp double t = 0.0; GEO::index_t f = 0.0; bool intersection = denseMeshAABB.segment_nearest_intersection(qA1, qB1, t, f); - if(intersection) + if (intersection) { - computeNormalHeight(*denseMeshAABB.mesh(), 1.0, t, f, worldToTriangleMatrix, q, qA1, qB1, - heightMap(xyoffset), normalMap(xyoffset)); + computeNormalHeight( + *denseMeshAABB.mesh(), 1.0, t, f, worldToTriangleMatrix, q, qA1, qB1, heightMap(xyoffset), normalMap(xyoffset)); } else { GEO::vec3 qA2 = q + (scaledTriangleNormal * epsilon); GEO::vec3 qB2 = q - scaledTriangleNormal; bool intersection = denseMeshAABB.segment_nearest_intersection(qA2, qB2, t, f); - if(intersection) + if (intersection) { - computeNormalHeight(*denseMeshAABB.mesh(), -1.0, t, f, worldToTriangleMatrix, q, qA2, qB2, - heightMap(xyoffset), normalMap(xyoffset)); + computeNormalHeight( + *denseMeshAABB.mesh(), -1.0, t, f, worldToTriangleMatrix, q, qA2, qB2, heightMap(xyoffset), normalMap(xyoffset)); } else { @@ -1459,11 +1452,11 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp } // Save Normal Map - if(bumpMappingParams.bumpType == EBumpMappingType::Normal && bumpMappingParams.bumpMappingFileType != image::EImageFileType::NONE) + if (bumpMappingParams.bumpType == EBumpMappingType::Normal && bumpMappingParams.bumpMappingFileType != image::EImageFileType::NONE) { unsigned int outTextureSide = texParams.textureSide; // downscale texture if required - if(texParams.downscale > 1) + if (texParams.downscale > 1) { ALICEVISION_LOG_INFO("Downscaling normal map (" << texParams.downscale << "x)."); image::Image resizedBuffer; @@ -1478,14 +1471,14 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp // X: -1 to +1 : Red : 0 to 255 // Y: -1 to +1 : Green : 0 to 255 // Z: 0 to -1 : Blue : 128 to 255 OR 0 to 255 (like Blender) - for(unsigned int i = 0; i < normalMap.size(); ++i) + for (unsigned int i = 0; i < normalMap.size(); ++i) // normalMap(i) = image::RGBfColor(normalMap[i].r * 0.5 + 0.5, // normalMap[i].g * 0.5 + 0.5, // normalMap[i].b); // B: // 0:+1 => 0-255 normalMap(i) = image::RGBfColor(normalMap(i).r() * 0.5 + 0.5, normalMap(i).g() * 0.5 + 0.5, - normalMap(i).b() * 0.5 + 0.5); // B: -1:+1 => 0-255 which means 0:+1 => 128-255 + normalMap(i).b() * 0.5 + 0.5); // B: -1:+1 => 0-255 which means 0:+1 => 128-255 material.normalType = bumpMappingParams.bumpMappingFileType; const std::string name = material.textureName(Material::TextureType::NORMAL, static_cast(atlasID)); @@ -1494,17 +1487,19 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp bfs::path normalMapPath = outPath / name; ALICEVISION_LOG_INFO("Writing normal map: " << normalMapPath.string()); - image::writeImage(normalMapPath.string(), normalMap, - image::ImageWriteOptions().fromColorSpace(image::EImageColorSpace::NO_CONVERSION) - .toColorSpace(image::EImageColorSpace::NO_CONVERSION) - .storageDataType(image::EStorageDataType::Half)); + image::writeImage(normalMapPath.string(), + normalMap, + image::ImageWriteOptions() + .fromColorSpace(image::EImageColorSpace::NO_CONVERSION) + .toColorSpace(image::EImageColorSpace::NO_CONVERSION) + .storageDataType(image::EStorageDataType::Half)); } // Save Height Maps - if(bumpMappingParams.bumpMappingFileType != image::EImageFileType::NONE || bumpMappingParams.displacementFileType != image::EImageFileType::NONE) + if (bumpMappingParams.bumpMappingFileType != image::EImageFileType::NONE || bumpMappingParams.displacementFileType != image::EImageFileType::NONE) { unsigned int outTextureSide = texParams.textureSide; - if(texParams.downscale > 1) + if (texParams.downscale > 1) { ALICEVISION_LOG_INFO("Downscaling height map (" << texParams.downscale << "x)."); image::Image resizedBuffer; @@ -1514,8 +1509,8 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp } // Height maps are only in .EXR at this time, so this will never be executed. - // - //if(bumpMappingParams.bumpMappingFileType != image::EImageFileType::EXR) + // + // if(bumpMappingParams.bumpMappingFileType != image::EImageFileType::EXR) //{ // // Y: [-1, 0, +1] => [0, 128, 255] // for(unsigned int i = 0; i < heightMap.size(); ++i) @@ -1523,7 +1518,7 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp //} // Save Bump Map - if(bumpMappingParams.bumpType == EBumpMappingType::Height) + if (bumpMappingParams.bumpType == EBumpMappingType::Height) { material.bumpType = bumpMappingParams.bumpMappingFileType; const std::string bumpName = material.textureName(Material::TextureType::BUMP, static_cast(atlasID)); @@ -1532,25 +1527,22 @@ void Texturing::_generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp bfs::path bumpMapPath = outPath / bumpName; ALICEVISION_LOG_INFO("Writing bump map: " << bumpMapPath); - image::writeImage(bumpMapPath.string(), heightMap, - image::ImageWriteOptions().storageDataType(image::EStorageDataType::Half)); + image::writeImage(bumpMapPath.string(), heightMap, image::ImageWriteOptions().storageDataType(image::EStorageDataType::Half)); } // Save Displacement Map - if(bumpMappingParams.displacementFileType != image::EImageFileType::NONE) + if (bumpMappingParams.displacementFileType != image::EImageFileType::NONE) { material.displacementType = bumpMappingParams.displacementFileType; - const std::string dispName = material.textureName(Material::TextureType::DISPLACEMENT, - static_cast(atlasID)); + const std::string dispName = material.textureName(Material::TextureType::DISPLACEMENT, static_cast(atlasID)); material.addTexture(Material::TextureType::DISPLACEMENT, dispName); bfs::path dispMapPath = outPath / dispName; ALICEVISION_LOG_INFO("Writing displacement map: " << dispMapPath); - image::writeImage(dispMapPath.string(), heightMap, - image::ImageWriteOptions().storageDataType(image::EStorageDataType::Half)); + image::writeImage(dispMapPath.string(), heightMap, image::ImageWriteOptions().storageDataType(image::EStorageDataType::Half)); } } } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/Texturing.hpp b/src/aliceVision/mesh/Texturing.hpp index ddfbf40b64..6b32c81f0a 100644 --- a/src/aliceVision/mesh/Texturing.hpp +++ b/src/aliceVision/mesh/Texturing.hpp @@ -23,9 +23,9 @@ namespace bfs = boost::filesystem; namespace GEO { - class MeshFacetsAABB; - class Mesh; -} +class MeshFacetsAABB; +class Mesh; +} // namespace GEO namespace aliceVision { namespace mesh { @@ -33,10 +33,11 @@ namespace mesh { /** * @brief Available mesh unwrapping methods */ -enum class EUnwrapMethod { - Basic = 0, //< Basic unwrapping based on visibilities - ABF = 1, //< Geogram: ABF++ - LSCM = 2 //< Geogram: Spectral LSCM +enum class EUnwrapMethod +{ + Basic = 0, //< Basic unwrapping based on visibilities + ABF = 1, //< Geogram: ABF++ + LSCM = 2 //< Geogram: Spectral LSCM }; /** @@ -63,7 +64,6 @@ std::string EBumpMappingType_enumToString(EBumpMappingType type); std::istream& operator>>(std::istream& in, EBumpMappingType& meshFileType); std::ostream& operator<<(std::ostream& os, EBumpMappingType meshFileType); - struct BumpMappingParams { image::EImageFileType bumpMappingFileType = image::EImageFileType::NONE; @@ -83,18 +83,18 @@ struct TexturingParams // Multi-band blending unsigned int nbBand = 4; unsigned int multiBandDownscale = 4; - std::vector multiBandNbContrib = {1, 5, 10, 0}; // number of contributions per frequency band for the multi-band blending + std::vector multiBandNbContrib = {1, 5, 10, 0}; // number of contributions per frequency band for the multi-band blending bool useScore = true; - double bestScoreThreshold = 0.1; //< 0.0 to disable filtering based on threshold to relative best score - double angleHardThreshold = 90.0; //< 0.0 to disable angle hard threshold filtering + double bestScoreThreshold = 0.1; //< 0.0 to disable filtering based on threshold to relative best score + double angleHardThreshold = 90.0; //< 0.0 to disable angle hard threshold filtering image::EImageFileType textureFileType = image::EImageFileType::NONE; - image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB; // color space for the texturing internal computation - image::EImageColorSpace outputColorSpace = image::EImageColorSpace::AUTO; // output file color space + image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB; // color space for the texturing internal computation + image::EImageColorSpace outputColorSpace = image::EImageColorSpace::AUTO; // output file color space mvsUtils::ECorrectEV correctEV{mvsUtils::ECorrectEV::NO_CORRECTION}; - bool forceVisibleByAllVertices = false; //< triangle visibility is based on the union of vertices visiblity + bool forceVisibleByAllVertices = false; //< triangle visibility is based on the union of vertices visiblity EVisibilityRemappingMethod visibilityRemappingMethod = EVisibilityRemappingMethod::PullPush; float subdivisionTargetRatio = 0.8; @@ -111,21 +111,17 @@ struct Texturing /// Material and texture information Material material; - ~Texturing() - { - delete mesh; - } - -public: + ~Texturing() { delete mesh; } + public: /// Clear internal mesh data void clear(); /// Load a mesh from a .obj file and initialize internal structures - void loadWithAtlas(const std::string& filepath, bool flipNormals=false); + void loadWithAtlas(const std::string& filepath, bool flipNormals = false); /// Load a textured mesh from a .obj and .mtl files - void loadWithMaterial(const std::string& filepath, bool flipNormals=false); + void loadWithMaterial(const std::string& filepath, bool flipNormals = false); /** * @brief Remap visibilities @@ -135,8 +131,7 @@ struct Texturing * @param[in] refMesh the reference mesh * @param[in] refPointsVisibilities the reference visibilities */ - void remapVisibilities(EVisibilityRemappingMethod remappingMethod, const mvsUtils::MultiViewParams& mp, - const Mesh& refMesh); + void remapVisibilities(EVisibilityRemappingMethod remappingMethod, const mvsUtils::MultiViewParams& mp, const Mesh& refMesh); /** * @brief Replace inner mesh with the mesh loaded from 'otherMeshPath' @@ -145,7 +140,7 @@ struct Texturing * @param otherMeshPath the mesh to load * @param flipNormals whether to flip normals when loading the mesh */ - void replaceMesh(const std::string& otherMeshPath, bool flipNormals=false); + void replaceMesh(const std::string& otherMeshPath, bool flipNormals = false); /// Returns whether UV coordinates are available inline bool hasUVs() const { return !mesh->uvCoords.empty(); } @@ -164,7 +159,7 @@ struct Texturing * * @param mp */ - void generateUVsBasicMethod(mvsUtils::MultiViewParams &mp); + void generateUVsBasicMethod(mvsUtils::MultiViewParams& mp); /** * @brief Update texture atlases, useful when the internal mesh has been sudivise @@ -192,14 +187,14 @@ struct Texturing void init(int nbLevels, int imgWidth, int imgHeight) { pyramid.resize(nbLevels); - for(auto& accuImage : pyramid) + for (auto& accuImage : pyramid) accuImage.resize(imgWidth, imgHeight); } }; /// Generate texture files for all texture atlases void generateTextures(const mvsUtils::MultiViewParams& mp, - const bfs::path &outPath, + const bfs::path& outPath, size_t memoryAvailable, image::EImageFileType textureFileType = image::EImageFileType::PNG); @@ -207,25 +202,32 @@ struct Texturing void generateTexturesSubSet(const mvsUtils::MultiViewParams& mp, const std::vector& atlasIDs, mvsUtils::ImagesCache>& imageCache, - const bfs::path &outPath, + const bfs::path& outPath, image::EImageFileType textureFileType = image::EImageFileType::PNG); - void generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, const Mesh& denseMesh, - const bfs::path& outPath, const mesh::BumpMappingParams& bumpMappingParams); - - void _generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, const GEO::MeshFacetsAABB& denseMeshAABB, - const GEO::Mesh& sparseMesh, size_t atlasID, - mvsUtils::ImagesCache >& imageCache, - const bfs::path& outPath, const mesh::BumpMappingParams& bumpMappingParams); - - ///Fill holes and write texture files for the given texture atlas - void writeTexture(AccuImage& atlasTexture, const std::size_t atlasID, const bfs::path& outPath, - image::EImageFileType textureFileType, const int level); + void generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, + const Mesh& denseMesh, + const bfs::path& outPath, + const mesh::BumpMappingParams& bumpMappingParams); + + void _generateNormalAndHeightMaps(const mvsUtils::MultiViewParams& mp, + const GEO::MeshFacetsAABB& denseMeshAABB, + const GEO::Mesh& sparseMesh, + size_t atlasID, + mvsUtils::ImagesCache>& imageCache, + const bfs::path& outPath, + const mesh::BumpMappingParams& bumpMappingParams); + + /// Fill holes and write texture files for the given texture atlas + void writeTexture(AccuImage& atlasTexture, + const std::size_t atlasID, + const bfs::path& outPath, + image::EImageFileType textureFileType, + const int level); /// Save textured mesh as an OBJ + MTL file - void saveAs(const bfs::path& dir, const std::string& basename, - aliceVision::mesh::EFileType meshFileType = aliceVision::mesh::EFileType::OBJ); + void saveAs(const bfs::path& dir, const std::string& basename, aliceVision::mesh::EFileType meshFileType = aliceVision::mesh::EFileType::OBJ); }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/UVAtlas.cpp b/src/aliceVision/mesh/UVAtlas.cpp index 625b6a21ad..626e638db5 100644 --- a/src/aliceVision/mesh/UVAtlas.cpp +++ b/src/aliceVision/mesh/UVAtlas.cpp @@ -12,11 +12,10 @@ namespace aliceVision { namespace mesh { -UVAtlas::UVAtlas(const Mesh& mesh, mvsUtils::MultiViewParams& mp, - unsigned int textureSide, unsigned int gutterSize) - : _textureSide(textureSide) - , _gutterSize(gutterSize) - , _mesh(mesh) +UVAtlas::UVAtlas(const Mesh& mesh, mvsUtils::MultiViewParams& mp, unsigned int textureSide, unsigned int gutterSize) + : _textureSide(textureSide), + _gutterSize(gutterSize), + _mesh(mesh) { std::vector charts; @@ -44,20 +43,20 @@ void UVAtlas::createCharts(std::vector& charts, mvsUtils::MultiViewParams // create one chart per triangle _triangleCameraIDs.resize(_mesh.tris.size()); charts.resize(_mesh.tris.size()); - #pragma omp parallel for - for(int i = 0; i < trisCams.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < trisCams.size(); ++i) { std::vector> commonCameraIDs; // project triangle in all cams auto cameras = trisCams[i]; - for(int c = 0; c < cameras.size(); ++c) + for (int c = 0; c < cameras.size(); ++c) { int cameraID = cameras[c]; // project triangle Mesh::triangle_proj tProj = _mesh.getTriangleProjection(i, mp, cameraID, mp.getWidth(cameraID), mp.getHeight(cameraID)); - if(!_mesh.isTriangleProjectionInImage(mp, tProj, cameraID, 10)) + if (!_mesh.isTriangleProjectionInImage(mp, tProj, cameraID, 10)) continue; const float area = _mesh.computeTriangleProjectionArea(tProj); @@ -70,10 +69,10 @@ void UVAtlas::createCharts(std::vector& charts, mvsUtils::MultiViewParams Chart& chart = charts[i]; for (int c = 0; c < commonCameraIDs.size(); ++c) { - // don't use visibility with less than half the resolution of the best one - if (c > 0 && commonCameraIDs[c].first < 0.5 * commonCameraIDs[0].first) - break; - chart.commonCameraIDs.emplace_back(commonCameraIDs[c].second); + // don't use visibility with less than half the resolution of the best one + if (c > 0 && commonCameraIDs[c].first < 0.5 * commonCameraIDs[0].first) + break; + chart.commonCameraIDs.emplace_back(commonCameraIDs[c].second); } // sort cameras by IDs std::sort(chart.commonCameraIDs.begin(), chart.commonCameraIDs.end()); @@ -82,18 +81,17 @@ void UVAtlas::createCharts(std::vector& charts, mvsUtils::MultiViewParams _triangleCameraIDs[i] = chart.commonCameraIDs; // store triangle ID - chart.triangleIDs.emplace_back(i); // one triangle per chart in a first place + chart.triangleIDs.emplace_back(i); // one triangle per chart in a first place } } void UVAtlas::packCharts(std::vector& charts, mvsUtils::MultiViewParams& mp) { - ALICEVISION_LOG_INFO("Packing texture charts (" << charts.size() << " charts)."); + ALICEVISION_LOG_INFO("Packing texture charts (" << charts.size() << " charts)."); - std::function findChart = [&](int cid) - { + std::function findChart = [&](int cid) { Chart& c = charts[cid]; - if(c.mergedWith >= 0) + if (c.mergedWith >= 0) { int r = findChart(c.mergedWith); c.mergedWith = r; @@ -104,7 +102,7 @@ void UVAtlas::packCharts(std::vector& charts, mvsUtils::MultiViewParams& // list mesh edges (with duplicates) std::vector alledges; - for(int i = 0; i < _mesh.tris.size(); ++i) + for (int i = 0; i < _mesh.tris.size(); ++i) { int a = _mesh.tris[i].v[0]; int b = _mesh.tris[i].v[1]; @@ -127,11 +125,11 @@ void UVAtlas::packCharts(std::vector& charts, mvsUtils::MultiViewParams& // merge edges (no duplicate) std::vector edges; auto eit = alledges.begin() + 1; - while(eit != alledges.end()) + while (eit != alledges.end()) { - auto& a = *(eit-1); + auto& a = *(eit - 1); auto& b = *eit; - if(a == b) + if (a == b) { a.triangleIDs.insert(a.triangleIDs.end(), b.triangleIDs.begin(), b.triangleIDs.end()); sort(a.triangleIDs.begin(), a.triangleIDs.end()); @@ -142,24 +140,25 @@ void UVAtlas::packCharts(std::vector& charts, mvsUtils::MultiViewParams& alledges.clear(); // merge charts - for(auto& e : edges) + for (auto& e : edges) { - if(e.triangleIDs.size() != 2) + if (e.triangleIDs.size() != 2) continue; int chartIDA = findChart(e.triangleIDs[0]); int chartIDB = findChart(e.triangleIDs[1]); - if(chartIDA == chartIDB) + if (chartIDA == chartIDB) continue; Chart& a = charts[chartIDA]; Chart& b = charts[chartIDB]; std::vector cameraIntersection; - std::set_intersection( - a.commonCameraIDs.begin(), a.commonCameraIDs.end(), - b.commonCameraIDs.begin(), b.commonCameraIDs.end(), - std::back_inserter(cameraIntersection)); - if(cameraIntersection.empty()) // need at least 1 camera in common + std::set_intersection(a.commonCameraIDs.begin(), + a.commonCameraIDs.end(), + b.commonCameraIDs.begin(), + b.commonCameraIDs.end(), + std::back_inserter(cameraIntersection)); + if (cameraIntersection.empty()) // need at least 1 camera in common continue; - if(a.triangleIDs.size() > b.triangleIDs.size()) + if (a.triangleIDs.size() > b.triangleIDs.size()) { // merge b in a a.commonCameraIDs = cameraIntersection; @@ -177,24 +176,21 @@ void UVAtlas::packCharts(std::vector& charts, mvsUtils::MultiViewParams& edges.clear(); // remove merged charts - charts.erase(remove_if(charts.begin(), charts.end(), [](Chart& c) - { - return (c.mergedWith >= 0); - }), charts.end()); + charts.erase(remove_if(charts.begin(), charts.end(), [](Chart& c) { return (c.mergedWith >= 0); }), charts.end()); } void UVAtlas::finalizeCharts(std::vector& charts, mvsUtils::MultiViewParams& mp) { - ALICEVISION_LOG_INFO("Finalize packed charts (" << charts.size() << " charts)."); + ALICEVISION_LOG_INFO("Finalize packed charts (" << charts.size() << " charts)."); - #pragma omp parallel for - for(int i = 0; i < charts.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < charts.size(); ++i) { auto& chart = charts[i]; // select reference =am - if(chart.commonCameraIDs.empty()) - continue; // skip triangles without visibility information + if (chart.commonCameraIDs.empty()) + continue; // skip triangles without visibility information // filter triangles (make unique) sort(chart.triangleIDs.begin(), chart.triangleIDs.end()); @@ -202,13 +198,13 @@ void UVAtlas::finalizeCharts(std::vector& charts, mvsUtils::MultiViewPara chart.sourceLU = Pixel(0, 0); chart.sourceRD = Pixel(0, 0); - for(int camId: chart.commonCameraIDs) + for (int camId : chart.commonCameraIDs) { // store triangle projs and compute chart bounds (in refCamera space) Pixel sourceLU = Pixel(std::numeric_limits::max(), std::numeric_limits::max()); Pixel sourceRD = Pixel(std::numeric_limits::min(), std::numeric_limits::min()); - - for(auto it = chart.triangleIDs.begin(); it != chart.triangleIDs.end(); ++it) + + for (auto it = chart.triangleIDs.begin(); it != chart.triangleIDs.end(); ++it) { Mesh::triangle_proj tp = _mesh.getTriangleProjection(*it, mp, camId, mp.getWidth(camId), mp.getHeight(camId)); sourceLU.x = std::min(sourceLU.x, tp.lu.x); @@ -225,11 +221,12 @@ void UVAtlas::finalizeCharts(std::vector& charts, mvsUtils::MultiViewPara } const int largerSize = std::max(chart.sourceWidth(), chart.sourceHeight()); - if(largerSize > chartMaxSize()) + if (largerSize > chartMaxSize()) { chart.downscale = static_cast(chartMaxSize()) / static_cast(largerSize); - ALICEVISION_LOG_WARNING("Downscaling chart (by " + std::to_string(chart.downscale) + ") to fit in texture." - "Set higher texture size for better results."); + ALICEVISION_LOG_WARNING("Downscaling chart (by " + std::to_string(chart.downscale) + + ") to fit in texture." + "Set higher texture size for better results."); } } } @@ -239,21 +236,20 @@ void UVAtlas::createTextureAtlases(std::vector& charts, mvsUtils::MultiVi ALICEVISION_LOG_INFO("Creating texture atlases."); // sort charts by size, descending - std::sort(charts.begin(), charts.end(), [](const Chart& a, const Chart& b) - { + std::sort(charts.begin(), charts.end(), [](const Chart& a, const Chart& b) { int wa = a.targetWidth(); int wb = b.targetWidth(); - if(wa == wb) + if (wa == wb) return a.targetHeight() > b.targetHeight(); return wa > wb; }); - std::size_t i = 0; // forward index - std::size_t j = charts.size() - 1; // backward index + std::size_t i = 0; // forward index + std::size_t j = charts.size() - 1; // backward index std::size_t texCount = 0; // insert charts into one or more texture atlas - while(i <= j) + while (i <= j) { texCount++; // create a texture atlas @@ -266,11 +262,10 @@ void UVAtlas::createTextureAtlases(std::vector& charts, mvsUtils::MultiVi root->RD.x = _textureSide - 1; root->RD.y = _textureSide - 1; - const auto insertChart = [&](size_t idx) -> bool - { + const auto insertChart = [&](size_t idx) -> bool { Chart& chart = charts[idx]; ChartRect* rect = root->insert(chart, _gutterSize); - if(!rect) + if (!rect) return false; // store the final position @@ -282,11 +277,17 @@ void UVAtlas::createTextureAtlases(std::vector& charts, mvsUtils::MultiVi return true; }; // insert as many charts as possible in forward direction (largest to smallest) - while(i <= j && insertChart(i)) { ++i; } + while (i <= j && insertChart(i)) + { + ++i; + } // fill potential empty space (i != j) in backward direction - while(j > i && insertChart(j)) { --j; } + while (j > i && insertChart(j)) + { + --j; + } - if(atlas.empty()) + if (atlas.empty()) throw std::runtime_error("Unable to add any chart to this atlas"); // atlas is full or all charts have been handled @@ -301,9 +302,9 @@ void UVAtlas::createTextureAtlases(std::vector& charts, mvsUtils::MultiVi void UVAtlas::ChartRect::clear() { - if(child[0]) + if (child[0]) child[0]->clear(); - if(child[1]) + if (child[1]) child[1]->clear(); delete child[0]; delete child[1]; @@ -311,13 +312,13 @@ void UVAtlas::ChartRect::clear() UVAtlas::ChartRect* UVAtlas::ChartRect::insert(Chart& chart, size_t gutter) { - if(child[0] || child[1]) // not a leaf + if (child[0] || child[1]) // not a leaf { - if(child[0]) - if(ChartRect* rect = child[0]->insert(chart, gutter)) + if (child[0]) + if (ChartRect* rect = child[0]->insert(chart, gutter)) return rect; - if(child[1]) - if(ChartRect* rect = child[1]->insert(chart, gutter)) + if (child[1]) + if (ChartRect* rect = child[1]->insert(chart, gutter)) return rect; return nullptr; } @@ -326,16 +327,17 @@ UVAtlas::ChartRect* UVAtlas::ChartRect::insert(Chart& chart, size_t gutter) size_t chartWidth = chart.targetWidth() + gutter * 2; size_t chartHeight = chart.targetHeight() + gutter * 2; // if there is already a chart here - if(c) return nullptr; + if (c) + return nullptr; // not enough space - if(chartWidth > (RD.x - LU.x)) + if (chartWidth > (RD.x - LU.x)) return nullptr; - if(chartHeight > (RD.y - LU.y)) + if (chartHeight > (RD.y - LU.y)) return nullptr; // split & create children - if(chartWidth >= chartHeight) + if (chartWidth >= chartHeight) { - if(chartWidth < (RD.x - LU.x)) + if (chartWidth < (RD.x - LU.x)) { child[0] = new ChartRect(); child[0]->LU.x = LU.x + chartWidth; @@ -343,7 +345,7 @@ UVAtlas::ChartRect* UVAtlas::ChartRect::insert(Chart& chart, size_t gutter) child[0]->RD.x = RD.x; child[0]->RD.y = LU.y + chartHeight; } - if(chartHeight < (RD.y - LU.y)) + if (chartHeight < (RD.y - LU.y)) { child[1] = new ChartRect(); child[1]->LU.x = LU.x; @@ -352,9 +354,9 @@ UVAtlas::ChartRect* UVAtlas::ChartRect::insert(Chart& chart, size_t gutter) child[1]->RD.y = RD.y; } } - else + else { - if(chartHeight < (RD.y - LU.y)) + if (chartHeight < (RD.y - LU.y)) { child[0] = new ChartRect(); child[0]->LU.x = LU.x; @@ -362,7 +364,7 @@ UVAtlas::ChartRect* UVAtlas::ChartRect::insert(Chart& chart, size_t gutter) child[0]->RD.x = LU.x + chartWidth; child[0]->RD.y = RD.y; } - if(chartWidth < (RD.x - LU.x)) + if (chartWidth < (RD.x - LU.x)) { child[1] = new ChartRect(); child[1]->LU.x = LU.x + chartWidth; @@ -377,5 +379,5 @@ UVAtlas::ChartRect* UVAtlas::ChartRect::insert(Chart& chart, size_t gutter) } } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/UVAtlas.hpp b/src/aliceVision/mesh/UVAtlas.hpp index 34ebc39929..54a051cd46 100644 --- a/src/aliceVision/mesh/UVAtlas.hpp +++ b/src/aliceVision/mesh/UVAtlas.hpp @@ -16,7 +16,7 @@ namespace mesh { class UVAtlas { -public: + public: struct Edge { std::pair pointIDs; @@ -27,14 +27,14 @@ class UVAtlas struct Chart { - int refCameraID = -1; // refCamera, used to project all contained triangles - std::vector commonCameraIDs; // list of common cameras - std::vector triangleIDs; // list of all contained triangles - Pixel sourceLU; // left-up pixel coordinates (in refCamera space) - Pixel sourceRD; // right-down pixel coordinates (in refCamera space) - Pixel targetLU; // left-up pixel coordinates (in uvatlas texture) - float downscale = 1.0f; // downscale factor applied to this chart - int mergedWith = -1; // ID of target chart, or -1 (not merged) + int refCameraID = -1; // refCamera, used to project all contained triangles + std::vector commonCameraIDs; // list of common cameras + std::vector triangleIDs; // list of all contained triangles + Pixel sourceLU; // left-up pixel coordinates (in refCamera space) + Pixel sourceRD; // right-down pixel coordinates (in refCamera space) + Pixel targetLU; // left-up pixel coordinates (in uvatlas texture) + float downscale = 1.0f; // downscale factor applied to this chart + int mergedWith = -1; // ID of target chart, or -1 (not merged) /// Chart width in refCamera space int sourceWidth() const { return (sourceRD.x - sourceLU.x); } @@ -49,31 +49,30 @@ class UVAtlas struct ChartRect { Chart* c = nullptr; - ChartRect* child[2] {nullptr, nullptr}; + ChartRect* child[2]{nullptr, nullptr}; Pixel LU; Pixel RD; void clear(); ChartRect* insert(Chart& chart, size_t gutter); }; -public: - UVAtlas(const Mesh& mesh, mvsUtils::MultiViewParams& mp, - unsigned int textureSide, unsigned int gutterSize); + public: + UVAtlas(const Mesh& mesh, mvsUtils::MultiViewParams& mp, unsigned int textureSide, unsigned int gutterSize); -public: + public: const std::vector>& atlases() const { return _atlases; } const std::vector& visibleCameras(int triangleID) const { return _triangleCameraIDs[triangleID]; } int textureSide() const { return _textureSide; } const Mesh& mesh() const { return _mesh; } inline int chartMaxSize() const { return (_textureSide - 1) - _gutterSize * 2; } -private: + private: void createCharts(std::vector& charts, mvsUtils::MultiViewParams& mp); void packCharts(std::vector& charts, mvsUtils::MultiViewParams& mp); void finalizeCharts(std::vector& charts, mvsUtils::MultiViewParams& mp); void createTextureAtlases(std::vector& charts, mvsUtils::MultiViewParams& mp); -private: + private: std::vector> _atlases; std::vector> _triangleCameraIDs; int _textureSide; @@ -81,5 +80,5 @@ class UVAtlas const Mesh& _mesh; }; -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/geoMesh.hpp b/src/aliceVision/mesh/geoMesh.hpp index befa3aeb6e..dd86cd6153 100644 --- a/src/aliceVision/mesh/geoMesh.hpp +++ b/src/aliceVision/mesh/geoMesh.hpp @@ -4,17 +4,16 @@ #include - namespace aliceVision { namespace mesh { /** -* @brief Create a Geogram GEO::Mesh from an aliceVision::Mesh -* -* @note only initialize vertices and facets -* @param[in] the source aliceVision mesh -* @param[out] the destination GEO::Mesh -*/ + * @brief Create a Geogram GEO::Mesh from an aliceVision::Mesh + * + * @note only initialize vertices and facets + * @param[in] the source aliceVision mesh + * @param[out] the destination GEO::Mesh + */ inline void toGeoMesh(const Mesh& src, GEO::Mesh& dst) { GEO::vector vertices; @@ -41,5 +40,5 @@ inline void toGeoMesh(const Mesh& src, GEO::Mesh& dst) assert(src.tris.size() == dst.facets.nb()); } -} -} +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/meshPostProcessing.cpp b/src/aliceVision/mesh/meshPostProcessing.cpp index 6ec1a6e07a..6ee996f7bf 100644 --- a/src/aliceVision/mesh/meshPostProcessing.cpp +++ b/src/aliceVision/mesh/meshPostProcessing.cpp @@ -18,16 +18,19 @@ namespace mesh { namespace bfs = boost::filesystem; -void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inout_ptsCams, mvsUtils::MultiViewParams& mp, - const std::string& debugFolderName, - StaticVector* hexahsToExcludeFromResultingMesh, Point3d* hexah) +void meshPostProcessing(Mesh*& inout_mesh, + StaticVector>& inout_ptsCams, + mvsUtils::MultiViewParams& mp, + const std::string& debugFolderName, + StaticVector* hexahsToExcludeFromResultingMesh, + Point3d* hexah) { long timer = std::clock(); ALICEVISION_LOG_INFO("Mesh post-processing."); bool exportDebug = (float)mp.userParams.get("delaunaycut.exportDebugGC", false); - if(exportDebug) + if (exportDebug) inout_mesh->save(debugFolderName + "rawGraphCut"); // copy ptsCams @@ -36,9 +39,8 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou StaticVector ptIdToNewPtId; bool doRemoveTrianglesInhexahsToExcludeFromResultingMesh = - (bool)mp.userParams.get("LargeScale.doRemoveTrianglesInhexahsToExcludeFromResultingMesh", - false); - if(doRemoveTrianglesInhexahsToExcludeFromResultingMesh && hexahsToExcludeFromResultingMesh) + (bool)mp.userParams.get("LargeScale.doRemoveTrianglesInhexahsToExcludeFromResultingMesh", false); + if (doRemoveTrianglesInhexahsToExcludeFromResultingMesh && hexahsToExcludeFromResultingMesh) { inout_mesh->removeTrianglesInHexahedrons(hexahsToExcludeFromResultingMesh); } @@ -47,14 +49,14 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou // remap visibilities inout_ptsCams.resize(inout_mesh->pts.size()); - for(int i = 0; i < ptIdToNewPtId.size(); ++i) + for (int i = 0; i < ptIdToNewPtId.size(); ++i) { int newId = ptIdToNewPtId[i]; - if(newId > -1) + if (newId > -1) { StaticVector& ptCamsNew = inout_ptsCams[newId]; ptCamsNew.reserve(sizeOfStaticVector(ptsCamsOld[i])); - for(int j = 0; j < sizeOfStaticVector(ptsCamsOld[i]); ++j) + for (int j = 0; j < sizeOfStaticVector(ptsCamsOld[i]); ++j) { ptCamsNew.push_back(ptsCamsOld[i][j]); } @@ -62,7 +64,7 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou } } - if(true) // TODO: how to remove it? + if (true) // TODO: how to remove it? { ALICEVISION_LOG_INFO("Mesh Cleaning."); @@ -73,14 +75,14 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou meOpt.init(); meOpt.cleanMesh(10); - if(exportDebug) + if (exportDebug) meOpt.save(debugFolderName + "MeshClean"); ///////////////////////////// { // Update pointCams after clean inout_ptsCams.reserveAdd(meOpt.newPtsOldPtId.size()); - for(int i = 0; i < meOpt.newPtsOldPtId.size(); i++) + for (int i = 0; i < meOpt.newPtsOldPtId.size(); i++) { int oldPtId = meOpt.newPtsOldPtId[i]; inout_ptsCams.push_back(inout_ptsCams[oldPtId]); @@ -89,7 +91,7 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou ///////////////////////////// StaticVectorBool ptsCanMove; - if(hexah != nullptr) + if (hexah != nullptr) { Point3d O = hexah[0]; Point3d vx = hexah[1] - hexah[0]; @@ -105,29 +107,27 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou ptsCanMove.reserve(meOpt.pts.size()); ptsCanMove.resize_with(meOpt.pts.size(), true); - for(int i = 0; i < meOpt.pts.size(); i++) + for (int i = 0; i < meOpt.pts.size(); i++) { float x = pointPlaneDistance(meOpt.pts[i], O, vx); float y = pointPlaneDistance(meOpt.pts[i], O, vy); float z = pointPlaneDistance(meOpt.pts[i], O, vz); - bool isHexahBorderPt = ((x < avel) || (x > svx - avel) || (y < avel) || (y > svy - avel) || - (z < avel) || (z > svz - avel)); + bool isHexahBorderPt = ((x < avel) || (x > svx - avel) || (y < avel) || (y > svy - avel) || (z < avel) || (z > svz - avel)); ptsCanMove[i] = ((isHexahBorderPt == false) || (sizeOfStaticVector(inout_ptsCams[i]) > 0)); //(*ptsCanMove)[i] = (isHexahBorderPt==false); } } ///////////////////////////// - bool doSmoothMesh = - mp.userParams.get("meshEnergyOpt.doSmoothMesh", true); + bool doSmoothMesh = mp.userParams.get("meshEnergyOpt.doSmoothMesh", true); int smoothNIter = mp.userParams.get("meshEnergyOpt.smoothNbIterations", 0); - if(doSmoothMesh && smoothNIter != 0) + if (doSmoothMesh && smoothNIter != 0) { ALICEVISION_LOG_INFO("Mesh smoothing."); float lambda = (float)mp.userParams.get("meshEnergyOpt.lambda", 1.0f); meOpt.optimizeSmooth(lambda, smoothNIter, ptsCanMove); - if(exportDebug) + if (exportDebug) meOpt.save(debugFolderName + "mesh_smoothed"); } @@ -140,6 +140,5 @@ void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inou ALICEVISION_LOG_INFO("Mesh post-processing done."); } - -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/meshPostProcessing.hpp b/src/aliceVision/mesh/meshPostProcessing.hpp index 1f5d4876c5..dee41d9283 100644 --- a/src/aliceVision/mesh/meshPostProcessing.hpp +++ b/src/aliceVision/mesh/meshPostProcessing.hpp @@ -16,12 +16,14 @@ class Point3d; namespace mesh { - void filterLargeEdgeTriangles(Mesh* me, float avelthr); -void meshPostProcessing(Mesh*& inout_mesh, StaticVector>& inout_ptsCams, mvsUtils::MultiViewParams& mp, - const std::string& debugFolderName, - StaticVector* hexahsToExcludeFromResultingMesh, Point3d* hexah); +void meshPostProcessing(Mesh*& inout_mesh, + StaticVector>& inout_ptsCams, + mvsUtils::MultiViewParams& mp, + const std::string& debugFolderName, + StaticVector* hexahsToExcludeFromResultingMesh, + Point3d* hexah); -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/meshVisibility.cpp b/src/aliceVision/mesh/meshVisibility.cpp index 28c829988e..c0f22ae348 100644 --- a/src/aliceVision/mesh/meshVisibility.cpp +++ b/src/aliceVision/mesh/meshVisibility.cpp @@ -16,7 +16,6 @@ #include #include - namespace aliceVision { namespace mesh { @@ -28,15 +27,14 @@ void getNearestVertices(const Mesh& refMesh, const Mesh& mesh, StaticVector GEO::AdaptiveKdTree refMesh_kdTree(3); refMesh_kdTree.set_points(refMesh.pts.size(), refMesh.pts.front().m); - #pragma omp parallel for - for(int i = 0; i < mesh.pts.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < mesh.pts.size(); ++i) { out_nearestVertex[i] = refMesh_kdTree.get_nearest_neighbor(mesh.pts[i].m); } ALICEVISION_LOG_DEBUG("getNearestVertices done."); } - void remapMeshVisibilities_pullVerticesVisibility(const Mesh& refMesh, Mesh& mesh) { ALICEVISION_LOG_DEBUG("remapMeshVisibility based on closest vertex start."); @@ -49,16 +47,16 @@ void remapMeshVisibilities_pullVerticesVisibility(const Mesh& refMesh, Mesh& mes out_ptsVisibilities.resize(mesh.pts.size()); - #pragma omp parallel for - for(int i = 0; i < mesh.pts.size(); ++i) +#pragma omp parallel for + for (int i = 0; i < mesh.pts.size(); ++i) { PointVisibility& pOut = out_ptsVisibilities[i]; int iRef = refMesh_kdTree.get_nearest_neighbor(mesh.pts[i].m); - if(iRef == -1) + if (iRef == -1) continue; const PointVisibility& pRef = refPtsVisibilities[iRef]; - if(pRef.empty()) + if (pRef.empty()) continue; pOut = pRef; @@ -67,8 +65,7 @@ void remapMeshVisibilities_pullVerticesVisibility(const Mesh& refMesh, Mesh& mes ALICEVISION_LOG_DEBUG("remapMeshVisibility done."); } - -double mesh_facet_edges_length(const GEO::Mesh &M, GEO::index_t f) +double mesh_facet_edges_length(const GEO::Mesh& M, GEO::index_t f) { const GEO::vec3& p0 = M.vertices.point(M.facets.vertex(f, 0)); const GEO::vec3& p1 = M.vertices.point(M.facets.vertex(f, 1)); @@ -89,7 +86,7 @@ void remapMeshVisibilities_pushVerticesVisibilityToTriangles(const Mesh& refMesh // MeshFacetAABB will reorder the mesh, so we need to keep indices GEO::Attribute reorderedVerticesAttr(meshG.vertices.attributes(), "reorder"); - for(int i = 0; i < meshG.vertices.nb(); ++i) + for (int i = 0; i < meshG.vertices.nb(); ++i) reorderedVerticesAttr[i] = i; GEO::MeshFacetsAABB meshAABB(meshG); // warning: mesh_reorder called inside @@ -101,7 +98,7 @@ void remapMeshVisibilities_pushVerticesVisibilityToTriangles(const Mesh& refMesh out_ptsVisibilities.resize(mesh.pts.size()); } - #pragma omp parallel for +#pragma omp parallel for for (int rvi = 0; rvi < refMesh.pts.size(); ++rvi) { const PointVisibility& rpVis = refPtsVisibilities[rvi]; @@ -112,16 +109,16 @@ void remapMeshVisibilities_pushVerticesVisibilityToTriangles(const Mesh& refMesh GEO::vec3 nearestPoint; double dist2 = 0.0; GEO::index_t f = meshAABB.nearest_facet(rp, nearestPoint, dist2); - if(f == GEO::NO_FACET) + if (f == GEO::NO_FACET) continue; double avgEdgeLength = mesh_facet_edges_length(meshG, f) / 3.0; // if average edge length is larger than the distance between the output mesh // and the closest point in the reference mesh. - if(std::sqrt(dist2) > avgEdgeLength) + if (std::sqrt(dist2) > avgEdgeLength) continue; - #pragma omp critical +#pragma omp critical { for (int i = 0; i < 3; ++i) { @@ -129,8 +126,8 @@ void remapMeshVisibilities_pushVerticesVisibilityToTriangles(const Mesh& refMesh if (v == GEO::NO_VERTEX) continue; PointVisibility& pOut = out_ptsVisibilities[reorderedVertices[v]]; - - for(int j = 0; j < rpVis.size(); ++j) + + for (int j = 0; j < rpVis.size(); ++j) pOut.push_back_distinct(rpVis[j]); } } @@ -151,14 +148,14 @@ void remapMeshVisibilities_meshItself(const mvsUtils::MultiViewParams& mp, Mesh& // MeshFacetAABB will reorder the mesh, so we need to keep indices GEO::Attribute reorderedVerticesAttr(meshG.vertices.attributes(), "reorder"); - for(int i = 0; i < meshG.vertices.nb(); ++i) + for (int i = 0; i < meshG.vertices.nb(); ++i) reorderedVerticesAttr[i] = i; - GEO::MeshFacetsAABB meshAABB(meshG); // warning: mesh_reorder called inside + GEO::MeshFacetsAABB meshAABB(meshG); // warning: mesh_reorder called inside GEO::vector reorderedVertices = reorderedVerticesAttr.get_vector(); - if(out_ptsVisibilities.size() != mesh.pts.size()) + if (out_ptsVisibilities.size() != mesh.pts.size()) { out_ptsVisibilities.resize(mesh.pts.size()); } @@ -168,20 +165,20 @@ void remapMeshVisibilities_meshItself(const mvsUtils::MultiViewParams& mp, Mesh& mesh.computeNormalsForPts(normalsPerVertex); #pragma omp parallel for - for(int vi = 0; vi < mesh.pts.size(); ++vi) + for (int vi = 0; vi < mesh.pts.size(); ++vi) { const Point3d& v = mesh.pts[vi]; PointVisibility& vertexVisibility = out_ptsVisibilities[vi]; const Point3d& n = normalsPerVertex[vi]; // Check by which camera the vertex is visible - for(std::size_t camIndex = 0; camIndex < nbCameras; ++camIndex) + for (std::size_t camIndex = 0; camIndex < nbCameras; ++camIndex) { const Point3d& c = mp.CArr[camIndex]; // check vertex normal (another solution would be to check each neighboring triangle) const double angle = angleBetwV1andV2((c - v).normalize(), normalsPerVertex[vi]); - if(angle > 90.0) + if (angle > 90.0) continue; const GEO::vec3 gv(v.x, v.y, v.z); @@ -189,7 +186,7 @@ void remapMeshVisibilities_meshItself(const mvsUtils::MultiViewParams& mp, Mesh& const GEO::vec3 vc = gc - gv; // check if there is an occlusion on the segment between the current mesh vertex and the camera const bool occlusion = meshAABB.ray_intersection(GEO::Ray(gv + (vc * 0.00001), vc), 1.0); - if(occlusion) + if (occlusion) continue; vertexVisibility.push_back(camIndex); @@ -197,5 +194,5 @@ void remapMeshVisibilities_meshItself(const mvsUtils::MultiViewParams& mp, Mesh& } } -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/mesh/meshVisibility.hpp b/src/aliceVision/mesh/meshVisibility.hpp index 1050066c06..7301f10e02 100644 --- a/src/aliceVision/mesh/meshVisibility.hpp +++ b/src/aliceVision/mesh/meshVisibility.hpp @@ -29,19 +29,20 @@ void getNearestVertices(const Mesh& refMesh, const Mesh& mesh, StaticVector * @param[in] refMesh input reference mesh * @param[in] mesh input target mesh */ -void remapMeshVisibilities_pullVerticesVisibility(const Mesh& refMesh, Mesh &mesh); +void remapMeshVisibilities_pullVerticesVisibility(const Mesh& refMesh, Mesh& mesh); /** -* @brief Transfer the visibility per vertex from one mesh to another. -* For each vertex of the @p refMesh, we search the closest triangle in the @p mesh and copy its visibility information to each vertex of the triangle. -* @note The visibility information is a list of camera IDs seeing the vertex. -* -* @param[in] refMesh input reference mesh -* @param[in] mesh input target mesh -*/ + * @brief Transfer the visibility per vertex from one mesh to another. + * For each vertex of the @p refMesh, we search the closest triangle in the @p mesh and copy its visibility information to each vertex of the + * triangle. + * @note The visibility information is a list of camera IDs seeing the vertex. + * + * @param[in] refMesh input reference mesh + * @param[in] mesh input target mesh + */ void remapMeshVisibilities_pushVerticesVisibilityToTriangles(const Mesh& refMesh, Mesh& mesh); void remapMeshVisibilities_meshItself(const mvsUtils::MultiViewParams& mp, Mesh& mesh); -} // namespace mesh -} // namespace aliceVision +} // namespace mesh +} // namespace aliceVision diff --git a/src/aliceVision/multiview/NViewDataSet.cpp b/src/aliceVision/multiview/NViewDataSet.cpp index 7218243b8c..43ef5b4530 100644 --- a/src/aliceVision/multiview/NViewDataSet.cpp +++ b/src/aliceVision/multiview/NViewDataSet.cpp @@ -15,155 +15,155 @@ namespace aliceVision { - -NViewDatasetConfigurator::NViewDatasetConfigurator(int fx, int fy, - int cx, int cy, double distance, double jitter_amount): - _fx(fx), _fy(fy), _cx(cx), _cy(cy), _dist(distance), - _jitter_amount(jitter_amount) +NViewDatasetConfigurator::NViewDatasetConfigurator(int fx, int fy, int cx, int cy, double distance, double jitter_amount) + : _fx(fx), + _fy(fy), + _cx(cx), + _cy(cy), + _dist(distance), + _jitter_amount(jitter_amount) {} -NViewDataSet NRealisticCamerasRing(size_t nviews, size_t npoints, - const NViewDatasetConfigurator& config) +NViewDataSet NRealisticCamerasRing(size_t nviews, size_t npoints, const NViewDatasetConfigurator& config) { - //-- Setup a camera circle rig. - NViewDataSet d; - d._n = nviews; - d._K.resize(nviews); - d._R.resize(nviews); - d._t.resize(nviews); - d._C.resize(nviews); - d._x.resize(nviews); - d._x_ids.resize(nviews); - - d._X.resize(3, npoints); - d._X.setRandom(); - d._X *= 0.45; - - Vecu all_point_ids(npoints); - for (size_t j = 0; j < npoints; ++j) - all_point_ids[j] = j; - - for (size_t i = 0; i < nviews; ++i) { - Vec3 camera_center, t, jitter, lookdir; - - const double theta = i * 2 * M_PI / nviews; - //-- Circle equation - camera_center << sin(theta), 0.0, cos(theta); // Y axis UP - camera_center *= config._dist; - d._C[i] = camera_center; - - jitter.setRandom(); - jitter *= config._jitter_amount / camera_center.norm(); - lookdir = -camera_center + jitter; - - d._K[i] << config._fx, 0, config._cx, - 0, config._fy, config._cy, - 0, 0, 1; - d._R[i] = LookAt(lookdir); // Y axis UP - d._t[i] = -d._R[i] * camera_center; // [t]=[-RC] Cf HZ. - d._x[i] = project(d.P(i), d._X); - d._x_ids[i] = all_point_ids; - } - return d; + //-- Setup a camera circle rig. + NViewDataSet d; + d._n = nviews; + d._K.resize(nviews); + d._R.resize(nviews); + d._t.resize(nviews); + d._C.resize(nviews); + d._x.resize(nviews); + d._x_ids.resize(nviews); + + d._X.resize(3, npoints); + d._X.setRandom(); + d._X *= 0.45; + + Vecu all_point_ids(npoints); + for (size_t j = 0; j < npoints; ++j) + all_point_ids[j] = j; + + for (size_t i = 0; i < nviews; ++i) + { + Vec3 camera_center, t, jitter, lookdir; + + const double theta = i * 2 * M_PI / nviews; + //-- Circle equation + camera_center << sin(theta), 0.0, cos(theta); // Y axis UP + camera_center *= config._dist; + d._C[i] = camera_center; + + jitter.setRandom(); + jitter *= config._jitter_amount / camera_center.norm(); + lookdir = -camera_center + jitter; + + d._K[i] << config._fx, 0, config._cx, 0, config._fy, config._cy, 0, 0, 1; + d._R[i] = LookAt(lookdir); // Y axis UP + d._t[i] = -d._R[i] * camera_center; // [t]=[-RC] Cf HZ. + d._x[i] = project(d.P(i), d._X); + d._x_ids[i] = all_point_ids; + } + return d; } -Mat34 NViewDataSet::P(size_t i)const { - assert(i < _n); - Mat34 P; - P_from_KRt(_K[i], _R[i], _t[i], P); - return P; +Mat34 NViewDataSet::P(size_t i) const +{ + assert(i < _n); + Mat34 P; + P_from_KRt(_K[i], _R[i], _t[i], P); + return P; } -void NViewDataSet::exportToPLY( - const std::string & out_file_name)const { - std::ofstream outfile; - outfile.open(out_file_name, std::ios_base::out); - if (outfile.is_open()) { - outfile << "ply" - << std::endl << "format ascii 1.0" - << std::endl << "comment NViewDataSet export" - << std::endl << "comment It shows 3D point structure and cameras" - << "+ camera looking direction" - << std::endl << "element vertex " << _X.cols() + _t.size()*2 - << std::endl << "property float x" - << std::endl << "property float y" - << std::endl << "property float z" - << std::endl << "property uchar red" - << std::endl << "property uchar green" - << std::endl << "property uchar blue" - << std::endl << "end_header" << std::endl; - - //-- Export 3D point cloud - for(Mat3X::Index i = 0; i < _X.cols(); ++i) { - // Exports the point position and point color - outfile << _X.col(i).transpose() - << " " << "255 255 255" << std::endl; - } - - //-- Export 3D camera position t = -RC - for(size_t i = 0; i < _t.size(); ++i) { - // Exports the camera position and camera color - outfile << (-_R[i].transpose()*_t[i]).transpose() - << " " << "0 255 0" << std::endl; - } - for(size_t i = 0; i < _t.size(); ++i) { - Vec3 test; - test << 0, 0 , 0.4; - // Exports the camera normal - outfile << ((-_R[i].transpose()*_t[i])+ - (_R[i].transpose()*test)).transpose() - << " " << "255 0 0" << std::endl; +void NViewDataSet::exportToPLY(const std::string& out_file_name) const +{ + std::ofstream outfile; + outfile.open(out_file_name, std::ios_base::out); + if (outfile.is_open()) + { + outfile << "ply" << std::endl + << "format ascii 1.0" << std::endl + << "comment NViewDataSet export" << std::endl + << "comment It shows 3D point structure and cameras" + << "+ camera looking direction" << std::endl + << "element vertex " << _X.cols() + _t.size() * 2 << std::endl + << "property float x" << std::endl + << "property float y" << std::endl + << "property float z" << std::endl + << "property uchar red" << std::endl + << "property uchar green" << std::endl + << "property uchar blue" << std::endl + << "end_header" << std::endl; + + //-- Export 3D point cloud + for (Mat3X::Index i = 0; i < _X.cols(); ++i) + { + // Exports the point position and point color + outfile << _X.col(i).transpose() << " " + << "255 255 255" << std::endl; + } + + //-- Export 3D camera position t = -RC + for (size_t i = 0; i < _t.size(); ++i) + { + // Exports the camera position and camera color + outfile << (-_R[i].transpose() * _t[i]).transpose() << " " + << "0 255 0" << std::endl; + } + for (size_t i = 0; i < _t.size(); ++i) + { + Vec3 test; + test << 0, 0, 0.4; + // Exports the camera normal + outfile << ((-_R[i].transpose() * _t[i]) + (_R[i].transpose() * test)).transpose() << " " + << "255 0 0" << std::endl; + } + outfile.close(); } - outfile.close(); - } } -NViewDataSet NRealisticCamerasCardioid(size_t nviews, size_t npoints, - const NViewDatasetConfigurator& config) +NViewDataSet NRealisticCamerasCardioid(size_t nviews, size_t npoints, const NViewDatasetConfigurator& config) { - //-- Setup a camera circle rig. - NViewDataSet d; - d._n = nviews; - d._K.resize(nviews); - d._R.resize(nviews); - d._t.resize(nviews); - d._C.resize(nviews); - d._x.resize(nviews); - d._x_ids.resize(nviews); - - d._X.resize(3, npoints); - d._X.setRandom(); - d._X *= 0.6; - - Vecu all_point_ids(npoints); - for (size_t j = 0; j < npoints; ++j) - all_point_ids[j] = j; - - for (size_t i = 0; i < nviews; ++i) { - Vec3 camera_center, t, jitter, lookdir; - - const double theta = i * 2 * M_PI / nviews; - //-- Cardioid - camera_center << - 2*sin(theta)-(sin(2*theta)), - 0.0, - 2*cos(theta)-(cos(2*theta)); // Y axis UP - camera_center *= config._dist; - d._C[i] = camera_center; - - jitter.setRandom(); - jitter *= config._jitter_amount / camera_center.norm(); - lookdir = -camera_center + jitter; - - d._K[i] << config._fx, 0, config._cx, - 0, config._fy, config._cy, - 0, 0, 1; - d._R[i] = LookAt(lookdir); // Y axis UP - d._t[i] = -d._R[i] * camera_center; // [t]=[-RC] Cf HZ. - d._x[i] = project(d.P(i), d._X); - d._x_ids[i] = all_point_ids; - } - return d; + //-- Setup a camera circle rig. + NViewDataSet d; + d._n = nviews; + d._K.resize(nviews); + d._R.resize(nviews); + d._t.resize(nviews); + d._C.resize(nviews); + d._x.resize(nviews); + d._x_ids.resize(nviews); + + d._X.resize(3, npoints); + d._X.setRandom(); + d._X *= 0.6; + + Vecu all_point_ids(npoints); + for (size_t j = 0; j < npoints; ++j) + all_point_ids[j] = j; + + for (size_t i = 0; i < nviews; ++i) + { + Vec3 camera_center, t, jitter, lookdir; + + const double theta = i * 2 * M_PI / nviews; + //-- Cardioid + camera_center << 2 * sin(theta) - (sin(2 * theta)), 0.0, + 2 * cos(theta) - (cos(2 * theta)); // Y axis UP + camera_center *= config._dist; + d._C[i] = camera_center; + + jitter.setRandom(); + jitter *= config._jitter_amount / camera_center.norm(); + lookdir = -camera_center + jitter; + + d._K[i] << config._fx, 0, config._cx, 0, config._fy, config._cy, 0, 0, 1; + d._R[i] = LookAt(lookdir); // Y axis UP + d._t[i] = -d._R[i] * camera_center; // [t]=[-RC] Cf HZ. + d._x[i] = project(d.P(i), d._X); + d._x_ids[i] = all_point_ids; + } + return d; } } // namespace aliceVision diff --git a/src/aliceVision/multiview/NViewDataSet.hpp b/src/aliceVision/multiview/NViewDataSet.hpp index 9137284127..ebd65d4610 100644 --- a/src/aliceVision/multiview/NViewDataSet.hpp +++ b/src/aliceVision/multiview/NViewDataSet.hpp @@ -20,50 +20,47 @@ namespace aliceVision { */ struct NViewDataSet { - /// Internal parameters (fx, fy, etc). - std::vector _K; - /// Rotation. - std::vector _R; - /// Translation. - std::vector _t; - /// Camera centers. - std::vector _C; - /// 3D points. - Mat3X _X; - /// Projected points; may have noise added. - std::vector _x; - /// Indexes of points corresponding to the projections - std::vector _x_ids; - /// Actual number of cameras. - std::size_t _n; + /// Internal parameters (fx, fy, etc). + std::vector _K; + /// Rotation. + std::vector _R; + /// Translation. + std::vector _t; + /// Camera centers. + std::vector _C; + /// 3D points. + Mat3X _X; + /// Projected points; may have noise added. + std::vector _x; + /// Indexes of points corresponding to the projections + std::vector _x_ids; + /// Actual number of cameras. + std::size_t _n; - /** - * @brief Return P=K*[R|t] for the Inth camera - * @param[in] i The Inth camera - * @return P=K*[R|t] for the Inth camera - */ - Mat34 P(size_t i) const; + /** + * @brief Return P=K*[R|t] for the Inth camera + * @param[in] i The Inth camera + * @return P=K*[R|t] for the Inth camera + */ + Mat34 P(size_t i) const; - /** - * @brief Export in PLY the point structure and camera and camera looking dir. - * @param[in] outFilename - */ - void exportToPLY(const std::string& outFilename) const; + /** + * @brief Export in PLY the point structure and camera and camera looking dir. + * @param[in] outFilename + */ + void exportToPLY(const std::string& outFilename) const; }; struct NViewDatasetConfigurator { - /// Internal camera parameters (focal, principal point) - int _fx, _fy, _cx, _cy; + /// Internal camera parameters (focal, principal point) + int _fx, _fy, _cx, _cy; - /// Camera random position parameters - double _dist; - double _jitter_amount; + /// Camera random position parameters + double _dist; + double _jitter_amount; - NViewDatasetConfigurator(int fx = 1000, int fy = 1000, - int cx = 500, int cy = 500, - double distance = 1.5, - double jitter_amount = 0.01 ); + NViewDatasetConfigurator(int fx = 1000, int fy = 1000, int cx = 500, int cy = 500, double distance = 1.5, double jitter_amount = 0.01); }; /** @@ -76,4 +73,4 @@ NViewDataSet NRealisticCamerasRing(std::size_t nviews, std::size_t npoints, cons */ NViewDataSet NRealisticCamerasCardioid(std::size_t nviews, std::size_t npoints, const NViewDatasetConfigurator& config = NViewDatasetConfigurator()); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/multiview/RelativePoseKernel.hpp b/src/aliceVision/multiview/RelativePoseKernel.hpp index 549734b92f..cf06bd5647 100644 --- a/src/aliceVision/multiview/RelativePoseKernel.hpp +++ b/src/aliceVision/multiview/RelativePoseKernel.hpp @@ -27,71 +27,72 @@ namespace multiview { * @note This kernel adapter is working for affine, homography, fundamental matrix * estimation. */ -template> -class RelativePoseKernel - : public robustEstimation::PointFittingRansacKernel +template> +class RelativePoseKernel : public robustEstimation::PointFittingRansacKernel { -public: - - using PFRansacKernel = robustEstimation::PointFittingRansacKernel; - - RelativePoseKernel(const Mat& x1, int w1, int h1, - const Mat& x2, int w2, int h2, - bool pointToLine = true) - : _x1n(x1.rows(), x1.cols()) - , _x2n(x2.rows(), x2.cols()) - , PFRansacKernel(_x1n, _x2n) // provide a reference to the internal var members - , _logalpha0(0.0) - , _N1(3, 3) - , _N2(3, 3) - , _pointToLine(pointToLine) - { - ALICEVISION_LOG_TRACE("RelativePoseKernel: x1: " << x1.rows() << "x" << x1.cols() << ", x2: " << x2.rows() << "x" << x2.cols()); - assert(2 == x1.rows()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - robustEstimation::normalizePointsFromImageSize(x1, &_x1n, &_N1, w1, h1); - robustEstimation::normalizePointsFromImageSize(x2, &_x2n, &_N2, w2, h2); - - // logAlpha0 is used to make error data scale invariant - if(pointToLine) + public: + using PFRansacKernel = robustEstimation::PointFittingRansacKernel; + + RelativePoseKernel(const Mat& x1, int w1, int h1, const Mat& x2, int w2, int h2, bool pointToLine = true) + : _x1n(x1.rows(), x1.cols()), + _x2n(x2.rows(), x2.cols()), + PFRansacKernel(_x1n, _x2n) // provide a reference to the internal var members + , + _logalpha0(0.0), + _N1(3, 3), + _N2(3, 3), + _pointToLine(pointToLine) { - // Ratio of containing diagonal image rectangle over image area - const double D = sqrt(w2 * (double) w2 + h2 * (double) h2); // diameter - const double A = w2 * (double) h2; // area - _logalpha0 = log10(2.0 * D / A / _N2(0, 0)); + ALICEVISION_LOG_TRACE("RelativePoseKernel: x1: " << x1.rows() << "x" << x1.cols() << ", x2: " << x2.rows() << "x" << x2.cols()); + assert(2 == x1.rows()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + robustEstimation::normalizePointsFromImageSize(x1, &_x1n, &_N1, w1, h1); + robustEstimation::normalizePointsFromImageSize(x2, &_x2n, &_N2, w2, h2); + + // logAlpha0 is used to make error data scale invariant + if (pointToLine) + { + // Ratio of containing diagonal image rectangle over image area + const double D = sqrt(w2 * (double)w2 + h2 * (double)h2); // diameter + const double A = w2 * (double)h2; // area + _logalpha0 = log10(2.0 * D / A / _N2(0, 0)); + } + else + { + // ratio of area : unit circle over image area + _logalpha0 = log10(M_PI / (w2 * (double)h2) / (_N2(0, 0) * _N2(0, 0))); + } } - else + + void unnormalize(ModelT_& model) const override { - // ratio of area : unit circle over image area - _logalpha0 = log10(M_PI / (w2 * (double) h2) / (_N2(0, 0) * _N2(0, 0))); + // Unnormalize model from the computed conditioning. + Mat3 H = model.getMatrix(); + UnnormalizerT_::unnormalize(_N1, _N2, &H); + model.setMatrix(H); } - } - - void unnormalize(ModelT_& model) const override - { - // Unnormalize model from the computed conditioning. - Mat3 H = model.getMatrix(); - UnnormalizerT_::unnormalize(_N1, _N2, &H); - model.setMatrix(H); - } - - double logalpha0() const override {return _logalpha0; } - double errorVectorDimension() const override {return (_pointToLine)? 1.0 : 2.0;} - Mat3 normalizer1() const override {return _N1;} - double thresholdNormalizer() const override {return _N2(0, 0);} - double unormalizeError(double val) const override {return sqrt(val) / _N2(0,0);} - -protected: - /// Normalized input data - Mat _x1n, _x2n; - /// Matrix used to normalize data - Mat3 _N1, _N2; - /// Alpha0 is used to make the error adaptive to the image size - double _logalpha0; - /// Store if error model is pointToLine or point to point - bool _pointToLine; + + double logalpha0() const override { return _logalpha0; } + double errorVectorDimension() const override { return (_pointToLine) ? 1.0 : 2.0; } + Mat3 normalizer1() const override { return _N1; } + double thresholdNormalizer() const override { return _N2(0, 0); } + double unormalizeError(double val) const override { return sqrt(val) / _N2(0, 0); } + + protected: + /// Normalized input data + Mat _x1n, _x2n; + /// Matrix used to normalize data + Mat3 _N1, _N2; + /// Alpha0 is used to make the error adaptive to the image size + double _logalpha0; + /// Store if error model is pointToLine or point to point + bool _pointToLine; }; /** @@ -106,76 +107,74 @@ class RelativePoseKernel * @note This kernel adapter is working only for essential matrix * estimation. */ -template> -class RelativePoseKernel_K - : public robustEstimation::PointFittingRansacKernel +template> +class RelativePoseKernel_K : public robustEstimation::PointFittingRansacKernel { -public: - - using PFRansacKernel = robustEstimation::PointFittingRansacKernel; - - RelativePoseKernel_K(const Mat& x1, int w1, int h1, - const Mat& x2, int w2, int h2, - const Mat3& K1, const Mat3& K2) - : PFRansacKernel(x1, x2) - , _N1(Mat3::Identity()) - , _N2(Mat3::Identity()) - , _logalpha0(0.0) - , _K1(K1) - , _K2(K2) - { - ALICEVISION_LOG_TRACE("RelativePoseKernel_K: x1: " << x1.rows() << "x" << x1.cols() << ", x2: " << x2.rows() << "x" << x2.cols()); - assert(2 == x1.rows()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - robustEstimation::applyTransformationToPoints(x1, _K1.inverse(), &_x1k); - robustEstimation::applyTransformationToPoints(x2, _K2.inverse(), &_x2k); - - // point to line probability (line is the epipolar line) - const double D = sqrt(w2 * static_cast(w2) + h2 * static_cast(h2)); // diameter - const double A = w2 * static_cast(h2); // area - _logalpha0 = log10(2.0 * D / A * .5); - } - - void fit(const std::vector& samples, std::vector& models) const override - { - const Mat x1 = buildSubsetMatrix(_x1k, samples); - const Mat x2 = buildSubsetMatrix(_x2k, samples); - - PFRansacKernel::PFKernel::_kernelSolver.solve(x1, x2, models); - } - - double error(std::size_t sample, const ModelT_& model) const override - { - Mat3 F; - fundamentalFromEssential(model.getMatrix(), _K1, _K2, &F); - const ModelT_ modelF(F); - return _errorEstimator.error(modelF, PFRansacKernel::PFKernel::_x1.col(sample), PFRansacKernel::PFKernel::_x2.col(sample)); - } - - void unnormalize(ModelT_& model) const override - { - // do nothing, no normalization in this case - } - - double logalpha0() const override {return _logalpha0; } - double errorVectorDimension() const override {return 0.5;} // point to line error - Mat3 normalizer1() const override {return _N1;} - double thresholdNormalizer() const override {return _N2(0, 0);} - double unormalizeError(double val) const override { return sqrt(val); } - -private: - - Mat _x1k, _x2k; - /// Matrix used to normalize data - Mat3 _N1, _N2; - /// Alpha0 is used to make the error adaptive to the image size - double _logalpha0; - /// Intrinsics camera parameter - Mat3 _K1, _K2; - /// solver error estimation - const ErrorT_ _errorEstimator; + public: + using PFRansacKernel = robustEstimation::PointFittingRansacKernel; + + RelativePoseKernel_K(const Mat& x1, int w1, int h1, const Mat& x2, int w2, int h2, const Mat3& K1, const Mat3& K2) + : PFRansacKernel(x1, x2), + _N1(Mat3::Identity()), + _N2(Mat3::Identity()), + _logalpha0(0.0), + _K1(K1), + _K2(K2) + { + ALICEVISION_LOG_TRACE("RelativePoseKernel_K: x1: " << x1.rows() << "x" << x1.cols() << ", x2: " << x2.rows() << "x" << x2.cols()); + assert(2 == x1.rows()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + robustEstimation::applyTransformationToPoints(x1, _K1.inverse(), &_x1k); + robustEstimation::applyTransformationToPoints(x2, _K2.inverse(), &_x2k); + + // point to line probability (line is the epipolar line) + const double D = sqrt(w2 * static_cast(w2) + h2 * static_cast(h2)); // diameter + const double A = w2 * static_cast(h2); // area + _logalpha0 = log10(2.0 * D / A * .5); + } + + void fit(const std::vector& samples, std::vector& models) const override + { + const Mat x1 = buildSubsetMatrix(_x1k, samples); + const Mat x2 = buildSubsetMatrix(_x2k, samples); + + PFRansacKernel::PFKernel::_kernelSolver.solve(x1, x2, models); + } + + double error(std::size_t sample, const ModelT_& model) const override + { + Mat3 F; + fundamentalFromEssential(model.getMatrix(), _K1, _K2, &F); + const ModelT_ modelF(F); + return _errorEstimator.error(modelF, PFRansacKernel::PFKernel::_x1.col(sample), PFRansacKernel::PFKernel::_x2.col(sample)); + } + + void unnormalize(ModelT_& model) const override + { + // do nothing, no normalization in this case + } + + double logalpha0() const override { return _logalpha0; } + double errorVectorDimension() const override { return 0.5; } // point to line error + Mat3 normalizer1() const override { return _N1; } + double thresholdNormalizer() const override { return _N2(0, 0); } + double unormalizeError(double val) const override { return sqrt(val); } + + private: + Mat _x1k, _x2k; + /// Matrix used to normalize data + Mat3 _N1, _N2; + /// Alpha0 is used to make the error adaptive to the image size + double _logalpha0; + /// Intrinsics camera parameter + Mat3 _K1, _K2; + /// solver error estimation + const ErrorT_ _errorEstimator; }; /** @@ -184,16 +183,19 @@ class RelativePoseKernel_K * that depends of the error model (point to line, or point to point) * This kernel adapter is working for affine, homography, fundamental matrix estimation. */ -template > -class RelativePoseSphericalKernel: public robustEstimation::PointFittingRansacKernel +template> +class RelativePoseSphericalKernel : public robustEstimation::PointFittingRansacKernel { -public: + public: using PFRansacKernel = robustEstimation::PointFittingRansacKernel; RelativePoseSphericalKernel(const Mat& x1, const Mat& x2) - : PFRansacKernel(x1, x2) // provide a reference to the input matrices - , _logalpha0(M_PI) + : PFRansacKernel(x1, x2) // provide a reference to the input matrices + , + _logalpha0(M_PI) { ALICEVISION_LOG_TRACE("RelativePoseSphericalKernel: x1: " << x1.rows() << "x" << x1.cols() << ", x2: " << x2.rows() << "x" << x2.cols()); assert(x1.rows() == 3); @@ -214,11 +216,10 @@ class RelativePoseSphericalKernel: public robustEstimation::PointFittingRansacKe double thresholdNormalizer() const { return 1; } double unormalizeError(double val) const override { return sqrt(val); } -protected: + protected: /// Alpha0 is used to make the error adaptive to the image size const double _logalpha0; }; - -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/ResectionKernel.hpp b/src/aliceVision/multiview/ResectionKernel.hpp index 070bf86068..87beb319e7 100644 --- a/src/aliceVision/multiview/ResectionKernel.hpp +++ b/src/aliceVision/multiview/ResectionKernel.hpp @@ -21,57 +21,58 @@ namespace multiview { * * @ref [1] "Robust and accurate calibration of camera networks". PhD. Pierre MOULON */ -template> -class ResectionKernel - : public robustEstimation::PointFittingRansacKernel +template> +class ResectionKernel : public robustEstimation::PointFittingRansacKernel { -public: - - using KernelBase = robustEstimation::PointFittingRansacKernel; - - /** - * @brief ResectionKernel constructor with unknown intrinsic - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d 3d points in the second image. One per column. - * @param[in] K intrinsic camera parameter - */ - ResectionKernel(const Mat& x2d, const Mat& x3d, int w, int h) - : _x2d(x2d.rows(), x2d.cols()) - , KernelBase(_x2d, x3d) - , _logalpha0(log10(M_PI)) - , _N1(3, 3) - { - assert(2 == x2d.rows()); - assert(3 == x3d.rows()); - assert(x2d.cols() == x3d.cols()); - - robustEstimation::normalizePointsFromImageSize(x2d, &_x2d, &_N1, w, h); - } - - void unnormalize(ModelT_& model) const override - { - // unnormalize model from the computed conditioning. - Mat34 P = model.getMatrix(); - UnnormalizerT_::unnormalize(_N1, Mat3::Identity(), &P); - model.setMatrix(P); - } - - double logalpha0() const override {return _logalpha0; } - double errorVectorDimension() const override {return 2.0;} // point to point error - Mat3 normalizer1() const override {return Mat3::Identity();} - double thresholdNormalizer() const override {return _N1(0,0);} - double unormalizeError(double val) const override {return sqrt(val) / _N1(0,0);} - -protected: - /// Normalized input data - Mat _x2d; - /// Matrix used to normalize data - Mat3 _N1; - /// Alpha0 is used to make the error adaptive to the image size - double _logalpha0; + public: + using KernelBase = robustEstimation::PointFittingRansacKernel; + + /** + * @brief ResectionKernel constructor with unknown intrinsic + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d 3d points in the second image. One per column. + * @param[in] K intrinsic camera parameter + */ + ResectionKernel(const Mat& x2d, const Mat& x3d, int w, int h) + : _x2d(x2d.rows(), x2d.cols()), + KernelBase(_x2d, x3d), + _logalpha0(log10(M_PI)), + _N1(3, 3) + { + assert(2 == x2d.rows()); + assert(3 == x3d.rows()); + assert(x2d.cols() == x3d.cols()); + + robustEstimation::normalizePointsFromImageSize(x2d, &_x2d, &_N1, w, h); + } + + void unnormalize(ModelT_& model) const override + { + // unnormalize model from the computed conditioning. + Mat34 P = model.getMatrix(); + UnnormalizerT_::unnormalize(_N1, Mat3::Identity(), &P); + model.setMatrix(P); + } + + double logalpha0() const override { return _logalpha0; } + double errorVectorDimension() const override { return 2.0; } // point to point error + Mat3 normalizer1() const override { return Mat3::Identity(); } + double thresholdNormalizer() const override { return _N1(0, 0); } + double unormalizeError(double val) const override { return sqrt(val) / _N1(0, 0); } + + protected: + /// Normalized input data + Mat _x2d; + /// Matrix used to normalize data + Mat3 _N1; + /// Alpha0 is used to make the error adaptive to the image size + double _logalpha0; }; - /** * @brief The kernel for the resection with known intrinsics (PnP) to be used with * the ACRANSAC / LORANSAC framework. @@ -80,63 +81,65 @@ class ResectionKernel * * @tparam SolverT The minimal solver able to find a solution from a minimum set of points, usually any PnP solver. * @tparam ErrorT The functor computing the error for each data sample with respect to the estimated model, usually a reprojection error functor. - * @tparam UnnormalizerT The functor used to normalize the data before the estimation of the model, usually a functor that normalize the point in camera - * coordinates (ie multiply by the inverse of the calibration matrix). + * @tparam UnnormalizerT The functor used to normalize the data before the estimation of the model, usually a functor that normalize the point in + * camera coordinates (ie multiply by the inverse of the calibration matrix). * @tparam SolverLsT = SolverT The least square solver that is used to find a solution from any set of data larger than the minimum required, * usually the 6 point algorithm which solves the resection problem by means of LS. * @tparam ModelT = Mat34Model The type of the model to estimate, the projection matrix. */ -template> -class ResectionKernel_K - : public robustEstimation::PointFittingRansacKernel +template> +class ResectionKernel_K : public robustEstimation::PointFittingRansacKernel { -public: - - using KernelBase = robustEstimation::PointFittingRansacKernel; - - /** - * @brief ResectionKernel constructor with known intrinsic - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d 3d points in the second image. One per column. - * @param[in] K intrinsic camera parameter - */ - ResectionKernel_K(const Mat& x2d, const Mat& x3d, const Mat3& K) - : _x2d(x2d.rows(), x2d.cols()) - , KernelBase(_x2d, x3d) - , _logalpha0(log10(M_PI)) - , _N1(K.inverse()) - , _K(K) - { - assert(2 == x2d.rows()); - assert(3 == x3d.rows()); - assert(x2d.cols() == x3d.cols()); - - // normalize points by inverse K - robustEstimation::applyTransformationToPoints(x2d, _N1, &_x2d); - } - - void unnormalize(ModelT_& model) const override - { - // unnormalize model from the computed conditioning. - model.setMatrix(_K * model.getMatrix()); - } - - double logalpha0() const override {return _logalpha0; } - double errorVectorDimension() const override {return 2.0;} // point to point error - Mat3 normalizer1() const override {return Mat3::Identity();} - double thresholdNormalizer() const override {return _N1(0,0);} - double unormalizeError(double val) const override {return sqrt(val) / _N1(0,0);} - -protected: - /// Normalized input data - Mat _x2d; - /// Matrix used to normalize data - Mat3 _N1; - /// Alpha0 is used to make the error adaptive to the image size - double _logalpha0; - /// intrinsic camera parameter - Mat3 _K; + public: + using KernelBase = robustEstimation::PointFittingRansacKernel; + + /** + * @brief ResectionKernel constructor with known intrinsic + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d 3d points in the second image. One per column. + * @param[in] K intrinsic camera parameter + */ + ResectionKernel_K(const Mat& x2d, const Mat& x3d, const Mat3& K) + : _x2d(x2d.rows(), x2d.cols()), + KernelBase(_x2d, x3d), + _logalpha0(log10(M_PI)), + _N1(K.inverse()), + _K(K) + { + assert(2 == x2d.rows()); + assert(3 == x3d.rows()); + assert(x2d.cols() == x3d.cols()); + + // normalize points by inverse K + robustEstimation::applyTransformationToPoints(x2d, _N1, &_x2d); + } + + void unnormalize(ModelT_& model) const override + { + // unnormalize model from the computed conditioning. + model.setMatrix(_K * model.getMatrix()); + } + + double logalpha0() const override { return _logalpha0; } + double errorVectorDimension() const override { return 2.0; } // point to point error + Mat3 normalizer1() const override { return Mat3::Identity(); } + double thresholdNormalizer() const override { return _N1(0, 0); } + double unormalizeError(double val) const override { return sqrt(val) / _N1(0, 0); } + + protected: + /// Normalized input data + Mat _x2d; + /// Matrix used to normalize data + Mat3 _N1; + /// Alpha0 is used to make the error adaptive to the image size + double _logalpha0; + /// intrinsic camera parameter + Mat3 _K; }; -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/Unnormalizer.cpp b/src/aliceVision/multiview/Unnormalizer.cpp index 30135227b2..2377381b24 100644 --- a/src/aliceVision/multiview/Unnormalizer.cpp +++ b/src/aliceVision/multiview/Unnormalizer.cpp @@ -12,21 +12,12 @@ namespace aliceVision { namespace multiview { // Denormalize the results. See HZ page 109. -void UnnormalizerT::unnormalize(const Mat3 &T1, const Mat3 &T2, Mat3 *H) -{ - *H = T2.transpose() * (*H) * T1; -} +void UnnormalizerT::unnormalize(const Mat3& T1, const Mat3& T2, Mat3* H) { *H = T2.transpose() * (*H) * T1; } // Denormalize the results. See HZ page 109. -void UnnormalizerI::unnormalize(const Mat3 &T1, const Mat3 &T2, Mat3 *H) -{ - *H = T2.inverse() * (*H) * T1; -} +void UnnormalizerI::unnormalize(const Mat3& T1, const Mat3& T2, Mat3* H) { *H = T2.inverse() * (*H) * T1; } -void UnnormalizerResection::unnormalize(const Mat &T, const Mat &U, Mat34 *P) -{ - *P = T.inverse() * (*P); -} +void UnnormalizerResection::unnormalize(const Mat& T, const Mat& U, Mat34* P) { *P = T.inverse() * (*P); } -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/Unnormalizer.hpp b/src/aliceVision/multiview/Unnormalizer.hpp index c9f988b6c2..53cd281f22 100644 --- a/src/aliceVision/multiview/Unnormalizer.hpp +++ b/src/aliceVision/multiview/Unnormalizer.hpp @@ -18,11 +18,11 @@ namespace multiview { */ struct UnnormalizerI { - /** - * @brief Denormalize the results. - * @see HZ page 109. - */ - static void unnormalize(const Mat3& T1, const Mat3& T2, Mat3* H); + /** + * @brief Denormalize the results. + * @see HZ page 109. + */ + static void unnormalize(const Mat3& T1, const Mat3& T2, Mat3* H); }; /** @@ -30,11 +30,11 @@ struct UnnormalizerI */ struct UnnormalizerT { - /** - * @brief Denormalize the results. - * @see HZ page 109. - */ - static void unnormalize(const Mat3& T1, const Mat3& T2, Mat3* H); + /** + * @brief Denormalize the results. + * @see HZ page 109. + */ + static void unnormalize(const Mat3& T1, const Mat3& T2, Mat3* H); }; /** @@ -42,8 +42,8 @@ struct UnnormalizerT */ struct UnnormalizerResection { - static void unnormalize(const Mat& T, const Mat& U, Mat34* P); + static void unnormalize(const Mat& T, const Mat& U, Mat34* P); }; -} //namespace multiview -} //namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/affineSolver.cpp b/src/aliceVision/multiview/affineSolver.cpp index e2dd93ac82..c3e502f6e7 100644 --- a/src/aliceVision/multiview/affineSolver.cpp +++ b/src/aliceVision/multiview/affineSolver.cpp @@ -23,40 +23,41 @@ namespace multiview { // | d | // | x | // | y | -bool affine2DFromCorrespondencesLinear(const Mat &x1, const Mat &x2, - Mat3 *M, - double expected_precision) { - assert(2 == x1.rows()); - assert(3 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); +bool affine2DFromCorrespondencesLinear(const Mat& x1, const Mat& x2, Mat3* M, double expected_precision) +{ + assert(2 == x1.rows()); + assert(3 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); - const Mat::Index n = x1.cols(); - Mat A = Mat::Zero(2*n, 6); - Mat b = Mat::Zero(2*n, 1); - for (Mat::Index i = 0; i < n; ++i) { - const Mat::Index j= i * 2; - A(j,0) = x1(0,i); - A(j,1) = x1(1,i); - A(j,4) = 1.0; + const Mat::Index n = x1.cols(); + Mat A = Mat::Zero(2 * n, 6); + Mat b = Mat::Zero(2 * n, 1); + for (Mat::Index i = 0; i < n; ++i) + { + const Mat::Index j = i * 2; + A(j, 0) = x1(0, i); + A(j, 1) = x1(1, i); + A(j, 4) = 1.0; - A(j+1,2) = x1(0,i); - A(j+1,3) = x1(1,i); - A(j+1,5) = 1.0; + A(j + 1, 2) = x1(0, i); + A(j + 1, 3) = x1(1, i); + A(j + 1, 5) = 1.0; - b(j,0) = x2(0,i); - b(j+1,0) = x2(1,i); - } - // Solve A x = B - Vec x = A.fullPivLu().solve(b); - if ((A * x).isApprox(b, expected_precision)) { - *M << x(0), x(1), x(4), - x(2), x(3), x(5), - 0.0, 0.0, 1.0; - return true; - } else { - return false; - } + b(j, 0) = x2(0, i); + b(j + 1, 0) = x2(1, i); + } + // Solve A x = B + Vec x = A.fullPivLu().solve(b); + if ((A * x).isApprox(b, expected_precision)) + { + *M << x(0), x(1), x(4), x(2), x(3), x(5), 0.0, 0.0, 1.0; + return true; + } + else + { + return false; + } } // Parametrization @@ -78,54 +79,55 @@ bool affine2DFromCorrespondencesLinear(const Mat &x1, const Mat &x2, // | x | // | y | // | z | -bool affine3DFromCorrespondencesLinear(const Mat &x1, - const Mat &x2, - Mat4 *M, - double expected_precision) { - assert(3 == x1.rows()); - assert(4 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); +bool affine3DFromCorrespondencesLinear(const Mat& x1, const Mat& x2, Mat4* M, double expected_precision) +{ + assert(3 == x1.rows()); + assert(4 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); - const Mat::Index n = x1.cols(); - Mat A = Mat::Zero(3*n, 12); - Mat b = Mat::Zero(3*n, 1); - for (Mat::Index i = 0; i < n; ++i) { - const Mat::Index j= i * 3; - const Mat::Index j1= j + 1; - const Mat::Index j2= j + 2; - A(j,0) = x1(0,i); - A(j,1) = x1(1,i); - A(j,2) = x1(2,i); - A(j,9) = 1.0; + const Mat::Index n = x1.cols(); + Mat A = Mat::Zero(3 * n, 12); + Mat b = Mat::Zero(3 * n, 1); + for (Mat::Index i = 0; i < n; ++i) + { + const Mat::Index j = i * 3; + const Mat::Index j1 = j + 1; + const Mat::Index j2 = j + 2; + A(j, 0) = x1(0, i); + A(j, 1) = x1(1, i); + A(j, 2) = x1(2, i); + A(j, 9) = 1.0; - A(j1,3) = x1(0,i); - A(j1,4) = x1(1,i); - A(j1,5) = x1(2,i); - A(j1,10)= 1.0; + A(j1, 3) = x1(0, i); + A(j1, 4) = x1(1, i); + A(j1, 5) = x1(2, i); + A(j1, 10) = 1.0; - A(j2,6) = x1(0,i); - A(j2,7) = x1(1,i); - A(j2,8) = x1(2,i); - A(j2,11)= 1.0; + A(j2, 6) = x1(0, i); + A(j2, 7) = x1(1, i); + A(j2, 8) = x1(2, i); + A(j2, 11) = 1.0; - b(j,0) = x2(0,i); - b(j1,0) = x2(1,i); - b(j2,0) = x2(2,i); - } - // Solve A x = B - Vec x = A.fullPivLu().solve(b); - if ((A * x).isApprox(b, expected_precision)) { - *M << x(0), x(1), x(2), x(9), // a b c x - x(3), x(4), x(5), x(10), // d e f y - x(6), x(7), x(8), x(11), // g h i z - 0.0, 0.0, 0.0, 1.0; - return true; - } else { - return false; - } + b(j, 0) = x2(0, i); + b(j1, 0) = x2(1, i); + b(j2, 0) = x2(2, i); + } + // Solve A x = B + Vec x = A.fullPivLu().solve(b); + if ((A * x).isApprox(b, expected_precision)) + { + *M << x(0), x(1), x(2), x(9), // a b c x + x(3), x(4), x(5), x(10), // d e f y + x(6), x(7), x(8), x(11), // g h i z + 0.0, 0.0, 0.0, 1.0; + return true; + } + else + { + return false; + } } -} // namespace multiview -} // namespace aliceVision - +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/affineSolver.hpp b/src/aliceVision/multiview/affineSolver.hpp index c217973b29..d0c0719f3c 100644 --- a/src/aliceVision/multiview/affineSolver.hpp +++ b/src/aliceVision/multiview/affineSolver.hpp @@ -61,5 +61,5 @@ bool affine2DFromCorrespondencesLinear(const Mat& x1, const Mat& x2, Mat3* M, do */ bool affine3DFromCorrespondencesLinear(const Mat& x1, const Mat& x2, Mat4* M, double expected_precision = EigenDoubleTraits::dummy_precision()); -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/affineSolver_test.cpp b/src/aliceVision/multiview/affineSolver_test.cpp index 2f64373b8a..dd2759294f 100644 --- a/src/aliceVision/multiview/affineSolver_test.cpp +++ b/src/aliceVision/multiview/affineSolver_test.cpp @@ -18,223 +18,190 @@ using namespace aliceVision; using namespace aliceVision::multiview; -BOOST_AUTO_TEST_CASE(Affine2DTest_TranslationX) { - Mat x1(2, 3); - x1 << 0, 1, 2, - 0, 1, 1; - - Mat x2(2, 3); - x2 << 1, 2, 3, - 0, 1, 1; - - Mat3 AffineMat; - BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &AffineMat)); - ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << AffineMat); - Mat3 ground_truth; - ground_truth << 1,0,1, - 0,1,0, - 0,0,1; - EXPECT_MATRIX_NEAR(AffineMat, ground_truth,1e-8); +BOOST_AUTO_TEST_CASE(Affine2DTest_TranslationX) +{ + Mat x1(2, 3); + x1 << 0, 1, 2, 0, 1, 1; + + Mat x2(2, 3); + x2 << 1, 2, 3, 0, 1, 1; + + Mat3 AffineMat; + BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &AffineMat)); + ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << AffineMat); + Mat3 ground_truth; + ground_truth << 1, 0, 1, 0, 1, 0, 0, 0, 1; + EXPECT_MATRIX_NEAR(AffineMat, ground_truth, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine2DTest_TranslationXY) { - Mat x1(2, 3); - x1 << 0, 1, 2, - 0, 1, 1; - - Mat x2(2, 3); - x2 << 1, 2, 3, - 1, 2, 2; - - Mat3 affine_mat; - BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << affine_mat); - Mat3 ground_truth; - ground_truth << 1,0,1, - 0,1,1, - 0,0,1; - EXPECT_MATRIX_NEAR(affine_mat, ground_truth,1e-8); +BOOST_AUTO_TEST_CASE(Affine2DTest_TranslationXY) +{ + Mat x1(2, 3); + x1 << 0, 1, 2, 0, 1, 1; + + Mat x2(2, 3); + x2 << 1, 2, 3, 1, 2, 2; + + Mat3 affine_mat; + BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << affine_mat); + Mat3 ground_truth; + ground_truth << 1, 0, 1, 0, 1, 1, 0, 0, 1; + EXPECT_MATRIX_NEAR(affine_mat, ground_truth, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine2DTest_Rotation45) { - Mat x1(2, 4); - x1 << 0, 1, 2, 5, - 0, 1, 2, 3; - - double angle = 45.0; - Mat3 rot; - rot << cos(angle), -sin(angle), 0, - sin(angle), cos(angle), 0, - 0, 0, 1; - Mat x2 = x1; - for(int i = 0; i < x2.cols(); ++i) { - x2.block<2,1>(0,i) = rot.block<2,2>(0,0) * x1.col(i) ; - } - - Mat3 affine_mat; - BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine2D " << affine_mat); - EXPECT_MATRIX_NEAR(affine_mat, rot, 1e-8); +BOOST_AUTO_TEST_CASE(Affine2DTest_Rotation45) +{ + Mat x1(2, 4); + x1 << 0, 1, 2, 5, 0, 1, 2, 3; + + double angle = 45.0; + Mat3 rot; + rot << cos(angle), -sin(angle), 0, sin(angle), cos(angle), 0, 0, 0, 1; + Mat x2 = x1; + for (int i = 0; i < x2.cols(); ++i) + { + x2.block<2, 1>(0, i) = rot.block<2, 2>(0, 0) * x1.col(i); + } + + Mat3 affine_mat; + BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine2D " << affine_mat); + EXPECT_MATRIX_NEAR(affine_mat, rot, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine2DTest_Rotation45AndTranslationXY) { - Mat x1(2, 4); - x1 << 0, 1, 2, 5, - 0, 1, 2, 3; - - double angle = 45.0; - Mat3 rot; - rot << cos(angle), sin(angle), -2, - -sin(angle), cos(angle), 5, - 0, 0, 1; - - Mat x2 = x1; - // Transform point from ground truth matrix - for(int i = 0; i < x2.cols(); ++i) { - x2.block<2,1>(0,i) = rot.block<2,2>(0,0) * x1.col(i);// rot - x2.block<2,1>(0,i) += rot.block<2,1>(0,2); // translation - } - - Mat3 affine_mat; - BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << affine_mat); - EXPECT_MATRIX_NEAR(affine_mat, rot, 1e-8); +BOOST_AUTO_TEST_CASE(Affine2DTest_Rotation45AndTranslationXY) +{ + Mat x1(2, 4); + x1 << 0, 1, 2, 5, 0, 1, 2, 3; + + double angle = 45.0; + Mat3 rot; + rot << cos(angle), sin(angle), -2, -sin(angle), cos(angle), 5, 0, 0, 1; + + Mat x2 = x1; + // Transform point from ground truth matrix + for (int i = 0; i < x2.cols(); ++i) + { + x2.block<2, 1>(0, i) = rot.block<2, 2>(0, 0) * x1.col(i); // rot + x2.block<2, 1>(0, i) += rot.block<2, 1>(0, 2); // translation + } + + Mat3 affine_mat; + BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << affine_mat); + EXPECT_MATRIX_NEAR(affine_mat, rot, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine2DTest_AffineGeneral) { - Mat x1(2, 4); - x1 << 0, 1, 2, 5, - 0, 1, 2, 3; - Mat3 m; - m << 3, -1, 4, - 6, -2, -3, - 0, 0, 1; - - Mat x2 = x1; - // Transform point from ground truth matrix - for(int i = 0; i < x2.cols(); ++i) { - x2.block<2,1>(0,i) = m.block<2,2>(0,0) * x1.col(i);// affine - x2.block<2,1>(0,i) += m.block<2,1>(0,2); // translation - } - - Mat3 affine_mat; - BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << affine_mat); - EXPECT_MATRIX_NEAR(affine_mat, m, 1e-8); +BOOST_AUTO_TEST_CASE(Affine2DTest_AffineGeneral) +{ + Mat x1(2, 4); + x1 << 0, 1, 2, 5, 0, 1, 2, 3; + Mat3 m; + m << 3, -1, 4, 6, -2, -3, 0, 0, 1; + + Mat x2 = x1; + // Transform point from ground truth matrix + for (int i = 0; i < x2.cols(); ++i) + { + x2.block<2, 1>(0, i) = m.block<2, 2>(0, 0) * x1.col(i); // affine + x2.block<2, 1>(0, i) += m.block<2, 1>(0, 2); // translation + } + + Mat3 affine_mat; + BOOST_CHECK(affine2DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine2D:\n" << affine_mat); + EXPECT_MATRIX_NEAR(affine_mat, m, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine3DTest_TranslationZ) { - Mat x1(3, 4); - x1 << 0, 1, 2, 3, - 0, 5, 1, 3, - 0, 1, 7, 3; - - Mat x2(3, 4); - x2 << 0, 1, 2, 3, - 0, 5, 1, 3, - 1, 2, 8, 4; - - Mat4 AffineMat; - BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &AffineMat)); - ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << AffineMat); - Mat4 ground_truth; - ground_truth << 1,0,0,0, - 0,1,0,0, - 0,0,1,1, - 0,0,0,1; - EXPECT_MATRIX_NEAR(AffineMat, ground_truth,1e-8); +BOOST_AUTO_TEST_CASE(Affine3DTest_TranslationZ) +{ + Mat x1(3, 4); + x1 << 0, 1, 2, 3, 0, 5, 1, 3, 0, 1, 7, 3; + + Mat x2(3, 4); + x2 << 0, 1, 2, 3, 0, 5, 1, 3, 1, 2, 8, 4; + + Mat4 AffineMat; + BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &AffineMat)); + ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << AffineMat); + Mat4 ground_truth; + ground_truth << 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1; + EXPECT_MATRIX_NEAR(AffineMat, ground_truth, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine3DTest_TranslationXYZ) { - Mat x1(3, 4); - x1 << 0, 1, 2, 3, - 0, 5, 1, 3, - 0, 1, 7, 3; - - Mat x2(3, 4); - x2 << 2, 3, 4, 5, - -1, 4, 0, 2, - 1, 2, 8, 4; - - Mat4 affine_mat; - BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << affine_mat); - Mat4 ground_truth; - ground_truth << 1,0,0, 2, - 0,1,0,-1, - 0,0,1, 1, - 0,0,0, 1; - EXPECT_MATRIX_NEAR(affine_mat, ground_truth,1e-8); +BOOST_AUTO_TEST_CASE(Affine3DTest_TranslationXYZ) +{ + Mat x1(3, 4); + x1 << 0, 1, 2, 3, 0, 5, 1, 3, 0, 1, 7, 3; + + Mat x2(3, 4); + x2 << 2, 3, 4, 5, -1, 4, 0, 2, 1, 2, 8, 4; + + Mat4 affine_mat; + BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << affine_mat); + Mat4 ground_truth; + ground_truth << 1, 0, 0, 2, 0, 1, 0, -1, 0, 0, 1, 1, 0, 0, 0, 1; + EXPECT_MATRIX_NEAR(affine_mat, ground_truth, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine3DTest_RotationAndTranslationXYZ) { - Mat x1(3, 4); - x1 << 0, 1, 2, 5, - 0, 1, 2, 3, - 0, 2, 0, 1; - - Mat4 M; - M.setIdentity(); - /* - M = AngleAxisd(45.0, Vector3f::UnitZ()) - * AngleAxisd(25.0, Vector3f::UnitX()) - * AngleAxisd(5.0, Vector3f::UnitZ());*/ - - // Rotation on x + translation - double angle = 45.0; - Mat4 rot; - rot << 1, 0, 0, 1, - 0, cos(angle), -sin(angle), 3, - 0, sin(angle), cos(angle),-2, - 0, 0, 0, 1; - M *= rot; - // Rotation on y - angle = 25.0; - rot << cos(angle), 0, sin(angle), 0, - 0, 1, 0, 0, - -sin(angle), 0, cos(angle), 0, - 0, 0, 0, 1; - M *= rot; - // Rotation on z - angle = 5.0; - rot << cos(angle), -sin(angle), 0, 0, - sin(angle), cos(angle), 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1; - M *= rot; - Mat x2 = x1; - // Transform point from ground affine matrix - for(int i = 0; i < x2.cols(); ++i) { - x2.block<3,1>(0,i) = M.block<3,3>(0,0) * x1.col(i) ; - x2.block<3,1>(0,i) += M.block<3,1>(0,3); // translation - } - - Mat4 affine_mat; - BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << affine_mat); - EXPECT_MATRIX_NEAR(affine_mat, M, 1e-8); +BOOST_AUTO_TEST_CASE(Affine3DTest_RotationAndTranslationXYZ) +{ + Mat x1(3, 4); + x1 << 0, 1, 2, 5, 0, 1, 2, 3, 0, 2, 0, 1; + + Mat4 M; + M.setIdentity(); + /* + M = AngleAxisd(45.0, Vector3f::UnitZ()) + * AngleAxisd(25.0, Vector3f::UnitX()) + * AngleAxisd(5.0, Vector3f::UnitZ());*/ + + // Rotation on x + translation + double angle = 45.0; + Mat4 rot; + rot << 1, 0, 0, 1, 0, cos(angle), -sin(angle), 3, 0, sin(angle), cos(angle), -2, 0, 0, 0, 1; + M *= rot; + // Rotation on y + angle = 25.0; + rot << cos(angle), 0, sin(angle), 0, 0, 1, 0, 0, -sin(angle), 0, cos(angle), 0, 0, 0, 0, 1; + M *= rot; + // Rotation on z + angle = 5.0; + rot << cos(angle), -sin(angle), 0, 0, sin(angle), cos(angle), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1; + M *= rot; + Mat x2 = x1; + // Transform point from ground affine matrix + for (int i = 0; i < x2.cols(); ++i) + { + x2.block<3, 1>(0, i) = M.block<3, 3>(0, 0) * x1.col(i); + x2.block<3, 1>(0, i) += M.block<3, 1>(0, 3); // translation + } + + Mat4 affine_mat; + BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << affine_mat); + EXPECT_MATRIX_NEAR(affine_mat, M, 1e-8); } -BOOST_AUTO_TEST_CASE(Affine3DTest_AffineGeneral) { - Mat x1(3, 4); - x1 << 0, 1, 2, 5, - 0, 1, 2, 3, - 2, 0, 1, 2; - Mat4 m; - m << 3, -1, 4, 1, - 6, -2, -3,-6, - 1, 0, 1, 2, - 0, 0, 0, 1; - - Mat x2 = x1; - // Transform point from ground truth affine matrix - for(int i = 0; i < x2.cols(); ++i) { - x2.block<3,1>(0,i) = m.block<3,3>(0,0) * x1.col(i);// affine - x2.block<3,1>(0,i) += m.block<3,1>(0,3); // translation - } - - Mat4 affine_mat; - BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &affine_mat)); - ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << affine_mat); - EXPECT_MATRIX_NEAR(affine_mat, m, 1e-8); +BOOST_AUTO_TEST_CASE(Affine3DTest_AffineGeneral) +{ + Mat x1(3, 4); + x1 << 0, 1, 2, 5, 0, 1, 2, 3, 2, 0, 1, 2; + Mat4 m; + m << 3, -1, 4, 1, 6, -2, -3, -6, 1, 0, 1, 2, 0, 0, 0, 1; + + Mat x2 = x1; + // Transform point from ground truth affine matrix + for (int i = 0; i < x2.cols(); ++i) + { + x2.block<3, 1>(0, i) = m.block<3, 3>(0, 0) * x1.col(i); // affine + x2.block<3, 1>(0, i) += m.block<3, 1>(0, 3); // translation + } + + Mat4 affine_mat; + BOOST_CHECK(affine3DFromCorrespondencesLinear(x1, x2, &affine_mat)); + ALICEVISION_LOG_DEBUG("Mat Affine3D:\n" << affine_mat); + EXPECT_MATRIX_NEAR(affine_mat, m, 1e-8); } diff --git a/src/aliceVision/multiview/epipolarEquation.hpp b/src/aliceVision/multiview/epipolarEquation.hpp index 50fcdad484..cf7f61d9cc 100644 --- a/src/aliceVision/multiview/epipolarEquation.hpp +++ b/src/aliceVision/multiview/epipolarEquation.hpp @@ -14,7 +14,7 @@ namespace aliceVision { namespace multiview { - + /** * @brief Build a 9 x n matrix from point matches, where each row is equivalent to the * equation x'T*F*x = 0 for a single correspondence pair (x', x). The domain of @@ -33,68 +33,51 @@ namespace multiview { * appropriate size already. */ template -inline void encodeEpipolarEquation(const TMatX& x1, const TMatX& x2, TMatA* A, const std::vector *weights = nullptr) +inline void encodeEpipolarEquation(const TMatX& x1, const TMatX& x2, TMatA* A, const std::vector* weights = nullptr) { - assert(x1.cols()==x2.cols()); + assert(x1.cols() == x2.cols()); - if(weights != nullptr) - { - assert(x1.cols()==weights->size()); - } + if (weights != nullptr) + { + assert(x1.cols() == weights->size()); + } - for(typename TMatX::Index i = 0; i < x1.cols(); ++i) - { - const Vec2 xx1 = x1.col(i); - const Vec2 xx2 = x2.col(i); + for (typename TMatX::Index i = 0; i < x1.cols(); ++i) + { + const Vec2 xx1 = x1.col(i); + const Vec2 xx2 = x2.col(i); - A->row(i) << - xx2(0) * xx1(0), // 0 represents x coords, - xx2(0) * xx1(1), // 1 represents y coords. - xx2(0), - xx2(1) * xx1(0), - xx2(1) * xx1(1), - xx2(1), - xx1(0), - xx1(1), - 1.0; + A->row(i) << xx2(0) * xx1(0), // 0 represents x coords, + xx2(0) * xx1(1), // 1 represents y coords. + xx2(0), xx2(1) * xx1(0), xx2(1) * xx1(1), xx2(1), xx1(0), xx1(1), 1.0; - if(weights != nullptr) - A->row(i) *= (*weights)[i]; - } + if (weights != nullptr) + A->row(i) *= (*weights)[i]; + } } - -template -inline void encodeEpipolarSphericalEquation(const TMatX& x1, const TMatX& x2, TMatA* A, - const std::vector* weights = nullptr) +template +inline void encodeEpipolarSphericalEquation(const TMatX& x1, const TMatX& x2, TMatA* A, const std::vector* weights = nullptr) { assert(x1.cols() == x2.cols()); - if(weights) + if (weights) { assert(x1.cols() == weights->size()); } - for(typename TMatX::Index i = 0; i < x1.cols(); ++i) + for (typename TMatX::Index i = 0; i < x1.cols(); ++i) { const Vec3 xx1 = x1.col(i); const Vec3 xx2 = x2.col(i); - A->row(i) << - xx2(0) * xx1(0), // 0 represents x coords, - xx2(0) * xx1(1), // 1 represents y coords. - xx2(0) * xx1(2), - xx2(1) * xx1(0), - xx2(1) * xx1(1), - xx2(1) * xx1(2), - xx2(2) * xx1(0), - xx2(2) * xx1(1), - xx2(2) * xx1(2); + A->row(i) << xx2(0) * xx1(0), // 0 represents x coords, + xx2(0) * xx1(1), // 1 represents y coords. + xx2(0) * xx1(2), xx2(1) * xx1(0), xx2(1) * xx1(1), xx2(1) * xx1(2), xx2(2) * xx1(0), xx2(2) * xx1(1), xx2(2) * xx1(2); - if(weights) + if (weights) { A->row(i) *= (*weights)[i]; } } } - -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/essential.cpp b/src/aliceVision/multiview/essential.cpp index 708c0ea9de..472b06a11f 100644 --- a/src/aliceVision/multiview/essential.cpp +++ b/src/aliceVision/multiview/essential.cpp @@ -14,127 +14,114 @@ namespace aliceVision { // HZ 9.6 page 257 (formula 9.12) -void essentialFromFundamental(const Mat3 &F, - const Mat3 &K1, - const Mat3 &K2, - Mat3 *E) { - *E = K2.transpose() * F * K1; -} +void essentialFromFundamental(const Mat3& F, const Mat3& K1, const Mat3& K2, Mat3* E) { *E = K2.transpose() * F * K1; } // HZ 9.6 page 257 (formula 9.12) // Or http://ai.stanford.edu/~birch/projective/node20.html -void fundamentalFromEssential(const Mat3 &E, - const Mat3 &K1, - const Mat3 &K2, - Mat3 *F) { - *F = K2.inverse().transpose() * E * K1.inverse(); -} +void fundamentalFromEssential(const Mat3& E, const Mat3& K1, const Mat3& K2, Mat3* F) { *F = K2.inverse().transpose() * E * K1.inverse(); } -void relativeCameraMotion(const Mat3 &R1, - const Vec3 &t1, - const Mat3 &R2, - const Vec3 &t2, - Mat3 *R, - Vec3 *t) { - *R = R2 * R1.transpose(); - *t = t2 - (*R) * t1; +void relativeCameraMotion(const Mat3& R1, const Vec3& t1, const Mat3& R2, const Vec3& t2, Mat3* R, Vec3* t) +{ + *R = R2 * R1.transpose(); + *t = t2 - (*R) * t1; } // HZ 9.6 page 257 -void essentialFromRt(const Mat3 &R1, - const Vec3 &t1, - const Mat3 &R2, - const Vec3 &t2, - Mat3 *E) { - Mat3 R; - Vec3 t; - relativeCameraMotion(R1, t1, R2, t2, &R, &t); - Mat3 Tx = CrossProductMatrix(t); - *E = Tx * R; +void essentialFromRt(const Mat3& R1, const Vec3& t1, const Mat3& R2, const Vec3& t2, Mat3* E) +{ + Mat3 R; + Vec3 t; + relativeCameraMotion(R1, t1, R2, t2, &R, &t); + Mat3 Tx = CrossProductMatrix(t); + *E = Tx * R; } // HZ 9.7 page 259 (Result 9.19) -void motionFromEssential(const Mat3 &E, - std::vector *Rs, - std::vector *ts) { - Eigen:: JacobiSVD USV(E, Eigen::ComputeFullU|Eigen::ComputeFullV); - Mat3 U = USV.matrixU(); - // Vec3 d = USV.singularValues(); - Mat3 Vt = USV.matrixV().transpose(); - - // Last column of U is undetermined since d = (a a 0). - if (U.determinant() < 0) { - U.col(2) *= -1; - } - // Last row of Vt is undetermined since d = (a a 0). - if (Vt.determinant() < 0) { - Vt.row(2) *= -1; - } - - Mat3 W; - W << 0, -1, 0, - 1, 0, 0, - 0, 0, 1; - - Mat3 U_W_Vt = U * W * Vt; - Mat3 U_Wt_Vt = U * W.transpose() * Vt; - - Rs->resize(4); - ts->resize(4); - (*Rs)[0] = U_W_Vt; (*ts)[0] = U.col(2); - (*Rs)[1] = U_W_Vt; (*ts)[1] = -U.col(2); - (*Rs)[2] = U_Wt_Vt; (*ts)[2] = U.col(2); - (*Rs)[3] = U_Wt_Vt; (*ts)[3] = -U.col(2); +void motionFromEssential(const Mat3& E, std::vector* Rs, std::vector* ts) +{ + Eigen::JacobiSVD USV(E, Eigen::ComputeFullU | Eigen::ComputeFullV); + Mat3 U = USV.matrixU(); + // Vec3 d = USV.singularValues(); + Mat3 Vt = USV.matrixV().transpose(); + + // Last column of U is undetermined since d = (a a 0). + if (U.determinant() < 0) + { + U.col(2) *= -1; + } + // Last row of Vt is undetermined since d = (a a 0). + if (Vt.determinant() < 0) + { + Vt.row(2) *= -1; + } + + Mat3 W; + W << 0, -1, 0, 1, 0, 0, 0, 0, 1; + + Mat3 U_W_Vt = U * W * Vt; + Mat3 U_Wt_Vt = U * W.transpose() * Vt; + + Rs->resize(4); + ts->resize(4); + (*Rs)[0] = U_W_Vt; + (*ts)[0] = U.col(2); + (*Rs)[1] = U_W_Vt; + (*ts)[1] = -U.col(2); + (*Rs)[2] = U_Wt_Vt; + (*ts)[2] = U.col(2); + (*Rs)[3] = U_Wt_Vt; + (*ts)[3] = -U.col(2); } // HZ 9.6 pag 259 (9.6.3 Geometrical interpretation of the 4 solutions) -int motionFromEssentialChooseSolution(const std::vector &Rs, - const std::vector &ts, - const Mat3 &K1, - const Vec2 &x1, - const Mat3 &K2, - const Vec2 &x2) { - assert(Rs.size() == 4); - assert(ts.size() == 4); - - Mat34 P1, P2; - // Set P1 = K1 [Id|0] - Mat3 R1 = Mat3::Identity(); - Vec3 t1 = Vec3::Zero(); - P_from_KRt(K1, R1, t1, P1); - - for (int i = 0; i < 4; ++i) { - const Mat3 &R2 = Rs[i]; - const Vec3 &t2 = ts[i]; - P_from_KRt(K2, R2, t2, P2); - Vec3 X; - multiview::TriangulateDLT(P1, x1, P2, x2, X); - // Test if point is front to the two cameras (positive depth) - if (Depth(R1, t1, X) > 0 && Depth(R2, t2, X) > 0) { - return i; +int motionFromEssentialChooseSolution(const std::vector& Rs, + const std::vector& ts, + const Mat3& K1, + const Vec2& x1, + const Mat3& K2, + const Vec2& x2) +{ + assert(Rs.size() == 4); + assert(ts.size() == 4); + + Mat34 P1, P2; + // Set P1 = K1 [Id|0] + Mat3 R1 = Mat3::Identity(); + Vec3 t1 = Vec3::Zero(); + P_from_KRt(K1, R1, t1, P1); + + for (int i = 0; i < 4; ++i) + { + const Mat3& R2 = Rs[i]; + const Vec3& t2 = ts[i]; + P_from_KRt(K2, R2, t2, P2); + Vec3 X; + multiview::TriangulateDLT(P1, x1, P2, x2, X); + // Test if point is front to the two cameras (positive depth) + if (Depth(R1, t1, X) > 0 && Depth(R2, t2, X) > 0) + { + return i; + } } - } - return -1; + return -1; } -bool motionFromEssentialAndCorrespondence(const Mat3 &E, - const Mat3 &K1, - const Vec2 &x1, - const Mat3 &K2, - const Vec2 &x2, - Mat3 *R, - Vec3 *t) { - std::vector Rs; - std::vector ts; - motionFromEssential(E, &Rs, &ts); - int solution = motionFromEssentialChooseSolution(Rs, ts, K1, x1, K2, x2); - if (solution >= 0) { - *R = Rs[solution]; - *t = ts[solution]; - return true; - } else { - return false; - } +bool motionFromEssentialAndCorrespondence(const Mat3& E, const Mat3& K1, const Vec2& x1, const Mat3& K2, const Vec2& x2, Mat3* R, Vec3* t) +{ + std::vector Rs; + std::vector ts; + motionFromEssential(E, &Rs, &ts); + int solution = motionFromEssentialChooseSolution(Rs, ts, K1, x1, K2, x2); + if (solution >= 0) + { + *R = Rs[solution]; + *t = ts[solution]; + return true; + } + else + { + return false; + } } } // namespace aliceVision diff --git a/src/aliceVision/multiview/essential.hpp b/src/aliceVision/multiview/essential.hpp index 863abb5b78..a207cbc731 100644 --- a/src/aliceVision/multiview/essential.hpp +++ b/src/aliceVision/multiview/essential.hpp @@ -22,45 +22,24 @@ namespace aliceVision { * * T = T2 T1^{-1} */ -void relativeCameraMotion(const Mat3& R1, - const Vec3& t1, - const Mat3& R2, - const Vec3& t2, - Mat3* R, - Vec3* t); +void relativeCameraMotion(const Mat3& R1, const Vec3& t1, const Mat3& R2, const Vec3& t2, Mat3* R, Vec3* t); /** * @brief Given F, Left/Right K matrix it compute the Essential matrix */ -void essentialFromFundamental(const Mat3& F, - const Mat3& K1, - const Mat3& K2, - Mat3* E); +void essentialFromFundamental(const Mat3& F, const Mat3& K1, const Mat3& K2, Mat3* E); /** * @brief Compute E as E = [t12]x R12. */ -void essentialFromRt(const Mat3& R1, - const Vec3& t1, - const Mat3& R2, - const Vec3& t2, - Mat3* E); +void essentialFromRt(const Mat3& R1, const Vec3& t1, const Mat3& R2, const Vec3& t2, Mat3* E); /** * @brief Given E, Left/Right K matrix it compute the Fundamental matrix */ -void fundamentalFromEssential(const Mat3& E, - const Mat3& K1, - const Mat3& K2, - Mat3* F); +void fundamentalFromEssential(const Mat3& E, const Mat3& K1, const Mat3& K2, Mat3* F); /** * @brief Test the possible R|t configuration to have point in front of the cameras * Return false if no possible configuration */ -bool motionFromEssentialAndCorrespondence(const Mat3& E, - const Mat3& K1, - const Vec2& x1, - const Mat3& K2, - const Vec2& x2, - Mat3* R, - Vec3* t); +bool motionFromEssentialAndCorrespondence(const Mat3& E, const Mat3& K1, const Vec2& x1, const Mat3& K2, const Vec2& x2, Mat3* R, Vec3* t); /** * @brief Choose one of the four possible motion solutions from an essential matrix. * Decides the right solution by checking that the triangulation of a match @@ -78,4 +57,4 @@ int motionFromEssentialChooseSolution(const std::vector& Rs, */ void motionFromEssential(const Mat3& E, std::vector* Rs, std::vector* ts); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/multiview/knownRotationTranslationKernel.hpp b/src/aliceVision/multiview/knownRotationTranslationKernel.hpp index 0911a59a70..8621dbc7c2 100644 --- a/src/aliceVision/multiview/knownRotationTranslationKernel.hpp +++ b/src/aliceVision/multiview/knownRotationTranslationKernel.hpp @@ -24,162 +24,146 @@ namespace multiview { */ struct TwoPointTranslationSolver { - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const - { - return 2; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const - { - return 1; - } - - /** - * @brief Solve the problem of camera translation. - * @param[in] xA - * @param[in] xB - * @param[in] R - * @param[out] models t - */ - void solve(const Mat& xA, const Mat& xB, const Mat3& R, std::vector>& models) const - { - const Mat3 Rt = R.transpose(); - - // A side bearing vectors - const Vec3 f1 = Vec3(xA.col(0)(0), xA.col(0)(1), 1.); - const Vec3 f2 = Vec3(xA.col(1)(0), xA.col(1)(1), 1.); - - // B side bearing vectors - const Vec3 f1prime = Rt * Vec3(xB.col(0)(0), xB.col(0)(1), 1.); - const Vec3 f2prime = Rt * Vec3(xB.col(1)(0), xB.col(1)(1), 1.); - - // compute the translation of the camera - const Vec3 c = ((f1.cross(f1prime)).cross(f2.cross(f2prime))).normalized(); - - // ensure the translation make the points in front of the cameras - const Vec3 opticalFlow = f1 - f1prime; - Vec3 translation = c; - if (opticalFlow.dot(translation) < 0) - translation = -translation; - models.emplace_back(-R * translation); - } + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const { return 2; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const { return 1; } + + /** + * @brief Solve the problem of camera translation. + * @param[in] xA + * @param[in] xB + * @param[in] R + * @param[out] models t + */ + void solve(const Mat& xA, const Mat& xB, const Mat3& R, std::vector>& models) const + { + const Mat3 Rt = R.transpose(); + + // A side bearing vectors + const Vec3 f1 = Vec3(xA.col(0)(0), xA.col(0)(1), 1.); + const Vec3 f2 = Vec3(xA.col(1)(0), xA.col(1)(1), 1.); + + // B side bearing vectors + const Vec3 f1prime = Rt * Vec3(xB.col(0)(0), xB.col(0)(1), 1.); + const Vec3 f2prime = Rt * Vec3(xB.col(1)(0), xB.col(1)(1), 1.); + + // compute the translation of the camera + const Vec3 c = ((f1.cross(f1prime)).cross(f2.cross(f2prime))).normalized(); + + // ensure the translation make the points in front of the cameras + const Vec3 opticalFlow = f1 - f1prime; + Vec3 translation = c; + if (opticalFlow.dot(translation) < 0) + translation = -translation; + models.emplace_back(-R * translation); + } }; /** * @brief Generic Solver to find the translation from a known Rotation. */ -template> +template> class TranslationFromKnowRotationKernel { -public: - - using SolverT = SolverT_; - using ErrorT = ErrorT_; - using ModelT = ModelT_; - - /** - * @brief TranslationFromKnowRotation constructor - * @param[in] pt2DA 2D points [camera coordinates] - * @param[in] pt2DB 2D points [camera coordinates] - * @param[in] R rotation matrix - */ - TranslationFromKnowRotationKernel(const Mat& x1, const Mat& x2, const Mat3& R) - : _x1(x1) - , _x2(x2) - , _R(R) - {} - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const - { - return _kernelSolver.getMinimumNbRequiredSamples(); - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const - { - return _kernelSolver.getMaximumNbModels(); - } - - /** - * @brief Extract required sample and fit model(s) to the sample - * @param[in] samples - * @param[out] models - */ - inline void fit(const std::vector& samples, std::vector& models) const - { - assert(2 == _x1.rows()); - assert(2 == _x2.rows()); - - const Mat x1 = buildSubsetMatrix(_x1, samples); - const Mat x2 = buildSubsetMatrix(_x2, samples); - - _kernelSolver.solve(x1, x2, _R, models); - } - - /** - * @brief error : distance of the sample to the epipolar line - * @param[in] sample - * @param[in] model - * @return error value - */ - inline double error(std::size_t sample, const ModelT& model) const - { - Mat34 poseA, poseB; - P_from_KRt(Mat3::Identity(), Mat3::Identity(), Vec3::Zero(), poseA); - P_from_KRt(Mat3::Identity(), _R, model.getMatrix(), poseB); - const robustEstimation::Mat3Model F(F_from_P(poseA, poseB)); - return _errorEstimator.error(F, _x1.col(sample), _x2.col(sample)); - } - - /** - * @brief Return the errors associated to the model and each sample point - * @param[in] model - * @param[out] errors - */ - inline virtual void errors(const ModelT& model, std::vector& errors) const - { - errors.resize(_x1.cols()); - for(std::size_t sample = 0; sample < _x1.cols(); ++sample) - errors.at(sample) = error(sample, model); - } - - /** - * @brief get the number of putative points - * @return number of putative points - */ - inline std::size_t nbSamples() const - { - return _x1.cols(); - } - -protected: - - /// left corresponding point - const Mat& _x1; - /// right corresponding point - const Mat& _x2; - /// two view solver - const SolverT _kernelSolver = SolverT(); - /// solver error estimation - const ErrorT _errorEstimator = ErrorT(); - /// R rotation matrix - const Mat3 _R; + public: + using SolverT = SolverT_; + using ErrorT = ErrorT_; + using ModelT = ModelT_; + + /** + * @brief TranslationFromKnowRotation constructor + * @param[in] pt2DA 2D points [camera coordinates] + * @param[in] pt2DB 2D points [camera coordinates] + * @param[in] R rotation matrix + */ + TranslationFromKnowRotationKernel(const Mat& x1, const Mat& x2, const Mat3& R) + : _x1(x1), + _x2(x2), + _R(R) + {} + + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const { return _kernelSolver.getMinimumNbRequiredSamples(); } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const { return _kernelSolver.getMaximumNbModels(); } + + /** + * @brief Extract required sample and fit model(s) to the sample + * @param[in] samples + * @param[out] models + */ + inline void fit(const std::vector& samples, std::vector& models) const + { + assert(2 == _x1.rows()); + assert(2 == _x2.rows()); + + const Mat x1 = buildSubsetMatrix(_x1, samples); + const Mat x2 = buildSubsetMatrix(_x2, samples); + + _kernelSolver.solve(x1, x2, _R, models); + } + + /** + * @brief error : distance of the sample to the epipolar line + * @param[in] sample + * @param[in] model + * @return error value + */ + inline double error(std::size_t sample, const ModelT& model) const + { + Mat34 poseA, poseB; + P_from_KRt(Mat3::Identity(), Mat3::Identity(), Vec3::Zero(), poseA); + P_from_KRt(Mat3::Identity(), _R, model.getMatrix(), poseB); + const robustEstimation::Mat3Model F(F_from_P(poseA, poseB)); + return _errorEstimator.error(F, _x1.col(sample), _x2.col(sample)); + } + + /** + * @brief Return the errors associated to the model and each sample point + * @param[in] model + * @param[out] errors + */ + inline virtual void errors(const ModelT& model, std::vector& errors) const + { + errors.resize(_x1.cols()); + for (std::size_t sample = 0; sample < _x1.cols(); ++sample) + errors.at(sample) = error(sample, model); + } + + /** + * @brief get the number of putative points + * @return number of putative points + */ + inline std::size_t nbSamples() const { return _x1.cols(); } + + protected: + /// left corresponding point + const Mat& _x1; + /// right corresponding point + const Mat& _x2; + /// two view solver + const SolverT _kernelSolver = SolverT(); + /// solver error estimation + const ErrorT _errorEstimator = ErrorT(); + /// R rotation matrix + const Mat3 _R; }; } // namespace multiview diff --git a/src/aliceVision/multiview/knownRotationTranslationKernel_test.cpp b/src/aliceVision/multiview/knownRotationTranslationKernel_test.cpp index 1e27711383..d9da1ef2d0 100644 --- a/src/aliceVision/multiview/knownRotationTranslationKernel_test.cpp +++ b/src/aliceVision/multiview/knownRotationTranslationKernel_test.cpp @@ -23,39 +23,40 @@ using namespace aliceVision; // use a 2 correspondences based solver BOOST_AUTO_TEST_CASE(TranslationFromKnowRotationKernel_Multiview) { - const int nViews = 10; - const int nbPoints = 2; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // suppose a camera with Unit matrix as K - - // solve the problem and check that fitted value are good enough - for(int nCameraIndex = 2; nCameraIndex < nViews; ++nCameraIndex) - { - const Mat x0 = d._x[0]; - const Mat xCam = d._x[nCameraIndex]; - // coordinates does not need to be normalized since we have used a unit K matrix. - - // compute GT (Ground Truth) motion - Mat3 R_GT; - Vec3 t_GT; - relativeCameraMotion(d._R[0], d._t[0], d._R[nCameraIndex], d._t[nCameraIndex], &R_GT, &t_GT); - - multiview::TranslationFromKnowRotationKernel<> kernel(x0, xCam, R_GT); - - std::size_t samples_[2]={0,1}; - std::vector samples(samples_, samples_ + 2); - std::vector> vec_t; - kernel.fit(samples, vec_t); - - BOOST_CHECK_EQUAL(1, vec_t.size()); - - // check that the fitted model is compatible with the data - // here the distance to the epipolar line is used - for (std::size_t i = 0; i < x0.cols(); ++i) { - BOOST_CHECK_SMALL(kernel.error(i, vec_t.at(0)), 1e-8); + const int nViews = 10; + const int nbPoints = 2; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // suppose a camera with Unit matrix as K + + // solve the problem and check that fitted value are good enough + for (int nCameraIndex = 2; nCameraIndex < nViews; ++nCameraIndex) + { + const Mat x0 = d._x[0]; + const Mat xCam = d._x[nCameraIndex]; + // coordinates does not need to be normalized since we have used a unit K matrix. + + // compute GT (Ground Truth) motion + Mat3 R_GT; + Vec3 t_GT; + relativeCameraMotion(d._R[0], d._t[0], d._R[nCameraIndex], d._t[nCameraIndex], &R_GT, &t_GT); + + multiview::TranslationFromKnowRotationKernel<> kernel(x0, xCam, R_GT); + + std::size_t samples_[2] = {0, 1}; + std::vector samples(samples_, samples_ + 2); + std::vector> vec_t; + kernel.fit(samples, vec_t); + + BOOST_CHECK_EQUAL(1, vec_t.size()); + + // check that the fitted model is compatible with the data + // here the distance to the epipolar line is used + for (std::size_t i = 0; i < x0.cols(); ++i) + { + BOOST_CHECK_SMALL(kernel.error(i, vec_t.at(0)), 1e-8); + } + + // check that the GT translation and the estimated one are equal + EXPECT_MATRIX_NEAR(t_GT.normalized(), vec_t.at(0).getMatrix().normalized(), 1e-8); } - - // check that the GT translation and the estimated one are equal - EXPECT_MATRIX_NEAR(t_GT.normalized(), vec_t.at(0).getMatrix().normalized(), 1e-8); - } } diff --git a/src/aliceVision/multiview/relativePose/Essential5PSolver.cpp b/src/aliceVision/multiview/relativePose/Essential5PSolver.cpp index a0f9a94dd1..55787705ae 100644 --- a/src/aliceVision/multiview/relativePose/Essential5PSolver.cpp +++ b/src/aliceVision/multiview/relativePose/Essential5PSolver.cpp @@ -15,74 +15,48 @@ namespace relativePose { Vec o1(const Vec& a, const Vec& b) { - Vec res = Vec::Zero(20); - - res(Pc::coef_xx) = a(Pc::coef_x) * b(Pc::coef_x); - res(Pc::coef_xy) = a(Pc::coef_x) * b(Pc::coef_y) - + a(Pc::coef_y) * b(Pc::coef_x); - res(Pc::coef_xz) = a(Pc::coef_x) * b(Pc::coef_z) - + a(Pc::coef_z) * b(Pc::coef_x); - res(Pc::coef_yy) = a(Pc::coef_y) * b(Pc::coef_y); - res(Pc::coef_yz) = a(Pc::coef_y) * b(Pc::coef_z) - + a(Pc::coef_z) * b(Pc::coef_y); - res(Pc::coef_zz) = a(Pc::coef_z) * b(Pc::coef_z); - res(Pc::coef_x) = a(Pc::coef_x) * b(Pc::coef_1) - + a(Pc::coef_1) * b(Pc::coef_x); - res(Pc::coef_y) = a(Pc::coef_y) * b(Pc::coef_1) - + a(Pc::coef_1) * b(Pc::coef_y); - res(Pc::coef_z) = a(Pc::coef_z) * b(Pc::coef_1) - + a(Pc::coef_1) * b(Pc::coef_z); - res(Pc::coef_1) = a(Pc::coef_1) * b(Pc::coef_1); - - return res; + Vec res = Vec::Zero(20); + + res(Pc::coef_xx) = a(Pc::coef_x) * b(Pc::coef_x); + res(Pc::coef_xy) = a(Pc::coef_x) * b(Pc::coef_y) + a(Pc::coef_y) * b(Pc::coef_x); + res(Pc::coef_xz) = a(Pc::coef_x) * b(Pc::coef_z) + a(Pc::coef_z) * b(Pc::coef_x); + res(Pc::coef_yy) = a(Pc::coef_y) * b(Pc::coef_y); + res(Pc::coef_yz) = a(Pc::coef_y) * b(Pc::coef_z) + a(Pc::coef_z) * b(Pc::coef_y); + res(Pc::coef_zz) = a(Pc::coef_z) * b(Pc::coef_z); + res(Pc::coef_x) = a(Pc::coef_x) * b(Pc::coef_1) + a(Pc::coef_1) * b(Pc::coef_x); + res(Pc::coef_y) = a(Pc::coef_y) * b(Pc::coef_1) + a(Pc::coef_1) * b(Pc::coef_y); + res(Pc::coef_z) = a(Pc::coef_z) * b(Pc::coef_1) + a(Pc::coef_1) * b(Pc::coef_z); + res(Pc::coef_1) = a(Pc::coef_1) * b(Pc::coef_1); + + return res; } Vec o2(const Vec& a, const Vec& b) { - Vec res(20); - - res(Pc::coef_xxx) = a(Pc::coef_xx) * b(Pc::coef_x); - res(Pc::coef_xxy) = a(Pc::coef_xx) * b(Pc::coef_y) - + a(Pc::coef_xy) * b(Pc::coef_x); - res(Pc::coef_xxz) = a(Pc::coef_xx) * b(Pc::coef_z) - + a(Pc::coef_xz) * b(Pc::coef_x); - res(Pc::coef_xyy) = a(Pc::coef_xy) * b(Pc::coef_y) - + a(Pc::coef_yy) * b(Pc::coef_x); - res(Pc::coef_xyz) = a(Pc::coef_xy) * b(Pc::coef_z) - + a(Pc::coef_yz) * b(Pc::coef_x) - + a(Pc::coef_xz) * b(Pc::coef_y); - res(Pc::coef_xzz) = a(Pc::coef_xz) * b(Pc::coef_z) - + a(Pc::coef_zz) * b(Pc::coef_x); - res(Pc::coef_yyy) = a(Pc::coef_yy) * b(Pc::coef_y); - res(Pc::coef_yyz) = a(Pc::coef_yy) * b(Pc::coef_z) - + a(Pc::coef_yz) * b(Pc::coef_y); - res(Pc::coef_yzz) = a(Pc::coef_yz) * b(Pc::coef_z) - + a(Pc::coef_zz) * b(Pc::coef_y); - res(Pc::coef_zzz) = a(Pc::coef_zz) * b(Pc::coef_z); - res(Pc::coef_xx) = a(Pc::coef_xx) * b(Pc::coef_1) - + a(Pc::coef_x) * b(Pc::coef_x); - res(Pc::coef_xy) = a(Pc::coef_xy) * b(Pc::coef_1) - + a(Pc::coef_x) * b(Pc::coef_y) - + a(Pc::coef_y) * b(Pc::coef_x); - res(Pc::coef_xz) = a(Pc::coef_xz) * b(Pc::coef_1) - + a(Pc::coef_x) * b(Pc::coef_z) - + a(Pc::coef_z) * b(Pc::coef_x); - res(Pc::coef_yy) = a(Pc::coef_yy) * b(Pc::coef_1) - + a(Pc::coef_y) * b(Pc::coef_y); - res(Pc::coef_yz) = a(Pc::coef_yz) * b(Pc::coef_1) - + a(Pc::coef_y) * b(Pc::coef_z) - + a(Pc::coef_z) * b(Pc::coef_y); - res(Pc::coef_zz) = a(Pc::coef_zz) * b(Pc::coef_1) - + a(Pc::coef_z) * b(Pc::coef_z); - res(Pc::coef_x) = a(Pc::coef_x) * b(Pc::coef_1) - + a(Pc::coef_1) * b(Pc::coef_x); - res(Pc::coef_y) = a(Pc::coef_y) * b(Pc::coef_1) - + a(Pc::coef_1) * b(Pc::coef_y); - res(Pc::coef_z) = a(Pc::coef_z) * b(Pc::coef_1) - + a(Pc::coef_1) * b(Pc::coef_z); - res(Pc::coef_1) = a(Pc::coef_1) * b(Pc::coef_1); - - return res; + Vec res(20); + + res(Pc::coef_xxx) = a(Pc::coef_xx) * b(Pc::coef_x); + res(Pc::coef_xxy) = a(Pc::coef_xx) * b(Pc::coef_y) + a(Pc::coef_xy) * b(Pc::coef_x); + res(Pc::coef_xxz) = a(Pc::coef_xx) * b(Pc::coef_z) + a(Pc::coef_xz) * b(Pc::coef_x); + res(Pc::coef_xyy) = a(Pc::coef_xy) * b(Pc::coef_y) + a(Pc::coef_yy) * b(Pc::coef_x); + res(Pc::coef_xyz) = a(Pc::coef_xy) * b(Pc::coef_z) + a(Pc::coef_yz) * b(Pc::coef_x) + a(Pc::coef_xz) * b(Pc::coef_y); + res(Pc::coef_xzz) = a(Pc::coef_xz) * b(Pc::coef_z) + a(Pc::coef_zz) * b(Pc::coef_x); + res(Pc::coef_yyy) = a(Pc::coef_yy) * b(Pc::coef_y); + res(Pc::coef_yyz) = a(Pc::coef_yy) * b(Pc::coef_z) + a(Pc::coef_yz) * b(Pc::coef_y); + res(Pc::coef_yzz) = a(Pc::coef_yz) * b(Pc::coef_z) + a(Pc::coef_zz) * b(Pc::coef_y); + res(Pc::coef_zzz) = a(Pc::coef_zz) * b(Pc::coef_z); + res(Pc::coef_xx) = a(Pc::coef_xx) * b(Pc::coef_1) + a(Pc::coef_x) * b(Pc::coef_x); + res(Pc::coef_xy) = a(Pc::coef_xy) * b(Pc::coef_1) + a(Pc::coef_x) * b(Pc::coef_y) + a(Pc::coef_y) * b(Pc::coef_x); + res(Pc::coef_xz) = a(Pc::coef_xz) * b(Pc::coef_1) + a(Pc::coef_x) * b(Pc::coef_z) + a(Pc::coef_z) * b(Pc::coef_x); + res(Pc::coef_yy) = a(Pc::coef_yy) * b(Pc::coef_1) + a(Pc::coef_y) * b(Pc::coef_y); + res(Pc::coef_yz) = a(Pc::coef_yz) * b(Pc::coef_1) + a(Pc::coef_y) * b(Pc::coef_z) + a(Pc::coef_z) * b(Pc::coef_y); + res(Pc::coef_zz) = a(Pc::coef_zz) * b(Pc::coef_1) + a(Pc::coef_z) * b(Pc::coef_z); + res(Pc::coef_x) = a(Pc::coef_x) * b(Pc::coef_1) + a(Pc::coef_1) * b(Pc::coef_x); + res(Pc::coef_y) = a(Pc::coef_y) * b(Pc::coef_1) + a(Pc::coef_1) * b(Pc::coef_y); + res(Pc::coef_z) = a(Pc::coef_z) * b(Pc::coef_1) + a(Pc::coef_1) * b(Pc::coef_z); + res(Pc::coef_1) = a(Pc::coef_1) * b(Pc::coef_1); + + return res; } /** @@ -90,11 +64,11 @@ Vec o2(const Vec& a, const Vec& b) */ Mat fivePointsNullspaceBasis(const Mat2X& x1, const Mat2X& x2) { - Eigen::Matrix A; - A.setZero(); // make A square until Eigen supports rectangular SVD. - encodeEpipolarEquation(x1, x2, &A); - Eigen::JacobiSVD > svd(A,Eigen::ComputeFullV); - return svd.matrixV().topRightCorner<9,4>(); + Eigen::Matrix A; + A.setZero(); // make A square until Eigen supports rectangular SVD. + encodeEpipolarEquation(x1, x2, &A); + Eigen::JacobiSVD> svd(A, Eigen::ComputeFullV); + return svd.matrixV().topRightCorner<9, 4>(); } /** @@ -102,106 +76,111 @@ Mat fivePointsNullspaceBasis(const Mat2X& x1, const Mat2X& x2) */ Mat fivePointsPolynomialConstraints(const Mat& EBasis) { - // build the polynomial form of E (equation (8) in Stewenius et al. [1]) - Vec E[3][3]; - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - E[i][j] = Vec::Zero(20); - E[i][j](Pc::coef_x) = EBasis(3 * i + j, 0); - E[i][j](Pc::coef_y) = EBasis(3 * i + j, 1); - E[i][j](Pc::coef_z) = EBasis(3 * i + j, 2); - E[i][j](Pc::coef_1) = EBasis(3 * i + j, 3); + // build the polynomial form of E (equation (8) in Stewenius et al. [1]) + Vec E[3][3]; + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + E[i][j] = Vec::Zero(20); + E[i][j](Pc::coef_x) = EBasis(3 * i + j, 0); + E[i][j](Pc::coef_y) = EBasis(3 * i + j, 1); + E[i][j](Pc::coef_z) = EBasis(3 * i + j, 2); + E[i][j](Pc::coef_1) = EBasis(3 * i + j, 3); + } } - } - - // the constraint matrix. - Mat M(10, 20); - int mrow = 0; - - // determinant constraint det(E) = 0; equation (19) of Nister [2]. - M.row(mrow++) = o2(o1(E[0][1], E[1][2]) - o1(E[0][2], E[1][1]), E[2][0]) + - o2(o1(E[0][2], E[1][0]) - o1(E[0][0], E[1][2]), E[2][1]) + - o2(o1(E[0][0], E[1][1]) - o1(E[0][1], E[1][0]), E[2][2]); - - // cubic singular values constraint. - // equation (20). - Vec EET[3][3]; - for (int i = 0; i < 3; ++i) { // since EET is symmetric, we only compute - for (int j = 0; j < 3; ++j) { // its upper triangular part. - if (i <= j) { - EET[i][j] = o1(E[i][0], E[j][0]) - + o1(E[i][1], E[j][1]) - + o1(E[i][2], E[j][2]); - } else { - EET[i][j] = EET[j][i]; - } + + // the constraint matrix. + Mat M(10, 20); + int mrow = 0; + + // determinant constraint det(E) = 0; equation (19) of Nister [2]. + M.row(mrow++) = o2(o1(E[0][1], E[1][2]) - o1(E[0][2], E[1][1]), E[2][0]) + o2(o1(E[0][2], E[1][0]) - o1(E[0][0], E[1][2]), E[2][1]) + + o2(o1(E[0][0], E[1][1]) - o1(E[0][1], E[1][0]), E[2][2]); + + // cubic singular values constraint. + // equation (20). + Vec EET[3][3]; + for (int i = 0; i < 3; ++i) + { // since EET is symmetric, we only compute + for (int j = 0; j < 3; ++j) + { // its upper triangular part. + if (i <= j) + { + EET[i][j] = o1(E[i][0], E[j][0]) + o1(E[i][1], E[j][1]) + o1(E[i][2], E[j][2]); + } + else + { + EET[i][j] = EET[j][i]; + } + } } - } - - // equation (21). - Vec (&L)[3][3] = EET; - Vec trace = 0.5 * (EET[0][0] + EET[1][1] + EET[2][2]); - for (int i = 0; i < 3; ++i) { - L[i][i] -= trace; - } - - // equation (23). - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - Vec LEij = o2(L[i][0], E[0][j]) - + o2(L[i][1], E[1][j]) - + o2(L[i][2], E[2][j]); - M.row(mrow++) = LEij; + + // equation (21). + Vec(&L)[3][3] = EET; + Vec trace = 0.5 * (EET[0][0] + EET[1][1] + EET[2][2]); + for (int i = 0; i < 3; ++i) + { + L[i][i] -= trace; } - } - return M; + // equation (23). + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + Vec LEij = o2(L[i][0], E[0][j]) + o2(L[i][1], E[1][j]) + o2(L[i][2], E[2][j]); + M.row(mrow++) = LEij; + } + } + + return M; } void Essential5PSolver::solve(const Mat& x1, const Mat& x2, std::vector& models) const { - assert(2 == x1.rows()); - assert(5 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - // step 1: Nullspace Extraction. - const Eigen::Matrix EBasis = fivePointsNullspaceBasis(x1, x2); - - // step 2: Constraint Expansion. - const Eigen::Matrix EConstraints = fivePointsPolynomialConstraints(EBasis); - - // step 3: Gauss-Jordan Elimination (done thanks to a LU decomposition). - typedef Eigen::Matrix Mat10; - Eigen::FullPivLU c_lu(EConstraints.block<10, 10>(0, 0)); - const Mat10 M = c_lu.solve(EConstraints.block<10, 10>(0, 10)); - - // for next steps we follow the matlab code given in Stewenius et al [1]. - // build action matrix. - const Mat10& B = M.topRightCorner<10,10>(); - Mat10 At = Mat10::Zero(10,10); - At.block<3, 10>(0, 0) = B.block<3, 10>(0, 0); - At.row(3) = B.row(4); - At.row(4) = B.row(5); - At.row(5) = B.row(7); - At(6,0) = At(7,1) = At(8,3) = At(9,6) = -1; - - Eigen::EigenSolver eigensolver(At); - const auto& eigenvectors = eigensolver.eigenvectors(); - const auto& eigenvalues = eigensolver.eigenvalues(); - - // build essential matrices for the real solutions. - models.reserve(10); - for(int s = 0; s < 10; ++s) - { - // only consider real solutions. - if(eigenvalues(s).imag() != 0) - continue; - - Mat3 E; - Eigen::Map(E.data()) = EBasis * eigenvectors.col(s).tail<4>().real(); - models.emplace_back(E.transpose()); - } + assert(2 == x1.rows()); + assert(5 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + // step 1: Nullspace Extraction. + const Eigen::Matrix EBasis = fivePointsNullspaceBasis(x1, x2); + + // step 2: Constraint Expansion. + const Eigen::Matrix EConstraints = fivePointsPolynomialConstraints(EBasis); + + // step 3: Gauss-Jordan Elimination (done thanks to a LU decomposition). + typedef Eigen::Matrix Mat10; + Eigen::FullPivLU c_lu(EConstraints.block<10, 10>(0, 0)); + const Mat10 M = c_lu.solve(EConstraints.block<10, 10>(0, 10)); + + // for next steps we follow the matlab code given in Stewenius et al [1]. + // build action matrix. + const Mat10& B = M.topRightCorner<10, 10>(); + Mat10 At = Mat10::Zero(10, 10); + At.block<3, 10>(0, 0) = B.block<3, 10>(0, 0); + At.row(3) = B.row(4); + At.row(4) = B.row(5); + At.row(5) = B.row(7); + At(6, 0) = At(7, 1) = At(8, 3) = At(9, 6) = -1; + + Eigen::EigenSolver eigensolver(At); + const auto& eigenvectors = eigensolver.eigenvectors(); + const auto& eigenvalues = eigensolver.eigenvalues(); + + // build essential matrices for the real solutions. + models.reserve(10); + for (int s = 0; s < 10; ++s) + { + // only consider real solutions. + if (eigenvalues(s).imag() != 0) + continue; + + Mat3 E; + Eigen::Map(E.data()) = EBasis * eigenvectors.col(s).tail<4>().real(); + models.emplace_back(E.transpose()); + } } } // namespace relativePose diff --git a/src/aliceVision/multiview/relativePose/Essential5PSolver.hpp b/src/aliceVision/multiview/relativePose/Essential5PSolver.hpp index 937ae01648..870d317a64 100644 --- a/src/aliceVision/multiview/relativePose/Essential5PSolver.hpp +++ b/src/aliceVision/multiview/relativePose/Essential5PSolver.hpp @@ -16,45 +16,38 @@ namespace relativePose { class Essential5PSolver : public robustEstimation::ISolver { -public: + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 5; } - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 5; - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 10; } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 10; - } + /** + * @brief Computes the relative pose of two calibrated cameras from 5 correspondences. + * @param[in] x1 Points in the first image. One per column. + * @param[in] x2 Corresponding points in the second image. One per column. + * @param[out] models A list of at most 10 candidate essential matrix solutions. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - /** - * @brief Computes the relative pose of two calibrated cameras from 5 correspondences. - * @param[in] x1 Points in the first image. One per column. - * @param[in] x2 Corresponding points in the second image. One per column. - * @param[out] models A list of at most 10 candidate essential matrix solutions. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("Essential5PSolver does not support problem solving with weights."); - } + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("Essential5PSolver does not support problem solving with weights."); + } }; /** @@ -72,27 +65,28 @@ class Essential5PSolver : public robustEstimation::ISolver& models) const { - assert(2 == x1.rows()); - assert(8 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - MatX9 A(x1.cols(), 9); - encodeEpipolarEquation(x1, x2, &A); - - Vec9 e; - Nullspace(A, e); - Mat3 E = Map(e.data()); - - // Find the closest essential matrix to E in frobenius norm - // E = UD'VT - if (x1.cols() > 8) - { - Eigen::JacobiSVD USV(E, Eigen::ComputeFullU | Eigen::ComputeFullV); - Vec3 d = USV.singularValues(); - const double a = d[0]; - const double b = d[1]; - d << (a+b)/2., (a+b)/2., 0.0; - E = USV.matrixU() * d.asDiagonal() * USV.matrixV().transpose(); - } - models.emplace_back(E); + assert(2 == x1.rows()); + assert(8 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + MatX9 A(x1.cols(), 9); + encodeEpipolarEquation(x1, x2, &A); + + Vec9 e; + Nullspace(A, e); + Mat3 E = Map(e.data()); + + // Find the closest essential matrix to E in frobenius norm + // E = UD'VT + if (x1.cols() > 8) + { + Eigen::JacobiSVD USV(E, Eigen::ComputeFullU | Eigen::ComputeFullV); + Vec3 d = USV.singularValues(); + const double a = d[0]; + const double b = d[1]; + d << (a + b) / 2., (a + b) / 2., 0.0; + E = USV.matrixU() * d.asDiagonal() * USV.matrixV().transpose(); + } + models.emplace_back(E); } } // namespace relativePose } // namespace multiview } // namespace aliceVision - diff --git a/src/aliceVision/multiview/relativePose/Essential8PSolver.hpp b/src/aliceVision/multiview/relativePose/Essential8PSolver.hpp index e817846d52..57575a0553 100644 --- a/src/aliceVision/multiview/relativePose/Essential8PSolver.hpp +++ b/src/aliceVision/multiview/relativePose/Essential8PSolver.hpp @@ -16,48 +16,41 @@ namespace relativePose { class Essential8PSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 8; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } - - /** - * @brief Eight-point algorithm for solving for the essential matrix from normalized - * image coordinates of point correspondences. - * See page 294 in HZ Result 11.1. - * - * @param[in] x1 Points in the first image. One per column. - * @param[in] x2 Corresponding points in the second image. One per column. - * @param[out] models A list of at most 10 candidate essential matrix solutions. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("Essential8PSolver does not support problem solving with weights."); - } + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 8; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } + + /** + * @brief Eight-point algorithm for solving for the essential matrix from normalized + * image coordinates of point correspondences. + * See page 294 in HZ Result 11.1. + * + * @param[in] x1 Points in the first image. One per column. + * @param[in] x2 Corresponding points in the second image. One per column. + * @param[out] models A list of at most 10 candidate essential matrix solutions. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; + + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("Essential8PSolver does not support problem solving with weights."); + } }; } // namespace relativePose diff --git a/src/aliceVision/multiview/relativePose/EssentialKernel.hpp b/src/aliceVision/multiview/relativePose/EssentialKernel.hpp index fc1b0a4667..1be27f5fc8 100644 --- a/src/aliceVision/multiview/relativePose/EssentialKernel.hpp +++ b/src/aliceVision/multiview/relativePose/EssentialKernel.hpp @@ -29,63 +29,60 @@ namespace relativePose { template class EssentialKernel : public robustEstimation::PointFittingKernel { -public: - - using KernelBase = robustEstimation::PointFittingKernel; - - EssentialKernel(const Mat& x1, const Mat& x2, const Mat3& K1, const Mat3& K2) - : robustEstimation::PointFittingKernel(x1,x2) - , _K1(K1) - , _K2(K2) - {} - - void fit(const std::vector& samples, std::vector& models) const override - { - const Mat x1 = buildSubsetMatrix(KernelBase::_x1, samples); - const Mat x2 = buildSubsetMatrix(KernelBase::_x2, samples); - - assert(2 == x1.rows()); - assert(KernelBase::_kernelSolver.getMinimumNbRequiredSamples() <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - // normalize the data (image coords to camera coords). - const Mat3 K1Inverse = _K1.inverse(); - const Mat3 K2Inverse = _K2.inverse(); - - Mat x1_normalized; - Mat x2_normalized; - - robustEstimation::applyTransformationToPoints(x1, K1Inverse, &x1_normalized); - robustEstimation::applyTransformationToPoints(x2, K2Inverse, &x2_normalized); - - KernelBase::_kernelSolver.solve(x1_normalized, x2_normalized, models); - } - - double error(std::size_t sample, const ModelT& model) const - { - Mat3 F; - fundamentalFromEssential(model.getMatrix(), _K1, _K2, &F); - robustEstimation::Mat3Model modelF(F); - return KernelBase::_errorEstimator.error(modelF, KernelBase::_x1.col(sample), KernelBase::_x2.col(sample)); - } - -protected: - - // The two camera calibrated camera matrix - Mat3 _K1, _K2; + public: + using KernelBase = robustEstimation::PointFittingKernel; + + EssentialKernel(const Mat& x1, const Mat& x2, const Mat3& K1, const Mat3& K2) + : robustEstimation::PointFittingKernel(x1, x2), + _K1(K1), + _K2(K2) + {} + + void fit(const std::vector& samples, std::vector& models) const override + { + const Mat x1 = buildSubsetMatrix(KernelBase::_x1, samples); + const Mat x2 = buildSubsetMatrix(KernelBase::_x2, samples); + + assert(2 == x1.rows()); + assert(KernelBase::_kernelSolver.getMinimumNbRequiredSamples() <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + // normalize the data (image coords to camera coords). + const Mat3 K1Inverse = _K1.inverse(); + const Mat3 K2Inverse = _K2.inverse(); + + Mat x1_normalized; + Mat x2_normalized; + + robustEstimation::applyTransformationToPoints(x1, K1Inverse, &x1_normalized); + robustEstimation::applyTransformationToPoints(x2, K2Inverse, &x2_normalized); + + KernelBase::_kernelSolver.solve(x1_normalized, x2_normalized, models); + } + + double error(std::size_t sample, const ModelT& model) const + { + Mat3 F; + fundamentalFromEssential(model.getMatrix(), _K1, _K2, &F); + robustEstimation::Mat3Model modelF(F); + return KernelBase::_errorEstimator.error(modelF, KernelBase::_x1.col(sample), KernelBase::_x2.col(sample)); + } + + protected: + // The two camera calibrated camera matrix + Mat3 _K1, _K2; }; /** * @brief Solver kernel for the 8pt Essential Matrix Estimation */ -typedef EssentialKernel Essential8PKernel; +typedef EssentialKernel Essential8PKernel; /** * @brief Solver kernel for the 5pt Essential Matrix Estimation */ -typedef EssentialKernel Essential5PKernel; - +typedef EssentialKernel Essential5PKernel; } // namespace relativePose } // namespace multiview diff --git a/src/aliceVision/multiview/relativePose/Fundamental10PSolver.cpp b/src/aliceVision/multiview/relativePose/Fundamental10PSolver.cpp index 20f274c4b4..2778a6be15 100644 --- a/src/aliceVision/multiview/relativePose/Fundamental10PSolver.cpp +++ b/src/aliceVision/multiview/relativePose/Fundamental10PSolver.cpp @@ -18,180 +18,296 @@ namespace relativePose { * @brief Computes column echelon form of a matrix * @author Jan Heller, adapted to aliceVision by Michal Polic */ -template +template void colEchelonForm(Eigen::MatrixBase& M, double pivtol = 1e-12) { - using Scalar = typename Derived::Scalar; + using Scalar = typename Derived::Scalar; - const int n = M.rows(); - const int m = M.cols(); + const int n = M.rows(); + const int m = M.cols(); - Eigen::Index i = 0; - Eigen::Index j = 0; + Eigen::Index i = 0; + Eigen::Index j = 0; - while((i < m) && (j < n)) - { - Scalar p = std::numeric_limits::min(); - Eigen::Index col = i; - - for(Eigen::Index k = i; k < m; ++k) - { - Scalar tp = std::abs(M(j, k)); - if(tp > p) - { - p = tp; - col = k; - } - } - - if(p < Scalar(pivtol)) + while ((i < m) && (j < n)) { - M.block(j, i, 1, m - i).setZero(); - ++j; + Scalar p = std::numeric_limits::min(); + Eigen::Index col = i; + + for (Eigen::Index k = i; k < m; ++k) + { + Scalar tp = std::abs(M(j, k)); + if (tp > p) + { + p = tp; + col = k; + } + } + + if (p < Scalar(pivtol)) + { + M.block(j, i, 1, m - i).setZero(); + ++j; + } + else + { + if (col != i) + M.block(j, i, n - j, 1).swap(M.block(j, col, n - j, 1)); + + M.block(j + 1, i, n - j - 1, 1) /= M(j, i); + M(j, i) = 1.0; + + for (Eigen::Index k = 0; k < m; ++k) + { + if (k == i) + continue; + + M.block(j, k, n - j, 1) -= M(j, k) * M.block(j, i, n - j, 1); + } + + ++i; + ++j; + } } - else - { - if(col != i) - M.block(j, i, n - j, 1).swap(M.block(j, col, n - j, 1)); - - M.block(j + 1, i, n - j - 1, 1) /= M(j, i); - M(j, i) = 1.0; - - for(Eigen::Index k = 0; k < m; ++k) - { - if(k == i) - continue; - - M.block(j, k, n - j, 1) -= M(j, k) * M.block(j, i, n - j, 1); - } - - ++i; - ++j; - } - } } -template +template int f10e_gb(const Eigen::Matrix& params, Eigen::MatrixBase& sols, double pivtol = 1e-16) { - Eigen::Matrix c; - - c(0) = params(0) * params(14) - params(4) * params(10); - c(1) = params(0) * params(16) + params(2) * params(14) - params(4) * params(12) - params(6) * params(10); - c(2) = params(2) * params(18) - params(8) * params(12); - c(3) = params(1) * params(15) - params(5) * params(11); - c(4) = params(1) * params(17) + params(3) * params(15) - params(5) * params(13) - params(7) * params(11); - c(5) = params(0) * params(15) + params(1) * params(14) - params(4) * params(11) - params(5) * params(10); - c(6) = params(0) * params(17) + params(1) * params(16) + params(2) * params(15) + params(3) * params(14) - params(4) * params(13) - params(5) * params(12) - params(6) * params(11) - params(7) * params(10); - c(7) = params(0) * params(18) + params(2) * params(16) - params(6) * params(12) - params(8) * params(10); - c(8) = params(0) * params(19) + params(1) * params(18) + params(2) * params(17) + params(3) * params(16) - params(6) * params(13) - params(7) * params(12) - params(8) * params(11) - params(9) * params(10); - c(9) = params(2) * params(19) + params(3) * params(18) - params(8) * params(13) - params(9) * params(12); - c(10) = params(1) * params(19) + params(3) * params(17) - params(7) * params(13) - params(9) * params(11); - c(11) = params(3) * params(19) - params(9) * params(13); - c(12) = params(0) * params(23) - params(4) * params(20); - c(13) = params(1) * params(23) + params(0) * params(25) - params(4) * params(21) - params(5) * params(20); - c(14) = params(2) * params(24) - params(8) * params(20); - c(15) = params(3) * params(24) + params(2) * params(27) - params(8) * params(21) - params(9) * params(20); - c(16) = params(1) * params(26) - params(5) * params(22); - c(17) = params(0) * params(24) + params(2) * params(23) - params(6) * params(20); - c(18) = params(0) * params(26) + params(1) * params(25) - params(4) * params(22) - params(5) * params(21); - c(19) = params(1) * params(24) + params(3) * params(23) + params(0) * params(27) + params(2) * params(25) - params(6) * params(21) - params(7) * params(20); - c(20) = params(0) * params(28) + params(1) * params(27) + params(2) * params(26) + params(3) * params(25) - params(6) * params(22) - params(7) * params(21); - c(21) = params(2) * params(28) + params(3) * params(27) - params(8) * params(22) - params(9) * params(21); - c(22) = params(1) * params(28) + params(3) * params(26) - params(7) * params(22); - c(23) = params(3) * params(28) - params(9) * params(22); - c(24) = params(10) * params(23) - params(14) * params(20); - c(25) = params(11) * params(23) + params(10) * params(25) - params(14) * params(21) - params(15) * params(20); - c(26) = params(12) * params(24) - params(18) * params(20); - c(27) = params(13) * params(24) + params(12) * params(27) - params(18) * params(21) - params(19) * params(20); - c(28) = params(11) * params(26) - params(15) * params(22); - c(29) = params(10) * params(24) + params(12) * params(23) - params(16) * params(20); - c(30) = params(10) * params(26) + params(11) * params(25) - params(14) * params(22) - params(15) * params(21); - c(31) = params(11) * params(24) + params(13) * params(23) + params(10) * params(27) + params(12) * params(25) - params(16) * params(21) - params(17) * params(20); - c(32) = params(10) * params(28) + params(11) * params(27) + params(12) * params(26) + params(13) * params(25) - params(16) * params(22) - params(17) * params(21); - c(33) = params(12) * params(28) + params(13) * params(27) - params(18) * params(22) - params(19) * params(21); - c(34) = params(11) * params(28) + params(13) * params(26) - params(17) * params(22); - c(35) = params(13) * params(28) - params(19) * params(22); - - Eigen::Matrix M; - M.setZero(); - - M(0) = c(0); M(61) = c(0); M(82) = c(0); - M(144) = c(0); M(2) = c(1); M(64) = c(1); - M(85) = c(1); M(148) = c(1); M(9) = c(2); - M(72) = c(2); M(93) = c(2); M(156) = c(2); - M(3) = c(3); M(66) = c(3); M(87) = c(3); - M(150) = c(3); M(7) = c(4); M(70) = c(4); - M(91) = c(4); M(154) = c(4); M(1) = c(5); - M(63) = c(5); M(84) = c(5); M(147) = c(5); - M(4) = c(6); M(67) = c(6); M(88) = c(6); - M(151) = c(6); M(5) = c(7); M(68) = c(7); - M(89) = c(7); M(152) = c(7); M(8) = c(8); - M(71) = c(8); M(92) = c(8); M(155) = c(8); - M(12) = c(9); M(75) = c(9); M(96) = c(9); - M(158) = c(9); M(11) = c(10); M(74) = c(10); - M(95) = c(10); M(157) = c(10); M(15) = c(11); - M(77) = c(11); M(98) = c(11); M(159) = c(11); - M(20) = c(12); M(102) = c(12); M(165) = c(12); - M(21) = c(13); M(104) = c(13); M(168) = c(13); - M(25) = c(14); M(109) = c(14); M(173) = c(14); - M(28) = c(15); M(112) = c(15); M(176) = c(15); - M(26) = c(16); M(110) = c(16); M(174) = c(16); - M(22) = c(17); M(105) = c(17); M(169) = c(17); - M(23) = c(18); M(107) = c(18); M(171) = c(18); - M(24) = c(19); M(108) = c(19); M(172) = c(19); - M(27) = c(20); M(111) = c(20); M(175) = c(20); - M(31) = c(21); M(115) = c(21); M(178) = c(21); - M(30) = c(22); M(114) = c(22); M(177) = c(22); - M(34) = c(23); M(117) = c(23); M(179) = c(23); - M(40) = c(24); M(122) = c(24); M(185) = c(24); - M(41) = c(25); M(124) = c(25); M(188) = c(25); - M(45) = c(26); M(129) = c(26); M(193) = c(26); - M(48) = c(27); M(132) = c(27); M(196) = c(27); - M(46) = c(28); M(130) = c(28); M(194) = c(28); - M(42) = c(29); M(125) = c(29); M(189) = c(29); - M(43) = c(30); M(127) = c(30); M(191) = c(30); - M(44) = c(31); M(128) = c(31); M(192) = c(31); - M(47) = c(32); M(131) = c(32); M(195) = c(32); - M(51) = c(33); M(135) = c(33); M(198) = c(33); - M(50) = c(34); M(134) = c(34); M(197) = c(34); - M(54) = c(35); M(137) = c(35); M(199) = c(35); - - colEchelonForm(M, pivtol); - - Eigen::Matrix A; - A.setZero(); - - A(0, 2) = 1.000000; A(1, 4) = 1.000000; A(2, 5) = 1.000000; - A(3, 7) = 1.000000; A(4, 8) = 1.000000; A(5, 9) = 1.000000; - A(6, 0) = -M(19, 9); A(6, 1) = -M(18, 9); A(6, 2) = -M(17, 9); - A(6, 3) = -M(16, 9); A(6, 4) = -M(15, 9); A(6, 5) = -M(14, 9); - A(6, 6) = -M(13, 9); A(6, 7) = -M(12, 9); A(6, 8) = -M(11, 9); - A(6, 9) = -M(10, 9); A(7, 0) = -M(19, 8); A(7, 1) = -M(18, 8); - A(7, 2) = -M(17, 8); A(7, 3) = -M(16, 8); A(7, 4) = -M(15, 8); - A(7, 5) = -M(14, 8); A(7, 6) = -M(13, 8); A(7, 7) = -M(12, 8); - A(7, 8) = -M(11, 8); A(7, 9) = -M(10, 8); A(8, 0) = -M(19, 7); - A(8, 1) = -M(18, 7); A(8, 2) = -M(17, 7); A(8, 3) = -M(16, 7); - A(8, 4) = -M(15, 7); A(8, 5) = -M(14, 7); A(8, 6) = -M(13, 7); - A(8, 7) = -M(12, 7); A(8, 8) = -M(11, 7); A(8, 9) = -M(10, 7); - A(9, 0) = -M(19, 6); A(9, 1) = -M(18, 6); A(9, 2) = -M(17, 6); - A(9, 3) = -M(16, 6); A(9, 4) = -M(15, 6); A(9, 5) = -M(14, 6); - A(9, 6) = -M(13, 6); A(9, 7) = -M(12, 6); A(9, 8) = -M(11, 6); - A(9, 9) = -M(10, 6); - - const Eigen::EigenSolver > eig(A); - Eigen::Matrix, 10, 2> esols; - esols.col(0).array() = eig.eigenvectors().row(2).array() / eig.eigenvectors().row(0).array(); - esols.col(1).array() = eig.eigenvectors().row(1).array() / eig.eigenvectors().row(0).array(); - - int nsols = 0; - for(Eigen::Index i = 0; i < 10; ++i) - { - if(esols.row(i).imag().isZero(100.0 * std::numeric_limits::epsilon())) - sols.col(nsols++) = esols.row(i).real(); - } - - return nsols; + Eigen::Matrix c; + + c(0) = params(0) * params(14) - params(4) * params(10); + c(1) = params(0) * params(16) + params(2) * params(14) - params(4) * params(12) - params(6) * params(10); + c(2) = params(2) * params(18) - params(8) * params(12); + c(3) = params(1) * params(15) - params(5) * params(11); + c(4) = params(1) * params(17) + params(3) * params(15) - params(5) * params(13) - params(7) * params(11); + c(5) = params(0) * params(15) + params(1) * params(14) - params(4) * params(11) - params(5) * params(10); + c(6) = params(0) * params(17) + params(1) * params(16) + params(2) * params(15) + params(3) * params(14) - params(4) * params(13) - + params(5) * params(12) - params(6) * params(11) - params(7) * params(10); + c(7) = params(0) * params(18) + params(2) * params(16) - params(6) * params(12) - params(8) * params(10); + c(8) = params(0) * params(19) + params(1) * params(18) + params(2) * params(17) + params(3) * params(16) - params(6) * params(13) - + params(7) * params(12) - params(8) * params(11) - params(9) * params(10); + c(9) = params(2) * params(19) + params(3) * params(18) - params(8) * params(13) - params(9) * params(12); + c(10) = params(1) * params(19) + params(3) * params(17) - params(7) * params(13) - params(9) * params(11); + c(11) = params(3) * params(19) - params(9) * params(13); + c(12) = params(0) * params(23) - params(4) * params(20); + c(13) = params(1) * params(23) + params(0) * params(25) - params(4) * params(21) - params(5) * params(20); + c(14) = params(2) * params(24) - params(8) * params(20); + c(15) = params(3) * params(24) + params(2) * params(27) - params(8) * params(21) - params(9) * params(20); + c(16) = params(1) * params(26) - params(5) * params(22); + c(17) = params(0) * params(24) + params(2) * params(23) - params(6) * params(20); + c(18) = params(0) * params(26) + params(1) * params(25) - params(4) * params(22) - params(5) * params(21); + c(19) = params(1) * params(24) + params(3) * params(23) + params(0) * params(27) + params(2) * params(25) - params(6) * params(21) - + params(7) * params(20); + c(20) = params(0) * params(28) + params(1) * params(27) + params(2) * params(26) + params(3) * params(25) - params(6) * params(22) - + params(7) * params(21); + c(21) = params(2) * params(28) + params(3) * params(27) - params(8) * params(22) - params(9) * params(21); + c(22) = params(1) * params(28) + params(3) * params(26) - params(7) * params(22); + c(23) = params(3) * params(28) - params(9) * params(22); + c(24) = params(10) * params(23) - params(14) * params(20); + c(25) = params(11) * params(23) + params(10) * params(25) - params(14) * params(21) - params(15) * params(20); + c(26) = params(12) * params(24) - params(18) * params(20); + c(27) = params(13) * params(24) + params(12) * params(27) - params(18) * params(21) - params(19) * params(20); + c(28) = params(11) * params(26) - params(15) * params(22); + c(29) = params(10) * params(24) + params(12) * params(23) - params(16) * params(20); + c(30) = params(10) * params(26) + params(11) * params(25) - params(14) * params(22) - params(15) * params(21); + c(31) = params(11) * params(24) + params(13) * params(23) + params(10) * params(27) + params(12) * params(25) - params(16) * params(21) - + params(17) * params(20); + c(32) = params(10) * params(28) + params(11) * params(27) + params(12) * params(26) + params(13) * params(25) - params(16) * params(22) - + params(17) * params(21); + c(33) = params(12) * params(28) + params(13) * params(27) - params(18) * params(22) - params(19) * params(21); + c(34) = params(11) * params(28) + params(13) * params(26) - params(17) * params(22); + c(35) = params(13) * params(28) - params(19) * params(22); + + Eigen::Matrix M; + M.setZero(); + + M(0) = c(0); + M(61) = c(0); + M(82) = c(0); + M(144) = c(0); + M(2) = c(1); + M(64) = c(1); + M(85) = c(1); + M(148) = c(1); + M(9) = c(2); + M(72) = c(2); + M(93) = c(2); + M(156) = c(2); + M(3) = c(3); + M(66) = c(3); + M(87) = c(3); + M(150) = c(3); + M(7) = c(4); + M(70) = c(4); + M(91) = c(4); + M(154) = c(4); + M(1) = c(5); + M(63) = c(5); + M(84) = c(5); + M(147) = c(5); + M(4) = c(6); + M(67) = c(6); + M(88) = c(6); + M(151) = c(6); + M(5) = c(7); + M(68) = c(7); + M(89) = c(7); + M(152) = c(7); + M(8) = c(8); + M(71) = c(8); + M(92) = c(8); + M(155) = c(8); + M(12) = c(9); + M(75) = c(9); + M(96) = c(9); + M(158) = c(9); + M(11) = c(10); + M(74) = c(10); + M(95) = c(10); + M(157) = c(10); + M(15) = c(11); + M(77) = c(11); + M(98) = c(11); + M(159) = c(11); + M(20) = c(12); + M(102) = c(12); + M(165) = c(12); + M(21) = c(13); + M(104) = c(13); + M(168) = c(13); + M(25) = c(14); + M(109) = c(14); + M(173) = c(14); + M(28) = c(15); + M(112) = c(15); + M(176) = c(15); + M(26) = c(16); + M(110) = c(16); + M(174) = c(16); + M(22) = c(17); + M(105) = c(17); + M(169) = c(17); + M(23) = c(18); + M(107) = c(18); + M(171) = c(18); + M(24) = c(19); + M(108) = c(19); + M(172) = c(19); + M(27) = c(20); + M(111) = c(20); + M(175) = c(20); + M(31) = c(21); + M(115) = c(21); + M(178) = c(21); + M(30) = c(22); + M(114) = c(22); + M(177) = c(22); + M(34) = c(23); + M(117) = c(23); + M(179) = c(23); + M(40) = c(24); + M(122) = c(24); + M(185) = c(24); + M(41) = c(25); + M(124) = c(25); + M(188) = c(25); + M(45) = c(26); + M(129) = c(26); + M(193) = c(26); + M(48) = c(27); + M(132) = c(27); + M(196) = c(27); + M(46) = c(28); + M(130) = c(28); + M(194) = c(28); + M(42) = c(29); + M(125) = c(29); + M(189) = c(29); + M(43) = c(30); + M(127) = c(30); + M(191) = c(30); + M(44) = c(31); + M(128) = c(31); + M(192) = c(31); + M(47) = c(32); + M(131) = c(32); + M(195) = c(32); + M(51) = c(33); + M(135) = c(33); + M(198) = c(33); + M(50) = c(34); + M(134) = c(34); + M(197) = c(34); + M(54) = c(35); + M(137) = c(35); + M(199) = c(35); + + colEchelonForm(M, pivtol); + + Eigen::Matrix A; + A.setZero(); + + A(0, 2) = 1.000000; + A(1, 4) = 1.000000; + A(2, 5) = 1.000000; + A(3, 7) = 1.000000; + A(4, 8) = 1.000000; + A(5, 9) = 1.000000; + A(6, 0) = -M(19, 9); + A(6, 1) = -M(18, 9); + A(6, 2) = -M(17, 9); + A(6, 3) = -M(16, 9); + A(6, 4) = -M(15, 9); + A(6, 5) = -M(14, 9); + A(6, 6) = -M(13, 9); + A(6, 7) = -M(12, 9); + A(6, 8) = -M(11, 9); + A(6, 9) = -M(10, 9); + A(7, 0) = -M(19, 8); + A(7, 1) = -M(18, 8); + A(7, 2) = -M(17, 8); + A(7, 3) = -M(16, 8); + A(7, 4) = -M(15, 8); + A(7, 5) = -M(14, 8); + A(7, 6) = -M(13, 8); + A(7, 7) = -M(12, 8); + A(7, 8) = -M(11, 8); + A(7, 9) = -M(10, 8); + A(8, 0) = -M(19, 7); + A(8, 1) = -M(18, 7); + A(8, 2) = -M(17, 7); + A(8, 3) = -M(16, 7); + A(8, 4) = -M(15, 7); + A(8, 5) = -M(14, 7); + A(8, 6) = -M(13, 7); + A(8, 7) = -M(12, 7); + A(8, 8) = -M(11, 7); + A(8, 9) = -M(10, 7); + A(9, 0) = -M(19, 6); + A(9, 1) = -M(18, 6); + A(9, 2) = -M(17, 6); + A(9, 3) = -M(16, 6); + A(9, 4) = -M(15, 6); + A(9, 5) = -M(14, 6); + A(9, 6) = -M(13, 6); + A(9, 7) = -M(12, 6); + A(9, 8) = -M(11, 6); + A(9, 9) = -M(10, 6); + + const Eigen::EigenSolver> eig(A); + Eigen::Matrix, 10, 2> esols; + esols.col(0).array() = eig.eigenvectors().row(2).array() / eig.eigenvectors().row(0).array(); + esols.col(1).array() = eig.eigenvectors().row(1).array() / eig.eigenvectors().row(0).array(); + + int nsols = 0; + for (Eigen::Index i = 0; i < 10; ++i) + { + if (esols.row(i).imag().isZero(100.0 * std::numeric_limits::epsilon())) + sols.col(nsols++) = esols.row(i).real(); + } + + return nsols; } /** @@ -216,94 +332,89 @@ int f10e_gb(const Eigen::Matrix& params, Eigen::MatrixBase& models) { - using Mat21 = Eigen::Matrix; + using Mat21 = Eigen::Matrix; - assert((X.rows() == 10 && X.cols() == 2) && "The first parameter (x) must be a 10x2 matrix"); - assert((U.rows() == 10 && U.cols() == 2) && "The second parameter (u) must be a 10x2 matrix"); + assert((X.rows() == 10 && X.cols() == 2) && "The first parameter (x) must be a 10x2 matrix"); + assert((U.rows() == 10 && U.cols() == 2) && "The second parameter (u) must be a 10x2 matrix"); - Eigen::Matrix Z1; - Eigen::Matrix Z2; - Eigen::Matrix A; + Eigen::Matrix Z1; + Eigen::Matrix Z2; + Eigen::Matrix A; - Z1.array() = X.col(0).array() * X.col(0).array() + X.col(1).array() * X.col(1).array(); - Z2.array() = U.col(0).array() * U.col(0).array() + U.col(1).array() * U.col(1).array(); + Z1.array() = X.col(0).array() * X.col(0).array() + X.col(1).array() * X.col(1).array(); + Z2.array() = U.col(0).array() * U.col(0).array() + U.col(1).array() * U.col(1).array(); - A.col(0).array() = X.col(0).array() * U.col(0).array(); - A.col(1).array() = X.col(0).array() * U.col(1).array(); - A.col(2).array() = X.col(1).array() * U.col(0).array(); - A.col(3).array() = X.col(1).array() * U.col(1).array(); - A.col(4).array() = U.col(0).array() * Z1.array(); - A.col(5).array() = U.col(0).array(); - A.col(6).array() = U.col(1).array() * Z1.array(); - A.col(7).array() = U.col(1).array(); - A.col(8).array() = X.col(0).array() * Z2.array(); - A.col(9).array() = X.col(0).array(); - A.col(10).array() = X.col(1).array() * Z2.array(); - A.col(11).array() = X.col(1).array(); - A.col(12).array() = Z1.array() * Z2.array(); - A.col(13).array() = Z1.array(); - A.col(14).array() = Z2.array(); - A.col(15).fill(1.0); + A.col(0).array() = X.col(0).array() * U.col(0).array(); + A.col(1).array() = X.col(0).array() * U.col(1).array(); + A.col(2).array() = X.col(1).array() * U.col(0).array(); + A.col(3).array() = X.col(1).array() * U.col(1).array(); + A.col(4).array() = U.col(0).array() * Z1.array(); + A.col(5).array() = U.col(0).array(); + A.col(6).array() = U.col(1).array() * Z1.array(); + A.col(7).array() = U.col(1).array(); + A.col(8).array() = X.col(0).array() * Z2.array(); + A.col(9).array() = X.col(0).array(); + A.col(10).array() = X.col(1).array() * Z2.array(); + A.col(11).array() = X.col(1).array(); + A.col(12).array() = Z1.array() * Z2.array(); + A.col(13).array() = Z1.array(); + A.col(14).array() = Z2.array(); + A.col(15).fill(1.0); - const Eigen::Matrix mr = A.block<10, 10>(0, 0).lu().solve(A.block<10, 6>(0, 10)); + const Eigen::Matrix mr = A.block<10, 10>(0, 0).lu().solve(A.block<10, 6>(0, 10)); - Eigen::Matrix params; + Eigen::Matrix params; - params << mr(5, 0), mr(5, 1), -mr(4, 0), -mr(4, 1), mr(5, 2), mr(5, 3), mr(5, 4) - mr(4, 2), - mr(5, 5) - mr(4, 3), -mr(4, 4), -mr(4, 5), - mr(7, 0), mr(7, 1), -mr(6, 0), -mr(6, 1), mr(7, 2), mr(7, 3), mr(7, 4) - mr(6, 2), - mr(7, 5) - mr(6, 3), -mr(6, 4), -mr(6, 5), - mr(9, 0), mr(9, 1) - mr(8, 0), -mr(8, 1), mr(9, 2), mr(9, 4), mr(9, 3) - mr(8, 2), - -mr(8, 3), mr(9, 5) - mr(8, 4), -mr(8, 5); + params << mr(5, 0), mr(5, 1), -mr(4, 0), -mr(4, 1), mr(5, 2), mr(5, 3), mr(5, 4) - mr(4, 2), mr(5, 5) - mr(4, 3), -mr(4, 4), -mr(4, 5), mr(7, 0), + mr(7, 1), -mr(6, 0), -mr(6, 1), mr(7, 2), mr(7, 3), mr(7, 4) - mr(6, 2), mr(7, 5) - mr(6, 3), -mr(6, 4), -mr(6, 5), mr(9, 0), + mr(9, 1) - mr(8, 0), -mr(8, 1), mr(9, 2), mr(9, 4), mr(9, 3) - mr(8, 2), -mr(8, 3), mr(9, 5) - mr(8, 4), -mr(8, 5); - Mat Ls(2, 10); - const int nsols = f10e_gb(params, Ls); + Mat Ls(2, 10); + const int nsols = f10e_gb(params, Ls); - if(nsols == 0) - return 0; + if (nsols == 0) + return 0; - Eigen::Matrix b; + Eigen::Matrix b; - b << mr(5, 0), mr(5, 1), -mr(4, 0), -mr(4, 1), mr(5, 2), mr(5, 3), - mr(5, 4) - mr(4, 2), mr(5, 5) - mr(4, 3), -mr(4, 4), -mr(4, 5); + b << mr(5, 0), mr(5, 1), -mr(4, 0), -mr(4, 1), mr(5, 2), mr(5, 3), mr(5, 4) - mr(4, 2), mr(5, 5) - mr(4, 3), -mr(4, 4), -mr(4, 5); - models.reserve(nsols); + models.reserve(nsols); - for(Eigen::Index i = 0; i < nsols; ++i) - { - const double l1 = Ls(0, i); - const double l2 = Ls(1, i); - const double l1l1 = l1 * l1; - const double l1l2 = l1 * l2; + for (Eigen::Index i = 0; i < nsols; ++i) + { + const double l1 = Ls(0, i); + const double l2 = Ls(1, i); + const double l1l1 = l1 * l1; + const double l1l2 = l1 * l2; - const Eigen::Matrix m1 = (Eigen::Matrix() << l1l2, l1, l2, 1).finished(); - const Eigen::Matrix m2 = (Eigen::Matrix() << l1l2 * l1, l1l1, l1l2, l1, l2, 1).finished(); + const Eigen::Matrix m1 = (Eigen::Matrix() << l1l2, l1, l2, 1).finished(); + const Eigen::Matrix m2 = (Eigen::Matrix() << l1l2 * l1, l1l1, l1l2, l1, l2, 1).finished(); - const double f23 = -b.block<6, 1>(4, 0).dot(m2) / b.block<4, 1>(0, 0).dot(m1); + const double f23 = -b.block<6, 1>(4, 0).dot(m2) / b.block<4, 1>(0, 0).dot(m1); - const Eigen::Matrix m3 = (Eigen::Matrix() << l2 * f23, f23, l1l2, l1, l2, 1).finished(); + const Eigen::Matrix m3 = (Eigen::Matrix() << l2 * f23, f23, l1l2, l1, l2, 1).finished(); - Mat3 F; - Mat21 L; + Mat3 F; + Mat21 L; - L << l1, l2; - F << m3.dot(-mr.row(0)), m3.dot(-mr.row(1)), m3.dot(-mr.row(9)), - m3.dot(-mr.row(2)), m3.dot(-mr.row(3)), f23, - m3.dot(-mr.row(5)), m3.dot(-mr.row(7)), 1; + L << l1, l2; + F << m3.dot(-mr.row(0)), m3.dot(-mr.row(1)), m3.dot(-mr.row(9)), m3.dot(-mr.row(2)), m3.dot(-mr.row(3)), f23, m3.dot(-mr.row(5)), + m3.dot(-mr.row(7)), 1; - models.emplace_back(F, L); - } + models.emplace_back(F, L); + } - return nsols; + return nsols; } void Fundamental10PSolver::solve(const Mat& x1, const Mat& x2, std::vector& models) const { - Mat x1_ = x1; - x1_.transposeInPlace(); - Mat x2_ = x2; - x2_.transposeInPlace(); - F10RelativePose(x1_, x2_, models); + Mat x1_ = x1; + x1_.transposeInPlace(); + Mat x2_ = x2; + x2_.transposeInPlace(); + F10RelativePose(x1_, x2_, models); } } // namespace relativePose diff --git a/src/aliceVision/multiview/relativePose/Fundamental10PSolver.hpp b/src/aliceVision/multiview/relativePose/Fundamental10PSolver.hpp index 9cfe4a25cc..753a8f4605 100644 --- a/src/aliceVision/multiview/relativePose/Fundamental10PSolver.hpp +++ b/src/aliceVision/multiview/relativePose/Fundamental10PSolver.hpp @@ -18,40 +18,35 @@ namespace relativePose { */ struct Fundamental10PModel : public robustEstimation::Mat3Model { - using Mat21 = Eigen::Matrix; + using Mat21 = Eigen::Matrix; - Fundamental10PModel() = default; + Fundamental10PModel() = default; - /** - * @brief Constructor - * @param[in] F the 3x3 fundamental matrix. - * @param[in] L a 2x1 matrix containing the two radial distortion parameters. - */ - Fundamental10PModel(const Mat3& F, const Mat21& L) - : robustEstimation::Mat3Model(F) , _L(L) - {} + /** + * @brief Constructor + * @param[in] F the 3x3 fundamental matrix. + * @param[in] L a 2x1 matrix containing the two radial distortion parameters. + */ + Fundamental10PModel(const Mat3& F, const Mat21& L) + : robustEstimation::Mat3Model(F), + _L(L) + {} - /** - * @brief Get the estimated radial distortion parameters. - * @return the radial distortion parameters as a 2x1 matrix. - */ - inline const Mat21& getRadialDistortion() const - { - return _L; - } + /** + * @brief Get the estimated radial distortion parameters. + * @return the radial distortion parameters as a 2x1 matrix. + */ + inline const Mat21& getRadialDistortion() const { return _L; } - /** - * @brief Get the estimated fundamental matrix. - * @return the fundamental matrix. - */ - inline const Mat3& getFundamentalMatrix() const - { - return getMatrix(); - } + /** + * @brief Get the estimated fundamental matrix. + * @return the fundamental matrix. + */ + inline const Mat3& getFundamentalMatrix() const { return getMatrix(); } -private: - /// the two radial distortion parameters - Mat21 _L; + private: + /// the two radial distortion parameters + Mat21 _L; }; /*** @@ -59,50 +54,43 @@ struct Fundamental10PModel : public robustEstimation::Mat3Model */ class Fundamental10PSolver : public robustEstimation::ISolver { -public: + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 10; } - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 10; - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 10; } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 10; - } + /** + * @brief Computes the relative pose and two radial distortion coefficients for two cameras from 10 correspondences. + * @see Efficient Solution to the Epipolar Geometry for Radially Distorted Cameras, + * The IEEE International Conference on Computer Vision (ICCV), + * Zuzana Kukelova, Jan Heller, Martin Bujnak, Andrew Fitzgibbon, Tomas Pajdla + * December, 2015, Santiago, Chile. + * + * @param[in] x1 Points in the first image. One per column. + * @param[in] x2 Corresponding points in the second image. One per column. + * @param[out] models A list of at most 10 candidate essential matrix solutions. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - /** - * @brief Computes the relative pose and two radial distortion coefficients for two cameras from 10 correspondences. - * @see Efficient Solution to the Epipolar Geometry for Radially Distorted Cameras, - * The IEEE International Conference on Computer Vision (ICCV), - * Zuzana Kukelova, Jan Heller, Martin Bujnak, Andrew Fitzgibbon, Tomas Pajdla - * December, 2015, Santiago, Chile. - * - * @param[in] x1 Points in the first image. One per column. - * @param[in] x2 Corresponding points in the second image. One per column. - * @param[out] models A list of at most 10 candidate essential matrix solutions. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("Fundamental10PModel does not support problem solving with weights."); - } + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("Fundamental10PModel does not support problem solving with weights."); + } }; } // namespace relativePose diff --git a/src/aliceVision/multiview/relativePose/Fundamental7PSolver.cpp b/src/aliceVision/multiview/relativePose/Fundamental7PSolver.cpp index bbf3227033..298c9d2437 100644 --- a/src/aliceVision/multiview/relativePose/Fundamental7PSolver.cpp +++ b/src/aliceVision/multiview/relativePose/Fundamental7PSolver.cpp @@ -16,83 +16,74 @@ namespace relativePose { void Fundamental7PSolver::solve(const Mat& x1, const Mat& x2, std::vector& models) const { - assert(2 == x1.rows()); - assert(7 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - Vec9 f1, f2; - - if(x1.cols() == 7) - { - // set up the homogeneous system Af = 0 from the equations x'T*F*x = 0. - - // in the minimal solution use fixed sized matrix to let Eigen and the - // compiler doing the maximum of optimization. - Mat9 A = Mat::Zero(9,9); - encodeEpipolarEquation(x1, x2, &A); - - // Eigen::FullPivLU luA(A); - // ALICEVISION_LOG_DEBUG("rank(A) = " << luA.rank()); - // Eigen::JacobiSVD svdA(A); - // ALICEVISION_LOG_DEBUG("Its singular values are:\n" << svdA.singularValues()); - - // find the two F matrices in the nullspace of A. - Nullspace2(A, f1, f2); - - // @fixme here there is a potential error, we should check that the size of - // null(A) is 2. Otherwise we have a family of possible solutions for the - // fundamental matrix (ie infinite solution). This happens, e.g., when matching - // the image against itself or in other degenerate configurations of the camera, - // such as pure rotation or correspondences all on the same plane (cf HZ pg296 table 11.1) - // This is not critical for just matching images with geometric validation, - // it becomes an issue if the estimated F has to be used for retrieving the - // motion of the camera. - } - else - { - // set up the homogeneous system Af = 0 from the equations x'T*F*x = 0. - Mat A(x1.cols(), 9); - encodeEpipolarEquation(x1, x2, &A); - - // find the two F matrices in the nullspace of A. - Nullspace2(A, f1, f2); - } - - Mat3 F1 = Map(f1.data()); - Mat3 F2 = Map(f2.data()); - - // use the condition det(F) = 0 to determine F. - // in other words, solve: det(F1 + a*F2) = 0 for a. - const double a = F1(0, 0), j = F2(0, 0), - b = F1(0, 1), k = F2(0, 1), - c = F1(0, 2), l = F2(0, 2), - d = F1(1, 0), m = F2(1, 0), - e = F1(1, 1), n = F2(1, 1), - f = F1(1, 2), o = F2(1, 2), - g = F1(2, 0), p = F2(2, 0), - h = F1(2, 1), q = F2(2, 1), - i = F1(2, 2), r = F2(2, 2); - - // run fundamental_7point_coeffs.py to get the below coefficients. - // the coefficients are in ascending powers of alpha, i.e. P[N]*x^N. - const double P[4] = { - a*e*i + b*f*g + c*d*h - a*f*h - b*d*i - c*e*g, - a*e*r + a*i*n + b*f*p + b*g*o + c*d*q + c*h*m + d*h*l + e*i*j + f*g*k - - a*f*q - a*h*o - b*d*r - b*i*m - c*e*p - c*g*n - d*i*k - e*g*l - f*h*j, - a*n*r + b*o*p + c*m*q + d*l*q + e*j*r + f*k*p + g*k*o + h*l*m + i*j*n - - a*o*q - b*m*r - c*n*p - d*k*r - e*l*p - f*j*q - g*l*n - h*j*o - i*k*m, - j*n*r + k*o*p + l*m*q - j*o*q - k*m*r - l*n*p }; - - // solve for the roots of P[3]*x^3 + P[2]*x^2 + P[1]*x + P[0] = 0. - double roots[3]; - const int nbRoots = SolveCubicPolynomial(P, roots); - - // build the fundamental matrix for each solution. - for(int kk = 0; kk < nbRoots; ++kk) - models.emplace_back(F1 + roots[kk] * F2); -} + assert(2 == x1.rows()); + assert(7 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + Vec9 f1, f2; + + if (x1.cols() == 7) + { + // set up the homogeneous system Af = 0 from the equations x'T*F*x = 0. + + // in the minimal solution use fixed sized matrix to let Eigen and the + // compiler doing the maximum of optimization. + Mat9 A = Mat::Zero(9, 9); + encodeEpipolarEquation(x1, x2, &A); + + // Eigen::FullPivLU luA(A); + // ALICEVISION_LOG_DEBUG("rank(A) = " << luA.rank()); + // Eigen::JacobiSVD svdA(A); + // ALICEVISION_LOG_DEBUG("Its singular values are:\n" << svdA.singularValues()); + // find the two F matrices in the nullspace of A. + Nullspace2(A, f1, f2); + + // @fixme here there is a potential error, we should check that the size of + // null(A) is 2. Otherwise we have a family of possible solutions for the + // fundamental matrix (ie infinite solution). This happens, e.g., when matching + // the image against itself or in other degenerate configurations of the camera, + // such as pure rotation or correspondences all on the same plane (cf HZ pg296 table 11.1) + // This is not critical for just matching images with geometric validation, + // it becomes an issue if the estimated F has to be used for retrieving the + // motion of the camera. + } + else + { + // set up the homogeneous system Af = 0 from the equations x'T*F*x = 0. + Mat A(x1.cols(), 9); + encodeEpipolarEquation(x1, x2, &A); + + // find the two F matrices in the nullspace of A. + Nullspace2(A, f1, f2); + } + + Mat3 F1 = Map(f1.data()); + Mat3 F2 = Map(f2.data()); + + // use the condition det(F) = 0 to determine F. + // in other words, solve: det(F1 + a*F2) = 0 for a. + const double a = F1(0, 0), j = F2(0, 0), b = F1(0, 1), k = F2(0, 1), c = F1(0, 2), l = F2(0, 2), d = F1(1, 0), m = F2(1, 0), e = F1(1, 1), + n = F2(1, 1), f = F1(1, 2), o = F2(1, 2), g = F1(2, 0), p = F2(2, 0), h = F1(2, 1), q = F2(2, 1), i = F1(2, 2), r = F2(2, 2); + + // run fundamental_7point_coeffs.py to get the below coefficients. + // the coefficients are in ascending powers of alpha, i.e. P[N]*x^N. + const double P[4] = {a * e * i + b * f * g + c * d * h - a * f * h - b * d * i - c * e * g, + a * e * r + a * i * n + b * f * p + b * g * o + c * d * q + c * h * m + d * h * l + e * i * j + f * g * k - a * f * q - + a * h * o - b * d * r - b * i * m - c * e * p - c * g * n - d * i * k - e * g * l - f * h * j, + a * n * r + b * o * p + c * m * q + d * l * q + e * j * r + f * k * p + g * k * o + h * l * m + i * j * n - a * o * q - + b * m * r - c * n * p - d * k * r - e * l * p - f * j * q - g * l * n - h * j * o - i * k * m, + j * n * r + k * o * p + l * m * q - j * o * q - k * m * r - l * n * p}; + + // solve for the roots of P[3]*x^3 + P[2]*x^2 + P[1]*x + P[0] = 0. + double roots[3]; + const int nbRoots = SolveCubicPolynomial(P, roots); + + // build the fundamental matrix for each solution. + for (int kk = 0; kk < nbRoots; ++kk) + models.emplace_back(F1 + roots[kk] * F2); +} void Fundamental7PSphericalSolver::solve(const Mat& x1, const Mat& x2, std::vector& models) const { @@ -102,7 +93,7 @@ void Fundamental7PSphericalSolver::solve(const Mat& x1, const Mat& x2, std::vect assert(x1.cols() == x2.cols()); Vec9 f1, f2; - if(x1.cols() == 7) + if (x1.cols() == 7) { // Set up the homogeneous system Af = 0 from the equations x'T*F*x = 0. typedef Eigen::Matrix Mat9; @@ -139,26 +130,24 @@ void Fundamental7PSphericalSolver::solve(const Mat& x1, const Mat& x2, std::vect // Then, use the condition det(F) = 0 to determine F. In other words, solve // det(F1 + a*F2) = 0 for a. - double a = F1(0, 0), j = F2(0, 0), b = F1(0, 1), k = F2(0, 1), c = F1(0, 2), l = F2(0, 2), d = F1(1, 0), - m = F2(1, 0), e = F1(1, 1), n = F2(1, 1), f = F1(1, 2), o = F2(1, 2), g = F1(2, 0), p = F2(2, 0), - h = F1(2, 1), q = F2(2, 1), i = F1(2, 2), r = F2(2, 2); + double a = F1(0, 0), j = F2(0, 0), b = F1(0, 1), k = F2(0, 1), c = F1(0, 2), l = F2(0, 2), d = F1(1, 0), m = F2(1, 0), e = F1(1, 1), n = F2(1, 1), + f = F1(1, 2), o = F2(1, 2), g = F1(2, 0), p = F2(2, 0), h = F1(2, 1), q = F2(2, 1), i = F1(2, 2), r = F2(2, 2); // Run fundamental_7point_coeffs.py to get the below coefficients. // The coefficients are in ascending powers of alpha, i.e. P[N]*x^N. - double P[4] = { - a * e * i + b * f * g + c * d * h - a * f * h - b * d * i - c * e * g, - a * e * r + a * i * n + b * f * p + b * g * o + c * d * q + c * h * m + d * h * l + e * i * j + f * g * k - - a * f * q - a * h * o - b * d * r - b * i * m - c * e * p - c * g * n - d * i * k - e * g * l - f * h * j, - a * n * r + b * o * p + c * m * q + d * l * q + e * j * r + f * k * p + g * k * o + h * l * m + i * j * n - - a * o * q - b * m * r - c * n * p - d * k * r - e * l * p - f * j * q - g * l * n - h * j * o - i * k * m, - j * n * r + k * o * p + l * m * q - j * o * q - k * m * r - l * n * p}; + double P[4] = {a * e * i + b * f * g + c * d * h - a * f * h - b * d * i - c * e * g, + a * e * r + a * i * n + b * f * p + b * g * o + c * d * q + c * h * m + d * h * l + e * i * j + f * g * k - a * f * q - a * h * o - + b * d * r - b * i * m - c * e * p - c * g * n - d * i * k - e * g * l - f * h * j, + a * n * r + b * o * p + c * m * q + d * l * q + e * j * r + f * k * p + g * k * o + h * l * m + i * j * n - a * o * q - b * m * r - + c * n * p - d * k * r - e * l * p - f * j * q - g * l * n - h * j * o - i * k * m, + j * n * r + k * o * p + l * m * q - j * o * q - k * m * r - l * n * p}; // Solve for the roots of P[3]*x^3 + P[2]*x^2 + P[1]*x + P[0] = 0. double roots[3]; int num_roots = SolveCubicPolynomial(P, roots); // Build the fundamental matrix for each solution. - for(int kk = 0; kk < num_roots; ++kk) + for (int kk = 0; kk < num_roots; ++kk) { models.emplace_back(F1 + roots[kk] * F2); } diff --git a/src/aliceVision/multiview/relativePose/Fundamental7PSolver.hpp b/src/aliceVision/multiview/relativePose/Fundamental7PSolver.hpp index c7a363b2c8..90eebf0b19 100644 --- a/src/aliceVision/multiview/relativePose/Fundamental7PSolver.hpp +++ b/src/aliceVision/multiview/relativePose/Fundamental7PSolver.hpp @@ -16,59 +16,51 @@ namespace relativePose { class Fundamental7PSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 7; - } + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 7; } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 3; - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 3; } - /** - * @brief Seven-point algorithm for solving for the fundamental matrix from point - * correspondences. @see page 281 in HZ - * - * Though oddly they use a different equation: \f$det(\alpha F_1 + (1-\alpha)F_2) = 0\f$. - * Since \f$F_1\f$ and \f$F2\f$ are projective, there's no need to balance the relative scale. - * Instead, here, the simpler equation is solved: \f$det(F_1 + \alpha F_2) = 0\f$. - * - * @see http://www.cs.unc.edu/~marc/tutorial/node55.html - * - * @param[in] x1 Points in the first image. One per column. - * @param[in] x2 Corresponding points in the second image. One per column. - * @param[out] models A list of at most 10 candidate essential matrix solutions. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; + /** + * @brief Seven-point algorithm for solving for the fundamental matrix from point + * correspondences. @see page 281 in HZ + * + * Though oddly they use a different equation: \f$det(\alpha F_1 + (1-\alpha)F_2) = 0\f$. + * Since \f$F_1\f$ and \f$F2\f$ are projective, there's no need to balance the relative scale. + * Instead, here, the simpler equation is solved: \f$det(F_1 + \alpha F_2) = 0\f$. + * + * @see http://www.cs.unc.edu/~marc/tutorial/node55.html + * + * @param[in] x1 Points in the first image. One per column. + * @param[in] x2 Corresponding points in the second image. One per column. + * @param[out] models A list of at most 10 candidate essential matrix solutions. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("Fundamental7PSolver does not support problem solving with weights."); - } + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("Fundamental7PSolver does not support problem solving with weights."); + } }; - class Fundamental7PSphericalSolver : public robustEstimation::ISolver { -public: + public: /** * @brief Return the minimum number of required samples * @return minimum number of required samples @@ -104,8 +96,7 @@ class Fundamental7PSphericalSolver : public robustEstimation::ISolver& models, - const std::vector& weights) const override + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override { throw std::logic_error("Fundamental7PSphericalSolver does not support problem solving with weights."); } diff --git a/src/aliceVision/multiview/relativePose/Fundamental8PSolver.cpp b/src/aliceVision/multiview/relativePose/Fundamental8PSolver.cpp index d3f3c41b13..658ec6a503 100644 --- a/src/aliceVision/multiview/relativePose/Fundamental8PSolver.cpp +++ b/src/aliceVision/multiview/relativePose/Fundamental8PSolver.cpp @@ -16,53 +16,56 @@ namespace relativePose { void solveProblem(const Mat& x1, const Mat& x2, std::vector& models, const std::vector* weights = nullptr) { - assert(2 == x1.rows()); - assert(8 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); + assert(2 == x1.rows()); + assert(8 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); - Vec9 f; + Vec9 f; - if(x1.cols() == 8) - { - // in the minimal solution use fixed sized matrix to let Eigen and the - // compiler doing the maximum of optimization. - Mat9 A = Mat::Zero(9,9); - encodeEpipolarEquation(x1, x2, &A, weights); - Nullspace(A, f); - } - else - { - MatX9 A(x1.cols(), 9); - encodeEpipolarEquation(x1, x2, &A, weights); - Nullspace(A, f); - } + if (x1.cols() == 8) + { + // in the minimal solution use fixed sized matrix to let Eigen and the + // compiler doing the maximum of optimization. + Mat9 A = Mat::Zero(9, 9); + encodeEpipolarEquation(x1, x2, &A, weights); + Nullspace(A, f); + } + else + { + MatX9 A(x1.cols(), 9); + encodeEpipolarEquation(x1, x2, &A, weights); + Nullspace(A, f); + } - Mat3 F = Map(f.data()); + Mat3 F = Map(f.data()); - // force the fundamental property if the A matrix has full rank. - // @see HZ 11.1.1 pag.280 - if(x1.cols() > 8) - { - // force fundamental matrix to have rank 2 - Eigen::JacobiSVD USV(F, Eigen::ComputeFullU | Eigen::ComputeFullV); - Vec3 d = USV.singularValues(); - d[2] = 0.0; - F = USV.matrixU() * d.asDiagonal() * USV.matrixV().transpose(); - } - models.emplace_back(F); + // force the fundamental property if the A matrix has full rank. + // @see HZ 11.1.1 pag.280 + if (x1.cols() > 8) + { + // force fundamental matrix to have rank 2 + Eigen::JacobiSVD USV(F, Eigen::ComputeFullU | Eigen::ComputeFullV); + Vec3 d = USV.singularValues(); + d[2] = 0.0; + F = USV.matrixU() * d.asDiagonal() * USV.matrixV().transpose(); + } + models.emplace_back(F); } void Fundamental8PSolver::solve(const Mat& x1, const Mat& x2, std::vector& models) const { - solveProblem(x1, x2, models); + solveProblem(x1, x2, models); } -void Fundamental8PSolver::solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const +void Fundamental8PSolver::solve(const Mat& x1, + const Mat& x2, + std::vector& models, + const std::vector& weights) const { - solveProblem(x1, x2, models, &weights); + solveProblem(x1, x2, models, &weights); } -} // namespace kernel -} // namespace fundamental +} // namespace relativePose +} // namespace multiview } // namespace aliceVision diff --git a/src/aliceVision/multiview/relativePose/Fundamental8PSolver.hpp b/src/aliceVision/multiview/relativePose/Fundamental8PSolver.hpp index 6fcb6f4bad..e81954ee34 100644 --- a/src/aliceVision/multiview/relativePose/Fundamental8PSolver.hpp +++ b/src/aliceVision/multiview/relativePose/Fundamental8PSolver.hpp @@ -16,42 +16,35 @@ namespace relativePose { class Fundamental8PSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 8; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } - - /** - * @brief - * @param[in] x1 Points in the first image. One per column. - * @param[in] x2 Corresponding points in the second image. One per column. - * @param[out] models A list of at most 10 candidate essential matrix solutions. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override; + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 8; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } + + /** + * @brief + * @param[in] x1 Points in the first image. One per column. + * @param[in] x2 Corresponding points in the second image. One per column. + * @param[out] models A list of at most 10 candidate essential matrix solutions. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models) const override; + + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override; }; } // namespace relativePose diff --git a/src/aliceVision/multiview/relativePose/FundamentalError.hpp b/src/aliceVision/multiview/relativePose/FundamentalError.hpp index f86ad7dc81..0d627fbb27 100644 --- a/src/aliceVision/multiview/relativePose/FundamentalError.hpp +++ b/src/aliceVision/multiview/relativePose/FundamentalError.hpp @@ -20,53 +20,52 @@ namespace relativePose { */ struct FundamentalSampsonError : public ISolverErrorRelativePose { - double error(const robustEstimation::Mat3Model& F, const Vec2& x1, const Vec2& x2) const override - { - const Vec3 x(x1(0), x1(1), 1.0); - const Vec3 y(x2(0), x2(1), 1.0); + double error(const robustEstimation::Mat3Model& F, const Vec2& x1, const Vec2& x2) const override + { + const Vec3 x(x1(0), x1(1), 1.0); + const Vec3 y(x2(0), x2(1), 1.0); - // @see page 287 equation (11.9) of HZ. - const Vec3 F_x = F.getMatrix() * x; - const Vec3 Ft_y = F.getMatrix().transpose() * y; + // @see page 287 equation (11.9) of HZ. + const Vec3 F_x = F.getMatrix() * x; + const Vec3 Ft_y = F.getMatrix().transpose() * y; - return Square(y.dot(F_x)) / ( F_x.head<2>().squaredNorm() + Ft_y.head<2>().squaredNorm()); - } + return Square(y.dot(F_x)) / (F_x.head<2>().squaredNorm() + Ft_y.head<2>().squaredNorm()); + } }; -struct FundamentalSymmetricEpipolarDistanceError: public ISolverErrorRelativePose +struct FundamentalSymmetricEpipolarDistanceError : public ISolverErrorRelativePose { - double error(const robustEstimation::Mat3Model& F, const Vec2& x1, const Vec2& x2) const override - { - const Vec3 x(x1(0), x1(1), 1.0); - const Vec3 y(x2(0), x2(1), 1.0); + double error(const robustEstimation::Mat3Model& F, const Vec2& x1, const Vec2& x2) const override + { + const Vec3 x(x1(0), x1(1), 1.0); + const Vec3 y(x2(0), x2(1), 1.0); - // @see page 288 equation (11.10) of HZ. - const Vec3 F_x = F.getMatrix() * x; - const Vec3 Ft_y = F.getMatrix().transpose() * y; + // @see page 288 equation (11.10) of HZ. + const Vec3 F_x = F.getMatrix() * x; + const Vec3 Ft_y = F.getMatrix().transpose() * y; - // @note the divide by 4 is to make this match the Sampson distance. - return Square(y.dot(F_x)) * ( 1.0 / F_x.head<2>().squaredNorm() + 1.0 / Ft_y.head<2>().squaredNorm()) / 4.0; - } + // @note the divide by 4 is to make this match the Sampson distance. + return Square(y.dot(F_x)) * (1.0 / F_x.head<2>().squaredNorm() + 1.0 / Ft_y.head<2>().squaredNorm()) / 4.0; + } }; struct FundamentalEpipolarDistanceError : public ISolverErrorRelativePose { - double error(const robustEstimation::Mat3Model& F, const Vec2& x1, const Vec2& x2) const override - { - // transfer error in image 2 - // @see page 287 equation (11.9) of HZ. - const Vec3 x(x1(0), x1(1), 1.0); - const Vec3 y(x2(0), x2(1), 1.0); - const Vec3 F_x = F.getMatrix() * x; + double error(const robustEstimation::Mat3Model& F, const Vec2& x1, const Vec2& x2) const override + { + // transfer error in image 2 + // @see page 287 equation (11.9) of HZ. + const Vec3 x(x1(0), x1(1), 1.0); + const Vec3 y(x2(0), x2(1), 1.0); + const Vec3 F_x = F.getMatrix() * x; - return Square(F_x.dot(y)) / F_x.head<2>().squaredNorm(); - } + return Square(F_x.dot(y)) / F_x.head<2>().squaredNorm(); + } }; - struct EpipolarSphericalDistanceError { - double error(const robustEstimation::Mat3Model& F, const Vec3& x, const Vec3& y) const // override + double error(const robustEstimation::Mat3Model& F, const Vec3& x, const Vec3& y) const // override { // Transfer error in image 2 // See page 287 equation (11.9) of HZ. @@ -76,7 +75,6 @@ struct EpipolarSphericalDistanceError } }; - } // namespace relativePose } // namespace multiview } // namespace aliceVision diff --git a/src/aliceVision/multiview/relativePose/FundamentalKernel.hpp b/src/aliceVision/multiview/relativePose/FundamentalKernel.hpp index 1f357344c9..6e121cd4b7 100644 --- a/src/aliceVision/multiview/relativePose/FundamentalKernel.hpp +++ b/src/aliceVision/multiview/relativePose/FundamentalKernel.hpp @@ -32,13 +32,15 @@ using Fundamental8PKernel = robustEstimation::PointFittingKernel; +using NormalizedFundamental7PKernel = + robustEstimation::NormalizedPointFittingKernel; /** * @brief Normalized 8pt kernel * @see conditioning from HZ (Algo 11.1) pag 282 */ -using NormalizedFundamental8PKernel = robustEstimation::NormalizedPointFittingKernel; +using NormalizedFundamental8PKernel = + robustEstimation::NormalizedPointFittingKernel; } // namespace relativePose } // namespace multiview diff --git a/src/aliceVision/multiview/relativePose/Homography4PSolver.cpp b/src/aliceVision/multiview/relativePose/Homography4PSolver.cpp index f9afa18b54..cbed39c5bd 100644 --- a/src/aliceVision/multiview/relativePose/Homography4PSolver.cpp +++ b/src/aliceVision/multiview/relativePose/Homography4PSolver.cpp @@ -18,70 +18,70 @@ namespace relativePose { * Use template in order to support fixed or dynamic sized matrix. * Allow solve H as homogeneous(x2) = H homogeneous(x1) */ -template +template void buildActionMatrix(Matrix& L, const Mat& x1, const Mat& x2) { - const Mat::Index n = x1.cols(); - for(Mat::Index i = 0; i < n; ++i) - { - Mat::Index j = 2 * i; - L(j, 0) = x1(0, i); - L(j, 1) = x1(1, i); - L(j, 2) = 1.0; - L(j, 6) = -x2(0, i) * x1(0, i); - L(j, 7) = -x2(0, i) * x1(1, i); - L(j, 8) = -x2(0, i); - - ++j; - L(j, 3) = x1(0, i); - L(j, 4) = x1(1, i); - L(j, 5) = 1.0; - L(j, 6) = -x2(1, i) * x1(0, i); - L(j, 7) = -x2(1, i) * x1(1, i); - L(j, 8) = -x2(1, i); - } + const Mat::Index n = x1.cols(); + for (Mat::Index i = 0; i < n; ++i) + { + Mat::Index j = 2 * i; + L(j, 0) = x1(0, i); + L(j, 1) = x1(1, i); + L(j, 2) = 1.0; + L(j, 6) = -x2(0, i) * x1(0, i); + L(j, 7) = -x2(0, i) * x1(1, i); + L(j, 8) = -x2(0, i); + + ++j; + L(j, 3) = x1(0, i); + L(j, 4) = x1(1, i); + L(j, 5) = 1.0; + L(j, 6) = -x2(1, i) * x1(0, i); + L(j, 7) = -x2(1, i) * x1(1, i); + L(j, 8) = -x2(1, i); + } } void Homography4PSolver::solve(const Mat& x1, const Mat& x2, std::vector& models) const { - assert(2 == x1.rows()); - assert(4 <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - const Mat::Index n = x1.cols(); - - Vec9 h; - if(n == 4) - { - // in the case of minimal configuration we use fixed sized matrix to let - // Eigen and the compiler doing the maximum of optimization. - typedef Eigen::Matrix Mat16_9; - Mat16_9 L = Mat::Zero(16, 9); - buildActionMatrix(L, x1, x2); - Nullspace(L, h); - } - else - { - MatX9 L = Mat::Zero(n * 2, 9); - buildActionMatrix(L, x1, x2); - Nullspace(L, h); - } - - // map the linear vector as the H matrix - Mat3 H = Map(h.data()); - models.emplace_back(H); + assert(2 == x1.rows()); + assert(4 <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + const Mat::Index n = x1.cols(); + + Vec9 h; + if (n == 4) + { + // in the case of minimal configuration we use fixed sized matrix to let + // Eigen and the compiler doing the maximum of optimization. + typedef Eigen::Matrix Mat16_9; + Mat16_9 L = Mat::Zero(16, 9); + buildActionMatrix(L, x1, x2); + Nullspace(L, h); + } + else + { + MatX9 L = Mat::Zero(n * 2, 9); + buildActionMatrix(L, x1, x2); + Nullspace(L, h); + } + + // map the linear vector as the H matrix + Mat3 H = Map(h.data()); + models.emplace_back(H); } /// Setup the Direct Linear Transform. /// Use template in order to support fixed or dynamic sized matrix. /// Allow solve H as homogeneous(p2) = H homogeneous(p1) -template +template void buildActionMatrixSpherical(Matrix& L, const Mat& p1, const Mat& p2) { const Mat::Index n = p1.cols(); - for(Mat::Index i = 0; i < n; ++i) + for (Mat::Index i = 0; i < n; ++i) { Mat::Index j = 2 * i; L(j, 3) = -p2(2, i) * p1(0, i); @@ -101,7 +101,6 @@ void buildActionMatrixSpherical(Matrix& L, const Mat& p1, const Mat& p2) } } - void Homography4PSphericalSolver::solve(const Mat& p1, const Mat& p2, std::vector& models) const { assert(4 <= p2.cols()); @@ -112,7 +111,7 @@ void Homography4PSphericalSolver::solve(const Mat& p1, const Mat& p2, std::vecto // No input normalization when on sphere Vec9 h; - if(n == 4) + if (n == 4) { // In the case of minimal configuration we use fixed sized matrix to let // Eigen and the compiler doing the maximum of optimization. diff --git a/src/aliceVision/multiview/relativePose/Homography4PSolver.hpp b/src/aliceVision/multiview/relativePose/Homography4PSolver.hpp index b112dc3de0..d44355406e 100644 --- a/src/aliceVision/multiview/relativePose/Homography4PSolver.hpp +++ b/src/aliceVision/multiview/relativePose/Homography4PSolver.hpp @@ -16,55 +16,47 @@ namespace relativePose { class Homography4PSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 4; - } + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 4; } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } - /** - * @brief Computes the homography that transforms x to y with the Direct Linear - * Transform (DLT). - * - * @param[in] x A 2xN matrix of column vectors. - * @param[in] y A 2xN matrix of column vectors. - * @param[out] Hs A vector into which the computed homography is stored. - * - * The estimated homography should approximately hold the condition y = H x. - */ - void solve(const Mat& x, const Mat& y, std::vector& models) const override; + /** + * @brief Computes the homography that transforms x to y with the Direct Linear + * Transform (DLT). + * + * @param[in] x A 2xN matrix of column vectors. + * @param[in] y A 2xN matrix of column vectors. + * @param[out] Hs A vector into which the computed homography is stored. + * + * The estimated homography should approximately hold the condition y = H x. + */ + void solve(const Mat& x, const Mat& y, std::vector& models) const override; - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("Homography4PSolver does not support problem solving with weights."); - } + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("Homography4PSolver does not support problem solving with weights."); + } }; - class Homography4PSphericalSolver : public robustEstimation::ISolver { -public: + public: /** * @brief Return the minimum number of required samples * @return minimum number of required samples @@ -96,8 +88,7 @@ class Homography4PSphericalSolver : public robustEstimation::ISolver& models, - const std::vector& weights) const override + void solve(const Mat& p1, const Mat& p2, std::vector& models, const std::vector& weights) const override { throw std::logic_error("Homography4PSphericalSolver does not support problem solving with weights."); } diff --git a/src/aliceVision/multiview/relativePose/HomographyKernel.hpp b/src/aliceVision/multiview/relativePose/HomographyKernel.hpp index 59de384696..a16f7a8a9d 100644 --- a/src/aliceVision/multiview/relativePose/HomographyKernel.hpp +++ b/src/aliceVision/multiview/relativePose/HomographyKernel.hpp @@ -25,7 +25,8 @@ typedef robustEstimation::PointFittingKernel NormalizedHomography4PKernel; +typedef robustEstimation::NormalizedPointFittingKernel + NormalizedHomography4PKernel; } // namespace relativePose } // namespace multiview diff --git a/src/aliceVision/multiview/relativePose/ISolverErrorRelativePose.hpp b/src/aliceVision/multiview/relativePose/ISolverErrorRelativePose.hpp index 240c5e5cfe..4a8e06b864 100644 --- a/src/aliceVision/multiview/relativePose/ISolverErrorRelativePose.hpp +++ b/src/aliceVision/multiview/relativePose/ISolverErrorRelativePose.hpp @@ -8,7 +8,6 @@ #include - namespace aliceVision { namespace multiview { namespace relativePose { @@ -16,10 +15,10 @@ namespace relativePose { /** * @brief Relative pose solver error interface. */ -template +template struct ISolverErrorRelativePose { - virtual double error(const ModelT& model, const Vec2& x1, const Vec2& x2) const = 0; + virtual double error(const ModelT& model, const Vec2& x1, const Vec2& x2) const = 0; }; } // namespace relativePose diff --git a/src/aliceVision/multiview/relativePose/Rotation3PSolver.cpp b/src/aliceVision/multiview/relativePose/Rotation3PSolver.cpp index c309fa5925..0bc6c146be 100644 --- a/src/aliceVision/multiview/relativePose/Rotation3PSolver.cpp +++ b/src/aliceVision/multiview/relativePose/Rotation3PSolver.cpp @@ -12,7 +12,6 @@ #include - namespace aliceVision { namespace multiview { namespace relativePose { @@ -28,11 +27,11 @@ void Rotation3PSolver::solve(const Mat& p1, const Mat& p2, std::vector - namespace aliceVision { namespace multiview { namespace relativePose { - struct Rotation3PSolver : public robustEstimation::ISolver { - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override { return 3; } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override { return 1; } - - /** - * @brief Computes the homography that transforms x to y with the Direct Linear - * Transform (DLT). - * - * @param[in] x A 2xN matrix of column vectors. - * @param[in] y A 2xN matrix of column vectors. - * @param[out] Hs A vector into which the computed homography is stored. - * - * The estimated homography should approximately hold the condition y = H x. - */ - void solve(const Mat& x, const Mat& y, std::vector& models) const override; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x1, const Mat& x2, std::vector& models, - const std::vector& weights) const override - { - throw std::logic_error("Rotation3PSolver does not support problem solving with weights."); - } + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 3; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } + + /** + * @brief Computes the homography that transforms x to y with the Direct Linear + * Transform (DLT). + * + * @param[in] x A 2xN matrix of column vectors. + * @param[in] y A 2xN matrix of column vectors. + * @param[out] Hs A vector into which the computed homography is stored. + * + * The estimated homography should approximately hold the condition y = H x. + */ + void solve(const Mat& x, const Mat& y, std::vector& models) const override; + + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("Rotation3PSolver does not support problem solving with weights."); + } }; struct RotationError @@ -76,7 +73,6 @@ struct RotationError } }; - -} // namespace kernel -} // namespace rotation +} // namespace relativePose +} // namespace multiview } // namespace aliceVision diff --git a/src/aliceVision/multiview/relativePose/essential5PSolver_test.cpp b/src/aliceVision/multiview/relativePose/essential5PSolver_test.cpp index 5ad4a607a3..236528ad0a 100644 --- a/src/aliceVision/multiview/relativePose/essential5PSolver_test.cpp +++ b/src/aliceVision/multiview/relativePose/essential5PSolver_test.cpp @@ -23,145 +23,130 @@ using namespace aliceVision; struct TestData { - // dataset that encapsulate : - // 3D points and their projection given P1 and P2 - // link between the two camera [R|t] - - Mat3X X; - Mat3 R; - Vec3 t; - Mat3 E; - Mat34 P1, P2; - Mat2X x1, x2; + // dataset that encapsulate : + // 3D points and their projection given P1 and P2 + // link between the two camera [R|t] + + Mat3X X; + Mat3 R; + Vec3 t; + Mat3 E; + Mat34 P1, P2; + Mat2X x1, x2; }; TestData SomeTestData() { - TestData d; + TestData d; - // modeling random 3D points, - // consider first camera as [R=I|t=0], - // second camera as [R=Rx*Ry*Rz|t=random], - // compute projection of the 3D points onto image plane. + // modeling random 3D points, + // consider first camera as [R=I|t=0], + // second camera as [R=Rx*Ry*Rz|t=random], + // compute projection of the 3D points onto image plane. - d.X = Mat3X::Random(3,5); + d.X = Mat3X::Random(3, 5); - // make point in front to the cameras. - d.X.row(0).array() -= .5; - d.X.row(1).array() -= .5; - d.X.row(2).array() += 1.0; - - d.R = RotationAroundZ(0.3) * RotationAroundX(0.1) * RotationAroundY(0.2); - do - { - d.t.setRandom(); - }while(!cheiralityTestAll(d.R, d.t, d.X)); + // make point in front to the cameras. + d.X.row(0).array() -= .5; + d.X.row(1).array() -= .5; + d.X.row(2).array() += 1.0; + d.R = RotationAroundZ(0.3) * RotationAroundX(0.1) * RotationAroundY(0.2); + do + { + d.t.setRandom(); + } while (!cheiralityTestAll(d.R, d.t, d.X)); - essentialFromRt(Mat3::Identity(), Vec3::Zero(), d.R, d.t, &d.E); + essentialFromRt(Mat3::Identity(), Vec3::Zero(), d.R, d.t, &d.E); - P_from_KRt(Mat3::Identity(), Mat3::Identity(), Vec3::Zero(), d.P1); - P_from_KRt(Mat3::Identity(), d.R, d.t, d.P2); + P_from_KRt(Mat3::Identity(), Mat3::Identity(), Vec3::Zero(), d.P1); + P_from_KRt(Mat3::Identity(), d.R, d.t, d.P2); - project(d.P1, d.X, d.x1); - project(d.P2, d.X, d.x2); + project(d.P1, d.X, d.x1); + project(d.P2, d.X, d.x2); - return d; + return d; } double EvalPolynomial(Vec p, double x, double y, double z) { - using namespace aliceVision::multiview::relativePose; - - return p(Pc::coef_xxx) * x * x * x - + p(Pc::coef_xxy) * x * x * y - + p(Pc::coef_xxz) * x * x * z - + p(Pc::coef_xyy) * x * y * y - + p(Pc::coef_xyz) * x * y * z - + p(Pc::coef_xzz) * x * z * z - + p(Pc::coef_yyy) * y * y * y - + p(Pc::coef_yyz) * y * y * z - + p(Pc::coef_yzz) * y * z * z - + p(Pc::coef_zzz) * z * z * z - + p(Pc::coef_xx) * x * x - + p(Pc::coef_xy) * x * y - + p(Pc::coef_xz) * x * z - + p(Pc::coef_yy) * y * y - + p(Pc::coef_yz) * y * z - + p(Pc::coef_zz) * z * z - + p(Pc::coef_x) * x - + p(Pc::coef_y) * y - + p(Pc::coef_z) * z - + p(Pc::coef_1) * 1; + using namespace aliceVision::multiview::relativePose; + + return p(Pc::coef_xxx) * x * x * x + p(Pc::coef_xxy) * x * x * y + p(Pc::coef_xxz) * x * x * z + p(Pc::coef_xyy) * x * y * y + + p(Pc::coef_xyz) * x * y * z + p(Pc::coef_xzz) * x * z * z + p(Pc::coef_yyy) * y * y * y + p(Pc::coef_yyz) * y * y * z + + p(Pc::coef_yzz) * y * z * z + p(Pc::coef_zzz) * z * z * z + p(Pc::coef_xx) * x * x + p(Pc::coef_xy) * x * y + p(Pc::coef_xz) * x * z + + p(Pc::coef_yy) * y * y + p(Pc::coef_yz) * y * z + p(Pc::coef_zz) * z * z + p(Pc::coef_x) * x + p(Pc::coef_y) * y + p(Pc::coef_z) * z + + p(Pc::coef_1) * 1; } BOOST_AUTO_TEST_CASE(o1_Evaluation) { - using namespace aliceVision::multiview::relativePose; - makeRandomOperationsReproducible(); - - Vec p1 = Vec::Zero(20), p2 = Vec::Zero(20); - - p1(Pc::coef_x) = double(rand()) / RAND_MAX; - p1(Pc::coef_y) = double(rand()) / RAND_MAX; - p1(Pc::coef_z) = double(rand()) / RAND_MAX; - p1(Pc::coef_1) = double(rand()) / RAND_MAX; - p2(Pc::coef_x) = double(rand()) / RAND_MAX; - p2(Pc::coef_y) = double(rand()) / RAND_MAX; - p2(Pc::coef_z) = double(rand()) / RAND_MAX; - p2(Pc::coef_1) = double(rand()) / RAND_MAX; - - Vec p3 = o1(p1, p2); - - for (double z = -5; z < 5; ++z) - for (double y = -5; y < 5; ++y) - for (double x = -5; x < 5; ++x) - BOOST_CHECK_SMALL(EvalPolynomial(p3, x, y, z) - EvalPolynomial(p1, x, y, z) * EvalPolynomial(p2, x, y, z), 1e-8); + using namespace aliceVision::multiview::relativePose; + makeRandomOperationsReproducible(); + + Vec p1 = Vec::Zero(20), p2 = Vec::Zero(20); + + p1(Pc::coef_x) = double(rand()) / RAND_MAX; + p1(Pc::coef_y) = double(rand()) / RAND_MAX; + p1(Pc::coef_z) = double(rand()) / RAND_MAX; + p1(Pc::coef_1) = double(rand()) / RAND_MAX; + p2(Pc::coef_x) = double(rand()) / RAND_MAX; + p2(Pc::coef_y) = double(rand()) / RAND_MAX; + p2(Pc::coef_z) = double(rand()) / RAND_MAX; + p2(Pc::coef_1) = double(rand()) / RAND_MAX; + + Vec p3 = o1(p1, p2); + + for (double z = -5; z < 5; ++z) + for (double y = -5; y < 5; ++y) + for (double x = -5; x < 5; ++x) + BOOST_CHECK_SMALL(EvalPolynomial(p3, x, y, z) - EvalPolynomial(p1, x, y, z) * EvalPolynomial(p2, x, y, z), 1e-8); } BOOST_AUTO_TEST_CASE(o2_Evaluation) { - using namespace aliceVision::multiview::relativePose; - makeRandomOperationsReproducible(); - - Vec p1 = Vec::Zero(20), p2 = Vec::Zero(20); - - p1(Pc::coef_xx) = double(rand()) / RAND_MAX; - p1(Pc::coef_xy) = double(rand()) / RAND_MAX; - p1(Pc::coef_xz) = double(rand()) / RAND_MAX; - p1(Pc::coef_yy) = double(rand()) / RAND_MAX; - p1(Pc::coef_yz) = double(rand()) / RAND_MAX; - p1(Pc::coef_zz) = double(rand()) / RAND_MAX; - p1(Pc::coef_x) = double(rand()) / RAND_MAX; - p1(Pc::coef_y) = double(rand()) / RAND_MAX; - p1(Pc::coef_z) = double(rand()) / RAND_MAX; - p1(Pc::coef_1) = double(rand()) / RAND_MAX; - p2(Pc::coef_x) = double(rand()) / RAND_MAX; - p2(Pc::coef_y) = double(rand()) / RAND_MAX; - p2(Pc::coef_z) = double(rand()) / RAND_MAX; - p2(Pc::coef_1) = double(rand()) / RAND_MAX; - - Vec p3 = o2(p1, p2); - - for (double z = -5; z < 5; ++z) - for (double y = -5; y < 5; ++y) - for (double x = -5; x < 5; ++x) - BOOST_CHECK_SMALL(EvalPolynomial(p3, x, y, z) - EvalPolynomial(p1, x, y, z) * EvalPolynomial(p2, x, y, z), 1e-8); + using namespace aliceVision::multiview::relativePose; + makeRandomOperationsReproducible(); + + Vec p1 = Vec::Zero(20), p2 = Vec::Zero(20); + + p1(Pc::coef_xx) = double(rand()) / RAND_MAX; + p1(Pc::coef_xy) = double(rand()) / RAND_MAX; + p1(Pc::coef_xz) = double(rand()) / RAND_MAX; + p1(Pc::coef_yy) = double(rand()) / RAND_MAX; + p1(Pc::coef_yz) = double(rand()) / RAND_MAX; + p1(Pc::coef_zz) = double(rand()) / RAND_MAX; + p1(Pc::coef_x) = double(rand()) / RAND_MAX; + p1(Pc::coef_y) = double(rand()) / RAND_MAX; + p1(Pc::coef_z) = double(rand()) / RAND_MAX; + p1(Pc::coef_1) = double(rand()) / RAND_MAX; + p2(Pc::coef_x) = double(rand()) / RAND_MAX; + p2(Pc::coef_y) = double(rand()) / RAND_MAX; + p2(Pc::coef_z) = double(rand()) / RAND_MAX; + p2(Pc::coef_1) = double(rand()) / RAND_MAX; + + Vec p3 = o2(p1, p2); + + for (double z = -5; z < 5; ++z) + for (double y = -5; y < 5; ++y) + for (double x = -5; x < 5; ++x) + BOOST_CHECK_SMALL(EvalPolynomial(p3, x, y, z) - EvalPolynomial(p1, x, y, z) * EvalPolynomial(p2, x, y, z), 1e-8); } /// Check that the E matrix fit the Essential Matrix properties /// Determinant is 0 /// -#define EXPECT_ESSENTIAL_MATRIX_PROPERTIES(E, expectedPrecision) { \ - BOOST_CHECK_SMALL(E.determinant(), expectedPrecision); \ - Mat3 O = 2 * E * E.transpose() * E - (E * E.transpose()).trace() * E; \ - Mat3 zero3x3 = Mat3::Zero(); \ - EXPECT_MATRIX_NEAR(zero3x3, O, expectedPrecision);\ -} +#define EXPECT_ESSENTIAL_MATRIX_PROPERTIES(E, expectedPrecision) \ + { \ + BOOST_CHECK_SMALL(E.determinant(), expectedPrecision); \ + Mat3 O = 2 * E * E.transpose() * E - (E * E.transpose()).trace() * E; \ + Mat3 zero3x3 = Mat3::Zero(); \ + EXPECT_MATRIX_NEAR(zero3x3, O, expectedPrecision); \ + } BOOST_AUTO_TEST_CASE(FivePointsRelativePose_Random) { - for(std::size_t trial = 0; trial < 100; ++trial) + for (std::size_t trial = 0; trial < 100; ++trial) { TestData d = SomeTestData(); @@ -178,29 +163,27 @@ BOOST_AUTO_TEST_CASE(FivePointsRelativePose_Random) // Recover rotation and translation from E Rs.resize(Es.size()); ts.resize(Es.size()); - for(std::size_t s = 0; s < Es.size(); ++s) + for (std::size_t s = 0; s < Es.size(); ++s) { - for(Eigen::Index c = 0; c < d.x1.cols(); ++c) + for (Eigen::Index c = 0; c < d.x1.cols(); ++c) { - const double v = - d.x2.col(c).homogeneous().transpose() * Es.at(s).getMatrix() * d.x1.col(c).homogeneous(); + const double v = d.x2.col(c).homogeneous().transpose() * Es.at(s).getMatrix() * d.x1.col(c).homogeneous(); BOOST_CHECK_SMALL(v, 1.0e-6); } Vec2 x1Col = d.x1.col(0); Vec2 x2Col = d.x2.col(0); - BOOST_CHECK(motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), Mat3::Identity(), x1Col, - Mat3::Identity(), x2Col, &Rs[s], &ts[s])); + BOOST_CHECK(motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), Mat3::Identity(), x1Col, Mat3::Identity(), x2Col, &Rs[s], &ts[s])); } bool bsolution_found = false; - for(std::size_t i = 0; i < Es.size(); ++i) + for (std::size_t i = 0; i < Es.size(); ++i) { -// std::cout << i << std::endl; + // std::cout << i << std::endl; // check that we find the correct relative orientation. - if(FrobeniusDistance(d.R, Rs[i]) < 1e-3 && (d.t / d.t.norm() - ts[i] / ts[i].norm()).norm() < 1e-3) + if (FrobeniusDistance(d.R, Rs[i]) < 1e-3 && (d.t / d.t.norm() - ts[i] / ts[i].norm()).norm() < 1e-3) { bsolution_found = true; // Check that E holds the essential matrix constraints. @@ -213,78 +196,74 @@ BOOST_AUTO_TEST_CASE(FivePointsRelativePose_Random) BOOST_AUTO_TEST_CASE(FivePointsRelativePose_test_data_sets) { + //-- Setup a circular camera rig and assert that 5PT relative pose works. + const int iNviews = 5; + NViewDataSet d = NRealisticCamerasRing(iNviews, 5, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K - //-- Setup a circular camera rig and assert that 5PT relative pose works. - const int iNviews = 5; - NViewDataSet d = NRealisticCamerasRing(iNviews, 5, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - // Compute pose [R|t] from 0 to [1;..;iNviews] - for(int i=1; i Es; // Essential. - std::vector Rs; // Rotation matrix. - std::vector ts; // Translation matrix. - - multiview::relativePose::Essential5PSolver solver; - - solver.solve(d._x[0], d._x[i], Es); - - // Recover rotation and translation from E. - Rs.resize(Es.size()); - ts.resize(Es.size()); - for (size_t s = 0; s < Es.size(); ++s) { - Vec2 x1Col = d._x[0].col(0); - Vec2 x2Col = d._x[i].col(0); - BOOST_CHECK( - motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), - d._K[0], - x1Col, - d._K[i], - x2Col, - &Rs[s], - &ts[s])); - } - //-- Compute Ground Truth motion - Mat3 R; - Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); - relativeCameraMotion(d._R[0], d._t[0], d._R[i], d._t[i], &R, &t); + // Compute pose [R|t] from 0 to [1;..;iNviews] + for (int i = 1; i < iNviews; ++i) + { + std::vector Es; // Essential. + std::vector Rs; // Rotation matrix. + std::vector ts; // Translation matrix. + + multiview::relativePose::Essential5PSolver solver; + + solver.solve(d._x[0], d._x[i], Es); - // Assert that found relative motion is correct for almost one model. - bool bsolution_found = false; - for (size_t nModel = 0; nModel < Es.size(); ++nModel) { + // Recover rotation and translation from E. + Rs.resize(Es.size()); + ts.resize(Es.size()); + for (size_t s = 0; s < Es.size(); ++s) + { + Vec2 x1Col = d._x[0].col(0); + Vec2 x2Col = d._x[i].col(0); + BOOST_CHECK(motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), d._K[0], x1Col, d._K[i], x2Col, &Rs[s], &ts[s])); + } + //-- Compute Ground Truth motion + Mat3 R; + Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); + relativeCameraMotion(d._R[0], d._t[0], d._R[i], d._t[i], &R, &t); - // Check that E holds the essential matrix constraints. - EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-8); + // Assert that found relative motion is correct for almost one model. + bool bsolution_found = false; + for (size_t nModel = 0; nModel < Es.size(); ++nModel) + { + // Check that E holds the essential matrix constraints. + EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-8); - // Check that we find the correct relative orientation. - if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 - && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3 ) { - bsolution_found = true; - } + // Check that we find the correct relative orientation. + if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3) + { + bsolution_found = true; + } + } + //-- Almost one solution must find the correct relative orientation + BOOST_CHECK(bsolution_found); } - //-- Almost one solution must find the correct relative orientation - BOOST_CHECK(bsolution_found); - } } -BOOST_AUTO_TEST_CASE(FivePointsNullspaceBasis_SatisfyEpipolarConstraint) { - - TestData d = SomeTestData(); +BOOST_AUTO_TEST_CASE(FivePointsNullspaceBasis_SatisfyEpipolarConstraint) +{ + TestData d = SomeTestData(); - Mat E_basis = multiview::relativePose::fivePointsNullspaceBasis(d.x1, d.x2); + Mat E_basis = multiview::relativePose::fivePointsNullspaceBasis(d.x1, d.x2); - for (int s = 0; s < 4; ++s) { - Mat3 E; - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - E(i, j) = E_basis(3 * i + j, s); - } - } - for (int i = 0; i < d.x1.cols(); ++i) { - Vec3 x1(d.x1(0,i), d.x1(1,i), 1); - Vec3 x2(d.x2(0,i), d.x2(1,i), 1); - BOOST_CHECK_SMALL(x2.dot(E * x1), 1e-6); + for (int s = 0; s < 4; ++s) + { + Mat3 E; + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + E(i, j) = E_basis(3 * i + j, s); + } + } + for (int i = 0; i < d.x1.cols(); ++i) + { + Vec3 x1(d.x1(0, i), d.x1(1, i), 1); + Vec3 x2(d.x2(0, i), d.x2(1, i), 1); + BOOST_CHECK_SMALL(x2.dot(E * x1), 1e-6); + } } - } } diff --git a/src/aliceVision/multiview/relativePose/essentialKernel_test.cpp b/src/aliceVision/multiview/relativePose/essentialKernel_test.cpp index 03f1113c3a..21dfbb0a04 100644 --- a/src/aliceVision/multiview/relativePose/essentialKernel_test.cpp +++ b/src/aliceVision/multiview/relativePose/essentialKernel_test.cpp @@ -11,7 +11,6 @@ #include #include - #define BOOST_TEST_MODULE essentialKernelSolver #include @@ -23,238 +22,225 @@ using namespace aliceVision::multiview; /// check that the E matrix fit the Essential Matrix properties /// determinant is 0 -#define EXPECT_ESSENTIAL_MATRIX_PROPERTIES(E, expectedPrecision) { \ - BOOST_CHECK_SMALL(E.determinant(), expectedPrecision); \ - Mat3 O = 2 * E * E.transpose() * E - (E * E.transpose()).trace() * E; \ - Mat3 zero3x3 = Mat3::Zero(); \ - EXPECT_MATRIX_NEAR(zero3x3, O, expectedPrecision);\ -} +#define EXPECT_ESSENTIAL_MATRIX_PROPERTIES(E, expectedPrecision) \ + { \ + BOOST_CHECK_SMALL(E.determinant(), expectedPrecision); \ + Mat3 O = 2 * E * E.transpose() * E - (E * E.transpose()).trace() * E; \ + Mat3 zero3x3 = Mat3::Zero(); \ + EXPECT_MATRIX_NEAR(zero3x3, O, expectedPrecision); \ + } BOOST_AUTO_TEST_CASE(Essential8PSolver_IdFocal) { - // setup a circular camera rig and assert that 8PT relative pose works. - const int iNviews = 5; - NViewDataSet d = NRealisticCamerasRing(iNviews, 8, NViewDatasetConfigurator(1,1,0,0,5,0)); // suppose a camera with Unit matrix as K - - for(int i=0; i Es; // essential, - std::vector Rs; // rotation matrix. - std::vector ts; // translation matrix. - - relativePose::Essential8PSolver solver; - solver.solve(d._x[i], d._x[(i+1)%iNviews], Es); - - // Recover rotation and translation from E. - Rs.resize(Es.size()); - ts.resize(Es.size()); + // setup a circular camera rig and assert that 8PT relative pose works. + const int iNviews = 5; + NViewDataSet d = NRealisticCamerasRing(iNviews, 8, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // suppose a camera with Unit matrix as K - for(int s = 0; s < Es.size(); ++s) + for (int i = 0; i < iNviews; ++i) { - Vec2 x1Col, x2Col; - x1Col << d._x[i].col(0); - x2Col << d._x[(i+1)%iNviews].col(0); - - BOOST_CHECK( - motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), - d._K[i], x1Col, - d._K[(i+1)%iNviews], x2Col, - &Rs[s], - &ts[s])); + std::vector Es; // essential, + std::vector Rs; // rotation matrix. + std::vector ts; // translation matrix. + + relativePose::Essential8PSolver solver; + solver.solve(d._x[i], d._x[(i + 1) % iNviews], Es); + + // Recover rotation and translation from E. + Rs.resize(Es.size()); + ts.resize(Es.size()); + + for (int s = 0; s < Es.size(); ++s) + { + Vec2 x1Col, x2Col; + x1Col << d._x[i].col(0); + x2Col << d._x[(i + 1) % iNviews].col(0); + + BOOST_CHECK(motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), d._K[i], x1Col, d._K[(i + 1) % iNviews], x2Col, &Rs[s], &ts[s])); + } + //-- Compute Ground Truth motion + Mat3 R; + Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); + relativeCameraMotion(d._R[i], d._t[i], d._R[(i + 1) % iNviews], d._t[(i + 1) % iNviews], &R, &t); + + // Assert that found relative motion is correct for almost one model. + bool bsolution_found = false; + for (std::size_t nModel = 0; nModel < Es.size(); ++nModel) + { + // Check that E holds the essential matrix constraints. + EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-8); + + // Check that we find the correct relative orientation. + if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3) + { + bsolution_found = true; + } + } + // almost one solution must find the correct relative orientation + BOOST_CHECK(bsolution_found); } - //-- Compute Ground Truth motion - Mat3 R; - Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); - relativeCameraMotion(d._R[i], d._t[i], d._R[(i+1)%iNviews], d._t[(i+1)%iNviews], &R, &t); - - // Assert that found relative motion is correct for almost one model. - bool bsolution_found = false; - for (std::size_t nModel = 0; nModel < Es.size(); ++nModel) - { - - // Check that E holds the essential matrix constraints. - EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-8); - - // Check that we find the correct relative orientation. - if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 - && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3 ) { - bsolution_found = true; - } - } - // almost one solution must find the correct relative orientation - BOOST_CHECK(bsolution_found); - } } BOOST_AUTO_TEST_CASE(Essential8PKernel_EightPointsRelativePose) { - using Kernel = relativePose::Essential8PKernel; - - int focal = 1000; - int principal_Point = 500; + using Kernel = relativePose::Essential8PKernel; - // setup a circular camera rig and assert that 8PT relative pose works. - const int iNviews = 5; - NViewDataSet d = NRealisticCamerasRing(iNviews, Kernel::SolverT().getMinimumNbRequiredSamples(), - NViewDatasetConfigurator(focal,focal,principal_Point,principal_Point,5,0)); // Suppose a camera with Unit matrix as K + int focal = 1000; + int principal_Point = 500; - for(int i=0; i Es; // Essential - std::vector Rs; // Rotation matrix. - std::vector ts; // Translation matrix. + // setup a circular camera rig and assert that 8PT relative pose works. + const int iNviews = 5; + NViewDataSet d = NRealisticCamerasRing( + iNviews, + Kernel::SolverT().getMinimumNbRequiredSamples(), + NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); // Suppose a camera with Unit matrix as K - // Direct value do not work. - // As we use reference, it cannot convert Mat2X& to Mat& - Mat x0 = d._x[i]; - Mat x1 = d._x[(i+1)%iNviews]; - - Kernel kernel(x0, x1, d._K[i], d._K[(i+1)%iNviews]); - std::vector samples; - - for (std::size_t k = 0; k < kernel.getMinimumNbRequiredSamples(); ++k) - { - samples.push_back(k); - } - - kernel.fit(samples, Es); - - // Recover rotation and translation from E. - Rs.resize(Es.size()); - ts.resize(Es.size()); - - for (int s = 0; s < Es.size(); ++s) + for (int i = 0; i < iNviews; ++i) { - Vec2 x1Col, x2Col; - x1Col << d._x[i].col(0); - x2Col << d._x[(i+1)%iNviews].col(0); - BOOST_CHECK( - motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), - d._K[i], x1Col, - d._K[(i+1)%iNviews], x2Col, - &Rs[s], - &ts[s])); + std::vector Es; // Essential + std::vector Rs; // Rotation matrix. + std::vector ts; // Translation matrix. + + // Direct value do not work. + // As we use reference, it cannot convert Mat2X& to Mat& + Mat x0 = d._x[i]; + Mat x1 = d._x[(i + 1) % iNviews]; + + Kernel kernel(x0, x1, d._K[i], d._K[(i + 1) % iNviews]); + std::vector samples; + + for (std::size_t k = 0; k < kernel.getMinimumNbRequiredSamples(); ++k) + { + samples.push_back(k); + } + + kernel.fit(samples, Es); + + // Recover rotation and translation from E. + Rs.resize(Es.size()); + ts.resize(Es.size()); + + for (int s = 0; s < Es.size(); ++s) + { + Vec2 x1Col, x2Col; + x1Col << d._x[i].col(0); + x2Col << d._x[(i + 1) % iNviews].col(0); + BOOST_CHECK(motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), d._K[i], x1Col, d._K[(i + 1) % iNviews], x2Col, &Rs[s], &ts[s])); + } + //-- Compute Ground Truth motion + Mat3 R; + Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); + relativeCameraMotion(d._R[i], d._t[i], d._R[(i + 1) % iNviews], d._t[(i + 1) % iNviews], &R, &t); + + // Assert that found relative motion is correct for almost one model. + bool bsolution_found = false; + for (std::size_t nModel = 0; nModel < Es.size(); ++nModel) + { + // Check that E holds the essential matrix constraints. + EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-8); + + // Check that we find the correct relative orientation. + if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3) + { + bsolution_found = true; + } + } + //-- Almost one solution must find the correct relative orientation + BOOST_CHECK(bsolution_found); } - //-- Compute Ground Truth motion - Mat3 R; - Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); - relativeCameraMotion(d._R[i], d._t[i], d._R[(i+1)%iNviews], d._t[(i+1)%iNviews], &R, &t); - - // Assert that found relative motion is correct for almost one model. - bool bsolution_found = false; - for (std::size_t nModel = 0; nModel < Es.size(); ++nModel) - { - - // Check that E holds the essential matrix constraints. - EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-8); - - // Check that we find the correct relative orientation. - if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 - && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3 ) { - bsolution_found = true; - } - } - //-- Almost one solution must find the correct relative orientation - BOOST_CHECK(bsolution_found); - } } BOOST_AUTO_TEST_CASE(Essential5PKernel_KernelError) { + Mat x1(2, 5), x2(2, 5); + x1 << 0, 0, 0, .8, .8, 0, -.5, .8, 0, .8; + x2 << 0, 0, 0, .8, .8, .1, -.4, .9, .1, .9; // Y Translated camera. - Mat x1(2, 5), x2(2, 5); - x1 << 0, 0, 0, .8, .8, - 0, -.5, .8, 0, .8; - x2 << 0, 0, 0, .8, .8, - .1, -.4, .9, .1, .9; // Y Translated camera. + using Kernel = relativePose::Essential5PKernel; - using Kernel = relativePose::Essential5PKernel; + Kernel kernel(x1, x2, Mat3::Identity(), Mat3::Identity()); - Kernel kernel(x1,x2, Mat3::Identity(), Mat3::Identity()); - - std::vector samples; - for (std::size_t i = 0; i < x1.cols(); ++i) { - samples.push_back(i); - } - std::vector Es; - kernel.fit(samples, Es); + std::vector samples; + for (std::size_t i = 0; i < x1.cols(); ++i) + { + samples.push_back(i); + } + std::vector Es; + kernel.fit(samples, Es); - for (int i = 0; i < Es.size(); ++i) { - for(int j = 0; j < x1.cols(); ++j) - BOOST_CHECK_SMALL(kernel.error(j, Es.at(i)), 1e-8); - } + for (int i = 0; i < Es.size(); ++i) + { + for (int j = 0; j < x1.cols(); ++j) + BOOST_CHECK_SMALL(kernel.error(j, Es.at(i)), 1e-8); + } } BOOST_AUTO_TEST_CASE(Essential5PKernel_FivePointsRelativePose) { - using Kernel = relativePose::Essential5PKernel; + using Kernel = relativePose::Essential5PKernel; - int focal = 1000; - int principal_Point = 500; + int focal = 1000; + int principal_Point = 500; - //-- Setup a circular camera rig and assert that 5PT relative pose works. - const int iNviews = 8; - NViewDataSet d = NRealisticCamerasRing(iNviews, Kernel::SolverT().getMinimumNbRequiredSamples(), - NViewDatasetConfigurator(focal,focal,principal_Point,principal_Point,5,0)); // Suppose a camera with Unit matrix as K + //-- Setup a circular camera rig and assert that 5PT relative pose works. + const int iNviews = 8; + NViewDataSet d = NRealisticCamerasRing( + iNviews, + Kernel::SolverT().getMinimumNbRequiredSamples(), + NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); // Suppose a camera with Unit matrix as K - std::size_t found = 0; - for(int i=1; i Es; // Essential - std::vector Rs; // Rotation matrix. - std::vector ts; // Translation matrix. - - // Direct value do not work. - // As we use reference, it cannot convert Mat2X& to Mat& - Mat x0 = d._x[0]; - Mat x1 = d._x[i]; - - Kernel kernel(x0, x1, d._K[0], d._K[1]); - std::vector samples; - for (std::size_t k = 0; k < kernel.getMinimumNbRequiredSamples(); ++k) - { - samples.push_back(k); - } - - kernel.fit(samples, Es); - - // Recover rotation and translation from E. - Rs.resize(Es.size()); - ts.resize(Es.size()); - for (int s = 0; s < Es.size(); ++s) - { - Vec2 x1Col, x2Col; - x1Col << d._x[0].col(0); - x2Col << d._x[i].col(0); - BOOST_CHECK( - motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), - d._K[0], x1Col, - d._K[i], x2Col, - &Rs[s], - &ts[s])); - } - //-- Compute Ground Truth motion - Mat3 R; - Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); - relativeCameraMotion(d._R[0], d._t[0], d._R[i], d._t[i], &R, &t); - - // Assert that found relative motion is correct for almost one model. - bool bsolution_found = false; - for (std::size_t nModel = 0; nModel < Es.size(); ++nModel) + std::size_t found = 0; + for (int i = 1; i < iNviews; ++i) { - // Check that E holds the essential matrix constraints. - EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-4); - - // Check that we find the correct relative orientation. - if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 - && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3 ) { - bsolution_found = true; - } + std::vector Es; // Essential + std::vector Rs; // Rotation matrix. + std::vector ts; // Translation matrix. + + // Direct value do not work. + // As we use reference, it cannot convert Mat2X& to Mat& + Mat x0 = d._x[0]; + Mat x1 = d._x[i]; + + Kernel kernel(x0, x1, d._K[0], d._K[1]); + std::vector samples; + for (std::size_t k = 0; k < kernel.getMinimumNbRequiredSamples(); ++k) + { + samples.push_back(k); + } + + kernel.fit(samples, Es); + + // Recover rotation and translation from E. + Rs.resize(Es.size()); + ts.resize(Es.size()); + for (int s = 0; s < Es.size(); ++s) + { + Vec2 x1Col, x2Col; + x1Col << d._x[0].col(0); + x2Col << d._x[i].col(0); + BOOST_CHECK(motionFromEssentialAndCorrespondence(Es.at(s).getMatrix(), d._K[0], x1Col, d._K[i], x2Col, &Rs[s], &ts[s])); + } + //-- Compute Ground Truth motion + Mat3 R; + Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); + relativeCameraMotion(d._R[0], d._t[0], d._R[i], d._t[i], &R, &t); + + // Assert that found relative motion is correct for almost one model. + bool bsolution_found = false; + for (std::size_t nModel = 0; nModel < Es.size(); ++nModel) + { + // Check that E holds the essential matrix constraints. + EXPECT_ESSENTIAL_MATRIX_PROPERTIES(Es.at(nModel).getMatrix(), 1e-4); + + // Check that we find the correct relative orientation. + if (FrobeniusDistance(R, Rs[nModel]) < 1e-3 && (t / t.norm() - ts[nModel] / ts[nModel].norm()).norm() < 1e-3) + { + bsolution_found = true; + } + } + // Almost one solution must find the correct relative orientation + BOOST_CHECK(bsolution_found); + if (bsolution_found) + found++; } - // Almost one solution must find the correct relative orientation - BOOST_CHECK(bsolution_found); - if (bsolution_found) - found++; - } - BOOST_CHECK_EQUAL(iNviews-1, found); + BOOST_CHECK_EQUAL(iNviews - 1, found); } diff --git a/src/aliceVision/multiview/relativePose/fundamental10PSolver_test.cpp b/src/aliceVision/multiview/relativePose/fundamental10PSolver_test.cpp index ca828b9ae2..ae384c215c 100644 --- a/src/aliceVision/multiview/relativePose/fundamental10PSolver_test.cpp +++ b/src/aliceVision/multiview/relativePose/fundamental10PSolver_test.cpp @@ -18,152 +18,170 @@ using namespace aliceVision::multiview; BOOST_AUTO_TEST_CASE(Fundamental10PSolver_8_solutions) { - // input data - Mat X = Mat(10, 2); - X << 1.425203022203948e-01, 1.802107961554276e-02, - -2.467473080283717e-01, 1.134013928865132e-01, - -2.165429045024671e-01, 1.016561086554276e-01, - -1.779508570620888e-01, -5.443423622532895e-02, - -9.503401906866776e-02, -1.912260035464638e-01, - 4.285402960526316e-01, 8.614113255550987e-02, - -3.182964124177631e-02, -1.139683291786595e-01, - -8.882934570312501e-02, 9.867373817845394e-02, - 3.867745811060855e-01, 1.629066868832237e-02, - 4.764409436677632e-01, -8.015930175781250e-02; - - Mat U = Mat(10, 2); - U << 1.470885587993421e-01, -3.716578433388158e-02, - -3.384269955283717e-01, 3.505518863075658e-02, - -2.903711258737665e-01, 2.336130242598684e-02, - -2.017722360711349e-01, -1.733409359580592e-01, - -1.022310598273026e-01, -1.984814292506168e-01, - 4.136125745271382e-01, 5.560238486842106e-02, - -4.780700683593750e-02, -1.279896786338405e-01, - -1.000526829769737e-01, 3.618122301603619e-02, - 3.628858064350329e-01, -6.060566149259868e-03, - 4.513226639597039e-01, -1.025031481291119e-01; - - // transpose for aliceVision - X.transposeInPlace(); - U.transposeInPlace(); - - // expected result - std::vector resF; - Mat3 resF1; resF1 << -5.751087019565978e+02, -4.158188692023641e+02, -3.933617847277337e+01, 5.623077923604983e+02, -1.155037417506222e+03, -3.603293875557833e+01, -5.277848487166921e-01, -9.666992599768776e-02, 1.000000000000000e+00; resF.push_back(resF1); - Mat3 resF2; resF2 << -2.199589063941379e+04, -2.847966717222817e+04, -1.568596434350923e+02, 2.668165615957419e+04, 3.794695160371831e+04, 3.582097175710577e+02, -1.985243717996453e+03, 3.206256740219327e+03, 1.000000000000000e+00; resF.push_back(resF2); - Mat3 resF3; resF3 << -2.406164718504593e+02, 1.051858077445098e+03, 1.029257202497226e+02, -9.384606808289793e+02, -1.561769129182295e+00, -7.588146214289489e+00, -2.671999077624011e+01, 1.072299253095423e+00, 1.000000000000000e+00; resF.push_back(resF3); - Mat3 resF4; resF4 << 4.773291645793250e+02, -7.340046095681545e+03, -7.622433073293142e+01, 7.353659444399805e+03, -7.228144265103590e+02, 2.313924014459177e+01, -4.096068639840655e+02, 1.760210427160575e+02, 1.000000000000000e+00; resF.push_back(resF4); - Mat3 resF5; resF5 << -3.634107580978586e+00, -6.861915294293681e+01, -1.419442789733012e+00, 1.043103524635315e+02, -9.905128840695012e+01, -1.288978514515288e+01, -5.004364829690772e+00, 1.759690666207339e+01, 1.000000000000000e+00; resF.push_back(resF5); - Mat3 resF6; resF6 << 3.845891112065496e+02, -3.376624483974341e+02, 1.058628706262809e+00, 4.567090509397883e+02, -1.782032107508239e+01, 6.603245933354176e-01, -3.057044617411873e+01, -1.171693147510399e+01, 1.000000000000000e+00; resF.push_back(resF6); - Mat3 resF7; resF7 << 9.173705728965071e-01, 6.566802897577452e+02, -9.150842187178472e+01, -6.985651797488533e+02, 6.389672979364663e+01, -2.813972637838619e+01, 1.224499375450345e+02, 2.273467046355762e+00, 1.000000000000000e+00; resF.push_back(resF7); - Mat3 resF8; resF8 << 9.260243587671360e+00, 2.901973712182848e+01, -4.525967397412600e+00, 3.873064498047484e+00, -3.308114531934898e+01, -1.268705935682481e+01, 3.742963523739931e+00, 1.576531480674961e+01, 1.000000000000000e+00; resF.push_back(resF8); - - std::vector resL; - Mat2X resL1(2, 1); resL1 << 6.664890785418972e+02, 9.360374635703592e-01; resL.push_back(resL1); - Mat2X resL2(2, 1); resL2 << -1.401232955798790e+02, 1.190659194737138e+03; resL.push_back(resL2); - Mat2X resL3(2, 1); resL3 << 5.478399615715365e+01, 1.277075963855622e+01; resL.push_back(resL3); - Mat2X resL4(2, 1); resL4 << -3.004792949356860e+01, 1.442424098845748e+02; resL.push_back(resL4); - Mat2X resL5(2, 1); resL5 << 5.619131137084262e+00, 1.528997232119745e+01; resL.push_back(resL5); - Mat2X resL6(2, 1); resL6 << -1.359819306123174e+00, -3.194231629423156e+02; resL.push_back(resL6); - Mat2X resL7(2, 1); resL7 << -7.668121663596985e+00, -1.015843563423398e+01; resL.push_back(resL7); - Mat2X resL8(2, 1); resL8 << -8.478427431999348e+00, -4.469547181112368e+00; resL.push_back(resL8); - - // process - std::vector models; - relativePose::Fundamental10PSolver().solve(X, U, models); - - // test results - if(resF.size() != models.size()) - BOOST_CHECK(false); - - //Results may not be in the same order - for(Eigen::Index i = 0; i < resF.size(); ++i) - { - relativePose::Fundamental10PModel model = models.at(i); - - bool oneEqual = false; - for (int j = 0; j < resF.size(); j++) + // input data + Mat X = Mat(10, 2); + X << 1.425203022203948e-01, 1.802107961554276e-02, -2.467473080283717e-01, 1.134013928865132e-01, -2.165429045024671e-01, 1.016561086554276e-01, + -1.779508570620888e-01, -5.443423622532895e-02, -9.503401906866776e-02, -1.912260035464638e-01, 4.285402960526316e-01, 8.614113255550987e-02, + -3.182964124177631e-02, -1.139683291786595e-01, -8.882934570312501e-02, 9.867373817845394e-02, 3.867745811060855e-01, 1.629066868832237e-02, + 4.764409436677632e-01, -8.015930175781250e-02; + + Mat U = Mat(10, 2); + U << 1.470885587993421e-01, -3.716578433388158e-02, -3.384269955283717e-01, 3.505518863075658e-02, -2.903711258737665e-01, 2.336130242598684e-02, + -2.017722360711349e-01, -1.733409359580592e-01, -1.022310598273026e-01, -1.984814292506168e-01, 4.136125745271382e-01, 5.560238486842106e-02, + -4.780700683593750e-02, -1.279896786338405e-01, -1.000526829769737e-01, 3.618122301603619e-02, 3.628858064350329e-01, -6.060566149259868e-03, + 4.513226639597039e-01, -1.025031481291119e-01; + + // transpose for aliceVision + X.transposeInPlace(); + U.transposeInPlace(); + + // expected result + std::vector resF; + Mat3 resF1; + resF1 << -5.751087019565978e+02, -4.158188692023641e+02, -3.933617847277337e+01, 5.623077923604983e+02, -1.155037417506222e+03, + -3.603293875557833e+01, -5.277848487166921e-01, -9.666992599768776e-02, 1.000000000000000e+00; + resF.push_back(resF1); + Mat3 resF2; + resF2 << -2.199589063941379e+04, -2.847966717222817e+04, -1.568596434350923e+02, 2.668165615957419e+04, 3.794695160371831e+04, + 3.582097175710577e+02, -1.985243717996453e+03, 3.206256740219327e+03, 1.000000000000000e+00; + resF.push_back(resF2); + Mat3 resF3; + resF3 << -2.406164718504593e+02, 1.051858077445098e+03, 1.029257202497226e+02, -9.384606808289793e+02, -1.561769129182295e+00, + -7.588146214289489e+00, -2.671999077624011e+01, 1.072299253095423e+00, 1.000000000000000e+00; + resF.push_back(resF3); + Mat3 resF4; + resF4 << 4.773291645793250e+02, -7.340046095681545e+03, -7.622433073293142e+01, 7.353659444399805e+03, -7.228144265103590e+02, + 2.313924014459177e+01, -4.096068639840655e+02, 1.760210427160575e+02, 1.000000000000000e+00; + resF.push_back(resF4); + Mat3 resF5; + resF5 << -3.634107580978586e+00, -6.861915294293681e+01, -1.419442789733012e+00, 1.043103524635315e+02, -9.905128840695012e+01, + -1.288978514515288e+01, -5.004364829690772e+00, 1.759690666207339e+01, 1.000000000000000e+00; + resF.push_back(resF5); + Mat3 resF6; + resF6 << 3.845891112065496e+02, -3.376624483974341e+02, 1.058628706262809e+00, 4.567090509397883e+02, -1.782032107508239e+01, + 6.603245933354176e-01, -3.057044617411873e+01, -1.171693147510399e+01, 1.000000000000000e+00; + resF.push_back(resF6); + Mat3 resF7; + resF7 << 9.173705728965071e-01, 6.566802897577452e+02, -9.150842187178472e+01, -6.985651797488533e+02, 6.389672979364663e+01, + -2.813972637838619e+01, 1.224499375450345e+02, 2.273467046355762e+00, 1.000000000000000e+00; + resF.push_back(resF7); + Mat3 resF8; + resF8 << 9.260243587671360e+00, 2.901973712182848e+01, -4.525967397412600e+00, 3.873064498047484e+00, -3.308114531934898e+01, + -1.268705935682481e+01, 3.742963523739931e+00, 1.576531480674961e+01, 1.000000000000000e+00; + resF.push_back(resF8); + + std::vector resL; + Mat2X resL1(2, 1); + resL1 << 6.664890785418972e+02, 9.360374635703592e-01; + resL.push_back(resL1); + Mat2X resL2(2, 1); + resL2 << -1.401232955798790e+02, 1.190659194737138e+03; + resL.push_back(resL2); + Mat2X resL3(2, 1); + resL3 << 5.478399615715365e+01, 1.277075963855622e+01; + resL.push_back(resL3); + Mat2X resL4(2, 1); + resL4 << -3.004792949356860e+01, 1.442424098845748e+02; + resL.push_back(resL4); + Mat2X resL5(2, 1); + resL5 << 5.619131137084262e+00, 1.528997232119745e+01; + resL.push_back(resL5); + Mat2X resL6(2, 1); + resL6 << -1.359819306123174e+00, -3.194231629423156e+02; + resL.push_back(resL6); + Mat2X resL7(2, 1); + resL7 << -7.668121663596985e+00, -1.015843563423398e+01; + resL.push_back(resL7); + Mat2X resL8(2, 1); + resL8 << -8.478427431999348e+00, -4.469547181112368e+00; + resL.push_back(resL8); + + // process + std::vector models; + relativePose::Fundamental10PSolver().solve(X, U, models); + + // test results + if (resF.size() != models.size()) + BOOST_CHECK(false); + + // Results may not be in the same order + for (Eigen::Index i = 0; i < resF.size(); ++i) { - bool testF = resF.at(j).isApprox(model.getMatrix(), 1e-1); - bool testL = resL.at(j).isApprox(model.getRadialDistortion(), 1e-1); + relativePose::Fundamental10PModel model = models.at(i); - if (testF && testL) + bool oneEqual = false; + for (int j = 0; j < resF.size(); j++) { - oneEqual = true; + bool testF = resF.at(j).isApprox(model.getMatrix(), 1e-1); + bool testL = resL.at(j).isApprox(model.getRadialDistortion(), 1e-1); + + if (testF && testL) + { + oneEqual = true; + } } + + BOOST_CHECK(oneEqual); } - - BOOST_CHECK(oneEqual); - } } BOOST_AUTO_TEST_CASE(Fundamental10PSolver_2_solutions) { - // input data - Mat X = Mat(10, 2); - X << -3.229677381013569e-01, 9.796846088610198e-02, - -1.859613679584704e-01, -1.331652189555921e-02, - 3.993208393297698e-01, -4.309213738692434e-02, - 4.570184647409539e-01, 6.516200015419409e-02, - -2.922935084292763e-02, -3.059326252184416e-01, - -1.386923378392270e-01, -4.580717387952302e-02, - -7.286097476356908e-02, 6.164560418379934e-02, - 5.396587171052632e-02, 9.743896484375000e-02, - 4.628304893092105e-01, -1.323552021227385e-01, - 4.939758300781250e-01, -4.290067973889802e-02; - - Mat U = Mat(10, 2); - U << -4.438024018940173e-01, 3.003572162828947e-05, - -2.212387888055099e-01, -1.264103939658717e-01, - 3.731781327097040e-01, -6.214079204358552e-02, - 4.439694053248355e-01, 3.183979235197369e-02, - 2.125557026110198e-01, -1.950167364823191e-01, - -1.560874216180099e-01, -7.848533228824013e-02, - -7.585523103412829e-02, -1.291279039884868e-02, - -1.167459909539474e-01, 2.065792686060855e-02, - 4.302636076274671e-01, -1.466708052785773e-01, - 4.942827405427632e-01, -8.317700837787830e-02; - - // transpose for aliceVision - X.transposeInPlace(); - U.transposeInPlace(); - - // expected result - std::vector resF; - { - Mat3 resF1; - resF1 << 1.732473500041804e+02, 7.711017146713161e+00, 9.471075243833084e+00, -1.518330376101678e+01, 3.516937871974938e+02, 1.097973146625093e+01, -1.046922253151639e-01, 6.610316585565101e-03, 1.000000000000000e+00; - resF.push_back(resF1); + // input data + Mat X = Mat(10, 2); + X << -3.229677381013569e-01, 9.796846088610198e-02, -1.859613679584704e-01, -1.331652189555921e-02, 3.993208393297698e-01, -4.309213738692434e-02, + 4.570184647409539e-01, 6.516200015419409e-02, -2.922935084292763e-02, -3.059326252184416e-01, -1.386923378392270e-01, -4.580717387952302e-02, + -7.286097476356908e-02, 6.164560418379934e-02, 5.396587171052632e-02, 9.743896484375000e-02, 4.628304893092105e-01, -1.323552021227385e-01, + 4.939758300781250e-01, -4.290067973889802e-02; + + Mat U = Mat(10, 2); + U << -4.438024018940173e-01, 3.003572162828947e-05, -2.212387888055099e-01, -1.264103939658717e-01, 3.731781327097040e-01, -6.214079204358552e-02, + 4.439694053248355e-01, 3.183979235197369e-02, 2.125557026110198e-01, -1.950167364823191e-01, -1.560874216180099e-01, -7.848533228824013e-02, + -7.585523103412829e-02, -1.291279039884868e-02, -1.167459909539474e-01, 2.065792686060855e-02, 4.302636076274671e-01, -1.466708052785773e-01, + 4.942827405427632e-01, -8.317700837787830e-02; + + // transpose for aliceVision + X.transposeInPlace(); + U.transposeInPlace(); + + // expected result + std::vector resF; + { + Mat3 resF1; + resF1 << 1.732473500041804e+02, 7.711017146713161e+00, 9.471075243833084e+00, -1.518330376101678e+01, 3.516937871974938e+02, + 1.097973146625093e+01, -1.046922253151639e-01, 6.610316585565101e-03, 1.000000000000000e+00; + resF.push_back(resF1); + + Mat3 resF2; + resF2 << -1.716387900591730e+04, -3.791505579727971e+04, -4.981406222447205e+00, 6.621894661079650e+03, 4.767612634161247e+04, + -1.134635374165869e+02, 1.131438262492466e+03, -1.429200024334792e+04, 1.000000000000000e+00; + resF.push_back(resF2); + } - Mat3 resF2; - resF2 << -1.716387900591730e+04, -3.791505579727971e+04, -4.981406222447205e+00, 6.621894661079650e+03, 4.767612634161247e+04, -1.134635374165869e+02, 1.131438262492466e+03, -1.429200024334792e+04, 1.000000000000000e+00; - resF.push_back(resF2); - } + std::vector resL; + { + relativePose::Fundamental10PModel::Mat21 resL1; + relativePose::Fundamental10PModel::Mat21 resL2; - std::vector resL; - { - relativePose::Fundamental10PModel::Mat21 resL1; - relativePose::Fundamental10PModel::Mat21 resL2; + resL1 << -1.904715904949555e+02, 4.147643173367164e-01; + resL.push_back(resL1); - resL1 << -1.904715904949555e+02, 4.147643173367164e-01; - resL.push_back(resL1); + resL2 << 1.258548483732838e+01, -2.918230091342369e+03; + resL.push_back(resL2); + } - resL2 << 1.258548483732838e+01, -2.918230091342369e+03; - resL.push_back(resL2); - } - - // process - std::vector models; - relativePose::Fundamental10PSolver().solve(X, U, models); - - // test results - if(resF.size() != models.size()) - BOOST_CHECK(false); - - for(Eigen::Index i = 0; i < resF.size(); ++i) - { - relativePose::Fundamental10PModel model = models.at(i); - BOOST_CHECK(resF.at(i).isApprox(model.getMatrix(), 1e-1)); - BOOST_CHECK(resL.at(i).isApprox(model.getRadialDistortion(), 1e-1)); - } + // process + std::vector models; + relativePose::Fundamental10PSolver().solve(X, U, models); + + // test results + if (resF.size() != models.size()) + BOOST_CHECK(false); + + for (Eigen::Index i = 0; i < resF.size(); ++i) + { + relativePose::Fundamental10PModel model = models.at(i); + BOOST_CHECK(resF.at(i).isApprox(model.getMatrix(), 1e-1)); + BOOST_CHECK(resL.at(i).isApprox(model.getRadialDistortion(), 1e-1)); + } } diff --git a/src/aliceVision/multiview/relativePose/fundamentalKernel_test.cpp b/src/aliceVision/multiview/relativePose/fundamentalKernel_test.cpp index f00cda2634..6d652a5a13 100644 --- a/src/aliceVision/multiview/relativePose/fundamentalKernel_test.cpp +++ b/src/aliceVision/multiview/relativePose/fundamentalKernel_test.cpp @@ -21,46 +21,43 @@ using namespace aliceVision::multiview; template bool colinear(const A& a, const B& b, double tolerance) { - const bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); + const bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); - if(!dims_match) - return false; + if (!dims_match) + return false; - const double c = CosinusBetweenMatrices(a, b); + const double c = CosinusBetweenMatrices(a, b); - if(c * c < 1) - { - double s = sqrt(1 - c * c); - return fabs(s) < tolerance; - } + if (c * c < 1) + { + double s = sqrt(1 - c * c); + return fabs(s) < tolerance; + } - return false; + return false; } // check the properties of a fundamental matrix: // 1. The determinant is 0 (rank deficient) // 2. The condition x'T*F*x = 0 is satisfied to precision. template -bool expectFundamentalProperties(const TMat& F, - const Mat& ptsA, - const Mat& ptsB, - double precision) +bool expectFundamentalProperties(const TMat& F, const Mat& ptsA, const Mat& ptsB, double precision) { - bool bOk = true; - bOk &= F.determinant() < precision; - assert(ptsA.cols() == ptsB.cols()); - Mat hptsA, hptsB; + bool bOk = true; + bOk &= F.determinant() < precision; + assert(ptsA.cols() == ptsB.cols()); + Mat hptsA, hptsB; - euclideanToHomogeneous(ptsA, hptsA); - euclideanToHomogeneous(ptsB, hptsB); + euclideanToHomogeneous(ptsA, hptsA); + euclideanToHomogeneous(ptsB, hptsB); - for(int i = 0; i < ptsA.cols(); ++i) - { - const double residual = hptsB.col(i).dot(F * hptsA.col(i)); - bOk &= residual < precision; - } + for (int i = 0; i < ptsA.cols(); ++i) + { + const double residual = hptsB.col(i).dot(F * hptsA.col(i)); + bOk &= residual < precision; + } - return bOk; + return bOk; } // check the fundamental fitting: @@ -69,67 +66,59 @@ bool expectFundamentalProperties(const TMat& F, template bool expectKernelProperties(const Mat& x1, const Mat& x2, Mat3* F_expected = nullptr) { - bool bOk = true; - const Kernel kernel(x1, x2); - std::vector samples; + bool bOk = true; + const Kernel kernel(x1, x2); + std::vector samples; - for(std::size_t i = 0; i < x1.cols(); ++i) - samples.push_back(i); + for (std::size_t i = 0; i < x1.cols(); ++i) + samples.push_back(i); - std::vector Fs; - kernel.fit(samples, Fs); + std::vector Fs; + kernel.fit(samples, Fs); - bOk &= (!Fs.empty()); + bOk &= (!Fs.empty()); - for(int i = 0; i < Fs.size(); ++i) - { - bOk &= expectFundamentalProperties(Fs.at(i).getMatrix(), x1, x2, 1e-8); - if(F_expected) - bOk &= colinear(Fs.at(i).getMatrix(), *F_expected, 1e-6); - } - return bOk; + for (int i = 0; i < Fs.size(); ++i) + { + bOk &= expectFundamentalProperties(Fs.at(i).getMatrix(), x1, x2, 1e-8); + if (F_expected) + bOk &= colinear(Fs.at(i).getMatrix(), *F_expected, 1e-6); + } + return bOk; } BOOST_AUTO_TEST_CASE(Fundamental7PKernel_EasyCase) { - Mat x1(2, 7), x2(2, 7); - x1 << 0, 0, 0, 1, 1, 1, 2, - 0, 1, 2, 0, 1, 2, 0; - x2 << 0, 0, 0, 1, 1, 1, 2, - 1, 2, 3, 1, 2, 3, 1; + Mat x1(2, 7), x2(2, 7); + x1 << 0, 0, 0, 1, 1, 1, 2, 0, 1, 2, 0, 1, 2, 0; + x2 << 0, 0, 0, 1, 1, 1, 2, 1, 2, 3, 1, 2, 3, 1; - BOOST_CHECK(expectKernelProperties(x1, x2)); + BOOST_CHECK(expectKernelProperties(x1, x2)); } BOOST_AUTO_TEST_CASE(NormalizedFundamental7PKernel_EasyCase) { - Mat x1(2, 7), x2(2, 7); - x1 << 0, 0, 0, 1, 1, 1, 2, - 0, 1, 2, 0, 1, 2, 0; - x2 << 0, 0, 0, 1, 1, 1, 2, - 1, 2, 3, 1, 2, 3, 1; + Mat x1(2, 7), x2(2, 7); + x1 << 0, 0, 0, 1, 1, 1, 2, 0, 1, 2, 0, 1, 2, 0; + x2 << 0, 0, 0, 1, 1, 1, 2, 1, 2, 3, 1, 2, 3, 1; - BOOST_CHECK(expectKernelProperties(x1, x2)); + BOOST_CHECK(expectKernelProperties(x1, x2)); } BOOST_AUTO_TEST_CASE(Fundamental8PKernel_EasyCase) { - Mat x1(2, 8), x2(2, 8); - x1 << 0, 0, 0, 1, 1, 1, 2, 2, - 0, 1, 2, 0, 1, 2, 0, 1; - x2 << 0, 0, 0, 1, 1, 1, 2, 2, - 1, 2, 3, 1, 2, 3, 1, 2; + Mat x1(2, 8), x2(2, 8); + x1 << 0, 0, 0, 1, 1, 1, 2, 2, 0, 1, 2, 0, 1, 2, 0, 1; + x2 << 0, 0, 0, 1, 1, 1, 2, 2, 1, 2, 3, 1, 2, 3, 1, 2; - BOOST_CHECK(expectKernelProperties(x1, x2)); + BOOST_CHECK(expectKernelProperties(x1, x2)); } BOOST_AUTO_TEST_CASE(NormalizedFundamental8PKernel_EasyCase) { - Mat x1(2, 8), x2(2, 8); - x1 << 0, 0, 0, 1, 1, 1, 2, 2, - 0, 1, 2, 0, 1, 2, 0, 1; - x2 << 0, 0, 0, 1, 1, 1, 2, 2, - 1, 2, 3, 1, 2, 3, 1, 2; + Mat x1(2, 8), x2(2, 8); + x1 << 0, 0, 0, 1, 1, 1, 2, 2, 0, 1, 2, 0, 1, 2, 0, 1; + x2 << 0, 0, 0, 1, 1, 1, 2, 2, 1, 2, 3, 1, 2, 3, 1, 2; - BOOST_CHECK(expectKernelProperties(x1, x2)); + BOOST_CHECK(expectKernelProperties(x1, x2)); } diff --git a/src/aliceVision/multiview/relativePose/homographyKernel_test.cpp b/src/aliceVision/multiview/relativePose/homographyKernel_test.cpp index c092e8d060..b00d09f15f 100644 --- a/src/aliceVision/multiview/relativePose/homographyKernel_test.cpp +++ b/src/aliceVision/multiview/relativePose/homographyKernel_test.cpp @@ -18,85 +18,75 @@ using namespace aliceVision::multiview; BOOST_AUTO_TEST_CASE(NormalizedHomography4PKernel_Fitting) { - // define 3 knows homographies (Use as GT). - std::vector H_gt(3); - - H_gt[0] = Mat3::Identity(); - H_gt[1] << 1, 0, -4, - 0, 1, 5, - 0, 0, 1; - H_gt[2] << 1, -2, 3, - 4, 5, -6, - -7, 8, 1; - - // define a set of points. - Mat x(2, 9), xh; - x << 0, 0, 0, 1, 1, 1, 2, 2, 2, - 0, 1, 2, 0, 1, 2, 0, 1, 2; - - euclideanToHomogeneous(x, xh); - - for(int i = 0; i < H_gt.size(); ++i) - { - // transform points by the ground truth homography. - Mat y, yh = H_gt[i] * xh; - homogeneousToEuclidean(yh, y); - - const relativePose::NormalizedHomography4PKernel kernel(x, y); - - std::size_t samples_[5] = { 0, 1, 2, 3, 4 }; - std::vector samples(samples_,samples_+5); - for(std::size_t j = 4; samples.size() < x.cols(); samples.push_back(j++)) - { - std::vector Hs; - kernel.fit(samples, Hs); - BOOST_CHECK_EQUAL(1, Hs.size()); + // define 3 knows homographies (Use as GT). + std::vector H_gt(3); + + H_gt[0] = Mat3::Identity(); + H_gt[1] << 1, 0, -4, 0, 1, 5, 0, 0, 1; + H_gt[2] << 1, -2, 3, 4, 5, -6, -7, 8, 1; + + // define a set of points. + Mat x(2, 9), xh; + x << 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2; + + euclideanToHomogeneous(x, xh); - // check that found matrix is equal to the GT - EXPECT_MATRIX_PROP(H_gt[i], Hs.at(0).getMatrix(), 1e-6); + for (int i = 0; i < H_gt.size(); ++i) + { + // transform points by the ground truth homography. + Mat y, yh = H_gt[i] * xh; + homogeneousToEuclidean(yh, y); + + const relativePose::NormalizedHomography4PKernel kernel(x, y); + + std::size_t samples_[5] = {0, 1, 2, 3, 4}; + std::vector samples(samples_, samples_ + 5); + for (std::size_t j = 4; samples.size() < x.cols(); samples.push_back(j++)) + { + std::vector Hs; + kernel.fit(samples, Hs); + BOOST_CHECK_EQUAL(1, Hs.size()); + + // check that found matrix is equal to the GT + EXPECT_MATRIX_PROP(H_gt[i], Hs.at(0).getMatrix(), 1e-6); + } } - } } BOOST_AUTO_TEST_CASE(Homography4PKernel_Fitting) { - // define 3 knows homographies (Use as GT). - std::vector H_gt(3); + // define 3 knows homographies (Use as GT). + std::vector H_gt(3); - H_gt[0] = Mat3::Identity(); - H_gt[1] << 1, 0, -4, - 0, 1, 5, - 0, 0, 1; - H_gt[2] << 1, -2, 3, - 4, 5, -6, - -7, 8, 1; + H_gt[0] = Mat3::Identity(); + H_gt[1] << 1, 0, -4, 0, 1, 5, 0, 0, 1; + H_gt[2] << 1, -2, 3, 4, 5, -6, -7, 8, 1; - // define a set of points. - Mat x(2, 9), xh; - x << 0, 0, 0, 1, 1, 1, 2, 2, 2, - 0, 1, 2, 0, 1, 2, 0, 1, 2; + // define a set of points. + Mat x(2, 9), xh; + x << 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2; - euclideanToHomogeneous(x, xh); + euclideanToHomogeneous(x, xh); - for(int i = 0; i < H_gt.size(); ++i) - { - // transform points by the ground truth homography. - Mat y, yh = H_gt[i] * xh; - homogeneousToEuclidean(yh, y); + for (int i = 0; i < H_gt.size(); ++i) + { + // transform points by the ground truth homography. + Mat y, yh = H_gt[i] * xh; + homogeneousToEuclidean(yh, y); - const relativePose::Homography4PKernel kernel(x, y); + const relativePose::Homography4PKernel kernel(x, y); - std::size_t samples_[5]={0,1,2,3,4}; - std::vector samples(samples_,samples_+5); + std::size_t samples_[5] = {0, 1, 2, 3, 4}; + std::vector samples(samples_, samples_ + 5); - for(std::size_t j = 4; samples.size() < x.cols(); samples.push_back(j++)) - { - std::vector Hs; - kernel.fit(samples, Hs); - BOOST_CHECK_EQUAL(1, Hs.size()); + for (std::size_t j = 4; samples.size() < x.cols(); samples.push_back(j++)) + { + std::vector Hs; + kernel.fit(samples, Hs); + BOOST_CHECK_EQUAL(1, Hs.size()); - // check that found matrix is equal to the GT - EXPECT_MATRIX_PROP(H_gt[i], Hs.at(0).getMatrix(), 1e-6); + // check that found matrix is equal to the GT + EXPECT_MATRIX_PROP(H_gt[i], Hs.at(0).getMatrix(), 1e-6); + } } - } } diff --git a/src/aliceVision/multiview/resection/EPnPKernel.hpp b/src/aliceVision/multiview/resection/EPnPKernel.hpp index 5189172440..af38c7cd4e 100644 --- a/src/aliceVision/multiview/resection/EPnPKernel.hpp +++ b/src/aliceVision/multiview/resection/EPnPKernel.hpp @@ -22,50 +22,49 @@ namespace resection { */ class EPnPKernel : public robustEstimation::PointFittingKernel { - public: + public: + using KernelBase = robustEstimation::PointFittingKernel; - using KernelBase = robustEstimation::PointFittingKernel; - - EPnPKernel(const Mat2X& x2d, const Mat3X& x3d) - : KernelBase(x2d, x3d) - , x_camera_(x2d) - , X_(x3d) - { + EPnPKernel(const Mat2X& x2d, const Mat3X& x3d) + : KernelBase(x2d, x3d), + x_camera_(x2d), + X_(x3d) + { assert(x2d.cols() == x3d.cols()); K_ = Mat3::Identity(); - } + } - EPnPKernel(const Mat2X& x2d, const Mat3X& x3d, const Mat3& K) - : KernelBase(x2d, x3d) - , X_(x3d), K_(K) + EPnPKernel(const Mat2X& x2d, const Mat3X& x3d, const Mat3& K) + : KernelBase(x2d, x3d), + X_(x3d), + K_(K) { - assert(x2d.cols() == x3d.cols()); - // Conversion from image coordinates to normalized camera coordinates - euclideanToNormalizedCamera(x2d, K, x_camera_); + assert(x2d.cols() == x3d.cols()); + // Conversion from image coordinates to normalized camera coordinates + euclideanToNormalizedCamera(x2d, K, x_camera_); } - void fit(const std::vector& samples, std::vector& models) const override - { - Mat2X x = buildSubsetMatrix(x_camera_, samples); - Mat3X X = buildSubsetMatrix(X_, samples); - Mat34 P; - Mat3 R; - Vec3 t; - if (_kernelSolver.resection(x, X, &R, &t)) + void fit(const std::vector& samples, std::vector& models) const override { - P_from_KRt(K_, R, t, P); - models.emplace_back(P); + Mat2X x = buildSubsetMatrix(x_camera_, samples); + Mat3X X = buildSubsetMatrix(X_, samples); + Mat34 P; + Mat3 R; + Vec3 t; + if (_kernelSolver.resection(x, X, &R, &t)) + { + P_from_KRt(K_, R, t, P); + models.emplace_back(P); + } } - } - private: - // x_camera_ contains the normalized camera coordinates - Mat2X x_camera_; - const Mat3X &X_; - Mat3 K_; + private: + // x_camera_ contains the normalized camera coordinates + Mat2X x_camera_; + const Mat3X& X_; + Mat3 K_; }; } // namespace resection } // namespace multiview } // namespace aliceVision - diff --git a/src/aliceVision/multiview/resection/EPnPSolver.cpp b/src/aliceVision/multiview/resection/EPnPSolver.cpp index 7749410f56..d97f52563d 100644 --- a/src/aliceVision/multiview/resection/EPnPSolver.cpp +++ b/src/aliceVision/multiview/resection/EPnPSolver.cpp @@ -20,30 +20,30 @@ namespace resection { */ void selectControlPoints(const Mat3X& x3d, Mat* xCentered, Mat34* xControlPoints) { - const std::size_t nbPoints = x3d.cols(); - - // the first virtual control point, C0, is the centroid. - Vec mean, variance; - MeanAndVarianceAlongRows(x3d, &mean, &variance); - xControlPoints->col(0) = mean; - - // computes PCA - xCentered->resize (3, nbPoints); - for(std::size_t c = 0; c < nbPoints; ++c) - { - xCentered->col(c) = x3d.col (c) - mean; - } - - Mat3 xCenteredSQ = (*xCentered) * xCentered->transpose(); - Eigen::JacobiSVD xCenteredSQsvd(xCenteredSQ, Eigen::ComputeFullU); - const Vec3 w = xCenteredSQsvd.singularValues(); - const Mat3 u = xCenteredSQsvd.matrixU(); - - for(std::size_t c = 0; c < 3; ++c) - { - const double k = std::sqrt(w(c) / nbPoints); - xControlPoints->col(c + 1) = mean + k * u.col(c); - } + const std::size_t nbPoints = x3d.cols(); + + // the first virtual control point, C0, is the centroid. + Vec mean, variance; + MeanAndVarianceAlongRows(x3d, &mean, &variance); + xControlPoints->col(0) = mean; + + // computes PCA + xCentered->resize(3, nbPoints); + for (std::size_t c = 0; c < nbPoints; ++c) + { + xCentered->col(c) = x3d.col(c) - mean; + } + + Mat3 xCenteredSQ = (*xCentered) * xCentered->transpose(); + Eigen::JacobiSVD xCenteredSQsvd(xCenteredSQ, Eigen::ComputeFullU); + const Vec3 w = xCenteredSQsvd.singularValues(); + const Mat3 u = xCenteredSQsvd.matrixU(); + + for (std::size_t c = 0; c < 3; ++c) + { + const double k = std::sqrt(w(c) / nbPoints); + xControlPoints->col(c + 1) = mean + k * u.col(c); + } } /** @@ -54,25 +54,25 @@ void selectControlPoints(const Mat3X& x3d, Mat* xCentered, Mat34* xControlPoints */ void computeBarycentricCoordinates(const Mat3X& xWorldCentered, const Mat34& xControlPoints, Mat4X* alphas) { - const std::size_t nbPoints = xWorldCentered.cols(); - Mat3 C2; + const std::size_t nbPoints = xWorldCentered.cols(); + Mat3 C2; - for(std::size_t c = 1; c < 4; ++c) - { - C2.col(c-1) = xControlPoints.col(c) - xControlPoints.col(0); - } + for (std::size_t c = 1; c < 4; ++c) + { + C2.col(c - 1) = xControlPoints.col(c) - xControlPoints.col(0); + } - Mat3 C2inv = C2.inverse(); - Mat3X a = C2inv * xWorldCentered; + Mat3 C2inv = C2.inverse(); + Mat3X a = C2inv * xWorldCentered; - alphas->resize(4, nbPoints); - alphas->setZero(); - alphas->block(1, 0, 3, nbPoints) = a; + alphas->resize(4, nbPoints); + alphas->setZero(); + alphas->block(1, 0, 3, nbPoints) = a; - for(std::size_t c = 0; c < nbPoints; ++c) - { - (*alphas)(0, c) = 1.0 - alphas->col(c).sum(); - } + for (std::size_t c = 0; c < nbPoints; ++c) + { + (*alphas)(0, c) = 1.0 - alphas->col(c).sum(); + } } /** @@ -84,42 +84,42 @@ void computeBarycentricCoordinates(const Mat3X& xWorldCentered, const Mat34& xCo */ void computePointsCoordinatesInCameraFrame(const Mat4X& alphas, const Vec4& betas, const Eigen::Matrix& U, Mat3X* xCamera) { - const std::size_t nbPoints = alphas.cols(); - - // estimates the control points in the camera reference frame. - Mat34 C2b; - C2b.setZero(); - for(std::size_t cu = 0; cu < 4; ++cu) - { - for(std::size_t c = 0; c < 4; ++c) + const std::size_t nbPoints = alphas.cols(); + + // estimates the control points in the camera reference frame. + Mat34 C2b; + C2b.setZero(); + for (std::size_t cu = 0; cu < 4; ++cu) { - C2b.col(c) += betas(cu) * U.block(11 - cu, c * 3, 1, 3).transpose(); + for (std::size_t c = 0; c < 4; ++c) + { + C2b.col(c) += betas(cu) * U.block(11 - cu, c * 3, 1, 3).transpose(); + } } - } - - // estimates the 3D points in the camera reference frame - xCamera->resize(3, nbPoints); - for(std::size_t c = 0; c < nbPoints; ++c) - { - xCamera->col(c) = C2b * alphas.col(c); - } - - // check the sign of the z coordinate of the points (should be positive) - std::size_t num_z_neg = 0; - for(Mat3X::Index i = 0; i < xCamera->cols(); ++i) - { - if((*xCamera)(2,i) < 0) + + // estimates the 3D points in the camera reference frame + xCamera->resize(3, nbPoints); + for (std::size_t c = 0; c < nbPoints; ++c) { - num_z_neg++; + xCamera->col(c) = C2b * alphas.col(c); + } + + // check the sign of the z coordinate of the points (should be positive) + std::size_t num_z_neg = 0; + for (Mat3X::Index i = 0; i < xCamera->cols(); ++i) + { + if ((*xCamera)(2, i) < 0) + { + num_z_neg++; + } + } + + // if more than 50% of z are negative, we change the signs + if (num_z_neg > 0.5 * xCamera->cols()) + { + C2b = -C2b; + *xCamera = -(*xCamera); } - } - - // if more than 50% of z are negative, we change the signs - if(num_z_neg > 0.5 * xCamera->cols()) - { - C2b = -C2b; - *xCamera = -(*xCamera); - } } /** @@ -131,343 +131,319 @@ void computePointsCoordinatesInCameraFrame(const Mat4X& alphas, const Vec4& beta */ void absoluteOrientation(const Mat3X& X, const Mat3X& Xp, Mat3* R, Vec3* t) { - const int nbPoints = static_cast(X.cols()); - Vec3 C = X.rowwise().sum() / nbPoints; // centroid of X. - Vec3 Cp = Xp.rowwise().sum() / nbPoints; // centroid of Xp. - - // normalize the two point sets. - Mat3X Xn(3, nbPoints), Xpn(3, nbPoints); - - for(int i = 0; i < nbPoints; ++i) - { - Xn.col(i) = X.col(i) - C; - Xpn.col(i) = Xp.col(i) - Cp; - } - - // construct the N matrix (pg. 635). - const double Sxx = Xn.row(0).dot(Xpn.row(0)); - const double Syy = Xn.row(1).dot(Xpn.row(1)); - const double Szz = Xn.row(2).dot(Xpn.row(2)); - const double Sxy = Xn.row(0).dot(Xpn.row(1)); - const double Syx = Xn.row(1).dot(Xpn.row(0)); - const double Sxz = Xn.row(0).dot(Xpn.row(2)); - const double Szx = Xn.row(2).dot(Xpn.row(0)); - const double Syz = Xn.row(1).dot(Xpn.row(2)); - const double Szy = Xn.row(2).dot(Xpn.row(1)); - - Mat4 N; - N << Sxx + Syy + Szz, Syz - Szy, Szx - Sxz, Sxy - Syx, - Syz - Szy, Sxx - Syy - Szz, Sxy + Syx, Szx + Sxz, - Szx - Sxz, Sxy + Syx, -Sxx + Syy - Szz, Syz + Szy, - Sxy - Syx, Szx + Sxz, Syz + Szy, -Sxx - Syy + Szz; - - // find the unit quaternion q that maximizes qNq. It is the eigenvector - // corresponding to the largest eigenvalue. - const Vec4 q = N.jacobiSvd(Eigen::ComputeFullU).matrixU().col(0); - - // retrieve the 3x3 rotation matrix. - const Vec4 qq = q.array() * q.array(); - - const double q0q1 = q(0) * q(1); - const double q0q2 = q(0) * q(2); - const double q0q3 = q(0) * q(3); - const double q1q2 = q(1) * q(2); - const double q1q3 = q(1) * q(3); - const double q2q3 = q(2) * q(3); - - (*R) << qq(0) + qq(1) - qq(2) - qq(3), - 2 * (q1q2 - q0q3), - 2 * (q1q3 + q0q2), - 2 * (q1q2+ q0q3), - qq(0) - qq(1) + qq(2) - qq(3), - 2 * (q2q3 - q0q1), - 2 * (q1q3 - q0q2), - 2 * (q2q3 + q0q1), - qq(0) - qq(1) - qq(2) + qq(3); - - // fix the handedness of the R matrix. - if(R->determinant() < 0) - { - R->row(2) = -R->row(2); - } - - // compute the final translation. - *t = Cp - *R * C; + const int nbPoints = static_cast(X.cols()); + Vec3 C = X.rowwise().sum() / nbPoints; // centroid of X. + Vec3 Cp = Xp.rowwise().sum() / nbPoints; // centroid of Xp. + + // normalize the two point sets. + Mat3X Xn(3, nbPoints), Xpn(3, nbPoints); + + for (int i = 0; i < nbPoints; ++i) + { + Xn.col(i) = X.col(i) - C; + Xpn.col(i) = Xp.col(i) - Cp; + } + + // construct the N matrix (pg. 635). + const double Sxx = Xn.row(0).dot(Xpn.row(0)); + const double Syy = Xn.row(1).dot(Xpn.row(1)); + const double Szz = Xn.row(2).dot(Xpn.row(2)); + const double Sxy = Xn.row(0).dot(Xpn.row(1)); + const double Syx = Xn.row(1).dot(Xpn.row(0)); + const double Sxz = Xn.row(0).dot(Xpn.row(2)); + const double Szx = Xn.row(2).dot(Xpn.row(0)); + const double Syz = Xn.row(1).dot(Xpn.row(2)); + const double Szy = Xn.row(2).dot(Xpn.row(1)); + + Mat4 N; + N << Sxx + Syy + Szz, Syz - Szy, Szx - Sxz, Sxy - Syx, Syz - Szy, Sxx - Syy - Szz, Sxy + Syx, Szx + Sxz, Szx - Sxz, Sxy + Syx, -Sxx + Syy - Szz, + Syz + Szy, Sxy - Syx, Szx + Sxz, Syz + Szy, -Sxx - Syy + Szz; + + // find the unit quaternion q that maximizes qNq. It is the eigenvector + // corresponding to the largest eigenvalue. + const Vec4 q = N.jacobiSvd(Eigen::ComputeFullU).matrixU().col(0); + + // retrieve the 3x3 rotation matrix. + const Vec4 qq = q.array() * q.array(); + + const double q0q1 = q(0) * q(1); + const double q0q2 = q(0) * q(2); + const double q0q3 = q(0) * q(3); + const double q1q2 = q(1) * q(2); + const double q1q3 = q(1) * q(3); + const double q2q3 = q(2) * q(3); + + (*R) << qq(0) + qq(1) - qq(2) - qq(3), 2 * (q1q2 - q0q3), 2 * (q1q3 + q0q2), 2 * (q1q2 + q0q3), qq(0) - qq(1) + qq(2) - qq(3), 2 * (q2q3 - q0q1), + 2 * (q1q3 - q0q2), 2 * (q2q3 + q0q1), qq(0) - qq(1) - qq(2) + qq(3); + + // fix the handedness of the R matrix. + if (R->determinant() < 0) + { + R->row(2) = -R->row(2); + } + + // compute the final translation. + *t = Cp - *R * C; } bool EPnPSolver::resection(const Mat2X& x2d, const Mat3X& x3d, Mat3* R, Vec3* t) const { - assert(x2d.cols() == x3d.cols()); - assert(x2d.cols() > 3); - - const std::size_t nbPoints = x3d.cols(); - - // select the control points. - Mat34 xControlPoints; - Mat xCentered; - - selectControlPoints(x3d, &xCentered, &xControlPoints); - - // compute the barycentric coordinates. - Mat4X alphas(4, nbPoints); - computeBarycentricCoordinates(xCentered, xControlPoints, &alphas); - - // estimates the M matrix with the barycentric coordinates - Mat M(2 * nbPoints, 12); - - for(std::size_t c = 0; c < nbPoints; ++c) - { - const double a0 = alphas(0, c); - const double a1 = alphas(1, c); - const double a2 = alphas(2, c); - const double a3 = alphas(3, c); - const double ui = x2d(0, c); - const double vi = x2d(1, c); - - M.block(2*c, 0, 2, 12) << a0, 0, - a0*(-ui), a1, 0, - a1*(-ui), a2, 0, - a2*(-ui), a3, 0, - a3*(-ui), 0, - a0, a0*(-vi), 0, - a1, a1*(-vi), 0, - a2, a2*(-vi), 0, - a3, a3*(-vi); - } - - // @todo: avoid the transpose by rewriting the u2.block() calls. - Eigen::JacobiSVD MtMsvd(M.transpose()*M, Eigen::ComputeFullU); - Eigen::Matrix u2 = MtMsvd.matrixU().transpose(); - - // estimate the L matrix. - Eigen::Matrix dv1; - Eigen::Matrix dv2; - Eigen::Matrix dv3; - Eigen::Matrix dv4; - - dv1.row(0) = u2.block(11, 0, 1, 3) - u2.block(11, 3, 1, 3); - dv1.row(1) = u2.block(11, 0, 1, 3) - u2.block(11, 6, 1, 3); - dv1.row(2) = u2.block(11, 0, 1, 3) - u2.block(11, 9, 1, 3); - dv1.row(3) = u2.block(11, 3, 1, 3) - u2.block(11, 6, 1, 3); - dv1.row(4) = u2.block(11, 3, 1, 3) - u2.block(11, 9, 1, 3); - dv1.row(5) = u2.block(11, 6, 1, 3) - u2.block(11, 9, 1, 3); - dv2.row(0) = u2.block(10, 0, 1, 3) - u2.block(10, 3, 1, 3); - dv2.row(1) = u2.block(10, 0, 1, 3) - u2.block(10, 6, 1, 3); - dv2.row(2) = u2.block(10, 0, 1, 3) - u2.block(10, 9, 1, 3); - dv2.row(3) = u2.block(10, 3, 1, 3) - u2.block(10, 6, 1, 3); - dv2.row(4) = u2.block(10, 3, 1, 3) - u2.block(10, 9, 1, 3); - dv2.row(5) = u2.block(10, 6, 1, 3) - u2.block(10, 9, 1, 3); - dv3.row(0) = u2.block( 9, 0, 1, 3) - u2.block( 9, 3, 1, 3); - dv3.row(1) = u2.block( 9, 0, 1, 3) - u2.block( 9, 6, 1, 3); - dv3.row(2) = u2.block( 9, 0, 1, 3) - u2.block( 9, 9, 1, 3); - dv3.row(3) = u2.block( 9, 3, 1, 3) - u2.block( 9, 6, 1, 3); - dv3.row(4) = u2.block( 9, 3, 1, 3) - u2.block( 9, 9, 1, 3); - dv3.row(5) = u2.block( 9, 6, 1, 3) - u2.block( 9, 9, 1, 3); - dv4.row(0) = u2.block( 8, 0, 1, 3) - u2.block( 8, 3, 1, 3); - dv4.row(1) = u2.block( 8, 0, 1, 3) - u2.block( 8, 6, 1, 3); - dv4.row(2) = u2.block( 8, 0, 1, 3) - u2.block( 8, 9, 1, 3); - dv4.row(3) = u2.block( 8, 3, 1, 3) - u2.block( 8, 6, 1, 3); - dv4.row(4) = u2.block( 8, 3, 1, 3) - u2.block( 8, 9, 1, 3); - dv4.row(5) = u2.block( 8, 6, 1, 3) - u2.block( 8, 9, 1, 3); - - Eigen::Matrix L; - for(std::size_t r = 0; r < 6; ++r) - { - L.row(r) << dv1.row(r).dot(dv1.row(r)), - 2.0 * dv1.row(r).dot(dv2.row(r)), - dv2.row(r).dot(dv2.row(r)), - 2.0 * dv1.row(r).dot(dv3.row(r)), - 2.0 * dv2.row(r).dot(dv3.row(r)), - dv3.row(r).dot(dv3.row(r)), - 2.0 * dv1.row(r).dot(dv4.row(r)), - 2.0 * dv2.row(r).dot(dv4.row(r)), - 2.0 * dv3.row(r).dot(dv4.row(r)), - dv4.row(r).dot(dv4.row(r)); - } - - Vec rho; - rho.resize(6); - rho << (xControlPoints.col(0) - xControlPoints.col(1)).squaredNorm(), - (xControlPoints.col(0) - xControlPoints.col(2)).squaredNorm(), - (xControlPoints.col(0) - xControlPoints.col(3)).squaredNorm(), - (xControlPoints.col(1) - xControlPoints.col(2)).squaredNorm(), - (xControlPoints.col(1) - xControlPoints.col(3)).squaredNorm(), - (xControlPoints.col(2) - xControlPoints.col(3)).squaredNorm(); - - // there are three possible solutions based on the three approximations of L - // (betas). below, each one is solved for then the best one is chosen. - Mat3X xCamera; - Mat3 K; K.setIdentity(); - std::vector Rs(3); - std::vector ts(3); - Vec rmse(3); - - bool bSol = false; - - // find the first possible solution for R, t corresponding to: - // Betas = [b00 b01 b11 b02 b12 b22 b03 b13 b23 b33] - // Betas_approx_1 = [b00 b01 b02 b03] - Vec4 betas = Vec4::Zero(); - Eigen::Matrix l_6x4; - - for(std::size_t r = 0; r < 6; ++r) - { - l_6x4.row(r) << L(r, 0), L(r, 1), L(r, 3), L(r, 6); - } - - Eigen::JacobiSVD svd_of_l4(l_6x4, Eigen::ComputeFullU | Eigen::ComputeFullV); - Vec4 b4 = svd_of_l4.solve(rho); - - if((l_6x4 * b4).isApprox(rho, 1e-3)) - { - if(b4(0) < 0) + assert(x2d.cols() == x3d.cols()); + assert(x2d.cols() > 3); + + const std::size_t nbPoints = x3d.cols(); + + // select the control points. + Mat34 xControlPoints; + Mat xCentered; + + selectControlPoints(x3d, &xCentered, &xControlPoints); + + // compute the barycentric coordinates. + Mat4X alphas(4, nbPoints); + computeBarycentricCoordinates(xCentered, xControlPoints, &alphas); + + // estimates the M matrix with the barycentric coordinates + Mat M(2 * nbPoints, 12); + + for (std::size_t c = 0; c < nbPoints; ++c) + { + const double a0 = alphas(0, c); + const double a1 = alphas(1, c); + const double a2 = alphas(2, c); + const double a3 = alphas(3, c); + const double ui = x2d(0, c); + const double vi = x2d(1, c); + + M.block(2 * c, 0, 2, 12) << a0, 0, a0 * (-ui), a1, 0, a1 * (-ui), a2, 0, a2 * (-ui), a3, 0, a3 * (-ui), 0, a0, a0 * (-vi), 0, a1, a1 * (-vi), + 0, a2, a2 * (-vi), 0, a3, a3 * (-vi); + } + + // @todo: avoid the transpose by rewriting the u2.block() calls. + Eigen::JacobiSVD MtMsvd(M.transpose() * M, Eigen::ComputeFullU); + Eigen::Matrix u2 = MtMsvd.matrixU().transpose(); + + // estimate the L matrix. + Eigen::Matrix dv1; + Eigen::Matrix dv2; + Eigen::Matrix dv3; + Eigen::Matrix dv4; + + dv1.row(0) = u2.block(11, 0, 1, 3) - u2.block(11, 3, 1, 3); + dv1.row(1) = u2.block(11, 0, 1, 3) - u2.block(11, 6, 1, 3); + dv1.row(2) = u2.block(11, 0, 1, 3) - u2.block(11, 9, 1, 3); + dv1.row(3) = u2.block(11, 3, 1, 3) - u2.block(11, 6, 1, 3); + dv1.row(4) = u2.block(11, 3, 1, 3) - u2.block(11, 9, 1, 3); + dv1.row(5) = u2.block(11, 6, 1, 3) - u2.block(11, 9, 1, 3); + dv2.row(0) = u2.block(10, 0, 1, 3) - u2.block(10, 3, 1, 3); + dv2.row(1) = u2.block(10, 0, 1, 3) - u2.block(10, 6, 1, 3); + dv2.row(2) = u2.block(10, 0, 1, 3) - u2.block(10, 9, 1, 3); + dv2.row(3) = u2.block(10, 3, 1, 3) - u2.block(10, 6, 1, 3); + dv2.row(4) = u2.block(10, 3, 1, 3) - u2.block(10, 9, 1, 3); + dv2.row(5) = u2.block(10, 6, 1, 3) - u2.block(10, 9, 1, 3); + dv3.row(0) = u2.block(9, 0, 1, 3) - u2.block(9, 3, 1, 3); + dv3.row(1) = u2.block(9, 0, 1, 3) - u2.block(9, 6, 1, 3); + dv3.row(2) = u2.block(9, 0, 1, 3) - u2.block(9, 9, 1, 3); + dv3.row(3) = u2.block(9, 3, 1, 3) - u2.block(9, 6, 1, 3); + dv3.row(4) = u2.block(9, 3, 1, 3) - u2.block(9, 9, 1, 3); + dv3.row(5) = u2.block(9, 6, 1, 3) - u2.block(9, 9, 1, 3); + dv4.row(0) = u2.block(8, 0, 1, 3) - u2.block(8, 3, 1, 3); + dv4.row(1) = u2.block(8, 0, 1, 3) - u2.block(8, 6, 1, 3); + dv4.row(2) = u2.block(8, 0, 1, 3) - u2.block(8, 9, 1, 3); + dv4.row(3) = u2.block(8, 3, 1, 3) - u2.block(8, 6, 1, 3); + dv4.row(4) = u2.block(8, 3, 1, 3) - u2.block(8, 9, 1, 3); + dv4.row(5) = u2.block(8, 6, 1, 3) - u2.block(8, 9, 1, 3); + + Eigen::Matrix L; + for (std::size_t r = 0; r < 6; ++r) { - b4 = -b4; + L.row(r) << dv1.row(r).dot(dv1.row(r)), 2.0 * dv1.row(r).dot(dv2.row(r)), dv2.row(r).dot(dv2.row(r)), 2.0 * dv1.row(r).dot(dv3.row(r)), + 2.0 * dv2.row(r).dot(dv3.row(r)), dv3.row(r).dot(dv3.row(r)), 2.0 * dv1.row(r).dot(dv4.row(r)), 2.0 * dv2.row(r).dot(dv4.row(r)), + 2.0 * dv3.row(r).dot(dv4.row(r)), dv4.row(r).dot(dv4.row(r)); } - b4(0) = std::sqrt(b4(0)); - betas << b4(0), b4(1) / b4(0), b4(2) / b4(0), b4(3) / b4(0); - computePointsCoordinatesInCameraFrame(alphas, betas, u2, &xCamera); - absoluteOrientation(x3d, xCamera, &Rs[0], &ts[0]); - rmse(0) = reprojectionErrorRMSE(x2d, x3d, K, Rs[0], ts[0]); - bSol = true; - } - else - { - // ALICEVISION_LOG_WARNING("First approximation of beta not good enough."); - ts[0].setZero(); - rmse(0) = std::numeric_limits::max(); - } - - // find the second possible solution for R, t corresponding to: - // Betas = [b00 b01 b11 b02 b12 b22 b03 b13 b23 b33] - // Betas_approx_2 = [b00 b01 b11] - betas.setZero(); - Eigen::Matrix l_6x3; - l_6x3 = L.block(0, 0, 6, 3); - Eigen::JacobiSVD svdOfL3(l_6x3, Eigen::ComputeFullU | Eigen::ComputeFullV); - Vec3 b3 = svdOfL3.solve(rho); - - if((l_6x3 * b3).isApprox(rho, 1e-3)) - { - if(b3(0) < 0) + Vec rho; + rho.resize(6); + rho << (xControlPoints.col(0) - xControlPoints.col(1)).squaredNorm(), (xControlPoints.col(0) - xControlPoints.col(2)).squaredNorm(), + (xControlPoints.col(0) - xControlPoints.col(3)).squaredNorm(), (xControlPoints.col(1) - xControlPoints.col(2)).squaredNorm(), + (xControlPoints.col(1) - xControlPoints.col(3)).squaredNorm(), (xControlPoints.col(2) - xControlPoints.col(3)).squaredNorm(); + + // there are three possible solutions based on the three approximations of L + // (betas). below, each one is solved for then the best one is chosen. + Mat3X xCamera; + Mat3 K; + K.setIdentity(); + std::vector Rs(3); + std::vector ts(3); + Vec rmse(3); + + bool bSol = false; + + // find the first possible solution for R, t corresponding to: + // Betas = [b00 b01 b11 b02 b12 b22 b03 b13 b23 b33] + // Betas_approx_1 = [b00 b01 b02 b03] + Vec4 betas = Vec4::Zero(); + Eigen::Matrix l_6x4; + + for (std::size_t r = 0; r < 6; ++r) { - betas(0) = std::sqrt(-b3(0)); - betas(1) = (b3(2) < 0) ? std::sqrt(-b3(2)) : 0; + l_6x4.row(r) << L(r, 0), L(r, 1), L(r, 3), L(r, 6); + } + + Eigen::JacobiSVD svd_of_l4(l_6x4, Eigen::ComputeFullU | Eigen::ComputeFullV); + Vec4 b4 = svd_of_l4.solve(rho); + + if ((l_6x4 * b4).isApprox(rho, 1e-3)) + { + if (b4(0) < 0) + { + b4 = -b4; + } + + b4(0) = std::sqrt(b4(0)); + betas << b4(0), b4(1) / b4(0), b4(2) / b4(0), b4(3) / b4(0); + computePointsCoordinatesInCameraFrame(alphas, betas, u2, &xCamera); + absoluteOrientation(x3d, xCamera, &Rs[0], &ts[0]); + rmse(0) = reprojectionErrorRMSE(x2d, x3d, K, Rs[0], ts[0]); + bSol = true; } else { - betas(0) = std::sqrt(b3(0)); - betas(1) = (b3(2) > 0) ? std::sqrt(b3(2)) : 0; + // ALICEVISION_LOG_WARNING("First approximation of beta not good enough."); + ts[0].setZero(); + rmse(0) = std::numeric_limits::max(); } - if (b3(1) < 0) { - betas(0) = -betas(0); + + // find the second possible solution for R, t corresponding to: + // Betas = [b00 b01 b11 b02 b12 b22 b03 b13 b23 b33] + // Betas_approx_2 = [b00 b01 b11] + betas.setZero(); + Eigen::Matrix l_6x3; + l_6x3 = L.block(0, 0, 6, 3); + Eigen::JacobiSVD svdOfL3(l_6x3, Eigen::ComputeFullU | Eigen::ComputeFullV); + Vec3 b3 = svdOfL3.solve(rho); + + if ((l_6x3 * b3).isApprox(rho, 1e-3)) + { + if (b3(0) < 0) + { + betas(0) = std::sqrt(-b3(0)); + betas(1) = (b3(2) < 0) ? std::sqrt(-b3(2)) : 0; + } + else + { + betas(0) = std::sqrt(b3(0)); + betas(1) = (b3(2) > 0) ? std::sqrt(b3(2)) : 0; + } + if (b3(1) < 0) + { + betas(0) = -betas(0); + } + + betas(2) = 0; + betas(3) = 0; + computePointsCoordinatesInCameraFrame(alphas, betas, u2, &xCamera); + absoluteOrientation(x3d, xCamera, &Rs[1], &ts[1]); + rmse(1) = reprojectionErrorRMSE(x2d, x3d, K, Rs[1], ts[1]); + bSol = true; } + else + { + // ALICEVISION_LOG_WARNING("Second approximation of beta not good enough."); + ts[1].setZero(); + rmse(1) = std::numeric_limits::max(); + } + + // find the third possible solution for R, t corresponding to: + // Betas = [b00 b01 b11 b02 b12 b22 b03 b13 b23 b33] + // Betas_approx_3 = [b00 b01 b11 b02 b12] + betas.setZero(); + Eigen::Matrix l_6x5; + l_6x5 = L.block(0, 0, 6, 5); + Eigen::JacobiSVD svdOfL5(l_6x5, Eigen::ComputeFullU | Eigen::ComputeFullV); + Vec b5 = svdOfL5.solve(rho); - betas(2) = 0; - betas(3) = 0; - computePointsCoordinatesInCameraFrame(alphas, betas, u2, &xCamera); - absoluteOrientation(x3d, xCamera, &Rs[1], &ts[1]); - rmse(1) = reprojectionErrorRMSE(x2d, x3d, K, Rs[1], ts[1]); - bSol = true; - } - else - { - // ALICEVISION_LOG_WARNING("Second approximation of beta not good enough."); - ts[1].setZero(); - rmse(1) = std::numeric_limits::max(); - } - - // find the third possible solution for R, t corresponding to: - // Betas = [b00 b01 b11 b02 b12 b22 b03 b13 b23 b33] - // Betas_approx_3 = [b00 b01 b11 b02 b12] - betas.setZero(); - Eigen::Matrix l_6x5; - l_6x5 = L.block(0, 0, 6, 5); - Eigen::JacobiSVD svdOfL5(l_6x5, Eigen::ComputeFullU | Eigen::ComputeFullV); - Vec b5 = svdOfL5.solve(rho); - - if((l_6x5 * b5).isApprox(rho, 1e-3)) - { - if (b5(0) < 0) + if ((l_6x5 * b5).isApprox(rho, 1e-3)) { - betas(0) = std::sqrt(-b5(0)); - - if(b5(2) < 0) - { - betas(1) = std::sqrt(-b5(2)); - } - else - { - b5(2) = 0; - } + if (b5(0) < 0) + { + betas(0) = std::sqrt(-b5(0)); + + if (b5(2) < 0) + { + betas(1) = std::sqrt(-b5(2)); + } + else + { + b5(2) = 0; + } + } + else + { + betas(0) = std::sqrt(b5(0)); + + if (b5(2) > 0) + { + betas(1) = std::sqrt(b5(2)); + } + else + { + b5(2) = 0; + } + } + + if (b5(1) < 0) + { + betas(0) = -betas(0); + } + betas(2) = b5(3) / betas(0); + betas(3) = 0; + computePointsCoordinatesInCameraFrame(alphas, betas, u2, &xCamera); + absoluteOrientation(x3d, xCamera, &Rs[2], &ts[2]); + rmse(2) = reprojectionErrorRMSE(x2d, x3d, K, Rs[2], ts[2]); + bSol = true; } else { - betas(0) = std::sqrt(b5(0)); - - if (b5(2) > 0) - { - betas(1) = std::sqrt(b5(2)); - } - else - { - b5(2) = 0; - } + // ALICEVISION_LOG_WARNING("Third approximation of beta not good enough."); + ts[2].setZero(); + rmse(2) = std::numeric_limits::max(); + } + + // finally, with all three solutions, select the (R, t) with the best RMSE. + std::size_t n = 0; + if (rmse(1) < rmse(0)) + { + n = 1; } - if(b5(1) < 0) + if (rmse(2) < rmse(n)) { - betas(0) = -betas(0); + n = 2; } - betas(2) = b5(3) / betas(0); - betas(3) = 0; - computePointsCoordinatesInCameraFrame(alphas, betas, u2, &xCamera); - absoluteOrientation(x3d, xCamera, &Rs[2], &ts[2]); - rmse(2) = reprojectionErrorRMSE(x2d, x3d, K, Rs[2], ts[2]); - bSol = true; - } - else - { - // ALICEVISION_LOG_WARNING("Third approximation of beta not good enough."); - ts[2].setZero(); - rmse(2) = std::numeric_limits::max(); - } - - // finally, with all three solutions, select the (R, t) with the best RMSE. - std::size_t n = 0; - if (rmse(1) < rmse(0)) - { - n = 1; - } - - if(rmse(2) < rmse(n)) - { - n = 2; - } - - if(bSol) - { - // If at least one solution have been found - *R = Rs[n]; - *t = ts[n]; - - return true; - } - return false; + + if (bSol) + { + // If at least one solution have been found + *R = Rs[n]; + *t = ts[n]; + + return true; + } + return false; } void EPnPSolver::solve(const Mat& x2d, const Mat& x3d, std::vector& models) const { - Mat3 R; - Vec3 t; - Mat34 P; - - if(resection(x2d, x3d, &R, &t)) - { - P_from_KRt(Mat3::Identity(), R, t, P); // K = Id - models.emplace_back(P); - } + Mat3 R; + Vec3 t; + Mat34 P; + + if (resection(x2d, x3d, &R, &t)) + { + P_from_KRt(Mat3::Identity(), R, t, P); // K = Id + models.emplace_back(P); + } } } // namespace resection diff --git a/src/aliceVision/multiview/resection/EPnPSolver.hpp b/src/aliceVision/multiview/resection/EPnPSolver.hpp index 6fa07ad26c..4e62a4eddc 100644 --- a/src/aliceVision/multiview/resection/EPnPSolver.hpp +++ b/src/aliceVision/multiview/resection/EPnPSolver.hpp @@ -15,63 +15,56 @@ namespace resection { class EPnPSolver : public robustEstimation::ISolver { -public: + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return /*5*/ 6; } - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return /*5*/ 6; - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } + /** + * @brief Solve the problem of camera pose. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A list of solutions. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; - /** - * @brief Solve the problem of camera pose. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A list of solutions. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; + /** + * @brief Solve the problem. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override + { + solve(x2d, x3d, models); + } - /** - * @brief Solve the problem. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override - { - solve(x2d, x3d, models); - } + /** + * @brief Computes the extrinsic parameters, R and t for a calibrated camera from 4 or + * more 3D points and their images. + * + * @param[in] x2d Image points in normalized camera coordinates, e.g. x2d = inv(K) * x_image + * @param[in] x3d 3D points in the world coordinate system + * @param[out] R Solution for the camera rotation matrix + * @param[out] t Solution for the camera translation vector - /** - * @brief Computes the extrinsic parameters, R and t for a calibrated camera from 4 or - * more 3D points and their images. - * - * @param[in] x2d Image points in normalized camera coordinates, e.g. x2d = inv(K) * x_image - * @param[in] x3d 3D points in the world coordinate system - * @param[out] R Solution for the camera rotation matrix - * @param[out] t Solution for the camera translation vector - - * @see "{EPnP: An Accurate $O(n)$ Solution to the PnP Problem", by V. Lepetit - * and F. Moreno-Noguer and P. Fua, IJCV 2009. vol. 81, no. 2 - * - * @note: the non-linear optimization is not implemented here. - */ - bool resection(const Mat2X& x2d, const Mat3X& x3d, Mat3* R, Vec3* t) const; + * @see "{EPnP: An Accurate $O(n)$ Solution to the PnP Problem", by V. Lepetit + * and F. Moreno-Noguer and P. Fua, IJCV 2009. vol. 81, no. 2 + * + * @note: the non-linear optimization is not implemented here. + */ + bool resection(const Mat2X& x2d, const Mat3X& x3d, Mat3* R, Vec3* t) const; }; } // namespace resection diff --git a/src/aliceVision/multiview/resection/ISolverErrorResection.hpp b/src/aliceVision/multiview/resection/ISolverErrorResection.hpp index d887b05fd6..1ff8b2eff7 100644 --- a/src/aliceVision/multiview/resection/ISolverErrorResection.hpp +++ b/src/aliceVision/multiview/resection/ISolverErrorResection.hpp @@ -13,10 +13,10 @@ namespace resection { /** * @brief Resection solver error interface. */ -template +template struct ISolverErrorResection { - virtual double error(const ModelT& model, const Vec2& x2d, const Vec3& x3d) const = 0; + virtual double error(const ModelT& model, const Vec2& x2d, const Vec3& x3d) const = 0; }; } // namespace resection diff --git a/src/aliceVision/multiview/resection/P3PSolver.cpp b/src/aliceVision/multiview/resection/P3PSolver.cpp index 7f3c7a7ef0..ab8f8b1084 100644 --- a/src/aliceVision/multiview/resection/P3PSolver.cpp +++ b/src/aliceVision/multiview/resection/P3PSolver.cpp @@ -15,50 +15,50 @@ namespace resection { void solveQuartic(const Vec5& factors, Vec4& realRoots) { - const double A = factors[0]; - const double B = factors[1]; - const double C = factors[2]; - const double D = factors[3]; - const double E = factors[4]; - - const double A_pw2 = A*A; - const double B_pw2 = B*B; - const double A_pw3 = A_pw2*A; - const double B_pw3 = B_pw2*B; - const double A_pw4 = A_pw3*A; - const double B_pw4 = B_pw3*B; - - const double alpha = -3 * B_pw2 / (8 * A_pw2) + C / A; - const double beta = B_pw3 / (8 * A_pw3) - B * C / (2 * A_pw2) + D / A; - const double gamma = -3 * B_pw4 / (256 * A_pw4) + B_pw2 * C / (16 * A_pw3) - B * D / (4 * A_pw2) + E / A; - - const double alpha_pw2 = alpha * alpha; - const double alpha_pw3 = alpha_pw2 * alpha; - - const std::complex P(-alpha_pw2 / 12 - gamma, 0); - const std::complex Q(-alpha_pw3 / 108 + alpha * gamma / 3 - pow(beta, 2) / 8, 0); - const std::complex R = -Q / 2.0 + sqrt(pow(Q, 2.0) / 4.0 + pow(P, 3.0) / 27.0); - - const std::complex U = pow(R, (1.0 / 3.0)); - std::complex y; - - if(U.real() == 0) - y = -5.0 * alpha / 6.0 - pow(Q, (1.0 / 3.0)); - else - y = -5.0 * alpha / 6.0 - P / (3.0 * U) + U; - - const std::complex w = sqrt(alpha + 2.0 * y); - - std::complex temp; - - temp = -B / (4.0 * A) + 0.5 * (w + sqrt(-(3.0 * alpha + 2.0 * y + 2.0 * beta / w))); - realRoots[0] = temp.real(); - temp = -B / (4.0 * A) + 0.5 * (w - sqrt(-(3.0 * alpha + 2.0 * y + 2.0 * beta / w))); - realRoots[1] = temp.real(); - temp = -B / (4.0 * A) + 0.5 * (-w + sqrt(-(3.0 * alpha + 2.0 * y - 2.0 * beta / w))); - realRoots[2] = temp.real(); - temp = -B / (4.0 * A) + 0.5 * (-w - sqrt(-(3.0 * alpha + 2.0 * y - 2.0 * beta / w))); - realRoots[3] = temp.real(); + const double A = factors[0]; + const double B = factors[1]; + const double C = factors[2]; + const double D = factors[3]; + const double E = factors[4]; + + const double A_pw2 = A * A; + const double B_pw2 = B * B; + const double A_pw3 = A_pw2 * A; + const double B_pw3 = B_pw2 * B; + const double A_pw4 = A_pw3 * A; + const double B_pw4 = B_pw3 * B; + + const double alpha = -3 * B_pw2 / (8 * A_pw2) + C / A; + const double beta = B_pw3 / (8 * A_pw3) - B * C / (2 * A_pw2) + D / A; + const double gamma = -3 * B_pw4 / (256 * A_pw4) + B_pw2 * C / (16 * A_pw3) - B * D / (4 * A_pw2) + E / A; + + const double alpha_pw2 = alpha * alpha; + const double alpha_pw3 = alpha_pw2 * alpha; + + const std::complex P(-alpha_pw2 / 12 - gamma, 0); + const std::complex Q(-alpha_pw3 / 108 + alpha * gamma / 3 - pow(beta, 2) / 8, 0); + const std::complex R = -Q / 2.0 + sqrt(pow(Q, 2.0) / 4.0 + pow(P, 3.0) / 27.0); + + const std::complex U = pow(R, (1.0 / 3.0)); + std::complex y; + + if (U.real() == 0) + y = -5.0 * alpha / 6.0 - pow(Q, (1.0 / 3.0)); + else + y = -5.0 * alpha / 6.0 - P / (3.0 * U) + U; + + const std::complex w = sqrt(alpha + 2.0 * y); + + std::complex temp; + + temp = -B / (4.0 * A) + 0.5 * (w + sqrt(-(3.0 * alpha + 2.0 * y + 2.0 * beta / w))); + realRoots[0] = temp.real(); + temp = -B / (4.0 * A) + 0.5 * (w - sqrt(-(3.0 * alpha + 2.0 * y + 2.0 * beta / w))); + realRoots[1] = temp.real(); + temp = -B / (4.0 * A) + 0.5 * (-w + sqrt(-(3.0 * alpha + 2.0 * y - 2.0 * beta / w))); + realRoots[2] = temp.real(); + temp = -B / (4.0 * A) + 0.5 * (-w - sqrt(-(3.0 * alpha + 2.0 * y - 2.0 * beta / w))); + realRoots[3] = temp.real(); } /** @@ -79,209 +79,189 @@ void solveQuartic(const Vec5& factors, Vec4& realRoots) */ bool computeP3PPoses(const Mat3& featureVectors, const Mat3& worldPoints, Mat& solutions) { - solutions = Mat(3, 4 * 4); + solutions = Mat(3, 4 * 4); - // extraction of world points + // extraction of world points - Vec3 P1 = worldPoints.col(0); - Vec3 P2 = worldPoints.col(1); - Vec3 P3 = worldPoints.col(2); + Vec3 P1 = worldPoints.col(0); + Vec3 P2 = worldPoints.col(1); + Vec3 P3 = worldPoints.col(2); - // verification that world points are not colinear + // verification that world points are not colinear - if(((P2 - P1).cross(P3 - Vec3(1, 1, 1))).norm() == 0) - return false; + if (((P2 - P1).cross(P3 - Vec3(1, 1, 1))).norm() == 0) + return false; - // extraction of feature vectors + // extraction of feature vectors - Vec3 f1 = featureVectors.col(0); - Vec3 f2 = featureVectors.col(1); - Vec3 f3 = featureVectors.col(2); + Vec3 f1 = featureVectors.col(0); + Vec3 f2 = featureVectors.col(1); + Vec3 f3 = featureVectors.col(2); - // creation of intermediate camera frame + // creation of intermediate camera frame - Vec3 e1 = f1; - Vec3 e3 = (f1.cross(f2)).normalized(); - Vec3 e2 = e3.cross(e1); + Vec3 e1 = f1; + Vec3 e3 = (f1.cross(f2)).normalized(); + Vec3 e2 = e3.cross(e1); - Mat3 T; - T.row(0) = e1; - T.row(1) = e2; - T.row(2) = e3; + Mat3 T; + T.row(0) = e1; + T.row(1) = e2; + T.row(2) = e3; - f3 = T * f3; + f3 = T * f3; - // reinforce that f3[2] > 0 for having theta in [0;pi] + // reinforce that f3[2] > 0 for having theta in [0;pi] - if(f3[2] > 0) - { - f1 = featureVectors.col(1); - f2 = featureVectors.col(0); - f3 = featureVectors.col(2); + if (f3[2] > 0) + { + f1 = featureVectors.col(1); + f2 = featureVectors.col(0); + f3 = featureVectors.col(2); - e1 = f1; - e3 = (f1.cross(f2)).normalized(); - e2 = e3.cross(e1); + e1 = f1; + e3 = (f1.cross(f2)).normalized(); + e2 = e3.cross(e1); - T.row(0) = e1; - T.row(1) = e2; - T.row(2) = e3; + T.row(0) = e1; + T.row(1) = e2; + T.row(2) = e3; - f3 = T * f3; + f3 = T * f3; - P1 = worldPoints.col(1); - P2 = worldPoints.col(0); - P3 = worldPoints.col(2); - } - - // creation of intermediate world frame - - const Vec3 n1 = (P2 - P1).normalized(); - const Vec3 n3 = (n1.cross(P3 - P1)).normalized(); - const Vec3 n2 = n3.cross(n1); - - Mat3 N; - N.row(0) = n1; - N.row(1) = n2; - N.row(2) = n3; - - // extraction of known parameters - - P3 = N * (P3 - P1); - - const double d_12 = (P2 - P1).norm(); - const double f_1 = f3[0] / f3[2]; - const double f_2 = f3[1] / f3[2]; - const double p_1 = P3[0]; - const double p_2 = P3[1]; - - const double cos_beta = f1.transpose() * f2; - - const double sign = ((cos_beta < 0) ? -1.0 : 1.0) ; - const double b = sign * std::sqrt(1.0 / (1.0 - pow(cos_beta, 2)) - 1.0); - - // definition of temporary variables for avoiding multiple computation - - const double f_1_pw2 = pow(f_1, 2); - const double f_2_pw2 = pow(f_2, 2); - const double p_1_pw2 = pow(p_1, 2); - const double p_1_pw3 = p_1_pw2 * p_1; - const double p_1_pw4 = p_1_pw3 * p_1; - const double p_2_pw2 = pow(p_2, 2); - const double p_2_pw3 = p_2_pw2 * p_2; - const double p_2_pw4 = p_2_pw3 * p_2; - const double d_12_pw2 = pow(d_12, 2); - const double b_pw2 = pow(b, 2); - - // computation of factors of 4th degree polynomial - - Vec5 factors; - factors << -f_2_pw2 * p_2_pw4 - p_2_pw4 * f_1_pw2 - p_2_pw4, - - 2. * p_2_pw3 * d_12 * b + - 2. * f_2_pw2 * p_2_pw3 * d_12 * b - - 2. * f_2 * p_2_pw3 * f_1*d_12, - - -f_2_pw2 * p_2_pw2 * p_1_pw2 - - f_2_pw2 * p_2_pw2 * d_12_pw2 * b_pw2 - - f_2_pw2 * p_2_pw2 * d_12_pw2 - + f_2_pw2 * p_2_pw4 - + p_2_pw4 * f_1_pw2 - + 2. * p_1 * p_2_pw2 * d_12 - + 2. * f_1 * f_2 * p_1 * p_2_pw2 * d_12 * b - - p_2_pw2 * p_1_pw2 * f_1_pw2 - + 2. * p_1 * p_2_pw2 * f_2_pw2 * d_12 - - p_2_pw2 * d_12_pw2 * b_pw2 - - 2. * p_1_pw2*p_2_pw2, - - 2. * p_1_pw2 * p_2 * d_12 * b - + 2. * f_2 * p_2_pw3 * f_1 * d_12 - - 2. * f_2_pw2 * p_2_pw3 * d_12 * b - - 2. * p_1 * p_2 * d_12_pw2*b, - - -2. * f_2 * p_2_pw2 * f_1 * p_1 * d_12 * b - + f_2_pw2 * p_2_pw2 * d_12_pw2 - + 2. * p_1_pw3 * d_12 - - p_1_pw2 * d_12_pw2 - + f_2_pw2 * p_2_pw2 * p_1_pw2 - - p_1_pw4 - - 2. * f_2_pw2 * p_2_pw2 * p_1 * d_12 - + p_2_pw2 * f_1_pw2 * p_1_pw2 - + f_2_pw2 * p_2_pw2 * d_12_pw2*b_pw2; - - // computation of roots - - Vec4 realRoots; - solveQuartic(factors, realRoots); - - // backsubstitution of each solution - - for(int i = 0; i < 4; ++i) - { - const double cot_alpha = (-f_1 * p_1 / f_2 - realRoots[i] * p_2 + d_12 * b) / (-f_1 * realRoots[i] * p_2 / f_2 + p_1 - d_12); - const double cos_theta = realRoots[i]; - double sin_theta = sqrt(1 - pow(realRoots[i], 2)); - const double sin_alpha = sqrt(1. / (pow(cot_alpha, 2) + 1)); - double cos_alpha = sqrt(1. - pow(sin_alpha, 2)); - - if(cot_alpha < 0) - cos_alpha = -cos_alpha; - - if(!is_finite(sin_theta)) - sin_theta = 0; - - Vec3 C(d_12 * cos_alpha * (sin_alpha * b + cos_alpha), - cos_theta * d_12 * sin_alpha * (sin_alpha * b + cos_alpha), - sin_theta * d_12 * sin_alpha * (sin_alpha * b + cos_alpha)); + P1 = worldPoints.col(1); + P2 = worldPoints.col(0); + P3 = worldPoints.col(2); + } - C = P1 + N.transpose() * C; + // creation of intermediate world frame - Mat3 R; - R << -cos_alpha, -sin_alpha*cos_theta, -sin_alpha*sin_theta, - sin_alpha, -cos_alpha*cos_theta, -cos_alpha*sin_theta, - 0, -sin_theta, cos_theta; + const Vec3 n1 = (P2 - P1).normalized(); + const Vec3 n3 = (n1.cross(P3 - P1)).normalized(); + const Vec3 n2 = n3.cross(n1); + + Mat3 N; + N.row(0) = n1; + N.row(1) = n2; + N.row(2) = n3; + + // extraction of known parameters + + P3 = N * (P3 - P1); + + const double d_12 = (P2 - P1).norm(); + const double f_1 = f3[0] / f3[2]; + const double f_2 = f3[1] / f3[2]; + const double p_1 = P3[0]; + const double p_2 = P3[1]; + + const double cos_beta = f1.transpose() * f2; + + const double sign = ((cos_beta < 0) ? -1.0 : 1.0); + const double b = sign * std::sqrt(1.0 / (1.0 - pow(cos_beta, 2)) - 1.0); + + // definition of temporary variables for avoiding multiple computation + + const double f_1_pw2 = pow(f_1, 2); + const double f_2_pw2 = pow(f_2, 2); + const double p_1_pw2 = pow(p_1, 2); + const double p_1_pw3 = p_1_pw2 * p_1; + const double p_1_pw4 = p_1_pw3 * p_1; + const double p_2_pw2 = pow(p_2, 2); + const double p_2_pw3 = p_2_pw2 * p_2; + const double p_2_pw4 = p_2_pw3 * p_2; + const double d_12_pw2 = pow(d_12, 2); + const double b_pw2 = pow(b, 2); - R = N.transpose() * R.transpose() * T; + // computation of factors of 4th degree polynomial - solutions.col(i * 4) = C; - solutions.block<3, 3>(0, i * 4 + 1) = R.transpose(); - } + Vec5 factors; + factors << -f_2_pw2 * p_2_pw4 - p_2_pw4 * f_1_pw2 - p_2_pw4, - return true; + 2. * p_2_pw3 * d_12 * b + 2. * f_2_pw2 * p_2_pw3 * d_12 * b - 2. * f_2 * p_2_pw3 * f_1 * d_12, + + -f_2_pw2 * p_2_pw2 * p_1_pw2 - f_2_pw2 * p_2_pw2 * d_12_pw2 * b_pw2 - f_2_pw2 * p_2_pw2 * d_12_pw2 + f_2_pw2 * p_2_pw4 + p_2_pw4 * f_1_pw2 + + 2. * p_1 * p_2_pw2 * d_12 + 2. * f_1 * f_2 * p_1 * p_2_pw2 * d_12 * b - p_2_pw2 * p_1_pw2 * f_1_pw2 + 2. * p_1 * p_2_pw2 * f_2_pw2 * d_12 - + p_2_pw2 * d_12_pw2 * b_pw2 - 2. * p_1_pw2 * p_2_pw2, + + 2. * p_1_pw2 * p_2 * d_12 * b + 2. * f_2 * p_2_pw3 * f_1 * d_12 - 2. * f_2_pw2 * p_2_pw3 * d_12 * b - 2. * p_1 * p_2 * d_12_pw2 * b, + + -2. * f_2 * p_2_pw2 * f_1 * p_1 * d_12 * b + f_2_pw2 * p_2_pw2 * d_12_pw2 + 2. * p_1_pw3 * d_12 - p_1_pw2 * d_12_pw2 + + f_2_pw2 * p_2_pw2 * p_1_pw2 - p_1_pw4 - 2. * f_2_pw2 * p_2_pw2 * p_1 * d_12 + p_2_pw2 * f_1_pw2 * p_1_pw2 + + f_2_pw2 * p_2_pw2 * d_12_pw2 * b_pw2; + + // computation of roots + + Vec4 realRoots; + solveQuartic(factors, realRoots); + + // backsubstitution of each solution + + for (int i = 0; i < 4; ++i) + { + const double cot_alpha = (-f_1 * p_1 / f_2 - realRoots[i] * p_2 + d_12 * b) / (-f_1 * realRoots[i] * p_2 / f_2 + p_1 - d_12); + const double cos_theta = realRoots[i]; + double sin_theta = sqrt(1 - pow(realRoots[i], 2)); + const double sin_alpha = sqrt(1. / (pow(cot_alpha, 2) + 1)); + double cos_alpha = sqrt(1. - pow(sin_alpha, 2)); + + if (cot_alpha < 0) + cos_alpha = -cos_alpha; + + if (!is_finite(sin_theta)) + sin_theta = 0; + + Vec3 C(d_12 * cos_alpha * (sin_alpha * b + cos_alpha), + cos_theta * d_12 * sin_alpha * (sin_alpha * b + cos_alpha), + sin_theta * d_12 * sin_alpha * (sin_alpha * b + cos_alpha)); + + C = P1 + N.transpose() * C; + + Mat3 R; + R << -cos_alpha, -sin_alpha * cos_theta, -sin_alpha * sin_theta, sin_alpha, -cos_alpha * cos_theta, -cos_alpha * sin_theta, 0, -sin_theta, + cos_theta; + + R = N.transpose() * R.transpose() * T; + + solutions.col(i * 4) = C; + solutions.block<3, 3>(0, i * 4 + 1) = R.transpose(); + } + + return true; } void P3PSolver::solve(const Mat& x2d, const Mat& x3d, std::vector& models) const { - assert(2 == x2d.rows()); - assert(3 == x3d.rows()); - assert(x2d.cols() == x3d.cols()); + assert(2 == x2d.rows()); + assert(3 == x3d.rows()); + assert(x2d.cols() == x3d.cols()); - Mat3 R; - Vec3 t; - Mat34 P; + Mat3 R; + Vec3 t; + Mat34 P; - Mat solutions = Mat(3, 16); + Mat solutions = Mat(3, 16); - Mat3 pt2D_3x3; - pt2D_3x3.block<2, 3>(0, 0) = x2d; - pt2D_3x3.row(2).fill(1); - pt2D_3x3.col(0).normalize(); - pt2D_3x3.col(1).normalize(); - pt2D_3x3.col(2).normalize(); + Mat3 pt2D_3x3; + pt2D_3x3.block<2, 3>(0, 0) = x2d; + pt2D_3x3.row(2).fill(1); + pt2D_3x3.col(0).normalize(); + pt2D_3x3.col(1).normalize(); + pt2D_3x3.col(2).normalize(); - Mat3 pt3D_3x3 = x3d; + Mat3 pt3D_3x3 = x3d; - if(computeP3PPoses(pt2D_3x3, pt3D_3x3, solutions)) - { - for(size_t i = 0; i < 4; ++i) + if (computeP3PPoses(pt2D_3x3, pt3D_3x3, solutions)) { - R = solutions.block<3, 3>(0, i * 4 + 1); - t = -R * solutions.col(i * 4); - P_from_KRt(Mat3::Identity(), R, t, P); // K = Id - - models.emplace_back(P); + for (size_t i = 0; i < 4; ++i) + { + R = solutions.block<3, 3>(0, i * 4 + 1); + t = -R * solutions.col(i * 4); + P_from_KRt(Mat3::Identity(), R, t, P); // K = Id + + models.emplace_back(P); + } } - } } } // namespace resection diff --git a/src/aliceVision/multiview/resection/P3PSolver.hpp b/src/aliceVision/multiview/resection/P3PSolver.hpp index bd9b58d81f..1f18ef3cac 100644 --- a/src/aliceVision/multiview/resection/P3PSolver.hpp +++ b/src/aliceVision/multiview/resection/P3PSolver.hpp @@ -18,47 +18,40 @@ typedef Eigen::Matrix Vec5; class P3PSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 3; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 4; - } - - /** - * @brief Solve the problem of camera pose. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A list of at most 4 candidate solutions. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; - - /** - * @brief Solve the problem. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("P3PSolver does not support problem solving with weights."); - } + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 3; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 4; } + + /** + * @brief Solve the problem of camera pose. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A list of at most 4 candidate solutions. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; + + /** + * @brief Solve the problem. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("P3PSolver does not support problem solving with weights."); + } }; } // namespace resection diff --git a/src/aliceVision/multiview/resection/P4PfSolver.cpp b/src/aliceVision/multiview/resection/P4PfSolver.cpp index 8494142fb8..4d092d3194 100644 --- a/src/aliceVision/multiview/resection/P4PfSolver.cpp +++ b/src/aliceVision/multiview/resection/P4PfSolver.cpp @@ -24,116 +24,116 @@ namespace resection { * @param[in] ccnt * @param[in] tol */ -void GJ(double *A, int rcnt, int ccnt, double tol) +void GJ(double* A, int rcnt, int ccnt, double tol) { - int row = 0; - int col = 0; - int ofs = 0; + int row = 0; + int col = 0; + int ofs = 0; - while(row < rcnt && col < ccnt) - { - // find pivot - double apivot = 0; - double pivot = 0; - int pivot_r = -1; - - int pofs = ofs; - for(int k = row; k < rcnt; ++k) + while (row < rcnt && col < ccnt) { - // pivot selection criteria here ! - if(std::abs(*(A + pofs)) > apivot) - { - pivot = *(A + pofs); - apivot = std::abs(pivot); - pivot_r = k; - } - pofs += ccnt; - } + // find pivot + double apivot = 0; + double pivot = 0; + int pivot_r = -1; - if(apivot < tol) - { - // empty col - shift to next col (or jump) - ++col; - ++ofs; - } - else - { - // process rows - double pivot_i = 1.0 / pivot; + int pofs = ofs; + for (int k = row; k < rcnt; ++k) + { + // pivot selection criteria here ! + if (std::abs(*(A + pofs)) > apivot) + { + pivot = *(A + pofs); + apivot = std::abs(pivot); + pivot_r = k; + } + pofs += ccnt; + } - // exchange pivot and selected rows - // + divide row - if(pivot_r == row) - { - int srcofs = ofs; - for(int c = col; c < ccnt; ++c) + if (apivot < tol) { - *(A + srcofs) = *(A + srcofs) * pivot_i; - srcofs++; + // empty col - shift to next col (or jump) + ++col; + ++ofs; } - } - else - { - int srcofs = ofs; - int dstofs = ccnt * pivot_r + col; - for(int c = col; c < ccnt; ++c) + else { - double b = *(A + srcofs); - *(A + srcofs) = *(A + dstofs) * pivot_i; - *(A + dstofs) = b; + // process rows + double pivot_i = 1.0 / pivot; - ++srcofs; - ++dstofs; - } - } + // exchange pivot and selected rows + // + divide row + if (pivot_r == row) + { + int srcofs = ofs; + for (int c = col; c < ccnt; ++c) + { + *(A + srcofs) = *(A + srcofs) * pivot_i; + srcofs++; + } + } + else + { + int srcofs = ofs; + int dstofs = ccnt * pivot_r + col; + for (int c = col; c < ccnt; ++c) + { + double b = *(A + srcofs); + *(A + srcofs) = *(A + dstofs) * pivot_i; + *(A + dstofs) = b; - // zero bottom - pofs = ofs + ccnt; - for(int k = row + 1; k < rcnt; ++k) - { - if(std::abs(*(A + pofs)) > tol) - { - // nonzero row - double b = *(A + pofs); - int dstofs = pofs + 1; - int srcofs = ofs + 1; - for(int c = col + 1; c < ccnt; ++c) - { - *(A + dstofs) = (*(A + dstofs) - *(A + srcofs) * b); - ++dstofs; - ++srcofs; - } - *(A + pofs) = 0; - } - pofs += ccnt; - } + ++srcofs; + ++dstofs; + } + } - // zero top - pofs = col; - for(int k = 0; k < row; ++k) - { - if(std::abs(*(A + pofs)) > tol) - { - // nonzero row - double b = *(A + pofs); - int dstofs = pofs + 1; - int srcofs = ofs + 1; - for(int c = col + 1; c < ccnt; ++c) - { - *(A + dstofs) = (*(A + dstofs) - *(A + srcofs) * b); - ++dstofs; - ++srcofs; - } - *(A + pofs) = 0; - } - pofs += ccnt; - } + // zero bottom + pofs = ofs + ccnt; + for (int k = row + 1; k < rcnt; ++k) + { + if (std::abs(*(A + pofs)) > tol) + { + // nonzero row + double b = *(A + pofs); + int dstofs = pofs + 1; + int srcofs = ofs + 1; + for (int c = col + 1; c < ccnt; ++c) + { + *(A + dstofs) = (*(A + dstofs) - *(A + srcofs) * b); + ++dstofs; + ++srcofs; + } + *(A + pofs) = 0; + } + pofs += ccnt; + } + + // zero top + pofs = col; + for (int k = 0; k < row; ++k) + { + if (std::abs(*(A + pofs)) > tol) + { + // nonzero row + double b = *(A + pofs); + int dstofs = pofs + 1; + int srcofs = ofs + 1; + for (int c = col + 1; c < ccnt; ++c) + { + *(A + dstofs) = (*(A + dstofs) - *(A + srcofs) * b); + ++dstofs; + ++srcofs; + } + *(A + pofs) = 0; + } + pofs += ccnt; + } - ++row; - ++col; - ofs += ccnt + 1; + ++row; + ++col; + ofs += ccnt + 1; + } } - } } /** @@ -145,99 +145,99 @@ void GJ(double *A, int rcnt, int ccnt, double tol) * @param[in] src5 * @param[out] dst1 */ -void computeCoefficients(const double *src1, const double *src2, const double *src3, const double *src4, const double *src5, double *dst1) +void computeCoefficients(const double* src1, const double* src2, const double* src3, const double* src4, const double* src5, double* dst1) { - // symbolic names. - const double glab = src1[0]; - const double glac = src1[1]; - const double glad = src1[2]; - const double glbc = src1[3]; - const double glbd = src1[4]; - const double glcd = src1[5]; - const double a1 = src2[0]; - const double a2 = src2[1]; - const double b1 = src3[0]; - const double b2 = src3[1]; - const double c1 = src4[0]; - const double c2 = src4[1]; - const double d1 = src5[0]; - const double d2 = src5[1]; + // symbolic names. + const double glab = src1[0]; + const double glac = src1[1]; + const double glad = src1[2]; + const double glbc = src1[3]; + const double glbd = src1[4]; + const double glcd = src1[5]; + const double a1 = src2[0]; + const double a2 = src2[1]; + const double b1 = src3[0]; + const double b2 = src3[1]; + const double c1 = src4[0]; + const double c2 = src4[1]; + const double d1 = src5[0]; + const double d2 = src5[1]; - const double t1 = 1 / glad; - const double t2 = t1 * glbc; - const double t3 = glab * t1; - const double t4 = glac * t1; - const double t5 = t2 - t3 - t4; - const double t9 = d2 * d2; - const double t11 = t3 * t9; - const double t12 = t4 * t9; - const double t13 = d1 * d1; - const double t14 = t4 * t13; - const double t16 = t3 * t13; - const double t24 = -a2 * b2 - b1 * a1; - const double t27 = -c1 * a1 - c2 * a2; - const double t28 = a1 * t1; - const double t32 = t1 * d1; - const double t33 = a1 * glac * t32; - const double t34 = a2 * d2; - const double t35 = t4 * t34; - const double t37 = a1 * glab * t32; - const double t39 = t3 * t34; - const double t41 = a2 * a2; - const double t42 = a1 * a1; - const double t43 = t4 * t41; - const double t46 = t42 * glac * t1; - const double t51 = t42 * glab * t1; - const double t53 = t42 * t1; - const double t56 = t3 * t41; - const double t59 = c1 * c1; - const double t60 = c2 * c2; - const double t67 = t1 * glbd; - const double t84 = glcd * t1; + const double t1 = 1 / glad; + const double t2 = t1 * glbc; + const double t3 = glab * t1; + const double t4 = glac * t1; + const double t5 = t2 - t3 - t4; + const double t9 = d2 * d2; + const double t11 = t3 * t9; + const double t12 = t4 * t9; + const double t13 = d1 * d1; + const double t14 = t4 * t13; + const double t16 = t3 * t13; + const double t24 = -a2 * b2 - b1 * a1; + const double t27 = -c1 * a1 - c2 * a2; + const double t28 = a1 * t1; + const double t32 = t1 * d1; + const double t33 = a1 * glac * t32; + const double t34 = a2 * d2; + const double t35 = t4 * t34; + const double t37 = a1 * glab * t32; + const double t39 = t3 * t34; + const double t41 = a2 * a2; + const double t42 = a1 * a1; + const double t43 = t4 * t41; + const double t46 = t42 * glac * t1; + const double t51 = t42 * glab * t1; + const double t53 = t42 * t1; + const double t56 = t3 * t41; + const double t59 = c1 * c1; + const double t60 = c2 * c2; + const double t67 = t1 * glbd; + const double t84 = glcd * t1; - // destination group 1 - dst1[0] = 1.0; - dst1[1] = t5 / 2.0; - dst1[2] = -1.0; - dst1[3] = -1.0; - dst1[4] = c2 * b2 + c1 * b1; - dst1[5] = -t5; - dst1[6] = t2 * t9 / 2.0 - t11 / 2.0 - t12 / 2.0 - t14 / 2.0 + t2 * t13 / 2.0 - t16 / 2.0; - dst1[7] = 1.0 - t4 / 2.0 - t3 / 2.0 + t2 / 2.0; - dst1[8] = t24; - dst1[9] = t27; - dst1[10] = -t28 * glbc * d1 + t33 + t35 + t37 - t2 * t34 + t39; - dst1[11] = t41 + t42 - t43 / 2.0 - t46 / 2.0 + t2 * t41 / 2.0 - t51 / 2.0 + t53 * glbc / 2.0 - t56 / 2.0; - dst1[12] = 1.0; - dst1[13] = -t4; - dst1[14] = -2.0; - dst1[15] = t59 + t60; - dst1[16] = 2.0 * t4; - dst1[17] = -t14 - t12; - dst1[18] = -t4 + 1.0; - dst1[19] = 2.0 * t27; - dst1[20] = 2.0 * t33 + 2.0 * t35; - dst1[21] = -t43 + t41 + t42 - t46; - dst1[22] = 1.0; - dst1[23] = t67 / 2.0 - 1.0 / 2.0 - t3 / 2.0; - dst1[24] = -1.0; - dst1[25] = t3 - t67; - dst1[26] = d2 * b2 + b1*d1; - dst1[27] = -t11 / 2.0 - t16 / 2.0 + t67 * t9 / 2.0 + t67 * t13 / 2.0 - t9 / 2.0 - t13 / 2.0; - dst1[28] = -t3 / 2.0 + t67 / 2.0 + 1.0 / 2.0; - dst1[29] = t24; - dst1[30] = -t28 * glbd * d1 + t37 + t39 - t67 * t34; - dst1[31] = t67 * t41 / 2.0 + t53 * glbd / 2.0 - t56 / 2.0 - t51 / 2.0 + t42 / 2.0 + t41 / 2.0; - dst1[32] = 1.0; - dst1[33] = -t4 / 2.0 + t84 / 2.0 - 1.0 / 2.0; - dst1[34] = -1.0; - dst1[35] = t4 - t84; - dst1[36] = c1 * d1 + c2*d2; - dst1[37] = t84 * t13 / 2.0 - t12 / 2.0 - t14 / 2.0 - t13 / 2.0 - t9 / 2.0 + t84 * t9 / 2.0; - dst1[38] = -t4 / 2.0 + 1.0 / 2.0 + t84 / 2.0; - dst1[39] = t27; - dst1[40] = t35 + t33 - t84 * t34 - glcd * a1 * t32; - dst1[41] = t42 / 2.0 + t41 / 2.0 - t43 / 2.0 + glcd * t42 * t1 / 2.0 - t46 / 2.0 + t84 * t41 / 2.0; + // destination group 1 + dst1[0] = 1.0; + dst1[1] = t5 / 2.0; + dst1[2] = -1.0; + dst1[3] = -1.0; + dst1[4] = c2 * b2 + c1 * b1; + dst1[5] = -t5; + dst1[6] = t2 * t9 / 2.0 - t11 / 2.0 - t12 / 2.0 - t14 / 2.0 + t2 * t13 / 2.0 - t16 / 2.0; + dst1[7] = 1.0 - t4 / 2.0 - t3 / 2.0 + t2 / 2.0; + dst1[8] = t24; + dst1[9] = t27; + dst1[10] = -t28 * glbc * d1 + t33 + t35 + t37 - t2 * t34 + t39; + dst1[11] = t41 + t42 - t43 / 2.0 - t46 / 2.0 + t2 * t41 / 2.0 - t51 / 2.0 + t53 * glbc / 2.0 - t56 / 2.0; + dst1[12] = 1.0; + dst1[13] = -t4; + dst1[14] = -2.0; + dst1[15] = t59 + t60; + dst1[16] = 2.0 * t4; + dst1[17] = -t14 - t12; + dst1[18] = -t4 + 1.0; + dst1[19] = 2.0 * t27; + dst1[20] = 2.0 * t33 + 2.0 * t35; + dst1[21] = -t43 + t41 + t42 - t46; + dst1[22] = 1.0; + dst1[23] = t67 / 2.0 - 1.0 / 2.0 - t3 / 2.0; + dst1[24] = -1.0; + dst1[25] = t3 - t67; + dst1[26] = d2 * b2 + b1 * d1; + dst1[27] = -t11 / 2.0 - t16 / 2.0 + t67 * t9 / 2.0 + t67 * t13 / 2.0 - t9 / 2.0 - t13 / 2.0; + dst1[28] = -t3 / 2.0 + t67 / 2.0 + 1.0 / 2.0; + dst1[29] = t24; + dst1[30] = -t28 * glbd * d1 + t37 + t39 - t67 * t34; + dst1[31] = t67 * t41 / 2.0 + t53 * glbd / 2.0 - t56 / 2.0 - t51 / 2.0 + t42 / 2.0 + t41 / 2.0; + dst1[32] = 1.0; + dst1[33] = -t4 / 2.0 + t84 / 2.0 - 1.0 / 2.0; + dst1[34] = -1.0; + dst1[35] = t4 - t84; + dst1[36] = c1 * d1 + c2 * d2; + dst1[37] = t84 * t13 / 2.0 - t12 / 2.0 - t14 / 2.0 - t13 / 2.0 - t9 / 2.0 + t84 * t9 / 2.0; + dst1[38] = -t4 / 2.0 + 1.0 / 2.0 + t84 / 2.0; + dst1[39] = t27; + dst1[40] = t35 + t33 - t84 * t34 - glcd * a1 * t32; + dst1[41] = t42 / 2.0 + t41 / 2.0 - t43 / 2.0 + glcd * t42 * t1 / 2.0 - t46 / 2.0 + t84 * t41 / 2.0; } /** @@ -251,829 +251,830 @@ void computeCoefficients(const double *src1, const double *src2, const double *s * @param[in] d1 (d2) = x (resp y) measurement of the fourth 2D point * @param[out] A 10 x 10 action matrix */ -void computeP4pfPoses(const double *glab, const double *a1, const double *b1, const double *c1, const double *d1, double *A) +void computeP4pfPoses(const double* glab, const double* a1, const double* b1, const double* c1, const double* d1, double* A) { - // precalculate polynomial equations coefficients - double M[6864]; - double coefs[42]; + // precalculate polynomial equations coefficients + double M[6864]; + double coefs[42]; - // prepare polynomial coefficients - computeCoefficients(glab, a1, b1, c1, d1, coefs); + // prepare polynomial coefficients + computeCoefficients(glab, a1, b1, c1, d1, coefs); - std::memset(M, 0, 6864 * sizeof (double)); + std::memset(M, 0, 6864 * sizeof(double)); - M[64] = coefs[0]; - M[403] = coefs[0]; - M[486] = coefs[0]; - M[572] = coefs[0]; - M[1533] = coefs[0]; - M[1616] = coefs[0]; - M[1702] = coefs[0]; - M[1787] = coefs[0]; - M[1874] = coefs[0]; - M[1960] = coefs[0]; - M[3979] = coefs[0]; - M[4063] = coefs[0]; - M[4149] = coefs[0]; - M[4234] = coefs[0]; - M[4321] = coefs[0]; - M[4407] = coefs[0]; - M[4494] = coefs[0]; - M[6161] = coefs[0]; - M[6248] = coefs[0]; - M[71] = coefs[1]; - M[411] = coefs[1]; - M[496] = coefs[1]; - M[582] = coefs[1]; - M[1539] = coefs[1]; - M[1626] = coefs[1]; - M[1712] = coefs[1]; - M[1798] = coefs[1]; - M[1884] = coefs[1]; - M[4071] = coefs[1]; - M[4157] = coefs[1]; - M[4244] = coefs[1]; - M[4330] = coefs[1]; - M[4416] = coefs[1]; - M[4500] = coefs[1]; - M[6165] = coefs[1]; - M[6252] = coefs[1]; - M[75] = coefs[2]; - M[419] = coefs[2]; - M[504] = coefs[2]; - M[590] = coefs[2]; - M[1551] = coefs[2]; - M[1635] = coefs[2]; - M[1721] = coefs[2]; - M[1806] = coefs[2]; - M[1892] = coefs[2]; - M[4001] = coefs[2]; - M[4085] = coefs[2]; - M[4171] = coefs[2]; - M[4256] = coefs[2]; - M[4342] = coefs[2]; - M[4428] = coefs[2]; - M[4512] = coefs[2]; - M[6171] = coefs[2]; - M[6255] = coefs[2]; - M[76] = coefs[3]; - M[420] = coefs[3]; - M[505] = coefs[3]; - M[591] = coefs[3]; - M[1552] = coefs[3]; - M[1636] = coefs[3]; - M[1722] = coefs[3]; - M[1807] = coefs[3]; - M[1893] = coefs[3]; - M[4002] = coefs[3]; - M[4086] = coefs[3]; - M[4172] = coefs[3]; - M[4257] = coefs[3]; - M[4343] = coefs[3]; - M[4429] = coefs[3]; - M[4513] = coefs[3]; - M[6172] = coefs[3]; - M[6256] = coefs[3]; - M[77] = coefs[4]; - M[421] = coefs[4]; - M[506] = coefs[4]; - M[592] = coefs[4]; - M[1553] = coefs[4]; - M[1637] = coefs[4]; - M[1723] = coefs[4]; - M[1808] = coefs[4]; - M[1894] = coefs[4]; - M[1980] = coefs[4]; - M[4087] = coefs[4]; - M[4173] = coefs[4]; - M[4258] = coefs[4]; - M[4344] = coefs[4]; - M[4430] = coefs[4]; - M[4514] = coefs[4]; - M[6173] = coefs[4]; - M[6257] = coefs[4]; - M[79] = coefs[5]; - M[423] = coefs[5]; - M[508] = coefs[5]; - M[1555] = coefs[5]; - M[1640] = coefs[5]; - M[1726] = coefs[5]; - M[1812] = coefs[5]; - M[1898] = coefs[5]; - M[4003] = coefs[5]; - M[4090] = coefs[5]; - M[4176] = coefs[5]; - M[4262] = coefs[5]; - M[4348] = coefs[5]; - M[4517] = coefs[5]; - M[6176] = coefs[5]; - M[6260] = coefs[5]; - M[82] = coefs[6]; - M[426] = coefs[6]; - M[513] = coefs[6]; - M[599] = coefs[6]; - M[1645] = coefs[6]; - M[1731] = coefs[6]; - M[1818] = coefs[6]; - M[1904] = coefs[6]; - M[1990] = coefs[6]; - M[4179] = coefs[6]; - M[4354] = coefs[6]; - M[4440] = coefs[6]; - M[4524] = coefs[6]; - M[6181] = coefs[6]; - M[6266] = coefs[6]; - M[83] = coefs[7]; - M[431] = coefs[7]; - M[516] = coefs[7]; - M[1567] = coefs[7]; - M[1652] = coefs[7]; - M[1825] = coefs[7]; - M[1911] = coefs[7]; - M[4019] = coefs[7]; - M[4104] = coefs[7]; - M[4190] = coefs[7]; - M[4276] = coefs[7]; - M[4362] = coefs[7]; - M[6277] = coefs[7]; - M[84] = coefs[8]; - M[432] = coefs[8]; - M[517] = coefs[8]; - M[603] = coefs[8]; - M[1568] = coefs[8]; - M[1653] = coefs[8]; - M[1739] = coefs[8]; - M[1826] = coefs[8]; - M[1912] = coefs[8]; - M[1998] = coefs[8]; - M[4020] = coefs[8]; - M[4105] = coefs[8]; - M[4191] = coefs[8]; - M[4277] = coefs[8]; - M[4363] = coefs[8]; - M[4449] = coefs[8]; - M[4532] = coefs[8]; - M[6195] = coefs[8]; - M[6278] = coefs[8]; - M[85] = coefs[9]; - M[433] = coefs[9]; - M[518] = coefs[9]; - M[604] = coefs[9]; - M[1569] = coefs[9]; - M[1654] = coefs[9]; - M[1740] = coefs[9]; - M[1913] = coefs[9]; - M[1999] = coefs[9]; - M[4021] = coefs[9]; - M[4106] = coefs[9]; - M[4192] = coefs[9]; - M[4364] = coefs[9]; - M[4450] = coefs[9]; - M[4533] = coefs[9]; - M[6196] = coefs[9]; - M[6279] = coefs[9]; - M[86] = coefs[10]; - M[434] = coefs[10]; - M[521] = coefs[10]; - M[607] = coefs[10]; - M[1570] = coefs[10]; - M[1657] = coefs[10]; - M[1743] = coefs[10]; - M[1830] = coefs[10]; - M[1916] = coefs[10]; - M[4109] = coefs[10]; - M[4195] = coefs[10]; - M[4282] = coefs[10]; - M[4368] = coefs[10]; - M[4454] = coefs[10]; - M[4538] = coefs[10]; - M[6200] = coefs[10]; - M[6284] = coefs[10]; - M[87] = coefs[11]; - M[438] = coefs[11]; - M[525] = coefs[11]; - M[611] = coefs[11]; - M[1578] = coefs[11]; - M[1665] = coefs[11]; - M[1751] = coefs[11]; - M[1838] = coefs[11]; - M[1924] = coefs[11]; - M[4034] = coefs[11]; - M[4121] = coefs[11]; - M[4207] = coefs[11]; - M[4294] = coefs[11]; - M[4380] = coefs[11]; - M[4551] = coefs[11]; - M[6214] = coefs[11]; - M[6298] = coefs[11]; - M[153] = coefs[12]; - M[668] = coefs[12]; - M[750] = coefs[12]; - M[837] = coefs[12]; - M[2062] = coefs[12]; - M[2145] = coefs[12]; - M[2232] = coefs[12]; - M[2319] = coefs[12]; - M[2403] = coefs[12]; - M[2490] = coefs[12]; - M[2577] = coefs[12]; - M[4596] = coefs[12]; - M[4679] = coefs[12]; - M[4766] = coefs[12]; - M[4850] = coefs[12]; - M[4937] = coefs[12]; - M[5024] = coefs[12]; - M[5110] = coefs[12]; - M[6338] = coefs[12]; - M[6424] = coefs[12]; - M[159] = coefs[13]; - M[675] = coefs[13]; - M[759] = coefs[13]; - M[846] = coefs[13]; - M[2067] = coefs[13]; - M[2154] = coefs[13]; - M[2241] = coefs[13]; - M[2328] = coefs[13]; - M[2413] = coefs[13]; - M[2499] = coefs[13]; - M[4686] = coefs[13]; - M[4773] = coefs[13]; - M[4859] = coefs[13]; - M[4945] = coefs[13]; - M[5032] = coefs[13]; - M[5115] = coefs[13]; - M[6341] = coefs[13]; - M[6427] = coefs[13]; - M[164] = coefs[14]; - M[684] = coefs[14]; - M[768] = coefs[14]; - M[855] = coefs[14]; - M[2080] = coefs[14]; - M[2164] = coefs[14]; - M[2251] = coefs[14]; - M[2338] = coefs[14]; - M[2422] = coefs[14]; - M[2508] = coefs[14]; - M[4618] = coefs[14]; - M[4701] = coefs[14]; - M[4788] = coefs[14]; - M[4872] = coefs[14]; - M[4958] = coefs[14]; - M[5045] = coefs[14]; - M[5128] = coefs[14]; - M[6348] = coefs[14]; - M[6431] = coefs[14]; - M[166] = coefs[15]; - M[686] = coefs[15]; - M[770] = coefs[15]; - M[857] = coefs[15]; - M[2082] = coefs[15]; - M[2253] = coefs[15]; - M[2340] = coefs[15]; - M[2424] = coefs[15]; - M[2510] = coefs[15]; - M[2597] = coefs[15]; - M[4703] = coefs[15]; - M[4790] = coefs[15]; - M[4874] = coefs[15]; - M[4960] = coefs[15]; - M[5047] = coefs[15]; - M[5130] = coefs[15]; - M[6350] = coefs[15]; - M[6433] = coefs[15]; - M[167] = coefs[16]; - M[687] = coefs[16]; - M[771] = coefs[16]; - M[2083] = coefs[16]; - M[2168] = coefs[16]; - M[2255] = coefs[16]; - M[2342] = coefs[16]; - M[2427] = coefs[16]; - M[2513] = coefs[16]; - M[4619] = coefs[16]; - M[4705] = coefs[16]; - M[4792] = coefs[16]; - M[4877] = coefs[16]; - M[4963] = coefs[16]; - M[5132] = coefs[16]; - M[6352] = coefs[16]; - M[6435] = coefs[16]; - M[170] = coefs[17]; - M[690] = coefs[17]; - M[776] = coefs[17]; - M[863] = coefs[17]; - M[2173] = coefs[17]; - M[2260] = coefs[17]; - M[2347] = coefs[17]; - M[2433] = coefs[17]; - M[2519] = coefs[17]; - M[2606] = coefs[17]; - M[4795] = coefs[17]; - M[4969] = coefs[17]; - M[5056] = coefs[17]; - M[5139] = coefs[17]; - M[6357] = coefs[17]; - M[6441] = coefs[17]; - M[171] = coefs[18]; - M[695] = coefs[18]; - M[779] = coefs[18]; - M[2095] = coefs[18]; - M[2180] = coefs[18]; - M[2267] = coefs[18]; - M[2440] = coefs[18]; - M[2526] = coefs[18]; - M[4635] = coefs[18]; - M[4719] = coefs[18]; - M[4806] = coefs[18]; - M[4891] = coefs[18]; - M[4977] = coefs[18]; - M[6452] = coefs[18]; - M[173] = coefs[19]; - M[697] = coefs[19]; - M[781] = coefs[19]; - M[868] = coefs[19]; - M[2097] = coefs[19]; - M[2182] = coefs[19]; - M[2269] = coefs[19]; - M[2356] = coefs[19]; - M[2442] = coefs[19]; - M[2528] = coefs[19]; - M[2615] = coefs[19]; - M[4637] = coefs[19]; - M[4721] = coefs[19]; - M[4808] = coefs[19]; - M[4893] = coefs[19]; - M[4979] = coefs[19]; - M[5066] = coefs[19]; - M[5148] = coefs[19]; - M[6372] = coefs[19]; - M[6454] = coefs[19]; - M[174] = coefs[20]; - M[698] = coefs[20]; - M[784] = coefs[20]; - M[871] = coefs[20]; - M[2098] = coefs[20]; - M[2185] = coefs[20]; - M[2272] = coefs[20]; - M[2359] = coefs[20]; - M[2445] = coefs[20]; - M[2531] = coefs[20]; - M[4724] = coefs[20]; - M[4811] = coefs[20]; - M[4897] = coefs[20]; - M[4983] = coefs[20]; - M[5070] = coefs[20]; - M[5153] = coefs[20]; - M[6376] = coefs[20]; - M[6459] = coefs[20]; - M[175] = coefs[21]; - M[702] = coefs[21]; - M[788] = coefs[21]; - M[875] = coefs[21]; - M[2106] = coefs[21]; - M[2193] = coefs[21]; - M[2280] = coefs[21]; - M[2367] = coefs[21]; - M[2453] = coefs[21]; - M[2539] = coefs[21]; - M[4650] = coefs[21]; - M[4736] = coefs[21]; - M[4823] = coefs[21]; - M[4909] = coefs[21]; - M[4995] = coefs[21]; - M[5166] = coefs[21]; - M[6390] = coefs[21]; - M[6473] = coefs[21]; - M[243] = coefs[22]; - M[935] = coefs[22]; - M[1019] = coefs[22]; - M[1105] = coefs[22]; - M[2681] = coefs[22]; - M[2765] = coefs[22]; - M[2851] = coefs[22]; - M[2936] = coefs[22]; - M[3022] = coefs[22]; - M[3108] = coefs[22]; - M[5209] = coefs[22]; - M[5293] = coefs[22]; - M[5379] = coefs[22]; - M[5463] = coefs[22]; - M[6515] = coefs[22]; - M[6601] = coefs[22]; - M[247] = coefs[23]; - M[939] = coefs[23]; - M[1024] = coefs[23]; - M[1110] = coefs[23]; - M[2683] = coefs[23]; - M[2770] = coefs[23]; - M[2856] = coefs[23]; - M[2942] = coefs[23]; - M[3028] = coefs[23]; - M[5213] = coefs[23]; - M[5298] = coefs[23]; - M[5384] = coefs[23]; - M[5468] = coefs[23]; - M[6517] = coefs[23]; - M[6604] = coefs[23]; - M[251] = coefs[24]; - M[947] = coefs[24]; - M[1032] = coefs[24]; - M[1118] = coefs[24]; - M[2695] = coefs[24]; - M[2779] = coefs[24]; - M[2865] = coefs[24]; - M[2950] = coefs[24]; - M[3036] = coefs[24]; - M[5227] = coefs[24]; - M[5310] = coefs[24]; - M[5396] = coefs[24]; - M[5480] = coefs[24]; - M[6523] = coefs[24]; - M[6607] = coefs[24]; - M[255] = coefs[25]; - M[951] = coefs[25]; - M[1036] = coefs[25]; - M[2699] = coefs[25]; - M[2784] = coefs[25]; - M[2870] = coefs[25]; - M[2956] = coefs[25]; - M[3042] = coefs[25]; - M[5232] = coefs[25]; - M[5316] = coefs[25]; - M[5485] = coefs[25]; - M[6528] = coefs[25]; - M[6612] = coefs[25]; - M[256] = coefs[26]; - M[952] = coefs[26]; - M[1037] = coefs[26]; - M[1123] = coefs[26]; - M[2700] = coefs[26]; - M[2785] = coefs[26]; - M[2871] = coefs[26]; - M[2957] = coefs[26]; - M[3043] = coefs[26]; - M[3129] = coefs[26]; - M[5233] = coefs[26]; - M[5317] = coefs[26]; - M[5403] = coefs[26]; - M[5486] = coefs[26]; - M[6529] = coefs[26]; - M[6613] = coefs[26]; - M[258] = coefs[27]; - M[954] = coefs[27]; - M[1041] = coefs[27]; - M[1127] = coefs[27]; - M[2789] = coefs[27]; - M[2875] = coefs[27]; - M[2962] = coefs[27]; - M[3048] = coefs[27]; - M[3134] = coefs[27]; - M[5235] = coefs[27]; - M[5322] = coefs[27]; - M[5408] = coefs[27]; - M[5492] = coefs[27]; - M[6533] = coefs[27]; - M[6618] = coefs[27]; - M[259] = coefs[28]; - M[959] = coefs[28]; - M[1044] = coefs[28]; - M[2711] = coefs[28]; - M[2796] = coefs[28]; - M[2969] = coefs[28]; - M[3055] = coefs[28]; - M[5246] = coefs[28]; - M[5330] = coefs[28]; - M[6629] = coefs[28]; - M[260] = coefs[29]; - M[960] = coefs[29]; - M[1045] = coefs[29]; - M[1131] = coefs[29]; - M[2712] = coefs[29]; - M[2797] = coefs[29]; - M[2883] = coefs[29]; - M[2970] = coefs[29]; - M[3056] = coefs[29]; - M[3142] = coefs[29]; - M[5247] = coefs[29]; - M[5331] = coefs[29]; - M[5417] = coefs[29]; - M[5500] = coefs[29]; - M[6547] = coefs[29]; - M[6630] = coefs[29]; - M[262] = coefs[30]; - M[962] = coefs[30]; - M[1049] = coefs[30]; - M[1135] = coefs[30]; - M[2714] = coefs[30]; - M[2801] = coefs[30]; - M[2887] = coefs[30]; - M[2974] = coefs[30]; - M[3060] = coefs[30]; - M[5251] = coefs[30]; - M[5336] = coefs[30]; - M[5422] = coefs[30]; - M[5506] = coefs[30]; - M[6552] = coefs[30]; - M[6636] = coefs[30]; - M[263] = coefs[31]; - M[966] = coefs[31]; - M[1053] = coefs[31]; - M[1139] = coefs[31]; - M[2722] = coefs[31]; - M[2809] = coefs[31]; - M[2895] = coefs[31]; - M[2982] = coefs[31]; - M[3068] = coefs[31]; - M[5263] = coefs[31]; - M[5348] = coefs[31]; - M[5519] = coefs[31]; - M[6566] = coefs[31]; - M[6650] = coefs[31]; - M[332] = coefs[32]; - M[1200] = coefs[32]; - M[1284] = coefs[32]; - M[1371] = coefs[32]; - M[1458] = coefs[32]; - M[3210] = coefs[32]; - M[3294] = coefs[32]; - M[3381] = coefs[32]; - M[3468] = coefs[32]; - M[3553] = coefs[32]; - M[3640] = coefs[32]; - M[3727] = coefs[32]; - M[3814] = coefs[32]; - M[3901] = coefs[32]; - M[5564] = coefs[32]; - M[5651] = coefs[32]; - M[5738] = coefs[32]; - M[5822] = coefs[32]; - M[5908] = coefs[32]; - M[5994] = coefs[32]; - M[6080] = coefs[32]; - M[6692] = coefs[32]; - M[6778] = coefs[32]; - M[335] = coefs[33]; - M[1203] = coefs[33]; - M[1288] = coefs[33]; - M[1375] = coefs[33]; - M[1462] = coefs[33]; - M[3211] = coefs[33]; - M[3298] = coefs[33]; - M[3385] = coefs[33]; - M[3472] = coefs[33]; - M[3558] = coefs[33]; - M[3645] = coefs[33]; - M[3732] = coefs[33]; - M[3819] = coefs[33]; - M[5567] = coefs[33]; - M[5654] = coefs[33]; - M[5741] = coefs[33]; - M[5826] = coefs[33]; - M[5912] = coefs[33]; - M[5999] = coefs[33]; - M[6084] = coefs[33]; - M[6693] = coefs[33]; - M[6780] = coefs[33]; - M[340] = coefs[34]; - M[1212] = coefs[34]; - M[1297] = coefs[34]; - M[1384] = coefs[34]; - M[1471] = coefs[34]; - M[3224] = coefs[34]; - M[3308] = coefs[34]; - M[3395] = coefs[34]; - M[3482] = coefs[34]; - M[3567] = coefs[34]; - M[3654] = coefs[34]; - M[3741] = coefs[34]; - M[3828] = coefs[34]; - M[5582] = coefs[34]; - M[5669] = coefs[34]; - M[5756] = coefs[34]; - M[5839] = coefs[34]; - M[5925] = coefs[34]; - M[6011] = coefs[34]; - M[6097] = coefs[34]; - M[6700] = coefs[34]; - M[6784] = coefs[34]; - M[343] = coefs[35]; - M[1215] = coefs[35]; - M[1300] = coefs[35]; - M[1387] = coefs[35]; - M[3227] = coefs[35]; - M[3312] = coefs[35]; - M[3399] = coefs[35]; - M[3486] = coefs[35]; - M[3572] = coefs[35]; - M[3659] = coefs[35]; - M[3746] = coefs[35]; - M[3833] = coefs[35]; - M[5586] = coefs[35]; - M[5673] = coefs[35]; - M[5760] = coefs[35]; - M[5844] = coefs[35]; - M[6016] = coefs[35]; - M[6101] = coefs[35]; - M[6704] = coefs[35]; - M[6788] = coefs[35]; - M[345] = coefs[36]; - M[1217] = coefs[36]; - M[1302] = coefs[36]; - M[1389] = coefs[36]; - M[1476] = coefs[36]; - M[3229] = coefs[36]; - M[3314] = coefs[36]; - M[3401] = coefs[36]; - M[3488] = coefs[36]; - M[3661] = coefs[36]; - M[3748] = coefs[36]; - M[3835] = coefs[36]; - M[3922] = coefs[36]; - M[5762] = coefs[36]; - M[5846] = coefs[36]; - M[5932] = coefs[36]; - M[6018] = coefs[36]; - M[6103] = coefs[36]; - M[6706] = coefs[36]; - M[6790] = coefs[36]; - M[346] = coefs[37]; - M[1218] = coefs[37]; - M[1305] = coefs[37]; - M[1392] = coefs[37]; - M[1479] = coefs[37]; - M[3317] = coefs[37]; - M[3404] = coefs[37]; - M[3491] = coefs[37]; - M[3578] = coefs[37]; - M[3665] = coefs[37]; - M[3752] = coefs[37]; - M[3839] = coefs[37]; - M[3926] = coefs[37]; - M[5763] = coefs[37]; - M[5850] = coefs[37]; - M[5936] = coefs[37]; - M[6023] = coefs[37]; - M[6108] = coefs[37]; - M[6709] = coefs[37]; - M[6794] = coefs[37]; - M[347] = coefs[38]; - M[1223] = coefs[38]; - M[1308] = coefs[38]; - M[1395] = coefs[38]; - M[3239] = coefs[38]; - M[3324] = coefs[38]; - M[3411] = coefs[38]; - M[3585] = coefs[38]; - M[3672] = coefs[38]; - M[3759] = coefs[38]; - M[3846] = coefs[38]; - M[5600] = coefs[38]; - M[5687] = coefs[38]; - M[5774] = coefs[38]; - M[5858] = coefs[38]; - M[6030] = coefs[38]; - M[6805] = coefs[38]; - M[349] = coefs[39]; - M[1225] = coefs[39]; - M[1310] = coefs[39]; - M[1397] = coefs[39]; - M[1484] = coefs[39]; - M[3241] = coefs[39]; - M[3326] = coefs[39]; - M[3413] = coefs[39]; - M[3500] = coefs[39]; - M[3674] = coefs[39]; - M[3761] = coefs[39]; - M[3848] = coefs[39]; - M[3935] = coefs[39]; - M[5602] = coefs[39]; - M[5689] = coefs[39]; - M[5776] = coefs[39]; - M[5860] = coefs[39]; - M[5946] = coefs[39]; - M[6032] = coefs[39]; - M[6117] = coefs[39]; - M[6724] = coefs[39]; - M[6807] = coefs[39]; - M[350] = coefs[40]; - M[1226] = coefs[40]; - M[1313] = coefs[40]; - M[1400] = coefs[40]; - M[1487] = coefs[40]; - M[3242] = coefs[40]; - M[3329] = coefs[40]; - M[3416] = coefs[40]; - M[3503] = coefs[40]; - M[3590] = coefs[40]; - M[3677] = coefs[40]; - M[3764] = coefs[40]; - M[3851] = coefs[40]; - M[5605] = coefs[40]; - M[5692] = coefs[40]; - M[5779] = coefs[40]; - M[5864] = coefs[40]; - M[5950] = coefs[40]; - M[6037] = coefs[40]; - M[6122] = coefs[40]; - M[6728] = coefs[40]; - M[6812] = coefs[40]; - M[351] = coefs[41]; - M[1230] = coefs[41]; - M[1317] = coefs[41]; - M[1404] = coefs[41]; - M[1491] = coefs[41]; - M[3250] = coefs[41]; - M[3337] = coefs[41]; - M[3424] = coefs[41]; - M[3511] = coefs[41]; - M[3598] = coefs[41]; - M[3685] = coefs[41]; - M[3772] = coefs[41]; - M[3859] = coefs[41]; - M[5617] = coefs[41]; - M[5704] = coefs[41]; - M[5791] = coefs[41]; - M[5876] = coefs[41]; - M[6050] = coefs[41]; - M[6135] = coefs[41]; - M[6742] = coefs[41]; - M[6826] = coefs[41]; + M[64] = coefs[0]; + M[403] = coefs[0]; + M[486] = coefs[0]; + M[572] = coefs[0]; + M[1533] = coefs[0]; + M[1616] = coefs[0]; + M[1702] = coefs[0]; + M[1787] = coefs[0]; + M[1874] = coefs[0]; + M[1960] = coefs[0]; + M[3979] = coefs[0]; + M[4063] = coefs[0]; + M[4149] = coefs[0]; + M[4234] = coefs[0]; + M[4321] = coefs[0]; + M[4407] = coefs[0]; + M[4494] = coefs[0]; + M[6161] = coefs[0]; + M[6248] = coefs[0]; + M[71] = coefs[1]; + M[411] = coefs[1]; + M[496] = coefs[1]; + M[582] = coefs[1]; + M[1539] = coefs[1]; + M[1626] = coefs[1]; + M[1712] = coefs[1]; + M[1798] = coefs[1]; + M[1884] = coefs[1]; + M[4071] = coefs[1]; + M[4157] = coefs[1]; + M[4244] = coefs[1]; + M[4330] = coefs[1]; + M[4416] = coefs[1]; + M[4500] = coefs[1]; + M[6165] = coefs[1]; + M[6252] = coefs[1]; + M[75] = coefs[2]; + M[419] = coefs[2]; + M[504] = coefs[2]; + M[590] = coefs[2]; + M[1551] = coefs[2]; + M[1635] = coefs[2]; + M[1721] = coefs[2]; + M[1806] = coefs[2]; + M[1892] = coefs[2]; + M[4001] = coefs[2]; + M[4085] = coefs[2]; + M[4171] = coefs[2]; + M[4256] = coefs[2]; + M[4342] = coefs[2]; + M[4428] = coefs[2]; + M[4512] = coefs[2]; + M[6171] = coefs[2]; + M[6255] = coefs[2]; + M[76] = coefs[3]; + M[420] = coefs[3]; + M[505] = coefs[3]; + M[591] = coefs[3]; + M[1552] = coefs[3]; + M[1636] = coefs[3]; + M[1722] = coefs[3]; + M[1807] = coefs[3]; + M[1893] = coefs[3]; + M[4002] = coefs[3]; + M[4086] = coefs[3]; + M[4172] = coefs[3]; + M[4257] = coefs[3]; + M[4343] = coefs[3]; + M[4429] = coefs[3]; + M[4513] = coefs[3]; + M[6172] = coefs[3]; + M[6256] = coefs[3]; + M[77] = coefs[4]; + M[421] = coefs[4]; + M[506] = coefs[4]; + M[592] = coefs[4]; + M[1553] = coefs[4]; + M[1637] = coefs[4]; + M[1723] = coefs[4]; + M[1808] = coefs[4]; + M[1894] = coefs[4]; + M[1980] = coefs[4]; + M[4087] = coefs[4]; + M[4173] = coefs[4]; + M[4258] = coefs[4]; + M[4344] = coefs[4]; + M[4430] = coefs[4]; + M[4514] = coefs[4]; + M[6173] = coefs[4]; + M[6257] = coefs[4]; + M[79] = coefs[5]; + M[423] = coefs[5]; + M[508] = coefs[5]; + M[1555] = coefs[5]; + M[1640] = coefs[5]; + M[1726] = coefs[5]; + M[1812] = coefs[5]; + M[1898] = coefs[5]; + M[4003] = coefs[5]; + M[4090] = coefs[5]; + M[4176] = coefs[5]; + M[4262] = coefs[5]; + M[4348] = coefs[5]; + M[4517] = coefs[5]; + M[6176] = coefs[5]; + M[6260] = coefs[5]; + M[82] = coefs[6]; + M[426] = coefs[6]; + M[513] = coefs[6]; + M[599] = coefs[6]; + M[1645] = coefs[6]; + M[1731] = coefs[6]; + M[1818] = coefs[6]; + M[1904] = coefs[6]; + M[1990] = coefs[6]; + M[4179] = coefs[6]; + M[4354] = coefs[6]; + M[4440] = coefs[6]; + M[4524] = coefs[6]; + M[6181] = coefs[6]; + M[6266] = coefs[6]; + M[83] = coefs[7]; + M[431] = coefs[7]; + M[516] = coefs[7]; + M[1567] = coefs[7]; + M[1652] = coefs[7]; + M[1825] = coefs[7]; + M[1911] = coefs[7]; + M[4019] = coefs[7]; + M[4104] = coefs[7]; + M[4190] = coefs[7]; + M[4276] = coefs[7]; + M[4362] = coefs[7]; + M[6277] = coefs[7]; + M[84] = coefs[8]; + M[432] = coefs[8]; + M[517] = coefs[8]; + M[603] = coefs[8]; + M[1568] = coefs[8]; + M[1653] = coefs[8]; + M[1739] = coefs[8]; + M[1826] = coefs[8]; + M[1912] = coefs[8]; + M[1998] = coefs[8]; + M[4020] = coefs[8]; + M[4105] = coefs[8]; + M[4191] = coefs[8]; + M[4277] = coefs[8]; + M[4363] = coefs[8]; + M[4449] = coefs[8]; + M[4532] = coefs[8]; + M[6195] = coefs[8]; + M[6278] = coefs[8]; + M[85] = coefs[9]; + M[433] = coefs[9]; + M[518] = coefs[9]; + M[604] = coefs[9]; + M[1569] = coefs[9]; + M[1654] = coefs[9]; + M[1740] = coefs[9]; + M[1913] = coefs[9]; + M[1999] = coefs[9]; + M[4021] = coefs[9]; + M[4106] = coefs[9]; + M[4192] = coefs[9]; + M[4364] = coefs[9]; + M[4450] = coefs[9]; + M[4533] = coefs[9]; + M[6196] = coefs[9]; + M[6279] = coefs[9]; + M[86] = coefs[10]; + M[434] = coefs[10]; + M[521] = coefs[10]; + M[607] = coefs[10]; + M[1570] = coefs[10]; + M[1657] = coefs[10]; + M[1743] = coefs[10]; + M[1830] = coefs[10]; + M[1916] = coefs[10]; + M[4109] = coefs[10]; + M[4195] = coefs[10]; + M[4282] = coefs[10]; + M[4368] = coefs[10]; + M[4454] = coefs[10]; + M[4538] = coefs[10]; + M[6200] = coefs[10]; + M[6284] = coefs[10]; + M[87] = coefs[11]; + M[438] = coefs[11]; + M[525] = coefs[11]; + M[611] = coefs[11]; + M[1578] = coefs[11]; + M[1665] = coefs[11]; + M[1751] = coefs[11]; + M[1838] = coefs[11]; + M[1924] = coefs[11]; + M[4034] = coefs[11]; + M[4121] = coefs[11]; + M[4207] = coefs[11]; + M[4294] = coefs[11]; + M[4380] = coefs[11]; + M[4551] = coefs[11]; + M[6214] = coefs[11]; + M[6298] = coefs[11]; + M[153] = coefs[12]; + M[668] = coefs[12]; + M[750] = coefs[12]; + M[837] = coefs[12]; + M[2062] = coefs[12]; + M[2145] = coefs[12]; + M[2232] = coefs[12]; + M[2319] = coefs[12]; + M[2403] = coefs[12]; + M[2490] = coefs[12]; + M[2577] = coefs[12]; + M[4596] = coefs[12]; + M[4679] = coefs[12]; + M[4766] = coefs[12]; + M[4850] = coefs[12]; + M[4937] = coefs[12]; + M[5024] = coefs[12]; + M[5110] = coefs[12]; + M[6338] = coefs[12]; + M[6424] = coefs[12]; + M[159] = coefs[13]; + M[675] = coefs[13]; + M[759] = coefs[13]; + M[846] = coefs[13]; + M[2067] = coefs[13]; + M[2154] = coefs[13]; + M[2241] = coefs[13]; + M[2328] = coefs[13]; + M[2413] = coefs[13]; + M[2499] = coefs[13]; + M[4686] = coefs[13]; + M[4773] = coefs[13]; + M[4859] = coefs[13]; + M[4945] = coefs[13]; + M[5032] = coefs[13]; + M[5115] = coefs[13]; + M[6341] = coefs[13]; + M[6427] = coefs[13]; + M[164] = coefs[14]; + M[684] = coefs[14]; + M[768] = coefs[14]; + M[855] = coefs[14]; + M[2080] = coefs[14]; + M[2164] = coefs[14]; + M[2251] = coefs[14]; + M[2338] = coefs[14]; + M[2422] = coefs[14]; + M[2508] = coefs[14]; + M[4618] = coefs[14]; + M[4701] = coefs[14]; + M[4788] = coefs[14]; + M[4872] = coefs[14]; + M[4958] = coefs[14]; + M[5045] = coefs[14]; + M[5128] = coefs[14]; + M[6348] = coefs[14]; + M[6431] = coefs[14]; + M[166] = coefs[15]; + M[686] = coefs[15]; + M[770] = coefs[15]; + M[857] = coefs[15]; + M[2082] = coefs[15]; + M[2253] = coefs[15]; + M[2340] = coefs[15]; + M[2424] = coefs[15]; + M[2510] = coefs[15]; + M[2597] = coefs[15]; + M[4703] = coefs[15]; + M[4790] = coefs[15]; + M[4874] = coefs[15]; + M[4960] = coefs[15]; + M[5047] = coefs[15]; + M[5130] = coefs[15]; + M[6350] = coefs[15]; + M[6433] = coefs[15]; + M[167] = coefs[16]; + M[687] = coefs[16]; + M[771] = coefs[16]; + M[2083] = coefs[16]; + M[2168] = coefs[16]; + M[2255] = coefs[16]; + M[2342] = coefs[16]; + M[2427] = coefs[16]; + M[2513] = coefs[16]; + M[4619] = coefs[16]; + M[4705] = coefs[16]; + M[4792] = coefs[16]; + M[4877] = coefs[16]; + M[4963] = coefs[16]; + M[5132] = coefs[16]; + M[6352] = coefs[16]; + M[6435] = coefs[16]; + M[170] = coefs[17]; + M[690] = coefs[17]; + M[776] = coefs[17]; + M[863] = coefs[17]; + M[2173] = coefs[17]; + M[2260] = coefs[17]; + M[2347] = coefs[17]; + M[2433] = coefs[17]; + M[2519] = coefs[17]; + M[2606] = coefs[17]; + M[4795] = coefs[17]; + M[4969] = coefs[17]; + M[5056] = coefs[17]; + M[5139] = coefs[17]; + M[6357] = coefs[17]; + M[6441] = coefs[17]; + M[171] = coefs[18]; + M[695] = coefs[18]; + M[779] = coefs[18]; + M[2095] = coefs[18]; + M[2180] = coefs[18]; + M[2267] = coefs[18]; + M[2440] = coefs[18]; + M[2526] = coefs[18]; + M[4635] = coefs[18]; + M[4719] = coefs[18]; + M[4806] = coefs[18]; + M[4891] = coefs[18]; + M[4977] = coefs[18]; + M[6452] = coefs[18]; + M[173] = coefs[19]; + M[697] = coefs[19]; + M[781] = coefs[19]; + M[868] = coefs[19]; + M[2097] = coefs[19]; + M[2182] = coefs[19]; + M[2269] = coefs[19]; + M[2356] = coefs[19]; + M[2442] = coefs[19]; + M[2528] = coefs[19]; + M[2615] = coefs[19]; + M[4637] = coefs[19]; + M[4721] = coefs[19]; + M[4808] = coefs[19]; + M[4893] = coefs[19]; + M[4979] = coefs[19]; + M[5066] = coefs[19]; + M[5148] = coefs[19]; + M[6372] = coefs[19]; + M[6454] = coefs[19]; + M[174] = coefs[20]; + M[698] = coefs[20]; + M[784] = coefs[20]; + M[871] = coefs[20]; + M[2098] = coefs[20]; + M[2185] = coefs[20]; + M[2272] = coefs[20]; + M[2359] = coefs[20]; + M[2445] = coefs[20]; + M[2531] = coefs[20]; + M[4724] = coefs[20]; + M[4811] = coefs[20]; + M[4897] = coefs[20]; + M[4983] = coefs[20]; + M[5070] = coefs[20]; + M[5153] = coefs[20]; + M[6376] = coefs[20]; + M[6459] = coefs[20]; + M[175] = coefs[21]; + M[702] = coefs[21]; + M[788] = coefs[21]; + M[875] = coefs[21]; + M[2106] = coefs[21]; + M[2193] = coefs[21]; + M[2280] = coefs[21]; + M[2367] = coefs[21]; + M[2453] = coefs[21]; + M[2539] = coefs[21]; + M[4650] = coefs[21]; + M[4736] = coefs[21]; + M[4823] = coefs[21]; + M[4909] = coefs[21]; + M[4995] = coefs[21]; + M[5166] = coefs[21]; + M[6390] = coefs[21]; + M[6473] = coefs[21]; + M[243] = coefs[22]; + M[935] = coefs[22]; + M[1019] = coefs[22]; + M[1105] = coefs[22]; + M[2681] = coefs[22]; + M[2765] = coefs[22]; + M[2851] = coefs[22]; + M[2936] = coefs[22]; + M[3022] = coefs[22]; + M[3108] = coefs[22]; + M[5209] = coefs[22]; + M[5293] = coefs[22]; + M[5379] = coefs[22]; + M[5463] = coefs[22]; + M[6515] = coefs[22]; + M[6601] = coefs[22]; + M[247] = coefs[23]; + M[939] = coefs[23]; + M[1024] = coefs[23]; + M[1110] = coefs[23]; + M[2683] = coefs[23]; + M[2770] = coefs[23]; + M[2856] = coefs[23]; + M[2942] = coefs[23]; + M[3028] = coefs[23]; + M[5213] = coefs[23]; + M[5298] = coefs[23]; + M[5384] = coefs[23]; + M[5468] = coefs[23]; + M[6517] = coefs[23]; + M[6604] = coefs[23]; + M[251] = coefs[24]; + M[947] = coefs[24]; + M[1032] = coefs[24]; + M[1118] = coefs[24]; + M[2695] = coefs[24]; + M[2779] = coefs[24]; + M[2865] = coefs[24]; + M[2950] = coefs[24]; + M[3036] = coefs[24]; + M[5227] = coefs[24]; + M[5310] = coefs[24]; + M[5396] = coefs[24]; + M[5480] = coefs[24]; + M[6523] = coefs[24]; + M[6607] = coefs[24]; + M[255] = coefs[25]; + M[951] = coefs[25]; + M[1036] = coefs[25]; + M[2699] = coefs[25]; + M[2784] = coefs[25]; + M[2870] = coefs[25]; + M[2956] = coefs[25]; + M[3042] = coefs[25]; + M[5232] = coefs[25]; + M[5316] = coefs[25]; + M[5485] = coefs[25]; + M[6528] = coefs[25]; + M[6612] = coefs[25]; + M[256] = coefs[26]; + M[952] = coefs[26]; + M[1037] = coefs[26]; + M[1123] = coefs[26]; + M[2700] = coefs[26]; + M[2785] = coefs[26]; + M[2871] = coefs[26]; + M[2957] = coefs[26]; + M[3043] = coefs[26]; + M[3129] = coefs[26]; + M[5233] = coefs[26]; + M[5317] = coefs[26]; + M[5403] = coefs[26]; + M[5486] = coefs[26]; + M[6529] = coefs[26]; + M[6613] = coefs[26]; + M[258] = coefs[27]; + M[954] = coefs[27]; + M[1041] = coefs[27]; + M[1127] = coefs[27]; + M[2789] = coefs[27]; + M[2875] = coefs[27]; + M[2962] = coefs[27]; + M[3048] = coefs[27]; + M[3134] = coefs[27]; + M[5235] = coefs[27]; + M[5322] = coefs[27]; + M[5408] = coefs[27]; + M[5492] = coefs[27]; + M[6533] = coefs[27]; + M[6618] = coefs[27]; + M[259] = coefs[28]; + M[959] = coefs[28]; + M[1044] = coefs[28]; + M[2711] = coefs[28]; + M[2796] = coefs[28]; + M[2969] = coefs[28]; + M[3055] = coefs[28]; + M[5246] = coefs[28]; + M[5330] = coefs[28]; + M[6629] = coefs[28]; + M[260] = coefs[29]; + M[960] = coefs[29]; + M[1045] = coefs[29]; + M[1131] = coefs[29]; + M[2712] = coefs[29]; + M[2797] = coefs[29]; + M[2883] = coefs[29]; + M[2970] = coefs[29]; + M[3056] = coefs[29]; + M[3142] = coefs[29]; + M[5247] = coefs[29]; + M[5331] = coefs[29]; + M[5417] = coefs[29]; + M[5500] = coefs[29]; + M[6547] = coefs[29]; + M[6630] = coefs[29]; + M[262] = coefs[30]; + M[962] = coefs[30]; + M[1049] = coefs[30]; + M[1135] = coefs[30]; + M[2714] = coefs[30]; + M[2801] = coefs[30]; + M[2887] = coefs[30]; + M[2974] = coefs[30]; + M[3060] = coefs[30]; + M[5251] = coefs[30]; + M[5336] = coefs[30]; + M[5422] = coefs[30]; + M[5506] = coefs[30]; + M[6552] = coefs[30]; + M[6636] = coefs[30]; + M[263] = coefs[31]; + M[966] = coefs[31]; + M[1053] = coefs[31]; + M[1139] = coefs[31]; + M[2722] = coefs[31]; + M[2809] = coefs[31]; + M[2895] = coefs[31]; + M[2982] = coefs[31]; + M[3068] = coefs[31]; + M[5263] = coefs[31]; + M[5348] = coefs[31]; + M[5519] = coefs[31]; + M[6566] = coefs[31]; + M[6650] = coefs[31]; + M[332] = coefs[32]; + M[1200] = coefs[32]; + M[1284] = coefs[32]; + M[1371] = coefs[32]; + M[1458] = coefs[32]; + M[3210] = coefs[32]; + M[3294] = coefs[32]; + M[3381] = coefs[32]; + M[3468] = coefs[32]; + M[3553] = coefs[32]; + M[3640] = coefs[32]; + M[3727] = coefs[32]; + M[3814] = coefs[32]; + M[3901] = coefs[32]; + M[5564] = coefs[32]; + M[5651] = coefs[32]; + M[5738] = coefs[32]; + M[5822] = coefs[32]; + M[5908] = coefs[32]; + M[5994] = coefs[32]; + M[6080] = coefs[32]; + M[6692] = coefs[32]; + M[6778] = coefs[32]; + M[335] = coefs[33]; + M[1203] = coefs[33]; + M[1288] = coefs[33]; + M[1375] = coefs[33]; + M[1462] = coefs[33]; + M[3211] = coefs[33]; + M[3298] = coefs[33]; + M[3385] = coefs[33]; + M[3472] = coefs[33]; + M[3558] = coefs[33]; + M[3645] = coefs[33]; + M[3732] = coefs[33]; + M[3819] = coefs[33]; + M[5567] = coefs[33]; + M[5654] = coefs[33]; + M[5741] = coefs[33]; + M[5826] = coefs[33]; + M[5912] = coefs[33]; + M[5999] = coefs[33]; + M[6084] = coefs[33]; + M[6693] = coefs[33]; + M[6780] = coefs[33]; + M[340] = coefs[34]; + M[1212] = coefs[34]; + M[1297] = coefs[34]; + M[1384] = coefs[34]; + M[1471] = coefs[34]; + M[3224] = coefs[34]; + M[3308] = coefs[34]; + M[3395] = coefs[34]; + M[3482] = coefs[34]; + M[3567] = coefs[34]; + M[3654] = coefs[34]; + M[3741] = coefs[34]; + M[3828] = coefs[34]; + M[5582] = coefs[34]; + M[5669] = coefs[34]; + M[5756] = coefs[34]; + M[5839] = coefs[34]; + M[5925] = coefs[34]; + M[6011] = coefs[34]; + M[6097] = coefs[34]; + M[6700] = coefs[34]; + M[6784] = coefs[34]; + M[343] = coefs[35]; + M[1215] = coefs[35]; + M[1300] = coefs[35]; + M[1387] = coefs[35]; + M[3227] = coefs[35]; + M[3312] = coefs[35]; + M[3399] = coefs[35]; + M[3486] = coefs[35]; + M[3572] = coefs[35]; + M[3659] = coefs[35]; + M[3746] = coefs[35]; + M[3833] = coefs[35]; + M[5586] = coefs[35]; + M[5673] = coefs[35]; + M[5760] = coefs[35]; + M[5844] = coefs[35]; + M[6016] = coefs[35]; + M[6101] = coefs[35]; + M[6704] = coefs[35]; + M[6788] = coefs[35]; + M[345] = coefs[36]; + M[1217] = coefs[36]; + M[1302] = coefs[36]; + M[1389] = coefs[36]; + M[1476] = coefs[36]; + M[3229] = coefs[36]; + M[3314] = coefs[36]; + M[3401] = coefs[36]; + M[3488] = coefs[36]; + M[3661] = coefs[36]; + M[3748] = coefs[36]; + M[3835] = coefs[36]; + M[3922] = coefs[36]; + M[5762] = coefs[36]; + M[5846] = coefs[36]; + M[5932] = coefs[36]; + M[6018] = coefs[36]; + M[6103] = coefs[36]; + M[6706] = coefs[36]; + M[6790] = coefs[36]; + M[346] = coefs[37]; + M[1218] = coefs[37]; + M[1305] = coefs[37]; + M[1392] = coefs[37]; + M[1479] = coefs[37]; + M[3317] = coefs[37]; + M[3404] = coefs[37]; + M[3491] = coefs[37]; + M[3578] = coefs[37]; + M[3665] = coefs[37]; + M[3752] = coefs[37]; + M[3839] = coefs[37]; + M[3926] = coefs[37]; + M[5763] = coefs[37]; + M[5850] = coefs[37]; + M[5936] = coefs[37]; + M[6023] = coefs[37]; + M[6108] = coefs[37]; + M[6709] = coefs[37]; + M[6794] = coefs[37]; + M[347] = coefs[38]; + M[1223] = coefs[38]; + M[1308] = coefs[38]; + M[1395] = coefs[38]; + M[3239] = coefs[38]; + M[3324] = coefs[38]; + M[3411] = coefs[38]; + M[3585] = coefs[38]; + M[3672] = coefs[38]; + M[3759] = coefs[38]; + M[3846] = coefs[38]; + M[5600] = coefs[38]; + M[5687] = coefs[38]; + M[5774] = coefs[38]; + M[5858] = coefs[38]; + M[6030] = coefs[38]; + M[6805] = coefs[38]; + M[349] = coefs[39]; + M[1225] = coefs[39]; + M[1310] = coefs[39]; + M[1397] = coefs[39]; + M[1484] = coefs[39]; + M[3241] = coefs[39]; + M[3326] = coefs[39]; + M[3413] = coefs[39]; + M[3500] = coefs[39]; + M[3674] = coefs[39]; + M[3761] = coefs[39]; + M[3848] = coefs[39]; + M[3935] = coefs[39]; + M[5602] = coefs[39]; + M[5689] = coefs[39]; + M[5776] = coefs[39]; + M[5860] = coefs[39]; + M[5946] = coefs[39]; + M[6032] = coefs[39]; + M[6117] = coefs[39]; + M[6724] = coefs[39]; + M[6807] = coefs[39]; + M[350] = coefs[40]; + M[1226] = coefs[40]; + M[1313] = coefs[40]; + M[1400] = coefs[40]; + M[1487] = coefs[40]; + M[3242] = coefs[40]; + M[3329] = coefs[40]; + M[3416] = coefs[40]; + M[3503] = coefs[40]; + M[3590] = coefs[40]; + M[3677] = coefs[40]; + M[3764] = coefs[40]; + M[3851] = coefs[40]; + M[5605] = coefs[40]; + M[5692] = coefs[40]; + M[5779] = coefs[40]; + M[5864] = coefs[40]; + M[5950] = coefs[40]; + M[6037] = coefs[40]; + M[6122] = coefs[40]; + M[6728] = coefs[40]; + M[6812] = coefs[40]; + M[351] = coefs[41]; + M[1230] = coefs[41]; + M[1317] = coefs[41]; + M[1404] = coefs[41]; + M[1491] = coefs[41]; + M[3250] = coefs[41]; + M[3337] = coefs[41]; + M[3424] = coefs[41]; + M[3511] = coefs[41]; + M[3598] = coefs[41]; + M[3685] = coefs[41]; + M[3772] = coefs[41]; + M[3859] = coefs[41]; + M[5617] = coefs[41]; + M[5704] = coefs[41]; + M[5791] = coefs[41]; + M[5876] = coefs[41]; + M[6050] = coefs[41]; + M[6135] = coefs[41]; + M[6742] = coefs[41]; + M[6826] = coefs[41]; - // GJ elimination - GJ(M, 78, 88, 2.2204e-11); + // GJ elimination + GJ(M, 78, 88, 2.2204e-11); - // action matrix - std::memset(A, 0, sizeof (double) * 100); + // action matrix + std::memset(A, 0, sizeof(double) * 100); - A[1] = 1; - A[15] = 1; - A[26] = 1; - A[37] = 1; - A[48] = 1; - A[50] = -M[6599]; - A[51] = -M[6598]; - A[52] = -M[6597]; - A[53] = -M[6596]; - A[54] = -M[6595]; - A[55] = -M[6594]; - A[56] = -M[6593]; - A[57] = -M[6592]; - A[58] = -M[6591]; - A[59] = -M[6590]; - A[60] = -M[6511]; - A[61] = -M[6510]; - A[62] = -M[6509]; - A[63] = -M[6508]; - A[64] = -M[6507]; - A[65] = -M[6506]; - A[66] = -M[6505]; - A[67] = -M[6504]; - A[68] = -M[6503]; - A[69] = -M[6502]; - A[70] = -M[6423]; - A[71] = -M[6422]; - A[72] = -M[6421]; - A[73] = -M[6420]; - A[74] = -M[6419]; - A[75] = -M[6418]; - A[76] = -M[6417]; - A[77] = -M[6416]; - A[78] = -M[6415]; - A[79] = -M[6414]; - A[80] = -M[6335]; - A[81] = -M[6334]; - A[82] = -M[6333]; - A[83] = -M[6332]; - A[84] = -M[6331]; - A[85] = -M[6330]; - A[86] = -M[6329]; - A[87] = -M[6328]; - A[88] = -M[6327]; - A[89] = -M[6326]; - A[90] = -M[6247]; - A[91] = -M[6246]; - A[92] = -M[6245]; - A[93] = -M[6244]; - A[94] = -M[6243]; - A[95] = -M[6242]; - A[96] = -M[6241]; - A[97] = -M[6240]; - A[98] = -M[6239]; - A[99] = -M[6238]; + A[1] = 1; + A[15] = 1; + A[26] = 1; + A[37] = 1; + A[48] = 1; + A[50] = -M[6599]; + A[51] = -M[6598]; + A[52] = -M[6597]; + A[53] = -M[6596]; + A[54] = -M[6595]; + A[55] = -M[6594]; + A[56] = -M[6593]; + A[57] = -M[6592]; + A[58] = -M[6591]; + A[59] = -M[6590]; + A[60] = -M[6511]; + A[61] = -M[6510]; + A[62] = -M[6509]; + A[63] = -M[6508]; + A[64] = -M[6507]; + A[65] = -M[6506]; + A[66] = -M[6505]; + A[67] = -M[6504]; + A[68] = -M[6503]; + A[69] = -M[6502]; + A[70] = -M[6423]; + A[71] = -M[6422]; + A[72] = -M[6421]; + A[73] = -M[6420]; + A[74] = -M[6419]; + A[75] = -M[6418]; + A[76] = -M[6417]; + A[77] = -M[6416]; + A[78] = -M[6415]; + A[79] = -M[6414]; + A[80] = -M[6335]; + A[81] = -M[6334]; + A[82] = -M[6333]; + A[83] = -M[6332]; + A[84] = -M[6331]; + A[85] = -M[6330]; + A[86] = -M[6329]; + A[87] = -M[6328]; + A[88] = -M[6327]; + A[89] = -M[6326]; + A[90] = -M[6247]; + A[91] = -M[6246]; + A[92] = -M[6245]; + A[93] = -M[6244]; + A[94] = -M[6243]; + A[95] = -M[6242]; + A[96] = -M[6241]; + A[97] = -M[6240]; + A[98] = -M[6239]; + A[99] = -M[6238]; } /** * @brief isNan * @param[in] A matrix */ -bool isNan(const Eigen::MatrixXcd &A) +bool isNan(const Eigen::MatrixXcd& A) { - const Mat B = A.real(); - for(Mat::Index i = 0; i < B.cols() * B.rows(); ++i) - { - if(std::isnan(B.data()[i])) return true; - } - return false; + const Mat B = A.real(); + for (Mat::Index i = 0; i < B.cols() * B.rows(); ++i) + { + if (std::isnan(B.data()[i])) + return true; + } + return false; } /** @@ -1081,42 +1082,42 @@ bool isNan(const Eigen::MatrixXcd &A) * @param[in] sol * @param[out] vSol */ -bool validSol(const Eigen::MatrixXcd &sol, Mat &vSol) +bool validSol(const Eigen::MatrixXcd& sol, Mat& vSol) { - assert(sol.cols() == 10 && sol.rows() == 4); + assert(sol.cols() == 10 && sol.rows() == 4); - Mat imSol = sol.imag(); - Mat reSol = sol.real(); - std::vector correct; - for(Mat::Index i = 0; i < 10; ++i) - { - bool isReal = true; - for(Mat::Index j = 0; j < 4; ++j) + Mat imSol = sol.imag(); + Mat reSol = sol.real(); + std::vector correct; + for (Mat::Index i = 0; i < 10; ++i) { - if(imSol(j, i) != 0) - { - isReal = false; - break; - } + bool isReal = true; + for (Mat::Index j = 0; j < 4; ++j) + { + if (imSol(j, i) != 0) + { + isReal = false; + break; + } + } + if (isReal && reSol(3, i) > 0) + { + correct.push_back(i); + } } - if(isReal && reSol(3, i) > 0) + if (correct.empty()) { - correct.push_back(i); + return false; } - } - if(correct.empty()) - { - return false; - } - else - { - vSol = Mat(4, correct.size()); - for(std::size_t i = 0; i < correct.size(); ++i) + else { - vSol.block(0, i, 4, 1) = reSol.block(0, correct.at(i), 4, 1); + vSol = Mat(4, correct.size()); + for (std::size_t i = 0; i < correct.size(); ++i) + { + vSol.block(0, i, 4, 1) = reSol.block(0, correct.at(i), 4, 1); + } } - } - return true; + return true; } /** @@ -1126,139 +1127,137 @@ bool validSol(const Eigen::MatrixXcd &sol, Mat &vSol) * @param[out] R * @param[out] t */ -void getRigidTransform(const Mat &pp1, const Mat &pp2, Mat &R, Vec3 &t) +void getRigidTransform(const Mat& pp1, const Mat& pp2, Mat& R, Vec3& t) { - Mat p1(pp1); - Mat p2(pp2); + Mat p1(pp1); + Mat p2(pp2); - // shift centers of gravity to the origin - const Mat p1mean = p1.rowwise().sum() * 0.25; - const Mat p2mean = p2.rowwise().sum() * 0.25; - for(Mat::Index i = 0; i < 4; i++) - { - p1.block(0, i, 3, 1) -= p1mean; - p2.block(0, i, 3, 1) -= p2mean; - } + // shift centers of gravity to the origin + const Mat p1mean = p1.rowwise().sum() * 0.25; + const Mat p2mean = p2.rowwise().sum() * 0.25; + for (Mat::Index i = 0; i < 4; i++) + { + p1.block(0, i, 3, 1) -= p1mean; + p2.block(0, i, 3, 1) -= p2mean; + } - // normalize to unit size - const Mat u1 = p1 * p1.colwise().norm().cwiseInverse().asDiagonal(); - const Mat u2 = p2 * p2.colwise().norm().cwiseInverse().asDiagonal(); + // normalize to unit size + const Mat u1 = p1 * p1.colwise().norm().cwiseInverse().asDiagonal(); + const Mat u2 = p2 * p2.colwise().norm().cwiseInverse().asDiagonal(); - // calc rotation - const Mat C = u2 * u1.transpose(); - Eigen::JacobiSVD svd(C, Eigen::ComputeThinU | Eigen::ComputeThinV); - const Mat U = svd.matrixU(); - const Mat V = svd.matrixV(); - Mat S = svd.singularValues(); + // calc rotation + const Mat C = u2 * u1.transpose(); + Eigen::JacobiSVD svd(C, Eigen::ComputeThinU | Eigen::ComputeThinV); + const Mat U = svd.matrixU(); + const Mat V = svd.matrixV(); + Mat S = svd.singularValues(); - // fit to rotation space - S(0) = (S(0) >= 0 ? 1 : -1); - S(1) = (S(1) >= 0 ? 1 : -1); - S(2) = ((U * V.transpose()).determinant() >= 0 ? 1 : -1); + // fit to rotation space + S(0) = (S(0) >= 0 ? 1 : -1); + S(1) = (S(1) >= 0 ? 1 : -1); + S(2) = ((U * V.transpose()).determinant() >= 0 ? 1 : -1); - R = U * S.asDiagonal() * V.transpose(); - t = -R * p1mean + p2mean; + R = U * S.asDiagonal() * V.transpose(); + t = -R * p1mean + p2mean; } void P4PfSolver::solve(const Mat& x2d, const Mat& x3d, std::vector& models) const { - Mat pt2D(x2d); - Mat pt3D(x3d); + Mat pt2D(x2d); + Mat pt3D(x3d); - assert(2 == pt2D.rows()); - assert(3 == pt3D.rows()); - assert(pt2D.cols() == pt3D.cols()); + assert(2 == pt2D.rows()); + assert(3 == pt3D.rows()); + assert(pt2D.cols() == pt3D.cols()); - const Vec3 mean3d = pt3D.rowwise().mean(); + const Vec3 mean3d = pt3D.rowwise().mean(); - pt3D -= (mean3d * Mat::Constant(1, 4, 1.0)); + pt3D -= (mean3d * Mat::Constant(1, 4, 1.0)); - const double var = pt3D.colwise().norm().sum() / 4; - const double var2d = pt2D.colwise().norm().sum() / 4; + const double var = pt3D.colwise().norm().sum() / 4; + const double var2d = pt2D.colwise().norm().sum() / 4; - pt3D *= (1 / var); - pt2D *= (1 / var2d); + pt3D *= (1 / var); + pt2D *= (1 / var2d); - const double tol = std::numeric_limits::epsilon(); - const double glab = (pt3D.col(0) - pt3D.col(1)).squaredNorm(); - const double glac = (pt3D.col(0) - pt3D.col(2)).squaredNorm(); - const double glad = (pt3D.col(0) - pt3D.col(3)).squaredNorm(); - const double glbc = (pt3D.col(1) - pt3D.col(2)).squaredNorm(); - const double glbd = (pt3D.col(1) - pt3D.col(3)).squaredNorm(); - const double glcd = (pt3D.col(2) - pt3D.col(3)).squaredNorm(); + const double tol = std::numeric_limits::epsilon(); + const double glab = (pt3D.col(0) - pt3D.col(1)).squaredNorm(); + const double glac = (pt3D.col(0) - pt3D.col(2)).squaredNorm(); + const double glad = (pt3D.col(0) - pt3D.col(3)).squaredNorm(); + const double glbc = (pt3D.col(1) - pt3D.col(2)).squaredNorm(); + const double glbd = (pt3D.col(1) - pt3D.col(3)).squaredNorm(); + const double glcd = (pt3D.col(2) - pt3D.col(3)).squaredNorm(); - // initial solution degeneracy - invalid input - if(glab * glac * glad * glbc * glbd * glcd < tol) - return; + // initial solution degeneracy - invalid input + if (glab * glac * glad * glbc * glbd * glcd < tol) + return; - Mat A = Mat::Zero(10, 10); - { - const double gl[] = {glab, glac, glad, glbc, glbd, glcd}; - const double *a1 = pt2D.col(0).data(); - const double *b1 = pt2D.col(1).data(); - const double *c1 = pt2D.col(2).data(); - const double *d1 = pt2D.col(3).data(); + Mat A = Mat::Zero(10, 10); + { + const double gl[] = {glab, glac, glad, glbc, glbd, glcd}; + const double* a1 = pt2D.col(0).data(); + const double* b1 = pt2D.col(1).data(); + const double* c1 = pt2D.col(2).data(); + const double* d1 = pt2D.col(3).data(); - computeP4pfPoses(gl, a1, b1, c1, d1, A.data()); - } + computeP4pfPoses(gl, a1, b1, c1, d1, A.data()); + } - Mat vSol; - { - Eigen::EigenSolver es(A.transpose()); - Eigen::MatrixXcd sol = es.eigenvectors(); - Eigen::MatrixXcd diag = sol.row(0).cwiseInverse().asDiagonal(); + Mat vSol; + { + Eigen::EigenSolver es(A.transpose()); + Eigen::MatrixXcd sol = es.eigenvectors(); + Eigen::MatrixXcd diag = sol.row(0).cwiseInverse().asDiagonal(); - sol = sol.block(1, 0, 4, 10) * diag; + sol = sol.block(1, 0, 4, 10) * diag; - // contain at least one NaN - if(isNan(sol)) - return; + // contain at least one NaN + if (isNan(sol)) + return; - // separarte valid solutions - if(!validSol(sol, vSol)) - return; - } + // separarte valid solutions + if (!validSol(sol, vSol)) + return; + } - // recover camera rotation and translation - for(Mat::Index i = 0; i < vSol.cols(); ++i) - { - const double f = sqrt(vSol(3, i)); - const double zd = vSol(0, i); - const double zc = vSol(1, i); - const double zb = vSol(2, i); + // recover camera rotation and translation + for (Mat::Index i = 0; i < vSol.cols(); ++i) + { + const double f = sqrt(vSol(3, i)); + const double zd = vSol(0, i); + const double zc = vSol(1, i); + const double zb = vSol(2, i); - // create p3d points in a camera coordinate system(using depths) - Mat p3dc = Mat(3, 4); - p3dc << pt2D(0, 0), zb * pt2D(0, 1), zc * pt2D(0, 2), zd * pt2D(0, 3), - pt2D(1, 0), zb * pt2D(1, 1), zc * pt2D(1, 2), zd * pt2D(1, 3), - f, zb * f, zc * f, zd * f; + // create p3d points in a camera coordinate system(using depths) + Mat p3dc = Mat(3, 4); + p3dc << pt2D(0, 0), zb * pt2D(0, 1), zc * pt2D(0, 2), zd * pt2D(0, 3), pt2D(1, 0), zb * pt2D(1, 1), zc * pt2D(1, 2), zd * pt2D(1, 3), f, + zb * f, zc * f, zd * f; - // fix scale(recover 'za') - Mat d = Mat(6, 1); - d(0, 0) = sqrt(glab / (p3dc.col(0) - p3dc.col(1)).squaredNorm()); - d(1, 0) = sqrt(glac / (p3dc.col(0) - p3dc.col(2)).squaredNorm()); - d(2, 0) = sqrt(glad / (p3dc.col(0) - p3dc.col(3)).squaredNorm()); - d(3, 0) = sqrt(glbc / (p3dc.col(1) - p3dc.col(2)).squaredNorm()); - d(4, 0) = sqrt(glbd / (p3dc.col(1) - p3dc.col(3)).squaredNorm()); - d(5, 0) = sqrt(glcd / (p3dc.col(2) - p3dc.col(3)).squaredNorm()); - // all d(i) should be equal... + // fix scale(recover 'za') + Mat d = Mat(6, 1); + d(0, 0) = sqrt(glab / (p3dc.col(0) - p3dc.col(1)).squaredNorm()); + d(1, 0) = sqrt(glac / (p3dc.col(0) - p3dc.col(2)).squaredNorm()); + d(2, 0) = sqrt(glad / (p3dc.col(0) - p3dc.col(3)).squaredNorm()); + d(3, 0) = sqrt(glbc / (p3dc.col(1) - p3dc.col(2)).squaredNorm()); + d(4, 0) = sqrt(glbd / (p3dc.col(1) - p3dc.col(3)).squaredNorm()); + d(5, 0) = sqrt(glcd / (p3dc.col(2) - p3dc.col(3)).squaredNorm()); + // all d(i) should be equal... - //gta = median(d); - const double gta = d.sum() / 6; - p3dc = gta * p3dc; + // gta = median(d); + const double gta = d.sum() / 6; + p3dc = gta * p3dc; - // calc camera - Mat Rr; - Vec3 tt; - getRigidTransform(pt3D, p3dc, Rr, tt); - const Vec3 t = var * tt - Rr * mean3d; + // calc camera + Mat Rr; + Vec3 tt; + getRigidTransform(pt3D, p3dc, Rr, tt); + const Vec3 t = var * tt - Rr * mean3d; - // output - models.emplace_back(Rr, t, f * var2d); - } + // output + models.emplace_back(Rr, t, f * var2d); + } } - -} // namespace resection -} // namespace multiview -} // namespace aliceVision +} // namespace resection +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/resection/P4PfSolver.hpp b/src/aliceVision/multiview/resection/P4PfSolver.hpp index 38b94091c4..f2dd3b692f 100644 --- a/src/aliceVision/multiview/resection/P4PfSolver.hpp +++ b/src/aliceVision/multiview/resection/P4PfSolver.hpp @@ -19,47 +19,42 @@ namespace resection { */ struct P4PfModel { - P4PfModel(Mat R, Vec3 t, double f) - : _R(R) - , _t(t) - , _f(f) - {} - - Mat34 getP() const - { - Mat34 P; - Mat K = Mat(3, 3); - - K << _f, 0, 0, - 0, _f, 0, - 0, 0, 1; - - P.block(0, 0, 3, 3) = K * _R; - P.block(0, 3, 3, 1) = K * _t; - - return P; - } - - /// rotation matrix - Mat _R; - /// translation vector - Vec3 _t; - /// focal length - double _f; + P4PfModel(Mat R, Vec3 t, double f) + : _R(R), + _t(t), + _f(f) + {} + + Mat34 getP() const + { + Mat34 P; + Mat K = Mat(3, 3); + + K << _f, 0, 0, 0, _f, 0, 0, 0, 1; + + P.block(0, 0, 3, 3) = K * _R; + P.block(0, 3, 3, 1) = K * _t; + + return P; + } + + /// rotation matrix + Mat _R; + /// translation vector + Vec3 _t; + /// focal length + double _f; }; struct P4PfError : public ISolverErrorResection { - /** - * @brief Compute the residual of the projection distance(p2d, project(P,p3d)) - * @param[in] model solution - * @param[in] p2d feature vector - * @param[in] p3d corresponding 3D world point - */ - inline double error(const P4PfModel& model, const Vec2& p2d, const Vec3& p3d) const override - { - return (p2d - project(model.getP(), p3d)).norm(); - } + /** + * @brief Compute the residual of the projection distance(p2d, project(P,p3d)) + * @param[in] model solution + * @param[in] p2d feature vector + * @param[in] p3d corresponding 3D world point + */ + inline double error(const P4PfModel& model, const Vec2& p2d, const Vec3& p3d) const override { return (p2d - project(model.getP(), p3d)).norm(); } }; /** @@ -71,50 +66,43 @@ struct P4PfError : public ISolverErrorResection */ class P4PfSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 4; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 10; - } - - /** - * @brief Solve the problem of camera pose. - * @param[in] x2d featureVectors 2 x 4 matrix with feature vectors with subtracted principal point (each column is a vector) - * @param[in] x3d worldPoints 3 x 4 matrix with corresponding 3D world points (each column is a point) - * @param[out] models M x n vector that will contain the each solution in structure P4PfModel (rotation matrix P4PfModel._R, - * translation vector P4PfModel._t, focal length P4PfModel._f). Following equation holds for each solution: - * lambda*pt2D = diag([P4PfModel._f P4PfModel._f 1])*[P4PfModel._R P4PfModel._t] * pt3D - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; - - /** - * @brief Solve the problem. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("P4PfSolver does not support problem solving with weights."); - } + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 4; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 10; } + + /** + * @brief Solve the problem of camera pose. + * @param[in] x2d featureVectors 2 x 4 matrix with feature vectors with subtracted principal point (each column is a vector) + * @param[in] x3d worldPoints 3 x 4 matrix with corresponding 3D world points (each column is a point) + * @param[out] models M x n vector that will contain the each solution in structure P4PfModel (rotation matrix P4PfModel._R, + * translation vector P4PfModel._t, focal length P4PfModel._f). Following equation holds for each solution: + * lambda*pt2D = diag([P4PfModel._f P4PfModel._f 1])*[P4PfModel._R P4PfModel._t] * pt3D + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; + + /** + * @brief Solve the problem. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("P4PfSolver does not support problem solving with weights."); + } }; -} // namespace resection -} // namespace multiview -} // namespace aliceVision +} // namespace resection +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/resection/P5PfrSolver.cpp b/src/aliceVision/multiview/resection/P5PfrSolver.cpp index 95a0e1898f..807b3e8cd8 100644 --- a/src/aliceVision/multiview/resection/P5PfrSolver.cpp +++ b/src/aliceVision/multiview/resection/P5PfrSolver.cpp @@ -21,342 +21,336 @@ namespace resection { * @brief Compute the nullspace, choose the algorithm based on input matrix size * @param[in] A matrix */ -Mat nullspace(const Mat &A) +Mat nullspace(const Mat& A) { - Mat N; - if(A.rows() < A.cols()) - { - // LU decomposition - Eigen::FullPivLU lu(A); - N = lu.kernel(); - } - else - { - // SVD decomposition - Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); - N = svd.matrixV(); - } - return N; + Mat N; + if (A.rows() < A.cols()) + { + // LU decomposition + Eigen::FullPivLU lu(A); + N = lu.kernel(); + } + else + { + // SVD decomposition + Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); + N = svd.matrixV(); + } + return N; } Mat P5PfrModel::divisionToPolynomialModelDistortion(const Mat& x2d /*,double maxRadius*/) const { - Vec k(3); - - // make k of length 3 if shorter - for(Vec::Index i = 0; i < 3; ++i) - { - k(i) = (i < _r.rows()) ? _r(i) : 0; - } - - Vec di(x2d.cols()); - Vec o(x2d.cols()); - - for(Vec::Index i = 0; i < x2d.cols(); ++i) - { - di(i) = x2d(0, i); - o(i) = 1; - } - - const Vec h1 = o + k(0) * di.array().pow(2).matrix() + k(1) * di.array().pow(4).matrix() + k(2) * di.array().pow(6).matrix(); - const Vec ri = h1.transpose().cwiseInverse().asDiagonal() * di; - const double Sr04 = ri.array().pow(4).sum(); - const double Sr06 = ri.array().pow(6).sum(); - const double Sr08 = ri.array().pow(8).sum(); - const double Sr10 = ri.array().pow(10).sum(); - const double Sr12 = ri.array().pow(12).sum(); - const double Sr14 = ri.array().pow(14).sum(); - const double Sr3d = (di.asDiagonal() * ri.array().pow(3).matrix()).sum(); - const double Sr5d = (di.asDiagonal() * ri.array().pow(5).matrix()).sum(); - const double Sr7d = (di.asDiagonal() * ri.array().pow(7).matrix()).sum(); - - Mat A = Mat(3, 3); - A << Sr06, Sr08, Sr10, Sr08, Sr10, Sr12, Sr10, Sr12, Sr14; - - Vec b = Vec(3); - b << Sr3d - Sr04, Sr5d - Sr06, Sr7d - Sr08; - - return A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); + Vec k(3); + + // make k of length 3 if shorter + for (Vec::Index i = 0; i < 3; ++i) + { + k(i) = (i < _r.rows()) ? _r(i) : 0; + } + + Vec di(x2d.cols()); + Vec o(x2d.cols()); + + for (Vec::Index i = 0; i < x2d.cols(); ++i) + { + di(i) = x2d(0, i); + o(i) = 1; + } + + const Vec h1 = o + k(0) * di.array().pow(2).matrix() + k(1) * di.array().pow(4).matrix() + k(2) * di.array().pow(6).matrix(); + const Vec ri = h1.transpose().cwiseInverse().asDiagonal() * di; + const double Sr04 = ri.array().pow(4).sum(); + const double Sr06 = ri.array().pow(6).sum(); + const double Sr08 = ri.array().pow(8).sum(); + const double Sr10 = ri.array().pow(10).sum(); + const double Sr12 = ri.array().pow(12).sum(); + const double Sr14 = ri.array().pow(14).sum(); + const double Sr3d = (di.asDiagonal() * ri.array().pow(3).matrix()).sum(); + const double Sr5d = (di.asDiagonal() * ri.array().pow(5).matrix()).sum(); + const double Sr7d = (di.asDiagonal() * ri.array().pow(7).matrix()).sum(); + + Mat A = Mat(3, 3); + A << Sr06, Sr08, Sr10, Sr08, Sr10, Sr12, Sr10, Sr12, Sr14; + + Vec b = Vec(3); + b << Sr3d - Sr04, Sr5d - Sr06, Sr7d - Sr08; + + return A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); } bool computePosesRD(const Mat& featureVectors, const Mat& worldPoints, int numOfRadialCoeff, std::vector* solutions) { - // Eliminate all linear stuff - Mat A = Mat(5, 8); - for(Vec::Index i = 0; i < 5; ++i) - { - Mat X = Mat(1, 8); - X << -featureVectors(1, i) * worldPoints(0, i), - -featureVectors(1, i) * worldPoints(1, i), - -featureVectors(1, i) * worldPoints(2, i), - -featureVectors(1, i), - featureVectors(0, i) * worldPoints(0, i), - featureVectors(0, i) * worldPoints(1, i), - featureVectors(0, i) * worldPoints(2, i), - featureVectors(0, i); - - A.block(i, 0, 1, 8) = X; - } - - // 3D Nullspace - const Mat N = nullspace(A); - - // Construct the matrix C - Mat C = Mat(2, 6); - C << N.block(0, 0, 3, 1).transpose() * N.block(4, 0, 3, 1), - N.block(0, 0, 3, 1).transpose() * N.block(4, 1, 3, 1) + N.block(0, 1, 3, 1).transpose() * N.block(4, 0, 3, 1), - N.block(0, 0, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(4, 0, 3, 1), - N.block(0, 1, 3, 1).transpose() * N.block(4, 1, 3, 1), - N.block(0, 1, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(4, 1, 3, 1), - N.block(0, 2, 3, 1).transpose() * N.block(4, 2, 3, 1), - N.block(0, 0, 3, 1).transpose() * N.block(0, 0, 3, 1) - N.block(4, 0, 3, 1).transpose() * N.block(4, 0, 3, 1), - N.block(0, 0, 3, 1).transpose() * N.block(0, 1, 3, 1) + N.block(0, 1, 3, 1).transpose() * N.block(0, 0, 3, 1) - (N.block(4, 0, 3, 1).transpose() * N.block(4, 1, 3, 1) + N.block(4, 1, 3, 1).transpose() * N.block(4, 0, 3, 1)), - N.block(0, 0, 3, 1).transpose() * N.block(0, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(0, 0, 3, 1) - (N.block(4, 0, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(4, 2, 3, 1).transpose() * N.block(4, 0, 3, 1)), - N.block(0, 1, 3, 1).transpose() * N.block(0, 1, 3, 1) - N.block(4, 1, 3, 1).transpose() * N.block(4, 1, 3, 1), - N.block(0, 1, 3, 1).transpose() * N.block(0, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(0, 1, 3, 1) - (N.block(4, 1, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(4, 2, 3, 1).transpose() * N.block(4, 1, 3, 1)), - N.block(0, 2, 3, 1).transpose() * N.block(0, 2, 3, 1) - N.block(4, 2, 3, 1).transpose() * N.block(4, 2, 3, 1); - - // Normalize C to get reasonable numbers when computing d - const Vec &c1 = C.row(0); - const Vec &c2 = C.row(1); - Mat sC = Mat(2, 2); - sC << (6 / c1.norm()), 0, 0, (6 / c2.norm()); - C = sC * C; - - // Determinant coefficients - Mat d = Mat(5, 1); - d << C(0, 0) * C(0, 0) * C(1, 3) * C(1, 3) - C(0, 0) * C(0, 1) * C(1, 1) * C(1, 3) - 2 * C(0, 0) * C(0, 3) * C(1, 0) * C(1, 3) + C(0, 0) * C(0, 3) * C(1, 1) * C(1, 1) + C(0, 1) * C(0, 1) * C(1, 0) * C(1, 3) - C(0, 1) * C(0, 3) * C(1, 0) * C(1, 1) + C(0, 3) * C(0, 3) * C(1, 0) * C(1, 0), - -C(0, 0) * C(0, 1) * C(1, 3) * C(1, 4) + 2 * C(0, 0) * C(0, 2) * C(1, 3) * C(1, 3) + 2 * C(0, 0) * C(0, 3) * C(1, 1) * C(1, 4) - 2 * C(0, 0) * C(0, 3) * C(1, 2) * C(1, 3) - C(0, 0) * C(0, 4) * C(1, 1) * C(1, 3) + C(0, 1) * C(0, 1) * C(1, 2) * C(1, 3) - C(0, 1) * C(0, 2) * C(1, 1) * C(1, 3) - C(0, 1) * C(0, 3) * C(1, 0) * C(1, 4) - C(0, 1) * C(0, 3) * C(1, 1) * C(1, 2) + 2 * C(0, 1) * C(0, 4) * C(1, 0) * C(1, 3) - 2 * C(0, 2) * C(0, 3) * C(1, 0) * C(1, 3) + C(0, 2) * C(0, 3) * C(1, 1) * C(1, 1) + 2 * C(0, 3) * C(0, 3) * C(1, 0) * C(1, 2) - C(0, 3) * C(0, 4) * C(1, 0) * C(1, 1), - -2 * C(0, 0) * C(0, 3) * C(1, 3) * C(1, 5) + C(0, 0) * C(0, 3) * C(1, 4) * C(1, 4) - C(0, 0) * C(0, 4) * C(1, 3) * C(1, 4) + 2 * C(0, 0) * C(0, 5) * C(1, 3) * C(1, 3) + C(0, 1) * C(0, 1) * C(1, 3) * C(1, 5) - C(0, 1) * C(0, 2) * C(1, 3) * C(1, 4) - C(0, 1) * C(0, 3) * C(1, 1) * C(1, 5) - C(0, 1) * C(0, 3) * C(1, 2) * C(1, 4) + 2 * C(0, 1) * C(0, 4) * C(1, 2) * C(1, 3) - C(0, 1) * C(0, 5) * C(1, 1) * C(1, 3) + C(0, 2) * C(0, 2) * C(1, 3) * C(1, 3) + 2 * C(0, 2) * C(0, 3) * C(1, 1) * C(1, 4) - 2 * C(0, 2) * C(0, 3) * C(1, 2) * C(1, 3) - C(0, 2) * C(0, 4) * C(1, 1) * C(1, 3) + 2 * C(0, 3) * C(0, 3) * C(1, 0) * C(1, 5) + C(0, 3) * C(0, 3) * C(1, 2) * C(1, 2) - C(0, 3) * C(0, 4) * C(1, 0) * C(1, 4) - C(0, 3) * C(0, 4) * C(1, 1) * C(1, 2) - 2 * C(0, 3) * C(0, 5) * C(1, 0) * C(1, 3) + C(0, 3) * C(0, 5) * C(1, 1) * C(1, 1) + C(0, 4) * C(0, 4) * C(1, 0) * C(1, 3), - -C(0, 1) * C(0, 3) * C(1, 4) * C(1, 5) + 2 * C(0, 1) * C(0, 4) * C(1, 3) * C(1, 5) - C(0, 1) * C(0, 5) * C(1, 3) * C(1, 4) - 2 * C(0, 2) * C(0, 3) * C(1, 3) * C(1, 5) + C(0, 2) * C(0, 3) * C(1, 4) * C(1, 4) - C(0, 2) * C(0, 4) * C(1, 3) * C(1, 4) + 2 * C(0, 2) * C(0, 5) * C(1, 3) * C(1, 3) + 2 * C(0, 3) * C(0, 3) * C(1, 2) * C(1, 5) - C(0, 3) * C(0, 4) * C(1, 1) * C(1, 5) - C(0, 3) * C(0, 4) * C(1, 2) * C(1, 4) + 2 * C(0, 3) * C(0, 5) * C(1, 1) * C(1, 4) - 2 * C(0, 3) * C(0, 5) * C(1, 2) * C(1, 3) + C(0, 4) * C(0, 4) * C(1, 2) * C(1, 3) - C(0, 4) * C(0, 5) * C(1, 1) * C(1, 3), - C(0, 3) * C(0, 3) * C(1, 5) * C(1, 5) - C(0, 3) * C(0, 4) * C(1, 4) * C(1, 5) - 2 * C(0, 3) * C(0, 5) * C(1, 3) * C(1, 5) + C(0, 3) * C(0, 5) * C(1, 4) * C(1, 4) + C(0, 4) * C(0, 4) * C(1, 3) * C(1, 5) - C(0, 4) * C(0, 5) * C(1, 3) * C(1, 4) + C(0, 5) * C(0, 5) * C(1, 3) * C(1, 3); - - // Companion matrix - d = d * (1.0 / d(0, 0)); - Mat M = Mat(4, 4); - M << 0, 0, 0, -d(4, 0), - 1, 0, 0, -d(3, 0), - 0, 1, 0, -d(2, 0), - 0, 0, 1, -d(1, 0); - - // solve it - Eigen::EigenSolver es(M); - Mat g1_im = es.eigenvalues().imag(); - Mat g1_re = es.eigenvalues().real(); - - // separate real solutions - const double eps = 2.2204e-16; - std::vector vec_g1_real; - for(Mat::Index i = 0; i < 4; ++i) - { - if(std::abs(g1_im(i, 0)) < eps) - vec_g1_real.push_back(g1_re(i, 0)); - } - if(vec_g1_real.empty()) - return false; - Vec g1 = Map(vec_g1_real.data(), vec_g1_real.size()); - - //get g2 : Sg1 * = 0 - // SG1 : = << C14 | C12*g1 + C15 | C11*g1 ^ 2 + C13*g1 + C16 | 0 >, - // < 0 | C14 | C12*g1 + C15 | C11*g1 ^ 2 + C13*g1 + C16 >, - // , - // < 0 | C24 | C22*g1 + C25 | C21*g1 ^ 2 + C23*g1 + C26 >> ; - Mat g2 = Mat(g1.rows(), g1.cols()); - for(Mat::Index i = 0; i < g1.rows(); ++i) - { - Mat M2G = Mat(4, 4); - M2G << C(0, 3), - C(0, 1) * g1(i) + C(0, 4), - C(0, 0) * g1(i) * g1(i) + C(0, 2) * g1(i) + C(0, 5), - 0, 0, - C(0, 3), - C(0, 1) * g1(i) + C(0, 4), - C(0, 0) * g1(i) * g1(i) + C(0, 2) * g1(i) + C(0, 5), - C(1, 3), - C(1, 1) * g1(i) + C(1, 4), - C(1, 0) * g1(i) * g1(i) + C(1, 2) * g1(i) + C(1, 5), - 0, 0, - C(1, 3), - C(1, 1) * g1(i) + C(1, 4), - C(1, 0) * g1(i) * g1(i) + C(1, 2) * g1(i) + C(1, 5); - - Mat NM2G = nullspace(M2G); - - g2(i) = NM2G(2, NM2G.cols() - 1) / NM2G(3, NM2G.cols() - 1); - } - - // Get P for all pairs of solutions[g1, g2] - for(int i = 0; i < g1.rows(); ++i) - { - // The first two rows of P(P : = zip((g1, g2)->N[1] * g1 + N[2] * g2 + N[3], G1, G2) : ) - const Vec4 p1 = N.block(0, 0, 4, 1) * g1(i) + N.block(0, 1, 4, 1) * g2(i) + N.block(0, 2, 4, 1); - const Vec4 p2 = N.block(4, 0, 4, 1) * g1(i) + N.block(4, 1, 4, 1) * g2(i) + N.block(4, 2, 4, 1); - - Mat34 P; - P.row(0) = ((1 / p1.block(0, 0, 3, 1).norm()) * p1).transpose(); - P.row(1) = ((1 / p2.block(0, 0, 3, 1).norm()) * p2).transpose(); - P.row(2) << P(0, 1) * P(1, 2) - P(0, 2) * P(1, 1), -P(0, 0) * P(1, 2) + P(0, 2) * P(1, 0), P(0, 0) * P(1, 1) - P(0, 1) * P(1, 0), 0; - - // Form equations on k p34 and t = 1 / f: B = 0 - Mat B = Mat(5, 6); - for(Mat::Index j = 0; j < 5; ++j) - { // for all point pairs[u, X] - const double r2 = featureVectors(0, j) * featureVectors(0, j) + featureVectors(1, j) * featureVectors(1, j); // temporary vals - const double ee11 = (P.block(0, 0, 1, 3) * worldPoints.col(j))(0, 0) + P(0, 3); - const double ee21 = (P.block(1, 0, 1, 3) * worldPoints.col(j))(0, 0) + P(1, 3); - const double ee31 = (P.block(2, 0, 1, 3) * worldPoints.col(j))(0, 0); - const double ee32 = featureVectors(1, j) * ee31; - const double ee33 = -featureVectors(0, j) * ee31; - - if(abs(featureVectors(1, j)) > abs(featureVectors(0, j))) - { - B.row(j) << featureVectors(1, j), ee32, -ee21*r2, -ee21 * r2*r2, -ee21 * r2 * r2*r2, -ee21; - } - else - { - B.row(j) << -featureVectors(0, j), ee33, ee11*r2, ee11 * r2*r2, ee11 * r2 * r2*r2, ee11; - } + // Eliminate all linear stuff + Mat A = Mat(5, 8); + for (Vec::Index i = 0; i < 5; ++i) + { + Mat X = Mat(1, 8); + X << -featureVectors(1, i) * worldPoints(0, i), -featureVectors(1, i) * worldPoints(1, i), -featureVectors(1, i) * worldPoints(2, i), + -featureVectors(1, i), featureVectors(0, i) * worldPoints(0, i), featureVectors(0, i) * worldPoints(1, i), + featureVectors(0, i) * worldPoints(2, i), featureVectors(0, i); + + A.block(i, 0, 1, 8) = X; } - // select columns - Mat U; - switch(numOfRadialCoeff) + // 3D Nullspace + const Mat N = nullspace(A); + + // Construct the matrix C + Mat C = Mat(2, 6); + C << N.block(0, 0, 3, 1).transpose() * N.block(4, 0, 3, 1), + N.block(0, 0, 3, 1).transpose() * N.block(4, 1, 3, 1) + N.block(0, 1, 3, 1).transpose() * N.block(4, 0, 3, 1), + N.block(0, 0, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(4, 0, 3, 1), + N.block(0, 1, 3, 1).transpose() * N.block(4, 1, 3, 1), + N.block(0, 1, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(4, 1, 3, 1), + N.block(0, 2, 3, 1).transpose() * N.block(4, 2, 3, 1), + N.block(0, 0, 3, 1).transpose() * N.block(0, 0, 3, 1) - N.block(4, 0, 3, 1).transpose() * N.block(4, 0, 3, 1), + N.block(0, 0, 3, 1).transpose() * N.block(0, 1, 3, 1) + N.block(0, 1, 3, 1).transpose() * N.block(0, 0, 3, 1) - + (N.block(4, 0, 3, 1).transpose() * N.block(4, 1, 3, 1) + N.block(4, 1, 3, 1).transpose() * N.block(4, 0, 3, 1)), + N.block(0, 0, 3, 1).transpose() * N.block(0, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(0, 0, 3, 1) - + (N.block(4, 0, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(4, 2, 3, 1).transpose() * N.block(4, 0, 3, 1)), + N.block(0, 1, 3, 1).transpose() * N.block(0, 1, 3, 1) - N.block(4, 1, 3, 1).transpose() * N.block(4, 1, 3, 1), + N.block(0, 1, 3, 1).transpose() * N.block(0, 2, 3, 1) + N.block(0, 2, 3, 1).transpose() * N.block(0, 1, 3, 1) - + (N.block(4, 1, 3, 1).transpose() * N.block(4, 2, 3, 1) + N.block(4, 2, 3, 1).transpose() * N.block(4, 1, 3, 1)), + N.block(0, 2, 3, 1).transpose() * N.block(0, 2, 3, 1) - N.block(4, 2, 3, 1).transpose() * N.block(4, 2, 3, 1); + + // Normalize C to get reasonable numbers when computing d + const Vec& c1 = C.row(0); + const Vec& c2 = C.row(1); + Mat sC = Mat(2, 2); + sC << (6 / c1.norm()), 0, 0, (6 / c2.norm()); + C = sC * C; + + // Determinant coefficients + Mat d = Mat(5, 1); + d << C(0, 0) * C(0, 0) * C(1, 3) * C(1, 3) - C(0, 0) * C(0, 1) * C(1, 1) * C(1, 3) - 2 * C(0, 0) * C(0, 3) * C(1, 0) * C(1, 3) + + C(0, 0) * C(0, 3) * C(1, 1) * C(1, 1) + C(0, 1) * C(0, 1) * C(1, 0) * C(1, 3) - C(0, 1) * C(0, 3) * C(1, 0) * C(1, 1) + + C(0, 3) * C(0, 3) * C(1, 0) * C(1, 0), + -C(0, 0) * C(0, 1) * C(1, 3) * C(1, 4) + 2 * C(0, 0) * C(0, 2) * C(1, 3) * C(1, 3) + 2 * C(0, 0) * C(0, 3) * C(1, 1) * C(1, 4) - + 2 * C(0, 0) * C(0, 3) * C(1, 2) * C(1, 3) - C(0, 0) * C(0, 4) * C(1, 1) * C(1, 3) + C(0, 1) * C(0, 1) * C(1, 2) * C(1, 3) - + C(0, 1) * C(0, 2) * C(1, 1) * C(1, 3) - C(0, 1) * C(0, 3) * C(1, 0) * C(1, 4) - C(0, 1) * C(0, 3) * C(1, 1) * C(1, 2) + + 2 * C(0, 1) * C(0, 4) * C(1, 0) * C(1, 3) - 2 * C(0, 2) * C(0, 3) * C(1, 0) * C(1, 3) + C(0, 2) * C(0, 3) * C(1, 1) * C(1, 1) + + 2 * C(0, 3) * C(0, 3) * C(1, 0) * C(1, 2) - C(0, 3) * C(0, 4) * C(1, 0) * C(1, 1), + -2 * C(0, 0) * C(0, 3) * C(1, 3) * C(1, 5) + C(0, 0) * C(0, 3) * C(1, 4) * C(1, 4) - C(0, 0) * C(0, 4) * C(1, 3) * C(1, 4) + + 2 * C(0, 0) * C(0, 5) * C(1, 3) * C(1, 3) + C(0, 1) * C(0, 1) * C(1, 3) * C(1, 5) - C(0, 1) * C(0, 2) * C(1, 3) * C(1, 4) - + C(0, 1) * C(0, 3) * C(1, 1) * C(1, 5) - C(0, 1) * C(0, 3) * C(1, 2) * C(1, 4) + 2 * C(0, 1) * C(0, 4) * C(1, 2) * C(1, 3) - + C(0, 1) * C(0, 5) * C(1, 1) * C(1, 3) + C(0, 2) * C(0, 2) * C(1, 3) * C(1, 3) + 2 * C(0, 2) * C(0, 3) * C(1, 1) * C(1, 4) - + 2 * C(0, 2) * C(0, 3) * C(1, 2) * C(1, 3) - C(0, 2) * C(0, 4) * C(1, 1) * C(1, 3) + 2 * C(0, 3) * C(0, 3) * C(1, 0) * C(1, 5) + + C(0, 3) * C(0, 3) * C(1, 2) * C(1, 2) - C(0, 3) * C(0, 4) * C(1, 0) * C(1, 4) - C(0, 3) * C(0, 4) * C(1, 1) * C(1, 2) - + 2 * C(0, 3) * C(0, 5) * C(1, 0) * C(1, 3) + C(0, 3) * C(0, 5) * C(1, 1) * C(1, 1) + C(0, 4) * C(0, 4) * C(1, 0) * C(1, 3), + -C(0, 1) * C(0, 3) * C(1, 4) * C(1, 5) + 2 * C(0, 1) * C(0, 4) * C(1, 3) * C(1, 5) - C(0, 1) * C(0, 5) * C(1, 3) * C(1, 4) - + 2 * C(0, 2) * C(0, 3) * C(1, 3) * C(1, 5) + C(0, 2) * C(0, 3) * C(1, 4) * C(1, 4) - C(0, 2) * C(0, 4) * C(1, 3) * C(1, 4) + + 2 * C(0, 2) * C(0, 5) * C(1, 3) * C(1, 3) + 2 * C(0, 3) * C(0, 3) * C(1, 2) * C(1, 5) - C(0, 3) * C(0, 4) * C(1, 1) * C(1, 5) - + C(0, 3) * C(0, 4) * C(1, 2) * C(1, 4) + 2 * C(0, 3) * C(0, 5) * C(1, 1) * C(1, 4) - 2 * C(0, 3) * C(0, 5) * C(1, 2) * C(1, 3) + + C(0, 4) * C(0, 4) * C(1, 2) * C(1, 3) - C(0, 4) * C(0, 5) * C(1, 1) * C(1, 3), + C(0, 3) * C(0, 3) * C(1, 5) * C(1, 5) - C(0, 3) * C(0, 4) * C(1, 4) * C(1, 5) - 2 * C(0, 3) * C(0, 5) * C(1, 3) * C(1, 5) + + C(0, 3) * C(0, 5) * C(1, 4) * C(1, 4) + C(0, 4) * C(0, 4) * C(1, 3) * C(1, 5) - C(0, 4) * C(0, 5) * C(1, 3) * C(1, 4) + + C(0, 5) * C(0, 5) * C(1, 3) * C(1, 3); + + // Companion matrix + d = d * (1.0 / d(0, 0)); + Mat M = Mat(4, 4); + M << 0, 0, 0, -d(4, 0), 1, 0, 0, -d(3, 0), 0, 1, 0, -d(2, 0), 0, 0, 1, -d(1, 0); + + // solve it + Eigen::EigenSolver es(M); + Mat g1_im = es.eigenvalues().imag(); + Mat g1_re = es.eigenvalues().real(); + + // separate real solutions + const double eps = 2.2204e-16; + std::vector vec_g1_real; + for (Mat::Index i = 0; i < 4; ++i) { - case 1: - U = Mat(6, 4); - U << 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 1; - break; - - case 2: - U = Mat(6, 5); - U << 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1; - break; - - case 3: - U = Mat::Identity(6, 6); - break; - - default: - std::cerr << "\nError: the number of radial parameters must be between 1 to 3!\n"; - return false; + if (std::abs(g1_im(i, 0)) < eps) + vec_g1_real.push_back(g1_re(i, 0)); } - B = B * U; + if (vec_g1_real.empty()) + return false; + Vec g1 = Map(vec_g1_real.data(), vec_g1_real.size()); + + // get g2 : Sg1 * = 0 + // SG1 : = << C14 | C12*g1 + C15 | C11*g1 ^ 2 + C13*g1 + C16 | 0 >, + // < 0 | C14 | C12*g1 + C15 | C11*g1 ^ 2 + C13*g1 + C16 >, + // , + // < 0 | C24 | C22*g1 + C25 | C21*g1 ^ 2 + C23*g1 + C26 >> ; + Mat g2 = Mat(g1.rows(), g1.cols()); + for (Mat::Index i = 0; i < g1.rows(); ++i) + { + Mat M2G = Mat(4, 4); + M2G << C(0, 3), C(0, 1) * g1(i) + C(0, 4), C(0, 0) * g1(i) * g1(i) + C(0, 2) * g1(i) + C(0, 5), 0, 0, C(0, 3), C(0, 1) * g1(i) + C(0, 4), + C(0, 0) * g1(i) * g1(i) + C(0, 2) * g1(i) + C(0, 5), C(1, 3), C(1, 1) * g1(i) + C(1, 4), + C(1, 0) * g1(i) * g1(i) + C(1, 2) * g1(i) + C(1, 5), 0, 0, C(1, 3), C(1, 1) * g1(i) + C(1, 4), + C(1, 0) * g1(i) * g1(i) + C(1, 2) * g1(i) + C(1, 5); - // find the right 1D null space - const Mat NBfull = nullspace(B); - const Mat NB = NBfull.col(NBfull.cols() - 1); - const Mat V = NB.col(NB.cols() - 1); - Mat tk = V * (1 / V(V.rows() - 1, V.cols() - 1)); + Mat NM2G = nullspace(M2G); - // make f positive - if(tk(1, 0) < 0) - { - tk.block(0, 0, 2, 1) = -tk.block(0, 0, 2, 1); - P.block(0, 0, 2, 4) = -P.block(0, 0, 2, 4); + g2(i) = NM2G(2, NM2G.cols() - 1) / NM2G(3, NM2G.cols() - 1); } - P(2, 3) = tk(0, 0) / tk(1, 0); - Mat K = Mat(3, 3); - K << 1.0 / tk(1, 0), 0, 0, 0, 1.0 / tk(1, 0), 0, 0, 0, 1; - Mat R = P.block(0, 0, 3, 3); - - //Mat C = -R.transpose() * P.block(0, 3, 3, 1); - Vec r = Vec(numOfRadialCoeff); - r << tk.block(tk.rows() - numOfRadialCoeff - 1, 0, numOfRadialCoeff, 1); - - // In[1] we have - // [u - u0][f 0 0] - // [v - v0] = [0 f 0][R | -R*C][X] - // [1 + r*((u - u0) ^ 2 + (v - v0) ^ 2)][0 0 1][1] - // but we want - // [(u - u0) / f] - // [(v - v0) / f] = [R | -R*C][X] - // [1 + (r*f ^ 2)*((u - u0) ^ 2 + (v - v0) ^ 2) / f ^ 2][1] - - // instead not deal with f dependent r - for(Mat::Index j = 0; j < numOfRadialCoeff; ++j) // f^2, f^4, f^6 - r(j) *= pow(K(0, 0), 2 * (j + 1)); - - // output - const Vec3 t = P.block(0, 3, 3, 1); - const double f = (1.0 / tk(1, 0)); - solutions->emplace_back(R, t, r, f); - } - return true; + // Get P for all pairs of solutions[g1, g2] + for (int i = 0; i < g1.rows(); ++i) + { + // The first two rows of P(P : = zip((g1, g2)->N[1] * g1 + N[2] * g2 + N[3], G1, G2) : ) + const Vec4 p1 = N.block(0, 0, 4, 1) * g1(i) + N.block(0, 1, 4, 1) * g2(i) + N.block(0, 2, 4, 1); + const Vec4 p2 = N.block(4, 0, 4, 1) * g1(i) + N.block(4, 1, 4, 1) * g2(i) + N.block(4, 2, 4, 1); + + Mat34 P; + P.row(0) = ((1 / p1.block(0, 0, 3, 1).norm()) * p1).transpose(); + P.row(1) = ((1 / p2.block(0, 0, 3, 1).norm()) * p2).transpose(); + P.row(2) << P(0, 1) * P(1, 2) - P(0, 2) * P(1, 1), -P(0, 0) * P(1, 2) + P(0, 2) * P(1, 0), P(0, 0) * P(1, 1) - P(0, 1) * P(1, 0), 0; + + // Form equations on k p34 and t = 1 / f: B = 0 + Mat B = Mat(5, 6); + for (Mat::Index j = 0; j < 5; ++j) + { // for all point pairs[u, X] + const double r2 = featureVectors(0, j) * featureVectors(0, j) + featureVectors(1, j) * featureVectors(1, j); // temporary vals + const double ee11 = (P.block(0, 0, 1, 3) * worldPoints.col(j))(0, 0) + P(0, 3); + const double ee21 = (P.block(1, 0, 1, 3) * worldPoints.col(j))(0, 0) + P(1, 3); + const double ee31 = (P.block(2, 0, 1, 3) * worldPoints.col(j))(0, 0); + const double ee32 = featureVectors(1, j) * ee31; + const double ee33 = -featureVectors(0, j) * ee31; + + if (abs(featureVectors(1, j)) > abs(featureVectors(0, j))) + { + B.row(j) << featureVectors(1, j), ee32, -ee21 * r2, -ee21 * r2 * r2, -ee21 * r2 * r2 * r2, -ee21; + } + else + { + B.row(j) << -featureVectors(0, j), ee33, ee11 * r2, ee11 * r2 * r2, ee11 * r2 * r2 * r2, ee11; + } + } + + // select columns + Mat U; + switch (numOfRadialCoeff) + { + case 1: + U = Mat(6, 4); + U << 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1; + break; + + case 2: + U = Mat(6, 5); + U << 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1; + break; + + case 3: + U = Mat::Identity(6, 6); + break; + + default: + std::cerr << "\nError: the number of radial parameters must be between 1 to 3!\n"; + return false; + } + B = B * U; + + // find the right 1D null space + const Mat NBfull = nullspace(B); + const Mat NB = NBfull.col(NBfull.cols() - 1); + const Mat V = NB.col(NB.cols() - 1); + Mat tk = V * (1 / V(V.rows() - 1, V.cols() - 1)); + + // make f positive + if (tk(1, 0) < 0) + { + tk.block(0, 0, 2, 1) = -tk.block(0, 0, 2, 1); + P.block(0, 0, 2, 4) = -P.block(0, 0, 2, 4); + } + + P(2, 3) = tk(0, 0) / tk(1, 0); + Mat K = Mat(3, 3); + K << 1.0 / tk(1, 0), 0, 0, 0, 1.0 / tk(1, 0), 0, 0, 0, 1; + Mat R = P.block(0, 0, 3, 3); + + // Mat C = -R.transpose() * P.block(0, 3, 3, 1); + Vec r = Vec(numOfRadialCoeff); + r << tk.block(tk.rows() - numOfRadialCoeff - 1, 0, numOfRadialCoeff, 1); + + // In[1] we have + // [u - u0][f 0 0] + // [v - v0] = [0 f 0][R | -R*C][X] + // [1 + r*((u - u0) ^ 2 + (v - v0) ^ 2)][0 0 1][1] + // but we want + // [(u - u0) / f] + // [(v - v0) / f] = [R | -R*C][X] + // [1 + (r*f ^ 2)*((u - u0) ^ 2 + (v - v0) ^ 2) / f ^ 2][1] + + // instead not deal with f dependent r + for (Mat::Index j = 0; j < numOfRadialCoeff; ++j) // f^2, f^4, f^6 + r(j) *= pow(K(0, 0), 2 * (j + 1)); + + // output + const Vec3 t = P.block(0, 3, 3, 1); + const double f = (1.0 / tk(1, 0)); + solutions->emplace_back(R, t, r, f); + } + return true; } bool computePosesRP(const Mat& featureVectors, const Mat& worldPoints, int numOfRadialCoeff, std::vector* solutions) { - if(computePosesRD(featureVectors, worldPoints, numOfRadialCoeff, solutions)) - { - const Mat p2dRadius = featureVectors.colwise().norm(); - for(std::size_t i = 0; i < solutions->size(); ++i) + if (computePosesRD(featureVectors, worldPoints, numOfRadialCoeff, solutions)) { - P5PfrModel &m = solutions->at(i); - m._r = m.divisionToPolynomialModelDistortion((1 / m._f) * p2dRadius /*,p2dRadius.maxCoeff(),*/); + const Mat p2dRadius = featureVectors.colwise().norm(); + for (std::size_t i = 0; i < solutions->size(); ++i) + { + P5PfrModel& m = solutions->at(i); + m._r = m.divisionToPolynomialModelDistortion((1 / m._f) * p2dRadius /*,p2dRadius.maxCoeff(),*/); + } + return true; } - return true; - } - return false; + return false; } double reprojectionErrorRD(const P5PfrModel& model, const Vec2& p2d, const Vec3& p3d) { - if(model._r.rows() > 1) - { - ALICEVISION_CERR("Projection function is not implemented for the radial division undistortion model for more than one parameter." << std::endl); - throw std::invalid_argument("Projection function is not implemented for the radial division undistortion model for more than one parameter."); - } - - Vec3 v = model._R * p3d + model._t; // from delta to epsilon - v *= 1.0 / v(2); // normalize to have v(3, :) = 1 - - // undistorted squared radius - const double ru2 = v(0) * v(0) + v(1) * v(1); - - // works for fish-eye, i.e. when distort image gets smaller on the image plane - const double h1 = sqrt(-4 * model._r(0) * ru2 + 1); - const double h2 = 0.5 * ((-2 * model._r(0) * ru2 + 1) - h1) * (1 / (model._r(0) * model._r(0))); - const double rd = sqrt(h2 * (1 / ru2)); - - // distort in epsilon - const double h3 = rd / sqrt(ru2); - Vec2 u; - u << v(0) * h3, v(1) * h3; - - // to alpha - u = model._f * u; - return (p2d - u).norm(); + if (model._r.rows() > 1) + { + ALICEVISION_CERR("Projection function is not implemented for the radial division undistortion model for more than one parameter." + << std::endl); + throw std::invalid_argument("Projection function is not implemented for the radial division undistortion model for more than one parameter."); + } + + Vec3 v = model._R * p3d + model._t; // from delta to epsilon + v *= 1.0 / v(2); // normalize to have v(3, :) = 1 + + // undistorted squared radius + const double ru2 = v(0) * v(0) + v(1) * v(1); + + // works for fish-eye, i.e. when distort image gets smaller on the image plane + const double h1 = sqrt(-4 * model._r(0) * ru2 + 1); + const double h2 = 0.5 * ((-2 * model._r(0) * ru2 + 1) - h1) * (1 / (model._r(0) * model._r(0))); + const double rd = sqrt(h2 * (1 / ru2)); + + // distort in epsilon + const double h3 = rd / sqrt(ru2); + Vec2 u; + u << v(0) * h3, v(1) * h3; + + // to alpha + u = model._f * u; + return (p2d - u).norm(); } double reprojectionErrorRP(const P5PfrModel& model, const Vec2& p2d, const Vec3& p3d) { - Vec3 v = model._R * p3d + model._t; // from delta to epsilon - v *= 1.0 / v(2); // normalize to have v(3, :) = 1 + Vec3 v = model._R * p3d + model._t; // from delta to epsilon + v *= 1.0 / v(2); // normalize to have v(3, :) = 1 - double t = 1; // the final radius parameter - const double r = std::hypot(v(0), v(1)); - for(Mat::Index i = 0; i < model._r.rows(); ++i) - t += model._r(i) * pow(r, 2 * (i + 1)); + double t = 1; // the final radius parameter + const double r = std::hypot(v(0), v(1)); + for (Mat::Index i = 0; i < model._r.rows(); ++i) + t += model._r(i) * pow(r, 2 * (i + 1)); - Vec2 u; - u << v(0) * t, v(1) * t; + Vec2 u; + u << v(0) * t, v(1) * t; - // to alpha - u = model._f * u; - return (p2d - u).norm(); + // to alpha + u = model._f * u; + return (p2d - u).norm(); } -} // namespace resection -} // namespace multiview -} // namespace aliceVision +} // namespace resection +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/resection/P5PfrSolver.hpp b/src/aliceVision/multiview/resection/P5PfrSolver.hpp index 99f7a188db..5c968e04e8 100644 --- a/src/aliceVision/multiview/resection/P5PfrSolver.hpp +++ b/src/aliceVision/multiview/resection/P5PfrSolver.hpp @@ -19,29 +19,29 @@ namespace resection { */ struct P5PfrModel { - P5PfrModel(const Mat& R, const Vec3& t, const Vec& r, double f) - : _R(R) - , _t(t) - , _r(r) - , _f(f) - {} - - /** - * @brief Inversion of the radial division undistortion to Brown polynomial distortion model conversion - * @param[in] x2d Points on which is the difference minimized, dmax//max(C.K([1 5]))*[0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.95 1] implicit - * @param[in] maxRadius Maximal distorted radius, 1 implicit - * @return camera description with polynomial radial distoriton parameters 'KRCp' - */ - Mat divisionToPolynomialModelDistortion(const Mat& x2d /*,double maxRadius*/) const; - - /// rotation matrix - Mat _R; - /// translation vector - Vec3 _t; - /// radial division undistortion parameters - Vec _r; - /// focal length - double _f; + P5PfrModel(const Mat& R, const Vec3& t, const Vec& r, double f) + : _R(R), + _t(t), + _r(r), + _f(f) + {} + + /** + * @brief Inversion of the radial division undistortion to Brown polynomial distortion model conversion + * @param[in] x2d Points on which is the difference minimized, dmax//max(C.K([1 5]))*[0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.95 1] implicit + * @param[in] maxRadius Maximal distorted radius, 1 implicit + * @return camera description with polynomial radial distoriton parameters 'KRCp' + */ + Mat divisionToPolynomialModelDistortion(const Mat& x2d /*,double maxRadius*/) const; + + /// rotation matrix + Mat _R; + /// translation vector + Vec3 _t; + /// radial division undistortion parameters + Vec _r; + /// focal length + double _f; }; /** @@ -78,23 +78,19 @@ double reprojectionErrorRD(const P5PfrModel& model, const Vec2& p2d, const Vec3& * @param[in] p3d corresponding 3D world point * @return reprojection error for Brown polynomial distortion model */ -double reprojectionErrorRP(const P5PfrModel& model, const Vec2 &p2d, const Vec3& p3d); +double reprojectionErrorRP(const P5PfrModel& model, const Vec2& p2d, const Vec3& p3d); struct P5PfrError : public ISolverErrorResection { - /** - * @brief Compute the residual of the projection distance(p2d, project(P,p3d)) - * @param[in] model solution - * @param[in] p2d feature vector - * @param[in] p3d corresponding 3D world point - */ - inline double error(const P5PfrModel& model, const Vec2& p2d, const Vec3& p3d) const override - { - return reprojectionErrorRD(model, p2d, p3d); - } + /** + * @brief Compute the residual of the projection distance(p2d, project(P,p3d)) + * @param[in] model solution + * @param[in] p2d feature vector + * @param[in] p3d corresponding 3D world point + */ + inline double error(const P5PfrModel& model, const Vec2& p2d, const Vec3& p3d) const override { return reprojectionErrorRD(model, p2d, p3d); } }; - /** * @brief Compute the absolute pose, focal length and radial distortion of a camera using three 3D-to-2D correspondences * @author Tomas Pajdla, adapted to aliceVision by Michal Polic @@ -105,65 +101,58 @@ struct P5PfrError : public ISolverErrorResection template class P5PfrSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 5; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 10; - } - - /** - * @brief Solve the problem of camera pose. - * - * @param[in] x2d featureVectors 2 x 5 matrix with feature vectors with principal point at [0; 0] (each column is a vector) - * @param[in] x3d worldPoints 3 x 5 matrix with corresponding 3D world points (each column is a point) - * @param[in] numOfRadialCoeff number of radial distortion parameters to be computed [min 1, max 3] - * @param[out] models M x n vector that will contain the each solution in structure M - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override - { - assert(2 == x2d.rows()); - assert(3 == x3d.rows()); - assert(5 == x3d.cols()); - assert(5 == x2d.cols()); - assert(numOfRadialCoeff_ >= 1 && numOfRadialCoeff_ <= 3 && "P5PfrSolver error: the number of radial parameters must be between 1 to 3 !"); - - // the radial distortion is represented by: the radial division undistortion - if(!computePosesRD(x2d, x3d, numOfRadialCoeff_, &models)) - models.clear(); - - // the radial distortion is represented by: Brown polynomial distortion model - //if(!P5PfrSolver::computePosesRP(x2d, x3d, numR, &models)) - // models.clear(); - } - - /** - * @brief Solve the problem of camera pose.. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override - { - throw std::logic_error("P5PfrSolver does not support problem solving with weights."); - } + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 5; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 10; } + + /** + * @brief Solve the problem of camera pose. + * + * @param[in] x2d featureVectors 2 x 5 matrix with feature vectors with principal point at [0; 0] (each column is a vector) + * @param[in] x3d worldPoints 3 x 5 matrix with corresponding 3D world points (each column is a point) + * @param[in] numOfRadialCoeff number of radial distortion parameters to be computed [min 1, max 3] + * @param[out] models M x n vector that will contain the each solution in structure M + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override + { + assert(2 == x2d.rows()); + assert(3 == x3d.rows()); + assert(5 == x3d.cols()); + assert(5 == x2d.cols()); + assert(numOfRadialCoeff_ >= 1 && numOfRadialCoeff_ <= 3 && "P5PfrSolver error: the number of radial parameters must be between 1 to 3 !"); + + // the radial distortion is represented by: the radial division undistortion + if (!computePosesRD(x2d, x3d, numOfRadialCoeff_, &models)) + models.clear(); + + // the radial distortion is represented by: Brown polynomial distortion model + // if(!P5PfrSolver::computePosesRP(x2d, x3d, numR, &models)) + // models.clear(); + } + + /** + * @brief Solve the problem of camera pose.. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override + { + throw std::logic_error("P5PfrSolver does not support problem solving with weights."); + } }; -} // namespace resection -} // namespace multiview -} // namespace aliceVision +} // namespace resection +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/resection/ProjectionDistanceError.hpp b/src/aliceVision/multiview/resection/ProjectionDistanceError.hpp index 80d975e353..9e8211c779 100644 --- a/src/aliceVision/multiview/resection/ProjectionDistanceError.hpp +++ b/src/aliceVision/multiview/resection/ProjectionDistanceError.hpp @@ -11,7 +11,6 @@ #include #include - namespace aliceVision { namespace multiview { namespace resection { @@ -22,10 +21,10 @@ namespace resection { */ struct ProjectionDistanceError : public ISolverErrorResection { - inline double error(const robustEstimation::Mat34Model& P, const Vec2& p2d, const Vec3& p3d) const override - { - return (project(P.getMatrix(), p3d) - p2d).norm(); - } + inline double error(const robustEstimation::Mat34Model& P, const Vec2& p2d, const Vec3& p3d) const override + { + return (project(P.getMatrix(), p3d) - p2d).norm(); + } }; /** @@ -34,10 +33,10 @@ struct ProjectionDistanceError : public ISolverErrorResection { - inline double error(const robustEstimation::Mat34Model& P, const Vec2& p2d, const Vec3& p3d) const override - { - return (project(P.getMatrix(), p3d) - p2d).squaredNorm(); - } + inline double error(const robustEstimation::Mat34Model& P, const Vec2& p2d, const Vec3& p3d) const override + { + return (project(P.getMatrix(), p3d) - p2d).squaredNorm(); + } }; } // namespace resection diff --git a/src/aliceVision/multiview/resection/Resection6PSolver.cpp b/src/aliceVision/multiview/resection/Resection6PSolver.cpp index bfdd6a51c9..3207b0d432 100644 --- a/src/aliceVision/multiview/resection/Resection6PSolver.cpp +++ b/src/aliceVision/multiview/resection/Resection6PSolver.cpp @@ -16,14 +16,14 @@ namespace resection { void translate(const Mat3X& X, const Vec3& vecTranslation, Mat3X* XPoints) { XPoints->resize(X.rows(), X.cols()); - for(int i = 0; i < X.cols(); ++i) + for (int i = 0; i < X.cols(); ++i) XPoints->col(i) = X.col(i) + vecTranslation; } -template +template double nullspaceRatio(TMat* A, TVec* nullspace) { - if(A->rows() >= A->cols()) + if (A->rows() >= A->cols()) { Eigen::JacobiSVD svd(*A, Eigen::ComputeFullV); (*nullspace) = svd.matrixV().col(A->cols() - 1); @@ -43,11 +43,11 @@ double nullspaceRatio(TMat* A, TVec* nullspace) * @brief Setup the Direct Linear Transform. * Use template in order to support fixed or dynamic sized matrix. */ -template +template void buildActionMatrix(Matrix& A, const Mat& pt2D, const Mat& XPoints) { const size_t n = pt2D.cols(); - for(size_t i = 0; i < n; ++i) + for (size_t i = 0; i < n; ++i) { size_t row_index = i * 2; const Vec3& X = XPoints.col(i); @@ -72,12 +72,11 @@ void buildActionMatrix(Matrix& A, const Mat& pt2D, const Mat& XPoints) A(row_index, 11) = -1.0 * x(1); } // Normalize each row - for(size_t i = 0; i < static_cast(A.rows()); ++i) + for (size_t i = 0; i < static_cast(A.rows()); ++i) A.row(i).normalize(); } -void solveProblem(const Mat& x2d, const Mat& x3d, std::vector& models, bool bcheck, - const std::vector& weights) +void solveProblem(const Mat& x2d, const Mat& x3d, std::vector& models, bool bcheck, const std::vector& weights) { assert(2 == x2d.rows()); assert(3 == x3d.rows()); @@ -88,8 +87,7 @@ void solveProblem(const Mat& x2d, const Mat& x3d, std::vector; Mat12 A = Mat12::Zero(12, 12); buildActionMatrix(A, x2d, XPoints); - if(!weights.empty()) + if (!weights.empty()) { - for(Mat12::Index ptIdx = 0; ptIdx < numPts; ++ptIdx) + for (Mat12::Index ptIdx = 0; ptIdx < numPts; ++ptIdx) { A.row(ptIdx * 2) *= weights[ptIdx]; A.row(ptIdx * 2 + 1) *= weights[ptIdx]; @@ -120,9 +118,9 @@ void solveProblem(const Mat& x2d, const Mat& x3d, std::vector 1e-5) // assert that at least only one solution if found by SVD + if (ratio > 1e-5) // assert that at least only one solution if found by SVD { Mat34 P = Map(p.data(), 4, 3).transpose(); P = P * translationMatrix; @@ -146,12 +144,12 @@ void solveProblem(const Mat& x2d, const Mat& x3d, std::vector 0) ? 1 : 0; } - if(cpt == numPts) + if (cpt == numPts) { models.emplace_back(P); } @@ -172,12 +170,14 @@ void Resection6PSolver::solve(const Mat& x2d, const Mat& x3d, std::vector& models, +void Resection6PSolver::solve(const Mat& x2d, + const Mat& x3d, + std::vector& models, const std::vector& weights) const { solveProblem(x2d, x3d, models, true, weights); } -} // namespace resection -} // namespace multiview -} // namespace aliceVision +} // namespace resection +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/resection/Resection6PSolver.hpp b/src/aliceVision/multiview/resection/Resection6PSolver.hpp index 4a6dfeec14..e3eaa6391e 100644 --- a/src/aliceVision/multiview/resection/Resection6PSolver.hpp +++ b/src/aliceVision/multiview/resection/Resection6PSolver.hpp @@ -15,50 +15,43 @@ namespace resection { class Resection6PSolver : public robustEstimation::ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 6; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } - - /** - * @brief Six-point resection - * P Matrix estimation (Pose estimation) - * Compute a projection matrix using linear least squares. - * Rely on Linear Resection algorithm. - * Work from 6 to N points. - * - * @note First 3d point will be translated in order to have X0 = (0,0,0,1) - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A list of solutions. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; - - /** - * @brief Solve the problem. - * - * @param[in] x2d 2d points in the first image. One per column. - * @param[in] x3d Corresponding 3d points in the second image. One per column. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override; + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 6; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } + + /** + * @brief Six-point resection + * P Matrix estimation (Pose estimation) + * Compute a projection matrix using linear least squares. + * Rely on Linear Resection algorithm. + * Work from 6 to N points. + * + * @note First 3d point will be translated in order to have X0 = (0,0,0,1) + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A list of solutions. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models) const override; + + /** + * @brief Solve the problem. + * + * @param[in] x2d 2d points in the first image. One per column. + * @param[in] x3d Corresponding 3d points in the second image. One per column. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + void solve(const Mat& x2d, const Mat& x3d, std::vector& models, const std::vector& weights) const override; }; } // namespace resection diff --git a/src/aliceVision/multiview/resection/Resection6PSolver_test.cpp b/src/aliceVision/multiview/resection/Resection6PSolver_test.cpp index b70d3d1a8c..3ae206bd40 100644 --- a/src/aliceVision/multiview/resection/Resection6PSolver_test.cpp +++ b/src/aliceVision/multiview/resection/Resection6PSolver_test.cpp @@ -24,15 +24,13 @@ using namespace aliceVision::multiview; BOOST_AUTO_TEST_CASE(Resection6PSolver) { - const std::size_t nbViews{3}; const std::size_t nbPoints{30}; // Suppose a camera with Unit matrix as K - const NViewDataSet d = NRealisticCamerasRing( - nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); + const NViewDataSet d = NRealisticCamerasRing(nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Solve the problem and check that fitted value are good enough - for(std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) + for (std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) { const Mat pts2d = d._x[camIndex]; const Mat pts3d = d._X; @@ -50,7 +48,7 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver) Mat34 estProjMat = projMats.at(0).getMatrix().array() / projMats.at(0).getMatrix().norm(); EXPECT_MATRIX_NEAR(gtProjMat, estProjMat, 1e-8); - for(Mat::Index i = 0; i < pts2d.cols(); ++i) + for (Mat::Index i = 0; i < pts2d.cols(); ++i) { const auto error = (project(estProjMat, Vec3(pts3d.col(i))) - pts2d.col(i)).norm(); BOOST_CHECK_SMALL(error, 1e-8); @@ -60,17 +58,15 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver) BOOST_AUTO_TEST_CASE(Resection6PSolver_weights) { - const std::size_t nbViews{3}; const std::size_t nbPoints{30}; // Suppose a camera with Unit matrix as K - const NViewDataSet d = NRealisticCamerasRing( - nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); + const NViewDataSet d = NRealisticCamerasRing(nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); const std::vector weights(nbPoints, 1.); // Solve the problem and check that fitted value are good enough - for(std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) + for (std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) { const Mat pts2d = d._x[camIndex]; const Mat pts3d = d._X; @@ -88,7 +84,7 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights) Mat34 estProjMat = projMats.at(0).getMatrix().array() / projMats.at(0).getMatrix().norm(); EXPECT_MATRIX_NEAR(gtProjMat, estProjMat, 1e-8); - for(Mat::Index i = 0; i < pts2d.cols(); ++i) + for (Mat::Index i = 0; i < pts2d.cols(); ++i) { const auto error = (project(estProjMat, Vec3(pts3d.col(i))) - pts2d.col(i)).norm(); BOOST_CHECK_SMALL(error, 1e-8); @@ -100,9 +96,9 @@ using VectorOfPair = std::vector>; bool isIndexInVector(const VectorOfPair& vec, VectorOfPair::value_type::first_type val) { - for(const auto v : vec) + for (const auto v : vec) { - if(val == v.first || val == v.second) + if (val == v.first || val == v.second) { return true; } @@ -118,17 +114,16 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights_outliers) const std::size_t nbViews{3}; const std::size_t nbPoints{30}; // Suppose a camera with Unit matrix as K - const NViewDataSet d = NRealisticCamerasRing( - nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); + const NViewDataSet d = NRealisticCamerasRing(nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); std::vector weights(nbPoints, 1.); Mat pts3d = d._X; // generate some outliers, simply swap the position of some 3d points - const VectorOfPair idx2swap{ {3, 10}, {15, 23} }; + const VectorOfPair idx2swap{{3, 10}, {15, 23}}; - for(const auto idx : idx2swap) + for (const auto idx : idx2swap) { // swap the relevant columns pts3d.col(idx.first).swap(pts3d.col(idx.second)); @@ -138,7 +133,7 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights_outliers) } // Solve the problem and check that fitted value are good enough - for(std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) + for (std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) { const Mat pts2d = d._x[camIndex]; @@ -155,10 +150,10 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights_outliers) Mat34 estProjMat = projMats.at(0).getMatrix().array() / projMats.at(0).getMatrix().norm(); EXPECT_MATRIX_NEAR(gtProjMat, estProjMat, 1e-8); - for(Mat::Index i = 0; i < pts2d.cols(); ++i) + for (Mat::Index i = 0; i < pts2d.cols(); ++i) { const auto error = (project(estProjMat, Vec3(pts3d.col(i))) - pts2d.col(i)).norm(); - if(isIndexInVector(idx2swap, i)) + if (isIndexInVector(idx2swap, i)) { // if it is an outlier BOOST_CHECK_GT(error, 1e-3); @@ -186,8 +181,7 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights_only6) const std::size_t nbViews{3}; const std::size_t nbPoints{6}; // Suppose a camera with Unit matrix as K - const NViewDataSet d = NRealisticCamerasRing( - nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); + const NViewDataSet d = NRealisticCamerasRing(nbViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); std::vector weights(nbPoints, 1.); @@ -197,9 +191,8 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights_only6) weights[1] = .5; weights[2] = .5; - // Solve the problem and check that fitted value are good enough - for(std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) + for (std::size_t camIndex = 0; camIndex < nbViews; ++camIndex) { const Mat pts2d = d._x[camIndex]; @@ -216,7 +209,7 @@ BOOST_AUTO_TEST_CASE(Resection6PSolver_weights_only6) Mat34 estProjMat = projMats.at(0).getMatrix().array() / projMats.at(0).getMatrix().norm(); EXPECT_MATRIX_NEAR(gtProjMat, estProjMat, 1e-8); - for(Mat::Index i = 0; i < pts2d.cols(); ++i) + for (Mat::Index i = 0; i < pts2d.cols(); ++i) { const auto error = (project(estProjMat, Vec3(pts3d.col(i))) - pts2d.col(i)).norm(); // given that all the associations 2d-3d are "perfect", even the ones with a smaller diff --git a/src/aliceVision/multiview/resection/ResectionKernel.hpp b/src/aliceVision/multiview/resection/ResectionKernel.hpp index 0504eac4ed..5925105faa 100644 --- a/src/aliceVision/multiview/resection/ResectionKernel.hpp +++ b/src/aliceVision/multiview/resection/ResectionKernel.hpp @@ -18,7 +18,7 @@ namespace resection { /** * @brief Usable solver for the 6pt Resection estimation */ -typedef robustEstimation::PointFittingKernel Resection6PKernel; +typedef robustEstimation::PointFittingKernel Resection6PKernel; } // namespace resection } // namespace multiview diff --git a/src/aliceVision/multiview/resection/p4pfSolver_test.cpp b/src/aliceVision/multiview/resection/p4pfSolver_test.cpp index d47e5b014c..bf07654d99 100644 --- a/src/aliceVision/multiview/resection/p4pfSolver_test.cpp +++ b/src/aliceVision/multiview/resection/p4pfSolver_test.cpp @@ -10,113 +10,98 @@ #include #include - #include #include #include - using namespace aliceVision; using namespace aliceVision::multiview; bool isEqual(const resection::P4PfModel first, const resection::P4PfModel second) { - double eps = 1e-3; - return ((first._R - second._R).norm() < first._R.maxCoeff() * eps && - (first._t - second._t).norm() < first._t.maxCoeff() * eps && - abs(first._f - second._f) < first._f * eps); + double eps = 1e-3; + return ((first._R - second._R).norm() < first._R.maxCoeff() * eps && (first._t - second._t).norm() < first._t.maxCoeff() * eps && + abs(first._f - second._f) < first._f * eps); } BOOST_AUTO_TEST_CASE(Resection_P4Pf_AssignmentWithOneResult) { - // INPUT DATA - Mat pt2D_1 = Mat(2, 4); - pt2D_1 << -493.1500, 1051.9100, 176.9500, -1621.9800, - -878.4550, -984.7530, -381.4300, -543.3450; - Mat pt3D_1 = Mat(3, 4); - pt3D_1 << 2.7518, 2.2375, 1.1940, 2.5778, - 0.1336, -0.3709, 0.2048, -0.9147, - -0.5491, -2.0511, 1.1480, -1.6151; - - // EXPECTED RESULT - Mat R_1 = Mat(3, 3); - R_1 << -0.97189, 0.05884, -0.22797, -0.02068, -0.98586, -0.16631, -0.23454, -0.15692, 0.95936; - Vec3 t_1; - t_1 << 2.00322, -1.27420, 2.92685; - resection::P4PfModel sol_1(R_1, t_1, 887.17549); - - // PROCESS THE RESECTION P4Pf - std::vector models_1; - resection::P4PfSolver solver; - solver.solve(pt2D_1, pt3D_1, models_1); - - bool pass = true; - if(!(models_1.size() == 1 && isEqual(models_1.at(0), sol_1))) - pass = false; - BOOST_CHECK(pass); + // INPUT DATA + Mat pt2D_1 = Mat(2, 4); + pt2D_1 << -493.1500, 1051.9100, 176.9500, -1621.9800, -878.4550, -984.7530, -381.4300, -543.3450; + Mat pt3D_1 = Mat(3, 4); + pt3D_1 << 2.7518, 2.2375, 1.1940, 2.5778, 0.1336, -0.3709, 0.2048, -0.9147, -0.5491, -2.0511, 1.1480, -1.6151; + + // EXPECTED RESULT + Mat R_1 = Mat(3, 3); + R_1 << -0.97189, 0.05884, -0.22797, -0.02068, -0.98586, -0.16631, -0.23454, -0.15692, 0.95936; + Vec3 t_1; + t_1 << 2.00322, -1.27420, 2.92685; + resection::P4PfModel sol_1(R_1, t_1, 887.17549); + + // PROCESS THE RESECTION P4Pf + std::vector models_1; + resection::P4PfSolver solver; + solver.solve(pt2D_1, pt3D_1, models_1); + + bool pass = true; + if (!(models_1.size() == 1 && isEqual(models_1.at(0), sol_1))) + pass = false; + BOOST_CHECK(pass); } BOOST_AUTO_TEST_CASE(Resection_P4Pf_AssignmentWithMoreResults) { - // DATA - Mat pt2D_2 = Mat(2, 4); - pt2D_2 << 774.88000, -772.31000, -1661.63300, -1836.57300, - -534.74500, -554.09400, -585.53300, -430.03000; - Mat pt3D_2 = Mat(3, 4); - pt3D_2 << 2.01852, 1.00709, 0.74051, 0.61962, - 0.02133, 0.30770, 0.16656, 0.11249, - -1.68077, 0.81502, 1.21056, 1.22624; - - // RESULTS - Mat R_21 = Mat(3, 3); - R_21 << 0.74908, 0.58601, -0.30898, 0.65890, -0.61061, 0.43933, 0.06879, -0.53268, -0.84352; - Mat R_22 = Mat(3, 3); - R_22 << 0.06352, -0.56461, -0.82291, -0.97260, 0.14975, -0.17781, 0.22362, 0.81166, -0.53963; - Mat R_23 = Mat(3, 3); - R_23 << 0.02362, -0.60298, -0.79741, -0.96400, 0.19758, -0.17796, 0.26486, 0.77290, -0.57661; - Vec3 t_21; - t_21 << -1.17794, -1.17674, 3.57853; - Vec3 t_22; - t_22 << 0.08257, 0.57753, 1.04335; - Vec3 t_23; - t_23 << 0.16029, 0.58720, 1.07571; - resection::P4PfModel sol_21(R_21, t_21, 4571.95746); - resection::P4PfModel sol_22(R_22, t_22, 1193.30606); - resection::P4PfModel sol_23(R_23, t_23, 1315.17564); - - // PROCESS - std::vector models_2; - resection::P4PfSolver solver; - solver.solve(pt2D_2, pt3D_2, models_2); - - bool pass = true; - if(!(models_2.size() == 3 - && isEqual(models_2.at(0), sol_21) - && isEqual(models_2.at(1), sol_22) - && isEqual(models_2.at(2), sol_23))) - pass = false; - BOOST_CHECK(pass); + // DATA + Mat pt2D_2 = Mat(2, 4); + pt2D_2 << 774.88000, -772.31000, -1661.63300, -1836.57300, -534.74500, -554.09400, -585.53300, -430.03000; + Mat pt3D_2 = Mat(3, 4); + pt3D_2 << 2.01852, 1.00709, 0.74051, 0.61962, 0.02133, 0.30770, 0.16656, 0.11249, -1.68077, 0.81502, 1.21056, 1.22624; + + // RESULTS + Mat R_21 = Mat(3, 3); + R_21 << 0.74908, 0.58601, -0.30898, 0.65890, -0.61061, 0.43933, 0.06879, -0.53268, -0.84352; + Mat R_22 = Mat(3, 3); + R_22 << 0.06352, -0.56461, -0.82291, -0.97260, 0.14975, -0.17781, 0.22362, 0.81166, -0.53963; + Mat R_23 = Mat(3, 3); + R_23 << 0.02362, -0.60298, -0.79741, -0.96400, 0.19758, -0.17796, 0.26486, 0.77290, -0.57661; + Vec3 t_21; + t_21 << -1.17794, -1.17674, 3.57853; + Vec3 t_22; + t_22 << 0.08257, 0.57753, 1.04335; + Vec3 t_23; + t_23 << 0.16029, 0.58720, 1.07571; + resection::P4PfModel sol_21(R_21, t_21, 4571.95746); + resection::P4PfModel sol_22(R_22, t_22, 1193.30606); + resection::P4PfModel sol_23(R_23, t_23, 1315.17564); + + // PROCESS + std::vector models_2; + resection::P4PfSolver solver; + solver.solve(pt2D_2, pt3D_2, models_2); + + bool pass = true; + if (!(models_2.size() == 3 && isEqual(models_2.at(0), sol_21) && isEqual(models_2.at(1), sol_22) && isEqual(models_2.at(2), sol_23))) + pass = false; + BOOST_CHECK(pass); } BOOST_AUTO_TEST_CASE(Resection_P4Pf_AssignmentWithNoResults) { - // DATA - Mat pt2D_3 = Mat(2, 4); - pt2D_3 << 774.88000, -570.41000, -1881.86960, 1529.54000, - -534.74500, -834.63100, -167.32000, -1203.28000; - Mat pt3D_3 = Mat(3, 4); - pt3D_3 << 2.01852, 1.28149, 0.55264, 2.29633, - 0.02133, 0.26101, 0.14578, -1.80998, - -1.68077, 0.70813, 1.22217, -1.76850; - - // PROCESS - std::vector models_3; - resection::P4PfSolver solver; - solver.solve(pt2D_3, pt3D_3, models_3); - - bool pass = true; - if(!models_3.empty()) - pass = false; - BOOST_CHECK(pass); + // DATA + Mat pt2D_3 = Mat(2, 4); + pt2D_3 << 774.88000, -570.41000, -1881.86960, 1529.54000, -534.74500, -834.63100, -167.32000, -1203.28000; + Mat pt3D_3 = Mat(3, 4); + pt3D_3 << 2.01852, 1.28149, 0.55264, 2.29633, 0.02133, 0.26101, 0.14578, -1.80998, -1.68077, 0.70813, 1.22217, -1.76850; + + // PROCESS + std::vector models_3; + resection::P4PfSolver solver; + solver.solve(pt2D_3, pt3D_3, models_3); + + bool pass = true; + if (!models_3.empty()) + pass = false; + BOOST_CHECK(pass); } diff --git a/src/aliceVision/multiview/resection/p5pfrSolver_test.cpp b/src/aliceVision/multiview/resection/p5pfrSolver_test.cpp index e6165d5b3e..ff2ac5d301 100644 --- a/src/aliceVision/multiview/resection/p5pfrSolver_test.cpp +++ b/src/aliceVision/multiview/resection/p5pfrSolver_test.cpp @@ -10,7 +10,6 @@ #include #include - #include #include @@ -19,655 +18,625 @@ using namespace aliceVision; using namespace aliceVision::multiview; -#define CHECK_SOLUTIONS(solutions, models, eps) \ -{ \ - BOOST_CHECK_EQUAL(models.size(), solutions.size()); \ - for(int i = 0; i < models.size(); ++i) \ - { \ - BOOST_CHECK_CLOSE(solutions.at(i)._f, models.at(i)._f, eps * solutions.at(i)._f); \ - EXPECT_MATRIX_NEAR(solutions.at(i)._R, models.at(i)._R, eps); \ - EXPECT_MATRIX_NEAR(solutions.at(i)._t, models.at(i)._t, eps); \ - EXPECT_MATRIX_NEAR(solutions.at(i)._r, models.at(i)._r, eps); \ - } \ -} +#define CHECK_SOLUTIONS(solutions, models, eps) \ + { \ + BOOST_CHECK_EQUAL(models.size(), solutions.size()); \ + for (int i = 0; i < models.size(); ++i) \ + { \ + BOOST_CHECK_CLOSE(solutions.at(i)._f, models.at(i)._f, eps* solutions.at(i)._f); \ + EXPECT_MATRIX_NEAR(solutions.at(i)._R, models.at(i)._R, eps); \ + EXPECT_MATRIX_NEAR(solutions.at(i)._t, models.at(i)._t, eps); \ + EXPECT_MATRIX_NEAR(solutions.at(i)._r, models.at(i)._r, eps); \ + } \ + } - -bool sortM(const resection::P5PfrModel &i, const resection::P5PfrModel &j) -{ - return (i._f < j._f); -} +bool sortM(const resection::P5PfrModel& i, const resection::P5PfrModel& j) { return (i._f < j._f); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_RandomRealExample) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << 4.803799999999999e+02, 5.422099999999998e+02, 1.508150000000000e+03, -1.640501000000000e+03, -4.026700000000001e+02, - -1.326581000000000e+03, -7.138680000000001e+02, -1.234743000000000e+03, -6.222770000000000e+02, -1.461412100000000e+03; + pt2D << 4.803799999999999e+02, 5.422099999999998e+02, 1.508150000000000e+03, -1.640501000000000e+03, -4.026700000000001e+02, + -1.326581000000000e+03, -7.138680000000001e+02, -1.234743000000000e+03, -6.222770000000000e+02, -1.461412100000000e+03; - pt3D << 3.175360000000000e+00, 2.531220000000000e+00, 2.420890000000000e+00, 7.578520000000000e-01, 3.880860000000000e+00, - -4.679980000000000e-01, 1.964960000000000e-01, -1.863250000000000e+00, 1.648030000000000e-01, 4.177460000000000e-01, - -8.605090000000000e-01, -1.507400000000000e+00, -1.828080000000000e+00, 1.208810000000000e+00, 2.980880000000000e-01; + pt3D << 3.175360000000000e+00, 2.531220000000000e+00, 2.420890000000000e+00, 7.578520000000000e-01, 3.880860000000000e+00, -4.679980000000000e-01, + 1.964960000000000e-01, -1.863250000000000e+00, 1.648030000000000e-01, 4.177460000000000e-01, -8.605090000000000e-01, -1.507400000000000e+00, + -1.828080000000000e+00, 1.208810000000000e+00, 2.980880000000000e-01; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << -9.430073299811607e-01, 2.928814581476584e-01, -1.579798312288744e-01, - -3.071682103315301e-01, -5.834954588038342e-01, 7.517850358427121e-01, - 1.280033834065588e-01, 7.574651813968776e-01, 6.402043680012413e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << -9.430073299811607e-01, 2.928814581476584e-01, -1.579798312288744e-01, -3.071682103315301e-01, -5.834954588038342e-01, + 7.517850358427121e-01, 1.280033834065588e-01, 7.574651813968776e-01, 6.402043680012413e-01; - Vec3 t1; - t1 << 3.351461753495843e+00, 3.662505952393360e-01, 1.522470547430807e+00; + Vec3 t1; + t1 << 3.351461753495843e+00, 3.662505952393360e-01, 1.522470547430807e+00; - const double f1 = 8.754207183076484e+03; + const double f1 = 8.754207183076484e+03; - Vec r1 = Vec(1); - r1 << -2.974934229711877e+01; + Vec r1 = Vec(1); + r1 << -2.974934229711877e+01; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << -9.288195037630559e-01, 2.583819805318196e-01, -2.655806498331628e-01, - -3.682532345104426e-01, -5.643279703727857e-01, 7.388663594504280e-01, - 4.103516421250264e-02, 7.840744186564097e-01, 6.193088254713202e-01; + Mat R2 = Mat(3, 3); + R2 << -9.288195037630559e-01, 2.583819805318196e-01, -2.655806498331628e-01, -3.682532345104426e-01, -5.643279703727857e-01, + 7.388663594504280e-01, 4.103516421250264e-02, 7.840744186564097e-01, 6.193088254713202e-01; - Vec3 t2; - t2 << 3.273710373948777e+00, 3.480925794418662e-01, 1.335812819189698e+00; + Vec3 t2; + t2 << 3.273710373948777e+00, 3.480925794418662e-01, 1.335812819189698e+00; - const double f2 = 6.523300619965637e+03; + const double f2 = 6.523300619965637e+03; - Vec r2 = Vec(1); - r2 << -1.716688911283097e+01; + Vec r2 = Vec(1); + r2 << -1.716688911283097e+01; - solutions.emplace_back(R2, t2, r2, f2); + solutions.emplace_back(R2, t2, r2, f2); - Mat R3 = Mat(3, 3); - R3 << 6.261772405396446e-01, 7.517419626249591e-01, -2.068479757187163e-01, - 7.785113680902823e-01, -6.173581864945597e-01, 1.130881042477301e-01, - -4.268621773305278e-02, -2.318466976191867e-01, -9.718153094177535e-01; + Mat R3 = Mat(3, 3); + R3 << 6.261772405396446e-01, 7.517419626249591e-01, -2.068479757187163e-01, 7.785113680902823e-01, -6.173581864945597e-01, 1.130881042477301e-01, + -4.268621773305278e-02, -2.318466976191867e-01, -9.718153094177535e-01; - Vec3 t3; - t3 << -2.286549972461777e+00, -1.360137887842060e+00, -5.183599359915888e-01; + Vec3 t3; + t3 << -2.286549972461777e+00, -1.360137887842060e+00, -5.183599359915888e-01; - const double f3 = 7.215366134863186e+03; + const double f3 = 7.215366134863186e+03; - Vec r3 = Vec(1); - r3 << -1.884020746278653e+01; + Vec r3 = Vec(1); + r3 << -1.884020746278653e+01; - solutions.emplace_back(R3, t3, r3, f3); + solutions.emplace_back(R3, t3, r3, f3); - Mat R4 = Mat(3, 3); - R4 << -2.156527230855822e-01, -6.509050028038228e-01, -7.278850048948177e-01, - -8.734050807538640e-01, 4.619070646021064e-01, -1.542900793440778e-01, - 4.366434105069141e-01, 6.024653857240441e-01, -6.681152528308791e-01; + Mat R4 = Mat(3, 3); + R4 << -2.156527230855822e-01, -6.509050028038228e-01, -7.278850048948177e-01, -8.734050807538640e-01, 4.619070646021064e-01, + -1.542900793440778e-01, 4.366434105069141e-01, 6.024653857240441e-01, -6.681152528308791e-01; - Vec3 t4; - t4 << 5.861602062248285e-01, 5.581995729744164e-01, 8.108474186437322e-01; + Vec3 t4; + t4 << 5.861602062248285e-01, 5.581995729744164e-01, 8.108474186437322e-01; - const double f4 = 1.770851635941407e+03; + const double f4 = 1.770851635941407e+03; - Vec r4 = Vec(1); - r4 << -2.945748397114161e-01; + Vec r4 = Vec(1); + r4 << -2.945748397114161e-01; - solutions.emplace_back(R4, t4, r4, f4); + solutions.emplace_back(R4, t4, r4, f4); - std::sort(solutions.begin(), solutions.end(), sortM); + std::sort(solutions.begin(), solutions.end(), sortM); - // process - std::vector models; - resection::P5PfrSolver<1> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // process + std::vector models; + resection::P5PfrSolver<1> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_Test01) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << 7.566900000000000e+00, 1.153300000000000e+01, -6.677100000000000e+00, -8.057900000000000e+00, 9.185599999999999e-01, - 9.171400000000000e+00, -8.827500000000001e+00, -8.109500000000001e+00, 6.638800000000000e+00, 4.434400000000000e-02; + pt2D << 7.566900000000000e+00, 1.153300000000000e+01, -6.677100000000000e+00, -8.057900000000000e+00, 9.185599999999999e-01, + 9.171400000000000e+00, -8.827500000000001e+00, -8.109500000000001e+00, 6.638800000000000e+00, 4.434400000000000e-02; - pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, - 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, - 1.100000000000000e+01, 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; + pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, + 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, 1.100000000000000e+01, + 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << -9.866769718754666e-01, 8.864003655023664e-02, 1.364239608391149e-01, - -8.610773932011366e-02, -9.959878698575466e-01, 2.436432485840338e-02, - 1.380362647996501e-01, 1.229255941612770e-02, 9.903508886163336e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << -9.866769718754666e-01, 8.864003655023664e-02, 1.364239608391149e-01, -8.610773932011366e-02, -9.959878698575466e-01, 2.436432485840338e-02, + 1.380362647996501e-01, 1.229255941612770e-02, 9.903508886163336e-01; - Vec3 t1; - t1 << -2.338498898349219e+00, -2.373410185570847e-01, -2.137124572474679e+02; + Vec3 t1; + t1 << -2.338498898349219e+00, -2.373410185570847e-01, -2.137124572474679e+02; - const double f1 = 1.194729635360126e+02; + const double f1 = 1.194729635360126e+02; - Vec r1 = Vec(1); - r1 << 4.426177298997219e+01; + Vec r1 = Vec(1); + r1 << 4.426177298997219e+01; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << 9.911472004734465e-01, -9.612106035721957e-02, 9.158585452706794e-02, - 9.612196425399246e-02, 9.953597879541551e-01, 4.411407010652809e-03, - -9.158490586119367e-02, 4.431058526264710e-03, 9.957874124223167e-01; + Mat R2 = Mat(3, 3); + R2 << 9.911472004734465e-01, -9.612106035721957e-02, 9.158585452706794e-02, 9.612196425399246e-02, 9.953597879541551e-01, 4.411407010652809e-03, + -9.158490586119367e-02, 4.431058526264710e-03, 9.957874124223167e-01; - Vec3 t2; - t2 << -4.762130757439627e-04, 4.667150046849628e-05, 4.194470650574878e-04; + Vec3 t2; + t2 << -4.762130757439627e-04, 4.667150046849628e-05, 4.194470650574878e-04; - const double f2 = 1.000134624927844e+01; + const double f2 = 1.000134624927844e+01; - Vec r2 = Vec(1); - r2 << -1.000869226503413e-01; + Vec r2 = Vec(1); + r2 << -1.000869226503413e-01; - resection::P5PfrModel m2(R2, t2, r2, f2); - solutions.emplace_back(m2); - std::sort(solutions.begin(), solutions.end(), sortM); + resection::P5PfrModel m2(R2, t2, r2, f2); + solutions.emplace_back(m2); + std::sort(solutions.begin(), solutions.end(), sortM); - // PROCESS - std::vector models; - resection::P5PfrSolver<1> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // PROCESS + std::vector models; + resection::P5PfrSolver<1> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_Test02) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << 7.566900000000001e+02, 1.153300000000000e+03, -6.677100000000000e+02, -8.057900000000000e+02, 9.185600000000000e+01, - 9.171400000000000e+02, -8.827500000000000e+02, -8.109500000000001e+02, 6.638800000000000e+02, 4.434400000000000e+00; + pt2D << 7.566900000000001e+02, 1.153300000000000e+03, -6.677100000000000e+02, -8.057900000000000e+02, 9.185600000000000e+01, + 9.171400000000000e+02, -8.827500000000000e+02, -8.109500000000001e+02, 6.638800000000000e+02, 4.434400000000000e+00; - pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, - 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, - 1.100000000000000e+01, 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; + pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, + 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, 1.100000000000000e+01, + 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << -9.866769718754656e-01, 8.864003655023635e-02, 1.364239608391232e-01, - -8.610773932011311e-02, -9.959878698575468e-01, 2.436432485840404e-02, - 1.380362647996585e-01, 1.229255941612769e-02, 9.903508886163327e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << -9.866769718754656e-01, 8.864003655023635e-02, 1.364239608391232e-01, -8.610773932011311e-02, -9.959878698575468e-01, 2.436432485840404e-02, + 1.380362647996585e-01, 1.229255941612769e-02, 9.903508886163327e-01; - Vec3 t1; - t1 << -2.338498898349279e+00, -2.373410185570856e-01, -4.243667543424492e+03; + Vec3 t1; + t1 << -2.338498898349279e+00, -2.373410185570856e-01, -4.243667543424492e+03; - const double f1 = 2.521752759667783e+05; + const double f1 = 2.521752759667783e+05; - Vec r1 = Vec(1); - r1 << 1.888402757691173e+04; + Vec r1 = Vec(1); + r1 << 1.888402757691173e+04; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << 9.911472004734461e-01, -9.612106035721975e-02, 9.158585452707382e-02, - 9.612196425399260e-02, 9.953597879541551e-01, 4.411407010652902e-03, - -9.158490586119952e-02, 4.431058526265199e-03, 9.957874124223163e-01; + Mat R2 = Mat(3, 3); + R2 << 9.911472004734461e-01, -9.612106035721975e-02, 9.158585452707382e-02, 9.612196425399260e-02, 9.953597879541551e-01, 4.411407010652902e-03, + -9.158490586119952e-02, 4.431058526265199e-03, 9.957874124223163e-01; - Vec3 t2; - t2 << -4.762130758018528e-04, 4.667150046743732e-05, 4.194544910240924e-04; + Vec3 t2; + t2 << -4.762130758018528e-04, 4.667150046743732e-05, 4.194544910240924e-04; - const double f2 = 1.000134625367708e+03; + const double f2 = 1.000134625367708e+03; - Vec r2 = Vec(1); - r2 << -1.000869225598735e-01; + Vec r2 = Vec(1); + r2 << -1.000869225598735e-01; - solutions.emplace_back(R2, t2, r2, f2); - std::sort(solutions.begin(), solutions.end(), sortM); + solutions.emplace_back(R2, t2, r2, f2); + std::sort(solutions.begin(), solutions.end(), sortM); - // PROCESS - std::vector models; - resection::P5PfrSolver<1> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // PROCESS + std::vector models; + resection::P5PfrSolver<1> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_Test03) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << 7.426900000000000e+00, 1.109600000000000e+01, -6.599600000000000e+00, -7.966500000000000e+00, 9.185600000000000e-01, - 9.001700000000000e+00, -8.493100000000000e+00, -8.015400000000000e+00, 6.563500000000000e+00, 4.434400000000000e-02; + pt2D << 7.426900000000000e+00, 1.109600000000000e+01, -6.599600000000000e+00, -7.966500000000000e+00, 9.185600000000000e-01, + 9.001700000000000e+00, -8.493100000000000e+00, -8.015400000000000e+00, 6.563500000000000e+00, 4.434400000000000e-02; - pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, - 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, - 1.100000000000000e+01, 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; + pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, + 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, 1.100000000000000e+01, + 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << 9.866726171702454e-01, -8.863862515507148e-02, -1.364563690599919e-01, - 8.610372986162385e-02, 9.959878340728819e-01, -2.437995247586850e-02, - 1.380698889343181e-01, 1.230562917641179e-02, 9.903460391500725e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << 9.866726171702454e-01, -8.863862515507148e-02, -1.364563690599919e-01, 8.610372986162385e-02, 9.959878340728819e-01, -2.437995247586850e-02, + 1.380698889343181e-01, 1.230562917641179e-02, 9.903460391500725e-01; - Vec3 t1; - t1 << 2.338816295443713e+00, 2.374547827269040e-01, 3.038794646050726e+00; + Vec3 t1; + t1 << 2.338816295443713e+00, 2.374547827269040e-01, 3.038794646050726e+00; - const double f1 = 6.808663417966336e+00; + const double f1 = 6.808663417966336e+00; - Vec r1 = Vec(3); - r1 << -1.541773606559228e+00, 1.125710955774016e+00, -1.694590906260655e-01; + Vec r1 = Vec(3); + r1 << -1.541773606559228e+00, 1.125710955774016e+00, -1.694590906260655e-01; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << 9.911441225386224e-01, -9.612050154153130e-02, 9.161974427244664e-02, - 9.612219384046608e-02, 9.953597965529948e-01, 4.404458794976624e-03, - -9.161796880765205e-02, 4.441237370965423e-03, 9.957843256459575e-01; + Mat R2 = Mat(3, 3); + R2 << 9.911441225386224e-01, -9.612050154153130e-02, 9.161974427244664e-02, 9.612219384046608e-02, 9.953597965529948e-01, 4.404458794976624e-03, + -9.161796880765205e-02, 4.441237370965423e-03, 9.957843256459575e-01; - Vec3 t2; - t2 << -8.389662176239775e-04, 8.924922397161790e-05, 4.403363708227871e-03; + Vec3 t2; + t2 << -8.389662176239775e-04, 8.924922397161790e-05, 4.403363708227871e-03; - const double f2 = 1.001148139451480e+01; + const double f2 = 1.001148139451480e+01; - Vec r2 = Vec(3); - r2 << -1.013497349186711e-01, -9.446252525415940e-03, -1.094974551029621e-03; + Vec r2 = Vec(3); + r2 << -1.013497349186711e-01, -9.446252525415940e-03, -1.094974551029621e-03; - solutions.emplace_back(R2, t2, r2, f2); - std::sort(solutions.begin(), solutions.end(), sortM); + solutions.emplace_back(R2, t2, r2, f2); + std::sort(solutions.begin(), solutions.end(), sortM); - // PROCESS - std::vector models; - resection::P5PfrSolver<3> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // PROCESS + std::vector models; + resection::P5PfrSolver<3> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_Test04) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << 7.442600000000000e+02, 1.116000000000000e+03, -6.606800000000000e+02, -7.975000000000000e+02, 9.185600000000000e+01, - 9.020800000000000e+02, -8.541500000000000e+02, -8.024200000000000e+02, 6.570500000000000e+02, 4.434400000000000e+00; + pt2D << 7.442600000000000e+02, 1.116000000000000e+03, -6.606800000000000e+02, -7.975000000000000e+02, 9.185600000000000e+01, + 9.020800000000000e+02, -8.541500000000000e+02, -8.024200000000000e+02, 6.570500000000000e+02, 4.434400000000000e+00; - pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, - 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, - 1.100000000000000e+01, 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; + pt3D << 9.000000000000000e+00, 1.000000000000000e+01, -1.000000000000000e+01, -1.100000000000000e+01, 0.000000000000000e+00, + 1.000000000000000e+01, -1.000000000000000e+01, -9.000000000000000e+00, 1.000000000000000e+01, 0.000000000000000e+00, 1.100000000000000e+01, + 9.000000000000000e+00, 1.000000000000000e+01, 1.100000000000000e+01, 7.000000000000000e+00; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << -9.866929474192679e-01, 8.865005182074735e-02, 1.363018555459043e-01, - -8.612834136352524e-02, -9.959876607489899e-01, 2.429996810126685e-02, - 1.379091596923318e-01, 1.223715440509363e-02, 9.903692825027544e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << -9.866929474192679e-01, 8.865005182074735e-02, 1.363018555459043e-01, -8.612834136352524e-02, -9.959876607489899e-01, 2.429996810126685e-02, + 1.379091596923318e-01, 1.223715440509363e-02, 9.903692825027544e-01; - Vec3 t1; - t1 << -2.337410845255324e+00, -2.368792577941530e-01, -8.313411561105022e+01; + Vec3 t1; + t1 << -2.337410845255324e+00, -2.368792577941530e-01, -8.313411561105022e+01; - const double f1 = 7.220370878892927e+03; + const double f1 = 7.220370878892927e+03; - Vec r1 = Vec(2); - r1 << -2.023313473211615e+01, 5.014947585259129e+02; + Vec r1 = Vec(2); + r1 << -2.023313473211615e+01, 5.014947585259129e+02; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << 9.911582426555702e-01, -9.612846516724592e-02, 9.145849441419568e-02, - 9.612595944501501e-02, 9.953592630191308e-01, 4.442684187302039e-03, - -9.146112800910124e-02, 4.388132473201126e-03, 9.957989788891634e-01; + Mat R2 = Mat(3, 3); + R2 << 9.911582426555702e-01, -9.612846516724592e-02, 9.145849441419568e-02, 9.612595944501501e-02, 9.953592630191308e-01, 4.442684187302039e-03, + -9.146112800910124e-02, 4.388132473201126e-03, 9.957989788891634e-01; - Vec3 t2; - t2 << 9.945786885438171e-04, -1.443041043924996e-04, -4.823891271638905e-03; + Vec3 t2; + t2 << 9.945786885438171e-04, -1.443041043924996e-04, -4.823891271638905e-03; - const double f2 = 9.988930531520911e+02; + const double f2 = 9.988930531520911e+02; - Vec r2 = Vec(2); - r2 << -9.900367868789792e-02, -1.021444916204432e-02; + Vec r2 = Vec(2); + r2 << -9.900367868789792e-02, -1.021444916204432e-02; - solutions.emplace_back(R2, t2, r2, f2); - std::sort(solutions.begin(), solutions.end(), sortM); + solutions.emplace_back(R2, t2, r2, f2); + std::sort(solutions.begin(), solutions.end(), sortM); - // PROCESS - std::vector models; - resection::P5PfrSolver<2> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // PROCESS + std::vector models; + resection::P5PfrSolver<2> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-2; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-2; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_Test05) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << -5.083665639282905e+02, -1.888279680387158e+02, 5.485850494158182e+02, 6.057378448766012e+02, 1.049611103316406e+02, - 6.135576285838145e+02, -1.221172634793433e+03, -3.523254600615324e+02, 3.014526831471967e+02, 6.551794931115911e+02; + pt2D << -5.083665639282905e+02, -1.888279680387158e+02, 5.485850494158182e+02, 6.057378448766012e+02, 1.049611103316406e+02, + 6.135576285838145e+02, -1.221172634793433e+03, -3.523254600615324e+02, 3.014526831471967e+02, 6.551794931115911e+02; - pt3D << -4.780000000000000e+00, -5.662000000000001e+01, -3.109000000000000e+01, -1.337000000000000e+01, -1.220000000000000e+00, - -8.390000000000001e+00, -3.940000000000000e+00, 2.115000000000000e+01, 2.512000000000000e+01, 1.024000000000000e+01, - 2.975400000000000e+02, 2.715000000000000e+02, 2.743600000000000e+02, 2.852800000000000e+02, 2.909600000000000e+02; + pt3D << -4.780000000000000e+00, -5.662000000000001e+01, -3.109000000000000e+01, -1.337000000000000e+01, -1.220000000000000e+00, + -8.390000000000001e+00, -3.940000000000000e+00, 2.115000000000000e+01, 2.512000000000000e+01, 1.024000000000000e+01, 2.975400000000000e+02, + 2.715000000000000e+02, 2.743600000000000e+02, 2.852800000000000e+02, 2.909600000000000e+02; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << 1.155567671802503e-01, 8.662877974144153e-01, -4.859959728328315e-01, - 9.628483489629153e-01, 2.253109491131032e-02, 2.691011086218072e-01, - 2.440690280601738e-01, -4.990368742016750e-01, -8.315001549782077e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << 1.155567671802503e-01, 8.662877974144153e-01, -4.859959728328315e-01, 9.628483489629153e-01, 2.253109491131032e-02, 2.691011086218072e-01, + 2.440690280601738e-01, -4.990368742016750e-01, -8.315001549782077e-01; - Vec3 t1; - t1 << 1.361540537034142e+02, -5.564066632096895e+01, 3.181374378543369e+02; + Vec3 t1; + t1 << 1.361540537034142e+02, -5.564066632096895e+01, 3.181374378543369e+02; - const double f1 = 2.021855309782332e+03; + const double f1 = 2.021855309782332e+03; - Vec r1 = Vec(1); - r1 << 8.255860596348720e-01; + Vec r1 = Vec(1); + r1 << 8.255860596348720e-01; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << 2.922089312019317e-02, -9.842762550966916e-01, 1.742021614621672e-01, - -7.850366300341083e-01, -1.304794743186365e-01, -6.055514811196674e-01, - 6.187597505574750e-01, -1.190603226703449e-01, -7.765056410971429e-01; + Mat R2 = Mat(3, 3); + R2 << 2.922089312019317e-02, -9.842762550966916e-01, 1.742021614621672e-01, -7.850366300341083e-01, -1.304794743186365e-01, + -6.055514811196674e-01, 6.187597505574750e-01, -1.190603226703449e-01, -7.765056410971429e-01; - Vec3 t2; - t2 << -4.387772837075708e+01, 1.559300291237510e+02, -5.017373099470832e+01; + Vec3 t2; + t2 << -4.387772837075708e+01, 1.559300291237510e+02, -5.017373099470832e+01; - const double f2 = 8.380484450967588e+03; + const double f2 = 8.380484450967588e+03; - Vec r2 = Vec(1); - r2 << 8.284870911169731e+00; + Vec r2 = Vec(1); + r2 << 8.284870911169731e+00; - solutions.emplace_back(R2, t2, r2, f2); + solutions.emplace_back(R2, t2, r2, f2); - Mat R3 = Mat(3, 3); - R3 << -1.801610707703882e-02, 9.822319557442656e-01, -1.868041889267577e-01, - 8.861462458888930e-01, 1.022150952868773e-01, 4.519921516934955e-01, - 4.630553431100972e-01, -1.573926917308747e-01, -8.722426782764643e-01; + Mat R3 = Mat(3, 3); + R3 << -1.801610707703882e-02, 9.822319557442656e-01, -1.868041889267577e-01, 8.861462458888930e-01, 1.022150952868773e-01, 4.519921516934955e-01, + 4.630553431100972e-01, -1.573926917308747e-01, -8.722426782764643e-01; - Vec3 t3; - t3 << 4.770602230971817e+01, -1.100448483622631e+02, 3.787350883298001e+02; + Vec3 t3; + t3 << 4.770602230971817e+01, -1.100448483622631e+02, 3.787350883298001e+02; - const double f3 = 3.755300014149142e+03; + const double f3 = 3.755300014149142e+03; - Vec r3 = Vec(1); - r3 << -1.915149580218268e-02; + Vec r3 = Vec(1); + r3 << -1.915149580218268e-02; - solutions.emplace_back(R3, t3, r3, f3); + solutions.emplace_back(R3, t3, r3, f3); - Mat R4 = Mat(3, 3); - R4 << -1.174221990502533e-01, 9.889452839990088e-01, 9.054972350218801e-02, - 9.756067174145514e-01, 9.784522777965984e-02, 1.965142344369749e-01, - 1.854819670636651e-01, 1.114160520610315e-01, -9.763108640373349e-01; + Mat R4 = Mat(3, 3); + R4 << -1.174221990502533e-01, 9.889452839990088e-01, 9.054972350218801e-02, 9.756067174145514e-01, 9.784522777965984e-02, 1.965142344369749e-01, + 1.854819670636651e-01, 1.114160520610315e-01, -9.763108640373349e-01; - Vec3 t4; - t4 << -3.325815868705528e+01, -3.602692800212063e+01, 5.594982831629274e+02; + Vec3 t4; + t4 << -3.325815868705528e+01, -3.602692800212063e+01, 5.594982831629274e+02; - const double f4 = 9.255353840201478e+03; + const double f4 = 9.255353840201478e+03; - Vec r4 = Vec(1); - r4 << -1.274066589972153e+00; + Vec r4 = Vec(1); + r4 << -1.274066589972153e+00; - solutions.emplace_back(R4, t4, r4, f4); - std::sort(solutions.begin(), solutions.end(), sortM); + solutions.emplace_back(R4, t4, r4, f4); + std::sort(solutions.begin(), solutions.end(), sortM); - // PROCESS - std::vector models; - resection::P5PfrSolver<1> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // PROCESS + std::vector models; + resection::P5PfrSolver<1> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_Test06) { - // DATA - Mat pt2D = Mat(2, 5); - Mat pt3D = Mat(3, 5); + // DATA + Mat pt2D = Mat(2, 5); + Mat pt3D = Mat(3, 5); - pt2D << -5.023000000000000e+02, -1.816000000000000e+02, 5.608000000000000e+02, 6.180000000000000e+02, 1.138000000000000e+02, - 6.223000000000000e+02, -1.226000000000000e+03, -3.493000000000000e+02, 3.088000000000000e+02, 6.635000000000000e+02; + pt2D << -5.023000000000000e+02, -1.816000000000000e+02, 5.608000000000000e+02, 6.180000000000000e+02, 1.138000000000000e+02, + 6.223000000000000e+02, -1.226000000000000e+03, -3.493000000000000e+02, 3.088000000000000e+02, 6.635000000000000e+02; - pt3D << -4.780000000000000e+00, -5.662000000000001e+01, -3.109000000000000e+01, -1.337000000000000e+01, -1.220000000000000e+00, - -8.390000000000001e+00, -3.940000000000000e+00, 2.115000000000000e+01, 2.512000000000000e+01, 1.024000000000000e+01, - 2.975400000000000e+02, 2.715000000000000e+02, 2.743600000000000e+02, 2.852800000000000e+02, 2.909600000000000e+02; + pt3D << -4.780000000000000e+00, -5.662000000000001e+01, -3.109000000000000e+01, -1.337000000000000e+01, -1.220000000000000e+00, + -8.390000000000001e+00, -3.940000000000000e+00, 2.115000000000000e+01, 2.512000000000000e+01, 1.024000000000000e+01, 2.975400000000000e+02, + 2.715000000000000e+02, 2.743600000000000e+02, 2.852800000000000e+02, 2.909600000000000e+02; - // SOLUTIONS - std::vector solutions; - Mat R1 = Mat(3, 3); - R1 << 1.158093303645603e-01, 8.661302625338441e-01, -4.862165847886791e-01, - 9.628499358654846e-01, 2.232902483922909e-02, 2.691122733239069e-01, - 2.439430261441067e-01, -4.993193196470409e-01, -8.313675583175423e-01; + // SOLUTIONS + std::vector solutions; + Mat R1 = Mat(3, 3); + R1 << 1.158093303645603e-01, 8.661302625338441e-01, -4.862165847886791e-01, 9.628499358654846e-01, 2.232902483922909e-02, 2.691122733239069e-01, + 2.439430261441067e-01, -4.993193196470409e-01, -8.313675583175423e-01; - Vec3 t1; - t1 << 1.364957077988834e+02, -5.546744398860090e+01, 3.201838786297712e+02; + Vec3 t1; + t1 << 1.364957077988834e+02, -5.546744398860090e+01, 3.201838786297712e+02; - const double f1 = 2.087740834197694e+03; + const double f1 = 2.087740834197694e+03; - Vec r1 = Vec(1); - r1 << 8.833511704539349e-01; + Vec r1 = Vec(1); + r1 << 8.833511704539349e-01; - solutions.emplace_back(R1, t1, r1, f1); + solutions.emplace_back(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << 3.005871612955928e-02, -9.845150899000348e-01, 1.727035359909268e-01, - -7.819107066457760e-01, -1.307939675733752e-01, -6.095150407324811e-01, - 6.226653358083665e-01, -1.167175042808154e-01, -7.737343883899128e-01; + Mat R2 = Mat(3, 3); + R2 << 3.005871612955928e-02, -9.845150899000348e-01, 1.727035359909268e-01, -7.819107066457760e-01, -1.307939675733752e-01, + -6.095150407324811e-01, 6.226653358083665e-01, -1.167175042808154e-01, -7.737343883899128e-01; - Vec3 t2; - t2 << -4.369759671728466e+01, 1.569393617329415e+02, -7.975480893440621e+01; + Vec3 t2; + t2 << -4.369759671728466e+01, 1.569393617329415e+02, -7.975480893440621e+01; - const double f2 = 9.270119409950667e+03; + const double f2 = 9.270119409950667e+03; - Vec r2 = Vec(1); - r2 << 1.030622373405317e+01; + Vec r2 = Vec(1); + r2 << 1.030622373405317e+01; - solutions.emplace_back(R2, t2, r2, f2); + solutions.emplace_back(R2, t2, r2, f2); - Mat R3 = Mat(3, 3); - R3 << -1.801610707683794e-02, 9.822319557441956e-01, -1.868041889271452e-01, - 8.861462458893246e-01, 1.022150952867334e-01, 4.519921516926820e-01, - 4.630553431092793e-01, -1.573926917314041e-01, -8.722426782768031e-01; + Mat R3 = Mat(3, 3); + R3 << -1.801610707683794e-02, 9.822319557441956e-01, -1.868041889271452e-01, 8.861462458893246e-01, 1.022150952867334e-01, 4.519921516926820e-01, + 4.630553431092793e-01, -1.573926917314041e-01, -8.722426782768031e-01; - Vec3 t3; - t3 << 4.797850960892938e+01, -1.098697561296521e+02, 3.782270371153628e+02; + Vec3 t3; + t3 << 4.797850960892938e+01, -1.098697561296521e+02, 3.782270371153628e+02; - const double f3 = 3.755281081447789e+03; + const double f3 = 3.755281081447789e+03; - Vec r3 = Vec(1); - r3 << 1.915149737466193e-02; + Vec r3 = Vec(1); + r3 << 1.915149737466193e-02; - solutions.emplace_back(R3, t3, r3, f3); + solutions.emplace_back(R3, t3, r3, f3); - Mat R4 = Mat(3, 3); - R4 << -1.199004189301619e-01, 9.879748719866909e-01, 9.761937237686719e-02, - 9.764291491248300e-01, 9.957808492669369e-02, 1.914949653168269e-01, - 1.794714636919536e-01, 1.182787272725487e-01, -9.766269177096513e-01; + Mat R4 = Mat(3, 3); + R4 << -1.199004189301619e-01, 9.879748719866909e-01, 9.761937237686719e-02, 9.764291491248300e-01, 9.957808492669369e-02, 1.914949653168269e-01, + 1.794714636919536e-01, 1.182787272725487e-01, -9.766269177096513e-01; - Vec3 t4; - t4 << -3.505229132105162e+01, -3.447366644241274e+01, 5.609300537327143e+02; + Vec3 t4; + t4 << -3.505229132105162e+01, -3.447366644241274e+01, 5.609300537327143e+02; - const double f4 = 9.400920059230813e+03; + const double f4 = 9.400920059230813e+03; - Vec r4 = Vec(1); - r4 << -1.441857759763210e+00; + Vec r4 = Vec(1); + r4 << -1.441857759763210e+00; - solutions.emplace_back(R4, t4, r4, f4); - std::sort(solutions.begin(), solutions.end(), sortM); + solutions.emplace_back(R4, t4, r4, f4); + std::sort(solutions.begin(), solutions.end(), sortM); - // PROCESS - std::vector models; - resection::P5PfrSolver<1> solver; - solver.solve(pt2D, pt3D, models); - std::sort(models.begin(), models.end(), sortM); + // PROCESS + std::vector models; + resection::P5PfrSolver<1> solver; + solver.solve(pt2D, pt3D, models); + std::sort(models.begin(), models.end(), sortM); - // BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - CHECK_SOLUTIONS(solutions, models, eps); + // BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + CHECK_SOLUTIONS(solutions, models, eps); } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_ReprojectionErrRD) { - // DATA - Mat R1 = Mat(3, 3); - R1 << -1.801610707703100e-02, 9.822319557442600e-01, -1.868041889267890e-01, - 8.861462458886890e-01, 1.022150952869810e-01, 4.519921516938730e-01, - 4.630553431104880e-01, -1.573926917308610e-01, -8.722426782762600e-01; - - Vec3 t1; - t1 << 4.770602230972729e+01, -1.100448483623751e+02, 3.787350878705134e+02; - - const double f1 = 3.755300000000000e+03; - - Vec r1 = Vec(1); - r1 << -1.915149736941400e-02; - - resection::P5PfrModel m1(R1, t1, r1, f1); - - Mat X = Mat(3, 5); - X << -4.780000000000000e+00, -5.662000000000001e+01, -3.109000000000000e+01, -1.337000000000000e+01, -1.220000000000000e+00, - -8.390000000000001e+00, -3.940000000000000e+00, 2.115000000000000e+01, 2.512000000000000e+01, 1.024000000000000e+01, - 2.975400000000000e+02, 2.715000000000000e+02, 2.743600000000000e+02, 2.852800000000000e+02, 2.909600000000000e+02; - - // SOLUTION - Mat u = Mat(2, 5); - u << -5.083665639282905e+02, -1.888279680387158e+02, 5.485850494158182e+02, 6.057378448766012e+02, 1.049611103316406e+02, - 6.135576285838145e+02, -1.221172634793433e+03, -3.523254600615324e+02, 3.014526831471967e+02, 6.551794931115911e+02; - - // PROCESS & BOOST_AUTO_TEST_CASE - const double eps = 1e-4; - for(int i = 0; i < 5; ++i) - { - Mat ui = u.col(i); - Mat Xi = X.col(i); - BOOST_CHECK(resection::reprojectionErrorRD(m1, Map(ui.data(), 2), Map(Xi.data(), 3)) < eps); - } + // DATA + Mat R1 = Mat(3, 3); + R1 << -1.801610707703100e-02, 9.822319557442600e-01, -1.868041889267890e-01, 8.861462458886890e-01, 1.022150952869810e-01, 4.519921516938730e-01, + 4.630553431104880e-01, -1.573926917308610e-01, -8.722426782762600e-01; + + Vec3 t1; + t1 << 4.770602230972729e+01, -1.100448483623751e+02, 3.787350878705134e+02; + + const double f1 = 3.755300000000000e+03; + + Vec r1 = Vec(1); + r1 << -1.915149736941400e-02; + + resection::P5PfrModel m1(R1, t1, r1, f1); + + Mat X = Mat(3, 5); + X << -4.780000000000000e+00, -5.662000000000001e+01, -3.109000000000000e+01, -1.337000000000000e+01, -1.220000000000000e+00, + -8.390000000000001e+00, -3.940000000000000e+00, 2.115000000000000e+01, 2.512000000000000e+01, 1.024000000000000e+01, 2.975400000000000e+02, + 2.715000000000000e+02, 2.743600000000000e+02, 2.852800000000000e+02, 2.909600000000000e+02; + + // SOLUTION + Mat u = Mat(2, 5); + u << -5.083665639282905e+02, -1.888279680387158e+02, 5.485850494158182e+02, 6.057378448766012e+02, 1.049611103316406e+02, 6.135576285838145e+02, + -1.221172634793433e+03, -3.523254600615324e+02, 3.014526831471967e+02, 6.551794931115911e+02; + + // PROCESS & BOOST_AUTO_TEST_CASE + const double eps = 1e-4; + for (int i = 0; i < 5; ++i) + { + Mat ui = u.col(i); + Mat Xi = X.col(i); + BOOST_CHECK(resection::reprojectionErrorRD(m1, Map(ui.data(), 2), Map(Xi.data(), 3)) < eps); + } } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_ReprojectionErrRP) { - // DATA - Mat R1 = Mat(3, 3); - R1 << -9.992060000000000e-01, -9.115130000000000e-04, -3.984260000000000e-02, - -2.467660000000000e-03, 9.992350000000000e-01, 3.902560000000000e-02, - 3.977660000000000e-02, 3.909300000000000e-02, -9.984440000000000e-01; - - Vec3 t1; - t1 << -9.757978898639005e-02, -1.432719535106094e+00, -7.429114914010337e-02; - - const double f1 = 4.003890000000000e+03; - - Vec r1 = Vec(2); - r1 << 2.609980000000000e-01, -8.600540000000000e-01; - - resection::P5PfrModel m1(R1, t1, r1, f1); - - Mat X = Mat(3, 5); - X << -1.789980000000000e+00, 3.126420000000000e-02, -8.818300000000000e-01, 4.003490000000000e-01, -2.801120000000000e-01, - 5.379380000000000e-01, 2.120090000000000e+00, 7.626530000000000e-01, -2.713720000000000e-01, -5.288820000000000e-01, - 7.695280000000000e+00, 8.668390000000001e+00, 8.591100000000001e+00, 8.153530000000000e+00, 6.836970000000000e+00; - - // SOLUTION - Mat u = Mat(2, 5); - u << -7.157065483965156e+02, 2.214390204167765e+02, -2.039687171099566e+02, 4.045162808436347e+02, 5.240453162355445e+01, - 3.053701505359891e+02, -4.762317643178445e+02, 1.542630206562505e+02, 6.822162604030057e+02, 9.904720783975658e+02; - - // PROCESS & BOOST_AUTO_TEST_CASE - const double eps = 1e-3; - for(int i = 0; i < 1; ++i) - { - Mat ui = u.col(i); - Mat Xi = X.col(i); - BOOST_CHECK(resection::reprojectionErrorRP(m1, Map(ui.data(), 2), Map(Xi.data(), 3)) < eps); - } + // DATA + Mat R1 = Mat(3, 3); + R1 << -9.992060000000000e-01, -9.115130000000000e-04, -3.984260000000000e-02, -2.467660000000000e-03, 9.992350000000000e-01, + 3.902560000000000e-02, 3.977660000000000e-02, 3.909300000000000e-02, -9.984440000000000e-01; + + Vec3 t1; + t1 << -9.757978898639005e-02, -1.432719535106094e+00, -7.429114914010337e-02; + + const double f1 = 4.003890000000000e+03; + + Vec r1 = Vec(2); + r1 << 2.609980000000000e-01, -8.600540000000000e-01; + + resection::P5PfrModel m1(R1, t1, r1, f1); + + Mat X = Mat(3, 5); + X << -1.789980000000000e+00, 3.126420000000000e-02, -8.818300000000000e-01, 4.003490000000000e-01, -2.801120000000000e-01, 5.379380000000000e-01, + 2.120090000000000e+00, 7.626530000000000e-01, -2.713720000000000e-01, -5.288820000000000e-01, 7.695280000000000e+00, 8.668390000000001e+00, + 8.591100000000001e+00, 8.153530000000000e+00, 6.836970000000000e+00; + + // SOLUTION + Mat u = Mat(2, 5); + u << -7.157065483965156e+02, 2.214390204167765e+02, -2.039687171099566e+02, 4.045162808436347e+02, 5.240453162355445e+01, 3.053701505359891e+02, + -4.762317643178445e+02, 1.542630206562505e+02, 6.822162604030057e+02, 9.904720783975658e+02; + + // PROCESS & BOOST_AUTO_TEST_CASE + const double eps = 1e-3; + for (int i = 0; i < 1; ++i) + { + Mat ui = u.col(i); + Mat Xi = X.col(i); + BOOST_CHECK(resection::reprojectionErrorRP(m1, Map(ui.data(), 2), Map(Xi.data(), 3)) < eps); + } } BOOST_AUTO_TEST_CASE(Resection_P5Pfr_ConversionRD2RP) { - // DATA - Mat pt2D = Mat(2, 5); - pt2D << 4.811200000000001e+02, 3.067499999999998e+02, -6.962300000000000e+02, 1.242750000000000e+03, 6.765399999999997e+02, - -1.153199999999999e+02, -9.486730000000000e+02, -9.567130000000000e+02, -8.622940000000000e+02, -1.491675000000000e+03; + // DATA + Mat pt2D = Mat(2, 5); + pt2D << 4.811200000000001e+02, 3.067499999999998e+02, -6.962300000000000e+02, 1.242750000000000e+03, 6.765399999999997e+02, + -1.153199999999999e+02, -9.486730000000000e+02, -9.567130000000000e+02, -8.622940000000000e+02, -1.491675000000000e+03; - Mat R1 = Mat(3, 3); - R1 << -2.147969435011516e-01, -6.504746055378523e-01, -7.285225189470387e-01, - -8.724048025820075e-01, 4.631236449953824e-01, -1.562893146636694e-01, - 4.390582347416017e-01, 6.019960772268922e-01, -6.669547132369798e-01; + Mat R1 = Mat(3, 3); + R1 << -2.147969435011516e-01, -6.504746055378523e-01, -7.285225189470387e-01, -8.724048025820075e-01, 4.631236449953824e-01, + -1.562893146636694e-01, 4.390582347416017e-01, 6.019960772268922e-01, -6.669547132369798e-01; - Vec3 t1; - t1 << 5.835566436335433e-01, 5.556535697883296e-01, 8.118408025611844e-01; + Vec3 t1; + t1 << 5.835566436335433e-01, 5.556535697883296e-01, 8.118408025611844e-01; - const double f1 = 1.769320983964984e+03; + const double f1 = 1.769320983964984e+03; - Vec r1 = Vec(1); - r1 << -2.905907042217655e-01; + Vec r1 = Vec(1); + r1 << -2.905907042217655e-01; - resection::P5PfrModel m1(R1, t1, r1, f1); + resection::P5PfrModel m1(R1, t1, r1, f1); - Mat R2 = Mat(3, 3); - R2 << -5.147789719583958e-01, -2.194669622313594e-01, -8.287562141657801e-01, - -5.244123822170940e-01, -6.841333275746363e-01, 5.069055567648162e-01, - -6.782287692267632e-01, 6.955543419392871e-01, 2.370862585696515e-01; + Mat R2 = Mat(3, 3); + R2 << -5.147789719583958e-01, -2.194669622313594e-01, -8.287562141657801e-01, -5.244123822170940e-01, -6.841333275746363e-01, + 5.069055567648162e-01, -6.782287692267632e-01, 6.955543419392871e-01, 2.370862585696515e-01; - Vec3 t2; - t2 << 1.273608583413997e+00, 2.506813631576317e-01, 2.141229474304712e+00; + Vec3 t2; + t2 << 1.273608583413997e+00, 2.506813631576317e-01, 2.141229474304712e+00; - const double f2 = 2.687625629669230e+03; + const double f2 = 2.687625629669230e+03; - Vec r2 = Vec(1); - r2 << -3.992521126906051e+00; + Vec r2 = Vec(1); + r2 << -3.992521126906051e+00; - resection::P5PfrModel m2(R2, t2, r2, f2); + resection::P5PfrModel m2(R2, t2, r2, f2); - // SOLUTIONS ( EXPECTED VALUES ) - Vec e_r1 = Vec(3); - e_r1 << -2.767886578664726e-01, 1.084793023180692e-01, -2.249627924724677e-02; - Vec e_r2 = Vec(3); - e_r2 << -2.058719740812550e+00, 9.689085352978840e-01, -1.271067176405148e-01; + // SOLUTIONS ( EXPECTED VALUES ) + Vec e_r1 = Vec(3); + e_r1 << -2.767886578664726e-01, 1.084793023180692e-01, -2.249627924724677e-02; + Vec e_r2 = Vec(3); + e_r2 << -2.058719740812550e+00, 9.689085352978840e-01, -1.271067176405148e-01; - // PROCESS & TESTS ( ACTUAL VALUES ) - const double eps = 1e-4; - Mat pt2D_radius = pt2D.colwise().norm(); + // PROCESS & TESTS ( ACTUAL VALUES ) + const double eps = 1e-4; + Mat pt2D_radius = pt2D.colwise().norm(); - m1._r = m1.divisionToPolynomialModelDistortion((1 / f1) * pt2D_radius /*, pt2D_radius.maxCoeff()*/); - EXPECT_MATRIX_NEAR(m1._r, e_r1, eps); + m1._r = m1.divisionToPolynomialModelDistortion((1 / f1) * pt2D_radius /*, pt2D_radius.maxCoeff()*/); + EXPECT_MATRIX_NEAR(m1._r, e_r1, eps); - m2._r = m2.divisionToPolynomialModelDistortion((1 / f2) * pt2D_radius /*, pt2D_radius.maxCoeff()*/); - EXPECT_MATRIX_NEAR(m2._r, e_r2, eps); + m2._r = m2.divisionToPolynomialModelDistortion((1 / f2) * pt2D_radius /*, pt2D_radius.maxCoeff()*/); + EXPECT_MATRIX_NEAR(m2._r, e_r2, eps); } - -//BOOST_AUTO_TEST_CASE(Resection_P5Pfr_RandomRealExample) { +// BOOST_AUTO_TEST_CASE(Resection_P5Pfr_RandomRealExample) { // // -// // PROCESS -// std::vector models; -// resection::P5PfrSolver::Solve(pt2D, pt3D, models); -// std::sort(models.begin(), models.end(), sortM); +// // PROCESS +// std::vector models; +// resection::P5PfrSolver::Solve(pt2D, pt3D, models); +// std::sort(models.begin(), models.end(), sortM); // -// double eps = 1e-4; -//CHECK_SOLUTIONS(solutions, models, eps); -//} - +// double eps = 1e-4; +// CHECK_SOLUTIONS(solutions, models, eps); +// } diff --git a/src/aliceVision/multiview/resection/resectionKernel_test.cpp b/src/aliceVision/multiview/resection/resectionKernel_test.cpp index 62fd7cea62..533709ac37 100644 --- a/src/aliceVision/multiview/resection/resectionKernel_test.cpp +++ b/src/aliceVision/multiview/resection/resectionKernel_test.cpp @@ -22,38 +22,37 @@ using namespace aliceVision::multiview; BOOST_AUTO_TEST_CASE(Resection_Kernel_Multiview) { + const int nViews = 3; + const int nbPoints = 10; + const NViewDataSet d = + NRealisticCamerasRing(nViews, nbPoints, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K - const int nViews = 3; - const int nbPoints = 10; - const NViewDataSet d = NRealisticCamerasRing(nViews, nbPoints, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K + const int nResectionCameraIndex = 2; - const int nResectionCameraIndex = 2; + // Solve the problem and check that fitted value are good enough + { + Mat x = d._x[nResectionCameraIndex]; + Mat X = d._X; + resection::Resection6PKernel kernel(x, X); - // Solve the problem and check that fitted value are good enough - { - Mat x = d._x[nResectionCameraIndex]; - Mat X = d._X; - resection::Resection6PKernel kernel(x, X); + std::size_t samples_[6] = {0, 1, 2, 3, 4, 5}; + std::vector samples(samples_, samples_ + 6); + std::vector Ps; - std::size_t samples_[6]={0,1,2,3,4,5}; - std::vector samples(samples_,samples_+6); - std::vector Ps; + kernel.fit(samples, Ps); - kernel.fit(samples, Ps); + for (std::size_t i = 0; i < x.cols(); ++i) + { + BOOST_CHECK_SMALL(kernel.error(i, Ps.at(0)), 1e-8); + } - for (std::size_t i = 0; i < x.cols(); ++i) - { - BOOST_CHECK_SMALL(kernel.error(i, Ps.at(0)), 1e-8); - } - - BOOST_CHECK_EQUAL(1, Ps.size()); + BOOST_CHECK_EQUAL(1, Ps.size()); - // Check that Projection matrix is near to the GT : - Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() / d.P(nResectionCameraIndex).norm(); - Mat34 COMPUTED_ProjectionMatrix = Ps.at(0).getMatrix().array() / Ps.at(0).getMatrix().norm(); - EXPECT_MATRIX_NEAR(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix, 1e-8); - } + // Check that Projection matrix is near to the GT : + Mat34 GT_ProjectionMatrix = d.P(nResectionCameraIndex).array() / d.P(nResectionCameraIndex).norm(); + Mat34 COMPUTED_ProjectionMatrix = Ps.at(0).getMatrix().array() / Ps.at(0).getMatrix().norm(); + EXPECT_MATRIX_NEAR(GT_ProjectionMatrix, COMPUTED_ProjectionMatrix, 1e-8); + } } /* @@ -110,106 +109,100 @@ void CreateCameraSystem(const Mat3& KK, const Vec& X_distances, const Mat3& R_input, const Vec3& T_input, - Mat2X *x_camera, - Mat3X *X_world, - Mat3 *R_expected, - Vec3 *T_expected) + Mat2X* x_camera, + Mat3X* X_world, + Mat3* R_expected, + Vec3* T_expected) { - const auto num_points = x_image.cols(); + const auto num_points = x_image.cols(); - Mat3X x_unit_cam(3, num_points); - x_unit_cam = KK.inverse() * x_image; + Mat3X x_unit_cam(3, num_points); + x_unit_cam = KK.inverse() * x_image; - // Create normalized camera coordinates to be used as an input to the PnP - // function, instead of using NormalizeColumnVectors(&x_unit_cam). - *x_camera = x_unit_cam.block(0, 0, 2, num_points); + // Create normalized camera coordinates to be used as an input to the PnP + // function, instead of using NormalizeColumnVectors(&x_unit_cam). + *x_camera = x_unit_cam.block(0, 0, 2, num_points); - for (int i = 0; i < num_points; ++i) - { - x_unit_cam.col(i).normalize(); - } + for (int i = 0; i < num_points; ++i) + { + x_unit_cam.col(i).normalize(); + } - // Create the 3D points in the camera system. - Mat X_camera(3, num_points); - for (int i = 0; i < num_points; ++i) - { - X_camera.col(i) = X_distances(i) * x_unit_cam.col(i); - } + // Create the 3D points in the camera system. + Mat X_camera(3, num_points); + for (int i = 0; i < num_points; ++i) + { + X_camera.col(i) = X_distances(i) * x_unit_cam.col(i); + } - // Apply the transformation to the camera 3D points - Mat translation_matrix(3, num_points); - translation_matrix.row(0).setConstant(T_input(0)); - translation_matrix.row(1).setConstant(T_input(1)); - translation_matrix.row(2).setConstant(T_input(2)); + // Apply the transformation to the camera 3D points + Mat translation_matrix(3, num_points); + translation_matrix.row(0).setConstant(T_input(0)); + translation_matrix.row(1).setConstant(T_input(1)); + translation_matrix.row(2).setConstant(T_input(2)); - *X_world = R_input * X_camera + translation_matrix; + *X_world = R_input * X_camera + translation_matrix; - // Create the expected result for comparison. - *R_expected = R_input.transpose(); - *T_expected = *R_expected * ( - T_input); + // Create the expected result for comparison. + *R_expected = R_input.transpose(); + *T_expected = *R_expected * (-T_input); } +BOOST_AUTO_TEST_CASE(EuclideanResection_Points6AllRandomInput) +{ + makeRandomOperationsReproducible(); + + Mat3 KK; + KK << 2796, 0, 800, 0, 2796, 600, 0, 0, 1; + + // Create random image points for a 1600x1200 image. + int w = 1600; + int h = 1200; + int num_points = 6; + Mat3X x_image(3, num_points); + x_image.row(0) = w * Vec::Random(num_points).array().abs(); + x_image.row(1) = h * Vec::Random(num_points).array().abs(); + x_image.row(2).setOnes(); + + // Normalized camera coordinates to be used as an input to the PnP function. + Mat2X x_camera; + Vec X_distances = 100 * Vec::Random(num_points).array().abs(); + + // Create the random camera motion R and t that resection should recover. + Mat3 R_input; + R_input = Eigen::AngleAxisd(rand(), Eigen::Vector3d::UnitZ()) * Eigen::AngleAxisd(rand(), Eigen::Vector3d::UnitY()) * + Eigen::AngleAxisd(rand(), Eigen::Vector3d::UnitZ()); + + Vec3 T_input; + T_input = T_input.setRandom().array() * 100; + + // Create the camera system. + Mat3 R_expected; + Vec3 T_expected; + Mat3X X_world; + CreateCameraSystem(KK, x_image, X_distances, R_input, T_input, &x_camera, &X_world, &R_expected, &T_expected); -BOOST_AUTO_TEST_CASE(EuclideanResection_Points6AllRandomInput) { - makeRandomOperationsReproducible(); - - Mat3 KK; - KK << 2796, 0, 800, - 0 , 2796, 600, - 0, 0, 1; - - // Create random image points for a 1600x1200 image. - int w = 1600; - int h = 1200; - int num_points = 6; - Mat3X x_image(3, num_points); - x_image.row(0) = w * Vec::Random(num_points).array().abs(); - x_image.row(1) = h * Vec::Random(num_points).array().abs(); - x_image.row(2).setOnes(); - - // Normalized camera coordinates to be used as an input to the PnP function. - Mat2X x_camera; - Vec X_distances = 100 * Vec::Random(num_points).array().abs(); - - // Create the random camera motion R and t that resection should recover. - Mat3 R_input; - R_input = Eigen::AngleAxisd(rand(), Eigen::Vector3d::UnitZ()) - * Eigen::AngleAxisd(rand(), Eigen::Vector3d::UnitY()) - * Eigen::AngleAxisd(rand(), Eigen::Vector3d::UnitZ()); - - Vec3 T_input; - T_input = T_input.setRandom().array() * 100; - - // Create the camera system. - Mat3 R_expected; - Vec3 T_expected; - Mat3X X_world; - CreateCameraSystem(KK, x_image, X_distances, R_input, T_input, - &x_camera, &X_world, &R_expected, &T_expected); - - - { - using Kernel = resection::EPnPKernel; - Kernel kernel(x_image.block(0, 0, 2, 6), X_world, KK); - - std::size_t samples_[6]={0,1,2,3,4,5}; - std::vector samples(samples_, samples_+6); - std::vector Ps; - kernel.fit(samples, Ps); - - BOOST_CHECK_EQUAL(1, Ps.size()); - - bool bFound = false; - for (std::size_t i = 0; i < Ps.size(); ++i) { - Mat3 R_output; - Vec3 T_output; - Mat3 K; - KRt_from_P(Ps.at(i).getMatrix(), K, R_output, T_output); - if(NormLInfinity(T_output-T_expected) < 1e-8 && NormLInfinity(R_output-R_expected) < 1e-8) - bFound = true; + using Kernel = resection::EPnPKernel; + Kernel kernel(x_image.block(0, 0, 2, 6), X_world, KK); + + std::size_t samples_[6] = {0, 1, 2, 3, 4, 5}; + std::vector samples(samples_, samples_ + 6); + std::vector Ps; + kernel.fit(samples, Ps); + + BOOST_CHECK_EQUAL(1, Ps.size()); + + bool bFound = false; + for (std::size_t i = 0; i < Ps.size(); ++i) + { + Mat3 R_output; + Vec3 T_output; + Mat3 K; + KRt_from_P(Ps.at(i).getMatrix(), K, R_output, T_output); + if (NormLInfinity(T_output - T_expected) < 1e-8 && NormLInfinity(R_output - R_expected) < 1e-8) + bFound = true; + } + BOOST_CHECK(bFound); } - BOOST_CHECK(bFound); - } - } diff --git a/src/aliceVision/multiview/resection/resectionLORansac_test.cpp b/src/aliceVision/multiview/resection/resectionLORansac_test.cpp index b9dbae0339..867ebe2f4b 100644 --- a/src/aliceVision/multiview/resection/resectionLORansac_test.cpp +++ b/src/aliceVision/multiview/resection/resectionLORansac_test.cpp @@ -30,234 +30,221 @@ using namespace aliceVision; -bool refinePoseAsItShouldbe(const Mat & pt3D, - const Mat & pt2D, - const std::vector & vec_inliers, - camera::IntrinsicBase * intrinsics, +bool refinePoseAsItShouldbe(const Mat& pt3D, + const Mat& pt2D, + const std::vector& vec_inliers, + camera::IntrinsicBase* intrinsics, geometry::Pose3& pose, bool b_refine_pose, bool b_refine_intrinsic) { - using namespace sfm; - using namespace sfmData; - - // Setup a tiny SfM scene with the corresponding 2D-3D data - SfMData sfm_data; - // view - std::shared_ptr view = std::make_shared("", 0, 0, 0); - sfm_data.getViews().emplace(0, view); - // pose - sfm_data.setPose(*view, CameraPose(pose)); - // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) - sfm_data.getIntrinsics().emplace(0, std::shared_ptr(intrinsics, [](camera::IntrinsicBase*) - { - })); - // structure data (2D-3D correspondences) - const double unknownScale = 0.0; - for(size_t i = 0; i < vec_inliers.size(); ++i) - { - const size_t idx = vec_inliers[i]; - Landmark landmark; - landmark.X = pt3D.col(idx); - landmark.observations[0] = Observation(pt2D.col(idx), UndefinedIndexT, unknownScale); - sfm_data.getLandmarks()[i] = std::move(landmark); - } - - BundleAdjustmentCeres bundle_adjustment_obj; - BundleAdjustment::ERefineOptions refineOptions = BundleAdjustment::REFINE_NONE; - if(b_refine_pose) - refineOptions |= sfm::BundleAdjustment::REFINE_ROTATION | sfm::BundleAdjustment::REFINE_TRANSLATION; - if(b_refine_intrinsic) - refineOptions |= sfm::BundleAdjustment::REFINE_INTRINSICS_ALL; - const bool b_BA_Status = bundle_adjustment_obj.adjust(sfm_data, refineOptions); - if(b_BA_Status) - { - pose = sfm_data.getPose(*view).getTransform(); - } - return b_BA_Status; + using namespace sfm; + using namespace sfmData; + + // Setup a tiny SfM scene with the corresponding 2D-3D data + SfMData sfm_data; + // view + std::shared_ptr view = std::make_shared("", 0, 0, 0); + sfm_data.getViews().emplace(0, view); + // pose + sfm_data.setPose(*view, CameraPose(pose)); + // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) + sfm_data.getIntrinsics().emplace(0, std::shared_ptr(intrinsics, [](camera::IntrinsicBase*) {})); + // structure data (2D-3D correspondences) + const double unknownScale = 0.0; + for (size_t i = 0; i < vec_inliers.size(); ++i) + { + const size_t idx = vec_inliers[i]; + Landmark landmark; + landmark.X = pt3D.col(idx); + landmark.observations[0] = Observation(pt2D.col(idx), UndefinedIndexT, unknownScale); + sfm_data.getLandmarks()[i] = std::move(landmark); + } + + BundleAdjustmentCeres bundle_adjustment_obj; + BundleAdjustment::ERefineOptions refineOptions = BundleAdjustment::REFINE_NONE; + if (b_refine_pose) + refineOptions |= sfm::BundleAdjustment::REFINE_ROTATION | sfm::BundleAdjustment::REFINE_TRANSLATION; + if (b_refine_intrinsic) + refineOptions |= sfm::BundleAdjustment::REFINE_INTRINSICS_ALL; + const bool b_BA_Status = bundle_adjustment_obj.adjust(sfm_data, refineOptions); + if (b_BA_Status) + { + pose = sfm_data.getPose(*view).getTransform(); + } + return b_BA_Status; } // test LORansac repetability over the same test case BOOST_AUTO_TEST_CASE(P3P_Ransac_noisyFromImagePoints) { - std::mt19937 randomNumberGenerator; - - // camera and image parameters - const std::size_t WIDTH = 1600; - const std::size_t HEIGHT = 1200; - const std::size_t FOCAL = 2000; - const std::size_t PPX = WIDTH / 2; - const std::size_t PPY = HEIGHT / 2; - - // simulation parameters - // number of trials to run - const std::size_t NUMTRIALS = 100; - // create outliers - const bool withOutliers = true; - const double OUTLIERSRATIO = .15; - assert(OUTLIERSRATIO <= 1.0 && OUTLIERSRATIO >= .0); - // parameters for generating 3D points - // 3D points are generating selecting a point on the image and then picking a - // points on its associated projection ray with a distance in [MINDIST, MAXDIST] - const double MINDIST = 15; - const double MAXDIST = 35; - assert(MINDIST <= MAXDIST); - // number of points to generate - const std::size_t nbPoints = 50; - // noise level in pixels - const double gaussianNoiseLevel = 4.0; - // tolerance errors for test to pass - const double maxAngularError = 0.1; - const double maxBaselineError = 0.01; - - std::vector vec_outliers; - - std::mt19937 gen; - std::uniform_real_distribution realDist(0, 1.0); - - // generate a random RT transform to apply to the 3D points - const Mat3 Rgt = rotationXYZ(M_PI_2 * realDist(gen), M_PI_2 * realDist(gen), M_PI_2 * realDist(gen)); - const Vec3 Tgt = MINDIST * Vec3(realDist(gen), realDist(gen), realDist(gen)); - Mat3 Kgt; - Kgt << FOCAL, 0, PPX, - 0, FOCAL, PPY, - 0, 0, 1; - const Vec3 Cgt = -Rgt.transpose() * Tgt; - - // draw some random points in the camera image - Mat pts2D = Mat(2, nbPoints); - Mat pts3D = Mat(3, nbPoints); - Mat pts3DGt = Mat(3, nbPoints); - - for(std::size_t i = 0; i < nbPoints; ++i) - { - // for each point add a 3D point lying on the projection ray and having a - // distance [MINDIST, MAXDIST] - pts2D.col(i) = Vec2(WIDTH * realDist(gen), HEIGHT * realDist(gen)); - Vec3 direction; - direction << ((pts2D.col(i) - Vec2(PPX, PPY)) / FOCAL), 1; - direction /= direction.norm(); - direction *= (MINDIST + (MAXDIST - MINDIST) * realDist(gen)); - pts3DGt.col(i) = direction; - // multiply by the inverse of the pose - pts3D.col(i) = Rgt.transpose() * direction + -Rgt.transpose() * Tgt; - } - - // add some gaussian noise to the 2d points - for(std::size_t i = 0; i < nbPoints; ++i) - { - const double theta = realDist(gen)*2 * M_PI; - const double radius = gaussianNoiseLevel * realDist(gen); - pts2D.col(i) += radius * Vec2(std::cos(theta), std::sin(theta)); - } - - if(withOutliers) - { - // take a random sample to be used as outliers - const std::size_t NUMOUTLIERS = std::size_t(OUTLIERSRATIO*nbPoints); - vec_outliers.resize(NUMOUTLIERS); - std::iota(vec_outliers.begin(), vec_outliers.end(), 0); - std::sort(vec_outliers.begin(), vec_outliers.end()); - - // add outliers - for(const auto &idx : vec_outliers) + std::mt19937 randomNumberGenerator; + + // camera and image parameters + const std::size_t WIDTH = 1600; + const std::size_t HEIGHT = 1200; + const std::size_t FOCAL = 2000; + const std::size_t PPX = WIDTH / 2; + const std::size_t PPY = HEIGHT / 2; + + // simulation parameters + // number of trials to run + const std::size_t NUMTRIALS = 100; + // create outliers + const bool withOutliers = true; + const double OUTLIERSRATIO = .15; + assert(OUTLIERSRATIO <= 1.0 && OUTLIERSRATIO >= .0); + // parameters for generating 3D points + // 3D points are generating selecting a point on the image and then picking a + // points on its associated projection ray with a distance in [MINDIST, MAXDIST] + const double MINDIST = 15; + const double MAXDIST = 35; + assert(MINDIST <= MAXDIST); + // number of points to generate + const std::size_t nbPoints = 50; + // noise level in pixels + const double gaussianNoiseLevel = 4.0; + // tolerance errors for test to pass + const double maxAngularError = 0.1; + const double maxBaselineError = 0.01; + + std::vector vec_outliers; + + std::mt19937 gen; + std::uniform_real_distribution realDist(0, 1.0); + + // generate a random RT transform to apply to the 3D points + const Mat3 Rgt = rotationXYZ(M_PI_2 * realDist(gen), M_PI_2 * realDist(gen), M_PI_2 * realDist(gen)); + const Vec3 Tgt = MINDIST * Vec3(realDist(gen), realDist(gen), realDist(gen)); + Mat3 Kgt; + Kgt << FOCAL, 0, PPX, 0, FOCAL, PPY, 0, 0, 1; + const Vec3 Cgt = -Rgt.transpose() * Tgt; + + // draw some random points in the camera image + Mat pts2D = Mat(2, nbPoints); + Mat pts3D = Mat(3, nbPoints); + Mat pts3DGt = Mat(3, nbPoints); + + for (std::size_t i = 0; i < nbPoints; ++i) + { + // for each point add a 3D point lying on the projection ray and having a + // distance [MINDIST, MAXDIST] + pts2D.col(i) = Vec2(WIDTH * realDist(gen), HEIGHT * realDist(gen)); + Vec3 direction; + direction << ((pts2D.col(i) - Vec2(PPX, PPY)) / FOCAL), 1; + direction /= direction.norm(); + direction *= (MINDIST + (MAXDIST - MINDIST) * realDist(gen)); + pts3DGt.col(i) = direction; + // multiply by the inverse of the pose + pts3D.col(i) = Rgt.transpose() * direction + -Rgt.transpose() * Tgt; + } + + // add some gaussian noise to the 2d points + for (std::size_t i = 0; i < nbPoints; ++i) + { + const double theta = realDist(gen) * 2 * M_PI; + const double radius = gaussianNoiseLevel * realDist(gen); + pts2D.col(i) += radius * Vec2(std::cos(theta), std::sin(theta)); + } + + if (withOutliers) { - std::size_t iter = 0; - Vec2 pt = Vec2(WIDTH * realDist(gen), HEIGHT * realDist(gen)); - while( (pt-pts2D.col(idx)).norm() < 15*gaussianNoiseLevel) - { - pt = Vec2(WIDTH * realDist(gen), HEIGHT * realDist(gen)); - ++iter; - // safeguard against infinite loops - assert(iter > 1000 && "Unable to generate a random point, iterations excedeed!"); - } - pts2D.col(idx) = pt; + // take a random sample to be used as outliers + const std::size_t NUMOUTLIERS = std::size_t(OUTLIERSRATIO * nbPoints); + vec_outliers.resize(NUMOUTLIERS); + std::iota(vec_outliers.begin(), vec_outliers.end(), 0); + std::sort(vec_outliers.begin(), vec_outliers.end()); + + // add outliers + for (const auto& idx : vec_outliers) + { + std::size_t iter = 0; + Vec2 pt = Vec2(WIDTH * realDist(gen), HEIGHT * realDist(gen)); + while ((pt - pts2D.col(idx)).norm() < 15 * gaussianNoiseLevel) + { + pt = Vec2(WIDTH * realDist(gen), HEIGHT * realDist(gen)); + ++iter; + // safeguard against infinite loops + assert(iter > 1000 && "Unable to generate a random point, iterations excedeed!"); + } + pts2D.col(idx) = pt; + } } - } - - for(std::size_t trial = 0; trial < NUMTRIALS; ++trial) - { - ALICEVISION_LOG_DEBUG("Trial #" << trial); - typedef multiview::resection::P3PSolver SolverType; - typedef multiview::resection::Resection6PSolver SolverLSType; - - typedef aliceVision::multiview::ResectionKernel_K KernelType; - - // this is just to simplify and use image plane coordinates instead of camera - // (pixel) coordinates - Mat pts2Dnorm; - robustEstimation::applyTransformationToPoints(pts2D, Kgt.inverse(), &pts2Dnorm); - KernelType kernel(pts2Dnorm, pts3D, Mat3::Identity()); - - std::vector inliers; - const double threshold = 2*gaussianNoiseLevel; - const double normalizedThreshold = Square(threshold / FOCAL); - robustEstimation::ScoreEvaluator scorer(normalizedThreshold); - robustEstimation::Mat34Model model = robustEstimation::LO_RANSAC(kernel, scorer, randomNumberGenerator, &inliers); - Mat34 Pest = model.getMatrix(); - const std::size_t numInliersFound = inliers.size(); - const std::size_t numInliersExpected = nbPoints-vec_outliers.size(); - - BOOST_CHECK(numInliersFound > kernel.getMinimumNbRequiredSamples() *2.5); - - Mat3 Rest; - Mat3 Kest; - Vec3 Test; - KRt_from_P(Pest, Kest, Rest, Test); - - ALICEVISION_LOG_DEBUG("Est: Pest:\n" << Pest - << "\nRest:\n" << Rest - << "\nKest:\n" << Kest - << "\ntest:\n" << Test); - - ALICEVISION_LOG_DEBUG("Solution found with " << numInliersFound << " inliers"); - ALICEVISION_LOG_DEBUG("Expected number of inliers " << numInliersExpected); - - BOOST_CHECK_EQUAL(numInliersFound, numInliersExpected); - - const double angError = radianToDegree(getRotationMagnitude(Rgt * Rest.transpose())); - ALICEVISION_LOG_DEBUG("Angular error: " << angError); - ALICEVISION_LOG_DEBUG("Baseline error: " << (Test - Tgt).squaredNorm()); - - camera::Pinhole camera{0, 0, 1, 1, 0, 0}; - geometry::Pose3 pose = geometry::poseFromRT(Rest, Test); - refinePoseAsItShouldbe(pts3D, - pts2Dnorm, - inliers, - &camera, - pose, - true, - false ); - - const double angErrorRef = radianToDegree(getRotationMagnitude(Rgt * pose.rotation().transpose())); - const double baselineErrorRef = (Tgt - pose.translation()).squaredNorm(); - ALICEVISION_LOG_DEBUG("Final angular error #"< inters(nbPoints); - std::sort(inliers.begin(), inliers.end()); - auto it = std::set_intersection(inliers.begin(), inliers.end(), - vec_outliers.begin(), vec_outliers.end(), - inters.begin()); - inters.resize(it-inters.begin()); - if(inters.size() > 0) - ALICEVISION_LOG_WARNING("******* there are " << inters.size() << " outliers considered as inliers"); - BOOST_CHECK_EQUAL(inters.size(), 0); + ALICEVISION_LOG_DEBUG("Trial #" << trial); + typedef multiview::resection::P3PSolver SolverType; + typedef multiview::resection::Resection6PSolver SolverLSType; + + typedef aliceVision::multiview::ResectionKernel_K + KernelType; + + // this is just to simplify and use image plane coordinates instead of camera + // (pixel) coordinates + Mat pts2Dnorm; + robustEstimation::applyTransformationToPoints(pts2D, Kgt.inverse(), &pts2Dnorm); + KernelType kernel(pts2Dnorm, pts3D, Mat3::Identity()); + + std::vector inliers; + const double threshold = 2 * gaussianNoiseLevel; + const double normalizedThreshold = Square(threshold / FOCAL); + robustEstimation::ScoreEvaluator scorer(normalizedThreshold); + robustEstimation::Mat34Model model = robustEstimation::LO_RANSAC(kernel, scorer, randomNumberGenerator, &inliers); + Mat34 Pest = model.getMatrix(); + const std::size_t numInliersFound = inliers.size(); + const std::size_t numInliersExpected = nbPoints - vec_outliers.size(); + + BOOST_CHECK(numInliersFound > kernel.getMinimumNbRequiredSamples() * 2.5); + + Mat3 Rest; + Mat3 Kest; + Vec3 Test; + KRt_from_P(Pest, Kest, Rest, Test); + + ALICEVISION_LOG_DEBUG("Est: Pest:\n" << Pest << "\nRest:\n" << Rest << "\nKest:\n" << Kest << "\ntest:\n" << Test); + + ALICEVISION_LOG_DEBUG("Solution found with " << numInliersFound << " inliers"); + ALICEVISION_LOG_DEBUG("Expected number of inliers " << numInliersExpected); + + BOOST_CHECK_EQUAL(numInliersFound, numInliersExpected); + + const double angError = radianToDegree(getRotationMagnitude(Rgt * Rest.transpose())); + ALICEVISION_LOG_DEBUG("Angular error: " << angError); + ALICEVISION_LOG_DEBUG("Baseline error: " << (Test - Tgt).squaredNorm()); + + camera::Pinhole camera{0, 0, 1, 1, 0, 0}; + geometry::Pose3 pose = geometry::poseFromRT(Rest, Test); + refinePoseAsItShouldbe(pts3D, pts2Dnorm, inliers, &camera, pose, true, false); + + const double angErrorRef = radianToDegree(getRotationMagnitude(Rgt * pose.rotation().transpose())); + const double baselineErrorRef = (Tgt - pose.translation()).squaredNorm(); + ALICEVISION_LOG_DEBUG("Final angular error #" << trial << " : " << angErrorRef); + ALICEVISION_LOG_DEBUG("Final baseline error #" << trial << " : " << baselineErrorRef); + + BOOST_CHECK_SMALL(angErrorRef, maxAngularError); + BOOST_CHECK_SMALL(baselineErrorRef, maxBaselineError); + + ALICEVISION_LOG_DEBUG("Refined pose:\n" + << "\nEst: Rest:\n" + << pose.rotation() << "\nCest:\n" + << pose.center() << "\nTest:\n" + << pose.translation()); + + if (withOutliers) + { + // test if inliers found and outliers GT have a empty intersection + std::vector inters(nbPoints); + std::sort(inliers.begin(), inliers.end()); + auto it = std::set_intersection(inliers.begin(), inliers.end(), vec_outliers.begin(), vec_outliers.end(), inters.begin()); + inters.resize(it - inters.begin()); + if (inters.size() > 0) + ALICEVISION_LOG_WARNING("******* there are " << inters.size() << " outliers considered as inliers"); + BOOST_CHECK_EQUAL(inters.size(), 0); + } } - } } diff --git a/src/aliceVision/multiview/rotationAveraging/common.hpp b/src/aliceVision/multiview/rotationAveraging/common.hpp index ac95d6362d..55395b3815 100644 --- a/src/aliceVision/multiview/rotationAveraging/common.hpp +++ b/src/aliceVision/multiview/rotationAveraging/common.hpp @@ -13,40 +13,44 @@ #include #include -namespace aliceVision { -namespace rotationAveraging { +namespace aliceVision { +namespace rotationAveraging { /// Representation of weighted relative rotations data between two poses -struct RelativeRotation { - IndexT i, j; // pose's indices - Mat3 Rij; // pose's relative rotation - float weight; - - RelativeRotation(IndexT i_=0, IndexT j_=0, const Mat3 & Rij_=Mat3::Identity(), float weight_=1.0f): - i(i_), j(j_), Rij(Rij_), weight(weight_) - {} +struct RelativeRotation +{ + IndexT i, j; // pose's indices + Mat3 Rij; // pose's relative rotation + float weight; + + RelativeRotation(IndexT i_ = 0, IndexT j_ = 0, const Mat3& Rij_ = Mat3::Identity(), float weight_ = 1.0f) + : i(i_), + j(j_), + Rij(Rij_), + weight(weight_) + {} }; typedef std::vector RelativeRotations; typedef std::map RelativeRotationsMap; /// List the pairs used by the relative rotations -inline PairSet getPairs(const RelativeRotations & relRots) +inline PairSet getPairs(const RelativeRotations& relRots) { - PairSet pairs; - for(RelativeRotations::const_iterator it = relRots.begin(); it != relRots.end(); ++it) - pairs.insert(std::make_pair(it->i, it->j)); - return pairs; + PairSet pairs; + for (RelativeRotations::const_iterator it = relRots.begin(); it != relRots.end(); ++it) + pairs.insert(std::make_pair(it->i, it->j)); + return pairs; } /// Convert a relative motion iterable sequence to RelativeRotation indexed by pairs -inline RelativeRotationsMap getMap(const RelativeRotations & relRots) +inline RelativeRotationsMap getMap(const RelativeRotations& relRots) { - RelativeRotationsMap map_rots; - for(RelativeRotations::const_iterator it = relRots.begin(); it != relRots.end(); ++it) - map_rots[std::make_pair(it->i, it->j)] = *it; - return map_rots; + RelativeRotationsMap map_rots; + for (RelativeRotations::const_iterator it = relRots.begin(); it != relRots.end(); ++it) + map_rots[std::make_pair(it->i, it->j)] = *it; + return map_rots; } -} // namespace rotationAveraging -} // namespace aliceVision +} // namespace rotationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/rotationAveraging/l1.cpp b/src/aliceVision/multiview/rotationAveraging/l1.cpp index 8a95c247ce..af4759ef4f 100644 --- a/src/aliceVision/multiview/rotationAveraging/l1.cpp +++ b/src/aliceVision/multiview/rotationAveraging/l1.cpp @@ -9,24 +9,23 @@ #include #ifdef ALICEVISION_ROTATION_AVERAGING_WITH_BOOST -#include -#include -#include -#include -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include + #include + #include + #include + #include #else -#include "lemon/list_graph.h" -#include "lemon/kruskal.h" -#include "lemon/adaptors.h" -#include "lemon/dfs.h" -#include "lemon/path.h" + #include "lemon/list_graph.h" + #include "lemon/kruskal.h" + #include "lemon/adaptors.h" + #include "lemon/dfs.h" + #include "lemon/path.h" #endif - #include "ceres/ceres.h" #include "ceres/rotation.h" @@ -40,9 +39,9 @@ using namespace boost; using namespace lemon; #endif -namespace aliceVision { -namespace rotationAveraging { -namespace l1 { +namespace aliceVision { +namespace rotationAveraging { +namespace l1 { // Minimum l1 error approximation: // @@ -61,232 +60,254 @@ namespace l1 { // (PA) can be recast as an LP. // template -inline bool TRobustRegressionL1PD( - const MATRIX_TYPE& A, - const Eigen::Matrix& y, - Eigen::Matrix& xp, - REAL pdtol, unsigned pdmaxiter) +inline bool TRobustRegressionL1PD(const MATRIX_TYPE& A, + const Eigen::Matrix& y, + Eigen::Matrix& xp, + REAL pdtol, + unsigned pdmaxiter) { - typedef Eigen::Matrix Matrix; - typedef Eigen::Matrix Vector; - const unsigned M = (unsigned)y.size(); - const unsigned N = (unsigned)xp.size(); - assert(A.rows() == M && A.cols() == N); - - const REAL alpha(0.01); - const REAL beta(0.5); - const REAL mu(10); - - Vector x(xp); - Vector Ax(A*x); - Vector tmpM1(y-Ax); - Vector tmpM2(-tmpM1); - Vector tmpM3(tmpM1.cwiseAbs()), tmpM4(M); - Vector u = (tmpM3*REAL(0.95)).array() + tmpM3.maxCoeff()*REAL(0.10); - Vector fu1 = tmpM2-u; - Vector fu2 = tmpM1-u; - - Vector lamu1(M), lamu2(M); - for (unsigned i=0; i Matrix; + typedef Eigen::Matrix Vector; + const unsigned M = (unsigned)y.size(); + const unsigned N = (unsigned)xp.size(); + assert(A.rows() == M && A.cols() == N); + + const REAL alpha(0.01); + const REAL beta(0.5); + const REAL mu(10); + + Vector x(xp); + Vector Ax(A * x); + Vector tmpM1(y - Ax); + Vector tmpM2(-tmpM1); + Vector tmpM3(tmpM1.cwiseAbs()), tmpM4(M); + Vector u = (tmpM3 * REAL(0.95)).array() + tmpM3.maxCoeff() * REAL(0.10); + Vector fu1 = tmpM2 - u; + Vector fu2 = tmpM1 - u; + + Vector lamu1(M), lamu2(M); + for (unsigned i = 0; i < M; ++i) + { + lamu1(i) = -1.0 / fu1(i); + lamu2(i) = -1.0 / fu2(i); } + const MATRIX_TYPE At(A.transpose()); + Vector Atv(At * (lamu1 - lamu2)); + REAL AtvNormSq = Atv.squaredNorm(); + Vector rdual((-lamu1 - lamu2).array() + REAL(1)); + REAL rdualNormSq = rdual.squaredNorm(); + + Vector w2(M), sig1(M), sig2(M), sigx(M), dx(N), up(N), Atdv(N); + Vector Axp(M), Atvp(M); + Vector &Adx(sigx), &du(w2), &w1p(dx); + Matrix H11p(N, N); + Vector &dlamu1(tmpM3), &dlamu2(tmpM4); + for (unsigned pditer = 0; pditer < pdmaxiter; ++pditer) + { + // surrogate duality gap + const REAL sdg(-(fu1.dot(lamu1) + fu2.dot(lamu2))); + if (sdg < pdtol) + break; + const REAL tau(mu * 2 * M / sdg); + const REAL inv_tau = REAL(-1) / tau; + tmpM1 = (-lamu1.cwiseProduct(fu1)).array() + inv_tau; + tmpM2 = (-lamu2.cwiseProduct(fu2)).array() + inv_tau; + const REAL resnorm = sqrt(AtvNormSq + rdualNormSq + tmpM1.squaredNorm() + tmpM2.squaredNorm()); + + for (unsigned i = 0; i < M; ++i) + { + REAL& tmpM3i = tmpM3(i); + tmpM3i = inv_tau / fu1(i); + REAL& tmpM4i = tmpM4(i); + tmpM4i = inv_tau / fu2(i); + w2(i) = tmpM3i + tmpM4i - REAL(1); + } - tmpM1 = lamu1.cwiseQuotient(fu1); - tmpM2 = lamu2.cwiseQuotient(fu2); - sig1 = -tmpM1 - tmpM2; - sig2 = tmpM1 - tmpM2; - sigx = sig1 - sig2.cwiseAbs2().cwiseQuotient(sig1); - - H11p = At*(Eigen::DiagonalMatrix(sigx)*A); - w1p = At*(tmpM4 - tmpM3 - (sig2.cwiseQuotient(sig1).cwiseProduct(w2))); - - // optimized solver as A is positive definite and symmetric - dx = H11p.ldlt().solve(w1p); - - Adx = A*dx; - - du = (w2 - sig2.cwiseProduct(Adx)).cwiseQuotient(sig1); - - dlamu1 = -tmpM1.cwiseProduct(Adx-du) - lamu1 + tmpM3; - dlamu2 = tmpM2.cwiseProduct(Adx+du) - lamu2 + tmpM4; - Atdv = At*(dlamu1-dlamu2); - - // make sure that the step is feasible: keeps lamu1,lamu2 > 0, fu1,fu2 < 0 - REAL s(1); - for (unsigned i=0; i tmp) - s = tmp; - } - REAL& dlamu2i = dlamu2(i); - if (dlamu2i < 0) { - const REAL tmp = -lamu2(i)/dlamu2i; - if (s > tmp) - s = tmp; - } - } - for (unsigned i=0; i 0) { - const REAL tmp = -fu1(i)/Adx_du; - if (s > tmp) - s = tmp; - } - const REAL _Adx_du = -Adxi-dui; - if (_Adx_du > 0) { - const REAL tmp = -fu2(i)/_Adx_du; - if (s > tmp) - s = tmp; - } + tmpM1 = lamu1.cwiseQuotient(fu1); + tmpM2 = lamu2.cwiseQuotient(fu2); + sig1 = -tmpM1 - tmpM2; + sig2 = tmpM1 - tmpM2; + sigx = sig1 - sig2.cwiseAbs2().cwiseQuotient(sig1); + + H11p = At * (Eigen::DiagonalMatrix(sigx) * A); + w1p = At * (tmpM4 - tmpM3 - (sig2.cwiseQuotient(sig1).cwiseProduct(w2))); + + // optimized solver as A is positive definite and symmetric + dx = H11p.ldlt().solve(w1p); + + Adx = A * dx; + + du = (w2 - sig2.cwiseProduct(Adx)).cwiseQuotient(sig1); + + dlamu1 = -tmpM1.cwiseProduct(Adx - du) - lamu1 + tmpM3; + dlamu2 = tmpM2.cwiseProduct(Adx + du) - lamu2 + tmpM4; + Atdv = At * (dlamu1 - dlamu2); + + // make sure that the step is feasible: keeps lamu1,lamu2 > 0, fu1,fu2 < 0 + REAL s(1); + for (unsigned i = 0; i < M; ++i) + { + REAL& dlamu1i = dlamu1(i); + if (dlamu1i < 0) + { + const REAL tmp = -lamu1(i) / dlamu1i; + if (s > tmp) + s = tmp; + } + REAL& dlamu2i = dlamu2(i); + if (dlamu2i < 0) + { + const REAL tmp = -lamu2(i) / dlamu2i; + if (s > tmp) + s = tmp; + } + } + for (unsigned i = 0; i < M; ++i) + { + REAL& Adxi = Adx(i); + REAL& dui = du(i); + const REAL Adx_du = Adxi - dui; + if (Adx_du > 0) + { + const REAL tmp = -fu1(i) / Adx_du; + if (s > tmp) + s = tmp; + } + const REAL _Adx_du = -Adxi - dui; + if (_Adx_du > 0) + { + const REAL tmp = -fu2(i) / _Adx_du; + if (s > tmp) + s = tmp; + } + } + s *= REAL(0.99); + + // backtrack + lamu1 += s * dlamu1; + lamu2 += s * dlamu2; + rdual = (-lamu1 - lamu2).array() + REAL(1); + rdualNormSq = rdual.squaredNorm(); + bool suffdec = false; + unsigned backiter = 0; + do + { + xp = x + s * dx; + up = u + s * du; + Axp = Ax + s * Adx; + Atvp = Atv + s * Atdv; + fu1 = Axp - y - up; + fu2 = -Axp + y - up; + AtvNormSq = Atvp.squaredNorm(); + tmpM1 = (-lamu1.cwiseProduct(fu1)).array() + inv_tau; + tmpM2 = (-lamu2.cwiseProduct(fu2)).array() + inv_tau; + const REAL newresnorm = sqrt(AtvNormSq + rdualNormSq + tmpM1.squaredNorm() + tmpM2.squaredNorm()); + suffdec = (newresnorm <= (REAL(1) - alpha * s) * resnorm); + s = beta * s; + if (++backiter > 32) + { + //("error: stuck backtracking, returning last iterate"); // see Section 4 of notes for more information + xp.swap(x); + return false; + } + } while (!suffdec); + + // next iteration + x.swap(xp); + u.swap(up); + Ax.swap(Axp); + Atv.swap(Atvp); } - s *= REAL(0.99); - - // backtrack - lamu1 += s*dlamu1; lamu2 += s*dlamu2; - rdual = (-lamu1-lamu2).array() + REAL(1); - rdualNormSq = rdual.squaredNorm(); - bool suffdec = false; - unsigned backiter = 0; - do { - xp = x + s*dx; up = u + s*du; - Axp = Ax + s*Adx; Atvp = Atv + s*Atdv; - fu1 = Axp - y - up; fu2 = -Axp + y - up; - AtvNormSq = Atvp.squaredNorm(); - tmpM1 = (-lamu1.cwiseProduct(fu1)).array() + inv_tau; - tmpM2 = (-lamu2.cwiseProduct(fu2)).array() + inv_tau; - const REAL newresnorm = sqrt(AtvNormSq + rdualNormSq + tmpM1.squaredNorm() + tmpM2.squaredNorm()); - suffdec = (newresnorm <= (REAL(1)-alpha*s)*resnorm); - s = beta*s; - if (++backiter > 32) { - //("error: stuck backtracking, returning last iterate"); // see Section 4 of notes for more information - xp.swap(x); - return false; - } - } while (!suffdec); - - // next iteration - x.swap(xp); u.swap(up); - Ax.swap(Axp); Atv.swap(Atvp); - } - return true; + return true; } -bool RobustRegressionL1PD( - const Eigen::Matrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL pdtol, unsigned pdmaxiter) +bool RobustRegressionL1PD(const Eigen::Matrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL pdtol, + unsigned pdmaxiter) { - return TRobustRegressionL1PD(A, b, x, pdtol, pdmaxiter); + return TRobustRegressionL1PD(A, b, x, pdtol, pdmaxiter); } -bool RobustRegressionL1PD( - const Eigen::SparseMatrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL pdtol, unsigned pdmaxiter) +bool RobustRegressionL1PD(const Eigen::SparseMatrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL pdtol, + unsigned pdmaxiter) { - return TRobustRegressionL1PD(A, b, x, pdtol, pdmaxiter); + return TRobustRegressionL1PD(A, b, x, pdtol, pdmaxiter); } /*----------------------------------------------------------------*/ // Iteratively Re-weighted Least Squares (IRLS) implementation template -inline bool TIterativelyReweightedLeastSquares( - const MATRIX_TYPE& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL sigma, REAL eps) +inline bool TIterativelyReweightedLeastSquares(const MATRIX_TYPE& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL sigma, + REAL eps) { - typedef Eigen::Matrix Matrix; - typedef Eigen::Matrix Vector; - const unsigned m = (unsigned)b.size(); - const unsigned n = (unsigned)x.size(); - assert(A.rows() == m && A.cols() == n); - - // iterate optimization till the desired precision is reached - Vector xp(n), e(m); - const REAL sigmaSq(Square(sigma)); - unsigned iter = 0; - REAL delta = std::numeric_limits::max(), deltap; - do { - xp = x; - // compute error vector - e = A*x-b; - // compute robust errors using the Huber-like loss function - for (unsigned i=0; i solver(AtF*A); // compute the Cholesky decomposition - if (solver.info() != Eigen::Success) { - ALICEVISION_LOG_WARNING("error: decomposing linear system failed"); - return false; - } - x = solver.solve(AtF*b); - if (solver.info() != Eigen::Success) { - ALICEVISION_LOG_WARNING("error: solving linear system failed"); - return false; - } - if (++iter > 32) - break; - deltap = delta; delta = (xp-x).norm(); - } while (delta > eps && (deltap-delta)/delta > 1e-2); - return true; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + const unsigned m = (unsigned)b.size(); + const unsigned n = (unsigned)x.size(); + assert(A.rows() == m && A.cols() == n); + + // iterate optimization till the desired precision is reached + Vector xp(n), e(m); + const REAL sigmaSq(Square(sigma)); + unsigned iter = 0; + REAL delta = std::numeric_limits::max(), deltap; + do + { + xp = x; + // compute error vector + e = A * x - b; + // compute robust errors using the Huber-like loss function + for (unsigned i = 0; i < m; ++i) + { + REAL& err = e(i); + const REAL errSq(Square(err)); + err = sigmaSq / (errSq + sigmaSq); + } + // solve the linear system using l2 norm + const MATRIX_TYPE AtF(A.transpose() * e.asDiagonal()); + const Eigen::LDLT solver(AtF * A); // compute the Cholesky decomposition + if (solver.info() != Eigen::Success) + { + ALICEVISION_LOG_WARNING("error: decomposing linear system failed"); + return false; + } + x = solver.solve(AtF * b); + if (solver.info() != Eigen::Success) + { + ALICEVISION_LOG_WARNING("error: solving linear system failed"); + return false; + } + if (++iter > 32) + break; + deltap = delta; + delta = (xp - x).norm(); + } while (delta > eps && (deltap - delta) / delta > 1e-2); + return true; } -bool IterativelyReweightedLeastSquares( - const Eigen::Matrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL sigma, REAL eps) +bool IterativelyReweightedLeastSquares(const Eigen::Matrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL sigma, + REAL eps) { - return TIterativelyReweightedLeastSquares(A, b, x, sigma, eps); + return TIterativelyReweightedLeastSquares(A, b, x, sigma, eps); } -bool IterativelyReweightedLeastSquares( - const Eigen::SparseMatrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL sigma, REAL eps) +bool IterativelyReweightedLeastSquares(const Eigen::SparseMatrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL sigma, + REAL eps) { - return TIterativelyReweightedLeastSquares(A, b, x, sigma, eps); + return TIterativelyReweightedLeastSquares(A, b, x, sigma, eps); } ///////////////////////// @@ -299,21 +320,19 @@ bool IterativelyReweightedLeastSquares( // upper-bound threshold = median+trust_region // lower-bound threshold = median-trust_region template -inline std::pair -ComputeX84Threshold(const TYPE* const values, size_t size, TYPE mul=TYPE(5.2)) +inline std::pair ComputeX84Threshold(const TYPE* const values, size_t size, TYPE mul = TYPE(5.2)) { - assert(size > 0); - typename std::vector data(values, values+size); - typename std::vector::iterator mid = data.begin() + size / 2; - std::nth_element(data.begin(), mid, data.end()); - const TYPE median = *mid; - // threshold = 5.2 * MEDIAN(ABS(values-median)); - for (size_t i=0; i 0); + typename std::vector data(values, values + size); + typename std::vector::iterator mid = data.begin() + size / 2; + std::nth_element(data.begin(), mid, data.end()); + const TYPE median = *mid; + // threshold = 5.2 * MEDIAN(ABS(values-median)); + for (size_t i = 0; i < size; ++i) + data[i] = std::abs(values[i] - median); + std::nth_element(data.begin(), mid, data.end()); + return std::make_pair(median, mul * (*mid)); +} // ComputeX84Threshold ///////////////////////// @@ -321,16 +340,21 @@ typedef aliceVision::Mat3 Matrix3x3; typedef std::vector IndexArr; // find the shortest cycle for the given graph and starting vertex -struct Node { - typedef IndexArr InternalType; - InternalType edges; // array of vertex indices +struct Node +{ + typedef IndexArr InternalType; + InternalType edges; // array of vertex indices }; typedef std::vector NodeArr; -struct Link { - size_t ID; // node index - size_t parentID;// parent link - inline Link(size_t _ID=0, size_t _parentID=0) : ID(_ID), parentID(_parentID) {} +struct Link +{ + size_t ID; // node index + size_t parentID; // parent link + inline Link(size_t _ID = 0, size_t _parentID = 0) + : ID(_ID), + parentID(_parentID) + {} }; typedef std::queue LinkQue; @@ -344,370 +368,366 @@ typedef boost::graph_traits::edge_iterator edge_iter; typedef lemon::ListGraph graph_t; typedef graph_t::EdgeMap map_EdgeMap; #endif -typedef std::map, Matrix3x3> MapEdgeIJ2R; +typedef std::map, Matrix3x3> MapEdgeIJ2R; // Look for the maximum spanning tree along the graph of relative rotations // since we look for the maximum spanning tree using a minimum spanning tree algorithm // weight are negated. size_t FindMaximumSpanningTree(const RelativeRotations& RelRs, graph_t& g, MapEdgeIJ2R& mapIJ2R, NodeArr& minGraph) { - assert(!RelRs.empty()); + assert(!RelRs.empty()); #ifdef ALICEVISION_ROTATION_AVERAGING_WITH_BOOST - for (size_t p = 0; p < RelRs.size(); ++p) { - const RelativeRotation& relR = RelRs[p]; - boost::add_edge(relR.i, relR.j, - relR.weight, g); - mapIJ2R[std::make_pair(relR.i, relR.j)] = relR.Rij; - mapIJ2R[std::make_pair(relR.j, relR.i)] = relR.Rij.transpose(); - } - // find the minimum spanning tree - const size_t nViews = boost::num_vertices(g); - minGraph.resize(nViews); - std::vector spanningTree; - boost::kruskal_minimum_spanning_tree(g, std::back_inserter(spanningTree)); - for (std::vector::const_iterator ei=spanningTree.begin(); ei!=spanningTree.end(); ++ei) { - const edge_t& edge = *ei; - minGraph[edge.m_source].edges.push_back(edge.m_target); - minGraph[edge.m_target].edges.push_back(edge.m_source); - } - const size_t nEdges = spanningTree.size(); - return nEdges; + for (size_t p = 0; p < RelRs.size(); ++p) + { + const RelativeRotation& relR = RelRs[p]; + boost::add_edge(relR.i, relR.j, -relR.weight, g); + mapIJ2R[std::make_pair(relR.i, relR.j)] = relR.Rij; + mapIJ2R[std::make_pair(relR.j, relR.i)] = relR.Rij.transpose(); + } + // find the minimum spanning tree + const size_t nViews = boost::num_vertices(g); + minGraph.resize(nViews); + std::vector spanningTree; + boost::kruskal_minimum_spanning_tree(g, std::back_inserter(spanningTree)); + for (std::vector::const_iterator ei = spanningTree.begin(); ei != spanningTree.end(); ++ei) + { + const edge_t& edge = *ei; + minGraph[edge.m_source].edges.push_back(edge.m_target); + minGraph[edge.m_target].edges.push_back(edge.m_source); + } + const size_t nEdges = spanningTree.size(); + return nEdges; #else - //A-- Compute the number of node we need - std::set setNodes; - for (size_t p = 0; p < RelRs.size(); ++p) { - const RelativeRotation& relR = RelRs[p]; - setNodes.insert(relR.i); - setNodes.insert(relR.j); - } - - //B-- Create a node graph for each element of the set - typedef std::map map_Size_t_Node; - map_Size_t_Node map_size_t_to_node; - for (std::set::const_iterator iter = setNodes.begin(); - iter != setNodes.end(); - ++iter) - { - map_size_t_to_node[*iter] = g.addNode(); - } - - //C-- Create a graph from RelRs with weighted edges - map_EdgeMap map_edgeMap(g); - for (size_t p = 0; p < RelRs.size(); ++p) { - const RelativeRotation& relR = RelRs[p]; - mapIJ2R[std::make_pair(relR.i, relR.j)] = relR.Rij; - mapIJ2R[std::make_pair(relR.j, relR.i)] = relR.Rij.transpose(); - - // add edge to the graph - graph_t::Edge edge = g.addEdge(map_size_t_to_node[relR.i], map_size_t_to_node[relR.j]); - map_edgeMap[ edge ] = - relR.weight; - } - - //D-- Compute the MST of the graph - std::vector tree_edge_vec; - lemon::kruskal(g, map_edgeMap, std::back_inserter(tree_edge_vec)); - - const size_t nViews = lemon::countNodes(g); - minGraph.resize(nViews); - - //E-- Export compute MST - for(size_t i= 0 ; i < tree_edge_vec.size(); i++) - { - minGraph[g.id(g.u(tree_edge_vec[i]))].edges.push_back(g.id(g.v(tree_edge_vec[i]))); - minGraph[g.id(g.v(tree_edge_vec[i]))].edges.push_back(g.id(g.u(tree_edge_vec[i]))); - } - return tree_edge_vec.size(); + // A-- Compute the number of node we need + std::set setNodes; + for (size_t p = 0; p < RelRs.size(); ++p) + { + const RelativeRotation& relR = RelRs[p]; + setNodes.insert(relR.i); + setNodes.insert(relR.j); + } + + // B-- Create a node graph for each element of the set + typedef std::map map_Size_t_Node; + map_Size_t_Node map_size_t_to_node; + for (std::set::const_iterator iter = setNodes.begin(); iter != setNodes.end(); ++iter) + { + map_size_t_to_node[*iter] = g.addNode(); + } + + // C-- Create a graph from RelRs with weighted edges + map_EdgeMap map_edgeMap(g); + for (size_t p = 0; p < RelRs.size(); ++p) + { + const RelativeRotation& relR = RelRs[p]; + mapIJ2R[std::make_pair(relR.i, relR.j)] = relR.Rij; + mapIJ2R[std::make_pair(relR.j, relR.i)] = relR.Rij.transpose(); + + // add edge to the graph + graph_t::Edge edge = g.addEdge(map_size_t_to_node[relR.i], map_size_t_to_node[relR.j]); + map_edgeMap[edge] = -relR.weight; + } + + // D-- Compute the MST of the graph + std::vector tree_edge_vec; + lemon::kruskal(g, map_edgeMap, std::back_inserter(tree_edge_vec)); + + const size_t nViews = lemon::countNodes(g); + minGraph.resize(nViews); + + // E-- Export compute MST + for (size_t i = 0; i < tree_edge_vec.size(); i++) + { + minGraph[g.id(g.u(tree_edge_vec[i]))].edges.push_back(g.id(g.v(tree_edge_vec[i]))); + minGraph[g.id(g.v(tree_edge_vec[i]))].edges.push_back(g.id(g.u(tree_edge_vec[i]))); + } + return tree_edge_vec.size(); #endif } //---------------------------------------------------------------- - // Filter the given relative rotations using the known global rotations // returns the number of inliers -unsigned int FilterRelativeRotations( - const RelativeRotations& RelRs, - const Matrix3x3Arr& Rs, - float threshold, - std::vector * vec_inliers) +unsigned int FilterRelativeRotations(const RelativeRotations& RelRs, const Matrix3x3Arr& Rs, float threshold, std::vector* vec_inliers) { - assert(!RelRs.empty() && !Rs.empty()); - assert(threshold >= 0); - // compute errors for each relative rotation - std::vector errors(RelRs.size()); - for(int r= 0; r res = ComputeX84Threshold(&errors[0], errors.size()); - threshold = res.first+res.second; - } - if (vec_inliers) { - vec_inliers->resize(RelRs.size()); - } - // mark outliers - unsigned int nInliers = 0; - for(int r=0; r= 0); + // compute errors for each relative rotation + std::vector errors(RelRs.size()); + for (int r = 0; r < RelRs.size(); ++r) + { + const RelativeRotation& relR = RelRs[r]; + const Matrix3x3& Ri = Rs[relR.i]; + const Matrix3x3& Rj = Rs[relR.j]; + const Matrix3x3& Rij = relR.Rij; + const Mat3 eRij(Rj.transpose() * Rij * Ri); + const aliceVision::Vec3 erij; + ceres::RotationMatrixToAngleAxis((const double*)eRij.data(), (double*)erij.data()); + errors[r] = (float)erij.norm(); + } + if (threshold == 0) + { + // estimate threshold + const std::pair res = ComputeX84Threshold(&errors[0], errors.size()); + threshold = res.first + res.second; + } if (vec_inliers) - (*vec_inliers)[r] = bInlier; - if (bInlier) - ++nInliers; - } - return nInliers; -} // FilterRelativeRotations + { + vec_inliers->resize(RelRs.size()); + } + // mark outliers + unsigned int nInliers = 0; + for (int r = 0; r < errors.size(); ++r) + { + bool bInlier = (errors[r] < threshold); + if (vec_inliers) + (*vec_inliers)[r] = bInlier; + if (bInlier) + ++nInliers; + } + return nInliers; +} // FilterRelativeRotations //---------------------------------------------------------------- - -REAL RelRotationAvgError(const RelativeRotations& RelRs, const Matrix3x3Arr& Rs, REAL* pMin=nullptr, REAL* pMax=nullptr) +REAL RelRotationAvgError(const RelativeRotations& RelRs, const Matrix3x3Arr& Rs, REAL* pMin = nullptr, REAL* pMax = nullptr) { #ifdef ALICEVISION_ROTATION_AVERAGING_WITH_BOOST - boost::accumulators::accumulator_set > acc; - - for(int i=0; i < RelRs.size(); ++i) { - const RelativeRotation& relR = RelRs[i]; - acc(aliceVision::FrobeniusNorm(relR.Rij - (Rs[relR.j]*Rs[relR.i].transpose()))); - } - if (pMin) - *pMin = boost::accumulators::min(acc); - if (pMax) - *pMax = boost::accumulators::max(acc); - return boost::accumulators::mean(acc); + boost::accumulators:: + accumulator_set> + acc; + + for (int i = 0; i < RelRs.size(); ++i) + { + const RelativeRotation& relR = RelRs[i]; + acc(aliceVision::FrobeniusNorm(relR.Rij - (Rs[relR.j] * Rs[relR.i].transpose()))); + } + if (pMin) + *pMin = boost::accumulators::min(acc); + if (pMax) + *pMax = boost::accumulators::max(acc); + return boost::accumulators::mean(acc); #else - std::vector vec_err(RelRs.size(), REAL(0.0)); - for(int i=0; i < RelRs.size(); ++i) { - const RelativeRotation& relR = RelRs[i]; - vec_err[i] = aliceVision::FrobeniusNorm(relR.Rij - (Rs[relR.j]*Rs[relR.i].transpose())); - } - BoxStats stats(vec_err.begin(), vec_err.end()); - if (pMin) - *pMin = stats.min; - if (pMax) - *pMax = stats.max; - return stats.mean; + std::vector vec_err(RelRs.size(), REAL(0.0)); + for (int i = 0; i < RelRs.size(); ++i) + { + const RelativeRotation& relR = RelRs[i]; + vec_err[i] = aliceVision::FrobeniusNorm(relR.Rij - (Rs[relR.j] * Rs[relR.i].transpose())); + } + BoxStats stats(vec_err.begin(), vec_err.end()); + if (pMin) + *pMin = stats.min; + if (pMax) + *pMax = stats.max; + return stats.mean; #endif } //---------------------------------------------------------------- -void InitRotationsMST -( - const RelativeRotations& RelRs, - Matrix3x3Arr& Rs, - const size_t nMainViewID -) +void InitRotationsMST(const RelativeRotations& RelRs, Matrix3x3Arr& Rs, const size_t nMainViewID) { - assert(!Rs.empty()); - - // -- Compute coarse global rotation estimates: - // - by finding the maximum spanning tree and linking the relative rotations - // - Initial solution is driven by relative rotations data confidence. - graph_t g; - MapEdgeIJ2R mapIJ2R; - NodeArr minGraph; - // find the Maximum Spanning Tree - FindMaximumSpanningTree(RelRs, g, mapIJ2R, minGraph); - g.clear(); - - // start from the main view and link all views using the relative rotation estimates - LinkQue stack; - stack.push(Link(nMainViewID, size_t(0))); - Rs[nMainViewID] = Matrix3x3::Identity(); - do { - const Link& link = stack.front(); - const Node& node = minGraph[link.ID]; - - for(Node::InternalType::const_iterator pEdge = node.edges.begin(); - pEdge != node.edges.end(); ++pEdge) { - const size_t edge = *pEdge; - if (edge == link.parentID) { - // compute the global rotation for the current node - assert(mapIJ2R.find(std::make_pair(link.parentID, link.ID)) != mapIJ2R.end()); - const Matrix3x3& Rij = mapIJ2R[std::make_pair(link.parentID, link.ID)]; - Rs[link.ID] = Rij * Rs[link.parentID]; - } else { - // add edge to the processing queue - stack.push(Link(edge, link.ID)); + assert(!Rs.empty()); + + // -- Compute coarse global rotation estimates: + // - by finding the maximum spanning tree and linking the relative rotations + // - Initial solution is driven by relative rotations data confidence. + graph_t g; + MapEdgeIJ2R mapIJ2R; + NodeArr minGraph; + // find the Maximum Spanning Tree + FindMaximumSpanningTree(RelRs, g, mapIJ2R, minGraph); + g.clear(); + + // start from the main view and link all views using the relative rotation estimates + LinkQue stack; + stack.push(Link(nMainViewID, size_t(0))); + Rs[nMainViewID] = Matrix3x3::Identity(); + do + { + const Link& link = stack.front(); + const Node& node = minGraph[link.ID]; + + for (Node::InternalType::const_iterator pEdge = node.edges.begin(); pEdge != node.edges.end(); ++pEdge) + { + const size_t edge = *pEdge; + if (edge == link.parentID) + { + // compute the global rotation for the current node + assert(mapIJ2R.find(std::make_pair(link.parentID, link.ID)) != mapIJ2R.end()); + const Matrix3x3& Rij = mapIJ2R[std::make_pair(link.parentID, link.ID)]; + Rs[link.ID] = Rij * Rs[link.parentID]; + } + else + { + // add edge to the processing queue + stack.push(Link(edge, link.ID)); + } } - } - stack.pop(); - } while(!stack.empty()); + stack.pop(); + } while (!stack.empty()); } // Robustly estimate global rotations from relative rotations as in: // "Efficient and Robust Large-Scale Rotation Averaging", Chatterjee and Govindu, 2013 // and detect outliers relative rotations and return them with 0 in arrInliers -bool GlobalRotationsRobust( - const RelativeRotations& RelRs, - Matrix3x3Arr& Rs, - const size_t nMainViewID, - float threshold, - std::vector * vec_Inliers) +bool GlobalRotationsRobust(const RelativeRotations& RelRs, + Matrix3x3Arr& Rs, + const size_t nMainViewID, + float threshold, + std::vector* vec_Inliers) { - assert(!Rs.empty()); + assert(!Rs.empty()); - // -- Compute coarse global rotation estimates: - InitRotationsMST(RelRs, Rs, nMainViewID); + // -- Compute coarse global rotation estimates: + InitRotationsMST(RelRs, Rs, nMainViewID); - // refine global rotations based on the relative rotations - const bool bOk = RefineRotationsAvgL1IRLS(RelRs, Rs, nMainViewID); + // refine global rotations based on the relative rotations + const bool bOk = RefineRotationsAvgL1IRLS(RelRs, Rs, nMainViewID); - // find outlier relative rotations - if (threshold>=0 && vec_Inliers) { - FilterRelativeRotations(RelRs, Rs, threshold, vec_Inliers); - } + // find outlier relative rotations + if (threshold >= 0 && vec_Inliers) + { + FilterRelativeRotations(RelRs, Rs, threshold, vec_Inliers); + } - return bOk; -} // GlobalRotationsRobust + return bOk; +} // GlobalRotationsRobust //---------------------------------------------------------------- - // build A in Ax=b -inline void _FillMappingMatrix( - const RelativeRotations& RelRs, - const size_t nMainViewID, - Eigen::SparseMatrix& A) +inline void _FillMappingMatrix(const RelativeRotations& RelRs, const size_t nMainViewID, Eigen::SparseMatrix& A) { - A.reserve(A.rows()*2); // estimate of the number of non-zeros (optional) - Eigen::SparseMatrix::Index i = 0, j = 0; - for(int r=0; r::Index i = 0, j = 0; + for (int r = 0; r < RelRs.size(); ++r) + { + const RelativeRotation& relR = RelRs[r]; + if (relR.i != nMainViewID) + { + j = 3 * (relR.i < nMainViewID ? relR.i : relR.i - 1); + A.insert(i + 0, j + 0) = REAL(-1); + A.insert(i + 1, j + 1) = REAL(-1); + A.insert(i + 2, j + 2) = REAL(-1); + } + if (relR.j != nMainViewID) + { + j = 3 * (relR.j < nMainViewID ? relR.j : relR.j - 1); + A.insert(i + 0, j + 0) = REAL(1); + A.insert(i + 1, j + 1) = REAL(1); + A.insert(i + 2, j + 2) = REAL(1); + } + i += 3; } - i+=3; - } - A.makeCompressed(); + A.makeCompressed(); } // compute errors for each relative rotation -inline void _FillErrorMatrix( - const RelativeRotations& RelRs, - const Matrix3x3Arr& Rs, - Eigen::Matrix& b) +inline void _FillErrorMatrix(const RelativeRotations& RelRs, const Matrix3x3Arr& Rs, Eigen::Matrix& b) { - for (size_t r = 0; r < RelRs.size(); ++r) { - const RelativeRotation& relR = RelRs[r]; - const Matrix3x3& Ri = Rs[relR.i]; - const Matrix3x3& Rj = Rs[relR.j]; - const Matrix3x3& Rij = relR.Rij; - const Mat3 eRij(Rj.transpose()*Rij*Ri); - const aliceVision::Vec3 erij; - ceres::RotationMatrixToAngleAxis((const double*)eRij.data(), (double*)erij.data()); - b.block<3,1>(3*r,0) = aliceVision::Vec3(erij*relR.weight); - } + for (size_t r = 0; r < RelRs.size(); ++r) + { + const RelativeRotation& relR = RelRs[r]; + const Matrix3x3& Ri = Rs[relR.i]; + const Matrix3x3& Rj = Rs[relR.j]; + const Matrix3x3& Rij = relR.Rij; + const Mat3 eRij(Rj.transpose() * Rij * Ri); + const aliceVision::Vec3 erij; + ceres::RotationMatrixToAngleAxis((const double*)eRij.data(), (double*)erij.data()); + b.block<3, 1>(3 * r, 0) = aliceVision::Vec3(erij * relR.weight); + } } // apply correction to global rotations -inline void _CorrectMatrix( - const Eigen::Matrix& x, - const size_t nMainViewID, - Matrix3x3Arr& Rs) +inline void _CorrectMatrix(const Eigen::Matrix& x, const size_t nMainViewID, Matrix3x3Arr& Rs) { - for (size_t r = 0; r < Rs.size(); ++r) { - if (r == nMainViewID) - continue; - Matrix3x3& Ri = Rs[r]; - const size_t i = (r(3*i,0)); - const Mat3 eRi; - ceres::AngleAxisToRotationMatrix((const double*)eRid.data(), (double*)eRi.data()); - Ri = Ri*eRi; - } + for (size_t r = 0; r < Rs.size(); ++r) + { + if (r == nMainViewID) + continue; + Matrix3x3& Ri = Rs[r]; + const size_t i = (r < nMainViewID ? r : r - 1); + aliceVision::Vec3 eRid = aliceVision::Vec3(x.block<3, 1>(3 * i, 0)); + const Mat3 eRi; + ceres::AngleAxisToRotationMatrix((const double*)eRid.data(), (double*)eRi.data()); + Ri = Ri * eRi; + } } // Refine the global rotations using to the given relative rotations, similar to: // "Efficient and Robust Large-Scale Rotation Averaging", Chatterjee and Govindu, 2013 // L1 Rotation Averaging (L1RA) and Iteratively Reweighted Least Squares (IRLS) implementations combined -bool RefineRotationsAvgL1IRLS( - const RelativeRotations& RelRs, - Matrix3x3Arr& Rs, - const size_t nMainViewID, - REAL sigma) +bool RefineRotationsAvgL1IRLS(const RelativeRotations& RelRs, Matrix3x3Arr& Rs, const size_t nMainViewID, REAL sigma) { - assert(!RelRs.empty() && !Rs.empty()); - assert(Rs[nMainViewID] == Matrix3x3::Identity()); - - REAL fMinBefore, fMaxBefore, fMeanBefore = RelRotationAvgError(RelRs, Rs, &fMinBefore, &fMaxBefore); - - const unsigned nObss = (unsigned)RelRs.size(); - const unsigned nVars = (unsigned)Rs.size()-1; // main view is kept constant - const unsigned m = nObss*3; - const unsigned n = nVars*3; - - // build mapping matrix A in Ax=b - Eigen::SparseMatrix A(m, n); - _FillMappingMatrix(RelRs, nMainViewID, A); - - // init x with 0 that corresponds to trusting completely the initial Ri guess - Vec x(Vec::Zero(n)), b(m); - - // L1RA iterate optimization till the desired precision is reached - REAL e = std::numeric_limits::max(), ep; - unsigned iter1 = 0; - do { - // compute errors for each relative rotation - _FillErrorMatrix(RelRs, Rs, b); - // solve the linear system using l1 norm - if (!RobustRegressionL1PD(A, b, x)) { - ALICEVISION_LOG_WARNING("error: l1 robust regression failed."); - return false; - } - ep = e; e = x.norm(); - if (ep < e) - break; - // apply correction to global rotations - _CorrectMatrix(x, nMainViewID, Rs); - } while (++iter1 < 32 && e > 1e-5 && (ep-e)/e > 1e-2); - // IRLS iterate optimization till the desired precision is reached - x.setZero(); - e = std::numeric_limits::max(); - unsigned iter2 = 0; - do { - // compute errors for each relative rotation - _FillErrorMatrix(RelRs, Rs, b); - // solve the linear system using l2 norm - if (!IterativelyReweightedLeastSquares(A, b, x, sigma)) { - ALICEVISION_LOG_WARNING("error: l2 iterative regression failed"); - return false; - } - ep = e; e = x.norm(); - if (ep < e) - break; - // apply correction to global rotations - _CorrectMatrix(x, nMainViewID, Rs); - } while (++iter2 < 32 && e > 1e-5 && (ep-e)/e > 1e-2); - - REAL fMinAfter, fMaxAfter, fMeanAfter = RelRotationAvgError(RelRs, Rs, &fMinAfter, &fMaxAfter); - - ALICEVISION_LOG_DEBUG("Refine global rotations using L1RA-IRLS and " << nObss << " relative rotations:\n" - << " error reduced from " << fMeanBefore << "(" < A(m, n); + _FillMappingMatrix(RelRs, nMainViewID, A); + + // init x with 0 that corresponds to trusting completely the initial Ri guess + Vec x(Vec::Zero(n)), b(m); + + // L1RA iterate optimization till the desired precision is reached + REAL e = std::numeric_limits::max(), ep; + unsigned iter1 = 0; + do + { + // compute errors for each relative rotation + _FillErrorMatrix(RelRs, Rs, b); + // solve the linear system using l1 norm + if (!RobustRegressionL1PD(A, b, x)) + { + ALICEVISION_LOG_WARNING("error: l1 robust regression failed."); + return false; + } + ep = e; + e = x.norm(); + if (ep < e) + break; + // apply correction to global rotations + _CorrectMatrix(x, nMainViewID, Rs); + } while (++iter1 < 32 && e > 1e-5 && (ep - e) / e > 1e-2); + // IRLS iterate optimization till the desired precision is reached + x.setZero(); + e = std::numeric_limits::max(); + unsigned iter2 = 0; + do + { + // compute errors for each relative rotation + _FillErrorMatrix(RelRs, Rs, b); + // solve the linear system using l2 norm + if (!IterativelyReweightedLeastSquares(A, b, x, sigma)) + { + ALICEVISION_LOG_WARNING("error: l2 iterative regression failed"); + return false; + } + ep = e; + e = x.norm(); + if (ep < e) + break; + // apply correction to global rotations + _CorrectMatrix(x, nMainViewID, Rs); + } while (++iter2 < 32 && e > 1e-5 && (ep - e) / e > 1e-2); + + REAL fMinAfter, fMaxAfter, fMeanAfter = RelRotationAvgError(RelRs, Rs, &fMinAfter, &fMaxAfter); + + ALICEVISION_LOG_DEBUG("Refine global rotations using L1RA-IRLS and " + << nObss << " relative rotations:\n" + << " error reduced from " << fMeanBefore << "(" << fMinBefore << " min, " << fMaxBefore << " max)\n" + << " to " << fMeanAfter << "(" << fMinAfter << "min," << fMaxAfter << "max)\n" + << " in " << iter1 << "+" << iter2 << "=" << iter1 + iter2 << " iterations"); + + return true; +} // RefineRotationsAvgL1IRLS + +} // namespace l1 +} // namespace rotationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/rotationAveraging/l1.hpp b/src/aliceVision/multiview/rotationAveraging/l1.hpp index 9d8a1558b5..b480a0378c 100644 --- a/src/aliceVision/multiview/rotationAveraging/l1.hpp +++ b/src/aliceVision/multiview/rotationAveraging/l1.hpp @@ -17,9 +17,9 @@ //- Date: December 2013. //- Conference: ICCV. -namespace aliceVision { -namespace rotationAveraging { -namespace l1 { +namespace aliceVision { +namespace rotationAveraging { +namespace l1 { // Defines typedef double REAL; @@ -32,12 +32,7 @@ typedef std::vector Matrix3x3Arr; * @param[out] Rs output global rotation matrices * @param[in] nMainViewID Id of the image considered as Identity (unit rotation) */ -void InitRotationsMST -( - const RelativeRotations& RelRs, - Matrix3x3Arr& Rs, - const size_t nMainViewID -); +void InitRotationsMST(const RelativeRotations& RelRs, Matrix3x3Arr& Rs, const size_t nMainViewID); /** * @brief Compute an initial estimation of global rotation and refines them under the L1 norm, [1]. @@ -48,12 +43,11 @@ void InitRotationsMST * @param[in] threshold (optionnal) threshold * @param[out] vec_inliers rotation labelled as inliers or outliers */ -bool GlobalRotationsRobust( - const RelativeRotations& RelRs, - Matrix3x3Arr& Rs, - const size_t nMainViewID, - float threshold = 0.f, - std::vector * vec_inliers = nullptr); +bool GlobalRotationsRobust(const RelativeRotations& RelRs, + Matrix3x3Arr& Rs, + const size_t nMainViewID, + float threshold = 0.f, + std::vector* vec_inliers = nullptr); /** * @brief Implementation of Iteratively Reweighted Least Squares (IRLS) [1]. @@ -63,11 +57,10 @@ bool GlobalRotationsRobust( * @param[in] nMainViewID Id of the image considered as Identity (unit rotation) * @param[in] sigma factor */ -bool RefineRotationsAvgL1IRLS( - const RelativeRotations& RelRs, - Matrix3x3Arr& Rs, - const size_t nMainViewID, - REAL sigma=aliceVision::degreeToRadian(5.0)); +bool RefineRotationsAvgL1IRLS(const RelativeRotations& RelRs, + Matrix3x3Arr& Rs, + const size_t nMainViewID, + REAL sigma = aliceVision::degreeToRadian(5.0)); /** * @brief Sort relative rotation as inlier, outlier rotations. @@ -77,43 +70,41 @@ bool RefineRotationsAvgL1IRLS( * @param[in] threshold used to label rotations as inlier, or outlier (if 0, threshold is computed with the X84 law) * @param[in] vec_inliers inlier, outlier labels */ -unsigned int FilterRelativeRotations( - const RelativeRotations& RelRs, - const Matrix3x3Arr& Rs, - float threshold = 0.f, - std::vector * vec_inliers = nullptr); - +unsigned int FilterRelativeRotations(const RelativeRotations& RelRs, + const Matrix3x3Arr& Rs, + float threshold = 0.f, + std::vector* vec_inliers = nullptr); // Minimization Stuff // L1RA [1] for dense A matrix -bool RobustRegressionL1PD( - const Eigen::Matrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL pdtol=1e-3, unsigned pdmaxiter=50); +bool RobustRegressionL1PD(const Eigen::Matrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL pdtol = 1e-3, + unsigned pdmaxiter = 50); // L1RA [1] for sparse A matrix -bool RobustRegressionL1PD( - const Eigen::SparseMatrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL pdtol=1e-3, unsigned pdmaxiter=50); +bool RobustRegressionL1PD(const Eigen::SparseMatrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL pdtol = 1e-3, + unsigned pdmaxiter = 50); /// IRLS [1] for dense A matrix -bool IterativelyReweightedLeastSquares( - const Eigen::Matrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL sigma, REAL eps=1e-5); +bool IterativelyReweightedLeastSquares(const Eigen::Matrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL sigma, + REAL eps = 1e-5); /// IRLS [1] for sparse A matrix -bool IterativelyReweightedLeastSquares( - const Eigen::SparseMatrix& A, - const Eigen::Matrix& b, - Eigen::Matrix& x, - REAL sigma, REAL eps=1e-5); - -} // namespace l1 -} // namespace rotationAveraging -} // namespace aliceVision +bool IterativelyReweightedLeastSquares(const Eigen::SparseMatrix& A, + const Eigen::Matrix& b, + Eigen::Matrix& x, + REAL sigma, + REAL eps = 1e-5); + +} // namespace l1 +} // namespace rotationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/rotationAveraging/l2.cpp b/src/aliceVision/multiview/rotationAveraging/l2.cpp index ae275a9ff3..afba9cc31f 100644 --- a/src/aliceVision/multiview/rotationAveraging/l2.cpp +++ b/src/aliceVision/multiview/rotationAveraging/l2.cpp @@ -17,7 +17,7 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif //-- @@ -29,27 +29,24 @@ //- Author : Daniel Martinec. //- Date : July 2, 2008. //-- -namespace aliceVision { -namespace rotationAveraging { -namespace l2 { +namespace aliceVision { +namespace rotationAveraging { +namespace l2 { // [1] 6.7.2 Consistent Rotation page 89 // Closest Rotation Estimation R = U*transpose(V) // approximate rotation in the Frobenius norm using SVD -Mat3 ClosestSVDRotationMatrix(const Mat3 & rotMat) +Mat3 ClosestSVDRotationMatrix(const Mat3& rotMat) { - // Closest orthogonal matrix - Eigen::JacobiSVD svd(rotMat,Eigen::ComputeFullV|Eigen::ComputeFullU); - const Mat3 U = svd.matrixU(); - const Mat3 V = svd.matrixV(); - return U*V.transpose(); + // Closest orthogonal matrix + Eigen::JacobiSVD svd(rotMat, Eigen::ComputeFullV | Eigen::ComputeFullU); + const Mat3 U = svd.matrixU(); + const Mat3 V = svd.matrixV(); + return U * V.transpose(); } // pair comparator -bool compare_first_abs(std::pair const &x, std::pair const &y) -{ - return fabs(x.first) < fabs(y.first); -} +bool compare_first_abs(std::pair const& x, std::pair const& y) { return fabs(x.first) < fabs(y.first); } //-- Solve the Global Rotation matrix registration for each camera given a list // of relative orientation using matrix parametrization @@ -74,228 +71,217 @@ bool compare_first_abs(std::pair const &x, std::pair c // vector.add( RelativeRotation(1,2, R12) ); // vector.add( RelativeRotation(0,2, R02) ); // -bool L2RotationAveraging( size_t nCamera, - const RelativeRotations& vec_relativeRot, - // Output - std::vector & vec_ApprRotMatrix) +bool L2RotationAveraging(size_t nCamera, + const RelativeRotations& vec_relativeRot, + // Output + std::vector& vec_ApprRotMatrix) { - const size_t nRotationEstimation = vec_relativeRot.size(); - //-- - // Setup the Action Matrix - //-- - std::vector > tripletList; - tripletList.reserve(nRotationEstimation*12); // 3*3 + 3 - //-- Encode constraint (6.62 Martinec Thesis page 100): - sMat::Index cpt = 0; - for(RelativeRotations::const_iterator - iter = vec_relativeRot.begin(); - iter != vec_relativeRot.end(); - iter++, cpt++) - { - const RelativeRotation & Elem = *iter; - - //-- Encode weight * ( rj - Rij * ri ) = 0 - const size_t i = iter->i; - const size_t j = iter->j; - - // A.block<3,3>(3 * cpt, 3 * i) = - Rij * weight; - tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * i, - iter->Rij(0,0) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * i + 1, - iter->Rij(0,1) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * i + 2, - iter->Rij(0,2) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * i, - iter->Rij(1,0) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * i + 1, - iter->Rij(1,1) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * i + 2, - iter->Rij(1,2) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * i, - iter->Rij(2,0) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * i + 1, - iter->Rij(2,1) * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * i + 2, - iter->Rij(2,2) * iter->weight)); - - // A.block<3,3>(3 * cpt, 3 * j) = Id * weight; - tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * j, 1.0 * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * j + 1, 1.0 * iter->weight)); - tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * j + 2, 1.0 * iter->weight)); - } - - // nCamera * 3 because each columns have 3 elements. - sMat A(nRotationEstimation*3, 3*nCamera); - A.setFromTriplets(tripletList.begin(), tripletList.end()); - tripletList.clear(); - - sMat AtAsparse = A.transpose() * A; - const Mat AtA = Mat(AtAsparse); // convert to dense - - // You can use either SVD or eigen solver (eigen solver will be faster) to solve Ax=0 - - // Solve Ax=0 => SVD - //Eigen::JacobiSVD svd(A,Eigen::ComputeFullV); - //const Vec & NullspaceVector0 = svd.matrixV().col(A.cols()-1); - //const Vec & NullspaceVector1 = svd.matrixV().col(A.cols()-2); - //const Vec & NullspaceVector2 = svd.matrixV().col(A.cols()-3); - - // Solve Ax=0 => eigen vectors - Eigen::SelfAdjointEigenSolver es(AtA, Eigen::ComputeEigenvectors); - - if (es.info() != Eigen::Success) - { - return false; - } - else - { - // Sort abs(eigenvalues) - std::vector > eigs(AtA.cols()); - for (size_t i = 0; i < AtA.cols(); ++i) + const size_t nRotationEstimation = vec_relativeRot.size(); + //-- + // Setup the Action Matrix + //-- + std::vector> tripletList; + tripletList.reserve(nRotationEstimation * 12); // 3*3 + 3 + //-- Encode constraint (6.62 Martinec Thesis page 100): + sMat::Index cpt = 0; + for (RelativeRotations::const_iterator iter = vec_relativeRot.begin(); iter != vec_relativeRot.end(); iter++, cpt++) { - eigs[i] = std::make_pair(es.eigenvalues()[i], es.eigenvectors().col(i)); + const RelativeRotation& Elem = *iter; + + //-- Encode weight * ( rj - Rij * ri ) = 0 + const size_t i = iter->i; + const size_t j = iter->j; + + // A.block<3,3>(3 * cpt, 3 * i) = - Rij * weight; + tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * i, -iter->Rij(0, 0) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * i + 1, -iter->Rij(0, 1) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * i + 2, -iter->Rij(0, 2) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * i, -iter->Rij(1, 0) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * i + 1, -iter->Rij(1, 1) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * i + 2, -iter->Rij(1, 2) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * i, -iter->Rij(2, 0) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * i + 1, -iter->Rij(2, 1) * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * i + 2, -iter->Rij(2, 2) * iter->weight)); + + // A.block<3,3>(3 * cpt, 3 * j) = Id * weight; + tripletList.push_back(Eigen::Triplet(3 * cpt, 3 * j, 1.0 * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 1, 3 * j + 1, 1.0 * iter->weight)); + tripletList.push_back(Eigen::Triplet(3 * cpt + 2, 3 * j + 2, 1.0 * iter->weight)); } - std::stable_sort(eigs.begin(), eigs.end(), &compare_first_abs); - const Vec & NullspaceVector0 = eigs[0].second; - const Vec & NullspaceVector1 = eigs[1].second; - const Vec & NullspaceVector2 = eigs[2].second; + // nCamera * 3 because each columns have 3 elements. + sMat A(nRotationEstimation * 3, 3 * nCamera); + A.setFromTriplets(tripletList.begin(), tripletList.end()); + tripletList.clear(); - //-- - // Search the closest matrix : - // - From solution of SVD get back column and reconstruct Rotation matrix - // - Enforce the orthogonality constraint - // (approximate rotation in the Frobenius norm using SVD). - //-- - vec_ApprRotMatrix.clear(); - vec_ApprRotMatrix.reserve(nCamera); - for(size_t i=0; i < nCamera; ++i) + sMat AtAsparse = A.transpose() * A; + const Mat AtA = Mat(AtAsparse); // convert to dense + + // You can use either SVD or eigen solver (eigen solver will be faster) to solve Ax=0 + + // Solve Ax=0 => SVD + // Eigen::JacobiSVD svd(A,Eigen::ComputeFullV); + // const Vec & NullspaceVector0 = svd.matrixV().col(A.cols()-1); + // const Vec & NullspaceVector1 = svd.matrixV().col(A.cols()-2); + // const Vec & NullspaceVector2 = svd.matrixV().col(A.cols()-3); + + // Solve Ax=0 => eigen vectors + Eigen::SelfAdjointEigenSolver es(AtA, Eigen::ComputeEigenvectors); + + if (es.info() != Eigen::Success) { - Mat3 Rotation; - Rotation << NullspaceVector0.segment(3 * i, 3), - NullspaceVector1.segment(3 * i, 3), - NullspaceVector2.segment(3 * i, 3); - - //-- Compute the closest SVD rotation matrix - Rotation = ClosestSVDRotationMatrix(Rotation); - vec_ApprRotMatrix.push_back(Rotation); + return false; } - // Force R0 to be Identity - const Mat3 R0T = vec_ApprRotMatrix[0].transpose(); - for(size_t i = 0; i < nCamera; ++i) { - vec_ApprRotMatrix[i] *= R0T; + else + { + // Sort abs(eigenvalues) + std::vector> eigs(AtA.cols()); + for (size_t i = 0; i < AtA.cols(); ++i) + { + eigs[i] = std::make_pair(es.eigenvalues()[i], es.eigenvectors().col(i)); + } + std::stable_sort(eigs.begin(), eigs.end(), &compare_first_abs); + + const Vec& NullspaceVector0 = eigs[0].second; + const Vec& NullspaceVector1 = eigs[1].second; + const Vec& NullspaceVector2 = eigs[2].second; + + //-- + // Search the closest matrix : + // - From solution of SVD get back column and reconstruct Rotation matrix + // - Enforce the orthogonality constraint + // (approximate rotation in the Frobenius norm using SVD). + //-- + vec_ApprRotMatrix.clear(); + vec_ApprRotMatrix.reserve(nCamera); + for (size_t i = 0; i < nCamera; ++i) + { + Mat3 Rotation; + Rotation << NullspaceVector0.segment(3 * i, 3), NullspaceVector1.segment(3 * i, 3), NullspaceVector2.segment(3 * i, 3); + + //-- Compute the closest SVD rotation matrix + Rotation = ClosestSVDRotationMatrix(Rotation); + vec_ApprRotMatrix.push_back(Rotation); + } + // Force R0 to be Identity + const Mat3 R0T = vec_ApprRotMatrix[0].transpose(); + for (size_t i = 0; i < nCamera; ++i) + { + vec_ApprRotMatrix[i] *= R0T; + } + + return true; } - - return true; - } } // Ceres Functor to minimize global rotation regarding fixed relative rotation -struct CeresPairRotationError { - CeresPairRotationError(const aliceVision::Vec3& relative_rotation, const double weight) - :relative_rotation_(relative_rotation), weight_(weight) {} - - // The error is given by the rotation cycle error (R2 * R1.t) * RRel.t - template - bool operator() (const T* angleAxis1, const T* angleAxis2, T* residuals) const - { - const T relative_rotation[3] = { - T(relative_rotation_[0]), - T(relative_rotation_[1]), - T(relative_rotation_[2])}; - - // Convert angle axis rotations to rotation matrices - Eigen::Matrix RRel, R1, R2; - ceres::AngleAxisToRotationMatrix( relative_rotation, RRel.data()); - ceres::AngleAxisToRotationMatrix( angleAxis1, R1.data()); - ceres::AngleAxisToRotationMatrix( angleAxis2, R2.data()); - - // Compute the "cycle" rotation error for the given edge: - // relative error between two given global rotations and the relative one - const Eigen::Matrix cycle_rotation_mat = (R2 * R1.transpose()) * RRel.transpose(); - Eigen::Matrix cycle_rotation; - ceres::RotationMatrixToAngleAxis( cycle_rotation_mat.data(), cycle_rotation.data()); - - residuals[0] = T(weight_) * cycle_rotation(0); - residuals[1] = T(weight_) * cycle_rotation(1); - residuals[2] = T(weight_) * cycle_rotation(2); - - return true; - } - - const aliceVision::Vec3 relative_rotation_; - const double weight_; +struct CeresPairRotationError +{ + CeresPairRotationError(const aliceVision::Vec3& relative_rotation, const double weight) + : relative_rotation_(relative_rotation), + weight_(weight) + {} + + // The error is given by the rotation cycle error (R2 * R1.t) * RRel.t + template + bool operator()(const T* angleAxis1, const T* angleAxis2, T* residuals) const + { + const T relative_rotation[3] = {T(relative_rotation_[0]), T(relative_rotation_[1]), T(relative_rotation_[2])}; + + // Convert angle axis rotations to rotation matrices + Eigen::Matrix RRel, R1, R2; + ceres::AngleAxisToRotationMatrix(relative_rotation, RRel.data()); + ceres::AngleAxisToRotationMatrix(angleAxis1, R1.data()); + ceres::AngleAxisToRotationMatrix(angleAxis2, R2.data()); + + // Compute the "cycle" rotation error for the given edge: + // relative error between two given global rotations and the relative one + const Eigen::Matrix cycle_rotation_mat = (R2 * R1.transpose()) * RRel.transpose(); + Eigen::Matrix cycle_rotation; + ceres::RotationMatrixToAngleAxis(cycle_rotation_mat.data(), cycle_rotation.data()); + + residuals[0] = T(weight_) * cycle_rotation(0); + residuals[1] = T(weight_) * cycle_rotation(1); + residuals[2] = T(weight_) * cycle_rotation(2); + + return true; + } + + const aliceVision::Vec3 relative_rotation_; + const double weight_; }; -bool L2RotationAveraging_Refine( - const RelativeRotations & vec_relativeRot, - std::vector & vec_ApprRotMatrix) +bool L2RotationAveraging_Refine(const RelativeRotations& vec_relativeRot, std::vector& vec_ApprRotMatrix) { - if (vec_relativeRot.empty() ||vec_ApprRotMatrix.empty() ) { - ALICEVISION_LOG_DEBUG("Skip nonlinear rotation optimization, no sufficient data provided "); - return false; -} + if (vec_relativeRot.empty() || vec_ApprRotMatrix.empty()) + { + ALICEVISION_LOG_DEBUG("Skip nonlinear rotation optimization, no sufficient data provided "); + return false; + } + + // Convert global rotation to AngleAxis representation + std::vector vec_Rot_AngleAxis(vec_ApprRotMatrix.size()); + for (int i = 0; i < vec_ApprRotMatrix.size(); ++i) + { + ceres::RotationMatrixToAngleAxis((const double*)vec_ApprRotMatrix[i].data(), vec_Rot_AngleAxis[i].data()); + } + + // Setup the problem and a robust loss function. + ceres::Problem problem; + const double robust_loss_width = 0.03; // 2° error along one axis (perhaps a bit too strict) + + for (size_t ii = 0; ii < vec_relativeRot.size(); ++ii) + { + const int i = vec_relativeRot[ii].i; + const int j = vec_relativeRot[ii].j; + const aliceVision::Mat3& Rrel = vec_relativeRot[ii].Rij; + const double w = vec_relativeRot[ii].weight; - // Convert global rotation to AngleAxis representation - std::vector vec_Rot_AngleAxis(vec_ApprRotMatrix.size()); - for (int i=0; i < vec_ApprRotMatrix.size(); ++i) - { - ceres::RotationMatrixToAngleAxis((const double*)vec_ApprRotMatrix[i].data(), vec_Rot_AngleAxis[i].data()); - } - - // Setup the problem and a robust loss function. - ceres::Problem problem; - const double robust_loss_width = 0.03; // 2° error along one axis (perhaps a bit too strict) - - for (size_t ii = 0; ii < vec_relativeRot.size(); ++ii) - { - const int i = vec_relativeRot[ii].i; - const int j = vec_relativeRot[ii].j; - const aliceVision::Mat3& Rrel = vec_relativeRot[ii].Rij; - const double w = vec_relativeRot[ii].weight; - - aliceVision::Vec3 rotAngleAxis; - ceres::RotationMatrixToAngleAxis((const double*)Rrel.data(), rotAngleAxis.data()); - - ceres::CostFunction* cost_function = - new ceres::AutoDiffCostFunction( - new CeresPairRotationError(rotAngleAxis, w)); - - ceres::LossFunction* loss_function = new ceres::SoftLOneLoss(w * robust_loss_width); - problem.AddResidualBlock(cost_function, - loss_function, - vec_Rot_AngleAxis[i].data(), - vec_Rot_AngleAxis[j].data()); - } - ceres::Solver::Options solverOptions; - // Since the problem is sparse, use a sparse solver - if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE) || + aliceVision::Vec3 rotAngleAxis; + ceres::RotationMatrixToAngleAxis((const double*)Rrel.data(), rotAngleAxis.data()); + + ceres::CostFunction* cost_function = + new ceres::AutoDiffCostFunction(new CeresPairRotationError(rotAngleAxis, w)); + + ceres::LossFunction* loss_function = new ceres::SoftLOneLoss(w * robust_loss_width); + problem.AddResidualBlock(cost_function, loss_function, vec_Rot_AngleAxis[i].data(), vec_Rot_AngleAxis[j].data()); + } + ceres::Solver::Options solverOptions; + // Since the problem is sparse, use a sparse solver + if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE) || #if ALICEVISION_CERES_HAS_CXSPARSE - ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE) || + ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE) || #endif - ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) - { - solverOptions.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; - } - else - { - solverOptions.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - } - // set number of threads, 1 if openMP is not enabled - solverOptions.num_threads = omp_get_max_threads(); + ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) + { + solverOptions.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; + } + else + { + solverOptions.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; + } + // set number of threads, 1 if openMP is not enabled + solverOptions.num_threads = omp_get_max_threads(); #if CERES_VERSION_MAJOR < 2 - solverOptions.num_linear_solver_threads = omp_get_max_threads(); + solverOptions.num_linear_solver_threads = omp_get_max_threads(); #endif - ceres::Solver::Summary summary; - ceres::Solve(solverOptions, &problem, &summary); - // ALICEVISION_LOG_DEBUG(summary.FullReport()); + ceres::Solver::Summary summary; + ceres::Solve(solverOptions, &problem, &summary); + // ALICEVISION_LOG_DEBUG(summary.FullReport()); - if (summary.IsSolutionUsable()) - { - // Convert back the AngleAxis rotations to rotations matrices - for (int i=0; i < vec_ApprRotMatrix.size(); ++i) + if (summary.IsSolutionUsable()) { - ceres::AngleAxisToRotationMatrix(vec_Rot_AngleAxis[i].data(), vec_ApprRotMatrix[i].data()); + // Convert back the AngleAxis rotations to rotations matrices + for (int i = 0; i < vec_ApprRotMatrix.size(); ++i) + { + ceres::AngleAxisToRotationMatrix(vec_Rot_AngleAxis[i].data(), vec_ApprRotMatrix[i].data()); + } + return true; } - return true; - } - return false; + return false; } -} // namespace l2 -} // namespace rotationAveraging -} // namespace aliceVision - - +} // namespace l2 +} // namespace rotationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/rotationAveraging/l2.hpp b/src/aliceVision/multiview/rotationAveraging/l2.hpp index 11e4770e3a..8dfd563557 100644 --- a/src/aliceVision/multiview/rotationAveraging/l2.hpp +++ b/src/aliceVision/multiview/rotationAveraging/l2.hpp @@ -11,7 +11,7 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif //-- @@ -23,14 +23,14 @@ //- Author : Daniel Martinec. //- Date : July 2, 2008. //-- -namespace aliceVision { -namespace rotationAveraging { -namespace l2 { +namespace aliceVision { +namespace rotationAveraging { +namespace l2 { // [1] 6.7.2 Consistent Rotation page 89 // Closest Rotation Estimation R = U*transpose(V) // approximate rotation in the Frobenius norm using SVD -Mat3 ClosestSVDRotationMatrix(const Mat3 & rotMat); +Mat3 ClosestSVDRotationMatrix(const Mat3& rotMat); //-- Solve the Global Rotation matrix registration for each camera given a list // of relative orientation using matrix parametrization @@ -55,16 +55,14 @@ Mat3 ClosestSVDRotationMatrix(const Mat3 & rotMat); // vector.add( RelativeRotation(1,2, R12) ); // vector.add( RelativeRotation(0,2, R02) ); // -bool L2RotationAveraging( size_t nCamera, - const RelativeRotations& vec_relativeRot, - // Output - std::vector & vec_ApprRotMatrix); +bool L2RotationAveraging(size_t nCamera, + const RelativeRotations& vec_relativeRot, + // Output + std::vector& vec_ApprRotMatrix); // None linear refinement of the rotation using an angle-axis representation -bool L2RotationAveraging_Refine( - const RelativeRotations & vec_relativeRot, - std::vector & vec_ApprRotMatrix); +bool L2RotationAveraging_Refine(const RelativeRotations& vec_relativeRot, std::vector& vec_ApprRotMatrix); -} // namespace l2 -} // namespace rotationAveraging -} // namespace aliceVision +} // namespace l2 +} // namespace rotationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/rotationAveraging/rotationAveraging_test.cpp b/src/aliceVision/multiview/rotationAveraging/rotationAveraging_test.cpp index 10a343c02a..93a82481ed 100644 --- a/src/aliceVision/multiview/rotationAveraging/rotationAveraging_test.cpp +++ b/src/aliceVision/multiview/rotationAveraging/rotationAveraging_test.cpp @@ -27,33 +27,32 @@ using namespace aliceVision::rotationAveraging; using namespace aliceVision::rotationAveraging::l1; using namespace aliceVision::rotationAveraging::l2; -BOOST_AUTO_TEST_CASE ( rotationAveraging_ClosestSVDRotationMatrix ) +BOOST_AUTO_TEST_CASE(rotationAveraging_ClosestSVDRotationMatrix) { - Mat3 rotx = RotationAroundX(0.3); + Mat3 rotx = RotationAroundX(0.3); - Mat3 Approximative_rotx = rotationAveraging::l2::ClosestSVDRotationMatrix(rotx); + Mat3 Approximative_rotx = rotationAveraging::l2::ClosestSVDRotationMatrix(rotx); - // Check that SVD have rebuilt the matrix correctly - EXPECT_MATRIX_NEAR( rotx, Approximative_rotx, 1e-8); - // Check the Frobenius distance between the approximated rot matrix and the GT - BOOST_CHECK_SMALL(FrobeniusDistance( rotx, Approximative_rotx), 1e-8); - // Check that the Matrix is a rotation matrix (determinant == 1) - BOOST_CHECK_SMALL( 1.0 - Approximative_rotx.determinant(), 1e-8); + // Check that SVD have rebuilt the matrix correctly + EXPECT_MATRIX_NEAR(rotx, Approximative_rotx, 1e-8); + // Check the Frobenius distance between the approximated rot matrix and the GT + BOOST_CHECK_SMALL(FrobeniusDistance(rotx, Approximative_rotx), 1e-8); + // Check that the Matrix is a rotation matrix (determinant == 1) + BOOST_CHECK_SMALL(1.0 - Approximative_rotx.determinant(), 1e-8); } - -BOOST_AUTO_TEST_CASE ( rotationAveraging_ClosestSVDRotationMatrixNoisy ) +BOOST_AUTO_TEST_CASE(rotationAveraging_ClosestSVDRotationMatrixNoisy) { - Mat3 rotx = RotationAroundX(0.3); + Mat3 rotx = RotationAroundX(0.3); - //-- Set a little of noise in the rotMatrix : - rotx(2,2) -= 0.02; - Mat3 Approximative_rotx = rotationAveraging::l2::ClosestSVDRotationMatrix(rotx); + //-- Set a little of noise in the rotMatrix : + rotx(2, 2) -= 0.02; + Mat3 Approximative_rotx = rotationAveraging::l2::ClosestSVDRotationMatrix(rotx); - // Check the Frobenius distance between the approximated rot matrix and the GT - BOOST_CHECK( FrobeniusDistance( rotx, Approximative_rotx) < 0.02); - // Check that the Matrix is a rotation matrix (determinant == 1) - BOOST_CHECK_SMALL( 1.0 - Approximative_rotx.determinant(), 1e-8); + // Check the Frobenius distance between the approximated rot matrix and the GT + BOOST_CHECK(FrobeniusDistance(rotx, Approximative_rotx) < 0.02); + // Check that the Matrix is a rotation matrix (determinant == 1) + BOOST_CHECK_SMALL(1.0 - Approximative_rotx.determinant(), 1e-8); } // Rotation averaging in a triplet: @@ -62,257 +61,254 @@ BOOST_AUTO_TEST_CASE ( rotationAveraging_ClosestSVDRotationMatrixNoisy ) // \ / // \ / // 1 -BOOST_AUTO_TEST_CASE ( rotationAveraging_RotationLeastSquare_3_Camera) +BOOST_AUTO_TEST_CASE(rotationAveraging_RotationLeastSquare_3_Camera) { - //-- - // Setup 3 camera that have a relative orientation of 120deg - // Set Z axis as UP Vector for the rotation - // They are in the same plane and looking in O={0,0,0} - //-- - Mat3 R01 = RotationAroundZ(2.*M_PI/3.0); //120deg - Mat3 R12 = RotationAroundZ(2.*M_PI/3.0); //120deg - Mat3 R20 = RotationAroundZ(2.*M_PI/3.0); //120deg - - std::vector vec_relativeRotEstimate; - vec_relativeRotEstimate.push_back( RelativeRotation(0,1, R01)); - vec_relativeRotEstimate.push_back( RelativeRotation(1,2, R12)); - vec_relativeRotEstimate.push_back( RelativeRotation(2,0, R20)); - - //- Solve the global rotation estimation problem : - std::vector vec_globalR; - L2RotationAveraging(3, vec_relativeRotEstimate, vec_globalR); - BOOST_CHECK_EQUAL(3, vec_globalR.size()); - // Check that the loop is closing - EXPECT_MATRIX_NEAR(Mat3::Identity(), (vec_globalR[0]*vec_globalR[1]*vec_globalR[2]), 1e-8); - - //-- - // Check that the found relative rotation matrix give the expected rotation. - // -> the started relative rotation (used in the action matrix). - //// /!\ Translation are not checked they are 0 by default. - //-- - Mat3 R; - Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); - relativeCameraMotion(vec_globalR[0], t0, vec_globalR[1], t1, &R, &t); - BOOST_CHECK_SMALL(FrobeniusDistance( R01, R), 1e-2); - - relativeCameraMotion(vec_globalR[1], t0, vec_globalR[2], t1, &R, &t); - BOOST_CHECK_SMALL(FrobeniusDistance( R12, R), 1e-2); - - relativeCameraMotion(vec_globalR[2], t0, vec_globalR[0], t1, &R, &t); - BOOST_CHECK_SMALL(FrobeniusDistance( R20, R), 1e-2); + //-- + // Setup 3 camera that have a relative orientation of 120deg + // Set Z axis as UP Vector for the rotation + // They are in the same plane and looking in O={0,0,0} + //-- + Mat3 R01 = RotationAroundZ(2. * M_PI / 3.0); // 120deg + Mat3 R12 = RotationAroundZ(2. * M_PI / 3.0); // 120deg + Mat3 R20 = RotationAroundZ(2. * M_PI / 3.0); // 120deg + + std::vector vec_relativeRotEstimate; + vec_relativeRotEstimate.push_back(RelativeRotation(0, 1, R01)); + vec_relativeRotEstimate.push_back(RelativeRotation(1, 2, R12)); + vec_relativeRotEstimate.push_back(RelativeRotation(2, 0, R20)); + + //- Solve the global rotation estimation problem : + std::vector vec_globalR; + L2RotationAveraging(3, vec_relativeRotEstimate, vec_globalR); + BOOST_CHECK_EQUAL(3, vec_globalR.size()); + // Check that the loop is closing + EXPECT_MATRIX_NEAR(Mat3::Identity(), (vec_globalR[0] * vec_globalR[1] * vec_globalR[2]), 1e-8); + + //-- + // Check that the found relative rotation matrix give the expected rotation. + // -> the started relative rotation (used in the action matrix). + //// /!\ Translation are not checked they are 0 by default. + //-- + Mat3 R; + Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); + relativeCameraMotion(vec_globalR[0], t0, vec_globalR[1], t1, &R, &t); + BOOST_CHECK_SMALL(FrobeniusDistance(R01, R), 1e-2); + + relativeCameraMotion(vec_globalR[1], t0, vec_globalR[2], t1, &R, &t); + BOOST_CHECK_SMALL(FrobeniusDistance(R12, R), 1e-2); + + relativeCameraMotion(vec_globalR[2], t0, vec_globalR[0], t1, &R, &t); + BOOST_CHECK_SMALL(FrobeniusDistance(R20, R), 1e-2); } -BOOST_AUTO_TEST_CASE ( rotationAveraging_RefineRotationsAvgL1IRLS_SimpleTriplet) +BOOST_AUTO_TEST_CASE(rotationAveraging_RefineRotationsAvgL1IRLS_SimpleTriplet) { - //-- - // Setup 3 camera that have a relative orientation of 120deg - // Set Z axis as UP Vector for the rotation - // They are in the same plane and looking in O={0,0,0} - //-- - Mat3 R01 = RotationAroundZ(2.*M_PI/3.0); //120deg - Mat3 R12 = RotationAroundZ(2.*M_PI/3.0); //120deg - Mat3 R20 = RotationAroundZ(2.*M_PI/3.0); //120deg - Mat3 Id = Mat3::Identity(); - - // Setup the relative motions (relative rotations) - RelativeRotations vec_relativeRotEstimate; - vec_relativeRotEstimate.push_back(RelativeRotation(0, 1, R01, 1)); - vec_relativeRotEstimate.push_back(RelativeRotation(1, 2, R12, 1)); - vec_relativeRotEstimate.push_back(RelativeRotation(2, 0, R20, 1)); - - //- Solve the global rotation estimation problem : - Matrix3x3Arr vec_globalR(3); - std::size_t nMainViewID = 0; - BOOST_CHECK(GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, NULL)); - - // Check that the loop is closing - EXPECT_MATRIX_NEAR(Mat3::Identity(), (vec_globalR[0]*vec_globalR[1]*vec_globalR[2]), 1e-4); - - //-- - // Check that the found relative rotation matrix give the expected rotation. - // -> the started relative rotation (used in the action matrix). - //// /!\ Translation are not checked they are 0 by default. - //-- - Mat3 R; - Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); - relativeCameraMotion(vec_globalR[0], t0, vec_globalR[1], t1, &R, &t); - BOOST_CHECK_SMALL(FrobeniusDistance( R01, R), 1e-8); - - relativeCameraMotion(vec_globalR[1], t0, vec_globalR[2], t1, &R, &t); - BOOST_CHECK_SMALL(FrobeniusDistance( R12, R), 1e-8); - - relativeCameraMotion(vec_globalR[2], t0, vec_globalR[0], t1, &R, &t); - BOOST_CHECK_SMALL(FrobeniusDistance( R20, R), 1e-8); + //-- + // Setup 3 camera that have a relative orientation of 120deg + // Set Z axis as UP Vector for the rotation + // They are in the same plane and looking in O={0,0,0} + //-- + Mat3 R01 = RotationAroundZ(2. * M_PI / 3.0); // 120deg + Mat3 R12 = RotationAroundZ(2. * M_PI / 3.0); // 120deg + Mat3 R20 = RotationAroundZ(2. * M_PI / 3.0); // 120deg + Mat3 Id = Mat3::Identity(); + + // Setup the relative motions (relative rotations) + RelativeRotations vec_relativeRotEstimate; + vec_relativeRotEstimate.push_back(RelativeRotation(0, 1, R01, 1)); + vec_relativeRotEstimate.push_back(RelativeRotation(1, 2, R12, 1)); + vec_relativeRotEstimate.push_back(RelativeRotation(2, 0, R20, 1)); + + //- Solve the global rotation estimation problem : + Matrix3x3Arr vec_globalR(3); + std::size_t nMainViewID = 0; + BOOST_CHECK(GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, NULL)); + + // Check that the loop is closing + EXPECT_MATRIX_NEAR(Mat3::Identity(), (vec_globalR[0] * vec_globalR[1] * vec_globalR[2]), 1e-4); + + //-- + // Check that the found relative rotation matrix give the expected rotation. + // -> the started relative rotation (used in the action matrix). + //// /!\ Translation are not checked they are 0 by default. + //-- + Mat3 R; + Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero(); + relativeCameraMotion(vec_globalR[0], t0, vec_globalR[1], t1, &R, &t); + BOOST_CHECK_SMALL(FrobeniusDistance(R01, R), 1e-8); + + relativeCameraMotion(vec_globalR[1], t0, vec_globalR[2], t1, &R, &t); + BOOST_CHECK_SMALL(FrobeniusDistance(R12, R), 1e-8); + + relativeCameraMotion(vec_globalR[2], t0, vec_globalR[0], t1, &R, &t); + BOOST_CHECK_SMALL(FrobeniusDistance(R20, R), 1e-8); } // Test over a loop of cameras -BOOST_AUTO_TEST_CASE ( rotationAveraging_RefineRotationsAvgL1IRLS_CompleteGraph) +BOOST_AUTO_TEST_CASE(rotationAveraging_RefineRotationsAvgL1IRLS_CompleteGraph) { - //-- Setup a circular camera rig - const int iNviews = 5; - NViewDataSet d = NRealisticCamerasRing(iNviews, 5, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - //Link each camera to the two next ones - RelativeRotations vec_relativeRotEstimate; - for (std::size_t i = 0; i < iNviews; ++i) - { - std::size_t index0 = i; - std::size_t index1 = (i+1)%iNviews; - std::size_t index2 = (i+2)%iNviews; - - //-- compute true relative rotations of the triplet - // (index0->index1), (index1,index2), (index0->index2) - Mat3 Rrel; - Vec3 trel; - relativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel); - vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1)); - - relativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel); - vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1)); - - relativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel); - vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1)); - } + //-- Setup a circular camera rig + const int iNviews = 5; + NViewDataSet d = NRealisticCamerasRing(iNviews, 5, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K - //- Solve the global rotation estimation problem : - Matrix3x3Arr vec_globalR(iNviews); - std::size_t nMainViewID = 0; - bool bTest = GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, NULL); - BOOST_CHECK(bTest); - - // Check that the loop is closing - Mat3 rotCumul = Mat3::Identity(); - for (std::size_t i = 0; i < iNviews; ++i) - { - rotCumul*= vec_globalR[i]; - } - // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT) - if ( SIGN(vec_globalR[0](0,0)) != SIGN( d._R[0](0,0) )) - { - Mat3 Rrel; - Vec3 trel; - relativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel); + // Link each camera to the two next ones + RelativeRotations vec_relativeRotEstimate; for (std::size_t i = 0; i < iNviews; ++i) - vec_globalR[i] *= Rrel; - } + { + std::size_t index0 = i; + std::size_t index1 = (i + 1) % iNviews; + std::size_t index2 = (i + 2) % iNviews; + + //-- compute true relative rotations of the triplet + // (index0->index1), (index1,index2), (index0->index2) + Mat3 Rrel; + Vec3 trel; + relativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel); + vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1)); + + relativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel); + vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1)); + + relativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel); + vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1)); + } - // Check that each global rotations is near the true ones - for (std::size_t i = 0; i < iNviews; ++i) - { - BOOST_CHECK_SMALL(FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8); - } - EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4); -} + //- Solve the global rotation estimation problem : + Matrix3x3Arr vec_globalR(iNviews); + std::size_t nMainViewID = 0; + bool bTest = GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, NULL); + BOOST_CHECK(bTest); + // Check that the loop is closing + Mat3 rotCumul = Mat3::Identity(); + for (std::size_t i = 0; i < iNviews; ++i) + { + rotCumul *= vec_globalR[i]; + } + // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT) + if (SIGN(vec_globalR[0](0, 0)) != SIGN(d._R[0](0, 0))) + { + Mat3 Rrel; + Vec3 trel; + relativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel); + for (std::size_t i = 0; i < iNviews; ++i) + vec_globalR[i] *= Rrel; + } + + // Check that each global rotations is near the true ones + for (std::size_t i = 0; i < iNviews; ++i) + { + BOOST_CHECK_SMALL(FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8); + } + EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4); +} // Test over a loop of camera that have 2 relative outliers rotations -BOOST_AUTO_TEST_CASE ( rotationAveraging_RefineRotationsAvgL1IRLS_CompleteGraph_outliers) +BOOST_AUTO_TEST_CASE(rotationAveraging_RefineRotationsAvgL1IRLS_CompleteGraph_outliers) { - //-- Setup a circular camera rig - const int iNviews = 5; - NViewDataSet d = NRealisticCamerasRing(iNviews, 5, - NViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K - - //Link each camera to the two next ones - RelativeRotations vec_relativeRotEstimate; - std::vector > vec_unique; - for (std::size_t i = 0; i < iNviews; ++i) - { - std::size_t index0 = i; - std::size_t index1 = (i+1)%iNviews; - std::size_t index2 = (i+2)%iNviews; - - //-- compute true relative rotations of the triplet - // (index0->index1), (index1,index2), (index0->index2) - - Mat3 Rrel; - Vec3 trel; - // Add the relative motion if do not exist yet - if ( std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index0, index1)) == vec_unique.end() - && std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index1, index0)) == vec_unique.end()) + //-- Setup a circular camera rig + const int iNviews = 5; + NViewDataSet d = NRealisticCamerasRing(iNviews, 5, NViewDatasetConfigurator(1, 1, 0, 0, 5, 0)); // Suppose a camera with Unit matrix as K + + // Link each camera to the two next ones + RelativeRotations vec_relativeRotEstimate; + std::vector> vec_unique; + for (std::size_t i = 0; i < iNviews; ++i) { - relativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel); - vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1)); - vec_unique.emplace_back(index0, index1); + std::size_t index0 = i; + std::size_t index1 = (i + 1) % iNviews; + std::size_t index2 = (i + 2) % iNviews; + + //-- compute true relative rotations of the triplet + // (index0->index1), (index1,index2), (index0->index2) + + Mat3 Rrel; + Vec3 trel; + // Add the relative motion if do not exist yet + if (std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index0, index1)) == vec_unique.end() && + std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index1, index0)) == vec_unique.end()) + { + relativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel); + vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1)); + vec_unique.emplace_back(index0, index1); + } + + if (std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index1, index2)) == vec_unique.end() && + std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index2, index1)) == vec_unique.end()) + { + relativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel); + vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1)); + vec_unique.emplace_back(index1, index2); + } + + if (std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index0, index2)) == vec_unique.end() && + std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index2, index0)) == vec_unique.end()) + { + relativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel); + vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1)); + vec_unique.emplace_back(index0, index2); + } } - if ( std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index1, index2)) == vec_unique.end() - && std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index2, index1)) == vec_unique.end()) + // Add 2 outliers rotations between (0->1), (2->3) + // (use a smaller weight since those rotations are less accurate) + for (std::size_t i = 0; i < vec_relativeRotEstimate.size(); ++i) { - relativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel); - vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1)); - vec_unique.emplace_back(index1, index2); + if (vec_relativeRotEstimate[i].i == 0 && vec_relativeRotEstimate[i].j == 1) + vec_relativeRotEstimate[i] = RelativeRotation(0, 1, RotationAroundX(degreeToRadian(0.1)), 0.5); + if (vec_relativeRotEstimate[i].i == 2 && vec_relativeRotEstimate[i].j == 3) + vec_relativeRotEstimate[i] = RelativeRotation(2, 3, RotationAroundX(degreeToRadian(0.6)), 0.5); } - if ( std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index0, index2)) == vec_unique.end() - && std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index2, index0)) == vec_unique.end()) + //- Solve the global rotation estimation problem : + Matrix3x3Arr vec_globalR(iNviews); + vec_globalR = d._R; + std::size_t nMainViewID = 0; + std::vector inliers; + bool bTest = GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, &inliers); + BOOST_CHECK(bTest); + + ALICEVISION_LOG_DEBUG("Inliers: " << inliers); + + // Check inlier list + BOOST_CHECK(std::accumulate(inliers.begin(), inliers.end(), 0) == 8); + // Check outlier have been found + BOOST_CHECK(inliers[0] == 0); + BOOST_CHECK(inliers[3] == 0); + + // Remove outliers and refine + RelativeRotations vec_relativeRotEstimateTemp; + for (std::size_t i = 0; i < inliers.size(); ++i) { - relativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel); - vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1)); - vec_unique.emplace_back(index0, index2); + if (inliers[i] == 1) + vec_relativeRotEstimateTemp.push_back(vec_relativeRotEstimate[i]); } - } + vec_relativeRotEstimate.swap(vec_relativeRotEstimateTemp); + BOOST_CHECK(GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, &inliers)); - // Add 2 outliers rotations between (0->1), (2->3) - // (use a smaller weight since those rotations are less accurate) - for (std::size_t i = 0; i < vec_relativeRotEstimate.size(); ++i) - { - if( vec_relativeRotEstimate[i].i == 0 && vec_relativeRotEstimate[i].j == 1) - vec_relativeRotEstimate[i] = RelativeRotation(0, 1, RotationAroundX(degreeToRadian(0.1)), 0.5); - if( vec_relativeRotEstimate[i].i == 2 && vec_relativeRotEstimate[i].j == 3) - vec_relativeRotEstimate[i] = RelativeRotation(2, 3, RotationAroundX(degreeToRadian(0.6)), 0.5); - } + // Check that the loop is closing + Mat3 rotCumul = vec_globalR[0]; + for (std::size_t i = 1; i < iNviews; ++i) + { + rotCumul *= vec_globalR[i]; + } - //- Solve the global rotation estimation problem : - Matrix3x3Arr vec_globalR(iNviews); - vec_globalR = d._R; - std::size_t nMainViewID = 0; - std::vector inliers; - bool bTest = GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, &inliers); - BOOST_CHECK(bTest); - - ALICEVISION_LOG_DEBUG("Inliers: " << inliers); - - // Check inlier list - BOOST_CHECK(std::accumulate(inliers.begin(), inliers.end(), 0) == 8); - // Check outlier have been found - BOOST_CHECK(inliers[0]== 0); - BOOST_CHECK(inliers[3] == 0); - - // Remove outliers and refine - RelativeRotations vec_relativeRotEstimateTemp; - for (std::size_t i = 0; i < inliers.size(); ++i) - { - if(inliers[i] == 1) - vec_relativeRotEstimateTemp.push_back(vec_relativeRotEstimate[i]); - } - vec_relativeRotEstimate.swap(vec_relativeRotEstimateTemp); - BOOST_CHECK( GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, &inliers)); - - // Check that the loop is closing - Mat3 rotCumul = vec_globalR[0]; - for (std::size_t i = 1; i < iNviews; ++i) - { - rotCumul*= vec_globalR[i]; - } + EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4); + // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT) + if (SIGN(vec_globalR[0](0, 0)) != SIGN(d._R[0](0, 0))) + { + Mat3 Rrel; + Vec3 trel; + relativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel); + for (std::size_t i = 0; i < iNviews; ++i) + vec_globalR[i] *= Rrel; + } - EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4); - // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT) - if ( SIGN(vec_globalR[0](0,0)) != SIGN( d._R[0](0,0) )) - { - Mat3 Rrel; - Vec3 trel; - relativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel); + // Check that each global rotations is near the true ones for (std::size_t i = 0; i < iNviews; ++i) - vec_globalR[i] *= Rrel; - } - - // Check that each global rotations is near the true ones - for (std::size_t i = 0; i < iNviews; ++i) - { - BOOST_CHECK_SMALL(FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8); - } + { + BOOST_CHECK_SMALL(FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8); + } } /* @@ -397,4 +393,3 @@ BOOST_AUTO_TEST_CASE ( rotationAveraging_RobustRegressionL1PD) TestRobustRegressionL1PD(); } */ - diff --git a/src/aliceVision/multiview/translationAveraging/common.hpp b/src/aliceVision/multiview/translationAveraging/common.hpp index 5b4359330a..1ba6176751 100644 --- a/src/aliceVision/multiview/translationAveraging/common.hpp +++ b/src/aliceVision/multiview/translationAveraging/common.hpp @@ -17,35 +17,34 @@ namespace aliceVision { namespace translationAveraging { /// Relative information [Rij|tij] for a pair -typedef std::pair< Pair, std::pair > relativeInfo; +typedef std::pair> relativeInfo; -typedef std::vector< relativeInfo > RelativeInfoVec; -typedef std::map< Pair, std::pair > RelativeInfoMap; +typedef std::vector RelativeInfoVec; +typedef std::map> RelativeInfoMap; // List the pairs used by the relative motions -inline PairSet getPairs(const RelativeInfoVec & vec_relative) +inline PairSet getPairs(const RelativeInfoVec& vec_relative) { - PairSet pair_set; - for(size_t i = 0; i < vec_relative.size(); ++i) - { - const relativeInfo & rel = vec_relative[i]; - pair_set.insert(Pair(rel.first.first, rel.first.second)); - } - return pair_set; + PairSet pair_set; + for (size_t i = 0; i < vec_relative.size(); ++i) + { + const relativeInfo& rel = vec_relative[i]; + pair_set.insert(Pair(rel.first.first, rel.first.second)); + } + return pair_set; } // List the index used by the relative motions -inline std::set getIndexT(const RelativeInfoVec & vec_relative) +inline std::set getIndexT(const RelativeInfoVec& vec_relative) { - std::set indexT_set; - for (RelativeInfoVec::const_iterator iter = vec_relative.begin(); - iter != vec_relative.end(); ++iter) - { - indexT_set.insert(iter->first.first); - indexT_set.insert(iter->first.second); - } - return indexT_set; + std::set indexT_set; + for (RelativeInfoVec::const_iterator iter = vec_relative.begin(); iter != vec_relative.end(); ++iter) + { + indexT_set.insert(iter->first.first); + indexT_set.insert(iter->first.second); + } + return indexT_set; } -} // namespace translationAveraging -} // namespace aliceVision +} // namespace translationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/translationAveraging/solver.hpp b/src/aliceVision/multiview/translationAveraging/solver.hpp index 199f69c1e9..ff23fab234 100644 --- a/src/aliceVision/multiview/translationAveraging/solver.hpp +++ b/src/aliceVision/multiview/translationAveraging/solver.hpp @@ -15,18 +15,15 @@ namespace translationAveraging { * * Implementation of [1] : "5. Solving the Translations Problem" equation (3) */ -bool solve_translations_problem_l2_chordal( - const int* edges, - const double* poses, - const double* weights, - int num_edges, - double loss_width, - double* X, - double function_tolerance, - double parameter_tolerance, - int max_iterations -); - +bool solve_translations_problem_l2_chordal(const int* edges, + const double* poses, + const double* weights, + int num_edges, + double loss_width, + double* X, + double function_tolerance, + double parameter_tolerance, + int max_iterations); /** * @brief Registration of relative translations to global translations. It implements LInf minimization of [2] @@ -54,15 +51,11 @@ bool solve_translations_problem_l2_chordal( * @param[in] d_l1_loss_threshold optionnal threshold for SoftL1 loss (-1: no loss function) * @return True if the registration can be solved */ -bool -solve_translations_problem_softl1 -( - const std::vector & vec_initial_estimates, - const bool b_translation_triplets, - const int nb_poses, - std::vector & translations, - const double d_l1_loss_threshold = 0.01 -); +bool solve_translations_problem_softl1(const std::vector& vec_initial_estimates, + const bool b_translation_triplets, + const int nb_poses, + std::vector& translations, + const double d_l1_loss_threshold = 0.01); -} // namespace translationAveraging -} // namespace aliceVision +} // namespace translationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/translationAveraging/solverL1Soft.cpp b/src/aliceVision/multiview/translationAveraging/solverL1Soft.cpp index cd4c91776b..86c42149af 100644 --- a/src/aliceVision/multiview/translationAveraging/solverL1Soft.cpp +++ b/src/aliceVision/multiview/translationAveraging/solverL1Soft.cpp @@ -25,201 +25,172 @@ namespace translationAveraging { // measure the consistency (residual error) from the relative translations (constant) to the scales and global camera translations struct RelativeTranslationError { - RelativeTranslationError - ( - double t_ij_x, - double t_ij_y, - double t_ij_z - ) - : t_ij_x_(t_ij_x), t_ij_y_(t_ij_y), t_ij_z_(t_ij_z) - {} - - template - bool operator() - ( - const T* const t_i, - const T* const t_j, - const T* const R_ij, - const T* const s_ij, - T* residuals - ) const - { - // - // residual = t_j - R_ij t_i - s_ij * t_ij - // - T rotated_t_i[3]; - // Rotate the point according the relative local rotation - ceres::AngleAxisRotatePoint(R_ij, t_i, rotated_t_i); - - residuals[0] = T(t_j[0] - rotated_t_i[0] - *s_ij * t_ij_x_); - residuals[1] = T(t_j[1] - rotated_t_i[1] - *s_ij * t_ij_y_); - residuals[2] = T(t_j[2] - rotated_t_i[2] - *s_ij * t_ij_z_); - - return true; - } + RelativeTranslationError(double t_ij_x, double t_ij_y, double t_ij_z) + : t_ij_x_(t_ij_x), + t_ij_y_(t_ij_y), + t_ij_z_(t_ij_z) + {} + + template + bool operator()(const T* const t_i, const T* const t_j, const T* const R_ij, const T* const s_ij, T* residuals) const + { + // + // residual = t_j - R_ij t_i - s_ij * t_ij + // + T rotated_t_i[3]; + // Rotate the point according the relative local rotation + ceres::AngleAxisRotatePoint(R_ij, t_i, rotated_t_i); + + residuals[0] = T(t_j[0] - rotated_t_i[0] - *s_ij * t_ij_x_); + residuals[1] = T(t_j[1] - rotated_t_i[1] - *s_ij * t_ij_y_); + residuals[2] = T(t_j[2] - rotated_t_i[2] - *s_ij * t_ij_z_); + + return true; + } - double t_ij_x_, t_ij_y_, t_ij_z_; + double t_ij_x_, t_ij_y_, t_ij_z_; }; // Cost penalizing scales smaller than 1. struct SmallScaleError { - SmallScaleError - ( - double weight = 1.0 - ) + SmallScaleError(double weight = 1.0) : weight_(weight) - {} - - template - bool operator() - ( - const T* const s_ij, T* residual - ) const - { - residual[0] = (*s_ij > T(1.0)) ? T(0.0) : (T(weight_) * (T(1.0) - *s_ij)); - return true; - } + {} - double weight_; -}; + template + bool operator()(const T* const s_ij, T* residual) const + { + residual[0] = (*s_ij > T(1.0)) ? T(0.0) : (T(weight_) * (T(1.0) - *s_ij)); + return true; + } + double weight_; +}; -bool solve_translations_problem_softl1 -( - const std::vector & vec_initial_estimates, - const bool b_translation_triplets, - const int nb_poses, - std::vector & translations, - const double d_l1_loss_threshold -) +bool solve_translations_problem_softl1(const std::vector& vec_initial_estimates, + const bool b_translation_triplets, + const int nb_poses, + std::vector& translations, + const double d_l1_loss_threshold) { - ceres::Problem problem; - - // Build the parameters arrays: - // - camera translation - // - relative translation group scales - // - relative rotations - - std::vector vec_translations(3*nb_poses, 1.0); - const unsigned nb_scales = vec_initial_estimates.size() / (b_translation_triplets ? 3 : 1); - std::vector vec_scales(nb_scales, 1.0); - - if (!b_translation_triplets) - { - // use random initialization, since using only single bearing vector results - // in a is less conditionned system. - for (int i=0; i vec_translations(3 * nb_poses, 1.0); + const unsigned nb_scales = vec_initial_estimates.size() / (b_translation_triplets ? 3 : 1); + std::vector vec_scales(nb_scales, 1.0); + + if (!b_translation_triplets) + { + // use random initialization, since using only single bearing vector results + // in a is less conditionned system. + for (int i = 0; i < nb_scales; ++i) + { + vec_scales[i] = (double)rand() / RAND_MAX; + } + + for (int i = 0; i < 3 * nb_poses; ++i) + { + vec_translations[i] = (double)rand() / RAND_MAX; + } } - for (int i=0; i<3*nb_poses; ++i) { - vec_translations[i] = (double)rand() / RAND_MAX; + // Relative rotations array + std::vector vec_relative_rotations(vec_initial_estimates.size() * 3, 0.0); + size_t cpt = 0; + for (const relativeInfo& info : vec_initial_estimates) + { + ceres::RotationMatrixToAngleAxis((const double*)info.second.first.data(), &vec_relative_rotations[cpt]); + cpt += 3; } - } - - // Relative rotations array - std::vector vec_relative_rotations(vec_initial_estimates.size()*3, 0.0); - size_t cpt = 0; - for (const relativeInfo& info : vec_initial_estimates) - { - ceres::RotationMatrixToAngleAxis( - (const double*)info.second.first.data(), - &vec_relative_rotations[cpt]); - cpt += 3; - } - - ceres::LossFunction * loss = - (d_l1_loss_threshold < 0) ? nullptr : new ceres::SoftLOneLoss(d_l1_loss_threshold); - - // Add constraints to the minimization - // - // A. Add cost functor from camera translation to the relative informations - cpt = 0; - IndexT scale_idx = 0; - for (const relativeInfo& info : vec_initial_estimates) - { - const Pair & ids = info.first; - const IndexT I = ids.first; - const IndexT J = ids.second; - const Vec3 t_ij = info.second.second; - - // Each Residual block takes 2 camera translations & the relative rotation & a scale - // and outputs a 3 dimensional residual. - ceres::CostFunction* cost_function = - new ceres::AutoDiffCostFunction( - new RelativeTranslationError(t_ij(0), t_ij(1), t_ij(2))); - problem.AddResidualBlock( - cost_function, - loss, - &vec_translations[I*3], - &vec_translations[J*3], - &vec_relative_rotations[cpt*3], - &vec_scales[scale_idx]); - // the relative rotation is set as constant - problem.SetParameterBlockConstant(&vec_relative_rotations[cpt*3]); - if (cpt % (b_translation_triplets ? 3 : 1) == 0 && cpt != 0) - scale_idx += 1; - ++cpt; - } - - // B. Constraint the scale factors: - // Prefer scale > 1, since a trivial solution is translations = {0,...,0}). - for (unsigned i = 0; i < nb_scales; ++i) - { - ceres::CostFunction* cost_function = - new ceres::AutoDiffCostFunction( - new SmallScaleError(1.0)); - - problem.AddResidualBlock(cost_function, nullptr, &vec_scales[i]); - } - // Set one center as known (to fix the gauge freedom) - vec_translations[0] = vec_translations[1] = vec_translations[2] = 0.0; - problem.SetParameterBlockConstant(&vec_translations[0]); - - // Solve - ceres::Solver::Options options; - options.minimizer_progress_to_stdout = false; - if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE) || + + ceres::LossFunction* loss = (d_l1_loss_threshold < 0) ? nullptr : new ceres::SoftLOneLoss(d_l1_loss_threshold); + + // Add constraints to the minimization + // + // A. Add cost functor from camera translation to the relative informations + cpt = 0; + IndexT scale_idx = 0; + for (const relativeInfo& info : vec_initial_estimates) + { + const Pair& ids = info.first; + const IndexT I = ids.first; + const IndexT J = ids.second; + const Vec3 t_ij = info.second.second; + + // Each Residual block takes 2 camera translations & the relative rotation & a scale + // and outputs a 3 dimensional residual. + ceres::CostFunction* cost_function = + new ceres::AutoDiffCostFunction(new RelativeTranslationError(t_ij(0), t_ij(1), t_ij(2))); + problem.AddResidualBlock( + cost_function, loss, &vec_translations[I * 3], &vec_translations[J * 3], &vec_relative_rotations[cpt * 3], &vec_scales[scale_idx]); + // the relative rotation is set as constant + problem.SetParameterBlockConstant(&vec_relative_rotations[cpt * 3]); + if (cpt % (b_translation_triplets ? 3 : 1) == 0 && cpt != 0) + scale_idx += 1; + ++cpt; + } + + // B. Constraint the scale factors: + // Prefer scale > 1, since a trivial solution is translations = {0,...,0}). + for (unsigned i = 0; i < nb_scales; ++i) + { + ceres::CostFunction* cost_function = new ceres::AutoDiffCostFunction(new SmallScaleError(1.0)); + + problem.AddResidualBlock(cost_function, nullptr, &vec_scales[i]); + } + // Set one center as known (to fix the gauge freedom) + vec_translations[0] = vec_translations[1] = vec_translations[2] = 0.0; + problem.SetParameterBlockConstant(&vec_translations[0]); + + // Solve + ceres::Solver::Options options; + options.minimizer_progress_to_stdout = false; + if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE) || #if ALICEVISION_CERES_HAS_CXSPARSE - ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE) || + ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE) || #endif - ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) - { - options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; - } - else - { - options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - } - options.max_num_iterations = std::max(50, (int)(nb_scales * 2)); - options.minimizer_progress_to_stdout = false; - options.logging_type = ceres::SILENT; - - // set number of threads, 1 if openMP is not enabled - options.num_threads = omp_get_max_threads(); + ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) + { + options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; + } + else + { + options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; + } + options.max_num_iterations = std::max(50, (int)(nb_scales * 2)); + options.minimizer_progress_to_stdout = false; + options.logging_type = ceres::SILENT; + + // set number of threads, 1 if openMP is not enabled + options.num_threads = omp_get_max_threads(); #if CERES_VERSION_MAJOR < 2 - options.num_linear_solver_threads = omp_get_max_threads(); + options.num_linear_solver_threads = omp_get_max_threads(); #endif - ceres::Solver::Summary summary; - ceres::Solve(options, &problem, &summary); - - if (!summary.IsSolutionUsable()) - { - ALICEVISION_LOG_DEBUG(summary.FullReport()); - return false; - } - - // Fill the global translations array - translations.resize(nb_poses); - cpt = 0; - for (unsigned i = 0; i < nb_poses; ++i, cpt+=3) - { - translations[i] << vec_translations[cpt], vec_translations[cpt+1], vec_translations[cpt+2]; - } - return true; + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + if (!summary.IsSolutionUsable()) + { + ALICEVISION_LOG_DEBUG(summary.FullReport()); + return false; + } + + // Fill the global translations array + translations.resize(nb_poses); + cpt = 0; + for (unsigned i = 0; i < nb_poses; ++i, cpt += 3) + { + translations[i] << vec_translations[cpt], vec_translations[cpt + 1], vec_translations[cpt + 2]; + } + return true; } -} // namespace translationAveraging -} // namespace aliceVision +} // namespace translationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/translationAveraging/solverL2Chordal.cpp b/src/aliceVision/multiview/translationAveraging/solverL2Chordal.cpp index 4d062c3a29..647b9d2c51 100644 --- a/src/aliceVision/multiview/translationAveraging/solverL2Chordal.cpp +++ b/src/aliceVision/multiview/translationAveraging/solverL2Chordal.cpp @@ -24,152 +24,154 @@ namespace translationAveraging { using ceres::AutoDiffCostFunction; using ceres::CostFunction; +using ceres::HuberLoss; using ceres::Problem; -using ceres::Solver; using ceres::Solve; -using ceres::HuberLoss; +using ceres::Solver; -struct ChordFunctor { - ChordFunctor(const double *direction, double weight) - : u_(direction), w_(weight){} - - const double *u_; // local estimate of the translation direction - const double w_; // weight - - // Residual computation (3x1: a residual along each unit axis) - template - bool operator()(const T* const x0, const T* const x1, T* residual) const - { - // compute ||x1 - x0||_2 - T norm = sqrt((x1[0]-x0[0])*(x1[0]-x0[0]) + - (x1[1]-x0[1])*(x1[1]-x0[1]) + - (x1[2]-x0[2])*(x1[2]-x0[2])); - residual[0] = w_*((x1[0]-x0[0]) / norm - T(u_[0])); - residual[1] = w_*((x1[1]-x0[1]) / norm - T(u_[1])); - residual[2] = w_*((x1[2]-x0[2]) / norm - T(u_[2])); - return true; - } +struct ChordFunctor +{ + ChordFunctor(const double* direction, double weight) + : u_(direction), + w_(weight) + {} + + const double* u_; // local estimate of the translation direction + const double w_; // weight + + // Residual computation (3x1: a residual along each unit axis) + template + bool operator()(const T* const x0, const T* const x1, T* residual) const + { + // compute ||x1 - x0||_2 + T norm = sqrt((x1[0] - x0[0]) * (x1[0] - x0[0]) + (x1[1] - x0[1]) * (x1[1] - x0[1]) + (x1[2] - x0[2]) * (x1[2] - x0[2])); + residual[0] = w_ * ((x1[0] - x0[0]) / norm - T(u_[0])); + residual[1] = w_ * ((x1[1] - x0[1]) / norm - T(u_[1])); + residual[2] = w_ * ((x1[2] - x0[2]) / norm - T(u_[2])); + return true; + } }; -void reindex_problem(int* edges, int num_edges, std::vector &reindex_lookup); - -bool solve_translations_problem_l2_chordal( - const int* edges, - const double* poses, - const double* weights, - int num_edges, - double loss_width, - double* X, - double function_tolerance, - double parameter_tolerance, - int max_iterations) +void reindex_problem(int* edges, int num_edges, std::vector& reindex_lookup); + +bool solve_translations_problem_l2_chordal(const int* edges, + const double* poses, + const double* weights, + int num_edges, + double loss_width, + double* X, + double function_tolerance, + double parameter_tolerance, + int max_iterations) { + // re index the edges to be a sequential set + std::vector _edges(edges, edges + 2 * num_edges); + std::vector reindex_lookup; + reindex_problem(&_edges[0], num_edges, reindex_lookup); + const int num_nodes = reindex_lookup.size(); + + // Init with a random guess solution + const std::size_t guessSize = 3 * num_nodes; + std::vector x(guessSize); + Mat randGuesses = Mat::Random(1, guessSize); + for (std::size_t i = 0; i < guessSize; ++i) + { + x[i] = randGuesses(0, i); + } - // re index the edges to be a sequential set - std::vector _edges(edges, edges+2*num_edges); - std::vector reindex_lookup; - reindex_problem(&_edges[0], num_edges, reindex_lookup); - const int num_nodes = reindex_lookup.size(); - - // Init with a random guess solution - const std::size_t guessSize = 3*num_nodes; - std::vector x(guessSize); - Mat randGuesses = Mat::Random(1, guessSize); - for (std::size_t i = 0; i < guessSize; ++i) - { - x[i] = randGuesses(0, i); - } - - // add the parameter blocks (a 3-vector for each node) - Problem problem; - for (int i=0; i( - new ChordFunctor(poses+3*i, weights[i])); - - if (loss_width == 0.0) { - // No robust loss function - problem.AddResidualBlock(cost_function, nullptr, &x[3*_edges[2*i+0]], &x[3*_edges[2*i+1]]); - } else { - problem.AddResidualBlock(cost_function, new ceres::HuberLoss(loss_width), &x[3*_edges[2*i+0]], &x[3*_edges[2*i+1]]); + // add the parameter blocks (a 3-vector for each node) + Problem problem; + for (int i = 0; i < num_nodes; ++i) + problem.AddParameterBlock(&x[3 * i], 3); + + // set the residual function (chordal distance for each edge) + for (int i = 0; i < num_edges; ++i) + { + CostFunction* cost_function = new AutoDiffCostFunction(new ChordFunctor(poses + 3 * i, weights[i])); + + if (loss_width == 0.0) + { + // No robust loss function + problem.AddResidualBlock(cost_function, nullptr, &x[3 * _edges[2 * i + 0]], &x[3 * _edges[2 * i + 1]]); + } + else + { + problem.AddResidualBlock(cost_function, new ceres::HuberLoss(loss_width), &x[3 * _edges[2 * i + 0]], &x[3 * _edges[2 * i + 1]]); + } } - } - // Fix first camera in {0,0,0}: fix the translation ambiguity - x[0] = x[1] = x[2] = 0.0; - problem.SetParameterBlockConstant(&x[0]); + // Fix first camera in {0,0,0}: fix the translation ambiguity + x[0] = x[1] = x[2] = 0.0; + problem.SetParameterBlockConstant(&x[0]); - // solve - Solver::Options options; - // set number of threads, 1 if openMP is not enabled - options.num_threads = omp_get_max_threads(); + // solve + Solver::Options options; + // set number of threads, 1 if openMP is not enabled + options.num_threads = omp_get_max_threads(); #if CERES_VERSION_MAJOR < 2 - options.num_linear_solver_threads = omp_get_max_threads(); + options.num_linear_solver_threads = omp_get_max_threads(); #endif - //options.minimizer_progress_to_stdout = true; - options.max_num_iterations = max_iterations; - options.function_tolerance = function_tolerance; - options.parameter_tolerance = parameter_tolerance; - if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE) || + // options.minimizer_progress_to_stdout = true; + options.max_num_iterations = max_iterations; + options.function_tolerance = function_tolerance; + options.parameter_tolerance = parameter_tolerance; + if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE) || #if ALICEVISION_CERES_HAS_CXSPARSE - ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE) || + ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE) || #endif - ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) - { - options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; - } - else - { - options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - } - - Solver::Summary summary; - Solve(options, &problem, &summary); - - ALICEVISION_LOG_DEBUG(summary.FullReport()); - - if (summary.IsSolutionUsable()) - { - // undo the re indexing - for (int i=0; i &reindex_lookup) +void reindex_problem(int* edges, int num_edges, std::vector& reindex_lookup) { - // get the unique set of nodes - std::set nodes; - for (int i=0; i<2*num_edges; ++i) - nodes.insert(edges[i]); - - reindex_lookup.clear(); - reindex_lookup.reserve(nodes.size()); - - std::map reindexing_key; - // iterator through them and assign a new Id to each vertex - std::set::const_iterator it; - int n=0; - for (it = nodes.begin(); it != nodes.end(); ++it, ++n) { - reindex_lookup.push_back(*it); - reindexing_key[*it] = n; - } - - // now renumber the edges - for (int i=0; i<2*num_edges; ++i) - edges[i] = reindexing_key[edges[i]]; + // get the unique set of nodes + std::set nodes; + for (int i = 0; i < 2 * num_edges; ++i) + nodes.insert(edges[i]); + + reindex_lookup.clear(); + reindex_lookup.reserve(nodes.size()); + + std::map reindexing_key; + // iterator through them and assign a new Id to each vertex + std::set::const_iterator it; + int n = 0; + for (it = nodes.begin(); it != nodes.end(); ++it, ++n) + { + reindex_lookup.push_back(*it); + reindexing_key[*it] = n; + } + + // now renumber the edges + for (int i = 0; i < 2 * num_edges; ++i) + edges[i] = reindexing_key[edges[i]]; } -} // namespace translationAveraging -} // namespace aliceVision +} // namespace translationAveraging +} // namespace aliceVision diff --git a/src/aliceVision/multiview/translationAveraging/translationAveragingTest.hpp b/src/aliceVision/multiview/translationAveraging/translationAveragingTest.hpp index 6f55ad67c5..616c9863e3 100644 --- a/src/aliceVision/multiview/translationAveraging/translationAveragingTest.hpp +++ b/src/aliceVision/multiview/translationAveraging/translationAveragingTest.hpp @@ -29,139 +29,121 @@ using namespace aliceVision::graph; using namespace svg; -int modifiedMod -( - int number, - int modulus -) +int modifiedMod(int number, int modulus) { - int result = number % modulus; - if (result < 0) result += modulus; - return result; + int result = number % modulus; + if (result < 0) + result += modulus; + return result; } //-- Export a series of camera positions to a SVG surface of specified squared size -void visibleCamPosToSVGSurface -( - const std::vector & vec_Ci, - const std::string & fileName -) +void visibleCamPosToSVGSurface(const std::vector& vec_Ci, const std::string& fileName) { - Mat points(3, vec_Ci.size()); - for(size_t i = 0; i < vec_Ci.size(); ++i) - { - points.col(i) = vec_Ci[i]; - } - - Vec mean, variance; - MeanAndVarianceAlongRows(points, &mean, &variance); - - const double xfactor = sqrt(2.0 / variance(0)); - const double yfactor = sqrt(2.0 / variance(2)); - - std::vector out = vec_Ci; - for(size_t i = 0; i < vec_Ci.size(); ++i) - { - out[i](0) = ((out[i](0) * xfactor) + -xfactor * mean(0)) * 30 + 100; - out[i](2) = ((out[i](2) * yfactor) + -yfactor * mean(2)) * 30 + 100; - } - - if (!fileName.empty()) - { - const double size = 200; - svgDrawer svgSurface_GT(size,size); - for(size_t i = 0; i < vec_Ci.size(); ++i) + Mat points(3, vec_Ci.size()); + for (size_t i = 0; i < vec_Ci.size(); ++i) { - svgSurface_GT.drawCircle(out[i](0), out[i](2), - 3,svgStyle().stroke("black",0.2).fill("red")); + points.col(i) = vec_Ci[i]; } - std::ostringstream osSvgGT; - osSvgGT << fileName; - std::ofstream svgFileGT( osSvgGT.str()); - svgFileGT << svgSurface_GT.closeSvgFile().str(); - } -} -NViewDataSet Setup_RelativeTranslations_AndNviewDataset -( - std::vector & vec_relative_estimates, - const int focal = 1000, - const int principal_Point = 500, - const int iNviews = 12, - const int iNbPoints = 6, - const bool bCardiod = true, - const bool bRelative_Translation_PerTriplet = false -) -{ - const NViewDataSet d = - bCardiod ? - NRealisticCamerasCardioid( - iNviews, iNbPoints, - NViewDatasetConfigurator(focal,focal,principal_Point,principal_Point,5,0)) - :NRealisticCamerasRing( - iNviews, iNbPoints, - NViewDatasetConfigurator(focal,focal,principal_Point,principal_Point,5,0)); - - // Init the relative pair of motion depending of the asked configuration: - // -2-view: bearing direction, - // -3-view: triplet of bearing direction. - std::vector< std::pair > map_pairs; - if (bRelative_Translation_PerTriplet) - { - std::vector< graph::Triplet > vec_triplets; - for (IndexT i = 0; i < iNviews; ++i) + Vec mean, variance; + MeanAndVarianceAlongRows(points, &mean, &variance); + + const double xfactor = sqrt(2.0 / variance(0)); + const double yfactor = sqrt(2.0 / variance(2)); + + std::vector out = vec_Ci; + for (size_t i = 0; i < vec_Ci.size(); ++i) { - const IndexT iPlus1 = modifiedMod(i+1,iNviews); - const IndexT iPlus2 = modifiedMod(i+2,iNviews); - //-- sort the triplet index to have a monotonic ascending series of value - IndexT triplet[3] = {i, iPlus1, iPlus2}; - std::sort(&triplet[0], &triplet[3]); - vec_triplets.push_back(Triplet(triplet[0],triplet[1],triplet[2])); + out[i](0) = ((out[i](0) * xfactor) + -xfactor * mean(0)) * 30 + 100; + out[i](2) = ((out[i](2) * yfactor) + -yfactor * mean(2)) * 30 + 100; } - for (size_t i = 0; i < vec_triplets.size(); ++i) + + if (!fileName.empty()) { - const graph::Triplet & triplet = vec_triplets[i]; - const size_t I = triplet.i, J = triplet.j , K = triplet.k; + const double size = 200; + svgDrawer svgSurface_GT(size, size); + for (size_t i = 0; i < vec_Ci.size(); ++i) + { + svgSurface_GT.drawCircle(out[i](0), out[i](2), 3, svgStyle().stroke("black", 0.2).fill("red")); + } + std::ostringstream osSvgGT; + osSvgGT << fileName; + std::ofstream svgFileGT(osSvgGT.str()); + svgFileGT << svgSurface_GT.closeSvgFile().str(); + } +} - map_pairs.emplace_back(I,J); - map_pairs.emplace_back(J,K); - map_pairs.emplace_back(I,K); +NViewDataSet Setup_RelativeTranslations_AndNviewDataset(std::vector& vec_relative_estimates, + const int focal = 1000, + const int principal_Point = 500, + const int iNviews = 12, + const int iNbPoints = 6, + const bool bCardiod = true, + const bool bRelative_Translation_PerTriplet = false) +{ + const NViewDataSet d = + bCardiod ? NRealisticCamerasCardioid(iNviews, iNbPoints, NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)) + : NRealisticCamerasRing(iNviews, iNbPoints, NViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0)); + + // Init the relative pair of motion depending of the asked configuration: + // -2-view: bearing direction, + // -3-view: triplet of bearing direction. + std::vector> map_pairs; + if (bRelative_Translation_PerTriplet) + { + std::vector vec_triplets; + for (IndexT i = 0; i < iNviews; ++i) + { + const IndexT iPlus1 = modifiedMod(i + 1, iNviews); + const IndexT iPlus2 = modifiedMod(i + 2, iNviews); + //-- sort the triplet index to have a monotonic ascending series of value + IndexT triplet[3] = {i, iPlus1, iPlus2}; + std::sort(&triplet[0], &triplet[3]); + vec_triplets.push_back(Triplet(triplet[0], triplet[1], triplet[2])); + } + for (size_t i = 0; i < vec_triplets.size(); ++i) + { + const graph::Triplet& triplet = vec_triplets[i]; + const size_t I = triplet.i, J = triplet.j, K = triplet.k; + + map_pairs.emplace_back(I, J); + map_pairs.emplace_back(J, K); + map_pairs.emplace_back(I, K); + } } - } - else - { - //-- Setup initial pairs that will be considered (link each camera to the two next) - for (size_t i = 0; i < iNviews; ++i) + else { - for (size_t j=i; j<=i+2; ++j) - { - const size_t jj = modifiedMod(j,iNviews); - if (i != jj) - map_pairs.emplace_back(i,jj); - } + //-- Setup initial pairs that will be considered (link each camera to the two next) + for (size_t i = 0; i < iNviews; ++i) + { + for (size_t j = i; j <= i + 2; ++j) + { + const size_t jj = modifiedMod(j, iNviews); + if (i != jj) + map_pairs.emplace_back(i, jj); + } + } } - } - // Compute all the required relative motions - for (const std::pair & iter : map_pairs) - { - const size_t I = iter.first; - const size_t J = iter.second; - - //-- Build camera alias over GT translations and rotations: - const Mat3 & RI = d._R[I]; - const Mat3 & RJ = d._R[J]; - const Vec3 & ti = d._t[I]; - const Vec3 & tj = d._t[J]; - - //-- Build relative motions (that feeds the Linear program formulation) + // Compute all the required relative motions + for (const std::pair& iter : map_pairs) { - Mat3 RijGt; - Vec3 tij; - relativeCameraMotion(RI, ti, RJ, tj, &RijGt, &tij); - vec_relative_estimates.push_back( - std::make_pair(std::make_pair(I, J), std::make_pair(RijGt, tij))); + const size_t I = iter.first; + const size_t J = iter.second; + + //-- Build camera alias over GT translations and rotations: + const Mat3& RI = d._R[I]; + const Mat3& RJ = d._R[J]; + const Vec3& ti = d._t[I]; + const Vec3& tj = d._t[J]; + + //-- Build relative motions (that feeds the Linear program formulation) + { + Mat3 RijGt; + Vec3 tij; + relativeCameraMotion(RI, ti, RJ, tj, &RijGt, &tij); + vec_relative_estimates.push_back(std::make_pair(std::make_pair(I, J), std::make_pair(RijGt, tij))); + } } - } - return d; + return d; } - diff --git a/src/aliceVision/multiview/translationAveraging/translationAveraging_test.cpp b/src/aliceVision/multiview/translationAveraging/translationAveraging_test.cpp index 02ce25cbb0..b604de114f 100644 --- a/src/aliceVision/multiview/translationAveraging/translationAveraging_test.cpp +++ b/src/aliceVision/multiview/translationAveraging/translationAveraging_test.cpp @@ -23,187 +23,175 @@ using namespace aliceVision; using namespace aliceVision::translationAveraging; -BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_Triplets_softL1_Ceres) { - - const int focal = 1000; - const int principal_Point = 500; - //-- Setup a circular camera rig or "cardioid". - const int iNviews = 12; - const int iNbPoints = 6; - - const bool bCardiod = true; - const bool bRelative_Translation_PerTriplet = true; - std::vector vec_relative_estimates; - - const NViewDataSet d = - Setup_RelativeTranslations_AndNviewDataset - ( - vec_relative_estimates, - focal, principal_Point, iNviews, iNbPoints, - bCardiod, bRelative_Translation_PerTriplet - ); - - d.exportToPLY("global_translations_from_triplets_GT.ply"); - visibleCamPosToSVGSurface(d._C, "global_translations_from_triplets_GT.svg"); - - // Solve the translation averaging problem: - std::vector vec_translations; - BOOST_CHECK(solve_translations_problem_softl1( - vec_relative_estimates, bRelative_Translation_PerTriplet, iNviews, vec_translations)); - - BOOST_CHECK_EQUAL(iNviews, vec_translations.size()); - - // Check accuracy of the found translations - for (unsigned i = 0; i < iNviews; ++i) - { - const Vec3 t = vec_translations[i]; - const Mat3 & Ri = d._R[i]; - const Vec3 C_computed = - Ri.transpose() * t; - - const Vec3 C_GT = d._C[i] - d._C[0]; - - //-- Check that found camera position is equal to GT value - if (i==0) { - EXPECT_MATRIX_NEAR(C_computed, C_GT, 1e-6); +BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_Triplets_softL1_Ceres) +{ + const int focal = 1000; + const int principal_Point = 500; + //-- Setup a circular camera rig or "cardioid". + const int iNviews = 12; + const int iNbPoints = 6; + + const bool bCardiod = true; + const bool bRelative_Translation_PerTriplet = true; + std::vector vec_relative_estimates; + + const NViewDataSet d = Setup_RelativeTranslations_AndNviewDataset( + vec_relative_estimates, focal, principal_Point, iNviews, iNbPoints, bCardiod, bRelative_Translation_PerTriplet); + + d.exportToPLY("global_translations_from_triplets_GT.ply"); + visibleCamPosToSVGSurface(d._C, "global_translations_from_triplets_GT.svg"); + + // Solve the translation averaging problem: + std::vector vec_translations; + BOOST_CHECK(solve_translations_problem_softl1(vec_relative_estimates, bRelative_Translation_PerTriplet, iNviews, vec_translations)); + + BOOST_CHECK_EQUAL(iNviews, vec_translations.size()); + + // Check accuracy of the found translations + for (unsigned i = 0; i < iNviews; ++i) + { + const Vec3 t = vec_translations[i]; + const Mat3& Ri = d._R[i]; + const Vec3 C_computed = -Ri.transpose() * t; + + const Vec3 C_GT = d._C[i] - d._C[0]; + + //-- Check that found camera position is equal to GT value + if (i == 0) + { + EXPECT_MATRIX_NEAR(C_computed, C_GT, 1e-6); + } + else + { + BOOST_CHECK_SMALL(DistanceLInfinity(C_computed.normalized(), C_GT.normalized()), 1e-6); + } } - else { - BOOST_CHECK_SMALL(DistanceLInfinity(C_computed.normalized(), C_GT.normalized()), 1e-6); +} + +BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_softl1_Ceres) +{ + const int focal = 1000; + const int principal_Point = 500; + //-- Setup a circular camera rig or "cardiod". + const int iNviews = 12; + const int iNbPoints = 6; + + const bool bCardiod = true; + const bool bRelative_Translation_PerTriplet = false; + std::vector vec_relative_estimates; + + const NViewDataSet d = Setup_RelativeTranslations_AndNviewDataset( + vec_relative_estimates, focal, principal_Point, iNviews, iNbPoints, bCardiod, bRelative_Translation_PerTriplet); + + d.exportToPLY("global_translations_from_Tij_GT.ply"); + visibleCamPosToSVGSurface(d._C, "global_translations_from_Tij_GT.svg"); + + // Solve the translation averaging problem: + std::vector vec_translations; + BOOST_CHECK(solve_translations_problem_softl1(vec_relative_estimates, bRelative_Translation_PerTriplet, iNviews, vec_translations)); + + BOOST_CHECK_EQUAL(iNviews, vec_translations.size()); + + // Check accuracy of the found translations + for (unsigned i = 0; i < iNviews; ++i) + { + const Vec3 t = vec_translations[i]; + const Mat3& Ri = d._R[i]; + const Vec3 C_computed = -Ri.transpose() * t; + + const Vec3 C_GT = d._C[i] - d._C[0]; + + //-- Check that found camera position is equal to GT value + if (i == 0) + { + EXPECT_MATRIX_NEAR(C_computed, C_GT, 1e-6); + } + else + { + BOOST_CHECK_SMALL(DistanceLInfinity(C_computed.normalized(), C_GT.normalized()), 1e-6); + } } - } } -BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_softl1_Ceres) { +BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_Triplets_l2_chordal) +{ + makeRandomOperationsReproducible(); - const int focal = 1000; - const int principal_Point = 500; - //-- Setup a circular camera rig or "cardiod". - const int iNviews = 12; - const int iNbPoints = 6; + const int focal = 1000; + const int principal_Point = 500; + //-- Setup a circular camera rig or "cardiod". + const int iNviews = 12; + const int iNbPoints = 6; - const bool bCardiod = true; - const bool bRelative_Translation_PerTriplet = false; - std::vector vec_relative_estimates; + const bool bCardiod = true; + const bool bRelative_Translation_PerTriplet = true; + std::vector vec_relative_estimates; - const NViewDataSet d = - Setup_RelativeTranslations_AndNviewDataset - ( - vec_relative_estimates, - focal, principal_Point, iNviews, iNbPoints, - bCardiod, bRelative_Translation_PerTriplet - ); + const NViewDataSet d = Setup_RelativeTranslations_AndNviewDataset( + vec_relative_estimates, focal, principal_Point, iNviews, iNbPoints, bCardiod, bRelative_Translation_PerTriplet); - d.exportToPLY("global_translations_from_Tij_GT.ply"); - visibleCamPosToSVGSurface(d._C, "global_translations_from_Tij_GT.svg"); + d.exportToPLY("global_translations_from_Tij_GT.ply"); + visibleCamPosToSVGSurface(d._C, "global_translations_from_Tij_GT.svg"); - // Solve the translation averaging problem: - std::vector vec_translations; - BOOST_CHECK(solve_translations_problem_softl1( - vec_relative_estimates, bRelative_Translation_PerTriplet, iNviews, vec_translations)); + //-- Compute the global translations from the triplets of heading directions + //- with the L2 minimization of a Chordal distance + std::vector vec_edges; + vec_edges.reserve(vec_relative_estimates.size() * 2); + std::vector vec_poses; + vec_poses.reserve(vec_relative_estimates.size() * 3); + std::vector vec_weights; + vec_weights.reserve(vec_relative_estimates.size()); - BOOST_CHECK_EQUAL(iNviews, vec_translations.size()); + for (int i = 0; i < vec_relative_estimates.size(); ++i) + { + const aliceVision::translationAveraging::relativeInfo& rel = vec_relative_estimates[i]; + vec_edges.push_back(rel.first.first); + vec_edges.push_back(rel.first.second); - // Check accuracy of the found translations - for (unsigned i = 0; i < iNviews; ++i) - { - const Vec3 t = vec_translations[i]; - const Mat3 & Ri = d._R[i]; - const Vec3 C_computed = - Ri.transpose() * t; + const Vec3 EdgeDirection = -(d._R[rel.first.second].transpose() * rel.second.second.normalized()); - const Vec3 C_GT = d._C[i] - d._C[0]; + vec_poses.push_back(EdgeDirection(0)); + vec_poses.push_back(EdgeDirection(1)); + vec_poses.push_back(EdgeDirection(2)); - //-- Check that found camera position is equal to GT value - if (i==0) { - EXPECT_MATRIX_NEAR(C_computed, C_GT, 1e-6); + vec_weights.push_back(1.0); } - else { - BOOST_CHECK_SMALL(DistanceLInfinity(C_computed.normalized(), C_GT.normalized()), 1e-6); - } - } -} - -BOOST_AUTO_TEST_CASE(translation_averaging_globalTi_from_tijs_Triplets_l2_chordal) { - makeRandomOperationsReproducible(); - - const int focal = 1000; - const int principal_Point = 500; - //-- Setup a circular camera rig or "cardiod". - const int iNviews = 12; - const int iNbPoints = 6; - - const bool bCardiod = true; - const bool bRelative_Translation_PerTriplet = true; - std::vector vec_relative_estimates; - - const NViewDataSet d = - Setup_RelativeTranslations_AndNviewDataset - ( - vec_relative_estimates, - focal, principal_Point, iNviews, iNbPoints, - bCardiod, bRelative_Translation_PerTriplet - ); - - d.exportToPLY("global_translations_from_Tij_GT.ply"); - visibleCamPosToSVGSurface(d._C, "global_translations_from_Tij_GT.svg"); - - //-- Compute the global translations from the triplets of heading directions - //- with the L2 minimization of a Chordal distance - std::vector vec_edges; - vec_edges.reserve(vec_relative_estimates.size() * 2); - std::vector vec_poses; - vec_poses.reserve(vec_relative_estimates.size() * 3); - std::vector vec_weights; - vec_weights.reserve(vec_relative_estimates.size()); - - for(int i=0; i < vec_relative_estimates.size(); ++i) - { - const aliceVision::translationAveraging::relativeInfo & rel = vec_relative_estimates[i]; - vec_edges.push_back(rel.first.first); - vec_edges.push_back(rel.first.second); - - const Vec3 EdgeDirection = -(d._R[rel.first.second].transpose() * rel.second.second.normalized()); - - vec_poses.push_back(EdgeDirection(0)); - vec_poses.push_back(EdgeDirection(1)); - vec_poses.push_back(EdgeDirection(2)); - - vec_weights.push_back(1.0); - } - - const double function_tolerance=1e-7, parameter_tolerance=1e-8; - const int max_iterations = 500; - - const double loss_width = 0.0; - - std::vector X(iNviews*3); - - BOOST_CHECK( - solve_translations_problem_l2_chordal( - &vec_edges[0], - &vec_poses[0], - &vec_weights[0], - vec_relative_estimates.size(), - loss_width, - &X[0], - function_tolerance, - parameter_tolerance, - max_iterations)); - - // Get back the camera translations in the global frame: - for (size_t i = 0; i < iNviews; ++i) - { - if (i==0) { //First camera supposed to be at Identity - const Vec3 C0(X[0], X[1], X[2]); - BOOST_CHECK_SMALL(DistanceLInfinity(C0, Vec3(0,0,0)), 1e-6); - } - else { - const Vec3 t_GT = (d._C[i] - d._C[0]); - - const Vec3 CI(X[i*3], X[i*3+1], X[i*3+2]); - const Vec3 C0(X[0], X[1], X[2]); - const Vec3 t_computed = CI - C0; - //-- Check that vector are colinear - BOOST_CHECK_SMALL(DistanceLInfinity(t_computed.normalized(), t_GT.normalized()), 1e-6); + const double function_tolerance = 1e-7, parameter_tolerance = 1e-8; + const int max_iterations = 500; + + const double loss_width = 0.0; + + std::vector X(iNviews * 3); + + BOOST_CHECK(solve_translations_problem_l2_chordal(&vec_edges[0], + &vec_poses[0], + &vec_weights[0], + vec_relative_estimates.size(), + loss_width, + &X[0], + function_tolerance, + parameter_tolerance, + max_iterations)); + + // Get back the camera translations in the global frame: + for (size_t i = 0; i < iNviews; ++i) + { + if (i == 0) + { // First camera supposed to be at Identity + const Vec3 C0(X[0], X[1], X[2]); + BOOST_CHECK_SMALL(DistanceLInfinity(C0, Vec3(0, 0, 0)), 1e-6); + } + else + { + const Vec3 t_GT = (d._C[i] - d._C[0]); + + const Vec3 CI(X[i * 3], X[i * 3 + 1], X[i * 3 + 2]); + const Vec3 C0(X[0], X[1], X[2]); + const Vec3 t_computed = CI - C0; + + //-- Check that vector are colinear + BOOST_CHECK_SMALL(DistanceLInfinity(t_computed.normalized(), t_GT.normalized()), 1e-6); + } } - } } diff --git a/src/aliceVision/multiview/triangulation/NViewsTriangulationLORansac.hpp b/src/aliceVision/multiview/triangulation/NViewsTriangulationLORansac.hpp index f6563b8856..6281e4665c 100644 --- a/src/aliceVision/multiview/triangulation/NViewsTriangulationLORansac.hpp +++ b/src/aliceVision/multiview/triangulation/NViewsTriangulationLORansac.hpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -27,196 +27,183 @@ namespace multiview { /** * @brief The kernel for triangulating a point from N views to be used with * the LORansac framework. - * + * * @tparam SolverArg The minimal solver able to find a solution from a * minimum set of points. * @tparam ErrorArg The functor computing the error for each data sample with * respect to the estimated model, usually a reprojection error functor. - * @tparam UnnormalizerArg The functor used to normalize the data before the + * @tparam UnnormalizerArg The functor used to normalize the data before the * estimation of the model. * @tparam ModelArg = Vec4 The type of the model to estimate, the 3D point. * @tparam SolverLSArg = SolverArg The least square solver that is used to find - * a solution from any set of data larger than the minimum required, usually a + * a solution from any set of data larger than the minimum required, usually a * DLT algorithm. */ -template , typename SolverLsT_ = robustEstimation::UndefinedSolver, typename ContainerT = Mat2X> -class NViewsTriangulationLORansac - : public robustEstimation::IRansacKernel +template, + typename SolverLsT_ = robustEstimation::UndefinedSolver, + typename ContainerT = Mat2X> +class NViewsTriangulationLORansac : public robustEstimation::IRansacKernel { -public: - - using SolverT = SolverT_; - using SolverLsT = SolverLsT_; - using UnnormalizerT = UnnormalizerT_; - using ErrorT = ErrorT_; - using ModelT = ModelT_; + public: + using SolverT = SolverT_; + using SolverLsT = SolverLsT_; + using UnnormalizerT = UnnormalizerT_; + using ErrorT = ErrorT_; + using ModelT = ModelT_; - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return _kernelSolver.getMinimumNbRequiredSamples(); - } + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return _kernelSolver.getMinimumNbRequiredSamples(); } - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return _kernelSolver.getMaximumNbModels(); - } + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return _kernelSolver.getMaximumNbModels(); } - inline std::size_t getMinimumNbRequiredSamplesLS() const override - { - return SolverLsT().getMinimumNbRequiredSamples(); - } - - /** - * @brief Constructor. - * @param[in] _pt2d The feature points, a 2xN matrix. - * @param[in] projMatrices The N projection matrix for each view. - */ - NViewsTriangulationLORansac(const ContainerT & _pt2d, const std::vector& projMatrices) - : _pt2d(_pt2d) - , _projMatrices(projMatrices) - { - assert(_projMatrices.size() == CountElements(_pt2d)); - } + inline std::size_t getMinimumNbRequiredSamplesLS() const override { return SolverLsT().getMinimumNbRequiredSamples(); } - /** - * @brief Triangulate 2 points. - * @param[in] samples The index of two points to triangulate. - * @param[out] models The estimated 3D points. - */ - void fit(const std::vector& samples, std::vector& models) const override - { - const ContainerT p2d = buildSubsetMatrix(_pt2d, samples); - std::vector sampledMats; - pick(sampledMats, _projMatrices, samples); - _kernelSolver.solve(p2d, sampledMats, models); - } + /** + * @brief Constructor. + * @param[in] _pt2d The feature points, a 2xN matrix. + * @param[in] projMatrices The N projection matrix for each view. + */ + NViewsTriangulationLORansac(const ContainerT& _pt2d, const std::vector& projMatrices) + : _pt2d(_pt2d), + _projMatrices(projMatrices) + { + assert(_projMatrices.size() == CountElements(_pt2d)); + } + + /** + * @brief Triangulate 2 points. + * @param[in] samples The index of two points to triangulate. + * @param[out] models The estimated 3D points. + */ + void fit(const std::vector& samples, std::vector& models) const override + { + const ContainerT p2d = buildSubsetMatrix(_pt2d, samples); + std::vector sampledMats; + pick(sampledMats, _projMatrices, samples); + _kernelSolver.solve(p2d, sampledMats, models); + } + + /** + * @brief Triangulate N points using the least squared solver. + * @param[in] inliers The index of the N points to triangulate. + * @param[out] models The estimated 3D point. + * @param[in] weights The optional array of weight for each of the N points. + */ + void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const override + { + const ContainerT p2d = buildSubsetMatrix(_pt2d, inliers); + std::vector sampledMats; + pick(sampledMats, _projMatrices, inliers); + _kernelSolverLs.solve(p2d, sampledMats, models, *weights); + } - /** - * @brief Triangulate N points using the least squared solver. - * @param[in] inliers The index of the N points to triangulate. - * @param[out] models The estimated 3D point. - * @param[in] weights The optional array of weight for each of the N points. - */ - void fitLS(const std::vector& inliers, - std::vector& models, - const std::vector *weights = nullptr) const override - { - const ContainerT p2d = buildSubsetMatrix(_pt2d, inliers); - std::vector sampledMats; - pick(sampledMats, _projMatrices, inliers); - _kernelSolverLs.solve(p2d, sampledMats, models, *weights); - } + /** + * @brief Compute the weights.. + * @param[in] model The 3D point for which the weights are computed. + * @param[in] inliers The array of the indices of the data to be used. + * @param[out] vec_weights The array of weight of the same size as \p inliers. + * @param[in] eps An optional threshold to max out the value of the threshold (typically + * to avoid division by zero or too small numbers). + */ + void computeWeights(const ModelT_& model, + const std::vector& inliers, + std::vector& vec_weights, + const double eps = 0.001) const override + { + const auto numInliers = inliers.size(); + vec_weights.resize(numInliers); + for (std::size_t sample = 0; sample < numInliers; ++sample) + { + const auto idx = inliers[sample]; + vec_weights[sample] = _errorEstimator.error(getElement(_pt2d, idx), _projMatrices[idx], model); + // avoid division by zero + vec_weights[sample] = 1 / std::pow(std::max(eps, vec_weights[sample]), 2); + } + } - /** - * @brief Compute the weights.. - * @param[in] model The 3D point for which the weights are computed. - * @param[in] inliers The array of the indices of the data to be used. - * @param[out] vec_weights The array of weight of the same size as \p inliers. - * @param[in] eps An optional threshold to max out the value of the threshold (typically - * to avoid division by zero or too small numbers). - */ - void computeWeights(const ModelT_& model, - const std::vector &inliers, - std::vector& vec_weights, - const double eps = 0.001) const override - { - const auto numInliers = inliers.size(); - vec_weights.resize(numInliers); - for(std::size_t sample = 0; sample < numInliers; ++sample) + /** + * @brief Error for the i-th view + * @param[in] sample The index of the view for which the error is computed. + * @param[in] model The 3D point. + * @return The estimation error for the given view and 3D point. + */ + double error(std::size_t sample, const ModelT_& model) const override { - const auto idx = inliers[sample]; - vec_weights[sample] = _errorEstimator.error(getElement(_pt2d, idx), _projMatrices[idx], model); - - // avoid division by zero - vec_weights[sample] = 1/std::pow(std::max(eps, vec_weights[sample]), 2); + return _errorEstimator.error(getElement(_pt2d, sample), _projMatrices[sample], model); } - } - - /** - * @brief Error for the i-th view - * @param[in] sample The index of the view for which the error is computed. - * @param[in] model The 3D point. - * @return The estimation error for the given view and 3D point. - */ - double error(std::size_t sample, const ModelT_& model) const override - { - return _errorEstimator.error(getElement(_pt2d, sample), _projMatrices[sample], model); - } - /** - * @brief Error for each view. - * @param[in] model The 3D point. - * @param[out] vec_errors The vector containing all the estimation errors for every view. - */ - void errors(const ModelT_& model, std::vector& errors) const override - { - errors.resize(nbSamples()); - for(Mat::Index i = 0; i < CountElements(_pt2d); ++i) - errors[i] = error(i, model); - } + /** + * @brief Error for each view. + * @param[in] model The 3D point. + * @param[out] vec_errors The vector containing all the estimation errors for every view. + */ + void errors(const ModelT_& model, std::vector& errors) const override + { + errors.resize(nbSamples()); + for (Mat::Index i = 0; i < CountElements(_pt2d); ++i) + errors[i] = error(i, model); + } + + /** + * @brief Unnormalize the model. (not used) + * @param[in,out] model the 3D point. + */ + void unnormalize(ModelT_& model) const override {} - /** - * @brief Unnormalize the model. (not used) - * @param[in,out] model the 3D point. - */ - void unnormalize(ModelT_& model) const override - {} + /** + * @brief Return the number of view. + * @return the number of view. + */ + std::size_t nbSamples() const override { return CountElements(_pt2d); } - /** - * @brief Return the number of view. - * @return the number of view. - */ - std::size_t nbSamples() const override - { - return CountElements(_pt2d); - } - - double logalpha0() const override - { - std::runtime_error("Method 'logalpha0()' is not defined for 'NViewsTriangulationLORansac'."); - return 0.0; - } + double logalpha0() const override + { + std::runtime_error("Method 'logalpha0()' is not defined for 'NViewsTriangulationLORansac'."); + return 0.0; + } - double errorVectorDimension() const override - { - std::runtime_error("Method 'errorVectorDimension()' is not defined for 'NViewsTriangulationLORansac'."); - return 0.0; - } + double errorVectorDimension() const override + { + std::runtime_error("Method 'errorVectorDimension()' is not defined for 'NViewsTriangulationLORansac'."); + return 0.0; + } - double unormalizeError(double val) const override - { - std::runtime_error("Method 'unormalizeError()' is not defined for 'NViewsTriangulationLORansac'."); - return 0.0; - } + double unormalizeError(double val) const override + { + std::runtime_error("Method 'unormalizeError()' is not defined for 'NViewsTriangulationLORansac'."); + return 0.0; + } - Mat3 normalizer1() const override - { - std::runtime_error("Method 'normalizer1()' is not defined for 'NViewsTriangulationLORansac'."); - return Mat3(); - } + Mat3 normalizer1() const override + { + std::runtime_error("Method 'normalizer1()' is not defined for 'NViewsTriangulationLORansac'."); + return Mat3(); + } - double thresholdNormalizer() const override - { - std::runtime_error("Method 'thresholdNormalizer()' is not defined for 'NViewsTriangulationLORansac'."); - return 0.0; - } + double thresholdNormalizer() const override + { + std::runtime_error("Method 'thresholdNormalizer()' is not defined for 'NViewsTriangulationLORansac'."); + return 0.0; + } -private: - const ContainerT & _pt2d; - const std::vector& _projMatrices; + private: + const ContainerT& _pt2d; + const std::vector& _projMatrices; - const SolverT _kernelSolver = SolverT(); - const SolverLsT _kernelSolverLs = SolverLsT(); - const ErrorT _errorEstimator = ErrorT(); + const SolverT _kernelSolver = SolverT(); + const SolverLsT _kernelSolverLs = SolverLsT(); + const ErrorT _errorEstimator = ErrorT(); }; /** @@ -225,18 +212,18 @@ class NViewsTriangulationLORansac template> struct ReprojectionError { - /** - * @brief Compute the reprojection error. - * @param[in] pt2d The 2d image point. - * @param[in] projMatrix The 3x4 projection matrix. - * @param[in] pt3d The 3d point. - * @return the reprojection error. - */ - inline double error(const Vec2& pt2d, const Mat34& projMatrix, const ModelT_& model) const - { - const Vec3 proj = projMatrix * model.getMatrix(); - return (pt2d - proj.hnormalized()).norm(); - } + /** + * @brief Compute the reprojection error. + * @param[in] pt2d The 2d image point. + * @param[in] projMatrix The 3x4 projection matrix. + * @param[in] pt3d The 3d point. + * @return the reprojection error. + */ + inline double error(const Vec2& pt2d, const Mat34& projMatrix, const ModelT_& model) const + { + const Vec3 proj = projMatrix * model.getMatrix(); + return (pt2d - proj.hnormalized()).norm(); + } }; /** @@ -245,27 +232,31 @@ struct ReprojectionError template> struct AngularError { - /** - * @brief Compute the error as the angular error. - * @param[in] pt2d The 2d image point. - * @param[in] projMatrix The 3x4 projection matrix. - * @param[in] pt3d The 3d point. - * @return the error as the angular error between the direction of the - * 3D point and the projection ray associated with the image point. - */ - inline double error(const Vec2& pt2d, const Mat34& projMatrix, const ModelT_& model) const - { - const Vec3 ray1 = pt2d.homogeneous(); - const Vec3 ray2 = projMatrix * model.getMatrix(); - return std::acos(ray1.normalized().transpose() * ray2.normalized()); - } + /** + * @brief Compute the error as the angular error. + * @param[in] pt2d The 2d image point. + * @param[in] projMatrix The 3x4 projection matrix. + * @param[in] pt3d The 3d point. + * @return the error as the angular error between the direction of the + * 3D point and the projection ray associated with the image point. + */ + inline double error(const Vec2& pt2d, const Mat34& projMatrix, const ModelT_& model) const + { + const Vec3 ray1 = pt2d.homogeneous(); + const Vec3 ray2 = projMatrix * model.getMatrix(); + return std::acos(ray1.normalized().transpose() * ray2.normalized()); + } }; - -using ContainerType = std::vector; +using ContainerType = std::vector; using LORansacTriangulationSolver = TriangulateNViewsSolver; template>> -using LORansacTriangulationKernel = NViewsTriangulationLORansac, LORansacTriangulationSolver, ContainerType>; -} // namespace multiview -} // namespace aliceVision +using LORansacTriangulationKernel = NViewsTriangulationLORansac, + LORansacTriangulationSolver, + ContainerType>; +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/triangulation/Triangulation.cpp b/src/aliceVision/multiview/triangulation/Triangulation.cpp index 5706fcbc2a..ac7b61aa3d 100644 --- a/src/aliceVision/multiview/triangulation/Triangulation.cpp +++ b/src/aliceVision/multiview/triangulation/Triangulation.cpp @@ -16,118 +16,114 @@ namespace aliceVision { namespace multiview { -void TriangulateNView(const Mat2X &x, - const std::vector< Mat34 > &Ps, - Vec4 & X, - const std::vector *weights) +void TriangulateNView(const Mat2X& x, const std::vector& Ps, Vec4& X, const std::vector* weights) { - Mat2X::Index nviews = x.cols(); - assert(static_cast(nviews) == Ps.size()); + Mat2X::Index nviews = x.cols(); + assert(static_cast(nviews) == Ps.size()); - Mat design = Mat::Zero(3 * nviews, 4 + nviews); - for(Mat2X::Index i = 0; i < nviews; i++) - { - design.block<3, 4>(3 * i, 0) = -Ps[i]; - design(3 * i + 0, 4 + i) = x(0, i); - design(3 * i + 1, 4 + i) = x(1, i); - design(3 * i + 2, 4 + i) = 1.0; - } - Vec X_and_alphas; - Nullspace(design, X_and_alphas); - X = X_and_alphas.head(4); + Mat design = Mat::Zero(3 * nviews, 4 + nviews); + for (Mat2X::Index i = 0; i < nviews; i++) + { + design.block<3, 4>(3 * i, 0) = -Ps[i]; + design(3 * i + 0, 4 + i) = x(0, i); + design(3 * i + 1, 4 + i) = x(1, i); + design(3 * i + 2, 4 + i) = 1.0; + } + Vec X_and_alphas; + Nullspace(design, X_and_alphas); + X = X_and_alphas.head(4); } -void TriangulateNViewLORANSAC(const std::vector & pts, +void TriangulateNViewLORANSAC(const std::vector& pts, const std::vector& Ps, - std::mt19937 & generator, - Vec4 & X, + std::mt19937& generator, + Vec4& X, std::vector* inliersIndex, const double& thresholdError) { - using TriangulationKernel = multiview::LORansacTriangulationKernel<>; - TriangulationKernel kernel(pts, Ps); - robustEstimation::ScoreEvaluator scorer(thresholdError); - robustEstimation::MatrixModel model; - model = robustEstimation::LO_RANSAC(kernel, scorer, generator, inliersIndex); - X = model.getMatrix(); + using TriangulationKernel = multiview::LORansacTriangulationKernel<>; + TriangulationKernel kernel(pts, Ps); + robustEstimation::ScoreEvaluator scorer(thresholdError); + robustEstimation::MatrixModel model; + model = robustEstimation::LO_RANSAC(kernel, scorer, generator, inliersIndex); + X = model.getMatrix(); } -double Triangulation::error(const Vec3 &X) const +double Triangulation::error(const Vec3& X) const { - double squared_reproj_error = 0.0; - for (const auto &view : views) - { - const Mat34& PMat = view.first; - const Vec2 & xy = view.second; - const Vec2 p = project(PMat, X); - squared_reproj_error += (xy - p).norm(); - } - return squared_reproj_error; + double squared_reproj_error = 0.0; + for (const auto& view : views) + { + const Mat34& PMat = view.first; + const Vec2& xy = view.second; + const Vec2 p = project(PMat, X); + squared_reproj_error += (xy - p).norm(); + } + return squared_reproj_error; } Vec3 Triangulation::compute(int iter) const { - const auto nviews = views.size(); - assert(nviews >= 2); - - // Iterative weighted linear least squares - Mat3 AtA; - Vec3 Atb, X; - std::vector weights(nviews, double(1.0)); - for(int it = 0; it < iter; ++it) - { - AtA.fill(0.0); - Atb.fill(0.0); - for(std::size_t i = 0; i < nviews; ++i) + const auto nviews = views.size(); + assert(nviews >= 2); + + // Iterative weighted linear least squares + Mat3 AtA; + Vec3 Atb, X; + std::vector weights(nviews, double(1.0)); + for (int it = 0; it < iter; ++it) { - const Mat34& PMat = views[i].first; - const Vec2 & p = views[i].second; - const double w = weights[i]; - - Vec3 v1, v2; - for(Mat::Index j = 0; j < 3; ++j) - { - v1[j] = w * (PMat(0, j) - p(0) * PMat(2, j)); - v2[j] = w * (PMat(1, j) - p(1) * PMat(2, j)); - Atb[j] += w * (v1[j] * (p(0) * PMat(2, 3) - PMat(0, 3)) - + v2[j] * (p(1) * PMat(2, 3) - PMat(1, 3))); - } - - for(Mat::Index k = 0; k < 3; ++k) - { - for(Mat::Index j = 0; j <= k; ++j) + AtA.fill(0.0); + Atb.fill(0.0); + for (std::size_t i = 0; i < nviews; ++i) { - const double v = v1[j] * v1[k] + v2[j] * v2[k]; - AtA(j, k) += v; - if(j < k) AtA(k, j) += v; + const Mat34& PMat = views[i].first; + const Vec2& p = views[i].second; + const double w = weights[i]; + + Vec3 v1, v2; + for (Mat::Index j = 0; j < 3; ++j) + { + v1[j] = w * (PMat(0, j) - p(0) * PMat(2, j)); + v2[j] = w * (PMat(1, j) - p(1) * PMat(2, j)); + Atb[j] += w * (v1[j] * (p(0) * PMat(2, 3) - PMat(0, 3)) + v2[j] * (p(1) * PMat(2, 3) - PMat(1, 3))); + } + + for (Mat::Index k = 0; k < 3; ++k) + { + for (Mat::Index j = 0; j <= k; ++j) + { + const double v = v1[j] * v1[k] + v2[j] * v2[k]; + AtA(j, k) += v; + if (j < k) + AtA(k, j) += v; + } + } } - } - } - X = AtA.inverse() * Atb; + X = AtA.inverse() * Atb; - // Compute reprojection error, min and max depth, and update weights - zmin = std::numeric_limits::max(); - zmax = -std::numeric_limits::max(); - err = 0; - for(std::size_t i = 0; i < nviews; ++i) - { - const Mat34& PMat = views[i].first; - const Vec2 & p = views[i].second; - const Vec3 xProj = PMat * Vec4(X(0), X(1), X(2), 1.0); - const double z = xProj(2); - const Vec2 x = xProj.head<2>() / z; - if(z < zmin) zmin = z; - if(z > zmax) zmax = z; - err += (p - x).norm(); - weights[i] = 1.0 / z; + // Compute reprojection error, min and max depth, and update weights + zmin = std::numeric_limits::max(); + zmax = -std::numeric_limits::max(); + err = 0; + for (std::size_t i = 0; i < nviews; ++i) + { + const Mat34& PMat = views[i].first; + const Vec2& p = views[i].second; + const Vec3 xProj = PMat * Vec4(X(0), X(1), X(2), 1.0); + const double z = xProj(2); + const Vec2 x = xProj.head<2>() / z; + if (z < zmin) + zmin = z; + if (z > zmax) + zmax = z; + err += (p - x).norm(); + weights[i] = 1.0 / z; + } } - } - return X; + return X; } - - -} // namespace multiview -} // namespace aliceVision - +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/triangulation/Triangulation.hpp b/src/aliceVision/multiview/triangulation/Triangulation.hpp index 3f15f89479..8054d84e7d 100644 --- a/src/aliceVision/multiview/triangulation/Triangulation.hpp +++ b/src/aliceVision/multiview/triangulation/Triangulation.hpp @@ -22,51 +22,45 @@ namespace multiview { * @brief Compute a 3D position of a point from several images of it. In particular, * compute the projective point X in R^4 such that x = PX. * Algorithm is the standard DLT; for derivation see appendix of Keir's thesis. - * It also allows to specify some (optional) weight for each point (solving the + * It also allows to specify some (optional) weight for each point (solving the * weighted least squared problem) - * + * * @param[in] x are 2D coordinates (x,y,1) in each image * @param[in] Ps is the list of projective matrices for each camera * @param[out] X is the estimated 3D point * @param[in] weights a (optional) list of weights for each point */ -void TriangulateNView(const Mat2X &x, - const std::vector< Mat34 > &Ps, - Vec4 & X, - const std::vector *weights = nullptr); +void TriangulateNView(const Mat2X& x, const std::vector& Ps, Vec4& X, const std::vector* weights = nullptr); /** * @brief Compute a 3D position of a point from several images of it. In particular, * compute the projective point X in R^4 such that x ~ PX. * Algorithm is the standard DLT - * It also allows to specify some (optional) weight for each point (solving the + * It also allows to specify some (optional) weight for each point (solving the * weighted least squared problem) - * + * * @param[in] x are 2D coordinates (x,y) in each image * @param[in] Ps is the list of projective matrices for each camera * @param[out] X is the estimated 3D point * @param[in] weights a (optional) list of weights for each point */ -template -void TriangulateNViewAlgebraic(const ContainerT &x, - const std::vector< Mat34 > &Ps, - Vec4 & X, - const std::vector *weights = nullptr) +template +void TriangulateNViewAlgebraic(const ContainerT& x, const std::vector& Ps, Vec4& X, const std::vector* weights = nullptr) { - Mat2X::Index nviews = CountElements(x); - assert(static_cast(nviews) == Ps.size()); - - Mat design(2 * nviews, 4); - for(Mat2X::Index i = 0; i < nviews; ++i) - { - design.block<2, 4>(2 * i, 0) = SkewMatMinimal(getElement(x, i)) * Ps[i]; - if(weights != nullptr) + Mat2X::Index nviews = CountElements(x); + assert(static_cast(nviews) == Ps.size()); + + Mat design(2 * nviews, 4); + for (Mat2X::Index i = 0; i < nviews; ++i) { - design.block<2, 4>(2 * i, 0) *= (*weights)[i]; + design.block<2, 4>(2 * i, 0) = SkewMatMinimal(getElement(x, i)) * Ps[i]; + if (weights != nullptr) + { + design.block<2, 4>(2 * i, 0) *= (*weights)[i]; + } } - } - Nullspace(design, X); + Nullspace(design, X); } /** @@ -74,99 +68,88 @@ void TriangulateNViewAlgebraic(const ContainerT &x, * compute the projective point X in R^4 such that x ~ PX. * Algorithm is Lo-RANSAC * It can return the the list of the cameras set as intlier by the Lo-RANSAC algorithm. - * + * * @param[in] x are 2D coordinates (x,y,1) in each image * @param[in] Ps is the list of projective matrices for each camera * @param[out] X is the estimated 3D point * @param[out] inliersIndex (optional) store the index of the cameras (following Ps ordering, not the view_id) set as Inliers by Lo-RANSAC * @param[in] thresholdError (optional) set a threashold value to the Lo-RANSAC scorer - */ -void TriangulateNViewLORANSAC(const std::vector &x, - const std::vector< Mat34 > &Ps, - std::mt19937 & generator, - Vec4 & X, - std::vector *inliersIndex = NULL, - const double & thresholdError = 4.0); + */ +void TriangulateNViewLORANSAC(const std::vector& x, + const std::vector& Ps, + std::mt19937& generator, + Vec4& X, + std::vector* inliersIndex = NULL, + const double& thresholdError = 4.0); -//Iterated linear method +// Iterated linear method class Triangulation { -public: + public: + std::size_t size() const { return views.size(); } - std::size_t size() const { return views.size();} + void clear() { views.clear(); } - void clear() { views.clear();} + void add(const Mat34& projMatrix, const Vec2& p) { views.emplace_back(projMatrix, p); } - void add(const Mat34& projMatrix, const Vec2 & p) - { - views.emplace_back(projMatrix, p); - } + // Return squared L2 sum of error + double error(const Vec3& X) const; - // Return squared L2 sum of error - double error(const Vec3 &X) const; + Vec3 compute(int iter = 3) const; - Vec3 compute(int iter = 3) const; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Accessors - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Accessors + // These values are defined after a successful call to compute + double minDepth() const { return zmin; } + double maxDepth() const { return zmax; } + double error() const { return err; } - // These values are defined after a successful call to compute - double minDepth() const { return zmin; } - double maxDepth() const { return zmax; } - double error() const { return err; } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Data members - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Data members - -protected: - mutable double zmin; // min depth, mutable since modified in compute(...) const; - mutable double zmax; // max depth, mutable since modified in compute(...) const; - mutable double err; // re-projection error, mutable since modified in compute(...) const; - std::vector< std::pair > views; // Proj matrix and associated image point + protected: + mutable double zmin; // min depth, mutable since modified in compute(...) const; + mutable double zmax; // max depth, mutable since modified in compute(...) const; + mutable double err; // re-projection error, mutable since modified in compute(...) const; + std::vector> views; // Proj matrix and associated image point }; -template -struct TriangulateNViewsSolver +template +struct TriangulateNViewsSolver { + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const { return 2; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const { return 1; } + + void solve(const ContainerT& x, const std::vector& Ps, std::vector>& X) const + { + Vec4 pt3d; + TriangulateNViewAlgebraic(x, Ps, pt3d); + X.push_back(robustEstimation::MatrixModel(pt3d)); + assert(X.size() == 1); + } - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const - { - return 2; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const - { - return 1; - } - - void solve(const ContainerT& x, const std::vector& Ps, std::vector>& X) const - { - Vec4 pt3d; - TriangulateNViewAlgebraic(x, Ps, pt3d); - X.push_back(robustEstimation::MatrixModel(pt3d)); - assert(X.size() == 1); - } - - - - void solve(const ContainerT& x, const std::vector& Ps, std::vector>& X, const std::vector& weights) const - { - Vec4 pt3d; - TriangulateNViewAlgebraic(x, Ps, pt3d, &weights); - X.push_back(robustEstimation::MatrixModel(pt3d)); - assert(X.size() == 1); - } - + void solve(const ContainerT& x, + const std::vector& Ps, + std::vector>& X, + const std::vector& weights) const + { + Vec4 pt3d; + TriangulateNViewAlgebraic(x, Ps, pt3d, &weights); + X.push_back(robustEstimation::MatrixModel(pt3d)); + assert(X.size() == 1); + } }; -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/triangulation/triangulationDLT.cpp b/src/aliceVision/multiview/triangulation/triangulationDLT.cpp index af6ccd5775..074bc553ca 100644 --- a/src/aliceVision/multiview/triangulation/triangulationDLT.cpp +++ b/src/aliceVision/multiview/triangulation/triangulationDLT.cpp @@ -14,56 +14,51 @@ namespace aliceVision { namespace multiview { // HZ 12.2 pag.312 -void TriangulateDLT(const Mat34 &P1, const Vec2 &x1, - const Mat34 &P2, const Vec2 &x2, - Vec4 &X_homogeneous) { - Mat4 design; - for (int i = 0; i < 4; ++i) { - design(0,i) = x1[0] * P1(2,i) - P1(0,i); - design(1,i) = x1[1] * P1(2,i) - P1(1,i); - design(2,i) = x2[0] * P2(2,i) - P2(0,i); - design(3,i) = x2[1] * P2(2,i) - P2(1,i); - } - Nullspace(design, X_homogeneous); +void TriangulateDLT(const Mat34& P1, const Vec2& x1, const Mat34& P2, const Vec2& x2, Vec4& X_homogeneous) +{ + Mat4 design; + for (int i = 0; i < 4; ++i) + { + design(0, i) = x1[0] * P1(2, i) - P1(0, i); + design(1, i) = x1[1] * P1(2, i) - P1(1, i); + design(2, i) = x2[0] * P2(2, i) - P2(0, i); + design(3, i) = x2[1] * P2(2, i) - P2(1, i); + } + Nullspace(design, X_homogeneous); } -void TriangulateDLT(const Mat34 &P1, const Vec2 &x1, - const Mat34 &P2, const Vec2 &x2, - Vec3 & X_euclidean) { - Vec4 X_homogeneous; - TriangulateDLT(P1, x1, P2, x2, X_homogeneous); - homogeneousToEuclidean(X_homogeneous, X_euclidean); +void TriangulateDLT(const Mat34& P1, const Vec2& x1, const Mat34& P2, const Vec2& x2, Vec3& X_euclidean) +{ + Vec4 X_homogeneous; + TriangulateDLT(P1, x1, P2, x2, X_homogeneous); + homogeneousToEuclidean(X_homogeneous, X_euclidean); } // Solve: // [cross(x0,P0) X = 0] // [cross(x1,P1) X = 0] -void TriangulateSphericalDLT(const Mat34 &P1, const Vec3 &x1, - const Mat34 &P2, const Vec3 &x2, - Vec4 & X_homogeneous) +void TriangulateSphericalDLT(const Mat34& P1, const Vec3& x1, const Mat34& P2, const Vec3& x2, Vec4& X_homogeneous) { - Mat design(6,4); - for (int i = 0; i < 4; ++i) { - design(0,i) = -x1[2] * P1(1,i) + x1[1] * P1(2,i); - design(1,i) = x1[2] * P1(0,i) - x1[0] * P1(2,i); - design(2,i) = -x1[1] * P1(0,i) + x1[0] * P1(1,i); + Mat design(6, 4); + for (int i = 0; i < 4; ++i) + { + design(0, i) = -x1[2] * P1(1, i) + x1[1] * P1(2, i); + design(1, i) = x1[2] * P1(0, i) - x1[0] * P1(2, i); + design(2, i) = -x1[1] * P1(0, i) + x1[0] * P1(1, i); - design(3,i) = -x2[2] * P2(1,i) + x2[1] * P2(2,i); - design(4,i) = x2[2] * P2(0,i) - x2[0] * P2(2,i); - design(5,i) = -x2[1] * P2(0,i) + x2[0] * P2(1,i); + design(3, i) = -x2[2] * P2(1, i) + x2[1] * P2(2, i); + design(4, i) = x2[2] * P2(0, i) - x2[0] * P2(2, i); + design(5, i) = -x2[1] * P2(0, i) + x2[0] * P2(1, i); } Nullspace(design, X_homogeneous); } -void TriangulateSphericalDLT(const Mat34 &P1, const Vec3 &x1, - const Mat34 &P2, const Vec3 &x2, - Vec3 &X_euclidean) +void TriangulateSphericalDLT(const Mat34& P1, const Vec3& x1, const Mat34& P2, const Vec3& x2, Vec3& X_euclidean) { Vec4 X_homogeneous; TriangulateSphericalDLT(P1, x1, P2, x2, X_homogeneous); homogeneousToEuclidean(X_homogeneous, X_euclidean); } - -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/triangulation/triangulationDLT.hpp b/src/aliceVision/multiview/triangulation/triangulationDLT.hpp index 334a2b9615..0de2d9faf1 100644 --- a/src/aliceVision/multiview/triangulation/triangulationDLT.hpp +++ b/src/aliceVision/multiview/triangulation/triangulationDLT.hpp @@ -21,10 +21,8 @@ namespace multiview { * @param P2 a projection matrix K (R | t) * @param x2 a 2d observation vector * @param X_homogeneous a homogeneous 3d point -*/ -void TriangulateDLT(const Mat34 &P1, const Vec2 &x1, - const Mat34 &P2, const Vec2 &x2, - Vec4 & X_homogeneous); + */ +void TriangulateDLT(const Mat34& P1, const Vec2& x1, const Mat34& P2, const Vec2& x2, Vec4& X_homogeneous); /** * Triangulate a point given a set of observations @@ -34,11 +32,8 @@ void TriangulateDLT(const Mat34 &P1, const Vec2 &x1, * @param P2 a projection matrix K (R | t) * @param x2 a 2d observation vector * @param X_homogeneous a 3d point -*/ -void TriangulateDLT(const Mat34 &P1, const Vec2 &x1, - const Mat34 &P2, const Vec2 &x2, - Vec3 & X_euclidean); - + */ +void TriangulateDLT(const Mat34& P1, const Vec2& x1, const Mat34& P2, const Vec2& x2, Vec3& X_euclidean); /** * Triangulate a point given a set of bearing vectors @@ -48,10 +43,8 @@ void TriangulateDLT(const Mat34 &P1, const Vec2 &x1, * @param P2 a projection matrix K (R | t) * @param x2 a unit bearing vector * @param X_homogeneous a homogeneous 3d point -*/ -void TriangulateSphericalDLT(const Mat34 &P1, const Vec3 &x1, - const Mat34 &P2, const Vec3 &x2, - Vec4 & X_homogeneous); + */ +void TriangulateSphericalDLT(const Mat34& P1, const Vec3& x1, const Mat34& P2, const Vec3& x2, Vec4& X_homogeneous); /** * Triangulate a point given a set of bearing vectors @@ -61,11 +54,8 @@ void TriangulateSphericalDLT(const Mat34 &P1, const Vec3 &x1, * @param P2 a projection matrix K (R | t) * @param x2 a unit bearing vector * @param X_homogeneous a 3d point -*/ -void TriangulateSphericalDLT(const Mat34 &P1, const Vec3 &x1, - const Mat34 &P2, const Vec3 &x2, - Vec3 & X_euclidean); - + */ +void TriangulateSphericalDLT(const Mat34& P1, const Vec3& x1, const Mat34& P2, const Vec3& x2, Vec3& X_euclidean); -} // namespace multiview -} // namespace aliceVision +} // namespace multiview +} // namespace aliceVision diff --git a/src/aliceVision/multiview/triangulation/triangulationDLT_test.cpp b/src/aliceVision/multiview/triangulation/triangulationDLT_test.cpp index f5973fa312..da42565fcd 100644 --- a/src/aliceVision/multiview/triangulation/triangulationDLT_test.cpp +++ b/src/aliceVision/multiview/triangulation/triangulationDLT_test.cpp @@ -19,16 +19,16 @@ using namespace aliceVision; BOOST_AUTO_TEST_CASE(Triangulation_TriangulateDLT) { - NViewDataSet d = NRealisticCamerasRing(2, 12); + NViewDataSet d = NRealisticCamerasRing(2, 12); - for (int i = 0; i < d._X.cols(); ++i) - { - Vec2 x1, x2; - x1 = d._x[0].col(i); - x2 = d._x[1].col(i); - Vec3 X_estimated, X_gt; - X_gt = d._X.col(i); - multiview::TriangulateDLT(d.P(0), x1, d.P(1), x2, X_estimated); - BOOST_CHECK_SMALL(DistanceLInfinity(X_estimated, X_gt), 1e-8); - } + for (int i = 0; i < d._X.cols(); ++i) + { + Vec2 x1, x2; + x1 = d._x[0].col(i); + x2 = d._x[1].col(i); + Vec3 X_estimated, X_gt; + X_gt = d._X.col(i); + multiview::TriangulateDLT(d.P(0), x1, d.P(1), x2, X_estimated); + BOOST_CHECK_SMALL(DistanceLInfinity(X_estimated, X_gt), 1e-8); + } } diff --git a/src/aliceVision/multiview/triangulation/triangulation_test.cpp b/src/aliceVision/multiview/triangulation/triangulation_test.cpp index cfdc9fa164..7ec96d2e61 100644 --- a/src/aliceVision/multiview/triangulation/triangulation_test.cpp +++ b/src/aliceVision/multiview/triangulation/triangulation_test.cpp @@ -20,71 +20,72 @@ using namespace aliceVision; BOOST_AUTO_TEST_CASE(TriangulateNView_FiveViews) { - const int nviews = 5; - const int npoints = 6; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints); - - // Collect P matrices together. - std::vector Ps(nviews); - for (int j = 0; j < nviews; ++j) - { - Ps[j] = d.P(j); - } - - for (int i = 0; i < npoints; ++i) - { - // Collect the image of point i in each frame. - Mat2X xs(2, nviews); + const int nviews = 5; + const int npoints = 6; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints); + + // Collect P matrices together. + std::vector Ps(nviews); for (int j = 0; j < nviews; ++j) { - xs.col(j) = d._x[j].col(i); + Ps[j] = d.P(j); } - Vec4 X; - multiview::TriangulateNView(xs, Ps, X); - // Check reprojection error. Should be nearly zero. - for (int j = 0; j < nviews; ++j) + for (int i = 0; i < npoints; ++i) { - Vec3 x_reprojected = Ps[j]*X; - x_reprojected /= x_reprojected(2); - const double error = (x_reprojected.head(2) - xs.col(j)).norm(); - BOOST_CHECK_SMALL(error, 1e-9); + // Collect the image of point i in each frame. + Mat2X xs(2, nviews); + for (int j = 0; j < nviews; ++j) + { + xs.col(j) = d._x[j].col(i); + } + Vec4 X; + multiview::TriangulateNView(xs, Ps, X); + + // Check reprojection error. Should be nearly zero. + for (int j = 0; j < nviews; ++j) + { + Vec3 x_reprojected = Ps[j] * X; + x_reprojected /= x_reprojected(2); + const double error = (x_reprojected.head(2) - xs.col(j)).norm(); + BOOST_CHECK_SMALL(error, 1e-9); + } } - } } -BOOST_AUTO_TEST_CASE(TriangulateNViewAlgebraic_FiveViews) { - const int nviews = 5; - const int npoints = 6; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints); - - // Collect P matrices together. - std::vector Ps(nviews); - for (int j = 0; j < nviews; ++j) - { - Ps[j] = d.P(j); - } - - for (int i = 0; i < npoints; ++i) - { - // Collect the image of point i in each frame. - Mat2X xs(2, nviews); +BOOST_AUTO_TEST_CASE(TriangulateNViewAlgebraic_FiveViews) +{ + const int nviews = 5; + const int npoints = 6; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints); + + // Collect P matrices together. + std::vector Ps(nviews); for (int j = 0; j < nviews; ++j) { - xs.col(j) = d._x[j].col(i); + Ps[j] = d.P(j); } - Vec4 X; - multiview::TriangulateNViewAlgebraic(xs, Ps, X); - // Check reprojection error. Should be nearly zero. - for (int j = 0; j < nviews; ++j) + for (int i = 0; i < npoints; ++i) { - Vec3 x_reprojected = Ps[j]*X; - x_reprojected /= x_reprojected(2); - const double error = (x_reprojected.head<2>() - xs.col(j)).norm(); - BOOST_CHECK_SMALL(error, 1e-9); + // Collect the image of point i in each frame. + Mat2X xs(2, nviews); + for (int j = 0; j < nviews; ++j) + { + xs.col(j) = d._x[j].col(i); + } + Vec4 X; + multiview::TriangulateNViewAlgebraic(xs, Ps, X); + + // Check reprojection error. Should be nearly zero. + for (int j = 0; j < nviews; ++j) + { + Vec3 x_reprojected = Ps[j] * X; + x_reprojected /= x_reprojected(2); + const double error = (x_reprojected.head<2>() - xs.col(j)).norm(); + BOOST_CHECK_SMALL(error, 1e-9); + } } - } } // test DLT triangulation using weights, it generates some random projection @@ -95,77 +96,76 @@ BOOST_AUTO_TEST_CASE(TriangulateNViewAlgebraic_FiveViews) { // is considered). BOOST_AUTO_TEST_CASE(Triangulate_NViewAlgebraic_WithWeights) { - makeRandomOperationsReproducible(); - - const std::size_t nbViews = 20; - const std::size_t nbOutliers = 8; - - // Collect random P matrices together. - std::vector Ps(nbViews); - for(std::size_t j = 0; j < nbViews; ++j) - { - Ps[j] = Mat34::Random(); - } - - // generate a random 3D point - Vec4 pt3d(Vec3::Random().homogeneous()); - - // project the 3D point and prepare weights - const double w = 1e8; - std::vector weights(nbViews, w); - Mat2X pt2d(2, nbViews); - for(std::size_t j = 0; j < nbViews; ++j) - { - if(j < nbViews - nbOutliers) + makeRandomOperationsReproducible(); + + const std::size_t nbViews = 20; + const std::size_t nbOutliers = 8; + + // Collect random P matrices together. + std::vector Ps(nbViews); + for (std::size_t j = 0; j < nbViews; ++j) { - // project the 3D point - pt2d.col(j) = (Ps[j] * pt3d).hnormalized(); + Ps[j] = Mat34::Random(); } - else + + // generate a random 3D point + Vec4 pt3d(Vec3::Random().homogeneous()); + + // project the 3D point and prepare weights + const double w = 1e8; + std::vector weights(nbViews, w); + Mat2X pt2d(2, nbViews); + for (std::size_t j = 0; j < nbViews; ++j) + { + if (j < nbViews - nbOutliers) + { + // project the 3D point + pt2d.col(j) = (Ps[j] * pt3d).hnormalized(); + } + else + { + // for the outliers just set them to some random value + pt2d.col(j) = Vec2::Random(); + // set the weight to 0 for the outliers + weights[j] = 0.0; + } + } + + Vec4 X; + multiview::TriangulateNViewAlgebraic(pt2d, Ps, X, &weights); + + // Check the reprojection error is nearly zero for inliers. + for (std::size_t j = 0; j < nbViews - nbOutliers; ++j) { - // for the outliers just set them to some random value - pt2d.col(j) = Vec2::Random(); - // set the weight to 0 for the outliers - weights[j] = 0.0; + const Vec2 x_reprojected = (Ps[j] * X).hnormalized(); + const double error = (x_reprojected - pt2d.col(j)).norm(); + BOOST_CHECK_SMALL(error, 1e-9); } - } - - Vec4 X; - multiview::TriangulateNViewAlgebraic(pt2d, Ps, X, &weights); - - // Check the reprojection error is nearly zero for inliers. - for (std::size_t j = 0; j < nbViews - nbOutliers; ++j) - { - const Vec2 x_reprojected = (Ps[j] * X).hnormalized(); - const double error = (x_reprojected - pt2d.col(j)).norm(); - BOOST_CHECK_SMALL(error, 1e-9); - } } BOOST_AUTO_TEST_CASE(Triangulate_NViewIterative_FiveViews) { - const int nviews = 5; - const int npoints = 6; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints); - - for(int i = 0; i < npoints; ++i) - { - - multiview::Triangulation triangulationObj; - for (int j = 0; j < nviews; ++j) - triangulationObj.add(d.P(j), d._x[j].col(i)); + const int nviews = 5; + const int npoints = 6; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints); - const Vec3 X = triangulationObj.compute(); - // Check reprojection error. Should be nearly zero. - BOOST_CHECK_SMALL(triangulationObj.error(X), 1e-9); - for (int j = 0; j < nviews; ++j) + for (int i = 0; i < npoints; ++i) { - Vec3 x_reprojected = d.P(j) * Vec4(X(0), X(1), X(2), 1.0); - x_reprojected /= x_reprojected(2); - const double error = (x_reprojected.head<2>() - d._x[j].col(i)).norm(); - BOOST_CHECK_SMALL(error, 1e-9); + multiview::Triangulation triangulationObj; + for (int j = 0; j < nviews; ++j) + triangulationObj.add(d.P(j), d._x[j].col(i)); + + const Vec3 X = triangulationObj.compute(); + // Check reprojection error. Should be nearly zero. + BOOST_CHECK_SMALL(triangulationObj.error(X), 1e-9); + for (int j = 0; j < nviews; ++j) + { + Vec3 x_reprojected = d.P(j) * Vec4(X(0), X(1), X(2), 1.0); + x_reprojected /= x_reprojected(2); + const double error = (x_reprojected.head<2>() - d._x[j].col(i)).norm(); + BOOST_CHECK_SMALL(error, 1e-9); + } } - } } //// Test triangulation as algebric problem, it generates some random projection @@ -176,60 +176,59 @@ BOOST_AUTO_TEST_CASE(Triangulate_NViewIterative_FiveViews) //// is considered). BOOST_AUTO_TEST_CASE(Triangulate_NViewIterative_LORANSAC) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - std::mt19937 randomNumberGenerator; - - const std::size_t numTrials = 100; - for(std::size_t trial = 0; trial < numTrials; ++ trial) - { - const std::size_t nbViews = 20; - const std::size_t nbOutliers = 8; - const std::size_t nbInliers = nbViews - nbOutliers; + std::mt19937 randomNumberGenerator; - // Collect random P matrices together. - std::vector Ps(nbViews); - for(std::size_t j = 0; j < nbViews; ++j) + const std::size_t numTrials = 100; + for (std::size_t trial = 0; trial < numTrials; ++trial) { - Ps[j] = Mat34::Random(); + const std::size_t nbViews = 20; + const std::size_t nbOutliers = 8; + const std::size_t nbInliers = nbViews - nbOutliers; + + // Collect random P matrices together. + std::vector Ps(nbViews); + for (std::size_t j = 0; j < nbViews; ++j) + { + Ps[j] = Mat34::Random(); + } + + // generate a random 3D point + Vec4 pt3d(Vec3::Random().homogeneous()); + + // project the 3D point and prepare weights + std::vector pt2d; + for (std::size_t j = 0; j < nbViews; ++j) + { + if (j < nbViews - nbOutliers) + { + // project the 3D point + pt2d.push_back((Ps[j] * pt3d).hnormalized()); + } + else + { + // for the outliers just set them to some random value + pt2d.push_back(Vec2::Random()); + // set the weight to 0 for the outliers + } + } + + std::vector inliers; + Vec4 X; + double const threshold = 0.01; // modify the default value: 4 pixels is too much in this configuration. + multiview::TriangulateNViewLORANSAC(pt2d, Ps, randomNumberGenerator, X, &inliers, threshold); + + // check inliers are correct + BOOST_CHECK_EQUAL(inliers.size(), nbInliers); + + // Check the reprojection error is nearly zero for inliers. + for (std::size_t j = 0; j < nbInliers; ++j) + { + const Vec2 x_reprojected = (Ps[j] * X).hnormalized(); + const double error = (x_reprojected - pt2d[j]).norm(); + // EXPECT_NEAR(error, 0.0, 1e-4); + BOOST_CHECK_SMALL(error, 1e-5); + } } - - // generate a random 3D point - Vec4 pt3d(Vec3::Random().homogeneous()); - - // project the 3D point and prepare weights - std::vector pt2d; - for(std::size_t j = 0; j < nbViews; ++j) - { - if(j < nbViews - nbOutliers) - { - // project the 3D point - pt2d.push_back((Ps[j] * pt3d).hnormalized()); - } - else - { - // for the outliers just set them to some random value - pt2d.push_back(Vec2::Random()); - // set the weight to 0 for the outliers - } - } - - std::vector inliers; - Vec4 X; - double const threshold = 0.01; // modify the default value: 4 pixels is too much in this configuration. - multiview::TriangulateNViewLORANSAC(pt2d, Ps, randomNumberGenerator, X, &inliers, threshold); - - // check inliers are correct - BOOST_CHECK_EQUAL(inliers.size(), nbInliers); - - // Check the reprojection error is nearly zero for inliers. - for (std::size_t j = 0; j < nbInliers; ++j) - { - const Vec2 x_reprojected = (Ps[j] * X).hnormalized(); - const double error = (x_reprojected - pt2d[j]).norm(); -// EXPECT_NEAR(error, 0.0, 1e-4); - BOOST_CHECK_SMALL(error, 1e-5); - } - } } - diff --git a/src/aliceVision/mvsData/Matrix3x3.hpp b/src/aliceVision/mvsData/Matrix3x3.hpp index 44598dc3bc..164c125252 100644 --- a/src/aliceVision/mvsData/Matrix3x3.hpp +++ b/src/aliceVision/mvsData/Matrix3x3.hpp @@ -17,8 +17,9 @@ namespace aliceVision { class Matrix3x3 { -public: - union { + public: + union + { struct { double m11, m12, m13, m21, m22, m23, m31, m32, m33; @@ -169,15 +170,9 @@ class Matrix3x3 return m11 * m22 * m33 - m11 * m23 * m32 - m12 * m21 * m33 + m12 * m23 * m31 + m13 * m21 * m32 - m13 * m22 * m31; } - inline bool isSingular() const - { - return (deteminant() == 0.0f); - } + inline bool isSingular() const { return (deteminant() == 0.0f); } - inline double det() const - { - return m11 * (m33 * m22 - m32 * m23) - m21 * (m33 * m12 - m32 * m13) + m31 * (m23 * m12 - m22 * m13); - } + inline double det() const { return m11 * (m33 * m22 - m32 * m23) - m21 * (m33 * m12 - m32 * m13) + m31 * (m23 * m12 - m22 * m13); } inline Matrix3x3 inverse() const { @@ -188,7 +183,7 @@ class Matrix3x3 inline Point3d mldivide(const Point3d& b) { - if(!isSingular()) + if (!isSingular()) { return inverse() * b; } @@ -274,7 +269,7 @@ class Matrix3x3 { double dt = det(); - if((fabs(dt) < 0.00000001f) || std::isnan(dt)) + if ((fabs(dt) < 0.00000001f) || std::isnan(dt)) { return false; } @@ -346,4 +341,4 @@ inline Matrix3x3 diag3x3(double d1, double d2, double d3) return m; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Matrix3x4.hpp b/src/aliceVision/mvsData/Matrix3x4.hpp index 6030ec8a80..9c9f9842c5 100644 --- a/src/aliceVision/mvsData/Matrix3x4.hpp +++ b/src/aliceVision/mvsData/Matrix3x4.hpp @@ -14,8 +14,9 @@ namespace aliceVision { class Matrix3x4 { -public: - union { + public: + union + { struct { double m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34; @@ -43,9 +44,8 @@ class Matrix3x4 inline Point3d operator*(const Point3d& _p) const { - return Point3d(m11 * _p.x + m12 * _p.y + m13 * _p.z + m14, - m21 * _p.x + m22 * _p.y + m23 * _p.z + m24, - m31 * _p.x + m32 * _p.y + m33 * _p.z + m34); + return Point3d( + m11 * _p.x + m12 * _p.y + m13 * _p.z + m14, m21 * _p.x + m22 * _p.y + m23 * _p.z + m24, m31 * _p.x + m32 * _p.y + m33 * _p.z + m34); } inline double deteminant() const @@ -77,7 +77,6 @@ class Matrix3x4 return m; } - void decomposeProjectionMatrix(Matrix3x3& K, Matrix3x3& R, Point3d& C) const { Matrix3x3 H = sub3x3(); @@ -85,7 +84,7 @@ class Matrix3x4 const bool cam_affine = (K.m33 == 0); - if(!cam_affine) + if (!cam_affine) { K = K / fabs(K.m33); } @@ -97,14 +96,14 @@ class Matrix3x4 throw std::runtime_error("Matrix3x4::decomposeProjectionMatrix: affine camera."); } - if(K.m11 < 0.0f) + if (K.m11 < 0.0f) { Matrix3x3 D = diag3x3(-1.0f, -1.0f, 1.0f); K = K * D; R = D * R; } - if(K.m22 < 0.0f) + if (K.m22 < 0.0f) { Matrix3x3 D = diag3x3(1.0f, -1.0f, -1.0f); K = K * D; @@ -155,4 +154,4 @@ inline Matrix3x4 operator|(const Matrix3x3& M, const Point3d& p) return m; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/OrientedPoint.hpp b/src/aliceVision/mvsData/OrientedPoint.hpp index 304051f9a6..8ce6399b23 100644 --- a/src/aliceVision/mvsData/OrientedPoint.hpp +++ b/src/aliceVision/mvsData/OrientedPoint.hpp @@ -12,9 +12,9 @@ namespace aliceVision { struct OrientedPoint { - Point3d p; // 3 * float : 3 * 4 = 12 Bytes : (one float is 4 Bytes : 3.4E +/- 38 (7 digits) ) - Point3d n; // 3 * float : 3 * 4 = 12 Bytes - float sim; // 4-Bytes : 3.4E +/- 38 (7 digits) + Point3d p; // 3 * float : 3 * 4 = 12 Bytes : (one float is 4 Bytes : 3.4E +/- 38 (7 digits) ) + Point3d n; // 3 * float : 3 * 4 = 12 Bytes + float sim; // 4-Bytes : 3.4E +/- 38 (7 digits) // TOTAL: 12 + 12 + 4 = 28 Bytes OrientedPoint() @@ -39,10 +39,7 @@ struct OrientedPoint return *this; } - bool operator>(const OrientedPoint& param) const - { - return (sim > param.sim); - } + bool operator>(const OrientedPoint& param) const { return (sim > param.sim); } }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Pixel.hpp b/src/aliceVision/mvsData/Pixel.hpp index 201e05e443..805e1514f2 100644 --- a/src/aliceVision/mvsData/Pixel.hpp +++ b/src/aliceVision/mvsData/Pixel.hpp @@ -12,7 +12,8 @@ namespace aliceVision { struct Pixel { - union { + union + { struct { int x, y; @@ -28,7 +29,6 @@ struct Pixel explicit Pixel(const Point2d& p) { - x = (int)floor(p.x + 0.5); y = (int)floor(p.y + 0.5); } @@ -39,10 +39,7 @@ struct Pixel y = _y; } - inline int& operator[](const int index) - { - return m[index]; - } + inline int& operator[](const int index) { return m[index]; } inline Pixel& operator=(const Pixel& param) { @@ -51,29 +48,17 @@ struct Pixel return *this; } - inline bool operator==(const Pixel& param) - { - return ((x == param.x) && (y == param.y)); - } + inline bool operator==(const Pixel& param) { return ((x == param.x) && (y == param.y)); } - inline Pixel operator-(const Pixel& _p) - { - return Pixel(x - _p.x, y - _p.y); - } + inline Pixel operator-(const Pixel& _p) { return Pixel(x - _p.x, y - _p.y); } - inline Pixel operator+(const Pixel& _p) - { - return Pixel(x + _p.x, y + _p.y); - } + inline Pixel operator+(const Pixel& _p) { return Pixel(x + _p.x, y + _p.y); } - inline Pixel operator*(const int& d) - { - return Pixel(x * d, y * d); - } + inline Pixel operator*(const int& d) { return Pixel(x * d, y * d); } inline Pixel operator/(int d) { - if(d == 0) + if (d == 0) { return Pixel(0, 0); } @@ -86,19 +71,10 @@ struct Pixel }; } - inline double size() - { - return sqrt((double)(x * x + y * y)); - } - inline int size2() - { - return x * x + y * y; - } + inline double size() { return sqrt((double)(x * x + y * y)); } + inline int size2() { return x * x + y * y; } - friend int dot(const Pixel& p1, const Pixel& p2) - { - return p1.x * p2.x + p1.y * p2.y; - } + friend int dot(const Pixel& p1, const Pixel& p2) { return p1.x * p2.x + p1.y * p2.y; } }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Point2d.hpp b/src/aliceVision/mvsData/Point2d.hpp index b8ec7b4651..e1736c14d3 100644 --- a/src/aliceVision/mvsData/Point2d.hpp +++ b/src/aliceVision/mvsData/Point2d.hpp @@ -13,8 +13,9 @@ namespace aliceVision { class Point2d { -public: - union { + public: + union + { struct { double x, y; @@ -47,30 +48,15 @@ class Point2d return *this; } - inline Point2d operator-(const Point2d& _p) const - { - return Point2d(x - _p.x, y - _p.y); - } + inline Point2d operator-(const Point2d& _p) const { return Point2d(x - _p.x, y - _p.y); } - inline Point2d operator+(const Point2d& _p) const - { - return Point2d(x + _p.x, y + _p.y); - } + inline Point2d operator+(const Point2d& _p) const { return Point2d(x + _p.x, y + _p.y); } - inline Point2d operator*(const double d) const - { - return Point2d(x * d, y * d); - } + inline Point2d operator*(const double d) const { return Point2d(x * d, y * d); } - inline Point2d operator+(const double d) const - { - return Point2d(x + d, y + d); - } + inline Point2d operator+(const double d) const { return Point2d(x + d, y + d); } - inline Point2d operator/(const double d) const - { - return Point2d(x / d, y / d); - } + inline Point2d operator/(const double d) const { return Point2d(x / d, y / d); } inline Point2d normalize() const { @@ -78,18 +64,12 @@ class Point2d return Point2d(x / d, y / d); } - inline double size() const - { - return std::sqrt(x * x + y * y); - } + inline double size() const { return std::sqrt(x * x + y * y); } friend double dot(const Point2d& p1, const Point2d& p2); }; -inline double dot(const Point2d& p1, const Point2d& p2) -{ - return p1.x * p2.x + p1.y * p2.y; -} +inline double dot(const Point2d& p1, const Point2d& p2) { return p1.x * p2.x + p1.y * p2.y; } inline std::ostream& operator<<(std::ostream& stream, const Point2d& p) { @@ -97,4 +77,4 @@ inline std::ostream& operator<<(std::ostream& stream, const Point2d& p) return stream; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Point3d.hpp b/src/aliceVision/mvsData/Point3d.hpp index 99c2b1edee..cc7a84ec47 100644 --- a/src/aliceVision/mvsData/Point3d.hpp +++ b/src/aliceVision/mvsData/Point3d.hpp @@ -14,16 +14,17 @@ // #include namespace GEO { - template - class vecng; +template +class vecng; }; namespace aliceVision { class Point3d { -public: - union { + public: + union + { struct { double x, y, z; @@ -44,7 +45,7 @@ class Point3d y = _y; z = _z; } - + inline Point3d(const double* const p) { x = p[0]; @@ -84,35 +85,17 @@ class Point3d return *this; } - inline bool operator==(const Point3d& param) const - { - return (x == param.x) && (y == param.y) && (z == param.z); - } + inline bool operator==(const Point3d& param) const { return (x == param.x) && (y == param.y) && (z == param.z); } - inline Point3d operator-(const Point3d& _p) const - { - return Point3d(x - _p.x, y - _p.y, z - _p.z); - } + inline Point3d operator-(const Point3d& _p) const { return Point3d(x - _p.x, y - _p.y, z - _p.z); } - inline Point3d operator-() const - { - return Point3d(-x, -y, -z); - } + inline Point3d operator-() const { return Point3d(-x, -y, -z); } - inline Point3d operator+(const Point3d& _p) const - { - return Point3d(x + _p.x, y + _p.y, z + _p.z); - } + inline Point3d operator+(const Point3d& _p) const { return Point3d(x + _p.x, y + _p.y, z + _p.z); } - inline Point3d operator*(const double d) const - { - return Point3d(x * d, y * d, z * d); - } + inline Point3d operator*(const double d) const { return Point3d(x * d, y * d, z * d); } - inline Point3d operator/(const double d) const - { - return Point3d(x / d, y / d, z / d); - } + inline Point3d operator/(const double d) const { return Point3d(x / d, y / d, z / d); } inline Point3d normalize() const { @@ -123,7 +106,7 @@ class Point3d inline double size() const { double d = x * x + y * y + z * z; - if(d == 0.0) + if (d == 0.0) { return 0.0; } @@ -131,15 +114,12 @@ class Point3d return sqrt(d); } - inline double size2() const - { - return x * x + y * y + z * z; - } + inline double size2() const { return x * x + y * y + z * z; } - template + template operator GEO::vecng<3, T>() const { - return GEO::vecng<3, T>(x, y, z); + return GEO::vecng<3, T>(x, y, z); } friend double dist(const Point3d& p1, const Point3d& p2); @@ -149,15 +129,9 @@ class Point3d friend Point3d proj(const Point3d& e, const Point3d& a); }; -inline double dist(const Point3d& p1, const Point3d& p2) -{ - return (p1 - p2).size(); -} +inline double dist(const Point3d& p1, const Point3d& p2) { return (p1 - p2).size(); } -inline double dot(const Point3d& p1, const Point3d& p2) -{ - return p1.x * p2.x + p1.y * p2.y + p1.z * p2.z; -} +inline double dot(const Point3d& p1, const Point3d& p2) { return p1.x * p2.x + p1.y * p2.y + p1.z * p2.z; } inline Point3d cross(const Point3d& a, const Point3d& b) { @@ -169,15 +143,9 @@ inline Point3d cross(const Point3d& a, const Point3d& b) return vc; } -inline Point3d proj(const Point3d& e, const Point3d& a) -{ - return e * (dot(e, a) / dot(e, e)); -} +inline Point3d proj(const Point3d& e, const Point3d& a) { return e * (dot(e, a) / dot(e, e)); } -inline double tripleProduct(const Point3d& a, const Point3d& b, const Point3d& c) -{ - return dot(a, cross(b, c)); -} +inline double tripleProduct(const Point3d& a, const Point3d& b, const Point3d& c) { return dot(a, cross(b, c)); } /** * @brief Solid angle of a tetrahedron. It takes 3 vectors OA, OB, AC to define the solid angle define by the triangle ABC arount the point O. @@ -200,9 +168,6 @@ inline std::ostream& operator<<(std::ostream& stream, const Point3d& p) return stream; } -inline Eigen::Matrix toEigen(const Point3d& v) -{ - return Eigen::Matrix(v.m); -} +inline Eigen::Matrix toEigen(const Point3d& v) { return Eigen::Matrix(v.m); } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Point4d.hpp b/src/aliceVision/mvsData/Point4d.hpp index 506f293ae8..41e30d972c 100644 --- a/src/aliceVision/mvsData/Point4d.hpp +++ b/src/aliceVision/mvsData/Point4d.hpp @@ -12,8 +12,9 @@ namespace aliceVision { class Point4d { -public: - union { + public: + union + { struct { float x, y, z, w; @@ -46,35 +47,17 @@ class Point4d return *this; } - inline bool operator==(const Point4d& param) const - { - return (x == param.x) && (y == param.y) && (z == param.z) && (w == param.w); - } + inline bool operator==(const Point4d& param) const { return (x == param.x) && (y == param.y) && (z == param.z) && (w == param.w); } - inline Point4d operator-(const Point4d& _p) const - { - return Point4d(x - _p.x, y - _p.y, z - _p.z, w - _p.w); - } + inline Point4d operator-(const Point4d& _p) const { return Point4d(x - _p.x, y - _p.y, z - _p.z, w - _p.w); } - inline Point4d operator-() const - { - return Point4d(-x, -y, -z, -w); - } + inline Point4d operator-() const { return Point4d(-x, -y, -z, -w); } - inline Point4d operator+(const Point4d& _p) const - { - return Point4d(x + _p.x, y + _p.y, z + _p.z, w + _p.w); - } + inline Point4d operator+(const Point4d& _p) const { return Point4d(x + _p.x, y + _p.y, z + _p.z, w + _p.w); } - inline Point4d operator*(const float d) const - { - return Point4d(x * d, y * d, z * d, w * d); - } + inline Point4d operator*(const float d) const { return Point4d(x * d, y * d, z * d, w * d); } - inline Point4d operator/(const float d) const - { - return Point4d(x / d, y / d, z / d, w / d); - } + inline Point4d operator/(const float d) const { return Point4d(x / d, y / d, z / d, w / d); } inline Point4d normalize() const { @@ -85,20 +68,14 @@ class Point4d inline float size() const { float d = x * x + y * y + z * z + w * w; - if(d == 0.0f) + if (d == 0.0f) return 0.0f; return sqrt(d); } - friend float dot(const Point4d& p1, const Point4d& p2) - { - return p1.x * p2.x + p1.y * p2.y + p1.z * p2.z + p1.w * p2.w; - } + friend float dot(const Point4d& p1, const Point4d& p2) { return p1.x * p2.x + p1.y * p2.y + p1.z * p2.z + p1.w * p2.w; } - friend Point4d proj(Point4d& e, Point4d& a) - { - return e * (dot(e, a) / dot(e, e)); - } + friend Point4d proj(Point4d& e, Point4d& a) { return e * (dot(e, a) / dot(e, e)); } }; inline std::ostream& operator<<(std::ostream& stream, const Point4d& p) @@ -107,4 +84,4 @@ inline std::ostream& operator<<(std::ostream& stream, const Point4d& p) return stream; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/ROI.hpp b/src/aliceVision/mvsData/ROI.hpp index 1c5fdb608b..c3a26158ae 100644 --- a/src/aliceVision/mvsData/ROI.hpp +++ b/src/aliceVision/mvsData/ROI.hpp @@ -8,22 +8,22 @@ // allows code sharing between NVCC and other compilers #if defined(__NVCC__) -#define CUDA_HOST_DEVICE __host__ __device__ -#define CUDA_HOST __host__ -#define CUDA_CEIL(f) ceil(f) -#define CUDA_FLOOR(f) floor(f) -#define CUDA_MIN(a,b) min(a,b) -#define CUDA_MAX(a,b) max(a,b) + #define CUDA_HOST_DEVICE __host__ __device__ + #define CUDA_HOST __host__ + #define CUDA_CEIL(f) ceil(f) + #define CUDA_FLOOR(f) floor(f) + #define CUDA_MIN(a, b) min(a, b) + #define CUDA_MAX(a, b) max(a, b) #else -#define CUDA_HOST_DEVICE -#define CUDA_HOST -#define CUDA_CEIL(f) std::ceil(f) -#define CUDA_FLOOR(f) std::floor(f) -#define CUDA_MIN(a,b) std::min(a, b) -#define CUDA_MAX(a,b) std::max(a, b) -#include -#include -#include + #define CUDA_HOST_DEVICE + #define CUDA_HOST + #define CUDA_CEIL(f) std::ceil(f) + #define CUDA_FLOOR(f) std::floor(f) + #define CUDA_MIN(a, b) std::min(a, b) + #define CUDA_MAX(a, b) std::max(a, b) + #include + #include + #include #endif namespace aliceVision { @@ -45,12 +45,11 @@ struct Range * @param[in] in_begin the range begin index * @param[in] in_end the range end index */ - CUDA_HOST_DEVICE Range(unsigned int in_begin, - unsigned int in_end) - : begin(in_begin) - , end(in_end) + CUDA_HOST_DEVICE Range(unsigned int in_begin, unsigned int in_end) + : begin(in_begin), + end(in_end) {} - + /** * @brief Return true if the given index is contained in the Range. * @param[in] i the given index @@ -65,17 +64,10 @@ struct Range * @param[in] i the given index * @return true if the given index point is contained in the Range */ - CUDA_HOST inline bool contains(unsigned int i) const - { - return ((begin <= i) && (end > i)); - } + CUDA_HOST inline bool contains(unsigned int i) const { return ((begin <= i) && (end > i)); } }; -inline Range intersect(const Range& a, const Range& b) -{ - return Range(CUDA_MAX(a.begin, b.begin), - CUDA_MIN(a.end, b.end)); -} +inline Range intersect(const Range& a, const Range& b) { return Range(CUDA_MAX(a.begin, b.begin), CUDA_MIN(a.end, b.end)); } /* * @struct ROI @@ -95,12 +87,9 @@ struct ROI * @param[in] in_beginY the range Y begin index * @param[in] in_endY the range Y end index */ - CUDA_HOST_DEVICE ROI(unsigned int in_beginX, - unsigned int in_endX, - unsigned int in_beginY, - unsigned int in_endY) - : x(in_beginX, in_endX) - , y(in_beginY, in_endY) + CUDA_HOST_DEVICE ROI(unsigned int in_beginX, unsigned int in_endX, unsigned int in_beginY, unsigned int in_endY) + : x(in_beginX, in_endX), + y(in_beginY, in_endY) {} /** @@ -108,17 +97,16 @@ struct ROI * @param[in] in_rangeX the X index range * @param[in] in_rangeY the Y index range */ - CUDA_HOST_DEVICE ROI(const Range& in_rangeX, - const Range& in_rangeY) - : x(in_rangeX) - , y(in_rangeY) + CUDA_HOST_DEVICE ROI(const Range& in_rangeX, const Range& in_rangeY) + : x(in_rangeX), + y(in_rangeY) {} /** * @brief Get the ROI width * @return the X range size */ - CUDA_HOST_DEVICE inline unsigned int width() const { return x.size(); } + CUDA_HOST_DEVICE inline unsigned int width() const { return x.size(); } /** * @brief Get the ROI height @@ -134,10 +122,7 @@ struct ROI * @param[in] in_y the given 2d point Y coordinate * @return true if the given 2d point is contained in the ROI */ - CUDA_HOST inline bool contains(unsigned int in_x, unsigned int in_y) const - { - return (x.contains(in_x) && y.contains(in_y)); - } + CUDA_HOST inline bool contains(unsigned int in_x, unsigned int in_y) const { return (x.contains(in_x) && y.contains(in_y)); } }; /** @@ -149,8 +134,7 @@ struct ROI */ CUDA_HOST inline bool checkImageROI(const ROI& roi, int width, int height) { - return ((roi.x.end <= (unsigned int)(width)) && (roi.x.begin < roi.x.end) && - (roi.y.end <= (unsigned int)(height)) && (roi.y.begin < roi.y.end)); + return ((roi.x.end <= (unsigned int)(width)) && (roi.x.begin < roi.x.end) && (roi.y.end <= (unsigned int)(height)) && (roi.y.begin < roi.y.end)); } /** @@ -194,11 +178,7 @@ CUDA_HOST inline Range inflateRange(const Range& range, float factor) * @param[in] downscale the downscale factor to apply * @return the downscaled ROI */ -CUDA_HOST inline ROI downscaleROI(const ROI& roi, float downscale) -{ - return ROI(downscaleRange(roi.x, downscale), - downscaleRange(roi.y, downscale)); -} +CUDA_HOST inline ROI downscaleROI(const ROI& roi, float downscale) { return ROI(downscaleRange(roi.x, downscale), downscaleRange(roi.y, downscale)); } /** * @brief Upscale the given ROI with the given upscale factor @@ -206,11 +186,7 @@ CUDA_HOST inline ROI downscaleROI(const ROI& roi, float downscale) * @param[in] upscale the upscale factor to apply * @return the upscaled ROI */ -CUDA_HOST inline ROI upscaleROI(const ROI& roi, float upscale) -{ - return ROI(upscaleRange(roi.x, upscale), - upscaleRange(roi.y, upscale)); -} +CUDA_HOST inline ROI upscaleROI(const ROI& roi, float upscale) { return ROI(upscaleRange(roi.x, upscale), upscaleRange(roi.y, upscale)); } /** * @brief Inflate the given ROI with the given factor @@ -218,17 +194,9 @@ CUDA_HOST inline ROI upscaleROI(const ROI& roi, float upscale) * @param[in] factor the inflate factor to apply * @return the Inflated ROI */ -CUDA_HOST inline ROI inflateROI(const ROI& roi, float factor) -{ - return ROI(inflateRange(roi.x, factor), - inflateRange(roi.y, factor)); -} - +CUDA_HOST inline ROI inflateROI(const ROI& roi, float factor) { return ROI(inflateRange(roi.x, factor), inflateRange(roi.y, factor)); } -inline ROI intersect(const ROI& a, const ROI& b) -{ - return ROI(intersect(a.x, b.x), intersect(a.y, b.y)); -} +inline ROI intersect(const ROI& a, const ROI& b) { return ROI(intersect(a.x, b.x), intersect(a.y, b.y)); } #if !defined(__NVCC__) inline std::ostream& operator<<(std::ostream& os, const Range& range) @@ -243,5 +211,4 @@ inline std::ostream& operator<<(std::ostream& os, const ROI& roi) } #endif -} // namespace aliceVision - +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Stat3d.cpp b/src/aliceVision/mvsData/Stat3d.cpp index 99463bdc46..7587f69cbb 100644 --- a/src/aliceVision/mvsData/Stat3d.cpp +++ b/src/aliceVision/mvsData/Stat3d.cpp @@ -10,10 +10,7 @@ namespace aliceVision { -double hypot2(double x, double y) -{ - return sqrt(x * x + y * y); -} +double hypot2(double x, double y) { return sqrt(x * x + y * y); } // Symmetric Householder reductio3 to tridiago3al form. void tred2(double V0[], double V1[], double V2[], double d[], double e[]) @@ -32,26 +29,25 @@ void tred2(double V0[], double V1[], double V2[], double d[], double e[]) V[2][1] = V2[1]; V[2][2] = V2[2]; - for(j = 0; j < 3; j++) + for (j = 0; j < 3; j++) { d[j] = V[3 - 1][j]; } // Householder reduction to tridiagonal form. - for(i = 3 - 1; i > 0; i--) + for (i = 3 - 1; i > 0; i--) { - // Scale to avoid under/overflow. scale = 0.0; h = 0.0; - for(k = 0; k < i; k++) + for (k = 0; k < i; k++) { scale = scale + fabs(d[k]); } - if(scale == 0.0) + if (scale == 0.0) { e[i] = d[i - 1]; - for(j = 0; j < i; j++) + for (j = 0; j < i; j++) { d[j] = V[i - 1][j]; V[i][j] = 0.0; @@ -60,34 +56,33 @@ void tred2(double V0[], double V1[], double V2[], double d[], double e[]) } else { - // Generate Householder vector. - for(k = 0; k < i; k++) + for (k = 0; k < i; k++) { d[k] /= scale; h += d[k] * d[k]; } f = d[i - 1]; g = sqrt(h); - if(f > 0) + if (f > 0) { g = -g; } e[i] = scale * g; h = h - f * g; d[i - 1] = f - g; - for(j = 0; j < i; j++) + for (j = 0; j < i; j++) { e[j] = 0.0; } // Apply similarity transformation to remaining columns. - for(j = 0; j < i; j++) + for (j = 0; j < i; j++) { f = d[j]; V[j][i] = f; g = e[j] + V[j][j] * f; - for(k = j + 1; k <= i - 1; k++) + for (k = j + 1; k <= i - 1; k++) { g += V[k][j] * d[k]; e[k] += V[k][j] * f; @@ -95,21 +90,21 @@ void tred2(double V0[], double V1[], double V2[], double d[], double e[]) e[j] = g; } f = 0.0; - for(j = 0; j < i; j++) + for (j = 0; j < i; j++) { e[j] /= h; f += e[j] * d[j]; } hh = f / (h + h); - for(j = 0; j < i; j++) + for (j = 0; j < i; j++) { e[j] -= hh * d[j]; } - for(j = 0; j < i; j++) + for (j = 0; j < i; j++) { f = d[j]; g = e[j]; - for(k = j; k <= i - 1; k++) + for (k = j; k <= i - 1; k++) { V[k][j] -= (f * e[k] + g * d[k]); } @@ -121,36 +116,36 @@ void tred2(double V0[], double V1[], double V2[], double d[], double e[]) } // Accumulate transformations. - for(i = 0; i < 3 - 1; i++) + for (i = 0; i < 3 - 1; i++) { V[3 - 1][i] = V[i][i]; V[i][i] = 1.0; h = d[i + 1]; - if(h != 0.0) + if (h != 0.0) { - for(k = 0; k <= i; k++) + for (k = 0; k <= i; k++) { d[k] = V[k][i + 1] / h; } - for(j = 0; j <= i; j++) + for (j = 0; j <= i; j++) { g = 0.0; - for(k = 0; k <= i; k++) + for (k = 0; k <= i; k++) { g += V[k][i + 1] * V[k][j]; } - for(k = 0; k <= i; k++) + for (k = 0; k <= i; k++) { V[k][j] -= g * d[k]; } } } - for(k = 0; k <= i; k++) + for (k = 0; k <= i; k++) { V[k][i + 1] = 0.0; } } - for(j = 0; j < 3; j++) + for (j = 0; j < 3; j++) { d[j] = V[3 - 1][j]; V[3 - 1][j] = 0.0; @@ -188,7 +183,7 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) V[2][1] = V2[1]; V[2][2] = V2[2]; - for(i = 1; i < 3; i++) + for (i = 1; i < 3; i++) { e[i - 1] = e[i]; } @@ -197,16 +192,15 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) f = 0.0; tst1 = 0.0; eps = pow(2.0, -52.0); - for(l = 0; l < 3; l++) + for (l = 0; l < 3; l++) { - // Fi3d small subdiago3al eleme3t tst1 = std::max(tst1, fabs(d[l]) + fabs(e[l])); m = l; - while(m < 3) + while (m < 3) { - if(fabs(e[m]) <= eps * tst1) + if (fabs(e[m]) <= eps * tst1) { break; } @@ -216,19 +210,19 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) // If m == l, d[l] is a3 eige3value, // otherwise, iterate. - if(m > l) + if (m > l) { iter = 0; do { - iter = iter + 1; // (Could check iteratio3 cou3t here.) + iter = iter + 1; // (Could check iteratio3 cou3t here.) // Compute implicit shift g = d[l]; p = (d[l + 1] - g) / (2.0 * e[l]); r = hypot2(p, 1.0); - if(p < 0) + if (p < 0) { r = -r; } @@ -236,7 +230,7 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) d[l + 1] = e[l] * (p + r); dl1 = d[l + 1]; h = g - d[l]; - for(i = l + 2; i < 3; i++) + for (i = l + 2; i < 3; i++) { d[i] -= h; } @@ -251,7 +245,7 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) el1 = e[l + 1]; s = 0.0; s2 = 0.0; - for(i = m - 1; i >= l; i--) + for (i = m - 1; i >= l; i--) { c3 = c2; c2 = c; @@ -267,7 +261,7 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) // Accumulate tra3sformatio3. - for(k = 0; k < 3; k++) + for (k = 0; k < 3; k++) { h = V[k][i + 1]; V[k][i + 1] = s * V[k][i] + c * h; @@ -280,7 +274,7 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) // Check for co3verge3ce. - } while(fabs(e[l]) > eps * tst1); + } while (fabs(e[l]) > eps * tst1); } d[l] = d[l] + f; e[l] = 0.0; @@ -288,23 +282,23 @@ void tql2(double V0[], double V1[], double V2[], double d[], double e[]) // Sort eige3values a3d correspo3di3g vectors. - for(i = 0; i < 3 - 1; i++) + for (i = 0; i < 3 - 1; i++) { k = i; p = d[i]; - for(j = i + 1; j < 3; j++) + for (j = i + 1; j < 3; j++) { - if(d[j] < p) + if (d[j] < p) { k = j; p = d[j]; } } - if(k != i) + if (k != i) { d[k] = d[i]; d[i] = p; - for(j = 0; j < 3; j++) + for (j = 0; j < 3; j++) { p = V[j][i]; V[j][i] = V[j][k]; @@ -342,4 +336,4 @@ void Stat3d::eigen_decomposition(double A[3][3], double V0[], double V1[], doubl tql2(V0, V1, V2, d, e); } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Stat3d.hpp b/src/aliceVision/mvsData/Stat3d.hpp index 125642f450..a7bbdaac0a 100644 --- a/src/aliceVision/mvsData/Stat3d.hpp +++ b/src/aliceVision/mvsData/Stat3d.hpp @@ -99,8 +99,8 @@ struct Stat3d d3 = (float)d[0]; } -private: + private: static void eigen_decomposition(double A[3][3], double V0[], double V1[], double V2[], double d[]); }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/StaticVector.cpp b/src/aliceVision/mvsData/StaticVector.cpp index 11b1f6760b..ffe9a30b64 100644 --- a/src/aliceVision/mvsData/StaticVector.cpp +++ b/src/aliceVision/mvsData/StaticVector.cpp @@ -13,7 +13,7 @@ namespace aliceVision { int getArrayLengthFromFile(const std::string& fileName) { FILE* f = fopen(fileName.c_str(), "rb"); - if(f == nullptr) + if (f == nullptr) { // printf("WARNING: file %s does not exists!\n", fileName.c_str()); return 0; @@ -21,14 +21,14 @@ int getArrayLengthFromFile(const std::string& fileName) int n = 0; size_t retval = fread(&n, sizeof(int), 1, f); - if( retval != sizeof(int) ) + if (retval != sizeof(int)) { ALICEVISION_LOG_WARNING("[IO] getArrayLengthFromFile: can't read array length (1)"); } - if(n == -1) + if (n == -1) { retval = fread(&n, sizeof(int), 1, f); - if( retval != sizeof(int) ) + if (retval != sizeof(int)) { ALICEVISION_LOG_WARNING("[IO] getArrayLengthFromFile: can't read array length (2)"); } @@ -37,4 +37,4 @@ int getArrayLengthFromFile(const std::string& fileName) return n; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/StaticVector.hpp b/src/aliceVision/mvsData/StaticVector.hpp index 273d610e77..f67f21c182 100644 --- a/src/aliceVision/mvsData/StaticVector.hpp +++ b/src/aliceVision/mvsData/StaticVector.hpp @@ -22,7 +22,7 @@ namespace aliceVision { -template +template class StaticVector { std::vector _data; @@ -32,27 +32,20 @@ class StaticVector typedef typename std::vector::reference Reference; typedef typename std::vector::const_reference ConstReference; -public: - StaticVector() - {} + public: + StaticVector() {} - StaticVector( int n ) - : _data( n ) + StaticVector(int n) + : _data(n) {} - StaticVector( int n, const T& value ) - : _data( n, value ) + StaticVector(int n, const T& value) + : _data(n, value) {} - const T& operator[](int index) const - { - return _data[index]; - } + const T& operator[](int index) const { return _data[index]; } - T& operator[](int index) - { - return _data[index]; - } + T& operator[](int index) { return _data[index]; } void clear() { _data.clear(); } @@ -77,56 +70,35 @@ class StaticVector void resize(int n) { _data.resize(n); } void resize(int n, T value) { _data.resize(n, value); } void resize_with(int n, const T& val) { _data.resize(n, val); } - void swap( StaticVector& other ) { _data.swap(other._data); } + void swap(StaticVector& other) { _data.swap(other._data); } void assign(int n, T value) { _data.assign(n, value); } - void shrink_to_fit() - { - _data.shrink_to_fit(); - } + void shrink_to_fit() { _data.shrink_to_fit(); } void reserveAddIfNeeded(int nplanned, int ntoallocated) { - if(size() + nplanned > capacity()) + if (size() + nplanned > capacity()) { reserveAdd(nplanned + ntoallocated); } } - void reserveAdd(int ntoallocated) - { - _data.reserve(capacity() + ntoallocated); - } + void reserveAdd(int ntoallocated) { _data.reserve(capacity() + ntoallocated); } - void push_back(const T& val) - { - _data.push_back(val); - } + void push_back(const T& val) { _data.push_back(val); } - void push_front(const T& val) - { - _data.insert(_data.begin(), val); - } + void push_front(const T& val) { _data.insert(_data.begin(), val); } - void push_back_arr(StaticVector* arr) - { - _data.insert(_data.end(), arr->getData().begin(), arr->getData().end()); - } + void push_back_arr(StaticVector* arr) { _data.insert(_data.end(), arr->getData().begin(), arr->getData().end()); } - void push_back_arr(StaticVector& arr) - { - _data.insert(_data.end(), arr.getData().begin(), arr.getData().end()); - } + void push_back_arr(StaticVector& arr) { _data.insert(_data.end(), arr.getData().begin(), arr.getData().end()); } - void remove(int i) - { - _data.erase(_data.begin() + i); - } + void remove(int i) { _data.erase(_data.begin() + i); } int push_back_distinct(const T& val) { int id = indexOf(val); - if(id == -1) + if (id == -1) _data.push_back(val); return id; } @@ -144,19 +116,16 @@ class StaticVector return it != _data.end() ? std::distance(_data.begin(), it) : -1; } - int indexOfSorted(const T& value) const - { - return indexOf(value); - } + int indexOfSorted(const T& value) const { return indexOf(value); } int indexOfNearestSorted(const T& value) const { // Retrieve the first element >= value in _data auto it = std::lower_bound(_data.begin(), _data.end(), value); - if(it == _data.end()) + if (it == _data.end()) return -1; // If we're between two values... - if(it != _data.begin()) + if (it != _data.begin()) { // ...select the index of the closest value between it (>= value) and prevIt (< value) auto prevIt = std::prev(it); @@ -167,7 +136,7 @@ class StaticVector int minValId() const { - if(_data.empty()) + if (_data.empty()) return -1; return std::distance(_data.begin(), std::min_element(_data.begin(), _data.end())); } @@ -184,31 +153,31 @@ class StaticVector // Avoid the problematic case of std::vector::operator[] using StaticVectorBool = StaticVector; -template +template int sizeOfStaticVector(const StaticVector* a) { - if(a == nullptr) + if (a == nullptr) return 0; return a->size(); } -template +template int sizeOfStaticVector(const StaticVector& a) { - if(a.empty()) + if (a.empty()) return 0; return a.size(); } -template +template int indexOf(T* arr, int n, const T& what) { int isthereindex = -1; int i = 0; - while((i < n) && (isthereindex == -1)) + while ((i < n) && (isthereindex == -1)) { - if(arr[i] == what) + if (arr[i] == what) { isthereindex = i; }; @@ -218,18 +187,18 @@ int indexOf(T* arr, int n, const T& what) return isthereindex; } -template +template void saveArrayOfArraysToFile(const std::string& fileName, StaticVector*>* aa) { ALICEVISION_LOG_DEBUG("[IO] saveArrayOfArraysToFile: " << fileName); FILE* f = fopen(fileName.c_str(), "wb"); int n = aa->size(); fwrite(&n, sizeof(int), 1, f); - for(int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { int m = 0; StaticVector* a = (*aa)[i]; - if(a == NULL) + if (a == NULL) { fwrite(&m, sizeof(int), 1, f); } @@ -237,7 +206,7 @@ void saveArrayOfArraysToFile(const std::string& fileName, StaticVectorsize(); fwrite(&m, sizeof(int), 1, f); - if(m > 0) + if (m > 0) { fwrite(&(*a)[0], sizeof(T), m, f); }; @@ -246,18 +215,18 @@ void saveArrayOfArraysToFile(const std::string& fileName, StaticVector +template void saveArrayOfArraysToFile(const std::string fileName, StaticVector>& aa) { ALICEVISION_LOG_DEBUG("[IO] saveArrayOfArraysToFile: " << fileName); FILE* f = fopen(fileName.c_str(), "wb"); int n = aa.size(); fwrite(&n, sizeof(int), 1, f); - for(int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { int m = 0; StaticVector& a = aa[i]; - if(a.empty()) + if (a.empty()) { fwrite(&m, sizeof(int), 1, f); } @@ -265,7 +234,7 @@ void saveArrayOfArraysToFile(const std::string fileName, StaticVector 0) + if (m > 0) { fwrite(&a[0], sizeof(T), m, f); }; @@ -274,19 +243,19 @@ void saveArrayOfArraysToFile(const std::string fileName, StaticVector +template StaticVector*>* loadArrayOfArraysFromFile(const std::string& fileName) { ALICEVISION_LOG_DEBUG("[IO] loadArrayOfArraysFromFile: " << fileName); FILE* f = fopen(fileName.c_str(), "rb"); - if(f == nullptr) + if (f == nullptr) { ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't open file " << fileName); } int n = 0; size_t retval = fread(&n, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't read outer array size"); @@ -294,21 +263,21 @@ StaticVector*>* loadArrayOfArraysFromFile(const std::string& fil StaticVector*>* aa = new StaticVector*>(); aa->reserve(n); aa->resize_with(n, NULL); - for(int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { int m = 0; retval = fread(&m, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't read inner array size"); } - if(m > 0) + if (m > 0) { StaticVector* a = new StaticVector(); a->resize(m); retval = fread(&(*a)[0], sizeof(T), m, f); - if( retval != m ) + if (retval != m) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't read vector element"); @@ -321,19 +290,19 @@ StaticVector*>* loadArrayOfArraysFromFile(const std::string& fil return aa; } -template +template void loadArrayOfArraysFromFile(StaticVector>& out_aa, const std::string& fileName) { ALICEVISION_LOG_DEBUG("[IO] loadArrayOfArraysFromFile: " << fileName); FILE* f = fopen(fileName.c_str(), "rb"); - if(f == nullptr) + if (f == nullptr) { ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't open file " << fileName); } int n = 0; size_t retval = fread(&n, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't read outer array size"); @@ -341,21 +310,21 @@ void loadArrayOfArraysFromFile(StaticVector>& out_aa, const std: out_aa.reserve(n); out_aa.resize(n); - for(int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { int m = 0; retval = fread(&m, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't read inner array size"); } - if(m > 0) + if (m > 0) { StaticVector& a = out_aa[i]; a.resize(m); retval = fread(&a[0], sizeof(T), m, f); - if( retval != m ) + if (retval != m) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayOfArraysFromFile: can't read vector element"); @@ -365,63 +334,61 @@ void loadArrayOfArraysFromFile(StaticVector>& out_aa, const std: fclose(f); } - -template +template void saveArrayToFile(const std::string& fileName, const StaticVector& a, bool docompress = true) { - saveArrayToFile( fileName, &a, docompress ); + saveArrayToFile(fileName, &a, docompress); } -template +template void saveArrayToFile(const std::string& fileName, const StaticVector* a, bool docompress = true) { ALICEVISION_LOG_DEBUG("[IO] saveArrayToFile: " << fileName); boost::filesystem::path filepath = fileName; - boost::filesystem::create_directories( filepath.parent_path() ); + boost::filesystem::create_directories(filepath.parent_path()); - if( !a ) + if (!a) { ALICEVISION_LOG_DEBUG("[IO] saveArrayToFile called with NULL static vector"); return; } - if( a->empty() ) + if (a->empty()) { ALICEVISION_LOG_WARNING("[IO] saveArrayToFile called with 0-sized static vector"); return; } - if((docompress == false) || (a->size() < 1000)) + if ((docompress == false) || (a->size() < 1000)) { FILE* f = fopen(fileName.c_str(), "wb"); - if( f == NULL ) + if (f == NULL) { - ALICEVISION_THROW_ERROR( "[IO] file " << fileName << " could not be opened, msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] file " << fileName << " could not be opened, msg: " << strerror(errno)); } int n = a->size(); - if( n == 0 ) + if (n == 0) { fclose(f); return; } int items = fwrite(&n, sizeof(int), 1, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); - ALICEVISION_THROW_ERROR( "[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno)); } items = fwrite(&(*a)[0], sizeof(T), n, f); - if( items < n && ferror(f) != 0 ) + if (items < n && ferror(f) != 0) { fclose(f); - ALICEVISION_THROW_ERROR( "[IO] failed to write n items to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write n items to " << fileName << ", msg: " << strerror(errno)); } fclose(f); } else { - /* =========================================================================== //from zlib - compress.c Compresses the source buffer into the destination buffer. The level @@ -440,32 +407,32 @@ void saveArrayToFile(const std::string& fileName, const StaticVector* a, bool Byte* compr = (Byte*)calloc((uInt)comprLen, 1); int err = compress(compr, &comprLen, (const Bytef*)(&(*a)[0]), sizeof(T) * a->size()); - if(err != Z_OK) + if (err != Z_OK) { ALICEVISION_LOG_ERROR("compress error " << err << " : " << (sizeof(T) * a->size()) << " -> " << comprLen << ", n " << a->size()); FILE* f = fopen(fileName.c_str(), "wb"); - if( f == NULL ) + if (f == NULL) { free(compr); - ALICEVISION_THROW_ERROR( "[IO] file " << fileName << " could not be opened, msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] file " << fileName << " could not be opened, msg: " << strerror(errno)); } int n = a->size(); - if( n > 0 ) + if (n > 0) { int items = fwrite(&n, sizeof(int), 1, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); free(compr); - ALICEVISION_THROW_ERROR( "[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno)); } items = fwrite(&(*a)[0], sizeof(T), n, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); free(compr); - ALICEVISION_THROW_ERROR( "[IO] failed to write " << n << " items to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write " << n << " items to " << fileName << ", msg: " << strerror(errno)); } } fclose(f); @@ -473,40 +440,40 @@ void saveArrayToFile(const std::string& fileName, const StaticVector* a, bool else { FILE* f = fopen(fileName.c_str(), "wb"); - if( f == NULL ) + if (f == NULL) { free(compr); - ALICEVISION_THROW_ERROR( "[IO] file " << fileName << " could not be opened, msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] file " << fileName << " could not be opened, msg: " << strerror(errno)); } int n = -1; int items = fwrite(&n, sizeof(int), 1, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); free(compr); - ALICEVISION_THROW_ERROR( "[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno)); } n = a->size(); items = fwrite(&n, sizeof(int), 1, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); free(compr); - ALICEVISION_THROW_ERROR( "[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write 1 int to " << fileName << ", msg: " << strerror(errno)); } items = fwrite(&comprLen, sizeof(uLong), 1, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); free(compr); - ALICEVISION_THROW_ERROR( "[IO] failed to write 1 uLong to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write 1 uLong to " << fileName << ", msg: " << strerror(errno)); } items = fwrite(compr, sizeof(Byte), comprLen, f); - if( items < 1 && ferror(f) != 0 ) + if (items < 1 && ferror(f) != 0) { fclose(f); free(compr); - ALICEVISION_THROW_ERROR( "[IO] failed to write " << comprLen << " items to " << fileName << ", msg: " << strerror(errno) ); + ALICEVISION_THROW_ERROR("[IO] failed to write " << comprLen << " items to " << fileName << ", msg: " << strerror(errno)); } fclose(f); }; @@ -515,13 +482,13 @@ void saveArrayToFile(const std::string& fileName, const StaticVector* a, bool }; } -template +template StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarning = false) { ALICEVISION_LOG_DEBUG("[IO] loadArrayFromFile: " << fileName); FILE* f = fopen(fileName.c_str(), "rb"); - if(f == NULL) + if (f == NULL) { ALICEVISION_THROW_ERROR("loadArrayFromFile : can't open file " << fileName); } @@ -529,17 +496,17 @@ StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarni { int n = 0; size_t retval = fread(&n, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayFromFile: can't read array size (1) from " << fileName); } StaticVector* a = NULL; - if(n == -1) + if (n == -1) { retval = fread(&n, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) { fclose(f); ALICEVISION_THROW_ERROR("[IO] loadArrayFromFile: can't read array size (2)"); @@ -549,7 +516,7 @@ StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarni uLong comprLen; retval = fread(&comprLen, sizeof(uLong), 1, f); - if( retval != 1 ) + if (retval != 1) { delete a; fclose(f); @@ -557,7 +524,7 @@ StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarni } Byte* compr = (Byte*)calloc((uInt)comprLen, 1); retval = fread(compr, sizeof(Byte), comprLen, f); - if( retval != comprLen ) + if (retval != comprLen) { delete a; fclose(f); @@ -567,14 +534,14 @@ StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarni uLong uncomprLen = sizeof(T) * n; int err = uncompress((Bytef*)(&(*a)[0]), &uncomprLen, compr, comprLen); - if(err != Z_OK) + if (err != Z_OK) { delete a; fclose(f); ALICEVISION_THROW_ERROR("uncompress error " << err << " : " << (sizeof(T) * n) << " -> " << uncomprLen << ", n " << n); } - if(uncomprLen != sizeof(T) * n) + if (uncomprLen != sizeof(T) * n) { delete a; fclose(f); @@ -588,7 +555,7 @@ StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarni a = new StaticVector(); a->resize(n); size_t retval = fread(&(*a)[0], sizeof(T), n, f); - if( retval != n ) + if (retval != n) { delete a; fclose(f); @@ -602,13 +569,13 @@ StaticVector* loadArrayFromFile(const std::string& fileName, bool printfWarni } } -template -bool loadArrayFromFile( StaticVector& out, const std::string& fileName, bool printfWarning = false) +template +bool loadArrayFromFile(StaticVector& out, const std::string& fileName, bool printfWarning = false) { ALICEVISION_LOG_DEBUG("[IO] loadArrayFromFile: " << fileName); FILE* f = fopen(fileName.c_str(), "rb"); - if(f == NULL) + if (f == NULL) { throw std::runtime_error("loadArrayFromFile : can't open file " + fileName); } @@ -616,35 +583,35 @@ bool loadArrayFromFile( StaticVector& out, const std::string& fileName, bool { int n = 0; size_t retval = fread(&n, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) ALICEVISION_LOG_WARNING("[IO] loadArrayFromFile: can't read array size (1) from " << fileName); out.clear(); - if(n == -1) + if (n == -1) { retval = fread(&n, sizeof(int), 1, f); - if( retval != 1 ) + if (retval != 1) ALICEVISION_LOG_WARNING("[IO] loadArrayFromFile: can't read array size (2)"); out.resize(n); uLong comprLen; retval = fread(&comprLen, sizeof(uLong), 1, f); - if( retval != 1 ) + if (retval != 1) ALICEVISION_LOG_WARNING("[IO] loadArrayFromFile: can't read ulong elem size"); Byte* compr = (Byte*)calloc((uInt)comprLen, 1); retval = fread(compr, sizeof(Byte), comprLen, f); - if( retval != comprLen ) + if (retval != comprLen) ALICEVISION_LOG_WARNING("[IO] loadArrayFromFile: can't read blob"); uLong uncomprLen = sizeof(T) * n; int err = uncompress((Bytef*)out.getDataWritable().data(), &uncomprLen, compr, comprLen); - if(err != Z_OK) + if (err != Z_OK) { ALICEVISION_LOG_ERROR("uncompress error " << err << " : " << (sizeof(T) * n) << " -> " << uncomprLen << ", n " << n); } - if(uncomprLen != sizeof(T) * n) + if (uncomprLen != sizeof(T) * n) { fclose(f); throw std::runtime_error("loadArrayFromFile: uncompression failed uncomprLen!=sizeof(T)*n"); @@ -656,7 +623,7 @@ bool loadArrayFromFile( StaticVector& out, const std::string& fileName, bool { out.resize(n); size_t retval = fread(out.getDataWritable().data(), sizeof(T), n, f); - if( retval != n ) + if (retval != n) ALICEVISION_LOG_WARNING("[IO] loadArrayFromFile: can't read n elements"); } @@ -666,98 +633,98 @@ bool loadArrayFromFile( StaticVector& out, const std::string& fileName, bool } } -template +template void loadArrayFromFileIntoArray(StaticVector* a, const std::string& fileName, bool printfWarning = false) { ALICEVISION_LOG_DEBUG("[IO] loadArrayFromFileIntoArray: " << fileName); FILE* f = fopen(fileName.c_str(), "rb"); - if(f == NULL) + if (f == NULL) { ALICEVISION_THROW_ERROR("loadArrayFromFileIntoArray: can not open file: " << fileName); } int n = 0; fread(&n, sizeof(int), 1, f); - - if(n == -1) + + if (n == -1) { fread(&n, sizeof(int), 1, f); - if(a->size() != n) + if (a->size() != n) { fclose(f); ALICEVISION_THROW_ERROR("loadArrayFromFileIntoArray: expected length " << a->size() << " loaded length " << n); } - + uLong comprLen; fread(&comprLen, sizeof(uLong), 1, f); Byte* compr = (Byte*)calloc((uInt)comprLen, 1); fread(compr, sizeof(Byte), comprLen, f); - + uLong uncomprLen = sizeof(T) * n; int err = uncompress((Bytef*)(&(*a)[0]), &uncomprLen, compr, comprLen); - - if(err != Z_OK) + + if (err != Z_OK) { fclose(f); ALICEVISION_THROW_ERROR("uncompress error " << err << " : " << (sizeof(T) * n) << " -> " << uncomprLen << ", n " << n); } - - if(uncomprLen != sizeof(T) * n) + + if (uncomprLen != sizeof(T) * n) { fclose(f); ALICEVISION_THROW_ERROR("loadArrayFromFileIntoArray: uncompression failed uncomprLen!=sizeof(T)*n"); } - + free(compr); } else { - if(a->size() != n) + if (a->size() != n) { fclose(f); ALICEVISION_THROW_ERROR("loadArrayFromFileIntoArray: expected length " << a->size() << " loaded length " << n); } fread(&(*a)[0], sizeof(T), n, f); } - + fclose(f); } int getArrayLengthFromFile(const std::string& fileName); -template +template void deleteAllPointers(StaticVector& vec) { - for (int i = 0; i < vec.size(); ++i) - { - if (vec[i] != NULL) + for (int i = 0; i < vec.size(); ++i) { - delete(vec[i]); - vec[i] = NULL; + if (vec[i] != NULL) + { + delete (vec[i]); + vec[i] = NULL; + } } - } } -template +template void deleteArrayOfArrays(StaticVector*>** aa) { - for(int i = 0; i < (*aa)->size(); i++) + for (int i = 0; i < (*aa)->size(); i++) { - if((*(*aa))[i] != NULL) + if ((*(*aa))[i] != NULL) { - delete((*(*aa))[i]); + delete ((*(*aa))[i]); (*(*aa))[i] = NULL; } } - delete(*aa); + delete (*aa); } -template +template void deleteArrayOfArrays(StaticVector*>& aa) { - for(int i = 0; i < aa.size(); i++) + for (int i = 0; i < aa.size(); i++) { - if(aa[i] != NULL) + if (aa[i] != NULL) { delete aa[i]; aa[i] = NULL; @@ -766,15 +733,15 @@ void deleteArrayOfArrays(StaticVector*>& aa) aa.clear(); } -template +template StaticVector*>* cloneArrayOfArrays(StaticVector*>* inAOA) { StaticVector*>* outAOA = new StaticVector*>(); outAOA->reserve(inAOA->size()); // copy - for(int i = 0; i < inAOA->size(); i++) + for (int i = 0; i < inAOA->size(); i++) { - if((*inAOA)[i] == NULL) + if ((*inAOA)[i] == NULL) { outAOA->push_back(NULL); } @@ -790,4 +757,4 @@ StaticVector*>* cloneArrayOfArrays(StaticVector* return outAOA; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Universe.cpp b/src/aliceVision/mvsData/Universe.cpp index 7c5b8178a3..159793a8ed 100644 --- a/src/aliceVision/mvsData/Universe.cpp +++ b/src/aliceVision/mvsData/Universe.cpp @@ -19,32 +19,29 @@ Universe::Universe(int elements) void Universe::initialize() { num = allelems; - for(int i = 0; i < allelems; i++) + for (int i = 0; i < allelems; i++) { elts[i].rank = 0; elts[i].size = 1; - elts[i].p = i; // initialized to the index + elts[i].p = i; // initialized to the index } } -Universe::~Universe() -{ - delete[] elts; -} +Universe::~Universe() { delete[] elts; } int Universe::find(int x) { int y = x; - while(y != elts[y].p) // follow the index stored in p if not the same that the index + while (y != elts[y].p) // follow the index stored in p if not the same that the index y = elts[y].p; - elts[x].p = y; // update x element to the final value (instead of keeping multiple indirections), so next time we will access it directly. + elts[x].p = y; // update x element to the final value (instead of keeping multiple indirections), so next time we will access it directly. return y; } void Universe::join(int x, int y) { // join elements in the one with the highest rank - if(elts[x].rank > elts[y].rank) + if (elts[x].rank > elts[y].rank) { elts[y].p = x; elts[x].size += elts[y].size; @@ -53,20 +50,20 @@ void Universe::join(int x, int y) { elts[x].p = y; elts[y].size += elts[x].size; - if(elts[x].rank == elts[y].rank) + if (elts[x].rank == elts[y].rank) elts[y].rank++; } - num--; // the number of elements has been reduced by one + num--; // the number of elements has been reduced by one } void Universe::addEdge(int x, int y) { int a = find(x); int b = find(y); - if(a != b) + if (a != b) { join(a, b); } } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Universe.hpp b/src/aliceVision/mvsData/Universe.hpp index 3dde2c6284..92cdd18593 100644 --- a/src/aliceVision/mvsData/Universe.hpp +++ b/src/aliceVision/mvsData/Universe.hpp @@ -20,7 +20,7 @@ typedef struct */ class Universe { -public: + public: explicit Universe(int elements); ~Universe(); /// Initialize all elements to the default values @@ -31,14 +31,11 @@ class Universe void join(int x, int y); void addEdge(int x, int y); - inline int size(int x) const - { - return elts[x].size; - } + inline int size(int x) const { return elts[x].size; } -public: + public: uni_elt* elts; int num, allelems; }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/Voxel.hpp b/src/aliceVision/mvsData/Voxel.hpp index d1afe381a5..90562e90f8 100644 --- a/src/aliceVision/mvsData/Voxel.hpp +++ b/src/aliceVision/mvsData/Voxel.hpp @@ -13,7 +13,8 @@ namespace aliceVision { struct Voxel { - union { + union + { struct { int x, y, z; @@ -35,10 +36,7 @@ struct Voxel z = _z; } - int& operator[](const int index) - { - return m[index]; - } + int& operator[](const int index) { return m[index]; } Voxel& operator=(const Voxel& param) { @@ -95,7 +93,7 @@ struct Voxel Voxel operator/(int d) const { - if(d == 0) + if (d == 0) return Voxel(0, 0, 0); Voxel p; @@ -108,20 +106,14 @@ struct Voxel float size() const { float d = static_cast(x * x + y * y + z * z); - if(d == 0.0f) + if (d == 0.0f) return 0.0f; return std::sqrt(d); } - bool operator==(const Voxel& param) const - { - return (x == param.x) && (y == param.y) && (z == param.z); - } + bool operator==(const Voxel& param) const { return (x == param.x) && (y == param.y) && (z == param.z); } - bool operator!=(const Voxel& param) const - { - return (x != param.x) || (y != param.y) || (z != param.z); - } + bool operator!=(const Voxel& param) const { return (x != param.x) || (y != param.y) || (z != param.z); } }; inline std::ostream& operator<<(std::ostream& out, const Voxel& v) @@ -130,4 +122,4 @@ inline std::ostream& operator<<(std::ostream& out, const Voxel& v) return out; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/geometry.cpp b/src/aliceVision/mvsData/geometry.cpp index 5cd70e9400..1a72d70fd1 100644 --- a/src/aliceVision/mvsData/geometry.cpp +++ b/src/aliceVision/mvsData/geometry.cpp @@ -33,12 +33,12 @@ double orientedPointPlaneDistance(const Point3d& point, const Point3d& planePoin void computeRotCS(Point3d* xax, Point3d* yax, const Point3d* n) { - xax->x = -n->y + n->z; // get any cross product + xax->x = -n->y + n->z; // get any cross product xax->y = +n->x + n->z; xax->z = -n->x - n->y; - if(fabs(xax->x) < 0.0000001 && fabs(xax->y) < 0.0000001 && fabs(xax->z) < 0.0000001) + if (fabs(xax->x) < 0.0000001 && fabs(xax->y) < 0.0000001 && fabs(xax->z) < 0.0000001) { - xax->x = -n->y - n->z; // get any cross product (complementar) + xax->x = -n->y - n->z; // get any cross product (complementar) xax->y = +n->x - n->z; xax->z = +n->x + n->y; } @@ -47,8 +47,15 @@ void computeRotCS(Point3d* xax, Point3d* yax, const Point3d* n) *yax = cross(*n, *xax); } -bool lineLineIntersect(double* k, double* l, Point3d* llis, Point3d* lli1, Point3d* lli2, const Point3d& p1, - const Point3d& p2, const Point3d& p3, const Point3d& p4) +bool lineLineIntersect(double* k, + double* l, + Point3d* llis, + Point3d* lli1, + Point3d* lli2, + const Point3d& p1, + const Point3d& p2, + const Point3d& p3, + const Point3d& p4) { /* % [pa, pb, mua, mub] = LineLineIntersect(p1,p2,p3,p4) @@ -78,7 +85,7 @@ bool lineLineIntersect(double* k, double* l, Point3d* llis, Point3d* lli1, Point p43[1] = p4.y - p3.y; p43[2] = p4.z - p3.z; - if((fabs(p43[0]) < FLT_EPSILON) && (fabs(p43[1]) < FLT_EPSILON) && (fabs(p43[2]) < FLT_EPSILON)) + if ((fabs(p43[0]) < FLT_EPSILON) && (fabs(p43[1]) < FLT_EPSILON) && (fabs(p43[2]) < FLT_EPSILON)) { return false; } @@ -87,7 +94,7 @@ bool lineLineIntersect(double* k, double* l, Point3d* llis, Point3d* lli1, Point p21[1] = p2.y - p1.y; p21[2] = p2.z - p1.z; - if((fabs(p21[0]) < FLT_EPSILON) && (fabs(p21[1]) < FLT_EPSILON) && (fabs(p21[2]) < FLT_EPSILON)) + if ((fabs(p21[0]) < FLT_EPSILON) && (fabs(p21[1]) < FLT_EPSILON) && (fabs(p21[2]) < FLT_EPSILON)) { return false; } @@ -100,7 +107,7 @@ bool lineLineIntersect(double* k, double* l, Point3d* llis, Point3d* lli1, Point denom = d2121 * d4343 - d4321 * d4321; - if(fabs(denom) < FLT_EPSILON) + if (fabs(denom) < FLT_EPSILON) { return false; } @@ -146,8 +153,7 @@ bool lineLineIntersect(Point3d& out, const Point3d& p1, const Point3d& v1, const return lineLineIntersect(&k, &l, &out, &lli1, &lli2, p1, p1v1, p2, p2v2); } -Point3d linePlaneIntersect(const Point3d& linePoint, const Point3d& lineVect, const Point3d& planePoint, - const Point3d& planeNormal) +Point3d linePlaneIntersect(const Point3d& linePoint, const Point3d& lineVect, const Point3d& planePoint, const Point3d& planeNormal) { const double planePoint_x = planePoint.x; const double planePoint_y = planePoint.y; @@ -161,14 +167,15 @@ Point3d linePlaneIntersect(const Point3d& linePoint, const Point3d& lineVect, co const double lineVect_x = lineVect.x; const double lineVect_y = lineVect.y; const double lineVect_z = lineVect.z; - const double k = ((planePoint_x * planeNormal_x + planePoint_y * planeNormal_y + planePoint_z * planeNormal_z) - (planeNormal_x * linePoint_x + planeNormal_y * linePoint_y + planeNormal_z * linePoint_z)) / (planeNormal_x * lineVect_x + planeNormal_y * lineVect_y + planeNormal_z * lineVect_z); + const double k = ((planePoint_x * planeNormal_x + planePoint_y * planeNormal_y + planePoint_z * planeNormal_z) - + (planeNormal_x * linePoint_x + planeNormal_y * linePoint_y + planeNormal_z * linePoint_z)) / + (planeNormal_x * lineVect_x + planeNormal_y * lineVect_y + planeNormal_z * lineVect_z); return linePoint + lineVect * k; //---KP---double k = (dot(planePoint, planeNormal) - dot(planeNormal, linePoint)) / dot(planeNormal, lineVect); //---KP---return linePoint + lineVect * k; } -void linePlaneIntersect(Point3d* out, const Point3d* linePoint, const Point3d* lineVect, const Point3d* planePoint, - const Point3d* planeNormal) +void linePlaneIntersect(Point3d* out, const Point3d* linePoint, const Point3d* lineVect, const Point3d* planePoint, const Point3d* planeNormal) { const double planePoint_x = planePoint->x; const double planePoint_y = planePoint->y; @@ -182,10 +189,12 @@ void linePlaneIntersect(Point3d* out, const Point3d* linePoint, const Point3d* l const double lineVect_x = lineVect->x; const double lineVect_y = lineVect->y; const double lineVect_z = lineVect->z; - const double k = ((planePoint_x * planeNormal_x + planePoint_y * planeNormal_y + planePoint_z * planeNormal_z) - (planeNormal_x * linePoint_x + planeNormal_y * linePoint_y + planeNormal_z * linePoint_z)) / (planeNormal_x * lineVect_x + planeNormal_y * lineVect_y + planeNormal_z * lineVect_z); - out->x = linePoint_x + (lineVect_x) * k; - out->y = linePoint_y + (lineVect_y) * k; - out->z = linePoint_z + (lineVect_z) * k; + const double k = ((planePoint_x * planeNormal_x + planePoint_y * planeNormal_y + planePoint_z * planeNormal_z) - + (planeNormal_x * linePoint_x + planeNormal_y * linePoint_y + planeNormal_z * linePoint_z)) / + (planeNormal_x * lineVect_x + planeNormal_y * lineVect_y + planeNormal_z * lineVect_z); + out->x = linePoint_x + (lineVect_x)*k; + out->y = linePoint_y + (lineVect_y)*k; + out->z = linePoint_z + (lineVect_z)*k; //---KP---double k = (dot(*planePoint, *planeNormal) - dot(*planeNormal, *linePoint)) / dot(*planeNormal, *lineVect); //---KP---*out = *linePoint + (*lineVect) * k; } @@ -197,7 +206,7 @@ double angleBetwV1andV2(const Point3d& iV1, const Point3d& iV2) const Point3d V2 = iV2.normalize(); const double a = acos((double)(V1.x * V2.x + V1.y * V2.y + V1.z * V2.z)); - if(std::isnan(a)) + if (std::isnan(a)) return 0.0; return fabs(a / (M_PI / 180.0)); @@ -259,8 +268,12 @@ void rotPointAroundVect(Point3d* out, const Point3d* X, const Point3d* vect, con out->z = o[2]; } -Point2d getLineTriangleIntersectBarycCoords(Point3d* P, const Point3d* A, const Point3d* B, const Point3d* C, - const Point3d* linePoint, const Point3d* lineVect) +Point2d getLineTriangleIntersectBarycCoords(Point3d* P, + const Point3d* A, + const Point3d* B, + const Point3d* C, + const Point3d* linePoint, + const Point3d* lineVect) { // flat code instead of all functions calls to improve performances const double A_x = A->x; @@ -281,7 +294,8 @@ Point2d getLineTriangleIntersectBarycCoords(Point3d* P, const Point3d* A, const const double _n_x = v0_y * v1_z - v0_z * v1_y; const double _n_y = v0_z * v1_x - v0_x * v1_z; const double _n_z = v0_x * v1_y - v0_y * v1_x; - const double k = ((A_x * _n_x + A_y * _n_y + A_z * _n_z) - (_n_x * linePoint_x + _n_y * linePoint_y + _n_z * linePoint_z)) / (_n_x * lineVect_x + _n_y * lineVect_y + _n_z * lineVect_z); + const double k = ((A_x * _n_x + A_y * _n_y + A_z * _n_z) - (_n_x * linePoint_x + _n_y * linePoint_y + _n_z * linePoint_z)) / + (_n_x * lineVect_x + _n_y * lineVect_y + _n_z * lineVect_z); const double P_x = linePoint_x + lineVect_x * k; const double P_y = linePoint_y + lineVect_y * k; const double P_z = linePoint_z + lineVect_z * k; @@ -303,37 +317,35 @@ Point2d getLineTriangleIntersectBarycCoords(Point3d* P, const Point3d* A, const P->y = P_y; P->z = P_z; -/* - const Point3d v0 = *C - *A; - const Point3d v1 = *B - *A; - const Point3d n = cross(v0.normalize(), v1.normalize()).normalize(); - linePlaneIntersect(P, linePoint, lineVect, A, &n); - // Compute vectors - const Point3d v2 = *P - *A; - // Compute dot products - const double dot00 = dot(v0, v0); - const double dot01 = dot(v0, v1); - const double dot02 = dot(v0, v2); - const double dot11 = dot(v1, v1); - const double dot12 = dot(v1, v2); - // Compute barycentric coordinates - const double invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01); - const double u = (dot11 * dot02 - dot01 * dot12) * invDenom; - const double v = (dot00 * dot12 - dot01 * dot02) * invDenom; -*/ + /* + const Point3d v0 = *C - *A; + const Point3d v1 = *B - *A; + const Point3d n = cross(v0.normalize(), v1.normalize()).normalize(); + linePlaneIntersect(P, linePoint, lineVect, A, &n); + // Compute vectors + const Point3d v2 = *P - *A; + // Compute dot products + const double dot00 = dot(v0, v0); + const double dot01 = dot(v0, v1); + const double dot02 = dot(v0, v2); + const double dot11 = dot(v1, v1); + const double dot12 = dot(v1, v2); + // Compute barycentric coordinates + const double invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01); + const double u = (dot11 * dot02 - dot01 * dot12) * invDenom; + const double v = (dot00 * dot12 - dot01 * dot02) * invDenom; + */ // Check if point is in triangle return Point2d(u, v); } -bool isLineInTriangle(Point3d* P, const Point3d* A, const Point3d* B, const Point3d* C, const Point3d* linePoint, - const Point3d* lineVect) +bool isLineInTriangle(Point3d* P, const Point3d* A, const Point3d* B, const Point3d* C, const Point3d* linePoint, const Point3d* lineVect) { return isPointInTriangle(getLineTriangleIntersectBarycCoords(P, A, B, C, linePoint, lineVect)); } -bool isLineSegmentInTriangle(Point3d& lpi, const Point3d& A, const Point3d& B, const Point3d& C, - const Point3d& linePoint1, const Point3d& linePoint2) +bool isLineSegmentInTriangle(Point3d& lpi, const Point3d& A, const Point3d& B, const Point3d& C, const Point3d& linePoint1, const Point3d& linePoint2) { Point3d v0 = C - A; Point3d v1 = B - A; @@ -403,38 +415,33 @@ bool isPointInTriangle(const Point2d& A, const Point2d& B, const Point2d& C, con bool lineSegmentsIntersect2DTest(const Point2d& A, const Point2d& B, const Point2d& C, const Point2d& D) { - const double r = ((A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y)) / - ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); - const double s = ((A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y)) / - ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); + const double r = ((A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y)) / ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); + const double s = ((A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y)) / ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); return ((r >= 0.0) && (r <= 1.0) && (s >= 0.0) && (s <= 1.0)); } bool lineSegmentsIntersect2DTest(Point2d& S, const Point2d& A, const Point2d& B, const Point2d& C, const Point2d& D) { - const double r = ((A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y)) / - ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); - const double s = ((A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y)) / - ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); + const double r = ((A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y)) / ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); + const double s = ((A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y)) / ((B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x)); S = A + (B - A) * r; return ((r >= 0.0) && (r <= 1.0) && (s >= 0.0) && (s <= 1.0)); } -bool ccw_tri_tri_intersection_2d(const Point2d& p1, const Point2d& q1, const Point2d& r1, const Point2d& p2, - const Point2d& q2, const Point2d& r2) +bool ccw_tri_tri_intersection_2d(const Point2d& p1, const Point2d& q1, const Point2d& r1, const Point2d& p2, const Point2d& q2, const Point2d& r2) { - if(ORIENT_2D(p2, q2, p1) > 0.0f) + if (ORIENT_2D(p2, q2, p1) > 0.0f) { - if(ORIENT_2D(q2, r2, p1) > 0.0f) + if (ORIENT_2D(q2, r2, p1) > 0.0f) { - if(ORIENT_2D(r2, p2, p1) > 0.0f) + if (ORIENT_2D(r2, p2, p1) > 0.0f) return true; else INTERSECTION_TEST_EDGE(p1, q1, r1, p2, q2, r2) } else { - if(ORIENT_2D(r2, p2, p1) > 0.0f) + if (ORIENT_2D(r2, p2, p1) > 0.0f) INTERSECTION_TEST_EDGE(p1, q1, r1, r2, p2, q2) else INTERSECTION_TEST_VERTEX(p1, q1, r1, p2, q2, r2) @@ -442,9 +449,9 @@ bool ccw_tri_tri_intersection_2d(const Point2d& p1, const Point2d& q1, const Poi } else { - if(ORIENT_2D(q2, r2, p1) > 0.0f) + if (ORIENT_2D(q2, r2, p1) > 0.0f) { - if(ORIENT_2D(r2, p2, p1) > 0.0f) + if (ORIENT_2D(r2, p2, p1) > 0.0f) INTERSECTION_TEST_EDGE(p1, q1, r1, q2, r2, p2) else INTERSECTION_TEST_VERTEX(p1, q1, r1, q2, r2, p2) @@ -456,12 +463,12 @@ bool ccw_tri_tri_intersection_2d(const Point2d& p1, const Point2d& q1, const Poi bool TrianglesOverlap(const Point2d* t1, const Point2d* t2) { - if(ORIENT_2D(t1[0], t1[1], t1[2]) < 0.0f) - if(ORIENT_2D(t2[0], t2[1], t2[2]) < 0.0f) + if (ORIENT_2D(t1[0], t1[1], t1[2]) < 0.0f) + if (ORIENT_2D(t2[0], t2[1], t2[2]) < 0.0f) return ccw_tri_tri_intersection_2d(t1[0], t1[2], t1[1], t2[0], t2[2], t2[1]); else return ccw_tri_tri_intersection_2d(t1[0], t1[2], t1[1], t2[0], t2[1], t2[2]); - else if(ORIENT_2D(t2[0], t2[1], t2[2]) < 0.0f) + else if (ORIENT_2D(t2[0], t2[1], t2[2]) < 0.0f) return ccw_tri_tri_intersection_2d(t1[0], t1[1], t1[2], t2[0], t2[2], t2[1]); else return ccw_tri_tri_intersection_2d(t1[0], t1[1], t1[2], t2[0], t2[1], t2[2]); @@ -473,4 +480,4 @@ bool interectsTriangleTriangle(const Point3d* tri1, const Point3d* tri2) return ok; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/geometry.hpp b/src/aliceVision/mvsData/geometry.hpp index c2c3a515d5..8a0d580e2c 100644 --- a/src/aliceVision/mvsData/geometry.hpp +++ b/src/aliceVision/mvsData/geometry.hpp @@ -17,107 +17,105 @@ namespace aliceVision { #define ORIENT_2D(a, b, c) ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)) -#define INTERSECTION_TEST_VERTEX(P1, Q1, R1, P2, Q2, R2) \ - { \ - if(ORIENT_2D(R2, P2, Q1) > 0.0f) \ - if(ORIENT_2D(R2, Q2, Q1) < 0.0f) \ - if(ORIENT_2D(P1, P2, Q1) > 0.0f) \ - { \ - if(ORIENT_2D(P1, Q2, Q1) < 0.0f) \ - return true; \ - else \ - return false; \ - } \ - else \ - { \ - if(ORIENT_2D(P1, P2, R1) > 0.0f) \ - if(ORIENT_2D(Q1, R1, P2) > 0.0f) \ - return true; \ - else \ - return false; \ - else \ - return false; \ - } \ - else if(ORIENT_2D(P1, Q2, Q1) < 0.0f) \ - if(ORIENT_2D(R2, Q2, R1) < 0.0f) \ - if(ORIENT_2D(Q1, R1, Q2) > 0.0f) \ - return true; \ - else \ - return false; \ - else \ - return false; \ - else \ - return false; \ - else if(ORIENT_2D(R2, P2, R1) > 0.0f) \ - if(ORIENT_2D(Q1, R1, R2) > 0.0f) \ - if(ORIENT_2D(P1, P2, R1) > 0.0f) \ - return true; \ - else \ - return false; \ - else if(ORIENT_2D(Q1, R1, Q2) > 0.0f) \ - { \ - if(ORIENT_2D(R2, R1, Q2) > 0.0f) \ - return true; \ - else \ - return false; \ - } \ - else \ - return false; \ - else \ - return false; \ - \ -}; - -#define INTERSECTION_TEST_EDGE(P1, Q1, R1, P2, Q2, R2) \ - { \ - if(ORIENT_2D(R2, P2, Q1) > 0.0f) \ - { \ - if(ORIENT_2D(P1, P2, Q1) > 0.0f) \ - { \ - if(ORIENT_2D(P1, Q1, R2) > 0.0f) \ - return true; \ - else \ - return false; \ - } \ - else \ - { \ - if(ORIENT_2D(Q1, R1, P2) > 0.0f) \ - { \ - if(ORIENT_2D(R1, P1, P2) > 0.0f) \ - return true; \ - else \ - return false; \ - } \ - else \ - return false; \ - } \ - } \ - else \ - { \ - if(ORIENT_2D(R2, P2, R1) > 0.0f) \ - { \ - if(ORIENT_2D(P1, P2, R1) > 0.0f) \ - { \ - if(ORIENT_2D(P1, R1, R2) > 0.0f) \ - return true; \ - else \ - { \ - if(ORIENT_2D(Q1, R1, R2) > 0.0f) \ - return true; \ - else \ - return false; \ - } \ - } \ - else \ - return false; \ - } \ - else \ - return false; \ - } \ +#define INTERSECTION_TEST_VERTEX(P1, Q1, R1, P2, Q2, R2) \ + { \ + if (ORIENT_2D(R2, P2, Q1) > 0.0f) \ + if (ORIENT_2D(R2, Q2, Q1) < 0.0f) \ + if (ORIENT_2D(P1, P2, Q1) > 0.0f) \ + { \ + if (ORIENT_2D(P1, Q2, Q1) < 0.0f) \ + return true; \ + else \ + return false; \ + } \ + else \ + { \ + if (ORIENT_2D(P1, P2, R1) > 0.0f) \ + if (ORIENT_2D(Q1, R1, P2) > 0.0f) \ + return true; \ + else \ + return false; \ + else \ + return false; \ + } \ + else if (ORIENT_2D(P1, Q2, Q1) < 0.0f) \ + if (ORIENT_2D(R2, Q2, R1) < 0.0f) \ + if (ORIENT_2D(Q1, R1, Q2) > 0.0f) \ + return true; \ + else \ + return false; \ + else \ + return false; \ + else \ + return false; \ + else if (ORIENT_2D(R2, P2, R1) > 0.0f) \ + if (ORIENT_2D(Q1, R1, R2) > 0.0f) \ + if (ORIENT_2D(P1, P2, R1) > 0.0f) \ + return true; \ + else \ + return false; \ + else if (ORIENT_2D(Q1, R1, Q2) > 0.0f) \ + { \ + if (ORIENT_2D(R2, R1, Q2) > 0.0f) \ + return true; \ + else \ + return false; \ + } \ + else \ + return false; \ + else \ + return false; \ + }; + +#define INTERSECTION_TEST_EDGE(P1, Q1, R1, P2, Q2, R2) \ + { \ + if (ORIENT_2D(R2, P2, Q1) > 0.0f) \ + { \ + if (ORIENT_2D(P1, P2, Q1) > 0.0f) \ + { \ + if (ORIENT_2D(P1, Q1, R2) > 0.0f) \ + return true; \ + else \ + return false; \ + } \ + else \ + { \ + if (ORIENT_2D(Q1, R1, P2) > 0.0f) \ + { \ + if (ORIENT_2D(R1, P1, P2) > 0.0f) \ + return true; \ + else \ + return false; \ + } \ + else \ + return false; \ + } \ + } \ + else \ + { \ + if (ORIENT_2D(R2, P2, R1) > 0.0f) \ + { \ + if (ORIENT_2D(P1, P2, R1) > 0.0f) \ + { \ + if (ORIENT_2D(P1, R1, R2) > 0.0f) \ + return true; \ + else \ + { \ + if (ORIENT_2D(Q1, R1, R2) > 0.0f) \ + return true; \ + else \ + return false; \ + } \ + } \ + else \ + return false; \ + } \ + else \ + return false; \ + } \ } -bool ccw_tri_tri_intersection_2d(const Point2d& p1, const Point2d& q1, const Point2d& r1, const Point2d& p2, - const Point2d& q2, const Point2d& r2); +bool ccw_tri_tri_intersection_2d(const Point2d& p1, const Point2d& q1, const Point2d& r1, const Point2d& p2, const Point2d& q2, const Point2d& r2); bool TrianglesOverlap(const Point2d* t1, const Point2d* t2); double pointLineDistance3D(const Point3d& point, const Point3d& linePoint, const Point3d& lineVectNormalized); @@ -126,26 +124,38 @@ double pointPlaneDistance(const Point3d& point, const Point3d& planePoint, const double orientedPointPlaneDistance(const Point3d& point, const Point3d& planePoint, const Point3d& planeNormal); void computeRotCS(Point3d* xax, Point3d* yax, const Point3d* n); -bool lineLineIntersect(double* k, double* l, Point3d* llis, Point3d* lli1, Point3d* lli2, const Point3d& p1, - const Point3d& p2, const Point3d& p3, const Point3d& p4); +bool lineLineIntersect(double* k, + double* l, + Point3d* llis, + Point3d* lli1, + Point3d* lli2, + const Point3d& p1, + const Point3d& p2, + const Point3d& p3, + const Point3d& p4); bool lineLineIntersectLeft(Point3d& out, const Point3d& p1, const Point3d& p2, const Point3d& p3, const Point3d& p4); bool lineLineIntersect(Point3d& out, const Point3d& p1, const Point3d& v1, const Point3d& p2, const Point3d& v2); -Point3d linePlaneIntersect(const Point3d& linePoint, const Point3d& lineVect, const Point3d& planePoint, - const Point3d& planeNormal); -void linePlaneIntersect(Point3d* out, const Point3d* linePoint, const Point3d* lineVect, const Point3d* planePoint, - const Point3d* planeNormal); +Point3d linePlaneIntersect(const Point3d& linePoint, const Point3d& lineVect, const Point3d& planePoint, const Point3d& planeNormal); +void linePlaneIntersect(Point3d* out, const Point3d* linePoint, const Point3d* lineVect, const Point3d* planePoint, const Point3d* planeNormal); double angleBetwV1andV2(const Point3d& iV1, const Point3d& iV2); double angleBetwABandAC(const Point3d& A, const Point3d& B, const Point3d& C); void rotPointAroundVect(double* out, const double* X, const double* vect, const double angle); void rotPointAroundVect(Point3d* out, const Point3d* X, const Point3d* vect, const double angle); -Point2d getLineTriangleIntersectBarycCoords(Point3d* P, const Point3d* A, const Point3d* B, const Point3d* C, - const Point3d* linePoint, const Point3d* lineVect); -bool isLineInTriangle(Point3d* P, const Point3d* A, const Point3d* B, const Point3d* C, const Point3d* linePoint, - const Point3d* lineVect); -bool isLineSegmentInTriangle(Point3d& lpi, const Point3d& A, const Point3d& B, const Point3d& C, - const Point3d& linePoint1, const Point3d& linePoint2); +Point2d getLineTriangleIntersectBarycCoords(Point3d* P, + const Point3d* A, + const Point3d* B, + const Point3d* C, + const Point3d* linePoint, + const Point3d* lineVect); +bool isLineInTriangle(Point3d* P, const Point3d* A, const Point3d* B, const Point3d* C, const Point3d* linePoint, const Point3d* lineVect); +bool isLineSegmentInTriangle(Point3d& lpi, + const Point3d& A, + const Point3d& B, + const Point3d& C, + const Point3d& linePoint1, + const Point3d& linePoint2); // P = A + u * (C - A) + v * (B - A) Point2d computeBarycentricCoordinates(const Point2d& A, const Point2d& B, const Point2d& C, const Point2d& P); @@ -156,4 +166,4 @@ bool lineSegmentsIntersect2DTest(Point2d& S, const Point2d& A, const Point2d& B, bool interectsTriangleTriangle(const Point3d* tri1, const Point3d* tri2); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/geometryTriTri.cpp b/src/aliceVision/mvsData/geometryTriTri.cpp index 2144f06533..ef9d65d89a 100644 --- a/src/aliceVision/mvsData/geometryTriTri.cpp +++ b/src/aliceVision/mvsData/geometryTriTri.cpp @@ -41,145 +41,151 @@ namespace aliceVision { #define EPSILON 0.000001 /* some macros */ -#define CROSS(dest, v1, v2) \ - dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; \ - dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; \ +#define CROSS(dest, v1, v2) \ + dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; \ + dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; \ dest[2] = v1[0] * v2[1] - v1[1] * v2[0]; #define DOT(v1, v2) (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]) -#define SUB(dest, v1, v2) \ - dest[0] = v1[0] - v2[0]; \ - dest[1] = v1[1] - v2[1]; \ +#define SUB(dest, v1, v2) \ + dest[0] = v1[0] - v2[0]; \ + dest[1] = v1[1] - v2[1]; \ dest[2] = v1[2] - v2[2]; -#define ADD(dest, v1, v2) \ - dest[0] = v1[0] + v2[0]; \ - dest[1] = v1[1] + v2[1]; \ +#define ADD(dest, v1, v2) \ + dest[0] = v1[0] + v2[0]; \ + dest[1] = v1[1] + v2[1]; \ dest[2] = v1[2] + v2[2]; -#define MULT(dest, v, factor) \ - dest[0] = factor * v[0]; \ - dest[1] = factor * v[1]; \ +#define MULT(dest, v, factor) \ + dest[0] = factor * v[0]; \ + dest[1] = factor * v[1]; \ dest[2] = factor * v[2]; -#define SET(dest, src) \ - dest[0] = src[0]; \ - dest[1] = src[1]; \ +#define SET(dest, src) \ + dest[0] = src[0]; \ + dest[1] = src[1]; \ dest[2] = src[2]; /* sort so that a<=b */ -#define SORT(a, b) \ - if(a > b) \ - { \ - double c; \ - c = a; \ - a = b; \ - b = c; \ +#define SORT(a, b) \ + if (a > b) \ + { \ + double c; \ + c = a; \ + a = b; \ + b = c; \ } -#define ISECT(VV0, VV1, VV2, D0, D1, D2, isect0, isect1) \ - isect0 = VV0 + (VV1 - VV0) * D0 / (D0 - D1); \ +#define ISECT(VV0, VV1, VV2, D0, D1, D2, isect0, isect1) \ + isect0 = VV0 + (VV1 - VV0) * D0 / (D0 - D1); \ isect1 = VV0 + (VV2 - VV0) * D0 / (D0 - D2); -#define COMPUTE_INTERVALS(VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, isect0, isect1) \ - if(D0D1 > 0.0f) \ - { \ - /* here we know that D0D2<=0.0 */ \ - /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ - ISECT(VV2, VV0, VV1, D2, D0, D1, isect0, isect1); \ - } \ - else if(D0D2 > 0.0f) \ - { \ - /* here we know that d0d1<=0.0 */ \ - ISECT(VV1, VV0, VV2, D1, D0, D2, isect0, isect1); \ - } \ - else if(D1 * D2 > 0.0f || D0 != 0.0f) \ - { \ - /* here we know that d0d1<=0.0 or that D0!=0.0 */ \ - ISECT(VV0, VV1, VV2, D0, D1, D2, isect0, isect1); \ - } \ - else if(D1 != 0.0f) \ - { \ - ISECT(VV1, VV0, VV2, D1, D0, D2, isect0, isect1); \ - } \ - else if(D2 != 0.0f) \ - { \ - ISECT(VV2, VV0, VV1, D2, D0, D1, isect0, isect1); \ - } \ - else \ - { \ - /* triangles are coplanar */ \ - return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); \ +#define COMPUTE_INTERVALS(VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, isect0, isect1) \ + if (D0D1 > 0.0f) \ + { \ + /* here we know that D0D2<=0.0 */ \ + /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ + ISECT(VV2, VV0, VV1, D2, D0, D1, isect0, isect1); \ + } \ + else if (D0D2 > 0.0f) \ + { \ + /* here we know that d0d1<=0.0 */ \ + ISECT(VV1, VV0, VV2, D1, D0, D2, isect0, isect1); \ + } \ + else if (D1 * D2 > 0.0f || D0 != 0.0f) \ + { \ + /* here we know that d0d1<=0.0 or that D0!=0.0 */ \ + ISECT(VV0, VV1, VV2, D0, D1, D2, isect0, isect1); \ + } \ + else if (D1 != 0.0f) \ + { \ + ISECT(VV1, VV0, VV2, D1, D0, D2, isect0, isect1); \ + } \ + else if (D2 != 0.0f) \ + { \ + ISECT(VV2, VV0, VV1, D2, D0, D1, isect0, isect1); \ + } \ + else \ + { \ + /* triangles are coplanar */ \ + return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); \ } /* this edge to edge test is based on Franlin Antonio's gem: "Faster Line Segment Intersection", in Graphics Gems III, pp. 199-202 */ -#define EDGE_EDGE_TEST(V0, U0, U1) \ - Bx = U0[i0] - U1[i0]; \ - By = U0[i1] - U1[i1]; \ - Cx = V0[i0] - U0[i0]; \ - Cy = V0[i1] - U0[i1]; \ - f = Ay * Bx - Ax * By; \ - d = By * Cx - Bx * Cy; \ - if((f > 0 && d >= 0 && d <= f) || (f < 0 && d <= 0 && d >= f)) \ - { \ - e = Ax * Cy - Ay * Cx; \ - if(f > 0) \ - { \ - if(e >= 0 && e <= f) \ - return 1; \ - } \ - else \ - { \ - if(e <= 0 && e >= f) \ - return 1; \ - } \ +#define EDGE_EDGE_TEST(V0, U0, U1) \ + Bx = U0[i0] - U1[i0]; \ + By = U0[i1] - U1[i1]; \ + Cx = V0[i0] - U0[i0]; \ + Cy = V0[i1] - U0[i1]; \ + f = Ay * Bx - Ax * By; \ + d = By * Cx - Bx * Cy; \ + if ((f > 0 && d >= 0 && d <= f) || (f < 0 && d <= 0 && d >= f)) \ + { \ + e = Ax * Cy - Ay * Cx; \ + if (f > 0) \ + { \ + if (e >= 0 && e <= f) \ + return 1; \ + } \ + else \ + { \ + if (e <= 0 && e >= f) \ + return 1; \ + } \ } -#define EDGE_AGAINST_TRI_EDGES(V0, V1, U0, U1, U2) \ - \ - { \ - double Ax, Ay, Bx, By, Cx, Cy, e, d, f; \ - Ax = V1[i0] - V0[i0]; \ - Ay = V1[i1] - V0[i1]; \ - /* test edge U0,U1 against V0,V1 */ \ - EDGE_EDGE_TEST(V0, U0, U1); \ - /* test edge U1,U2 against V0,V1 */ \ - EDGE_EDGE_TEST(V0, U1, U2); \ - /* test edge U2,U1 against V0,V1 */ \ - EDGE_EDGE_TEST(V0, U2, U0); \ +#define EDGE_AGAINST_TRI_EDGES(V0, V1, U0, U1, U2) \ + \ + { \ + double Ax, Ay, Bx, By, Cx, Cy, e, d, f; \ + Ax = V1[i0] - V0[i0]; \ + Ay = V1[i1] - V0[i1]; \ + /* test edge U0,U1 against V0,V1 */ \ + EDGE_EDGE_TEST(V0, U0, U1); \ + /* test edge U1,U2 against V0,V1 */ \ + EDGE_EDGE_TEST(V0, U1, U2); \ + /* test edge U2,U1 against V0,V1 */ \ + EDGE_EDGE_TEST(V0, U2, U0); \ } -#define POINT_IN_TRI(V0, U0, U1, U2) \ - \ - { \ - double a, b, c, d0, d1, d2; \ - /* is T1 completly inside T2? */ \ - /* check if V0 is inside tri(U0,U1,U2) */ \ - a = U1[i1] - U0[i1]; \ - b = -(U1[i0] - U0[i0]); \ - c = -a * U0[i0] - b * U0[i1]; \ - d0 = a * V0[i0] + b * V0[i1] + c; \ - \ - a = U2[i1] - U1[i1]; \ - b = -(U2[i0] - U1[i0]); \ - c = -a * U1[i0] - b * U1[i1]; \ - d1 = a * V0[i0] + b * V0[i1] + c; \ - \ - a = U0[i1] - U2[i1]; \ - b = -(U0[i0] - U2[i0]); \ - c = -a * U2[i0] - b * U2[i1]; \ - d2 = a * V0[i0] + b * V0[i1] + c; \ - if(d0 * d1 > 0.0) \ - { \ - if(d0 * d2 > 0.0) \ - return 1; \ - } \ +#define POINT_IN_TRI(V0, U0, U1, U2) \ + \ + { \ + double a, b, c, d0, d1, d2; \ + /* is T1 completly inside T2? */ \ + /* check if V0 is inside tri(U0,U1,U2) */ \ + a = U1[i1] - U0[i1]; \ + b = -(U1[i0] - U0[i0]); \ + c = -a * U0[i0] - b * U0[i1]; \ + d0 = a * V0[i0] + b * V0[i1] + c; \ + \ + a = U2[i1] - U1[i1]; \ + b = -(U2[i0] - U1[i0]); \ + c = -a * U1[i0] - b * U1[i1]; \ + d1 = a * V0[i0] + b * V0[i1] + c; \ + \ + a = U0[i1] - U2[i1]; \ + b = -(U0[i0] - U2[i0]); \ + c = -a * U2[i0] - b * U2[i1]; \ + d2 = a * V0[i0] + b * V0[i1] + c; \ + if (d0 * d1 > 0.0) \ + { \ + if (d0 * d2 > 0.0) \ + return 1; \ + } \ } -int coplanar_tri_tri(const double N[3], const double V0[3], const double V1[3], const double V2[3], const double U0[3], const double U1[3], const double U2[3]) +int coplanar_tri_tri(const double N[3], + const double V0[3], + const double V1[3], + const double V2[3], + const double U0[3], + const double U1[3], + const double U2[3]) { double A[3]; short i0, i1; @@ -188,9 +194,9 @@ int coplanar_tri_tri(const double N[3], const double V0[3], const double V1[3], A[0] = fabs(N[0]); A[1] = fabs(N[1]); A[2] = fabs(N[2]); - if(A[0] > A[1]) + if (A[0] > A[1]) { - if(A[0] > A[2]) + if (A[0] > A[2]) { i0 = 1; /* A[0] is greatest */ i1 = 2; @@ -203,7 +209,7 @@ int coplanar_tri_tri(const double N[3], const double V0[3], const double V1[3], } else /* A[0]<=A[1] */ { - if(A[2] > A[1]) + if (A[2] > A[1]) { i0 = 0; /* A[2] is greatest */ i1 = 1; @@ -254,18 +260,18 @@ int tri_tri_intersect(const double V0[3], const double V1[3], const double V2[3] /* coplanarity robustness check */ #if USE_EPSILON_TEST == true - if(fabs(du0) < EPSILON) + if (fabs(du0) < EPSILON) du0 = 0.0; - if(fabs(du1) < EPSILON) + if (fabs(du1) < EPSILON) du1 = 0.0; - if(fabs(du2) < EPSILON) + if (fabs(du2) < EPSILON) du2 = 0.0; #endif du0du1 = du0 * du1; du0du2 = du0 * du2; - if(du0du1 > 0.0f && du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */ - return 0; /* no intersection occurs */ + if (du0du1 > 0.0f && du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */ + return 0; /* no intersection occurs */ /* compute plane of triangle (U0,U1,U2) */ SUB(E1, U1, U0); @@ -280,19 +286,19 @@ int tri_tri_intersect(const double V0[3], const double V1[3], const double V2[3] dv2 = DOT(N2, V2) + d2; #if USE_EPSILON_TEST == true - if(fabs(dv0) < EPSILON) + if (fabs(dv0) < EPSILON) dv0 = 0.0; - if(fabs(dv1) < EPSILON) + if (fabs(dv1) < EPSILON) dv1 = 0.0; - if(fabs(dv2) < EPSILON) + if (fabs(dv2) < EPSILON) dv2 = 0.0; #endif dv0dv1 = dv0 * dv1; dv0dv2 = dv0 * dv2; - if(dv0dv1 > 0.0f && dv0dv2 > 0.0f) /* same sign on all of them + not equal 0 ? */ - return 0; /* no intersection occurs */ + if (dv0dv1 > 0.0f && dv0dv2 > 0.0f) /* same sign on all of them + not equal 0 ? */ + return 0; /* no intersection occurs */ /* compute direction of intersection line */ CROSS(D, N1, N2); @@ -302,9 +308,9 @@ int tri_tri_intersect(const double V0[3], const double V1[3], const double V2[3] index = 0; b = fabs(D[1]); c = fabs(D[2]); - if(b > max) + if (b > max) max = b, index = 1; - if(c > max) + if (c > max) max = c, index = 2; /* this is the simplified projection onto L*/ @@ -325,80 +331,91 @@ int tri_tri_intersect(const double V0[3], const double V1[3], const double V2[3] SORT(isect1[0], isect1[1]); SORT(isect2[0], isect2[1]); - if(isect1[1] < isect2[0] || isect2[1] < isect1[0]) + if (isect1[1] < isect2[0] || isect2[1] < isect1[0]) return 0; return 1; } -#define NEWCOMPUTE_INTERVALS(VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, A, B, C, X0, X1) \ - \ - { \ - if(D0D1 > 0.0f) \ - { \ - /* here we know that D0D2<=0.0 */ \ - /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ - A = VV2; \ - B = (VV0 - VV2) * D2; \ - C = (VV1 - VV2) * D2; \ - X0 = D2 - D0; \ - X1 = D2 - D1; \ - } \ - else if(D0D2 > 0.0f) \ - { \ - /* here we know that d0d1<=0.0 */ \ - A = VV1; \ - B = (VV0 - VV1) * D1; \ - C = (VV2 - VV1) * D1; \ - X0 = D1 - D0; \ - X1 = D1 - D2; \ - } \ - else if(D1 * D2 > 0.0f || D0 != 0.0f) \ - { \ - /* here we know that d0d1<=0.0 or that D0!=0.0 */ \ - A = VV0; \ - B = (VV1 - VV0) * D0; \ - C = (VV2 - VV0) * D0; \ - X0 = D0 - D1; \ - X1 = D0 - D2; \ - } \ - else if(D1 != 0.0f) \ - { \ - A = VV1; \ - B = (VV0 - VV1) * D1; \ - C = (VV2 - VV1) * D1; \ - X0 = D1 - D0; \ - X1 = D1 - D2; \ - } \ - else if(D2 != 0.0f) \ - { \ - A = VV2; \ - B = (VV0 - VV2) * D2; \ - C = (VV1 - VV2) * D2; \ - X0 = D2 - D0; \ - X1 = D2 - D1; \ - } \ - else \ - { \ - /* triangles are coplanar */ \ - return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); \ - } \ +#define NEWCOMPUTE_INTERVALS(VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, A, B, C, X0, X1) \ + \ + { \ + if (D0D1 > 0.0f) \ + { \ + /* here we know that D0D2<=0.0 */ \ + /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ + A = VV2; \ + B = (VV0 - VV2) * D2; \ + C = (VV1 - VV2) * D2; \ + X0 = D2 - D0; \ + X1 = D2 - D1; \ + } \ + else if (D0D2 > 0.0f) \ + { \ + /* here we know that d0d1<=0.0 */ \ + A = VV1; \ + B = (VV0 - VV1) * D1; \ + C = (VV2 - VV1) * D1; \ + X0 = D1 - D0; \ + X1 = D1 - D2; \ + } \ + else if (D1 * D2 > 0.0f || D0 != 0.0f) \ + { \ + /* here we know that d0d1<=0.0 or that D0!=0.0 */ \ + A = VV0; \ + B = (VV1 - VV0) * D0; \ + C = (VV2 - VV0) * D0; \ + X0 = D0 - D1; \ + X1 = D0 - D2; \ + } \ + else if (D1 != 0.0f) \ + { \ + A = VV1; \ + B = (VV0 - VV1) * D1; \ + C = (VV2 - VV1) * D1; \ + X0 = D1 - D0; \ + X1 = D1 - D2; \ + } \ + else if (D2 != 0.0f) \ + { \ + A = VV2; \ + B = (VV0 - VV2) * D2; \ + C = (VV1 - VV2) * D2; \ + X0 = D2 - D0; \ + X1 = D2 - D1; \ + } \ + else \ + { \ + /* triangles are coplanar */ \ + return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); \ + } \ } /* sort so that a<=b */ -#define SORT2(a, b, smallest) \ - if(a > b) \ - { \ - double c; \ - c = a; \ - a = b; \ - b = c; \ - smallest = 1; \ - } \ - else \ +#define SORT2(a, b, smallest) \ + if (a > b) \ + { \ + double c; \ + c = a; \ + a = b; \ + b = c; \ + smallest = 1; \ + } \ + else \ smallest = 0; -inline void isect2(const double VTX0[3], const double VTX1[3], const double VTX2[3], double VV0, double VV1, double VV2, double D0, double D1, - double D2, double* isect0, double* isect1, double isectpoint0[3], double isectpoint1[3]) +inline void isect2(const double VTX0[3], + const double VTX1[3], + const double VTX2[3], + double VV0, + double VV1, + double VV2, + double D0, + double D1, + double D2, + double* isect0, + double* isect1, + double isectpoint0[3], + double isectpoint1[3]) { double tmp = D0 / (D0 - D1); double diff[3]; @@ -414,44 +431,56 @@ inline void isect2(const double VTX0[3], const double VTX1[3], const double VTX2 } #if 0 -#define ISECT2(VTX0, VTX1, VTX2, VV0, VV1, VV2, D0, D1, D2, isect0, isect1, isectpoint0, isectpoint1) \ - tmp = D0 / (D0 - D1); \ - isect0 = VV0 + (VV1 - VV0) * tmp; \ - SUB(diff, VTX1, VTX0); \ - MULT(diff, diff, tmp); \ - ADD(isectpoint0, diff, VTX0); \ - tmp = D0 / (D0 - D2); + #define ISECT2(VTX0, VTX1, VTX2, VV0, VV1, VV2, D0, D1, D2, isect0, isect1, isectpoint0, isectpoint1) \ + tmp = D0 / (D0 - D1); \ + isect0 = VV0 + (VV1 - VV0) * tmp; \ + SUB(diff, VTX1, VTX0); \ + MULT(diff, diff, tmp); \ + ADD(isectpoint0, diff, VTX0); \ + tmp = D0 / (D0 - D2); /* isect1=VV0+(VV2-VV0)*tmp; \ */ /* SUB(diff,VTX2,VTX0); \ */ /* MULT(diff,diff,tmp); \ */ /* ADD(isectpoint1,VTX0,diff); */ #endif -inline int compute_intervals_isectline(const double VERT0[3], const double VERT1[3], const double VERT2[3], double VV0, double VV1, double VV2, - double D0, double D1, double D2, double D0D1, double D0D2, double* isect0, - double* isect1, double isectpoint0[3], double isectpoint1[3]) +inline int compute_intervals_isectline(const double VERT0[3], + const double VERT1[3], + const double VERT2[3], + double VV0, + double VV1, + double VV2, + double D0, + double D1, + double D2, + double D0D1, + double D0D2, + double* isect0, + double* isect1, + double isectpoint0[3], + double isectpoint1[3]) { - if(D0D1 > 0.0f) + if (D0D1 > 0.0f) { /* here we know that D0D2<=0.0 */ /* that is D0, D1 are on the same side, D2 on the other or on the plane */ isect2(VERT2, VERT0, VERT1, VV2, VV0, VV1, D2, D0, D1, isect0, isect1, isectpoint0, isectpoint1); } - else if(D0D2 > 0.0f) + else if (D0D2 > 0.0f) { /* here we know that d0d1<=0.0 */ isect2(VERT1, VERT0, VERT2, VV1, VV0, VV2, D1, D0, D2, isect0, isect1, isectpoint0, isectpoint1); } - else if(D1 * D2 > 0.0f || D0 != 0.0f) + else if (D1 * D2 > 0.0f || D0 != 0.0f) { /* here we know that d0d1<=0.0 or that D0!=0.0 */ isect2(VERT0, VERT1, VERT2, VV0, VV1, VV2, D0, D1, D2, isect0, isect1, isectpoint0, isectpoint1); } - else if(D1 != 0.0f) + else if (D1 != 0.0f) { isect2(VERT1, VERT0, VERT2, VV1, VV0, VV2, D1, D0, D2, isect0, isect1, isectpoint0, isectpoint1); } - else if(D2 != 0.0f) + else if (D2 != 0.0f) { isect2(VERT2, VERT0, VERT1, VV2, VV0, VV1, D2, D0, D1, isect0, isect1, isectpoint0, isectpoint1); } @@ -463,13 +492,12 @@ inline int compute_intervals_isectline(const double VERT0[3], const double VERT1 return 0; } -#define COMPUTE_INTERVALS_ISECTLINE(VERT0, VERT1, VERT2, VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, isect0, isect1, \ - isectpoint0, isectpoint1) \ - if(D0D1 > 0.0f) \ - { \ - /* here we know that D0D2<=0.0 */ \ - /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ - isect2(VERT2, VERT0, VERT1, VV2, VV0, VV1, D2, D0, D1, &isect0, &isect1, isectpoint0, isectpoint1); \ +#define COMPUTE_INTERVALS_ISECTLINE(VERT0, VERT1, VERT2, VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, isect0, isect1, isectpoint0, isectpoint1) \ + if (D0D1 > 0.0f) \ + { \ + /* here we know that D0D2<=0.0 */ \ + /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ + isect2(VERT2, VERT0, VERT1, VV2, VV0, VV1, D2, D0, D1, &isect0, &isect1, isectpoint0, isectpoint1); \ } #if 0 else if(D0D2>0.0f) \ @@ -498,8 +526,15 @@ else if(D0D2>0.0f) \ } #endif -int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], const double V2[3], const double U0[3], const double U1[3], const double U2[3], - int* coplanar, double isectpt1[3], double isectpt2[3]) +int tri_tri_intersect_with_isectline(const double V0[3], + const double V1[3], + const double V2[3], + const double U0[3], + const double U1[3], + const double U2[3], + int* coplanar, + double isectpt1[3], + double isectpt2[3]) { double E1[3], E2[3]; double N1[3], N2[3], d1, d2; @@ -529,18 +564,18 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con /* coplanarity robustness check */ #if USE_EPSILON_TEST == true - if(fabs(du0) < EPSILON) + if (fabs(du0) < EPSILON) du0 = 0.0; - if(fabs(du1) < EPSILON) + if (fabs(du1) < EPSILON) du1 = 0.0; - if(fabs(du2) < EPSILON) + if (fabs(du2) < EPSILON) du2 = 0.0; #endif du0du1 = du0 * du1; du0du2 = du0 * du2; - if(du0du1 > 0.0f && du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */ - return 0; /* no intersection occurs */ + if (du0du1 > 0.0f && du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */ + return 0; /* no intersection occurs */ /* compute plane of triangle (U0,U1,U2) */ SUB(E1, U1, U0); @@ -555,19 +590,19 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con dv2 = DOT(N2, V2) + d2; #if USE_EPSILON_TEST == true - if(fabs(dv0) < EPSILON) + if (fabs(dv0) < EPSILON) dv0 = 0.0; - if(fabs(dv1) < EPSILON) + if (fabs(dv1) < EPSILON) dv1 = 0.0; - if(fabs(dv2) < EPSILON) + if (fabs(dv2) < EPSILON) dv2 = 0.0; #endif dv0dv1 = dv0 * dv1; dv0dv2 = dv0 * dv2; - if(dv0dv1 > 0.0f && dv0dv2 > 0.0f) /* same sign on all of them + not equal 0 ? */ - return 0; /* no intersection occurs */ + if (dv0dv1 > 0.0f && dv0dv2 > 0.0f) /* same sign on all of them + not equal 0 ? */ + return 0; /* no intersection occurs */ /* compute direction of intersection line */ CROSS(D, N1, N2); @@ -577,9 +612,9 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con index = 0; b = fabs(D[1]); c = fabs(D[2]); - if(b > max) + if (b > max) max = b, index = 1; - if(c > max) + if (c > max) max = c, index = 2; /* this is the simplified projection onto L*/ @@ -592,26 +627,25 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con up2 = U2[index]; /* compute interval for triangle 1 */ - *coplanar = compute_intervals_isectline(V0, V1, V2, vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, &isect1[0], - &isect1[1], isectpointA1, isectpointA2); - if(*coplanar != 0) + *coplanar = + compute_intervals_isectline(V0, V1, V2, vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, &isect1[0], &isect1[1], isectpointA1, isectpointA2); + if (*coplanar != 0) return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); /* compute interval for triangle 2 */ - compute_intervals_isectline(U0, U1, U2, up0, up1, up2, du0, du1, du2, du0du1, du0du2, &isect2[0], &isect2[1], - isectpointB1, isectpointB2); + compute_intervals_isectline(U0, U1, U2, up0, up1, up2, du0, du1, du2, du0du1, du0du2, &isect2[0], &isect2[1], isectpointB1, isectpointB2); SORT2(isect1[0], isect1[1], smallest1); SORT2(isect2[0], isect2[1], smallest2); - if(isect1[1] < isect2[0] || isect2[1] < isect1[0]) + if (isect1[1] < isect2[0] || isect2[1] < isect1[0]) return 0; /* at this point, we know that the triangles intersect */ - if(isect2[0] < isect1[0]) + if (isect2[0] < isect1[0]) { - if(smallest1 == 0) + if (smallest1 == 0) { SET(isectpt1, isectpointA1); } @@ -620,9 +654,9 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con SET(isectpt1, isectpointA2); } - if(isect2[1] < isect1[1]) + if (isect2[1] < isect1[1]) { - if(smallest2 == 0) + if (smallest2 == 0) { SET(isectpt2, isectpointB2); } @@ -633,7 +667,7 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con } else { - if(smallest1 == 0) + if (smallest1 == 0) { SET(isectpt2, isectpointA2); } @@ -645,7 +679,7 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con } else { - if(smallest2 == 0) + if (smallest2 == 0) { SET(isectpt1, isectpointB1); } @@ -654,9 +688,9 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con SET(isectpt1, isectpointB2); } - if(isect2[1] > isect1[1]) + if (isect2[1] > isect1[1]) { - if(smallest1 == 0) + if (smallest1 == 0) { SET(isectpt2, isectpointA2); } @@ -667,7 +701,7 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con } else { - if(smallest2 == 0) + if (smallest2 == 0) { SET(isectpt2, isectpointB2); } @@ -680,4 +714,4 @@ int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], con return 1; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/geometryTriTri.hpp b/src/aliceVision/mvsData/geometryTriTri.hpp index 672a4ce8d8..dadbbadd64 100644 --- a/src/aliceVision/mvsData/geometryTriTri.hpp +++ b/src/aliceVision/mvsData/geometryTriTri.hpp @@ -10,7 +10,14 @@ namespace aliceVision { int tri_tri_intersect(const double V0[3], const double V1[3], const double V2[3], const double U0[3], const double U1[3], const double U2[3]); -int tri_tri_intersect_with_isectline(const double V0[3], const double V1[3], const double V2[3], const double U0[3], const double U1[3], const double U2[3], - int* coplanar, double isectpt1[3], double isectpt2[3]); +int tri_tri_intersect_with_isectline(const double V0[3], + const double V1[3], + const double V2[3], + const double U0[3], + const double U1[3], + const double U2[3], + int* coplanar, + double isectpt1[3], + double isectpt2[3]); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/structures.cpp b/src/aliceVision/mvsData/structures.cpp index b5c2bf20d4..fae391ccca 100644 --- a/src/aliceVision/mvsData/structures.cpp +++ b/src/aliceVision/mvsData/structures.cpp @@ -14,7 +14,7 @@ int qSortCompareFloatAsc(const void* ia, const void* ib) { float a = *(float*)ia; float b = *(float*)ib; - if(a > b) + if (a > b) { return 1; } @@ -26,7 +26,7 @@ int qSortCompareIntAsc(const void* ia, const void* ib) { int a = *(int*)ia; int b = *(int*)ib; - if(a > b) + if (a > b) { return 1; } @@ -38,7 +38,7 @@ int qsortCompareSortedIdDesc(const void* ia, const void* ib) { SortedId a = *(SortedId*)ia; SortedId b = *(SortedId*)ib; - if(a.value > b.value) + if (a.value > b.value) { return -1; } @@ -50,7 +50,7 @@ int qsortCompareSortedIdAsc(const void* ia, const void* ib) { SortedId a = *(SortedId*)ia; SortedId b = *(SortedId*)ib; - if(a.value < b.value) + if (a.value < b.value) { return -1; } @@ -62,7 +62,7 @@ int qSortCompareVoxelByXAsc(const void* ia, const void* ib) { Voxel a = *(Voxel*)ia; Voxel b = *(Voxel*)ib; - if(a.x < b.x) + if (a.x < b.x) { return -1; } @@ -74,7 +74,7 @@ int qSortCompareVoxelByYAsc(const void* ia, const void* ib) { Voxel a = *(Voxel*)ia; Voxel b = *(Voxel*)ib; - if(a.y < b.y) + if (a.y < b.y) { return -1; } @@ -86,7 +86,7 @@ int qSortCompareVoxelByZAsc(const void* ia, const void* ib) { Voxel a = *(Voxel*)ia; Voxel b = *(Voxel*)ib; - if(a.z < b.z) + if (a.z < b.z) { return -1; } @@ -98,7 +98,7 @@ int qSortComparePixelByXDesc(const void* ia, const void* ib) { Pixel a = *(Pixel*)ia; Pixel b = *(Pixel*)ib; - if(a.x > b.x) + if (a.x > b.x) { return -1; } @@ -110,7 +110,7 @@ int qSortComparePixelByXAsc(const void* ia, const void* ib) { Pixel a = *(Pixel*)ia; Pixel b = *(Pixel*)ib; - if(a.x < b.x) + if (a.x < b.x) { return -1; } @@ -123,21 +123,21 @@ int indexOfSortedVoxelArrByX(int val, StaticVector& values, int startId, int lef = startId; int rig = stopId; int mid = lef + (rig - lef) / 2; - while((rig - lef) > 1) + while ((rig - lef) > 1) { - if((val >= values[lef].x) && (val < values[mid].x)) + if ((val >= values[lef].x) && (val < values[mid].x)) { - //lef = lef; + // lef = lef; rig = mid; mid = lef + (rig - lef) / 2; } - if((val >= values[mid].x) && (val <= values[rig].x)) + if ((val >= values[mid].x) && (val <= values[rig].x)) { lef = mid; - //rig = rig; + // rig = rig; mid = lef + (rig - lef) / 2; } - if((val < values[lef].x) || (val > values[rig].x)) + if ((val < values[lef].x) || (val > values[rig].x)) { lef = 0; rig = 0; @@ -146,11 +146,11 @@ int indexOfSortedVoxelArrByX(int val, StaticVector& values, int startId, } int id = -1; - if(val == values[lef].x) + if (val == values[lef].x) { id = lef; } - if(val == values[rig].x) + if (val == values[rig].x) { id = rig; } @@ -158,4 +158,4 @@ int indexOfSortedVoxelArrByX(int val, StaticVector& values, int startId, return id; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsData/structures.hpp b/src/aliceVision/mvsData/structures.hpp index d2cf5fadad..e46e128e7a 100644 --- a/src/aliceVision/mvsData/structures.hpp +++ b/src/aliceVision/mvsData/structures.hpp @@ -28,11 +28,11 @@ struct ImageParams std::string path; ImageParams(IndexT _viewId, int _width, int _height, const std::string& _path) - : viewId(_viewId) - , width(_width) - , height(_height) - , size(_width * _height) - , path(_path) + : viewId(_viewId), + width(_width), + height(_height), + size(_width * _height), + path(_path) {} ImageParams& operator=(const ImageParams& param) @@ -50,7 +50,7 @@ struct SortedId int id; float value; - SortedId(){} + SortedId() {} SortedId(int _id, float _value) { @@ -65,15 +65,9 @@ struct SortedId return *this; } - inline bool operator>(const SortedId& param) const - { - return (value > param.value); - } + inline bool operator>(const SortedId& param) const { return (value > param.value); } - inline bool operator<(const SortedId& param) const - { - return (value < param.value); - } + inline bool operator<(const SortedId& param) const { return (value < param.value); } }; int qSortCompareFloatAsc(const void* ia, const void* ib); @@ -91,7 +85,7 @@ struct IdValue int id; float value; - IdValue(){} + IdValue() {} IdValue(int _id, float _value) { @@ -106,15 +100,9 @@ struct IdValue return *this; } - inline bool operator>(const IdValue& param) const - { - return (value > param.value); - } + inline bool operator>(const IdValue& param) const { return (value > param.value); } - inline bool operator<(const IdValue& param) const - { - return (value < param.value); - } + inline bool operator<(const IdValue& param) const { return (value < param.value); } }; struct mv2DTriangle @@ -142,4 +130,4 @@ struct CameraMatrices int indexOfSortedVoxelArrByX(int val, StaticVector& values, int startId, int stopId); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/ImagesCache.cpp b/src/aliceVision/mvsUtils/ImagesCache.cpp index f4651505fb..c7e42f4f10 100644 --- a/src/aliceVision/mvsUtils/ImagesCache.cpp +++ b/src/aliceVision/mvsUtils/ImagesCache.cpp @@ -15,56 +15,57 @@ namespace mvsUtils { std::string ECorrectEV_enumToString(const ECorrectEV correctEV) { - switch(correctEV) + switch (correctEV) { - case ECorrectEV::NO_CORRECTION: return "no exposure correction"; - case ECorrectEV::APPLY_CORRECTION: return "exposure correction"; - default: ; + case ECorrectEV::NO_CORRECTION: + return "no exposure correction"; + case ECorrectEV::APPLY_CORRECTION: + return "exposure correction"; + default:; } throw std::out_of_range("No string defined for ECorrectEV: " + std::to_string(int(correctEV))); } - template -ImagesCache::ImagesCache(const MultiViewParams& mp, image::EImageColorSpace colorspace, - ECorrectEV correctEV) - : _mp(mp) - , _colorspace(colorspace) - , _correctEV(correctEV) +ImagesCache::ImagesCache(const MultiViewParams& mp, image::EImageColorSpace colorspace, ECorrectEV correctEV) + : _mp(mp), + _colorspace(colorspace), + _correctEV(correctEV) { std::vector imagesNames; - for(int rc = 0; rc < _mp.getNbCameras(); rc++) + for (int rc = 0; rc < _mp.getNbCameras(); rc++) { imagesNames.push_back(_mp.getImagePath(rc)); } - initIC( imagesNames ); + initIC(imagesNames); } template -ImagesCache::ImagesCache(const MultiViewParams& mp, image::EImageColorSpace colorspace, - std::vector& imagesNames - , ECorrectEV correctEV) - : _mp(mp) - , _colorspace(colorspace) - , _correctEV(correctEV) +ImagesCache::ImagesCache(const MultiViewParams& mp, + image::EImageColorSpace colorspace, + std::vector& imagesNames, + ECorrectEV correctEV) + : _mp(mp), + _colorspace(colorspace), + _correctEV(correctEV) { - initIC( imagesNames ); + initIC(imagesNames); } template -void ImagesCache::initIC( std::vector& imagesNames ) +void ImagesCache::initIC(std::vector& imagesNames) { float oneimagemb = (sizeof(Color) * _mp.getMaxImageWidth() * _mp.getMaxImageHeight()) / 1024.f / 1024.f; float maxmbCPU = (float)_mp.userParams.get("images_cache.maxmbCPU", 5000); - int npreload = std::max((int)(maxmbCPU / oneimagemb), 5); // image cache has a minimum size of 5 + int npreload = std::max((int)(maxmbCPU / oneimagemb), 5); // image cache has a minimum size of 5 npreload = std::min(_mp.ncams, npreload); - for(int rc = 0; rc < _mp.ncams; rc++) + for (int rc = 0; rc < _mp.ncams; rc++) { _imagesNames.push_back(imagesNames[rc]); } - _camIdMapId.resize( _mp.ncams, -1 ); + _camIdMapId.resize(_mp.ncams, -1); setCacheSize(npreload); { @@ -74,8 +75,6 @@ void ImagesCache::initIC( std::vector& imagesNames ) std::vector imagesMutexesTmp(_mp.ncams); _imagesMutexes.swap(imagesMutexesTmp); } - - } template @@ -83,8 +82,8 @@ void ImagesCache::setCacheSize(int nbPreload) { _N_PRELOADED_IMAGES = nbPreload; _imgs.resize(_N_PRELOADED_IMAGES); - _mapIdCamId.resize( _N_PRELOADED_IMAGES, -1 ); - _mapIdClock.resize( _N_PRELOADED_IMAGES, clock() ); + _mapIdCamId.resize(_N_PRELOADED_IMAGES, -1); + _mapIdClock.resize(_N_PRELOADED_IMAGES, clock()); } template @@ -92,14 +91,14 @@ void ImagesCache::refreshData(int camId) { // printf("camId %i\n",camId); // test if the image is in the memory - if(_camIdMapId[camId] == -1) + if (_camIdMapId[camId] == -1) { // remove the oldest one int mapId = _mapIdClock.minValId(); int oldCamId = _mapIdCamId[mapId]; - if(oldCamId>=0) + if (oldCamId >= 0) _camIdMapId[oldCamId] = -1; - // TODO: oldCamId should be protected if already used + // TODO: oldCamId should be protected if already used // replace with new new _camIdMapId[camId] = mapId; @@ -122,15 +121,15 @@ void ImagesCache::refreshData(int camId) } else { - ALICEVISION_LOG_DEBUG("Reuse " << _imagesNames.at(camId) << " from image cache. "); + ALICEVISION_LOG_DEBUG("Reuse " << _imagesNames.at(camId) << " from image cache. "); } } template void ImagesCache::refreshImage_sync(int camId) { - std::lock_guard lock(_imagesMutexes[camId]); - refreshData(camId); + std::lock_guard lock(_imagesMutexes[camId]); + refreshData(camId); } template @@ -142,7 +141,7 @@ void ImagesCache::refreshImage_async(int camId) template void ImagesCache::refreshImages_sync(const std::vector& camIds) { - for(int camId: camIds) + for (int camId : camIds) refreshImage_sync(camId); } @@ -155,5 +154,5 @@ void ImagesCache::refreshImages_async(const std::vector& camIds) template class ImagesCache>; template class ImagesCache>; -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/ImagesCache.hpp b/src/aliceVision/mvsUtils/ImagesCache.hpp index f65a567694..198eab5f3e 100644 --- a/src/aliceVision/mvsUtils/ImagesCache.hpp +++ b/src/aliceVision/mvsUtils/ImagesCache.hpp @@ -28,15 +28,14 @@ enum class ECorrectEV std::string ECorrectEV_enumToString(const ECorrectEV correctEV); - template class ImagesCache { -public: + public: using Color = typename Image::Tpixel; using ImgSharedPtr = std::shared_ptr; -private: + private: ImagesCache(const ImagesCache&) = delete; const MultiViewParams& _mp; @@ -56,20 +55,20 @@ class ImagesCache image::EImageColorSpace _colorspace{image::EImageColorSpace::AUTO}; ECorrectEV _correctEV{ECorrectEV::NO_CORRECTION}; -public: - ImagesCache(const MultiViewParams& mp, image::EImageColorSpace colorspace, - ECorrectEV correctEV = ECorrectEV::NO_CORRECTION); + public: + ImagesCache(const MultiViewParams& mp, image::EImageColorSpace colorspace, ECorrectEV correctEV = ECorrectEV::NO_CORRECTION); - ImagesCache(const MultiViewParams& mp, image::EImageColorSpace colorspace, + ImagesCache(const MultiViewParams& mp, + image::EImageColorSpace colorspace, std::vector& imagesNames, ECorrectEV correctEV = ECorrectEV::NO_CORRECTION); - void initIC( std::vector& imagesNames ); + void initIC(std::vector& imagesNames); void setCacheSize(int nbPreload); void setCorrectEV(const ECorrectEV correctEV) { _correctEV = correctEV; } ~ImagesCache() = default; - inline ImgSharedPtr getImg_sync( int camId ) + inline ImgSharedPtr getImg_sync(int camId) { refreshImage_sync(camId); const int imageId = _camIdMapId[camId]; @@ -86,5 +85,5 @@ class ImagesCache void refreshImages_async(const std::vector& camIds); }; -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/MultiViewParams.cpp b/src/aliceVision/mvsUtils/MultiViewParams.cpp index 32bcc9ef06..3cb73719a7 100644 --- a/src/aliceVision/mvsUtils/MultiViewParams.cpp +++ b/src/aliceVision/mvsUtils/MultiViewParams.cpp @@ -40,11 +40,11 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, const std::string& depthMapsFilterFolder, bool readFromDepthMaps, int downscale) - : _sfmData(sfmData) - , _imagesFolder(imagesFolder + "/") - , _depthMapsFolder(depthMapsFolder + "/") - , _depthMapsFilterFolder(depthMapsFilterFolder + "/") - , _processDownscale(downscale) + : _sfmData(sfmData), + _imagesFolder(imagesFolder + "/"), + _depthMapsFolder(depthMapsFolder + "/"), + _depthMapsFilterFolder(depthMapsFilterFolder + "/"), + _processDownscale(downscale) { verbose = userParams.get("global.verbose", true); simThr = userParams.get("global.simThr", 0.0); @@ -52,82 +52,78 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, // load image uid, path and dimensions { - std::set> dimensions; // for print only + std::set> dimensions; // for print only int i = 0; - for(const auto& viewPair : sfmData.getViews()) + for (const auto& viewPair : sfmData.getViews()) { - const sfmData::View& view = *(viewPair.second.get()); + const sfmData::View& view = *(viewPair.second.get()); - if(!sfmData.isPoseAndIntrinsicDefined(&view)) - continue; + if (!sfmData.isPoseAndIntrinsicDefined(&view)) + continue; - std::string path = view.getImage().getImagePath(); - - if(readFromDepthMaps) - { - if(depthMapsFolder.empty()) - { - // use output of depth map filtering - path = getFileNameFromViewId(*this, view.getViewId(), mvsUtils::EFileType::depthMapFiltered); - } - else - { - // use output of depth map estimation - path = getFileNameFromViewId(*this, view.getViewId(), mvsUtils::EFileType::depthMap); - } - } - else if(_imagesFolder != "/" && !_imagesFolder.empty() && fs::is_directory(_imagesFolder) && !fs::is_empty(_imagesFolder)) - { - // find folder file extension - std::vector paths = utils::getFilesPathsFromFolder(_imagesFolder, - [&view](const fs::path& path) - { - return (path.stem() == std::to_string(view.getViewId()) && - (image::isSupportedUndistortFormat(path.extension().string()))); - } - ); + std::string path = view.getImage().getImagePath(); - // if path was not found - if(paths.empty()) + if (readFromDepthMaps) { - throw std::runtime_error("Cannot find image file coresponding to the view '" + - std::to_string(view.getViewId()) + "' in folder '" + _imagesFolder + "'."); + if (depthMapsFolder.empty()) + { + // use output of depth map filtering + path = getFileNameFromViewId(*this, view.getViewId(), mvsUtils::EFileType::depthMapFiltered); + } + else + { + // use output of depth map estimation + path = getFileNameFromViewId(*this, view.getViewId(), mvsUtils::EFileType::depthMap); + } } - else if(paths.size() > 1) + else if (_imagesFolder != "/" && !_imagesFolder.empty() && fs::is_directory(_imagesFolder) && !fs::is_empty(_imagesFolder)) { - throw std::runtime_error("Ambiguous case: Multiple image file found for the view '" + - std::to_string(view.getViewId()) + "' in folder '" + _imagesFolder + "'."); - } + // find folder file extension + std::vector paths = utils::getFilesPathsFromFolder(_imagesFolder, [&view](const fs::path& path) { + return (path.stem() == std::to_string(view.getViewId()) && (image::isSupportedUndistortFormat(path.extension().string()))); + }); + + // if path was not found + if (paths.empty()) + { + throw std::runtime_error("Cannot find image file coresponding to the view '" + std::to_string(view.getViewId()) + + "' in folder '" + _imagesFolder + "'."); + } + else if (paths.size() > 1) + { + throw std::runtime_error("Ambiguous case: Multiple image file found for the view '" + std::to_string(view.getViewId()) + + "' in folder '" + _imagesFolder + "'."); + } - path = _imagesFolder + std::to_string(view.getViewId()) + fs::path(paths[0]).extension().string(); - } + path = _imagesFolder + std::to_string(view.getViewId()) + fs::path(paths[0]).extension().string(); + } - dimensions.emplace(view.getImage().getWidth(), view.getImage().getHeight()); - _imagesParams.emplace_back(view.getViewId(), view.getImage().getWidth(), view.getImage().getHeight(), path); - _imageIdsPerViewId[view.getViewId()] = i; - ++i; + dimensions.emplace(view.getImage().getWidth(), view.getImage().getHeight()); + _imagesParams.emplace_back(view.getViewId(), view.getImage().getWidth(), view.getImage().getHeight(), path); + _imageIdsPerViewId[view.getViewId()] = i; + ++i; } ALICEVISION_LOG_INFO("Found " << dimensions.size() << " image dimension(s): "); - for(const auto& dim : dimensions) + for (const auto& dim : dimensions) ALICEVISION_LOG_INFO("\t- [" << dim.first << "x" << dim.second << "]"); } - ncams = getNbCameras(); //TODO : always use getNbCameras() instead of ncams + ncams = getNbCameras(); // TODO : always use getNbCameras() instead of ncams // Resize internal structures resizeCams(getNbCameras()); - for(int i = 0; i < getNbCameras(); ++i) + for (int i = 0; i < getNbCameras(); ++i) { const ImageParams& imgParams = _imagesParams.at(i); oiio::ParamValueList metadata; oiio::ParamValueList::const_iterator scaleIt = metadata.end(); oiio::ParamValueList::const_iterator pIt = metadata.end(); - + const bool fileExists = fs::exists(imgParams.path); - if(fileExists) + if (fileExists) { metadata = image::readImageMetadata(imgParams.path); scaleIt = metadata.find("AliceVision:downscale"); @@ -135,12 +131,12 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, } // find image scale information - if(scaleIt != metadata.end() && scaleIt->type() == oiio::TypeDesc::INT) + if (scaleIt != metadata.end() && scaleIt->type() == oiio::TypeDesc::INT) { // use aliceVision image metadata _imagesScale.at(i) = scaleIt->get_int(); } - else if(fileExists) + else if (fileExists) { // use image dimension int w, h; @@ -148,11 +144,11 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, const int widthScale = imgParams.width / w; const int heightScale = imgParams.height / h; - if((widthScale != 1) && (heightScale != 1)) + if ((widthScale != 1) && (heightScale != 1)) ALICEVISION_LOG_INFO("Reading '" << imgParams.path << "' x" << widthScale << "downscale from file dimension" << std::endl << "\t- No 'AliceVision:downscale' metadata found."); - if(widthScale != heightScale) + if (widthScale != heightScale) throw std::runtime_error("Scale of file: '" + imgParams.path + "' is not uniform, check image dimension ratio."); _imagesScale.at(i) = widthScale; @@ -161,7 +157,7 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, FocK1K2Arr.at(i) = Point3d(-1.0, -1.0, -1.0); // load camera matrices - if(pIt != metadata.end() && pIt->type() == oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::MATRIX44)) + if (pIt != metadata.end() && pIt->type() == oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::MATRIX44)) { ALICEVISION_LOG_DEBUG("Reading view " << getViewId(i) << " projection matrix from image metadata."); loadMatricesFromRawProjectionMatrix(i, static_cast(pIt->data())); @@ -172,7 +168,7 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, const std::string fileNameP = getFileNameFromIndex(*this, i, EFileType::P); const std::string fileNameD = getFileNameFromIndex(*this, i, EFileType::D); - if(fs::exists(fileNameP) && fs::exists(fileNameD)) + if (fs::exists(fileNameP) && fs::exists(fileNameD)) { ALICEVISION_LOG_DEBUG("Reading view " << getViewId(i) << " projection matrix from file '" << fileNameP << "'."); @@ -185,7 +181,7 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, } } - if(KArr[i].m11 > (float)(getWidth(i) * 100)) + if (KArr[i].m11 > (float)(getWidth(i) * 100)) { ALICEVISION_LOG_WARNING("Camera " << i << " at infinity. Setting to zero"); @@ -236,7 +232,6 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, ALICEVISION_LOG_INFO("Overall maximum dimension: [" << _maxImageWidth << "x" << _maxImageHeight << "]"); } - void MultiViewParams::loadMatricesFromTxtFile(int index, const std::string& fileNameP, const std::string& fileNameD) { if (!fs::exists(fileNameP)) @@ -245,14 +240,14 @@ void MultiViewParams::loadMatricesFromTxtFile(int index, const std::string& file std::ifstream in{fileNameP}; char fc; in >> fc; - if(fc == 'C') // FURUKAWA'S PROJCTION MATRIX FILE FORMAT + if (fc == 'C') // FURUKAWA'S PROJCTION MATRIX FILE FORMAT { - in >> fc; // O - in >> fc; // N - in >> fc; // T - in >> fc; // O - in >> fc; // U - in >> fc; // R + in >> fc; // O + in >> fc; // N + in >> fc; // T + in >> fc; // O + in >> fc; // U + in >> fc; // R } else { @@ -266,8 +261,8 @@ void MultiViewParams::loadMatricesFromTxtFile(int index, const std::string& file // apply scale to camera matrix (camera matrix is scale 1) const int imgScale = _imagesScale.at(index) * _processDownscale; - for(int i=0; i< 8; ++i) - pMatrix.m[i] /= static_cast(imgScale); + for (int i = 0; i < 8; ++i) + pMatrix.m[i] /= static_cast(imgScale); pMatrix.decomposeProjectionMatrix(KArr[index], RArr[index], CArr[index]); iKArr[index] = KArr[index].inverse(); @@ -283,47 +278,47 @@ void MultiViewParams::loadMatricesFromTxtFile(int index, const std::string& file void MultiViewParams::loadMatricesFromRawProjectionMatrix(int index, const double* rawProjMatix) { - Matrix3x4& pMatrix = camArr.at(index); - std::copy_n(rawProjMatix, 12, pMatrix.m); - - // apply scale to camera matrix (camera matrix is scale 1) - const double imgScale = double(_imagesScale.at(index) * _processDownscale); - for(int i = 0; i < 8; ++i) - pMatrix.m[i] /= imgScale; - - pMatrix.decomposeProjectionMatrix(KArr.at(index), RArr.at(index), CArr.at(index)); - iKArr.at(index) = KArr.at(index).inverse(); - iRArr.at(index) = RArr.at(index).inverse(); - iCamArr.at(index) = iRArr.at(index) * iKArr.at(index); + Matrix3x4& pMatrix = camArr.at(index); + std::copy_n(rawProjMatix, 12, pMatrix.m); + + // apply scale to camera matrix (camera matrix is scale 1) + const double imgScale = double(_imagesScale.at(index) * _processDownscale); + for (int i = 0; i < 8; ++i) + pMatrix.m[i] /= imgScale; + + pMatrix.decomposeProjectionMatrix(KArr.at(index), RArr.at(index), CArr.at(index)); + iKArr.at(index) = KArr.at(index).inverse(); + iRArr.at(index) = RArr.at(index).inverse(); + iCamArr.at(index) = iRArr.at(index) * iKArr.at(index); } void MultiViewParams::loadMatricesFromSfM(int index) { - using RowMatrixXd = Eigen::Matrix; + using RowMatrixXd = Eigen::Matrix; - const sfmData::View& view = *(_sfmData.getViews().at(getViewId(index))); - sfmData::Intrinsics::const_iterator intrinsicIt = _sfmData.getIntrinsics().find(view.getIntrinsicId()); + const sfmData::View& view = *(_sfmData.getViews().at(getViewId(index))); + sfmData::Intrinsics::const_iterator intrinsicIt = _sfmData.getIntrinsics().find(view.getIntrinsicId()); - std::shared_ptr ptrIntrinsic = intrinsicIt->second; - std::shared_ptr ptrPinHole = std::dynamic_pointer_cast(ptrIntrinsic); - if (!ptrPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in loadMatricesFromRawProjectionMatrix"); - return; - } + std::shared_ptr ptrIntrinsic = intrinsicIt->second; + std::shared_ptr ptrPinHole = std::dynamic_pointer_cast(ptrIntrinsic); + if (!ptrPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in loadMatricesFromRawProjectionMatrix"); + return; + } - const Mat34 P = ptrPinHole->getProjectiveEquivalent(_sfmData.getPose(view).getTransform()); - std::vector vP(P.size()); - Eigen::Map(vP.data(), P.rows(), P.cols()) = P; + const Mat34 P = ptrPinHole->getProjectiveEquivalent(_sfmData.getPose(view).getTransform()); + std::vector vP(P.size()); + Eigen::Map(vP.data(), P.rows(), P.cols()) = P; - loadMatricesFromRawProjectionMatrix(index, vP.data()); + loadMatricesFromRawProjectionMatrix(index, vP.data()); } -MultiViewParams::~MultiViewParams() -{} +MultiViewParams::~MultiViewParams() {} const std::map& MultiViewParams::getMetadata(int index) const { - return _sfmData.getViews().at(getViewId(index))->getImage().getMetadata(); + return _sfmData.getViews().at(getViewId(index))->getImage().getMetadata(); } bool MultiViewParams::is3DPointInFrontOfCam(const Point3d* X, int rc) const @@ -333,16 +328,13 @@ bool MultiViewParams::is3DPointInFrontOfCam(const Point3d* X, int rc) const return XT.z >= 0; } -void MultiViewParams::getPixelFor3DPoint(Point2d* out, const Point3d& X, int rc) const -{ - getPixelFor3DPoint(out, X, camArr[rc]); -} +void MultiViewParams::getPixelFor3DPoint(Point2d* out, const Point3d& X, int rc) const { getPixelFor3DPoint(out, X, camArr[rc]); } void MultiViewParams::getPixelFor3DPoint(Point2d* out, const Point3d& X, const Matrix3x4& P) const { Point3d XT = P * X; - if(XT.z <= 0) + if (XT.z <= 0) { out->x = -1.0; out->y = -1.0; @@ -358,7 +350,7 @@ void MultiViewParams::getPixelFor3DPoint(Pixel* out, const Point3d& X, int rc) c { Point3d XT = camArr[rc] * X; - if(XT.z <= 0) + if (XT.z <= 0) { out->x = -1; out->y = -1; @@ -389,7 +381,7 @@ double MultiViewParams::getCamPixelSize(const Point3d& x0, int cam) const double MultiViewParams::getCamPixelSize(const Point3d& x0, int cam, float d) const { - if(d == 0.0f) + if (d == 0.0f) { return 0.0f; } @@ -404,12 +396,12 @@ double MultiViewParams::getCamPixelSize(const Point3d& x0, int cam, float d) con } /** -* @brief Return the size of a pixel in space with an offset -* of "d" pixels in the target camera (along the epipolar line). -*/ + * @brief Return the size of a pixel in space with an offset + * of "d" pixels in the target camera (along the epipolar line). + */ double MultiViewParams::getCamPixelSizeRcTc(const Point3d& p, int rc, int tc, float d) const { - if(d == 0.0f) + if (d == 0.0f) { return 0.0f; } @@ -429,7 +421,7 @@ double MultiViewParams::getCamPixelSizeRcTc(const Point3d& p, int rc, int tc, fl // tpix1 is tpix with an offset of d pixels along the epipolar line Point2d tpix1 = tpix + pixelVect * d; - if(!triangulateMatch(p1, rpix, tpix1, rc, tc, *this)) + if (!triangulateMatch(p1, rpix, tpix1, rc, tc, *this)) { // Fallback to compute the pixel size using only the rc camera return getCamPixelSize(p, rc, d); @@ -451,33 +443,32 @@ double MultiViewParams::getCamPixelSizePlaneSweepAlpha(const Point3d& p, int rc, return (avRcTc + avRc) * 0.5; } -double MultiViewParams::getCamPixelSizePlaneSweepAlpha(const Point3d& p, int rc, StaticVector* tcams, int scale, - int step) const +double MultiViewParams::getCamPixelSizePlaneSweepAlpha(const Point3d& p, int rc, StaticVector* tcams, int scale, int step) const { - //float av1 = 0.0f; + // float av1 = 0.0f; double avmax = 0.0; - for(int c = 0; c < tcams->size(); c++) + for (int c = 0; c < tcams->size(); c++) { double dpxs = getCamPixelSizePlaneSweepAlpha(p, rc, (*tcams)[c], scale, step); - //av1 += dpxs; + // av1 += dpxs; avmax = std::max(avmax, dpxs); } - //av1 /= (float)(tcams->size()); - // return av1; + // av1 /= (float)(tcams->size()); + // return av1; return avmax; } double MultiViewParams::getCamsMinPixelSize(const Point3d& x0, const StaticVector& tcams) const { - if(tcams.empty()) + if (tcams.empty()) { return 0.0f; } double minPixSize = 1000000000.0; - for(int ci = 0; ci < (int)tcams.size(); ci++) + for (int ci = 0; ci < (int)tcams.size(); ci++) { double pixSize = getCamPixelSize(x0, (int)tcams[ci]); - if(minPixSize > pixSize) + if (minPixSize > pixSize) { minPixSize = pixSize; } @@ -499,26 +490,21 @@ bool MultiViewParams::isPixelInSourceImage(const Pixel& pixRC, int camId, int ma bool MultiViewParams::isPixelInImage(const Pixel& pix, int camId, int margin) const { - return ((pix.x >= margin) && (pix.x < getWidth(camId) - margin) && - (pix.y >= margin) && (pix.y < getHeight(camId) - margin)); -} -bool MultiViewParams::isPixelInImage(const Pixel& pix, int camId) const -{ - return isPixelInImage(pix, camId, g_border); + return ((pix.x >= margin) && (pix.x < getWidth(camId) - margin) && (pix.y >= margin) && (pix.y < getHeight(camId) - margin)); } +bool MultiViewParams::isPixelInImage(const Pixel& pix, int camId) const { return isPixelInImage(pix, camId, g_border); } -bool MultiViewParams::isPixelInImage(const Point2d& pix, int camId) const -{ - return isPixelInImage(Pixel(pix), camId); -} +bool MultiViewParams::isPixelInImage(const Point2d& pix, int camId) const { return isPixelInImage(Pixel(pix), camId); } -bool MultiViewParams::isPixelInImage(const Point2d& pix, int camId, int margin) const -{ - return isPixelInImage(Pixel(pix), camId, margin); -} +bool MultiViewParams::isPixelInImage(const Point2d& pix, int camId, int margin) const { return isPixelInImage(Pixel(pix), camId, margin); } -void MultiViewParams::decomposeProjectionMatrix(Point3d& Co, Matrix3x3& Ro, Matrix3x3& iRo, Matrix3x3& Ko, - Matrix3x3& iKo, Matrix3x3& iPo, const Matrix3x4& P) const +void MultiViewParams::decomposeProjectionMatrix(Point3d& Co, + Matrix3x3& Ro, + Matrix3x3& iRo, + Matrix3x3& Ko, + Matrix3x3& iKo, + Matrix3x3& iPo, + const Matrix3x4& P) const { P.decomposeProjectionMatrix(Ko, Ro, Co); iKo = Ko.inverse(); @@ -526,154 +512,154 @@ void MultiViewParams::decomposeProjectionMatrix(Point3d& Co, Matrix3x3& Ro, Matr iPo = iRo * iKo; } - StaticVector MultiViewParams::findNearestCamsFromLandmarks(int rc, int nbNearestCams) const { - StaticVector out; - std::vector ids; - ids.reserve(getNbCameras()); + StaticVector out; + std::vector ids; + ids.reserve(getNbCameras()); - for(int tc = 0; tc < getNbCameras(); ++tc) - ids.push_back(SortedId(tc,0)); + for (int tc = 0; tc < getNbCameras(); ++tc) + ids.push_back(SortedId(tc, 0)); - const IndexT viewId = getViewId(rc); - const sfmData::View& view = *(_sfmData.getViews().at(viewId)); - const geometry::Pose3 pose = _sfmData.getPose(view).getTransform(); - const camera::IntrinsicBase* intrinsicPtr = _sfmData.getIntrinsicPtr(view.getIntrinsicId()); + const IndexT viewId = getViewId(rc); + const sfmData::View& view = *(_sfmData.getViews().at(viewId)); + const geometry::Pose3 pose = _sfmData.getPose(view).getTransform(); + const camera::IntrinsicBase* intrinsicPtr = _sfmData.getIntrinsicPtr(view.getIntrinsicId()); - for(const auto& landmarkPair :_sfmData.getLandmarks()) - { - const auto& observations = landmarkPair.second.observations; + for (const auto& landmarkPair : _sfmData.getLandmarks()) + { + const auto& observations = landmarkPair.second.observations; - auto viewObsIt = observations.find(viewId); - if(viewObsIt == observations.end()) - continue; + auto viewObsIt = observations.find(viewId); + if (viewObsIt == observations.end()) + continue; - for(const auto& observationPair : observations) - { - const IndexT otherViewId = observationPair.first; + for (const auto& observationPair : observations) + { + const IndexT otherViewId = observationPair.first; - if(otherViewId == viewId) - continue; + if (otherViewId == viewId) + continue; - const sfmData::View& otherView = *(_sfmData.getViews().at(otherViewId)); - const geometry::Pose3 otherPose = _sfmData.getPose(otherView).getTransform(); - const camera::IntrinsicBase* otherIntrinsicPtr = _sfmData.getIntrinsicPtr(otherView.getIntrinsicId()); + const sfmData::View& otherView = *(_sfmData.getViews().at(otherViewId)); + const geometry::Pose3 otherPose = _sfmData.getPose(otherView).getTransform(); + const camera::IntrinsicBase* otherIntrinsicPtr = _sfmData.getIntrinsicPtr(otherView.getIntrinsicId()); - const double angle = camera::angleBetweenRays(pose, intrinsicPtr, otherPose, otherIntrinsicPtr, viewObsIt->second.x, observationPair.second.x); + const double angle = + camera::angleBetweenRays(pose, intrinsicPtr, otherPose, otherIntrinsicPtr, viewObsIt->second.x, observationPair.second.x); - if(angle < _minViewAngle || angle > _maxViewAngle) - continue; + if (angle < _minViewAngle || angle > _maxViewAngle) + continue; - const int tc = getIndexFromViewId(otherViewId); - ++ids.at(tc).value; + const int tc = getIndexFromViewId(otherViewId); + ++ids.at(tc).value; + } } - } - qsort(&ids[0], ids.size(), sizeof(SortedId), qsortCompareSortedIdDesc); + qsort(&ids[0], ids.size(), sizeof(SortedId), qsortCompareSortedIdDesc); - // ensure the ideal number of target cameras is not superior to the actual number of cameras - const int maxTc = std::min({getNbCameras(), nbNearestCams, static_cast(ids.size())}); - out.reserve(maxTc); + // ensure the ideal number of target cameras is not superior to the actual number of cameras + const int maxTc = std::min({getNbCameras(), nbNearestCams, static_cast(ids.size())}); + out.reserve(maxTc); - for(int i = 0; i < maxTc; ++i) - { - // a minimum of 10 common points is required (10*2 because points are stored in both rc/tc combinations) - if(ids[i].value > (10 * 2)) - out.push_back(ids[i].id); - } + for (int i = 0; i < maxTc; ++i) + { + // a minimum of 10 common points is required (10*2 because points are stored in both rc/tc combinations) + if (ids[i].value > (10 * 2)) + out.push_back(ids[i].id); + } - if(out.size() < nbNearestCams) - ALICEVISION_LOG_INFO("Found only " << out.size() << "/" << nbNearestCams << " nearest cameras for view id: " << getViewId(rc)); + if (out.size() < nbNearestCams) + ALICEVISION_LOG_INFO("Found only " << out.size() << "/" << nbNearestCams << " nearest cameras for view id: " << getViewId(rc)); - return out; + return out; } std::vector MultiViewParams::findTileNearestCams(int rc, int nbNearestCams, const std::vector& tCams, const ROI& roi) const { - auto plateauFunction = [](int a, int b, int c, int d, int x) - { - if(x > a && x <= b) - return (float(x - a) / float(b - a)); - if(x > b && x <= c) - return 1.0f; - if(x > c && x <= d) - return 1.0f - (float(x - c) / float(d - c)); - return 0.f; - }; + auto plateauFunction = [](int a, int b, int c, int d, int x) { + if (x > a && x <= b) + return (float(x - a) / float(b - a)); + if (x > b && x <= c) + return 1.0f; + if (x > c && x <= d) + return 1.0f - (float(x - c) / float(d - c)); + return 0.f; + }; - std::vector out; - std::map tcScore; + std::vector out; + std::map tcScore; - for(std::size_t i = 0; i < tCams.size(); ++i) - tcScore[tCams[i]] = 0.0f; + for (std::size_t i = 0; i < tCams.size(); ++i) + tcScore[tCams[i]] = 0.0f; - const sfmData::SfMData& sfmData = getInputSfMData(); + const sfmData::SfMData& sfmData = getInputSfMData(); - const IndexT viewId = getViewId(rc); - const sfmData::View& view = *(sfmData.getViews().at(viewId)); - const geometry::Pose3 pose = sfmData.getPose(view).getTransform(); - const camera::IntrinsicBase* intrinsicPtr = sfmData.getIntrinsicPtr(view.getIntrinsicId()); + const IndexT viewId = getViewId(rc); + const sfmData::View& view = *(sfmData.getViews().at(viewId)); + const geometry::Pose3 pose = sfmData.getPose(view).getTransform(); + const camera::IntrinsicBase* intrinsicPtr = sfmData.getIntrinsicPtr(view.getIntrinsicId()); - const ROI fullsizeRoi = upscaleROI(roi, getProcessDownscale()); // landmark observations are in the full-size image coordinate system + const ROI fullsizeRoi = upscaleROI(roi, getProcessDownscale()); // landmark observations are in the full-size image coordinate system - for(const auto& landmarkPair : sfmData.getLandmarks()) - { - const auto& observations = landmarkPair.second.observations; + for (const auto& landmarkPair : sfmData.getLandmarks()) + { + const auto& observations = landmarkPair.second.observations; - auto viewObsIt = observations.find(viewId); + auto viewObsIt = observations.find(viewId); - // has landmark observation for the R camera - if(viewObsIt == observations.end()) - continue; + // has landmark observation for the R camera + if (viewObsIt == observations.end()) + continue; - // landmark R camera observation is in the image full-size ROI - if(!fullsizeRoi.contains(viewObsIt->second.x.x(), viewObsIt->second.x.y())) - continue; + // landmark R camera observation is in the image full-size ROI + if (!fullsizeRoi.contains(viewObsIt->second.x.x(), viewObsIt->second.x.y())) + continue; - for(const auto& observationPair : observations) - { - const IndexT otherViewId = observationPair.first; + for (const auto& observationPair : observations) + { + const IndexT otherViewId = observationPair.first; - // other view should not be the R camera - if(otherViewId == viewId) - continue; + // other view should not be the R camera + if (otherViewId == viewId) + continue; - const int tc = getIndexFromViewId(otherViewId); + const int tc = getIndexFromViewId(otherViewId); - // other view should be a T camera - if(tcScore.find(tc) == tcScore.end()) - continue; + // other view should be a T camera + if (tcScore.find(tc) == tcScore.end()) + continue; - const sfmData::View& otherView = *(sfmData.getViews().at(otherViewId)); - const geometry::Pose3 otherPose = sfmData.getPose(otherView).getTransform(); - const camera::IntrinsicBase* otherIntrinsicPtr = sfmData.getIntrinsicPtr(otherView.getIntrinsicId()); + const sfmData::View& otherView = *(sfmData.getViews().at(otherViewId)); + const geometry::Pose3 otherPose = sfmData.getPose(otherView).getTransform(); + const camera::IntrinsicBase* otherIntrinsicPtr = sfmData.getIntrinsicPtr(otherView.getIntrinsicId()); - const double angle = camera::angleBetweenRays(pose, intrinsicPtr, otherPose, otherIntrinsicPtr, viewObsIt->second.x, observationPair.second.x); + const double angle = + camera::angleBetweenRays(pose, intrinsicPtr, otherPose, otherIntrinsicPtr, viewObsIt->second.x, observationPair.second.x); - tcScore[tc] += plateauFunction(1,10,50,150, angle); + tcScore[tc] += plateauFunction(1, 10, 50, 150, angle); + } } - } - std::vector ids; - ids.reserve(tcScore.size()); + std::vector ids; + ids.reserve(tcScore.size()); - for(const auto& tcScorePair : tcScore) - { - if(tcScorePair.second > 0.0f) - ids.push_back(SortedId(tcScorePair.first, tcScorePair.second)); - } + for (const auto& tcScorePair : tcScore) + { + if (tcScorePair.second > 0.0f) + ids.push_back(SortedId(tcScorePair.first, tcScorePair.second)); + } - qsort(&ids[0], ids.size(), sizeof(SortedId), qsortCompareSortedIdDesc); + qsort(&ids[0], ids.size(), sizeof(SortedId), qsortCompareSortedIdDesc); - // ensure the ideal number of target cameras is not superior to the actual number of cameras - const int maxTc = std::min(std::min(getNbCameras(), nbNearestCams), static_cast(ids.size())); - out.reserve(maxTc); + // ensure the ideal number of target cameras is not superior to the actual number of cameras + const int maxTc = std::min(std::min(getNbCameras(), nbNearestCams), static_cast(ids.size())); + out.reserve(maxTc); - for(int i = 0; i < maxTc; ++i) - out.push_back(ids[i].id); + for (int i = 0; i < maxTc; ++i) + out.push_back(ids[i].id); - return out; + return out; } StaticVector MultiViewParams::findCamsWhichIntersectsHexahedron(const Point3d hexah[8], const std::string& minMaxDepthsFileName) const @@ -681,15 +667,15 @@ StaticVector MultiViewParams::findCamsWhichIntersectsHexahedron(const Point StaticVector* minMaxDepths = loadArrayFromFile(minMaxDepthsFileName); StaticVector tcams; tcams.reserve(getNbCameras()); - for(int rc = 0; rc < getNbCameras(); rc++) + for (int rc = 0; rc < getNbCameras(); rc++) { const float minDepth = (*minMaxDepths)[rc].x; const float maxDepth = (*minMaxDepths)[rc].y; - if((minDepth > 0.0f) && (maxDepth > minDepth)) + if ((minDepth > 0.0f) && (maxDepth > minDepth)) { Point3d rchex[8]; getCamHexahedron(CArr.at(rc), iCamArr.at(rc), getWidth(rc), getHeight(rc), minDepth, maxDepth, rchex); - if(intersectsHexahedronHexahedron(rchex, hexah)) + if (intersectsHexahedronHexahedron(rchex, hexah)) tcams.push_back(rc); } } @@ -701,28 +687,29 @@ StaticVector MultiViewParams::findCamsWhichIntersectsHexahedron(const Point { StaticVector tcams; tcams.reserve(getNbCameras()); - for(int rc = 0; rc < getNbCameras(); rc++) + for (int rc = 0; rc < getNbCameras(); rc++) { const auto metadata = image::readImageMetadata(getImagePath(rc)); const float minDepth = metadata.get_float("AliceVision:minDepth", -1); const float maxDepth = metadata.get_float("AliceVision:maxDepth", -1); - if(minDepth == -1 && maxDepth == -1) + if (minDepth == -1 && maxDepth == -1) { - ALICEVISION_LOG_WARNING("Cannot find min / max depth metadata in image: " << getImagePath(rc) << ". Assumes that all images should be used."); + ALICEVISION_LOG_WARNING("Cannot find min / max depth metadata in image: " << getImagePath(rc) + << ". Assumes that all images should be used."); tcams.push_back(rc); } else { Point3d rchex[8]; getCamHexahedron(CArr.at(rc), iCamArr.at(rc), getWidth(rc), getHeight(rc), minDepth, maxDepth, rchex); - if(intersectsHexahedronHexahedron(rchex, hexah)) + if (intersectsHexahedronHexahedron(rchex, hexah)) tcams.push_back(rc); } } return tcams; } -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/MultiViewParams.hpp b/src/aliceVision/mvsUtils/MultiViewParams.hpp index f92e54d5bb..404ed430eb 100644 --- a/src/aliceVision/mvsUtils/MultiViewParams.hpp +++ b/src/aliceVision/mvsUtils/MultiViewParams.hpp @@ -26,7 +26,7 @@ namespace bpt = boost::property_tree; namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace mvsUtils { @@ -84,7 +84,7 @@ enum class EFileType class MultiViewParams { -public: + public: /// prepareDenseScene data std::string _imagesFolder; /// camera projection matrix P @@ -126,114 +126,53 @@ class MultiViewParams const Point3d p = CArr[camIndex] + (iCamArr[camIndex] * pix).normalize() * depth; return p; } - inline const std::string& getImagePath(int index) const - { - return _imagesParams.at(index).path; - } + inline const std::string& getImagePath(int index) const { return _imagesParams.at(index).path; } - inline int getViewId(int index) const - { - return _imagesParams.at(index).viewId; - } + inline int getViewId(int index) const { return _imagesParams.at(index).viewId; } - inline int getOriginalWidth(int index) const - { - return _imagesParams.at(index).width; - } + inline int getOriginalWidth(int index) const { return _imagesParams.at(index).width; } - inline int getOriginalHeight(int index) const - { - return _imagesParams.at(index).height; - } + inline int getOriginalHeight(int index) const { return _imagesParams.at(index).height; } - inline int getOriginalSize(int index) const - { - return _imagesParams.at(index).size; - } + inline int getOriginalSize(int index) const { return _imagesParams.at(index).size; } - inline int getWidth(int index) const - { - return _imagesParams.at(index).width / getDownscaleFactor(index); - } + inline int getWidth(int index) const { return _imagesParams.at(index).width / getDownscaleFactor(index); } - inline int getHeight(int index) const - { - return _imagesParams.at(index).height / getDownscaleFactor(index); - } + inline int getHeight(int index) const { return _imagesParams.at(index).height / getDownscaleFactor(index); } - inline int getSize(int index) const - { - return _imagesParams.at(index).size / getDownscaleFactor(index); - } + inline int getSize(int index) const { return _imagesParams.at(index).size / getDownscaleFactor(index); } - inline const std::vector& getImagesParams() const - { - return _imagesParams; - } + inline const std::vector& getImagesParams() const { return _imagesParams; } - inline const ImageParams& getImageParams(int i) const - { - return _imagesParams.at(i); - } + inline const ImageParams& getImageParams(int i) const { return _imagesParams.at(i); } - inline int getDownscaleFactor(int index) const - { - return _imagesScale.at(index) * _processDownscale; - } + inline int getDownscaleFactor(int index) const { return _imagesScale.at(index) * _processDownscale; } - inline int getProcessDownscale() const - { - return _processDownscale; - } + inline int getProcessDownscale() const { return _processDownscale; } - inline int getMaxImageOriginalWidth() const - { - return _maxImageWidth; - } + inline int getMaxImageOriginalWidth() const { return _maxImageWidth; } - inline int getMaxImageOriginalHeight() const - { - return _maxImageHeight; - } + inline int getMaxImageOriginalHeight() const { return _maxImageHeight; } - inline int getMaxImageWidth() const - { - return _maxImageWidth / getProcessDownscale(); - } + inline int getMaxImageWidth() const { return _maxImageWidth / getProcessDownscale(); } - inline int getMaxImageHeight() const - { - return _maxImageHeight / getProcessDownscale(); - } + inline int getMaxImageHeight() const { return _maxImageHeight / getProcessDownscale(); } - inline int getNbCameras() const - { - return _imagesParams.size(); - } + inline int getNbCameras() const { return _imagesParams.size(); } - inline int getIndexFromViewId(IndexT viewId) const - { - return _imageIdsPerViewId.at(viewId); - } + inline int getIndexFromViewId(IndexT viewId) const { return _imageIdsPerViewId.at(viewId); } - inline float getMinViewAngle() const - { - return _minViewAngle; - } + inline float getMinViewAngle() const { return _minViewAngle; } - inline float getMaxViewAngle() const - { - return _maxViewAngle; - } + inline float getMaxViewAngle() const { return _maxViewAngle; } inline std::vector getOriginalP(int index) const { - std::vector p44; // projection matrix (4x4) scale 1 - const Matrix3x4& p34 = camArr.at(index); // projection matrix (3x4) scale = getDownscaleFactor() + std::vector p44; // projection matrix (4x4) scale 1 + const Matrix3x4& p34 = camArr.at(index); // projection matrix (3x4) scale = getDownscaleFactor() const int downscale = getDownscaleFactor(index); p44.assign(p34.m, p34.m + 12); - std::transform(p44.begin(), p44.begin() + 8, p44.begin(), - [&](double p){ return p * downscale; }); + std::transform(p44.begin(), p44.begin() + 8, p44.begin(), [&](double p) { return p * downscale; }); p44.push_back(0); p44.push_back(0); p44.push_back(0); @@ -241,20 +180,11 @@ class MultiViewParams return p44; } - inline const std::string& getDepthMapsFolder() const - { - return _depthMapsFolder; - } + inline const std::string& getDepthMapsFolder() const { return _depthMapsFolder; } - inline const std::string& getDepthMapsFilterFolder() const - { - return _depthMapsFilterFolder; - } + inline const std::string& getDepthMapsFilterFolder() const { return _depthMapsFilterFolder; } - inline const sfmData::SfMData& getInputSfMData() const - { - return _sfmData; - } + inline const sfmData::SfMData& getInputSfMData() const { return _sfmData; } const std::map& getMetadata(int index) const; @@ -277,7 +207,8 @@ class MultiViewParams bool isPixelInImage(const Pixel& pix, int camId) const; bool isPixelInImage(const Point2d& pix, int camId) const; bool isPixelInImage(const Point2d& pix, int camId, int margin) const; - void decomposeProjectionMatrix(Point3d& Co, Matrix3x3& Ro, Matrix3x3& iRo, Matrix3x3& Ko, Matrix3x3& iKo, Matrix3x3& iPo, const Matrix3x4& P) const; + void decomposeProjectionMatrix(Point3d& Co, Matrix3x3& Ro, Matrix3x3& iRo, Matrix3x3& Ko, Matrix3x3& iKo, Matrix3x3& iPo, const Matrix3x4& P) + const; /** * @brief findCamsWhichIntersectsHexahedron @@ -312,17 +243,11 @@ class MultiViewParams */ std::vector findTileNearestCams(int rc, int nbNearestCams, const std::vector& tCams, const ROI& roi) const; - inline void setMinViewAngle(float minViewAngle) - { - _minViewAngle = minViewAngle; - } + inline void setMinViewAngle(float minViewAngle) { _minViewAngle = minViewAngle; } - inline void setMaxViewAngle(float maxViewAngle) - { - _maxViewAngle = maxViewAngle; - } + inline void setMaxViewAngle(float maxViewAngle) { _maxViewAngle = maxViewAngle; } -private: + private: /// image params list (width, height, size) std::vector _imagesParams; /// image id per view id @@ -367,5 +292,5 @@ class MultiViewParams } }; -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/TileParams.cpp b/src/aliceVision/mvsUtils/TileParams.cpp index 39942b56d6..000afaf75b 100644 --- a/src/aliceVision/mvsUtils/TileParams.cpp +++ b/src/aliceVision/mvsUtils/TileParams.cpp @@ -19,38 +19,38 @@ void getTileRoiList(const TileParams& tileParams, int imageWidth, int imageHeigh assert(2 * tileParams.padding < tileParams.bufferHeight); // single tile case - if(hasOnlyOneTile(tileParams, imageWidth, imageHeight)) + if (hasOnlyOneTile(tileParams, imageWidth, imageHeight)) { - out_tileRoiList.emplace_back(0, imageWidth, 0, imageHeight); - return; + out_tileRoiList.emplace_back(0, imageWidth, 0, imageHeight); + return; } // compute maximum effective tile width and height: maximum size without padding - const int maxEffectiveTileWidth = tileParams.bufferWidth - 2 * tileParams.padding; + const int maxEffectiveTileWidth = tileParams.bufferWidth - 2 * tileParams.padding; const int maxEffectiveTileHeight = tileParams.bufferHeight - 2 * tileParams.padding; // compute nb of tile buffers per side - const int nbTileSideX = divideRoundUp(imageWidth , maxEffectiveTileWidth); + const int nbTileSideX = divideRoundUp(imageWidth, maxEffectiveTileWidth); const int nbTileSideY = divideRoundUp(imageHeight, maxEffectiveTileHeight); // allocate roi list out_tileRoiList.resize(nbTileSideX * nbTileSideY); // compute downscaled image width and height - const int downscaledImageWidth = divideRoundUp(imageWidth, maxDownscale); + const int downscaledImageWidth = divideRoundUp(imageWidth, maxDownscale); const int downscaledImageHeight = divideRoundUp(imageHeight, maxDownscale); // compute effective tile width and height for the best tile layout at the maximum downscale - const int effectiveTileWidth = divideRoundUp(downscaledImageWidth , nbTileSideX) * maxDownscale; + const int effectiveTileWidth = divideRoundUp(downscaledImageWidth, nbTileSideX) * maxDownscale; const int effectiveTileHeight = divideRoundUp(downscaledImageHeight, nbTileSideY) * maxDownscale; // compute each tile ROI - for(int i = 0; i < nbTileSideX; ++i) + for (int i = 0; i < nbTileSideX; ++i) { const int beginX = i * effectiveTileWidth; const int endX = std::min((i + 1) * effectiveTileWidth + tileParams.padding, imageWidth); - for(int j = 0; j < nbTileSideY; ++j) + for (int j = 0; j < nbTileSideY; ++j) { const int beginY = j * effectiveTileHeight; const int endY = std::min((j + 1) * effectiveTileHeight + tileParams.padding, imageHeight); @@ -62,60 +62,60 @@ void getTileRoiList(const TileParams& tileParams, int imageWidth, int imageHeigh void logTileRoiList(const TileParams& tileParams, int imageWidth, int imageHeight, int maxDownscale, const std::vector& in_tileRoiList) { - // compute maximum effective tile width and height: maximum size without padding - const int maxEffectiveTileWidth = tileParams.bufferWidth - 2 * tileParams.padding; - const int maxEffectiveTileHeight = tileParams.bufferHeight - 2 * tileParams.padding; - - // compute nb of tile buffers per side - const int nbTileSideX = divideRoundUp(imageWidth , maxEffectiveTileWidth); - const int nbTileSideY = divideRoundUp(imageHeight, maxEffectiveTileHeight); - - // compute downscaled image width and height - const int downscaledImageWidth = divideRoundUp(imageWidth, maxDownscale); - const int downscaledImageHeight = divideRoundUp(imageHeight, maxDownscale); - - // compute effective tile width and height for the best tile layout at the maximum downscale - const int effectiveTileWidth = divideRoundUp(downscaledImageWidth , nbTileSideX) * maxDownscale; - const int effectiveTileHeight = divideRoundUp(downscaledImageHeight, nbTileSideY) * maxDownscale; - - std::ostringstream ostr; - ostr << "Tiling information: " << std::endl - << "\t- parameters: " << std::endl - << "\t - buffer width: " << tileParams.bufferWidth << " px" << std::endl - << "\t - buffer height: " << tileParams.bufferHeight << " px" << std::endl - << "\t - padding: " << tileParams.padding << " px" << std::endl - << "\t- maximum downscale: " << maxDownscale << std::endl - << "\t- maximum image width: " << imageWidth << " px" << std::endl - << "\t- maximum image height: " << imageHeight << " px" << std::endl; - - if(hasOnlyOneTile(tileParams, imageWidth, imageHeight)) - { + // compute maximum effective tile width and height: maximum size without padding + const int maxEffectiveTileWidth = tileParams.bufferWidth - 2 * tileParams.padding; + const int maxEffectiveTileHeight = tileParams.bufferHeight - 2 * tileParams.padding; + + // compute nb of tile buffers per side + const int nbTileSideX = divideRoundUp(imageWidth, maxEffectiveTileWidth); + const int nbTileSideY = divideRoundUp(imageHeight, maxEffectiveTileHeight); + + // compute downscaled image width and height + const int downscaledImageWidth = divideRoundUp(imageWidth, maxDownscale); + const int downscaledImageHeight = divideRoundUp(imageHeight, maxDownscale); + + // compute effective tile width and height for the best tile layout at the maximum downscale + const int effectiveTileWidth = divideRoundUp(downscaledImageWidth, nbTileSideX) * maxDownscale; + const int effectiveTileHeight = divideRoundUp(downscaledImageHeight, nbTileSideY) * maxDownscale; + + std::ostringstream ostr; + ostr << "Tiling information: " << std::endl + << "\t- parameters: " << std::endl + << "\t - buffer width: " << tileParams.bufferWidth << " px" << std::endl + << "\t - buffer height: " << tileParams.bufferHeight << " px" << std::endl + << "\t - padding: " << tileParams.padding << " px" << std::endl + << "\t- maximum downscale: " << maxDownscale << std::endl + << "\t- maximum image width: " << imageWidth << " px" << std::endl + << "\t- maximum image height: " << imageHeight << " px" << std::endl; + + if (hasOnlyOneTile(tileParams, imageWidth, imageHeight)) + { + ALICEVISION_LOG_INFO(ostr.str()); + ALICEVISION_LOG_INFO("Maximum image size is smaller than one tile, use only one tile."); + return; + } + + ostr << "\t- maximum effective tile width: " << maxEffectiveTileWidth << " px" << std::endl + << "\t- maximum effective tile height: " << maxEffectiveTileHeight << " px" << std::endl + << "\t- # tiles on X-side: " << nbTileSideX << std::endl + << "\t- # tiles on Y-side: " << nbTileSideY << std::endl + << "\t- effective tile width: " << effectiveTileWidth << " px" << std::endl + << "\t- effective tile height: " << effectiveTileHeight << " px" << std::endl + << "\t- tile list: " << std::endl; + + if (in_tileRoiList.empty()) + ostr << "\t empty" << std::endl; + + for (size_t i = 0; i < in_tileRoiList.size(); ++i) + { + const ROI& roi = in_tileRoiList.at(i); + + ostr << "\t - tile (" << (i + 1) << "/" << in_tileRoiList.size() << ") " + << "size: " << roi.width() << "x" << roi.height() << " px, roi: [" << roi << "]" << std::endl; + } + ALICEVISION_LOG_INFO(ostr.str()); - ALICEVISION_LOG_INFO("Maximum image size is smaller than one tile, use only one tile."); - return; - } - - ostr << "\t- maximum effective tile width: " << maxEffectiveTileWidth << " px" << std::endl - << "\t- maximum effective tile height: " << maxEffectiveTileHeight << " px" << std::endl - << "\t- # tiles on X-side: " << nbTileSideX << std::endl - << "\t- # tiles on Y-side: " << nbTileSideY << std::endl - << "\t- effective tile width: " << effectiveTileWidth << " px" << std::endl - << "\t- effective tile height: " << effectiveTileHeight << " px" << std::endl - << "\t- tile list: " << std::endl; - - if(in_tileRoiList.empty()) - ostr << "\t empty" << std::endl; - - for(size_t i = 0; i < in_tileRoiList.size(); ++i) - { - const ROI& roi = in_tileRoiList.at(i); - - ostr << "\t - tile (" << (i + 1) << "/" << in_tileRoiList.size() << ") " - << "size: " << roi.width() << "x" << roi.height() << " px, roi: [" << roi << "]" << std::endl; - } - - ALICEVISION_LOG_INFO(ostr.str()); } -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/TileParams.hpp b/src/aliceVision/mvsUtils/TileParams.hpp index 65457246d5..c1629d94f1 100644 --- a/src/aliceVision/mvsUtils/TileParams.hpp +++ b/src/aliceVision/mvsUtils/TileParams.hpp @@ -18,26 +18,26 @@ namespace mvsUtils { */ struct TileParams { - // user parameters + // user parameters - int bufferWidth = 1024; - int bufferHeight = 1024; - int padding = 64; + int bufferWidth = 1024; + int bufferHeight = 1024; + int padding = 64; }; /** -* @brief Check if the given image size can contain only one tile -* @param[in] tileParams the tile parameters -* @param[in] imageWidth the image width -* @param[in] imageHeight the image height -* @return true if single tile case -*/ + * @brief Check if the given image size can contain only one tile + * @param[in] tileParams the tile parameters + * @param[in] imageWidth the image width + * @param[in] imageHeight the image height + * @return true if single tile case + */ inline bool hasOnlyOneTile(const TileParams& tileParams, int imageWidth, int imageHeight) { - return (tileParams.bufferHeight >= imageWidth && tileParams.bufferHeight >= imageHeight); + return (tileParams.bufferHeight >= imageWidth && tileParams.bufferHeight >= imageHeight); } - /** +/** * @brief Get tile list from tile parameters and image width/height * @param[in] tileParams the tile parameters * @param[in] imageWidth the image width @@ -48,14 +48,14 @@ inline bool hasOnlyOneTile(const TileParams& tileParams, int imageWidth, int ima void getTileRoiList(const TileParams& tileParams, int imageWidth, int imageHeight, int maxDownscale, std::vector& out_tileRoiList); /** -* @brief Log tile list and tile parameters -* @param[in] tileParams the tile parameters -* @param[in] imageWidth the image width used for the tile ROI list computation -* @param[in] imageHeight the image height used for the tile ROI list computation -* @param[in] maxDownscale the maximum downscale that can be applied to the image -* @param[in] in_tileRoiList the tile ROI list -*/ + * @brief Log tile list and tile parameters + * @param[in] tileParams the tile parameters + * @param[in] imageWidth the image width used for the tile ROI list computation + * @param[in] imageHeight the image height used for the tile ROI list computation + * @param[in] maxDownscale the maximum downscale that can be applied to the image + * @param[in] in_tileRoiList the tile ROI list + */ void logTileRoiList(const TileParams& tileParams, int imageWidth, int imageHeight, int maxDownscale, const std::vector& in_tileRoiList); -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/common.cpp b/src/aliceVision/mvsUtils/common.cpp index 69e1184d86..2e2aec8a7f 100644 --- a/src/aliceVision/mvsUtils/common.cpp +++ b/src/aliceVision/mvsUtils/common.cpp @@ -20,14 +20,13 @@ namespace aliceVision { namespace mvsUtils { -bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1, Point2d linePoint2, - const MultiViewParams& mp, int camId) +bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1, Point2d linePoint2, const MultiViewParams& mp, int camId) { Point2d v = linePoint2 - linePoint1; - if(v.size() < FLT_EPSILON) + if (v.size() < FLT_EPSILON) { - return false; // bad configuration ... forward motion with cental ref pixel + return false; // bad configuration ... forward motion with cental ref pixel } v = v.normalize(); @@ -46,7 +45,7 @@ bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1 // a*0 + b*y + c = 0; y = -c / b; double x = 0; double y = -c / b; - if((y >= 0) && (y < rh)) + if ((y >= 0) && (y < rh)) { *pFrom = Point2d(x, y); intersections++; @@ -56,9 +55,9 @@ bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1 // a*rw + b*y + c = 0; y = (-c-a*rw) / b; x = rw; y = (-c - a * rw) / b; - if((y >= 0) && (y < rh)) + if ((y >= 0) && (y < rh)) { - if(intersections == 0) + if (intersections == 0) { *pFrom = Point2d(x, y); } @@ -73,9 +72,9 @@ bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1 // a*x + b*0 + c = 0; x = -c / a; x = -c / a; y = 0; - if((x >= 0) && (x < rw)) + if ((x >= 0) && (x < rw)) { - if(intersections == 0) + if (intersections == 0) { *pFrom = Point2d(x, y); } @@ -90,9 +89,9 @@ bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1 // a*x + b*rh + c = 0; x = (-c-b*rh) / a; x = (-c - b * rh) / a; y = rh; - if((x >= 0) && (x < rw)) + if ((x >= 0) && (x < rw)) { - if(intersections == 0) + if (intersections == 0) { *pFrom = Point2d(x, y); } @@ -103,9 +102,9 @@ bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1 intersections++; } - if(intersections == 2) + if (intersections == 2) { - if((linePoint1 - *pFrom).size() > (linePoint1 - *pTo).size()) + if ((linePoint1 - *pFrom).size() > (linePoint1 - *pTo).size()) { Point2d p = *pFrom; *pFrom = *pTo; @@ -117,8 +116,7 @@ bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1 return false; } -bool getTarEpipolarDirectedLine(Point2d* pFromTar, Point2d* pToTar, Point2d refpix, int refCam, int tarCam, - const MultiViewParams& mp) +bool getTarEpipolarDirectedLine(Point2d* pFromTar, Point2d* pToTar, Point2d refpix, int refCam, int tarCam, const MultiViewParams& mp) { const Matrix3x4& rP = mp.camArr[refCam]; const Matrix3x4& tP = mp.camArr[tarCam]; @@ -154,8 +152,7 @@ bool getTarEpipolarDirectedLine(Point2d* pFromTar, Point2d* pToTar, Point2d refp return get2dLineImageIntersection(pFromTar, pToTar, tarpix1, tarpix2, mp, tarCam); } -bool triangulateMatch(Point3d& out, const Point2d& refpix, const Point2d& tarpix, int refCam, int tarCam, - const MultiViewParams& mp) +bool triangulateMatch(Point3d& out, const Point2d& refpix, const Point2d& tarpix, int refCam, int tarCam, const MultiViewParams& mp) { Point3d refvect = mp.iCamArr[refCam] * refpix; refvect = refvect.normalize(); @@ -171,14 +168,11 @@ bool triangulateMatch(Point3d& out, const Point2d& refpix, const Point2d& tarpix return lineLineIntersect(&k, &l, &out, &lli1, &lli2, mp.CArr[refCam], refpoint, mp.CArr[tarCam], tarpoint); } -long initEstimate() -{ - return clock(); -} +long initEstimate() { return clock(); } void printfEstimate(int i, int n, long startTime) { - if((int)((float)i / ((float)n / 100.0)) != (int)((float)(i + 1) / ((float)n / 100.0))) + if ((int)((float)i / ((float)n / 100.0)) != (int)((float)(i + 1) / ((float)n / 100.0))) { int perc = (int)((float)i / ((float)n / 100.0)); @@ -197,15 +191,12 @@ void printfEstimate(int i, int n, long startTime) float d1 = (float)(t2 - startTime) / (float)CLOCKS_PER_SEC; int elapsedsec = (int)d1 - (int)floor(d1 / 60.0) * 60; - if(elapsedsec > 15) - ALICEVISION_LOG_INFO(perc << "% - remaining time: " << days << " days "<< ihour <<":" << iminu << ":" << iseco); - + if (elapsedsec > 15) + ALICEVISION_LOG_INFO(perc << "% - remaining time: " << days << " days " << ihour << ":" << iminu << ":" << iseco); } } -void finishEstimate() -{ -} +void finishEstimate() {} std::string formatElapsedTime(long t1) { @@ -216,9 +207,8 @@ std::string formatElapsedTime(long t1) int sec = (int)d1 - (int)floor(d1 / 60.0) * 60; int mil = (int)((d1 - (int)floor(d1)) * 1000); - std::string out = "Elapsed time: " + num2strTwoDecimal(min) + " minutes " + - num2strTwoDecimal(sec) + " seconds " + - num2strThreeDigits(mil) + " miliseconds\n"; + std::string out = + "Elapsed time: " + num2strTwoDecimal(min) + " minutes " + num2strTwoDecimal(sec) + " seconds " + num2strThreeDigits(mil) + " miliseconds\n"; return out; } @@ -234,7 +224,7 @@ bool checkPair(const Point3d& p, int rc, int tc, const MultiViewParams& mp, doub bool checkCamPairAngle(int rc, int tc, const MultiViewParams& mp, double minAng, double maxAng) { - if(rc == tc) + if (rc == tc) { return false; } @@ -302,13 +292,7 @@ void getHexahedronTriangles(Point3d tris[12][3], const Point3d hexah[8]) } // hexahedron format ... 0-3 frontal face, 4-7 back face -void getCamHexahedron(const Point3d& position, - const Matrix3x3& iCam, - int width, - int height, - float minDepth, - float maxDepth, - Point3d hexah[8]) +void getCamHexahedron(const Point3d& position, const Matrix3x3& iCam, int width, int height, float minDepth, float maxDepth, Point3d hexah[8]) { const float w = static_cast(width); const float h = static_cast(height); @@ -331,24 +315,24 @@ bool intersectsHexahedronHexahedron(const Point3d rchex[8], const Point3d tchex[ getHexahedronTriangles(rctris, rchex); getHexahedronTriangles(tctris, tchex); - for(int t1 = 0; t1 < 12; t1++) + for (int t1 = 0; t1 < 12; t1++) { - for(int t2 = 0; t2 < 12; t2++) + for (int t2 = 0; t2 < 12; t2++) { - if(interectsTriangleTriangle(rctris[t1], tctris[t2])) + if (interectsTriangleTriangle(rctris[t1], tctris[t2])) { return true; } } } - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { - if(isPointInHexahedron(rchex[i], tchex)) + if (isPointInHexahedron(rchex[i], tchex)) { return true; } - if(isPointInHexahedron(tchex[i], rchex)) + if (isPointInHexahedron(tchex[i], rchex)) { return true; } @@ -364,7 +348,7 @@ StaticVector* triangleHexahedronIntersection(Point3d& A, Point3d& B, Po StaticVector* out = new StaticVector(); out->reserve(40); - for(int i = 0; i < 12; i++) + for (int i = 0; i < 12; i++) { Point3d a = tris[i][0]; Point3d b = tris[i][1]; @@ -374,7 +358,7 @@ StaticVector* triangleHexahedronIntersection(Point3d& A, Point3d& B, Po Point3d i1, i2; bool ok = (bool)tri_tri_intersect_with_isectline(A.m, B.m, C.m, a.m, b.m, c.m, &coplanar, i1.m, i2.m); - if(ok) + if (ok) { out->push_back(i1); out->push_back(i2); @@ -391,14 +375,14 @@ StaticVector* lineSegmentHexahedronIntersection(const Point3d& linePoin StaticVector* out = new StaticVector(); out->reserve(40); - for(int i = 0; i < 12; i++) + for (int i = 0; i < 12; i++) { Point3d a = tris[i][0]; Point3d b = tris[i][1]; Point3d c = tris[i][2]; Point3d lpi; - if(isLineSegmentInTriangle(lpi, a, b, c, linePoint1, linePoint2)) + if (isLineSegmentInTriangle(lpi, a, b, c, linePoint1, linePoint2)) { out->push_back(lpi); } @@ -407,12 +391,9 @@ StaticVector* lineSegmentHexahedronIntersection(const Point3d& linePoin return out; } -void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const MultiViewParams& mp, int rc, - Point2d P[4], StaticVector& out) +void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const MultiViewParams& mp, int rc, Point2d P[4], StaticVector& out) { - const double maxd = std::max({(mp.CArr[rc] - A).size(), - (mp.CArr[rc] - B).size(), - (mp.CArr[rc] - C).size()}) * 1000.0f; + const double maxd = std::max({(mp.CArr[rc] - A).size(), (mp.CArr[rc] - B).size(), (mp.CArr[rc] - C).size()}) * 1000.0f; out.reserve(40); @@ -424,7 +405,7 @@ void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const Mul b = mp.CArr[rc] + (mp.iCamArr[rc] * P[0]).normalize() * maxd; c = mp.CArr[rc] + (mp.iCamArr[rc] * P[1]).normalize() * maxd; bool ok = (bool)tri_tri_intersect_with_isectline(A.m, B.m, C.m, a.m, b.m, c.m, &coplanar, i1.m, i2.m); - if(ok) + if (ok) { out.push_back(i1); out.push_back(i2); @@ -434,7 +415,7 @@ void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const Mul b = mp.CArr[rc] + (mp.iCamArr[rc] * P[1]).normalize() * maxd; c = mp.CArr[rc] + (mp.iCamArr[rc] * P[2]).normalize() * maxd; ok = (bool)tri_tri_intersect_with_isectline(A.m, B.m, C.m, a.m, b.m, c.m, &coplanar, i1.m, i2.m); - if(ok) + if (ok) { out.push_back(i1); out.push_back(i2); @@ -444,7 +425,7 @@ void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const Mul b = mp.CArr[rc] + (mp.iCamArr[rc] * P[2]).normalize() * maxd; c = mp.CArr[rc] + (mp.iCamArr[rc] * P[3]).normalize() * maxd; ok = (bool)tri_tri_intersect_with_isectline(A.m, B.m, C.m, a.m, b.m, c.m, &coplanar, i1.m, i2.m); - if(ok) + if (ok) { out.push_back(i1); out.push_back(i2); @@ -454,7 +435,7 @@ void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const Mul b = mp.CArr[rc] + (mp.iCamArr[rc] * P[3]).normalize() * maxd; c = mp.CArr[rc] + (mp.iCamArr[rc] * P[0]).normalize() * maxd; ok = (bool)tri_tri_intersect_with_isectline(A.m, B.m, C.m, a.m, b.m, c.m, &coplanar, i1.m, i2.m); - if(ok) + if (ok) { out.push_back(i1); out.push_back(i2); @@ -473,7 +454,7 @@ bool isPointInHexahedron(const Point3d& p, const Point3d* hexah) Point3d n = cross(a - b, b - c).normalize(); double d1 = orientedPointPlaneDistance(p, a, n); double d2 = orientedPointPlaneDistance(d, a, n); - if(d1 * d2 < 0.0) + if (d1 * d2 < 0.0) return false; a = hexah[0]; @@ -483,7 +464,7 @@ bool isPointInHexahedron(const Point3d& p, const Point3d* hexah) n = cross(a - b, b - c).normalize(); d1 = orientedPointPlaneDistance(p, a, n); d2 = orientedPointPlaneDistance(d, a, n); - if(d1 * d2 < 0.0) + if (d1 * d2 < 0.0) return false; a = hexah[1]; @@ -493,7 +474,7 @@ bool isPointInHexahedron(const Point3d& p, const Point3d* hexah) n = cross(a - b, b - c).normalize(); d1 = orientedPointPlaneDistance(p, a, n); d2 = orientedPointPlaneDistance(d, a, n); - if(d1 * d2 < 0.0) + if (d1 * d2 < 0.0) return false; a = hexah[2]; @@ -503,7 +484,7 @@ bool isPointInHexahedron(const Point3d& p, const Point3d* hexah) n = cross(a - b, b - c).normalize(); d1 = orientedPointPlaneDistance(p, a, n); d2 = orientedPointPlaneDistance(d, a, n); - if(d1 * d2 < 0.0) + if (d1 * d2 < 0.0) return false; a = hexah[0]; @@ -513,7 +494,7 @@ bool isPointInHexahedron(const Point3d& p, const Point3d* hexah) n = cross(a - b, b - c).normalize(); d1 = orientedPointPlaneDistance(p, a, n); d2 = orientedPointPlaneDistance(d, a, n); - if(d1 * d2 < 0.0) + if (d1 * d2 < 0.0) return false; a = hexah[4]; @@ -528,23 +509,23 @@ bool isPointInHexahedron(const Point3d& p, const Point3d* hexah) double computeHexahedronVolume(const Point3d* hexah) { - const double w = std::abs(hexah[1].x - hexah[0].x); - const double h = std::abs(hexah[3].y - hexah[0].y); - const double l = std::abs(hexah[4].z - hexah[0].z); + const double w = std::abs(hexah[1].x - hexah[0].x); + const double h = std::abs(hexah[3].y - hexah[0].y); + const double l = std::abs(hexah[4].z - hexah[0].z); - return (l * w * h); + return (l * w * h); } void inflateHexahedron(const Point3d hexahIn[8], Point3d hexahOut[8], float scale) { Point3d cg = Point3d(0.0f, 0.0f, 0.0f); - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { cg = cg + hexahIn[i]; } cg = cg / 8.0f; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { hexahOut[i] = cg + (hexahIn[i] - cg) * scale; } @@ -582,18 +563,17 @@ plot(x,y) */ -StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, - StaticVector*>* ptsCams) +StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, StaticVector*>* ptsCams) { StaticVector* nCamsPts = new StaticVector(); nCamsPts->reserve(mp.ncams); nCamsPts->resize_with(mp.ncams, 0); - for(int i = 0; i < ptsCams->size(); ++i) + for (int i = 0; i < ptsCams->size(); ++i) { - for(int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) + for (int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) { int rc = (*(*ptsCams)[i])[j]; - if((rc >= 0) && (rc < mp.ncams)) + if ((rc >= 0) && (rc < mp.ncams)) { (*nCamsPts)[rc]++; } @@ -606,19 +586,19 @@ StaticVector*>* convertObjectsCamsToCamsObjects(const MultiVie StaticVector*>* camsPts = new StaticVector*>(); camsPts->reserve(mp.ncams); - for(int rc = 0; rc < mp.ncams; ++rc) + for (int rc = 0; rc < mp.ncams; ++rc) { auto* camPts = new StaticVector(); camPts->reserve((*nCamsPts)[rc]); camsPts->push_back(camPts); } - for(int i = 0; i < ptsCams->size(); i++) + for (int i = 0; i < ptsCams->size(); i++) { - for(int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) + for (int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) { int rc = (*(*ptsCams)[i])[j]; - if((rc >= 0) && (rc < mp.ncams)) + if ((rc >= 0) && (rc < mp.ncams)) { (*camsPts)[rc]->push_back(i); } @@ -629,15 +609,14 @@ StaticVector*>* convertObjectsCamsToCamsObjects(const MultiVie return camsPts; } -StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, - StaticVector*>* ptsCams) +StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, StaticVector*>* ptsCams) { StaticVector* nCamsPts = new StaticVector(); nCamsPts->reserve(mp.ncams); nCamsPts->resize_with(mp.ncams, 0); - for(int i = 0; i < ptsCams->size(); i++) + for (int i = 0; i < ptsCams->size(); i++) { - for(int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) + for (int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) { int rc = (*(*ptsCams)[i])[j].x; (*nCamsPts)[rc]++; @@ -646,16 +625,16 @@ StaticVector*>* convertObjectsCamsToCamsObjects(const MultiV StaticVector*>* camsPts = new StaticVector*>(); camsPts->reserve(mp.ncams); - for(int rc = 0; rc < mp.ncams; rc++) + for (int rc = 0; rc < mp.ncams; rc++) { auto* camPts = new StaticVector(); camPts->reserve((*nCamsPts)[rc]); camsPts->push_back(camPts); } - for(int i = 0; i < ptsCams->size(); i++) + for (int i = 0; i < ptsCams->size(); i++) { - for(int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) + for (int j = 0; j < sizeOfStaticVector((*ptsCams)[i]); j++) { int rc = (*(*ptsCams)[i])[j].x; int value = (*(*ptsCams)[i])[j].y; @@ -690,23 +669,23 @@ StaticVector* computeVoxels(const Point3d* space, const Voxel& dimensio // printf("%i %i %i %i\n",dimensions.x,dimensions.y,dimensions.z,nvoxels,voxels->size()); int id = 0; - for(int xp = 0; xp < dimensions.x; xp++) + for (int xp = 0; xp < dimensions.x; xp++) { - for(int yp = 0; yp < dimensions.y; yp++) + for (int yp = 0; yp < dimensions.y; yp++) { - for(int zp = 0; zp < dimensions.z; zp++) + for (int zp = 0; zp < dimensions.z; zp++) { float x = (float)xp * stepx; float y = (float)yp * stepy; float z = (float)zp * stepz; - (*voxels)[id * 8 + 0] = ox + vx * x + vy * y + vz * z; // x, y, z - (*voxels)[id * 8 + 1] = ox + vx * (x + stepx) + vy * y + vz * z; // x+1, y, z - (*voxels)[id * 8 + 2] = ox + vx * (x + stepx) + vy * (y + stepy) + vz * z; // x+1, y+1, z - (*voxels)[id * 8 + 3] = ox + vx * x + vy * (y + stepy) + vz * z; // x, y+1, z - (*voxels)[id * 8 + 4] = ox + vx * x + vy * y + vz * (z + stepz); // x, y, z+1 - (*voxels)[id * 8 + 5] = ox + vx * (x + stepx) + vy * y + vz * (z + stepz); // x+1, y, z+1 - (*voxels)[id * 8 + 6] = ox + vx * (x + stepx) + vy * (y + stepy) + vz * (z + stepz); // x+1, y+1, z+1 - (*voxels)[id * 8 + 7] = ox + vx * x + vy * (y + stepy) + vz * (z + stepz); // x, y+1, z+1 + (*voxels)[id * 8 + 0] = ox + vx * x + vy * y + vz * z; // x, y, z + (*voxels)[id * 8 + 1] = ox + vx * (x + stepx) + vy * y + vz * z; // x+1, y, z + (*voxels)[id * 8 + 2] = ox + vx * (x + stepx) + vy * (y + stepy) + vz * z; // x+1, y+1, z + (*voxels)[id * 8 + 3] = ox + vx * x + vy * (y + stepy) + vz * z; // x, y+1, z + (*voxels)[id * 8 + 4] = ox + vx * x + vy * y + vz * (z + stepz); // x, y, z+1 + (*voxels)[id * 8 + 5] = ox + vx * (x + stepx) + vy * y + vz * (z + stepz); // x+1, y, z+1 + (*voxels)[id * 8 + 6] = ox + vx * (x + stepx) + vy * (y + stepy) + vz * (z + stepz); // x+1, y+1, z+1 + (*voxels)[id * 8 + 7] = ox + vx * x + vy * (y + stepy) + vz * (z + stepz); // x, y+1, z+1 id++; } } @@ -732,7 +711,7 @@ int findNSubstrsInString(const std::string& str, const std::string& val) int last = 0; int n = 0; int pos = str.find(val, last); - while(pos > -1) + while (pos > -1) { n++; last = pos + val.length(); @@ -766,13 +745,13 @@ std::string num2strThreeDigits(int index) { std::string ms; - if(index < 10) + if (index < 10) { ms = "00" + num2str(index); } else { - if(index < 100) + if (index < 100) { ms = "0" + num2str(index); } @@ -796,13 +775,13 @@ std::string num2strFourDecimal(int index) std::string num2strTwoDecimal(int index) { std::string ms; - if(index < 10) + if (index < 10) { ms = "0" + num2str(index); } else { - if(index < 100) + if (index < 100) { ms = num2str(index); } @@ -811,5 +790,5 @@ std::string num2strTwoDecimal(int index) return ms; } -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/common.hpp b/src/aliceVision/mvsUtils/common.hpp index 61fe72045d..2e63179535 100644 --- a/src/aliceVision/mvsUtils/common.hpp +++ b/src/aliceVision/mvsUtils/common.hpp @@ -19,20 +19,14 @@ namespace aliceVision { namespace mvsUtils { -bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1, Point2d linePoint2, - const MultiViewParams& mp, int camId); -bool getTarEpipolarDirectedLine(Point2d* pFromTar, Point2d* pToTar, Point2d refpix, int refCam, int tarCam, - const MultiViewParams& mp); -bool triangulateMatch(Point3d& out, const Point2d& refpix, const Point2d& tarpix, int refCam, int tarCam, - const MultiViewParams& mp); +bool get2dLineImageIntersection(Point2d* pFrom, Point2d* pTo, Point2d linePoint1, Point2d linePoint2, const MultiViewParams& mp, int camId); +bool getTarEpipolarDirectedLine(Point2d* pFromTar, Point2d* pToTar, Point2d refpix, int refCam, int tarCam, const MultiViewParams& mp); +bool triangulateMatch(Point3d& out, const Point2d& refpix, const Point2d& tarpix, int refCam, int tarCam, const MultiViewParams& mp); long initEstimate(); void printfEstimate(int i, int n, long startTime); void finishEstimate(); std::string formatElapsedTime(long t1); -inline void printfElapsedTime(long t1, const std::string& prefix = "") -{ - ALICEVISION_LOG_DEBUG(prefix << " " << formatElapsedTime(t1)); -} +inline void printfElapsedTime(long t1, const std::string& prefix = "") { ALICEVISION_LOG_DEBUG(prefix << " " << formatElapsedTime(t1)); } // SampleCnt calculates number of samples needed to be done int gaussKernelVoting(StaticVector* pts, float sigma); float angularDistnace(OrientedPoint* op1, OrientedPoint* op2); @@ -44,16 +38,13 @@ void getCamHexahedron(const Point3d& position, const Matrix3x3& iCam, int width, bool intersectsHexahedronHexahedron(const Point3d rchex[8], const Point3d tchex[8]); StaticVector* lineSegmentHexahedronIntersection(const Point3d& linePoint1, const Point3d& linePoint2, const Point3d hexah[8]); StaticVector* triangleHexahedronIntersection(Point3d& A, Point3d& B, Point3d& C, Point3d hexah[8]); -void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const MultiViewParams& mp, int rc, - Point2d P[4], StaticVector& out); -bool isPointInHexahedron(const Point3d &p, const Point3d* hexah); +void triangleRectangleIntersection(Point3d& A, Point3d& B, Point3d& C, const MultiViewParams& mp, int rc, Point2d P[4], StaticVector& out); +bool isPointInHexahedron(const Point3d& p, const Point3d* hexah); double computeHexahedronVolume(const Point3d* hexah); void inflateHexahedron(const Point3d hexahIn[8], Point3d hexahOut[8], float scale); -StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, - StaticVector*>* ptsCams); -StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, - StaticVector*>* ptsCams); +StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, StaticVector*>* ptsCams); +StaticVector*>* convertObjectsCamsToCamsObjects(const MultiViewParams& mp, StaticVector*>* ptsCams); StaticVector* computeVoxels(const Point3d* space, const Voxel& dimensions); std::vector createRandomArrayOfIntegers(const int size, const unsigned int seed = 0); @@ -66,5 +57,5 @@ std::string num2strThreeDigits(int index); std::string num2strFourDecimal(int index); std::string num2strTwoDecimal(int index); -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/fileIO.cpp b/src/aliceVision/mvsUtils/fileIO.cpp index cbf32b0956..743129b62c 100644 --- a/src/aliceVision/mvsUtils/fileIO.cpp +++ b/src/aliceVision/mvsUtils/fileIO.cpp @@ -18,335 +18,344 @@ namespace aliceVision { namespace mvsUtils { -std::string getFileNameFromViewId(const MultiViewParams& mp, int viewId, EFileType fileType, const std::string& customSuffix, int tileBeginX, int tileBeginY) +std::string getFileNameFromViewId(const MultiViewParams& mp, + int viewId, + EFileType fileType, + const std::string& customSuffix, + int tileBeginX, + int tileBeginY) { - std::string folder = mp._imagesFolder; - std::string suffix; - std::string tileSuffix; - std::string ext; + std::string folder = mp._imagesFolder; + std::string suffix; + std::string tileSuffix; + std::string ext; - if(tileBeginX >= 0 && tileBeginY >= 0) - { - tileSuffix = std::string("_" + std::to_string(tileBeginX) + "_" + std::to_string(tileBeginY)); - } - - switch(fileType) - { - case EFileType::P: - { - suffix = "_P"; - ext = "txt"; - break; - } - case EFileType::K: - { - suffix = "_K"; - ext = "txt"; - break; - } - case EFileType::iK: - { - suffix = "_iK"; - ext = "txt"; - break; - } - case EFileType::R: - { - suffix = "_R"; - ext = "txt"; - break; - } - case EFileType::iR: - { - suffix = "_iR"; - ext = "txt"; - break; - } - case EFileType::C: - { - suffix = "_C"; - ext = "txt"; - break; - } - case EFileType::iP: - { - suffix = "_iP"; - ext = "txt"; - break; - } - case EFileType::har: - { - suffix = "_har"; - ext = "bin"; - break; - } - case EFileType::prematched: - { - suffix = "_prematched"; - ext = "bin"; - break; - } - case EFileType::growed: - { - suffix = "_growed"; - ext = "bin"; - break; - } - case EFileType::occMap: - { - suffix = "_occMap"; - ext = "bin"; - break; - } - case EFileType::nearMap: - { - suffix = "_nearMap"; - ext = "bin"; - break; - } - case EFileType::op: - { - suffix = "_op"; - ext = "bin"; - break; - } - case EFileType::wshed: - { - suffix = "_wshed"; - ext = "bin"; - break; - } - case EFileType::img: - { - suffix = "_img"; - ext = "bin"; - break; - } - case EFileType::imgT: - { - suffix = "_imgT"; - ext = "bin"; - break; - } - case EFileType::graphCutMap: - { - suffix = "_graphCutMap"; - ext = "bin"; - break; - } - case EFileType::graphCutPts: - { - suffix = "_graphCutPts"; - ext = "bin"; - break; - } - case EFileType::growedMap: - { - suffix = "_growedMap"; - ext = "bin"; - break; - } - case EFileType::agreedMap: - { - suffix = "_agreedMap"; - ext = "bin"; - break; - } - case EFileType::agreedPts: - { - suffix = "_agreedPts"; - ext = "bin"; - break; - } - case EFileType::refinedMap: - { - suffix = "_refinedMap"; - ext = "bin"; - break; - } - case EFileType::seeds_sfm: - { - suffix = "_seeds_sfm"; - ext = "bin"; - break; - } - case EFileType::radial_disortion: - { - suffix = "_rd"; - ext = "bin"; - break; - } - case EFileType::graphCutMesh: - { - suffix = "_graphCutMesh"; - ext = "bin"; - break; - } - case EFileType::agreedMesh: - { - suffix = "_agreedMesh"; - ext = "bin"; - break; - } - case EFileType::nearestAgreedMap: - { - suffix = "_nearestAgreedMap"; - ext = "bin"; - break; - } - case EFileType::segPlanes: - { - suffix = "_segPlanes"; - ext = "bin"; - break; - } - case EFileType::agreedVisMap: - { - suffix = "_agreedVisMap"; - ext = "bin"; - break; - } - case EFileType::diskSizeMap: - { - suffix = "_diskSizeMap"; - ext = "bin"; - break; - } - case EFileType::depthMap: - { - folder = mp.getDepthMapsFolder(); - suffix = "_depthMap"; - ext = "exr"; - break; - } - case EFileType::depthMapFiltered: - { - folder = mp.getDepthMapsFilterFolder(); - suffix = "_depthMap"; - ext = "exr"; - break; - } - case EFileType::simMap: - { - folder = mp.getDepthMapsFolder(); - suffix = "_simMap"; - ext = "exr"; - break; - } - case EFileType::simMapFiltered: - { - folder = mp.getDepthMapsFilterFolder(); - suffix = "_simMap"; - ext = "exr"; - break; - } - case EFileType::normalMap: - { - folder = mp.getDepthMapsFolder(); - suffix = "_normalMap"; - ext = "exr"; - break; - } - case EFileType::normalMapFiltered: - { - folder = mp.getDepthMapsFilterFolder(); - suffix = "_normalMap"; - ext = "exr"; - break; - } - case EFileType::thicknessMap: - { - folder = mp.getDepthMapsFolder(); - suffix = "_thicknessMap"; - ext = "exr"; - break; - } - case EFileType::pixSizeMap: - { - folder = mp.getDepthMapsFolder(); - suffix = "_pixSizeMap"; - ext = "exr"; - break; - } - case EFileType::mapPtsTmp: - { - suffix = "_mapPts"; - ext = "tmp"; - break; - } - case EFileType::mapPtsSimsTmp: - { - suffix = "_mapPtsSims"; - ext = "tmp"; - break; - } - case EFileType::camMap: - { - suffix = "_camMap"; - ext = "bin"; - break; - } - case EFileType::nmodMap: - { - folder = mp.getDepthMapsFilterFolder(); - suffix = "_nmodMap"; - ext = "png"; - break; - } - case EFileType::D: - { - suffix = "_D"; - ext = "txt"; - break; - } - case EFileType::volume: - { - folder = mp.getDepthMapsFolder(); - suffix = "_volume"; - ext = "abc"; - break; - } - case EFileType::volumeCross: - { - folder = mp.getDepthMapsFolder(); - suffix = "_volumeCross"; - ext = "abc"; - break; - } - case EFileType::volumeTopographicCut: - { - folder = mp.getDepthMapsFolder(); - suffix = "_volumeTopographicCut"; - ext = "abc"; - break; - } - case EFileType::stats9p: - { - folder = mp.getDepthMapsFolder(); - suffix = "_9p"; - ext = "csv"; - break; - } - case EFileType::tilePattern: - { - folder = mp.getDepthMapsFolder(); - suffix = "_tilePattern"; - ext = "obj"; - break; - } - } + if (tileBeginX >= 0 && tileBeginY >= 0) + { + tileSuffix = std::string("_" + std::to_string(tileBeginX) + "_" + std::to_string(tileBeginY)); + } + + switch (fileType) + { + case EFileType::P: + { + suffix = "_P"; + ext = "txt"; + break; + } + case EFileType::K: + { + suffix = "_K"; + ext = "txt"; + break; + } + case EFileType::iK: + { + suffix = "_iK"; + ext = "txt"; + break; + } + case EFileType::R: + { + suffix = "_R"; + ext = "txt"; + break; + } + case EFileType::iR: + { + suffix = "_iR"; + ext = "txt"; + break; + } + case EFileType::C: + { + suffix = "_C"; + ext = "txt"; + break; + } + case EFileType::iP: + { + suffix = "_iP"; + ext = "txt"; + break; + } + case EFileType::har: + { + suffix = "_har"; + ext = "bin"; + break; + } + case EFileType::prematched: + { + suffix = "_prematched"; + ext = "bin"; + break; + } + case EFileType::growed: + { + suffix = "_growed"; + ext = "bin"; + break; + } + case EFileType::occMap: + { + suffix = "_occMap"; + ext = "bin"; + break; + } + case EFileType::nearMap: + { + suffix = "_nearMap"; + ext = "bin"; + break; + } + case EFileType::op: + { + suffix = "_op"; + ext = "bin"; + break; + } + case EFileType::wshed: + { + suffix = "_wshed"; + ext = "bin"; + break; + } + case EFileType::img: + { + suffix = "_img"; + ext = "bin"; + break; + } + case EFileType::imgT: + { + suffix = "_imgT"; + ext = "bin"; + break; + } + case EFileType::graphCutMap: + { + suffix = "_graphCutMap"; + ext = "bin"; + break; + } + case EFileType::graphCutPts: + { + suffix = "_graphCutPts"; + ext = "bin"; + break; + } + case EFileType::growedMap: + { + suffix = "_growedMap"; + ext = "bin"; + break; + } + case EFileType::agreedMap: + { + suffix = "_agreedMap"; + ext = "bin"; + break; + } + case EFileType::agreedPts: + { + suffix = "_agreedPts"; + ext = "bin"; + break; + } + case EFileType::refinedMap: + { + suffix = "_refinedMap"; + ext = "bin"; + break; + } + case EFileType::seeds_sfm: + { + suffix = "_seeds_sfm"; + ext = "bin"; + break; + } + case EFileType::radial_disortion: + { + suffix = "_rd"; + ext = "bin"; + break; + } + case EFileType::graphCutMesh: + { + suffix = "_graphCutMesh"; + ext = "bin"; + break; + } + case EFileType::agreedMesh: + { + suffix = "_agreedMesh"; + ext = "bin"; + break; + } + case EFileType::nearestAgreedMap: + { + suffix = "_nearestAgreedMap"; + ext = "bin"; + break; + } + case EFileType::segPlanes: + { + suffix = "_segPlanes"; + ext = "bin"; + break; + } + case EFileType::agreedVisMap: + { + suffix = "_agreedVisMap"; + ext = "bin"; + break; + } + case EFileType::diskSizeMap: + { + suffix = "_diskSizeMap"; + ext = "bin"; + break; + } + case EFileType::depthMap: + { + folder = mp.getDepthMapsFolder(); + suffix = "_depthMap"; + ext = "exr"; + break; + } + case EFileType::depthMapFiltered: + { + folder = mp.getDepthMapsFilterFolder(); + suffix = "_depthMap"; + ext = "exr"; + break; + } + case EFileType::simMap: + { + folder = mp.getDepthMapsFolder(); + suffix = "_simMap"; + ext = "exr"; + break; + } + case EFileType::simMapFiltered: + { + folder = mp.getDepthMapsFilterFolder(); + suffix = "_simMap"; + ext = "exr"; + break; + } + case EFileType::normalMap: + { + folder = mp.getDepthMapsFolder(); + suffix = "_normalMap"; + ext = "exr"; + break; + } + case EFileType::normalMapFiltered: + { + folder = mp.getDepthMapsFilterFolder(); + suffix = "_normalMap"; + ext = "exr"; + break; + } + case EFileType::thicknessMap: + { + folder = mp.getDepthMapsFolder(); + suffix = "_thicknessMap"; + ext = "exr"; + break; + } + case EFileType::pixSizeMap: + { + folder = mp.getDepthMapsFolder(); + suffix = "_pixSizeMap"; + ext = "exr"; + break; + } + case EFileType::mapPtsTmp: + { + suffix = "_mapPts"; + ext = "tmp"; + break; + } + case EFileType::mapPtsSimsTmp: + { + suffix = "_mapPtsSims"; + ext = "tmp"; + break; + } + case EFileType::camMap: + { + suffix = "_camMap"; + ext = "bin"; + break; + } + case EFileType::nmodMap: + { + folder = mp.getDepthMapsFilterFolder(); + suffix = "_nmodMap"; + ext = "png"; + break; + } + case EFileType::D: + { + suffix = "_D"; + ext = "txt"; + break; + } + case EFileType::volume: + { + folder = mp.getDepthMapsFolder(); + suffix = "_volume"; + ext = "abc"; + break; + } + case EFileType::volumeCross: + { + folder = mp.getDepthMapsFolder(); + suffix = "_volumeCross"; + ext = "abc"; + break; + } + case EFileType::volumeTopographicCut: + { + folder = mp.getDepthMapsFolder(); + suffix = "_volumeTopographicCut"; + ext = "abc"; + break; + } + case EFileType::stats9p: + { + folder = mp.getDepthMapsFolder(); + suffix = "_9p"; + ext = "csv"; + break; + } + case EFileType::tilePattern: + { + folder = mp.getDepthMapsFolder(); + suffix = "_tilePattern"; + ext = "obj"; + break; + } + } - const std::string fileName = folder + std::to_string(viewId) + suffix + customSuffix + tileSuffix + "." + ext; + const std::string fileName = folder + std::to_string(viewId) + suffix + customSuffix + tileSuffix + "." + ext; - return fileName; + return fileName; } -std::string getFileNameFromIndex(const MultiViewParams& mp, int index, EFileType mv_file_type, const std::string& customSuffix, int tileBeginX, int tileBeginY) +std::string getFileNameFromIndex(const MultiViewParams& mp, + int index, + EFileType mv_file_type, + const std::string& customSuffix, + int tileBeginX, + int tileBeginY) { return getFileNameFromViewId(mp, mp.getViewId(index), mv_file_type, customSuffix, tileBeginX, tileBeginY); } - Matrix3x4 load3x4MatrixFromFile(std::istream& in) { Matrix3x4 m; @@ -373,12 +382,11 @@ Matrix3x4 load3x4MatrixFromFile(std::istream& in) } template -void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Image& img, - image::EImageColorSpace colorspace, ECorrectEV correctEV) +void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Image& img, image::EImageColorSpace colorspace, ECorrectEV correctEV) { // check image size - auto checkImageSize = [&path, &mp, camId, &img](){ - if((mp.getOriginalWidth(camId) != img.Width()) || (mp.getOriginalHeight(camId) != img.Height())) + auto checkImageSize = [&path, &mp, camId, &img]() { + if ((mp.getOriginalWidth(camId) != img.Width()) || (mp.getOriginalHeight(camId) != img.Height())) { std::stringstream s; s << "Bad image dimension for camera : " << camId << "\n"; @@ -389,7 +397,7 @@ void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Im } }; - if(correctEV == ECorrectEV::NO_CORRECTION) + if (correctEV == ECorrectEV::NO_CORRECTION) { image::readImage(path, img, colorspace); checkImageSize(); @@ -404,7 +412,7 @@ void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Im float exposureCompensation = metadata.get_float("AliceVision:EVComp", -1); - if(exposureCompensation == -1) + if (exposureCompensation == -1) { exposureCompensation = 1.0f; ALICEVISION_LOG_INFO("Cannot compensate exposure. PrepareDenseScene needs to be update"); @@ -413,7 +421,7 @@ void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Im { ALICEVISION_LOG_INFO(" exposure compensation for image " << camId + 1 << ": " << exposureCompensation); - for(std::size_t i = 0; i < img.size(); ++i) + for (std::size_t i = 0; i < img.size(); ++i) { img(i)[0] *= exposureCompensation; img(i)[1] *= exposureCompensation; @@ -427,7 +435,7 @@ void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Im // scale choosed by the user and apply during the process const int processScale = mp.getProcessDownscale(); - if(processScale > 1) + if (processScale > 1) { ALICEVISION_LOG_DEBUG("Downscale (x" << processScale << ") image: " << mp.getViewId(camId) << "."); Image bmpr; @@ -436,12 +444,18 @@ void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Im } } -template void loadImage>(const std::string& path, const MultiViewParams& mp, int camId, +template void loadImage>(const std::string& path, + const MultiViewParams& mp, + int camId, image::Image& img, - image::EImageColorSpace colorspace, ECorrectEV correctEV); -template void loadImage>(const std::string& path, const MultiViewParams& mp, int camId, + image::EImageColorSpace colorspace, + ECorrectEV correctEV); +template void loadImage>(const std::string& path, + const MultiViewParams& mp, + int camId, image::Image& img, - image::EImageColorSpace colorspace, ECorrectEV correctEV); + image::EImageColorSpace colorspace, + ECorrectEV correctEV); -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/fileIO.hpp b/src/aliceVision/mvsUtils/fileIO.hpp index 9afb6be379..fc92451a44 100644 --- a/src/aliceVision/mvsUtils/fileIO.hpp +++ b/src/aliceVision/mvsUtils/fileIO.hpp @@ -59,8 +59,7 @@ std::string getFileNameFromIndex(const MultiViewParams& mp, Matrix3x4 load3x4MatrixFromFile(std::istream& in); template -void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Image& img, - image::EImageColorSpace colorspace, ECorrectEV correctEV); +void loadImage(const std::string& path, const MultiViewParams& mp, int camId, Image& img, image::EImageColorSpace colorspace, ECorrectEV correctEV); -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/mapIO.cpp b/src/aliceVision/mvsUtils/mapIO.cpp index 30cba3f714..1c87ba831b 100644 --- a/src/aliceVision/mvsUtils/mapIO.cpp +++ b/src/aliceVision/mvsUtils/mapIO.cpp @@ -26,17 +26,34 @@ namespace mvsUtils { */ std::string getMapNameFromFileType(EFileType fileType) { - switch(fileType) - { - case EFileType::depthMap: return "depth map"; break; - case EFileType::depthMapFiltered: return "filtered depth map"; break; - case EFileType::simMap: return "similarity map"; break; - case EFileType::simMapFiltered: return "filtered similarity map"; break; - case EFileType::normalMap: return "normal map"; break; - case EFileType::normalMapFiltered: return "filtered normal map"; break; - case EFileType::thicknessMap: return "thickness map"; break; - case EFileType::pixSizeMap: return "pixSize map"; break; - default: break; + switch (fileType) + { + case EFileType::depthMap: + return "depth map"; + break; + case EFileType::depthMapFiltered: + return "filtered depth map"; + break; + case EFileType::simMap: + return "similarity map"; + break; + case EFileType::simMapFiltered: + return "filtered similarity map"; + break; + case EFileType::normalMap: + return "normal map"; + break; + case EFileType::normalMapFiltered: + return "filtered normal map"; + break; + case EFileType::thicknessMap: + return "thickness map"; + break; + case EFileType::pixSizeMap: + return "pixSize map"; + break; + default: + break; } return "unknown map"; } @@ -52,23 +69,23 @@ void getRoiFromMetadata(const std::string& mapTilePath, ROI& out_roi) const auto roiBeginXIt = metadata.find("AliceVision:roiBeginX"); const auto roiBeginYIt = metadata.find("AliceVision:roiBeginY"); - const auto roiEndXIt = metadata.find("AliceVision:roiEndX"); - const auto roiEndYIt = metadata.find("AliceVision:roiEndY"); + const auto roiEndXIt = metadata.find("AliceVision:roiEndX"); + const auto roiEndYIt = metadata.find("AliceVision:roiEndY"); - if(roiBeginXIt != metadata.end() && roiBeginXIt->type() == oiio::TypeDesc::INT) + if (roiBeginXIt != metadata.end() && roiBeginXIt->type() == oiio::TypeDesc::INT) out_roi.x.begin = roiBeginXIt->get_int(); - if(roiBeginYIt != metadata.end() && roiBeginYIt->type() == oiio::TypeDesc::INT) + if (roiBeginYIt != metadata.end() && roiBeginYIt->type() == oiio::TypeDesc::INT) out_roi.y.begin = roiBeginYIt->get_int(); - if(roiEndXIt != metadata.end() && roiEndXIt->type() == oiio::TypeDesc::INT) + if (roiEndXIt != metadata.end() && roiEndXIt->type() == oiio::TypeDesc::INT) out_roi.x.end = roiEndXIt->get_int(); - if(roiEndYIt != metadata.end() && roiEndYIt->type() == oiio::TypeDesc::INT) + if (roiEndYIt != metadata.end() && roiEndYIt->type() == oiio::TypeDesc::INT) out_roi.y.end = roiEndYIt->get_int(); // invalid or no roi metadata - if((out_roi.x.begin < 0) || (out_roi.y.begin < 0) || (out_roi.x.end <= 0) || (out_roi.y.end <= 0)) + if ((out_roi.x.begin < 0) || (out_roi.y.begin < 0) || (out_roi.x.end <= 0) || (out_roi.y.end <= 0)) { ALICEVISION_THROW_ERROR("Cannot find ROI information in file: " << fs::path(mapTilePath).filename().string()); } @@ -83,21 +100,21 @@ void getTileParamsFromMetadata(const std::string& mapTilePath, TileParams& out_t { const oiio::ParamValueList metadata = image::readImageMetadata(mapTilePath); - const auto tileWidthIt = metadata.find("AliceVision:tileBufferWidth"); - const auto tileHeightIt = metadata.find("AliceVision:tileBufferHeight"); + const auto tileWidthIt = metadata.find("AliceVision:tileBufferWidth"); + const auto tileHeightIt = metadata.find("AliceVision:tileBufferHeight"); const auto tilePaddingIt = metadata.find("AliceVision:tilePadding"); - if(tileWidthIt != metadata.end() && tileWidthIt->type() == oiio::TypeDesc::INT) + if (tileWidthIt != metadata.end() && tileWidthIt->type() == oiio::TypeDesc::INT) out_tileParams.bufferWidth = tileWidthIt->get_int(); - if(tileHeightIt != metadata.end() && tileHeightIt->type() == oiio::TypeDesc::INT) + if (tileHeightIt != metadata.end() && tileHeightIt->type() == oiio::TypeDesc::INT) out_tileParams.bufferHeight = tileHeightIt->get_int(); - if(tilePaddingIt != metadata.end() && tilePaddingIt->type() == oiio::TypeDesc::INT) + if (tilePaddingIt != metadata.end() && tilePaddingIt->type() == oiio::TypeDesc::INT) out_tileParams.padding = tilePaddingIt->get_int(); // invalid or no tile metadata - if((out_tileParams.bufferWidth <= 0) || (out_tileParams.bufferHeight <= 0) || (out_tileParams.padding < 0)) + if ((out_tileParams.bufferWidth <= 0) || (out_tileParams.bufferHeight <= 0) || (out_tileParams.padding < 0)) { ALICEVISION_THROW_ERROR("Cannot find tile parameters in file: " << fs::path(mapTilePath).filename().string()); } @@ -120,14 +137,14 @@ void getTilePathList(int rc, const fs::path mapPath(getFileNameFromIndex(mp, rc, fileType, customSuffix)); const fs::path mapDirectory(mapPath.parent_path()); - if(!is_directory(mapDirectory)) + if (!is_directory(mapDirectory)) ALICEVISION_THROW_ERROR("Cannot find " << getMapNameFromFileType(fileType) << " directory (rc: " << rc << ")."); const boost::regex mapPattern(mapPath.stem().string() + "_\\d+_\\d+" + mapPath.extension().string()); - for(auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(mapDirectory), {})) + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(mapDirectory), {})) { - if(boost::regex_match(entry.path().filename().string(), mapPattern)) + if (boost::regex_match(entry.path().filename().string(), mapPattern)) out_mapTilePathList.push_back(entry.path().string()); } } @@ -148,12 +165,8 @@ void getTilePathList(int rc, * @param lu left-up corner of the intersection area in the tile coordinate system * @param in_tileMap image of the tile */ -template -void weightTileBorder(int a, int b, int c, int d, - int borderWidth, - int borderHeight, - const Point2d& lu, - image::Image& in_tileMap) +template +void weightTileBorder(int a, int b, int c, int d, int borderWidth, int borderHeight, const Point2d& lu, image::Image& in_tileMap) { const Point2d rd = lu + Point2d(borderWidth, borderHeight); @@ -167,9 +180,9 @@ void weightTileBorder(int a, int b, int c, int d, const double borderWidth_m = borderWidth - 2.0 * margin; const double borderHeight_m = borderHeight - 2.0 * margin; - for(int x = lu.x; x < endX; ++x) + for (int x = lu.x; x < endX; ++x) { - for(int y = lu.y; y < endY; ++y) + for (int y = lu.y; y < endY; ++y) { // bilinear interpolation const float r_x = clamp((rd_m.x - x) / borderWidth_m, 0.0, 1.0); @@ -195,7 +208,7 @@ void weightTileBorder(int a, int b, int c, int d, * @param[in] in_tileMap the tile map to add * @param[in,out] inout_map the full output map */ -template +template void addSingleTileMapWeighted(int rc, const MultiViewParams& mp, const TileParams& tileParams, @@ -219,7 +232,7 @@ void addSingleTileMapWeighted(int rc, const bool lastRow = (roi.y.end == mp.getHeight(rc)); // weight the top left corner - if(!firstColumn || !firstRow) + if (!firstColumn || !firstRow) { const Point2d lu(0, 0); const int b = (firstRow) ? 1 : 0; @@ -228,7 +241,7 @@ void addSingleTileMapWeighted(int rc, } // weight the bottom left corner - if(!firstColumn || !lastRow) + if (!firstColumn || !lastRow) { const Point2d lu(0, tileHeight - tilePadding); const int a = (firstColumn) ? 1 : 0; @@ -237,7 +250,7 @@ void addSingleTileMapWeighted(int rc, } // weight the top right corner - if(!lastColumn || !firstRow) + if (!lastColumn || !firstRow) { const Point2d lu(tileWidth - tilePadding, 0); const int a = (firstRow) ? 1 : 0; @@ -246,7 +259,7 @@ void addSingleTileMapWeighted(int rc, } // weight the bottom right corner - if(!lastColumn || !lastRow) + if (!lastColumn || !lastRow) { const Point2d lu(tileWidth - tilePadding, tileHeight - tilePadding); const int b = (lastColumn) ? 1 : 0; @@ -255,37 +268,37 @@ void addSingleTileMapWeighted(int rc, } // weight the top border - if(!firstRow) + if (!firstRow) { const Point2d lu(tilePadding, 0); weightTileBorder(0, 0, 1, 1, tileWidth - 2 * tilePadding, tilePadding, lu, in_tileMap); } // weight the bottom border - if(!lastRow) + if (!lastRow) { const Point2d lu(tilePadding, tileHeight - tilePadding); weightTileBorder(1, 1, 0, 0, tileWidth - 2 * tilePadding, tilePadding, lu, in_tileMap); } // weight the left border - if(!firstColumn) + if (!firstColumn) { const Point2d lu(0, tilePadding); weightTileBorder(0, 1, 1, 0, tilePadding, tileHeight - 2 * tilePadding, lu, in_tileMap); } // weight the right border - if(!lastColumn) + if (!lastColumn) { const Point2d lu(tileWidth - tilePadding, tilePadding); weightTileBorder(1, 0, 0, 1, tilePadding, tileHeight - 2 * tilePadding, lu, in_tileMap); } // add weighted tile to the depth/sim map - for(int x = downscaledRoi.x.begin; x < downscaledRoi.x.end; ++x) + for (int x = downscaledRoi.x.begin; x < downscaledRoi.x.end; ++x) { - for(int y = downscaledRoi.y.begin; y < downscaledRoi.y.end; ++y) + for (int y = downscaledRoi.y.begin; y < downscaledRoi.y.end; ++y) { const int tx = x - downscaledRoi.x.begin; const int ty = y - downscaledRoi.y.begin; @@ -295,7 +308,7 @@ void addSingleTileMapWeighted(int rc, } } -template +template void readMapFromFileOrTiles(int rc, const MultiViewParams& mp, EFileType fileType, @@ -306,13 +319,13 @@ void readMapFromFileOrTiles(int rc, { // assert scale & step assert(scale > 0); - assert(step > 0); + assert(step > 0); // single file fullsize map path const std::string mapPath = getFileNameFromIndex(mp, rc, fileType, customSuffix); // check single file fullsize map exists - if(fs::exists(mapPath)) + if (fs::exists(mapPath)) { ALICEVISION_LOG_TRACE("Load depth map (full image): " << mapPath << ", scale: " << scale << ", step: " << step); // read single file fullsize map @@ -325,25 +338,26 @@ void readMapFromFileOrTiles(int rc, const ROI imageRoi(Range(0, mp.getWidth(rc)), Range(0, mp.getHeight(rc))); const int scaleStep = scale * step; - const int width = divideRoundUp(mp.getWidth(rc) , scaleStep); + const int width = divideRoundUp(mp.getWidth(rc), scaleStep); const int height = divideRoundUp(mp.getHeight(rc), scaleStep); // the output full map - out_map.resize(width, height, true, T(0.f)); // should be initialized, additive process + out_map.resize(width, height, true, T(0.f)); // should be initialized, additive process // get tile map path list for the given R camera std::vector mapTilePathList; getTilePathList(rc, mp, fileType, customSuffix, mapTilePathList); - if(mapTilePathList.empty()) + if (mapTilePathList.empty()) { // map can be empty ALICEVISION_LOG_INFO("Cannot find any " << getMapNameFromFileType(fileType) << " tile file (rc: " << rc << ")."); - return; // nothing to do, already initialized + return; // nothing to do, already initialized } else { - ALICEVISION_LOG_TRACE("Load depth map from " << mapTilePathList.size() << " tiles. First tile: " << mapTilePathList[0] << ", scale: " << scale << ", step: " << step); + ALICEVISION_LOG_TRACE("Load depth map from " << mapTilePathList.size() << " tiles. First tile: " << mapTilePathList[0] << ", scale: " << scale + << ", step: " << step); } // get tileParams from first tile file metadata @@ -353,18 +367,18 @@ void readMapFromFileOrTiles(int rc, // get tile roi list from each file metadata std::vector tileRoiList; tileRoiList.resize(mapTilePathList.size()); - for(size_t i = 0; i < mapTilePathList.size(); ++i) + for (size_t i = 0; i < mapTilePathList.size(); ++i) { getRoiFromMetadata(mapTilePathList.at(i), tileRoiList.at(i)); } // read and add each tile to the full map - for(size_t i = 0; i < tileRoiList.size(); ++i) + for (size_t i = 0; i < tileRoiList.size(); ++i) { const ROI roi = intersect(tileRoiList.at(i), imageRoi); const std::string mapTilePath = getFileNameFromIndex(mp, rc, fileType, customSuffix, roi.x.begin, roi.y.begin); - if(roi.isEmpty()) + if (roi.isEmpty()) continue; try @@ -376,14 +390,14 @@ void readMapFromFileOrTiles(int rc, // add tile to the full map addSingleTileMapWeighted(rc, mp, tileParams, roi, scaleStep, tileMap, out_map); } - catch(const std::exception& e) + catch (const std::exception& e) { ALICEVISION_LOG_WARNING("Cannot find map (rc: " << rc << "): " << fs::path(mapTilePath).filename().string()); } } } -template +template void writeMapToFileOrTile(int rc, const MultiViewParams& mp, const EFileType fileType, @@ -396,19 +410,19 @@ void writeMapToFileOrTile(int rc, { // assert scale & step assert(scale > 0); - assert(step > 0); + assert(step > 0); const int scaleStep = scale * step; // get image dimensions at scale / stepXY - const int imageWidth = divideRoundUp(mp.getWidth(rc) , scaleStep); + const int imageWidth = divideRoundUp(mp.getWidth(rc), scaleStep); const int imageHeight = divideRoundUp(mp.getHeight(rc), scaleStep); // get downscaled ROI const ROI downscaledROI = downscaleROI(roi, scaleStep); // check input map dimensions - assert(in_map.Width() == downscaledROI.width() && in_map.Width() <= imageWidth); + assert(in_map.Width() == downscaledROI.width() && in_map.Width() <= imageWidth); assert(in_map.Height() == downscaledROI.height() && in_map.Height() <= imageHeight); // set OIIO ROI for map writing @@ -421,7 +435,7 @@ void writeMapToFileOrTile(int rc, // output map path std::string mapPath; - if(downscaledROI.width() != imageWidth || downscaledROI.height() != imageHeight) // is a tile + if (downscaledROI.width() != imageWidth || downscaledROI.height() != imageHeight) // is a tile { // tiled map mapPath = getFileNameFromIndex(mp, rc, fileType, customSuffix, roi.x.begin, roi.y.begin); @@ -440,17 +454,17 @@ void writeMapToFileOrTile(int rc, // roi metadata { - metadata.push_back(oiio::ParamValue("AliceVision:roiBeginX", int(roi.x.begin))); - metadata.push_back(oiio::ParamValue("AliceVision:roiBeginY", int(roi.y.begin))); - metadata.push_back(oiio::ParamValue("AliceVision:roiEndX", int(roi.x.end))); - metadata.push_back(oiio::ParamValue("AliceVision:roiEndY", int(roi.y.end))); + metadata.push_back(oiio::ParamValue("AliceVision:roiBeginX", int(roi.x.begin))); + metadata.push_back(oiio::ParamValue("AliceVision:roiBeginY", int(roi.y.begin))); + metadata.push_back(oiio::ParamValue("AliceVision:roiEndX", int(roi.x.end))); + metadata.push_back(oiio::ParamValue("AliceVision:roiEndY", int(roi.y.end))); } // tile params metadata { - metadata.push_back(oiio::ParamValue("AliceVision:tileBufferWidth", tileParams.bufferWidth)); + metadata.push_back(oiio::ParamValue("AliceVision:tileBufferWidth", tileParams.bufferWidth)); metadata.push_back(oiio::ParamValue("AliceVision:tileBufferHeight", tileParams.bufferHeight)); - metadata.push_back(oiio::ParamValue("AliceVision:tilePadding", tileParams.padding)); + metadata.push_back(oiio::ParamValue("AliceVision:tilePadding", tileParams.padding)); } // projection matrix metadata @@ -464,7 +478,7 @@ void writeMapToFileOrTile(int rc, Point3d C = mp.CArr[rc]; Matrix3x3 iP = mp.iCamArr[rc]; - if(scaleStep > 1) + if (scaleStep > 1) { Matrix3x4 P = mp.camArr[rc]; for (int i = 0; i < 8; ++i) @@ -472,10 +486,10 @@ void writeMapToFileOrTile(int rc, Matrix3x3 K, iK; Matrix3x3 R, iR; - P.decomposeProjectionMatrix(K, R, C); // replace C + P.decomposeProjectionMatrix(K, R, C); // replace C iK = K.inverse(); iR = R.inverse(); - iP = iR * iK; // replace iP + iP = iR * iK; // replace iP } metadata.push_back(oiio::ParamValue("AliceVision:CArr", oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::VEC3), 1, C.m)); @@ -483,17 +497,17 @@ void writeMapToFileOrTile(int rc, } // min/max/nb depth metadata (for depth map only) - if((fileType == EFileType::depthMap) || (fileType == EFileType::depthMapFiltered)) + if ((fileType == EFileType::depthMap) || (fileType == EFileType::depthMapFiltered)) { const int nbDepthValues = std::count_if(in_map.data(), in_map.data() + in_map.size(), [](float v) { return v > 0.0f; }); float maxDepth = -1.0f; float minDepth = std::numeric_limits::max(); - for(int i = 0; i < in_map.size(); ++i) + for (int i = 0; i < in_map.size(); ++i) { const float depth = in_map(i); - if(depth <= -1.0f) + if (depth <= -1.0f) continue; maxDepth = std::max(maxDepth, depth); @@ -505,13 +519,12 @@ void writeMapToFileOrTile(int rc, metadata.push_back(oiio::ParamValue("AliceVision:maxDepth", maxDepth)); } - // set colorspace image::ImageWriteOptions mapWriteOptions; mapWriteOptions.toColorSpace(image::EImageColorSpace::NO_CONVERSION); // set storage type - if((fileType == EFileType::depthMap) || (fileType == EFileType::depthMapFiltered)) + if ((fileType == EFileType::depthMap) || (fileType == EFileType::depthMapFiltered)) { mapWriteOptions.storageDataType(image::EStorageDataType::Float); } @@ -521,12 +534,7 @@ void writeMapToFileOrTile(int rc, } // write map - image::writeImage(mapPath, - in_map, - mapWriteOptions, - metadata, - displayRoi, - pixelRoi); + image::writeImage(mapPath, in_map, mapWriteOptions, metadata, displayRoi, pixelRoi); } void addTileMapWeighted(int rc, @@ -541,23 +549,23 @@ void addTileMapWeighted(int rc, } void readMap(int rc, - const MultiViewParams& mp, - const EFileType fileType, - image::Image& out_map, - int scale, - int step, - const std::string& customSuffix) + const MultiViewParams& mp, + const EFileType fileType, + image::Image& out_map, + int scale, + int step, + const std::string& customSuffix) { readMapFromFileOrTiles(rc, mp, fileType, out_map, scale, step, customSuffix); } void readMap(int rc, - const MultiViewParams& mp, - const EFileType fileType, - image::Image& out_map, - int scale, - int step, - const std::string& customSuffix) + const MultiViewParams& mp, + const EFileType fileType, + image::Image& out_map, + int scale, + int step, + const std::string& customSuffix) { readMapFromFileOrTiles(rc, mp, fileType, out_map, scale, step, customSuffix); } @@ -588,11 +596,7 @@ void writeMap(int rc, writeMapToFileOrTile(rc, mp, fileType, tileParams, roi, in_map, scale, step, customSuffix); } -unsigned long getNbDepthValuesFromDepthMap(int rc, - const MultiViewParams& mp, - int scale, - int step, - const std::string& customSuffix) +unsigned long getNbDepthValuesFromDepthMap(int rc, const MultiViewParams& mp, int scale, int step, const std::string& customSuffix) { const std::string depthMapPath = getFileNameFromIndex(mp, rc, EFileType::depthMapFiltered, customSuffix); int nbDepthValues = -1; @@ -600,20 +604,20 @@ unsigned long getNbDepthValuesFromDepthMap(int rc, bool fromTiles = false; // get nbDepthValues from metadata - if (fs::exists(depthMapPath)) // untilled + if (fs::exists(depthMapPath)) // untilled { fileExists = true; const oiio::ParamValueList metadata = image::readImageMetadata(depthMapPath); nbDepthValues = metadata.get_int("AliceVision:nbDepthValues", -1); } - else // tilled + else // tilled { std::vector mapTilePathList; getTilePathList(rc, mp, EFileType::depthMapFiltered, customSuffix, mapTilePathList); - if(mapTilePathList.empty()) // depth map can be empty + if (mapTilePathList.empty()) // depth map can be empty { - ALICEVISION_LOG_INFO("Cannot find any depth map tile file (rc: " << rc << ")."); + ALICEVISION_LOG_INFO("Cannot find any depth map tile file (rc: " << rc << ")."); } else { @@ -621,27 +625,29 @@ unsigned long getNbDepthValuesFromDepthMap(int rc, fromTiles = true; } - for(const std::string& mapTilePath : mapTilePathList) + for (const std::string& mapTilePath : mapTilePathList) { const oiio::ParamValueList metadata = image::readImageMetadata(mapTilePath); const int nbTileDepthValues = metadata.get_int("AliceVision:nbDepthValues", -1); - if(nbTileDepthValues < 0) + if (nbTileDepthValues < 0) ALICEVISION_THROW_ERROR("Cannot find or incorrect 'AliceVision:nbDepthValues' metadata in depth map tile (rc: " << rc << ")"); nbDepthValues += nbTileDepthValues; } } - ALICEVISION_LOG_TRACE("DepthMap: " << depthMapPath << ", fileExists: " << fileExists << ", fromTiles: " << fromTiles << ", nbDepthValues (from metadata): " << nbDepthValues); + ALICEVISION_LOG_TRACE("DepthMap: " << depthMapPath << ", fileExists: " << fileExists << ", fromTiles: " << fromTiles + << ", nbDepthValues (from metadata): " << nbDepthValues); // no metadata compute number of depth values - if(fileExists && nbDepthValues < 0) + if (fileExists && nbDepthValues < 0) { image::Image depthMap; - ALICEVISION_LOG_WARNING("Can't find or invalid 'nbDepthValues' metadata in depth map (rc: " << rc << "). Recompute the number of valid values."); + ALICEVISION_LOG_WARNING("Can't find or invalid 'nbDepthValues' metadata in depth map (rc: " << rc + << "). Recompute the number of valid values."); readMap(rc, mp, EFileType::depthMapFiltered, depthMap, scale, step, customSuffix); @@ -652,20 +658,17 @@ unsigned long getNbDepthValuesFromDepthMap(int rc, return nbDepthValues; } -void deleteMapTiles(int rc, - const MultiViewParams& mp, - const EFileType fileType, - const std::string& customSuffix) +void deleteMapTiles(int rc, const MultiViewParams& mp, const EFileType fileType, const std::string& customSuffix) { std::vector mapTilePathList; getTilePathList(rc, mp, fileType, customSuffix, mapTilePathList); - if(mapTilePathList.empty()) // depth map can be empty - ALICEVISION_LOG_INFO("Cannot find any " << getMapNameFromFileType(fileType) << " tile file to delete (rc: " << rc << ")."); + if (mapTilePathList.empty()) // depth map can be empty + ALICEVISION_LOG_INFO("Cannot find any " << getMapNameFromFileType(fileType) << " tile file to delete (rc: " << rc << ")."); // delete map tile files - for(const std::string& mapTilePath : mapTilePathList) + for (const std::string& mapTilePath : mapTilePathList) { try { @@ -678,5 +681,5 @@ void deleteMapTiles(int rc, } } -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/mvsUtils/mapIO.hpp b/src/aliceVision/mvsUtils/mapIO.hpp index c866cfcdca..3f771ac1bc 100644 --- a/src/aliceVision/mvsUtils/mapIO.hpp +++ b/src/aliceVision/mvsUtils/mapIO.hpp @@ -109,9 +109,9 @@ inline void writeMap(int rc, int step = 1, const std::string& customSuffix = "") { - const TileParams tileParams; // default tile parameters, no tiles - const ROI roi = ROI(0, mp.getWidth(rc), 0, mp.getHeight(rc)); // fullsize roi - writeMap(rc, mp, fileType, tileParams, roi, in_map, scale, step, customSuffix); + const TileParams tileParams; // default tile parameters, no tiles + const ROI roi = ROI(0, mp.getWidth(rc), 0, mp.getHeight(rc)); // fullsize roi + writeMap(rc, mp, fileType, tileParams, roi, in_map, scale, step, customSuffix); } inline void writeMap(int rc, @@ -122,9 +122,9 @@ inline void writeMap(int rc, int step = 1, const std::string& customSuffix = "") { - const TileParams tileParams; // default tile parameters, no tiles - const ROI roi = ROI(0, mp.getWidth(rc), 0, mp.getHeight(rc)); // fullsize roi - writeMap(rc, mp, fileType, tileParams, roi, in_map, scale, step, customSuffix); + const TileParams tileParams; // default tile parameters, no tiles + const ROI roi = ROI(0, mp.getWidth(rc), 0, mp.getHeight(rc)); // fullsize roi + writeMap(rc, mp, fileType, tileParams, roi, in_map, scale, step, customSuffix); } /** @@ -135,11 +135,7 @@ inline void writeMap(int rc, * @param[in] step the depth map step factor * @param[in] customSuffix the filename custom suffix */ -unsigned long getNbDepthValuesFromDepthMap(int rc, - const MultiViewParams& mp, - int scale = 1, - int step = 1, - const std::string& customSuffix = ""); +unsigned long getNbDepthValuesFromDepthMap(int rc, const MultiViewParams& mp, int scale = 1, int step = 1, const std::string& customSuffix = ""); /** * @brief Delete map tiles files. @@ -148,10 +144,7 @@ unsigned long getNbDepthValuesFromDepthMap(int rc, * @param[in] fileType the map fileType enum * @param[in] customSuffix the filename custom suffix */ -void deleteMapTiles(int rc, - const MultiViewParams& mp, - const EFileType fileType, - const std::string& customSuffix = ""); +void deleteMapTiles(int rc, const MultiViewParams& mp, const EFileType fileType, const std::string& customSuffix = ""); -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/numeric/Accumulator.hpp b/src/aliceVision/numeric/Accumulator.hpp index e4e139e39d..e1cb0d300d 100644 --- a/src/aliceVision/numeric/Accumulator.hpp +++ b/src/aliceVision/numeric/Accumulator.hpp @@ -11,20 +11,44 @@ namespace aliceVision { template -struct Accumulator { typedef T Type; }; +struct Accumulator +{ + typedef T Type; +}; template<> -struct Accumulator { typedef float Type; }; +struct Accumulator +{ + typedef float Type; +}; template<> -struct Accumulator { typedef float Type; }; +struct Accumulator +{ + typedef float Type; +}; template<> -struct Accumulator { typedef float Type; }; +struct Accumulator +{ + typedef float Type; +}; template<> -struct Accumulator { typedef float Type; }; +struct Accumulator +{ + typedef float Type; +}; template<> -struct Accumulator { typedef float Type; }; +struct Accumulator +{ + typedef float Type; +}; template<> -struct Accumulator { typedef float Type; }; +struct Accumulator +{ + typedef float Type; +}; template<> -struct Accumulator { typedef unsigned int Type; }; +struct Accumulator +{ + typedef unsigned int Type; +}; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/numeric/Container.cpp b/src/aliceVision/numeric/Container.cpp index 1490cf63d2..420cf9fc9e 100644 --- a/src/aliceVision/numeric/Container.cpp +++ b/src/aliceVision/numeric/Container.cpp @@ -8,104 +8,100 @@ namespace aliceVision { -template <> -size_t CountElements(const Mat2X &A) +template<> +size_t CountElements(const Mat2X& A) { return A.cols(); } -template <> -size_t CountElements(const Mat3X &A) +template<> +size_t CountElements(const Mat3X& A) { return A.cols(); } -template <> -size_t CountElements(const Mat &A) +template<> +size_t CountElements(const Mat& A) { return A.cols(); } -template <> -size_t CountElements>(const std::vector &A) +template<> +size_t CountElements>(const std::vector& A) { return A.size(); } -template <> -size_t ElementSize(const Mat2X &A) +template<> +size_t ElementSize(const Mat2X& A) { return 2; } -template <> -size_t ElementSize(const Mat3X &A) +template<> +size_t ElementSize(const Mat3X& A) { return 3; } -template <> -size_t ElementSize(const Mat &A) +template<> +size_t ElementSize(const Mat& A) { return A.rows(); } -template <> -size_t ElementSize>(const std::vector &A) +template<> +size_t ElementSize>(const std::vector& A) { return 2; } -template <> -Element::const_type getElement(const Mat2X & A, size_t index) +template<> +Element::const_type getElement(const Mat2X& A, size_t index) { return A.col(index); } -template <> -Element::type getElement(Mat2X & A, size_t index) +template<> +Element::type getElement(Mat2X& A, size_t index) { return A.col(index); } - -template <> -Element::const_type getElement(const Mat3X & A, size_t index) +template<> +Element::const_type getElement(const Mat3X& A, size_t index) { return A.col(index); } -template <> -Element::type getElement(Mat3X & A, size_t index) +template<> +Element::type getElement(Mat3X& A, size_t index) { return A.col(index); } - -template <> -Element::const_type getElement(const Mat & A, size_t index) +template<> +Element::const_type getElement(const Mat& A, size_t index) { return A.col(index); } -template <> -Element::type getElement(Mat & A, size_t index) +template<> +Element::type getElement(Mat& A, size_t index) { return A.col(index); } -template <> -Element>::const_type getElement>(const std::vector & A, size_t index) +template<> +Element>::const_type getElement>(const std::vector& A, size_t index) { return A[index]; } -template <> -Element>::type getElement>(std::vector & A, size_t index) +template<> +Element>::type getElement>(std::vector& A, size_t index) { return A[index]; } - } // namespace aliceVision - diff --git a/src/aliceVision/numeric/Container.hpp b/src/aliceVision/numeric/Container.hpp index 2e55a90141..cdc6c6efb5 100644 --- a/src/aliceVision/numeric/Container.hpp +++ b/src/aliceVision/numeric/Container.hpp @@ -10,126 +10,129 @@ namespace aliceVision { -template -size_t CountElements(const TMat &A) +template +size_t CountElements(const TMat& A) { - static_assert(sizeof(TMat)!=sizeof(TMat), "CountElements must be specialized"); - return 0; + static_assert(sizeof(TMat) != sizeof(TMat), "CountElements must be specialized"); + return 0; } -template <> size_t CountElements(const Mat2X &A); -template <> size_t CountElements(const Mat3X &A); -template <> size_t CountElements(const Mat &A); -template <> size_t CountElements>(const std::vector &A); - -template -size_t ElementSize(const TMat &A) +template<> +size_t CountElements(const Mat2X& A); +template<> +size_t CountElements(const Mat3X& A); +template<> +size_t CountElements(const Mat& A); +template<> +size_t CountElements>(const std::vector& A); + +template +size_t ElementSize(const TMat& A) { - static_assert(sizeof(TMat)!=sizeof(TMat), "CountElements must be specialized"); - return 0; + static_assert(sizeof(TMat) != sizeof(TMat), "CountElements must be specialized"); + return 0; } -template <> size_t ElementSize(const Mat2X &A); -template <> size_t ElementSize(const Mat3X &A); -template <> size_t ElementSize(const Mat &A); -template <> size_t ElementSize>(const std::vector &A); +template<> +size_t ElementSize(const Mat2X& A); +template<> +size_t ElementSize(const Mat3X& A); +template<> +size_t ElementSize(const Mat& A); +template<> +size_t ElementSize>(const std::vector& A); -template +template struct Element; -template <> +template<> struct Element { typedef Mat2X::ConstColXpr const_type; typedef Mat2X::ColXpr type; - - static Mat2X create(size_t elementSize, size_t count) - { - return Mat2X(elementSize, count); - } + + static Mat2X create(size_t elementSize, size_t count) { return Mat2X(elementSize, count); } }; -template <> +template<> struct Element { typedef Mat3X::ConstColXpr const_type; typedef Mat3X::ColXpr type; - static Mat3X create(size_t elementSize, size_t count) - { - return Mat3X(elementSize, count); - } + static Mat3X create(size_t elementSize, size_t count) { return Mat3X(elementSize, count); } }; -template <> +template<> struct Element { typedef Mat::ConstColXpr const_type; typedef Mat::ColXpr type; - static Mat create(size_t elementSize, size_t count) - { - return Mat(elementSize, count); - } + static Mat create(size_t elementSize, size_t count) { return Mat(elementSize, count); } }; -template <> +template<> struct Element> { - typedef const Vec2 & const_type; - typedef Vec2 & type; + typedef const Vec2& const_type; + typedef Vec2& type; - static std::vector create(size_t elementSize, size_t count) - { - return std::vector(count); - } + static std::vector create(size_t elementSize, size_t count) { return std::vector(count); } }; -template -typename Element::const_type getElement(const TMat & A, size_t index) +template +typename Element::const_type getElement(const TMat& A, size_t index) { - static_assert(sizeof(TMat)!=sizeof(TMat), "CountElements must be specialized"); + static_assert(sizeof(TMat) != sizeof(TMat), "CountElements must be specialized"); return 0; } -template -typename Element::type getElement(TMat & A, size_t index) +template +typename Element::type getElement(TMat& A, size_t index) { - static_assert(sizeof(TMat)!=sizeof(TMat), "CountElements must be specialized"); + static_assert(sizeof(TMat) != sizeof(TMat), "CountElements must be specialized"); return 0; } -template <> typename Element::const_type getElement(const Mat2X & A, size_t index); -template <> typename Element::const_type getElement(const Mat3X & A, size_t index); -template <> typename Element::const_type getElement(const Mat & A, size_t index); -template <> typename Element>::const_type getElement>(const std::vector & A, size_t index); - -template <> typename Element::type getElement(Mat2X & A, size_t index); -template <> typename Element::type getElement(Mat3X & A, size_t index); -template <> typename Element::type getElement(Mat & A, size_t index); -template <> typename Element>::type getElement>(std::vector & A, size_t index); +template<> +typename Element::const_type getElement(const Mat2X& A, size_t index); +template<> +typename Element::const_type getElement(const Mat3X& A, size_t index); +template<> +typename Element::const_type getElement(const Mat& A, size_t index); +template<> +typename Element>::const_type getElement>(const std::vector& A, size_t index); + +template<> +typename Element::type getElement(Mat2X& A, size_t index); +template<> +typename Element::type getElement(Mat3X& A, size_t index); +template<> +typename Element::type getElement(Mat& A, size_t index); +template<> +typename Element>::type getElement>(std::vector& A, size_t index); /** * @brief It extracts the columns of given indices from the given matrix - * + * * @param[in] A The NxM input matrix * @param[in] columns The list of K indices to extract * @return A NxK matrix */ -template -TMat buildSubsetMatrix(const TMat &A, const TCols &columns) +template +TMat buildSubsetMatrix(const TMat& A, const TCols& columns) { - using T = Element; - TMat compressed = T::create(ElementSize(A), columns.size()); - for(std::size_t i = 0; i < static_cast (columns.size()); ++i) - { - // check for indices out of range - assert(columns[i]; + TMat compressed = T::create(ElementSize(A), columns.size()); + for (std::size_t i = 0; i < static_cast(columns.size()); ++i) + { + // check for indices out of range + assert(columns[i] < CountElements(A)); + getElement(compressed, i) = getElement(A, columns[i]); + } + return compressed; +} -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/numeric/LMFunctor.hpp b/src/aliceVision/numeric/LMFunctor.hpp index 18e49051e4..1a530b5e46 100644 --- a/src/aliceVision/numeric/LMFunctor.hpp +++ b/src/aliceVision/numeric/LMFunctor.hpp @@ -12,32 +12,38 @@ #include #include -namespace aliceVision -{ - using namespace Eigen; +namespace aliceVision { +using namespace Eigen; // Generic functor Levenberg-Marquardt minimization -template +template struct LMFunctor { - typedef _Scalar Scalar; - enum { - InputsAtCompileTime = NX, - ValuesAtCompileTime = NY - }; - typedef Matrix InputType; - typedef Matrix ValueType; - typedef Matrix JacobianType; - - const int m_inputs, m_values; - - LMFunctor() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {} - LMFunctor(int inputs, int values) : m_inputs(inputs), m_values(values) {} - - int inputs() const { return m_inputs; } - int values() const { return m_values; } - - // you should define that in the subclass : - // void operator() (const InputType& x, ValueType* v, JacobianType* _j=0) const; + typedef _Scalar Scalar; + enum + { + InputsAtCompileTime = NX, + ValuesAtCompileTime = NY + }; + typedef Matrix InputType; + typedef Matrix ValueType; + typedef Matrix JacobianType; + + const int m_inputs, m_values; + + LMFunctor() + : m_inputs(InputsAtCompileTime), + m_values(ValuesAtCompileTime) + {} + LMFunctor(int inputs, int values) + : m_inputs(inputs), + m_values(values) + {} + + int inputs() const { return m_inputs; } + int values() const { return m_values; } + + // you should define that in the subclass : + // void operator() (const InputType& x, ValueType* v, JacobianType* _j=0) const; }; -}; // namespace aliceVision +}; // namespace aliceVision diff --git a/src/aliceVision/numeric/MathTrait.hpp b/src/aliceVision/numeric/MathTrait.hpp index b721010648..3c92046433 100644 --- a/src/aliceVision/numeric/MathTrait.hpp +++ b/src/aliceVision/numeric/MathTrait.hpp @@ -9,389 +9,384 @@ #include -namespace aliceVision -{ - /* Type trait used to specialize math functions */ +namespace aliceVision { +/* Type trait used to specialize math functions */ - template - class MathTrait - { +template +class MathTrait +{ public: - // Trigonometric functions - static inline T cos( const T val ) ; - static inline T sin( const T val ) ; - static inline T tan( const T val ) ; + static inline T cos(const T val); + static inline T sin(const T val); + static inline T tan(const T val); // Inverse trigonometric functions - static inline T acos( const T val ) ; - static inline T asin( const T val ) ; - static inline T atan( const T val ) ; - static inline T atan2( const T y , const T x ) ; + static inline T acos(const T val); + static inline T asin(const T val); + static inline T atan(const T val); + static inline T atan2(const T y, const T x); // Exponential functions - static inline T exp( const T val ) ; - static inline T log( const T val ) ; - static inline T log10( const T val ) ; + static inline T exp(const T val); + static inline T log(const T val); + static inline T log10(const T val); // Power functions - static inline T pow( const T base , const T expo ) ; - static inline T sqrt( const T val ) ; - static inline T cbrt( const T val ) ; + static inline T pow(const T base, const T expo); + static inline T sqrt(const T val); + static inline T cbrt(const T val); // Rounding functions - static inline T floor( const T val ) ; - static inline T ceil( const T val ) ; - static inline T round( const T val ) ; + static inline T floor(const T val); + static inline T ceil(const T val); + static inline T round(const T val); // Absolute value - static inline T abs( const T val ) ; - } ; - - - /** - ** Float specialization - **/ - template<> - inline float MathTrait::cos( const float val ) - { - return cosf( val ) ; - } - - template<> - inline float MathTrait::sin( const float val ) - { - return sinf( val ) ; - } - - template<> - inline float MathTrait::tan( const float val ) - { - return tanf( val ) ; - } - - template<> - inline float MathTrait::acos( const float val ) - { - return acosf( val ) ; - } - - template<> - inline float MathTrait::asin( const float val ) - { - return asinf( val ) ; - } - - template<> - inline float MathTrait::atan( const float val ) - { - return atanf( val ) ; - } - - template<> - inline float MathTrait::atan2( const float y , const float x ) - { - return atan2f( y , x ) ; - } - - template<> - inline float MathTrait::exp( const float val ) - { - return expf( val ) ; - } - - template<> - inline float MathTrait::log( const float val ) - { - return logf( val ) ; - } - - template<> - inline float MathTrait::log10( const float val ) - { - return log10f( val ) ; - } - - template<> - inline float MathTrait::pow( const float base , const float expo ) - { - return powf( base , expo ) ; - } - - template<> - inline float MathTrait::sqrt( const float val ) - { - return sqrtf( val ) ; - } - - template<> - inline float MathTrait::cbrt( const float val ) - { + static inline T abs(const T val); +}; + +/** + ** Float specialization + **/ +template<> +inline float MathTrait::cos(const float val) +{ + return cosf(val); +} + +template<> +inline float MathTrait::sin(const float val) +{ + return sinf(val); +} + +template<> +inline float MathTrait::tan(const float val) +{ + return tanf(val); +} + +template<> +inline float MathTrait::acos(const float val) +{ + return acosf(val); +} + +template<> +inline float MathTrait::asin(const float val) +{ + return asinf(val); +} + +template<> +inline float MathTrait::atan(const float val) +{ + return atanf(val); +} + +template<> +inline float MathTrait::atan2(const float y, const float x) +{ + return atan2f(y, x); +} + +template<> +inline float MathTrait::exp(const float val) +{ + return expf(val); +} + +template<> +inline float MathTrait::log(const float val) +{ + return logf(val); +} + +template<> +inline float MathTrait::log10(const float val) +{ + return log10f(val); +} + +template<> +inline float MathTrait::pow(const float base, const float expo) +{ + return powf(base, expo); +} + +template<> +inline float MathTrait::sqrt(const float val) +{ + return sqrtf(val); +} + +template<> +inline float MathTrait::cbrt(const float val) +{ #ifdef _MSC_VER - return cbrt( val ) ; + return cbrt(val); #else - return cbrtf( val ) ; + return cbrtf(val); #endif - } - - template<> - inline float MathTrait::floor( const float val ) - { - return floorf( val ) ; - } - - template<> - inline float MathTrait::ceil( const float val ) - { - return ceilf( val ) ; - } - - template<> - inline float MathTrait::round( const float val ) - { - if( val >= 0.0f ) +} + +template<> +inline float MathTrait::floor(const float val) +{ + return floorf(val); +} + +template<> +inline float MathTrait::ceil(const float val) +{ + return ceilf(val); +} + +template<> +inline float MathTrait::round(const float val) +{ + if (val >= 0.0f) { - return floorf( val + 0.5f ) ; + return floorf(val + 0.5f); } else { - return ceilf( val - 0.5f ) ; + return ceilf(val - 0.5f); } - } - - template<> - inline float MathTrait::abs( const float val ) - { - return fabsf( val ) ; - } - - - /** - ** Double specialization - **/ - template<> - inline double MathTrait::cos( const double val ) - { - return cos( val ) ; - } - - template<> - inline double MathTrait::sin( const double val ) - { - return sin( val ) ; - } - - template<> - inline double MathTrait::tan( const double val ) - { - return tan( val ) ; - } - - template<> - inline double MathTrait::acos( const double val ) - { - return acos( val ) ; - } - - template<> - inline double MathTrait::asin( const double val ) - { - return asin( val ) ; - } - - template<> - inline double MathTrait::atan( const double val ) - { - return atan( val ) ; - } - - template<> - inline double MathTrait::atan2( const double y , const double x ) - { - return atan2( y , x ) ; - } - - template<> - inline double MathTrait::exp( const double val ) - { - return exp( val ) ; - } - - template<> - inline double MathTrait::log( const double val ) - { - return log( val ) ; - } - - template<> - inline double MathTrait::log10( const double val ) - { - return log10( val ) ; - } - - template<> - inline double MathTrait::pow( const double base , const double expo ) - { - return pow( base , expo ) ; - } - - template<> - inline double MathTrait::sqrt( const double val ) - { - return sqrt( val ) ; - } - - template<> - inline double MathTrait::cbrt( const double val ) - { - return cbrt( val ) ; - } - - template<> - inline double MathTrait::floor( const double val ) - { - return floor( val ) ; - } - - template<> - inline double MathTrait::ceil( const double val ) - { - return ceil( val ) ; - } - - template<> - inline double MathTrait::round( const double val ) - { - if( val >= 0.0 ) +} + +template<> +inline float MathTrait::abs(const float val) +{ + return fabsf(val); +} + +/** + ** Double specialization + **/ +template<> +inline double MathTrait::cos(const double val) +{ + return cos(val); +} + +template<> +inline double MathTrait::sin(const double val) +{ + return sin(val); +} + +template<> +inline double MathTrait::tan(const double val) +{ + return tan(val); +} + +template<> +inline double MathTrait::acos(const double val) +{ + return acos(val); +} + +template<> +inline double MathTrait::asin(const double val) +{ + return asin(val); +} + +template<> +inline double MathTrait::atan(const double val) +{ + return atan(val); +} + +template<> +inline double MathTrait::atan2(const double y, const double x) +{ + return atan2(y, x); +} + +template<> +inline double MathTrait::exp(const double val) +{ + return exp(val); +} + +template<> +inline double MathTrait::log(const double val) +{ + return log(val); +} + +template<> +inline double MathTrait::log10(const double val) +{ + return log10(val); +} + +template<> +inline double MathTrait::pow(const double base, const double expo) +{ + return pow(base, expo); +} + +template<> +inline double MathTrait::sqrt(const double val) +{ + return sqrt(val); +} + +template<> +inline double MathTrait::cbrt(const double val) +{ + return cbrt(val); +} + +template<> +inline double MathTrait::floor(const double val) +{ + return floor(val); +} + +template<> +inline double MathTrait::ceil(const double val) +{ + return ceil(val); +} + +template<> +inline double MathTrait::round(const double val) +{ + if (val >= 0.0) { - return floor( val + 0.5 ) ; + return floor(val + 0.5); } else { - return ceil( val - 0.5 ) ; + return ceil(val - 0.5); } - } - - template<> - inline double MathTrait::abs( const double val ) - { - return fabs( val ) ; - } - - - /** - ** long double specialization - **/ - template<> - inline long double MathTrait::cos( const long double val ) - { - return cosl( val ) ; - } - - template<> - inline long double MathTrait::sin( const long double val ) - { - return sinl( val ) ; - } - - template<> - inline long double MathTrait::tan( const long double val ) - { - return tanl( val ) ; - } - - template<> - inline long double MathTrait::acos( const long double val ) - { - return acosl( val ) ; - } - - template<> - inline long double MathTrait::asin( const long double val ) - { - return asinl( val ) ; - } - - template<> - inline long double MathTrait::atan( const long double val ) - { - return atanl( val ) ; - } - - template<> - inline long double MathTrait::atan2( const long double y , const long double x ) - { - return atan2l( y , x ) ; - } - - template<> - inline long double MathTrait::exp( const long double val ) - { - return expl( val ) ; - } - - template<> - inline long double MathTrait::log( const long double val ) - { - return logl( val ) ; - } - - template<> - inline long double MathTrait::log10( const long double val ) - { - return log10l( val ) ; - } - - template<> - inline long double MathTrait::pow( const long double base , const long double expo ) - { - return powl( base , expo ) ; - } - - template<> - inline long double MathTrait::sqrt( const long double val ) - { - return sqrtl( val ) ; - } - - template<> - inline long double MathTrait::cbrt( const long double val ) - { +} + +template<> +inline double MathTrait::abs(const double val) +{ + return fabs(val); +} + +/** + ** long double specialization + **/ +template<> +inline long double MathTrait::cos(const long double val) +{ + return cosl(val); +} + +template<> +inline long double MathTrait::sin(const long double val) +{ + return sinl(val); +} + +template<> +inline long double MathTrait::tan(const long double val) +{ + return tanl(val); +} + +template<> +inline long double MathTrait::acos(const long double val) +{ + return acosl(val); +} + +template<> +inline long double MathTrait::asin(const long double val) +{ + return asinl(val); +} + +template<> +inline long double MathTrait::atan(const long double val) +{ + return atanl(val); +} + +template<> +inline long double MathTrait::atan2(const long double y, const long double x) +{ + return atan2l(y, x); +} + +template<> +inline long double MathTrait::exp(const long double val) +{ + return expl(val); +} + +template<> +inline long double MathTrait::log(const long double val) +{ + return logl(val); +} + +template<> +inline long double MathTrait::log10(const long double val) +{ + return log10l(val); +} + +template<> +inline long double MathTrait::pow(const long double base, const long double expo) +{ + return powl(base, expo); +} + +template<> +inline long double MathTrait::sqrt(const long double val) +{ + return sqrtl(val); +} + +template<> +inline long double MathTrait::cbrt(const long double val) +{ #ifdef _MSC_VER - return cbrt( val ) ; + return cbrt(val); #else - return cbrtl( val ) ; + return cbrtl(val); #endif - } - - template<> - inline long double MathTrait::floor( const long double val ) - { - return floorl( val ) ; - } - - template<> - inline long double MathTrait::ceil( const long double val ) - { - return ceill( val ) ; - } - - template<> - inline long double MathTrait::round( const long double val ) - { - if( val >= 0.0l ) +} + +template<> +inline long double MathTrait::floor(const long double val) +{ + return floorl(val); +} + +template<> +inline long double MathTrait::ceil(const long double val) +{ + return ceill(val); +} + +template<> +inline long double MathTrait::round(const long double val) +{ + if (val >= 0.0l) { - return floorl( val + 0.5l ) ; + return floorl(val + 0.5l); } else { - return ceill( val - 0.5l ) ; + return ceill(val - 0.5l); } - } +} - template<> - inline long double MathTrait::abs( const long double val ) - { - return fabsl( val ) ; - } +template<> +inline long double MathTrait::abs(const long double val) +{ + return fabsl(val); } +} // namespace aliceVision diff --git a/src/aliceVision/numeric/algebra.hpp b/src/aliceVision/numeric/algebra.hpp index 3af27b94b4..f652a48e51 100644 --- a/src/aliceVision/numeric/algebra.hpp +++ b/src/aliceVision/numeric/algebra.hpp @@ -21,31 +21,30 @@ // AliceVision does not support Eigen with alignment, unless C++17 aligned new feature is enabled. // So ensure Eigen is used with the correct flags. #ifndef ALICEVISION_EIGEN_REQUIRE_ALIGNMENT -#ifndef EIGEN_MAX_ALIGN_BYTES -#error "EIGEN_MAX_ALIGN_BYTES is not defined" -#elif EIGEN_MAX_ALIGN_BYTES != 0 -#error "EIGEN_MAX_ALIGN_BYTES is defined but not 0" -#endif + #ifndef EIGEN_MAX_ALIGN_BYTES + #error "EIGEN_MAX_ALIGN_BYTES is not defined" + #elif EIGEN_MAX_ALIGN_BYTES != 0 + #error "EIGEN_MAX_ALIGN_BYTES is defined but not 0" + #endif -#ifndef EIGEN_MAX_STATIC_ALIGN_BYTES -#error "EIGEN_MAX_STATIC_ALIGN_BYTES is not defined" -#elif EIGEN_MAX_STATIC_ALIGN_BYTES != 0 -#error "EIGEN_MAX_STATIC_ALIGN_BYTES is defined but not 0" -#endif + #ifndef EIGEN_MAX_STATIC_ALIGN_BYTES + #error "EIGEN_MAX_STATIC_ALIGN_BYTES is not defined" + #elif EIGEN_MAX_STATIC_ALIGN_BYTES != 0 + #error "EIGEN_MAX_STATIC_ALIGN_BYTES is defined but not 0" + #endif #endif #include #include -namespace aliceVision -{ +namespace aliceVision { -template -double Nullspace(const TMat & A, TVec & nullspace) +template +double Nullspace(const TMat& A, TVec& nullspace) { Eigen::JacobiSVD svd(A, Eigen::ComputeFullV); - const auto & vec = svd.singularValues(); - const auto & V = svd.matrixV(); + const auto& vec = svd.singularValues(); + const auto& V = svd.matrixV(); nullspace = V.col(V.cols() - 1); @@ -53,7 +52,7 @@ double Nullspace(const TMat & A, TVec & nullspace) { return 0.0; } - + return vec(vec.rows() - 1); } @@ -63,12 +62,12 @@ double Nullspace(const TMat & A, TVec & nullspace) /// the singular value corresponding to the solution x1. Destroys A and resizes /// x if necessary. -template +template inline double Nullspace2(const TMat& A, TVec1& x1, TVec2& x2) { Eigen::JacobiSVD svd(A, Eigen::ComputeFullV); - const auto & vec = svd.singularValues(); - const auto & V = svd.matrixV(); + const auto& vec = svd.singularValues(); + const auto& V = svd.matrixV(); x1 = V.col(V.cols() - 1); x2 = V.col(V.cols() - 2); @@ -77,8 +76,8 @@ inline double Nullspace2(const TMat& A, TVec1& x1, TVec2& x2) { return 0.0; } - + return vec(vec.rows() - 1); } -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/numeric/gps.cpp b/src/aliceVision/numeric/gps.cpp index 547c3b1fae..0022c45855 100644 --- a/src/aliceVision/numeric/gps.cpp +++ b/src/aliceVision/numeric/gps.cpp @@ -9,12 +9,10 @@ #include #include -namespace aliceVision -{ +namespace aliceVision { Vec3 WGS84ToCartesian(const Vec3& gps) { - // equatorial radius WGS84 major axis const static double equRadius = 6378137.0; const static double flattening = 1.0 / 298.257222101; @@ -33,15 +31,13 @@ Vec3 WGS84ToCartesian(const Vec3& gps) // Normalized radius const double normRadius = equRadius / std::sqrt(1.0 - sqrEccentricity * sinLat * sinLat); - return {(normRadius + alt) * cosLat * cosLon, - (normRadius + alt) * cosLat * sinLon, - (normRadius * (1.0 - sqrEccentricity) + alt) * sinLat}; + return {(normRadius + alt) * cosLat * cosLon, (normRadius + alt) * cosLat * sinLon, (normRadius * (1.0 - sqrEccentricity) + alt) * sinLat}; } double parseAltitudeFromString(const std::string& alt, const std::string& altRef) { static const std::array allowedRef = {"0", "1"}; - if(!std::any_of(allowedRef.cbegin(), allowedRef.cend(), [altRef](const std::string& s){return altRef == s;})) + if (!std::any_of(allowedRef.cbegin(), allowedRef.cend(), [altRef](const std::string& s) { return altRef == s; })) { const auto message = "Unexpected value for gps reference: " + altRef; ALICEVISION_LOG_ERROR(message); @@ -53,11 +49,11 @@ double parseAltitudeFromString(const std::string& alt, const std::string& altRef return ref > 0.0 ? -altitude : altitude; } -//WGS84 +// WGS84 double parseGPSFromString(const std::string& gpsDegrees, const std::string& gpsRef) { static const std::array allowedRef = {"W", "E", "N", "S"}; - if(!std::any_of(allowedRef.cbegin(), allowedRef.cend(), [gpsRef](const std::string& s){return gpsRef == s;})) + if (!std::any_of(allowedRef.cbegin(), allowedRef.cend(), [gpsRef](const std::string& s) { return gpsRef == s; })) { const auto message = "Unexpected value for gps reference: " + gpsRef; ALICEVISION_LOG_ERROR(message); @@ -69,7 +65,7 @@ double parseGPSFromString(const std::string& gpsDegrees, const std::string& gpsR double gpsCoord{0}; - for(std::size_t i = 0; i < result.size(); ++i) + for (std::size_t i = 0; i < result.size(); ++i) { const auto d = std::stod(result[i]); gpsCoord += d * std::pow(60., -static_cast(i)); @@ -78,4 +74,4 @@ double parseGPSFromString(const std::string& gpsDegrees, const std::string& gpsR return (gpsRef == "S" || gpsRef == "W") ? -gpsCoord : gpsCoord; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/numeric/gps.hpp b/src/aliceVision/numeric/gps.hpp index 39276c0d35..63e11c83cb 100644 --- a/src/aliceVision/numeric/gps.hpp +++ b/src/aliceVision/numeric/gps.hpp @@ -39,4 +39,4 @@ double parseAltitudeFromString(const std::string& alt, const std::string& altRef */ double parseGPSFromString(const std::string& gpsDegrees, const std::string& gpsRef); -} \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/numeric/gps_test.cpp b/src/aliceVision/numeric/gps_test.cpp index b920e31f33..409c8c0fec 100644 --- a/src/aliceVision/numeric/gps_test.cpp +++ b/src/aliceVision/numeric/gps_test.cpp @@ -17,7 +17,7 @@ using namespace aliceVision; -BOOST_AUTO_TEST_CASE ( conversionTest ) +BOOST_AUTO_TEST_CASE(conversionTest) { const Vec3 gps{43.597824, 1.4548992, 150}; const Vec3 expected{4625021.304, 117467.403, 4375942.605}; @@ -26,7 +26,7 @@ BOOST_AUTO_TEST_CASE ( conversionTest ) EXPECT_MATRIX_CLOSE_FRACTION(res, expected, 1e-5); } -BOOST_AUTO_TEST_CASE ( parseGPSTest ) +BOOST_AUTO_TEST_CASE(parseGPSTest) { const std::string gps{"1, 27, 19.0008"}; const double expected{1.45528}; @@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE ( parseGPSTest ) BOOST_CHECK_THROW(parseGPSFromString(gps, "G"), std::invalid_argument); } -BOOST_AUTO_TEST_CASE ( parseAltTest ) +BOOST_AUTO_TEST_CASE(parseAltTest) { const std::string gps{"10785.65"}; const double expected{10785.65}; diff --git a/src/aliceVision/numeric/lmFunctor_test.cpp b/src/aliceVision/numeric/lmFunctor_test.cpp index d0afadeb76..194df03b39 100644 --- a/src/aliceVision/numeric/lmFunctor_test.cpp +++ b/src/aliceVision/numeric/lmFunctor_test.cpp @@ -31,90 +31,91 @@ using namespace svg; // f(x, c1, c2) = exp(c1*x) + c2 struct lm_Refine_functor : LMFunctor { - lm_Refine_functor(int inputs, int values, - const Vec & x, const Vec & y): LMFunctor(inputs,values), - _x(x), _y(y){} - - // The residual operator compute errors for the given x parameter - int operator()(const Vec &x, Vec &fvec) const{ - // x contain the putative optimizer values - double c1 = x[0]; - double c2 = x[1]; - - // Evaluate the function cost for each input value - for (Mat::Index i = 0; i < _x.rows(); ++i){ - fvec[i] = _y[i] - (exp(c1 * _x[i]) + c2); + lm_Refine_functor(int inputs, int values, const Vec& x, const Vec& y) + : LMFunctor(inputs, values), + _x(x), + _y(y) + {} + + // The residual operator compute errors for the given x parameter + int operator()(const Vec& x, Vec& fvec) const + { + // x contain the putative optimizer values + double c1 = x[0]; + double c2 = x[1]; + + // Evaluate the function cost for each input value + for (Mat::Index i = 0; i < _x.rows(); ++i) + { + fvec[i] = _y[i] - (exp(c1 * _x[i]) + c2); + } + return 0; } - return 0; - } - const Vec & _x, & _y; // Store data reference for cost evaluation + const Vec &_x, &_y; // Store data reference for cost evaluation }; -BOOST_AUTO_TEST_CASE(LM_MimimaSearchViaLM) { - - Vec x(10); - x << .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5; - Vec y(10); - y << 1.7788, 1.6065, 1.4723, 1.3678, 1.2865, 1.2231, 1.1737, 1.1353, 1.1053, 1.0820; - // We are looking for the exponential function that best fit the given point x,y - // f(x, c1, c2) = exp(c1*x) + c2 - lm_Refine_functor functor( - 2, // cardinal of the parameters: 2 (c1,c2) - x.rows(), // cardinal of computed residual - x, y // Data that allow to compute the residual - ); - typedef Eigen::NumericalDiff NumDiffT; - NumDiffT numDiff(functor); - - Eigen::LevenbergMarquardt lm(numDiff); - lm.parameters.maxfev = 100; - - // The starting point to optimize (a sufficiently approximate value) - Vec xlm(2); - xlm << 0.0, 0.0; - - // Optimization by using LevenbergMarquardt routine - int info = lm.minimize(xlm); - // Get back optimized value - ALICEVISION_LOG_DEBUG("info: " << info); - - Vec minima = xlm; - Vec2 GT(-.4999, .9999); - EXPECT_MATRIX_NEAR(GT, xlm, 1e-3); - - // Evaluation of the residual of the found solution - Vec fvec(10); - functor(xlm, fvec); - BOOST_CHECK_SMALL(fvec.norm(), 1e-3); - // We cannot expect more precision since input data are limited in precision - - // Export computed result to a SVG file - { - //Draw input data and found curve (magnification by 10 for visualization): - double dFactor = 10.0; - svgDrawer svgSurface(6*dFactor, 4*dFactor); - // Draw found curve - Vec xFound(30),yFound(30); - int cpt=0; - for (double i=0.0; i < 6.0; i+=.2, ++cpt) { - xFound[cpt] = i*dFactor; - yFound[cpt] = (4 - (exp(xlm[0] * i) + xlm[1]))*dFactor; +BOOST_AUTO_TEST_CASE(LM_MimimaSearchViaLM) +{ + Vec x(10); + x << .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5; + Vec y(10); + y << 1.7788, 1.6065, 1.4723, 1.3678, 1.2865, 1.2231, 1.1737, 1.1353, 1.1053, 1.0820; + // We are looking for the exponential function that best fit the given point x,y + // f(x, c1, c2) = exp(c1*x) + c2 + lm_Refine_functor functor(2, // cardinal of the parameters: 2 (c1,c2) + x.rows(), // cardinal of computed residual + x, + y // Data that allow to compute the residual + ); + typedef Eigen::NumericalDiff NumDiffT; + NumDiffT numDiff(functor); + + Eigen::LevenbergMarquardt lm(numDiff); + lm.parameters.maxfev = 100; + + // The starting point to optimize (a sufficiently approximate value) + Vec xlm(2); + xlm << 0.0, 0.0; + + // Optimization by using LevenbergMarquardt routine + int info = lm.minimize(xlm); + // Get back optimized value + ALICEVISION_LOG_DEBUG("info: " << info); + + Vec minima = xlm; + Vec2 GT(-.4999, .9999); + EXPECT_MATRIX_NEAR(GT, xlm, 1e-3); + + // Evaluation of the residual of the found solution + Vec fvec(10); + functor(xlm, fvec); + BOOST_CHECK_SMALL(fvec.norm(), 1e-3); + // We cannot expect more precision since input data are limited in precision + + // Export computed result to a SVG file + { + // Draw input data and found curve (magnification by 10 for visualization): + double dFactor = 10.0; + svgDrawer svgSurface(6 * dFactor, 4 * dFactor); + // Draw found curve + Vec xFound(30), yFound(30); + int cpt = 0; + for (double i = 0.0; i < 6.0; i += .2, ++cpt) + { + xFound[cpt] = i * dFactor; + yFound[cpt] = (4 - (exp(xlm[0] * i) + xlm[1])) * dFactor; + } + + svgSurface.drawPolyline(xFound.data(), xFound.data() + 30, yFound.data(), yFound.data() + 30, svgStyle().stroke("blue", 1.f)); + + // Draw point in a second time to put them in the top layer + for (Vec::Index i = 0; i < x.rows(); ++i) + svgSurface.drawCircle(x[i] * dFactor, (4 - y[i]) * dFactor, 1, svgStyle().fill("red").noStroke()); + + std::ostringstream osSvg; + osSvg << "exponentialRegression_unit_test.svg"; + std::ofstream svgFile(osSvg.str()); + svgFile << svgSurface.closeSvgFile().str(); } - - svgSurface.drawPolyline( - xFound.data(), xFound.data()+30, - yFound.data(), yFound.data()+30, - svgStyle().stroke("blue", 1.f)); - - //Draw point in a second time to put them in the top layer - for (Vec::Index i = 0; i < x.rows(); ++i) - svgSurface.drawCircle(x[i]*dFactor, (4-y[i])*dFactor, 1, - svgStyle().fill("red").noStroke()); - - std::ostringstream osSvg; - osSvg << "exponentialRegression_unit_test.svg"; - std::ofstream svgFile(osSvg.str()); - svgFile << svgSurface.closeSvgFile().str(); - } } diff --git a/src/aliceVision/numeric/numeric.cpp b/src/aliceVision/numeric/numeric.cpp index 84d34954e8..6f7147f727 100644 --- a/src/aliceVision/numeric/numeric.cpp +++ b/src/aliceVision/numeric/numeric.cpp @@ -14,159 +14,147 @@ namespace aliceVision { -Mat23 SkewMatMinimal(const Vec2 &x) +Mat23 SkewMatMinimal(const Vec2& x) { - Mat23 skew; - skew << - 0, -1, x(1), - 1, 0, -x(0); - return skew; + Mat23 skew; + skew << 0, -1, x(1), 1, 0, -x(0); + return skew; } -Mat3 CrossProductMatrix(const Vec3 &x) { - Mat3 X; - X << 0, -x(2), x(1), - x(2), 0, -x(0), - -x(1), x(0), 0; - return X; +Mat3 CrossProductMatrix(const Vec3& x) +{ + Mat3 X; + X << 0, -x(2), x(1), x(2), 0, -x(0), -x(1), x(0), 0; + return X; } -Mat3 RotationAroundX(double angle) { - return Eigen::AngleAxisd(angle, Vec3::UnitX()).toRotationMatrix(); -} +Mat3 RotationAroundX(double angle) { return Eigen::AngleAxisd(angle, Vec3::UnitX()).toRotationMatrix(); } -Mat3 RotationAroundY(double angle) { - return Eigen::AngleAxisd(angle, Vec3::UnitY()).toRotationMatrix(); -} +Mat3 RotationAroundY(double angle) { return Eigen::AngleAxisd(angle, Vec3::UnitY()).toRotationMatrix(); } -Mat3 RotationAroundZ(double angle) { - return Eigen::AngleAxisd(angle, Vec3::UnitZ()).toRotationMatrix(); -} +Mat3 RotationAroundZ(double angle) { return Eigen::AngleAxisd(angle, Vec3::UnitZ()).toRotationMatrix(); } + +Mat3 rotationXYZ(double angleX, double angleY, double angleZ) { return RotationAroundX(angleX) * RotationAroundY(angleY) * RotationAroundZ(angleZ); } -Mat3 rotationXYZ(double angleX, double angleY, double angleZ) +double getRotationMagnitude(const Mat3& R2) { - return RotationAroundX(angleX)*RotationAroundY(angleY)*RotationAroundZ(angleZ); + const Mat3 R1 = Mat3::Identity(); + double cos_theta = (R1.array() * R2.array()).sum() / 3.0; + cos_theta = clamp(cos_theta, -1.0, 1.0); + return std::acos(cos_theta); } -double getRotationMagnitude(const Mat3 & R2) { - const Mat3 R1 = Mat3::Identity(); - double cos_theta = (R1.array() * R2.array()).sum() / 3.0; - cos_theta = clamp(cos_theta, -1.0, 1.0); - return std::acos(cos_theta); -} +double rotationDifference(const Mat3& R1, const Mat3& R2) { return getRotationMagnitude(R1 * R2.transpose()); } -double rotationDifference(const Mat3 & R1, const Mat3 & R2) +Mat3 LookAt(const Vec3& center, const Vec3& up) { - return getRotationMagnitude(R1*R2.transpose()); + Vec3 zc = center.normalized(); + Vec3 xc = up.cross(zc).normalized(); + Vec3 yc = zc.cross(xc); + Mat3 R; + R.row(0) = xc; + R.row(1) = yc; + R.row(2) = zc; + return R; } -Mat3 LookAt(const Vec3 ¢er, const Vec3 & up) { - Vec3 zc = center.normalized(); - Vec3 xc = up.cross(zc).normalized(); - Vec3 yc = zc.cross(xc); - Mat3 R; - R.row(0) = xc; - R.row(1) = yc; - R.row(2) = zc; - return R; -} - -//eyePosition3D is a XYZ position. This is where you are (your eye is). -//center3D is the XYZ position where you want to look at. -//upVector3D is a XYZ normalized vector. Quite often 0.0, 1.0, 0.0 +// eyePosition3D is a XYZ position. This is where you are (your eye is). +// center3D is the XYZ position where you want to look at. +// upVector3D is a XYZ normalized vector. Quite often 0.0, 1.0, 0.0 -Mat3 LookAt2(const Vec3 &eyePosition3D, - const Vec3 ¢er3D, - const Vec3 &upVector3D ) +Mat3 LookAt2(const Vec3& eyePosition3D, const Vec3& center3D, const Vec3& upVector3D) { - Vec3 forward, side, up; - Mat3 matrix2, resultMatrix; - //------------------ - forward = center3D - eyePosition3D; - forward.normalize(); - //------------------ - //Side = forward x up - //ComputeNormalOfPlane(side, forward, upVector3D); - side[0]=(forward[1]*upVector3D[2])-(forward[2]*upVector3D[1]); - side[1]=(forward[2]*upVector3D[0])-(forward[0]*upVector3D[2]); - side[2]=(forward[0]*upVector3D[1])-(forward[1]*upVector3D[0]); - side.normalize(); - //------------------ - //Recompute up as: up = side x forward - //ComputeNormalOfPlane(up, side, forward); - up[0]=(side[1]*forward[2])-(side[2]*forward[1]); - up[1]=(side[2]*forward[0])-(side[0]*forward[2]); - up[2]=(side[0]*forward[1])-(side[1]*forward[0]); - - //------------------ - matrix2(0) = side[0]; - matrix2(1) = side[1]; - matrix2(2) = side[2]; - //------------------ - matrix2(3) = up[0]; - matrix2(4) = up[1]; - matrix2(5) = up[2]; - //------------------ - matrix2(6) = -forward[0]; - matrix2(7) = -forward[1]; - matrix2(8) = -forward[2]; - - return matrix2; + Vec3 forward, side, up; + Mat3 matrix2, resultMatrix; + //------------------ + forward = center3D - eyePosition3D; + forward.normalize(); + //------------------ + // Side = forward x up + // ComputeNormalOfPlane(side, forward, upVector3D); + side[0] = (forward[1] * upVector3D[2]) - (forward[2] * upVector3D[1]); + side[1] = (forward[2] * upVector3D[0]) - (forward[0] * upVector3D[2]); + side[2] = (forward[0] * upVector3D[1]) - (forward[1] * upVector3D[0]); + side.normalize(); + //------------------ + // Recompute up as: up = side x forward + // ComputeNormalOfPlane(up, side, forward); + up[0] = (side[1] * forward[2]) - (side[2] * forward[1]); + up[1] = (side[2] * forward[0]) - (side[0] * forward[2]); + up[2] = (side[0] * forward[1]) - (side[1] * forward[0]); + + //------------------ + matrix2(0) = side[0]; + matrix2(1) = side[1]; + matrix2(2) = side[2]; + //------------------ + matrix2(3) = up[0]; + matrix2(4) = up[1]; + matrix2(5) = up[2]; + //------------------ + matrix2(6) = -forward[0]; + matrix2(7) = -forward[1]; + matrix2(8) = -forward[2]; + + return matrix2; } -void MeanAndVarianceAlongRows(const Mat &A, - Vec *mean_pointer, - Vec *variance_pointer) { - Vec &mean = *mean_pointer; - Vec &variance = *variance_pointer; +void MeanAndVarianceAlongRows(const Mat& A, Vec* mean_pointer, Vec* variance_pointer) +{ + Vec& mean = *mean_pointer; + Vec& variance = *variance_pointer; Mat::Index n = A.rows(); double m = static_cast(A.cols()); mean = variance = Vec::Zero(n); - for (Mat::Index i = 0; i < n; ++i) { - mean(i) += A.row(i).array().sum(); - variance(i) += (A.row(i).array() * A.row(i).array()).array().sum(); + for (Mat::Index i = 0; i < n; ++i) + { + mean(i) += A.row(i).array().sum(); + variance(i) += (A.row(i).array() * A.row(i).array()).array().sum(); } mean /= m; - for (Mat::Index i = 0; i < n; ++i) { - variance(i) = variance(i) / m - Square(mean(i)); + for (Mat::Index i = 0; i < n; ++i) + { + variance(i) = variance(i) / m - Square(mean(i)); } } -bool exportMatToTextFile(const Mat & mat, const std::string & filename, - const std::string & sPrefix) +bool exportMatToTextFile(const Mat& mat, const std::string& filename, const std::string& sPrefix) { - bool bOk = false; - std::ofstream outfile; - outfile.open(filename, std::ios_base::out); - if (outfile.is_open()) { - outfile << sPrefix << "=[" << std::endl; - for (int j=0; j < mat.rows(); ++j) { - for (int i=0; i < mat.cols(); ++i) { - outfile << mat(j,i) << " "; - } - outfile << ";\n"; + bool bOk = false; + std::ofstream outfile; + outfile.open(filename, std::ios_base::out); + if (outfile.is_open()) + { + outfile << sPrefix << "=[" << std::endl; + for (int j = 0; j < mat.rows(); ++j) + { + for (int i = 0; i < mat.cols(); ++i) + { + outfile << mat(j, i) << " "; + } + outfile << ";\n"; + } + outfile << "];"; + bOk = true; } - outfile << "];"; - bOk = true; - } - outfile.close(); - return bOk; + outfile.close(); + return bOk; } void makeRandomOperationsReproducible() { - unsigned seed = 1234567; + unsigned seed = 1234567; - const char* seed_string = std::getenv("ALICEVISION_RANDOM_SEED"); - if (seed_string != nullptr) { - seed = std::stol(seed_string); - } + const char* seed_string = std::getenv("ALICEVISION_RANDOM_SEED"); + if (seed_string != nullptr) + { + seed = std::stol(seed_string); + } - // Eigen uses std::rand in its Random() member functions. - std::srand(seed); + // Eigen uses std::rand in its Random() member functions. + std::srand(seed); } } // namespace aliceVision - diff --git a/src/aliceVision/numeric/numeric.hpp b/src/aliceVision/numeric/numeric.hpp index 5d08a698e1..f294c85965 100644 --- a/src/aliceVision/numeric/numeric.hpp +++ b/src/aliceVision/numeric/numeric.hpp @@ -24,7 +24,6 @@ #endif #endif - //-- // Eigen // http://eigen.tuxfamily.org/dox-devel/QuickRefPage.html @@ -50,20 +49,20 @@ namespace aliceVision { // Check MSVC #if _WIN32 || _WIN64 -#if _WIN64 -#define ENV64BIT -#else -#define ENV32BIT -#endif + #if _WIN64 + #define ENV64BIT + #else + #define ENV32BIT + #endif #endif // Check GCC #if __GNUC__ -#if __x86_64__ || __ppc64__ || _LP64 -#define ENV64BIT -#else -#define ENV32BIT -#endif + #if __x86_64__ || __ppc64__ || _LP64 + #define ENV64BIT + #else + #define ENV32BIT + #endif #endif using Eigen::Map; @@ -89,7 +88,7 @@ using Mat34 = Eigen::Matrix; using Vec2 = Eigen::Matrix; using Vec4 = Eigen::Matrix; using Vec6 = Eigen::Matrix; -#else // 64 bits compiler +#else // 64 bits compiler using Mat23 = Eigen::Matrix; using Mat34 = Eigen::Matrix; using Vec2 = Eigen::Vector2d; @@ -97,7 +96,6 @@ using Vec4 = Eigen::Vector4d; using Vec6 = Eigen::Matrix; #endif - using Mat4 = Eigen::Matrix; using Matu = Eigen::Matrix; @@ -131,16 +129,16 @@ using sRMat = Eigen::SparseMatrix; template inline T Square(T x) { - return x * x; + return x * x; } /// Clamp return the number if inside range, else min or max range. template -inline T clamp(const T & val, const T& min, const T & max) +inline T clamp(const T& val, const T& min, const T& max) { - return std::max(min, std::min(val, max)); - //(val < min) ? val : ((val>max) ? val : max); + return std::max(min, std::min(val, max)); + //(val < min) ? val : ((val>max) ? val : max); } inline bool isSimilar(double a, double b) @@ -154,20 +152,19 @@ inline bool isSimilar(float a, float b) return std::abs(diff) < 1e-8f; } - /** * @brief Create a minimal skew matrix from a 2d vector. * @param[in] x A 2d vector whose 3rd coordinate is supposed to be 1. * @return The minimal ske matrix: [0, -1, x(1); 1, 0, -x(0);] */ -Mat23 SkewMatMinimal(const Vec2 &x); +Mat23 SkewMatMinimal(const Vec2& x); /** * @brief Create a cross product matrix from a 3d vector. * @param x A 3d vector. * @return the cross matrix representation of the input vector. */ -Mat3 CrossProductMatrix(const Vec3 &x); +Mat3 CrossProductMatrix(const Vec3& x); // Create a rotation matrix around axis X with the provided radian angle Mat3 RotationAroundX(double angle); @@ -181,204 +178,183 @@ Mat3 RotationAroundZ(double angle); Mat3 rotationXYZ(double angleX, double angleY, double angleZ); // Degree to Radian (suppose input in [0;360]) -template +template inline T degreeToRadian(T degree) { - static_assert(std::is_floating_point::value, "degreeToRadian: must be floating point."); - return degree * boost::math::constants::pi() / 180.0; + static_assert(std::is_floating_point::value, "degreeToRadian: must be floating point."); + return degree * boost::math::constants::pi() / 180.0; } // Radian to degree -template +template inline T radianToDegree(T radian) { - static_assert(std::is_floating_point::value, "radianToDegree: must be floating point."); - return radian / boost::math::constants::pi() * 180.0; + static_assert(std::is_floating_point::value, "radianToDegree: must be floating point."); + return radian / boost::math::constants::pi() * 180.0; } /// Return in radian the mean rotation amplitude of the given rotation matrix /// Computed as the mean of matrix column dot products to an Identity matrix -double getRotationMagnitude(const Mat3 & R2); +double getRotationMagnitude(const Mat3& R2); /** * @brief Compute the angle between two rotation matrices. * @param[in] R1 The first rotation matrix. * @param[in] R2 The second rotation matrix. - * @return The angle between the two rotations as the angle of the rotation + * @return The angle between the two rotations as the angle of the rotation * matrix R1*R2.transpose(). */ -double rotationDifference(const Mat3 & R1, const Mat3 & R2); +double rotationDifference(const Mat3& R1, const Mat3& R2); -inline double SIGN(double x) -{ - return x < 0.0 ? -1.0 : 1.0; -} +inline double SIGN(double x) { return x < 0.0 ? -1.0 : 1.0; } // L1 norm = Sum (|x0| + |x1| + |xn|) template -inline double NormL1(const TVec &x) +inline double NormL1(const TVec& x) { - return x.array().abs().sum(); + return x.array().abs().sum(); } // L2 norm = Sqrt (Sum (x0^2 + x1^2 + xn^2)) template -inline double NormL2(const TVec &x) +inline double NormL2(const TVec& x) { - return x.norm(); + return x.norm(); } // LInfinity norm = max (|x0|, |x1|, ..., |xn|) template -inline double NormLInfinity(const TVec &x) +inline double NormLInfinity(const TVec& x) { - return x.array().abs().maxCoeff(); + return x.array().abs().maxCoeff(); } template -inline double DistanceL1(const TVec &x, const TVec &y) +inline double DistanceL1(const TVec& x, const TVec& y) { - return (x - y).array().abs().sum(); + return (x - y).array().abs().sum(); } template -inline double DistanceL2(const TVec &x, const TVec &y) +inline double DistanceL2(const TVec& x, const TVec& y) { - return (x - y).norm(); + return (x - y).norm(); } template -inline double DistanceLInfinity(const TVec &x, const TVec &y) +inline double DistanceLInfinity(const TVec& x, const TVec& y) { - return NormLInfinity(x - y); + return NormLInfinity(x - y); } template inline bool AreVecNearEqual(const TVec& x, const TVec& y, const double epsilon) { - assert(x.cols() == y.cols()); - for(typename TVec::Index i = 0; i < x.cols(); ++i) - { - if((y(i) - epsilon > x(i)) - || (x(i) > y(i) + epsilon)) - return false; - } - return true; + assert(x.cols() == y.cols()); + for (typename TVec::Index i = 0; i < x.cols(); ++i) + { + if ((y(i) - epsilon > x(i)) || (x(i) > y(i) + epsilon)) + return false; + } + return true; } template inline bool AreMatNearEqual(const TMat& X, const TMat& Y, const double epsilon) { - assert(X.cols() == Y.cols()); - assert(X.rows() == Y.rows()); - for(typename TMat::Index i = 0; i < X.rows(); ++i) - { - for(typename TMat::Index j = 0; j < X.cols(); ++j) + assert(X.cols() == Y.cols()); + assert(X.rows() == Y.rows()); + for (typename TMat::Index i = 0; i < X.rows(); ++i) { - if((Y(i,j) - epsilon > X(i,j)) - || (X(i,j) > Y(i,j) + epsilon)) - return false; + for (typename TMat::Index j = 0; j < X.cols(); ++j) + { + if ((Y(i, j) - epsilon > X(i, j)) || (X(i, j) > Y(i, j) + epsilon)) + return false; + } } - } - return true; + return true; } // Make a rotation matrix such that center becomes the direction of the // positive z-axis, and y is oriented close to up by default. -Mat3 LookAt(const Vec3 ¢er, const Vec3 & up = Vec3::UnitY()); +Mat3 LookAt(const Vec3& center, const Vec3& up = Vec3::UnitY()); -Mat3 LookAt2(const Vec3 &eyePosition3D, - const Vec3 ¢er3D = Vec3::Zero(), - const Vec3 &upVector3D = Vec3::UnitY()); +Mat3 LookAt2(const Vec3& eyePosition3D, const Vec3& center3D = Vec3::Zero(), const Vec3& upVector3D = Vec3::UnitY()); -#define SUM_OR_DYNAMIC(x,y) (x==Eigen::Dynamic||y==Eigen::Dynamic)?Eigen::Dynamic:(x+y) +#define SUM_OR_DYNAMIC(x, y) (x == Eigen::Dynamic || y == Eigen::Dynamic) ? Eigen::Dynamic : (x + y) template struct hstack_return { - using Scalar = typename Derived1::Scalar; - - enum - { - RowsAtCompileTime = Derived1::RowsAtCompileTime, - ColsAtCompileTime = SUM_OR_DYNAMIC(Derived1::ColsAtCompileTime, Derived2::ColsAtCompileTime), - Options = Derived1::Flags & Eigen::RowMajorBit ? Eigen::RowMajor : 0, - MaxRowsAtCompileTime = Derived1::MaxRowsAtCompileTime, - MaxColsAtCompileTime = SUM_OR_DYNAMIC(Derived1::MaxColsAtCompileTime, Derived2::MaxColsAtCompileTime) - }; - typedef Eigen::Matrix type; + using Scalar = typename Derived1::Scalar; + + enum + { + RowsAtCompileTime = Derived1::RowsAtCompileTime, + ColsAtCompileTime = SUM_OR_DYNAMIC(Derived1::ColsAtCompileTime, Derived2::ColsAtCompileTime), + Options = Derived1::Flags & Eigen::RowMajorBit ? Eigen::RowMajor : 0, + MaxRowsAtCompileTime = Derived1::MaxRowsAtCompileTime, + MaxColsAtCompileTime = SUM_OR_DYNAMIC(Derived1::MaxColsAtCompileTime, Derived2::MaxColsAtCompileTime) + }; + typedef Eigen::Matrix type; }; template -typename hstack_return::type -HStack(const Eigen::MatrixBase& lhs, const Eigen::MatrixBase& rhs) +typename hstack_return::type HStack(const Eigen::MatrixBase& lhs, const Eigen::MatrixBase& rhs) { - typename hstack_return::type res; - res.resize(lhs.rows(), lhs.cols() + rhs.cols()); - res << lhs, rhs; - return res; + typename hstack_return::type res; + res.resize(lhs.rows(), lhs.cols() + rhs.cols()); + res << lhs, rhs; + return res; } template struct vstack_return { - using Scalar = typename Derived1::Scalar; - - enum - { - RowsAtCompileTime = SUM_OR_DYNAMIC(Derived1::RowsAtCompileTime, Derived2::RowsAtCompileTime), - ColsAtCompileTime = Derived1::ColsAtCompileTime, - Options = Derived1::Flags & Eigen::RowMajorBit ? Eigen::RowMajor : 0, - MaxRowsAtCompileTime = SUM_OR_DYNAMIC(Derived1::MaxRowsAtCompileTime, Derived2::MaxRowsAtCompileTime), - MaxColsAtCompileTime = Derived1::MaxColsAtCompileTime - }; - typedef Eigen::Matrix type; + using Scalar = typename Derived1::Scalar; + + enum + { + RowsAtCompileTime = SUM_OR_DYNAMIC(Derived1::RowsAtCompileTime, Derived2::RowsAtCompileTime), + ColsAtCompileTime = Derived1::ColsAtCompileTime, + Options = Derived1::Flags & Eigen::RowMajorBit ? Eigen::RowMajor : 0, + MaxRowsAtCompileTime = SUM_OR_DYNAMIC(Derived1::MaxRowsAtCompileTime, Derived2::MaxRowsAtCompileTime), + MaxColsAtCompileTime = Derived1::MaxColsAtCompileTime + }; + typedef Eigen::Matrix type; }; template -typename vstack_return::type -VStack(const Eigen::MatrixBase& lhs, const Eigen::MatrixBase& rhs) +typename vstack_return::type VStack(const Eigen::MatrixBase& lhs, const Eigen::MatrixBase& rhs) { - typename vstack_return::type res; - res.resize(lhs.rows() + rhs.rows(), lhs.cols()); - res << lhs, rhs; - return res; + typename vstack_return::type res; + res.resize(lhs.rows() + rhs.rows(), lhs.cols()); + res << lhs, rhs; + return res; } #undef SUM_OR_DYNAMIC template -inline double FrobeniusNorm(const TMat &A) +inline double FrobeniusNorm(const TMat& A) { - return A.norm(); + return A.norm(); } template -inline double FrobeniusDistance(const TMat &A, const TMat &B) +inline double FrobeniusDistance(const TMat& A, const TMat& B) { - return FrobeniusNorm(A - B); + return FrobeniusNorm(A - B); } template -double CosinusBetweenMatrices(const TMat &a, const TMat &b) +double CosinusBetweenMatrices(const TMat& a, const TMat& b) { - return (a.array() * b.array()).sum() / - FrobeniusNorm(a) / FrobeniusNorm(b); + return (a.array() * b.array()).sum() / FrobeniusNorm(a) / FrobeniusNorm(b); } - /** * @brief Given a vector of element and a vector containing a selection of its indices, * it returns a new vector containing only the selected elements of the original vector @@ -388,85 +364,89 @@ double CosinusBetweenMatrices(const TMat &a, const TMat &b) * @param[in] selection The vector containing the selection of elements of \p input * through the indices specified in \p selection. */ -template +template void pick(std::vector& result, const std::vector& input, const std::vector::size_type>& selection) { - result.reserve(selection.size()); - std::transform(selection.begin(), selection.end(), std::back_inserter(result), - [&input](typename std::vector::size_type idx) { - return input.at(idx); - }); + result.reserve(selection.size()); + std::transform( + selection.begin(), selection.end(), std::back_inserter(result), [&input](typename std::vector::size_type idx) { return input.at(idx); }); } -void MeanAndVarianceAlongRows(const Mat &A, - Vec *mean_pointer, - Vec *variance_pointer); +void MeanAndVarianceAlongRows(const Mat& A, Vec* mean_pointer, Vec* variance_pointer); -bool exportMatToTextFile(const Mat & mat, const std::string & filename, - const std::string & sPrefix = "A"); +bool exportMatToTextFile(const Mat& mat, const std::string& filename, const std::string& sPrefix = "A"); inline int is_finite(const double val) { #ifdef _MSC_VER - return _finite(val); + return _finite(val); #else - return std::isfinite(val); + return std::isfinite(val); #endif } /** Get back the min, mean, median and the max * values of an iterable sequence. */ -template +template struct BoxStats { Type min{}, max{}, mean{}, median{}, firstQuartile{}, thirdQuartile{}; BoxStats() = default; - template + template BoxStats(DataInputIterator begin, DataInputIterator end) { - compute(begin, end); + compute(begin, end); } - template + template void compute(DataInputIterator begin, DataInputIterator end) { - if(std::distance(begin, end) < 1) - { - min = 0; - max = 0; - mean = 0; - median = 0; - firstQuartile = 0; - thirdQuartile = 0; - return; - } - - std::vector vec_val(begin, end); - std::sort(vec_val.begin(), vec_val.end()); - min = vec_val[0]; - max = vec_val[vec_val.size() - 1]; - mean = accumulate(vec_val.begin(), vec_val.end(), Type(0)) - / static_cast (vec_val.size()); - median = vec_val[vec_val.size() / 2]; - firstQuartile = vec_val[vec_val.size() / 4]; - thirdQuartile = vec_val[(vec_val.size() * 3) / 4]; + if (std::distance(begin, end) < 1) + { + min = 0; + max = 0; + mean = 0; + median = 0; + firstQuartile = 0; + thirdQuartile = 0; + return; + } + + std::vector vec_val(begin, end); + std::sort(vec_val.begin(), vec_val.end()); + min = vec_val[0]; + max = vec_val[vec_val.size() - 1]; + mean = accumulate(vec_val.begin(), vec_val.end(), Type(0)) / static_cast(vec_val.size()); + median = vec_val[vec_val.size() / 2]; + firstQuartile = vec_val[vec_val.size() / 4]; + thirdQuartile = vec_val[(vec_val.size() * 3) / 4]; } }; -template +template inline std::ostream& operator<<(std::ostream& os, const BoxStats obj) { - os << "\t min: " << obj.min << "\n" - "\t mean: " << obj.mean << "\n" - "\t median: " << obj.median << "\n" - "\t max: " << obj.max << "\n" - "\t first quartile: " << obj.firstQuartile << "\n" - "\t third quartile: " << obj.thirdQuartile; - - return os; + os << "\t min: " << obj.min + << "\n" + "\t mean: " + << obj.mean + << "\n" + "\t median: " + << obj.median + << "\n" + "\t max: " + << obj.max + << "\n" + "\t first quartile: " + << obj.firstQuartile + << "\n" + "\t third quartile: " + << obj.thirdQuartile; + + return os; } /** @@ -482,27 +462,26 @@ inline std::ostream& operator<<(std::ostream& os, const BoxStats obj) ** @param nb_split Number of desired split ** @param d_range Output splitted range **/ -template < typename T > -void SplitRange(const T range_start, const T range_end, const int nb_split, - std::vector< T > & d_range) +template +void SplitRange(const T range_start, const T range_end, const int nb_split, std::vector& d_range) { - const T range_length = range_end - range_start; - if(range_length < nb_split) - { - d_range.push_back(range_start); - d_range.push_back(range_end); - } - else - { - const T delta_range = range_length / nb_split; - - d_range.push_back(range_start); - for(int i = 1; i < nb_split; ++i) + const T range_length = range_end - range_start; + if (range_length < nb_split) + { + d_range.push_back(range_start); + d_range.push_back(range_end); + } + else { - d_range.push_back(range_start + i * delta_range); + const T delta_range = range_length / nb_split; + + d_range.push_back(range_start); + for (int i = 1; i < nb_split; ++i) + { + d_range.push_back(range_start + i * delta_range); + } + d_range.push_back(range_end); } - d_range.push_back(range_end); - } } template @@ -511,9 +490,12 @@ constexpr T divideRoundUp(T x, T y) static_assert(std::is_integral::value, "divideRoundUp only works with integer arguments"); const auto xPos = x >= 0; const auto yPos = y >= 0; - if (xPos == yPos) { + if (xPos == yPos) + { return x / y + T((x % y) != 0); - } else { + } + else + { // negative result, rounds towards zero anyways return x / y; } @@ -529,4 +511,4 @@ constexpr T divideRoundUp(T x, T y) */ void makeRandomOperationsReproducible(); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/numeric/numeric_test.cpp b/src/aliceVision/numeric/numeric_test.cpp index 9ec39868e6..a114893643 100644 --- a/src/aliceVision/numeric/numeric_test.cpp +++ b/src/aliceVision/numeric/numeric_test.cpp @@ -20,99 +20,128 @@ using namespace aliceVision; //-- Assert that stream interface is available -BOOST_AUTO_TEST_CASE ( TinyMatrix_print ) +BOOST_AUTO_TEST_CASE(TinyMatrix_print) { - Mat3 testMatrix = Mat3::Identity(); - ALICEVISION_LOG_DEBUG(testMatrix); + Mat3 testMatrix = Mat3::Identity(); + ALICEVISION_LOG_DEBUG(testMatrix); } -BOOST_AUTO_TEST_CASE ( TinyMatrix_checkIdentity ) +BOOST_AUTO_TEST_CASE(TinyMatrix_checkIdentity) { - Mat3 testMatrix, expected; + Mat3 testMatrix, expected; - // build expected matrix and the test matrix - expected.fill(0); - expected(0,0) = expected(1,1) = expected(2,2) = 1.0; + // build expected matrix and the test matrix + expected.fill(0); + expected(0, 0) = expected(1, 1) = expected(2, 2) = 1.0; - testMatrix.setIdentity(); - ALICEVISION_LOG_DEBUG(testMatrix); - //-- Compare expected to the testMatrix. - EXPECT_MATRIX_NEAR( expected, testMatrix, 1e-8); + testMatrix.setIdentity(); + ALICEVISION_LOG_DEBUG(testMatrix); + //-- Compare expected to the testMatrix. + EXPECT_MATRIX_NEAR(expected, testMatrix, 1e-8); } -BOOST_AUTO_TEST_CASE ( TinyMatrix_product ) +BOOST_AUTO_TEST_CASE(TinyMatrix_product) { - Mat3 a, b, expected; - - // build expected matrix and the test matrix - a(0,0) = 1.0; a(0,1) = 2.0; a(0,2) = 3.0; - a(1,0) = 4.0; a(1,1) = 5.0; a(1,2) = 6.0; - a(2,0) = 7.0; a(2,1) = 8.0; a(2,2) = 9.0; - - b(0,0) = 10.0; b(0,1) = 11.0; b(0,2) = 12.0; - b(1,0) = 13.0; b(1,1) = 14.0; b(1,2) = 15.0; - b(2,0) = 16.0; b(2,1) = 17.0; b(2,2) = 18.0; - - Mat3 resAxB = a*b; - Mat3 expected_resAxB; - { - Mat3 & t = expected_resAxB; - t(0,0) = 84.0; t(0,1) = 90.0; t(0,2) = 96.0; - t(1,0) = 201.0; t(1,1) = 216.0; t(1,2) = 231.0; - t(2,0) = 318.0; t(2,1) = 342.0; t(2,2) = 366.0; - } - - Mat3 resBxA = b*a; - Mat3 expected_resBxA; - { - Mat3 & t = expected_resBxA; - t(0,0) = 138; t(0,1) = 171; t(0,2) = 204; - t(1,0) = 174; t(1,1) = 216; t(1,2) = 258; - t(2,0) = 210; t(2,1) = 261; t(2,2) = 312; - } - - //-- Tests - EXPECT_MATRIX_NEAR( expected_resAxB, resAxB, 1e-8); - EXPECT_MATRIX_NEAR( expected_resBxA, resBxA, 1e-8); + Mat3 a, b, expected; + + // build expected matrix and the test matrix + a(0, 0) = 1.0; + a(0, 1) = 2.0; + a(0, 2) = 3.0; + a(1, 0) = 4.0; + a(1, 1) = 5.0; + a(1, 2) = 6.0; + a(2, 0) = 7.0; + a(2, 1) = 8.0; + a(2, 2) = 9.0; + + b(0, 0) = 10.0; + b(0, 1) = 11.0; + b(0, 2) = 12.0; + b(1, 0) = 13.0; + b(1, 1) = 14.0; + b(1, 2) = 15.0; + b(2, 0) = 16.0; + b(2, 1) = 17.0; + b(2, 2) = 18.0; + + Mat3 resAxB = a * b; + Mat3 expected_resAxB; + { + Mat3& t = expected_resAxB; + t(0, 0) = 84.0; + t(0, 1) = 90.0; + t(0, 2) = 96.0; + t(1, 0) = 201.0; + t(1, 1) = 216.0; + t(1, 2) = 231.0; + t(2, 0) = 318.0; + t(2, 1) = 342.0; + t(2, 2) = 366.0; + } + + Mat3 resBxA = b * a; + Mat3 expected_resBxA; + { + Mat3& t = expected_resBxA; + t(0, 0) = 138; + t(0, 1) = 171; + t(0, 2) = 204; + t(1, 0) = 174; + t(1, 1) = 216; + t(1, 2) = 258; + t(2, 0) = 210; + t(2, 1) = 261; + t(2, 2) = 312; + } + + //-- Tests + EXPECT_MATRIX_NEAR(expected_resAxB, resAxB, 1e-8); + EXPECT_MATRIX_NEAR(expected_resBxA, resBxA, 1e-8); } -BOOST_AUTO_TEST_CASE(TinyMatrix_LookAt) { - // Simple orthogonality check. - Vec3 e; e[0]= 1; e[1] = 2; e[2] = 3; - Mat3 R = LookAt(e); - Mat3 I = Mat3::Identity(); - Mat3 RRT = R*R.transpose(); - Mat3 RTR = R.transpose()*R; - - EXPECT_MATRIX_NEAR(I, RRT, 1e-15); - EXPECT_MATRIX_NEAR(I, RTR, 1e-15); +BOOST_AUTO_TEST_CASE(TinyMatrix_LookAt) +{ + // Simple orthogonality check. + Vec3 e; + e[0] = 1; + e[1] = 2; + e[2] = 3; + Mat3 R = LookAt(e); + Mat3 I = Mat3::Identity(); + Mat3 RRT = R * R.transpose(); + Mat3 RTR = R.transpose() * R; + + EXPECT_MATRIX_NEAR(I, RRT, 1e-15); + EXPECT_MATRIX_NEAR(I, RTR, 1e-15); } -BOOST_AUTO_TEST_CASE(Numeric_buildSubsetMatrix) { - Mat2X A(2, 5); - A << 1, 2, 3, 4, 5, - 6, 7, 8, 9, 10; - Vec2i columns; columns << 0, 2; - Mat2X extracted = buildSubsetMatrix(A, columns); - BOOST_CHECK_SMALL(1- extracted(0,0), 1e-15); - BOOST_CHECK_SMALL(3- extracted(0,1), 1e-15); - BOOST_CHECK_SMALL(6- extracted(1,0), 1e-15); - BOOST_CHECK_SMALL(8- extracted(1,1), 1e-15); +BOOST_AUTO_TEST_CASE(Numeric_buildSubsetMatrix) +{ + Mat2X A(2, 5); + A << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; + Vec2i columns; + columns << 0, 2; + Mat2X extracted = buildSubsetMatrix(A, columns); + BOOST_CHECK_SMALL(1 - extracted(0, 0), 1e-15); + BOOST_CHECK_SMALL(3 - extracted(0, 1), 1e-15); + BOOST_CHECK_SMALL(6 - extracted(1, 0), 1e-15); + BOOST_CHECK_SMALL(8 - extracted(1, 1), 1e-15); } -BOOST_AUTO_TEST_CASE(Numeric_MeanAndVarianceAlongRows) { - int n = 4; - Mat points(2,n); - points << 0, 0, 1, 1, - 0, 2, 1, 3; +BOOST_AUTO_TEST_CASE(Numeric_MeanAndVarianceAlongRows) +{ + int n = 4; + Mat points(2, n); + points << 0, 0, 1, 1, 0, 2, 1, 3; - Vec mean, variance; - MeanAndVarianceAlongRows(points, &mean, &variance); + Vec mean, variance; + MeanAndVarianceAlongRows(points, &mean, &variance); - BOOST_CHECK_SMALL(0.5-mean(0), 1e-8); - BOOST_CHECK_SMALL(1.5-mean(1), 1e-8); - BOOST_CHECK_SMALL(0.25-variance(0), 1e-8); - BOOST_CHECK_SMALL(1.25-variance(1), 1e-8); + BOOST_CHECK_SMALL(0.5 - mean(0), 1e-8); + BOOST_CHECK_SMALL(1.5 - mean(1), 1e-8); + BOOST_CHECK_SMALL(0.25 - variance(0), 1e-8); + BOOST_CHECK_SMALL(1.25 - variance(1), 1e-8); } BOOST_AUTO_TEST_CASE(Numeric_divideRoundUp) diff --git a/src/aliceVision/numeric/polynomial.hpp b/src/aliceVision/numeric/polynomial.hpp index 33af6bfba8..d444108bd1 100644 --- a/src/aliceVision/numeric/polynomial.hpp +++ b/src/aliceVision/numeric/polynomial.hpp @@ -23,87 +23,95 @@ namespace aliceVision { // // The GSL cubic solver was used as a reference for this routine. template -int SolveCubicPolynomial(Real a, Real b, Real c, - Real *x0, Real *x1, Real *x2) { - Real q = a * a - 3 * b; - Real r = 2 * a * a * a - 9 * a * b + 27 * c; +int SolveCubicPolynomial(Real a, Real b, Real c, Real* x0, Real* x1, Real* x2) +{ + Real q = a * a - 3 * b; + Real r = 2 * a * a * a - 9 * a * b + 27 * c; - Real Q = q / 9; - Real R = r / 54; + Real Q = q / 9; + Real R = r / 54; - Real Q3 = Q * Q * Q; - Real R2 = R * R; + Real Q3 = Q * Q * Q; + Real R2 = R * R; - Real CR2 = 729 * r * r; - Real CQ3 = 2916 * q * q * q; + Real CR2 = 729 * r * r; + Real CQ3 = 2916 * q * q * q; - if (R == 0 && Q == 0) { - // Tripple root in one place. - *x0 = *x1 = *x2 = -a / 3 ; - return 3; - - } else if (CR2 == CQ3) { - // This test is actually R2 == Q3, written in a form suitable for exact - // computation with integers. - // - // Due to finite precision some double roots may be missed, and considered - // to be a pair of complex roots z = x +/- epsilon i close to the real - // axis. - Real sqrtQ = sqrt (Q); - if (R > 0) { - *x0 = -2 * sqrtQ - a / 3; - *x1 = sqrtQ - a / 3; - *x2 = sqrtQ - a / 3; - } else { - *x0 = -sqrtQ - a / 3; - *x1 = -sqrtQ - a / 3; - *x2 = 2 * sqrtQ - a / 3; + if (R == 0 && Q == 0) + { + // Tripple root in one place. + *x0 = *x1 = *x2 = -a / 3; + return 3; } - return 3; - - } else if (CR2 < CQ3) { - // This case is equivalent to R2 < Q3. - Real sqrtQ = sqrt (Q); - Real sqrtQ3 = sqrtQ * sqrtQ * sqrtQ; - Real theta = acos (R / sqrtQ3); - Real norm = -2 * sqrtQ; - *x0 = norm * cos (theta / 3) - a / 3; - *x1 = norm * cos ((theta + 2.0 * M_PI) / 3) - a / 3; - *x2 = norm * cos ((theta - 2.0 * M_PI) / 3) - a / 3; - - // Put the roots in ascending order. - if (*x0 > *x1) { - std::swap(*x0, *x1); + else if (CR2 == CQ3) + { + // This test is actually R2 == Q3, written in a form suitable for exact + // computation with integers. + // + // Due to finite precision some double roots may be missed, and considered + // to be a pair of complex roots z = x +/- epsilon i close to the real + // axis. + Real sqrtQ = sqrt(Q); + if (R > 0) + { + *x0 = -2 * sqrtQ - a / 3; + *x1 = sqrtQ - a / 3; + *x2 = sqrtQ - a / 3; + } + else + { + *x0 = -sqrtQ - a / 3; + *x1 = -sqrtQ - a / 3; + *x2 = 2 * sqrtQ - a / 3; + } + return 3; } - if (*x1 > *x2) { - std::swap(*x1, *x2); - if (*x0 > *x1) { - std::swap(*x0, *x1); - } + else if (CR2 < CQ3) + { + // This case is equivalent to R2 < Q3. + Real sqrtQ = sqrt(Q); + Real sqrtQ3 = sqrtQ * sqrtQ * sqrtQ; + Real theta = acos(R / sqrtQ3); + Real norm = -2 * sqrtQ; + *x0 = norm * cos(theta / 3) - a / 3; + *x1 = norm * cos((theta + 2.0 * M_PI) / 3) - a / 3; + *x2 = norm * cos((theta - 2.0 * M_PI) / 3) - a / 3; + + // Put the roots in ascending order. + if (*x0 > *x1) + { + std::swap(*x0, *x1); + } + if (*x1 > *x2) + { + std::swap(*x1, *x2); + if (*x0 > *x1) + { + std::swap(*x0, *x1); + } + } + return 3; } - return 3; - } - Real sgnR = (R >= 0 ? 1 : -1); - Real A = -sgnR * pow (fabs (R) + sqrt (R2 - Q3), 1.0/3.0); - Real B = Q / A ; - *x0 = A + B - a / 3; - return 1; + Real sgnR = (R >= 0 ? 1 : -1); + Real A = -sgnR * pow(fabs(R) + sqrt(R2 - Q3), 1.0 / 3.0); + Real B = Q / A; + *x0 = A + B - a / 3; + return 1; } // The coefficients are in ascending powers, i.e. coeffs[N]*x^N. template -int SolveCubicPolynomial(const Real *coeffs, Real *solutions) { - if (coeffs[0] == 0.0) { - // TODO(keir): This is a quadratic not a cubic. Implement a quadratic - // solver! - return 0; - } - Real a = coeffs[2] / coeffs[3]; - Real b = coeffs[1] / coeffs[3]; - Real c = coeffs[0] / coeffs[3]; - return SolveCubicPolynomial(a, b, c, - solutions + 0, - solutions + 1, - solutions + 2); +int SolveCubicPolynomial(const Real* coeffs, Real* solutions) +{ + if (coeffs[0] == 0.0) + { + // TODO(keir): This is a quadratic not a cubic. Implement a quadratic + // solver! + return 0; + } + Real a = coeffs[2] / coeffs[3]; + Real b = coeffs[1] / coeffs[3]; + Real c = coeffs[0] / coeffs[3]; + return SolveCubicPolynomial(a, b, c, solutions + 0, solutions + 1, solutions + 2); } } // namespace aliceVision diff --git a/src/aliceVision/numeric/polynomial_test.cpp b/src/aliceVision/numeric/polynomial_test.cpp index 640f545be3..22040b9671 100644 --- a/src/aliceVision/numeric/polynomial_test.cpp +++ b/src/aliceVision/numeric/polynomial_test.cpp @@ -23,11 +23,11 @@ using namespace aliceVision; // // x^3 - (c+b+a) * x^2 + (a*b+(b+a)*c) * x - a*b*c = 0. // = p = q = r -void CoeffsForCubicZeros(double a, double b, double c, - double *p, double *q, double *r) { - *p = -(c + b + a); - *q = (a * b + (b + a) * c); - *r = -a * b * c; +void CoeffsForCubicZeros(double a, double b, double c, double* p, double* q, double* r) +{ + *p = -(c + b + a); + *q = (a * b + (b + a) * c); + *r = -a * b * c; } // Find the polynomial coefficients of x in the equation // @@ -37,50 +37,61 @@ void CoeffsForCubicZeros(double a, double b, double c, // // x^4 - (d+c+b+a) * x^3 + (d*(c+b+a) + a*b+(b+a)*c) * x^2 // - (d*(a*b+(b+a)*c)+a*b*c) * x + a*b*c*d = 0. -void CoeffsForQuarticZeros(double a, double b, double c, double d, - double *p, double *q, double *r, double *s) { - *p = -(d + c + b + a); - *q = (d * (c + b + a) + a * b + (b + a) * c); - *r = -(d * (a * b + (b + a) * c) + a * b * c); - *s = a * b * c *d; +void CoeffsForQuarticZeros(double a, double b, double c, double d, double* p, double* q, double* r, double* s) +{ + *p = -(d + c + b + a); + *q = (d * (c + b + a) + a * b + (b + a) * c); + *r = -(d * (a * b + (b + a) * c) + a * b * c); + *s = a * b * c * d; } -BOOST_AUTO_TEST_CASE(Poly_SolveCubicPolynomial) { - double a, b, c, aa, bb, cc; - double p, q, r; +BOOST_AUTO_TEST_CASE(Poly_SolveCubicPolynomial) +{ + double a, b, c, aa, bb, cc; + double p, q, r; - a = 1; b = 2; c = 3; - CoeffsForCubicZeros(a, b, c, &p, &q, &r); - BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p,q,r, &aa, &bb, &cc)); - BOOST_CHECK_SMALL(a-aa, 1e-10); - BOOST_CHECK_SMALL(b-bb, 1e-10); - BOOST_CHECK_SMALL(c-cc, 1e-10); + a = 1; + b = 2; + c = 3; + CoeffsForCubicZeros(a, b, c, &p, &q, &r); + BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p, q, r, &aa, &bb, &cc)); + BOOST_CHECK_SMALL(a - aa, 1e-10); + BOOST_CHECK_SMALL(b - bb, 1e-10); + BOOST_CHECK_SMALL(c - cc, 1e-10); - a = 0; b = 1; c = 3; - CoeffsForCubicZeros(a, b, c, &p, &q, &r); - BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p,q,r, &aa, &bb, &cc)); - BOOST_CHECK_SMALL(a-aa, 1e-10); - BOOST_CHECK_SMALL(b-bb, 1e-10); - BOOST_CHECK_SMALL(c-cc, 1e-10); + a = 0; + b = 1; + c = 3; + CoeffsForCubicZeros(a, b, c, &p, &q, &r); + BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p, q, r, &aa, &bb, &cc)); + BOOST_CHECK_SMALL(a - aa, 1e-10); + BOOST_CHECK_SMALL(b - bb, 1e-10); + BOOST_CHECK_SMALL(c - cc, 1e-10); - a = -10; b = 0; c = 1; - CoeffsForCubicZeros(a, b, c, &p, &q, &r); - BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p,q,r, &aa, &bb, &cc)); - BOOST_CHECK_SMALL(a-aa, 1e-10); - BOOST_CHECK_SMALL(b-bb, 1e-10); - BOOST_CHECK_SMALL(c-cc, 1e-10); + a = -10; + b = 0; + c = 1; + CoeffsForCubicZeros(a, b, c, &p, &q, &r); + BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p, q, r, &aa, &bb, &cc)); + BOOST_CHECK_SMALL(a - aa, 1e-10); + BOOST_CHECK_SMALL(b - bb, 1e-10); + BOOST_CHECK_SMALL(c - cc, 1e-10); - a = -8; b = 1; c = 3; - CoeffsForCubicZeros(a, b, c, &p, &q, &r); - BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p,q,r, &aa, &bb, &cc)); - BOOST_CHECK_SMALL(a-aa, 1e-10); - BOOST_CHECK_SMALL(b-bb, 1e-10); - BOOST_CHECK_SMALL(c-cc, 1e-10); + a = -8; + b = 1; + c = 3; + CoeffsForCubicZeros(a, b, c, &p, &q, &r); + BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p, q, r, &aa, &bb, &cc)); + BOOST_CHECK_SMALL(a - aa, 1e-10); + BOOST_CHECK_SMALL(b - bb, 1e-10); + BOOST_CHECK_SMALL(c - cc, 1e-10); - a = 28; b = 28; c = 105; - CoeffsForCubicZeros(a, b, c, &p, &q, &r); - BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p,q,r, &aa, &bb, &cc)); - BOOST_CHECK_SMALL(a-aa, 1e-10); - BOOST_CHECK_SMALL(b-bb, 1e-10); - BOOST_CHECK_SMALL(c-cc, 1e-10); + a = 28; + b = 28; + c = 105; + CoeffsForCubicZeros(a, b, c, &p, &q, &r); + BOOST_CHECK_EQUAL(3, SolveCubicPolynomial(p, q, r, &aa, &bb, &cc)); + BOOST_CHECK_SMALL(a - aa, 1e-10); + BOOST_CHECK_SMALL(b - bb, 1e-10); + BOOST_CHECK_SMALL(c - cc, 1e-10); } diff --git a/src/aliceVision/numeric/projection.cpp b/src/aliceVision/numeric/projection.cpp index bc99e969e9..fa854377ea 100644 --- a/src/aliceVision/numeric/projection.cpp +++ b/src/aliceVision/numeric/projection.cpp @@ -13,245 +13,172 @@ namespace aliceVision { /// Compute P = K[R|t] -void P_from_KRt(const Mat3 &K, const Mat3 &R, const Vec3 &t, Mat34 &P) -{ - P = P_from_KRt(K, R, t); -} +void P_from_KRt(const Mat3& K, const Mat3& R, const Vec3& t, Mat34& P) { P = P_from_KRt(K, R, t); } + +Mat34 P_from_KRt(const Mat3& K, const Mat3& R, const Vec3& t) { return K * HStack(R, t); } + +void KRt_from_P(const Mat34& P, Mat3& Kp, Mat3& Rp, Vec3& tp) +{ + // Decompose using the RQ decomposition HZ A4.1.1 pag.579. + Mat3 K = P.block(0, 0, 3, 3); + + Mat3 Q; + Q.setIdentity(); + + // Set K(2,1) to zero. + if (K(2, 1) != 0) + { + double c = -K(2, 2); + double s = K(2, 1); + const double length = std::hypot(c, s); + c /= length; + s /= length; + Mat3 Qx; + Qx << 1, 0, 0, 0, c, -s, 0, s, c; + K = K * Qx; + Q = Qx.transpose() * Q; + } + // Set K(2,0) to zero. + if (K(2, 0) != 0) + { + double c = K(2, 2); + double s = K(2, 0); + const double length = std::hypot(c, s); + c /= length; + s /= length; + Mat3 Qy; + Qy << c, 0, s, 0, 1, 0, -s, 0, c; + K = K * Qy; + Q = Qy.transpose() * Q; + } + // Set K(1,0) to zero. + if (K(1, 0) != 0) + { + double c = -K(1, 1); + double s = K(1, 0); + const double length = std::hypot(c, s); + c /= length; + s /= length; + Mat3 Qz; + Qz << c, -s, 0, s, c, 0, 0, 0, 1; + K = K * Qz; + Q = Qz.transpose() * Q; + } + + Mat3 R = Q; + + // Mat3 H = P.block(0, 0, 3, 3); + // RQ decomposition + // Eigen::HouseholderQR qr(H); + // Mat3 K = qr.matrixQR().triangularView(); + // Mat3 R = qr.householderQ(); + + // Ensure that the diagonal is positive and R determinant == 1. + if (K(2, 2) < 0) + { + K = -K; + R = -R; + } + if (K(1, 1) < 0) + { + Mat3 S; + S << 1, 0, 0, 0, -1, 0, 0, 0, 1; + K = K * S; + R = S * R; + } + if (K(0, 0) < 0) + { + Mat3 S; + S << -1, 0, 0, 0, 1, 0, 0, 0, 1; + K = K * S; + R = S * R; + } + + // Compute translation. + Eigen::PartialPivLU lu(K); + Vec3 t = lu.solve(P.col(3)); + + if (R.determinant() < 0) + { + R = -R; + t = -t; + } + + // scale K so that K(2,2) = 1 + K = K / K(2, 2); + + Kp = K; + Rp = R; + tp = t; +} + +Mat3 F_from_P(const Mat34& P1, const Mat34& P2) +{ + Mat3 F12; + + typedef Eigen::Matrix Mat24; + Mat24 X1 = P1.block<2, 4>(1, 0); + Mat24 X2; + X2 << P1.row(2), P1.row(0); + Mat24 X3 = P1.block<2, 4>(0, 0); + Mat24 Y1 = P2.block<2, 4>(1, 0); + Mat24 Y2; + Y2 << P2.row(2), P2.row(0); + Mat24 Y3 = P2.block<2, 4>(0, 0); + + Mat4 X1Y1, X2Y1, X3Y1, X1Y2, X2Y2, X3Y2, X1Y3, X2Y3, X3Y3; + X1Y1 << X1, Y1; + X2Y1 << X2, Y1; + X3Y1 << X3, Y1; + X1Y2 << X1, Y2; + X2Y2 << X2, Y2; + X3Y2 << X3, Y2; + X1Y3 << X1, Y3; + X2Y3 << X2, Y3; + X3Y3 << X3, Y3; + + F12 << X1Y1.determinant(), X2Y1.determinant(), X3Y1.determinant(), X1Y2.determinant(), X2Y2.determinant(), X3Y2.determinant(), X1Y3.determinant(), + X2Y3.determinant(), X3Y3.determinant(); + + return F12; +} -Mat34 P_from_KRt(const Mat3 &K, const Mat3 &R, const Vec3 &t) -{ - return K * HStack(R,t); -} +Vec2 project(const Mat34& P, const Vec3& X) { return Vec3(P * X.homogeneous()).hnormalized(); } -void KRt_from_P(const Mat34 &P, Mat3 &Kp, Mat3 &Rp, Vec3 &tp) -{ - - // Decompose using the RQ decomposition HZ A4.1.1 pag.579. - Mat3 K = P.block(0, 0, 3, 3); - - Mat3 Q; - Q.setIdentity(); - - // Set K(2,1) to zero. - if (K(2,1) != 0) - { - double c = -K(2,2); - double s = K(2,1); - const double length = std::hypot(c, s); - c /= length; - s /= length; - Mat3 Qx; - Qx << 1, 0, 0, - 0, c, -s, - 0, s, c; - K = K * Qx; - Q = Qx.transpose() * Q; - } - // Set K(2,0) to zero. - if (K(2,0) != 0) - { - double c = K(2,2); - double s = K(2,0); - const double length = std::hypot(c, s); - c /= length; - s /= length; - Mat3 Qy; - Qy << c, 0, s, - 0, 1, 0, - -s, 0, c; - K = K * Qy; - Q = Qy.transpose() * Q; - } - // Set K(1,0) to zero. - if (K(1,0) != 0) - { - double c = -K(1,1); - double s = K(1,0); - const double length = std::hypot(c, s); - c /= length; - s /= length; - Mat3 Qz; - Qz << c,-s, 0, - s, c, 0, - 0, 0, 1; - K = K * Qz; - Q = Qz.transpose() * Q; - } - - Mat3 R = Q; - - //Mat3 H = P.block(0, 0, 3, 3); - // RQ decomposition - //Eigen::HouseholderQR qr(H); - //Mat3 K = qr.matrixQR().triangularView(); - //Mat3 R = qr.householderQ(); - - // Ensure that the diagonal is positive and R determinant == 1. - if (K(2,2) < 0) - { - K = -K; - R = -R; - } - if (K(1,1) < 0) - { - Mat3 S; - S << 1, 0, 0, - 0,-1, 0, - 0, 0, 1; - K = K * S; - R = S * R; - } - if (K(0,0) < 0) - { - Mat3 S; - S << -1, 0, 0, - 0, 1, 0, - 0, 0, 1; - K = K * S; - R = S * R; - } - - // Compute translation. - Eigen::PartialPivLU lu(K); - Vec3 t = lu.solve(P.col(3)); - - if(R.determinant() < 0) - { - R = -R; - t = -t; - } - - // scale K so that K(2,2) = 1 - K = K / K(2,2); - - Kp = K; - Rp = R; - tp = t; -} +void project(const Mat34& P, const Mat3X& X, Mat2X& x) { project(P, Mat4X(X.colwise().homogeneous()), x); } -Mat3 F_from_P(const Mat34 & P1, const Mat34 & P2) -{ - Mat3 F12; - - typedef Eigen::Matrix Mat24; - Mat24 X1 = P1.block<2, 4>(1, 0); - Mat24 X2; - X2 << P1.row(2), P1.row(0); - Mat24 X3 = P1.block<2, 4>(0, 0); - Mat24 Y1 = P2.block<2, 4>(1, 0); - Mat24 Y2; - Y2 << P2.row(2), P2.row(0); - Mat24 Y3 = P2.block<2, 4>(0, 0); - - - Mat4 X1Y1, X2Y1, X3Y1, X1Y2, X2Y2, X3Y2, X1Y3, X2Y3, X3Y3; - X1Y1 << X1, Y1; - X2Y1 << X2, Y1; - X3Y1 << X3, Y1; - X1Y2 << X1, Y2; - X2Y2 << X2, Y2; - X3Y2 << X3, Y2; - X1Y3 << X1, Y3; - X2Y3 << X2, Y3; - X3Y3 << X3, Y3; - - - F12 << X1Y1.determinant(), X2Y1.determinant(), X3Y1.determinant(), - X1Y2.determinant(), X2Y2.determinant(), X3Y2.determinant(), - X1Y3.determinant(), X2Y3.determinant(), X3Y3.determinant(); - - return F12; -} +void project(const Mat34& P, const Mat4X& X, Mat2X& x) { x = project(P, X); } -Vec2 project(const Mat34 &P, const Vec3 &X) -{ - return Vec3(P * X.homogeneous()).hnormalized(); -} +Mat2X project(const Mat34& P, const Mat3X& X) { return project(P, Mat4X(X.colwise().homogeneous())); } -void project(const Mat34 &P, const Mat3X &X, Mat2X &x) -{ - project(P, Mat4X(X.colwise().homogeneous()), x); -} +Mat2X project(const Mat34& P, const Mat4X& X) { return Mat3X(P * X).colwise().hnormalized(); } -void project(const Mat34 &P, const Mat4X &X, Mat2X &x) -{ - x = project(P, X); -} - -Mat2X project(const Mat34 &P, const Mat3X &X) -{ - return project(P, Mat4X(X.colwise().homogeneous())); -} - -Mat2X project(const Mat34 &P, const Mat4X &X) -{ - return Mat3X(P * X).colwise().hnormalized(); -} - -void homogeneousToEuclidean(const Vec4 &H, Vec3 & X) -{ - X = H.hnormalized(); -} +void homogeneousToEuclidean(const Vec4& H, Vec3& X) { X = H.hnormalized(); } -void euclideanToHomogeneous(const Mat &X, Mat & H) -{ - H = X.colwise().homogeneous(); -} +void euclideanToHomogeneous(const Mat& X, Mat& H) { H = X.colwise().homogeneous(); } -double Depth(const Mat3 &R, const Vec3 &t, const Vec3 &X) -{ - return (R*X)[2] + t[2]; -} +double Depth(const Mat3& R, const Vec3& t, const Vec3& X) { return (R * X)[2] + t[2]; } -Vecb cheiralityTest(const Mat3 &R, const Vec3 &t, const Mat3X &X) -{ - return ((R*X).row(2) + t[2]*Mat::Ones(1, X.cols())).array() >= .0; -} +Vecb cheiralityTest(const Mat3& R, const Vec3& t, const Mat3X& X) { return ((R * X).row(2) + t[2] * Mat::Ones(1, X.cols())).array() >= .0; } -bool cheiralityTestAll(const Mat3 &R, const Vec3 &t, const Mat3X &X) -{ - return cheiralityTest(R, t, X).all(); -} +bool cheiralityTestAll(const Mat3& R, const Vec3& t, const Mat3X& X) { return cheiralityTest(R, t, X).all(); } -Vec3 euclideanToHomogeneous(const Vec2 &x) -{ - return x.homogeneous(); -} +Vec3 euclideanToHomogeneous(const Vec2& x) { return x.homogeneous(); } -void homogeneousToEuclidean(const Mat &H, Mat &X) -{ - X = H.colwise().hnormalized(); -} +void homogeneousToEuclidean(const Mat& H, Mat& X) { X = H.colwise().hnormalized(); } -Mat3X euclideanToHomogeneous(const Mat2X &x) -{ - return x.colwise().homogeneous(); -} +Mat3X euclideanToHomogeneous(const Mat2X& x) { return x.colwise().homogeneous(); } -void euclideanToHomogeneous(const Mat2X &x, Mat3X &h) -{ - h = x.colwise().homogeneous(); -} +void euclideanToHomogeneous(const Mat2X& x, Mat3X& h) { h = x.colwise().homogeneous(); } -void homogeneousToEuclidean(const Mat3X &h, Mat2X &e) -{ - e = h.colwise().hnormalized(); -} +void homogeneousToEuclidean(const Mat3X& h, Mat2X& e) { e = h.colwise().hnormalized(); } -void euclideanToNormalizedCamera(const Mat2X &x, const Mat3 &K, Mat2X &n) -{ - homogeneousToNormalizedCamera(x.colwise().homogeneous(), K, n); -} +void euclideanToNormalizedCamera(const Mat2X& x, const Mat3& K, Mat2X& n) { homogeneousToNormalizedCamera(x.colwise().homogeneous(), K, n); } -void homogeneousToNormalizedCamera(const Mat3X &x, const Mat3 &K, Mat2X &n) -{ - n = (K.inverse() * x).colwise().hnormalized(); -} +void homogeneousToNormalizedCamera(const Mat3X& x, const Mat3& K, Mat2X& n) { n = (K.inverse() * x).colwise().hnormalized(); } /// Estimates the root mean square error (2D) -double reprojectionErrorRMSE(const Mat2X &x_image, - const Mat4X &X_world, - const Mat34 &P) +double reprojectionErrorRMSE(const Mat2X& x_image, const Mat4X& X_world, const Mat34& P) { const std::size_t num_points = x_image.cols(); const Mat2X dx = project(P, X_world) - x_image; @@ -259,16 +186,11 @@ double reprojectionErrorRMSE(const Mat2X &x_image, } /// Estimates the root mean square error (2D) -double reprojectionErrorRMSE(const Mat2X &x_image, - const Mat3X &X_world, - const Mat3 &K, - const Mat3 &R, - const Vec3 &t) +double reprojectionErrorRMSE(const Mat2X& x_image, const Mat3X& X_world, const Mat3& K, const Mat3& R, const Vec3& t) { Mat34 P; P_from_KRt(K, R, t, P); return reprojectionErrorRMSE(x_image, X_world.colwise().homogeneous(), P); } -} // namespace aliceVision - +} // namespace aliceVision diff --git a/src/aliceVision/numeric/projection.hpp b/src/aliceVision/numeric/projection.hpp index 6cb51a53bf..aa4da8a18e 100644 --- a/src/aliceVision/numeric/projection.hpp +++ b/src/aliceVision/numeric/projection.hpp @@ -20,7 +20,7 @@ namespace aliceVision { /** * @brief Compute P = K[R|t] */ -void P_from_KRt(const Mat3& K, const Mat3& R, const Vec3& t, Mat34 & P); +void P_from_KRt(const Mat3& K, const Mat3& R, const Vec3& t, Mat34& P); /** * @brief Compute P = K[R|t] @@ -31,7 +31,7 @@ Mat34 P_from_KRt(const Mat3& K, const Mat3& R, const Vec3& t); /** * @brief Decompose using the RQ decomposition HZ A4.1.1 pag.579. */ -void KRt_from_P(const Mat34& P, Mat3 & Kp, Mat3 & Rp, Vec3 & tp); +void KRt_from_P(const Mat34& P, Mat3& Kp, Mat3& Rp, Vec3& tp); /** * @brief Compute a fundamental matrix from projection matrices @@ -51,7 +51,7 @@ double Depth(const Mat3& R, const Vec3& t, const Vec3& X); * @return A vector of boolean of the same size as the number ot points. The corresponding value is true if the point * is in front of the camera, false otherwise. */ -Vecb cheiralityTest(const Mat3 &R, const Vec3 &t, const Mat3X &X); +Vecb cheiralityTest(const Mat3& R, const Vec3& t, const Mat3X& X); /** * @brief Test whether all the given points are in front of the camera. @@ -60,7 +60,7 @@ Vecb cheiralityTest(const Mat3 &R, const Vec3 &t, const Mat3X &X); * @param[in] X the 3D points to test. * @return true if all the points is in front of the camera, false otherwise. */ -bool cheiralityTestAll(const Mat3 &R, const Vec3 &t, const Mat3X &X); +bool cheiralityTestAll(const Mat3& R, const Vec3& t, const Mat3X& X); /** * @brief Compute P*[X|1.0]. Transformed from homogeneous to euclidean coordinates. @@ -70,7 +70,7 @@ Vec2 project(const Mat34& P, const Vec3& X); /** * @brief Compute P*[X|1.0] for the X list of point (3D point). */ -void project(const Mat34& P, const Mat3X& X, Mat2X & x); +void project(const Mat34& P, const Mat3X& X, Mat2X& x); /** * @brief Compute P*[X|1.0] for the X list of point (4D point). @@ -95,7 +95,7 @@ void homogeneousToEuclidean(const Vec4& H, Vec3& X); /** * @brief Change euclidean coordinates to homogeneous. */ -void euclideanToHomogeneous(const Mat& X, Mat & H); +void euclideanToHomogeneous(const Mat& X, Mat& H); /** * @brief Change euclidean coordinates to homogeneous. @@ -105,7 +105,7 @@ Vec3 euclideanToHomogeneous(const Vec2& x); /** * @brief Change homogeneous coordinates to euclidean. */ -void homogeneousToEuclidean(const Mat& H, Mat & X); +void homogeneousToEuclidean(const Mat& H, Mat& X); /** * @brief Change euclidean coordinates to homogeneous. @@ -115,7 +115,7 @@ Mat3X euclideanToHomogeneous(const Mat2X& x); /** * @brief Change euclidean coordinates to homogeneous. */ -void euclideanToHomogeneous(const Mat2X& x, Mat3X & h); +void euclideanToHomogeneous(const Mat2X& x, Mat3X& h); /** * @brief Change homogeneous coordinates to euclidean. @@ -142,4 +142,4 @@ double reprojectionErrorRMSE(const Mat2X& x_image, const Mat4X& X_world, const M */ double reprojectionErrorRMSE(const Mat2X& x_image, const Mat3X& X_world, const Mat3& K, const Mat3& R, const Vec3& t); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/numeric/projection_test.cpp b/src/aliceVision/numeric/projection_test.cpp index ce64b22d6a..11876f6c12 100644 --- a/src/aliceVision/numeric/projection_test.cpp +++ b/src/aliceVision/numeric/projection_test.cpp @@ -20,19 +20,19 @@ BOOST_AUTO_TEST_CASE(Numeric_Cheirality) { const Eigen::Index numPts{10}; Mat3X pt3d(3, numPts); - pt3d << 0.680375, 0.59688, -0.329554, 0.10794, -0.270431, 0.83239, -0.716795, -0.514226, -0.686642, -0.782382, - -0.211234, 0.823295, 0.536459, -0.0452059, 0.0268018, 0.271423, 0.213938, -0.725537, -0.198111, 0.997849, - 0.566198, -0.604897, -0.444451, 0.257742, 0.904459, 0.434594, -0.967399, 0.608354, -0.740419, -0.563486; + pt3d << 0.680375, 0.59688, -0.329554, 0.10794, -0.270431, 0.83239, -0.716795, -0.514226, -0.686642, -0.782382, -0.211234, 0.823295, 0.536459, + -0.0452059, 0.0268018, 0.271423, 0.213938, -0.725537, -0.198111, 0.997849, 0.566198, -0.604897, -0.444451, 0.257742, 0.904459, 0.434594, + -0.967399, 0.608354, -0.740419, -0.563486; const Mat3 R = RotationAroundZ(0.3) * RotationAroundX(0.1) * RotationAroundY(0.2); - const Vec3 t = {0.0258648, 0.678224, 0.22528}; + const Vec3 t = {0.0258648, 0.678224, 0.22528}; Vecb expected = Vecb(numPts); expected << true, false, false, true, true, true, false, true, false, false; const Vecb test = cheiralityTest(R, t, pt3d); - for(Eigen::Index i = 0; i < numPts; ++i) + for (Eigen::Index i = 0; i < numPts; ++i) { BOOST_CHECK_EQUAL(expected(i), test(i)); } diff --git a/src/aliceVision/panorama/alphaCompositer.hpp b/src/aliceVision/panorama/alphaCompositer.hpp index 4c4009f795..ac39e3a042 100644 --- a/src/aliceVision/panorama/alphaCompositer.hpp +++ b/src/aliceVision/panorama/alphaCompositer.hpp @@ -8,34 +8,35 @@ #include "compositer.hpp" -namespace aliceVision -{ +namespace aliceVision { class AlphaCompositer : public Compositer { -public: - AlphaCompositer(size_t outputWidth, size_t outputHeight) - : Compositer(outputWidth, outputHeight) - { - } + public: + AlphaCompositer(size_t outputWidth, size_t outputHeight) + : Compositer(outputWidth, outputHeight) + {} virtual bool append(aliceVision::image::Image& color, aliceVision::image::Image& inputMask, - aliceVision::image::Image& inputWeights, - int offsetX, int offsetY) + aliceVision::image::Image& inputWeights, + int offsetX, + int offsetY) { - offsetX -= _outputRoi.left; - offsetY -= _outputRoi.top; + offsetX -= _outputRoi.left; + offsetY -= _outputRoi.top; - for(int i = 0; i < color.Height(); i++) + for (int i = 0; i < color.Height(); i++) { int y = i + offsetY; - if (y < 0 || y >= _outputRoi.height) continue; + if (y < 0 || y >= _outputRoi.height) + continue; for (int j = 0; j < color.Width(); j++) { int x = j + offsetX; - if (x < 0 || x >= _outputRoi.width) continue; + if (x < 0 || x >= _outputRoi.width) + continue; if (!inputMask(i, j)) { @@ -56,21 +57,21 @@ class AlphaCompositer : public Compositer virtual bool terminate() { - for (int i = 0; i < _panorama.Height(); i++) + for (int i = 0; i < _panorama.Height(); i++) { for (int j = 0; j < _panorama.Width(); j++) { image::RGBAfColor r; image::RGBAfColor c = _panorama(i, j); - if (c.a() < 1e-6f) + if (c.a() < 1e-6f) { r.r() = 1.0f; - r.g() = 0.0f; + r.g() = 0.0f; r.b() = 0.0f; r.a() = 0.0f; } - else + else { r.r() = c.r() / c.a(); r.g() = c.g() / c.a(); @@ -86,4 +87,4 @@ class AlphaCompositer : public Compositer } }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/boundingBox.cpp b/src/aliceVision/panorama/boundingBox.cpp index e8fde6a634..8770394716 100644 --- a/src/aliceVision/panorama/boundingBox.cpp +++ b/src/aliceVision/panorama/boundingBox.cpp @@ -6,8 +6,7 @@ #include "boundingBox.hpp" -namespace aliceVision -{ +namespace aliceVision { std::ostream& operator<<(std::ostream& os, const BoundingBox& in) { @@ -19,4 +18,4 @@ std::ostream& operator<<(std::ostream& os, const BoundingBox& in) return os; } -} \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/boundingBox.hpp b/src/aliceVision/panorama/boundingBox.hpp index 2aae8e846b..1faeef000c 100644 --- a/src/aliceVision/panorama/boundingBox.hpp +++ b/src/aliceVision/panorama/boundingBox.hpp @@ -12,12 +12,10 @@ #include #include -namespace aliceVision -{ +namespace aliceVision { struct BoundingBox { - int left; int top; int width; @@ -32,32 +30,22 @@ struct BoundingBox }; BoundingBox(int l, int t, int w, int h) - : left(l) - , top(t) - , width(w) - , height(h) - { - } + : left(l), + top(t), + width(w), + height(h) + {} - int getRight() const { - return left + width - 1; - } + int getRight() const { return left + width - 1; } - int getBottom() const { - return top + height - 1; - } + int getBottom() const { return top + height - 1; } - bool isEmpty() const { - return (width <= 0 || height <= 0); - } + bool isEmpty() const { return (width <= 0 || height <= 0); } - int area() const { - return width * height; - } + int area() const { return width * height; } void snapToGrid(uint32_t gridSize) { - int right = getRight(); int bottom = getBottom(); @@ -118,22 +106,25 @@ struct BoundingBox return intersection; } - bool isInside(const BoundingBox& other) const + bool isInside(const BoundingBox& other) const { + if (other.left > left) + return false; + if (other.top > top) + return false; - if (other.left > left) return false; - if (other.top > top) return false; - - if (other.getRight() < getRight()) return false; - if (other.getBottom() < getBottom()) return false; + if (other.getRight() < getRight()) + return false; + if (other.getBottom() < getBottom()) + return false; return true; } - BoundingBox dilate(int units) + BoundingBox dilate(int units) { BoundingBox b; - + b.left = left - units; b.top = top - units; b.width = width + units * 2; @@ -142,9 +133,9 @@ struct BoundingBox return b; } - void clampLeft() + void clampLeft() { - if (left < 0) + if (left < 0) { width += left; left = 0; @@ -160,9 +151,9 @@ struct BoundingBox } } - void clampTop() + void clampTop() { - if (top < 0) + if (top < 0) { height += top; top = 0; @@ -218,37 +209,37 @@ struct BoundingBox int nwidth = getRight() - sleft; int nheight = getBottom() - stop; - + b.width = int(ceil(double(nwidth) / double(factor))); b.height = int(ceil(double(nheight) / double(factor))); return b; } - BoundingBox limitInside(const BoundingBox & other) const + BoundingBox limitInside(const BoundingBox& other) const { BoundingBox b; b.left = left; - if (b.left < other.left) + if (b.left < other.left) { b.left = other.left; } b.top = top; - if (b.top < other.top) + if (b.top < other.top) { b.top = other.top; } int nright = getRight(); - if (nright > other.getRight()) + if (nright > other.getRight()) { nright = other.getRight(); } int nbottom = getBottom(); - if (nbottom > other.getBottom()) + if (nbottom > other.getBottom()) { nbottom = other.getBottom(); } @@ -262,4 +253,4 @@ struct BoundingBox std::ostream& operator<<(std::ostream& os, const BoundingBox& in); -} +} // namespace aliceVision diff --git a/src/aliceVision/panorama/cachedImage.cpp b/src/aliceVision/panorama/cachedImage.cpp index 105492b12e..3488c9e7c6 100644 --- a/src/aliceVision/panorama/cachedImage.cpp +++ b/src/aliceVision/panorama/cachedImage.cpp @@ -6,44 +6,39 @@ #include "cachedImage.hpp" -namespace aliceVision -{ +namespace aliceVision { -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType) +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType) { - std::unique_ptr out = oiio::ImageOutput::create(path); - if(!out) + if (!out) { return false; } oiio::TypeDesc typeColor = oiio::TypeDesc::FLOAT; - if (storageDataType == image::EStorageDataType::Half || storageDataType == image::EStorageDataType::HalfFinite) + if (storageDataType == image::EStorageDataType::Half || storageDataType == image::EStorageDataType::HalfFinite) { typeColor = oiio::TypeDesc::HALF; - } - + } oiio::ImageSpec spec(_width, _height, 4, typeColor); spec.tile_width = _tileSize; spec.tile_height = _tileSize; - if(!out->open(path, spec)) + if (!out->open(path, spec)) { return false; } - - for(int i = 0; i < _tilesArray.size(); i++) - { + for (int i = 0; i < _tilesArray.size(); i++) + { std::vector& row = _tilesArray[i]; - for(int j = 0; j < row.size(); j++) + for (int j = 0; j < row.size(); j++) { - - if(!row[j]->acquire()) + if (!row[j]->acquire()) { return false; } @@ -59,40 +54,37 @@ bool CachedImage::writeImage(const std::string& path, const i return true; } -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType) +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType) { - std::unique_ptr out = oiio::ImageOutput::create(path); - if(!out) + if (!out) { return false; } oiio::TypeDesc typeColor = oiio::TypeDesc::FLOAT; - if (storageDataType == image::EStorageDataType::Half || storageDataType == image::EStorageDataType::HalfFinite) + if (storageDataType == image::EStorageDataType::Half || storageDataType == image::EStorageDataType::HalfFinite) { typeColor = oiio::TypeDesc::HALF; - } + } oiio::ImageSpec spec(_width, _height, 3, typeColor); spec.tile_width = _tileSize; spec.tile_height = _tileSize; - if(!out->open(path, spec)) + if (!out->open(path, spec)) { return false; } - - for(int i = 0; i < _tilesArray.size(); i++) - { + for (int i = 0; i < _tilesArray.size(); i++) + { std::vector& row = _tilesArray[i]; - for(int j = 0; j < row.size(); j++) + for (int j = 0; j < row.size(); j++) { - - if(!row[j]->acquire()) + if (!row[j]->acquire()) { return false; } @@ -108,12 +100,11 @@ bool CachedImage::writeImage(const std::string& path, const im return true; } -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType) +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType) { - std::unique_ptr out = oiio::ImageOutput::create(path); - if(!out) + if (!out) { return false; } @@ -122,20 +113,18 @@ bool CachedImage::writeImage(const std::string& path, const image::EStor spec.tile_width = _tileSize; spec.tile_height = _tileSize; - if(!out->open(path, spec)) + if (!out->open(path, spec)) { return false; } - - for(int i = 0; i < _tilesArray.size(); i++) - { + for (int i = 0; i < _tilesArray.size(); i++) + { std::vector& row = _tilesArray[i]; - for(int j = 0; j < row.size(); j++) + for (int j = 0; j < row.size(); j++) { - - if(!row[j]->acquire()) + if (!row[j]->acquire()) { return false; } @@ -151,12 +140,11 @@ bool CachedImage::writeImage(const std::string& path, const image::EStor return true; } -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType) +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType) { - std::unique_ptr out = oiio::ImageOutput::create(path); - if(!out) + if (!out) { return false; } @@ -165,20 +153,18 @@ bool CachedImage::writeImage(const std::string& path, const image::EStora spec.tile_width = _tileSize; spec.tile_height = _tileSize; - if(!out->open(path, spec)) + if (!out->open(path, spec)) { return false; } - - for(int i = 0; i < _tilesArray.size(); i++) - { + for (int i = 0; i < _tilesArray.size(); i++) + { std::vector& row = _tilesArray[i]; - for(int j = 0; j < row.size(); j++) + for (int j = 0; j < row.size(); j++) { - - if(!row[j]->acquire()) + if (!row[j]->acquire()) { return false; } @@ -194,12 +180,11 @@ bool CachedImage::writeImage(const std::string& path, const image::EStora return true; } -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType) +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType) { - std::unique_ptr out = oiio::ImageOutput::create(path); - if(!out) + if (!out) { return false; } @@ -208,20 +193,18 @@ bool CachedImage::writeImage(const std::string& path, const image spec.tile_width = _tileSize; spec.tile_height = _tileSize; - if(!out->open(path, spec)) + if (!out->open(path, spec)) { return false; } - - for(int i = 0; i < _tilesArray.size(); i++) - { + for (int i = 0; i < _tilesArray.size(); i++) + { std::vector& row = _tilesArray[i]; - for(int j = 0; j < row.size(); j++) + for (int j = 0; j < row.size(); j++) { - - if(!row[j]->acquire()) + if (!row[j]->acquire()) { return false; } @@ -237,4 +220,4 @@ bool CachedImage::writeImage(const std::string& path, const image return true; } -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/cachedImage.hpp b/src/aliceVision/panorama/cachedImage.hpp index 5661f13e82..edf61e6410 100644 --- a/src/aliceVision/panorama/cachedImage.hpp +++ b/src/aliceVision/panorama/cachedImage.hpp @@ -13,20 +13,17 @@ #include #include -namespace aliceVision -{ +namespace aliceVision { -template +template class CachedImage { -public: + public: using RowType = std::vector; -public: - + public: bool createImage(std::shared_ptr manager, size_t width, size_t height) { - _width = width; _height = height; _tileSize = manager->getTileWidth(); @@ -39,28 +36,26 @@ class CachedImage _memoryWidth = countWidth * _tileSize; _memoryHeight = countHeight * _tileSize; - for(int i = 0; i < countHeight; i++) + for (int i = 0; i < countHeight; i++) { - int tile_height = manager->getTileHeight(); - if(i == countHeight - 1) + if (i == countHeight - 1) { tile_height = height - (i * tile_height); } std::vector row; - for(int j = 0; j < countWidth; j++) + for (int j = 0; j < countWidth; j++) { - int tile_width = manager->getTileWidth(); - if(j == countWidth - 1) + if (j == countWidth - 1) { tile_width = width - (i * tile_width); } image::CachedTile::smart_pointer tile = manager->requireNewCachedTile(tile_width, tile_height); - if(tile == nullptr) + if (tile == nullptr) { return false; } @@ -74,32 +69,28 @@ class CachedImage return true; } - bool writeImage(const std::string& path, const image::EStorageDataType &storageDataType = image::EStorageDataType::Auto) + bool writeImage(const std::string& path, const image::EStorageDataType& storageDataType = image::EStorageDataType::Auto) { - ALICEVISION_LOG_ERROR("incorrect template function"); return false; } - template + template bool perPixelOperation(UnaryFunction f) { - - for(int i = 0; i < _tilesArray.size(); i++) + for (int i = 0; i < _tilesArray.size(); i++) { + RowType& row = _tilesArray[i]; - RowType & row = _tilesArray[i]; - - for(int j = 0; j < _tilesArray[i].size(); j++) + for (int j = 0; j < _tilesArray[i].size(); j++) { - image::CachedTile::smart_pointer ptr = row[j]; - if(!ptr) + if (!ptr) { continue; } - if(!ptr->acquire()) + if (!ptr->acquire()) { continue; } @@ -113,40 +104,39 @@ class CachedImage return true; } - template - bool perPixelOperation(CachedImage & other, BinaryFunction f) + template + bool perPixelOperation(CachedImage& other, BinaryFunction f) { - if (other.getWidth() != _width || other.getHeight() != _height) + if (other.getWidth() != _width || other.getHeight() != _height) { return false; } - for(int i = 0; i < _tilesArray.size(); i++) + for (int i = 0; i < _tilesArray.size(); i++) { RowType& row = _tilesArray[i]; RowType& rowOther = other.getTiles()[i]; - for(int j = 0; j < _tilesArray[i].size(); j++) + for (int j = 0; j < _tilesArray[i].size(); j++) { - image::CachedTile::smart_pointer ptr = row[j]; - if(!ptr) + if (!ptr) { continue; } image::CachedTile::smart_pointer ptrOther = rowOther[j]; - if(!ptrOther) + if (!ptrOther) { continue; } - if(!ptr->acquire()) + if (!ptr->acquire()) { continue; } - if(!ptrOther->acquire()) + if (!ptrOther->acquire()) { continue; } @@ -161,28 +151,30 @@ class CachedImage return true; } - bool deepCopy(CachedImage & source) + bool deepCopy(CachedImage& source) { - if (source._memoryWidth != _memoryWidth) return false; - if (source._memoryHeight != _memoryHeight) return false; - if (source._tileSize != _tileSize) return false; + if (source._memoryWidth != _memoryWidth) + return false; + if (source._memoryHeight != _memoryHeight) + return false; + if (source._tileSize != _tileSize) + return false; - for(int i = 0; i < _tilesArray.size(); i++) + for (int i = 0; i < _tilesArray.size(); i++) { - RowType & row = _tilesArray[i]; - RowType & rowSource = source._tilesArray[i]; + RowType& row = _tilesArray[i]; + RowType& rowSource = source._tilesArray[i]; - for(int j = 0; j < _tilesArray[i].size(); j++) + for (int j = 0; j < _tilesArray[i].size(); j++) { - image::CachedTile::smart_pointer ptr = row[j]; - if(!ptr) + if (!ptr) { continue; } image::CachedTile::smart_pointer ptrSource = rowSource[j]; - if(!ptrSource) + if (!ptrSource) { continue; } @@ -197,9 +189,9 @@ class CachedImage continue; } - T * data = (T*)ptr->getDataPointer(); - T * dataSource = (T*)ptrSource->getDataPointer(); - + T* data = (T*)ptr->getDataPointer(); + T* dataSource = (T*)ptrSource->getDataPointer(); + std::memcpy(data, dataSource, _tileSize * _tileSize * sizeof(T)); } } @@ -207,7 +199,7 @@ class CachedImage return true; } - bool assign(const aliceVision::image::Image& input, const BoundingBox & inputBb, const BoundingBox & outputBb) + bool assign(const aliceVision::image::Image& input, const BoundingBox& inputBb, const BoundingBox& outputBb) { BoundingBox outputMemoryBb; outputMemoryBb.left = 0; @@ -215,7 +207,7 @@ class CachedImage outputMemoryBb.width = _width; outputMemoryBb.height = _height; - if(!outputBb.isInside(outputMemoryBb)) + if (!outputBb.isInside(outputMemoryBb)) { return false; } @@ -226,27 +218,26 @@ class CachedImage inputMemoryBb.width = input.Width(); inputMemoryBb.height = input.Height(); - if(!inputBb.isInside(inputMemoryBb)) + if (!inputBb.isInside(inputMemoryBb)) { return false; } - if (inputBb.width != outputBb.width) + if (inputBb.width != outputBb.width) { return false; } - if (inputBb.height != outputBb.height) + if (inputBb.height != outputBb.height) { return false; } - - //Make sure we have our bounding box aligned with the tiles + // Make sure we have our bounding box aligned with the tiles BoundingBox snapedBb = outputBb; snapedBb.snapToGrid(_tileSize); - //Compute grid parameters + // Compute grid parameters BoundingBox gridBb; gridBb.left = snapedBb.left / _tileSize; gridBb.top = snapedBb.top / _tileSize; @@ -256,42 +247,46 @@ class CachedImage int delta_y = outputBb.top - snapedBb.top; int delta_x = outputBb.left - snapedBb.left; - for(int i = 0; i < gridBb.height; i++) + for (int i = 0; i < gridBb.height; i++) { - //ibb.top + i * tileSize --> snapedBb.top + delta + i * tileSize + // ibb.top + i * tileSize --> snapedBb.top + delta + i * tileSize int ti = gridBb.top + i; int oy = ti * _tileSize; int sy = inputBb.top - delta_y + i * _tileSize; - - RowType & row = _tilesArray[ti]; - for(int j = 0; j < gridBb.width; j++) + RowType& row = _tilesArray[ti]; + + for (int j = 0; j < gridBb.width; j++) { int tj = gridBb.left + j; int ox = tj * _tileSize; int sx = inputBb.left - delta_x + j * _tileSize; image::CachedTile::smart_pointer ptr = row[tj]; - if(!ptr) + if (!ptr) { continue; } - if(!ptr->acquire()) + if (!ptr->acquire()) { continue; } T* data = (T*)ptr->getDataPointer(); - for(int y = 0; y < _tileSize; y++) + for (int y = 0; y < _tileSize; y++) { - for(int x = 0; x < _tileSize; x++) + for (int x = 0; x < _tileSize; x++) { - if (sy + y < inputBb.top || sy + y > inputBb.getBottom()) continue; - if (sx + x < inputBb.left || sx + x > inputBb.getRight()) continue; - if (oy + y < outputBb.top || oy + y > outputBb.getBottom()) continue; - if (ox + x < outputBb.left || ox + x > outputBb.getRight()) continue; + if (sy + y < inputBb.top || sy + y > inputBb.getBottom()) + continue; + if (sx + x < inputBb.left || sx + x > inputBb.getRight()) + continue; + if (oy + y < outputBb.top || oy + y > outputBb.getBottom()) + continue; + if (ox + x < outputBb.left || ox + x > outputBb.getRight()) + continue; data[y * _tileSize + x] = input(sy + y, sx + x); } @@ -302,7 +297,7 @@ class CachedImage return true; } - bool extract(aliceVision::image::Image& output, const BoundingBox & outputBb, const BoundingBox& inputBb) + bool extract(aliceVision::image::Image& output, const BoundingBox& outputBb, const BoundingBox& inputBb) { BoundingBox thisBb; thisBb.left = 0; @@ -310,7 +305,7 @@ class CachedImage thisBb.width = _width; thisBb.height = _height; - if(!inputBb.isInside(thisBb)) + if (!inputBb.isInside(thisBb)) { return false; } @@ -321,22 +316,21 @@ class CachedImage outputMemoryBb.width = output.Width(); outputMemoryBb.height = output.Height(); - if(!outputBb.isInside(outputMemoryBb)) + if (!outputBb.isInside(outputMemoryBb)) { return false; } - if (inputBb.width != outputBb.width) + if (inputBb.width != outputBb.width) { return false; } - if (inputBb.height != outputBb.height) + if (inputBb.height != outputBb.height) { return false; } - BoundingBox snapedBb = inputBb; snapedBb.snapToGrid(_tileSize); @@ -349,42 +343,45 @@ class CachedImage int delta_y = inputBb.top - snapedBb.top; int delta_x = inputBb.left - snapedBb.left; - for(int i = 0; i < gridBb.height; i++) + for (int i = 0; i < gridBb.height; i++) { int ti = gridBb.top + i; int oy = ti * _tileSize; int sy = outputBb.top - delta_y + i * _tileSize; + RowType& row = _tilesArray[ti]; - RowType & row = _tilesArray[ti]; - - for(int j = 0; j < gridBb.width; j++) + for (int j = 0; j < gridBb.width; j++) { int tj = gridBb.left + j; int ox = tj * _tileSize; int sx = outputBb.left - delta_x + j * _tileSize; image::CachedTile::smart_pointer ptr = row[tj]; - if(!ptr) + if (!ptr) { continue; } - if(!ptr->acquire()) + if (!ptr->acquire()) { continue; } T* data = (T*)ptr->getDataPointer(); - for(int y = 0; y < _tileSize; y++) + for (int y = 0; y < _tileSize; y++) { - for(int x = 0; x < _tileSize; x++) + for (int x = 0; x < _tileSize; x++) { - if (sy + y < outputBb.top || sy + y > outputBb.getBottom()) continue; - if (sx + x < outputBb.left || sx + x > outputBb.getRight()) continue; - if (oy + y < inputBb.top || oy + y > inputBb.getBottom()) continue; - if (ox + x < inputBb.left || ox + x > inputBb.getRight()) continue; + if (sy + y < outputBb.top || sy + y > outputBb.getBottom()) + continue; + if (sx + x < outputBb.left || sx + x > outputBb.getRight()) + continue; + if (oy + y < inputBb.top || oy + y > inputBb.getBottom()) + continue; + if (ox + x < inputBb.left || ox + x > inputBb.getRight()) + continue; output(sy + y, sx + x) = data[y * _tileSize + x]; } @@ -395,20 +392,20 @@ class CachedImage return true; } - static bool getTileAsImage(image::Image & ret, image::CachedTile::smart_pointer tile) + static bool getTileAsImage(image::Image& ret, image::CachedTile::smart_pointer tile) { - if(!tile) + if (!tile) { return false; } - if(!tile->acquire()) + if (!tile->acquire()) { return false; } ret.resize(tile->getTileWidth(), tile->getTileHeight()); - T * data = (T*)tile->getDataPointer(); + T* data = (T*)tile->getDataPointer(); for (int i = 0; i < tile->getTileHeight(); i++) { for (int j = 0; j < tile->getTileWidth(); j++) @@ -421,7 +418,7 @@ class CachedImage return true; } - static bool setTileWithImage(image::CachedTile::smart_pointer tile, const image::Image & ret) + static bool setTileWithImage(image::CachedTile::smart_pointer tile, const image::Image& ret) { if (ret.Width() != tile->getTileWidth()) { @@ -433,17 +430,17 @@ class CachedImage return false; } - if(!tile) + if (!tile) { return false; } - if(!tile->acquire()) + if (!tile->acquire()) { return false; } - T * data = (T*)tile->getDataPointer(); + T* data = (T*)tile->getDataPointer(); for (int i = 0; i < tile->getTileHeight(); i++) { for (int j = 0; j < tile->getTileWidth(); j++) @@ -456,14 +453,9 @@ class CachedImage return true; } - bool fill(const T & val) + bool fill(const T& val) { - if (!perPixelOperation( - [val](T) -> T - { - return val; - }) - ) + if (!perPixelOperation([val](T) -> T { return val; })) { return false; } @@ -479,7 +471,7 @@ class CachedImage int getTileSize() const { return _tileSize; } -private: + private: int _width; int _height; int _memoryWidth; @@ -489,19 +481,19 @@ class CachedImage std::vector _tilesArray; }; -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType); +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType); -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType); +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType); -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType); +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType); -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType); +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType); -template <> -bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType &storageDataType); +template<> +bool CachedImage::writeImage(const std::string& path, const image::EStorageDataType& storageDataType); -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/compositer.hpp b/src/aliceVision/panorama/compositer.hpp index 618b4fb897..330fc03e3f 100644 --- a/src/aliceVision/panorama/compositer.hpp +++ b/src/aliceVision/panorama/compositer.hpp @@ -12,41 +12,38 @@ #include "imageOps.hpp" #include "seams.hpp" -namespace aliceVision -{ +namespace aliceVision { class Compositer { -public: - Compositer(int width, int height) : - _panoramaWidth(width), - _panoramaHeight(height) - { - - } + public: + Compositer(int width, int height) + : _panoramaWidth(width), + _panoramaHeight(height) + {} - virtual ~Compositer() - { - - } + virtual ~Compositer() {} virtual bool append(aliceVision::image::Image& color, aliceVision::image::Image& inputMask, - aliceVision::image::Image& inputWeights, - int offsetX, int offsetY) + aliceVision::image::Image& inputWeights, + int offsetX, + int offsetY) { offsetX -= _outputRoi.left; offsetY -= _outputRoi.top; - for(int i = 0; i < color.Height(); i++) + for (int i = 0; i < color.Height(); i++) { int y = i + offsetY; - if (y < 0 || y >= _outputRoi.height) continue; + if (y < 0 || y >= _outputRoi.height) + continue; for (int j = 0; j < color.Width(); j++) { int x = j + offsetX; - if (x < 0 || x >= _outputRoi.width) continue; + if (x < 0 || x >= _outputRoi.width) + continue; if (!inputMask(i, j)) { @@ -63,38 +60,35 @@ class Compositer return true; } - virtual bool initialize(const BoundingBox & outputRoi) { - + virtual bool initialize(const BoundingBox& outputRoi) + { _outputRoi = outputRoi; - - if (_outputRoi.left < 0) return false; - if (_outputRoi.top < 0) return false; - if (_outputRoi.getRight() >= _panoramaWidth) return false; - if (_outputRoi.getBottom() >= _panoramaHeight) return false; + + if (_outputRoi.left < 0) + return false; + if (_outputRoi.top < 0) + return false; + if (_outputRoi.getRight() >= _panoramaWidth) + return false; + if (_outputRoi.getBottom() >= _panoramaHeight) + return false; _panorama = image::Image(_outputRoi.width, _outputRoi.height, true, image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f)); - return true; + return true; } virtual bool terminate() { return true; } - - image::Image & getOutput() - { - return _panorama; - } + image::Image& getOutput() { return _panorama; } - virtual int getBorderSize() const - { - return 0; - } + virtual int getBorderSize() const { return 0; } -protected: + protected: image::Image _panorama; int _panoramaWidth; int _panoramaHeight; BoundingBox _outputRoi; }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/coordinatesMap.cpp b/src/aliceVision/panorama/coordinatesMap.cpp index f4204116f9..3121ca2b60 100644 --- a/src/aliceVision/panorama/coordinatesMap.cpp +++ b/src/aliceVision/panorama/coordinatesMap.cpp @@ -8,35 +8,32 @@ #include "sphericalMapping.hpp" -namespace aliceVision -{ +namespace aliceVision { -bool CoordinatesMap::build(const std::pair& panoramaSize, const geometry::Pose3& pose, - const aliceVision::camera::IntrinsicBase& intrinsics, const BoundingBox& coarseBbox) +bool CoordinatesMap::build(const std::pair& panoramaSize, + const geometry::Pose3& pose, + const aliceVision::camera::IntrinsicBase& intrinsics, + const BoundingBox& coarseBbox) { - /* Effectively compute the warping map */ _coordinates = aliceVision::image::Image(coarseBbox.width, coarseBbox.height, false); _mask = aliceVision::image::Image(coarseBbox.width, coarseBbox.height, true, 0); - int max_x = 0; int max_y = 0; int min_x = std::numeric_limits::max(); int min_y = std::numeric_limits::max(); - for(int y = 0; y < coarseBbox.height; y++) + for (int y = 0; y < coarseBbox.height; y++) { - int cy = y + coarseBbox.top; if (cy < 0 || cy >= panoramaSize.second) { continue; } - for(int x = 0; x < coarseBbox.width; x++) + for (int x = 0; x < coarseBbox.width; x++) { - int cx = x + coarseBbox.left; Vec3 ray = SphericalMapping::fromEquirectangular(Vec2(cx, cy), panoramaSize.first, panoramaSize.second); @@ -46,7 +43,7 @@ bool CoordinatesMap::build(const std::pair& panoramaSize, const geomet * This test is camera type dependent */ Vec3 transformedRay = pose(ray); - if(!intrinsics.isVisibleRay(transformedRay)) + if (!intrinsics.isVisibleRay(transformedRay)) { continue; } @@ -60,7 +57,7 @@ bool CoordinatesMap::build(const std::pair& panoramaSize, const geomet /** * Ignore invalid coordinates */ - if(!intrinsics.isVisible(pix_disto)) + if (!intrinsics.isVisible(pix_disto)) { continue; } @@ -88,16 +85,15 @@ bool CoordinatesMap::build(const std::pair& panoramaSize, const geomet bool CoordinatesMap::computeScale(double& result, float ratioUpscale) { - std::vector scales; size_t real_height = _coordinates.Height(); size_t real_width = _coordinates.Width(); - for(int i = 1; i < real_height - 2; i++) + for (int i = 1; i < real_height - 2; i++) { - for(int j = 1; j < real_width - 2; j++) + for (int j = 1; j < real_width - 2; j++) { - if(!_mask(i, j) || !_mask(i, j + 1) || !_mask(i + 1, j)) + if (!_mask(i, j) || !_mask(i, j + 1) || !_mask(i + 1, j)) { continue; } @@ -108,14 +104,14 @@ bool CoordinatesMap::computeScale(double& result, float ratioUpscale) double dyy = _coordinates(i + 1, j).y() - _coordinates(i, j).y(); double det = std::abs(dxx * dyy - dxy * dyx); - if(det < 1e-12) + if (det < 1e-12) continue; scales.push_back(det); } } - if(scales.empty()) + if (scales.empty()) return false; std::sort(scales.begin(), scales.end()); @@ -125,4 +121,4 @@ bool CoordinatesMap::computeScale(double& result, float ratioUpscale) return true; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/coordinatesMap.hpp b/src/aliceVision/panorama/coordinatesMap.hpp index 614f2108cd..21a9050316 100644 --- a/src/aliceVision/panorama/coordinatesMap.hpp +++ b/src/aliceVision/panorama/coordinatesMap.hpp @@ -11,20 +11,21 @@ #include "boundingBox.hpp" -namespace aliceVision -{ +namespace aliceVision { class CoordinatesMap { -public: + public: /** * Build coordinates map given camera properties * @param panoramaSize desired output panoramaSize * @param pose the camera pose wrt an arbitrary reference frame * @param intrinsics the camera intrinsics */ - bool build(const std::pair& panoramaSize, const geometry::Pose3& pose, - const aliceVision::camera::IntrinsicBase& intrinsics, const BoundingBox& coarseBbox); + bool build(const std::pair& panoramaSize, + const geometry::Pose3& pose, + const aliceVision::camera::IntrinsicBase& intrinsics, + const BoundingBox& coarseBbox); bool computeScale(double& result, float ratioUpscale); @@ -38,7 +39,7 @@ class CoordinatesMap const aliceVision::image::Image& getMask() const { return _mask; } -private: + private: size_t _offset_x = 0; size_t _offset_y = 0; @@ -47,4 +48,4 @@ class CoordinatesMap BoundingBox _boundingBox; }; -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/distance.cpp b/src/aliceVision/panorama/distance.cpp index 135e6867a8..3511a28209 100644 --- a/src/aliceVision/panorama/distance.cpp +++ b/src/aliceVision/panorama/distance.cpp @@ -6,12 +6,10 @@ #include "distance.hpp" -namespace aliceVision -{ +namespace aliceVision { bool distanceToCenter(aliceVision::image::Image& _weights, const CoordinatesMap& map, int width, int height) { - const aliceVision::image::Image& coordinates = map.getCoordinates(); const aliceVision::image::Image& mask = map.getMask(); @@ -20,15 +18,14 @@ bool distanceToCenter(aliceVision::image::Image& _weights, const Coordina _weights = aliceVision::image::Image(coordinates.Width(), coordinates.Height()); - for(int i = 0; i < _weights.Height(); i++) + for (int i = 0; i < _weights.Height(); i++) { - for(int j = 0; j < _weights.Width(); j++) + for (int j = 0; j < _weights.Width(); j++) { - _weights(i, j) = 0.0f; bool valid = mask(i, j); - if(!valid) + if (!valid) { continue; } @@ -48,20 +45,13 @@ bool distanceToCenter(aliceVision::image::Image& _weights, const Coordina return true; } -static inline int f(int x_i, int gi) noexcept -{ - return (x_i * x_i) + gi * gi; -} +static inline int f(int x_i, int gi) noexcept { return (x_i * x_i) + gi * gi; } -static inline int sep(int i, int u, int gi, int gu, int) noexcept -{ - return (u * u - i * i + gu * gu - gi * gi) / (2 * (u - i)); -} +static inline int sep(int i, int u, int gi, int gu, int) noexcept { return (u * u - i * i + gu * gu - gi * gi) / (2 * (u - i)); } /// Code adapted from VFLib: https://github.com/vinniefalco/VFLib (Licence MIT) bool computeDistanceMap(image::Image& distance, const image::Image& mask) { - int width = mask.Width(); int height = mask.Height(); @@ -69,22 +59,20 @@ bool computeDistanceMap(image::Image& distance, const image::Image buf(width, height); /* Per column distance 1D calculation */ - for(int j = 0; j < width; j++) + for (int j = 0; j < width; j++) { buf(0, j) = mask(0, j) ? 0 : maxval; /*Top to bottom accumulation */ - for(int i = 1; i < height; i++) + for (int i = 1; i < height; i++) { - buf(i, j) = mask(i, j) ? 0 : 1 + buf(i - 1, j); } /*Bottom to top correction */ - for(int i = height - 2; i >= 0; i--) + for (int i = height - 2; i >= 0; i--) { - - if(buf(i + 1, j) < buf(i, j)) + if (buf(i + 1, j) < buf(i, j)) { buf(i, j) = 1 + buf(i + 1, j); } @@ -95,19 +83,19 @@ bool computeDistanceMap(image::Image& distance, const image::Image t(std::max(width, height)); /*Per row scan*/ - for(int i = 0; i < height; i++) + for (int i = 0; i < height; i++) { int q = 0; s[0] = 0; t[0] = 0; // scan 3 - for(int j = 1; j < width; j++) + for (int j = 1; j < width; j++) { - while(q >= 0 && f(t[q] - s[q], buf(i, s[q])) > f(t[q] - j, buf(i, j))) + while (q >= 0 && f(t[q] - s[q], buf(i, s[q])) > f(t[q] - j, buf(i, j))) q--; - if(q < 0) + if (q < 0) { q = 0; s[0] = j; @@ -116,7 +104,7 @@ bool computeDistanceMap(image::Image& distance, const image::Image& distance, const image::Image= 0; --j) + for (int j = width - 1; j >= 0; --j) { int const d = f(j - s[q], buf(i, s[q])); distance(i, j) = d; - if(j == t[q]) + if (j == t[q]) --q; } } @@ -139,4 +127,4 @@ bool computeDistanceMap(image::Image& distance, const image::Image& _weights, const CoordinatesMap& map, int width, int height); bool computeDistanceMap(image::Image& distance, const image::Image& mask); -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/feathering.cpp b/src/aliceVision/panorama/feathering.cpp index 257c0ba591..df3a32348f 100644 --- a/src/aliceVision/panorama/feathering.cpp +++ b/src/aliceVision/panorama/feathering.cpp @@ -6,8 +6,7 @@ #include "feathering.hpp" -namespace aliceVision -{ +namespace aliceVision { bool feathering(aliceVision::image::Image& output, const aliceVision::image::Image& color, @@ -30,42 +29,41 @@ bool feathering(aliceVision::image::Image& output, image::Image half(width / 2, height / 2); image::Image half_mask(width / 2, height / 2); - for(int i = 0; i < half.Height(); i++) + for (int i = 0; i < half.Height(); i++) { - int di = i * 2; - for(int j = 0; j < half.Width(); j++) + for (int j = 0; j < half.Width(); j++) { int dj = j * 2; int count = 0; half(i, j) = image::RGBfColor(0.0, 0.0, 0.0); - if(src_mask(di, dj)) + if (src_mask(di, dj)) { half(i, j) += src(di, dj); count++; } - if(src_mask(di, dj + 1)) + if (src_mask(di, dj + 1)) { half(i, j) += src(di, dj + 1); count++; } - if(src_mask(di + 1, dj)) + if (src_mask(di + 1, dj)) { half(i, j) += src(di + 1, dj); count++; } - if(src_mask(di + 1, dj + 1)) + if (src_mask(di + 1, dj + 1)) { half(i, j) += src(di + 1, dj + 1); count++; } - if(count > 0) + if (count > 0) { half(i, j) /= float(count); half_mask(i, j) = 1; @@ -86,10 +84,10 @@ bool feathering(aliceVision::image::Image& output, lvl++; } - //Now we want to make sure we have no masked pixel with undefined color - //So we compute the mean of all valid pixels, and set the invalid pixels to this value - image::Image & lastImage = feathering[feathering.size() - 1]; - image::Image & lastMask = feathering_mask[feathering_mask.size() - 1]; + // Now we want to make sure we have no masked pixel with undefined color + // So we compute the mean of all valid pixels, and set the invalid pixels to this value + image::Image& lastImage = feathering[feathering.size() - 1]; + image::Image& lastMask = feathering_mask[feathering_mask.size() - 1]; image::RGBfColor sum(0.0f); int count = 0; for (int y = 0; y < height; y++) @@ -106,7 +104,7 @@ bool feathering(aliceVision::image::Image& output, } } - if (count > 0) + if (count > 0) { image::RGBfColor mean; mean.r() = sum.r() / float(count); @@ -126,32 +124,30 @@ bool feathering(aliceVision::image::Image& output, } } - - //Now, level by level, we fill masked pixel with the estimated value from - //The lower level. - for(int lvl = feathering.size() - 2; lvl >= 0; lvl--) + // Now, level by level, we fill masked pixel with the estimated value from + // The lower level. + for (int lvl = feathering.size() - 2; lvl >= 0; lvl--) { - image::Image& src = feathering[lvl]; image::Image& src_mask = feathering_mask[lvl]; image::Image& ref = feathering[lvl + 1]; image::Image& ref_mask = feathering_mask[lvl + 1]; - for(int i = 0; i < src_mask.Height(); i++) + for (int i = 0; i < src_mask.Height(); i++) { - for(int j = 0; j < src_mask.Width(); j++) + for (int j = 0; j < src_mask.Width(); j++) { - if(!src_mask(i, j)) + if (!src_mask(i, j)) { int mi = i / 2; int mj = j / 2; - if(mi >= ref_mask.Height()) + if (mi >= ref_mask.Height()) { mi = ref_mask.Height() - 1; } - if(mj >= ref_mask.Width()) + if (mj >= ref_mask.Width()) { mj = ref_mask.Width() - 1; } @@ -168,33 +164,32 @@ bool feathering(aliceVision::image::Image& output, return true; } - -bool feathering(CachedImage & input_output, CachedImage & inputMask) +bool feathering(CachedImage& input_output, CachedImage& inputMask) { - if (input_output.getTileSize() < 2) + if (input_output.getTileSize() < 2) { return false; } - if (input_output.getTileSize() != inputMask.getTileSize()) + if (input_output.getTileSize() != inputMask.getTileSize()) { return false; } - if (input_output.getWidth() != inputMask.getWidth()) + if (input_output.getWidth() != inputMask.getWidth()) { return false; } - if (input_output.getHeight() != inputMask.getHeight()) + if (input_output.getHeight() != inputMask.getHeight()) { return false; } - std::vector> & tilesColor = input_output.getTiles(); - std::vector> & tilesMask = inputMask.getTiles(); + std::vector>& tilesColor = input_output.getTiles(); + std::vector>& tilesMask = inputMask.getTiles(); - if (tilesColor.empty()) + if (tilesColor.empty()) { return false; } @@ -203,7 +198,7 @@ bool feathering(CachedImage & input_output, CachedImage & input_output, CachedImage rowColor = tilesColor[i]; std::vector rowMask = tilesMask[i]; for (int j = 0; j < rowColor.size(); j++) { - if (!CachedImage::getTileAsImage(colorTile, rowColor[j])) + if (!CachedImage::getTileAsImage(colorTile, rowColor[j])) { return false; } - if (!CachedImage::getTileAsImage(maskTile, rowMask[j])) + if (!CachedImage::getTileAsImage(maskTile, rowMask[j])) { return false; } - while (1) + while (1) { image::Image smallerTile(colorTile.Width() / 2, colorTile.Height() / 2); image::Image smallerMask(maskTile.Width() / 2, maskTile.Height() / 2); - for(int y = 0; y < smallerTile.Height(); y++) + for (int y = 0; y < smallerTile.Height(); y++) { int dy = y * 2; - for(int x = 0; x < smallerTile.Width(); x++) + for (int x = 0; x < smallerTile.Width(); x++) { int dx = x * 2; @@ -249,31 +244,31 @@ bool feathering(CachedImage & input_output, CachedImage 0) + if (count > 0) { smallerTile(y, x) /= float(count); smallerMask(y, x) = 1; @@ -298,25 +293,24 @@ bool feathering(CachedImage & input_output, CachedImage rowColor = tilesColor[i]; std::vector rowMask = tilesMask[i]; for (int j = 0; j < rowColor.size(); j++) { - if (!CachedImage::getTileAsImage(colorTile, rowColor[j])) + if (!CachedImage::getTileAsImage(colorTile, rowColor[j])) { return false; } - if (!CachedImage::getTileAsImage(maskTile, rowMask[j])) + if (!CachedImage::getTileAsImage(maskTile, rowMask[j])) { return false; } @@ -327,18 +321,18 @@ bool feathering(CachedImage & input_output, CachedImage & largerTile = pyramid_colors[pyramid_colors.size() - 1]; - image::Image & largerMask = pyramid_masks[pyramid_masks.size() - 1]; + image::Image& largerTile = pyramid_colors[pyramid_colors.size() - 1]; + image::Image& largerMask = pyramid_masks[pyramid_masks.size() - 1]; image::Image smallerTile(largerTile.Width() / 2, largerTile.Height() / 2); image::Image smallerMask(largerMask.Width() / 2, largerMask.Height() / 2); - for(int y = 0; y < smallerTile.Height(); y++) + for (int y = 0; y < smallerTile.Height(); y++) { int dy = y * 2; - for(int x = 0; x < smallerTile.Width(); x++) + for (int x = 0; x < smallerTile.Width(); x++) { int dx = x * 2; @@ -346,31 +340,31 @@ bool feathering(CachedImage & input_output, CachedImage 0) + if (count > 0) { smallerTile(y, x) /= float(count); smallerMask(y, x) = 1; @@ -382,49 +376,46 @@ bool feathering(CachedImage & input_output, CachedImage & img = pyramid_colors[pyramid_colors.size() - 1]; - image::Image & mask = pyramid_masks[pyramid_masks.size() - 1]; + image::Image& img = pyramid_colors[pyramid_colors.size() - 1]; + image::Image& mask = pyramid_masks[pyramid_masks.size() - 1]; - if (!mask(0, 0)) + if (!mask(0, 0)) { mask(0, 0) = 255; img(0, 0) = featheredGrid(i, j); } - - for(int lvl = pyramid_colors.size() - 2; lvl >= 0; lvl--) + for (int lvl = pyramid_colors.size() - 2; lvl >= 0; lvl--) { + image::Image& src = pyramid_colors[lvl]; + image::Image& src_mask = pyramid_masks[lvl]; + image::Image& ref = pyramid_colors[lvl + 1]; + image::Image& ref_mask = pyramid_masks[lvl + 1]; - image::Image & src = pyramid_colors[lvl]; - image::Image & src_mask = pyramid_masks[lvl]; - image::Image & ref = pyramid_colors[lvl + 1]; - image::Image & ref_mask = pyramid_masks[lvl + 1]; - - for(int i = 0; i < src_mask.Height(); i++) + for (int i = 0; i < src_mask.Height(); i++) { - for(int j = 0; j < src_mask.Width(); j++) + for (int j = 0; j < src_mask.Width(); j++) { - if(!src_mask(i, j)) + if (!src_mask(i, j)) { int mi = i / 2; int mj = j / 2; - if(mi >= ref_mask.Height()) + if (mi >= ref_mask.Height()) { mi = ref_mask.Height() - 1; } - if(mj >= ref_mask.Width()) + if (mj >= ref_mask.Width()) { mj = ref_mask.Width() - 1; } @@ -436,7 +427,7 @@ bool feathering(CachedImage & input_output, CachedImage::setTileWithImage(rowColor[j], pyramid_colors[0])) + if (!CachedImage::setTileWithImage(rowColor[j], pyramid_colors[0])) { return false; } @@ -446,4 +437,4 @@ bool feathering(CachedImage & input_output, CachedImage -namespace aliceVision -{ +namespace aliceVision { bool feathering(aliceVision::image::Image& output, const aliceVision::image::Image& color, const aliceVision::image::Image& inputMask); -bool feathering(CachedImage& input_output, - CachedImage& inputMask); +bool feathering(CachedImage& input_output, CachedImage& inputMask); -} \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/gaussian.cpp b/src/aliceVision/panorama/gaussian.cpp index d5ef8b63ca..6e264045b9 100644 --- a/src/aliceVision/panorama/gaussian.cpp +++ b/src/aliceVision/panorama/gaussian.cpp @@ -8,13 +8,11 @@ #include -namespace aliceVision -{ +namespace aliceVision { -GaussianPyramidNoMask::GaussianPyramidNoMask(const size_t width_base, const size_t height_base, - const size_t limit_scales) - : _width_base(width_base) - , _height_base(height_base) +GaussianPyramidNoMask::GaussianPyramidNoMask(const size_t width_base, const size_t height_base, const size_t limit_scales) + : _width_base(width_base), + _height_base(height_base) { /** * Compute optimal scale @@ -29,9 +27,8 @@ GaussianPyramidNoMask::GaussianPyramidNoMask(const size_t width_base, const size **/ size_t new_width = _width_base; size_t new_height = _height_base; - for(int i = 0; i < _scales; i++) + for (int i = 0; i < _scales; i++) { - _pyramid_color.push_back(image::Image(new_width, new_height, true, image::RGBfColor(0))); _filter_buffer.push_back(image::Image(new_width, new_height, true, image::RGBfColor(0))); new_height /= 2; @@ -41,19 +38,17 @@ GaussianPyramidNoMask::GaussianPyramidNoMask(const size_t width_base, const size bool GaussianPyramidNoMask::process(const image::Image& input) { - - if(input.Height() != _pyramid_color[0].Height()) + if (input.Height() != _pyramid_color[0].Height()) return false; - if(input.Width() != _pyramid_color[0].Width()) + if (input.Width() != _pyramid_color[0].Width()) return false; /** * Build pyramid */ _pyramid_color[0] = input; - for(int lvl = 0; lvl < _scales - 1; lvl++) + for (int lvl = 0; lvl < _scales - 1; lvl++) { - const image::Image& source = _pyramid_color[lvl]; image::Image& dst = _filter_buffer[lvl]; @@ -64,15 +59,13 @@ bool GaussianPyramidNoMask::process(const image::Image& input) return true; } -bool GaussianPyramidNoMask::downscale(image::Image& output, - const image::Image& input) +bool GaussianPyramidNoMask::downscale(image::Image& output, const image::Image& input) { - - for(int i = 0; i < output.Height(); i++) + for (int i = 0; i < output.Height(); i++) { int ui = i * 2; - for(int j = 0; j < output.Width(); j++) + for (int j = 0; j < output.Width(); j++) { int uj = j * 2; @@ -83,4 +76,4 @@ bool GaussianPyramidNoMask::downscale(image::Image& output, return true; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/gaussian.hpp b/src/aliceVision/panorama/gaussian.hpp index 66c2f1cfc7..2ed6832731 100644 --- a/src/aliceVision/panorama/gaussian.hpp +++ b/src/aliceVision/panorama/gaussian.hpp @@ -8,12 +8,11 @@ #include -namespace aliceVision -{ +namespace aliceVision { class GaussianPyramidNoMask { -public: + public: GaussianPyramidNoMask(const size_t width_base, const size_t height_base, const size_t limit_scales = 64); bool process(const image::Image& input); @@ -26,7 +25,7 @@ class GaussianPyramidNoMask std::vector>& getPyramidColor() { return _pyramid_color; } -protected: + protected: std::vector> _pyramid_color; std::vector> _filter_buffer; size_t _width_base; @@ -34,47 +33,46 @@ class GaussianPyramidNoMask size_t _scales; }; -template -inline void convolveRow(typename image::Image::RowXpr output_row, typename image::Image::ConstRowXpr input_row, - const Eigen::Matrix& kernel, bool loop) +template +inline void convolveRow(typename image::Image::RowXpr output_row, + typename image::Image::ConstRowXpr input_row, + const Eigen::Matrix& kernel, + bool loop) { - const int radius = 2; - for(int j = 0; j < input_row.cols(); j++) + for (int j = 0; j < input_row.cols(); j++) { - T sum = T(); float sumw = 0.0f; - for(int k = 0; k < kernel.size(); k++) + for (int k = 0; k < kernel.size(); k++) { - float w = kernel(k); int col = j + k - radius; /* mirror 5432 | 123456 | 5432 */ - if(!loop) + if (!loop) { - if(col < 0) + if (col < 0) { col = -col; } - if(col >= input_row.cols()) + if (col >= input_row.cols()) { col = input_row.cols() - 1 - (col + 1 - input_row.cols()); } } else { - if(col < 0) + if (col < 0) { col = input_row.cols() + col; } - if(col >= input_row.cols()) + if (col >= input_row.cols()) { col = col - input_row.cols(); } @@ -88,20 +86,16 @@ inline void convolveRow(typename image::Image::RowXpr output_row, typename im } } -template -inline void convolveColumns(typename image::Image::RowXpr output_row, const image::Image& input_rows, - const Eigen::Matrix& kernel) +template +inline void convolveColumns(typename image::Image::RowXpr output_row, const image::Image& input_rows, const Eigen::Matrix& kernel) { - - for(int j = 0; j < output_row.cols(); j++) + for (int j = 0; j < output_row.cols(); j++) { - T sum = T(); float sumw = 0.0f; - for(int k = 0; k < kernel.size(); k++) + for (int k = 0; k < kernel.size(); k++) { - float w = kernel(k); sum += w * input_rows(k, j); sumw += w; @@ -111,11 +105,10 @@ inline void convolveColumns(typename image::Image::RowXpr output_row, const i } } -template +template bool convolveGaussian5x5(image::Image& output, const image::Image& input, bool loop = false) { - - if(output.size() != input.size()) + if (output.size() != input.size()) { return false; } @@ -138,9 +131,8 @@ bool convolveGaussian5x5(image::Image& output, const image::Image& input, convolveRow(buf.row(3), input.row(1), kernel, loop); convolveRow(buf.row(4), input.row(2), kernel, loop); - for(int i = 0; i < output.Height() - 3; i++) + for (int i = 0; i < output.Height() - 3; i++) { - convolveColumns(output.row(i), buf, kernel); buf.row(0) = buf.row(1); @@ -174,4 +166,4 @@ bool convolveGaussian5x5(image::Image& output, const image::Image& input, return true; } -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/graphcut.hpp b/src/aliceVision/panorama/graphcut.hpp index 4f89b1db8f..ad6f8f2b8a 100644 --- a/src/aliceVision/panorama/graphcut.hpp +++ b/src/aliceVision/panorama/graphcut.hpp @@ -16,8 +16,7 @@ #include "imageOps.hpp" #include "seams.hpp" -namespace aliceVision -{ +namespace aliceVision { /** * @brief Maxflow computation based on a standard Adjacency List graph reprensentation. @@ -26,14 +25,14 @@ namespace aliceVision */ class MaxFlow_AdjList { -public: + public: using NodeType = int; using ValueType = float; - using Traits = boost::adjacency_list_traits; using edge_descriptor = typename Traits::edge_descriptor; using vertex_descriptor = typename Traits::vertex_descriptor; @@ -44,24 +43,24 @@ class MaxFlow_AdjList ValueType residual{}; edge_descriptor reverse; }; - using Graph = boost::adjacency_list; using VertexIterator = typename boost::graph_traits::vertex_iterator; -public: + public: explicit MaxFlow_AdjList(size_t numNodes) - : _graph(numNodes + 2) - , _S(NodeType(numNodes)) - , _T(NodeType(numNodes + 1)) + : _graph(numNodes + 2), + _S(NodeType(numNodes)), + _T(NodeType(numNodes + 1)) { VertexIterator vi, vi_end; - for(boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi) + for (boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi) { _graph.m_vertices[*vi].m_out_edges.reserve(9); } @@ -118,10 +117,16 @@ class MaxFlow_AdjList std::vector pred(nbVertices); std::vector dist(nbVertices); - ValueType v = boost::boykov_kolmogorov_max_flow(_graph, boost::get(&Edge::capacity, _graph), + ValueType v = boost::boykov_kolmogorov_max_flow(_graph, + boost::get(&Edge::capacity, _graph), boost::get(&Edge::residual, _graph), - boost::get(&Edge::reverse, _graph), &pred[0], &_color[0], - &dist[0], boost::get(boost::vertex_index, _graph), _S, _T); + boost::get(&Edge::reverse, _graph), + &pred[0], + &_color[0], + &dist[0], + boost::get(boost::vertex_index, _graph), + _S, + _T); return v; } @@ -131,20 +136,19 @@ class MaxFlow_AdjList /// is full inline bool isTarget(NodeType n) const { return (_color[n] == boost::white_color); } -protected: + protected: Graph _graph; std::vector _color; - const NodeType _S; //< emptyness - const NodeType _T; //< fullness + const NodeType _S; //< emptyness + const NodeType _T; //< fullness }; - bool computeSeamsMap(image::Image& seams, const image::Image& labels); class GraphcutSeams { -public: - struct InputData + public: + struct InputData { IndexT id; BoundingBox rect; @@ -154,22 +158,20 @@ class GraphcutSeams using IndexedColor = std::pair; using PixelInfo = std::vector; - -public: + public: GraphcutSeams(size_t outputWidth, size_t outputHeight) - : _outputWidth(outputWidth) - , _outputHeight(outputHeight) - , _maximal_distance_change(outputWidth + outputHeight) - , _labels(outputWidth, outputHeight, true, UndefinedIndexT) - { - } + : _outputWidth(outputWidth), + _outputHeight(outputHeight), + _maximal_distance_change(outputWidth + outputHeight), + _labels(outputWidth, outputHeight, true, UndefinedIndexT) + {} virtual ~GraphcutSeams() = default; - std::pair findIndex(const PixelInfo & pix, IndexT id) + std::pair findIndex(const PixelInfo& pix, IndexT id) { - for (int i = 0; i < pix.size(); i++) + for (int i = 0; i < pix.size(); i++) { if (pix[i].first == id) { @@ -180,9 +182,9 @@ class GraphcutSeams return std::make_pair(UndefinedIndexT, image::RGBfColor(0.0f)); } - bool existIndex(const PixelInfo & pix, IndexT id) + bool existIndex(const PixelInfo& pix, IndexT id) { - for (int i = 0; i < pix.size(); i++) + for (int i = 0; i < pix.size(); i++) { if (pix[i].first == id) { @@ -193,22 +195,25 @@ class GraphcutSeams return false; } - bool setOriginalLabels(const image::Image & existing_labels) - { + bool setOriginalLabels(const image::Image& existing_labels) + { _labels = existing_labels; return true; } - bool append(const image::Image& input, const image::Image& inputMask, IndexT currentIndex, size_t offset_x, size_t offset_y) + bool append(const image::Image& input, + const image::Image& inputMask, + IndexT currentIndex, + size_t offset_x, + size_t offset_y) { - - if(inputMask.Width() != input.Width()) + if (inputMask.Width() != input.Width()) { return false; } - if(inputMask.Height() != input.Height()) + if (inputMask.Height() != input.Height()) { return false; } @@ -225,18 +230,14 @@ class GraphcutSeams _inputs[currentIndex] = data; - return true; } - void setMaximalDistance(int dist) - { - _maximal_distance_change = dist; - } + void setMaximalDistance(int dist) { _maximal_distance_change = dist; } - bool createInputOverlappingObservations(image::Image & graphCutInput, const BoundingBox & interestBbox) + bool createInputOverlappingObservations(image::Image& graphCutInput, const BoundingBox& interestBbox) { - for (auto & otherInput : _inputs) + for (auto& otherInput : _inputs) { BoundingBox otherBbox = otherInput.second.rect; BoundingBox otherBboxLoop = otherInput.second.rect; @@ -255,15 +256,17 @@ class GraphcutSeams { continue; } - + image::Image otherColor(otherInputBbox.width, otherInputBbox.height); - otherColor.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width) = otherInput.second.color.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width); + otherColor.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width) = + otherInput.second.color.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width); image::Image otherMask(otherInputBbox.width, otherInputBbox.height); - otherMask.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width) = otherInput.second.mask.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width); - + otherMask.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width) = + otherInput.second.mask.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width); + if (!intersection.isEmpty()) - { + { BoundingBox interestOther = intersection; interestOther.left -= otherBbox.left; interestOther.top -= otherBbox.top; @@ -281,14 +284,13 @@ class GraphcutSeams { int x_other = interestOther.left + x; int x_current = interestThis.left + x; - if (!otherMask(y_other, x_other)) { continue; } - - PixelInfo & pix = graphCutInput(y_current, x_current); + + PixelInfo& pix = graphCutInput(y_current, x_current); pix.push_back(std::make_pair(otherInput.first, otherColor(y_other, x_other))); } } @@ -304,7 +306,6 @@ class GraphcutSeams interestThis.left -= interestBbox.left; interestThis.top -= interestBbox.top; - for (int y = 0; y < intersectionLoop.height; y++) { int y_other = interestOther.top + y; @@ -320,7 +321,7 @@ class GraphcutSeams continue; } - PixelInfo & pix = graphCutInput(y_current, x_current); + PixelInfo& pix = graphCutInput(y_current, x_current); pix.push_back(std::make_pair(otherInput.first, otherColor(y_other, x_other))); } } @@ -336,7 +337,6 @@ class GraphcutSeams interestThis.left -= interestBbox.left; interestThis.top -= interestBbox.top; - for (int y = 0; y < intersectionLoopRight.height; y++) { int y_other = interestOther.top + y; @@ -352,7 +352,7 @@ class GraphcutSeams continue; } - PixelInfo & pix = graphCutInput(y_current, x_current); + PixelInfo& pix = graphCutInput(y_current, x_current); pix.push_back(std::make_pair(otherInput.first, otherColor(y_other, x_other))); } } @@ -362,13 +362,13 @@ class GraphcutSeams return true; } - bool fixUpscaling(image::Image & labels, const image::Image & graphCutInput) + bool fixUpscaling(image::Image& labels, const image::Image& graphCutInput) { - //Because of upscaling, some labels may be incorrect - //Some pixels may be affected to labels they don't see. - for (int y = 0; y < graphCutInput.Height(); y++) + // Because of upscaling, some labels may be incorrect + // Some pixels may be affected to labels they don't see. + for (int y = 0; y < graphCutInput.Height(); y++) { - for (int x = 0; x < graphCutInput.Width(); x++) + for (int x = 0; x < graphCutInput.Width(); x++) { IndexT label = labels(y, x); @@ -378,8 +378,8 @@ class GraphcutSeams } // If there is no input for this pixel, we can't do anything - const PixelInfo & pix = graphCutInput(y, x); - + const PixelInfo& pix = graphCutInput(y, x); + // Check if the input associated to this label is seen by this pixel auto it = existIndex(pix, label); if (it) @@ -390,36 +390,36 @@ class GraphcutSeams // Look for another label in the neighboorhood which is seen by this pixel bool modified = false; bool hadUndefined = false; - for (int l = -1; l <= 1; l++) + for (int l = -1; l <= 1; l++) { int ny = y + l; - if (ny < 0 || ny >= labels.Height()) + if (ny < 0 || ny >= labels.Height()) { continue; - } + } for (int c = -1; c <= 1; c++) { int nx = x + c; - if (nx < 0 || nx >= labels.Width()) + if (nx < 0 || nx >= labels.Width()) { continue; } - IndexT otherLabel = labels(ny, nx); + IndexT otherLabel = labels(ny, nx); if (otherLabel == label) { continue; } - if (otherLabel == UndefinedIndexT) + if (otherLabel == UndefinedIndexT) { hadUndefined = true; continue; } // Check that this other label is seen by our pixel - const PixelInfo & pixOther = graphCutInput(ny, nx); + const PixelInfo& pixOther = graphCutInput(ny, nx); auto itOther = existIndex(pixOther, otherLabel); if (!itOther) { @@ -435,7 +435,7 @@ class GraphcutSeams } } - if (!modified) + if (!modified) { labels(y, x) = UndefinedIndexT; } @@ -445,17 +445,17 @@ class GraphcutSeams return true; } - bool computeInputDistanceMap(image::Image & distanceMap, const image::Image & localLabels, IndexT inputId) + bool computeInputDistanceMap(image::Image& distanceMap, const image::Image& localLabels, IndexT inputId) { image::Image binarizedWorld(localLabels.Width(), localLabels.Height()); - for(int y = 0; y < localLabels.Height(); y++) + for (int y = 0; y < localLabels.Height(); y++) { - for(int x = 0; x < localLabels.Width(); x++) + for (int x = 0; x < localLabels.Width(); x++) { IndexT label = localLabels(y, x); - if(label == inputId) + if (label == inputId) { binarizedWorld(y, x) = 1; } @@ -467,12 +467,12 @@ class GraphcutSeams } image::Image seams(localLabels.Width(), localLabels.Height()); - if(!computeSeamsMap(seams, binarizedWorld)) + if (!computeSeamsMap(seams, binarizedWorld)) { return false; } - if(!computeDistanceMap(distanceMap, seams)) + if (!computeDistanceMap(distanceMap, seams)) { return false; } @@ -480,29 +480,28 @@ class GraphcutSeams return true; } - bool processInput(double & newCost, InputData & input) - { - - //Get bounding box of input in panorama - //Dilate to have some pixels outside of the input + bool processInput(double& newCost, InputData& input) + { + // Get bounding box of input in panorama + // Dilate to have some pixels outside of the input BoundingBox localBbox = input.rect.dilate(3); localBbox.clampLeft(); localBbox.clampTop(); localBbox.clampBottom(_labels.Height() - 1); - - //Output must keep a margin also + + // Output must keep a margin also BoundingBox outputBbox = input.rect; outputBbox.left = input.rect.left - localBbox.left; outputBbox.top = input.rect.top - localBbox.top; - //Extract labels for the ROI + // Extract labels for the ROI image::Image localLabels(localBbox.width, localBbox.height); - if (!loopyImageExtract(localLabels, _labels, localBbox)) + if (!loopyImageExtract(localLabels, _labels, localBbox)) { return false; - } + } - //Build the input + // Build the input image::Image graphCutInput(localBbox.width, localBbox.height, true); if (!createInputOverlappingObservations(graphCutInput, localBbox)) { @@ -515,11 +514,11 @@ class GraphcutSeams return false; } - // Backup update for upscaling + // Backup update for upscaling BoundingBox inputBb = localBbox; inputBb.left = 0; inputBb.top = 0; - if (!loopyImageAssign(_labels, localLabels, localBbox, inputBb)) + if (!loopyImageAssign(_labels, localLabels, localBbox, inputBb)) { return false; } @@ -531,8 +530,8 @@ class GraphcutSeams return false; } - //Remove pixels too far from seams - for (int i = 0; i < graphCutInput.Height(); i++) + // Remove pixels too far from seams + for (int i = 0; i < graphCutInput.Height(); i++) { for (int j = 0; j < graphCutInput.Width(); j++) { @@ -543,31 +542,30 @@ class GraphcutSeams { graphCutInput(i, j).clear(); } - } + } } - + double oldCost = cost(localLabels, graphCutInput, input.id); - if (!alphaExpansion(localLabels, distanceMap, graphCutInput, input.id)) + if (!alphaExpansion(localLabels, distanceMap, graphCutInput, input.id)) { return false; } newCost = cost(localLabels, graphCutInput, input.id); - if (newCost < oldCost) - { + { BoundingBox inputBb = localBbox; inputBb.left = 0; inputBb.top = 0; - - if (!loopyImageAssign(_labels, localLabels, localBbox, inputBb)) + + if (!loopyImageAssign(_labels, localLabels, localBbox, inputBb)) { return false; } } - else + else { - newCost = oldCost; + newCost = oldCost; } return true; @@ -576,7 +574,7 @@ class GraphcutSeams bool process() { std::map costs; - for (auto & info : _inputs) + for (auto& info : _inputs) { costs[info.first] = std::numeric_limits::max(); } @@ -589,14 +587,13 @@ class GraphcutSeams bool hasChange = false; int pos = 0; - for (auto & info : _inputs) - { + for (auto& info : _inputs) + { double cost; - if (!processInput(cost, info.second)) + if (!processInput(cost, info.second)) { return false; } - if (costs[info.first] != cost) { @@ -614,13 +611,13 @@ class GraphcutSeams return true; } - double cost(const image::Image localLabels, const image::Image & input, IndexT currentLabel) + double cost(const image::Image localLabels, const image::Image& input, IndexT currentLabel) { double cost = 0.0; for (int y = 0; y < input.Height() - 1; y++) { - for(int x = 0; x < input.Width() - 1; x++) + for (int x = 0; x < input.Width() - 1; x++) { int xp = x + 1; int yp = y + 1; @@ -629,11 +626,11 @@ class GraphcutSeams IndexT labelx = localLabels(y, xp); IndexT labely = localLabels(yp, x); - if(label == UndefinedIndexT) + if (label == UndefinedIndexT) continue; - if(labelx == UndefinedIndexT) + if (labelx == UndefinedIndexT) continue; - if(labely == UndefinedIndexT) + if (labely == UndefinedIndexT) continue; image::RGBfColor CColorLC; @@ -651,7 +648,6 @@ class GraphcutSeams bool hasCLX = false; bool hasCLY = false; - auto it = findIndex(input(y, x), label); if (it.first != UndefinedIndexT) { @@ -701,27 +697,27 @@ class GraphcutSeams YColorLY = it.second; } - if(!hasCLC || !hasXLX || !hasYLY) + if (!hasCLC || !hasXLX || !hasYLY) { continue; } - if(!hasCLX) + if (!hasCLX) { CColorLX = CColorLC; } - if(!hasCLY) + if (!hasCLY) { CColorLY = CColorLC; } - if(!hasXLC) + if (!hasXLC) { XColorLC = XColorLX; } - if(!hasYLC) + if (!hasYLC) { YColorLC = YColorLY; } @@ -743,15 +739,14 @@ class GraphcutSeams return cost; } - bool alphaExpansion(image::Image & labels, const image::Image & distanceMap, const image::Image & input, IndexT currentLabel) + bool alphaExpansion(image::Image& labels, const image::Image& distanceMap, const image::Image& input, IndexT currentLabel) { image::Image mask(labels.Width(), labels.Height(), true, 0); image::Image ids(labels.Width(), labels.Height(), true, -1); image::Image color_label(labels.Width(), labels.Height(), true, image::RGBfColor(0.0f, 0.0f, 0.0f)); image::Image color_other(labels.Width(), labels.Height(), true, image::RGBfColor(0.0f, 0.0f, 0.0f)); - - for (int y = 0; y < labels.Height(); y++) + for (int y = 0; y < labels.Height(); y++) { for (int x = 0; x < labels.Width(); x++) { @@ -780,41 +775,41 @@ class GraphcutSeams { mask(y, x) = 2; } - else + else { mask(y, x) |= 2; } } } - // If the pixel may be a new kingdom for alpha - if(mask(y, x) == 1) + // If the pixel may be a new kingdom for alpha + if (mask(y, x) == 1) { color_label(y, x) = currentColor; color_other(y, x) = currentColor; } - else if(mask(y, x) == 2) + else if (mask(y, x) == 2) { color_label(y, x) = otherColor; color_other(y, x) = otherColor; } - else if(mask(y, x) == 3) + else if (mask(y, x) == 3) { color_label(y, x) = currentColor; color_other(y, x) = otherColor; } } - } + } // The rectangle is a grid. // However we want to ignore a lot of pixel. // Let's create an index per valid pixels for graph cut reference int count = 0; - for(int y = 0; y < labels.Height(); y++) + for (int y = 0; y < labels.Height(); y++) { - for(int x = 0; x < labels.Width(); x++) + for (int x = 0; x < labels.Width(); x++) { - if(mask(y, x) == 0) + if (mask(y, x) == 0) { continue; } @@ -822,24 +817,23 @@ class GraphcutSeams ids(y, x) = count; count++; } - } + } - //Create graph + // Create graph MaxFlow_AdjList gc(count); size_t countValid = 0; - for(int y = 0; y < labels.Height(); y++) + for (int y = 0; y < labels.Height(); y++) { - for(int x = 0; x < labels.Width(); x++) + for (int x = 0; x < labels.Width(); x++) { - - // If this pixel is not valid, ignore - if(mask(y, x) == 0) + // If this pixel is not valid, ignore + if (mask(y, x) == 0) { continue; } - // Get this pixel ID + // Get this pixel ID int node_id = ids(y, x); int ym1 = std::max(y - 1, 0); @@ -847,40 +841,36 @@ class GraphcutSeams int yp1 = std::min(y + 1, labels.Height() - 1); int xp1 = std::min(x + 1, labels.Width() - 1); - if(mask(y, x) == 1) + if (mask(y, x) == 1) { - // Only add nodes close to borders - if(mask(ym1, xm1) == 1 && mask(ym1, x) == 1 && mask(ym1, xp1) == 1 && - mask(y, xm1) == 1 && mask(y, xp1) == 1 && - mask(yp1, xm1) == 1 && mask(yp1, x) == 1 && mask(yp1, xp1) == 1) + // Only add nodes close to borders + if (mask(ym1, xm1) == 1 && mask(ym1, x) == 1 && mask(ym1, xp1) == 1 && mask(y, xm1) == 1 && mask(y, xp1) == 1 && + mask(yp1, xm1) == 1 && mask(yp1, x) == 1 && mask(yp1, xp1) == 1) { continue; } - - //This pixel is only seen by alpha. - //Enforce its domination by stating that removing this pixel - //from alpha territoy is infinitly costly (impossible). + // This pixel is only seen by alpha. + // Enforce its domination by stating that removing this pixel + // from alpha territoy is infinitly costly (impossible). gc.addNodeToSource(node_id, 100000); } - else if(mask(y, x) == 2) + else if (mask(y, x) == 2) { // Only add nodes close to borders - if(mask(ym1, xm1) == 2 && mask(ym1, x) == 2 && mask(ym1, xp1) == 2 && - mask(y, xm1) == 2 && mask(y, xp1) == 2 && - mask(yp1, xm1) == 2 && mask(yp1, x) == 2 && mask(yp1, xp1) == 2) + if (mask(ym1, xm1) == 2 && mask(ym1, x) == 2 && mask(ym1, xp1) == 2 && mask(y, xm1) == 2 && mask(y, xp1) == 2 && + mask(yp1, xm1) == 2 && mask(yp1, x) == 2 && mask(yp1, xp1) == 2) { continue; } - //This pixel is only seen by an ennemy. - //Enforce its domination by stating that removing this pixel - //from ennemy territory is infinitly costly (impossible). + // This pixel is only seen by an ennemy. + // Enforce its domination by stating that removing this pixel + // from ennemy territory is infinitly costly (impossible). gc.addNodeToSink(node_id, 100000); } - else if(mask(y, x) == 3) + else if (mask(y, x) == 3) { - // This pixel is seen by both alpha and enemies but is owned by ennemy. // Make sure that changing node owner will have no direct cost. // Connect it to both alpha and ennemy for the moment @@ -892,26 +882,23 @@ class GraphcutSeams } } - - if(countValid == 0) + if (countValid == 0) { - // We have no possibility for territory expansion + // We have no possibility for territory expansion // let's exit return true; } - // Loop over alpha bounding box. // Let's define the transition cost. // When two neighboor pixels have different labels, there is a seam (border) cost. // Graph cut will try to make sure the territory will have a minimal border cost - for(int y = 0; y < labels.Height(); y++) + for (int y = 0; y < labels.Height(); y++) { - for(int x = 0; x < labels.Width(); x++) + for (int x = 0; x < labels.Width(); x++) { - - if(mask(y, x) == 0) + if (mask(y, x) == 0) { continue; } @@ -919,16 +906,15 @@ class GraphcutSeams int node_id = ids(y, x); // Make sure it is possible to estimate this horizontal border - if(y < mask.Height() - 1) + if (y < mask.Height() - 1) { // Make sure the other pixel is owned by someone - if(mask(y + 1, x)) + if (mask(y + 1, x)) { - int other_node_id = ids(y + 1, x); float w = 1000; - if(((mask(y, x) & 1) && (mask(y + 1, x) & 2)) || ((mask(y, x) & 2) && (mask(y + 1, x) & 1))) + if (((mask(y, x) & 1) && (mask(y + 1, x) & 2)) || ((mask(y, x) & 2) && (mask(y + 1, x) & 1))) { float d1 = (color_label(y, x) - color_other(y, x)).norm(); float d2 = (color_label(y + 1, x) - color_other(y + 1, x)).norm(); @@ -943,16 +929,14 @@ class GraphcutSeams } } - if(x < mask.Width() - 1) + if (x < mask.Width() - 1) { - - if(mask(y, x + 1)) + if (mask(y, x + 1)) { - int other_node_id = ids(y, x + 1); float w = 1000; - if(((mask(y, x) & 1) && (mask(y, x + 1) & 2)) || ((mask(y, x) & 2) && (mask(y, x + 1) & 1))) + if (((mask(y, x) & 1) && (mask(y, x + 1) & 2)) || ((mask(y, x) & 2) && (mask(y, x + 1) & 1))) { float d1 = (color_label(y, x) - color_other(y, x)).norm(); float d2 = (color_label(y, x + 1) - color_other(y, x + 1)).norm(); @@ -972,17 +956,16 @@ class GraphcutSeams gc.compute(); int changeCount = 0; - for(int y = 0; y < labels.Height(); y++) + for (int y = 0; y < labels.Height(); y++) { - for(int x = 0; x < labels.Width(); x++) + for (int x = 0; x < labels.Width(); x++) { IndexT label = labels(y, x); int id = ids(y, x); - if(gc.isSource(id)) + if (gc.isSource(id)) { - - if(label != currentLabel) + if (label != currentLabel) { changeCount++; } @@ -995,13 +978,9 @@ class GraphcutSeams return true; } - image::Image & getLabels() - { - return _labels; - } - -private: + image::Image& getLabels() { return _labels; } + private: std::map _inputs; int _outputWidth; @@ -1010,4 +989,4 @@ class GraphcutSeams image::Image _labels; }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/imageOps.cpp b/src/aliceVision/panorama/imageOps.cpp index b5d2ba795b..015f4510bf 100644 --- a/src/aliceVision/panorama/imageOps.cpp +++ b/src/aliceVision/panorama/imageOps.cpp @@ -8,12 +8,11 @@ #include "gaussian.hpp" #include "feathering.hpp" -namespace aliceVision -{ +namespace aliceVision { -void removeNegativeValues(image::Image & img) +void removeNegativeValues(image::Image& img) { - for (int i = 0; i < img.Height(); i++) + for (int i = 0; i < img.Height(); i++) { for (int j = 0; j < img.Width(); j++) { @@ -29,12 +28,12 @@ void removeNegativeValues(image::Image & img) ret.r() = 0.0; } - if(rpix.g() < 0.0) + if (rpix.g() < 0.0) { ret.g() = 0.0; } - if(rpix.b() < 0.0) + if (rpix.b() < 0.0) { ret.b() = 0.0; } @@ -44,4 +43,4 @@ void removeNegativeValues(image::Image & img) } } -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/imageOps.hpp b/src/aliceVision/panorama/imageOps.hpp index 8f36eec26a..48864cbe7b 100644 --- a/src/aliceVision/panorama/imageOps.hpp +++ b/src/aliceVision/panorama/imageOps.hpp @@ -12,14 +12,14 @@ namespace aliceVision { -template +template bool downscale(aliceVision::image::Image& outputColor, const aliceVision::image::Image& inputColor) { - for(int i = 0; i < outputColor.Height(); i++) + for (int i = 0; i < outputColor.Height(); i++) { int di = i * 2; - for(int j = 0; j < outputColor.Width(); j++) + for (int j = 0; j < outputColor.Width(); j++) { int dj = j * 2; @@ -30,21 +30,19 @@ bool downscale(aliceVision::image::Image& outputColor, const aliceVision::ima return true; } - -template +template bool upscale(aliceVision::image::Image& outputColor, const aliceVision::image::Image& inputColor) { - size_t width = inputColor.Width(); size_t height = inputColor.Height(); size_t dwidth = outputColor.Width(); size_t dheight = outputColor.Height(); - for(int i = 0; i < height - 1; i++) + for (int i = 0; i < height - 1; i++) { int di = i * 2; - for(int j = 0; j < width - 1; j++) + for (int j = 0; j < width - 1; j++) { int dj = j * 2; @@ -106,30 +104,26 @@ bool upscale(aliceVision::image::Image& outputColor, const aliceVision::image return true; } -template -bool substract(aliceVision::image::Image& AminusB, const aliceVision::image::Image& A, - const aliceVision::image::Image& B) +template +bool substract(aliceVision::image::Image& AminusB, const aliceVision::image::Image& A, const aliceVision::image::Image& B) { - size_t width = AminusB.Width(); size_t height = AminusB.Height(); - if(AminusB.size() != A.size()) + if (AminusB.size() != A.size()) { return false; } - if(AminusB.size() != B.size()) + if (AminusB.size() != B.size()) { return false; } - for(int i = 0; i < height; i++) + for (int i = 0; i < height; i++) { - - for(int j = 0; j < width; j++) + for (int j = 0; j < width; j++) { - AminusB(i, j) = A(i, j) - B(i, j); } } @@ -137,30 +131,26 @@ bool substract(aliceVision::image::Image& AminusB, const aliceVision::image:: return true; } -template -bool addition(aliceVision::image::Image& AplusB, const aliceVision::image::Image& A, - const aliceVision::image::Image& B) +template +bool addition(aliceVision::image::Image& AplusB, const aliceVision::image::Image& A, const aliceVision::image::Image& B) { - size_t width = AplusB.Width(); size_t height = AplusB.Height(); - if(AplusB.size() != A.size()) + if (AplusB.size() != A.size()) { return false; } - if(AplusB.size() != B.size()) + if (AplusB.size() != B.size()) { return false; } - for(int i = 0; i < height; i++) + for (int i = 0; i < height; i++) { - - for(int j = 0; j < width; j++) + for (int j = 0; j < width; j++) { - AplusB(i, j) = A(i, j) + B(i, j); } } @@ -170,27 +160,31 @@ bool addition(aliceVision::image::Image& AplusB, const aliceVision::image::Im void removeNegativeValues(image::Image& img); -template -bool loopyImageAssign(image::Image & output, const aliceVision::image::Image & input, const BoundingBox & assignedOutputBb, const BoundingBox & assignedInputBb) +template +bool loopyImageAssign(image::Image& output, + const aliceVision::image::Image& input, + const BoundingBox& assignedOutputBb, + const BoundingBox& assignedInputBb) { BoundingBox inputBb = assignedInputBb; BoundingBox outputBb = assignedOutputBb; - if (inputBb.width != outputBb.width) + if (inputBb.width != outputBb.width) { return false; } - if (inputBb.height != outputBb.height) + if (inputBb.height != outputBb.height) { return false; } - if (assignedOutputBb.getRight() < output.Width()) + if (assignedOutputBb.getRight() < output.Width()) { - output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); + output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = + input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); } - else + else { int left_1 = assignedOutputBb.left; int left_2 = 0; @@ -202,48 +196,53 @@ bool loopyImageAssign(image::Image & output, const aliceVision::image::Image< inputBb.width = width1; outputBb.width = width1; - output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); + output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = + input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); inputBb.left = width1; outputBb.left = 0; - //no overlap + // no overlap int width2_clamped = std::min(width2, left_1); inputBb.width = width2_clamped; outputBb.width = width2_clamped; - if (width2_clamped == 0) return true; + if (width2_clamped == 0) + return true; - output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); + output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = + input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); } return true; } -template -bool loopyCachedImageAssign(CachedImage & output, const aliceVision::image::Image & input, const BoundingBox & assignedOutputBb, const BoundingBox & assignedInputBb) +template +bool loopyCachedImageAssign(CachedImage& output, + const aliceVision::image::Image& input, + const BoundingBox& assignedOutputBb, + const BoundingBox& assignedInputBb) { BoundingBox inputBb = assignedInputBb; BoundingBox outputBb = assignedOutputBb; - if (inputBb.width != outputBb.width) + if (inputBb.width != outputBb.width) { return false; } - if (inputBb.height != outputBb.height) + if (inputBb.height != outputBb.height) { return false; } - if (assignedOutputBb.getRight() < output.getWidth()) + if (assignedOutputBb.getRight() < output.getWidth()) { - - if (!output.assign(input, inputBb, outputBb)) + if (!output.assign(input, inputBb, outputBb)) { return false; } } - else + else { int left_1 = assignedOutputBb.left; int left_2 = 0; @@ -254,8 +253,7 @@ bool loopyCachedImageAssign(CachedImage & output, const aliceVision::image::I outputBb.left = left_1; inputBb.width = width1; outputBb.width = width1; - - + if (!output.assign(input, inputBb, outputBb)) { return false; @@ -264,12 +262,12 @@ bool loopyCachedImageAssign(CachedImage & output, const aliceVision::image::I inputBb.left = width1; outputBb.left = 0; - //no overlap + // no overlap int width2_clamped = std::min(width2, left_1); inputBb.width = width2_clamped; outputBb.width = width2_clamped; - if (width2_clamped == 0) return true; - + if (width2_clamped == 0) + return true; if (!output.assign(input, inputBb, outputBb)) { @@ -280,9 +278,9 @@ bool loopyCachedImageAssign(CachedImage & output, const aliceVision::image::I return true; } -template -bool loopyImageExtract(image::Image & output, const image::Image & input, const BoundingBox & extractedInputBb) -{ +template +bool loopyImageExtract(image::Image& output, const image::Image& input, const BoundingBox& extractedInputBb) +{ BoundingBox outputBb; BoundingBox inputBb; @@ -292,30 +290,31 @@ bool loopyImageExtract(image::Image & output, const image::Image & input, outputBb.height = std::min(extractedInputBb.height, output.Height()); inputBb = extractedInputBb; - - if (inputBb.getRight() < input.Width()) + + if (inputBb.getRight() < input.Width()) { - output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); + output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = + input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); } - else - { + else + { int availableWidth = output.Width(); - while (availableWidth > 0) + while (availableWidth > 0) { inputBb.clampRight(input.Width() - 1); int extractedWidth = std::min(inputBb.width, availableWidth); - inputBb.width = extractedWidth; outputBb.width = extractedWidth; - output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); - - //Update the bouding box for output + output.block(outputBb.top, outputBb.left, outputBb.height, outputBb.width) = + input.block(inputBb.top, inputBb.left, inputBb.height, inputBb.width); + + // Update the bouding box for output outputBb.left += extractedWidth; availableWidth -= extractedWidth; - - //All the input is available. + + // All the input is available. inputBb.left = 0; inputBb.width = input.Width(); } @@ -324,9 +323,9 @@ bool loopyImageExtract(image::Image & output, const image::Image & input, return true; } -template -bool loopyCachedImageExtract(aliceVision::image::Image & output, CachedImage & input, const BoundingBox & extractedInputBb) -{ +template +bool loopyCachedImageExtract(aliceVision::image::Image& output, CachedImage& input, const BoundingBox& extractedInputBb) +{ BoundingBox outputBb; BoundingBox inputBb; @@ -336,36 +335,35 @@ bool loopyCachedImageExtract(aliceVision::image::Image & output, CachedImage< outputBb.height = std::min(extractedInputBb.height, output.Height()); inputBb = extractedInputBb; - - if (inputBb.getRight() < input.getWidth()) + + if (inputBb.getRight() < input.getWidth()) { - if (!input.extract(output, outputBb, inputBb)) + if (!input.extract(output, outputBb, inputBb)) { return false; } } - else - { + else + { int availableWidth = output.Width(); - while (availableWidth > 0) + while (availableWidth > 0) { inputBb.clampRight(input.getWidth() - 1); int extractedWidth = std::min(inputBb.width, availableWidth); - inputBb.width = extractedWidth; outputBb.width = extractedWidth; - if (!input.extract(output, outputBb, inputBb)) + if (!input.extract(output, outputBb, inputBb)) { return false; } - - //Update the bouding box for output + + // Update the bouding box for output outputBb.left += extractedWidth; availableWidth -= extractedWidth; - - //All the input is available. + + // All the input is available. inputBb.left = 0; inputBb.width = input.getWidth(); } @@ -374,17 +372,17 @@ bool loopyCachedImageExtract(aliceVision::image::Image & output, CachedImage< return true; } - -template -bool makeImagePyramidCompatible(image::Image& output, - int & outOffsetX, int & outOffsetY, +template +bool makeImagePyramidCompatible(image::Image& output, + int& outOffsetX, + int& outOffsetY, const image::Image& input, - int offsetX, int offsetY, + int offsetX, + int offsetY, size_t borderSize, size_t num_levels) { - - if(num_levels == 0) + if (num_levels == 0) { return false; } @@ -436,5 +434,4 @@ bool makeImagePyramidCompatible(image::Image& output, return true; } - -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/laplacianCompositer.hpp b/src/aliceVision/panorama/laplacianCompositer.hpp index 294790eb7f..81ec70c901 100644 --- a/src/aliceVision/panorama/laplacianCompositer.hpp +++ b/src/aliceVision/panorama/laplacianCompositer.hpp @@ -10,50 +10,46 @@ #include "feathering.hpp" #include "laplacianPyramid.hpp" -namespace aliceVision -{ +namespace aliceVision { class LaplacianCompositer : public Compositer { -public: + public: LaplacianCompositer(size_t outputWidth, size_t outputHeight, size_t scale) - : Compositer(outputWidth, outputHeight) - , _pyramidPanorama(outputWidth, outputHeight, scale + 1) - , _bands(scale + 1) - { - } + : Compositer(outputWidth, outputHeight), + _pyramidPanorama(outputWidth, outputHeight, scale + 1), + _bands(scale + 1) + {} - virtual ~LaplacianCompositer() - { - - } + virtual ~LaplacianCompositer() {} - virtual bool initialize(const BoundingBox & outputRoi) - { + virtual bool initialize(const BoundingBox& outputRoi) + { _outputRoi = outputRoi; - - - if (_outputRoi.left < 0) return false; - if (_outputRoi.top < 0) return false; - if (_outputRoi.getRight() >= _panoramaWidth) return false; - if (_outputRoi.getBottom() >= _panoramaHeight) return false; + + if (_outputRoi.left < 0) + return false; + if (_outputRoi.top < 0) + return false; + if (_outputRoi.getRight() >= _panoramaWidth) + return false; + if (_outputRoi.getBottom() >= _panoramaHeight) + return false; return _pyramidPanorama.initialize(); } - virtual int getBorderSize() const - { - return _gaussianFilterRadius; - } - + virtual int getBorderSize() const { return _gaussianFilterRadius; } + virtual bool append(aliceVision::image::Image& color, aliceVision::image::Image& inputMask, - aliceVision::image::Image& inputWeights, - int offsetX, int offsetY) + aliceVision::image::Image& inputWeights, + int offsetX, + int offsetY) { // Fill Color images masked parts with fake but coherent info aliceVision::image::Image feathered; - if (!feathering(feathered, color, inputMask)) + if (!feathering(feathered, color, inputMask)) { return false; } @@ -61,9 +57,9 @@ class LaplacianCompositer : public Compositer color = aliceVision::image::Image(); // To log space for hdr - for(int i = 0; i < feathered.Height(); i++) + for (int i = 0; i < feathered.Height(); i++) { - for(int j = 0; j < feathered.Width(); j++) + for (int j = 0; j < feathered.Width(); j++) { feathered(i, j).r() = std::log(std::max(1e-8f, feathered(i, j).r())); feathered(i, j).g() = std::log(std::max(1e-8f, feathered(i, j).g())); @@ -71,13 +67,13 @@ class LaplacianCompositer : public Compositer } } - // Convert mask to alpha layer + // Convert mask to alpha layer image::Image maskFloat(inputMask.Width(), inputMask.Height()); - for(int i = 0; i < inputMask.Height(); i++) + for (int i = 0; i < inputMask.Height(); i++) { - for(int j = 0; j < inputMask.Width(); j++) + for (int j = 0; j < inputMask.Width(); j++) { - if(inputMask(i, j)) + if (inputMask(i, j)) { maskFloat(i, j) = 1.0f; } @@ -88,7 +84,6 @@ class LaplacianCompositer : public Compositer } } - BoundingBox bb; bb.left = offsetX; bb.top = offsetY; @@ -102,7 +97,7 @@ class LaplacianCompositer : public Compositer contentbb.left = bb.left - potbb.left; contentbb.top = bb.top - potbb.top; - if (!_pyramidPanorama.apply(feathered, maskFloat, inputWeights, potbb, contentbb)) + if (!_pyramidPanorama.apply(feathered, maskFloat, inputWeights, potbb, contentbb)) { return false; } @@ -114,12 +109,12 @@ class LaplacianCompositer : public Compositer { _panorama = image::Image(_outputRoi.width, _outputRoi.height, true, image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f)); - if (!_pyramidPanorama.rebuild(_panorama, _outputRoi)) + if (!_pyramidPanorama.rebuild(_panorama, _outputRoi)) { return false; } - for (int i = 0; i < _outputRoi.height; i++) + for (int i = 0; i < _outputRoi.height; i++) { for (int j = 0; j < _outputRoi.width; j++) { @@ -138,10 +133,10 @@ class LaplacianCompositer : public Compositer return true; } -protected: + protected: const int _gaussianFilterRadius = 2; LaplacianPyramid _pyramidPanorama; size_t _bands; }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/laplacianPyramid.cpp b/src/aliceVision/panorama/laplacianPyramid.cpp index 6e71b7af38..22ce8d4a02 100644 --- a/src/aliceVision/panorama/laplacianPyramid.cpp +++ b/src/aliceVision/panorama/laplacianPyramid.cpp @@ -10,23 +10,19 @@ #include "gaussian.hpp" #include "compositer.hpp" -namespace aliceVision -{ +namespace aliceVision { -LaplacianPyramid::LaplacianPyramid(size_t base_width, size_t base_height, size_t max_levels) : -_baseWidth(base_width), -_baseHeight(base_height), -_maxLevels(max_levels) +LaplacianPyramid::LaplacianPyramid(size_t base_width, size_t base_height, size_t max_levels) + : _baseWidth(base_width), + _baseHeight(base_height), + _maxLevels(max_levels) { omp_init_lock(&_merge_lock); } -LaplacianPyramid::~LaplacianPyramid() -{ - omp_destroy_lock(&_merge_lock); -} +LaplacianPyramid::~LaplacianPyramid() { omp_destroy_lock(&_merge_lock); } -bool LaplacianPyramid::initialize() +bool LaplacianPyramid::initialize() { size_t width = _baseWidth; size_t height = _baseHeight; @@ -35,7 +31,7 @@ bool LaplacianPyramid::initialize() double max_scale = 1.0 / pow(2.0, _maxLevels - 1); /*Prepare pyramid*/ - for(int lvl = 0; lvl < _maxLevels; lvl++) + for (int lvl = 0; lvl < _maxLevels; lvl++) { image::Image color(width, height, true, image::RGBfColor(0.0f, 0.0f, 0.0f)); image::Image weights(width, height, true, 0.0f); @@ -52,11 +48,12 @@ bool LaplacianPyramid::initialize() bool LaplacianPyramid::apply(aliceVision::image::Image& source, aliceVision::image::Image& mask, - aliceVision::image::Image& weights, - const BoundingBox &outputBoundingBox, const BoundingBox &contentBoudingBox) + aliceVision::image::Image& weights, + const BoundingBox& outputBoundingBox, + const BoundingBox& contentBoudingBox) { - //We assume the input source has been feathered - //the outputBoundingBox has been scaled to accept the pyramid scales + // We assume the input source has been feathered + // the outputBoundingBox has been scaled to accept the pyramid scales int width = outputBoundingBox.width; int height = outputBoundingBox.height; int offsetX = outputBoundingBox.left; @@ -82,7 +79,7 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source mask = aliceVision::image::Image(); weights = aliceVision::image::Image(); - for(int l = 0; l < _levels.size() - 1; l++) + for (int l = 0; l < _levels.size() - 1; l++) { BoundingBox inputBbox; inputBbox.left = offsetX; @@ -96,11 +93,11 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source image::Image bufFloat(width, height); // Apply mask to content before convolution - for(int i = 0; i < height; i++) + for (int i = 0; i < height; i++) { - for(int j = 0; j < width; j++) + for (int j = 0; j < width; j++) { - if(std::abs(currentMask(i, j)) > 1e-6) + if (std::abs(currentMask(i, j)) > 1e-6) { bufMasked(i, j) = currentColor(i, j); } @@ -114,25 +111,25 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source } } - if (!convolveGaussian5x5(buf, bufMasked)) + if (!convolveGaussian5x5(buf, bufMasked)) { return false; } - if (!convolveGaussian5x5(bufFloat, currentMask)) + if (!convolveGaussian5x5(bufFloat, currentMask)) { return false; } - //Normalize given mask + // Normalize given mask //(Make sure the convolution sum is 1) - for(int i = 0; i < buf.Height(); i++) + for (int i = 0; i < buf.Height(); i++) { - for(int j = 0; j < buf.Width(); j++) + for (int j = 0; j < buf.Width(); j++) { float m = bufFloat(i, j); - if(std::abs(m) > 1e-6) + if (std::abs(m) > 1e-6) { buf(i, j).r() = buf(i, j).r() / m; buf(i, j).g() = buf(i, j).g() / m; @@ -149,7 +146,6 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source } } - int nextWidth = width / 2; int nextHeight = int(floor(float(height) / 2.0f)); @@ -157,45 +153,45 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source nextWeights = aliceVision::image::Image(nextWidth, nextHeight); nextMask = aliceVision::image::Image(nextWidth, nextHeight); - if (!downscale(nextColor, buf)) + if (!downscale(nextColor, buf)) { return false; } - if (!downscale(nextMask, bufFloat)) + if (!downscale(nextMask, bufFloat)) { return false; } - if (!upscale(buf, nextColor)) + if (!upscale(buf, nextColor)) { return false; } - //Filter - if (!convolveGaussian5x5(buf2, buf)) + // Filter + if (!convolveGaussian5x5(buf2, buf)) { return false; } - //Values must be multiplied by 4 as our upscale was using - //filling of 0 values - for(int i = 0; i < buf2.Height(); i++) + // Values must be multiplied by 4 as our upscale was using + // filling of 0 values + for (int i = 0; i < buf2.Height(); i++) { - for(int j = 0; j < buf2.Width(); j++) - { + for (int j = 0; j < buf2.Width(); j++) + { buf2(i, j) *= 4.0f; } } - //Only keep the difference (Band pass) - if (!substract(currentColor, currentColor, buf2)) + // Only keep the difference (Band pass) + if (!substract(currentColor, currentColor, buf2)) { return false; } - - //Downscale weights - if (!convolveGaussian5x5(bufFloat, currentWeights)) + + // Downscale weights + if (!convolveGaussian5x5(bufFloat, currentWeights)) { return false; } @@ -205,7 +201,7 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source return false; } - //Merge this view with previous ones + // Merge this view with previous ones omp_set_lock(&_merge_lock); bool res = merge(currentColor, currentWeights, l, offsetX, offsetY); omp_unset_lock(&_merge_lock); @@ -215,15 +211,15 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source return false; } - //Swap buffers + // Swap buffers currentColor = nextColor; currentWeights = nextWeights; currentMask = nextMask; width = nextWidth; height = nextHeight; - //Thanks to previous operation, - //We are sure the offset is a power of two for the required levels + // Thanks to previous operation, + // We are sure the offset is a power of two for the required levels offsetX = offsetX / 2; offsetY = offsetY / 2; } @@ -238,27 +234,30 @@ bool LaplacianPyramid::apply(aliceVision::image::Image& source omp_set_lock(&_merge_lock); _inputInfos.push_back(iinfo); omp_unset_lock(&_merge_lock); - return true; } bool LaplacianPyramid::merge(const aliceVision::image::Image& oimg, - const aliceVision::image::Image& oweight, - size_t level, int offsetX, int offsetY) + const aliceVision::image::Image& oweight, + size_t level, + int offsetX, + int offsetY) { - image::Image & img = _levels[level]; - image::Image & weight = _weights[level]; + image::Image& img = _levels[level]; + image::Image& weight = _weights[level]; - for(int i = 0; i < oimg.Height(); i++) - { + for (int i = 0; i < oimg.Height(); i++) + { int y = i + offsetY; - if (y < 0 || y >= img.Height()) continue; + if (y < 0 || y >= img.Height()) + continue; - for(int j = 0; j < oimg.Width(); j++) - { + for (int j = 0; j < oimg.Width(); j++) + { int x = j + offsetX; - if (x < 0 || x >= img.Width()) continue; + if (x < 0 || x >= img.Width()) + continue; img(y, x).r() += oimg(i, j).r() * oweight(i, j); img(y, x).g() += oimg(i, j).g() * oweight(i, j); @@ -267,13 +266,12 @@ bool LaplacianPyramid::merge(const aliceVision::image::Image& } } - return true; } -bool LaplacianPyramid::rebuild(image::Image& output, const BoundingBox & roi) +bool LaplacianPyramid::rebuild(image::Image& output, const BoundingBox& roi) { - for (InputInfo & iinfo : _inputInfos) + for (InputInfo& iinfo : _inputInfos) { if (!merge(iinfo.color, iinfo.weights, _levels.size() - 1, iinfo.offsetX, iinfo.offsetY)) { @@ -282,22 +280,22 @@ bool LaplacianPyramid::rebuild(image::Image& output, const Bo } // We first want to compute the final pixels mean - for(int l = 0; l < _levels.size(); l++) + for (int l = 0; l < _levels.size(); l++) { - image::Image & level = _levels[l]; - image::Image & weight = _weights[l]; + image::Image& level = _levels[l]; + image::Image& weight = _weights[l]; - for (int i = 0; i < level.Height(); i++) + for (int i = 0; i < level.Height(); i++) { for (int j = 0; j < level.Width(); j++) { float w = weight(i, j); image::RGBfColor c = level(i, j); - if (w < 1e-6) + if (w < 1e-6) { - level(i, j) = image::RGBfColor(0.0f, 0.0f, 0.0f); - continue; + level(i, j) = image::RGBfColor(0.0f, 0.0f, 0.0f); + continue; } image::RGBfColor r; @@ -313,7 +311,7 @@ bool LaplacianPyramid::rebuild(image::Image& output, const Bo removeNegativeValues(_levels[_levels.size() - 1]); - for(int l = _levels.size() - 2; l >= 0; l--) + for (int l = _levels.size() - 2; l >= 0; l--) { int halfLevel = l + 1; int currentLevel = l; @@ -321,24 +319,24 @@ bool LaplacianPyramid::rebuild(image::Image& output, const Bo aliceVision::image::Image buf(_levels[currentLevel].Width(), _levels[currentLevel].Height()); aliceVision::image::Image buf2(_levels[currentLevel].Width(), _levels[currentLevel].Height()); - if (!upscale(buf, _levels[halfLevel])) + if (!upscale(buf, _levels[halfLevel])) { return false; } - if (!convolveGaussian5x5(buf2, buf, false)) + if (!convolveGaussian5x5(buf2, buf, false)) { return false; } - for(int y = 0; y < buf2.Height(); y++) + for (int y = 0; y < buf2.Height(); y++) { - for(int x = 0; x < buf2.Width(); x++) + for (int x = 0; x < buf2.Width(); x++) { buf2(y, x) *= 4.0f; } } - + if (!addition(_levels[currentLevel], _levels[currentLevel], buf2)) { return false; @@ -346,14 +344,14 @@ bool LaplacianPyramid::rebuild(image::Image& output, const Bo removeNegativeValues(_levels[currentLevel]); } - - image::Image & level = _levels[0]; - image::Image & weight = _weights[0]; - for(int i = 0; i < roi.height; i++) + + image::Image& level = _levels[0]; + image::Image& weight = _weights[0]; + for (int i = 0; i < roi.height; i++) { int y = i + roi.top; - for(int j = 0; j < roi.width; j++) + for (int j = 0; j < roi.width; j++) { int x = j + roi.left; @@ -375,4 +373,4 @@ bool LaplacianPyramid::rebuild(image::Image& output, const Bo return true; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/laplacianPyramid.hpp b/src/aliceVision/panorama/laplacianPyramid.hpp index 3a02707754..cb487bb205 100644 --- a/src/aliceVision/panorama/laplacianPyramid.hpp +++ b/src/aliceVision/panorama/laplacianPyramid.hpp @@ -10,40 +10,42 @@ #include -namespace aliceVision -{ +namespace aliceVision { class LaplacianPyramid { -public: - struct InputInfo + public: + struct InputInfo { - aliceVision::image::Image color; + aliceVision::image::Image color; aliceVision::image::Image mask; aliceVision::image::Image weights; int offsetX; int offsetY; }; -public: + public: LaplacianPyramid(size_t base_width, size_t base_height, size_t max_levels); virtual ~LaplacianPyramid(); bool initialize(); - + bool apply(aliceVision::image::Image& source, - aliceVision::image::Image& mask, + aliceVision::image::Image& mask, aliceVision::image::Image& weights, - const BoundingBox &outputBoundingBox, const BoundingBox &contentBoudingBox); + const BoundingBox& outputBoundingBox, + const BoundingBox& contentBoudingBox); - bool merge(const aliceVision::image::Image& oimg, + bool merge(const aliceVision::image::Image& oimg, const aliceVision::image::Image& oweight, - size_t level, int offset_x, int offset_y); + size_t level, + int offset_x, + int offset_y); - bool rebuild(image::Image& output, const BoundingBox & roi); + bool rebuild(image::Image& output, const BoundingBox& roi); -private: + private: int _baseWidth; int _baseHeight; int _maxLevels; @@ -54,4 +56,4 @@ class LaplacianPyramid std::vector _inputInfos; }; -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/panoramaMap.cpp b/src/aliceVision/panorama/panoramaMap.cpp index 3feb263fe5..c155f7a3d3 100644 --- a/src/aliceVision/panorama/panoramaMap.cpp +++ b/src/aliceVision/panorama/panoramaMap.cpp @@ -9,18 +9,17 @@ #include #include -namespace aliceVision -{ +namespace aliceVision { -bool PanoramaMap::append(IndexT index, const BoundingBox & box) +bool PanoramaMap::append(IndexT index, const BoundingBox& box) { BoundingBox bbox = box; - - // if the reference bounding box is looping on the right of the panorama, + + // if the reference bounding box is looping on the right of the panorama, // make it loop on the left instead to reduce the number of further possibilities - if (bbox.getRight() >= _panoramaWidth && bbox.width < _panoramaWidth) + if (bbox.getRight() >= _panoramaWidth && bbox.width < _panoramaWidth) { - bbox.left -= _panoramaWidth; + bbox.left -= _panoramaWidth; } _map[index] = bbox; @@ -28,9 +27,8 @@ bool PanoramaMap::append(IndexT index, const BoundingBox & box) return true; } -bool PanoramaMap::intersect(const BoundingBox & box1, const BoundingBox & box2) const +bool PanoramaMap::intersect(const BoundingBox& box1, const BoundingBox& box2) const { - BoundingBox extentedBox1 = box1.divide(_scale).dilate(_borderSize).multiply(_scale); BoundingBox extentedBox2 = box2.divide(_scale).dilate(_borderSize).multiply(_scale); @@ -40,17 +38,17 @@ bool PanoramaMap::intersect(const BoundingBox & box1, const BoundingBox & box2) BoundingBox otherBboxLoopRight = extentedBox2; otherBboxLoopRight.left = otherBbox.left + _panoramaWidth; - if (!extentedBox1.intersectionWith(otherBbox).isEmpty()) + if (!extentedBox1.intersectionWith(otherBbox).isEmpty()) { return true; } - if (!extentedBox1.intersectionWith(otherBboxLoop).isEmpty()) + if (!extentedBox1.intersectionWith(otherBboxLoop).isEmpty()) { return true; } - if (!extentedBox1.intersectionWith(otherBboxLoopRight).isEmpty()) + if (!extentedBox1.intersectionWith(otherBboxLoopRight).isEmpty()) { return true; } @@ -58,7 +56,7 @@ bool PanoramaMap::intersect(const BoundingBox & box1, const BoundingBox & box2) return false; } -bool PanoramaMap::getOverlaps(std::vector & overlaps, IndexT reference) const +bool PanoramaMap::getOverlaps(std::vector& overlaps, IndexT reference) const { if (_map.find(reference) == _map.end()) { @@ -70,7 +68,7 @@ bool PanoramaMap::getOverlaps(std::vector & overlaps, IndexT reference) return getOverlaps(overlaps, bbref); } -bool PanoramaMap::getOverlaps(std::vector & overlaps, const BoundingBox & referenceBoundingBox) const +bool PanoramaMap::getOverlaps(std::vector& overlaps, const BoundingBox& referenceBoundingBox) const { for (auto it : _map) { @@ -83,15 +81,20 @@ bool PanoramaMap::getOverlaps(std::vector & overlaps, const BoundingBox return true; } - -bool PanoramaMap::getIntersectionsList(std::vector & intersections, std::vector & currentBoundingBoxes, const IndexT & referenceIndex, const IndexT & otherIndex) const +bool PanoramaMap::getIntersectionsList(std::vector& intersections, + std::vector& currentBoundingBoxes, + const IndexT& referenceIndex, + const IndexT& otherIndex) const { BoundingBox referenceBoundingBox = _map.at(referenceIndex); - + return getIntersectionsList(intersections, currentBoundingBoxes, referenceBoundingBox, otherIndex); } -bool PanoramaMap::getIntersectionsList(std::vector & intersections, std::vector & currentBoundingBoxes, const BoundingBox & referenceBoundingBox, const IndexT & otherIndex) const +bool PanoramaMap::getIntersectionsList(std::vector& intersections, + std::vector& currentBoundingBoxes, + const BoundingBox& referenceBoundingBox, + const IndexT& otherIndex) const { BoundingBox referenceBoundingBoxReduced = referenceBoundingBox.divide(_scale).dilate(_borderSize); @@ -106,7 +109,7 @@ bool PanoramaMap::getIntersectionsList(std::vector & intersections, BoundingBox intersection = intersectionSmall.multiply(_scale); intersection = intersection.limitInside(otherBoundingBox); - if (!intersection.isEmpty()) + if (!intersection.isEmpty()) { intersections.push_back(intersection); currentBoundingBoxes.push_back(otherBoundingBox); @@ -120,13 +123,13 @@ bool PanoramaMap::getIntersectionsList(std::vector & intersections, otherBoundingBoxLoop.left -= _panoramaWidth; BoundingBox otherBoundingBoxReduced = otherBoundingBoxLoop.divide(_scale).dilate(_borderSize); BoundingBox intersectionSmall = referenceBoundingBoxReduced.intersectionWith(otherBoundingBoxReduced); - + if (!intersectionSmall.isEmpty()) - { + { BoundingBox intersection = intersectionSmall.multiply(_scale); intersection = intersection.limitInside(otherBoundingBoxLoop); - if (!intersection.isEmpty()) + if (!intersection.isEmpty()) { intersections.push_back(intersection); currentBoundingBoxes.push_back(otherBoundingBoxLoop); @@ -141,11 +144,11 @@ bool PanoramaMap::getIntersectionsList(std::vector & intersections, BoundingBox otherBoundingBoxReduced = otherBoundingBoxLoop.divide(_scale).dilate(_borderSize); BoundingBox intersectionSmall = referenceBoundingBoxReduced.intersectionWith(otherBoundingBoxReduced); if (!intersectionSmall.isEmpty()) - { + { BoundingBox intersection = intersectionSmall.multiply(_scale); intersection = intersection.limitInside(otherBoundingBoxLoop); - if (!intersection.isEmpty()) + if (!intersection.isEmpty()) { intersections.push_back(intersection); currentBoundingBoxes.push_back(otherBoundingBoxLoop); @@ -160,11 +163,11 @@ bool PanoramaMap::getIntersectionsList(std::vector & intersections, BoundingBox otherBoundingBoxReduced = otherBoundingBoxLoop.divide(_scale).dilate(_borderSize); BoundingBox intersectionSmall = referenceBoundingBoxReduced.intersectionWith(otherBoundingBoxReduced); if (!intersectionSmall.isEmpty()) - { + { BoundingBox intersection = intersectionSmall.multiply(_scale); intersection = intersection.limitInside(otherBoundingBoxLoop); - if (!intersection.isEmpty()) + if (!intersection.isEmpty()) { intersections.push_back(intersection); currentBoundingBoxes.push_back(otherBoundingBoxLoop); @@ -175,36 +178,33 @@ bool PanoramaMap::getIntersectionsList(std::vector & intersections, return true; } -bool PanoramaMap::optimizeChunks(std::vector> & chunks, int chunkSize) { - +bool PanoramaMap::optimizeChunks(std::vector>& chunks, int chunkSize) +{ int countViews = _map.size(); int countChunks = divideRoundUp(countViews, chunkSize); chunks.clear(); chunks.resize(countChunks); - for (auto item : _map) + for (auto item : _map) { - std::sort(chunks.begin(), chunks.end(), - [this](const std::vector & first, const std::vector & second) + std::sort(chunks.begin(), chunks.end(), [this](const std::vector& first, const std::vector& second) { + size_t size_first = 0; + for (int i = 0; i < first.size(); i++) { - size_t size_first = 0; - for (int i = 0; i < first.size(); i++) - { - IndexT curIndex = first[i]; - size_first += _map.at(curIndex).area(); - } - - size_t size_second = 0; - for (int i = 0; i < second.size(); i++) - { - IndexT curIndex = second[i]; - size_second += _map.at(curIndex).area(); - } - - return (size_first < size_second); + IndexT curIndex = first[i]; + size_first += _map.at(curIndex).area(); } - ); + + size_t size_second = 0; + for (int i = 0; i < second.size(); i++) + { + IndexT curIndex = second[i]; + size_second += _map.at(curIndex).area(); + } + + return (size_first < size_second); + }); chunks[0].push_back(item.first); } @@ -212,4 +212,4 @@ bool PanoramaMap::optimizeChunks(std::vector> & chunks, int return true; } -} +} // namespace aliceVision diff --git a/src/aliceVision/panorama/panoramaMap.hpp b/src/aliceVision/panorama/panoramaMap.hpp index 46f058eae4..1b86c96e01 100644 --- a/src/aliceVision/panorama/panoramaMap.hpp +++ b/src/aliceVision/panorama/panoramaMap.hpp @@ -13,50 +13,35 @@ #include #include -namespace aliceVision -{ - +namespace aliceVision { class PanoramaMap { -public: - PanoramaMap(int width, int height, int scale, int borderSize) : - _panoramaWidth(width), - _panoramaHeight(height), - _scale(scale), - _borderSize(borderSize) - { - } + public: + PanoramaMap(int width, int height, int scale, int borderSize) + : _panoramaWidth(width), + _panoramaHeight(height), + _scale(scale), + _borderSize(borderSize) + {} - bool append(IndexT index, const BoundingBox & box); + bool append(IndexT index, const BoundingBox& box); - bool getOverlaps(std::vector & overlaps, IndexT reference) const; + bool getOverlaps(std::vector& overlaps, IndexT reference) const; - bool getOverlaps(std::vector & overlaps, const BoundingBox & referenceBoundingBox) const; + bool getOverlaps(std::vector& overlaps, const BoundingBox& referenceBoundingBox) const; - int getWidth() const - { - return _panoramaWidth; - } + int getWidth() const { return _panoramaWidth; } - int getHeight() const - { - return _panoramaHeight; - } + int getHeight() const { return _panoramaHeight; } - size_t getScale() const - { - return _scale; - } + size_t getScale() const { return _scale; } - size_t getBorderSize() const - { - return _borderSize; - } + size_t getBorderSize() const { return _borderSize; } - bool getBoundingBox(BoundingBox & bb, const IndexT & id) const + bool getBoundingBox(BoundingBox& bb, const IndexT& id) const { - if (_map.find(id) == _map.end()) + if (_map.find(id) == _map.end()) { return false; } @@ -66,16 +51,22 @@ class PanoramaMap return true; } - bool getIntersectionsList(std::vector & intersections, std::vector & currentBoundingBoxes, const IndexT & referenceIndex, const IndexT & otherIndex) const; - - bool getIntersectionsList(std::vector & intersections, std::vector & currentBoundingBoxes, const BoundingBox & referenceBoundingBox, const IndexT & otherIndex) const; + bool getIntersectionsList(std::vector& intersections, + std::vector& currentBoundingBoxes, + const IndexT& referenceIndex, + const IndexT& otherIndex) const; + + bool getIntersectionsList(std::vector& intersections, + std::vector& currentBoundingBoxes, + const BoundingBox& referenceBoundingBox, + const IndexT& otherIndex) const; - bool optimizeChunks(std::vector> & chunks, int chunkSize); + bool optimizeChunks(std::vector>& chunks, int chunkSize); -private: - bool intersect(const BoundingBox & box1, const BoundingBox & box2) const; + private: + bool intersect(const BoundingBox& box1, const BoundingBox& box2) const; -private: + private: std::map _map; int _panoramaWidth; @@ -84,4 +75,4 @@ class PanoramaMap int _borderSize; }; -} \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/remapBbox.cpp b/src/aliceVision/panorama/remapBbox.cpp index 85f0f7dd41..2250daba59 100644 --- a/src/aliceVision/panorama/remapBbox.cpp +++ b/src/aliceVision/panorama/remapBbox.cpp @@ -7,23 +7,21 @@ #include "remapBbox.hpp" #include "sphericalMapping.hpp" -namespace aliceVision -{ +namespace aliceVision { bool isPoleInTriangle(const Vec3& pt1, const Vec3& pt2, const Vec3& pt3) { - - double a = (pt2.x() * pt3.z() - pt3.x() * pt2.z()) / (pt1.x() * pt2.z() - pt1.x() * pt3.z() - pt2.x() * pt1.z() + - pt2.x() * pt3.z() + pt3.x() * pt1.z() - pt3.x() * pt2.z()); - double b = (-pt1.x() * pt3.z() + pt3.x() * pt1.z()) / (pt1.x() * pt2.z() - pt1.x() * pt3.z() - pt2.x() * pt1.z() + - pt2.x() * pt3.z() + pt3.x() * pt1.z() - pt3.x() * pt2.z()); + double a = (pt2.x() * pt3.z() - pt3.x() * pt2.z()) / + (pt1.x() * pt2.z() - pt1.x() * pt3.z() - pt2.x() * pt1.z() + pt2.x() * pt3.z() + pt3.x() * pt1.z() - pt3.x() * pt2.z()); + double b = (-pt1.x() * pt3.z() + pt3.x() * pt1.z()) / + (pt1.x() * pt2.z() - pt1.x() * pt3.z() - pt2.x() * pt1.z() + pt2.x() * pt3.z() + pt3.x() * pt1.z() - pt3.x() * pt2.z()); double c = 1.0 - a - b; - if(a < 0.0 || a > 1.0) + if (a < 0.0 || a > 1.0) return false; - if(b < 0.0 || b > 1.0) + if (b < 0.0 || b > 1.0) return false; - if(c < 0.0 || c > 1.0) + if (c < 0.0 || c > 1.0) return false; return true; @@ -34,7 +32,7 @@ bool crossHorizontalLoop(const Vec3& pt1, const Vec3& pt2) Vec3 direction = pt2 - pt1; /*Vertical line*/ - if(std::abs(direction(0)) < 1e-12) + if (std::abs(direction(0)) < 1e-12) { return false; } @@ -42,9 +40,9 @@ bool crossHorizontalLoop(const Vec3& pt1, const Vec3& pt2) double t = -pt1(0) / direction(0); Vec3 cross = pt1 + direction * t; - if(t >= 0.0 && t <= 1.0) + if (t >= 0.0 && t <= 1.0) { - if(cross(2) < 0.0) + if (cross(2) < 0.0) { return true; } @@ -55,7 +53,6 @@ bool crossHorizontalLoop(const Vec3& pt1, const Vec3& pt2) Vec3 getExtremaY(const Vec3& pt1, const Vec3& pt2) { - Vec3 delta = pt2 - pt1; double dx = delta(0); double dy = delta(1); @@ -64,26 +61,26 @@ Vec3 getExtremaY(const Vec3& pt1, const Vec3& pt2) double sy = pt1(1); double sz = pt1(2); - double ot_y = -(dx * sx * sy - (dy * sx) * (dy * sx) - (dy * sz) * (dy * sz) + dz * sy * sz) / - (dx * dx * sy - dx * dy * sx - dy * dz * sz + dz * dz * sy); + double ot_y = + -(dx * sx * sy - (dy * sx) * (dy * sx) - (dy * sz) * (dy * sz) + dz * sy * sz) / (dx * dx * sy - dx * dy * sx - dy * dz * sz + dz * dz * sy); Vec3 pt_extrema = pt1 + ot_y * delta; return pt_extrema.normalized(); } -bool computeCoarseBB_Equidistant(BoundingBox& coarse_bbox, const std::pair& panoramaSize, - const geometry::Pose3& pose, const aliceVision::camera::IntrinsicBase& intrinsics) +bool computeCoarseBB_Equidistant(BoundingBox& coarse_bbox, + const std::pair& panoramaSize, + const geometry::Pose3& pose, + const aliceVision::camera::IntrinsicBase& intrinsics) { - const aliceVision::camera::Equidistant& cam = dynamic_cast(intrinsics); bool loop = false; std::vector vec_bool(panoramaSize.second, false); - for(int i = 0; i < panoramaSize.second; i++) + for (int i = 0; i < panoramaSize.second; i++) { - { Vec3 ray = SphericalMapping::fromEquirectangular(Vec2(0, i), panoramaSize.first, panoramaSize.second); @@ -92,7 +89,7 @@ bool computeCoarseBB_Equidistant(BoundingBox& coarse_bbox, const std::pair= 0; x--) + for (int x = panoramaSize.first - 1; x >= 0; x--) { - size_t count = 0; - for(int i = 0; i < panoramaSize.second; i++) + for (int i = 0; i < panoramaSize.second; i++) { - - if(vec_bool[i] == false) + if (vec_bool[i] == false) { continue; } @@ -179,7 +173,7 @@ bool computeCoarseBB_Equidistant(BoundingBox& coarse_bbox, const std::pair& panoramaSize, - const geometry::Pose3& pose, const aliceVision::camera::IntrinsicBase& intrinsics) +bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, + const std::pair& panoramaSize, + const geometry::Pose3& pose, + const aliceVision::camera::IntrinsicBase& intrinsics) { - int bbox_left, bbox_top; int bbox_right, bbox_bottom; int bbox_width, bbox_height; @@ -229,32 +224,35 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair /*Estimate distorted maximal distance from optical center*/ Vec2 pts[] = {{0.0f, 0.0f}, {intrinsics.w(), 0.0f}, {intrinsics.w(), intrinsics.h()}, {0.0f, intrinsics.h()}}; float max_radius = 0.0; - for(int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { - Vec2 ptmeter = intrinsics.ima2cam(pts[i]); float radius = ptmeter.norm(); max_radius = std::max(max_radius, radius); } /* Estimate undistorted maximal distance from optical center */ - float max_radius_distorted = max_radius; // intrinsics.getMaximalDistortion(0.0, max_radius); + float max_radius_distorted = max_radius; // intrinsics.getMaximalDistortion(0.0, max_radius); /* Coarse rectangle bouding box in camera space We add intermediate points to ensure arclength between 2 points is never more than 180° */ - Vec2 pts_radius[] = {{-max_radius_distorted, -max_radius_distorted}, {0, -max_radius_distorted}, - {max_radius_distorted, -max_radius_distorted}, {max_radius_distorted, 0}, - {max_radius_distorted, max_radius_distorted}, {0, max_radius_distorted}, - {-max_radius_distorted, max_radius_distorted}, {-max_radius_distorted, 0}}; + Vec2 pts_radius[] = {{-max_radius_distorted, -max_radius_distorted}, + {0, -max_radius_distorted}, + {max_radius_distorted, -max_radius_distorted}, + {max_radius_distorted, 0}, + {max_radius_distorted, max_radius_distorted}, + {0, max_radius_distorted}, + {-max_radius_distorted, max_radius_distorted}, + {-max_radius_distorted, 0}}; /* Transform bounding box into the panorama frame. Point are on a unit sphere. */ Vec3 rotated_pts[8]; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { Vec3 pt3d = intrinsics.toUnitSphere(pts_radius[i]); rotated_pts[i] = pose.rotation().transpose() * pt3d; @@ -264,7 +262,7 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair bbox_top = panoramaSize.second; bbox_bottom = 0; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { int i2 = (i + 1) % 8; @@ -292,10 +290,10 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair pole |= isPoleInTriangle(rotated_pts[1], rotated_pts[3], rotated_pts[5]); pole |= isPoleInTriangle(rotated_pts[1], rotated_pts[5], rotated_pts[7]); - if(pole) + if (pole) { Vec3 normal = (rotated_pts[1] - rotated_pts[0]).cross(rotated_pts[3] - rotated_pts[0]); - if(normal(1) > 0) + if (normal(1) > 0) { // Lower pole bbox_bottom = panoramaSize.second - 1; @@ -311,7 +309,7 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair /*Check if we cross the horizontal loop*/ bool crossH = false; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { int i2 = (i + 1) % 8; @@ -319,21 +317,21 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair crossH |= cross; } - if(pole) + if (pole) { /*Easy : if we cross the pole, the width is full*/ bbox_left = 0; bbox_right = panoramaSize.first - 1; bbox_width = bbox_right - bbox_left + 1; } - else if(crossH) + else if (crossH) { int first_cross = 0; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { int i2 = (i + 1) % 8; bool cross = crossHorizontalLoop(rotated_pts[i], rotated_pts[i2]); - if(cross) + if (cross) { first_cross = i; break; @@ -343,9 +341,8 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair bbox_left = panoramaSize.first - 1; bbox_right = 0; bool is_right = true; - for(int index = 0; index < 8; index++) + for (int index = 0; index < 8; index++) { - int i = (index + first_cross) % 8; int i2 = (i + 1) % 8; @@ -354,9 +351,9 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair /*[----right //// left-----]*/ bool cross = crossHorizontalLoop(rotated_pts[i], rotated_pts[i2]); - if(cross) + if (cross) { - if(res_1(0) > res_2(0)) + if (res_1(0) > res_2(0)) { /*[----res2 //// res1----]*/ bbox_left = std::min(int(res_1(0)), bbox_left); bbox_right = std::max(int(res_2(0)), bbox_right); @@ -371,7 +368,7 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair } else { - if(is_right) + if (is_right) { bbox_right = std::max(int(res_1(0)), bbox_right); bbox_right = std::max(int(res_2(0)), bbox_right); @@ -383,7 +380,7 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair } } } - + bbox_width = bbox_right + (panoramaSize.first - bbox_left); } else @@ -391,7 +388,7 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair /*horizontal default solution : no border crossing, no pole*/ bbox_left = panoramaSize.first; bbox_right = 0; - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { Vec2 res = SphericalMapping::toEquirectangular(rotated_pts[i], panoramaSize.first, panoramaSize.second); bbox_left = std::min(int(floor(res(0))), bbox_left); @@ -409,17 +406,18 @@ bool computeCoarseBB_Pinhole(BoundingBox& coarse_bbox, const std::pair return true; } -bool computeCoarseBB(BoundingBox& coarse_bbox, const std::pair& panoramaSize, const geometry::Pose3& pose, +bool computeCoarseBB(BoundingBox& coarse_bbox, + const std::pair& panoramaSize, + const geometry::Pose3& pose, const aliceVision::camera::IntrinsicBase& intrinsics) { - bool ret = true; - if(isPinhole(intrinsics.getType())) + if (isPinhole(intrinsics.getType())) { ret = computeCoarseBB_Pinhole(coarse_bbox, panoramaSize, pose, intrinsics); } - else if(isEquidistant(intrinsics.getType())) + else if (isEquidistant(intrinsics.getType())) { ret = computeCoarseBB_Equidistant(coarse_bbox, panoramaSize, pose, intrinsics); } @@ -435,4 +433,4 @@ bool computeCoarseBB(BoundingBox& coarse_bbox, const std::pair& panora return ret; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/remapBbox.hpp b/src/aliceVision/panorama/remapBbox.hpp index 5dda92ec6f..b49271c31d 100644 --- a/src/aliceVision/panorama/remapBbox.hpp +++ b/src/aliceVision/panorama/remapBbox.hpp @@ -12,10 +12,11 @@ #include "boundingBox.hpp" -namespace aliceVision -{ +namespace aliceVision { -bool computeCoarseBB(BoundingBox& coarse_bbox, const std::pair& panoramaSize, const geometry::Pose3& pose, +bool computeCoarseBB(BoundingBox& coarse_bbox, + const std::pair& panoramaSize, + const geometry::Pose3& pose, const aliceVision::camera::IntrinsicBase& intrinsics); } \ No newline at end of file diff --git a/src/aliceVision/panorama/seams.cpp b/src/aliceVision/panorama/seams.cpp index 34ebcc3452..e7a1887eb3 100644 --- a/src/aliceVision/panorama/seams.cpp +++ b/src/aliceVision/panorama/seams.cpp @@ -11,20 +11,18 @@ #include "compositer.hpp" #include "feathering.hpp" -namespace aliceVision -{ - +namespace aliceVision { bool computeSeamsMap(image::Image& seams, const image::Image& labels) { - if(seams.size() != labels.size()) + if (seams.size() != labels.size()) { return false; } seams.fill(0); - for(int j = 1; j < labels.Width() - 1; j++) + for (int j = 1; j < labels.Width() - 1; j++) { IndexT label = labels(0, j); IndexT same = true; @@ -35,7 +33,7 @@ bool computeSeamsMap(image::Image& seams, const image::Image& seams, const image::Image& seams, const image::Image& seams, const image::Image& seams, const image::Image& inout, aliceVisio continue; } - if(mask(i, j)) + if (mask(i, j)) { inout(di, dj) = image::RGBAfColor(0.0f, 1.0f, 0.0f, 1.0f); } } - for(int i = 0; i < mask.Height(); i++) + for (int i = 0; i < mask.Height(); i++) { int j = mask.Width() - 1; int di = i + offset_y; @@ -123,13 +119,13 @@ void drawBorders(aliceVision::image::Image& inout, aliceVisio continue; } - if(mask(i, j)) + if (mask(i, j)) { inout(di, dj) = image::RGBAfColor(0.0f, 1.0f, 0.0f, 1.0f); } } - for(int j = 0; j < mask.Width(); j++) + for (int j = 0; j < mask.Width(); j++) { int i = 0; int di = i + offset_y; @@ -139,13 +135,13 @@ void drawBorders(aliceVision::image::Image& inout, aliceVisio continue; } - if(mask(i, j)) + if (mask(i, j)) { inout(di, dj) = image::RGBAfColor(0.0f, 1.0f, 0.0f, 1.0f); } } - for(int j = 0; j < mask.Width(); j++) + for (int j = 0; j < mask.Width(); j++) { int i = mask.Height() - 1; int di = i + offset_y; @@ -155,27 +151,25 @@ void drawBorders(aliceVision::image::Image& inout, aliceVisio continue; } - if(mask(i, j)) + if (mask(i, j)) { inout(di, dj) = image::RGBAfColor(0.0f, 1.0f, 0.0f, 1.0f); } } - for(int i = 1; i < mask.Height() - 1; i++) + for (int i = 1; i < mask.Height() - 1; i++) { - int di = i + offset_y; - for(int j = 1; j < mask.Width() - 1; j++) + for (int j = 1; j < mask.Width() - 1; j++) { - int dj = j + offset_x; if (di < 0 || dj < 0 || di >= inout.Height() || dj >= inout.Width()) { continue; } - if(!mask(i, j)) + if (!mask(i, j)) { continue; } @@ -187,7 +181,7 @@ void drawBorders(aliceVision::image::Image& inout, aliceVisio others &= mask(i, j + 1); others &= mask(i + 1, j - 1); others &= mask(i + 1, j + 1); - if(others) + if (others) { continue; } @@ -197,17 +191,19 @@ void drawBorders(aliceVision::image::Image& inout, aliceVisio } } -void drawSeams(aliceVision::image::Image & inout, aliceVision::image::Image & labels, int offset_x, int offset_y) { - - for (int i = 1; i < labels.Height() - 1; i++) +void drawSeams(aliceVision::image::Image& inout, aliceVision::image::Image& labels, int offset_x, int offset_y) +{ + for (int i = 1; i < labels.Height() - 1; i++) { int di = i + offset_y; - if (di < 0 || di >= inout.Height()) continue; + if (di < 0 || di >= inout.Height()) + continue; - for (int j = 1; j < labels.Width() - 1; j++) + for (int j = 1; j < labels.Width() - 1; j++) { int dj = j + offset_x; - if (dj < 0 || dj >= inout.Width()) continue; + if (dj < 0 || dj >= inout.Width()) + continue; IndexT label = labels(i, j); IndexT same = true; @@ -219,7 +215,8 @@ void drawSeams(aliceVision::image::Image & inout, aliceVision same &= (labels(i + 1, j - 1) == label); same &= (labels(i + 1, j + 1) == label); - if (same) { + if (same) + { continue; } @@ -229,23 +226,27 @@ void drawSeams(aliceVision::image::Image & inout, aliceVision } bool WTASeams::append(const aliceVision::image::Image& inputMask, - const aliceVision::image::Image& inputWeights, - IndexT currentIndex, size_t offset_x, size_t offset_y) + const aliceVision::image::Image& inputWeights, + IndexT currentIndex, + size_t offset_x, + size_t offset_y) { if (inputMask.size() != inputWeights.size()) { return false; - } + } for (size_t i = 0; i < inputWeights.Height(); i++) { int y = i + offset_y; - if (y < 0 || y >= _panoramaHeight) continue; + if (y < 0 || y >= _panoramaHeight) + continue; for (size_t j = 0; j < inputWeights.Width(); j++) { int x = j + offset_x; - if (x < 0 || x >= _panoramaWidth) continue; + if (x < 0 || x >= _panoramaWidth) + continue; if (!inputMask(i, j)) { @@ -264,23 +265,26 @@ bool WTASeams::append(const aliceVision::image::Image& inputMask, } bool WTASeams::appendWithLoop(const aliceVision::image::Image& inputMask, - const aliceVision::image::Image& inputWeights, - IndexT currentIndex, size_t offset_x, size_t offset_y) + const aliceVision::image::Image& inputWeights, + IndexT currentIndex, + size_t offset_x, + size_t offset_y) { if (inputMask.size() != inputWeights.size()) { return false; - } + } for (size_t i = 0; i < inputWeights.Height(); i++) { int y = i + offset_y; - if (y < 0 || y >= _panoramaHeight) continue; + if (y < 0 || y >= _panoramaHeight) + continue; for (size_t j = 0; j < inputWeights.Width(); j++) { int x = j + offset_x; - if (x < 0) + if (x < 0) { x += _panoramaWidth; } @@ -305,7 +309,7 @@ bool WTASeams::appendWithLoop(const aliceVision::image::Image& in return true; } -bool HierarchicalGraphcutSeams::initialize(const image::Image & labels) +bool HierarchicalGraphcutSeams::initialize(const image::Image& labels) { int width = _outputWidth; int height = _outputHeight; @@ -314,12 +318,12 @@ bool HierarchicalGraphcutSeams::initialize(const image::Image & labels) { _graphcuts.emplace_back(width, height); - //Divide by 2 (rounding to the superior integer) + // Divide by 2 (rounding to the superior integer) width = int(ceil(float(width) / 2.0f)); height = int(ceil(float(height) / 2.0f)); } - if (!_graphcuts[0].setOriginalLabels(labels)) + if (!_graphcuts[0].setOriginalLabels(labels)) { return false; } @@ -331,9 +335,8 @@ bool HierarchicalGraphcutSeams::initialize(const image::Image & labels) for (int l = 1; l < _countLevels; l++) { - - image::Image & largerLabels = _graphcuts[l - 1].getLabels(); - image::Image & smallerLabels = _graphcuts[l].getLabels(); + image::Image& largerLabels = _graphcuts[l - 1].getLabels(); + image::Image& smallerLabels = _graphcuts[l].getLabels(); downscale(smallerLabels, largerLabels); } @@ -342,8 +345,10 @@ bool HierarchicalGraphcutSeams::initialize(const image::Image & labels) } bool HierarchicalGraphcutSeams::append(const aliceVision::image::Image& input, - const aliceVision::image::Image& inputMask, - IndexT currentIndex, size_t offsetX, size_t offsetY) + const aliceVision::image::Image& inputMask, + IndexT currentIndex, + size_t offsetX, + size_t offsetY) { // Make sure input is compatible with pyramid processing int newOffsetX, newOffsetY; @@ -354,7 +359,7 @@ bool HierarchicalGraphcutSeams::append(const aliceVision::image::Image feathered; - if (!feathering(feathered, potImage, potMask)) + if (!feathering(feathered, potImage, potMask)) { return false; } @@ -364,18 +369,18 @@ bool HierarchicalGraphcutSeams::append(const aliceVision::image::Image nextMask(newWidth, newHeight); aliceVision::image::Image buf(width, height); - //Convolve + divide + // Convolve + divide convolveGaussian5x5(buf, feathered); downscale(nextImage, buf); - //Just nearest neighboor divide for mask - for(int i = 0; i < nextMask.Height(); i++) + // Just nearest neighboor divide for mask + for (int i = 0; i < nextMask.Height(); i++) { int di = i * 2; - for(int j = 0; j < nextMask.Width(); j++) + for (int j = 0; j < nextMask.Width(); j++) { int dj = j * 2; @@ -422,44 +427,43 @@ bool HierarchicalGraphcutSeams::append(const aliceVision::image::Image= 0; level--) +{ + for (int level = _countLevels - 1; level >= 0; level--) { ALICEVISION_LOG_INFO("Hierachical graphcut processing level #" << level); - image::Image & smallLabels = _graphcuts[level].getLabels(); + image::Image& smallLabels = _graphcuts[level].getLabels(); int w = smallLabels.Width(); int h = smallLabels.Height(); - + if (level == _countLevels - 1) { - _graphcuts[level].setMaximalDistance(sqrt(w*w + h*h)); + _graphcuts[level].setMaximalDistance(sqrt(w * w + h * h)); } - else + else { double sw = double(0.2 * w); double sh = double(0.2 * h); - _graphcuts[level].setMaximalDistance(sqrt(sw*sw + sh*sh)); + _graphcuts[level].setMaximalDistance(sqrt(sw * sw + sh * sh)); } - - if(!_graphcuts[level].process()) + if (!_graphcuts[level].process()) { return false; } - if (level == 0) + if (level == 0) { return true; } - - //Enlarge result of this level to be an initialization for next level - image::Image & largeLabels = _graphcuts[level - 1].getLabels(); - for (int y = 0; y < largeLabels.Height(); y++) + // Enlarge result of this level to be an initialization for next level + image::Image& largeLabels = _graphcuts[level - 1].getLabels(); + + for (int y = 0; y < largeLabels.Height(); y++) { int hy = y / 2; - + for (int x = 0; x < largeLabels.Width(); x++) { int hx = x / 2; @@ -468,26 +472,26 @@ bool HierarchicalGraphcutSeams::process() } } - return true; } -bool getMaskFromLabels(aliceVision::image::Image & mask, image::Image & labels, IndexT index, int offset_x, int offset_y) +bool getMaskFromLabels(aliceVision::image::Image& mask, image::Image& labels, IndexT index, int offset_x, int offset_y) { - - for (int i = 0; i < mask.Height(); i++) + for (int i = 0; i < mask.Height(); i++) { int y = i + offset_y; - for (int j = 0; j < mask.Width(); j++) + for (int j = 0; j < mask.Width(); j++) { int x = j + offset_x; mask(i, j) = 0; - if (y < 0 || y >= labels.Height()) continue; - if (x < 0 || x >= labels.Width()) continue; + if (y < 0 || y >= labels.Height()) + continue; + if (x < 0 || x >= labels.Width()) + continue; - if (labels(y, x) == index) + if (labels(y, x) == index) { mask(i, j) = 1.0f; } @@ -497,4 +501,4 @@ bool getMaskFromLabels(aliceVision::image::Image & mask, image::Image & inout, aliceVision::image::Image & mask, int offset_x, int offset_y); +void drawBorders(aliceVision::image::Image& inout, aliceVision::image::Image& mask, int offset_x, int offset_y); -void drawSeams(aliceVision::image::Image & inout, aliceVision::image::Image & labels, int offset_x, int offset_y); +void drawSeams(aliceVision::image::Image& inout, aliceVision::image::Image& labels, int offset_x, int offset_y); -bool getMaskFromLabels(aliceVision::image::Image & mask, image::Image & labels, IndexT index, int offset_x, int offset_y); +bool getMaskFromLabels(aliceVision::image::Image& mask, image::Image& labels, IndexT index, int offset_x, int offset_y); class WTASeams { -public: + public: WTASeams(size_t outputWidth, size_t outputHeight) - : _weights(outputWidth, outputHeight, true, 0.0f) - , _labels(outputWidth, outputHeight, true, UndefinedIndexT) - , _panoramaWidth(outputWidth) - , _panoramaHeight(outputHeight) - { - } + : _weights(outputWidth, outputHeight, true, 0.0f), + _labels(outputWidth, outputHeight, true, UndefinedIndexT), + _panoramaWidth(outputWidth), + _panoramaHeight(outputHeight) + {} virtual ~WTASeams() = default; - + bool append(const aliceVision::image::Image& inputMask, - const aliceVision::image::Image& inputWeights, - IndexT currentIndex, size_t offset_x, size_t offset_y); + const aliceVision::image::Image& inputWeights, + IndexT currentIndex, + size_t offset_x, + size_t offset_y); bool appendWithLoop(const aliceVision::image::Image& inputMask, - const aliceVision::image::Image& inputWeights, - IndexT currentIndex, size_t offset_x, size_t offset_y); + const aliceVision::image::Image& inputWeights, + IndexT currentIndex, + size_t offset_x, + size_t offset_y); - image::Image & getLabels() - { - return _labels; - } + image::Image& getLabels() { return _labels; } -private: + private: image::Image _weights; image::Image _labels; @@ -57,30 +56,28 @@ class WTASeams class HierarchicalGraphcutSeams { -public: + public: HierarchicalGraphcutSeams(size_t outputWidth, size_t outputHeight, size_t countLevels) - : _outputWidth(outputWidth) - , _outputHeight(outputHeight) - , _countLevels(countLevels) - { - } + : _outputWidth(outputWidth), + _outputHeight(outputHeight), + _countLevels(countLevels) + {} virtual ~HierarchicalGraphcutSeams() = default; bool initialize(const image::Image& labels); virtual bool append(const aliceVision::image::Image& input, - const aliceVision::image::Image& inputMask, - IndexT currentIndex, size_t offset_x, size_t offset_y); + const aliceVision::image::Image& inputMask, + IndexT currentIndex, + size_t offset_x, + size_t offset_y); bool process(); - image::Image& getLabels() - { - return _graphcuts[0].getLabels(); - } + image::Image& getLabels() { return _graphcuts[0].getLabels(); } -private: + private: std::vector _graphcuts; size_t _countLevels; @@ -88,4 +85,4 @@ class HierarchicalGraphcutSeams size_t _outputHeight; }; -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/sphericalMapping.cpp b/src/aliceVision/panorama/sphericalMapping.cpp index c11f817809..b60909054a 100644 --- a/src/aliceVision/panorama/sphericalMapping.cpp +++ b/src/aliceVision/panorama/sphericalMapping.cpp @@ -6,11 +6,9 @@ #include "sphericalMapping.hpp" -namespace aliceVision -{ +namespace aliceVision { -namespace SphericalMapping -{ +namespace SphericalMapping { /** * Map from equirectangular to spherical coordinates * @param equirectangular equirectangular coordinates @@ -39,7 +37,6 @@ Vec3 fromEquirectangular(const Vec2& equirectangular, int width, int height) */ Vec2 toEquirectangular(const Vec3& spherical, int width, int height) { - double vertical_angle = asin(spherical(1)); double horizontal_angle = atan2(spherical(0), spherical(2)); @@ -49,5 +46,5 @@ Vec2 toEquirectangular(const Vec3& spherical, int width, int height) return Vec2(longitude, latitude); } -} // namespace SphericalMapping -} // namespace aliceVision \ No newline at end of file +} // namespace SphericalMapping +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/sphericalMapping.hpp b/src/aliceVision/panorama/sphericalMapping.hpp index 39502fd9fa..4e3b93b097 100644 --- a/src/aliceVision/panorama/sphericalMapping.hpp +++ b/src/aliceVision/panorama/sphericalMapping.hpp @@ -8,11 +8,9 @@ #include -namespace aliceVision -{ +namespace aliceVision { -namespace SphericalMapping -{ +namespace SphericalMapping { /** * Map from equirectangular to spherical coordinates * @param equirectangular equirectangular coordinates @@ -31,5 +29,5 @@ Vec3 fromEquirectangular(const Vec2& equirectangular, int width, int height); */ Vec2 toEquirectangular(const Vec3& spherical, int width, int height); -} // namespace SphericalMapping -} // namespace aliceVision \ No newline at end of file +} // namespace SphericalMapping +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/panorama/warper.cpp b/src/aliceVision/panorama/warper.cpp index 21ecd5705f..4e7761c4c4 100644 --- a/src/aliceVision/panorama/warper.cpp +++ b/src/aliceVision/panorama/warper.cpp @@ -11,7 +11,6 @@ namespace aliceVision { bool Warper::warp(const CoordinatesMap& map, const aliceVision::image::Image& source) { - /** * Copy additional info from map */ @@ -31,13 +30,12 @@ bool Warper::warp(const CoordinatesMap& map, const aliceVision::image::Image(coordinates.Width(), coordinates.Height(), true, - image::RGBfColor(1.0, 0.0, 0.0)); + _color = aliceVision::image::Image(coordinates.Width(), coordinates.Height(), true, image::RGBfColor(1.0, 0.0, 0.0)); /** * Multi level warp */ - for(size_t i = 0; i < _color.Height(); i++) + for (size_t i = 0; i < _color.Height(); i++) { int next_i = i + 1; @@ -89,25 +85,21 @@ bool GaussianWarper::warp(const CoordinatesMap& map, const GaussianPyramidNoMask next_i = i - 1; } - - for(size_t j = 0; j < _color.Width(); j++) + for (size_t j = 0; j < _color.Width(); j++) { - bool valid = _mask(i, j); - if(!valid) + if (!valid) { continue; } int next_j = j + 1; - if (j == _color.Width() - 1) { next_j = j - 1; } - if (!_mask(next_i, j) || !_mask(i, next_j)) { const Eigen::Vector2f& coord = coordinates(i, j); @@ -126,9 +118,9 @@ bool GaussianWarper::warp(const CoordinatesMap& map, const GaussianPyramidNoMask float dyx = coord_pm(1) - coord_mm(1); float dyy = coord_mp(1) - coord_mm(1); float det = std::abs(dxx * dyy - dxy * dyx); - float scale = /*sqrtf*/(det); //logsqrt = 0.5log + float scale = /*sqrtf*/ (det); // logsqrt = 0.5log - float flevel = std::max(0.0f,0.5f * log2f(scale)); + float flevel = std::max(0.0f, 0.5f * log2f(scale)); int blevel = std::min(max_level, int(floor(flevel))); float dscale, x, y; @@ -137,19 +129,22 @@ bool GaussianWarper::warp(const CoordinatesMap& map, const GaussianPyramidNoMask y = coord_mm(1) * dscale; /*Fallback to first level if outside*/ - if(x >= mlsource[blevel].Width() - 1 || y >= mlsource[blevel].Height() - 1) + if (x >= mlsource[blevel].Width() - 1 || y >= mlsource[blevel].Height() - 1) { _color(i, j) = sampler(mlsource[0], coord_mm(1), coord_mm(0)); continue; } _color(i, j) = sampler(mlsource[blevel], y, x); - + if (clamp) { - if (_color(i, j).r() > HALF_MAX) _color(i, j).r() = HALF_MAX; - if (_color(i, j).g() > HALF_MAX) _color(i, j).g() = HALF_MAX; - if (_color(i, j).b() > HALF_MAX) _color(i, j).b() = HALF_MAX; + if (_color(i, j).r() > HALF_MAX) + _color(i, j).r() = HALF_MAX; + if (_color(i, j).g() > HALF_MAX) + _color(i, j).g() = HALF_MAX; + if (_color(i, j).b() > HALF_MAX) + _color(i, j).b() = HALF_MAX; } } } @@ -157,4 +152,4 @@ bool GaussianWarper::warp(const CoordinatesMap& map, const GaussianPyramidNoMask return true; } -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/panorama/warper.hpp b/src/aliceVision/panorama/warper.hpp index 0fda709ba6..15b07ee1e3 100644 --- a/src/aliceVision/panorama/warper.hpp +++ b/src/aliceVision/panorama/warper.hpp @@ -9,13 +9,11 @@ #include "coordinatesMap.hpp" #include "gaussian.hpp" - -namespace aliceVision -{ +namespace aliceVision { class Warper { -public: + public: virtual bool warp(const CoordinatesMap& map, const aliceVision::image::Image& source); const aliceVision::image::Image& getColor() const { return _color; } @@ -26,7 +24,7 @@ class Warper size_t getOffsetY() const { return _offset_y; } -protected: + protected: size_t _offset_x = 0; size_t _offset_y = 0; @@ -36,8 +34,8 @@ class Warper class GaussianWarper : public Warper { -public: + public: virtual bool warp(const CoordinatesMap& map, const GaussianPyramidNoMask& pyramid, bool clamp); }; -} // namespace aliceVision \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/photometricStereo/normalIntegration.cpp b/src/aliceVision/photometricStereo/normalIntegration.cpp index f57c5b88a5..f521d577f4 100644 --- a/src/aliceVision/photometricStereo/normalIntegration.cpp +++ b/src/aliceVision/photometricStereo/normalIntegration.cpp @@ -32,8 +32,7 @@ namespace aliceVision { namespace photometricStereo { -void normalIntegration(const std::string& inputPath, const bool& perspective, const int& downscale, - const std::string& outputFolder) +void normalIntegration(const std::string& inputPath, const bool& perspective, const int& downscale, const std::string& outputFolder) { std::string normalMapPath = inputPath + "/normals.png"; std::string pathToK = inputPath + "/K.txt"; @@ -41,7 +40,7 @@ void normalIntegration(const std::string& inputPath, const bool& perspective, co image::Image normalsImPNG; image::readImage(normalMapPath, normalsImPNG, image::EImageColorSpace::NO_CONVERSION); - Eigen::MatrixXf K = Eigen::MatrixXf::Zero(3,3); + Eigen::MatrixXf K = Eigen::MatrixXf::Zero(3, 3); readMatrix(pathToK, K); image::Image normalsMask; @@ -75,11 +74,17 @@ void normalIntegration(const std::string& inputPath, const bool& perspective, co std::string pathToDM = outputFolder + "/output.exr"; - image::writeImage(pathToDM, distanceMap, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); + image::writeImage( + pathToDM, + distanceMap, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); } -void normalIntegration(const sfmData::SfMData& sfmData, const std::string& inputPath, const bool& perspective, - const int& downscale, const std::string& outputFolder) +void normalIntegration(const sfmData::SfMData& sfmData, + const std::string& inputPath, + const bool& perspective, + const int& downscale, + const std::string& outputFolder) { image::Image normalsImPNG; @@ -90,7 +95,7 @@ void normalIntegration(const sfmData::SfMData& sfmData, const std::string& input if (sfmData.getPoses().size() > 0) { - for (auto& poseIt: sfmData.getPoses()) + for (auto& poseIt : sfmData.getPoses()) { // Read associated normal map : image::readImage(inputPath + "/" + std::to_string(poseIt.first) + "_normals.png", normalsImPNG, image::EImageColorSpace::NO_CONVERSION); @@ -99,26 +104,26 @@ void normalIntegration(const sfmData::SfMData& sfmData, const std::string& input int nbRows = normalsImPNG.rows(); // Find one view associated with the pose - for (auto& viewIt: sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { poseId = viewIt.second->getPoseId(); if (poseId == poseIt.first) { - viewId = viewIt.first; - // Get intrinsics associated with this view : - intrinsicId = viewIt.second->getIntrinsicId(); - const float focalPx = sfmData.getIntrinsics().at(intrinsicId)->getParams().at(0); - const float x_p = (nbCols) / 2 + sfmData.getIntrinsics().at(intrinsicId)->getParams().at(2); - const float y_p = (nbRows) / 2 + sfmData.getIntrinsics().at(intrinsicId)->getParams().at(3); - - // Create K matrix - K(0, 0) = focalPx; - K(1, 1) = focalPx; - K(0, 2) = x_p; - K(1, 2) = y_p; - K(2, 2) = 1; - - break; + viewId = viewIt.first; + // Get intrinsics associated with this view : + intrinsicId = viewIt.second->getIntrinsicId(); + const float focalPx = sfmData.getIntrinsics().at(intrinsicId)->getParams().at(0); + const float x_p = (nbCols) / 2 + sfmData.getIntrinsics().at(intrinsicId)->getParams().at(2); + const float y_p = (nbRows) / 2 + sfmData.getIntrinsics().at(intrinsicId)->getParams().at(3); + + // Create K matrix + K(0, 0) = focalPx; + K(1, 1) = focalPx; + K(0, 2) = x_p; + K(1, 2) = y_p; + K(2, 2) = 1; + + break; } } @@ -135,13 +140,13 @@ void normalIntegration(const sfmData::SfMData& sfmData, const std::string& input normalsMask(i, j) = 1.0; for (int ch = 0; ch < 3; ++ch) { - if (ch ==0) + if (ch == 0) { normalsImPNG2(i, j)(ch) = normalsImPNG(i, j)(ch) / 127.5 - 1; } else { - normalsImPNG2(i, j)(ch) = - (normalsImPNG(i, j)(ch) / 127.5 - 1); + normalsImPNG2(i, j)(ch) = -(normalsImPNG(i, j)(ch) / 127.5 - 1); } } } @@ -188,9 +193,14 @@ void normalIntegration(const sfmData::SfMData& sfmData, const std::string& input Mat34 P = camPinHole->getProjectiveEquivalent(pose); oiio::ParamValueList metadata; - metadata.attribute("AliceypeDesc::DOUBLE, oiio::TypeDesVision:storageDataType", image::EStorageDataType_enumToString(image::EStorageDataType::Float)); + metadata.attribute("AliceypeDesc::DOUBLE, oiio::TypeDesVision:storageDataType", + image::EStorageDataType_enumToString(image::EStorageDataType::Float)); metadata.push_back(oiio::ParamValue("AliceVision:P", oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::MATRIX44), 1, P.data())); - image::writeImage(pathToDM, distanceMap, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float), metadata); + image::writeImage( + pathToDM, + distanceMap, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float), + metadata); } } else @@ -289,19 +299,25 @@ void normalIntegration(const sfmData::SfMData& sfmData, const std::string& input Point3d C = Point3d(0.0,0.0,0.0); oiio::ParamValueList metadata; - metadata.push_back(oiio::ParamValue("AliceVision:CArr", oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::VEC3), 1, C.m)); //C.data())); - metadata.push_back(oiio::ParamValue("AliceVision:iCamArr", oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::MATRIX33), 1, iP.m)); // iP.data())); + metadata.push_back(oiio::ParamValue("AliceVision:CArr", oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::VEC3), 1, C.m)); //C.data())); + metadata.push_back(oiio::ParamValue("AliceVision:iCamArr", oiio::TypeDesc(oiio::TypeDesc::DOUBLE, oiio::TypeDesc::MATRIX33), 1, iP.m)); // + iP.data())); */ std::string pathToDM = outputFolder + "/" + std::to_string(poseId) + "_depthMap.exr"; - image::writeImage(pathToDM, distanceMap, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); + image::writeImage( + pathToDM, + distanceMap, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); } } -void DCTIntegration(const image::Image& normals, image::Image& depth, bool perspective, - const Eigen::Matrix3f& K, const image::Image& normalsMask) +void DCTIntegration(const image::Image& normals, + image::Image& depth, + bool perspective, + const Eigen::Matrix3f& K, + const image::Image& normalsMask) { - int nbCols = normals.cols(); int nbRows = normals.rows(); @@ -330,11 +346,11 @@ void DCTIntegration(const image::Image& normals, image::Image< { for (int i = 0; i < nbRows; i++) { - double denom = 4 * (pow(sin(0.5 * M_PI * j/nbCols), 2) + pow(sin(0.5 * M_PI * i/nbRows), 2)); + double denom = 4 * (pow(sin(0.5 * M_PI * j / nbCols), 2) + pow(sin(0.5 * M_PI * i / nbRows), 2)); denom = std::max(denom, 0.0001); z_bar_bar.at(i, j) = fcos.at(i, j) / denom; } - } + } // Inverse cosine transform cv::Mat z(nbRows, nbCols, CV_32FC1); @@ -349,10 +365,12 @@ void DCTIntegration(const image::Image& normals, image::Image< if (perspective) { depth(i, j) = -std::exp(z.at(i, j)); - } else { + } + else + { depth(i, j) = z.at(i, j); } - } + } else { depth(i, j) = nanf("1"); @@ -378,8 +396,12 @@ void DCTIntegration(const image::Image& normals, image::Image< } } -void normal2PQ(const image::Image& normals, Eigen::MatrixXf& p, Eigen::MatrixXf& q, bool perspective, - const Eigen::Matrix3f& K, const image::Image& normalsMask) +void normal2PQ(const image::Image& normals, + Eigen::MatrixXf& p, + Eigen::MatrixXf& q, + bool perspective, + const Eigen::Matrix3f& K, + const image::Image& normalsMask) { image::Image normalsX(p.cols(), p.rows()); image::Image normalsY(p.cols(), p.rows()); @@ -420,7 +442,7 @@ void normal2PQ(const image::Image& normals, Eigen::MatrixXf& p p(i, j) = -normalsX(i, j) / denom; q(i, j) = -normalsY(i, j) / denom; } - } + } } } else @@ -467,7 +489,7 @@ void getDivergenceField(const Eigen::MatrixXf& p, const Eigen::MatrixXf& q, Eige Eigen::MatrixXf px = 0.5 * (px_right - px_left); - // Div(p,q) + // Div(p,q) f = px + qy; } @@ -493,7 +515,7 @@ void setBoundaryConditions(const Eigen::MatrixXf& p, const Eigen::MatrixXf& q, E f.block(nbRows - 1, 1, 1, nbCols - 2) = f.block(nbRows - 1, 1, 1, nbCols - 2) - b.block(nbRows - 1, 1, 1, nbCols - 2); f.block(1, 0, nbRows - 2, 1) = f.block(1, 0, nbRows - 2, 1) - b.block(1, 0, nbRows - 2, 1); f.block(1, nbCols - 1, nbRows - 2, 1) = f.block(1, nbCols - 1, nbRows - 2, 1) - b.block(1, nbCols - 1, nbRows - 2, 1); - + // Modification near the corners f(0, 0) = f(0, 0) - sqrt(2) * b(0, 0); f(0, nbCols - 1) = f(0, nbCols - 1) - sqrt(2) * b(0, nbCols - 1); @@ -529,15 +551,17 @@ void adjustScale(const sfmData::SfMData& sfmData, image::Image& initDepth estimatedDepths(i) = initDepth(rowInd, colInd); } - float num = estimatedDepths.transpose()*knownDepths; - float denom = estimatedDepths.transpose()*estimatedDepths; - float scale = num/denom; + float num = estimatedDepths.transpose() * knownDepths; + float denom = estimatedDepths.transpose() * estimatedDepths; + float scale = num / denom; initDepth *= scale; } - -void getZ0FromLandmarks(const sfmData::SfMData& sfmData, image::Image& z0, image::Image& maskZ0, - const size_t viewID, const image::Image& mask) +void getZ0FromLandmarks(const sfmData::SfMData& sfmData, + image::Image& z0, + image::Image& maskZ0, + const size_t viewID, + const image::Image& mask) { const sfmData::Landmarks& landmarks = sfmData.getLandmarks(); const sfmData::LandmarksPerView landmarksPerView = sfmData::getLandmarksPerViews(sfmData); @@ -565,15 +589,18 @@ void getZ0FromLandmarks(const sfmData::SfMData& sfmData, image::Image& z0 } } -void smoothIntegration(const image::Image& normals, image::Image& depth, bool perspective, - const Eigen::Matrix3f& K, const image::Image& mask, const image::Image& z0, +void smoothIntegration(const image::Image& normals, + image::Image& depth, + bool perspective, + const Eigen::Matrix3f& K, + const image::Image& mask, + const image::Image& z0, const image::Image& maskZ0) { std::cout << "WIP" << std::endl; } -void convertZtoDistance(const aliceVision::image::Image& zMap, aliceVision::image::Image& distanceMap, - const Eigen::Matrix3f& K) +void convertZtoDistance(const aliceVision::image::Image& zMap, aliceVision::image::Image& distanceMap, const Eigen::Matrix3f& K) { int nbRows = zMap.rows(); int nbCols = zMap.cols(); @@ -592,8 +619,7 @@ void convertZtoDistance(const aliceVision::image::Image& zMap, aliceVisio } } -void convertDistanceToZ(const aliceVision::image::Image& distanceMap, aliceVision::image::Image& zMap, - const Eigen::Matrix3f& K) +void convertDistanceToZ(const aliceVision::image::Image& distanceMap, aliceVision::image::Image& zMap, const Eigen::Matrix3f& K) { int nbRows = zMap.rows(); int nbCols = zMap.cols(); @@ -612,7 +638,6 @@ void convertDistanceToZ(const aliceVision::image::Image& distanceMap, ali } } - void loadNormalMap(aliceVision::image::Image inputNormals, const aliceVision::image::Image& normalsMask, aliceVision::image::Image& outputNormals) @@ -642,5 +667,5 @@ void loadNormalMap(aliceVision::image::Image input } } -} -} +} // namespace photometricStereo +} // namespace aliceVision diff --git a/src/aliceVision/photometricStereo/normalIntegration.hpp b/src/aliceVision/photometricStereo/normalIntegration.hpp index 0785543f6f..40781f7d79 100644 --- a/src/aliceVision/photometricStereo/normalIntegration.hpp +++ b/src/aliceVision/photometricStereo/normalIntegration.hpp @@ -17,17 +17,26 @@ namespace aliceVision { namespace photometricStereo { -void normalIntegration(const std::string& inputPath, const bool& perspective, const int& downscale, - const std::string& outputFolder); +void normalIntegration(const std::string& inputPath, const bool& perspective, const int& downscale, const std::string& outputFolder); -void normalIntegration(const sfmData::SfMData& sfmData, const std::string& inputPath, const bool& perspective, - const int& downscale, const std::string& outputFolder); +void normalIntegration(const sfmData::SfMData& sfmData, + const std::string& inputPath, + const bool& perspective, + const int& downscale, + const std::string& outputFolder); -void DCTIntegration(const image::Image& normals, image::Image& depth, bool perspective, - const Eigen::Matrix3f& K, const image::Image& normalsMask); +void DCTIntegration(const image::Image& normals, + image::Image& depth, + bool perspective, + const Eigen::Matrix3f& K, + const image::Image& normalsMask); -void normal2PQ(const image::Image& normals, Eigen::MatrixXf& p, Eigen::MatrixXf& q, bool perspective, - const Eigen::Matrix3f& K, const image::Image& normalsMask); +void normal2PQ(const image::Image& normals, + Eigen::MatrixXf& p, + Eigen::MatrixXf& q, + bool perspective, + const Eigen::Matrix3f& K, + const image::Image& normalsMask); void getDivergenceField(const Eigen::MatrixXf& p, const Eigen::MatrixXf& q, Eigen::MatrixXf& f); @@ -35,22 +44,25 @@ void setBoundaryConditions(const Eigen::MatrixXf& p, const Eigen::MatrixXf& q, E void adjustScale(const sfmData::SfMData& sfmData, image::Image& initDepth, size_t viewID); -void getZ0FromLandmarks(const sfmData::SfMData& sfmData, image::Image& z0, image::Image& maskZ0, - const size_t viewID, const image::Image& mask); - -void smoothIntegration(const image::Image& normals, image::Image& depth, bool perspective, - const Eigen::Matrix3f& K, const image::Image& mask, const image::Image& z0, +void getZ0FromLandmarks(const sfmData::SfMData& sfmData, + image::Image& z0, + image::Image& maskZ0, + const size_t viewID, + const image::Image& mask); + +void smoothIntegration(const image::Image& normals, + image::Image& depth, + bool perspective, + const Eigen::Matrix3f& K, + const image::Image& mask, + const image::Image& z0, const image::Image& maskZ0); -void convertZtoDistance(const image::Image& zMap, image::Image& distanceMap, - const Eigen::Matrix3f& K); - -void convertDistanceToZ(const image::Image& distanceMap, image::Image& zMap, - const Eigen::Matrix3f& K); +void convertZtoDistance(const image::Image& zMap, image::Image& distanceMap, const Eigen::Matrix3f& K); -void loadNormalMap(image::Image inputNormals, const image::Image& normalsMask, - image::Image& outputNormals); +void convertDistanceToZ(const image::Image& distanceMap, image::Image& zMap, const Eigen::Matrix3f& K); -} -} +void loadNormalMap(image::Image inputNormals, const image::Image& normalsMask, image::Image& outputNormals); +} // namespace photometricStereo +} // namespace aliceVision diff --git a/src/aliceVision/photometricStereo/photometricDataIO.cpp b/src/aliceVision/photometricStereo/photometricDataIO.cpp index 7817ea5c7a..b56b1d1a32 100644 --- a/src/aliceVision/photometricStereo/photometricDataIO.cpp +++ b/src/aliceVision/photometricStereo/photometricDataIO.cpp @@ -42,7 +42,7 @@ void loadLightIntensities(const std::string& intFileName, std::vector& imageList, - Eigen::MatrixXf& lightMat, std::vector>& intList) +void buildLightMatFromJSON(const std::string& fileName, + const std::vector& imageList, + Eigen::MatrixXf& lightMat, + std::vector>& intList) { // Main tree bpt::ptree fileTree; @@ -147,37 +148,39 @@ void buildLightMatFromJSON(const std::string& fileName, const std::vector currentIntensities; - for (auto& intensities: lightsName.second.get_child("intensity")) - { - currentIntensities[cpt] = intensities.second.get_value(); - ++cpt; - } - intList.push_back(currentIntensities); - - cpt = 0; - - for (auto& direction: lightsName.second.get_child("direction")) - { - lightMat(lineNumber, cpt) = direction.second.get_value(); - ++cpt; - } - ++lineNumber; - } + fs::path imagePathFS = fs::path(currentImPath); + if (boost::algorithm::icontains(imagePathFS.stem().string(), lightsName.first)) + { + std::array currentIntensities; + for (auto& intensities : lightsName.second.get_child("intensity")) + { + currentIntensities[cpt] = intensities.second.get_value(); + ++cpt; + } + intList.push_back(currentIntensities); + + cpt = 0; + + for (auto& direction : lightsName.second.get_child("direction")) + { + lightMat(lineNumber, cpt) = direction.second.get_value(); + ++cpt; + } + ++lineNumber; + } } } } -void buildLightMatFromJSON(const std::string& fileName, const std::vector& indices, - Eigen::MatrixXf& lightMat, std::vector>& intList) +void buildLightMatFromJSON(const std::string& fileName, + const std::vector& indices, + Eigen::MatrixXf& lightMat, + std::vector>& intList) { // Main tree bpt::ptree fileTree; @@ -186,16 +189,16 @@ void buildLightMatFromJSON(const std::string& fileName, const std::vector("lightId", UndefinedIndexT); if (lightIndex != UndefinedIndexT) { std::array currentIntensities; - for (auto& intensities: light.second.get_child("intensity")) + for (auto& intensities : light.second.get_child("intensity")) { currentIntensities[cpt] = intensities.second.get_value(); ++cpt; @@ -204,11 +207,11 @@ void buildLightMatFromJSON(const std::string& fileName, const std::vector(); - ++cpt; - } + for (auto& direction : light.second.get_child("direction")) + { + lightMat(lightIndex, cpt) = direction.second.get_value(); + ++cpt; + } } else { @@ -216,7 +219,7 @@ void buildLightMatFromJSON(const std::string& fileName, const std::vector currentIntensities; - for (auto& intensities: light.second.get_child("intensity")) + for (auto& intensities : light.second.get_child("intensity")) { currentIntensities[cpt] = intensities.second.get_value(); ++cpt; @@ -225,12 +228,12 @@ void buildLightMatFromJSON(const std::string& fileName, const std::vector(); - ++cpt; - } - ++lineNumber; + for (auto& direction : light.second.get_child("direction")) + { + lightMat(lineNumber, cpt) = direction.second.get_value(); + ++cpt; + } + ++lineNumber; } } } @@ -257,7 +260,6 @@ void loadMask(std::string const& maskName, image::Image& mask) { image::readImage(maskName, mask, image::EImageColorSpace::SRGB); } - } void getIndMask(image::Image const& mask, std::vector& indices) @@ -287,7 +289,7 @@ void intensityScaling(std::array const& intensities, image::Image const& intensities, image::Image& imageIn, const std::vector& indices, - Eigen::MatrixXf& imageOut) +void image2PsMatrix(const image::Image& imageIn, const std::vector& indices, Eigen::MatrixXf& imageOut) { int nbRows = imageIn.rows(); int nbCols = imageIn.cols(); @@ -307,15 +308,14 @@ void image2PsMatrix(const image::Image& imageIn, const std::ve int j = floor(currentIdx / nbRows); int i = currentIdx - j * nbRows; - for(int ch = 0; ch < 3; ++ch) + for (int ch = 0; ch < 3; ++ch) { imageOut(ch, iterator) = imageIn(i, j)(ch); } } } -void image2PsMatrix(const image::Image& imageIn, const image::Image& mask, - Eigen::MatrixXf& imageOut) +void image2PsMatrix(const image::Image& imageIn, const image::Image& mask, Eigen::MatrixXf& imageOut) { int nbRows = imageIn.rows(); int nbCols = imageIn.cols(); @@ -329,7 +329,7 @@ void image2PsMatrix(const image::Image& imageIn, const image:: { if ((!hasMask) || mask(i, j) > 0.7) { - for(int ch = 0; ch < 3; ++ch) + for (int ch = 0; ch < 3; ++ch) { imageOut(ch, index) = imageIn(i, j)(ch); } @@ -339,8 +339,7 @@ void image2PsMatrix(const image::Image& imageIn, const image:: } } -void image2PsMatrix(const image::Image& imageIn, const image::Image& mask, - Eigen::VectorXf& imageOut) +void image2PsMatrix(const image::Image& imageIn, const image::Image& mask, Eigen::VectorXf& imageOut) { int nbRows = imageIn.rows(); int nbCols = imageIn.cols(); @@ -379,8 +378,7 @@ void reshapeInImage(const Eigen::MatrixXf& matrixIn, image::Image& normalsIm, - image::Image& normalsImPNG) +void convertNormalMap2png(const image::Image& normalsIm, image::Image& normalsImPNG) { int nbRows = normalsIm.rows(); int nbCols = normalsIm.cols(); @@ -389,8 +387,7 @@ void convertNormalMap2png(const image::Image& normalsIm, { for (int i = 0; i < nbRows; ++i) { - if (normalsIm(i, j)(0) * normalsIm(i, j)(0) + normalsIm(i, j)(1) * normalsIm(i, j)(1) + - normalsIm(i, j)(2) * normalsIm(i, j)(2) == 0) + if (normalsIm(i, j)(0) * normalsIm(i, j)(0) + normalsIm(i, j)(1) * normalsIm(i, j)(1) + normalsIm(i, j)(2) * normalsIm(i, j)(2) == 0) { normalsImPNG(i, j)(0) = 0; normalsImPNG(i, j)(1) = 0; @@ -429,31 +426,32 @@ void readMatrix(const std::string& fileName, Eigen::MatrixXf& matrix) matFile.close(); } -void writePSResults(const std::string& outputPath, const image::Image& normals, - const image::Image& albedo) +void writePSResults(const std::string& outputPath, const image::Image& normals, const image::Image& albedo) { - image::writeImage(outputPath + "/normals.exr", normals, - image::ImageWriteOptions() - .toColorSpace(image::EImageColorSpace::NO_CONVERSION) - .storageDataType(image::EStorageDataType::Float)); - image::writeImage(outputPath + "/albedo.exr", albedo, - image::ImageWriteOptions() - .toColorSpace(image::EImageColorSpace::NO_CONVERSION) - .storageDataType(image::EStorageDataType::Float)); + image::writeImage( + outputPath + "/normals.exr", + normals, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); + image::writeImage( + outputPath + "/albedo.exr", + albedo, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); } -void writePSResults(const std::string& outputPath, const image::Image& normals, - const image::Image& albedo, const IndexT poseId) +void writePSResults(const std::string& outputPath, + const image::Image& normals, + const image::Image& albedo, + const IndexT poseId) { - image::writeImage(outputPath + "/" + std::to_string(poseId) + "_normals.exr", normals, - image::ImageWriteOptions() - .toColorSpace(image::EImageColorSpace::NO_CONVERSION) - .storageDataType(image::EStorageDataType::Float)); - image::writeImage(outputPath + "/" + std::to_string(poseId) + "_albedo.exr", albedo, - image::ImageWriteOptions() - .toColorSpace(image::EImageColorSpace::NO_CONVERSION) - .storageDataType(image::EStorageDataType::Float)); + image::writeImage( + outputPath + "/" + std::to_string(poseId) + "_normals.exr", + normals, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); + image::writeImage( + outputPath + "/" + std::to_string(poseId) + "_albedo.exr", + albedo, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); } -} -} +} // namespace photometricStereo +} // namespace aliceVision diff --git a/src/aliceVision/photometricStereo/photometricDataIO.hpp b/src/aliceVision/photometricStereo/photometricDataIO.hpp index fb0635e80d..e1e71bc4fc 100644 --- a/src/aliceVision/photometricStereo/photometricDataIO.hpp +++ b/src/aliceVision/photometricStereo/photometricDataIO.hpp @@ -33,8 +33,7 @@ void loadLightIntensities(const std::string& intFileName, std::vector& imageList, - Eigen::MatrixXf& lightMat, std::vector>& intList); +void buildLightMatFromJSON(const std::string& fileName, + const std::vector& imageList, + Eigen::MatrixXf& lightMat, + std::vector>& intList); /** * @brief Load light data from a JSON file to an Eigen matrix (with a list of indices) @@ -60,7 +61,9 @@ void buildLightMatFromJSON(const std::string& fileName, const std::vector& indices, Eigen::MatrixXf& lightMat, +void buildLightMatFromJSON(const std::string& fileName, + const std::vector& indices, + Eigen::MatrixXf& lightMat, std::vector>& intList); /** @@ -90,8 +93,7 @@ void intensityScaling(std::array const& intensities, image::Image& imageIn, const std::vector& indices, - Eigen::MatrixXf& imageOut); +void image2PsMatrix(const image::Image& imageIn, const std::vector& indices, Eigen::MatrixXf& imageOut); /** * @brief Rearrange (masked) values of color images in an Eigen matrix @@ -99,8 +101,7 @@ void image2PsMatrix(const image::Image& imageIn, const std::ve * @param[in] mask Input mask * @param[out] imageOut The resulting matrix that can be used in PS solver */ -void image2PsMatrix(const image::Image& imageIn, const image::Image& mask, - Eigen::MatrixXf& imageOut); +void image2PsMatrix(const image::Image& imageIn, const image::Image& mask, Eigen::MatrixXf& imageOut); /** * @brief Rearrange (masked) values of gray-level images in an Eigen matrix @@ -138,8 +139,7 @@ void readMatrix(const std::string& fileName, Eigen::MatrixXf& matrix); * @param[in] normals The normal map to save * @param[in] albedo The albedo map to save */ -void writePSResults(const std::string& outputPath, const image::Image& normals, - const image::Image& albedo); +void writePSResults(const std::string& outputPath, const image::Image& normals, const image::Image& albedo); /** * @brief Write the PS results in a given folder, using pose ID in the name (multi-view context only) @@ -148,8 +148,10 @@ void writePSResults(const std::string& outputPath, const image::Image& normals, - const image::Image& albedo, const IndexT poseId); +void writePSResults(const std::string& outputPath, + const image::Image& normals, + const image::Image& albedo, + const IndexT poseId); -} -} +} // namespace photometricStereo +} // namespace aliceVision diff --git a/src/aliceVision/photometricStereo/photometricStereo.cpp b/src/aliceVision/photometricStereo/photometricStereo.cpp index aea3c7d744..b51d864c32 100644 --- a/src/aliceVision/photometricStereo/photometricStereo.cpp +++ b/src/aliceVision/photometricStereo/photometricStereo.cpp @@ -27,8 +27,11 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace photometricStereo { -void photometricStereo(const std::string& inputPath, const std::string& lightData, const std::string& outputPath, - const PhotometricSteroParameters& PSParameters, image::Image& normals, +void photometricStereo(const std::string& inputPath, + const std::string& lightData, + const std::string& outputPath, + const PhotometricSteroParameters& PSParameters, + image::Image& normals, image::Image& albedo) { size_t dim = 3; @@ -41,8 +44,8 @@ void photometricStereo(const std::string& inputPath, const std::string& lightDat std::string pictureFolder = inputPath + "/PS_Pictures/"; getPicturesNames(pictureFolder, imageList); - std::vector> intList; // Light intensities - Eigen::MatrixXf lightMat(imageList.size(), dim); // Light directions + std::vector> intList; // Light intensities + Eigen::MatrixXf lightMat(imageList.size(), dim); // Light directions if (fs::is_directory(lightData)) { @@ -78,9 +81,13 @@ void photometricStereo(const std::string& inputPath, const std::string& lightDat image::writeImage(outputPath + "/mask.png", mask, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION)); } -void photometricStereo(const sfmData::SfMData& sfmData, const std::string& lightData, const std::string& maskPath, - const std::string& outputPath, const PhotometricSteroParameters& PSParameters, - image::Image& normals, image::Image& albedo) +void photometricStereo(const sfmData::SfMData& sfmData, + const std::string& lightData, + const std::string& maskPath, + const std::string& outputPath, + const PhotometricSteroParameters& PSParameters, + image::Image& normals, + image::Image& albedo) { bool skipAll = true; bool groupedImages = false; @@ -93,12 +100,12 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light std::string pathToAmbiant = ""; std::map> viewsPerPoseId; - for (auto& viewIt: sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { viewsPerPoseId[viewIt.second->getPoseId()].push_back(viewIt.second->getViewId()); } - for (auto& posesIt: viewsPerPoseId) + for (auto& posesIt : viewsPerPoseId) { std::vector imageList; @@ -106,19 +113,19 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light std::vector& initViewIds = posesIt.second; std::map idMap; - for (auto& viewId: initViewIds) + for (auto& viewId : initViewIds) { std::map currentMetadata = sfmData.getView(viewId).getImage().getMetadata(); idMap[currentMetadata.at("Exif:DateTimeDigitized")] = viewId; } std::vector viewIds; - for (const auto& [currentTime, viewId]: idMap) + for (const auto& [currentTime, viewId] : idMap) { viewIds.push_back(viewId); } - for (auto& viewId: viewIds) + for (auto& viewId : viewIds) { const fs::path imagePath = fs::path(sfmData.getView(viewId).getImage().getImagePath()); if (!boost::algorithm::icontains(imagePath.stem().string(), "ambiant")) @@ -132,10 +139,10 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light } } - std::vector> intList; // Light intensities - Eigen::MatrixXf lightMat(imageList.size(), dim); // Light directions + std::vector> intList; // Light intensities + Eigen::MatrixXf lightMat(imageList.size(), dim); // Light directions - if (fs::is_directory(lightData)) // #pragma omp parallel for + if (fs::is_directory(lightData)) // #pragma omp parallel for { loadPSData(lightData, PSParameters.SHOrder, intList, lightMat); } @@ -176,7 +183,9 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light photometricStereo(imageList, intList, lightMat, mask, pathToAmbiant, PSParameters, normals, albedo); writePSResults(outputPath, normals, albedo, posesIt.first); - image::writeImage(outputPath + "/" + std::to_string(posesIt.first) + "_mask.png", mask, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION)); + image::writeImage(outputPath + "/" + std::to_string(posesIt.first) + "_mask.png", + mask, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION)); if (sfmData.getPoses().size() > 0) { @@ -184,37 +193,38 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light applyRotation(rotation, normals); } - image::writeImage(outputPath + "/" + std::to_string(posesIt.first) + "_normals_w.exr", normals, image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); + image::writeImage( + outputPath + "/" + std::to_string(posesIt.first) + "_normals_w.exr", + normals, + image::ImageWriteOptions().toColorSpace(image::EImageColorSpace::NO_CONVERSION).storageDataType(image::EStorageDataType::Float)); } if (skipAll) { - ALICEVISION_LOG_ERROR( - "All images were skipped and no photometric stereo could be processed. This might happen if no sphere has " - "been detected in any of the input images, or if the light could not be calibrated."); + ALICEVISION_LOG_ERROR("All images were skipped and no photometric stereo could be processed. This might happen if no sphere has " + "been detected in any of the input images, or if the light could not be calibrated."); ALICEVISION_THROW(std::invalid_argument, "No image was processed for the photometric stereo."); } if (!groupedImages) { - ALICEVISION_LOG_WARNING( - "No images shared the same pose ID. " - << "Input images need to be located in a folder that starts with 'ps_' to be grouped together using their " - "pose ID. The photometric stereo cannot run otherwise."); + ALICEVISION_LOG_WARNING("No images shared the same pose ID. " + << "Input images need to be located in a folder that starts with 'ps_' to be grouped together using their " + "pose ID. The photometric stereo cannot run otherwise."); } sfmData::SfMData albedoSfmData = sfmData; std::set viewIdsToRemove; // Create Albedo SfmData - for (auto& viewIt: albedoSfmData.getViews()) + for (auto& viewIt : albedoSfmData.getViews()) { const IndexT viewId = viewIt.first; IndexT poseId = viewIt.second->getPoseId(); if (viewId == poseId) { - sfmData::View * view = albedoSfmData.getViews().at(viewId).get(); + sfmData::View* view = albedoSfmData.getViews().at(viewId).get(); std::string imagePath = outputPath + "/" + std::to_string(poseId) + "_albedo.exr"; view->getImage().setImagePath(imagePath); } @@ -223,29 +233,35 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light viewIdsToRemove.insert(viewId); } } - for (auto r: viewIdsToRemove) + for (auto r : viewIdsToRemove) albedoSfmData.getViews().erase(r); - sfmDataIO::Save(albedoSfmData, outputPath + "/albedoMaps.sfm", sfmDataIO::ESfMData(sfmDataIO::VIEWS|sfmDataIO::INTRINSICS|sfmDataIO::EXTRINSICS)); + sfmDataIO::Save( + albedoSfmData, outputPath + "/albedoMaps.sfm", sfmDataIO::ESfMData(sfmDataIO::VIEWS | sfmDataIO::INTRINSICS | sfmDataIO::EXTRINSICS)); // Create Normal SfmData sfmData::SfMData normalSfmData = albedoSfmData; - for(auto& viewIt: normalSfmData.getViews()) + for (auto& viewIt : normalSfmData.getViews()) { const IndexT viewId = viewIt.first; IndexT poseId = viewIt.second->getPoseId(); - sfmData::View * view = normalSfmData.getViews().at(viewId).get(); + sfmData::View* view = normalSfmData.getViews().at(viewId).get(); std::string imagePath = outputPath + "/" + std::to_string(poseId) + "_normals_w.exr"; view->getImage().setImagePath(imagePath); } - sfmDataIO::Save(normalSfmData, outputPath + "/normalMaps.sfm", sfmDataIO::ESfMData(sfmDataIO::VIEWS|sfmDataIO::INTRINSICS|sfmDataIO::EXTRINSICS)); + sfmDataIO::Save( + normalSfmData, outputPath + "/normalMaps.sfm", sfmDataIO::ESfMData(sfmDataIO::VIEWS | sfmDataIO::INTRINSICS | sfmDataIO::EXTRINSICS)); } -void photometricStereo(const std::vector& imageList, const std::vector>& intList, - const Eigen::MatrixXf& lightMat, image::Image& mask, const std::string& pathToAmbiant, - const PhotometricSteroParameters& PSParameters, image::Image& normals, +void photometricStereo(const std::vector& imageList, + const std::vector>& intList, + const Eigen::MatrixXf& lightMat, + image::Image& mask, + const std::string& pathToAmbiant, + const PhotometricSteroParameters& PSParameters, + image::Image& normals, image::Image& albedo) { size_t maskSize; @@ -379,7 +395,9 @@ void photometricStereo(const std::vector& imageList, const std::vec image2PsMatrix(imageFloat, currentMaskIndices, currentPicture); imMat.block(3 * i, 0, 3, currentMaskSize) = currentPicture; - imMat_gray.block(i, 0, 1, currentMaskSize) = currentPicture.block(0, 0, 1, currentMaskSize) * 0.2126 + currentPicture.block(1, 0, 1, currentMaskSize) * 0.7152 + currentPicture.block(2, 0, 1, currentMaskSize) * 0.0722; + imMat_gray.block(i, 0, 1, currentMaskSize) = currentPicture.block(0, 0, 1, currentMaskSize) * 0.2126 + + currentPicture.block(1, 0, 1, currentMaskSize) * 0.7152 + + currentPicture.block(2, 0, 1, currentMaskSize) * 0.0722; } Eigen::MatrixXf M_channel(3, currentMaskSize); @@ -392,7 +410,7 @@ void photometricStereo(const std::vector& imageList, const std::vec float epsilon = 0.001; // Errors (E) and Lagrange multiplicators (W) initialisation - Eigen::MatrixXf E = lightMat*M_channel - imMat_gray; + Eigen::MatrixXf E = lightMat * M_channel - imMat_gray; Eigen::MatrixXf W = Eigen::MatrixXf::Zero(E.rows(), E.cols()); Eigen::MatrixXf M_kminus1; @@ -430,7 +448,7 @@ void photometricStereo(const std::vector& imageList, const std::vec { if (hasMask) { - currentIdx = indices.at(i); // Index in picture + currentIdx = indices.at(i); // Index in picture } else { @@ -453,7 +471,7 @@ void photometricStereo(const std::vector& imageList, const std::vec { if (hasMask) { - currentIdx = indices.at(i); // Index in picture + currentIdx = indices.at(i); // Index in picture } else { @@ -475,7 +493,7 @@ void photometricStereo(const std::vector& imageList, const std::vec { if (hasMask) { - currentIdx = currentMaskIndices.at(i); // Index in picture + currentIdx = currentMaskIndices.at(i); // Index in picture } else { @@ -500,7 +518,7 @@ void photometricStereo(const std::vector& imageList, const std::vec { if (hasMask) { - currentIdx = currentMaskIndices.at(i); // Index in picture + currentIdx = currentMaskIndices.at(i); // Index in picture } else { @@ -512,19 +530,18 @@ void photometricStereo(const std::vector& imageList, const std::vec } } - image::Image normalsIm(pictCols,pictRows); + image::Image normalsIm(pictCols, pictRows); reshapeInImage(normalsVect, normalsIm); normals = normalsIm; - image::Image albedoIm(pictCols,pictRows); + image::Image albedoIm(pictCols, pictRows); albedoVect = albedoVect / albedoVect.maxCoeff(); reshapeInImage(albedoVect, albedoIm); albedo = albedoIm; } -void loadPSData(const std::string& folderPath, const size_t HS_order, std::vector>& intList, - Eigen::MatrixXf& lightMat) +void loadPSData(const std::string& folderPath, const size_t HS_order, std::vector>& intList, Eigen::MatrixXf& lightMat) { std::string intFileName; std::string pathToCM; @@ -567,9 +584,10 @@ void getPicturesNames(const std::string& folderPath, std::vector& i std::string fileExtension = fs::extension(currentFilePath.string()); std::transform(fileExtension.begin(), fileExtension.end(), fileExtension.begin(), ::tolower); - if (!boost::algorithm::icontains(currentFilePath.stem().string(), "mask") && !boost::algorithm::icontains(currentFilePath.stem().string(), "ambiant")) + if (!boost::algorithm::icontains(currentFilePath.stem().string(), "mask") && + !boost::algorithm::icontains(currentFilePath.stem().string(), "ambiant")) { - for (const std::string& extension: extensions) + for (const std::string& extension : extensions) { if (fileExtension == extension) { @@ -579,13 +597,10 @@ void getPicturesNames(const std::string& folderPath, std::vector& i } } - std::sort(imageList.begin(),imageList.end(),compareFunction); // Sort the vector + std::sort(imageList.begin(), imageList.end(), compareFunction); // Sort the vector } -bool compareFunction(std::string a, std::string b) -{ - return a < b; -} +bool compareFunction(std::string a, std::string b) { return a < b; } void shrink(const Eigen::MatrixXf& mat, const float rho, Eigen::MatrixXf& E) { @@ -610,9 +625,7 @@ void median(const Eigen::MatrixXf& d, float& median) Eigen::MatrixXf aux = d; std::sort(aux.data(), aux.data() + aux.size()); size_t middle = aux.size() / 2; - aux.size() % 2 == 0 ? - median = aux((aux.size() - 1) / 2) + aux((aux.size() + 1) / 2) : - median = aux(middle); + aux.size() % 2 == 0 ? median = aux((aux.size() - 1) / 2) + aux((aux.size() + 1) / 2) : median = aux(middle); } void slice(const std::vector& inputVector, int start, int numberOfElements, std::vector& currentMaskIndices) @@ -636,5 +649,5 @@ void applyRotation(const Eigen::MatrixXd& rotation, image::Image& normals, +void photometricStereo(const std::string& inputPath, + const std::string& lightData, + const std::string& outputPath, + const PhotometricSteroParameters& PSParameters, + image::Image& normals, image::Image& albedo); /** @@ -49,9 +51,13 @@ void photometricStereo(const std::string& inputPath, const std::string& lightDat * @param[out] normals Normal map of the scene * @param[out] albedo Albedo map of the scene */ -void photometricStereo(const sfmData::SfMData& sfmData, const std::string& lightData, const std::string& maskPath, - const std::string& outputPath, const PhotometricSteroParameters& PSParameters, - image::Image& normals, image::Image& albedo); +void photometricStereo(const sfmData::SfMData& sfmData, + const std::string& lightData, + const std::string& maskPath, + const std::string& outputPath, + const PhotometricSteroParameters& PSParameters, + image::Image& normals, + image::Image& albedo); /** * @brief Apply the PS algorithm for a given set of pictures sharing the same pose @@ -64,9 +70,13 @@ void photometricStereo(const sfmData::SfMData& sfmData, const std::string& light * @param[out] normals Normal map of the scene * @param[out] albedo Albedo map of the scene */ -void photometricStereo(const std::vector& imageList, const std::vector>& intList, - const Eigen::MatrixXf& lightMat, image::Image& mask, const std::string& pathToAmbiant, - const PhotometricSteroParameters& PSParameters, image::Image& normals, +void photometricStereo(const std::vector& imageList, + const std::vector>& intList, + const Eigen::MatrixXf& lightMat, + image::Image& mask, + const std::string& pathToAmbiant, + const PhotometricSteroParameters& PSParameters, + image::Image& normals, image::Image& albedo); /** @@ -76,8 +86,7 @@ void photometricStereo(const std::vector& imageList, const std::vec * @param[out] intList Intensities of lights * @param[out] lightMat Directions of lights */ -void loadPSData(const std::string& folderPath, const size_t HS_order, std::vector>& intList, - Eigen::MatrixXf& lightMat); +void loadPSData(const std::string& folderPath, const size_t HS_order, std::vector>& intList, Eigen::MatrixXf& lightMat); /** * @brief Get the name of the pictures in a given folder @@ -109,9 +118,8 @@ void shrink(const Eigen::MatrixXf& mat, const float rho, Eigen::MatrixXf& E); */ void median(const Eigen::MatrixXf& d, float& median); -void slice(const std::vector& inputVector, int start, int numberOfElements, - std::vector& currentMaskIndices); +void slice(const std::vector& inputVector, int start, int numberOfElements, std::vector& currentMaskIndices); void applyRotation(const Eigen::MatrixXd& rotation, image::Image& normals); -} -} +} // namespace photometricStereo +} // namespace aliceVision diff --git a/src/aliceVision/prettyprint.hpp b/src/aliceVision/prettyprint.hpp index ca6225e0e2..0820a1b198 100644 --- a/src/aliceVision/prettyprint.hpp +++ b/src/aliceVision/prettyprint.hpp @@ -22,444 +22,484 @@ * Distributed under the Boost Software License, Version 1.0. * (See http://www.boost.org/LICENSE_1_0.txt) */ -namespace pretty_print +namespace pretty_print { +namespace detail { +// SFINAE type trait to detect whether T::const_iterator exists. + +struct sfinae_base { - namespace detail - { - // SFINAE type trait to detect whether T::const_iterator exists. + using yes = char; + using no = yes[2]; +}; - struct sfinae_base - { - using yes = char; - using no = yes[2]; - }; +template +struct has_const_iterator : private sfinae_base +{ + private: + template + static yes& test(typename C::const_iterator*); + template + static no& test(...); - template - struct has_const_iterator : private sfinae_base - { - private: - template static yes & test(typename C::const_iterator*); - template static no & test(...); - public: - static const bool value = sizeof(test(nullptr)) == sizeof(yes); - using type = T; - }; - - template - struct has_begin_end : private sfinae_base - { - private: -#ifdef _MSC_VER - // Work around MSVC ICE in 15.9.x by moving decltype out to template - // typename - template - static yes & f(typename std::enable_if< - std::is_same(&C::begin)), - typename C::const_iterator(C::*)() const>::value>::type *); -#else // _MSCV_VER - template - static yes & f(typename std::enable_if< - std::is_same(&C::begin)), - typename C::const_iterator(C::*)() const>::value>::type *); -#endif // _MSCV_VER - - template static no & f(...); + public: + static const bool value = sizeof(test(nullptr)) == sizeof(yes); + using type = T; +}; +template +struct has_begin_end : private sfinae_base +{ + private: #ifdef _MSC_VER - template - static yes & g(typename std::enable_if< - std::is_same(&C::end)), - typename C::const_iterator(C::*)() const>::value, void>::type*); -#else // _MSCV_VER - template - static yes & g(typename std::enable_if< - std::is_same(&C::end)), - typename C::const_iterator(C::*)() const>::value, void>::type*); -#endif // _MSCV_VER - - template static no & g(...); - - public: - static bool const beg_value = sizeof(f(nullptr)) == sizeof(yes); - static bool const end_value = sizeof(g(nullptr)) == sizeof(yes); - }; - - } // namespace detail - - - // Holds the delimiter values for a specific character type - - template - struct delimiters_values - { - using char_type = TChar; - const char_type * prefix; - const char_type * delimiter; - const char_type * postfix; - }; + // Work around MSVC ICE in 15.9.x by moving decltype out to template + // typename + template + static yes& f(typename std::enable_if< + std::is_same(&C::begin)), typename C::const_iterator (C::*)() const>::value>::type*); +#else // _MSCV_VER + template + static yes& f(typename std::enable_if(&C::begin)), + typename C::const_iterator (C::*)() const>::value>::type*); +#endif // _MSCV_VER + + template + static no& f(...); +#ifdef _MSC_VER + template + static yes& g( + typename std::enable_if(&C::end)), typename C::const_iterator (C::*)() const>::value, + void>::type*); +#else // _MSCV_VER + template + static yes& g(typename std::enable_if(&C::end)), + typename C::const_iterator (C::*)() const>::value, + void>::type*); +#endif // _MSCV_VER + + template + static no& g(...); + + public: + static bool const beg_value = sizeof(f(nullptr)) == sizeof(yes); + static bool const end_value = sizeof(g(nullptr)) == sizeof(yes); +}; + +} // namespace detail + +// Holds the delimiter values for a specific character type + +template +struct delimiters_values +{ + using char_type = TChar; + const char_type* prefix; + const char_type* delimiter; + const char_type* postfix; +}; - // Defines the delimiter values for a specific container and character type +// Defines the delimiter values for a specific container and character type - template - struct delimiters - { - using type = delimiters_values; - static const type values; - }; +template +struct delimiters +{ + using type = delimiters_values; + static const type values; +}; +// Functor to print containers. You can use this directly if you want +// to specificy a non-default delimiters type. The printing logic can +// be customized by specializing the nested template. - // Functor to print containers. You can use this directly if you want - // to specificy a non-default delimiters type. The printing logic can - // be customized by specializing the nested template. +template, typename TDelimiters = delimiters> +struct print_container_helper +{ + using delimiters_type = TDelimiters; + using ostream_type = std::basic_ostream; - template , - typename TDelimiters = delimiters> - struct print_container_helper + template + struct printer { - using delimiters_type = TDelimiters; - using ostream_type = std::basic_ostream; - - template - struct printer + static void print_body(const U& c, ostream_type& stream) { - static void print_body(const U & c, ostream_type & stream) - { - using std::begin; - using std::end; + using std::begin; + using std::end; - auto it = begin(c); - const auto the_end = end(c); + auto it = begin(c); + const auto the_end = end(c); - if (it != the_end) + if (it != the_end) + { + for (;;) { - for ( ; ; ) - { - stream << *it; + stream << *it; - if (++it == the_end) break; + if (++it == the_end) + break; if (delimiters_type::values.delimiter != NULL) stream << delimiters_type::values.delimiter; - } } } - }; - - print_container_helper(const T & container) - : container_(container) - { } - - inline void operator()(ostream_type & stream) const - { - if (delimiters_type::values.prefix != NULL) - stream << delimiters_type::values.prefix; - - printer::print_body(container_, stream); - - if (delimiters_type::values.postfix != NULL) - stream << delimiters_type::values.postfix; } - - private: - const T & container_; }; - // Specialization for pairs + print_container_helper(const T& container) + : container_(container) + {} - template - template - struct print_container_helper::printer> + inline void operator()(ostream_type& stream) const { - using ostream_type = typename print_container_helper::ostream_type; + if (delimiters_type::values.prefix != NULL) + stream << delimiters_type::values.prefix; - static void print_body(const std::pair & c, ostream_type & stream) - { - stream << c.first; - if (print_container_helper::delimiters_type::values.delimiter != NULL) - stream << print_container_helper::delimiters_type::values.delimiter; - stream << c.second; - } - }; + printer::print_body(container_, stream); - // Specialization for tuples + if (delimiters_type::values.postfix != NULL) + stream << delimiters_type::values.postfix; + } - template - template - struct print_container_helper::printer> - { - using ostream_type = typename print_container_helper::ostream_type; - using element_type = std::tuple; + private: + const T& container_; +}; - template struct Int { }; +// Specialization for pairs - static void print_body(const element_type & c, ostream_type & stream) - { - tuple_print(c, stream, Int<0>()); - } +template +template +struct print_container_helper::printer> +{ + using ostream_type = typename print_container_helper::ostream_type; - static void tuple_print(const element_type &, ostream_type &, Int) - { - } + static void print_body(const std::pair& c, ostream_type& stream) + { + stream << c.first; + if (print_container_helper::delimiters_type::values.delimiter != NULL) + stream << print_container_helper::delimiters_type::values.delimiter; + stream << c.second; + } +}; - static void tuple_print(const element_type & c, ostream_type & stream, - typename std::conditional, std::nullptr_t>::type) - { - stream << std::get<0>(c); - tuple_print(c, stream, Int<1>()); - } +// Specialization for tuples - template - static void tuple_print(const element_type & c, ostream_type & stream, Int) - { - if (print_container_helper::delimiters_type::values.delimiter != NULL) - stream << print_container_helper::delimiters_type::values.delimiter; +template +template +struct print_container_helper::printer> +{ + using ostream_type = typename print_container_helper::ostream_type; + using element_type = std::tuple; - stream << std::get(c); + template + struct Int + {}; - tuple_print(c, stream, Int()); - } - }; + static void print_body(const element_type& c, ostream_type& stream) { tuple_print(c, stream, Int<0>()); } - // Prints a print_container_helper to the specified stream. + static void tuple_print(const element_type&, ostream_type&, Int) {} - template - inline std::basic_ostream & operator<<( - std::basic_ostream & stream, - const print_container_helper & helper) + static void tuple_print(const element_type& c, + ostream_type& stream, + typename std::conditional, std::nullptr_t>::type) { - helper(stream); - return stream; + stream << std::get<0>(c); + tuple_print(c, stream, Int<1>()); } + template + static void tuple_print(const element_type& c, ostream_type& stream, Int) + { + if (print_container_helper::delimiters_type::values.delimiter != NULL) + stream << print_container_helper::delimiters_type::values.delimiter; - // Basic is_container template; specialize to derive from std::true_type for all desired container types - - template - struct is_container : public std::integral_constant::value && - detail::has_begin_end::beg_value && - detail::has_begin_end::end_value> { }; - - template - struct is_container : std::true_type { }; - - template - struct is_container : std::false_type { }; + stream << std::get(c); - template - struct is_container> : std::true_type { }; + tuple_print(c, stream, Int()); + } +}; - template - struct is_container> : std::true_type { }; +// Prints a print_container_helper to the specified stream. - template - struct is_container> : std::true_type { }; +template +inline std::basic_ostream& operator<<(std::basic_ostream& stream, + const print_container_helper& helper) +{ + helper(stream); + return stream; +} +// Basic is_container template; specialize to derive from std::true_type for all desired container types - // Default delimiters +template +struct is_container + : public std::integral_constant::value && detail::has_begin_end::beg_value && detail::has_begin_end::end_value> +{}; - template struct delimiters { static const delimiters_values values; }; - template const delimiters_values delimiters::values = { "[", ", ", "]" }; - template struct delimiters { static const delimiters_values values; }; - template const delimiters_values delimiters::values = { L"[", L", ", L"]" }; +template +struct is_container : std::true_type +{}; +template +struct is_container : std::false_type +{}; - // Delimiters for (multi)set and unordered_(multi)set +template +struct is_container> : std::true_type +{}; - template - struct delimiters< ::std::set, char> { static const delimiters_values values; }; +template +struct is_container> : std::true_type +{}; - template - const delimiters_values delimiters< ::std::set, char>::values = { "{", ", ", "}" }; +template +struct is_container> : std::true_type +{}; - template - struct delimiters< ::std::set, wchar_t> { static const delimiters_values values; }; +// Default delimiters - template - const delimiters_values delimiters< ::std::set, wchar_t>::values = { L"{", L", ", L"}" }; +template +struct delimiters +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters::values = {"[", ", ", "]"}; +template +struct delimiters +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters::values = {L"[", L", ", L"]"}; - template - struct delimiters< ::std::multiset, char> { static const delimiters_values values; }; +// Delimiters for (multi)set and unordered_(multi)set - template - const delimiters_values delimiters< ::std::multiset, char>::values = { "{", ", ", "}" }; +template +struct delimiters<::std::set, char> +{ + static const delimiters_values values; +}; - template - struct delimiters< ::std::multiset, wchar_t> { static const delimiters_values values; }; +template +const delimiters_values delimiters<::std::set, char>::values = {"{", ", ", "}"}; - template - const delimiters_values delimiters< ::std::multiset, wchar_t>::values = { L"{", L", ", L"}" }; +template +struct delimiters<::std::set, wchar_t> +{ + static const delimiters_values values; +}; - template - struct delimiters< ::std::unordered_set, char> { static const delimiters_values values; }; +template +const delimiters_values delimiters<::std::set, wchar_t>::values = {L"{", L", ", L"}"}; - template - const delimiters_values delimiters< ::std::unordered_set, char>::values = { "{", ", ", "}" }; +template +struct delimiters<::std::multiset, char> +{ + static const delimiters_values values; +}; - template - struct delimiters< ::std::unordered_set, wchar_t> { static const delimiters_values values; }; +template +const delimiters_values delimiters<::std::multiset, char>::values = {"{", ", ", "}"}; - template - const delimiters_values delimiters< ::std::unordered_set, wchar_t>::values = { L"{", L", ", L"}" }; +template +struct delimiters<::std::multiset, wchar_t> +{ + static const delimiters_values values; +}; - template - struct delimiters< ::std::unordered_multiset, char> { static const delimiters_values values; }; +template +const delimiters_values delimiters<::std::multiset, wchar_t>::values = {L"{", L", ", L"}"}; - template - const delimiters_values delimiters< ::std::unordered_multiset, char>::values = { "{", ", ", "}" }; +template +struct delimiters<::std::unordered_set, char> +{ + static const delimiters_values values; +}; - template - struct delimiters< ::std::unordered_multiset, wchar_t> { static const delimiters_values values; }; +template +const delimiters_values delimiters<::std::unordered_set, char>::values = {"{", ", ", "}"}; - template - const delimiters_values delimiters< ::std::unordered_multiset, wchar_t>::values = { L"{", L", ", L"}" }; +template +struct delimiters<::std::unordered_set, wchar_t> +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters<::std::unordered_set, wchar_t>::values = {L"{", L", ", L"}"}; - // Delimiters for pair and tuple +template +struct delimiters<::std::unordered_multiset, char> +{ + static const delimiters_values values; +}; - template struct delimiters, char> { static const delimiters_values values; }; - template const delimiters_values delimiters, char>::values = { "(", ", ", ")" }; - template struct delimiters< ::std::pair, wchar_t> { static const delimiters_values values; }; - template const delimiters_values delimiters< ::std::pair, wchar_t>::values = { L"(", L", ", L")" }; +template +const delimiters_values delimiters<::std::unordered_multiset, char>::values = {"{", ", ", "}"}; - template struct delimiters, char> { static const delimiters_values values; }; - template const delimiters_values delimiters, char>::values = { "(", ", ", ")" }; - template struct delimiters< ::std::tuple, wchar_t> { static const delimiters_values values; }; - template const delimiters_values delimiters< ::std::tuple, wchar_t>::values = { L"(", L", ", L")" }; +template +struct delimiters<::std::unordered_multiset, wchar_t> +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters<::std::unordered_multiset, wchar_t>::values = {L"{", L", ", L"}"}; - // Type-erasing helper class for easy use of custom delimiters. - // Requires TCharTraits = std::char_traits and TChar = char or wchar_t, and MyDelims needs to be defined for TChar. - // Usage: "cout << pretty_print::custom_delims(x)". +// Delimiters for pair and tuple - struct custom_delims_base - { - virtual ~custom_delims_base() { } - virtual std::ostream & stream(::std::ostream &) = 0; - virtual std::wostream & stream(::std::wostream &) = 0; - }; +template +struct delimiters, char> +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters, char>::values = {"(", ", ", ")"}; +template +struct delimiters<::std::pair, wchar_t> +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters<::std::pair, wchar_t>::values = {L"(", L", ", L")"}; - template - struct custom_delims_wrapper : custom_delims_base - { - custom_delims_wrapper(const T & t_) : t(t_) { } +template +struct delimiters, char> +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters, char>::values = {"(", ", ", ")"}; +template +struct delimiters<::std::tuple, wchar_t> +{ + static const delimiters_values values; +}; +template +const delimiters_values delimiters<::std::tuple, wchar_t>::values = {L"(", L", ", L")"}; - std::ostream & stream(std::ostream & s) - { - return s << print_container_helper, Delims>(t); - } +// Type-erasing helper class for easy use of custom delimiters. +// Requires TCharTraits = std::char_traits and TChar = char or wchar_t, and MyDelims needs to be defined for TChar. +// Usage: "cout << pretty_print::custom_delims(x)". - std::wostream & stream(std::wostream & s) - { - return s << print_container_helper, Delims>(t); - } +struct custom_delims_base +{ + virtual ~custom_delims_base() {} + virtual std::ostream& stream(::std::ostream&) = 0; + virtual std::wostream& stream(::std::wostream&) = 0; +}; - private: - const T & t; - }; +template +struct custom_delims_wrapper : custom_delims_base +{ + custom_delims_wrapper(const T& t_) + : t(t_) + {} - template - struct custom_delims - { - template - custom_delims(const Container & c) : base(new custom_delims_wrapper(c)) { } + std::ostream& stream(std::ostream& s) { return s << print_container_helper, Delims>(t); } - std::unique_ptr base; - }; + std::wostream& stream(std::wostream& s) { return s << print_container_helper, Delims>(t); } - template - inline std::basic_ostream & operator<<(std::basic_ostream & s, const custom_delims & p) - { - return p.base->stream(s); - } + private: + const T& t; +}; +template +struct custom_delims +{ + template + custom_delims(const Container& c) + : base(new custom_delims_wrapper(c)) + {} - // A wrapper for a C-style array given as pointer-plus-size. - // Usage: std::cout << pretty_print_array(arr, n) << std::endl; + std::unique_ptr base; +}; - template - struct array_wrapper_n - { - typedef const T * const_iterator; - typedef T value_type; +template +inline std::basic_ostream& operator<<(std::basic_ostream& s, const custom_delims& p) +{ + return p.base->stream(s); +} - array_wrapper_n(const T * const a, size_t n) : _array(a), _n(n) { } - inline const_iterator begin() const { return _array; } - inline const_iterator end() const { return _array + _n; } +// A wrapper for a C-style array given as pointer-plus-size. +// Usage: std::cout << pretty_print_array(arr, n) << std::endl; - private: - const T * const _array; - size_t _n; - }; +template +struct array_wrapper_n +{ + typedef const T* const_iterator; + typedef T value_type; + array_wrapper_n(const T* const a, size_t n) + : _array(a), + _n(n) + {} + inline const_iterator begin() const { return _array; } + inline const_iterator end() const { return _array + _n; } - // A wrapper for hash-table based containers that offer local iterators to each bucket. - // Usage: std::cout << bucket_print(m, 4) << std::endl; (Prints bucket 5 of container m.) + private: + const T* const _array; + size_t _n; +}; - template - struct bucket_print_wrapper - { - typedef typename T::const_local_iterator const_iterator; - typedef typename T::size_type size_type; +// A wrapper for hash-table based containers that offer local iterators to each bucket. +// Usage: std::cout << bucket_print(m, 4) << std::endl; (Prints bucket 5 of container m.) - const_iterator begin() const - { - return m_map.cbegin(n); - } +template +struct bucket_print_wrapper +{ + typedef typename T::const_local_iterator const_iterator; + typedef typename T::size_type size_type; - const_iterator end() const - { - return m_map.cend(n); - } + const_iterator begin() const { return m_map.cbegin(n); } - bucket_print_wrapper(const T & m, size_type bucket) : m_map(m), n(bucket) { } + const_iterator end() const { return m_map.cend(n); } - private: - const T & m_map; - const size_type n; - }; + bucket_print_wrapper(const T& m, size_type bucket) + : m_map(m), + n(bucket) + {} -} // namespace pretty_print + private: + const T& m_map; + const size_type n; +}; +} // namespace pretty_print // Global accessor functions for the convenience wrappers template -inline pretty_print::array_wrapper_n pretty_print_array(const T * const a, size_t n) +inline pretty_print::array_wrapper_n pretty_print_array(const T* const a, size_t n) { return pretty_print::array_wrapper_n(a, n); } -template pretty_print::bucket_print_wrapper -bucket_print(const T & m, typename T::size_type n) +template +pretty_print::bucket_print_wrapper bucket_print(const T& m, typename T::size_type n) { return pretty_print::bucket_print_wrapper(m, n); } - // Main magic entry point: An overload snuck into namespace std. // Can we do better? namespace Eigen { -template +template class EigenBase; } -namespace std -{ - // Prints a container to the stream using default delimiters - template - struct is_eigen_object : std::is_base_of, T> - {}; +namespace std { +// Prints a container to the stream using default delimiters +template +struct is_eigen_object : std::is_base_of, T> +{}; - template - inline typename enable_if< ::pretty_print::is_container::value && !is_eigen_object::value, - basic_ostream &>::type - operator<<(basic_ostream & stream, const T & container) - { - return stream << ::pretty_print::print_container_helper(container); - } +template +inline typename enable_if<::pretty_print::is_container::value && !is_eigen_object::value, basic_ostream&>::type operator<<( + basic_ostream& stream, + const T& container) +{ + return stream << ::pretty_print::print_container_helper(container); } +} // namespace std diff --git a/src/aliceVision/rig/ResidualError.hpp b/src/aliceVision/rig/ResidualError.hpp index fab3b5f2a9..8021c3f1d8 100644 --- a/src/aliceVision/rig/ResidualError.hpp +++ b/src/aliceVision/rig/ResidualError.hpp @@ -7,389 +7,383 @@ #pragma once #include -#include //todo: not generic - // only radial3 is currently supported - // todo: allows internal parameters refinement +#include //todo: not generic + // only radial3 is currently supported + // todo: allows internal parameters refinement #include #include - namespace aliceVision { namespace rig { class ResidualErrorMainCameraFunctor { -public : - ResidualErrorMainCameraFunctor(const camera::Pinhole & intrinsics, - const Vec2 & pt2d, - const Vec3 & pt3d) - { - // Set the intrinsics - _K = intrinsics.K(); - - _params.reserve(4); - _params.push_back(intrinsics.getParams()[4]); - _params.push_back(intrinsics.getParams()[5]); - _params.push_back(intrinsics.getParams()[6]); - - // Set the observation - _observation[0] = pt2d[0]; - _observation[1] = pt2d[1]; - - // Set the 3D point - _point(0) = pt3d(0); - _point(1) = pt3d(1); - _point(2) = pt3d(2); - - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. -// enum { -// OFFSET_FOCAL_LENGTH = 0, -// OFFSET_PRINCIPAL_POINT_X = 1, -// OFFSET_PRINCIPAL_POINT_Y = 2, -// OFFSET_DISTO_K1 = 3, -// OFFSET_DISTO_K2 = 4, -// OFFSET_DISTO_K3 = 5, -// }; - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()(const T* const cam_Rt, T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_3dpoint[3]; - pos_3dpoint[0]= T(_point(0)); - pos_3dpoint[1]= T(_point(1)); - pos_3dpoint[2]= T(_point(2)); - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - const T focal = T(_K(0,0)); - const T principal_point_x = T(_K(0,2)); - const T principal_point_y = T(_K(1,2)); - const T k1 = T(_params[0]); - const T k2 = T(_params[1]); - const T k3 = T(_params[2]); - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r4 = r2 * r2; - const T r6 = r4 * r2; - const T r_coeff = (T(1) + k1*r2 + k2*r4 + k3*r6); - const T x_d = x_u * r_coeff; - const T y_d = y_u * r_coeff; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focal * x_d; - const T projected_y = principal_point_y + focal * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - out_residuals[0] = projected_x - _observation[0]; - out_residuals[1] = projected_y - _observation[1]; - - return true; - } - -private : - aliceVision::Mat3 _K; // Calibration matrix - std::vector _params; // {K1, K2, K3} - aliceVision::Vec3 _point; // 3D point - aliceVision::Vec2 _observation; // its image location + public: + ResidualErrorMainCameraFunctor(const camera::Pinhole& intrinsics, const Vec2& pt2d, const Vec3& pt3d) + { + // Set the intrinsics + _K = intrinsics.K(); + + _params.reserve(4); + _params.push_back(intrinsics.getParams()[4]); + _params.push_back(intrinsics.getParams()[5]); + _params.push_back(intrinsics.getParams()[6]); + + // Set the observation + _observation[0] = pt2d[0]; + _observation[1] = pt2d[1]; + + // Set the 3D point + _point(0) = pt3d(0); + _point(1) = pt3d(1); + _point(2) = pt3d(2); + } + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + // enum { + // OFFSET_FOCAL_LENGTH = 0, + // OFFSET_PRINCIPAL_POINT_X = 1, + // OFFSET_PRINCIPAL_POINT_Y = 2, + // OFFSET_DISTO_K1 = 3, + // OFFSET_DISTO_K2 = 4, + // OFFSET_DISTO_K3 = 5, + // }; + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_Rt, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_3dpoint[3]; + pos_3dpoint[0] = T(_point(0)); + pos_3dpoint[1] = T(_point(1)); + pos_3dpoint[2] = T(_point(2)); + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + const T focal = T(_K(0, 0)); + const T principal_point_x = T(_K(0, 2)); + const T principal_point_y = T(_K(1, 2)); + const T k1 = T(_params[0]); + const T k2 = T(_params[1]); + const T k3 = T(_params[2]); + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r4 = r2 * r2; + const T r6 = r4 * r2; + const T r_coeff = (T(1) + k1 * r2 + k2 * r4 + k3 * r6); + const T x_d = x_u * r_coeff; + const T y_d = y_u * r_coeff; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focal * x_d; + const T projected_y = principal_point_y + focal * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + out_residuals[0] = projected_x - _observation[0]; + out_residuals[1] = projected_y - _observation[1]; + + return true; + } + + private: + aliceVision::Mat3 _K; // Calibration matrix + std::vector _params; // {K1, K2, K3} + aliceVision::Vec3 _point; // 3D point + aliceVision::Vec2 _observation; // its image location }; class ResidualErrorSecondaryCameraFunctor { -public : - ResidualErrorSecondaryCameraFunctor(const camera::Pinhole & intrinsics, - const aliceVision::Vec2 & pt2d, - const aliceVision::Vec3 & pt3d) // const double* const pos_2dpoint - { - // Set the intrinsics - _K = intrinsics.K(); - - _params.reserve(3); - _params.push_back(intrinsics.getParams()[3]); - _params.push_back(intrinsics.getParams()[4]); - _params.push_back(intrinsics.getParams()[5]); - - // Set the observation - _observation[0] = pt2d[0]; - _observation[1] = pt2d[1]; - - // Set the 3D point - _point(0) = pt3d(0); - _point(1) = pt3d(1); - _point(2) = pt3d(2); - - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. -// enum { -// OFFSET_FOCAL_LENGTH = 0, -// OFFSET_PRINCIPAL_POINT_X = 1, -// OFFSET_PRINCIPAL_POINT_Y = 2, -// OFFSET_DISTO_K1 = 3, -// OFFSET_DISTO_K2 = 4, -// OFFSET_DISTO_K3 = 5, -// }; - - /** - * @param[in] cam_Rt_main: Main camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] cam_Rt_relative: Relative pose of the witness camera relatively to the main one - * parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[out] out_residuals - */ - template - bool operator()(const T* const cam_Rt_main, const T* const cam_Rt_relative, T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * RMain = cam_Rt_main; - const T * tMain = &cam_Rt_main[3]; - - const T * RRelative = cam_Rt_relative; - const T * tRelative = &cam_Rt_relative[3]; - - T pos_3dpoint[3]; - pos_3dpoint[0]= T(_point(0)); - pos_3dpoint[1]= T(_point(1)); - pos_3dpoint[2]= T(_point(2)); - - T pos_tmp[3]; - // Rotate the point according the relative rotation first - ceres::AngleAxisRotatePoint(RMain, pos_3dpoint, pos_tmp); - - // Apply the relative translation first - pos_tmp[0] += tMain[0]; - pos_tmp[1] += tMain[1]; - pos_tmp[2] += tMain[2]; - - T pos_proj[3]; - // Rotate the point according the main camera rotation - ceres::AngleAxisRotatePoint(RRelative, pos_tmp, pos_proj); - - // Apply the main camera translation - pos_proj[0] += tRelative[0]; - pos_proj[1] += tRelative[1]; - pos_proj[2] += tRelative[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - const T focal = T(_K(0,0)); - const T principal_point_x = T(_K(0,2)); - const T principal_point_y = T(_K(1,2)); - const T k1 = T(_params[0]); - const T k2 = T(_params[1]); - const T k3 = T(_params[2]); - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r4 = r2 * r2; - const T r6 = r4 * r2; - const T r_coeff = (T(1) + k1*r2 + k2*r4 + k3*r6); - const T x_d = x_u * r_coeff; - const T y_d = y_u * r_coeff; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focal * x_d; - const T projected_y = principal_point_y + focal * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - out_residuals[0] = projected_x - _observation[0]; - out_residuals[1] = projected_y - _observation[1]; - - return true; - } - -private : - aliceVision::Mat3 _K; // Calibration matrix - std::vector _params; // {K1, K2, K3} - aliceVision::Vec3 _point; // 3D point - aliceVision::Vec2 _observation; // its image location + public: + ResidualErrorSecondaryCameraFunctor(const camera::Pinhole& intrinsics, + const aliceVision::Vec2& pt2d, + const aliceVision::Vec3& pt3d) // const double* const pos_2dpoint + { + // Set the intrinsics + _K = intrinsics.K(); + + _params.reserve(3); + _params.push_back(intrinsics.getParams()[3]); + _params.push_back(intrinsics.getParams()[4]); + _params.push_back(intrinsics.getParams()[5]); + + // Set the observation + _observation[0] = pt2d[0]; + _observation[1] = pt2d[1]; + + // Set the 3D point + _point(0) = pt3d(0); + _point(1) = pt3d(1); + _point(2) = pt3d(2); + } + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + // enum { + // OFFSET_FOCAL_LENGTH = 0, + // OFFSET_PRINCIPAL_POINT_X = 1, + // OFFSET_PRINCIPAL_POINT_Y = 2, + // OFFSET_DISTO_K1 = 3, + // OFFSET_DISTO_K2 = 4, + // OFFSET_DISTO_K3 = 5, + // }; + + /** + * @param[in] cam_Rt_main: Main camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] cam_Rt_relative: Relative pose of the witness camera relatively to the main one + * parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_Rt_main, const T* const cam_Rt_relative, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + const T* RMain = cam_Rt_main; + const T* tMain = &cam_Rt_main[3]; + + const T* RRelative = cam_Rt_relative; + const T* tRelative = &cam_Rt_relative[3]; + + T pos_3dpoint[3]; + pos_3dpoint[0] = T(_point(0)); + pos_3dpoint[1] = T(_point(1)); + pos_3dpoint[2] = T(_point(2)); + + T pos_tmp[3]; + // Rotate the point according the relative rotation first + ceres::AngleAxisRotatePoint(RMain, pos_3dpoint, pos_tmp); + + // Apply the relative translation first + pos_tmp[0] += tMain[0]; + pos_tmp[1] += tMain[1]; + pos_tmp[2] += tMain[2]; + + T pos_proj[3]; + // Rotate the point according the main camera rotation + ceres::AngleAxisRotatePoint(RRelative, pos_tmp, pos_proj); + + // Apply the main camera translation + pos_proj[0] += tRelative[0]; + pos_proj[1] += tRelative[1]; + pos_proj[2] += tRelative[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + const T focal = T(_K(0, 0)); + const T principal_point_x = T(_K(0, 2)); + const T principal_point_y = T(_K(1, 2)); + const T k1 = T(_params[0]); + const T k2 = T(_params[1]); + const T k3 = T(_params[2]); + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r4 = r2 * r2; + const T r6 = r4 * r2; + const T r_coeff = (T(1) + k1 * r2 + k2 * r4 + k3 * r6); + const T x_d = x_u * r_coeff; + const T y_d = y_u * r_coeff; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focal * x_d; + const T projected_y = principal_point_y + focal * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + out_residuals[0] = projected_x - _observation[0]; + out_residuals[1] = projected_y - _observation[1]; + + return true; + } + + private: + aliceVision::Mat3 _K; // Calibration matrix + std::vector _params; // {K1, K2, K3} + aliceVision::Vec3 _point; // 3D point + aliceVision::Vec2 _observation; // its image location }; /** * @brief class to be used when the the poses of the witness cameras are known and - * must be kept fixed (no refining) and only the pose of the whole rig must be + * must be kept fixed (no refining) and only the pose of the whole rig must be * refined */ class ResidualErrorSecondaryCameraFixedRelativeFunctor { -public : - ResidualErrorSecondaryCameraFixedRelativeFunctor(const camera::Pinhole & intrinsics, - const Vec2 & pt2d, - const Vec3 & pt3d, - const geometry::Pose3 &relativePose) // const double* const pos_2dpoint - { - // Set the intrinsics - _K = intrinsics.K(); - - _params.reserve(3); - _params.push_back(intrinsics.getParams()[3]); - _params.push_back(intrinsics.getParams()[4]); - _params.push_back(intrinsics.getParams()[5]); - - // Set the observation - _observation[0] = pt2d[0]; - _observation[1] = pt2d[1]; - - // Set the 3D point - _point(0) = pt3d(0); - _point(1) = pt3d(1); - _point(2) = pt3d(2); - - const aliceVision::Mat3 & R = relativePose.rotation(); - const aliceVision::Vec3 & t = relativePose.translation(); - - // convert the relative pose into angle axis representation - ceres::RotationMatrixToAngleAxis((const double*)R.data(), _relativePose); - // and set the translation - _relativePose[3] = t(0); - _relativePose[4] = t(1); - _relativePose[5] = t(2); - - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. -// enum { -// OFFSET_FOCAL_LENGTH = 0, -// OFFSET_PRINCIPAL_POINT_X = 1, -// OFFSET_PRINCIPAL_POINT_Y = 2, -// OFFSET_DISTO_K1 = 3, -// OFFSET_DISTO_K2 = 4, -// OFFSET_DISTO_K3 = 5, -// }; - - /** - * @param[in] cam_Rt_main: Main camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[out] out_residuals - */ - template - bool operator()(const T* const cam_Rt_main, T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * RMain = cam_Rt_main; - const T * tMain = &cam_Rt_main[3]; - - T RRelative[3]; - RRelative[0] = T(_relativePose[0]); - RRelative[1] = T(_relativePose[1]); - RRelative[2] = T(_relativePose[2]); - T tRelative[3]; - tRelative[0] = T(_relativePose[3]); - tRelative[1] = T(_relativePose[4]); - tRelative[2] = T(_relativePose[5]); - - T pos_3dpoint[3]; - pos_3dpoint[0]= T(_point(0)); - pos_3dpoint[1]= T(_point(1)); - pos_3dpoint[2]= T(_point(2)); - - T pos_tmp[3]; - // Rotate the point according the main rotation first - ceres::AngleAxisRotatePoint(RMain, pos_3dpoint, pos_tmp); - - // Apply the main translation first - pos_tmp[0] += tMain[0]; - pos_tmp[1] += tMain[1]; - pos_tmp[2] += tMain[2]; - - T pos_proj[3]; - // Rotate the point according the relative camera rotation - ceres::AngleAxisRotatePoint(RRelative, pos_tmp, pos_proj); - - // Apply the relative camera translation - pos_proj[0] += T(tRelative[0]); - pos_proj[1] += T(tRelative[1]); - pos_proj[2] += T(tRelative[2]); - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - const T focal = T(_K(0,0)); - const T principal_point_x = T(_K(0,2)); - const T principal_point_y = T(_K(1,2)); - const T k1 = T(_params[0]); - const T k2 = T(_params[1]); - const T k3 = T(_params[2]); - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r4 = r2 * r2; - const T r6 = r4 * r2; - const T r_coeff = (T(1) + k1*r2 + k2*r4 + k3*r6); - const T x_d = x_u * r_coeff; - const T y_d = y_u * r_coeff; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focal * x_d; - const T projected_y = principal_point_y + focal * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - out_residuals[0] = projected_x - _observation[0]; - out_residuals[1] = projected_y - _observation[1]; - - return true; - } - -private : - aliceVision::Mat3 _K; // Calibration matrix - std::vector _params; // {K1, K2, K3} - aliceVision::Vec3 _point; // 3D point - aliceVision::Vec2 _observation; // its image location - double _relativePose[6]; // the relative pose of the witness camera wrt - // in angle axis format the main camera + public: + ResidualErrorSecondaryCameraFixedRelativeFunctor(const camera::Pinhole& intrinsics, + const Vec2& pt2d, + const Vec3& pt3d, + const geometry::Pose3& relativePose) // const double* const pos_2dpoint + { + // Set the intrinsics + _K = intrinsics.K(); + + _params.reserve(3); + _params.push_back(intrinsics.getParams()[3]); + _params.push_back(intrinsics.getParams()[4]); + _params.push_back(intrinsics.getParams()[5]); + + // Set the observation + _observation[0] = pt2d[0]; + _observation[1] = pt2d[1]; + + // Set the 3D point + _point(0) = pt3d(0); + _point(1) = pt3d(1); + _point(2) = pt3d(2); + + const aliceVision::Mat3& R = relativePose.rotation(); + const aliceVision::Vec3& t = relativePose.translation(); + + // convert the relative pose into angle axis representation + ceres::RotationMatrixToAngleAxis((const double*)R.data(), _relativePose); + // and set the translation + _relativePose[3] = t(0); + _relativePose[4] = t(1); + _relativePose[5] = t(2); + } + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + // enum { + // OFFSET_FOCAL_LENGTH = 0, + // OFFSET_PRINCIPAL_POINT_X = 1, + // OFFSET_PRINCIPAL_POINT_Y = 2, + // OFFSET_DISTO_K1 = 3, + // OFFSET_DISTO_K2 = 4, + // OFFSET_DISTO_K3 = 5, + // }; + + /** + * @param[in] cam_Rt_main: Main camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_Rt_main, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + const T* RMain = cam_Rt_main; + const T* tMain = &cam_Rt_main[3]; + + T RRelative[3]; + RRelative[0] = T(_relativePose[0]); + RRelative[1] = T(_relativePose[1]); + RRelative[2] = T(_relativePose[2]); + T tRelative[3]; + tRelative[0] = T(_relativePose[3]); + tRelative[1] = T(_relativePose[4]); + tRelative[2] = T(_relativePose[5]); + + T pos_3dpoint[3]; + pos_3dpoint[0] = T(_point(0)); + pos_3dpoint[1] = T(_point(1)); + pos_3dpoint[2] = T(_point(2)); + + T pos_tmp[3]; + // Rotate the point according the main rotation first + ceres::AngleAxisRotatePoint(RMain, pos_3dpoint, pos_tmp); + + // Apply the main translation first + pos_tmp[0] += tMain[0]; + pos_tmp[1] += tMain[1]; + pos_tmp[2] += tMain[2]; + + T pos_proj[3]; + // Rotate the point according the relative camera rotation + ceres::AngleAxisRotatePoint(RRelative, pos_tmp, pos_proj); + + // Apply the relative camera translation + pos_proj[0] += T(tRelative[0]); + pos_proj[1] += T(tRelative[1]); + pos_proj[2] += T(tRelative[2]); + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + const T focal = T(_K(0, 0)); + const T principal_point_x = T(_K(0, 2)); + const T principal_point_y = T(_K(1, 2)); + const T k1 = T(_params[0]); + const T k2 = T(_params[1]); + const T k3 = T(_params[2]); + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r4 = r2 * r2; + const T r6 = r4 * r2; + const T r_coeff = (T(1) + k1 * r2 + k2 * r4 + k3 * r6); + const T x_d = x_u * r_coeff; + const T y_d = y_u * r_coeff; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focal * x_d; + const T projected_y = principal_point_y + focal * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + out_residuals[0] = projected_x - _observation[0]; + out_residuals[1] = projected_y - _observation[1]; + + return true; + } + + private: + aliceVision::Mat3 _K; // Calibration matrix + std::vector _params; // {K1, K2, K3} + aliceVision::Vec3 _point; // 3D point + aliceVision::Vec2 _observation; // its image location + double _relativePose[6]; // the relative pose of the witness camera wrt + // in angle axis format the main camera }; -} -} \ No newline at end of file +} // namespace rig +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/rig/Rig.cpp b/src/aliceVision/rig/Rig.cpp index 6de1f0bbe5..512dd2bf15 100644 --- a/src/aliceVision/rig/Rig.cpp +++ b/src/aliceVision/rig/Rig.cpp @@ -15,713 +15,681 @@ #include #ifdef VISUAL_DEBUG_MODE -#include + #include #endif namespace aliceVision { namespace rig { -Rig::~Rig() -{ -} +Rig::~Rig() {} -const std::size_t Rig::getRelativePosesSize() const -{ - return _vRelativePoses.size(); -} +const std::size_t Rig::getRelativePosesSize() const { return _vRelativePoses.size(); } -const geometry::Pose3& Rig::getRelativePose(std::size_t i) const -{ - return _vRelativePoses.at(i-1); -} +const geometry::Pose3& Rig::getRelativePose(std::size_t i) const { return _vRelativePoses.at(i - 1); } -const std::vector& Rig::getRelativePoses() const -{ - return _vRelativePoses; -} +const std::vector& Rig::getRelativePoses() const { return _vRelativePoses; } -const geometry::Pose3 & Rig::getPose(std::size_t i) const -{ - return _vPoses.at(i); -} +const geometry::Pose3& Rig::getPose(std::size_t i) const { return _vPoses.at(i); } -const std::size_t Rig::getPosesSize( ) const -{ - return _vPoses.size(); -} +const std::size_t Rig::getPosesSize() const { return _vPoses.size(); } -const std::vector & Rig::getPoses( ) const -{ - return _vPoses; -} +const std::vector& Rig::getPoses() const { return _vPoses; } -void Rig::setTrackingResult( - std::vector vLocalizationResults, - std::size_t i) +void Rig::setTrackingResult(std::vector vLocalizationResults, std::size_t i) { - _vLocalizationResults.emplace(i, vLocalizationResults); + _vLocalizationResults.emplace(i, vLocalizationResults); } bool Rig::initializeCalibration() { - if(_isInitialized) - { - ALICEVISION_LOG_DEBUG("The rig is already initialized"); - return _isInitialized; - } - // check that there are cameras - assert(!_vLocalizationResults.empty()); - - // make all the cameras have the same number of localizationResults (equal to the shortest clip) - const std::size_t nCams = _vLocalizationResults.size(); - { - std::size_t shortestSeqLength = std::numeric_limits::max(); - for (const auto& v: _vLocalizationResults) - shortestSeqLength = std::min(shortestSeqLength, v.second.size()); - for (auto& v: _vLocalizationResults) - v.second.resize(shortestSeqLength); - - if(shortestSeqLength == 0) + if (_isInitialized) { - ALICEVISION_LOG_DEBUG("The calibration results are empty!"); - return false; + ALICEVISION_LOG_DEBUG("The rig is already initialized"); + return _isInitialized; } - } - - // Tracker of the main cameras - std::vector & resMainCamera = _vLocalizationResults[0]; - - // Clear all relative poses - _vRelativePoses.clear(); - _vRelativePoses.reserve(nCams-1); - - // Loop over all witness cameras - for(std::size_t iLocalizer=1 ; iLocalizer < nCams ; ++iLocalizer) - { - // Perform the pose averaging over all relative pose between the main camera - // (index 0) and the witness camera (index i) - std::vector & resWitnessCamera = _vLocalizationResults[iLocalizer]; - - // vRelativePoses will store all the relative poses overall frames where both - // the pose computation of the main camera and witness camera succeed - std::vector vRelativePoses; - vRelativePoses.reserve(resWitnessCamera.size()); - - for(std::size_t iView=0 ; iView < resWitnessCamera.size() ; ++iView ) + // check that there are cameras + assert(!_vLocalizationResults.empty()); + + // make all the cameras have the same number of localizationResults (equal to the shortest clip) + const std::size_t nCams = _vLocalizationResults.size(); { - // Check that both pose computations succeeded - if ( resMainCamera[iView].isValid() && resWitnessCamera[iView].isValid() ) - { - // q0 ~ [poseMain] Q - // q1 ~ [relative][poseMain] Q = [poseWitness]Q - // [relative] = [poseWitness]*inv([poseMain]) - vRelativePoses.push_back(computeRelativePose(resMainCamera[iView].getPose(), - resWitnessCamera[iView].getPose())); - } + std::size_t shortestSeqLength = std::numeric_limits::max(); + for (const auto& v : _vLocalizationResults) + shortestSeqLength = std::min(shortestSeqLength, v.second.size()); + for (auto& v : _vLocalizationResults) + v.second.resize(shortestSeqLength); + + if (shortestSeqLength == 0) + { + ALICEVISION_LOG_DEBUG("The calibration results are empty!"); + return false; + } } - if(vRelativePoses.empty()) + + // Tracker of the main cameras + std::vector& resMainCamera = _vLocalizationResults[0]; + + // Clear all relative poses + _vRelativePoses.clear(); + _vRelativePoses.reserve(nCams - 1); + + // Loop over all witness cameras + for (std::size_t iLocalizer = 1; iLocalizer < nCams; ++iLocalizer) { - ALICEVISION_CERR("Unable to find candidate poses between the main camera and " - "the witness camera " << iLocalizer << "\nAborting..."); - return false; + // Perform the pose averaging over all relative pose between the main camera + // (index 0) and the witness camera (index i) + std::vector& resWitnessCamera = _vLocalizationResults[iLocalizer]; + + // vRelativePoses will store all the relative poses overall frames where both + // the pose computation of the main camera and witness camera succeed + std::vector vRelativePoses; + vRelativePoses.reserve(resWitnessCamera.size()); + + for (std::size_t iView = 0; iView < resWitnessCamera.size(); ++iView) + { + // Check that both pose computations succeeded + if (resMainCamera[iView].isValid() && resWitnessCamera[iView].isValid()) + { + // q0 ~ [poseMain] Q + // q1 ~ [relative][poseMain] Q = [poseWitness]Q + // [relative] = [poseWitness]*inv([poseMain]) + vRelativePoses.push_back(computeRelativePose(resMainCamera[iView].getPose(), resWitnessCamera[iView].getPose())); + } + } + if (vRelativePoses.empty()) + { + ALICEVISION_CERR("Unable to find candidate poses between the main camera and " + "the witness camera " + << iLocalizer << "\nAborting..."); + return false; + } + geometry::Pose3 optimalRelativePose; + findBestRelativePose(vRelativePoses, iLocalizer, optimalRelativePose); + + // poseAveraging(vRelativePoses, averageRelativePose); + _vRelativePoses.push_back(optimalRelativePose); } - geometry::Pose3 optimalRelativePose; - findBestRelativePose(vRelativePoses, iLocalizer, optimalRelativePose ); - - //poseAveraging(vRelativePoses, averageRelativePose); - _vRelativePoses.push_back(optimalRelativePose); - } - - // Update all poses in all localization results - for(std::size_t iRelativePose = 0 ; iRelativePose < _vRelativePoses.size() ; ++iRelativePose ) - { - std::size_t iRes = iRelativePose+1; - for(std::size_t iView = 0 ; iView < _vLocalizationResults[iRes].size() ; ++iView ) + + // Update all poses in all localization results + for (std::size_t iRelativePose = 0; iRelativePose < _vRelativePoses.size(); ++iRelativePose) { - if( _vLocalizationResults[0][iView].isValid() ) - { - const geometry::Pose3 & relativePose = _vRelativePoses[iRelativePose]; - - const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(_vLocalizationResults[0][iView].getPose(), relativePose); - _vLocalizationResults[iRes][iView].setPose(poseWitnessCamera); - - } + std::size_t iRes = iRelativePose + 1; + for (std::size_t iView = 0; iView < _vLocalizationResults[iRes].size(); ++iView) + { + if (_vLocalizationResults[0][iView].isValid()) + { + const geometry::Pose3& relativePose = _vRelativePoses[iRelativePose]; + + const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(_vLocalizationResults[0][iView].getPose(), relativePose); + _vLocalizationResults[iRes][iView].setPose(poseWitnessCamera); + } + } } - } - _isInitialized = true; - return _isInitialized; + _isInitialized = true; + return _isInitialized; } // From a set of relative pose, find the optimal one for a given tracker iTraker which // minimize the reprojection errors over all images -void Rig::findBestRelativePose( - const std::vector & vPoses, - std::size_t iLocalizer, - geometry::Pose3 & result ) +void Rig::findBestRelativePose(const std::vector& vPoses, std::size_t iLocalizer, geometry::Pose3& result) { - assert(iLocalizer < _vLocalizationResults.size()); - assert(_vLocalizationResults.size() > 0); - - const std::vector & resMainCamera = _vLocalizationResults[0]; - const std::vector & resWitnessCamera = _vLocalizationResults[iLocalizer]; - - double minReprojError = std::numeric_limits::max(); - std::size_t iMin = 0; - - assert(vPoses.size() > 0); - - for(std::size_t i=0 ; i < vPoses.size() ; ++i) - { - const geometry::Pose3 & relativePose = vPoses[i]; - - double error = 0; - for(std::size_t j=0 ; j < resWitnessCamera.size() ; ++j ) + assert(iLocalizer < _vLocalizationResults.size()); + assert(_vLocalizationResults.size() > 0); + + const std::vector& resMainCamera = _vLocalizationResults[0]; + const std::vector& resWitnessCamera = _vLocalizationResults[iLocalizer]; + + double minReprojError = std::numeric_limits::max(); + std::size_t iMin = 0; + + assert(vPoses.size() > 0); + + for (std::size_t i = 0; i < vPoses.size(); ++i) { - // Check that both pose computations succeeded - if ( ( resMainCamera[j].isValid() ) && ( resWitnessCamera[j].isValid() ) ) - { - // [poseWitness] = [relativePose]*[poseMainCamera] - const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(resMainCamera[j].getPose(), relativePose); - error += reprojectionError(resWitnessCamera[j], poseWitnessCamera); - } + const geometry::Pose3& relativePose = vPoses[i]; + + double error = 0; + for (std::size_t j = 0; j < resWitnessCamera.size(); ++j) + { + // Check that both pose computations succeeded + if ((resMainCamera[j].isValid()) && (resWitnessCamera[j].isValid())) + { + // [poseWitness] = [relativePose]*[poseMainCamera] + const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(resMainCamera[j].getPose(), relativePose); + error += reprojectionError(resWitnessCamera[j], poseWitnessCamera); + } + } + if (error < minReprojError) + { + iMin = i; + minReprojError = error; + } } - if ( error < minReprojError ) - { - iMin = i; - minReprojError = error; - } - } - result = vPoses[iMin]; - - displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); - displayRelativePoseReprojection(result, iLocalizer); + result = vPoses[iMin]; + + displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); + displayRelativePoseReprojection(result, iLocalizer); } // Display reprojection error based on a relative pose -void Rig::displayRelativePoseReprojection(const geometry::Pose3 & relativePose, std::size_t iLocalizer) +void Rig::displayRelativePoseReprojection(const geometry::Pose3& relativePose, std::size_t iLocalizer) { #ifdef VISUAL_DEBUG_MODE - std::vector & mainLocalizerResults = _vLocalizationResults[0]; - std::vector & witnessLocalizerResults = _vLocalizationResults[iLocalizer]; - - // Set the marker size - std::size_t semiWidth = 3.0; - std::size_t marginUndisto = 200; - - for(int iView=0 ; iView < witnessLocalizerResults.size() ; ++iView ) - { - // Check that both pose computations succeed - if ( witnessLocalizerResults[iView].isValid() ) + std::vector& mainLocalizerResults = _vLocalizationResults[0]; + std::vector& witnessLocalizerResults = _vLocalizationResults[iLocalizer]; + + // Set the marker size + std::size_t semiWidth = 3.0; + std::size_t marginUndisto = 200; + + for (int iView = 0; iView < witnessLocalizerResults.size(); ++iView) { - const std::size_t width = witnessLocalizerResults[iView].getIntrinsics()._w; - const std::size_t height = witnessLocalizerResults[iView].getIntrinsics()._h; - - // Window to display reprojection errors - cv::Mat imgRes(height+2*marginUndisto, width+2*marginUndisto, CV_8UC3); - imgRes = cv::Scalar(255,255,255); - cvNamedWindow("Reprojection", CV_WINDOW_NORMAL); - cv::moveWindow("Reprojection",0,0); - cv::resizeWindow("Reprojection", width/2, height/2); - cv::imshow("Reprojection", imgRes); - - const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(mainLocalizerResults[iView].getPose(), relativePose); - - const aliceVision::Mat points2D = witnessLocalizerResults[iView].getPt2D(); - const aliceVision::Mat points3D = witnessLocalizerResults[iView].getPt3D(); - - for(const IndexT iInlier : witnessLocalizerResults[iView].getInliers()) - { - // Reprojections - const Vec3 & point3D = points3D.col(iInlier); - // Its reprojection - Vec2 itsReprojection = witnessLocalizerResults[iView].getIntrinsics().project(poseWitnessCamera, point3D); - // Its associated observation location - const Vec2 & point2D = points2D.col(iInlier); - - // Display reprojections and observations - cv::rectangle(imgRes, - cvPoint(marginUndisto+point2D(0)-semiWidth,marginUndisto+point2D(1)-semiWidth), - cvPoint(marginUndisto+point2D(0)+semiWidth,marginUndisto+point2D(1)+semiWidth), - cv::Scalar(0,255,0)); - - cv::rectangle(imgRes, - cvPoint(marginUndisto+itsReprojection(0)-semiWidth,marginUndisto+itsReprojection(1)-semiWidth), - cvPoint(marginUndisto+itsReprojection(0)+semiWidth,marginUndisto+itsReprojection(1)+semiWidth), - cv::Scalar(255,0,0)); - } - cv::imshow("Reprojection", imgRes); - cvpause(); + // Check that both pose computations succeed + if (witnessLocalizerResults[iView].isValid()) + { + const std::size_t width = witnessLocalizerResults[iView].getIntrinsics()._w; + const std::size_t height = witnessLocalizerResults[iView].getIntrinsics()._h; + + // Window to display reprojection errors + cv::Mat imgRes(height + 2 * marginUndisto, width + 2 * marginUndisto, CV_8UC3); + imgRes = cv::Scalar(255, 255, 255); + cvNamedWindow("Reprojection", CV_WINDOW_NORMAL); + cv::moveWindow("Reprojection", 0, 0); + cv::resizeWindow("Reprojection", width / 2, height / 2); + cv::imshow("Reprojection", imgRes); + + const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(mainLocalizerResults[iView].getPose(), relativePose); + + const aliceVision::Mat points2D = witnessLocalizerResults[iView].getPt2D(); + const aliceVision::Mat points3D = witnessLocalizerResults[iView].getPt3D(); + + for (const IndexT iInlier : witnessLocalizerResults[iView].getInliers()) + { + // Reprojections + const Vec3& point3D = points3D.col(iInlier); + // Its reprojection + Vec2 itsReprojection = witnessLocalizerResults[iView].getIntrinsics().project(poseWitnessCamera, point3D); + // Its associated observation location + const Vec2& point2D = points2D.col(iInlier); + + // Display reprojections and observations + cv::rectangle(imgRes, + cvPoint(marginUndisto + point2D(0) - semiWidth, marginUndisto + point2D(1) - semiWidth), + cvPoint(marginUndisto + point2D(0) + semiWidth, marginUndisto + point2D(1) + semiWidth), + cv::Scalar(0, 255, 0)); + + cv::rectangle(imgRes, + cvPoint(marginUndisto + itsReprojection(0) - semiWidth, marginUndisto + itsReprojection(1) - semiWidth), + cvPoint(marginUndisto + itsReprojection(0) + semiWidth, marginUndisto + itsReprojection(1) + semiWidth), + cv::Scalar(255, 0, 0)); + } + cv::imshow("Reprojection", imgRes); + cvpause(); + } } - } #endif } bool Rig::optimizeCalibration() { - if(!_isInitialized) - { - ALICEVISION_LOG_DEBUG("The rig is yet initialized"); - return _isInitialized; - } - - assert(_vRelativePoses.size() > 0); - assert(_vLocalizationResults.size() > 0); - - ceres::Problem problem; - - // Add relative pose as a parameter block over all witness cameras. - std::vector > vRelativePoses; - vRelativePoses.reserve(_vRelativePoses.size()); - for(std::size_t iRelativePose = 0 ; iRelativePose < _vRelativePoses.size() ; ++iRelativePose ) - { - geometry::Pose3 & pose = _vRelativePoses[iRelativePose]; - - const aliceVision::Mat3 & R = pose.rotation(); - const aliceVision::Vec3 & t = pose.translation(); - - double angleAxis[3]; - ceres::RotationMatrixToAngleAxis((const double*)R.data(), angleAxis); - - std::vector relativePose; - relativePose.reserve(6); //angleAxis + translation - relativePose.push_back(angleAxis[0]); - relativePose.push_back(angleAxis[1]); - relativePose.push_back(angleAxis[2]); - relativePose.push_back(t(0)); - relativePose.push_back(t(1)); - relativePose.push_back(t(2)); - - vRelativePoses.push_back(relativePose); - } - for(std::vector &pose : vRelativePoses) - { - double * parameter_block = &pose[0]; - assert(parameter_block && "parameter_block is null in vRelativePoses"); - problem.AddParameterBlock(parameter_block, 6); - } - - // Add rig pose (i.e. main camera pose) as a parameter block over all views (i.e. timestamps). - std::map > vMainPoses; - for(std::size_t iView = 0 ; iView < _vLocalizationResults[0].size() ; ++iView ) - { - if(!_vLocalizationResults[0][iView].isValid()) - continue; - - const geometry::Pose3 & pose = _vLocalizationResults[0][iView].getPose(); - const aliceVision::Mat3 & R = pose.rotation(); - const aliceVision::Vec3 & t = pose.translation(); - - double angleAxis[3]; - ceres::RotationMatrixToAngleAxis((const double*)R.data(), angleAxis); - - std::vector mainPose; - mainPose.reserve(6); //angleAxis + translation - mainPose.push_back(angleAxis[0]); - mainPose.push_back(angleAxis[1]); - mainPose.push_back(angleAxis[2]); - mainPose.push_back(t(0)); - mainPose.push_back(t(1)); - mainPose.push_back(t(2)); - - vMainPoses.emplace(iView, mainPose); - } - - for(auto &elem : vMainPoses) - { - auto pose = elem.second; - double * parameter_block = &pose[0]; - assert(parameter_block && "parameter_block is null in vMainPoses"); - problem.AddParameterBlock(parameter_block, 6); - } - -// The following code can be used if the intrinsics have to be refined in the bundle adjustment - -//for (Intrinsics::const_iterator itIntrinsic = sfm_data.getIntrinsics().begin(); -// itIntrinsic != sfm_data.getIntrinsics().end(); ++itIntrinsic) -//{ -// for (int iLocalizer = 0 ; iLocalizer < _vLocalizationResults.size() ; ++iLocalizer ) -// { -// -// for (int iView = 0 ; iView < _vLocalizationResults[iLocalizer].size() ; ++iView ) -// { -// const localization::LocalizationResult & localizationResult = _vLocalizationResults[iLocalizer][iView]; -// -// if ( localizationResult.isValid() ) -// { -// std::vector intrinsics; -// -// switch (localizationResult.getIntrinsics().getType()) -// { -// case PINHOLE_CAMERA: -// { -// std::vector vec_params = localizationResult.getIntrinsics().getParams(); -// intrinsics.reserve(3); -// intrinsics.push_back(vec_params[0]); -// intrinsics.push_back(vec_params[1]); -// intrinsics.push_back(vec_params[2]); -// -// double * parameter_block = &intrinsics[0]; -// problem.AddParameterBlock(parameter_block, 3); -// if (true) //(!bRefineIntrinsics) -// { -// //set the whole parameter block as constant for best performance. -// problem.SetParameterBlockConstant(parameter_block); -// } -// } -// case PINHOLE_CAMERA_RADIAL3 -// { -// std::vector vec_params = localizationResult.getIntrinsics().getParams(); -// intrinsics.reserve(6); -// intrinsics.push_back(vec_params[0]); -// intrinsics.push_back(vec_params[1]); -// intrinsics.push_back(vec_params[2]); -// intrinsics.push_back(vec_params[3]); -// intrinsics.push_back(vec_params[4]); -// intrinsics.push_back(vec_params[5]); -// -// double * parameter_block = &intrinsics[0]; -// problem.AddParameterBlock(parameter_block, 6); -// if (true) //(!bRefineIntrinsics) -// { -// //set the whole parameter block as constant for best performance. -// problem.SetParameterBlockConstant(parameter_block); -// } -// } -// } -// } -// } -// } - - // Set a LossFunction to be less penalized by false measurements - // - set it to NULL if you don't want use a lossFunction. - ceres::LossFunction * p_LossFunction = nullptr;//new ceres::HuberLoss(Square(4.0)); - // todo: make the LOSS function and the parameter an option - - // For all visibility add reprojections errors: - for(std::size_t iLocalizer = 0 ; iLocalizer < _vLocalizationResults.size() ; ++iLocalizer) - { - const std::vector & currentResult = _vLocalizationResults[iLocalizer]; - - for(std::size_t iView = 0 ; iView < currentResult.size() ; ++iView) + if (!_isInitialized) { - // if the current localization is not valid skip it - if(!currentResult[iView].isValid()) - continue; - - // if it is not the main camera and the same view was not localized for the - // main camera, skip it - if( (iLocalizer != 0) && (!_vLocalizationResults[0][iView].isValid()) ) - continue; - - // Get the inliers 3D points - const Mat & points3D = currentResult[iView].getPt3D(); - // Get their image locations (also referred as observations) - const Mat & points2D = currentResult[iView].getPt2D(); - - // Add a residual block for all inliers - for(const IndexT iPoint : currentResult[iView].getInliers() ) - { - // Each Residual block takes a point and a camera as input and outputs a 2 - // dimensional residual. Internally, the cost function stores the observations - // and the 3D point and compares the reprojection against the observation. - ceres::CostFunction* cost_function; - - // Add a residual block for the main camera - if ( iLocalizer == 0 ) - { - // Vector-2 residual, pose of the rig parameterized by 6 parameters - cost_function = new ceres::AutoDiffCostFunction( - new ResidualErrorMainCameraFunctor(currentResult[iView].getIntrinsics(), points2D.col(iPoint), points3D.col(iPoint) )); - - if (cost_function) - { - assert(vMainPoses.find(iView) != vMainPoses.end()); - problem.AddResidualBlock( cost_function, - p_LossFunction, - &vMainPoses[iView][0]); - }else - { - ALICEVISION_CERR("Fail in adding residual block for the main camera"); - } - - }else - // Add a residual block for a secondary camera + ALICEVISION_LOG_DEBUG("The rig is yet initialized"); + return _isInitialized; + } + + assert(_vRelativePoses.size() > 0); + assert(_vLocalizationResults.size() > 0); + + ceres::Problem problem; + + // Add relative pose as a parameter block over all witness cameras. + std::vector> vRelativePoses; + vRelativePoses.reserve(_vRelativePoses.size()); + for (std::size_t iRelativePose = 0; iRelativePose < _vRelativePoses.size(); ++iRelativePose) + { + geometry::Pose3& pose = _vRelativePoses[iRelativePose]; + + const aliceVision::Mat3& R = pose.rotation(); + const aliceVision::Vec3& t = pose.translation(); + + double angleAxis[3]; + ceres::RotationMatrixToAngleAxis((const double*)R.data(), angleAxis); + + std::vector relativePose; + relativePose.reserve(6); // angleAxis + translation + relativePose.push_back(angleAxis[0]); + relativePose.push_back(angleAxis[1]); + relativePose.push_back(angleAxis[2]); + relativePose.push_back(t(0)); + relativePose.push_back(t(1)); + relativePose.push_back(t(2)); + + vRelativePoses.push_back(relativePose); + } + for (std::vector& pose : vRelativePoses) + { + double* parameter_block = &pose[0]; + assert(parameter_block && "parameter_block is null in vRelativePoses"); + problem.AddParameterBlock(parameter_block, 6); + } + + // Add rig pose (i.e. main camera pose) as a parameter block over all views (i.e. timestamps). + std::map> vMainPoses; + for (std::size_t iView = 0; iView < _vLocalizationResults[0].size(); ++iView) + { + if (!_vLocalizationResults[0][iView].isValid()) + continue; + + const geometry::Pose3& pose = _vLocalizationResults[0][iView].getPose(); + const aliceVision::Mat3& R = pose.rotation(); + const aliceVision::Vec3& t = pose.translation(); + + double angleAxis[3]; + ceres::RotationMatrixToAngleAxis((const double*)R.data(), angleAxis); + + std::vector mainPose; + mainPose.reserve(6); // angleAxis + translation + mainPose.push_back(angleAxis[0]); + mainPose.push_back(angleAxis[1]); + mainPose.push_back(angleAxis[2]); + mainPose.push_back(t(0)); + mainPose.push_back(t(1)); + mainPose.push_back(t(2)); + + vMainPoses.emplace(iView, mainPose); + } + + for (auto& elem : vMainPoses) + { + auto pose = elem.second; + double* parameter_block = &pose[0]; + assert(parameter_block && "parameter_block is null in vMainPoses"); + problem.AddParameterBlock(parameter_block, 6); + } + + // The following code can be used if the intrinsics have to be refined in the bundle adjustment + + // for (Intrinsics::const_iterator itIntrinsic = sfm_data.getIntrinsics().begin(); + // itIntrinsic != sfm_data.getIntrinsics().end(); ++itIntrinsic) + //{ + // for (int iLocalizer = 0 ; iLocalizer < _vLocalizationResults.size() ; ++iLocalizer ) + // { + // + // for (int iView = 0 ; iView < _vLocalizationResults[iLocalizer].size() ; ++iView ) + // { + // const localization::LocalizationResult & localizationResult = _vLocalizationResults[iLocalizer][iView]; + // + // if ( localizationResult.isValid() ) + // { + // std::vector intrinsics; + // + // switch (localizationResult.getIntrinsics().getType()) + // { + // case PINHOLE_CAMERA: + // { + // std::vector vec_params = localizationResult.getIntrinsics().getParams(); + // intrinsics.reserve(3); + // intrinsics.push_back(vec_params[0]); + // intrinsics.push_back(vec_params[1]); + // intrinsics.push_back(vec_params[2]); + // + // double * parameter_block = &intrinsics[0]; + // problem.AddParameterBlock(parameter_block, 3); + // if (true) //(!bRefineIntrinsics) + // { + // //set the whole parameter block as constant for best performance. + // problem.SetParameterBlockConstant(parameter_block); + // } + // } + // case PINHOLE_CAMERA_RADIAL3 + // { + // std::vector vec_params = localizationResult.getIntrinsics().getParams(); + // intrinsics.reserve(6); + // intrinsics.push_back(vec_params[0]); + // intrinsics.push_back(vec_params[1]); + // intrinsics.push_back(vec_params[2]); + // intrinsics.push_back(vec_params[3]); + // intrinsics.push_back(vec_params[4]); + // intrinsics.push_back(vec_params[5]); + // + // double * parameter_block = &intrinsics[0]; + // problem.AddParameterBlock(parameter_block, 6); + // if (true) //(!bRefineIntrinsics) + // { + // //set the whole parameter block as constant for best performance. + // problem.SetParameterBlockConstant(parameter_block); + // } + // } + // } + // } + // } + // } + + // Set a LossFunction to be less penalized by false measurements + // - set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* p_LossFunction = nullptr; // new ceres::HuberLoss(Square(4.0)); + // todo: make the LOSS function and the parameter an option + + // For all visibility add reprojections errors: + for (std::size_t iLocalizer = 0; iLocalizer < _vLocalizationResults.size(); ++iLocalizer) + { + const std::vector& currentResult = _vLocalizationResults[iLocalizer]; + + for (std::size_t iView = 0; iView < currentResult.size(); ++iView) { - // Vector-2 residual, pose of the rig parameterized by 6 parameters - // + relative pose of the secondary camera parameterized by 6 parameters - - cost_function = new ceres::AutoDiffCostFunction( - new ResidualErrorSecondaryCameraFunctor(currentResult[iView].getIntrinsics(), points2D.col(iPoint), points3D.col(iPoint))); - - if (cost_function) - { - assert(vMainPoses.find(iView) != vMainPoses.end()); - assert(iLocalizer-1 < vRelativePoses.size()); - problem.AddResidualBlock( cost_function, - p_LossFunction, - &vMainPoses[iView][0], - &vRelativePoses[iLocalizer-1][0]); - }else - { - ALICEVISION_CERR("Fail in adding residual block for a secondary camera"); - } + // if the current localization is not valid skip it + if (!currentResult[iView].isValid()) + continue; + + // if it is not the main camera and the same view was not localized for the + // main camera, skip it + if ((iLocalizer != 0) && (!_vLocalizationResults[0][iView].isValid())) + continue; + + // Get the inliers 3D points + const Mat& points3D = currentResult[iView].getPt3D(); + // Get their image locations (also referred as observations) + const Mat& points2D = currentResult[iView].getPt2D(); + + // Add a residual block for all inliers + for (const IndexT iPoint : currentResult[iView].getInliers()) + { + // Each Residual block takes a point and a camera as input and outputs a 2 + // dimensional residual. Internally, the cost function stores the observations + // and the 3D point and compares the reprojection against the observation. + ceres::CostFunction* cost_function; + + // Add a residual block for the main camera + if (iLocalizer == 0) + { + // Vector-2 residual, pose of the rig parameterized by 6 parameters + cost_function = new ceres::AutoDiffCostFunction( + new ResidualErrorMainCameraFunctor(currentResult[iView].getIntrinsics(), points2D.col(iPoint), points3D.col(iPoint))); + + if (cost_function) + { + assert(vMainPoses.find(iView) != vMainPoses.end()); + problem.AddResidualBlock(cost_function, p_LossFunction, &vMainPoses[iView][0]); + } + else + { + ALICEVISION_CERR("Fail in adding residual block for the main camera"); + } + } + else + // Add a residual block for a secondary camera + { + // Vector-2 residual, pose of the rig parameterized by 6 parameters + // + relative pose of the secondary camera parameterized by 6 parameters + + cost_function = new ceres::AutoDiffCostFunction( + new ResidualErrorSecondaryCameraFunctor(currentResult[iView].getIntrinsics(), points2D.col(iPoint), points3D.col(iPoint))); + + if (cost_function) + { + assert(vMainPoses.find(iView) != vMainPoses.end()); + assert(iLocalizer - 1 < vRelativePoses.size()); + problem.AddResidualBlock(cost_function, p_LossFunction, &vMainPoses[iView][0], &vRelativePoses[iLocalizer - 1][0]); + } + else + { + ALICEVISION_CERR("Fail in adding residual block for a secondary camera"); + } + } + } } - } } - } - - // Configure a BA engine and run it - // todo: Set the most appropriate options - aliceVision::sfm::BundleAdjustmentCeres::CeresOptions aliceVision_options(true); - - ceres::Solver::Options options; - - options.preconditioner_type = aliceVision_options.preconditionerType; - options.linear_solver_type = aliceVision_options.linearSolverType; - options.sparse_linear_algebra_library_type = aliceVision_options.sparseLinearAlgebraLibraryType; - options.minimizer_progress_to_stdout = aliceVision_options.verbose; - options.logging_type = ceres::SILENT; - options.num_threads = 1;//aliceVision_options._nbThreads; + + // Configure a BA engine and run it + // todo: Set the most appropriate options + aliceVision::sfm::BundleAdjustmentCeres::CeresOptions aliceVision_options(true); + + ceres::Solver::Options options; + + options.preconditioner_type = aliceVision_options.preconditionerType; + options.linear_solver_type = aliceVision_options.linearSolverType; + options.sparse_linear_algebra_library_type = aliceVision_options.sparseLinearAlgebraLibraryType; + options.minimizer_progress_to_stdout = aliceVision_options.verbose; + options.logging_type = ceres::SILENT; + options.num_threads = 1; // aliceVision_options._nbThreads; #if CERES_VERSION_MAJOR < 2 - options.num_linear_solver_threads = 1;//aliceVision_options._nbThreads; + options.num_linear_solver_threads = 1; // aliceVision_options._nbThreads; #endif - - // Solve BA - ceres::Solver::Summary summary; - ceres::Solve(options, &problem, &summary); - - if (aliceVision_options.summary) - ALICEVISION_LOG_DEBUG(summary.FullReport()); - - // If no error, get back refined parameters - if (!summary.IsSolutionUsable()) - { + + // Solve BA + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + if (aliceVision_options.summary) + ALICEVISION_LOG_DEBUG(summary.FullReport()); + + // If no error, get back refined parameters + if (!summary.IsSolutionUsable()) + { + if (aliceVision_options.verbose) + ALICEVISION_LOG_DEBUG("Bundle Adjustment failed."); + return false; + } + if (aliceVision_options.verbose) - ALICEVISION_LOG_DEBUG("Bundle Adjustment failed."); - return false; - } - - if (aliceVision_options.verbose) - { - // Display statistics about the minimization - ALICEVISION_LOG_DEBUG("Bundle Adjustment statistics (approximated RMSE):"); - ALICEVISION_LOG_DEBUG(" #localizers: " << _vLocalizationResults.size()); - ALICEVISION_LOG_DEBUG(" #views: " << _vLocalizationResults[0].size()); - ALICEVISION_LOG_DEBUG(" #residuals: " << summary.num_residuals); - ALICEVISION_LOG_DEBUG(" Initial RMSE: " << std::sqrt( summary.initial_cost / summary.num_residuals)); - ALICEVISION_LOG_DEBUG(" Final RMSE: " << std::sqrt( summary.final_cost / summary.num_residuals)); - } - - // Update relative pose after optimization - for(std::size_t iRelativePose = 0 ; iRelativePose < _vRelativePoses.size() ; ++iRelativePose) - { - aliceVision::Mat3 R_refined; - std::vector vPose; - vPose.reserve(6); - ceres::AngleAxisToRotationMatrix(&vRelativePoses[iRelativePose][0], R_refined.data()); - aliceVision::Vec3 t_refined(vRelativePoses[iRelativePose][3], vRelativePoses[iRelativePose][4], vRelativePoses[iRelativePose][5]); - // Update the pose - geometry::Pose3 & pose = _vRelativePoses[iRelativePose]; - pose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); - } - - // Update the main camera pose after optimization - for(std::size_t iView = 0 ; iView < _vLocalizationResults[0].size() ; ++iView) - { - if( _vLocalizationResults[0][iView].isValid() ) { - aliceVision::Mat3 R_refined; - std::vector vPose; - vPose.reserve(6); - ceres::AngleAxisToRotationMatrix(&vMainPoses[iView][0], R_refined.data()); - aliceVision::Vec3 t_refined(vMainPoses[iView][3], vMainPoses[iView][4], vMainPoses[iView][5]); - // Push the optimized pose - geometry::Pose3 pose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); - _vLocalizationResults[0][iView].setPose(pose); - _vPoses.push_back(pose); + // Display statistics about the minimization + ALICEVISION_LOG_DEBUG("Bundle Adjustment statistics (approximated RMSE):"); + ALICEVISION_LOG_DEBUG(" #localizers: " << _vLocalizationResults.size()); + ALICEVISION_LOG_DEBUG(" #views: " << _vLocalizationResults[0].size()); + ALICEVISION_LOG_DEBUG(" #residuals: " << summary.num_residuals); + ALICEVISION_LOG_DEBUG(" Initial RMSE: " << std::sqrt(summary.initial_cost / summary.num_residuals)); + ALICEVISION_LOG_DEBUG(" Final RMSE: " << std::sqrt(summary.final_cost / summary.num_residuals)); } - } - - displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); - - // Update all poses over all witness cameras after optimization - for(std::size_t iRelativePose = 0; iRelativePose < _vRelativePoses.size(); ++iRelativePose) - { - const std::size_t iLocalizer = iRelativePose+1; - // Loop over all views - for(std::size_t iView = 0; iView < _vLocalizationResults[iLocalizer].size(); ++iView) + + // Update relative pose after optimization + for (std::size_t iRelativePose = 0; iRelativePose < _vRelativePoses.size(); ++iRelativePose) { - // If the localization has succeeded then if the witness camera localization succeeded - // then update the pose else continue. - if( _vLocalizationResults[0][iView].isValid() && _vLocalizationResults[iLocalizer][iView].isValid() ) - { - // Retrieve the witness camera pose from the main camera one. - const geometry::Pose3 poseWitnessCamera = poseFromMainToWitness(_vLocalizationResults[0][iView].getPose(), _vRelativePoses[iRelativePose]); - _vLocalizationResults[iLocalizer][iView].setPose(poseWitnessCamera); - } + aliceVision::Mat3 R_refined; + std::vector vPose; + vPose.reserve(6); + ceres::AngleAxisToRotationMatrix(&vRelativePoses[iRelativePose][0], R_refined.data()); + aliceVision::Vec3 t_refined(vRelativePoses[iRelativePose][3], vRelativePoses[iRelativePose][4], vRelativePoses[iRelativePose][5]); + // Update the pose + geometry::Pose3& pose = _vRelativePoses[iRelativePose]; + pose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); } - displayRelativePoseReprojection(_vRelativePoses[iRelativePose], iLocalizer); - } - // Possibility to update the intrinsics here - - // Update camera intrinsics with refined data - // for (Intrinsics::iterator itIntrinsic = sfm_data.getIntrinsics().begin(); - // itIntrinsic != sfm_data.getIntrinsics().end(); ++itIntrinsic) - // { - // const IndexT indexCam = itIntrinsic->first; - // - // const std::vector & vec_params = map_intrinsics[indexCam]; - // itIntrinsic->second.get()->updateFromParams(vec_params); - // } - return true; -} + // Update the main camera pose after optimization + for (std::size_t iView = 0; iView < _vLocalizationResults[0].size(); ++iView) + { + if (_vLocalizationResults[0][iView].isValid()) + { + aliceVision::Mat3 R_refined; + std::vector vPose; + vPose.reserve(6); + ceres::AngleAxisToRotationMatrix(&vMainPoses[iView][0], R_refined.data()); + aliceVision::Vec3 t_refined(vMainPoses[iView][3], vMainPoses[iView][4], vMainPoses[iView][5]); + // Push the optimized pose + geometry::Pose3 pose = geometry::Pose3(R_refined, -R_refined.transpose() * t_refined); + _vLocalizationResults[0][iView].setPose(pose); + _vPoses.push_back(pose); + } + } -bool Rig::saveCalibration(const std::string &filename) -{ - return saveRigCalibration(filename, _vRelativePoses); + displayRelativePoseReprojection(geometry::Pose3(aliceVision::Mat3::Identity(), aliceVision::Vec3::Zero()), 0); + + // Update all poses over all witness cameras after optimization + for (std::size_t iRelativePose = 0; iRelativePose < _vRelativePoses.size(); ++iRelativePose) + { + const std::size_t iLocalizer = iRelativePose + 1; + // Loop over all views + for (std::size_t iView = 0; iView < _vLocalizationResults[iLocalizer].size(); ++iView) + { + // If the localization has succeeded then if the witness camera localization succeeded + // then update the pose else continue. + if (_vLocalizationResults[0][iView].isValid() && _vLocalizationResults[iLocalizer][iView].isValid()) + { + // Retrieve the witness camera pose from the main camera one. + const geometry::Pose3 poseWitnessCamera = + poseFromMainToWitness(_vLocalizationResults[0][iView].getPose(), _vRelativePoses[iRelativePose]); + _vLocalizationResults[iLocalizer][iView].setPose(poseWitnessCamera); + } + } + displayRelativePoseReprojection(_vRelativePoses[iRelativePose], iLocalizer); + } + // Possibility to update the intrinsics here + + // Update camera intrinsics with refined data + // for (Intrinsics::iterator itIntrinsic = sfm_data.getIntrinsics().begin(); + // itIntrinsic != sfm_data.getIntrinsics().end(); ++itIntrinsic) + // { + // const IndexT indexCam = itIntrinsic->first; + // + // const std::vector & vec_params = map_intrinsics[indexCam]; + // itIntrinsic->second.get()->updateFromParams(vec_params); + // } + return true; } +bool Rig::saveCalibration(const std::string& filename) { return saveRigCalibration(filename, _vRelativePoses); } // it returns poseWitnessCamera*inv(poseMainCamera) -geometry::Pose3 computeRelativePose(const geometry::Pose3 &poseMainCamera, const geometry::Pose3 &poseWitnessCamera) +geometry::Pose3 computeRelativePose(const geometry::Pose3& poseMainCamera, const geometry::Pose3& poseWitnessCamera) { - const aliceVision::Mat3 & R1 = poseMainCamera.rotation(); - const aliceVision::Vec3 & t1 = poseMainCamera.translation(); - const aliceVision::Mat3 & R2 = poseWitnessCamera.rotation(); - const aliceVision::Vec3 & t2 = poseWitnessCamera.translation(); + const aliceVision::Mat3& R1 = poseMainCamera.rotation(); + const aliceVision::Vec3& t1 = poseMainCamera.translation(); + const aliceVision::Mat3& R2 = poseWitnessCamera.rotation(); + const aliceVision::Vec3& t2 = poseWitnessCamera.translation(); - const aliceVision::Mat3 R12 = R2 * R1.transpose(); - const aliceVision::Vec3 t12 = t2 - R12 * t1; + const aliceVision::Mat3 R12 = R2 * R1.transpose(); + const aliceVision::Vec3 t12 = t2 - R12 * t1; - return geometry::Pose3( R12 , -R12.transpose()*t12 ); + return geometry::Pose3(R12, -R12.transpose() * t12); } - // it returns relativePose*poseMainCamera -geometry::Pose3 poseFromMainToWitness(const geometry::Pose3 &poseMainCamera, const geometry::Pose3 &relativePose) +geometry::Pose3 poseFromMainToWitness(const geometry::Pose3& poseMainCamera, const geometry::Pose3& relativePose) { - const aliceVision::Mat3 & R1 = poseMainCamera.rotation(); - const aliceVision::Vec3 & t1 = poseMainCamera.translation(); + const aliceVision::Mat3& R1 = poseMainCamera.rotation(); + const aliceVision::Vec3& t1 = poseMainCamera.translation(); - const aliceVision::Mat3 & R12 = relativePose.rotation(); - const aliceVision::Vec3 & t12 = relativePose.translation(); + const aliceVision::Mat3& R12 = relativePose.rotation(); + const aliceVision::Vec3& t12 = relativePose.translation(); - const aliceVision::Mat3 R2 = R12 * R1; - const aliceVision::Vec3 t2 = R12 * t1 + t12 ; - - return geometry::Pose3( R2 , -R2.transpose() * t2 ); + const aliceVision::Mat3 R2 = R12 * R1; + const aliceVision::Vec3 t2 = R12 * t1 + t12; + + return geometry::Pose3(R2, -R2.transpose() * t2); } -double reprojectionError(const localization::LocalizationResult & localizationResult, const geometry::Pose3 & pose) +double reprojectionError(const localization::LocalizationResult& localizationResult, const geometry::Pose3& pose) { - double residual = 0; - - for(const IndexT iInliers : localizationResult.getInliers()) - { - // Inlier 3D point - const Vec3 & point3D = localizationResult.getPt3D().col(iInliers); - // Its reprojection - const Vec2 itsReprojection = localizationResult.getIntrinsics().project(pose, point3D.homogeneous()); - // Its associated observation location - const Vec2 & point2D = localizationResult.getPt2D().col(iInliers); - // Residual - residual += (point2D(0) - itsReprojection(0))*(point2D(0) - itsReprojection(0)); - residual += (point2D(1) - itsReprojection(1))*(point2D(1) - itsReprojection(1)); - } - return residual; + double residual = 0; + + for (const IndexT iInliers : localizationResult.getInliers()) + { + // Inlier 3D point + const Vec3& point3D = localizationResult.getPt3D().col(iInliers); + // Its reprojection + const Vec2 itsReprojection = localizationResult.getIntrinsics().project(pose, point3D.homogeneous()); + // Its associated observation location + const Vec2& point2D = localizationResult.getPt2D().col(iInliers); + // Residual + residual += (point2D(0) - itsReprojection(0)) * (point2D(0) - itsReprojection(0)); + residual += (point2D(1) - itsReprojection(1)) * (point2D(1) - itsReprojection(1)); + } + return residual; } -void cvpause(){ +void cvpause() +{ #ifdef VISUAL_DEBUG_MODE - int keyboard; - while( !(char) keyboard ){ // ASCII code for 'CR' - keyboard = cv::waitKey( 0 ); - } - - if ( (char) keyboard == 'q' ) - { - ALICEVISION_CERR("The program has been manually stopped"); - std::exit(0); - } + int keyboard; + while (!(char)keyboard) + { // ASCII code for 'CR' + keyboard = cv::waitKey(0); + } + + if ((char)keyboard == 'q') + { + ALICEVISION_CERR("The program has been manually stopped"); + std::exit(0); + } #endif } -bool loadRigCalibration(const std::string &filename, std::vector &subposes) +bool loadRigCalibration(const std::string& filename, std::vector& subposes) { - std::ifstream fs(filename, std::ios::in); - if(!fs.is_open()) - { - ALICEVISION_CERR("Unable to load the calibration file " << filename); - throw std::invalid_argument("Unable to load the calibration file "+filename); - } - - // first read the number of cameras subposes stores - std::size_t numCameras = 0; - fs >> numCameras; - ALICEVISION_LOG_DEBUG("Found " << numCameras << " cameras"); - subposes.reserve(numCameras); - - for(std::size_t cam = 0; cam < numCameras; ++cam) - { - // load the rotation part - Mat3 rot; - for(std::size_t i = 0; i < 3; ++i) - for(std::size_t j = 0; j < 3; ++j) - fs >> rot(i,j); - - // load the center - Vec3 center; - fs >> center(0); - fs >> center(1); - fs >> center(2); - - // add the pose in the vector - subposes.emplace_back(rot, center); - } - - bool isOk = fs.good(); - fs.close(); - return isOk; + std::ifstream fs(filename, std::ios::in); + if (!fs.is_open()) + { + ALICEVISION_CERR("Unable to load the calibration file " << filename); + throw std::invalid_argument("Unable to load the calibration file " + filename); + } + + // first read the number of cameras subposes stores + std::size_t numCameras = 0; + fs >> numCameras; + ALICEVISION_LOG_DEBUG("Found " << numCameras << " cameras"); + subposes.reserve(numCameras); + + for (std::size_t cam = 0; cam < numCameras; ++cam) + { + // load the rotation part + Mat3 rot; + for (std::size_t i = 0; i < 3; ++i) + for (std::size_t j = 0; j < 3; ++j) + fs >> rot(i, j); + + // load the center + Vec3 center; + fs >> center(0); + fs >> center(1); + fs >> center(2); + + // add the pose in the vector + subposes.emplace_back(rot, center); + } + + bool isOk = fs.good(); + fs.close(); + return isOk; } -//numCam -//R[0][0] // first camera rotation -//R[0][1] +// numCam +// R[0][0] // first camera rotation +// R[0][1] //... -//C[0] // first camera center -//C[1] -//C[2] -//R[0][0] // second camera rotation +// C[0] // first camera center +// C[1] +// C[2] +// R[0][0] // second camera rotation //... -bool saveRigCalibration(const std::string &filename, const std::vector &subposes) +bool saveRigCalibration(const std::string& filename, const std::vector& subposes) { - std::ofstream fs(filename, std::ios::out); - if(!fs.is_open()) - { - ALICEVISION_CERR("Unable to create the calibration file " << filename); - throw std::invalid_argument("Unable to create the calibration file "+filename); - } - fs << subposes.size() << std::endl; - - for(const geometry::Pose3 & p : subposes) - { - // write the rotatiop part - const Mat3 &rot = p.rotation(); - for(std::size_t i = 0; i < 3; ++i) - for(std::size_t j = 0; j < 3; ++j) - fs << rot(i,j) << std::endl; - - // write the center - const Vec3 ¢er = p.center(); - fs << center(0) << std::endl; - fs << center(1) << std::endl; - fs << center(2) << std::endl; - } - const bool isOk = fs.good(); - fs.close(); - return isOk; + std::ofstream fs(filename, std::ios::out); + if (!fs.is_open()) + { + ALICEVISION_CERR("Unable to create the calibration file " << filename); + throw std::invalid_argument("Unable to create the calibration file " + filename); + } + fs << subposes.size() << std::endl; + + for (const geometry::Pose3& p : subposes) + { + // write the rotatiop part + const Mat3& rot = p.rotation(); + for (std::size_t i = 0; i < 3; ++i) + for (std::size_t j = 0; j < 3; ++j) + fs << rot(i, j) << std::endl; + + // write the center + const Vec3& center = p.center(); + fs << center(0) << std::endl; + fs << center(1) << std::endl; + fs << center(2) << std::endl; + } + const bool isOk = fs.good(); + fs.close(); + return isOk; } -} // namespace rig -} // namespace aliceVision +} // namespace rig +} // namespace aliceVision diff --git a/src/aliceVision/rig/Rig.hpp b/src/aliceVision/rig/Rig.hpp index 193050af45..9b8b1ab635 100644 --- a/src/aliceVision/rig/Rig.hpp +++ b/src/aliceVision/rig/Rig.hpp @@ -15,124 +15,117 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -//#define VISUAL_DEBUG_MODE +// #define VISUAL_DEBUG_MODE #endif namespace aliceVision { namespace rig { - -class Rig { -public: - - Rig() : _isInitialized(false) {}; - - virtual ~Rig(); - - // Accessors - std::size_t nCams() const { return _vLocalizationResults.size(); } - - bool isInitialized() const { return _isInitialized; } - - std::vector & getLocalizationResults(IndexT i) - { - return _vLocalizationResults[i]; - } - - const std::size_t getRelativePosesSize() const; - - const geometry::Pose3& getRelativePose(std::size_t i) const; - - const std::vector& getRelativePoses() const; - - const geometry::Pose3 & getPose(std::size_t i) const; - - const std::size_t getPosesSize() const; - - const std::vector & getPoses() const; - - /* - * @brief Compute the initial guess related to the relative positions between all witness - * cameras and the main camera - * @return True if the initial calibration succeed, false otherwise. - */ - bool initializeCalibration(); - - void setTrackingResult( - std::vector vLocalizationResults, - std::size_t i); - - /* - * @brief From a set of relative poses, find the one for a given - * localization result which minimizes the reprojection errors over - * all views. - * - * @param[in] vPoses Relative poses to test - * @param[in] Index of the considered witness camera - * @param[out] Best relative pose belonging in vPoses - */ - void findBestRelativePose( - const std::vector & vPoses, - std::size_t iRes, - geometry::Pose3 & result ); - - /* - * @brief Perform the rig bundle adjustment - */ - bool optimizeCalibration(); - - /** - * @brief Save the calibrated poses to a text file. - * @param filename The filename for the calibration file. - * @return true if everything went ok. - * @see saveRigCalibration() - */ - bool saveCalibration(const std::string &filename); - - /* - * @brief Visual debug function displaying the reprojected 3D points and their - * associated observation. - */ - void displayRelativePoseReprojection(const geometry::Pose3 & relativePose, std::size_t iTracker); - -private: - // Set of localization results (each of them associated to a camera) - // The FIRST INDEX is associated to the MAIN CAMERA for which the pose - // corresponds to the entire system pose. - std::map > _vLocalizationResults; - - // (_vRelativePoses.size == nCams-1) where nCams represents the number of cameras - // including the main one - // _vRelativePoses contains the relative poses of all witness cameras - std::vector _vRelativePoses; - - // Rig pose - std::vector _vPoses; // (i.e., by convention, pose of the main camera) - - bool _isInitialized; + +class Rig +{ + public: + Rig() + : _isInitialized(false){}; + + virtual ~Rig(); + + // Accessors + std::size_t nCams() const { return _vLocalizationResults.size(); } + + bool isInitialized() const { return _isInitialized; } + + std::vector& getLocalizationResults(IndexT i) { return _vLocalizationResults[i]; } + + const std::size_t getRelativePosesSize() const; + + const geometry::Pose3& getRelativePose(std::size_t i) const; + + const std::vector& getRelativePoses() const; + + const geometry::Pose3& getPose(std::size_t i) const; + + const std::size_t getPosesSize() const; + + const std::vector& getPoses() const; + + /* + * @brief Compute the initial guess related to the relative positions between all witness + * cameras and the main camera + * @return True if the initial calibration succeed, false otherwise. + */ + bool initializeCalibration(); + + void setTrackingResult(std::vector vLocalizationResults, std::size_t i); + + /* + * @brief From a set of relative poses, find the one for a given + * localization result which minimizes the reprojection errors over + * all views. + * + * @param[in] vPoses Relative poses to test + * @param[in] Index of the considered witness camera + * @param[out] Best relative pose belonging in vPoses + */ + void findBestRelativePose(const std::vector& vPoses, std::size_t iRes, geometry::Pose3& result); + + /* + * @brief Perform the rig bundle adjustment + */ + bool optimizeCalibration(); + + /** + * @brief Save the calibrated poses to a text file. + * @param filename The filename for the calibration file. + * @return true if everything went ok. + * @see saveRigCalibration() + */ + bool saveCalibration(const std::string& filename); + + /* + * @brief Visual debug function displaying the reprojected 3D points and their + * associated observation. + */ + void displayRelativePoseReprojection(const geometry::Pose3& relativePose, std::size_t iTracker); + + private: + // Set of localization results (each of them associated to a camera) + // The FIRST INDEX is associated to the MAIN CAMERA for which the pose + // corresponds to the entire system pose. + std::map> _vLocalizationResults; + + // (_vRelativePoses.size == nCams-1) where nCams represents the number of cameras + // including the main one + // _vRelativePoses contains the relative poses of all witness cameras + std::vector _vRelativePoses; + + // Rig pose + std::vector _vPoses; // (i.e., by convention, pose of the main camera) + + bool _isInitialized; }; /* * @brief For a given localization result, compute the sum of the squared reprojection errors * (over all points) related to another pose than the one previously computed and store * in the provided localizationResult instance. - * + * * @param[in] localizationResult The localization result * @param[in] pose The pose * @return The reprojection error over all inliers store in localizationResult */ -double reprojectionError(const localization::LocalizationResult & localizationResult, const geometry::Pose3 & pose); +double reprojectionError(const localization::LocalizationResult& localizationResult, const geometry::Pose3& pose); /* - * @brief Compute the witness camera from the main camera pose and the relative pose + * @brief Compute the witness camera from the main camera pose and the relative pose * from the main camera to the witness camera. - * + * * @param[in] poseMainCamera Pose of the main camera. * @param[in] relativePose Relative pose from the main camera to the witness camera. * @return The absolute pose of the witness camera as relativePose*poseMainCamera */ -geometry::Pose3 poseFromMainToWitness(const geometry::Pose3 &poseMainCamera, const geometry::Pose3 &relativePose); +geometry::Pose3 poseFromMainToWitness(const geometry::Pose3& poseMainCamera, const geometry::Pose3& relativePose); -geometry::Pose3 computeRelativePose(const geometry::Pose3 &poseMainCamera, const geometry::Pose3 &poseWitnessCamera); +geometry::Pose3 computeRelativePose(const geometry::Pose3& poseMainCamera, const geometry::Pose3& poseWitnessCamera); /* * @brief Visual debug function doing a pause during the program execution. @@ -144,7 +137,7 @@ void cvpause(); * @param[in] filename The file from which to load the subposes. * @param[out] subposes The loaded subposes. * @return true if everything went ok. - * + * * The format of the file is the following * numCam * R[0][0] // first camera rotation @@ -156,14 +149,14 @@ void cvpause(); * R[0][0] // second camera rotation * ... */ -bool loadRigCalibration(const std::string &filename, std::vector &subposes); +bool loadRigCalibration(const std::string& filename, std::vector& subposes); /** * @brief Save the set of subposes from a simple text file. * @param[in] filename The file to which the subposes are saved. * @param[in] subposes The subposes to write. * @return true if everything went ok. - * + * * The format of the file is the following * numCam * R[0][0] // first camera rotation @@ -175,8 +168,7 @@ bool loadRigCalibration(const std::string &filename, std::vector &subposes); - -} // namespace rig -} // namespace aliceVision +bool saveRigCalibration(const std::string& filename, const std::vector& subposes); +} // namespace rig +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/ACRansac.hpp b/src/aliceVision/robustEstimation/ACRansac.hpp index d359091f6d..bb91d4e433 100644 --- a/src/aliceVision/robustEstimation/ACRansac.hpp +++ b/src/aliceVision/robustEstimation/ACRansac.hpp @@ -21,96 +21,96 @@ #include namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /** * @brief Logarithm (base 10) of binomial coefficient */ -template -T logcombi(std::size_t k, std::size_t n, const std::vector& vec_log10) // vec_log10: lookuptable in [0,n+1] +template +T logcombi(std::size_t k, std::size_t n, const std::vector& vec_log10) // vec_log10: lookuptable in [0,n+1] { - if (k>=n || k<=0) return(0.0f); - if (n-k= n || k <= 0) + return (0.0f); + if (n - k < k) + k = n - k; + T r = 0.0f; + for (std::size_t i = 1; i <= k; ++i) + r += vec_log10[n - i + 1] - vec_log10[i]; + return r; } /** * @brief Tabulate logcombi(.,n) */ template -void makelogcombi_n(std::size_t n, std::vector& l, std::vector& vec_log10) // vec_log10: lookuptable [0,n+1] +void makelogcombi_n(std::size_t n, std::vector& l, std::vector& vec_log10) // vec_log10: lookuptable [0,n+1] { - l.resize(n+1); - for (std::size_t k = 0; k <= n; ++k) - l[k] = logcombi(k, n, vec_log10); + l.resize(n + 1); + for (std::size_t k = 0; k <= n; ++k) + l[k] = logcombi(k, n, vec_log10); } /** * @brief Tabulate logcombi(k,.) */ template -void makelogcombi_k(std::size_t k, std::size_t nmax, std::vector& l, std::vector& vec_log10) // vec_log10: lookuptable [0,n+1] +void makelogcombi_k(std::size_t k, std::size_t nmax, std::vector& l, std::vector& vec_log10) // vec_log10: lookuptable [0,n+1] { - l.resize(nmax+1); - for (std::size_t n = 0; n <= nmax; ++n) - l[n] = logcombi(k, n, vec_log10); + l.resize(nmax + 1); + for (std::size_t n = 0; n <= nmax; ++n) + l[n] = logcombi(k, n, vec_log10); } -template +template void makelogcombi(std::size_t k, std::size_t n, std::vector& vec_logc_k, std::vector& vec_logc_n) { - // compute a lookuptable of log10 value for the range [0,n+1] - std::vector vec_log10(n + 1); - for (std::size_t k = 0; k <= n; ++k) - vec_log10[k] = log10((Type)k); + // compute a lookuptable of log10 value for the range [0,n+1] + std::vector vec_log10(n + 1); + for (std::size_t k = 0; k <= n; ++k) + vec_log10[k] = log10((Type)k); - makelogcombi_n(n, vec_logc_n, vec_log10); - makelogcombi_k(k, n, vec_logc_k, vec_log10); + makelogcombi_n(n, vec_logc_n, vec_log10); + makelogcombi_k(k, n, vec_logc_k, vec_log10); } /** * @brief NFA and associated index */ -using ErrorIndex = std::pair; +using ErrorIndex = std::pair; /** * @brief Find best NFA and its index wrt square error threshold in e. */ -inline ErrorIndex bestNFA(int startIndex, //number of point required for estimation +inline ErrorIndex bestNFA(int startIndex, // number of point required for estimation double logalpha0, const std::vector& e, double loge0, double maxThreshold, - const std::vector &logc_n, - const std::vector &logc_k, + const std::vector& logc_n, + const std::vector& logc_k, double errorVectorDimension = 1.0) { - ErrorIndex bestIndex(std::numeric_limits::infinity(), startIndex); - const size_t n = e.size(); - - for(size_t k = startIndex + 1; k <= n && e[k - 1].first <= maxThreshold; ++k) - { - double squaredResidual = e[k - 1].first; - double residual = sqrt(squaredResidual) + std::numeric_limits::epsilon(); - - const double logalpha = logalpha0 + errorVectorDimension * log10(residual); - const double nfa = loge0 + logalpha * (double) (k - startIndex) + logc_n[k] + logc_k[k]; - - - if(nfa < bestIndex.first) + ErrorIndex bestIndex(std::numeric_limits::infinity(), startIndex); + const size_t n = e.size(); + + for (size_t k = startIndex + 1; k <= n && e[k - 1].first <= maxThreshold; ++k) { - bestIndex = ErrorIndex(nfa, k); + double squaredResidual = e[k - 1].first; + double residual = sqrt(squaredResidual) + std::numeric_limits::epsilon(); + + const double logalpha = logalpha0 + errorVectorDimension * log10(residual); + const double nfa = loge0 + logalpha * (double)(k - startIndex) + logc_n[k] + logc_k[k]; + + if (nfa < bestIndex.first) + { + bestIndex = ErrorIndex(nfa, k); + } } - } - return bestIndex; + return bestIndex; } - /** * @brief An implementation of the "Random Sample Consensus" algorithm based on a-contrario estimator * to automatically estimate the error threshold. @@ -144,155 +144,145 @@ inline ErrorIndex bestNFA(int startIndex, //number of point required for estimat */ template std::pair ACRANSAC(const Kernel& kernel, - std::mt19937 &randomNumberGenerator, + std::mt19937& randomNumberGenerator, std::vector& vec_inliers, std::size_t nIter = 1024, typename Kernel::ModelT* model = nullptr, double precision = std::numeric_limits::infinity()) { - vec_inliers.clear(); - - const std::size_t sizeSample = kernel.getMinimumNbRequiredSamples(); - const std::size_t nData = kernel.nbSamples(); - if (nData <= (std::size_t)sizeSample) - return std::make_pair(0.0,0.0); - - const double maxThreshold = (precision==std::numeric_limits::infinity()) ? - std::numeric_limits::infinity() : - precision * precision * kernel.thresholdNormalizer() * kernel.thresholdNormalizer(); - - std::vector vec_residuals(nData); // [residual,index] - std::vector vec_residuals_(nData); - - // Possible sampling indices [0,..,nData] (will change in the optimization phase) - std::vector vec_index(nData); - std::iota(vec_index.begin(), vec_index.end(), 0); - - // Precompute log combi - const double loge0 = log10((double)kernel.getMaximumNbModels() * (nData-sizeSample)); - std::vector vec_logc_n, vec_logc_k; - makelogcombi(sizeSample, nData, vec_logc_k, vec_logc_n); - - // Output parameters - double minNFA = std::numeric_limits::infinity(); - double errorMax = std::numeric_limits::infinity(); - - // Reserve 10% of iterations for focused sampling - size_t nIterReserve = nIter/10; - nIter -= nIterReserve; - - bool bACRansacMode = (precision == std::numeric_limits::infinity()); - - // Main estimation loop. - for(std::size_t iter = 0; iter < nIter; ++iter) - { - std::vector vec_sample(sizeSample); // Sample indices - if (bACRansacMode) - uniformSample(randomNumberGenerator, sizeSample, vec_index, vec_sample); // Get random sample - else - uniformSample(randomNumberGenerator, sizeSample, nData, vec_sample); // Get random sample - - std::vector vec_models; // Up to max_models solutions - kernel.fit(vec_sample, vec_models); - - // Evaluate models - bool better = false; - for (std::size_t k = 0; k < vec_models.size(); ++k) - { - // Residuals computation and ordering - kernel.errors(vec_models[k], vec_residuals_); + vec_inliers.clear(); - if (!bACRansacMode) - { - unsigned int nInlier = 0; - for (std::size_t i = 0; i < nData; ++i) - { - if (vec_residuals_[i] <= maxThreshold) - ++nInlier; - } - if (nInlier > 2.5 * sizeSample) // does the model is meaningful - bACRansacMode = true; - } - if (bACRansacMode) - { - for (size_t i = 0; i < nData; ++i) - { - const double error = vec_residuals_[i]; - vec_residuals[i] = ErrorIndex(error, i); - } - std::sort(vec_residuals.begin(), vec_residuals.end()); - - // Most meaningful discrimination inliers/outliers - const ErrorIndex best = bestNFA( - sizeSample, - kernel.logalpha0(), - vec_residuals, - loge0, - maxThreshold, - vec_logc_n, - vec_logc_k, - kernel.errorVectorDimension()); - - if (best.first < minNFA /*&& vec_residuals[best.second-1].first < errorMax*/) - { - // A better model was found - better = true; - minNFA = best.first; - vec_inliers.resize(best.second); - for (size_t i=0; i no meaningful model found after nIterReserve*2 iterations - if (!bACRansacMode && iter > nIterReserve*2) - break; - - // ACRANSAC optimization: draw samples among best set of inliers so far - if (bACRansacMode && ((better && minNFA<0) || (iter+1==nIter && nIterReserve))) + const std::size_t sizeSample = kernel.getMinimumNbRequiredSamples(); + const std::size_t nData = kernel.nbSamples(); + if (nData <= (std::size_t)sizeSample) + return std::make_pair(0.0, 0.0); + + const double maxThreshold = (precision == std::numeric_limits::infinity()) + ? std::numeric_limits::infinity() + : precision * precision * kernel.thresholdNormalizer() * kernel.thresholdNormalizer(); + + std::vector vec_residuals(nData); // [residual,index] + std::vector vec_residuals_(nData); + + // Possible sampling indices [0,..,nData] (will change in the optimization phase) + std::vector vec_index(nData); + std::iota(vec_index.begin(), vec_index.end(), 0); + + // Precompute log combi + const double loge0 = log10((double)kernel.getMaximumNbModels() * (nData - sizeSample)); + std::vector vec_logc_n, vec_logc_k; + makelogcombi(sizeSample, nData, vec_logc_k, vec_logc_n); + + // Output parameters + double minNFA = std::numeric_limits::infinity(); + double errorMax = std::numeric_limits::infinity(); + + // Reserve 10% of iterations for focused sampling + size_t nIterReserve = nIter / 10; + nIter -= nIterReserve; + + bool bACRansacMode = (precision == std::numeric_limits::infinity()); + + // Main estimation loop. + for (std::size_t iter = 0; iter < nIter; ++iter) { - if (vec_inliers.empty()) - { - // No model found at all so far - ++nIter; // Continue to look for any model, even not meaningful - --nIterReserve; - } - else - { + std::vector vec_sample(sizeSample); // Sample indices + if (bACRansacMode) + uniformSample(randomNumberGenerator, sizeSample, vec_index, vec_sample); // Get random sample + else + uniformSample(randomNumberGenerator, sizeSample, nData, vec_sample); // Get random sample + + std::vector vec_models; // Up to max_models solutions + kernel.fit(vec_sample, vec_models); + + // Evaluate models + bool better = false; + for (std::size_t k = 0; k < vec_models.size(); ++k) + { + // Residuals computation and ordering + kernel.errors(vec_models[k], vec_residuals_); + + if (!bACRansacMode) + { + unsigned int nInlier = 0; + for (std::size_t i = 0; i < nData; ++i) + { + if (vec_residuals_[i] <= maxThreshold) + ++nInlier; + } + if (nInlier > 2.5 * sizeSample) // does the model is meaningful + bACRansacMode = true; + } + if (bACRansacMode) + { + for (size_t i = 0; i < nData; ++i) + { + const double error = vec_residuals_[i]; + vec_residuals[i] = ErrorIndex(error, i); + } + std::sort(vec_residuals.begin(), vec_residuals.end()); + + // Most meaningful discrimination inliers/outliers + const ErrorIndex best = + bestNFA(sizeSample, kernel.logalpha0(), vec_residuals, loge0, maxThreshold, vec_logc_n, vec_logc_k, kernel.errorVectorDimension()); + + if (best.first < minNFA /*&& vec_residuals[best.second-1].first < errorMax*/) + { + // A better model was found + better = true; + minNFA = best.first; + vec_inliers.resize(best.second); + for (size_t i = 0; i < best.second; ++i) + vec_inliers[i] = vec_residuals[i].second; + errorMax = vec_residuals[best.second - 1].first; // Error threshold + if (model) + *model = vec_models[k]; + + ALICEVISION_LOG_TRACE(" nfa=" << minNFA << " inliers=" << best.second << "/" << nData << " precisionNormalized=" << errorMax + << " precision=" << kernel.unormalizeError(errorMax) << " (iter=" << iter + << ",sample=" << vec_sample << ")"); + } + } // if(bACRansacMode) + } // for(size_t k... + + // Early exit test -> no meaningful model found after nIterReserve*2 iterations + if (!bACRansacMode && iter > nIterReserve * 2) + break; + // ACRANSAC optimization: draw samples among best set of inliers so far - vec_index = vec_inliers; - if(nIterReserve) + if (bACRansacMode && ((better && minNFA < 0) || (iter + 1 == nIter && nIterReserve))) { - nIter = iter + 1 + nIterReserve; - nIterReserve = 0; + if (vec_inliers.empty()) + { + // No model found at all so far + ++nIter; // Continue to look for any model, even not meaningful + --nIterReserve; + } + else + { + // ACRANSAC optimization: draw samples among best set of inliers so far + vec_index = vec_inliers; + if (nIterReserve) + { + nIter = iter + 1 + nIterReserve; + nIterReserve = 0; + } + } } - } } - } - if(minNFA >= 0) - vec_inliers.clear(); + if (minNFA >= 0) + vec_inliers.clear(); - if (!vec_inliers.empty()) - { - if (model) - kernel.unnormalize(*model); - errorMax = kernel.unormalizeError(errorMax); - } + if (!vec_inliers.empty()) + { + if (model) + kernel.unnormalize(*model); + errorMax = kernel.unormalizeError(errorMax); + } - return std::make_pair(errorMax, minNFA); + return std::make_pair(errorMax, minNFA); } -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/IRansacKernel.hpp b/src/aliceVision/robustEstimation/IRansacKernel.hpp index 00dcc8b745..6c1b9e01b2 100644 --- a/src/aliceVision/robustEstimation/IRansacKernel.hpp +++ b/src/aliceVision/robustEstimation/IRansacKernel.hpp @@ -11,7 +11,7 @@ #include namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /** * @brief A generic kernel used for the ACRANSAC / LORANSAC framework. @@ -20,95 +20,97 @@ namespace robustEstimation{ * @note Handle data normalization and compute the corresponding logalpha 0 * that depends of the error model (point to line, or point to point) */ -template +template class IRansacKernel { public: - - /** - * @brief Return the minimum number of required samples for the solver - * @return minimum number of required samples - */ - virtual std::size_t getMinimumNbRequiredSamples() const = 0; - - /** - * @brief Return the minimum number of required samples for the solver Ls - * @return minimum number of required samples - */ - virtual std::size_t getMinimumNbRequiredSamplesLS() const = 0; - - /** - * @brief Return the maximum number of models for the solver - * @return maximum number of models - */ - virtual std::size_t getMaximumNbModels() const = 0; - - /** - * @brief This function is called to estimate the model from the minimum number - * of sample \p minSample (i.e. minimal problem solver). - * @param[in] samples A vector containing the indices of the data to be used for - * the minimal estimation. - * @param[out] models The model(s) estimated by the minimal solver. - */ - virtual void fit(const std::vector& samples, std::vector& models) const = 0; - - /** - * @brief This function is called to estimate the model using a least squared - * algorithm from a minumum of \p minSampleLS. - * @param[in] inliers An array containing the indices of the data to use. - * @param[out] models The model(s) estimated using the least squared algorithm. - * @param[in] weights An optional array of weights, one for each sample - */ - virtual void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const = 0; - - /** - * @brief Function used to estimate the weights, typically used by the least square algorithm. - * @param[in] model The model against which the weights are computed. - * @param[in] inliers The array of the indices of the data to be used. - * @param[out] vec_weights The array of weight of the same size as \p inliers. - * @param[in] eps An optional threshold to max out the value of the threshold (typically - * to avoid division by zero or too small numbers). - */ - virtual void computeWeights(const ModelT& model, const std::vector& inliers, std::vector& weights, const double eps = 0.001) const = 0; - - /** - * @brief Function that computes the estimation error for a given model and a given element. - * @param[in] sample The index of the element for which the error is computed. - * @param[in] model The model to consider. - * @return The estimation error for the given element and the given model. - */ - virtual double error(std::size_t sample, const ModelT& model) const = 0; - - /** - * @brief Function that computes the estimation error for a given model and all the elements. - * @param[in] model The model to consider. - * @param[out] vec_errors The vector containing all the estimation errors for every element. - */ - virtual void errors(const ModelT& model, std::vector& errors) const = 0; - - /** - * @brief Function used to unnormalize the model. - * @param[in,out] model The model to unnormalize. - */ - virtual void unnormalize(ModelT& model) const = 0; - - /** - * @brief The number of elements in the data. - * @return the number of elements in the data. - */ - virtual std::size_t nbSamples() const = 0; - - /** - * @brief Get logalpha0, Alpha0 is used to make the error adaptive to the image size - * @return logalpha0 - */ - virtual double logalpha0() const = 0; - - virtual double errorVectorDimension() const = 0; - virtual double unormalizeError(double val) const = 0; - virtual Mat3 normalizer1() const = 0; - virtual double thresholdNormalizer() const = 0; + /** + * @brief Return the minimum number of required samples for the solver + * @return minimum number of required samples + */ + virtual std::size_t getMinimumNbRequiredSamples() const = 0; + + /** + * @brief Return the minimum number of required samples for the solver Ls + * @return minimum number of required samples + */ + virtual std::size_t getMinimumNbRequiredSamplesLS() const = 0; + + /** + * @brief Return the maximum number of models for the solver + * @return maximum number of models + */ + virtual std::size_t getMaximumNbModels() const = 0; + + /** + * @brief This function is called to estimate the model from the minimum number + * of sample \p minSample (i.e. minimal problem solver). + * @param[in] samples A vector containing the indices of the data to be used for + * the minimal estimation. + * @param[out] models The model(s) estimated by the minimal solver. + */ + virtual void fit(const std::vector& samples, std::vector& models) const = 0; + + /** + * @brief This function is called to estimate the model using a least squared + * algorithm from a minumum of \p minSampleLS. + * @param[in] inliers An array containing the indices of the data to use. + * @param[out] models The model(s) estimated using the least squared algorithm. + * @param[in] weights An optional array of weights, one for each sample + */ + virtual void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const = 0; + + /** + * @brief Function used to estimate the weights, typically used by the least square algorithm. + * @param[in] model The model against which the weights are computed. + * @param[in] inliers The array of the indices of the data to be used. + * @param[out] vec_weights The array of weight of the same size as \p inliers. + * @param[in] eps An optional threshold to max out the value of the threshold (typically + * to avoid division by zero or too small numbers). + */ + virtual void computeWeights(const ModelT& model, + const std::vector& inliers, + std::vector& weights, + const double eps = 0.001) const = 0; + + /** + * @brief Function that computes the estimation error for a given model and a given element. + * @param[in] sample The index of the element for which the error is computed. + * @param[in] model The model to consider. + * @return The estimation error for the given element and the given model. + */ + virtual double error(std::size_t sample, const ModelT& model) const = 0; + + /** + * @brief Function that computes the estimation error for a given model and all the elements. + * @param[in] model The model to consider. + * @param[out] vec_errors The vector containing all the estimation errors for every element. + */ + virtual void errors(const ModelT& model, std::vector& errors) const = 0; + + /** + * @brief Function used to unnormalize the model. + * @param[in,out] model The model to unnormalize. + */ + virtual void unnormalize(ModelT& model) const = 0; + + /** + * @brief The number of elements in the data. + * @return the number of elements in the data. + */ + virtual std::size_t nbSamples() const = 0; + + /** + * @brief Get logalpha0, Alpha0 is used to make the error adaptive to the image size + * @return logalpha0 + */ + virtual double logalpha0() const = 0; + + virtual double errorVectorDimension() const = 0; + virtual double unormalizeError(double val) const = 0; + virtual Mat3 normalizer1() const = 0; + virtual double thresholdNormalizer() const = 0; }; -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/ISolver.hpp b/src/aliceVision/robustEstimation/ISolver.hpp index 57dfaf9d2f..06e4eb3d6c 100644 --- a/src/aliceVision/robustEstimation/ISolver.hpp +++ b/src/aliceVision/robustEstimation/ISolver.hpp @@ -20,36 +20,35 @@ namespace robustEstimation { template class ISolver { -public: - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - virtual std::size_t getMinimumNbRequiredSamples() const = 0; - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - virtual std::size_t getMaximumNbModels() const = 0; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - */ - virtual void solve(const Mat& x1, const Mat& x2, std::vector& models) const = 0; - - /** - * @brief Solve the problem. - * @param[in] x1 A 2xN matrix of column vectors. - * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. - * @param[out] models A vector into which the computed models are stored. - * @param[in] weights. - */ - virtual void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const = 0; + public: + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + virtual std::size_t getMinimumNbRequiredSamples() const = 0; + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + virtual std::size_t getMaximumNbModels() const = 0; + + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + */ + virtual void solve(const Mat& x1, const Mat& x2, std::vector& models) const = 0; + + /** + * @brief Solve the problem. + * @param[in] x1 A 2xN matrix of column vectors. + * @param[in] x2 A 2xN (relative pose) or 3xN (resection) matrix of column vectors. + * @param[out] models A vector into which the computed models are stored. + * @param[in] weights. + */ + virtual void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const = 0; }; /** @@ -58,29 +57,28 @@ class ISolver template class UndefinedSolver : public ISolver { -public: - - std::size_t getMinimumNbRequiredSamples() const override - { - throw std::runtime_error("Undefined solver used in kernel."); - return 0; - } - - std::size_t getMaximumNbModels() const override - { - throw std::runtime_error("Undefined solver used in kernel."); - return 0; - } - - void solve(const Mat& x1, const Mat& x2, std::vector& models) const override - { - throw std::runtime_error("Undefined solver used in kernel."); - } - - void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override - { - throw std::runtime_error("Undefined solver used in kernel."); - } + public: + std::size_t getMinimumNbRequiredSamples() const override + { + throw std::runtime_error("Undefined solver used in kernel."); + return 0; + } + + std::size_t getMaximumNbModels() const override + { + throw std::runtime_error("Undefined solver used in kernel."); + return 0; + } + + void solve(const Mat& x1, const Mat& x2, std::vector& models) const override + { + throw std::runtime_error("Undefined solver used in kernel."); + } + + void solve(const Mat& x1, const Mat& x2, std::vector& models, const std::vector& weights) const override + { + throw std::runtime_error("Undefined solver used in kernel."); + } }; /** @@ -89,29 +87,20 @@ class UndefinedSolver : public ISolver template struct MatrixModel { - MatrixModel() = default; + MatrixModel() = default; - explicit MatrixModel(const MatrixT& matrix) - : _matrix(matrix) - {} + explicit MatrixModel(const MatrixT& matrix) + : _matrix(matrix) + {} - inline const MatrixT& getMatrix() const - { - return _matrix; - } + inline const MatrixT& getMatrix() const { return _matrix; } - inline MatrixT& getMatrix() - { - return _matrix; - } + inline MatrixT& getMatrix() { return _matrix; } - inline void setMatrix(const MatrixT& matrix) - { - _matrix = matrix; - } + inline void setMatrix(const MatrixT& matrix) { _matrix = matrix; } -protected: - MatrixT _matrix{}; + protected: + MatrixT _matrix{}; }; /** @@ -124,6 +113,5 @@ using Mat3Model = MatrixModel; */ using Mat34Model = MatrixModel; - -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/LORansac.hpp b/src/aliceVision/robustEstimation/LORansac.hpp index eb9f4bdeeb..af0339b190 100644 --- a/src/aliceVision/robustEstimation/LORansac.hpp +++ b/src/aliceVision/robustEstimation/LORansac.hpp @@ -17,152 +17,150 @@ #include namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /** * @brief It performs an iterative reweighted least square (IRLS) estimation of the problem * defined by \p Kernel. At each step it perform a LS estimation using weights * for each data element computed iteratively on some residual error. - * This implementation follow the Algorithm 3 described in - * + * This implementation follow the Algorithm 3 described in + * * Karel Lebeda, Jiri Matas, Ondrej Chum: * Fixing the Locally Optimized RANSAC. BMVC 2012: 1-11 - * + * * @tparam Kernel The kernel used in the LORansac estimator which must provide a - * minimum solver and a LS solver, the latter used here for the IRLS + * minimum solver and a LS solver, the latter used here for the IRLS * @see aliceVision/robustEstimation/LORansacKernel.hpp * @tparam Scorer The scorer used in the LORansac estimator @see ScoreEvaluator - * + * * @param[in] kernel The kernel used in the LORansac estimator. * @param[in] scorer The scorer used in the LORansac estimator. * @param[out] best_model The best model found at the end of the iterations. * @param[out] inliers The inliers supporting the best model. - * @param[in] mtheta A threshold multiplier used to vary the threshold of the scorer. + * @param[in] mtheta A threshold multiplier used to vary the threshold of the scorer. * @param[in] numIter The max number of iterations to run. * @return the score of the best model as computed by the Scorer, infinity if the * process does not converge. */ template -double iterativeReweightedLeastSquares(const Kernel &kernel, - const Scorer &scorer, - typename Kernel::ModelT &best_model, - std::vector &inliers, +double iterativeReweightedLeastSquares(const Kernel& kernel, + const Scorer& scorer, + typename Kernel::ModelT& best_model, + std::vector& inliers, double mtheta = std::sqrt(2), std::size_t numIter = 4, bool verbose = false) { - const std::size_t total_samples = kernel.nbSamples(); - const std::size_t min_samples = kernel.getMinimumNbRequiredSamplesLS(); - double theta = scorer.getThreshold(); - // used in the iterations to update (reduce) the threshold value - const double deltaTetha = (mtheta*theta - theta) / (numIter-1); - - std::vector all_samples(total_samples); - std::iota(all_samples.begin(), all_samples.end(), 0); - - // find inliers from best model with threshold theta - inliers.clear(); - scorer.score(kernel, best_model, all_samples, inliers, theta); - - if(inliers.size() < min_samples) - { + const std::size_t total_samples = kernel.nbSamples(); + const std::size_t min_samples = kernel.getMinimumNbRequiredSamplesLS(); + double theta = scorer.getThreshold(); + // used in the iterations to update (reduce) the threshold value + const double deltaTetha = (mtheta * theta - theta) / (numIter - 1); + + std::vector all_samples(total_samples); + std::iota(all_samples.begin(), all_samples.end(), 0); + + // find inliers from best model with threshold theta inliers.clear(); - if(verbose) + scorer.score(kernel, best_model, all_samples, inliers, theta); + + if (inliers.size() < min_samples) { - ALICEVISION_LOG_WARNING("[IRLS] returning cause inliers.size() < min_samples"); + inliers.clear(); + if (verbose) + { + ALICEVISION_LOG_WARNING("[IRLS] returning cause inliers.size() < min_samples"); + } + return std::numeric_limits::infinity(); } - return std::numeric_limits::infinity(); - } - - // LS model from the above inliers - std::vector models; - kernel.fitLS(inliers, models); - if (models.size() == 0) - { - inliers.clear(); - if(verbose) + + // LS model from the above inliers + std::vector models; + kernel.fitLS(inliers, models); + if (models.size() == 0) { - ALICEVISION_LOG_WARNING("[IRLS] returning cause models.size() == 0"); + inliers.clear(); + if (verbose) + { + ALICEVISION_LOG_WARNING("[IRLS] returning cause models.size() == 0"); + } + return std::numeric_limits::infinity(); } - return std::numeric_limits::infinity(); - } - - // change threshold for refinement - theta *= mtheta; - - // iterative refinement - for(std::size_t i = 0; i < numIter; ++i) - { - // find inliers on the best-so-far model - // @todo maybe inliers instead of all samples to save some computation - inliers.clear(); - scorer.score(kernel, models[0], all_samples, inliers, theta); - if(inliers.size() < min_samples) + // change threshold for refinement + theta *= mtheta; + + // iterative refinement + for (std::size_t i = 0; i < numIter; ++i) { - inliers.clear(); - if(verbose) - { - ALICEVISION_LOG_WARNING("[IRLS] returning cause inliers.size() < min_samples"); - } - return std::numeric_limits::infinity(); + // find inliers on the best-so-far model + // @todo maybe inliers instead of all samples to save some computation + inliers.clear(); + scorer.score(kernel, models[0], all_samples, inliers, theta); + + if (inliers.size() < min_samples) + { + inliers.clear(); + if (verbose) + { + ALICEVISION_LOG_WARNING("[IRLS] returning cause inliers.size() < min_samples"); + } + return std::numeric_limits::infinity(); + } + // ALICEVISION_LOG_DEBUG("[IRLS] #" << i + // << " theta: " << theta + // << " num inliers: " << inliers.size()); + + // compute the weights for the inliers + std::vector weights; + kernel.computeWeights(models[0], inliers, weights); + + // LS with weights on inliers + models.clear(); + kernel.fitLS(inliers, models, &weights); + if (models.size() != 1) // LS fitting must always return 1 model + { + if (verbose) + { + ALICEVISION_LOG_WARNING("[IRLS] found " << models.size() << " models, aborting..."); + } + return std::numeric_limits::infinity(); + } + + // update the threshold + theta -= deltaTetha; } -// ALICEVISION_LOG_DEBUG("[IRLS] #" << i -// << " theta: " << theta -// << " num inliers: " << inliers.size()); - - // compute the weights for the inliers - std::vector weights; - kernel.computeWeights(models[0], inliers, weights); - - // LS with weights on inliers - models.clear(); - kernel.fitLS(inliers, models, &weights); - if(models.size() != 1) // LS fitting must always return 1 model + + assert(models.size() == 1); + best_model = models[0]; + inliers.clear(); + const double score = scorer.score(kernel, best_model, all_samples, inliers, theta); + if (verbose) { - if(verbose) - { - ALICEVISION_LOG_WARNING("[IRLS] found "<< models.size() << " models, aborting..."); - } - return std::numeric_limits::infinity(); + ALICEVISION_LOG_DEBUG("[IRLS] returning with num inliers: " << inliers.size() << " and score " << score); } - - // update the threshold - theta -= deltaTetha; - } - - assert(models.size()==1); - best_model = models[0]; - inliers.clear(); - const double score = scorer.score(kernel, best_model, all_samples, inliers, theta); - if(verbose) - { - ALICEVISION_LOG_DEBUG("[IRLS] returning with num inliers: " << inliers.size() - << " and score " << score); - } - return score; + return score; } - /** * @brief The local optimization step used by LORansac. It takes as input a model - * and its inliers computed by a minimal solver and refine the solution by using - * IRLS (@see iterativeReweightedLeastSquares()). It first estimates a new model + * and its inliers computed by a minimal solver and refine the solution by using + * IRLS (@see iterativeReweightedLeastSquares()). It first estimates a new model * using LS and its associated inliers. Then it repeatedly (\p numRep) re-estimate - * a new model by LS on a randomly drawn sample of those inliers and then refine + * a new model by LS on a randomly drawn sample of those inliers and then refine * the model by IRLS. The best found model (in terms of number of supporting inliers * is then returned. - * - * This implementation follow the Algorithm 2 described in - * + * + * This implementation follow the Algorithm 2 described in + * * Karel Lebeda, Jiri Matas, Ondrej Chum: - * Fixing the Locally Optimized RANSAC. BMVC 2012: 1-11 - * + * Fixing the Locally Optimized RANSAC. BMVC 2012: 1-11 + * * @tparam Kernel The kernel used in the LORansac estimator which must provide a - * minimum solver and a LS solver, the latter used here for the IRLS + * minimum solver and a LS solver, the latter used here for the IRLS * @see aliceVision/robustEstimation/LORansacKernel.hpp * @tparam Scorer The scorer used in the LORansac estimator @see ScoreEvaluator - * + * * @param[in] kernel The kernel used in the LORansac estimator. * @param[in] scorer The scorer used in the LORansac estimator. * @param[in,out] best_model In input the model estimated by a minimum solver, as @@ -176,7 +174,7 @@ double iterativeReweightedLeastSquares(const Kernel &kernel, template double localOptimization(const Kernel& kernel, const Scorer& scorer, - std::mt19937 &randomNumberGenerator, + std::mt19937& randomNumberGenerator, typename Kernel::ModelT& bestModel, std::vector& bestInliers, double mtheta = std::sqrt(2), @@ -184,115 +182,113 @@ double localOptimization(const Kernel& kernel, std::size_t minSampleSize = 10, bool verbose = false) { - const std::size_t total_samples = kernel.nbSamples(); - const std::size_t min_samples = kernel.getMinimumNbRequiredSamplesLS(); - assert((total_samples > min_samples) && - "[localOptimization] not enough data to estimate the model!"); - - const double theta = scorer.getThreshold(); - - std::vector all_samples(total_samples); - std::iota(all_samples.begin(), all_samples.end(), 0); - - std::size_t debugInit = 0; - if(!bestInliers.empty()) - { - debugInit = bestInliers.size(); - bestInliers.clear(); - } - double bestScore = scorer.score(kernel, bestModel, all_samples, bestInliers, theta); - if(debugInit != 0) assert(debugInit == bestInliers.size()); - - // so far this is the best model - std::size_t bestNumInliers = bestInliers.size(); - if(verbose) - { - ALICEVISION_LOG_DEBUG("[localOptim] so far best num inliers: " << bestNumInliers); - ALICEVISION_LOG_DEBUG("[localOptim] so far best model:\n" << bestModel.getMatrix()); - ALICEVISION_LOG_DEBUG("[localOptim] so far best score: " << bestScore); - } - - // find inliers from best model with larger threshold t*m over all the samples - std::vector inliersBase; - scorer.score(kernel, bestModel, all_samples, inliersBase, theta*mtheta); - assert((inliersBase.size() > min_samples) && - "[localOptimization] not enough data in inliersBase to estimate the model!"); - - // LS model from the above inliers - std::vector models; -// ALICEVISION_LOG_DEBUG("[localOptim] before: "); - kernel.fitLS(inliersBase, models); - if (models.size()== 0) - { - return bestScore; - } - - // find inliers with t again over all the samples - inliersBase.clear(); - scorer.score(kernel, models[0], all_samples, inliersBase, theta); - - // sample of size sampleSize from the last best inliers - const std::size_t sampleSize = std::min(minSampleSize, inliersBase.size()/2); - if(sampleSize <= kernel.getMinimumNbRequiredSamplesLS()) - { - if(verbose) + const std::size_t total_samples = kernel.nbSamples(); + const std::size_t min_samples = kernel.getMinimumNbRequiredSamplesLS(); + assert((total_samples > min_samples) && "[localOptimization] not enough data to estimate the model!"); + + const double theta = scorer.getThreshold(); + + std::vector all_samples(total_samples); + std::iota(all_samples.begin(), all_samples.end(), 0); + + std::size_t debugInit = 0; + if (!bestInliers.empty()) { - ALICEVISION_LOG_DEBUG("breaking cause sampleSize is " << sampleSize); + debugInit = bestInliers.size(); + bestInliers.clear(); } - return bestScore; - } - - // do numRep resampling + iterative LS - for(std::size_t i = 0; i < numRep; ++i) - { - std::vector sample; - uniformSample(randomNumberGenerator, sampleSize, inliersBase, sample); - assert(sampleSize > kernel.getMinimumNbRequiredSamplesLS()); - assert(sample.size() > kernel.getMinimumNbRequiredSamplesLS()); - - // LS estimation from the sample - models.clear(); - kernel.fitLS(sample, models); + double bestScore = scorer.score(kernel, bestModel, all_samples, bestInliers, theta); + if (debugInit != 0) + assert(debugInit == bestInliers.size()); + + // so far this is the best model + std::size_t bestNumInliers = bestInliers.size(); + if (verbose) + { + ALICEVISION_LOG_DEBUG("[localOptim] so far best num inliers: " << bestNumInliers); + ALICEVISION_LOG_DEBUG("[localOptim] so far best model:\n" << bestModel.getMatrix()); + ALICEVISION_LOG_DEBUG("[localOptim] so far best score: " << bestScore); + } + + // find inliers from best model with larger threshold t*m over all the samples + std::vector inliersBase; + scorer.score(kernel, bestModel, all_samples, inliersBase, theta * mtheta); + assert((inliersBase.size() > min_samples) && "[localOptimization] not enough data in inliersBase to estimate the model!"); + + // LS model from the above inliers + std::vector models; + // ALICEVISION_LOG_DEBUG("[localOptim] before: "); + kernel.fitLS(inliersBase, models); if (models.size() == 0) { - continue; + return bestScore; + } + + // find inliers with t again over all the samples + inliersBase.clear(); + scorer.score(kernel, models[0], all_samples, inliersBase, theta); + + // sample of size sampleSize from the last best inliers + const std::size_t sampleSize = std::min(minSampleSize, inliersBase.size() / 2); + if (sampleSize <= kernel.getMinimumNbRequiredSamplesLS()) + { + if (verbose) + { + ALICEVISION_LOG_DEBUG("breaking cause sampleSize is " << sampleSize); + } + return bestScore; } - // IRLS - std::vector inliers; - const double score = iterativeReweightedLeastSquares(kernel, scorer, models[0], inliers); - - // store new best model if it is the case - if((inliers.size() > bestNumInliers) || - ((inliers.size() == bestNumInliers) && (score < bestScore))) + // do numRep resampling + iterative LS + for (std::size_t i = 0; i < numRep; ++i) { - bestNumInliers = inliers.size(); - bestScore = score; - bestModel = models[0]; - bestInliers.swap(inliers); - if(verbose) - { - ALICEVISION_LOG_DEBUG("[localOptim] new best num inliers: " << bestNumInliers); - } + std::vector sample; + uniformSample(randomNumberGenerator, sampleSize, inliersBase, sample); + assert(sampleSize > kernel.getMinimumNbRequiredSamplesLS()); + assert(sample.size() > kernel.getMinimumNbRequiredSamplesLS()); + + // LS estimation from the sample + models.clear(); + kernel.fitLS(sample, models); + if (models.size() == 0) + { + continue; + } + + // IRLS + std::vector inliers; + const double score = iterativeReweightedLeastSquares(kernel, scorer, models[0], inliers); + + // store new best model if it is the case + if ((inliers.size() > bestNumInliers) || ((inliers.size() == bestNumInliers) && (score < bestScore))) + { + bestNumInliers = inliers.size(); + bestScore = score; + bestModel = models[0]; + bestInliers.swap(inliers); + if (verbose) + { + ALICEVISION_LOG_DEBUG("[localOptim] new best num inliers: " << bestNumInliers); + } + } } - } - return bestScore; + return bestScore; } //@todo make visible parameters for the optimization step /** * @brief Implementation of the LORansac framework. - * - * This implementation follow the Algorithm 1 described in - * + * + * This implementation follow the Algorithm 1 described in + * * Karel Lebeda, Jiri Matas, Ondrej Chum: - * Fixing the Locally Optimized RANSAC. BMVC 2012: 1-11 - * + * Fixing the Locally Optimized RANSAC. BMVC 2012: 1-11 + * * @tparam Kernel The kernel used in the LORansac estimator which must provide a - * minimum solver and a LS solver, the latter used here for the IRLS + * minimum solver and a LS solver, the latter used here for the IRLS * @see aliceVision/robustEstimation/LORansacKernel.hpp * @tparam Scorer The scorer used in the LORansac estimator @see ScoreEvaluator - * + * * @param[in] kernel The kernel containing the problem to solve. * @param[in] scorer The scorer used to asses the model quality. * @param[out] best_inliers The indices of the samples supporting the best model. @@ -306,121 +302,115 @@ double localOptimization(const Kernel& kernel, template typename Kernel::ModelT LO_RANSAC(const Kernel& kernel, const Scorer& scorer, - std::mt19937 & randomNumberGenerator, + std::mt19937& randomNumberGenerator, std::vector* best_inliers = NULL, double* best_score = NULL, bool bVerbose = false, std::size_t max_iterations = 100, double outliers_probability = 1e-2) { - assert(outliers_probability < 1.0); - assert(outliers_probability > 0.0); - std::size_t iteration = 0; - const std::size_t min_samples = kernel.getMinimumNbRequiredSamples(); - const std::size_t total_samples = kernel.nbSamples(); - - const std::size_t really_max_iterations = 4096; - - std::size_t bestNumInliers = 0; - double bestInlierRatio = 0.0; - typename Kernel::ModelT bestModel; - - // Test if we have sufficient points for the kernel. - if (total_samples < min_samples) - { - if (best_inliers) { - best_inliers->clear(); - } - return bestModel; - } - - // In this robust estimator, the scorer always works on all the data points - // at once. So precompute the list ahead of time [0,..,total_samples]. - std::vector all_samples(total_samples); - std::iota(all_samples.begin(), all_samples.end(), 0); + assert(outliers_probability < 1.0); + assert(outliers_probability > 0.0); + std::size_t iteration = 0; + const std::size_t min_samples = kernel.getMinimumNbRequiredSamples(); + const std::size_t total_samples = kernel.nbSamples(); - for(iteration = 0; iteration < max_iterations; ++iteration) - { - std::vector sample; - uniformSample(randomNumberGenerator, min_samples, total_samples, sample); + const std::size_t really_max_iterations = 4096; - std::vector models; - kernel.fit(sample, models); + std::size_t bestNumInliers = 0; + double bestInlierRatio = 0.0; + typename Kernel::ModelT bestModel; - // Compute the inlier list for each fit. - for(std::size_t i = 0; i < models.size(); ++i) + // Test if we have sufficient points for the kernel. + if (total_samples < min_samples) { - std::vector inliers; - double score = scorer.score(kernel, models.at(i), all_samples, inliers); - if(bVerbose) - { - ALICEVISION_LOG_DEBUG("sample=" << sample); - ALICEVISION_LOG_DEBUG("model " << i << " e: " << score); - } - - if (bestNumInliers <= inliers.size()) - { - bestModel = models[i]; - //** LOCAL OPTIMIZATION - if(bVerbose) + if (best_inliers) { - ALICEVISION_LOG_DEBUG("Before Optim: num inliers: " << inliers.size() - << " score: " << score - << " kernel minimum nb required samples LS: " << kernel.getMinimumNbRequiredSamplesLS() - ); - - ALICEVISION_LOG_DEBUG("Model:\n" << bestModel.getMatrix()); + best_inliers->clear(); } - - if(inliers.size() > kernel.getMinimumNbRequiredSamplesLS()) - { - score = localOptimization(kernel, scorer, randomNumberGenerator, bestModel, inliers); - } - - if(bVerbose) - { - ALICEVISION_LOG_DEBUG("After Optim: num inliers: " << inliers.size() - << " score: " << score); - ALICEVISION_LOG_DEBUG("Model:\n" << bestModel.getMatrix()); - } - - bestNumInliers = inliers.size(); - bestInlierRatio = inliers.size() / double(total_samples); + return bestModel; + } - if (best_inliers) - { - best_inliers->swap(inliers); - } + // In this robust estimator, the scorer always works on all the data points + // at once. So precompute the list ahead of time [0,..,total_samples]. + std::vector all_samples(total_samples); + std::iota(all_samples.begin(), all_samples.end(), 0); - if(bVerbose) - { - ALICEVISION_LOG_DEBUG(" inliers=" << bestNumInliers << "/" << total_samples - << " (iter=" << iteration - << " ,i=" << i - << " ,sample=" << sample - << ")"); - } - if (bestInlierRatio) + for (iteration = 0; iteration < max_iterations; ++iteration) + { + std::vector sample; + uniformSample(randomNumberGenerator, min_samples, total_samples, sample); + + std::vector models; + kernel.fit(sample, models); + + // Compute the inlier list for each fit. + for (std::size_t i = 0; i < models.size(); ++i) { - max_iterations = iterationsRequired(min_samples, - outliers_probability, - bestInlierRatio); - // safeguard to not get stuck in a big number of iterations - max_iterations = std::min(max_iterations, really_max_iterations); - if(bVerbose) - ALICEVISION_LOG_DEBUG("New max_iteration: " << max_iterations); + std::vector inliers; + double score = scorer.score(kernel, models.at(i), all_samples, inliers); + if (bVerbose) + { + ALICEVISION_LOG_DEBUG("sample=" << sample); + ALICEVISION_LOG_DEBUG("model " << i << " e: " << score); + } + + if (bestNumInliers <= inliers.size()) + { + bestModel = models[i]; + //** LOCAL OPTIMIZATION + if (bVerbose) + { + ALICEVISION_LOG_DEBUG("Before Optim: num inliers: " << inliers.size() << " score: " << score + << " kernel minimum nb required samples LS: " + << kernel.getMinimumNbRequiredSamplesLS()); + + ALICEVISION_LOG_DEBUG("Model:\n" << bestModel.getMatrix()); + } + + if (inliers.size() > kernel.getMinimumNbRequiredSamplesLS()) + { + score = localOptimization(kernel, scorer, randomNumberGenerator, bestModel, inliers); + } + + if (bVerbose) + { + ALICEVISION_LOG_DEBUG("After Optim: num inliers: " << inliers.size() << " score: " << score); + ALICEVISION_LOG_DEBUG("Model:\n" << bestModel.getMatrix()); + } + + bestNumInliers = inliers.size(); + bestInlierRatio = inliers.size() / double(total_samples); + + if (best_inliers) + { + best_inliers->swap(inliers); + } + + if (bVerbose) + { + ALICEVISION_LOG_DEBUG(" inliers=" << bestNumInliers << "/" << total_samples << " (iter=" << iteration << " ,i=" << i + << " ,sample=" << sample << ")"); + } + if (bestInlierRatio) + { + max_iterations = iterationsRequired(min_samples, outliers_probability, bestInlierRatio); + // safeguard to not get stuck in a big number of iterations + max_iterations = std::min(max_iterations, really_max_iterations); + if (bVerbose) + ALICEVISION_LOG_DEBUG("New max_iteration: " << max_iterations); + } + } } - } } - } - if (best_score) - *best_score = bestNumInliers; - - if(bestNumInliers) - kernel.unnormalize(bestModel); - - return bestModel; + if (best_score) + *best_score = bestNumInliers; + + if (bestNumInliers) + kernel.unnormalize(bestModel); + + return bestModel; } -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/LineKernel.hpp b/src/aliceVision/robustEstimation/LineKernel.hpp index d64280bfd5..31eda5322c 100644 --- a/src/aliceVision/robustEstimation/LineKernel.hpp +++ b/src/aliceVision/robustEstimation/LineKernel.hpp @@ -17,12 +17,12 @@ namespace robustEstimation { inline double pointToLineError(const Vec2& lineEq, const Vec2& xs) { - const double b = lineEq[0]; - const double a = lineEq[1]; - const double x = xs[0]; - const double y = xs[1]; - const double e = y - (a * x + b); - return e*e; + const double b = lineEq[0]; + const double a = lineEq[1]; + const double x = xs[0]; + const double y = xs[1]; + const double e = y - (a * x + b); + return e * e; } /** @@ -30,218 +30,192 @@ inline double pointToLineError(const Vec2& lineEq, const Vec2& xs) */ class LineKernel : public IRansacKernel> { -public: - - using ModelT = robustEstimation::MatrixModel; - - explicit LineKernel(const Mat2X& xs) - : _xs(xs) - , _logalpha0(0) - {} - - LineKernel(const Mat2X& xs, int w, int h) - : _xs(xs) - { - // model error as point to line error - // ratio of containing diagonal image rectangle over image area - const double D = sqrt(w * w * 1.0 + h * h); // diameter - const double A = w * h; // area - _logalpha0 = log10(2.0 * D / A / 1.0); - } - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return 2; - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return 1; - } - - /** - * @brief Return the minimum number of required samples for the solver Ls - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamplesLS() const override - { - return 2; - } - - /** - * @brief The number of elements in the data. - * @return the number of elements in the data. - */ - inline std::size_t nbSamples() const override - { - return static_cast(_xs.cols()); - } - - /** - * @brief This function is called to estimate the model from the minimum number - * of sample \p minSample (i.e. minimal problem solver). - * @param[in] samples A vector containing the indices of the data to be used for - * the minimal estimation. - * @param[out] models The model(s) estimated by the minimal solver. - */ - void fit(const std::vector& samples, std::vector& models) const override - { - assert(samples.size() >= getMinimumNbRequiredSamples()); - - // standard least squares solution. - const Mat2X sampled_xs = buildSubsetMatrix(_xs, samples); - - Mat X(sampled_xs.cols(), 2); - X.col(0).setOnes(); - X.col(1) = sampled_xs.row(0).transpose(); - - const Mat A(X.transpose() * X); - const Vec b(X.transpose() * sampled_xs.row(1).transpose()); - - Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); - - const Vec2 ba = svd.solve(b); - - models.emplace_back(ba); - } - - /** - * @brief This function is called to estimate the model using a least squared - * algorithm from a minumum of \p minSampleLS. - * @param[in] inliers An array containing the indices of the data to use. - * @param[out] models The model(s) estimated using the least squared algorithm. - * @param[in] weights An optional array of weights, one for each sample - */ - void fitLS(const std::vector& samples, std::vector& models, const std::vector* weights = nullptr) const override - { - if(weights == nullptr) + public: + using ModelT = robustEstimation::MatrixModel; + + explicit LineKernel(const Mat2X& xs) + : _xs(xs), + _logalpha0(0) + {} + + LineKernel(const Mat2X& xs, int w, int h) + : _xs(xs) + { + // model error as point to line error + // ratio of containing diagonal image rectangle over image area + const double D = sqrt(w * w * 1.0 + h * h); // diameter + const double A = w * h; // area + _logalpha0 = log10(2.0 * D / A / 1.0); + } + + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return 2; } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return 1; } + + /** + * @brief Return the minimum number of required samples for the solver Ls + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamplesLS() const override { return 2; } + + /** + * @brief The number of elements in the data. + * @return the number of elements in the data. + */ + inline std::size_t nbSamples() const override { return static_cast(_xs.cols()); } + + /** + * @brief This function is called to estimate the model from the minimum number + * of sample \p minSample (i.e. minimal problem solver). + * @param[in] samples A vector containing the indices of the data to be used for + * the minimal estimation. + * @param[out] models The model(s) estimated by the minimal solver. + */ + void fit(const std::vector& samples, std::vector& models) const override + { + assert(samples.size() >= getMinimumNbRequiredSamples()); + + // standard least squares solution. + const Mat2X sampled_xs = buildSubsetMatrix(_xs, samples); + + Mat X(sampled_xs.cols(), 2); + X.col(0).setOnes(); + X.col(1) = sampled_xs.row(0).transpose(); + + const Mat A(X.transpose() * X); + const Vec b(X.transpose() * sampled_xs.row(1).transpose()); + + Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); + + const Vec2 ba = svd.solve(b); + + models.emplace_back(ba); + } + + /** + * @brief This function is called to estimate the model using a least squared + * algorithm from a minumum of \p minSampleLS. + * @param[in] inliers An array containing the indices of the data to use. + * @param[out] models The model(s) estimated using the least squared algorithm. + * @param[in] weights An optional array of weights, one for each sample + */ + void fitLS(const std::vector& samples, std::vector& models, const std::vector* weights = nullptr) const override { - fit(samples, models); - return; + if (weights == nullptr) + { + fit(samples, models); + return; + } + + assert(samples.size() >= getMinimumNbRequiredSamples()); + + // standard least squares solution. + const Mat2X sampled_xs = buildSubsetMatrix(_xs, samples); + + const std::size_t numPts = sampled_xs.cols(); + + assert(numPts == weights->size()); + + // create the weight matrix + Mat W = Mat::Zero(numPts, numPts); + for (std::size_t i = 0; i < numPts; ++i) + W(i, i) = (*weights)[i]; + + Mat X(numPts, 2); + X.col(0).setOnes(); + X.col(1) = sampled_xs.row(0).transpose(); + const Mat A(X.transpose() * W * X); + const Vec b(X.transpose() * W * sampled_xs.row(1).transpose()); + Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); + const Vec2 ba = svd.solve(b); + models.emplace_back(ba); } - assert(samples.size() >= getMinimumNbRequiredSamples()); - - // standard least squares solution. - const Mat2X sampled_xs = buildSubsetMatrix(_xs, samples); - - - const std::size_t numPts = sampled_xs.cols(); - - assert(numPts == weights->size()); - - // create the weight matrix - Mat W = Mat::Zero(numPts, numPts); - for(std::size_t i = 0; i < numPts; ++i) - W(i,i) = (*weights)[i]; - - Mat X(numPts, 2); - X.col(0).setOnes(); - X.col(1) = sampled_xs.row(0).transpose(); - const Mat A(X.transpose() * W * X); - const Vec b(X.transpose() * W * sampled_xs.row(1).transpose()); - Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); - const Vec2 ba = svd.solve(b); - models.emplace_back(ba); - } - - /** - * @brief Function used to estimate the weights, typically used by the least square algorithm. - * @param[in] model The model against which the weights are computed. - * @param[in] inliers The array of the indices of the data to be used. - * @param[out] vec_weights The array of weight of the same size as \p inliers. - * @param[in] eps An optional threshold to max out the value of the threshold (typically - * to avoid division by zero or too small numbers). - */ - void computeWeights(const ModelT& model, const std::vector& inliers, std::vector& weights, const double eps = 0.001) const override - { - const auto nbInliers = inliers.size(); - weights.resize(nbInliers); - for(std::size_t sample = 0; sample < nbInliers; ++sample) + /** + * @brief Function used to estimate the weights, typically used by the least square algorithm. + * @param[in] model The model against which the weights are computed. + * @param[in] inliers The array of the indices of the data to be used. + * @param[out] vec_weights The array of weight of the same size as \p inliers. + * @param[in] eps An optional threshold to max out the value of the threshold (typically + * to avoid division by zero or too small numbers). + */ + void computeWeights(const ModelT& model, + const std::vector& inliers, + std::vector& weights, + const double eps = 0.001) const override { - const auto idx = inliers[sample]; - weights[sample] = error(idx, model); + const auto nbInliers = inliers.size(); + weights.resize(nbInliers); + for (std::size_t sample = 0; sample < nbInliers; ++sample) + { + const auto idx = inliers[sample]; + weights[sample] = error(idx, model); + + // avoid division by zero + weights[sample] = 1 / std::pow(std::max(eps, weights[sample]), 2); + } + } + + /** + * @brief Function that computes the estimation error for a given model and a given element. + * @param[in] sample The index of the element for which the error is computed. + * @param[in] model The model to consider. + * @return The estimation error for the given element and the given model. + */ + inline double error(std::size_t sample, const ModelT& model) const override + { + // point to line error + const Vec2& xs = _xs.col(sample); + return pointToLineError(model.getMatrix(), xs); + } + + /** + * @brief Function that computes the estimation error for a given model and all the elements. + * @param[in] model The model to consider. + * @param[out] vec_errors The vector containing all the estimation errors for every element. + */ + inline void errors(const ModelT& model, std::vector& errors) const override + { + errors.resize(_xs.cols()); + for (std::size_t sample = 0; sample < _xs.cols(); ++sample) + errors.at(sample) = error(sample, model); + } - // avoid division by zero - weights[sample] = 1 / std::pow(std::max(eps, weights[sample]), 2); + /** + * @brief Function used to unnormalize the model. + * @param[in,out] model The model to unnormalize. + */ + inline void unnormalize(ModelT& model) const override + { + // nothing to do, model is left unchanged } - } - - /** - * @brief Function that computes the estimation error for a given model and a given element. - * @param[in] sample The index of the element for which the error is computed. - * @param[in] model The model to consider. - * @return The estimation error for the given element and the given model. - */ - inline double error(std::size_t sample, const ModelT& model) const override - { - // point to line error - const Vec2& xs = _xs.col(sample); - return pointToLineError(model.getMatrix(), xs); - } - - /** - * @brief Function that computes the estimation error for a given model and all the elements. - * @param[in] model The model to consider. - * @param[out] vec_errors The vector containing all the estimation errors for every element. - */ - inline void errors(const ModelT& model, std::vector& errors) const override - { - errors.resize(_xs.cols()); - for(std::size_t sample = 0; sample < _xs.cols(); ++sample) - errors.at(sample) = error(sample, model); - } - - /** - * @brief Function used to unnormalize the model. - * @param[in,out] model The model to unnormalize. - */ - inline void unnormalize(ModelT& model) const override - { - // nothing to do, model is left unchanged - } - - /** - * @brief Get logalpha0, Alpha0 is used to make the error adaptive to the image size - * @return logalpha0 - */ - inline double logalpha0() const override - { - return _logalpha0; - } - - inline double errorVectorDimension() const override - { - return 1.0; - } - - inline Mat3 normalizer1() const override - { - return Mat3::Identity(); - } - - inline double thresholdNormalizer() const override - { - return 1.0; - } - - inline double unormalizeError(double val) const override - { - return std::sqrt(val); - } - -private: - const Mat2X& _xs; - double _logalpha0; + + /** + * @brief Get logalpha0, Alpha0 is used to make the error adaptive to the image size + * @return logalpha0 + */ + inline double logalpha0() const override { return _logalpha0; } + + inline double errorVectorDimension() const override { return 1.0; } + + inline Mat3 normalizer1() const override { return Mat3::Identity(); } + + inline double thresholdNormalizer() const override { return 1.0; } + + inline double unormalizeError(double val) const override { return std::sqrt(val); } + + private: + const Mat2X& _xs; + double _logalpha0; }; -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/PointFittingKernel.hpp b/src/aliceVision/robustEstimation/PointFittingKernel.hpp index fc809eb0ae..787cd98133 100644 --- a/src/aliceVision/robustEstimation/PointFittingKernel.hpp +++ b/src/aliceVision/robustEstimation/PointFittingKernel.hpp @@ -47,132 +47,120 @@ namespace robustEstimation { template class PointFittingKernel { -public: - - using SolverT = SolverT_; - using ErrorT = ErrorT_; - using ModelT = ModelT_; - - PointFittingKernel(const Mat& x1, const Mat& x2) - : _x1(x1) - , _x2(x2) - {} - - /** - * @brief Return the minimum number of required samples - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const - { - return _kernelSolver.getMinimumNbRequiredSamples(); - } - - /** - * @brief Return the maximum number of models - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const - { - return _kernelSolver.getMaximumNbModels(); - } - - /** - * @brief Extract required sample and fit model(s) to the sample - * @param[in] samples - * @param[out] models - */ - inline virtual void fit(const std::vector& samples, std::vector& models) const - { - const Mat x1 = buildSubsetMatrix(_x1, samples); - const Mat x2 = buildSubsetMatrix(_x2, samples); - _kernelSolver.solve(x1, x2, models); - } - - /** - * @brief Return the error associated to the model and a sample point - * @param[in] sample - * @param[in] model - * @return error value - */ - inline virtual double error(std::size_t sample, const ModelT& model) const - { - return _errorEstimator.error(model, _x1.col(sample), _x2.col(sample)); - } - - /** - * @brief Return the errors associated to the model and each sample point - * @param[in] model - * @param[out] errors - */ - inline virtual void errors(const ModelT& model, std::vector& errors) const - { - errors.resize(_x1.cols()); - for(std::size_t sample = 0; sample < _x1.cols(); ++sample) - errors.at(sample) = error(sample, model); - } - - /** - * @brief get the number of putative points - * @return number of putative points - */ - inline std::size_t nbSamples() const - { - return _x1.cols(); - } - -protected: - - /// left corresponding data - const Mat& _x1; - /// right corresponding data - const Mat& _x2; - /// two view solver - const SolverT _kernelSolver{}; - /// solver error estimation - const ErrorT _errorEstimator{}; + public: + using SolverT = SolverT_; + using ErrorT = ErrorT_; + using ModelT = ModelT_; + + PointFittingKernel(const Mat& x1, const Mat& x2) + : _x1(x1), + _x2(x2) + {} + + /** + * @brief Return the minimum number of required samples + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const { return _kernelSolver.getMinimumNbRequiredSamples(); } + + /** + * @brief Return the maximum number of models + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const { return _kernelSolver.getMaximumNbModels(); } + + /** + * @brief Extract required sample and fit model(s) to the sample + * @param[in] samples + * @param[out] models + */ + inline virtual void fit(const std::vector& samples, std::vector& models) const + { + const Mat x1 = buildSubsetMatrix(_x1, samples); + const Mat x2 = buildSubsetMatrix(_x2, samples); + _kernelSolver.solve(x1, x2, models); + } + + /** + * @brief Return the error associated to the model and a sample point + * @param[in] sample + * @param[in] model + * @return error value + */ + inline virtual double error(std::size_t sample, const ModelT& model) const + { + return _errorEstimator.error(model, _x1.col(sample), _x2.col(sample)); + } + + /** + * @brief Return the errors associated to the model and each sample point + * @param[in] model + * @param[out] errors + */ + inline virtual void errors(const ModelT& model, std::vector& errors) const + { + errors.resize(_x1.cols()); + for (std::size_t sample = 0; sample < _x1.cols(); ++sample) + errors.at(sample) = error(sample, model); + } + + /** + * @brief get the number of putative points + * @return number of putative points + */ + inline std::size_t nbSamples() const { return _x1.cols(); } + + protected: + /// left corresponding data + const Mat& _x1; + /// right corresponding data + const Mat& _x2; + /// two view solver + const SolverT _kernelSolver{}; + /// solver error estimation + const ErrorT _errorEstimator{}; }; template class NormalizedPointFittingKernel : public PointFittingKernel { -public: - - using KernelBase = PointFittingKernel; - - NormalizedPointFittingKernel(const Mat& x1, const Mat& x2) - : KernelBase(x1, x2) - {} - - /** - * @brief Extract required sample and fit model(s) to the sample - * @param[in] samples - * @param[out] models - */ - inline void fit(const std::vector& samples, std::vector& models) const override - { - const Mat x1 = buildSubsetMatrix(KernelBase::_x1, samples); - const Mat x2 = buildSubsetMatrix(KernelBase::_x2, samples); - - assert(2 == x1.rows()); - assert(KernelBase::getMinimumNbRequiredSamples() <= x1.cols()); - assert(x1.rows() == x2.rows()); - assert(x1.cols() == x2.cols()); - - // normalize the data. - Mat3 T1; - Mat3 T2; - Mat x1_normalized; - Mat x2_normalized; - - normalizePoints(x1, &x1_normalized, &T1); - normalizePoints(x2, &x2_normalized, &T2); - - KernelBase::_kernelSolver.solve(x1_normalized, x2_normalized, models); - - // unnormalize model from the computed conditioning. - for(int i = 0; i < models.size(); ++i) - UnnormalizerT_::unnormalize(T1, T2, &(models.at(i).getMatrix())); - } + public: + using KernelBase = PointFittingKernel; + + NormalizedPointFittingKernel(const Mat& x1, const Mat& x2) + : KernelBase(x1, x2) + {} + + /** + * @brief Extract required sample and fit model(s) to the sample + * @param[in] samples + * @param[out] models + */ + inline void fit(const std::vector& samples, std::vector& models) const override + { + const Mat x1 = buildSubsetMatrix(KernelBase::_x1, samples); + const Mat x2 = buildSubsetMatrix(KernelBase::_x2, samples); + + assert(2 == x1.rows()); + assert(KernelBase::getMinimumNbRequiredSamples() <= x1.cols()); + assert(x1.rows() == x2.rows()); + assert(x1.cols() == x2.cols()); + + // normalize the data. + Mat3 T1; + Mat3 T2; + Mat x1_normalized; + Mat x2_normalized; + + normalizePoints(x1, &x1_normalized, &T1); + normalizePoints(x2, &x2_normalized, &T2); + + KernelBase::_kernelSolver.solve(x1_normalized, x2_normalized, models); + + // unnormalize model from the computed conditioning. + for (int i = 0; i < models.size(); ++i) + UnnormalizerT_::unnormalize(T1, T2, &(models.at(i).getMatrix())); + } }; } // namespace robustEstimation diff --git a/src/aliceVision/robustEstimation/PointFittingRansacKernel.hpp b/src/aliceVision/robustEstimation/PointFittingRansacKernel.hpp index 165423bcb9..ebede90aa8 100644 --- a/src/aliceVision/robustEstimation/PointFittingRansacKernel.hpp +++ b/src/aliceVision/robustEstimation/PointFittingRansacKernel.hpp @@ -28,129 +28,108 @@ namespace robustEstimation { * @note Handle data normalization and compute the corresponding logalpha 0 * that depends of the error model (point to line, or point to point) */ -template > -class PointFittingRansacKernel - : public robustEstimation::IRansacKernel - , public robustEstimation::PointFittingKernel +template> +class PointFittingRansacKernel : public robustEstimation::IRansacKernel, + public robustEstimation::PointFittingKernel { -public: - - using PFKernel = robustEstimation::PointFittingKernel; - - PointFittingRansacKernel(const Mat& x1, const Mat& x2) - : PFKernel(x1, x2) - {} - - /** - * @brief Return the minimum number of required samples for the solver - * @return minimum number of required samples - */ - std::size_t getMinimumNbRequiredSamples() const override - { - return PFKernel::getMinimumNbRequiredSamples(); - } - - std::size_t getMinimumNbRequiredSamplesLS() const override - { - return _solverLs.getMinimumNbRequiredSamples(); - } - - /** - * @brief Return the maximum number of models for the solver - * @return maximum number of models - */ - std::size_t getMaximumNbModels() const override - { - return PFKernel::getMaximumNbModels(); - } - - /** - * @brief This function is called to estimate the model from the minimum number - * of sample \p minSample (i.e. minimal problem solver). - * @param[in] samples A vector containing the indices of the data to be used for - * the minimal estimation. - * @param[out] models The model(s) estimated by the minimal solver. - */ - void fit(const std::vector& samples, std::vector& models) const override - { - PFKernel::fit(samples, models); - } - - - void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const override - { - const Mat x1 = buildSubsetMatrix(PFKernel::_x1, inliers); - const Mat x2 = buildSubsetMatrix(PFKernel::_x2, inliers); - - if(weights == nullptr) - _solverLs.solve(x1, x2, models); - else - _solverLs.solve(x1, x2, models, *weights); - } - - void computeWeights(const ModelT_& model, const std::vector& inliers, std::vector& weights, const double eps = 0.001) const override - { - const auto numInliers = inliers.size(); - weights.resize(numInliers); - for(std::size_t sample = 0; sample < numInliers; ++sample) + public: + using PFKernel = robustEstimation::PointFittingKernel; + + PointFittingRansacKernel(const Mat& x1, const Mat& x2) + : PFKernel(x1, x2) + {} + + /** + * @brief Return the minimum number of required samples for the solver + * @return minimum number of required samples + */ + std::size_t getMinimumNbRequiredSamples() const override { return PFKernel::getMinimumNbRequiredSamples(); } + + std::size_t getMinimumNbRequiredSamplesLS() const override { return _solverLs.getMinimumNbRequiredSamples(); } + + /** + * @brief Return the maximum number of models for the solver + * @return maximum number of models + */ + std::size_t getMaximumNbModels() const override { return PFKernel::getMaximumNbModels(); } + + /** + * @brief This function is called to estimate the model from the minimum number + * of sample \p minSample (i.e. minimal problem solver). + * @param[in] samples A vector containing the indices of the data to be used for + * the minimal estimation. + * @param[out] models The model(s) estimated by the minimal solver. + */ + void fit(const std::vector& samples, std::vector& models) const override { PFKernel::fit(samples, models); } + + void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const override { - const auto idx = inliers[sample]; - weights[sample] = PFKernel::_errorEstimator.error(model, PFKernel::_x1.col(idx), PFKernel::_x2.col(idx)); - // avoid division by zero - weights[sample] = 1.0 / std::pow(std::max(eps, weights[sample]), 2); + const Mat x1 = buildSubsetMatrix(PFKernel::_x1, inliers); + const Mat x2 = buildSubsetMatrix(PFKernel::_x2, inliers); + + if (weights == nullptr) + _solverLs.solve(x1, x2, models); + else + _solverLs.solve(x1, x2, models, *weights); } - } - - /** - * @brief Function that computes the estimation error for a given model and a given element. - * @param[in] sample The index of the element for which the error is computed. - * @param[in] model The model to consider. - * @return The estimation error for the given element and the given model. - */ - double error(std::size_t sample, const ModelT_& model) const override - { - return PFKernel::error(sample, model); - } - - /** - * @brief Function that computes the estimation error for a given model and all the elements. - * @param[in] model The model to consider. - * @param[out] vec_errors The vector containing all the estimation errors for every element. - */ - void errors(const ModelT_& model, std::vector& errors) const override - { - PFKernel::errors(model, errors); - } - - /** - * @brief Function used to unnormalize the model. - * @param[in,out] model The model to unnormalize. - */ - virtual void unnormalize(ModelT_& model) const = 0; - - /** - * @brief The number of elements in the data. - * @return the number of elements in the data. - */ - std::size_t nbSamples() const override - { - return PFKernel::nbSamples(); - } - - /** - * @brief Get logalpha0, Alpha0 is used to make the error adaptive to the image size - * @return logalpha0 - */ - virtual double logalpha0() const = 0; - - virtual double errorVectorDimension() const = 0; - virtual double unormalizeError(double val) const = 0; - virtual Mat3 normalizer1() const = 0; - virtual double thresholdNormalizer() const = 0; - -private: - const SolverLsT_ _solverLs{}; + + void computeWeights(const ModelT_& model, + const std::vector& inliers, + std::vector& weights, + const double eps = 0.001) const override + { + const auto numInliers = inliers.size(); + weights.resize(numInliers); + for (std::size_t sample = 0; sample < numInliers; ++sample) + { + const auto idx = inliers[sample]; + weights[sample] = PFKernel::_errorEstimator.error(model, PFKernel::_x1.col(idx), PFKernel::_x2.col(idx)); + // avoid division by zero + weights[sample] = 1.0 / std::pow(std::max(eps, weights[sample]), 2); + } + } + + /** + * @brief Function that computes the estimation error for a given model and a given element. + * @param[in] sample The index of the element for which the error is computed. + * @param[in] model The model to consider. + * @return The estimation error for the given element and the given model. + */ + double error(std::size_t sample, const ModelT_& model) const override { return PFKernel::error(sample, model); } + + /** + * @brief Function that computes the estimation error for a given model and all the elements. + * @param[in] model The model to consider. + * @param[out] vec_errors The vector containing all the estimation errors for every element. + */ + void errors(const ModelT_& model, std::vector& errors) const override { PFKernel::errors(model, errors); } + + /** + * @brief Function used to unnormalize the model. + * @param[in,out] model The model to unnormalize. + */ + virtual void unnormalize(ModelT_& model) const = 0; + + /** + * @brief The number of elements in the data. + * @return the number of elements in the data. + */ + std::size_t nbSamples() const override { return PFKernel::nbSamples(); } + + /** + * @brief Get logalpha0, Alpha0 is used to make the error adaptive to the image size + * @return logalpha0 + */ + virtual double logalpha0() const = 0; + + virtual double errorVectorDimension() const = 0; + virtual double unormalizeError(double val) const = 0; + virtual Mat3 normalizer1() const = 0; + virtual double thresholdNormalizer() const = 0; + + private: + const SolverLsT_ _solverLs{}; }; -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/Ransac.hpp b/src/aliceVision/robustEstimation/Ransac.hpp index 0c05bba2f9..55cc5dfcaa 100644 --- a/src/aliceVision/robustEstimation/Ransac.hpp +++ b/src/aliceVision/robustEstimation/Ransac.hpp @@ -17,11 +17,11 @@ #include namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /** * @brief Implementation of the "Random Sample Consensus" algorithm (Fischler&Bolles 1981). - * + * * @details The number of tests is reevaluated down as soon as more inliers are * found. Return the found model. * 1. The model. @@ -35,94 +35,87 @@ namespace robustEstimation{ * 4. Kernel::error(Model, int) -> error */ template -typename Kernel::ModelT RANSAC( - const Kernel& kernel, - const Scorer& scorer, - std::mt19937 & randomNumberGenerator, - std::vector* best_inliers = nullptr, - double* best_score = nullptr, - bool bVerbose = true, - double outliers_probability = 1e-2) +typename Kernel::ModelT RANSAC(const Kernel& kernel, + const Scorer& scorer, + std::mt19937& randomNumberGenerator, + std::vector* best_inliers = nullptr, + double* best_score = nullptr, + bool bVerbose = true, + double outliers_probability = 1e-2) { - assert(outliers_probability < 1.0); - assert(outliers_probability > 0.0); - size_t iteration = 0; - const size_t min_samples = kernel.getMinimumNbRequiredSamples(); - const size_t total_samples = kernel.nbSamples(); + assert(outliers_probability < 1.0); + assert(outliers_probability > 0.0); + size_t iteration = 0; + const size_t min_samples = kernel.getMinimumNbRequiredSamples(); + const size_t total_samples = kernel.nbSamples(); - size_t max_iterations = 100; - const size_t really_max_iterations = 4096; + size_t max_iterations = 100; + const size_t really_max_iterations = 4096; - size_t best_num_inliers = 0; - double best_inlier_ratio = 0.0; - typename Kernel::ModelT best_model; + size_t best_num_inliers = 0; + double best_inlier_ratio = 0.0; + typename Kernel::ModelT best_model; - // Test if we have sufficient points for the kernel. - if (total_samples < min_samples) - { - if (best_inliers) { - best_inliers->resize(0); + // Test if we have sufficient points for the kernel. + if (total_samples < min_samples) + { + if (best_inliers) + { + best_inliers->resize(0); + } + return best_model; } - return best_model; - } - - // In this robust estimator, the scorer always works on all the data points - // at once. So precompute the list ahead of time [0,..,total_samples]. - std::vector all_samples(total_samples); - std::iota(all_samples.begin(), all_samples.end(), 0); - for (iteration = 0; - iteration < max_iterations && - iteration < really_max_iterations; ++iteration) - { - std::vector sample; - uniformSample(randomNumberGenerator, min_samples, total_samples, sample); + // In this robust estimator, the scorer always works on all the data points + // at once. So precompute the list ahead of time [0,..,total_samples]. + std::vector all_samples(total_samples); + std::iota(all_samples.begin(), all_samples.end(), 0); - std::vector models; - kernel.fit(sample, models); + for (iteration = 0; iteration < max_iterations && iteration < really_max_iterations; ++iteration) + { + std::vector sample; + uniformSample(randomNumberGenerator, min_samples, total_samples, sample); - // Compute the inlier list for each fit. - for (size_t i = 0; i < models.size(); ++i) - { - std::vector inliers; - scorer.score(kernel, models[i], all_samples, inliers); + std::vector models; + kernel.fit(sample, models); - if (best_num_inliers < inliers.size()) + // Compute the inlier list for each fit. + for (size_t i = 0; i < models.size(); ++i) { - best_num_inliers = inliers.size(); - best_inlier_ratio = inliers.size() / double(total_samples); - best_model = models[i]; - if (best_inliers) - { - best_inliers->swap(inliers); - } - if(bVerbose) - { - ALICEVISION_LOG_DEBUG("inliers=" << best_num_inliers << "/" << total_samples - << " (iter=" << iteration - << ", sample=" << sample - << ")"); - } - if (best_inlier_ratio) - { - max_iterations = iterationsRequired(min_samples, - outliers_probability, - best_inlier_ratio); - if(bVerbose) - ALICEVISION_LOG_DEBUG("New max_iteration: " << max_iterations); - } + std::vector inliers; + scorer.score(kernel, models[i], all_samples, inliers); + + if (best_num_inliers < inliers.size()) + { + best_num_inliers = inliers.size(); + best_inlier_ratio = inliers.size() / double(total_samples); + best_model = models[i]; + if (best_inliers) + { + best_inliers->swap(inliers); + } + if (bVerbose) + { + ALICEVISION_LOG_DEBUG("inliers=" << best_num_inliers << "/" << total_samples << " (iter=" << iteration << ", sample=" << sample + << ")"); + } + if (best_inlier_ratio) + { + max_iterations = iterationsRequired(min_samples, outliers_probability, best_inlier_ratio); + if (bVerbose) + ALICEVISION_LOG_DEBUG("New max_iteration: " << max_iterations); + } + } } - } - } - if (best_score) - *best_score = best_num_inliers; - - if(best_num_inliers) - kernel.unnormalize(best_model); - - return best_model; -} + } + if (best_score) + *best_score = best_num_inliers; + if (best_num_inliers) + kernel.unnormalize(best_model); + + return best_model; +} -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/ScoreEvaluator.hpp b/src/aliceVision/robustEstimation/ScoreEvaluator.hpp index 17257c6946..6d75aac9df 100644 --- a/src/aliceVision/robustEstimation/ScoreEvaluator.hpp +++ b/src/aliceVision/robustEstimation/ScoreEvaluator.hpp @@ -8,7 +8,7 @@ #pragma once namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /** * @brief Templated Functor class to evaluate a given model over a set of samples. @@ -16,50 +16,44 @@ namespace robustEstimation{ template class ScoreEvaluator { -public: - explicit ScoreEvaluator(double threshold) - : _threshold(threshold) - {} + public: + explicit ScoreEvaluator(double threshold) + : _threshold(threshold) + {} - template - double score(const Kernel& kernel, - const typename Kernel::ModelT& model, - const std::vector& samples, - std::vector& inliers, - double threshold) const - { - double cost = 0.0; - for(std::size_t j = 0; j < samples.size(); ++j) + template + double score(const Kernel& kernel, const typename Kernel::ModelT& model, const std::vector& samples, std::vector& inliers, double threshold) + const { - double error = kernel.error(samples.at(j), model); - if (error < threshold) - { - cost += error; - inliers.push_back(samples[j]); - } - else - { -// cost += threshold; - cost += error; - } + double cost = 0.0; + for (std::size_t j = 0; j < samples.size(); ++j) + { + double error = kernel.error(samples.at(j), model); + if (error < threshold) + { + cost += error; + inliers.push_back(samples[j]); + } + else + { + // cost += threshold; + cost += error; + } + } + return cost; } - return cost; - } - template - double score(const Kernel &kernel, - const typename Kernel::ModelT& model, - const std::vector& samples, - std::vector& inliers) const - { - return score(kernel, model, samples, inliers, _threshold); - } - - double getThreshold() const {return _threshold;} - -private: - double _threshold; + template + double score(const Kernel& kernel, const typename Kernel::ModelT& model, const std::vector& samples, std::vector& inliers) const + { + return score(kernel, model, samples, inliers, _threshold); + } + + double getThreshold() const { return _threshold; } + + private: + double _threshold; }; -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/acRansac_test.cpp b/src/aliceVision/robustEstimation/acRansac_test.cpp index ecba7be845..d9a6b7962c 100644 --- a/src/aliceVision/robustEstimation/acRansac_test.cpp +++ b/src/aliceVision/robustEstimation/acRansac_test.cpp @@ -29,83 +29,80 @@ using namespace aliceVision::robustEstimation; // test ACRANSAC with the AC-adapted Line kernel in a noise/outlier free dataset BOOST_AUTO_TEST_CASE(RansacLineFitter_OutlierFree) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 5); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; - // The base estimator - LineKernel lineKernel(xy, 12, 12); + // The base estimator + LineKernel lineKernel(xy, 12, 12); - // check the best model that fit the most of the data in a robust framework (ACRANSAC). - std::vector inliers; + // check the best model that fit the most of the data in a robust framework (ACRANSAC). + std::vector inliers; - robustEstimation::MatrixModel model; - ACRANSAC(lineKernel, randomNumberGenerator, inliers, 300, &model); + robustEstimation::MatrixModel model; + ACRANSAC(lineKernel, randomNumberGenerator, inliers, 300, &model); - BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); - BOOST_CHECK_EQUAL(5, inliers.size()); + BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); + BOOST_CHECK_EQUAL(5, inliers.size()); } // test without getting back the model BOOST_AUTO_TEST_CASE(RansacLineFitter_OutlierFree_DoNotGetBackModel) { - std::mt19937 randomNumberGenerator; + std::mt19937 randomNumberGenerator; - Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; + Mat2X xy(2, 5); + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; - LineKernel lineKernel(xy, 12, 12); - std::vector inliers; + LineKernel lineKernel(xy, 12, 12); + std::vector inliers; - ACRANSAC(lineKernel, randomNumberGenerator, inliers); + ACRANSAC(lineKernel, randomNumberGenerator, inliers); - BOOST_CHECK_EQUAL(5, inliers.size()); + BOOST_CHECK_EQUAL(5, inliers.size()); } BOOST_AUTO_TEST_CASE(RansacLineFitter_OneOutlier) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 6); - // y = 2x + 1 with an outlier - xy << 1, 2, 3, 4, 5, 100, // (100,-123) is the outlier - 3, 5, 7, 9, 11, -123; + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 6); + // y = 2x + 1 with an outlier + xy << 1, 2, 3, 4, 5, 100, // (100,-123) is the outlier + 3, 5, 7, 9, 11, -123; - // The base estimator - LineKernel lineKernel(xy, 12, 12); + // The base estimator + LineKernel lineKernel(xy, 12, 12); - // Check the best model that fit the most of the data - // in a robust framework (ACRANSAC). - std::vector inliers; - robustEstimation::MatrixModel model; + // Check the best model that fit the most of the data + // in a robust framework (ACRANSAC). + std::vector inliers; + robustEstimation::MatrixModel model; - ACRANSAC(lineKernel, randomNumberGenerator, inliers, 300, &model); + ACRANSAC(lineKernel, randomNumberGenerator, inliers, 300, &model); - BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); - BOOST_CHECK_EQUAL(5, inliers.size()); + BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); + BOOST_CHECK_EQUAL(5, inliers.size()); } - // test if the robust estimator do not return inlier if too few point // was given for an estimation. BOOST_AUTO_TEST_CASE(RansacLineFitter_TooFewPoints) { - std::mt19937 randomNumberGenerator; - Vec2 xy; - // y = 2x + 1 - xy << 1, 2; - LineKernel lineKernel(xy, 12, 12); - std::vector inliers; + std::mt19937 randomNumberGenerator; + Vec2 xy; + // y = 2x + 1 + xy << 1, 2; + LineKernel lineKernel(xy, 12, 12); + std::vector inliers; - ACRANSAC(lineKernel, randomNumberGenerator, inliers); + ACRANSAC(lineKernel, randomNumberGenerator, inliers); - BOOST_CHECK_EQUAL(0, inliers.size()); + BOOST_CHECK_EQUAL(0, inliers.size()); } // from a GT model : @@ -114,110 +111,111 @@ BOOST_AUTO_TEST_CASE(RansacLineFitter_TooFewPoints) // Check that the number of inliers and the model are correct. BOOST_AUTO_TEST_CASE(RansacLineFitter_RealisticCase) { - std::mt19937 randomNumberGenerator; - const int NbPoints = 100; - const float outlierRatio = .3; - Mat2X xy(2, NbPoints); - - Vec2 GTModel; // y = 6.3 x + (-2.0) - GTModel << -2.0, 6.3; - - //-- Build the point list according the given model - for(Mat::Index i = 0; i < NbPoints; ++i) - { - xy.col(i) << i, (double) i * GTModel[1] + GTModel[0]; - } - - // Setup a normal distribution in order to make outlier not aligned - std::mt19937 gen; - std::normal_distribution<> d(0, 5); // More or less 5 units - - //-- Simulate outliers (for the asked percentage amount of the datum) - const int nbPtToNoise = (int) NbPoints * outlierRatio; - std::vector vec_samples(nbPtToNoise); // Fit with unique random index - std::iota(vec_samples.begin(), vec_samples.end(), 0); - for(std::size_t i = 0; i < vec_samples.size(); ++i) - { - const std::size_t randomIndex = vec_samples[i]; - // Start from a outlier point (0,0) - // and move it in a given small range (since it must remains in an outlier area) - xy.col(randomIndex) << d(gen), d(gen); - } - - // The base estimator - LineKernel lineKernel(xy, 12, 12); - - // Check the best model that fit the most of the data - // in a robust framework (ACRANSAC). - std::vector inliers; - robustEstimation::MatrixModel model; - - ACRANSAC(lineKernel, randomNumberGenerator, inliers, 300, &model); - - BOOST_CHECK_EQUAL(NbPoints - nbPtToNoise, inliers.size()); - BOOST_CHECK_SMALL(GTModel(0) - model.getMatrix()[0], 1e-9); - BOOST_CHECK_SMALL(GTModel(1) - model.getMatrix()[1], 1e-9); + std::mt19937 randomNumberGenerator; + const int NbPoints = 100; + const float outlierRatio = .3; + Mat2X xy(2, NbPoints); + + Vec2 GTModel; // y = 6.3 x + (-2.0) + GTModel << -2.0, 6.3; + + //-- Build the point list according the given model + for (Mat::Index i = 0; i < NbPoints; ++i) + { + xy.col(i) << i, (double)i * GTModel[1] + GTModel[0]; + } + + // Setup a normal distribution in order to make outlier not aligned + std::mt19937 gen; + std::normal_distribution<> d(0, 5); // More or less 5 units + + //-- Simulate outliers (for the asked percentage amount of the datum) + const int nbPtToNoise = (int)NbPoints * outlierRatio; + std::vector vec_samples(nbPtToNoise); // Fit with unique random index + std::iota(vec_samples.begin(), vec_samples.end(), 0); + for (std::size_t i = 0; i < vec_samples.size(); ++i) + { + const std::size_t randomIndex = vec_samples[i]; + // Start from a outlier point (0,0) + // and move it in a given small range (since it must remains in an outlier area) + xy.col(randomIndex) << d(gen), d(gen); + } + + // The base estimator + LineKernel lineKernel(xy, 12, 12); + + // Check the best model that fit the most of the data + // in a robust framework (ACRANSAC). + std::vector inliers; + robustEstimation::MatrixModel model; + + ACRANSAC(lineKernel, randomNumberGenerator, inliers, 300, &model); + + BOOST_CHECK_EQUAL(NbPoints - nbPtToNoise, inliers.size()); + BOOST_CHECK_SMALL(GTModel(0) - model.getMatrix()[0], 1e-9); + BOOST_CHECK_SMALL(GTModel(1) - model.getMatrix()[1], 1e-9); } // generate nbPoints along a line and add gaussian noise. // move some point in the dataset to create outlier contamined data -void generateLine(Mat & points, std::size_t nbPoints, int W, int H, float noise, float outlierRatio) +void generateLine(Mat& points, std::size_t nbPoints, int W, int H, float noise, float outlierRatio) { - points = Mat(2, nbPoints); - - Vec2 lineEq(50, 0.3); - - // Setup a normal distribution of mean 0 and amplitude equal to noise - std::mt19937 gen; - std::normal_distribution<> normDistribution(0, noise); - - for(std::size_t i = 0; i < nbPoints; ++i) - { - const float x = i + normDistribution(gen); - float y = normDistribution(gen) + (lineEq[1] * x + lineEq[0]); - points.col(i) = Vec2(x, y); - } - - // generate outlier - std::normal_distribution<> d_outlier(0, 0.2); - std::size_t count = outlierRatio * nbPoints; - const auto vec_indices = randSample(gen, 0, nbPoints, count); -// std::copy(vec_indices.begin(), vec_indices.end(), std::ostream_iterator(std::cout, " ")); -// std::cout << "\n"; - assert(vec_indices.size() == count); -// std::cout << nbPoints << " " << count << "\n"; - for(const auto &idx : vec_indices) - { - std::cout << idx << " "; - assert(idx < points.cols()); - points.col(idx) = Vec2(rand() % W + d_outlier(gen), rand() % H - d_outlier(gen)); - } + points = Mat(2, nbPoints); + + Vec2 lineEq(50, 0.3); + + // Setup a normal distribution of mean 0 and amplitude equal to noise + std::mt19937 gen; + std::normal_distribution<> normDistribution(0, noise); + + for (std::size_t i = 0; i < nbPoints; ++i) + { + const float x = i + normDistribution(gen); + float y = normDistribution(gen) + (lineEq[1] * x + lineEq[0]); + points.col(i) = Vec2(x, y); + } + + // generate outlier + std::normal_distribution<> d_outlier(0, 0.2); + std::size_t count = outlierRatio * nbPoints; + const auto vec_indices = randSample(gen, 0, nbPoints, count); + // std::copy(vec_indices.begin(), vec_indices.end(), std::ostream_iterator(std::cout, " ")); + // std::cout << "\n"; + assert(vec_indices.size() == count); + // std::cout << nbPoints << " " << count << "\n"; + for (const auto& idx : vec_indices) + { + std::cout << idx << " "; + assert(idx < points.cols()); + points.col(idx) = Vec2(rand() % W + d_outlier(gen), rand() % H - d_outlier(gen)); + } } /// Structure used to avoid repetition in a given series struct IndMatchd { - - IndMatchd(double i = 0, double j = 0) : _i(i), _j(j) { } - - friend bool operator==(const IndMatchd& m1, const IndMatchd& m2) - { - return (m1._i == m2._i && m1._j == m2._j); - } - - /// Lexicographical ordering of matches. Used to remove duplicates. - friend bool operator<(const IndMatchd& m1, const IndMatchd& m2) - { - if(m1._i < m2._i) return true; - if(m1._i > m2._i) return false; - - if(m1._j < m2._j) return true; - else - return false; - - } - - double _i, _j; + IndMatchd(double i = 0, double j = 0) + : _i(i), + _j(j) + {} + + friend bool operator==(const IndMatchd& m1, const IndMatchd& m2) { return (m1._i == m2._i && m1._j == m2._j); } + + /// Lexicographical ordering of matches. Used to remove duplicates. + friend bool operator<(const IndMatchd& m1, const IndMatchd& m2) + { + if (m1._i < m2._i) + return true; + if (m1._i > m2._i) + return false; + + if (m1._j < m2._j) + return true; + else + return false; + } + + double _i, _j; }; // test ACRANSAC adaptability to noise @@ -226,48 +224,46 @@ struct IndMatchd // by having it's estimated confidence threshold growing. BOOST_AUTO_TEST_CASE(RansacLineFitter_ACRANSACSimu) { - - const int S = 100; - const int W = S, H = S; - const float outlierRatio = .3f; - Vec2 GTModel; // y = 2x + 1 - GTModel << -2, .3; - std::mt19937 gen; - - std::vector vec_gaussianValue; - for(std::size_t i = 0; i < 10; ++i) - { - vec_gaussianValue.push_back(i / 10. * 5.); - } - - for(const auto& iter : vec_gaussianValue) - { - const double gaussianNoiseLevel = iter; - - std::size_t numPoints = 2.0 * S * sqrt(2.0); - - Mat2X points(2, numPoints); - std::vector vec_inliersGT; - generateLine(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, points, vec_inliersGT); - // robust line estimation - robustEstimation::MatrixModel model; - - // The base estimator - LineKernel lineKernel(points, W, H); - - // Check the best model that fit the most of the data - // in a robust framework (ACRANSAC). - std::vector vec_inliers; - const std::pair ret = ACRANSAC(lineKernel, gen, vec_inliers, 1000, &model); - const double errorMax = ret.first; - - std::cout << "gaussianNoiseLevel " << gaussianNoiseLevel << " \tsqrt(errorMax) " << sqrt(errorMax) << std::endl; - std::cout << "line " << model.getMatrix() << std::endl; - const std::size_t expectedInliers = vec_inliersGT.size(); - - // ACRansac is not really repeatable so we can check at least it does not - // find more inliers than expected. - BOOST_CHECK(vec_inliers.size() <= expectedInliers); - - } + const int S = 100; + const int W = S, H = S; + const float outlierRatio = .3f; + Vec2 GTModel; // y = 2x + 1 + GTModel << -2, .3; + std::mt19937 gen; + + std::vector vec_gaussianValue; + for (std::size_t i = 0; i < 10; ++i) + { + vec_gaussianValue.push_back(i / 10. * 5.); + } + + for (const auto& iter : vec_gaussianValue) + { + const double gaussianNoiseLevel = iter; + + std::size_t numPoints = 2.0 * S * sqrt(2.0); + + Mat2X points(2, numPoints); + std::vector vec_inliersGT; + generateLine(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, points, vec_inliersGT); + // robust line estimation + robustEstimation::MatrixModel model; + + // The base estimator + LineKernel lineKernel(points, W, H); + + // Check the best model that fit the most of the data + // in a robust framework (ACRANSAC). + std::vector vec_inliers; + const std::pair ret = ACRANSAC(lineKernel, gen, vec_inliers, 1000, &model); + const double errorMax = ret.first; + + std::cout << "gaussianNoiseLevel " << gaussianNoiseLevel << " \tsqrt(errorMax) " << sqrt(errorMax) << std::endl; + std::cout << "line " << model.getMatrix() << std::endl; + const std::size_t expectedInliers = vec_inliersGT.size(); + + // ACRansac is not really repeatable so we can check at least it does not + // find more inliers than expected. + BOOST_CHECK(vec_inliers.size() <= expectedInliers); + } } diff --git a/src/aliceVision/robustEstimation/conditioning.cpp b/src/aliceVision/robustEstimation/conditioning.cpp index 0548305155..35d9840540 100644 --- a/src/aliceVision/robustEstimation/conditioning.cpp +++ b/src/aliceVision/robustEstimation/conditioning.cpp @@ -12,68 +12,60 @@ namespace aliceVision { namespace robustEstimation { // HZ 4.4.4 pag.109 -void preconditionerFromPoints(const Mat &points, Mat3 *T) { - Vec mean, variance; - MeanAndVarianceAlongRows(points, &mean, &variance); +void preconditionerFromPoints(const Mat& points, Mat3* T) +{ + Vec mean, variance; + MeanAndVarianceAlongRows(points, &mean, &variance); - double xfactor = sqrt(2.0 / variance(0)); - double yfactor = sqrt(2.0 / variance(1)); + double xfactor = sqrt(2.0 / variance(0)); + double yfactor = sqrt(2.0 / variance(1)); - // If variance is equal to 0.0 set scaling factor to identity. - // -> Else it will provide nan value (because division by 0). - if (variance(0) < 1e-8) - xfactor = mean(0) = 1.0; - if (variance(1) < 1e-8) - yfactor = mean(1) = 1.0; + // If variance is equal to 0.0 set scaling factor to identity. + // -> Else it will provide nan value (because division by 0). + if (variance(0) < 1e-8) + xfactor = mean(0) = 1.0; + if (variance(1) < 1e-8) + yfactor = mean(1) = 1.0; - *T << xfactor, 0, -xfactor * mean(0), - 0, yfactor, -yfactor * mean(1), - 0, 0, 1; + *T << xfactor, 0, -xfactor * mean(0), 0, yfactor, -yfactor * mean(1), 0, 0, 1; } -void preconditionerFromImageSize(int width, int height, Mat3 *T) { - // Build the normalization matrix - double dNorm = 1.0 / sqrt( static_cast(width*height) ); +void preconditionerFromImageSize(int width, int height, Mat3* T) +{ + // Build the normalization matrix + double dNorm = 1.0 / sqrt(static_cast(width * height)); - (*T) = Mat3::Identity(); - (*T)(0,0) = (*T)(1,1) = dNorm; - (*T)(0,2) = -.5f*width*dNorm; - (*T)(1,2) = -.5*height*dNorm; + (*T) = Mat3::Identity(); + (*T)(0, 0) = (*T)(1, 1) = dNorm; + (*T)(0, 2) = -.5f * width * dNorm; + (*T)(1, 2) = -.5 * height * dNorm; } -void applyTransformationToPoints(const Mat &points, - const Mat3 &T, - Mat *transformed_points) +void applyTransformationToPoints(const Mat& points, const Mat3& T, Mat* transformed_points) { - const Mat::Index n = points.cols(); - transformed_points->resize(2,n); - for (Mat::Index i = 0; i < n; ++i) { - Vec3 in, out; - in << points(0, i), points(1, i), 1; - out = T * in; - (*transformed_points)(0, i) = out(0)/out(2); - (*transformed_points)(1, i) = out(1)/out(2); - } + const Mat::Index n = points.cols(); + transformed_points->resize(2, n); + for (Mat::Index i = 0; i < n; ++i) + { + Vec3 in, out; + in << points(0, i), points(1, i), 1; + out = T * in; + (*transformed_points)(0, i) = out(0) / out(2); + (*transformed_points)(1, i) = out(1) / out(2); + } } -void normalizePointsFromImageSize(const Mat &points, - Mat *normalized_points, - Mat3 *T, - int width, - int height) +void normalizePointsFromImageSize(const Mat& points, Mat* normalized_points, Mat3* T, int width, int height) { - preconditionerFromImageSize(width, height, T); - applyTransformationToPoints(points, *T, normalized_points); + preconditionerFromImageSize(width, height, T); + applyTransformationToPoints(points, *T, normalized_points); } - -void normalizePoints(const Mat &points, - Mat *normalized_points, - Mat3 *T) +void normalizePoints(const Mat& points, Mat* normalized_points, Mat3* T) { - preconditionerFromPoints(points, T); - applyTransformationToPoints(points, *T, normalized_points); + preconditionerFromPoints(points, T); + applyTransformationToPoints(points, *T, normalized_points); } -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/conditioning.hpp b/src/aliceVision/robustEstimation/conditioning.hpp index cefcf23557..48463bb210 100644 --- a/src/aliceVision/robustEstimation/conditioning.hpp +++ b/src/aliceVision/robustEstimation/conditioning.hpp @@ -31,12 +31,12 @@ void normalizePoints(const Mat& points, Mat* normalized_points, Mat3* T); /** * @brief Point conditioning (compute Transformation matrix) */ -void preconditionerFromImageSize(int width, int height, Mat3 *T); +void preconditionerFromImageSize(int width, int height, Mat3* T); /** * @brief Normalize point from image coordinates to [-.5, .5] */ void normalizePointsFromImageSize(const Mat& points, Mat* normalized_points, Mat3* T, int width, int height); -} //namespace robustEstimation -} //namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/estimators.hpp b/src/aliceVision/robustEstimation/estimators.hpp index 4190b2505e..849f2c12d6 100644 --- a/src/aliceVision/robustEstimation/estimators.hpp +++ b/src/aliceVision/robustEstimation/estimators.hpp @@ -17,55 +17,52 @@ namespace robustEstimation { enum class ERobustEstimator { - START = 0, - ACRANSAC = 1, //< A-Contrario Ransac. - RANSAC = 2, //< Classic Ransac. - LSMEDS = 3, //< Variant of RANSAC using Least Median of Squares. - LORANSAC = 4, //< LO-Ransac. - MAXCONSENSUS = 5, //< Naive implementation of RANSAC without noise and iteration reduction options. - END + START = 0, + ACRANSAC = 1, //< A-Contrario Ransac. + RANSAC = 2, //< Classic Ransac. + LSMEDS = 3, //< Variant of RANSAC using Least Median of Squares. + LORANSAC = 4, //< LO-Ransac. + MAXCONSENSUS = 5, //< Naive implementation of RANSAC without noise and iteration reduction options. + END }; inline std::string ERobustEstimator_enumToString(ERobustEstimator estimator) { - switch(estimator) - { - case ERobustEstimator::ACRANSAC: - return "acransac"; - case ERobustEstimator::RANSAC: - return "ransac"; - case ERobustEstimator::LSMEDS: - return "lsmeds"; - case ERobustEstimator::LORANSAC: - return "loransac"; - case ERobustEstimator::MAXCONSENSUS: - return "maxconsensus"; - case ERobustEstimator::START: - case ERobustEstimator::END: - break; - } - throw std::out_of_range("Invalid Ransac type Enum"); + switch (estimator) + { + case ERobustEstimator::ACRANSAC: + return "acransac"; + case ERobustEstimator::RANSAC: + return "ransac"; + case ERobustEstimator::LSMEDS: + return "lsmeds"; + case ERobustEstimator::LORANSAC: + return "loransac"; + case ERobustEstimator::MAXCONSENSUS: + return "maxconsensus"; + case ERobustEstimator::START: + case ERobustEstimator::END: + break; + } + throw std::out_of_range("Invalid Ransac type Enum"); } inline ERobustEstimator ERobustEstimator_stringToEnum(const std::string& estimator) { - if(estimator == "acransac") - return ERobustEstimator::ACRANSAC; - if(estimator == "ransac") - return ERobustEstimator::RANSAC; - if(estimator == "lsmeds") - return ERobustEstimator::LSMEDS; - if(estimator == "loransac") - return ERobustEstimator::LORANSAC; - if(estimator == "maxconsensus") - return ERobustEstimator::MAXCONSENSUS; - throw std::out_of_range("Invalid Ransac type string " + estimator); + if (estimator == "acransac") + return ERobustEstimator::ACRANSAC; + if (estimator == "ransac") + return ERobustEstimator::RANSAC; + if (estimator == "lsmeds") + return ERobustEstimator::LSMEDS; + if (estimator == "loransac") + return ERobustEstimator::LORANSAC; + if (estimator == "maxconsensus") + return ERobustEstimator::MAXCONSENSUS; + throw std::out_of_range("Invalid Ransac type string " + estimator); } -inline std::ostream& operator<<(std::ostream& os, ERobustEstimator e) -{ - return os << ERobustEstimator_enumToString(e); -} +inline std::ostream& operator<<(std::ostream& os, ERobustEstimator e) { return os << ERobustEstimator_enumToString(e); } inline std::istream& operator>>(std::istream& in, ERobustEstimator& estimatorType) { @@ -86,32 +83,29 @@ inline std::istream& operator>>(std::istream& in, ERobustEstimator& estimatorTyp * @param value The value for the reprojection or matching error. * @return true if the value is compatible */ -inline bool adjustRobustEstimatorThreshold(ERobustEstimator e, double &value, double defaultLoRansac) +inline bool adjustRobustEstimatorThreshold(ERobustEstimator e, double& value, double defaultLoRansac) { - if(e != ERobustEstimator::LORANSAC && - e != ERobustEstimator::ACRANSAC) - { - ALICEVISION_CERR("Only " << ERobustEstimator::ACRANSAC - << " and " << ERobustEstimator::LORANSAC - << " are supported."); - return false; - } - if(value == 0) - { - if (e == ERobustEstimator::ACRANSAC) + if (e != ERobustEstimator::LORANSAC && e != ERobustEstimator::ACRANSAC) { - // for acransac set it to infinity - value = std::numeric_limits::infinity(); + ALICEVISION_CERR("Only " << ERobustEstimator::ACRANSAC << " and " << ERobustEstimator::LORANSAC << " are supported."); + return false; } - else if(e == ERobustEstimator::LORANSAC) + if (value == 0) { - // for loransac we need a threshold > 0 - value = defaultLoRansac; + if (e == ERobustEstimator::ACRANSAC) + { + // for acransac set it to infinity + value = std::numeric_limits::infinity(); + } + else if (e == ERobustEstimator::LORANSAC) + { + // for loransac we need a threshold > 0 + value = defaultLoRansac; + } } - } - return true; + return true; } -} //namespace robustEstimation -} //namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/leastMedianOfSquares.hpp b/src/aliceVision/robustEstimation/leastMedianOfSquares.hpp index 57d2ab609f..dc9040ba59 100644 --- a/src/aliceVision/robustEstimation/leastMedianOfSquares.hpp +++ b/src/aliceVision/robustEstimation/leastMedianOfSquares.hpp @@ -29,91 +29,81 @@ namespace robustEstimation { * @ref LMedS : Z. Zhang. Determining The Epipolar Geometry And Its Uncertainty. * A Review IJCV 1998 */ -template +template double leastMedianOfSquares(const Kernel& kernel, typename Kernel::ModelT* model = nullptr, double* outlierThreshold = nullptr, double outlierRatio = 0.5, double minProba = 0.99) { - const std::size_t min_samples = kernel.getMinimumNbRequiredSamples(); - const std::size_t total_samples = kernel.nbSamples(); + const std::size_t min_samples = kernel.getMinimumNbRequiredSamples(); + const std::size_t total_samples = kernel.nbSamples(); - std::vector residuals(total_samples); // Array for storing residuals + std::vector residuals(total_samples); // Array for storing residuals - double dBestMedian = std::numeric_limits::max(); + double dBestMedian = std::numeric_limits::max(); - // Required number of iterations is evaluated from outliers ratio - const std::size_t N = (min_samples < total_samples) ? - getNumSamples(minProba, outlierRatio, min_samples) : 0; + // Required number of iterations is evaluated from outliers ratio + const std::size_t N = (min_samples < total_samples) ? getNumSamples(minProba, outlierRatio, min_samples) : 0; - for(std::size_t i = 0; i < N; i++) - { - - std::vector vec_sample(min_samples); - // Get Samples indexes - uniformSample(min_samples, total_samples, vec_sample); + for (std::size_t i = 0; i < N; i++) + { + std::vector vec_sample(min_samples); + // Get Samples indexes + uniformSample(min_samples, total_samples, vec_sample); - // Estimate parameters: the solutions are stored in a vector - std::vector models; - kernel.Fit(vec_sample, &models); + // Estimate parameters: the solutions are stored in a vector + std::vector models; + kernel.Fit(vec_sample, &models); - // Now test the solutions on the whole data - for(std::size_t k = 0; k < models.size(); ++k) - { - //Compute Residuals : - for(std::size_t sampleIdx = 0; sampleIdx < total_samples; ++sampleIdx) - { - double error = kernel.Error(sampleIdx, models[k]); - residuals[sampleIdx] = error; - } - - // Compute median - std::vector::iterator itMedian = residuals.begin() + - std::size_t(total_samples * (1. - outlierRatio)); - std::nth_element(residuals.begin(), itMedian, residuals.end()); - double median = *itMedian; - - // Store best solution - if(median < dBestMedian) - { - dBestMedian = median; - if(model) + // Now test the solutions on the whole data + for (std::size_t k = 0; k < models.size(); ++k) { - (*model) = models[k]; + // Compute Residuals : + for (std::size_t sampleIdx = 0; sampleIdx < total_samples; ++sampleIdx) + { + double error = kernel.Error(sampleIdx, models[k]); + residuals[sampleIdx] = error; + } + + // Compute median + std::vector::iterator itMedian = residuals.begin() + std::size_t(total_samples * (1. - outlierRatio)); + std::nth_element(residuals.begin(), itMedian, residuals.end()); + double median = *itMedian; + + // Store best solution + if (median < dBestMedian) + { + dBestMedian = median; + if (model) + { + (*model) = models[k]; + } + } } - } } - } - - // This array of precomputed values corresponds to the inverse - // cumulative function for a normal distribution. For more information - // consult the litterature (Robust Regression for Outlier Detection, - // rouseeuw-leroy). The values are computed for each 5% - static const double ICDF[21] = { - 1.4e16, 15.94723940, 7.957896558, 5.287692054, - 3.947153876, 3.138344200, 2.595242369, 2.203797543, - 1.906939402, 1.672911853, 1.482602218, 1.323775627, - 1.188182950, 1.069988721, 0.9648473415, 0.8693011162, - 0.7803041458, 0.6946704675, 0.6079568319, 0.5102134568, - 0.3236002672 - }; - - // Evaluate the outlier threshold - if(outlierThreshold) - { - double sigma = ICDF[int((1. - outlierRatio)*20.)] * - (1. + 5. / double(total_samples - min_samples)); - *outlierThreshold = (double) (sigma * sigma * dBestMedian * 4.); - if(N == 0) + + // This array of precomputed values corresponds to the inverse + // cumulative function for a normal distribution. For more information + // consult the litterature (Robust Regression for Outlier Detection, + // rouseeuw-leroy). The values are computed for each 5% + static const double ICDF[21] = {1.4e16, 15.94723940, 7.957896558, 5.287692054, 3.947153876, 3.138344200, 2.595242369, + 2.203797543, 1.906939402, 1.672911853, 1.482602218, 1.323775627, 1.188182950, 1.069988721, + 0.9648473415, 0.8693011162, 0.7803041458, 0.6946704675, 0.6079568319, 0.5102134568, 0.3236002672}; + + // Evaluate the outlier threshold + if (outlierThreshold) { - *outlierThreshold = std::numeric_limits::max(); + double sigma = ICDF[int((1. - outlierRatio) * 20.)] * (1. + 5. / double(total_samples - min_samples)); + *outlierThreshold = (double)(sigma * sigma * dBestMedian * 4.); + if (N == 0) + { + *outlierThreshold = std::numeric_limits::max(); + } } - } - return dBestMedian; + return dBestMedian; } - -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/leastMedianOfSquares_test.cpp b/src/aliceVision/robustEstimation/leastMedianOfSquares_test.cpp index d43f5d8219..ec0039c72f 100644 --- a/src/aliceVision/robustEstimation/leastMedianOfSquares_test.cpp +++ b/src/aliceVision/robustEstimation/leastMedianOfSquares_test.cpp @@ -16,131 +16,127 @@ #include #include - using namespace aliceVision; using namespace aliceVision::robustEstimation; static const double dExpectedPrecision = 1e-9; template -void EvalInlier(const Kernel & kernel, const typename Kernel::Model & model, - double dThreshold, std::vector * vec_inliers) +void EvalInlier(const Kernel& kernel, const typename Kernel::Model& model, double dThreshold, std::vector* vec_inliers) { - ScoreEvaluator scorer(dThreshold); - std::vector vec_index(kernel.NumSamples()); - for (size_t i = 0; i < kernel.NumSamples(); ++i) - vec_index[i] = i; + ScoreEvaluator scorer(dThreshold); + std::vector vec_index(kernel.NumSamples()); + for (size_t i = 0; i < kernel.NumSamples(); ++i) + vec_index[i] = i; - scorer.Score(kernel, model, vec_index, &(*vec_inliers)); + scorer.Score(kernel, model, vec_index, &(*vec_inliers)); } // Test without outlier -BOOST_AUTO_TEST_CASE(LMedsLineFitter_OutlierFree) { - - Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; - - // The base estimator - LineKernel kernel(xy); - - // Check the best model that fit the most of the data - // in a robust framework (LMeds). - Vec2 model; - double dThreshold = std::numeric_limits::infinity(); - double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); - BOOST_CHECK_CLOSE(2.0, model[1], dExpectedPrecision); - BOOST_CHECK_CLOSE(1.0, model[0], dExpectedPrecision); - BOOST_CHECK_CLOSE(0.0, dBestMedian, dExpectedPrecision); - BOOST_CHECK_CLOSE(0.0, dThreshold, dExpectedPrecision); - //Compute which point are inliers (error below dThreshold) - std::vector vec_inliers; - EvalInlier(kernel, model, dExpectedPrecision, &vec_inliers); - BOOST_CHECK_EQUAL(5, vec_inliers.size()); +BOOST_AUTO_TEST_CASE(LMedsLineFitter_OutlierFree) +{ + Mat2X xy(2, 5); + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; + + // The base estimator + LineKernel kernel(xy); + + // Check the best model that fit the most of the data + // in a robust framework (LMeds). + Vec2 model; + double dThreshold = std::numeric_limits::infinity(); + double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); + BOOST_CHECK_CLOSE(2.0, model[1], dExpectedPrecision); + BOOST_CHECK_CLOSE(1.0, model[0], dExpectedPrecision); + BOOST_CHECK_CLOSE(0.0, dBestMedian, dExpectedPrecision); + BOOST_CHECK_CLOSE(0.0, dThreshold, dExpectedPrecision); + // Compute which point are inliers (error below dThreshold) + std::vector vec_inliers; + EvalInlier(kernel, model, dExpectedPrecision, &vec_inliers); + BOOST_CHECK_EQUAL(5, vec_inliers.size()); } // Test efficiency of LMeds to find (inlier/outlier) in contamined data -BOOST_AUTO_TEST_CASE(LMedsLineFitter_OneOutlier) { - - Mat2X xy(2, 6); - // y = 2x + 1 with an outlier - xy << 1, 2, 3, 4, 5, 100, // outlier! - 3, 5, 7, 9, 11, -123; // outlier! - - LineKernel kernel(xy); - - Vec2 model; - double dThreshold = std::numeric_limits::infinity(); - double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); - BOOST_CHECK_CLOSE(2.0, model[1], dExpectedPrecision); - BOOST_CHECK_CLOSE(1.0, model[0], dExpectedPrecision); - BOOST_CHECK_CLOSE(0.0, dBestMedian, dExpectedPrecision); - BOOST_CHECK_CLOSE(0.0, dThreshold, dExpectedPrecision); - //Compute which point are inliers (error below dThreshold) - std::vector vec_inliers; - EvalInlier(kernel, model, dExpectedPrecision, &vec_inliers); - BOOST_CHECK_EQUAL(5, vec_inliers.size()); +BOOST_AUTO_TEST_CASE(LMedsLineFitter_OneOutlier) +{ + Mat2X xy(2, 6); + // y = 2x + 1 with an outlier + xy << 1, 2, 3, 4, 5, 100, // outlier! + 3, 5, 7, 9, 11, -123; // outlier! + + LineKernel kernel(xy); + + Vec2 model; + double dThreshold = std::numeric_limits::infinity(); + double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); + BOOST_CHECK_CLOSE(2.0, model[1], dExpectedPrecision); + BOOST_CHECK_CLOSE(1.0, model[0], dExpectedPrecision); + BOOST_CHECK_CLOSE(0.0, dBestMedian, dExpectedPrecision); + BOOST_CHECK_CLOSE(0.0, dThreshold, dExpectedPrecision); + // Compute which point are inliers (error below dThreshold) + std::vector vec_inliers; + EvalInlier(kernel, model, dExpectedPrecision, &vec_inliers); + BOOST_CHECK_EQUAL(5, vec_inliers.size()); } // Critical test: // Test if the robust estimator do not return inlier if too few point // was given for an estimation. -BOOST_AUTO_TEST_CASE(LMedsLineFitter_TooFewPoints) { - - Mat2X xy(2, 1); - xy << 1, - 3; // y = 2x + 1 with x = 1 - LineKernel kernel(xy); - - Vec2 model; - double dThreshold = std::numeric_limits::infinity(); - double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); - //No inliers - BOOST_CHECK_EQUAL( dBestMedian, std::numeric_limits::max()); +BOOST_AUTO_TEST_CASE(LMedsLineFitter_TooFewPoints) +{ + Mat2X xy(2, 1); + xy << 1, + 3; // y = 2x + 1 with x = 1 + LineKernel kernel(xy); + + Vec2 model; + double dThreshold = std::numeric_limits::infinity(); + double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); + // No inliers + BOOST_CHECK_EQUAL(dBestMedian, std::numeric_limits::max()); } // From a GT model : // Compute a list of point that fit the model. // Add white noise to given amount of points in this list. // Check that the number of inliers and the model are correct. -BOOST_AUTO_TEST_CASE(LMedsLineFitter_RealisticCase) { - - const int NbPoints = 30; - const float outlierRatio = .3; //works with .4 - Mat2X xy(2, NbPoints); - - Vec2 GTModel; // y = 2x + 1 - GTModel << -2.0, 6.3; - - //-- Build the point list according the given model - for(Mat::Index i = 0; i < NbPoints; ++i) - { - xy.col(i) << i, (double)i*GTModel[1] + GTModel[0]; - } - - //-- Add some noise (for the asked percentage amount) - int nbPtToNoise = (int) NbPoints * outlierRatio; - vector vec_samples; // Fit with unique random index - uniformSample(nbPtToNoise, NbPoints, vec_samples); - for(size_t i = 0; i ::infinity(); - double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); - BOOST_CHECK_CLOSE(-2.0, model[0], dExpectedPrecision); - BOOST_CHECK_CLOSE(6.3, model[1], dExpectedPrecision); - //Compute which point are inliers (error below dThreshold) - std::vector vec_inliers; - EvalInlier(kernel, model, dThreshold, &vec_inliers); - BOOST_CHECK(vec_inliers.size()>0); - BOOST_CHECK_EQUAL(NbPoints-nbPtToNoise, vec_inliers.size()); +BOOST_AUTO_TEST_CASE(LMedsLineFitter_RealisticCase) +{ + const int NbPoints = 30; + const float outlierRatio = .3; // works with .4 + Mat2X xy(2, NbPoints); + + Vec2 GTModel; // y = 2x + 1 + GTModel << -2.0, 6.3; + + //-- Build the point list according the given model + for (Mat::Index i = 0; i < NbPoints; ++i) + { + xy.col(i) << i, (double)i * GTModel[1] + GTModel[0]; + } + + //-- Add some noise (for the asked percentage amount) + int nbPtToNoise = (int)NbPoints * outlierRatio; + vector vec_samples; // Fit with unique random index + uniformSample(nbPtToNoise, NbPoints, vec_samples); + for (size_t i = 0; i < vec_samples.size(); ++i) + { + const size_t randomIndex = vec_samples[i]; + // Additive random noise + xy.col(randomIndex) << xy.col(randomIndex)(0) + rand() % 2 - 3, xy.col(randomIndex)(1) + rand() % 8 - 6; + } + + LineKernel kernel(xy); + + Vec2 model; + double dThreshold = std::numeric_limits::infinity(); + double dBestMedian = leastMedianOfSquares(kernel, &model, &dThreshold); + BOOST_CHECK_CLOSE(-2.0, model[0], dExpectedPrecision); + BOOST_CHECK_CLOSE(6.3, model[1], dExpectedPrecision); + // Compute which point are inliers (error below dThreshold) + std::vector vec_inliers; + EvalInlier(kernel, model, dThreshold, &vec_inliers); + BOOST_CHECK(vec_inliers.size() > 0); + BOOST_CHECK_EQUAL(NbPoints - nbPtToNoise, vec_inliers.size()); } diff --git a/src/aliceVision/robustEstimation/lineKernel_test.cpp b/src/aliceVision/robustEstimation/lineKernel_test.cpp index dd4156af79..1a41be168e 100644 --- a/src/aliceVision/robustEstimation/lineKernel_test.cpp +++ b/src/aliceVision/robustEstimation/lineKernel_test.cpp @@ -20,20 +20,19 @@ using namespace aliceVision::robustEstimation; // since the line fitter isn't so simple, test it in isolation. BOOST_AUTO_TEST_CASE(LineFitter_ItWorks) { - Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; - - std::vector models; - LineKernel kernel(xy); - std::vector samples; - - for(size_t i = 0; i < xy.cols(); ++i) - samples.push_back(i); - - kernel.fit(samples, models); - BOOST_CHECK_EQUAL(1, models.size()); - BOOST_CHECK_SMALL(2.0 - models.at(0).getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - models.at(0).getMatrix()[0], 1e-9); + Mat2X xy(2, 5); + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; + + std::vector models; + LineKernel kernel(xy); + std::vector samples; + + for (size_t i = 0; i < xy.cols(); ++i) + samples.push_back(i); + + kernel.fit(samples, models); + BOOST_CHECK_EQUAL(1, models.size()); + BOOST_CHECK_SMALL(2.0 - models.at(0).getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - models.at(0).getMatrix()[0], 1e-9); } diff --git a/src/aliceVision/robustEstimation/lineTestGenerator.hpp b/src/aliceVision/robustEstimation/lineTestGenerator.hpp index 1d1b49af30..b2cb994602 100644 --- a/src/aliceVision/robustEstimation/lineTestGenerator.hpp +++ b/src/aliceVision/robustEstimation/lineTestGenerator.hpp @@ -12,16 +12,15 @@ #include "LineKernel.hpp" #include -#include +#include #include #include #include - /** * @brief Generate a svg file with the ground truth line, the estimated one, the * estimated inliers and outliers. - * + * * @param[in] outfile The name of the svg file to generate. * @param[in] W The width of the image to generate. * @param[in] H The height of the image to generate. @@ -30,45 +29,44 @@ * @param[in] points The points from which the lines are generated. * @param[in] vec_inliers The inliers that fit the estimated line. */ -void drawTest(const std::string &outfile, - int imageWidth, +void drawTest(const std::string& outfile, + int imageWidth, int imageHeight, - const aliceVision::Vec2 &lineGT, - const aliceVision::Vec2 &lineEst, - const aliceVision::Mat &points, - const std::vector &vec_inliers) + const aliceVision::Vec2& lineGT, + const aliceVision::Vec2& lineEst, + const aliceVision::Mat& points, + const std::vector& vec_inliers) { - const std::size_t nbPoints = points.cols(); - svg::svgDrawer svgTest(imageWidth, imageHeight); - for(std::size_t i = 0; i < nbPoints; ++i) - { - std::string sCol = "red"; - float x = points.col(i)[0]; - float y = points.col(i)[1]; - if(std::find(vec_inliers.begin(), vec_inliers.end(), i) != vec_inliers.end()) + const std::size_t nbPoints = points.cols(); + svg::svgDrawer svgTest(imageWidth, imageHeight); + for (std::size_t i = 0; i < nbPoints; ++i) { - sCol = "green"; + std::string sCol = "red"; + float x = points.col(i)[0]; + float y = points.col(i)[1]; + if (std::find(vec_inliers.begin(), vec_inliers.end(), i) != vec_inliers.end()) + { + sCol = "green"; + } + svgTest.drawCircle(x, y, 1, svg::svgStyle().fill(sCol).noStroke()); } - svgTest.drawCircle(x, y, 1, svg::svgStyle().fill(sCol).noStroke()); - } - //draw the found line - float xa = 0, xb = imageWidth; - float ya = lineEst[1] * xa + lineEst[0]; - float yb = lineEst[1] * xb + lineEst[0]; - svgTest.drawLine(xa, ya, xb, yb, svg::svgStyle().stroke("blue", 0.5)); - //draw the GT line - ya = lineGT[1] * xa + lineGT[0]; - yb = lineGT[1] * xb + lineGT[0]; - svgTest.drawLine(xa, ya, xb, yb, svg::svgStyle().stroke("black", 0.5)); - - // ostringstream osSvg; - // osSvg << gaussianNoiseLevel << "_line_" << sqrt(errorMax) << ".svg"; - std::ofstream svgFile(outfile); - svgFile << svgTest.closeSvgFile().str(); - svgFile.close(); + // draw the found line + float xa = 0, xb = imageWidth; + float ya = lineEst[1] * xa + lineEst[0]; + float yb = lineEst[1] * xb + lineEst[0]; + svgTest.drawLine(xa, ya, xb, yb, svg::svgStyle().stroke("blue", 0.5)); + // draw the GT line + ya = lineGT[1] * xa + lineGT[0]; + yb = lineGT[1] * xb + lineGT[0]; + svgTest.drawLine(xa, ya, xb, yb, svg::svgStyle().stroke("black", 0.5)); + + // ostringstream osSvg; + // osSvg << gaussianNoiseLevel << "_line_" << sqrt(errorMax) << ".svg"; + std::ofstream svgFile(outfile); + svgFile << svgTest.closeSvgFile().str(); + svgFile.close(); } - /** * @brief Generate a set of point fitting a line with the given amount of noise * and outliers. @@ -81,72 +79,70 @@ void drawTest(const std::string &outfile, * @param[out] vec_inliersGT The indices of the inliers. */ void generateLine(std::size_t numPoints, - double outlierRatio, - double gaussianNoiseLevel, - const aliceVision::Vec2& GTModel, - std::mt19937& gen, - aliceVision::Mat2X& outPoints, - std::vector& outInliers) + double outlierRatio, + double gaussianNoiseLevel, + const aliceVision::Vec2& GTModel, + std::mt19937& gen, + aliceVision::Mat2X& outPoints, + std::vector& outInliers) { - assert(outlierRatio >= 0 && outlierRatio < 1); - assert(gaussianNoiseLevel >= 0); - assert(numPoints == outPoints.cols()); - - std::normal_distribution<> d(0, gaussianNoiseLevel); - std::uniform_real_distribution realDist(0, 1.0); - const bool withNoise = (gaussianNoiseLevel > std::numeric_limits::epsilon()); + assert(outlierRatio >= 0 && outlierRatio < 1); + assert(gaussianNoiseLevel >= 0); + assert(numPoints == outPoints.cols()); - //-- Build the point list according the given model adding some noise - for(std::size_t i = 0; i < numPoints; ++i) - { - outPoints.col(i) << i, (double) i * GTModel[1] + GTModel[0]; - if(withNoise) + std::normal_distribution<> d(0, gaussianNoiseLevel); + std::uniform_real_distribution realDist(0, 1.0); + const bool withNoise = (gaussianNoiseLevel > std::numeric_limits::epsilon()); + + //-- Build the point list according the given model adding some noise + for (std::size_t i = 0; i < numPoints; ++i) { - const double theta = realDist(gen)*2 * M_PI; -// std::cout << theta << std::endl; - const double radius = d(gen); -// std::cout << radius << std::endl; - outPoints.col(i) += radius * aliceVision::Vec2(std::cos(theta), std::sin(theta)); + outPoints.col(i) << i, (double)i * GTModel[1] + GTModel[0]; + if (withNoise) + { + const double theta = realDist(gen) * 2 * M_PI; + // std::cout << theta << std::endl; + const double radius = d(gen); + // std::cout << radius << std::endl; + outPoints.col(i) += radius * aliceVision::Vec2(std::cos(theta), std::sin(theta)); + } } - } - const int W = std::abs(outPoints(0, 0) - outPoints(0, numPoints - 1)); - const int H = (int) std::fabs(outPoints(1, 0) - outPoints(1, numPoints - 1)); - - //-- Add some outliers (for the asked percentage amount) - const std::size_t nbPtToNoise = (std::size_t) numPoints * outlierRatio; - outInliers.resize(numPoints); - std::iota(outInliers.begin(), outInliers.end(), 0); - - std::vector vec_outliers(nbPtToNoise); - std::iota(vec_outliers.begin(), vec_outliers.end(), 0); -// cout << "xy\n" << xy << std::endl; -// cout << "idx\n"; -// std::copy(vec_outliers.begin(), vec_outliers.end(), std::ostream_iterator(std::cout, " ")); - for(std::size_t i = 0; i < vec_outliers.size(); ++i) - { - const std::size_t randomIndex = vec_outliers[i]; - - outInliers.erase(std::remove(outInliers.begin(), outInliers.end(), randomIndex), outInliers.end()); - - aliceVision::Vec2 pt; - double distance = 0; - // try to generate a point that is well far from the line - std::size_t timeToStop = 0; - const double minDistance = (withNoise) ? 15 * gaussianNoiseLevel : 15; - while(distance < minDistance) + const int W = std::abs(outPoints(0, 0) - outPoints(0, numPoints - 1)); + const int H = (int)std::fabs(outPoints(1, 0) - outPoints(1, numPoints - 1)); + + //-- Add some outliers (for the asked percentage amount) + const std::size_t nbPtToNoise = (std::size_t)numPoints * outlierRatio; + outInliers.resize(numPoints); + std::iota(outInliers.begin(), outInliers.end(), 0); + + std::vector vec_outliers(nbPtToNoise); + std::iota(vec_outliers.begin(), vec_outliers.end(), 0); + // cout << "xy\n" << xy << std::endl; + // cout << "idx\n"; + // std::copy(vec_outliers.begin(), vec_outliers.end(), std::ostream_iterator(std::cout, " ")); + for (std::size_t i = 0; i < vec_outliers.size(); ++i) { - assert(timeToStop < 200); - pt(0) = realDist(gen) * W; - pt(1) = realDist(gen) * H; - distance = aliceVision::robustEstimation::pointToLineError(GTModel, pt); - ++timeToStop; + const std::size_t randomIndex = vec_outliers[i]; + + outInliers.erase(std::remove(outInliers.begin(), outInliers.end(), randomIndex), outInliers.end()); + + aliceVision::Vec2 pt; + double distance = 0; + // try to generate a point that is well far from the line + std::size_t timeToStop = 0; + const double minDistance = (withNoise) ? 15 * gaussianNoiseLevel : 15; + while (distance < minDistance) + { + assert(timeToStop < 200); + pt(0) = realDist(gen) * W; + pt(1) = realDist(gen) * H; + distance = aliceVision::robustEstimation::pointToLineError(GTModel, pt); + ++timeToStop; + } + // total += timeToStop; + + outPoints.col(randomIndex) = pt; } -// total += timeToStop; - - outPoints.col(randomIndex) = pt; - } -// std::cout << "\nTotal attempts: " << total << std::endl; - assert(numPoints - nbPtToNoise == outInliers.size()); - + // std::cout << "\nTotal attempts: " << total << std::endl; + assert(numPoints - nbPtToNoise == outInliers.size()); } - diff --git a/src/aliceVision/robustEstimation/loRansac_test.cpp b/src/aliceVision/robustEstimation/loRansac_test.cpp index 4116a96eee..9e2e14d1f7 100644 --- a/src/aliceVision/robustEstimation/loRansac_test.cpp +++ b/src/aliceVision/robustEstimation/loRansac_test.cpp @@ -26,90 +26,82 @@ using namespace aliceVision; using namespace aliceVision::robustEstimation; void lineFittingTest(std::size_t numPoints, - double outlierRatio, - double gaussianNoiseLevel, - const Vec2& GTModel, - std::mt19937& gen, - Vec2& estimatedModel, - std::vector& vec_inliers) + double outlierRatio, + double gaussianNoiseLevel, + const Vec2& GTModel, + std::mt19937& gen, + Vec2& estimatedModel, + std::vector& vec_inliers) { - - assert(outlierRatio >= 0 && outlierRatio < 1); - assert(gaussianNoiseLevel >= 0); - - Mat2X xy(2, numPoints); - std::vector vec_inliersGT; - generateLine(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, xy, vec_inliersGT); - - const bool withNoise = (gaussianNoiseLevel > std::numeric_limits::epsilon()); - const std::size_t expectedInliers = numPoints - (std::size_t) numPoints * outlierRatio; - const double threshold = (withNoise) ? 3 * gaussianNoiseLevel : 0.3; - LineKernel kernel(xy); - LineKernel::ModelT model = LO_RANSAC(kernel, ScoreEvaluator(threshold), gen, &vec_inliers); - estimatedModel = model.getMatrix(); - - ALICEVISION_LOG_DEBUG("#inliers found : " << vec_inliers.size() << " expected: " << numPoints - expectedInliers); - ALICEVISION_LOG_DEBUG("model[0] found : " << estimatedModel[0] << " expected: " << GTModel[0]); - ALICEVISION_LOG_DEBUG("model[1] found : " << estimatedModel[1] << " expected: " << GTModel[1]); - - const std::string base = "testRansac_line_t" + std::to_string(threshold) + "_n" + std::to_string(gaussianNoiseLevel); - const int W = std::abs(xy(0, 0) - xy(0, numPoints - 1)); - const int H = (int) std::fabs(xy(1, 0) - xy(1, numPoints - 1)); - drawTest(base + "_LORANSACtrial" + std::to_string(0) + ".svg", - W, H, - GTModel, - estimatedModel, - xy, - vec_inliers); + assert(outlierRatio >= 0 && outlierRatio < 1); + assert(gaussianNoiseLevel >= 0); + + Mat2X xy(2, numPoints); + std::vector vec_inliersGT; + generateLine(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, xy, vec_inliersGT); + + const bool withNoise = (gaussianNoiseLevel > std::numeric_limits::epsilon()); + const std::size_t expectedInliers = numPoints - (std::size_t)numPoints * outlierRatio; + const double threshold = (withNoise) ? 3 * gaussianNoiseLevel : 0.3; + LineKernel kernel(xy); + LineKernel::ModelT model = LO_RANSAC(kernel, ScoreEvaluator(threshold), gen, &vec_inliers); + estimatedModel = model.getMatrix(); + + ALICEVISION_LOG_DEBUG("#inliers found : " << vec_inliers.size() << " expected: " << numPoints - expectedInliers); + ALICEVISION_LOG_DEBUG("model[0] found : " << estimatedModel[0] << " expected: " << GTModel[0]); + ALICEVISION_LOG_DEBUG("model[1] found : " << estimatedModel[1] << " expected: " << GTModel[1]); + + const std::string base = "testRansac_line_t" + std::to_string(threshold) + "_n" + std::to_string(gaussianNoiseLevel); + const int W = std::abs(xy(0, 0) - xy(0, numPoints - 1)); + const int H = (int)std::fabs(xy(1, 0) - xy(1, numPoints - 1)); + drawTest(base + "_LORANSACtrial" + std::to_string(0) + ".svg", W, H, GTModel, estimatedModel, xy, vec_inliers); } - BOOST_AUTO_TEST_CASE(LoRansacLineFitter_IdealCaseLoRansac) { - const std::size_t numPoints = 300; - const double outlierRatio = .3; - const double gaussianNoiseLevel = 0.0; - const std::size_t numTrials = 10; - Mat2X xy(2, numPoints); - std::mt19937 gen; - - Vec2 GTModel; // y = 2x + 6.3 - GTModel << -2.0, 6.3; - - for(std::size_t trial = 0; trial < numTrials; ++trial) - { - Vec2 model; - std::vector inliers; - lineFittingTest(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, model, inliers); - const std::size_t expectedInliers = numPoints - (std::size_t) numPoints * outlierRatio; - - BOOST_CHECK_EQUAL(expectedInliers, inliers.size()); - BOOST_CHECK_SMALL(GTModel[0]-model[0], 1e-2); - BOOST_CHECK_SMALL(GTModel[1]-model[1], 1e-2); - } + const std::size_t numPoints = 300; + const double outlierRatio = .3; + const double gaussianNoiseLevel = 0.0; + const std::size_t numTrials = 10; + Mat2X xy(2, numPoints); + std::mt19937 gen; + + Vec2 GTModel; // y = 2x + 6.3 + GTModel << -2.0, 6.3; + + for (std::size_t trial = 0; trial < numTrials; ++trial) + { + Vec2 model; + std::vector inliers; + lineFittingTest(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, model, inliers); + const std::size_t expectedInliers = numPoints - (std::size_t)numPoints * outlierRatio; + + BOOST_CHECK_EQUAL(expectedInliers, inliers.size()); + BOOST_CHECK_SMALL(GTModel[0] - model[0], 1e-2); + BOOST_CHECK_SMALL(GTModel[1] - model[1], 1e-2); + } } - BOOST_AUTO_TEST_CASE(LoRansacLineFitter_RealCaseLoRansac) { - const std::size_t numPoints = 300; - const double outlierRatio = .3; - const double gaussianNoiseLevel = 0.01; - const std::size_t numTrials = 10; - - Mat2X xy(2, numPoints); - - Vec2 GTModel; // y = 2x + 1 - GTModel << -2, .3; - - std::mt19937 gen; - - for(std::size_t trial = 0; trial < numTrials; ++trial) - { - Vec2 model; - std::vector inliers; - lineFittingTest(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, model, inliers); - const std::size_t expectedInliers = numPoints - (std::size_t) numPoints * outlierRatio; - BOOST_CHECK_EQUAL(expectedInliers, inliers.size()); - } + const std::size_t numPoints = 300; + const double outlierRatio = .3; + const double gaussianNoiseLevel = 0.01; + const std::size_t numTrials = 10; + + Mat2X xy(2, numPoints); + + Vec2 GTModel; // y = 2x + 1 + GTModel << -2, .3; + + std::mt19937 gen; + + for (std::size_t trial = 0; trial < numTrials; ++trial) + { + Vec2 model; + std::vector inliers; + lineFittingTest(numPoints, outlierRatio, gaussianNoiseLevel, GTModel, gen, model, inliers); + const std::size_t expectedInliers = numPoints - (std::size_t)numPoints * outlierRatio; + BOOST_CHECK_EQUAL(expectedInliers, inliers.size()); + } } diff --git a/src/aliceVision/robustEstimation/maxConsensus.hpp b/src/aliceVision/robustEstimation/maxConsensus.hpp index 1c3b7d6c10..f619f9f62c 100644 --- a/src/aliceVision/robustEstimation/maxConsensus.hpp +++ b/src/aliceVision/robustEstimation/maxConsensus.hpp @@ -12,7 +12,7 @@ #include namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /// Naive implementation of RANSAC without noise and iteration reduction options /// Pick max_iteration times N_samples and Fit a solution. @@ -32,10 +32,10 @@ namespace robustEstimation{ template typename Kernel::ModelT maxConsensus(const Kernel& kernel, const Scorer& scorer, - std::mt19937 & randomNumberGenerator, + std::mt19937& randomNumberGenerator, std::vector* best_inliers = nullptr, - std::size_t max_iteration = 1024) { - + std::size_t max_iteration = 1024) +{ const std::size_t min_samples = kernel.getMinimumNbRequiredSamples(); const std::size_t total_samples = kernel.nbSamples(); @@ -43,52 +43,52 @@ typename Kernel::ModelT maxConsensus(const Kernel& kernel, typename Kernel::ModelT best_model; // Test if we have sufficient points to for the kernel. - if (total_samples < min_samples) + if (total_samples < min_samples) { - if (best_inliers) - { - best_inliers->resize(0); - } - return best_model; + if (best_inliers) + { + best_inliers->resize(0); + } + return best_model; } // In this robust estimator, the scorer always works on all the data points // at once. So precompute the list ahead of time. std::vector all_samples; - for(std::size_t i = 0; i < total_samples; ++i) + for (std::size_t i = 0; i < total_samples; ++i) { - all_samples.push_back(i); + all_samples.push_back(i); } - for(std::size_t iteration = 0; iteration < max_iteration; ++iteration) + for (std::size_t iteration = 0; iteration < max_iteration; ++iteration) { - std::vector sample; - uniformSample(randomNumberGenerator, min_samples, total_samples, sample); + std::vector sample; + uniformSample(randomNumberGenerator, min_samples, total_samples, sample); - std::vector models; - kernel.fit(sample, models); + std::vector models; + kernel.fit(sample, models); - // Compute costs for each fit. - for(std::size_t i = 0; i < models.size(); ++i) - { - std::vector inliers; - scorer.score(kernel, models[i], all_samples, inliers); - - if (best_num_inliers < inliers.size()) + // Compute costs for each fit. + for (std::size_t i = 0; i < models.size(); ++i) { - //ALICEVISION_LOG_DEBUG("Fit cost: " << cost/inliers.size() - // << ", number of inliers: " << inliers.size()); - best_num_inliers = inliers.size(); - best_model = models[i]; - if (best_inliers) - { - best_inliers->swap(inliers); - } + std::vector inliers; + scorer.score(kernel, models[i], all_samples, inliers); + + if (best_num_inliers < inliers.size()) + { + // ALICEVISION_LOG_DEBUG("Fit cost: " << cost/inliers.size() + // << ", number of inliers: " << inliers.size()); + best_num_inliers = inliers.size(); + best_model = models[i]; + if (best_inliers) + { + best_inliers->swap(inliers); + } + } } - } } return best_model; } -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/maxConsensus_test.cpp b/src/aliceVision/robustEstimation/maxConsensus_test.cpp index 76dc6270ab..a97f10fc7e 100644 --- a/src/aliceVision/robustEstimation/maxConsensus_test.cpp +++ b/src/aliceVision/robustEstimation/maxConsensus_test.cpp @@ -22,55 +22,53 @@ using namespace aliceVision::robustEstimation; // Test without outlier BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OutlierFree) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; - - // The base estimator - LineKernel kernel(xy); - - // Check the best model that fit the most of the data - // in a robust framework (Max-consensus). - std::vector inliers; - LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); - BOOST_CHECK_EQUAL(5, inliers.size()); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 5); + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; + + // The base estimator + LineKernel kernel(xy); + + // Check the best model that fit the most of the data + // in a robust framework (Max-consensus). + std::vector inliers; + LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); + BOOST_CHECK_EQUAL(5, inliers.size()); } // Test without getting back the model BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OutlierFree_DoNotGetBackModel) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; - - LineKernel kernel(xy); - std::vector inliers; - LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_EQUAL(5, inliers.size()); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 5); + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; + + LineKernel kernel(xy); + std::vector inliers; + LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_EQUAL(5, inliers.size()); } // Test efficiency of MaxConsensus to find (inlier/outlier) in contamined data BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OneOutlier) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 6); - // y = 2x + 1 with an outlier - xy << 1, 2, 3, 4, 5, 100, // outlier! - 3, 5, 7, 9, 11, -123; // outlier! - - LineKernel kernel(xy); - - std::vector inliers; - LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); - BOOST_CHECK_EQUAL(5, inliers.size()); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 6); + // y = 2x + 1 with an outlier + xy << 1, 2, 3, 4, 5, 100, // outlier! + 3, 5, 7, 9, 11, -123; // outlier! + + LineKernel kernel(xy); + + std::vector inliers; + LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); + BOOST_CHECK_EQUAL(5, inliers.size()); } // Critical test: @@ -78,14 +76,14 @@ BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OneOutlier) // was given for an estimation. BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_TooFewPoints) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 1); - xy << 1, - 3; // y = 2x + 1 with x = 1 - LineKernel kernel(xy); - std::vector inliers; - const LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_EQUAL(0, inliers.size()); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 1); + xy << 1, + 3; // y = 2x + 1 with x = 1 + LineKernel kernel(xy); + std::vector inliers; + const LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_EQUAL(0, inliers.size()); } // From a GT model : @@ -94,36 +92,35 @@ BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_TooFewPoints) // Check that the number of inliers and the model are correct. BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_RealisticCase) { - std::mt19937 randomNumberGenerator; - const int numPoints = 30; - const float outlierRatio = .3; //works with .4 - Mat2X xy(2, numPoints); - - Vec2 GTModel; // y = 2x + 1 - GTModel << -2.0, 6.3; - - //-- Build the point list according the given model - for(Mat::Index i = 0; i < numPoints; ++i) - { - xy.col(i) << i, (double)i*GTModel[1] + GTModel[0]; - } - - //-- Add some noise (for the asked percentage amount) - int nbPtToNoise = (int) numPoints * outlierRatio; - std::vector vec_samples; // Fit with unique random index - uniformSample(randomNumberGenerator, nbPtToNoise, numPoints, vec_samples); - for(std::size_t i = 0; i inliers; - LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_EQUAL(numPoints-nbPtToNoise, inliers.size()); - BOOST_CHECK_SMALL((-2.0) - model.getMatrix()[0], 1e-9); - BOOST_CHECK_SMALL( 6.3 - model.getMatrix()[1], 1e-9); + std::mt19937 randomNumberGenerator; + const int numPoints = 30; + const float outlierRatio = .3; // works with .4 + Mat2X xy(2, numPoints); + + Vec2 GTModel; // y = 2x + 1 + GTModel << -2.0, 6.3; + + //-- Build the point list according the given model + for (Mat::Index i = 0; i < numPoints; ++i) + { + xy.col(i) << i, (double)i * GTModel[1] + GTModel[0]; + } + + //-- Add some noise (for the asked percentage amount) + int nbPtToNoise = (int)numPoints * outlierRatio; + std::vector vec_samples; // Fit with unique random index + uniformSample(randomNumberGenerator, nbPtToNoise, numPoints, vec_samples); + for (std::size_t i = 0; i < vec_samples.size(); ++i) + { + const std::size_t randomIndex = vec_samples[i]; + // Additive random noise + xy.col(randomIndex) << xy.col(randomIndex)(0) + rand() % 2 - 3, xy.col(randomIndex)(1) + rand() % 8 - 6; + } + + LineKernel kernel(xy); + std::vector inliers; + LineKernel::ModelT model = maxConsensus(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_EQUAL(numPoints - nbPtToNoise, inliers.size()); + BOOST_CHECK_SMALL((-2.0) - model.getMatrix()[0], 1e-9); + BOOST_CHECK_SMALL(6.3 - model.getMatrix()[1], 1e-9); } diff --git a/src/aliceVision/robustEstimation/randSampling.hpp b/src/aliceVision/robustEstimation/randSampling.hpp index d928af9224..23a14220bf 100644 --- a/src/aliceVision/robustEstimation/randSampling.hpp +++ b/src/aliceVision/robustEstimation/randSampling.hpp @@ -17,19 +17,18 @@ #include namespace aliceVision { -namespace robustEstimation{ - +namespace robustEstimation { /** - * @brief Generate a unique random samples without replacement in the + * @brief Generate a unique random samples without replacement in the * range [lowerBound upperBound). * It is modeled after Matlab function with the same name, and it tries to optimize * the generation of the random samples: if the number of required samples is a - * large ratio of the range, then it shuffles a vector containing all the numbers + * large ratio of the range, then it shuffles a vector containing all the numbers * in the range and it takes the first numSamples elements. Otherwise it proceeds - * by drawing random numbers until the numSamples elements are generated, using + * by drawing random numbers until the numSamples elements are generated, using * Robert Floyd's algorithm. - * + * * @param[in] generator the random number generator to use * @param[in] lowerBound The lower bound of the range. * @param[in] upperBound The upper bound of the range (not included). @@ -37,78 +36,71 @@ namespace robustEstimation{ * @return samples The vector containing the samples. */ template -inline std::vector randSample(std::mt19937 & randomNumberGenerator, - IntT lowerBound, - IntT upperBound, - IntT numSamples) +inline std::vector randSample(std::mt19937& randomNumberGenerator, IntT lowerBound, IntT upperBound, IntT numSamples) { - const auto rangeSize = upperBound - lowerBound; - - assert(lowerBound < upperBound); - assert(numSamples <= rangeSize); - static_assert(std::is_integral::value, "Only integer types are supported"); + const auto rangeSize = upperBound - lowerBound; + + assert(lowerBound < upperBound); + assert(numSamples <= rangeSize); + static_assert(std::is_integral::value, "Only integer types are supported"); - if(numSamples * 1.5 > rangeSize) - { - // if the number of required samples is a large fraction of the range size - // generate a vector with all the elements in the range, shuffle it and - // return the first numSample elements. - // this should be more time efficient than drawing at each time. - std::vector result(rangeSize); - std::iota(result.begin(), result.end(), lowerBound); - std::shuffle(result.begin(), result.end(), randomNumberGenerator); - result.resize(numSamples); - return result; - } - else - { - // otherwise if the number of required samples is small wrt the range - // use the optimized Robert Floyd algorithm. - // this has linear complexity and minimize the memory usage. - std::unordered_set samples; - for(IntT d = upperBound - numSamples; d < upperBound; ++d) + if (numSamples * 1.5 > rangeSize) { - IntT t = std::uniform_int_distribution<>(0, d)(randomNumberGenerator) + lowerBound; - if(samples.find(t) == samples.end()) - samples.insert(t); - else - samples.insert(d); + // if the number of required samples is a large fraction of the range size + // generate a vector with all the elements in the range, shuffle it and + // return the first numSample elements. + // this should be more time efficient than drawing at each time. + std::vector result(rangeSize); + std::iota(result.begin(), result.end(), lowerBound); + std::shuffle(result.begin(), result.end(), randomNumberGenerator); + result.resize(numSamples); + return result; + } + else + { + // otherwise if the number of required samples is small wrt the range + // use the optimized Robert Floyd algorithm. + // this has linear complexity and minimize the memory usage. + std::unordered_set samples; + for (IntT d = upperBound - numSamples; d < upperBound; ++d) + { + IntT t = std::uniform_int_distribution<>(0, d)(randomNumberGenerator) + lowerBound; + if (samples.find(t) == samples.end()) + samples.insert(t); + else + samples.insert(d); + } + assert(samples.size() == numSamples); + std::vector result(std::make_move_iterator(samples.begin()), std::make_move_iterator(samples.end())); + return result; } - assert(samples.size() == numSamples); - std::vector result(std::make_move_iterator(samples.begin()), - std::make_move_iterator(samples.end())); - return result; - } } /** -* @brief Pick a random subset of the integers in the range [0, upperBound). -* -* @param[in] generator the random number generator to use -* @param[in] numSamples The number of samples to produce. -* @param[in] upperBound The upper bound of the range. -* @param[out] samples The set containing the random numbers in the range [0, upperBound) -*/ + * @brief Pick a random subset of the integers in the range [0, upperBound). + * + * @param[in] generator the random number generator to use + * @param[in] numSamples The number of samples to produce. + * @param[in] upperBound The upper bound of the range. + * @param[out] samples The set containing the random numbers in the range [0, upperBound) + */ template -inline void uniformSample(std::mt19937 & randomNumberGenerator, - std::size_t numSamples, - std::size_t upperBound, - std::set &samples) +inline void uniformSample(std::mt19937& randomNumberGenerator, std::size_t numSamples, std::size_t upperBound, std::set& samples) { - assert(numSamples <= upperBound); - static_assert(std::is_integral::value, "Only integer types are supported"); - - const auto vecSamples = randSample(randomNumberGenerator, 0, upperBound, numSamples); - for(const auto& s : vecSamples) - { - samples.insert(s); - } - assert(samples.size() == numSamples); + assert(numSamples <= upperBound); + static_assert(std::is_integral::value, "Only integer types are supported"); + + const auto vecSamples = randSample(randomNumberGenerator, 0, upperBound, numSamples); + for (const auto& s : vecSamples) + { + samples.insert(s); + } + assert(samples.size() == numSamples); } /** * @brief Generate a unique random samples in the range [lowerBound upperBound). - * + * * @param[in] generator the random number generator to use * @param[in] lowerBound The lower bound of the range. * @param[in] upperBound The upper bound of the range (not included). @@ -116,53 +108,50 @@ inline void uniformSample(std::mt19937 & randomNumberGenerator, * @param[out] samples The vector containing the samples. */ template -inline void uniformSample(std::mt19937 & randomNumberGenerator, +inline void uniformSample(std::mt19937& randomNumberGenerator, std::size_t lowerBound, std::size_t upperBound, std::size_t numSamples, - std::vector &samples) + std::vector& samples) { - samples = randSample(randomNumberGenerator, lowerBound, upperBound, numSamples); + samples = randSample(randomNumberGenerator, lowerBound, upperBound, numSamples); } /** * @brief Generate a unique random samples in the range [0 upperBound). - * + * * @param[in] generator the random number generator to use * @param[in] numSamples Number of unique samples to draw. * @param[in] upperBound The value at the end of the range (not included). * @param[out] samples The vector containing the samples. */ template -inline void uniformSample(std::mt19937 & randomNumberGenerator, - std::size_t numSamples, - std::size_t upperBound, - std::vector &samples) +inline void uniformSample(std::mt19937& randomNumberGenerator, std::size_t numSamples, std::size_t upperBound, std::vector& samples) { - uniformSample(randomNumberGenerator, 0, upperBound, numSamples, samples); + uniformSample(randomNumberGenerator, 0, upperBound, numSamples, samples); } /** * @brief Generate a random sequence containing a sampling without replacement of * of the elements of the input vector. - * + * * @param[in] generator the random number generator to use * @param[in] sampleSize The size of the sample to generate. * @param[in] elements The possible data indices. * @param[out] sample The random sample of sizeSample indices. */ -inline void uniformSample(std::mt19937 & randomNumberGenerator, +inline void uniformSample(std::mt19937& randomNumberGenerator, std::size_t sampleSize, const std::vector& elements, std::vector& sample) { - sample = randSample(randomNumberGenerator, 0, elements.size(), sampleSize); - assert(sample.size() == sampleSize); - for(auto& s : sample) - { - s = elements[ s ]; - } + sample = randSample(randomNumberGenerator, 0, elements.size(), sampleSize); + assert(sample.size() == sampleSize); + for (auto& s : sample) + { + s = elements[s]; + } } -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/randSampling_test.cpp b/src/aliceVision/robustEstimation/randSampling_test.cpp index e1a10ccf1f..4a2adff947 100644 --- a/src/aliceVision/robustEstimation/randSampling_test.cpp +++ b/src/aliceVision/robustEstimation/randSampling_test.cpp @@ -19,104 +19,105 @@ using namespace aliceVision; using namespace aliceVision::robustEstimation; // Assert that each time exactly N random number are picked (no repetition) -BOOST_AUTO_TEST_CASE(UniformSampleTest_NoRepetions) { +BOOST_AUTO_TEST_CASE(UniformSampleTest_NoRepetions) +{ + std::mt19937 randomNumberGenerator; - std::mt19937 randomNumberGenerator; - - for(std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) - { - //Size of the data set - for(std::size_t numSamples = 1; numSamples <= upperBound; numSamples *= 2) - { - //Size of the consensus set - std::vector samples; - std::cout << "Upper " << upperBound << " Lower " << 0 << " numSamples " << numSamples << "\n"; - uniformSample(randomNumberGenerator, numSamples, upperBound, samples); - std::set myset; - for(const auto& s : samples) - { - myset.insert(s); - BOOST_CHECK(s >= 0); - BOOST_CHECK(s < upperBound); - } - BOOST_CHECK_EQUAL(numSamples, myset.size()); + for (std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) + { + // Size of the data set + for (std::size_t numSamples = 1; numSamples <= upperBound; numSamples *= 2) + { + // Size of the consensus set + std::vector samples; + std::cout << "Upper " << upperBound << " Lower " << 0 << " numSamples " << numSamples << "\n"; + uniformSample(randomNumberGenerator, numSamples, upperBound, samples); + std::set myset; + for (const auto& s : samples) + { + myset.insert(s); + BOOST_CHECK(s >= 0); + BOOST_CHECK(s < upperBound); + } + BOOST_CHECK_EQUAL(numSamples, myset.size()); + } } - } } -BOOST_AUTO_TEST_CASE(UniformSampleTest_UniformSampleSet) { - - std::mt19937 randomNumberGenerator; +BOOST_AUTO_TEST_CASE(UniformSampleTest_UniformSampleSet) +{ + std::mt19937 randomNumberGenerator; - for(std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) - { - //Size of the data set - for(std::size_t numSample = 1; numSample <= upperBound; numSample *= 2) - { - //Size of the consensus set - std::cout << "Upper " << upperBound << " Lower " << 0 << " numSamples " << numSample << "\n"; - std::set samples; - uniformSample(randomNumberGenerator, numSample, upperBound, samples); - BOOST_CHECK_EQUAL(numSample, samples.size()); - for(const auto& s : samples) - { - BOOST_CHECK(s >= 0); - BOOST_CHECK(s < upperBound); - } + for (std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) + { + // Size of the data set + for (std::size_t numSample = 1; numSample <= upperBound; numSample *= 2) + { + // Size of the consensus set + std::cout << "Upper " << upperBound << " Lower " << 0 << " numSamples " << numSample << "\n"; + std::set samples; + uniformSample(randomNumberGenerator, numSample, upperBound, samples); + BOOST_CHECK_EQUAL(numSample, samples.size()); + for (const auto& s : samples) + { + BOOST_CHECK(s >= 0); + BOOST_CHECK(s < upperBound); + } + } } - } } -BOOST_AUTO_TEST_CASE(UniformSampleTest_NoRepetionsBeginEnd) { +BOOST_AUTO_TEST_CASE(UniformSampleTest_NoRepetionsBeginEnd) +{ + std::mt19937 randomNumberGenerator; - std::mt19937 randomNumberGenerator; - - for(std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) - { - //Size of the data set - for(std::size_t numSamples = 1; numSamples <= upperBound; numSamples *= 2) - { - //Size of the consensus set - assert(upperBound >= numSamples); - const std::size_t begin = upperBound-numSamples; - std::cout << "Upper " << upperBound << " Lower " << begin << " numSamples " << numSamples << "\n"; - std::vector samples; - uniformSample(randomNumberGenerator, begin, upperBound, numSamples, samples); - std::set myset; - for(const auto& s : samples) - { - myset.insert(s); - BOOST_CHECK(s >= begin); - BOOST_CHECK(s < upperBound); - } - BOOST_CHECK_EQUAL(numSamples, myset.size()); + for (std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) + { + // Size of the data set + for (std::size_t numSamples = 1; numSamples <= upperBound; numSamples *= 2) + { + // Size of the consensus set + assert(upperBound >= numSamples); + const std::size_t begin = upperBound - numSamples; + std::cout << "Upper " << upperBound << " Lower " << begin << " numSamples " << numSamples << "\n"; + std::vector samples; + uniformSample(randomNumberGenerator, begin, upperBound, numSamples, samples); + std::set myset; + for (const auto& s : samples) + { + myset.insert(s); + BOOST_CHECK(s >= begin); + BOOST_CHECK(s < upperBound); + } + BOOST_CHECK_EQUAL(numSamples, myset.size()); + } } - } } -BOOST_AUTO_TEST_CASE(UniformSampleTest_randSample) { - std::mt19937 randomNumberGenerator; +BOOST_AUTO_TEST_CASE(UniformSampleTest_randSample) +{ + std::mt19937 randomNumberGenerator; + + for (std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) + { + for (std::size_t numSamples = 1; numSamples <= upperBound; numSamples *= 2) + { + assert(upperBound >= numSamples); + const std::size_t lowerBound = upperBound - numSamples; + const auto samples = randSample(randomNumberGenerator, lowerBound, upperBound, numSamples); - for(std::size_t upperBound = 1; upperBound < 513; upperBound *= 2) - { - for(std::size_t numSamples = 1; numSamples <= upperBound; numSamples *= 2) - { - assert(upperBound >= numSamples); - const std::size_t lowerBound = upperBound-numSamples; - const auto samples = randSample(randomNumberGenerator, lowerBound, upperBound, numSamples); - - std::set myset; - std::cout << "Upper " << upperBound << " Lower " << lowerBound << " numSamples " << numSamples << "\n"; - for(const auto& s : samples) - { -// std::cout << samples[i] << " "; - myset.insert(s); - BOOST_CHECK(s >= lowerBound); - BOOST_CHECK(s < upperBound); - } -// std::cout << "\n"; - // this verifies no repetitions - BOOST_CHECK_EQUAL(numSamples, myset.size()); + std::set myset; + std::cout << "Upper " << upperBound << " Lower " << lowerBound << " numSamples " << numSamples << "\n"; + for (const auto& s : samples) + { + // std::cout << samples[i] << " "; + myset.insert(s); + BOOST_CHECK(s >= lowerBound); + BOOST_CHECK(s < upperBound); + } + // std::cout << "\n"; + // this verifies no repetitions + BOOST_CHECK_EQUAL(numSamples, myset.size()); + } } - } } diff --git a/src/aliceVision/robustEstimation/ransacTools.hpp b/src/aliceVision/robustEstimation/ransacTools.hpp index b9a67f05ce..6542fcf3ec 100644 --- a/src/aliceVision/robustEstimation/ransacTools.hpp +++ b/src/aliceVision/robustEstimation/ransacTools.hpp @@ -11,7 +11,7 @@ #include namespace aliceVision { -namespace robustEstimation{ +namespace robustEstimation { /** * @brief Number of samplings to have at least \a minProba probability of absence of @@ -23,13 +23,13 @@ namespace robustEstimation{ */ inline std::size_t getNumSamples(double minProba, double outlierRatio, std::size_t sampleSize) { - return static_cast(std::log(1.-minProba) / std::log(1.-std::pow(1.-outlierRatio, static_cast(sampleSize)))); + return static_cast(std::log(1. - minProba) / std::log(1. - std::pow(1. - outlierRatio, static_cast(sampleSize)))); } inline std::size_t iterationsRequired(std::size_t min_samples, double outliersProbability, double inlierRatio) { - return static_cast(std::log(outliersProbability) / std::log(1.0 - std::pow(inlierRatio, static_cast(min_samples)))); + return static_cast(std::log(outliersProbability) / std::log(1.0 - std::pow(inlierRatio, static_cast(min_samples)))); } -} // namespace robustEstimation -} // namespace aliceVision +} // namespace robustEstimation +} // namespace aliceVision diff --git a/src/aliceVision/robustEstimation/ransac_test.cpp b/src/aliceVision/robustEstimation/ransac_test.cpp index a66efc1e89..9ed05633fe 100644 --- a/src/aliceVision/robustEstimation/ransac_test.cpp +++ b/src/aliceVision/robustEstimation/ransac_test.cpp @@ -22,40 +22,39 @@ using namespace aliceVision::robustEstimation; // Test without outlier BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OutlierFree) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 5); - // y = 2x + 1 - xy << 1, 2, 3, 4, 5, - 3, 5, 7, 9, 11; - - // The base estimator - LineKernel kernel(xy); - - // Check the best model that fit the most of the data - // in a robust framework (Ransac). - std::vector inliers; - LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); - BOOST_CHECK_EQUAL(5, inliers.size()); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 5); + // y = 2x + 1 + xy << 1, 2, 3, 4, 5, 3, 5, 7, 9, 11; + + // The base estimator + LineKernel kernel(xy); + + // Check the best model that fit the most of the data + // in a robust framework (Ransac). + std::vector inliers; + LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); + BOOST_CHECK_EQUAL(5, inliers.size()); } // Test efficiency of MaxConsensus to find (inlier/outlier) in contamined data BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OneOutlier) { - std::mt19937 randomNumberGenerator; - Mat2X xy(2, 6); - // y = 2x + 1 with an outlier - xy << 1, 2, 3, 4, 5, 100, // outlier! - 3, 5, 7, 9, 11, -123; // outlier! - - LineKernel kernel(xy); - - std::vector inliers; - LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); - BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); - BOOST_CHECK_EQUAL(5, inliers.size()); + std::mt19937 randomNumberGenerator; + Mat2X xy(2, 6); + // y = 2x + 1 with an outlier + xy << 1, 2, 3, 4, 5, 100, // outlier! + 3, 5, 7, 9, 11, -123; // outlier! + + LineKernel kernel(xy); + + std::vector inliers; + LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_SMALL(2.0 - model.getMatrix()[1], 1e-9); + BOOST_CHECK_SMALL(1.0 - model.getMatrix()[0], 1e-9); + BOOST_CHECK_EQUAL(5, inliers.size()); } // Critical test: @@ -63,15 +62,15 @@ BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_OneOutlier) // was given for an estimation. BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_TooFewPoints) { - std::mt19937 randomNumberGenerator; - - Mat2X xy(2, 1); - xy << 1, - 3; // y = 2x + 1 with x = 1 - LineKernel kernel(xy); - std::vector inliers; - const LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_EQUAL(0, inliers.size()); + std::mt19937 randomNumberGenerator; + + Mat2X xy(2, 1); + xy << 1, + 3; // y = 2x + 1 with x = 1 + LineKernel kernel(xy); + std::vector inliers; + const LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_EQUAL(0, inliers.size()); } // From a GT model : @@ -80,26 +79,25 @@ BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_TooFewPoints) // Check that the number of inliers and the model are correct. BOOST_AUTO_TEST_CASE(MaxConsensusLineFitter_RealisticCase) { - std::mt19937 randomNumberGenerator; - - const int NbPoints = 30; - const double outlierRatio = .3; //works with 40 - Mat2X xy(2, NbPoints); - std::mt19937 gen; - - Vec2 GTModel; // y = 2x + 1 - GTModel << -2.0, 6.3; - - //-- Add some noise (for the asked percentage amount) - const std::size_t nbPtToNoise = (std::size_t) NbPoints*outlierRatio; - std::vector vec_inliersGT; - generateLine(NbPoints, outlierRatio, 0.0, GTModel, gen, xy, vec_inliersGT); - - - LineKernel kernel(xy); - std::vector inliers; - LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); - BOOST_CHECK_EQUAL(NbPoints-nbPtToNoise, inliers.size()); - BOOST_CHECK_SMALL((-2.0) - model.getMatrix()[0], 1e-9); - BOOST_CHECK_SMALL( 6.3 - model.getMatrix()[1], 1e-9); + std::mt19937 randomNumberGenerator; + + const int NbPoints = 30; + const double outlierRatio = .3; // works with 40 + Mat2X xy(2, NbPoints); + std::mt19937 gen; + + Vec2 GTModel; // y = 2x + 1 + GTModel << -2.0, 6.3; + + //-- Add some noise (for the asked percentage amount) + const std::size_t nbPtToNoise = (std::size_t)NbPoints * outlierRatio; + std::vector vec_inliersGT; + generateLine(NbPoints, outlierRatio, 0.0, GTModel, gen, xy, vec_inliersGT); + + LineKernel kernel(xy); + std::vector inliers; + LineKernel::ModelT model = RANSAC(kernel, ScoreEvaluator(0.3), randomNumberGenerator, &inliers); + BOOST_CHECK_EQUAL(NbPoints - nbPtToNoise, inliers.size()); + BOOST_CHECK_SMALL((-2.0) - model.getMatrix()[0], 1e-9); + BOOST_CHECK_SMALL(6.3 - model.getMatrix()[1], 1e-9); } diff --git a/src/aliceVision/segmentation/segmentation.cpp b/src/aliceVision/segmentation/segmentation.cpp index f5bb576790..a53494fcc1 100644 --- a/src/aliceVision/segmentation/segmentation.cpp +++ b/src/aliceVision/segmentation/segmentation.cpp @@ -7,7 +7,7 @@ #include "segmentation.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) -#include + #include #endif #include @@ -17,23 +17,22 @@ namespace aliceVision { namespace segmentation { - -void imageToPlanes(std::vector & output, const image::Image::Base & source) +void imageToPlanes(std::vector& output, const image::Image::Base& source) { size_t planeSize = source.rows() * source.cols(); - + output.resize(planeSize * 3); - float * planeR = output.data(); - float * planeG = planeR + planeSize; - float * planeB = planeG + planeSize; + float* planeR = output.data(); + float* planeG = planeR + planeSize; + float* planeB = planeG + planeSize; size_t pos = 0; for (int i = 0; i < source.rows(); i++) { for (int j = 0; j < source.cols(); j++) { - const image::RGBfColor & rgb = source(i, j); + const image::RGBfColor& rgb = source(i, j); planeR[pos] = rgb.r(); planeG[pos] = rgb.g(); planeB[pos] = rgb.b(); @@ -51,53 +50,53 @@ bool Segmentation::initialize() Ort::SessionOptions ortSessionOptions; - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) - OrtCUDAProviderOptionsV2* cuda_options = nullptr; - api.CreateCUDAProviderOptions(&cuda_options); - api.SessionOptionsAppendExecutionProvider_CUDA_V2(static_cast(ortSessionOptions), cuda_options); - api.ReleaseCUDAProviderOptions(cuda_options); - - #if defined(_WIN32) || defined(_WIN64) - std::wstring modelWeights(_parameters.modelWeights.begin(), _parameters.modelWeights.end()); - _ortSession = std::make_unique(*_ortEnvironment, modelWeights.c_str(), ortSessionOptions); - #else - _ortSession = std::make_unique(*_ortEnvironment, _parameters.modelWeights.c_str(), ortSessionOptions); - #endif - - Ort::MemoryInfo memInfoCuda("Cuda", OrtAllocatorType::OrtArenaAllocator, 0, OrtMemType::OrtMemTypeDefault); - Ort::Allocator cudaAllocator(*_ortSession, memInfoCuda); - - _output.resize(_parameters.classes.size() * _parameters.modelHeight * _parameters.modelWidth); - _cudaInput = cudaAllocator.Alloc(_output.size() * sizeof(float)); - _cudaOutput = cudaAllocator.Alloc(_output.size() * sizeof(float)); +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) + OrtCUDAProviderOptionsV2* cuda_options = nullptr; + api.CreateCUDAProviderOptions(&cuda_options); + api.SessionOptionsAppendExecutionProvider_CUDA_V2(static_cast(ortSessionOptions), cuda_options); + api.ReleaseCUDAProviderOptions(cuda_options); + + #if defined(_WIN32) || defined(_WIN64) + std::wstring modelWeights(_parameters.modelWeights.begin(), _parameters.modelWeights.end()); + _ortSession = std::make_unique(*_ortEnvironment, modelWeights.c_str(), ortSessionOptions); + #else + _ortSession = std::make_unique(*_ortEnvironment, _parameters.modelWeights.c_str(), ortSessionOptions); + #endif + + Ort::MemoryInfo memInfoCuda("Cuda", OrtAllocatorType::OrtArenaAllocator, 0, OrtMemType::OrtMemTypeDefault); + Ort::Allocator cudaAllocator(*_ortSession, memInfoCuda); + + _output.resize(_parameters.classes.size() * _parameters.modelHeight * _parameters.modelWidth); + _cudaInput = cudaAllocator.Alloc(_output.size() * sizeof(float)); + _cudaOutput = cudaAllocator.Alloc(_output.size() * sizeof(float)); +#else + #if defined(_WIN32) || defined(_WIN64) + std::wstring modelWeights(_parameters.modelWeights.begin(), _parameters.modelWeights.end()); + _ortSession = std::make_unique(ortEnvironment, modelWeights.c_str(), ortSessionOptions); #else - #if defined(_WIN32) || defined(_WIN64) - std::wstring modelWeights(_parameters.modelWeights.begin(), _parameters.modelWeights.end()); - _ortSession = std::make_unique(ortEnvironment, modelWeights.c_str(), ortSessionOptions); - #else - _ortSession = std::make_unique(*_ortEnvironment, _parameters.modelWeights.c_str(), ortSessionOptions); - #endif + _ortSession = std::make_unique(*_ortEnvironment, _parameters.modelWeights.c_str(), ortSessionOptions); #endif +#endif return true; } bool Segmentation::terminate() { - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) - Ort::MemoryInfo mem_info_cuda("Cuda", OrtAllocatorType::OrtArenaAllocator, 0, OrtMemType::OrtMemTypeDefault); - Ort::Allocator cudaAllocator(*_ortSession, mem_info_cuda); - cudaAllocator.Free(_cudaInput); - cudaAllocator.Free(_cudaOutput); - #endif +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) + Ort::MemoryInfo mem_info_cuda("Cuda", OrtAllocatorType::OrtArenaAllocator, 0, OrtMemType::OrtMemTypeDefault); + Ort::Allocator cudaAllocator(*_ortSession, mem_info_cuda); + cudaAllocator.Free(_cudaInput); + cudaAllocator.Free(_cudaOutput); +#endif return true; } -bool Segmentation::processImage(image::Image &labels, const image::Image & source) +bool Segmentation::processImage(image::Image& labels, const image::Image& source) { - //Todo : handle orientation and small images smaller than model input - + // Todo : handle orientation and small images smaller than model input + // Compute the optimal resized size such that: // - at last one dimension fit the model // - both dimensions are larger or equal than the model dimensions @@ -105,32 +104,28 @@ bool Segmentation::processImage(image::Image &labels, const image::Image int resizedWidth = 0; if (source.Height() < source.Width()) { - resizedWidth = static_cast( - static_cast(source.Width()) * static_cast(_parameters.modelHeight) - / static_cast(source.Height())); + resizedWidth = + static_cast(static_cast(source.Width()) * static_cast(_parameters.modelHeight) / static_cast(source.Height())); if (resizedWidth < _parameters.modelWidth) { resizedWidth = _parameters.modelWidth; - resizedHeight = static_cast( - static_cast(resizedWidth) * static_cast(_parameters.modelHeight) - / static_cast(_parameters.modelWidth)); + resizedHeight = static_cast(static_cast(resizedWidth) * static_cast(_parameters.modelHeight) / + static_cast(_parameters.modelWidth)); } else { resizedHeight = _parameters.modelHeight; } } - else + else { - resizedHeight = static_cast( - static_cast(source.Height()) * static_cast(_parameters.modelWidth) - / static_cast(source.Width())); + resizedHeight = + static_cast(static_cast(source.Height()) * static_cast(_parameters.modelWidth) / static_cast(source.Width())); if (resizedHeight < _parameters.modelHeight) { resizedHeight = _parameters.modelHeight; - resizedWidth = static_cast( - static_cast(resizedHeight) * static_cast(_parameters.modelWidth) - / static_cast(_parameters.modelHeight)); + resizedWidth = static_cast(static_cast(resizedHeight) * static_cast(_parameters.modelWidth) / + static_cast(_parameters.modelHeight)); } else { @@ -138,14 +133,14 @@ bool Segmentation::processImage(image::Image &labels, const image::Image } } - //Resize image + // Resize image image::Image resized; imageAlgo::resizeImage(resizedWidth, resizedHeight, source, resized); - //Normalize image to fit model statistics + // Normalize image to fit model statistics for (int i = 0; i < resizedHeight; i++) { - for (int j = 0; j < resizedWidth;j++) + for (int j = 0; j < resizedWidth; j++) { image::RGBfColor value = resized(i, j); resized(i, j) = (value - _parameters.center) * _parameters.scale; @@ -163,22 +158,22 @@ bool Segmentation::processImage(image::Image &labels, const image::Image return true; } -bool Segmentation::tiledProcess(image::Image & labels, const image::Image & source) -{ - //Compute the theorical tiles count +bool Segmentation::tiledProcess(image::Image& labels, const image::Image& source) +{ + // Compute the theorical tiles count int cwidth = divideRoundUp(source.Width(), _parameters.modelWidth); int cheight = divideRoundUp(source.Height(), _parameters.modelHeight); image::Image scoredLabels(source.Width(), source.Height(), true, {0, 0.0f}); - //Loop over tiles + // Loop over tiles for (int i = 0; i < cheight; i++) { - //Compute starting point with overlap on previous + // Compute starting point with overlap on previous int y = std::max(0, int(i * _parameters.modelHeight - _parameters.overlapRatio * _parameters.modelHeight)); int ly = y + _parameters.modelHeight; - //If we are on the end border, shift on the other side + // If we are on the end border, shift on the other side int shifty = source.Height() - ly; if (shifty < 0) { @@ -187,31 +182,30 @@ bool Segmentation::tiledProcess(image::Image & labels, const image::Imag for (int j = 0; j < cwidth; j++) { - //Compute starting point with overlap on previous + // Compute starting point with overlap on previous int x = std::max(0, int(j * _parameters.modelWidth - _parameters.overlapRatio * _parameters.modelWidth)); int lx = x + _parameters.modelWidth; - //If we are on the end border, shift on the other side + // If we are on the end border, shift on the other side int shiftx = source.Width() - lx; if (shiftx < 0) { x = std::max(0, x + shiftx); } - //x and y contains the position of the tile in the input image - auto & block = source.block(y, x, _parameters.modelHeight, _parameters.modelWidth); + // x and y contains the position of the tile in the input image + auto& block = source.block(y, x, _parameters.modelHeight, _parameters.modelWidth); - //Compute tile + // Compute tile image::Image tileLabels(_parameters.modelWidth, _parameters.modelHeight, true, {0, 0.0f}); - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) processTileGPU(tileLabels, block); - #else +#else processTile(tileLabels, block); - #endif - +#endif - //Update the global labeling + // Update the global labeling mergeLabels(scoredLabels, tileLabels, x, y); } } @@ -221,9 +215,9 @@ bool Segmentation::tiledProcess(image::Image & labels, const image::Imag return true; } -bool Segmentation::mergeLabels(image::Image & labels, image::Image & tileLabels, int tileX, int tileY) +bool Segmentation::mergeLabels(image::Image& labels, image::Image& tileLabels, int tileX, int tileY) { - for (int i = 0; i < tileLabels.Height(); i++) + for (int i = 0; i < tileLabels.Height(); i++) { int y = i + tileY; for (int j = 0; j < tileLabels.Width(); j++) @@ -240,7 +234,7 @@ bool Segmentation::mergeLabels(image::Image & labels, image::Image< return true; } -bool Segmentation::labelsFromModelOutput(image::Image & labels, const std::vector & modelOutput) +bool Segmentation::labelsFromModelOutput(image::Image& labels, const std::vector& modelOutput) { for (int outputY = 0; outputY < _parameters.modelHeight; outputY++) { @@ -252,7 +246,7 @@ bool Segmentation::labelsFromModelOutput(image::Image & labels, con for (int classe = 0; classe < _parameters.classes.size(); classe++) { int classPos = classe * _parameters.modelWidth * _parameters.modelHeight; - int pos = classPos + outputY * _parameters.modelWidth + outputX; + int pos = classPos + outputY * _parameters.modelWidth + outputX; float val = modelOutput[pos]; if (val > maxVal) @@ -261,46 +255,38 @@ bool Segmentation::labelsFromModelOutput(image::Image & labels, con maxClasse = classe; } } - + labels(outputY, outputX) = {static_cast(maxClasse), static_cast(maxVal)}; } } - + return true; } -bool Segmentation::processTile(image::Image & labels, const image::Image::Base & source) +bool Segmentation::processTile(image::Image& labels, const image::Image::Base& source) { Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); std::vector inputNames{"input"}; std::vector outputNames{"output"}; - std::vector inputDimensions = - {1, 3, _parameters.modelHeight, _parameters.modelWidth}; - std::vector outputDimensions = - {1, static_cast(_parameters.classes.size()), _parameters.modelHeight, _parameters.modelWidth}; + std::vector inputDimensions = {1, 3, _parameters.modelHeight, _parameters.modelWidth}; + std::vector outputDimensions = {1, static_cast(_parameters.classes.size()), _parameters.modelHeight, _parameters.modelWidth}; std::vector output(_parameters.classes.size() * _parameters.modelHeight * _parameters.modelWidth); - Ort::Value outputTensors = Ort::Value::CreateTensor( - memInfo, - output.data(), output.size(), - outputDimensions.data(), outputDimensions.size() - ); + Ort::Value outputTensors = + Ort::Value::CreateTensor(memInfo, output.data(), output.size(), outputDimensions.data(), outputDimensions.size()); std::vector transformedInput; imageToPlanes(transformedInput, source); - Ort::Value inputTensors = Ort::Value::CreateTensor( - memInfo, - transformedInput.data(), transformedInput.size(), - inputDimensions.data(), inputDimensions.size() - ); + Ort::Value inputTensors = + Ort::Value::CreateTensor(memInfo, transformedInput.data(), transformedInput.size(), inputDimensions.data(), inputDimensions.size()); - try + try { _ortSession->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensors, 1, outputNames.data(), &outputTensors, 1); - } - catch (const Ort::Exception& exception) + } + catch (const Ort::Exception& exception) { ALICEVISION_LOG_ERROR("ERROR running model inference: " << exception.what()); return false; @@ -315,24 +301,18 @@ bool Segmentation::processTile(image::Image & labels, const image:: } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) -bool Segmentation::processTileGPU(image::Image & labels, const image::Image::Base & source) +bool Segmentation::processTileGPU(image::Image& labels, const image::Image::Base& source) { Ort::MemoryInfo mem_info_cuda("Cuda", OrtAllocatorType::OrtArenaAllocator, 0, OrtMemType::OrtMemTypeDefault); Ort::Allocator cudaAllocator(*_ortSession, mem_info_cuda); std::vector inputNames{"input"}; std::vector outputNames{"output"}; - std::vector inputDimensions = - {1, 3, _parameters.modelHeight, _parameters.modelWidth}; - std::vector outputDimensions = - {1, static_cast(_parameters.classes.size()), _parameters.modelHeight, _parameters.modelWidth}; + std::vector inputDimensions = {1, 3, _parameters.modelHeight, _parameters.modelWidth}; + std::vector outputDimensions = {1, static_cast(_parameters.classes.size()), _parameters.modelHeight, _parameters.modelWidth}; - Ort::Value outputTensors = Ort::Value::CreateTensor( - mem_info_cuda, - reinterpret_cast(_cudaOutput), _output.size(), - outputDimensions.data(), outputDimensions.size() - ); + mem_info_cuda, reinterpret_cast(_cudaOutput), _output.size(), outputDimensions.data(), outputDimensions.size()); std::vector transformedInput; imageToPlanes(transformedInput, source); @@ -340,16 +320,13 @@ bool Segmentation::processTileGPU(image::Image & labels, const imag cudaMemcpy(_cudaInput, transformedInput.data(), sizeof(float) * transformedInput.size(), cudaMemcpyHostToDevice); Ort::Value inputTensors = Ort::Value::CreateTensor( - mem_info_cuda, - reinterpret_cast(_cudaInput), transformedInput.size(), - inputDimensions.data(), inputDimensions.size() - ); + mem_info_cuda, reinterpret_cast(_cudaInput), transformedInput.size(), inputDimensions.data(), inputDimensions.size()); - try + try { _ortSession->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensors, 1, outputNames.data(), &outputTensors, 1); - } - catch (const Ort::Exception& exception) + } + catch (const Ort::Exception& exception) { ALICEVISION_LOG_ERROR("ERROR running model inference: " << exception.what()); return false; @@ -366,5 +343,5 @@ bool Segmentation::processTileGPU(image::Image & labels, const imag } #endif -} //aliceVision -} //segmentation \ No newline at end of file +} // namespace segmentation +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/segmentation/segmentation.hpp b/src/aliceVision/segmentation/segmentation.hpp index 9e99955003..cdfcf2b287 100644 --- a/src/aliceVision/segmentation/segmentation.hpp +++ b/src/aliceVision/segmentation/segmentation.hpp @@ -5,7 +5,7 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. #include -#include +#include #include #include @@ -27,11 +27,8 @@ struct ScoredLabel class Segmentation { -public: - const std::vector & getClasses() - { - return _parameters.classes; - } + public: + const std::vector& getClasses() { return _parameters.classes; } struct Parameters { @@ -44,8 +41,9 @@ class Segmentation double overlapRatio; }; -public: - Segmentation(const Parameters & parameters) : _parameters(parameters) + public: + Segmentation(const Parameters& parameters) + : _parameters(parameters) { if (!initialize()) { @@ -53,28 +51,24 @@ class Segmentation } } - virtual ~Segmentation() - { - terminate(); - } + virtual ~Segmentation() { terminate(); } /** * Process an input image to estimate segmentation * @param labels the labels image resulting from the process * @param source is the input image to process */ - bool processImage(image::Image &labels, const image::Image & source); - -private: + bool processImage(image::Image& labels, const image::Image& source); + private: /** * Onnx creation code - */ + */ bool initialize(); - + /** * Onnx destruction code - */ + */ bool terminate(); /** @@ -82,30 +76,30 @@ class Segmentation * @param labels the output label image * @param source the input image to process */ - bool tiledProcess(image::Image &labels, const image::Image & source); + bool tiledProcess(image::Image& labels, const image::Image& source); /** * Transform model output to a label image * @param labels the output labels imaage * @param modeloutput the model output vector */ - bool labelsFromModelOutput(image::Image & labels, const std::vector & modelOutput); + bool labelsFromModelOutput(image::Image& labels, const std::vector& modelOutput); /** * Process effectively a buffer of the model input size * param labels the output labels * @param source the source tile */ - bool processTile(image::Image & labels, const image::Image::Base & source); + bool processTile(image::Image& labels, const image::Image::Base& source); - /** - * Process effectively a buffer of the model input size - * param labels the output labels - * @param source the source tile - */ - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) - bool processTileGPU(image::Image & labels, const image::Image::Base & source); - #endif +/** + * Process effectively a buffer of the model input size + * param labels the output labels + * @param source the source tile + */ +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) + bool processTileGPU(image::Image& labels, const image::Image::Base& source); +#endif /** * Merge tile labels with global labels image @@ -114,20 +108,20 @@ class Segmentation * @param tileX the position of the tile in the global image * @param tileY the position of the tile in the global image */ - bool mergeLabels(image::Image & labels, image::Image & tileLabels, int tileX, int tileY); + bool mergeLabels(image::Image& labels, image::Image& tileLabels, int tileX, int tileY); -protected: + protected: Parameters _parameters; std::unique_ptr _ortEnvironment; std::unique_ptr _ortSession; - + std::vector _output; - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) - void * _cudaOutput; - void * _cudaInput; - #endif +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CUDA) + void* _cudaOutput; + void* _cudaInput; +#endif }; -} //aliceVision -} //segmentation \ No newline at end of file +} // namespace segmentation +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sensorDB/Datasheet.cpp b/src/aliceVision/sensorDB/Datasheet.cpp index 4f8f50b45f..14a2be4fe2 100644 --- a/src/aliceVision/sensorDB/Datasheet.cpp +++ b/src/aliceVision/sensorDB/Datasheet.cpp @@ -18,42 +18,38 @@ namespace sensorDB { bool Datasheet::operator==(const Datasheet& other) const { - std::string brandA = _brand; - std::string brandB = other._brand; + std::string brandA = _brand; + std::string brandB = other._brand; - boost::algorithm::to_lower(brandA); - boost::algorithm::to_lower(brandB); + boost::algorithm::to_lower(brandA); + boost::algorithm::to_lower(brandB); - brandA.erase(std::remove_if(brandA.begin(), brandA.end(), ::ispunct), brandA.end()); //remove punctuation - brandB.erase(std::remove_if(brandB.begin(), brandB.end(), ::ispunct), brandB.end()); //remove punctuation + brandA.erase(std::remove_if(brandA.begin(), brandA.end(), ::ispunct), brandA.end()); // remove punctuation + brandB.erase(std::remove_if(brandB.begin(), brandB.end(), ::ispunct), brandB.end()); // remove punctuation - brandA.erase(std::remove_if(brandA.begin(), brandA.end(), ::isspace), brandA.end()); //remove spaces - brandB.erase(std::remove_if(brandB.begin(), brandB.end(), ::isspace), brandB.end()); //remove spaces + brandA.erase(std::remove_if(brandA.begin(), brandA.end(), ::isspace), brandA.end()); // remove spaces + brandB.erase(std::remove_if(brandB.begin(), brandB.end(), ::isspace), brandB.end()); // remove spaces - if((brandA == brandB) || - (boost::algorithm::starts_with(brandA, brandB)) || - (boost::algorithm::starts_with(brandB, brandA))) - { - std::string modelA = _model; - std::string modelB = other._model; + if ((brandA == brandB) || (boost::algorithm::starts_with(brandA, brandB)) || (boost::algorithm::starts_with(brandB, brandA))) + { + std::string modelA = _model; + std::string modelB = other._model; - boost::algorithm::to_lower(modelA); - boost::algorithm::to_lower(modelB); + boost::algorithm::to_lower(modelA); + boost::algorithm::to_lower(modelB); - modelA.erase(std::remove_if(modelA.begin(), modelA.end(), ::ispunct), modelA.end()); //remove punctuation - modelB.erase(std::remove_if(modelB.begin(), modelB.end(), ::ispunct), modelB.end()); //remove punctuation + modelA.erase(std::remove_if(modelA.begin(), modelA.end(), ::ispunct), modelA.end()); // remove punctuation + modelB.erase(std::remove_if(modelB.begin(), modelB.end(), ::ispunct), modelB.end()); // remove punctuation - modelA.erase(std::remove_if(modelA.begin(), modelA.end(), ::isspace), modelA.end()); //remove spaces - modelB.erase(std::remove_if(modelB.begin(), modelB.end(), ::isspace), modelB.end()); //remove spaces + modelA.erase(std::remove_if(modelA.begin(), modelA.end(), ::isspace), modelA.end()); // remove spaces + modelB.erase(std::remove_if(modelB.begin(), modelB.end(), ::isspace), modelB.end()); // remove spaces - if((modelA == modelB) || - (boost::algorithm::ends_with(modelA, modelB)) || - (boost::algorithm::ends_with(modelB, modelA))) - return true; - } + if ((modelA == modelB) || (boost::algorithm::ends_with(modelA, modelB)) || (boost::algorithm::ends_with(modelB, modelA))) + return true; + } - return false; + return false; } -} // namespace sensorDB -} // namespace aliceVision +} // namespace sensorDB +} // namespace aliceVision diff --git a/src/aliceVision/sensorDB/Datasheet.hpp b/src/aliceVision/sensorDB/Datasheet.hpp index 43d2b1cabc..e0e46c45ce 100644 --- a/src/aliceVision/sensorDB/Datasheet.hpp +++ b/src/aliceVision/sensorDB/Datasheet.hpp @@ -17,28 +17,26 @@ namespace sensorDB { */ struct Datasheet { - Datasheet() = default; - - /** - * @brief Datasheet Constructor - * @param[in] brand - * @param[in] model - * @param[in] sensorSize - */ - Datasheet(const std::string& brand, - const std::string& model, - const double& sensorWidth) - : _brand(brand) - , _model(model) - , _sensorWidth(sensorWidth) - {} - - bool operator==(const Datasheet& other) const; - - std::string _brand; - std::string _model; - double _sensorWidth; + Datasheet() = default; + + /** + * @brief Datasheet Constructor + * @param[in] brand + * @param[in] model + * @param[in] sensorSize + */ + Datasheet(const std::string& brand, const std::string& model, const double& sensorWidth) + : _brand(brand), + _model(model), + _sensorWidth(sensorWidth) + {} + + bool operator==(const Datasheet& other) const; + + std::string _brand; + std::string _model; + double _sensorWidth; }; -} // namespace sensorDB -} // namespace aliceVision +} // namespace sensorDB +} // namespace aliceVision diff --git a/src/aliceVision/sensorDB/parseDatabase.cpp b/src/aliceVision/sensorDB/parseDatabase.cpp index 57e148cca6..b2b47c60cb 100644 --- a/src/aliceVision/sensorDB/parseDatabase.cpp +++ b/src/aliceVision/sensorDB/parseDatabase.cpp @@ -25,45 +25,45 @@ namespace sensorDB { bool parseDatabase(const std::string& databaseFilePath, std::vector& databaseStructure) { - std::ifstream fileIn(databaseFilePath); - if(!fileIn || !fs::exists(databaseFilePath) || !fs::is_regular_file(databaseFilePath)) - return false; + std::ifstream fileIn(databaseFilePath); + if (!fileIn || !fs::exists(databaseFilePath) || !fs::is_regular_file(databaseFilePath)) + return false; - std::string line; - while(fileIn.good()) - { - getline( fileIn, line); - if(!line.empty()) + std::string line; + while (fileIn.good()) { - if(line[0] != '#') - { - std::vector values; - boost::split(values, line, boost::is_any_of(";")); - - if(values.size() >= 4) + getline(fileIn, line); + if (!line.empty()) { - const std::string brand = values[0]; - const std::string model = values[1]; - const double sensorWidth = std::stod(values[2]); - databaseStructure.emplace_back(brand, model, sensorWidth); + if (line[0] != '#') + { + std::vector values; + boost::split(values, line, boost::is_any_of(";")); + + if (values.size() >= 4) + { + const std::string brand = values[0]; + const std::string model = values[1]; + const double sensorWidth = std::stod(values[2]); + databaseStructure.emplace_back(brand, model, sensorWidth); + } + } } - } } - } - return true; + return true; } bool getInfo(const std::string& brand, const std::string& model, const std::vector& databaseStructure, Datasheet& datasheetContent) { - Datasheet refDatasheet(brand, model, -1.); - auto datasheet = std::find(databaseStructure.begin(), databaseStructure.end(), refDatasheet); + Datasheet refDatasheet(brand, model, -1.); + auto datasheet = std::find(databaseStructure.begin(), databaseStructure.end(), refDatasheet); - if(datasheet == databaseStructure.end()) - return false; + if (datasheet == databaseStructure.end()) + return false; - datasheetContent = *datasheet; - return true; + datasheetContent = *datasheet; + return true; } -} // namespace sensorDB -} // namespace aliceVision +} // namespace sensorDB +} // namespace aliceVision diff --git a/src/aliceVision/sensorDB/parseDatabase.hpp b/src/aliceVision/sensorDB/parseDatabase.hpp index 23f8c6bd30..868747bb70 100644 --- a/src/aliceVision/sensorDB/parseDatabase.hpp +++ b/src/aliceVision/sensorDB/parseDatabase.hpp @@ -33,5 +33,5 @@ bool parseDatabase(const std::string& databaseFilePath, std::vector& */ bool getInfo(const std::string& brand, const std::string& model, const std::vector& databaseStructure, Datasheet& datasheetContent); -} // namespace sensorDB -} // namespace aliceVision +} // namespace sensorDB +} // namespace aliceVision diff --git a/src/aliceVision/sensorDB/parseDatabase_test.cpp b/src/aliceVision/sensorDB/parseDatabase_test.cpp index f803214b1f..4241d22c10 100644 --- a/src/aliceVision/sensorDB/parseDatabase_test.cpp +++ b/src/aliceVision/sensorDB/parseDatabase_test.cpp @@ -23,92 +23,91 @@ static const std::string sDatabase = (fs::path(THIS_SOURCE_DIR) / "cameraSensors BOOST_AUTO_TEST_CASE(InvalidDatabase) { - std::vector vec_database; - const std::string sfileDatabase = std::string(THIS_SOURCE_DIR); + std::vector vec_database; + const std::string sfileDatabase = std::string(THIS_SOURCE_DIR); - BOOST_CHECK(! parseDatabase( sfileDatabase, vec_database ) ); - BOOST_CHECK( vec_database.empty() ); + BOOST_CHECK(!parseDatabase(sfileDatabase, vec_database)); + BOOST_CHECK(vec_database.empty()); } BOOST_AUTO_TEST_CASE(ValidDatabase) { - std::vector vec_database; - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK( !vec_database.empty() ); + std::vector vec_database; + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(!vec_database.empty()); } BOOST_AUTO_TEST_CASE(ParseDatabaseSD900) { - std::vector vec_database; - Datasheet datasheet; - const std::string sModel = "Canon PowerShot SD900"; - const std::string sBrand = "Canon"; - - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK( getInfo( sBrand, sModel, vec_database, datasheet ) ); - BOOST_CHECK_EQUAL( "Canon", datasheet._brand ); - BOOST_CHECK_EQUAL( "Canon PowerShot SD900", datasheet._model ); - BOOST_CHECK_EQUAL( 7.144, datasheet._sensorWidth ); + std::vector vec_database; + Datasheet datasheet; + const std::string sModel = "Canon PowerShot SD900"; + const std::string sBrand = "Canon"; + + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(getInfo(sBrand, sModel, vec_database, datasheet)); + BOOST_CHECK_EQUAL("Canon", datasheet._brand); + BOOST_CHECK_EQUAL("Canon PowerShot SD900", datasheet._model); + BOOST_CHECK_EQUAL(7.144, datasheet._sensorWidth); } BOOST_AUTO_TEST_CASE(ParseDatabaseA710_IS) { - std::vector vec_database; - Datasheet datasheet; - const std::string sModel = "Canon PowerShot A710 IS"; - const std::string sBrand = "Canon"; - - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK( getInfo( sBrand, sModel, vec_database, datasheet ) ); - BOOST_CHECK_EQUAL( "Canon", datasheet._brand ); - BOOST_CHECK_EQUAL( "Canon PowerShot A710 IS", datasheet._model ); - BOOST_CHECK_EQUAL( 5.744, datasheet._sensorWidth ); + std::vector vec_database; + Datasheet datasheet; + const std::string sModel = "Canon PowerShot A710 IS"; + const std::string sBrand = "Canon"; + + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(getInfo(sBrand, sModel, vec_database, datasheet)); + BOOST_CHECK_EQUAL("Canon", datasheet._brand); + BOOST_CHECK_EQUAL("Canon PowerShot A710 IS", datasheet._model); + BOOST_CHECK_EQUAL(5.744, datasheet._sensorWidth); } BOOST_AUTO_TEST_CASE(ParseDatabaseNotExist) { - std::vector vec_database; - Datasheet datasheet; - const std::string sModel = "NotExistModel"; - const std::string sBrand = "NotExistBrand"; + std::vector vec_database; + Datasheet datasheet; + const std::string sModel = "NotExistModel"; + const std::string sBrand = "NotExistBrand"; - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK(! getInfo( sBrand, sModel, vec_database, datasheet ) ); + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(!getInfo(sBrand, sModel, vec_database, datasheet)); } - BOOST_AUTO_TEST_CASE(ParseDatabaseCanon_EOS_550D) { - std::vector vec_database; - Datasheet datasheet; - const std::string sModel = "Canon EOS 550D"; - const std::string sBrand = "Canon"; - - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK( getInfo( sBrand, sModel, vec_database, datasheet ) ); - BOOST_CHECK_EQUAL( 22.3, datasheet._sensorWidth ); + std::vector vec_database; + Datasheet datasheet; + const std::string sModel = "Canon EOS 550D"; + const std::string sBrand = "Canon"; + + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(getInfo(sBrand, sModel, vec_database, datasheet)); + BOOST_CHECK_EQUAL(22.3, datasheet._sensorWidth); } BOOST_AUTO_TEST_CASE(ParseDatabaseCanon_EOS_5D_Mark_II) { - std::vector vec_database; - Datasheet datasheet; - const std::string sModel = "Canon EOS 5D Mark II"; - const std::string sBrand = "Canon"; - - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK( getInfo( sBrand, sModel, vec_database, datasheet ) ); - BOOST_CHECK_EQUAL( 36, datasheet._sensorWidth ); + std::vector vec_database; + Datasheet datasheet; + const std::string sModel = "Canon EOS 5D Mark II"; + const std::string sBrand = "Canon"; + + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(getInfo(sBrand, sModel, vec_database, datasheet)); + BOOST_CHECK_EQUAL(36, datasheet._sensorWidth); } BOOST_AUTO_TEST_CASE(ParseDatabaseCanon_EOS_1100D) { - std::vector vec_database; - Datasheet datasheet; - const std::string sModel = "Canon EOS 1100D"; - const std::string sBrand = "Canon"; - - BOOST_CHECK( parseDatabase( sDatabase, vec_database ) ); - BOOST_CHECK( getInfo( sBrand, sModel, vec_database, datasheet ) ); - BOOST_CHECK_EQUAL( 22.2, datasheet._sensorWidth ); + std::vector vec_database; + Datasheet datasheet; + const std::string sModel = "Canon EOS 1100D"; + const std::string sBrand = "Canon"; + + BOOST_CHECK(parseDatabase(sDatabase, vec_database)); + BOOST_CHECK(getInfo(sBrand, sModel, vec_database, datasheet)); + BOOST_CHECK_EQUAL(22.2, datasheet._sensorWidth); } diff --git a/src/aliceVision/sfm/FrustumFilter.cpp b/src/aliceVision/sfm/FrustumFilter.cpp index 2b7c4dd40a..3de5244e20 100644 --- a/src/aliceVision/sfm/FrustumFilter.cpp +++ b/src/aliceVision/sfm/FrustumFilter.cpp @@ -26,196 +26,185 @@ using namespace aliceVision::geometry::halfPlane; // Constructor FrustumFilter::FrustumFilter(const sfmData::SfMData& sfmData, const double zNear, const double zFar) { - //-- Init Z_Near & Z_Far for all valid views - init_z_near_z_far_depth(sfmData, zNear, zFar); - const bool bComputed_Z = (zNear == -1. && zFar == -1.) && !sfmData.getLandmarks().empty(); - _bTruncated = (zNear != -1. && zFar != -1.) || bComputed_Z; - initFrustum(sfmData); + //-- Init Z_Near & Z_Far for all valid views + init_z_near_z_far_depth(sfmData, zNear, zFar); + const bool bComputed_Z = (zNear == -1. && zFar == -1.) && !sfmData.getLandmarks().empty(); + _bTruncated = (zNear != -1. && zFar != -1.) || bComputed_Z; + initFrustum(sfmData); } // Init a frustum for each valid views of the SfM scene void FrustumFilter::initFrustum(const sfmData::SfMData& sfmData) { - for (NearFarPlanesT::const_iterator it = z_near_z_far_perView.begin(); - it != z_near_z_far_perView.end(); ++it) - { - const sfmData::View * view = sfmData.getViews().at(it->first).get(); - if (!sfmData.isPoseAndIntrinsicDefined(view)) - continue; - sfmData::Intrinsics::const_iterator iterIntrinsic = sfmData.getIntrinsics().find(view->getIntrinsicId()); - if (!isPinhole(iterIntrinsic->second.get()->getType())) - continue; - - const Pose3 pose = sfmData.getPose(*view).getTransform(); - - const Pinhole * cam = dynamic_cast(iterIntrinsic->second.get()); - if (cam == nullptr) - continue; - - const Frustum f(cam->w(), cam->h(), cam->K(), - pose.rotation(), pose.center(), it->second.first, it->second.second); - frustum_perView[view->getViewId()] = f; - } + for (NearFarPlanesT::const_iterator it = z_near_z_far_perView.begin(); it != z_near_z_far_perView.end(); ++it) + { + const sfmData::View* view = sfmData.getViews().at(it->first).get(); + if (!sfmData.isPoseAndIntrinsicDefined(view)) + continue; + sfmData::Intrinsics::const_iterator iterIntrinsic = sfmData.getIntrinsics().find(view->getIntrinsicId()); + if (!isPinhole(iterIntrinsic->second.get()->getType())) + continue; + + const Pose3 pose = sfmData.getPose(*view).getTransform(); + + const Pinhole* cam = dynamic_cast(iterIntrinsic->second.get()); + if (cam == nullptr) + continue; + + const Frustum f(cam->w(), cam->h(), cam->K(), pose.rotation(), pose.center(), it->second.first, it->second.second); + frustum_perView[view->getViewId()] = f; + } } PairSet FrustumFilter::getFrustumIntersectionPairs() const { - PairSet pairs; - // List active view Id - std::vector viewIds; - viewIds.reserve(z_near_z_far_perView.size()); - std::transform(z_near_z_far_perView.begin(), z_near_z_far_perView.end(), - std::back_inserter(viewIds), stl::RetrieveKey()); - - auto progressDisplay = system::createConsoleProgressDisplay(viewIds.size() * (viewIds.size()-1)/2, - std::cout, "\nCompute frustum intersection\n"); - - // Exhaustive comparison (use the fact that the intersect function is symmetric) - #pragma omp parallel for - for (int i = 0; i < (int)viewIds.size(); ++i) - { - for (size_t j = i+1; j < viewIds.size(); ++j) + PairSet pairs; + // List active view Id + std::vector viewIds; + viewIds.reserve(z_near_z_far_perView.size()); + std::transform(z_near_z_far_perView.begin(), z_near_z_far_perView.end(), std::back_inserter(viewIds), stl::RetrieveKey()); + + auto progressDisplay = + system::createConsoleProgressDisplay(viewIds.size() * (viewIds.size() - 1) / 2, std::cout, "\nCompute frustum intersection\n"); + +// Exhaustive comparison (use the fact that the intersect function is symmetric) +#pragma omp parallel for + for (int i = 0; i < (int)viewIds.size(); ++i) { - if (frustum_perView.at(viewIds[i]).intersect(frustum_perView.at(viewIds[j]))) - { - #pragma omp critical + for (size_t j = i + 1; j < viewIds.size(); ++j) { - pairs.insert(std::make_pair(viewIds[i], viewIds[j])); + if (frustum_perView.at(viewIds[i]).intersect(frustum_perView.at(viewIds[j]))) + { +#pragma omp critical + { + pairs.insert(std::make_pair(viewIds[i], viewIds[j])); + } + } + ++progressDisplay; } - } - ++progressDisplay; } - } - return pairs; + return pairs; } // Export defined frustum in PLY file for viewing -bool FrustumFilter::export_Ply(const std::string & filename) const +bool FrustumFilter::export_Ply(const std::string& filename) const { - std::ofstream of(filename); - if (!of.is_open()) - return false; - // Vertex count evaluation - // Faces count evaluation - std::size_t vertex_count = 0; - std::size_t face_count = 0; - for (FrustumsT::const_iterator it = frustum_perView.begin(); - it != frustum_perView.end(); ++it) - { - if (it->second.isInfinite()) - { - vertex_count += 5; - face_count += 5; // 4 triangles + 1 quad - } - else // truncated + std::ofstream of(filename); + if (!of.is_open()) + return false; + // Vertex count evaluation + // Faces count evaluation + std::size_t vertex_count = 0; + std::size_t face_count = 0; + for (FrustumsT::const_iterator it = frustum_perView.begin(); it != frustum_perView.end(); ++it) { - vertex_count += 8; - face_count += 6; // 6 quads + if (it->second.isInfinite()) + { + vertex_count += 5; + face_count += 5; // 4 triangles + 1 quad + } + else // truncated + { + vertex_count += 8; + face_count += 6; // 6 quads + } } - } - - of << "ply" << '\n' - << "format ascii 1.0" << '\n' - << "element vertex " << vertex_count << '\n' - << "property float x" << '\n' - << "property float y" << '\n' - << "property float z" << '\n' - << "element face " << face_count << '\n' - << "property list uchar int vertex_index" << '\n' - << "end_header" << '\n'; - - // Export frustums points - for (FrustumsT::const_iterator it = frustum_perView.begin(); - it != frustum_perView.end(); ++it) - { - const std::vector & points = it->second.frustum_points(); - for (int i=0; i < points.size(); ++i) - of << points[i].transpose() << '\n'; - } - - // Export frustums faces - IndexT count = 0; - for (FrustumsT::const_iterator it = frustum_perView.begin(); - it != frustum_perView.end(); ++it) - { - if (it->second.isInfinite()) // infinite frustum: drawn normalized cone: 4 faces + + of << "ply" << '\n' + << "format ascii 1.0" << '\n' + << "element vertex " << vertex_count << '\n' + << "property float x" << '\n' + << "property float y" << '\n' + << "property float z" << '\n' + << "element face " << face_count << '\n' + << "property list uchar int vertex_index" << '\n' + << "end_header" << '\n'; + + // Export frustums points + for (FrustumsT::const_iterator it = frustum_perView.begin(); it != frustum_perView.end(); ++it) { - of << "3 " << count + 0 << ' ' << count + 1 << ' ' << count + 2 << '\n' - << "3 " << count + 0 << ' ' << count + 2 << ' ' << count + 3 << '\n' - << "3 " << count + 0 << ' ' << count + 3 << ' ' << count + 4 << '\n' - << "3 " << count + 0 << ' ' << count + 4 << ' ' << count + 1 << '\n' - << "4 " << count + 1 << ' ' << count + 2 << ' ' << count + 3 << ' ' << count + 4 << '\n'; - count += 5; + const std::vector& points = it->second.frustum_points(); + for (int i = 0; i < points.size(); ++i) + of << points[i].transpose() << '\n'; } - else // truncated frustum: 6 faces + + // Export frustums faces + IndexT count = 0; + for (FrustumsT::const_iterator it = frustum_perView.begin(); it != frustum_perView.end(); ++it) { - of << "4 " << count + 0 << ' ' << count + 1 << ' ' << count + 2 << ' ' << count + 3 << '\n' - << "4 " << count + 0 << ' ' << count + 1 << ' ' << count + 5 << ' ' << count + 4 << '\n' - << "4 " << count + 1 << ' ' << count + 5 << ' ' << count + 6 << ' ' << count + 2 << '\n' - << "4 " << count + 3 << ' ' << count + 7 << ' ' << count + 6 << ' ' << count + 2 << '\n' - << "4 " << count + 0 << ' ' << count + 4 << ' ' << count + 7 << ' ' << count + 3 << '\n' - << "4 " << count + 4 << ' ' << count + 5 << ' ' << count + 6 << ' ' << count + 7 << '\n'; - count += 8; + if (it->second.isInfinite()) // infinite frustum: drawn normalized cone: 4 faces + { + of << "3 " << count + 0 << ' ' << count + 1 << ' ' << count + 2 << '\n' + << "3 " << count + 0 << ' ' << count + 2 << ' ' << count + 3 << '\n' + << "3 " << count + 0 << ' ' << count + 3 << ' ' << count + 4 << '\n' + << "3 " << count + 0 << ' ' << count + 4 << ' ' << count + 1 << '\n' + << "4 " << count + 1 << ' ' << count + 2 << ' ' << count + 3 << ' ' << count + 4 << '\n'; + count += 5; + } + else // truncated frustum: 6 faces + { + of << "4 " << count + 0 << ' ' << count + 1 << ' ' << count + 2 << ' ' << count + 3 << '\n' + << "4 " << count + 0 << ' ' << count + 1 << ' ' << count + 5 << ' ' << count + 4 << '\n' + << "4 " << count + 1 << ' ' << count + 5 << ' ' << count + 6 << ' ' << count + 2 << '\n' + << "4 " << count + 3 << ' ' << count + 7 << ' ' << count + 6 << ' ' << count + 2 << '\n' + << "4 " << count + 0 << ' ' << count + 4 << ' ' << count + 7 << ' ' << count + 3 << '\n' + << "4 " << count + 4 << ' ' << count + 5 << ' ' << count + 6 << ' ' << count + 7 << '\n'; + count += 8; + } } - } - of.flush(); - bool bOk = of.good(); - of.close(); - return bOk; + of.flush(); + bool bOk = of.good(); + of.close(); + return bOk; } void FrustumFilter::init_z_near_z_far_depth(const sfmData::SfMData& sfmData, const double zNear, const double zFar) { - // If z_near & z_far are -1 and structure if not empty, - // compute the values for each camera and the structure - const bool bComputed_Z = (zNear == -1. && zFar == -1.) && !sfmData.getLandmarks().empty(); - if(bComputed_Z) // Compute the near & far planes from the structure and view observations - { - for(sfmData::Landmarks::const_iterator itL = sfmData.getLandmarks().begin(); - itL != sfmData.getLandmarks().end(); ++itL) + // If z_near & z_far are -1 and structure if not empty, + // compute the values for each camera and the structure + const bool bComputed_Z = (zNear == -1. && zFar == -1.) && !sfmData.getLandmarks().empty(); + if (bComputed_Z) // Compute the near & far planes from the structure and view observations { - const sfmData::Landmark & landmark = itL->second; - const Vec3 & X = landmark.X; - for(sfmData::Observations::const_iterator iterO = landmark.observations.begin(); - iterO != landmark.observations.end(); ++iterO) - { - const IndexT id_view = iterO->first; - const sfmData::Observation & ob = iterO->second; - const sfmData::View * view = sfmData.getViews().at(id_view).get(); - if (!sfmData.isPoseAndIntrinsicDefined(view)) - continue; - - sfmData::Intrinsics::const_iterator iterIntrinsic = sfmData.getIntrinsics().find(view->getIntrinsicId()); - const Pose3 pose = sfmData.getPose(*view).getTransform(); - const double z = pose.depth(X); - NearFarPlanesT::iterator itZ = z_near_z_far_perView.find(id_view); - if (itZ != z_near_z_far_perView.end()) + for (sfmData::Landmarks::const_iterator itL = sfmData.getLandmarks().begin(); itL != sfmData.getLandmarks().end(); ++itL) { - if ( z < itZ->second.first) - itZ->second.first = z; - else - if ( z > itZ->second.second) - itZ->second.second = z; + const sfmData::Landmark& landmark = itL->second; + const Vec3& X = landmark.X; + for (sfmData::Observations::const_iterator iterO = landmark.observations.begin(); iterO != landmark.observations.end(); ++iterO) + { + const IndexT id_view = iterO->first; + const sfmData::Observation& ob = iterO->second; + const sfmData::View* view = sfmData.getViews().at(id_view).get(); + if (!sfmData.isPoseAndIntrinsicDefined(view)) + continue; + + sfmData::Intrinsics::const_iterator iterIntrinsic = sfmData.getIntrinsics().find(view->getIntrinsicId()); + const Pose3 pose = sfmData.getPose(*view).getTransform(); + const double z = pose.depth(X); + NearFarPlanesT::iterator itZ = z_near_z_far_perView.find(id_view); + if (itZ != z_near_z_far_perView.end()) + { + if (z < itZ->second.first) + itZ->second.first = z; + else if (z > itZ->second.second) + itZ->second.second = z; + } + else + z_near_z_far_perView[id_view] = std::make_pair(z, z); + } } - else - z_near_z_far_perView[id_view] = std::make_pair(z,z); - } } - } - else - { - // Init the same near & far limit for all the valid views - for(sfmData::Views::const_iterator it = sfmData.getViews().begin(); - it != sfmData.getViews().end(); ++it) + else { - const sfmData::View * view = it->second.get(); - if(!sfmData.isPoseAndIntrinsicDefined(view)) - continue; - z_near_z_far_perView[view->getViewId()] = std::make_pair(zNear, zFar); + // Init the same near & far limit for all the valid views + for (sfmData::Views::const_iterator it = sfmData.getViews().begin(); it != sfmData.getViews().end(); ++it) + { + const sfmData::View* view = it->second.get(); + if (!sfmData.isPoseAndIntrinsicDefined(view)) + continue; + z_near_z_far_perView[view->getViewId()] = std::make_pair(zNear, zFar); + } } - } } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/FrustumFilter.hpp b/src/aliceVision/sfm/FrustumFilter.hpp index 875a3f4670..c8131c2cf6 100644 --- a/src/aliceVision/sfm/FrustumFilter.hpp +++ b/src/aliceVision/sfm/FrustumFilter.hpp @@ -14,41 +14,40 @@ namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { class FrustumFilter { -public: - typedef HashMap FrustumsT; - typedef HashMap > NearFarPlanesT; + public: + typedef HashMap FrustumsT; + typedef HashMap> NearFarPlanesT; - FrustumFilter(const sfmData::SfMData& sfmData, const double zNear = -1., const double zFar = -1.); + FrustumFilter(const sfmData::SfMData& sfmData, const double zNear = -1., const double zFar = -1.); - /// init a frustum for each valid views of the SfM scene - void initFrustum(const sfmData::SfMData& sfmData); + /// init a frustum for each valid views of the SfM scene + void initFrustum(const sfmData::SfMData& sfmData); - /// return intersecting View frustum pairs - PairSet getFrustumIntersectionPairs() const; + /// return intersecting View frustum pairs + PairSet getFrustumIntersectionPairs() const; - /// export defined frustum in PLY file for viewing - bool export_Ply(const std::string& filename) const; + /// export defined frustum in PLY file for viewing + bool export_Ply(const std::string& filename) const; -private: + private: + /// Init near and far plane depth from SfMData structure or defined value + void init_z_near_z_far_depth(const sfmData::SfMData& sfmData, const double zNear = -1., const double zFar = -1.); - /// Init near and far plane depth from SfMData structure or defined value - void init_z_near_z_far_depth(const sfmData::SfMData& sfmData, const double zNear = -1., const double zFar = -1.); + // Data - // Data - - /// tell if we use truncated or infinite frustum - bool _bTruncated; - /// frustum for the valid view (defined pose+intrinsic) - FrustumsT frustum_perView; - /// Near & Far plane distance per view - NearFarPlanesT z_near_z_far_perView; + /// tell if we use truncated or infinite frustum + bool _bTruncated; + /// frustum for the valid view (defined pose+intrinsic) + FrustumsT frustum_perView; + /// Near & Far plane distance per view + NearFarPlanesT z_near_z_far_perView; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/LocalBundleAdjustmentGraph.cpp b/src/aliceVision/sfm/LocalBundleAdjustmentGraph.cpp index 72334d2818..92c1eed12f 100644 --- a/src/aliceVision/sfm/LocalBundleAdjustmentGraph.cpp +++ b/src/aliceVision/sfm/LocalBundleAdjustmentGraph.cpp @@ -21,747 +21,746 @@ namespace sfm { LocalBundleAdjustmentGraph::LocalBundleAdjustmentGraph(const sfmData::SfMData& sfmData) { - for(const auto& it : sfmData.getIntrinsics()) - { - const IndexT intrinsicId = it.first; - const auto intrinsicPtr = it.second; - - IntrinsicHistory intrinsicHistory; - intrinsicHistory.nbPoses = 0; - intrinsicHistory.focalLength = intrinsicPtr->getParams().at(0); - intrinsicHistory.isConstant = intrinsicPtr->isLocked(); - - _intrinsicsHistory[intrinsicId].push_back(intrinsicHistory); - _mapFocalIsConstant[intrinsicId] = intrinsicPtr->isLocked(); - } + for (const auto& it : sfmData.getIntrinsics()) + { + const IndexT intrinsicId = it.first; + const auto intrinsicPtr = it.second; + + IntrinsicHistory intrinsicHistory; + intrinsicHistory.nbPoses = 0; + intrinsicHistory.focalLength = intrinsicPtr->getParams().at(0); + intrinsicHistory.isConstant = intrinsicPtr->isLocked(); + + _intrinsicsHistory[intrinsicId].push_back(intrinsicHistory); + _mapFocalIsConstant[intrinsicId] = intrinsicPtr->isLocked(); + } } std::map LocalBundleAdjustmentGraph::getDistancesHistogram() const { - std::map histogram; - - for(const auto& x : _distancePerViewId) - { - if(histogram.find(x.second) == histogram.end()) - histogram[x.second] = 1; - else - histogram.at(x.second)++; - } - return histogram; + std::map histogram; + + for (const auto& x : _distancePerViewId) + { + if (histogram.find(x.second) == histogram.end()) + histogram[x.second] = 1; + else + histogram.at(x.second)++; + } + return histogram; } void LocalBundleAdjustmentGraph::setAllParametersToRefine(const sfmData::SfMData& sfmData) { - _distancePerViewId.clear(); - _distancePerPoseId.clear(); - _statePerPoseId.clear(); - _statePerIntrinsicId.clear(); - _statePerLandmarkId.clear(); - - // poses - for(sfmData::Poses::const_iterator itPose = sfmData.getPoses().begin(); itPose != sfmData.getPoses().end(); ++itPose) - _statePerPoseId[itPose->first] = BundleAdjustment::EParameterState::REFINED; - - // instrinsics - for(const auto& itIntrinsic: sfmData.getIntrinsics()) - _statePerIntrinsicId[itIntrinsic.first] = BundleAdjustment::EParameterState::REFINED; - - // landmarks - for(const auto& itLandmark: sfmData.getLandmarks()) - _statePerLandmarkId[itLandmark.first] = BundleAdjustment::EParameterState::REFINED; + _distancePerViewId.clear(); + _distancePerPoseId.clear(); + _statePerPoseId.clear(); + _statePerIntrinsicId.clear(); + _statePerLandmarkId.clear(); + + // poses + for (sfmData::Poses::const_iterator itPose = sfmData.getPoses().begin(); itPose != sfmData.getPoses().end(); ++itPose) + _statePerPoseId[itPose->first] = BundleAdjustment::EParameterState::REFINED; + + // instrinsics + for (const auto& itIntrinsic : sfmData.getIntrinsics()) + _statePerIntrinsicId[itIntrinsic.first] = BundleAdjustment::EParameterState::REFINED; + + // landmarks + for (const auto& itLandmark : sfmData.getLandmarks()) + _statePerLandmarkId[itLandmark.first] = BundleAdjustment::EParameterState::REFINED; } void LocalBundleAdjustmentGraph::saveIntrinsicsToHistory(const sfmData::SfMData& sfmData) { - // count the number of poses for each intrinsic - std::map intrinsicUsage; - - for(const auto& itView : sfmData.getViews()) - { - const sfmData::View * view = itView.second.get(); - - if(sfmData.isPoseAndIntrinsicDefined(view)) + // count the number of poses for each intrinsic + std::map intrinsicUsage; + + for (const auto& itView : sfmData.getViews()) + { + const sfmData::View* view = itView.second.get(); + + if (sfmData.isPoseAndIntrinsicDefined(view)) + { + auto itIntr = intrinsicUsage.find(view->getIntrinsicId()); + if (itIntr == intrinsicUsage.end()) + intrinsicUsage[view->getIntrinsicId()] = 1; + else + intrinsicUsage[view->getIntrinsicId()]++; + } + } + + // complete the intrinsics history with the current focal lengths + for (const auto& it : sfmData.getIntrinsics()) { - auto itIntr = intrinsicUsage.find(view->getIntrinsicId()); - if(itIntr == intrinsicUsage.end()) - intrinsicUsage[view->getIntrinsicId()] = 1; - else - intrinsicUsage[view->getIntrinsicId()]++; + const IndexT intrinsicId = it.first; + const auto intrinsicPtr = it.second; + + IntrinsicHistory intrinsicHistory; + intrinsicHistory.nbPoses = intrinsicUsage[intrinsicId]; + intrinsicHistory.focalLength = intrinsicPtr->getParams().at(0); + intrinsicHistory.isConstant = isFocalLengthConstant(intrinsicId); + + _intrinsicsHistory.at(intrinsicId).push_back(intrinsicHistory); } - } - - // complete the intrinsics history with the current focal lengths - for(const auto& it : sfmData.getIntrinsics()) - { - const IndexT intrinsicId = it.first; - const auto intrinsicPtr = it.second; - - IntrinsicHistory intrinsicHistory; - intrinsicHistory.nbPoses = intrinsicUsage[intrinsicId]; - intrinsicHistory.focalLength = intrinsicPtr->getParams().at(0); - intrinsicHistory.isConstant = isFocalLengthConstant(intrinsicId); - - _intrinsicsHistory.at(intrinsicId).push_back(intrinsicHistory); - } } void LocalBundleAdjustmentGraph::exportIntrinsicsHistory(const std::string& folder, const std::string& filename) { - ALICEVISION_LOG_DEBUG("Exporting intrinsics history..."); - std::ofstream os; - os.open((fs::path(folder) / filename).string(), std::ios::app); - os.seekp(0, std::ios::end); // put the cursor at the end + ALICEVISION_LOG_DEBUG("Exporting intrinsics history..."); + std::ofstream os; + os.open((fs::path(folder) / filename).string(), std::ios::app); + os.seekp(0, std::ios::end); // put the cursor at the end - for(const auto& intrinsicHistoryPair : _intrinsicsHistory) - os << intrinsicHistoryPair.first << ";;;;"; - os << "\n"; + for (const auto& intrinsicHistoryPair : _intrinsicsHistory) + os << intrinsicHistoryPair.first << ";;;;"; + os << "\n"; - for(const auto& intrinsicHistoryPair : _intrinsicsHistory) - os << "#poses;f;isConstant;;"; - os << "\n"; + for (const auto& intrinsicHistoryPair : _intrinsicsHistory) + os << "#poses;f;isConstant;;"; + os << "\n"; - const std::size_t nbIterations = _intrinsicsHistory.begin()->second.size(); + const std::size_t nbIterations = _intrinsicsHistory.begin()->second.size(); - for(std::size_t i = 0; i < nbIterations; ++i) - { - for(const auto& intrinsicHistoryPair : _intrinsicsHistory) + for (std::size_t i = 0; i < nbIterations; ++i) { - const IntrinsicHistory& intrinsicsHistory = intrinsicHistoryPair.second.at(i); - os << intrinsicsHistory.nbPoses << ";"<< intrinsicsHistory.focalLength << ";" << intrinsicsHistory.isConstant << ";;"; + for (const auto& intrinsicHistoryPair : _intrinsicsHistory) + { + const IntrinsicHistory& intrinsicsHistory = intrinsicHistoryPair.second.at(i); + os << intrinsicsHistory.nbPoses << ";" << intrinsicsHistory.focalLength << ";" << intrinsicsHistory.isConstant << ";;"; + } + os << "\n"; } - os << "\n"; - } - os.close(); + os.close(); } bool LocalBundleAdjustmentGraph::removeViews(const sfmData::SfMData& sfmData, const std::set& removedViewsId) { - std::size_t numRemovedNode = 0; - std::map> removedEdgesByIntrinsic; + std::size_t numRemovedNode = 0; + std::map> removedEdgesByIntrinsic; - for(const IndexT& viewId : removedViewsId) - { - const auto it = _nodePerViewId.find(viewId); - if(it == _nodePerViewId.end()) + for (const IndexT& viewId : removedViewsId) { - ALICEVISION_LOG_WARNING("The view id: " << viewId << " does not exist in the graph, cannot remove it."); - continue; - } + const auto it = _nodePerViewId.find(viewId); + if (it == _nodePerViewId.end()) + { + ALICEVISION_LOG_WARNING("The view id: " << viewId << " does not exist in the graph, cannot remove it."); + continue; + } - // keep track of node incident edges that are going to be removed - // in order to update _intrinsicEdgesId accordingly - { - const IndexT intrinsicId = sfmData.getView(viewId).getIntrinsicId(); - const auto intrinsicIt = _intrinsicEdgesId.find(intrinsicId); - if(intrinsicIt != _intrinsicEdgesId.end()) - { - // store incident edge ids before removal - for(lemon::ListGraph::IncEdgeIt e(_graph, it->second); e != lemon::INVALID; ++e) + // keep track of node incident edges that are going to be removed + // in order to update _intrinsicEdgesId accordingly { - removedEdgesByIntrinsic[intrinsicId].push_back(_graph.id(lemon::ListGraph::Edge(e))); + const IndexT intrinsicId = sfmData.getView(viewId).getIntrinsicId(); + const auto intrinsicIt = _intrinsicEdgesId.find(intrinsicId); + if (intrinsicIt != _intrinsicEdgesId.end()) + { + // store incident edge ids before removal + for (lemon::ListGraph::IncEdgeIt e(_graph, it->second); e != lemon::INVALID; ++e) + { + removedEdgesByIntrinsic[intrinsicId].push_back(_graph.id(lemon::ListGraph::Edge(e))); + } + } } - } + + _graph.erase(it->second); // this function erase a node with its incident arcs + _viewIdPerNode.erase(it->second); + _nodePerViewId.erase(it->first); // warning: invalidates the iterator "it", so it can not be used after this line + + ++numRemovedNode; + ALICEVISION_LOG_DEBUG("The view #" << viewId << " has been successfully removed to the distance graph."); } - - _graph.erase(it->second); // this function erase a node with its incident arcs - _viewIdPerNode.erase(it->second); - _nodePerViewId.erase(it->first); // warning: invalidates the iterator "it", so it can not be used after this line - - ++numRemovedNode; - ALICEVISION_LOG_DEBUG("The view #" << viewId << " has been successfully removed to the distance graph."); - } - - // remove erased edges from _intrinsicsEdgesId - for(auto& edgesIt : removedEdgesByIntrinsic) - { - const IndexT intrinsicId = edgesIt.first; - std::vector& edgeIds = _intrinsicEdgesId[intrinsicId]; - std::vector& removedEdges = edgesIt.second; - - std::vector newEdgeIds; - // sort before using set_difference - std::sort(edgeIds.begin(), edgeIds.end()); - std::sort(removedEdges.begin(), removedEdges.end()); - - std::set_difference( - edgeIds.begin(), edgeIds.end(), - removedEdges.begin(), removedEdges.end(), - std::back_inserter(newEdgeIds) - ); - std::swap(edgeIds, newEdgeIds); - - if(edgeIds.empty()) + + // remove erased edges from _intrinsicsEdgesId + for (auto& edgesIt : removedEdgesByIntrinsic) { - _intrinsicEdgesId.erase(intrinsicId); + const IndexT intrinsicId = edgesIt.first; + std::vector& edgeIds = _intrinsicEdgesId[intrinsicId]; + std::vector& removedEdges = edgesIt.second; + + std::vector newEdgeIds; + // sort before using set_difference + std::sort(edgeIds.begin(), edgeIds.end()); + std::sort(removedEdges.begin(), removedEdges.end()); + + std::set_difference(edgeIds.begin(), edgeIds.end(), removedEdges.begin(), removedEdges.end(), std::back_inserter(newEdgeIds)); + std::swap(edgeIds, newEdgeIds); + + if (edgeIds.empty()) + { + _intrinsicEdgesId.erase(intrinsicId); + } } - } - return numRemovedNode == removedViewsId.size(); + return numRemovedNode == removedViewsId.size(); } int LocalBundleAdjustmentGraph::getPoseDistance(const IndexT poseId) const { - if(_distancePerPoseId.find(poseId) == _distancePerPoseId.end()) - { - ALICEVISION_LOG_DEBUG("The pose #" << poseId << " does not exist in the '_mapDistancePerPoseId' (map size: " << _distancePerPoseId.size() << ") \n"); - return -1; - } - return _distancePerPoseId.at(poseId); + if (_distancePerPoseId.find(poseId) == _distancePerPoseId.end()) + { + ALICEVISION_LOG_DEBUG("The pose #" << poseId << " does not exist in the '_mapDistancePerPoseId' (map size: " << _distancePerPoseId.size() + << ") \n"); + return -1; + } + return _distancePerPoseId.at(poseId); } int LocalBundleAdjustmentGraph::getViewDistance(const IndexT viewId) const { - if(_distancePerViewId.find(viewId) == _distancePerViewId.end()) - { - ALICEVISION_LOG_DEBUG("Cannot get the graph-distance of the view #" << viewId << ": does not exist in the '_mapDistancePerViewId':\n"); - return -1; - } - return _distancePerViewId.at(viewId); + if (_distancePerViewId.find(viewId) == _distancePerViewId.end()) + { + ALICEVISION_LOG_DEBUG("Cannot get the graph-distance of the view #" << viewId << ": does not exist in the '_mapDistancePerViewId':\n"); + return -1; + } + return _distancePerViewId.at(viewId); } BundleAdjustment::EParameterState LocalBundleAdjustmentGraph::getStateFromDistance(int distance) const { - if(distance >= 0 && distance <= _graphDistanceLimit) // [0; D] - return BundleAdjustment::EParameterState::REFINED; - else if(distance == _graphDistanceLimit + 1) // {D+1} - return BundleAdjustment::EParameterState::CONSTANT; + if (distance >= 0 && distance <= _graphDistanceLimit) // [0; D] + return BundleAdjustment::EParameterState::REFINED; + else if (distance == _graphDistanceLimit + 1) // {D+1} + return BundleAdjustment::EParameterState::CONSTANT; - // [-inf; 0[ U [D+2; +inf.[ (-1: not connected to the new views) - return BundleAdjustment::EParameterState::IGNORED; + // [-inf; 0[ U [D+2; +inf.[ (-1: not connected to the new views) + return BundleAdjustment::EParameterState::IGNORED; } -void LocalBundleAdjustmentGraph::updateGraphWithNewViews( - const sfmData::SfMData& sfmData, - const track::TracksPerView& map_tracksPerView, - const std::set& newReconstructedViews, - const std::size_t minNbOfMatches) +void LocalBundleAdjustmentGraph::updateGraphWithNewViews(const sfmData::SfMData& sfmData, + const track::TracksPerView& map_tracksPerView, + const std::set& newReconstructedViews, + const std::size_t minNbOfMatches) { - // identify the views we need to add to the graph: - // - this is the first Local BA: the graph is still empty, so add all the posed views of the scene - // - else: add the newly posed views only. - // add the posed views to the graph: - // - each node represents the posed views - // - each edge links 2 views if they share at least 'kMinNbOfMatches' matches - - ALICEVISION_LOG_DEBUG("Updating the distances graph with newly resected views..."); - - // identify the views we need to add to the graph: - std::set addedViewsId; - - if(_graph.maxNodeId() + 1 == 0) // the graph is empty: add all the poses of the scene - { - ALICEVISION_LOG_DEBUG("The graph is empty: initial pair & new view(s) added."); - for(const auto & x : sfmData.getViews()) + // identify the views we need to add to the graph: + // - this is the first Local BA: the graph is still empty, so add all the posed views of the scene + // - else: add the newly posed views only. + // add the posed views to the graph: + // - each node represents the posed views + // - each edge links 2 views if they share at least 'kMinNbOfMatches' matches + + ALICEVISION_LOG_DEBUG("Updating the distances graph with newly resected views..."); + + // identify the views we need to add to the graph: + std::set addedViewsId; + + if (_graph.maxNodeId() + 1 == 0) // the graph is empty: add all the poses of the scene { - if(sfmData.isPoseAndIntrinsicDefined(x.first)) - addedViewsId.insert(x.first); + ALICEVISION_LOG_DEBUG("The graph is empty: initial pair & new view(s) added."); + for (const auto& x : sfmData.getViews()) + { + if (sfmData.isPoseAndIntrinsicDefined(x.first)) + addedViewsId.insert(x.first); + } } - } - else // the graph is not empty - addedViewsId = newReconstructedViews; - - // add nodes to the graph - std::size_t nbAddedNodes = 0; - for(const IndexT& viewId : addedViewsId) - { - // check if the node does not already exist in the graph - // it happens when multiple local BA are run successively, with no new reconstructed views. - if(_nodePerViewId.find(viewId) != _nodePerViewId.end()) + else // the graph is not empty + addedViewsId = newReconstructedViews; + + // add nodes to the graph + std::size_t nbAddedNodes = 0; + for (const IndexT& viewId : addedViewsId) { - ALICEVISION_LOG_DEBUG("Cannot add the view id: " << viewId << " to the graph, already exists in the graph."); - continue; + // check if the node does not already exist in the graph + // it happens when multiple local BA are run successively, with no new reconstructed views. + if (_nodePerViewId.find(viewId) != _nodePerViewId.end()) + { + ALICEVISION_LOG_DEBUG("Cannot add the view id: " << viewId << " to the graph, already exists in the graph."); + continue; + } + + // check if the node corresponds to a posed views + if (!sfmData.isPoseAndIntrinsicDefined(viewId)) + { + ALICEVISION_LOG_WARNING("Cannot add the view id: " << viewId << " to the graph, its pose & intrinsic are not defined."); + continue; + } + + lemon::ListGraph::Node newNode = _graph.addNode(); + _nodePerViewId[viewId] = newNode; + _viewIdPerNode[newNode] = viewId; + ++nbAddedNodes; } - // check if the node corresponds to a posed views - if(!sfmData.isPoseAndIntrinsicDefined(viewId)) + // add edges to the graph + std::size_t numAddedEdges = 0; + if (!addedViewsId.empty()) { - ALICEVISION_LOG_WARNING("Cannot add the view id: " << viewId << " to the graph, its pose & intrinsic are not defined."); - continue; + // each new view need to be connected to the graph + // we create the 'minNbOfEdgesPerView' best edges and all the other with more than 'minNbOfMatches' shared landmarks + const std::size_t minNbOfEdgesPerView = 10; + std::vector newEdges = getNewEdges(sfmData, map_tracksPerView, addedViewsId, minNbOfMatches, minNbOfEdgesPerView); + numAddedEdges = newEdges.size(); + + for (const Pair& edge : newEdges) + _graph.addEdge(_nodePerViewId.at(edge.first), _nodePerViewId.at(edge.second)); + + numAddedEdges += addIntrinsicEdgesToTheGraph(sfmData, addedViewsId); } - - lemon::ListGraph::Node newNode = _graph.addNode(); - _nodePerViewId[viewId] = newNode; - _viewIdPerNode[newNode] = viewId; - ++nbAddedNodes; - } - - // add edges to the graph - std::size_t numAddedEdges = 0; - if(!addedViewsId.empty()) - { - // each new view need to be connected to the graph - // we create the 'minNbOfEdgesPerView' best edges and all the other with more than 'minNbOfMatches' shared landmarks - const std::size_t minNbOfEdgesPerView = 10; - std::vector newEdges = getNewEdges(sfmData, map_tracksPerView, addedViewsId, minNbOfMatches, minNbOfEdgesPerView); - numAddedEdges = newEdges.size(); - - for(const Pair& edge: newEdges) - _graph.addEdge(_nodePerViewId.at(edge.first), _nodePerViewId.at(edge.second)); - - numAddedEdges += addIntrinsicEdgesToTheGraph(sfmData, addedViewsId); - } - - ALICEVISION_LOG_DEBUG("The distances graph has been completed with " << nbAddedNodes<< " nodes & " << numAddedEdges << " edges."); - ALICEVISION_LOG_DEBUG("It contains " << _graph.maxNodeId() + 1 << " nodes & " << _graph.maxEdgeId() + 1 << " edges"); + + ALICEVISION_LOG_DEBUG("The distances graph has been completed with " << nbAddedNodes << " nodes & " << numAddedEdges << " edges."); + ALICEVISION_LOG_DEBUG("It contains " << _graph.maxNodeId() + 1 << " nodes & " << _graph.maxEdgeId() + 1 << " edges"); } void LocalBundleAdjustmentGraph::computeGraphDistances(const sfmData::SfMData& sfmData, const std::set& newReconstructedViews) -{ - ALICEVISION_LOG_DEBUG("Computing graph-distances..."); - - // reset the maps - _distancePerViewId.clear(); - _distancePerPoseId.clear(); - - // setup Breadth First Search using Lemon - lemon::Bfs bfs(_graph); - bfs.init(); - - // add source views for the bfs visit of the _graph - for(const IndexT viewId: newReconstructedViews) - { - auto it = _nodePerViewId.find(viewId); - if(it == _nodePerViewId.end()) - ALICEVISION_LOG_WARNING("The reconstructed view #" << viewId << " cannot be added as source for the BFS: does not exist in the graph."); - else - bfs.addSource(it->second); - } - bfs.start(); - - // handle bfs results (distances) - for(const auto& x : _nodePerViewId) // each node in the graph - { - auto& node = x.second; - int d = -1; - - if(bfs.reached(node)) +{ + ALICEVISION_LOG_DEBUG("Computing graph-distances..."); + + // reset the maps + _distancePerViewId.clear(); + _distancePerPoseId.clear(); + + // setup Breadth First Search using Lemon + lemon::Bfs bfs(_graph); + bfs.init(); + + // add source views for the bfs visit of the _graph + for (const IndexT viewId : newReconstructedViews) + { + auto it = _nodePerViewId.find(viewId); + if (it == _nodePerViewId.end()) + ALICEVISION_LOG_WARNING("The reconstructed view #" << viewId << " cannot be added as source for the BFS: does not exist in the graph."); + else + bfs.addSource(it->second); + } + bfs.start(); + + // handle bfs results (distances) + for (const auto& x : _nodePerViewId) // each node in the graph { - d = bfs.dist(node); - // dist(): "If node v is not reached from the root(s), then the return value of this function is undefined." - // this is why the distance is previously set to -1. + auto& node = x.second; + int d = -1; + + if (bfs.reached(node)) + { + d = bfs.dist(node); + // dist(): "If node v is not reached from the root(s), then the return value of this function is undefined." + // this is why the distance is previously set to -1. + } + _distancePerViewId[x.first] = d; + } + + // re-mapping from to : + for (auto x : _distancePerViewId) + { + // get the poseId of the camera no. viewId + const IndexT idPose = sfmData.getViews().at(x.first)->getPoseId(); // PoseId of a resected camera + + auto poseIt = _distancePerPoseId.find(idPose); + // if multiple views share the same pose + if (poseIt != _distancePerPoseId.end()) + poseIt->second = std::min(poseIt->second, x.second); + else + _distancePerPoseId[idPose] = x.second; } - _distancePerViewId[x.first] = d; - } - - // re-mapping from to : - for(auto x: _distancePerViewId) - { - // get the poseId of the camera no. viewId - const IndexT idPose = sfmData.getViews().at(x.first)->getPoseId(); // PoseId of a resected camera - - auto poseIt = _distancePerPoseId.find(idPose); - // if multiple views share the same pose - if(poseIt != _distancePerPoseId.end()) - poseIt->second = std::min(poseIt->second, x.second); - else - _distancePerPoseId[idPose] = x.second; - } } void LocalBundleAdjustmentGraph::convertDistancesToStates(const sfmData::SfMData& sfmData) { - // reset the maps - _statePerPoseId.clear(); - _statePerIntrinsicId.clear(); - _statePerLandmarkId.clear(); - - const std::size_t kWindowSize = 25; //< nb of the last value in which compute the variation - const double kStdevPercentage = 1.0; //< limit percentage of the Std deviation according to the range of all the parameters (e.i. focal) - - // D = the distance of the 'active' region (kLimitDistance) - // W = the range (in term of num. of poses) on which to study the focal length variations (kWindowSize) - // L = the max. percentage of the variations of the focal length to consider it as cosntant (kStdevPercentage) - // - a posed views is setas to: - // - Refined <=> dist <= D - // - Constant <=> dist == D+1 - // - Ignored <=> else (dist = -1 U [D+2; +inf.[) - // - an intrinsic is set to: - // - Refined by default - // - Constant <=> its focal lenght is considered as stable in its W last saved values - // according to all of its values. - // - a landmarks is set to: - // - Ignored by default - // - Refined <=> its connected to a refined camera - - // poses - for(const auto& posePair : sfmData.getPoses()) - { - const IndexT poseId = posePair.first; - const int distance = getPoseDistance(poseId); - const BundleAdjustment::EParameterState state = getStateFromDistance(distance); - - _statePerPoseId[poseId] = state; - } - - // instrinsics - checkFocalLengthsConsistency(kWindowSize, kStdevPercentage); - - for(const auto& itIntrinsic: sfmData.getIntrinsics()) - { - if(isFocalLengthConstant(itIntrinsic.first)) - _statePerIntrinsicId[itIntrinsic.first] = BundleAdjustment::EParameterState::CONSTANT; - else - _statePerIntrinsicId[itIntrinsic.first] = BundleAdjustment::EParameterState::REFINED; - } - - // landmarks - for(const auto& itLandmark: sfmData.getLandmarks()) - { - const IndexT landmarkId = itLandmark.first; - const sfmData::Observations& observations = itLandmark.second.observations; - - assert(observations.size() >= 2); - - std::array states = {false, false, false}; - for(const auto& observationIt: observations) + // reset the maps + _statePerPoseId.clear(); + _statePerIntrinsicId.clear(); + _statePerLandmarkId.clear(); + + const std::size_t kWindowSize = 25; //< nb of the last value in which compute the variation + const double kStdevPercentage = 1.0; //< limit percentage of the Std deviation according to the range of all the parameters (e.i. focal) + + // D = the distance of the 'active' region (kLimitDistance) + // W = the range (in term of num. of poses) on which to study the focal length variations (kWindowSize) + // L = the max. percentage of the variations of the focal length to consider it as cosntant (kStdevPercentage) + // - a posed views is setas to: + // - Refined <=> dist <= D + // - Constant <=> dist == D+1 + // - Ignored <=> else (dist = -1 U [D+2; +inf.[) + // - an intrinsic is set to: + // - Refined by default + // - Constant <=> its focal lenght is considered as stable in its W last saved values + // according to all of its values. + // - a landmarks is set to: + // - Ignored by default + // - Refined <=> its connected to a refined camera + + // poses + for (const auto& posePair : sfmData.getPoses()) { - const int distance = getViewDistance(observationIt.first); - const BundleAdjustment::EParameterState viewState = getStateFromDistance(distance); - states.at(static_cast(viewState)) = true; + const IndexT poseId = posePair.first; + const int distance = getPoseDistance(poseId); + const BundleAdjustment::EParameterState state = getStateFromDistance(distance); + + _statePerPoseId[poseId] = state; } - // in the general case, a landmark can NOT have observations from refined AND ignored cameras. - // in pratice, there is a minimal number of common points to declare the connection between images. - // so we can have some points that are not declared in the graph of cameras connections. - // for these particular cases, we can have landmarks with refined AND ignored cameras. - // in this particular case, we prefer to ignore the landmark to avoid wrong/unconstraint refinements. - - if(!states.at(static_cast(BundleAdjustment::EParameterState::REFINED)) || - states.at(static_cast(BundleAdjustment::EParameterState::IGNORED))) - _statePerLandmarkId[landmarkId] = BundleAdjustment::EParameterState::IGNORED; - else - _statePerLandmarkId[landmarkId] = BundleAdjustment::EParameterState::REFINED; - } -} + // instrinsics + checkFocalLengthsConsistency(kWindowSize, kStdevPercentage); -std::vector LocalBundleAdjustmentGraph::getNewEdges( - const sfmData::SfMData& sfmData, - const track::TracksPerView& tracksPerView, - const std::set& newViewsId, - const std::size_t minNbOfMatches, - const std::size_t minNbOfEdgesPerView) -{ - std::vector newEdges; - - // get landmarks id. of all the reconstructed 3D points (: landmarks) - // TODO: avoid copy and use boost::transform_iterator - std::set landmarkIds; - std::transform(sfmData.getLandmarks().begin(), sfmData.getLandmarks().end(), - std::inserter(landmarkIds, landmarkIds.begin()), - stl::RetrieveKey()); - - for(IndexT viewId: newViewsId) - { - std::map sharedLandmarksPerView; - - // get all the tracks of the new added view - const aliceVision::track::TrackIdSet& newViewTrackIds = tracksPerView.at(viewId); - - // keep the reconstructed tracks (with an associated landmark) - std::vector newViewLandmarks; // all landmarks (already reconstructed) visible from the new view - - newViewLandmarks.reserve(newViewTrackIds.size()); - std::set_intersection(newViewTrackIds.begin(), newViewTrackIds.end(), - landmarkIds.begin(), landmarkIds.end(), - std::back_inserter(newViewLandmarks)); - - // retrieve the common track Ids - for(IndexT landmarkId: newViewLandmarks) + for (const auto& itIntrinsic : sfmData.getIntrinsics()) { - for(const auto& observations: sfmData.getLandmarks().at(landmarkId).observations) - { - if(observations.first == viewId) - continue; // do not compare an observation with itself - - // increment the number of common landmarks between the new view and the already - // reconstructed cameras (observations). - auto it = sharedLandmarksPerView.find(observations.first); - if(it == sharedLandmarksPerView.end()) // the first common landmark - sharedLandmarksPerView[observations.first] = 1; + if (isFocalLengthConstant(itIntrinsic.first)) + _statePerIntrinsicId[itIntrinsic.first] = BundleAdjustment::EParameterState::CONSTANT; else - ++it->second; - } + _statePerIntrinsicId[itIntrinsic.first] = BundleAdjustment::EParameterState::REFINED; } - using ViewNbLandmarks = std::pair; + // landmarks + for (const auto& itLandmark : sfmData.getLandmarks()) + { + const IndexT landmarkId = itLandmark.first; + const sfmData::Observations& observations = itLandmark.second.observations; - std::vector sharedLandmarksPerViewSorted; - sharedLandmarksPerViewSorted.reserve(sharedLandmarksPerView.size()); - for(const auto& sharedLandmarkPair: sharedLandmarksPerView) - sharedLandmarksPerViewSorted.push_back(sharedLandmarkPair); + assert(observations.size() >= 2); - std::sort(sharedLandmarksPerViewSorted.begin(), sharedLandmarksPerViewSorted.end(), [](const ViewNbLandmarks& a, const ViewNbLandmarks& b){ return (a.second > b.second); }); + std::array states = {false, false, false}; + for (const auto& observationIt : observations) + { + const int distance = getViewDistance(observationIt.first); + const BundleAdjustment::EParameterState viewState = getStateFromDistance(distance); + states.at(static_cast(viewState)) = true; + } - std::size_t nbEdgesPerView = 0; - for(const ViewNbLandmarks& sharedLandmarkPair : sharedLandmarksPerViewSorted) - { - if(nbEdgesPerView >= minNbOfEdgesPerView && - sharedLandmarkPair.second < minNbOfMatches) - break; + // in the general case, a landmark can NOT have observations from refined AND ignored cameras. + // in pratice, there is a minimal number of common points to declare the connection between images. + // so we can have some points that are not declared in the graph of cameras connections. + // for these particular cases, we can have landmarks with refined AND ignored cameras. + // in this particular case, we prefer to ignore the landmark to avoid wrong/unconstraint refinements. - // edges format: pair - newEdges.emplace_back(std::min(viewId, sharedLandmarkPair.first), std::max(viewId, sharedLandmarkPair.first)); - ++nbEdgesPerView; + if (!states.at(static_cast(BundleAdjustment::EParameterState::REFINED)) || + states.at(static_cast(BundleAdjustment::EParameterState::IGNORED))) + _statePerLandmarkId[landmarkId] = BundleAdjustment::EParameterState::IGNORED; + else + _statePerLandmarkId[landmarkId] = BundleAdjustment::EParameterState::REFINED; } - } - return newEdges; } -void LocalBundleAdjustmentGraph::checkFocalLengthsConsistency(const std::size_t windowSize, const double stdevPercentageLimit) +std::vector LocalBundleAdjustmentGraph::getNewEdges(const sfmData::SfMData& sfmData, + const track::TracksPerView& tracksPerView, + const std::set& newViewsId, + const std::size_t minNbOfMatches, + const std::size_t minNbOfEdgesPerView) { - ALICEVISION_LOG_DEBUG("Checking, for each camera, if the focal length is stable..."); - std::size_t numOfConstFocal = 0; - - for(const auto& intrinsicEntry : _intrinsicsHistory) - { - const IndexT idIntrinsic = intrinsicEntry.first; - - // do not compute the variation, if the intrinsic has already be set as constant. - if(isFocalLengthConstant(idIntrinsic)) - { - numOfConstFocal++; - continue; - } - - // get the full history of intrinsic parameters - std::vector allNbPoses; - std::vector allFocalLengths; - - for(const IntrinsicHistory& intrinsicHistory : intrinsicEntry.second) - { - allNbPoses.push_back(intrinsicHistory.nbPoses); - allFocalLengths.push_back(intrinsicHistory.focalLength); - } - - // clean 'intrinsicsHistorical': - // [4 5 5 7 8 6 9] - // - detect duplicates -> [4 (5) 5 7 8 6 9] - // - detecting removed cameras -> [4 5 (7 8) 6 9] - std::vector filteredNbPoses(allNbPoses); - std::vector filteredFocalLengths(allFocalLengths); - - std::size_t numPosesEndWindow = allNbPoses.back(); - - for(int id = filteredNbPoses.size()-2; id > 0; --id) - { - if(filteredNbPoses.size() < 2) - break; - - if(filteredNbPoses.at(id) >= filteredNbPoses.at(id+1)) - { - filteredNbPoses.erase(filteredNbPoses.begin()+id); - filteredFocalLengths.erase(filteredFocalLengths.begin()+id); - } - } - - // detect limit according to 'kWindowSize': - if(numPosesEndWindow < windowSize) - continue; - - IndexT idStartWindow = 0; - for(int id = filteredNbPoses.size()-2; id > 0; --id) + std::vector newEdges; + + // get landmarks id. of all the reconstructed 3D points (: landmarks) + // TODO: avoid copy and use boost::transform_iterator + std::set landmarkIds; + std::transform(sfmData.getLandmarks().begin(), sfmData.getLandmarks().end(), std::inserter(landmarkIds, landmarkIds.begin()), stl::RetrieveKey()); + + for (IndexT viewId : newViewsId) { - if(numPosesEndWindow - filteredNbPoses.at(id) >= windowSize) - { - idStartWindow = id; - break; - } + std::map sharedLandmarksPerView; + + // get all the tracks of the new added view + const aliceVision::track::TrackIdSet& newViewTrackIds = tracksPerView.at(viewId); + + // keep the reconstructed tracks (with an associated landmark) + std::vector newViewLandmarks; // all landmarks (already reconstructed) visible from the new view + + newViewLandmarks.reserve(newViewTrackIds.size()); + std::set_intersection( + newViewTrackIds.begin(), newViewTrackIds.end(), landmarkIds.begin(), landmarkIds.end(), std::back_inserter(newViewLandmarks)); + + // retrieve the common track Ids + for (IndexT landmarkId : newViewLandmarks) + { + for (const auto& observations : sfmData.getLandmarks().at(landmarkId).observations) + { + if (observations.first == viewId) + continue; // do not compare an observation with itself + + // increment the number of common landmarks between the new view and the already + // reconstructed cameras (observations). + auto it = sharedLandmarksPerView.find(observations.first); + if (it == sharedLandmarksPerView.end()) // the first common landmark + sharedLandmarksPerView[observations.first] = 1; + else + ++it->second; + } + } + + using ViewNbLandmarks = std::pair; + + std::vector sharedLandmarksPerViewSorted; + sharedLandmarksPerViewSorted.reserve(sharedLandmarksPerView.size()); + for (const auto& sharedLandmarkPair : sharedLandmarksPerView) + sharedLandmarksPerViewSorted.push_back(sharedLandmarkPair); + + std::sort(sharedLandmarksPerViewSorted.begin(), sharedLandmarksPerViewSorted.end(), [](const ViewNbLandmarks& a, const ViewNbLandmarks& b) { + return (a.second > b.second); + }); + + std::size_t nbEdgesPerView = 0; + for (const ViewNbLandmarks& sharedLandmarkPair : sharedLandmarksPerViewSorted) + { + if (nbEdgesPerView >= minNbOfEdgesPerView && sharedLandmarkPair.second < minNbOfMatches) + break; + + // edges format: pair + newEdges.emplace_back(std::min(viewId, sharedLandmarkPair.first), std::max(viewId, sharedLandmarkPair.first)); + ++nbEdgesPerView; + } } - - // compute the standard deviation for each parameter, between [idLimit; end()] - std::vector subValuesVec (filteredFocalLengths.begin()+idStartWindow, filteredFocalLengths.end()); - double stdev = standardDeviation(subValuesVec); - - // normalize stdev (: divide by the range of the values) - double minVal = *std::min_element(filteredFocalLengths.begin(), filteredFocalLengths.end()); - double maxVal = *std::max_element(filteredFocalLengths.begin(), filteredFocalLengths.end()); - double normStdev = stdev / (maxVal - minVal); - - // check if the normed standard deviation is < stdevPercentageLimit - if(normStdev * 100.0 <= stdevPercentageLimit) + return newEdges; +} + +void LocalBundleAdjustmentGraph::checkFocalLengthsConsistency(const std::size_t windowSize, const double stdevPercentageLimit) +{ + ALICEVISION_LOG_DEBUG("Checking, for each camera, if the focal length is stable..."); + std::size_t numOfConstFocal = 0; + + for (const auto& intrinsicEntry : _intrinsicsHistory) { - _mapFocalIsConstant.at(idIntrinsic) = true; - removeIntrinsicEdgesFromTheGraph(idIntrinsic); - numOfConstFocal++; - ALICEVISION_LOG_DEBUG("The intrinsic #" << idIntrinsic << " is now considered to be stable.\n"); + const IndexT idIntrinsic = intrinsicEntry.first; + + // do not compute the variation, if the intrinsic has already be set as constant. + if (isFocalLengthConstant(idIntrinsic)) + { + numOfConstFocal++; + continue; + } + + // get the full history of intrinsic parameters + std::vector allNbPoses; + std::vector allFocalLengths; + + for (const IntrinsicHistory& intrinsicHistory : intrinsicEntry.second) + { + allNbPoses.push_back(intrinsicHistory.nbPoses); + allFocalLengths.push_back(intrinsicHistory.focalLength); + } + + // clean 'intrinsicsHistorical': + // [4 5 5 7 8 6 9] + // - detect duplicates -> [4 (5) 5 7 8 6 9] + // - detecting removed cameras -> [4 5 (7 8) 6 9] + std::vector filteredNbPoses(allNbPoses); + std::vector filteredFocalLengths(allFocalLengths); + + std::size_t numPosesEndWindow = allNbPoses.back(); + + for (int id = filteredNbPoses.size() - 2; id > 0; --id) + { + if (filteredNbPoses.size() < 2) + break; + + if (filteredNbPoses.at(id) >= filteredNbPoses.at(id + 1)) + { + filteredNbPoses.erase(filteredNbPoses.begin() + id); + filteredFocalLengths.erase(filteredFocalLengths.begin() + id); + } + } + + // detect limit according to 'kWindowSize': + if (numPosesEndWindow < windowSize) + continue; + + IndexT idStartWindow = 0; + for (int id = filteredNbPoses.size() - 2; id > 0; --id) + { + if (numPosesEndWindow - filteredNbPoses.at(id) >= windowSize) + { + idStartWindow = id; + break; + } + } + + // compute the standard deviation for each parameter, between [idLimit; end()] + std::vector subValuesVec(filteredFocalLengths.begin() + idStartWindow, filteredFocalLengths.end()); + double stdev = standardDeviation(subValuesVec); + + // normalize stdev (: divide by the range of the values) + double minVal = *std::min_element(filteredFocalLengths.begin(), filteredFocalLengths.end()); + double maxVal = *std::max_element(filteredFocalLengths.begin(), filteredFocalLengths.end()); + double normStdev = stdev / (maxVal - minVal); + + // check if the normed standard deviation is < stdevPercentageLimit + if (normStdev * 100.0 <= stdevPercentageLimit) + { + _mapFocalIsConstant.at(idIntrinsic) = true; + removeIntrinsicEdgesFromTheGraph(idIntrinsic); + numOfConstFocal++; + ALICEVISION_LOG_DEBUG("The intrinsic #" << idIntrinsic << " is now considered to be stable.\n"); + } } - } - ALICEVISION_LOG_DEBUG(numOfConstFocal << "/" << _mapFocalIsConstant.size() << " intrinsics with a stable focal."); + ALICEVISION_LOG_DEBUG(numOfConstFocal << "/" << _mapFocalIsConstant.size() << " intrinsics with a stable focal."); } -template +template double LocalBundleAdjustmentGraph::standardDeviation(const std::vector& data) -{ - const double mean = std::accumulate(data.begin(), data.end(), 0.0) / data.size(); - std::vector diff(data.size()); - std::transform(data.begin(), data.end(), diff.begin(), [mean](double x) { return x - mean; }); - const double sqSum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0); - return std::sqrt(sqSum / data.size()); -} - - +{ + const double mean = std::accumulate(data.begin(), data.end(), 0.0) / data.size(); + std::vector diff(data.size()); + std::transform(data.begin(), data.end(), diff.begin(), [mean](double x) { return x - mean; }); + const double sqSum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0); + return std::sqrt(sqSum / data.size()); +} void LocalBundleAdjustmentGraph::drawGraph(const sfmData::SfMData& sfmData, const std::string& folder, const std::string& nameComplement) { - if(!fs::exists(folder)) - fs::create_directory(folder); - - std::stringstream dotStream; - dotStream << "digraph lemon_dot_example {" << "\n"; - - // node - dotStream << " node [ shape=ellipse, penwidth=5.0, fontname=Helvetica, fontsize=40 ];" << "\n"; - for(lemon::ListGraph::NodeIt n(_graph); n!=lemon::INVALID; ++n) - { - const IndexT viewId = _viewIdPerNode[n]; - const int viewDist = _distancePerViewId[viewId]; - - std::string color = ", color="; - if(viewDist == 0) color += "red"; - else if(viewDist == 1 ) color += "green"; - else if(viewDist == 2 ) color += "blue"; - else color += "black"; - dotStream << " n" << _graph.id(n) - << " [ label=\"" << viewId << ": D" << viewDist << " K" << sfmData.getViews().at(viewId)->getIntrinsicId() << "\"" << color << "]; " << "\n"; - } - - // edge - dotStream << " edge [ shape=ellipse, fontname=Helvetica, fontsize=5, color=black ];" << "\n"; - for(lemon::ListGraph::EdgeIt e(_graph); e!=lemon::INVALID; ++e) - { - dotStream << " n" << _graph.id(_graph.u(e)) << " -> " << " n" << _graph.id(_graph.v(e)); - if(_intrinsicEdgesId.find(static_cast(_graph.id(e))) != _intrinsicEdgesId.end()) - dotStream << " [color=red]\n"; - else - dotStream << "\n"; - } - dotStream << "}" << "\n"; - - const std::string dotFilepath = (fs::path(folder) / ("graph_" + std::to_string(_viewIdPerNode.size()) + "_" + nameComplement + ".dot")).string(); - std::ofstream dotFile; - dotFile.open(dotFilepath); - dotFile.write(dotStream.str().c_str(), dotStream.str().length()); - dotFile.close(); - - ALICEVISION_LOG_DEBUG("The graph '"<< dotFilepath << "' has been saved."); + if (!fs::exists(folder)) + fs::create_directory(folder); + + std::stringstream dotStream; + dotStream << "digraph lemon_dot_example {" + << "\n"; + + // node + dotStream << " node [ shape=ellipse, penwidth=5.0, fontname=Helvetica, fontsize=40 ];" + << "\n"; + for (lemon::ListGraph::NodeIt n(_graph); n != lemon::INVALID; ++n) + { + const IndexT viewId = _viewIdPerNode[n]; + const int viewDist = _distancePerViewId[viewId]; + + std::string color = ", color="; + if (viewDist == 0) + color += "red"; + else if (viewDist == 1) + color += "green"; + else if (viewDist == 2) + color += "blue"; + else + color += "black"; + dotStream << " n" << _graph.id(n) << " [ label=\"" << viewId << ": D" << viewDist << " K" << sfmData.getViews().at(viewId)->getIntrinsicId() + << "\"" << color << "]; " + << "\n"; + } + + // edge + dotStream << " edge [ shape=ellipse, fontname=Helvetica, fontsize=5, color=black ];" + << "\n"; + for (lemon::ListGraph::EdgeIt e(_graph); e != lemon::INVALID; ++e) + { + dotStream << " n" << _graph.id(_graph.u(e)) << " -> " + << " n" << _graph.id(_graph.v(e)); + if (_intrinsicEdgesId.find(static_cast(_graph.id(e))) != _intrinsicEdgesId.end()) + dotStream << " [color=red]\n"; + else + dotStream << "\n"; + } + dotStream << "}" + << "\n"; + + const std::string dotFilepath = (fs::path(folder) / ("graph_" + std::to_string(_viewIdPerNode.size()) + "_" + nameComplement + ".dot")).string(); + std::ofstream dotFile; + dotFile.open(dotFilepath); + dotFile.write(dotStream.str().c_str(), dotStream.str().length()); + dotFile.close(); + + ALICEVISION_LOG_DEBUG("The graph '" << dotFilepath << "' has been saved."); } std::size_t LocalBundleAdjustmentGraph::addIntrinsicEdgesToTheGraph(const sfmData::SfMData& sfmData, const std::set& newReconstructedViews) { - std::map newIntrinsicEdges; - - for(IndexT newViewId : newReconstructedViews) // for each new view - { - IndexT newViewIntrinsicId = sfmData.getViews().at(newViewId)->getIntrinsicId(); - - if(isFocalLengthConstant(newViewIntrinsicId)) // do not add edges for a constant intrinsic - continue; - - // for each reconstructed view in the graph - // warning: at this point, "_nodePerViewId" already contains the "newReconstructedViews" - for(const auto& x : _nodePerViewId) + std::map newIntrinsicEdges; + + for (IndexT newViewId : newReconstructedViews) // for each new view { - const auto& otherViewId = x.first; - - // if the new view share the same intrinsic than previously reconstructed views - // note: do not compare a view with itself (must be tested since "_nodePerViewId" contains "newReconstructedViews") - if(newViewId != otherViewId - && newViewIntrinsicId == sfmData.getViews().at(otherViewId)->getIntrinsicId()) - { - // register a new intrinsic edge between those views - newIntrinsicEdges[std::minmax(otherViewId, newViewId)] = newViewIntrinsicId; - } + IndexT newViewIntrinsicId = sfmData.getViews().at(newViewId)->getIntrinsicId(); + + if (isFocalLengthConstant(newViewIntrinsicId)) // do not add edges for a constant intrinsic + continue; + + // for each reconstructed view in the graph + // warning: at this point, "_nodePerViewId" already contains the "newReconstructedViews" + for (const auto& x : _nodePerViewId) + { + const auto& otherViewId = x.first; + + // if the new view share the same intrinsic than previously reconstructed views + // note: do not compare a view with itself (must be tested since "_nodePerViewId" contains "newReconstructedViews") + if (newViewId != otherViewId && newViewIntrinsicId == sfmData.getViews().at(otherViewId)->getIntrinsicId()) + { + // register a new intrinsic edge between those views + newIntrinsicEdges[std::minmax(otherViewId, newViewId)] = newViewIntrinsicId; + } + } + } + + // create registered intrinsic edges in lemon graph + // and update _intrinsicEdgesId accordingly + for (const auto& newEdge : newIntrinsicEdges) + { + const lemon::ListGraph::Edge edge = _graph.addEdge(_nodePerViewId[newEdge.first.first], _nodePerViewId[newEdge.first.second]); + _intrinsicEdgesId[newEdge.second].push_back(_graph.id(edge)); } - } - - // create registered intrinsic edges in lemon graph - // and update _intrinsicEdgesId accordingly - for(const auto& newEdge : newIntrinsicEdges) - { - const lemon::ListGraph::Edge edge = _graph.addEdge(_nodePerViewId[newEdge.first.first], _nodePerViewId[newEdge.first.second]); - _intrinsicEdgesId[newEdge.second].push_back(_graph.id(edge)); - } - return newIntrinsicEdges.size(); + return newIntrinsicEdges.size(); } void LocalBundleAdjustmentGraph::removeIntrinsicEdgesFromTheGraph(IndexT intrinsicId) { - if(_intrinsicEdgesId.count(intrinsicId) == 0) - return; - for(const int edgeId : _intrinsicEdgesId.at(intrinsicId)) - { - const auto& edge = _graph.edgeFromId(edgeId); - assert(_graph.valid(edge)); - _graph.erase(edge); - } - _intrinsicEdgesId.erase(intrinsicId); + if (_intrinsicEdgesId.count(intrinsicId) == 0) + return; + for (const int edgeId : _intrinsicEdgesId.at(intrinsicId)) + { + const auto& edge = _graph.edgeFromId(edgeId); + assert(_graph.valid(edge)); + _graph.erase(edge); + } + _intrinsicEdgesId.erase(intrinsicId); } - std::size_t LocalBundleAdjustmentGraph::updateRigEdgesToTheGraph(const sfmData::SfMData& sfmData) { - std::size_t numAddedEdges = 0; + std::size_t numAddedEdges = 0; - // remove all rig edges - for(auto& edgesPerRid: _rigEdgesId) - { - for(const int edgeId : edgesPerRid.second) + // remove all rig edges + for (auto& edgesPerRid : _rigEdgesId) { - const auto& edge = _graph.edgeFromId(edgeId); - assert(_graph.valid(edge)); - _graph.erase(edge); + for (const int edgeId : edgesPerRid.second) + { + const auto& edge = _graph.edgeFromId(edgeId); + assert(_graph.valid(edge)); + _graph.erase(edge); + } + } + _rigEdgesId.clear(); + + // recreate rig edges + std::map> viewIdsPerRig; + + for (const auto& viewNode : _nodePerViewId) // for each reconstructed view in the graph + { + const sfmData::View& view = sfmData.getView(viewNode.first); + if (view.isPoseIndependant()) + continue; + viewIdsPerRig[view.getRigId()].push_back(viewNode.first); } - } - _rigEdgesId.clear(); - - // recreate rig edges - std::map> viewIdsPerRig; - - for(const auto& viewNode : _nodePerViewId) // for each reconstructed view in the graph - { - const sfmData::View& view = sfmData.getView(viewNode.first); - if(view.isPoseIndependant()) - continue; - viewIdsPerRig[view.getRigId()].push_back(viewNode.first); - } - - for(auto& it : viewIdsPerRig) - std::sort(it.second.begin(), it.second.end()); - - for(const auto& it : viewIdsPerRig) - { - // if(sfmData.getRig(rigId).isLocked()) // TODO - // continue; - const IndexT rigId = it.first; - const std::vector& views = it.second; - for(int i = 0; i < views.size(); ++i) + + for (auto& it : viewIdsPerRig) + std::sort(it.second.begin(), it.second.end()); + + for (const auto& it : viewIdsPerRig) { - for(int j = i; j < views.size(); ++j) - { - lemon::ListGraph::Edge edge = _graph.addEdge(_nodePerViewId[views[i]], _nodePerViewId[views[j]]); - _rigEdgesId[rigId].push_back(_graph.id(edge)); - numAddedEdges++; - } + // if(sfmData.getRig(rigId).isLocked()) // TODO + // continue; + const IndexT rigId = it.first; + const std::vector& views = it.second; + for (int i = 0; i < views.size(); ++i) + { + for (int j = i; j < views.size(); ++j) + { + lemon::ListGraph::Edge edge = _graph.addEdge(_nodePerViewId[views[i]], _nodePerViewId[views[j]]); + _rigEdgesId[rigId].push_back(_graph.id(edge)); + numAddedEdges++; + } + } } - } - return numAddedEdges; + return numAddedEdges; } unsigned int LocalBundleAdjustmentGraph::countNodes() const { - unsigned int count = 0; - for(lemon::ListGraph::NodeIt n(_graph); n != lemon::INVALID; ++n) - ++count; - return count; + unsigned int count = 0; + for (lemon::ListGraph::NodeIt n(_graph); n != lemon::INVALID; ++n) + ++count; + return count; } unsigned int LocalBundleAdjustmentGraph::countEdges() const { - unsigned int count = 0; - for(lemon::ListGraph::EdgeIt e(_graph); e != lemon::INVALID; ++e) - ++count; - return count; + unsigned int count = 0; + for (lemon::ListGraph::EdgeIt e(_graph); e != lemon::INVALID; ++e) + ++count; + return count; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/LocalBundleAdjustmentGraph.hpp b/src/aliceVision/sfm/LocalBundleAdjustmentGraph.hpp index 9720bf2018..d599e31879 100644 --- a/src/aliceVision/sfm/LocalBundleAdjustmentGraph.hpp +++ b/src/aliceVision/sfm/LocalBundleAdjustmentGraph.hpp @@ -12,12 +12,11 @@ #include - namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { @@ -27,358 +26,338 @@ namespace sfm { */ class LocalBundleAdjustmentGraph { - -public: - - explicit LocalBundleAdjustmentGraph(const sfmData::SfMData& sfmData); - - /** - * @brief Return the number of posed views for each graph-distance - * @return map - */ - std::map getDistancesHistogram() const; - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific pose. - * @param[in] poseId The given pose Id - * @return BundleAdjustment::EParameterState - */ - inline BundleAdjustment::EParameterState getPoseState(const IndexT poseId) const - { - return _statePerPoseId.at(poseId); - } - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific intrinsic. - * @param[in] intrinsicId The given intrinsic Id - * @return BundleAdjustment::EParameterState - */ - inline BundleAdjustment::EParameterState getIntrinsicState(const IndexT intrinsicId) const - { - return _statePerIntrinsicId.at(intrinsicId); - } - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific landmark. - * @param[in] landmarkId The given landmark Id - * @return BundleAdjustment::EParameterState - */ - inline BundleAdjustment::EParameterState getLandmarkState(const IndexT landmarkId) const - { - return _statePerLandmarkId.at(landmarkId); - } - - /** - * @brief Return the number of poses with the given BundleAdjustment::EParameterState. - * @param[in] state The given BundleAdjustment::EParameterState. - * @return number of poses with the given BundleAdjustment::EParameterState. - */ - inline std::size_t getNbPosesPerState(BundleAdjustment::EParameterState state) const - { - std::size_t nb = 0; - for(const auto& poseStatePair : _statePerPoseId) - if(poseStatePair.second == state) - ++nb; - return nb; - } - - /** - * @brief Return the number of intrinsics with the given BundleAdjustment::EParameterState. - * @param[in] state The given BundleAdjustment::EParameterState. - * @return number of intrinsics with the given BundleAdjustment::EParameterState. - */ - inline std::size_t getNbIntrinsicsPerState(BundleAdjustment::EParameterState state) const - { - std::size_t nb = 0; - for(const auto& intrinsicStatePair : _statePerIntrinsicId) - if(intrinsicStatePair.second == state) - ++nb; - return nb; - } - - /** - * @brief Return the number of landmarks with the given BundleAdjustment::EParameterState. - * @param[in] state The given BundleAdjustment::EParameterState. - * @return number of landmarks with the given BundleAdjustment::EParameterState. - */ - inline std::size_t getNbLandmarksPerState(BundleAdjustment::EParameterState state) const - { - std::size_t nb = 0; - for(const auto& landmarkStatePair : _statePerLandmarkId) - if(landmarkStatePair.second == state) - ++nb; - return nb; - } - - /** - * @brief Get the graph-distance limit setting the Active region - * @return graph-distance limit setting - */ - inline std::size_t getGraphDistanceLimit() const - { - return _graphDistanceLimit; - } - - /** - * @brief Set the graph-distance limit setting the Active region - * @param[in] graph-distance limit setting - */ - inline void setGraphDistanceLimit(const std::size_t limit) - { - _graphDistanceLimit = limit; - } - - /** - * @brief Set every parameters of the BA problem to Refine: the Local BA becomes a classic BA. - * @param[in] sfmData contains all the information about the reconstruction - */ - void setAllParametersToRefine(const sfmData::SfMData& sfmData); - - /** - * @brief Save all the intrinsics to the memory to retain the evolution of each focal length during the reconstruction. - * @param[in] sfmData contains all the information about the reconstruction notably current focal lengths - */ - void saveIntrinsicsToHistory(const sfmData::SfMData& sfmData); - - /** - * @brief Export the history of each intrinsics. It create a file \a K_.csv in \c folder. - * @param [in] folder The folder where you want to save the intrinsic history file - * @param [in] filename The filename of the intrinsic history file - */ - void exportIntrinsicsHistory(const std::string& folder, const std::string& filename); - - /** - * @brief Remove specific views from the LocalBA graph. - * @details Delete the nodes corresponding to those views and all their incident edges. - * @param[in] sfmData contains all the information about the reconstruction - * @param[in] removedViewsId Set of views index to remove - * @return true if the number of removed node is equal to the size of \c removedViewsId - */ - bool removeViews(const sfmData::SfMData& sfmData, const std::set& removedViewsId); - - /** - * @brief Complete the graph with the newly resected views or all the posed views if the graph is empty. - * @param[in] sfmData contains all the information about the reconstruction - * @param[in] map_tracksPerView A map giving the tracks for each view - * @param[in] newReconstructedViews The list of the newly resected views - * @param[in] kMinNbOfMatches The min. number of shared matches to create an edge between two views (nodes) - */ - void updateGraphWithNewViews(const sfmData::SfMData& sfmData, - const track::TracksPerView& map_tracksPerView, - const std::set& newImageIndex, - const std::size_t kMinNbOfMatches = 50); - - /** - * @brief Compute the intragraph-distance between all the nodes of the graph (posed views) and the newly resected views. - * @details The graph-distances are computed using a Breadth-first Search (BFS) method. - * @param[in] sfmData contains all the information about the reconstruction, notably the posed views - * @param[in] newReconstructedViews The list of the newly resected views used (used as source in the BFS algorithm) - */ - void computeGraphDistances(const sfmData::SfMData& sfmData, const std::set& newReconstructedViews); - - /** - * @brief Use the graph-distances of each posed view to set each parameter of the problem (poses, intrinsics, landmarks): - * - as Refined (will be refined during the adjustment) - * - as Constant (will be set as constant in the adjustment) - * - as Ignored (won't be used during th adjustment). - * @details Here is the algorithm assignating a state at each parameter: - * \b D = the distance of the 'active' region (\c kLimitDistance) - * \b W = the range (in term of num. of poses) on which to study the focal length variations (kWindowSize) - * \b L = the max. percentage of the variations of the focal length to consider it as constant (kStdevPercentage) - * - a Pose is set to: - * - \a Refined <=> dist <= D - * - \a Constant <=> dist == D+1 - * - \a Ignored <=> else (dist = -1 U [D+2; +inf.[) - * - an Intrinsic is set to: - * - \a Refined by default - * - \a Constant <=> its focal lenght is considered as stable in its W last saved values according to all of its values. - * - a Landmarks is set to: - * - \a Ignored by default - * - \a Refined <=> its connected to a refined camera - * @param[in] sfmData contains all the information about the reconstruction - */ - void convertDistancesToStates(const sfmData::SfMData& sfmData); - - /** - * @brief Update rigs edges. - * @param[in] sfmData contains all the information about the reconstruction - * @return - */ - std::size_t updateRigEdgesToTheGraph(const sfmData::SfMData& sfmData); - - /** - * @brief Count and return the number of nodes in the underlying lemon graph. - * @return The number of nodes in the graph. - */ - unsigned int countNodes() const; - - /** - * @brief Count and return the number of edges in the underlying lemon graph. - * @return The number of edges in the graph. - */ - unsigned int countEdges() const; - -private: - - /** - * @brief Return the distance between a specific pose and the new posed views. - * @param[in] poseId is the index of the poseId - * @return Return \c -1 if the pose is not connected to any new posed view. - */ - int getPoseDistance(const IndexT poseId) const; - - /** - * @brief Return the distance between a specific view and the new posed views. - * @param[in] viewId is the index of the view - * @return Return \c -1 if the view is not connected to any new posed view. - */ - int getViewDistance(const IndexT viewId) const; - - /** - * @brief Return the state for a given distance - * @param[in] distance between two views - * @return BundleAdjustment::EParameterState - */ - BundleAdjustment::EParameterState getStateFromDistance(int distance) const; - - /** - * @brief Draw the current graph in the given directory. - * @details The file is name \a graph__. - * Node format: [: D K]. - * Node color: red (D=0), green (D=1), blue (D=2) or black (D>2 or D=-1) - * Edge color: black (classic) or red (due to the intrinsic edges) - * @param[in] sfmData contains all the information about the reconstruction - * @param[in] dir - * @param[in] nameComplement - */ - void drawGraph(const sfmData::SfMData& sfmData, const std::string& folder, const std::string& nameComplement = ""); - - /** - * @brief Compute, for each camera the variation of the last \a windowSize values of the focal length. - * If the focal lenght variations are considered as enought constant the function updates \a _mapFocalIsConstant. - * @details Pipeline: - * \b H: the history of all the focal length for a given intrinsic - * \b S: the subpart of H including the last \a wondowSize values only. - * \b sigma = stddev(S) - * \b sigma_normalized = sigma / (max(H) - min(H)) - * \c if sigma_normalized < \a stdevPercentageLimit \c then the limit is reached. - * @param[in] windowSize Compute the variation on the \a windowSize parameters - * @param[in] stdevPercentageLimit The limit is reached when the standard deviation of the \a windowSize values is less than \a stdevPecentageLimit % of the range of all the values. - */ - void checkFocalLengthsConsistency(const std::size_t windowSize, const double stdevPercentageLimit); - - /** - * @brief Count the number of shared landmarks between all the new views and each already resected cameras. - * @param[in] sfmData contains all the information about the reconstruction - * @param[in] map_tracksPerView A map giving the tracks for each view - * @param[in] newViewsId A set with the views index that we want to count matches with resected cameras. - * @return A map giving the number of matches for each images pair. - */ - static std::vector getNewEdges(const sfmData::SfMData& sfmData, - const track::TracksPerView& map_tracksPerView, - const std::set& newViewsId, - const std::size_t minNbOfMatches, - const std::size_t minNbOfEdgesPerView); - - /** - * @brief Return the state of the focal length (constant or not) for a specific intrinsic. - * @param intrinsicId To update the focal lengths states, use \c LocalBundleAdjustmentGraph::checkFocalLengthsConsistency() - * @return true if the focal length is considered as Constant - */ - inline bool isFocalLengthConstant(const IndexT intrinsicId) const - { - return _mapFocalIsConstant.at(intrinsicId); - } - - /** - * @brief Compute the standard deviation. - * @param[in] The vector of values - * @return The standard deviation - */ - template - static double standardDeviation(const std::vector& data); - - /** - * @brief Add an edge in the graph when 2 views share a same intrinsic not considered as Constant - * @param[in] sfmData contains all the information about the reconstruction - * @param[in] newReconstructedViews - * @return - */ - std::size_t addIntrinsicEdgesToTheGraph(const sfmData::SfMData& sfmData, const std::set& newReconstructedViews); - - /** - * @brief Remove all the edges added by the \c addIntrinsicEdgesToTheGraph function related to . - * @param[in] intrinsicId - */ - void removeIntrinsicEdgesFromTheGraph(IndexT intrinsicId); - - // Distances data - // - Local BA needs to know the distance of all the old posed views to the new resected views. - // - The bundle adjustment will be processed on the closest poses only. - - /// A graph where nodes are poses and an edge exists when 2 poses shared at least 'kMinNbOfMatches' matches. - lemon::ListGraph _graph; - /// The graph-distance limit setting the Active region (default value: 1) - std::size_t _graphDistanceLimit = 1; - /// Associates each view (indexed by its viewId) to its corresponding node in the graph. - std::map _nodePerViewId; - /// Associates each node (in the graph) to its corresponding view. - std::map _viewIdPerNode; - /// Store the graph-distances from the new views (0: is a new view, -1: is not connected to the new views) - std::map _distancePerViewId; - /// Store the graph-distances from the new poses (0: is a new pose, -1: is not connected to the new poses) - std::map _distancePerPoseId; - /// Store the \c EParameterState of each pose in the scene. - std::map _statePerPoseId; - /// Store the \c EParameterState of each intrinsic in the scene. - std::map _statePerIntrinsicId; - /// Store the \c EParameterState of each landmark in the scene. - std::map _statePerLandmarkId; - - // Intrinsics data - // - Local BA needs to know the evolution of all the intrinsics parameters. - // - When camera parameters are enought refined (no variation) they are set to constant in the BA. - - struct IntrinsicHistory - { - std::size_t nbPoses = 0; - double focalLength = 0.0; - bool isConstant = false; - }; - - /** - * @brief Save the progression for all the intrinsics parameters - * @details - * K1: - * 0 1200 - * 1 1250 - * ... - * K2: - * ... - */ - using IntrinsicsHistory = std::map>; - - /// Backup of the intrinsics focal length values - IntrinsicsHistory _intrinsicsHistory; - - /** - * @brief Indicates, for each intrinsic, if its focallength has been concidered as constant. - * - */ - std::map _mapFocalIsConstant; - - /** - * @brief Store the Lemon index of the edges added for the intrinsic links "the intrinsic-edges" - * - */ - std::map> _intrinsicEdgesId; - - /** - * @brief Store the Lemon index of the edges added for the rig links "the intrinsic-edges" - * - */ - std::map> _rigEdgesId; + public: + explicit LocalBundleAdjustmentGraph(const sfmData::SfMData& sfmData); + + /** + * @brief Return the number of posed views for each graph-distance + * @return map + */ + std::map getDistancesHistogram() const; + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific pose. + * @param[in] poseId The given pose Id + * @return BundleAdjustment::EParameterState + */ + inline BundleAdjustment::EParameterState getPoseState(const IndexT poseId) const { return _statePerPoseId.at(poseId); } + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific intrinsic. + * @param[in] intrinsicId The given intrinsic Id + * @return BundleAdjustment::EParameterState + */ + inline BundleAdjustment::EParameterState getIntrinsicState(const IndexT intrinsicId) const { return _statePerIntrinsicId.at(intrinsicId); } + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific landmark. + * @param[in] landmarkId The given landmark Id + * @return BundleAdjustment::EParameterState + */ + inline BundleAdjustment::EParameterState getLandmarkState(const IndexT landmarkId) const { return _statePerLandmarkId.at(landmarkId); } + + /** + * @brief Return the number of poses with the given BundleAdjustment::EParameterState. + * @param[in] state The given BundleAdjustment::EParameterState. + * @return number of poses with the given BundleAdjustment::EParameterState. + */ + inline std::size_t getNbPosesPerState(BundleAdjustment::EParameterState state) const + { + std::size_t nb = 0; + for (const auto& poseStatePair : _statePerPoseId) + if (poseStatePair.second == state) + ++nb; + return nb; + } + + /** + * @brief Return the number of intrinsics with the given BundleAdjustment::EParameterState. + * @param[in] state The given BundleAdjustment::EParameterState. + * @return number of intrinsics with the given BundleAdjustment::EParameterState. + */ + inline std::size_t getNbIntrinsicsPerState(BundleAdjustment::EParameterState state) const + { + std::size_t nb = 0; + for (const auto& intrinsicStatePair : _statePerIntrinsicId) + if (intrinsicStatePair.second == state) + ++nb; + return nb; + } + + /** + * @brief Return the number of landmarks with the given BundleAdjustment::EParameterState. + * @param[in] state The given BundleAdjustment::EParameterState. + * @return number of landmarks with the given BundleAdjustment::EParameterState. + */ + inline std::size_t getNbLandmarksPerState(BundleAdjustment::EParameterState state) const + { + std::size_t nb = 0; + for (const auto& landmarkStatePair : _statePerLandmarkId) + if (landmarkStatePair.second == state) + ++nb; + return nb; + } + + /** + * @brief Get the graph-distance limit setting the Active region + * @return graph-distance limit setting + */ + inline std::size_t getGraphDistanceLimit() const { return _graphDistanceLimit; } + + /** + * @brief Set the graph-distance limit setting the Active region + * @param[in] graph-distance limit setting + */ + inline void setGraphDistanceLimit(const std::size_t limit) { _graphDistanceLimit = limit; } + + /** + * @brief Set every parameters of the BA problem to Refine: the Local BA becomes a classic BA. + * @param[in] sfmData contains all the information about the reconstruction + */ + void setAllParametersToRefine(const sfmData::SfMData& sfmData); + + /** + * @brief Save all the intrinsics to the memory to retain the evolution of each focal length during the reconstruction. + * @param[in] sfmData contains all the information about the reconstruction notably current focal lengths + */ + void saveIntrinsicsToHistory(const sfmData::SfMData& sfmData); + + /** + * @brief Export the history of each intrinsics. It create a file \a K_.csv in \c folder. + * @param [in] folder The folder where you want to save the intrinsic history file + * @param [in] filename The filename of the intrinsic history file + */ + void exportIntrinsicsHistory(const std::string& folder, const std::string& filename); + + /** + * @brief Remove specific views from the LocalBA graph. + * @details Delete the nodes corresponding to those views and all their incident edges. + * @param[in] sfmData contains all the information about the reconstruction + * @param[in] removedViewsId Set of views index to remove + * @return true if the number of removed node is equal to the size of \c removedViewsId + */ + bool removeViews(const sfmData::SfMData& sfmData, const std::set& removedViewsId); + + /** + * @brief Complete the graph with the newly resected views or all the posed views if the graph is empty. + * @param[in] sfmData contains all the information about the reconstruction + * @param[in] map_tracksPerView A map giving the tracks for each view + * @param[in] newReconstructedViews The list of the newly resected views + * @param[in] kMinNbOfMatches The min. number of shared matches to create an edge between two views (nodes) + */ + void updateGraphWithNewViews(const sfmData::SfMData& sfmData, + const track::TracksPerView& map_tracksPerView, + const std::set& newImageIndex, + const std::size_t kMinNbOfMatches = 50); + + /** + * @brief Compute the intragraph-distance between all the nodes of the graph (posed views) and the newly resected views. + * @details The graph-distances are computed using a Breadth-first Search (BFS) method. + * @param[in] sfmData contains all the information about the reconstruction, notably the posed views + * @param[in] newReconstructedViews The list of the newly resected views used (used as source in the BFS algorithm) + */ + void computeGraphDistances(const sfmData::SfMData& sfmData, const std::set& newReconstructedViews); + + /** + * @brief Use the graph-distances of each posed view to set each parameter of the problem (poses, intrinsics, landmarks): + * - as Refined (will be refined during the adjustment) + * - as Constant (will be set as constant in the adjustment) + * - as Ignored (won't be used during th adjustment). + * @details Here is the algorithm assignating a state at each parameter: + * \b D = the distance of the 'active' region (\c kLimitDistance) + * \b W = the range (in term of num. of poses) on which to study the focal length variations (kWindowSize) + * \b L = the max. percentage of the variations of the focal length to consider it as constant (kStdevPercentage) + * - a Pose is set to: + * - \a Refined <=> dist <= D + * - \a Constant <=> dist == D+1 + * - \a Ignored <=> else (dist = -1 U [D+2; +inf.[) + * - an Intrinsic is set to: + * - \a Refined by default + * - \a Constant <=> its focal lenght is considered as stable in its W last saved values according to all of its values. + * - a Landmarks is set to: + * - \a Ignored by default + * - \a Refined <=> its connected to a refined camera + * @param[in] sfmData contains all the information about the reconstruction + */ + void convertDistancesToStates(const sfmData::SfMData& sfmData); + + /** + * @brief Update rigs edges. + * @param[in] sfmData contains all the information about the reconstruction + * @return + */ + std::size_t updateRigEdgesToTheGraph(const sfmData::SfMData& sfmData); + + /** + * @brief Count and return the number of nodes in the underlying lemon graph. + * @return The number of nodes in the graph. + */ + unsigned int countNodes() const; + + /** + * @brief Count and return the number of edges in the underlying lemon graph. + * @return The number of edges in the graph. + */ + unsigned int countEdges() const; + + private: + /** + * @brief Return the distance between a specific pose and the new posed views. + * @param[in] poseId is the index of the poseId + * @return Return \c -1 if the pose is not connected to any new posed view. + */ + int getPoseDistance(const IndexT poseId) const; + + /** + * @brief Return the distance between a specific view and the new posed views. + * @param[in] viewId is the index of the view + * @return Return \c -1 if the view is not connected to any new posed view. + */ + int getViewDistance(const IndexT viewId) const; + + /** + * @brief Return the state for a given distance + * @param[in] distance between two views + * @return BundleAdjustment::EParameterState + */ + BundleAdjustment::EParameterState getStateFromDistance(int distance) const; + + /** + * @brief Draw the current graph in the given directory. + * @details The file is name \a graph__. + * Node format: [: D K]. + * Node color: red (D=0), green (D=1), blue (D=2) or black (D>2 or D=-1) + * Edge color: black (classic) or red (due to the intrinsic edges) + * @param[in] sfmData contains all the information about the reconstruction + * @param[in] dir + * @param[in] nameComplement + */ + void drawGraph(const sfmData::SfMData& sfmData, const std::string& folder, const std::string& nameComplement = ""); + + /** + * @brief Compute, for each camera the variation of the last \a windowSize values of the focal length. + * If the focal lenght variations are considered as enought constant the function updates \a _mapFocalIsConstant. + * @details Pipeline: + * \b H: the history of all the focal length for a given intrinsic + * \b S: the subpart of H including the last \a wondowSize values only. + * \b sigma = stddev(S) + * \b sigma_normalized = sigma / (max(H) - min(H)) + * \c if sigma_normalized < \a stdevPercentageLimit \c then the limit is reached. + * @param[in] windowSize Compute the variation on the \a windowSize parameters + * @param[in] stdevPercentageLimit The limit is reached when the standard deviation of the \a windowSize values is less than \a + * stdevPecentageLimit % of the range of all the values. + */ + void checkFocalLengthsConsistency(const std::size_t windowSize, const double stdevPercentageLimit); + + /** + * @brief Count the number of shared landmarks between all the new views and each already resected cameras. + * @param[in] sfmData contains all the information about the reconstruction + * @param[in] map_tracksPerView A map giving the tracks for each view + * @param[in] newViewsId A set with the views index that we want to count matches with resected cameras. + * @return A map giving the number of matches for each images pair. + */ + static std::vector getNewEdges(const sfmData::SfMData& sfmData, + const track::TracksPerView& map_tracksPerView, + const std::set& newViewsId, + const std::size_t minNbOfMatches, + const std::size_t minNbOfEdgesPerView); + + /** + * @brief Return the state of the focal length (constant or not) for a specific intrinsic. + * @param intrinsicId To update the focal lengths states, use \c LocalBundleAdjustmentGraph::checkFocalLengthsConsistency() + * @return true if the focal length is considered as Constant + */ + inline bool isFocalLengthConstant(const IndexT intrinsicId) const { return _mapFocalIsConstant.at(intrinsicId); } + + /** + * @brief Compute the standard deviation. + * @param[in] The vector of values + * @return The standard deviation + */ + template + static double standardDeviation(const std::vector& data); + + /** + * @brief Add an edge in the graph when 2 views share a same intrinsic not considered as Constant + * @param[in] sfmData contains all the information about the reconstruction + * @param[in] newReconstructedViews + * @return + */ + std::size_t addIntrinsicEdgesToTheGraph(const sfmData::SfMData& sfmData, const std::set& newReconstructedViews); + + /** + * @brief Remove all the edges added by the \c addIntrinsicEdgesToTheGraph function related to . + * @param[in] intrinsicId + */ + void removeIntrinsicEdgesFromTheGraph(IndexT intrinsicId); + + // Distances data + // - Local BA needs to know the distance of all the old posed views to the new resected views. + // - The bundle adjustment will be processed on the closest poses only. + + /// A graph where nodes are poses and an edge exists when 2 poses shared at least 'kMinNbOfMatches' matches. + lemon::ListGraph _graph; + /// The graph-distance limit setting the Active region (default value: 1) + std::size_t _graphDistanceLimit = 1; + /// Associates each view (indexed by its viewId) to its corresponding node in the graph. + std::map _nodePerViewId; + /// Associates each node (in the graph) to its corresponding view. + std::map _viewIdPerNode; + /// Store the graph-distances from the new views (0: is a new view, -1: is not connected to the new views) + std::map _distancePerViewId; + /// Store the graph-distances from the new poses (0: is a new pose, -1: is not connected to the new poses) + std::map _distancePerPoseId; + /// Store the \c EParameterState of each pose in the scene. + std::map _statePerPoseId; + /// Store the \c EParameterState of each intrinsic in the scene. + std::map _statePerIntrinsicId; + /// Store the \c EParameterState of each landmark in the scene. + std::map _statePerLandmarkId; + + // Intrinsics data + // - Local BA needs to know the evolution of all the intrinsics parameters. + // - When camera parameters are enought refined (no variation) they are set to constant in the BA. + + struct IntrinsicHistory + { + std::size_t nbPoses = 0; + double focalLength = 0.0; + bool isConstant = false; + }; + + /** + * @brief Save the progression for all the intrinsics parameters + * @details + * K1: + * 0 1200 + * 1 1250 + * ... + * K2: + * ... + */ + using IntrinsicsHistory = std::map>; + + /// Backup of the intrinsics focal length values + IntrinsicsHistory _intrinsicsHistory; + + /** + * @brief Indicates, for each intrinsic, if its focallength has been concidered as constant. + * + */ + std::map _mapFocalIsConstant; + + /** + * @brief Store the Lemon index of the edges added for the intrinsic links "the intrinsic-edges" + * + */ + std::map> _intrinsicEdgesId; + + /** + * @brief Store the Lemon index of the edges added for the rig links "the intrinsic-edges" + * + */ + std::map> _rigEdgesId; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/ResidualErrorConstraintFunctor.hpp b/src/aliceVision/sfm/ResidualErrorConstraintFunctor.hpp index 240f1ce2bb..a3b32b2be7 100644 --- a/src/aliceVision/sfm/ResidualErrorConstraintFunctor.hpp +++ b/src/aliceVision/sfm/ResidualErrorConstraintFunctor.hpp @@ -4,19 +4,19 @@ #include - - namespace aliceVision { namespace sfm { -template -double getJetValue(const T & val) { - return val.a; +template +double getJetValue(const T& val) +{ + return val.a; } -template <> -double getJetValue(const double & val) { - return val; +template<> +double getJetValue(const double& val) +{ + return val; } /** @@ -25,92 +25,89 @@ double getJetValue(const double & val) { * Data parameter blocks are the following <2,3,6,6> * - 2 => dimension of the residuals, * - 3 => the intrinsic data block for the first view [focal, principal point x, principal point y], - * - 3 => the camera extrinsic data block for the first view - * - 3 => the camera extrinsic data block for the second view + * - 3 => the camera extrinsic data block for the first view + * - 3 => the camera extrinsic data block for the second view * */ struct ResidualErrorConstraintFunctor_Pinhole { - ResidualErrorConstraintFunctor_Pinhole(int w, int h, const Vec3 & pos_2dpoint_first, const Vec3 & pos_2dpoint_second) - : m_center(double(w) * 0.5, double(h) * 0.5), m_pos_2dpoint_first(pos_2dpoint_first), m_pos_2dpoint_second(pos_2dpoint_second) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH = 0, - OFFSET_PRINCIPAL_POINT_X = 1, - OFFSET_PRINCIPAL_POINT_Y = 2 - }; - - template - void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - - - out(0) = (pt(0) - principal_point_x) / focal; - out(1) = (pt(1) - principal_point_y) / focal; - out(2) = static_cast(1.0); - } - - template - void unlift(const T* const cam_K, const Eigen::Matrix< T, 3, 1> & pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - - Eigen::Matrix< T, 3, 1> proj_pt = pt / pt(2); - - out(0) = proj_pt(0) * focal + principal_point_x; - out(1) = proj_pt(1) * focal + principal_point_y; - out(2) = static_cast(1.0); - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_R1, - const T* const cam_R2, - T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone; - - Eigen::Matrix< T, 3, 1> pt3d_1; - - lift(cam_K, m_pos_2dpoint_first, pt3d_1); - - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - - twoRone = twoRo * oneRo.transpose(); - - Eigen::Matrix< T, 3, 1> pt3d_2_est = twoRone * pt3d_1; - - Eigen::Matrix< T, 3, 1> pt2d_2_est; - unlift(cam_K, pt3d_2_est, pt2d_2_est); - - Eigen::Matrix< T, 3, 1> residual = pt2d_2_est - m_pos_2dpoint_second; - - out_residuals[0] = residual(0); - out_residuals[1] = residual(1); - - return true; - } - - Vec3 m_pos_2dpoint_first; // The 2D observation in first view - Vec3 m_pos_2dpoint_second; // The 2D observation in second view - Vec2 m_center; // Image center + ResidualErrorConstraintFunctor_Pinhole(int w, int h, const Vec3& pos_2dpoint_first, const Vec3& pos_2dpoint_second) + : m_center(double(w) * 0.5, double(h) * 0.5), + m_pos_2dpoint_first(pos_2dpoint_first), + m_pos_2dpoint_second(pos_2dpoint_second) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH = 0, + OFFSET_PRINCIPAL_POINT_X = 1, + OFFSET_PRINCIPAL_POINT_Y = 2 + }; + + template + void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + + out(0) = (pt(0) - principal_point_x) / focal; + out(1) = (pt(1) - principal_point_y) / focal; + out(2) = static_cast(1.0); + } + + template + void unlift(const T* const cam_K, const Eigen::Matrix& pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + + Eigen::Matrix proj_pt = pt / pt(2); + + out(0) = proj_pt(0) * focal + principal_point_x; + out(1) = proj_pt(1) * focal + principal_point_y; + out(2) = static_cast(1.0); + } + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone; + + Eigen::Matrix pt3d_1; + + lift(cam_K, m_pos_2dpoint_first, pt3d_1); + + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + + twoRone = twoRo * oneRo.transpose(); + + Eigen::Matrix pt3d_2_est = twoRone * pt3d_1; + + Eigen::Matrix pt2d_2_est; + unlift(cam_K, pt3d_2_est, pt2d_2_est); + + Eigen::Matrix residual = pt2d_2_est - m_pos_2dpoint_second; + + out_residuals[0] = residual(0); + out_residuals[1] = residual(1); + + return true; + } + + Vec3 m_pos_2dpoint_first; // The 2D observation in first view + Vec3 m_pos_2dpoint_second; // The 2D observation in second view + Vec2 m_center; // Image center }; /** @@ -119,135 +116,136 @@ struct ResidualErrorConstraintFunctor_Pinhole * Data parameter blocks are the following <2,4,6,6> * - 2 => dimension of the residuals, * - 4 => the intrinsic data block for the first view [focal, principal point x, principal point y], - * - 3 => the camera extrinsic data block for the first view - * - 3 => the camera extrinsic data block for the second view + * - 3 => the camera extrinsic data block for the first view + * - 3 => the camera extrinsic data block for the second view * */ struct ResidualErrorConstraintFunctor_PinholeRadialK1 { - ResidualErrorConstraintFunctor_PinholeRadialK1(int w, int h, const Vec3 & pos_2dpoint_first, const Vec3 & pos_2dpoint_second) - : m_center(double(w) * 0.5, double(h) * 0.5), m_pos_2dpoint_first(pos_2dpoint_first), m_pos_2dpoint_second(pos_2dpoint_second) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH = 0, - OFFSET_PRINCIPAL_POINT_X = 1, - OFFSET_PRINCIPAL_POINT_Y = 2, - OFFSET_DISTO_K1 = 3 - }; - - static double distoFunctor(const std::vector & params, double r2) - { - const double k1 = params[0]; - return r2 * Square(1.+r2*k1); - } - - template - void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - - //Unshift then unscale back to meters - T xd = (pt(0) - principal_point_x) / focal; - T yd = (pt(1) - principal_point_y) / focal; - T distorted_radius = sqrt(xd*xd + yd*yd); - - /*A hack to obtain undistorted point even if using automatic diff*/ - double xd_real, yd_real, k1_real; - xd_real = getJetValue(xd); - yd_real = getJetValue(yd); - k1_real = getJetValue(k1); - - /*get rescaler*/ - std::vector distortionParams = {k1_real}; - double distorted_radius_square = xd_real * xd_real + yd_real * yd_real; - double rescaler = ::sqrt(camera::radial_distortion::bisection_Radius_Solve(distortionParams, distorted_radius_square, distoFunctor));; - - if (distorted_radius < 1e-12) { - out(0) = xd; - out(1) = yd; - out(2) = static_cast(1.0); + ResidualErrorConstraintFunctor_PinholeRadialK1(int w, int h, const Vec3& pos_2dpoint_first, const Vec3& pos_2dpoint_second) + : m_center(double(w) * 0.5, double(h) * 0.5), + m_pos_2dpoint_first(pos_2dpoint_first), + m_pos_2dpoint_second(pos_2dpoint_second) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH = 0, + OFFSET_PRINCIPAL_POINT_X = 1, + OFFSET_PRINCIPAL_POINT_Y = 2, + OFFSET_DISTO_K1 = 3 + }; + + static double distoFunctor(const std::vector& params, double r2) + { + const double k1 = params[0]; + return r2 * Square(1. + r2 * k1); + } + + template + void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + + // Unshift then unscale back to meters + T xd = (pt(0) - principal_point_x) / focal; + T yd = (pt(1) - principal_point_y) / focal; + T distorted_radius = sqrt(xd * xd + yd * yd); + + /*A hack to obtain undistorted point even if using automatic diff*/ + double xd_real, yd_real, k1_real; + xd_real = getJetValue(xd); + yd_real = getJetValue(yd); + k1_real = getJetValue(k1); + + /*get rescaler*/ + std::vector distortionParams = {k1_real}; + double distorted_radius_square = xd_real * xd_real + yd_real * yd_real; + double rescaler = ::sqrt(camera::radial_distortion::bisection_Radius_Solve(distortionParams, distorted_radius_square, distoFunctor)); + ; + + if (distorted_radius < 1e-12) + { + out(0) = xd; + out(1) = yd; + out(2) = static_cast(1.0); + } + else + { + out(0) = (xd / distorted_radius) * static_cast(rescaler); + out(1) = (yd / distorted_radius) * static_cast(rescaler); + out(2) = static_cast(1.0); + } } - else { - out(0) = (xd / distorted_radius) * static_cast(rescaler); - out(1) = (yd / distorted_radius) * static_cast(rescaler); - out(2) = static_cast(1.0); + + template + void unlift(const T* const cam_K, const Eigen::Matrix& pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + + // Project on plane + Eigen::Matrix proj_pt = pt / pt(2); + + // Apply distortion + const T r2 = proj_pt(0) * proj_pt(0) + proj_pt(1) * proj_pt(1); + const T r_coeff = (T(1) + k1 * r2); + const T x_d = proj_pt(0) * r_coeff; + const T y_d = proj_pt(1) * r_coeff; + + // Scale and shift + out(0) = x_d * focal + principal_point_x; + out(1) = y_d * focal + principal_point_y; + out(2) = static_cast(1.0); } - } - - template - void unlift(const T* const cam_K, const Eigen::Matrix< T, 3, 1> & pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - - //Project on plane - Eigen::Matrix< T, 3, 1> proj_pt = pt / pt(2); - - //Apply distortion - const T r2 = proj_pt(0)*proj_pt(0) + proj_pt(1)*proj_pt(1); - const T r_coeff = (T(1) + k1*r2); - const T x_d = proj_pt(0) * r_coeff; - const T y_d = proj_pt(1) * r_coeff; - - //Scale and shift - out(0) = x_d * focal + principal_point_x; - out(1) = y_d * focal + principal_point_y; - out(2) = static_cast(1.0); - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_R1, - const T* const cam_R2, - T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone; - - Eigen::Matrix pt3d_1; - - //From pixel to meters - lift(cam_K, m_pos_2dpoint_first, pt3d_1); - - //Build relative rotation - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - twoRone = twoRo * oneRo.transpose(); - - //Transform point - Eigen::Matrix< T, 3, 1> pt3d_2_est = twoRone * pt3d_1; - - //Project back to image space in pixels - Eigen::Matrix< T, 3, 1> pt2d_2_est; - unlift(cam_K, pt3d_2_est, pt2d_2_est); - - //Compute residual - Eigen::Matrix< T, 3, 1> residual = pt2d_2_est - m_pos_2dpoint_second; - - out_residuals[0] = residual(0); - out_residuals[1] = residual(1); - - return true; - } - - Vec3 m_pos_2dpoint_first; // The 2D observation in first view - Vec3 m_pos_2dpoint_second; // The 2D observation in second view - Vec2 m_center; // Image center + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone; + + Eigen::Matrix pt3d_1; + + // From pixel to meters + lift(cam_K, m_pos_2dpoint_first, pt3d_1); + + // Build relative rotation + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + twoRone = twoRo * oneRo.transpose(); + + // Transform point + Eigen::Matrix pt3d_2_est = twoRone * pt3d_1; + + // Project back to image space in pixels + Eigen::Matrix pt2d_2_est; + unlift(cam_K, pt3d_2_est, pt2d_2_est); + + // Compute residual + Eigen::Matrix residual = pt2d_2_est - m_pos_2dpoint_second; + + out_residuals[0] = residual(0); + out_residuals[1] = residual(1); + + return true; + } + + Vec3 m_pos_2dpoint_first; // The 2D observation in first view + Vec3 m_pos_2dpoint_second; // The 2D observation in second view + Vec2 m_center; // Image center }; /** @@ -256,149 +254,149 @@ struct ResidualErrorConstraintFunctor_PinholeRadialK1 * Data parameter blocks are the following <2,6,6,6> * - 2 => dimension of the residuals, * - 4 => the intrinsic data block for the first view [focal, principal point x, principal point y, K1, K2, K3], - * - 3 => the camera extrinsic data block for the first view - * - 3 => the camera extrinsic data block for the second view + * - 3 => the camera extrinsic data block for the first view + * - 3 => the camera extrinsic data block for the second view * */ struct ResidualErrorConstraintFunctor_PinholeRadialK3 { - ResidualErrorConstraintFunctor_PinholeRadialK3(int w, int h, const Vec3 & pos_2dpoint_first, const Vec3 & pos_2dpoint_second) - : m_center(double(w) * 0.5, double(h) * 0.5), m_pos_2dpoint_first(pos_2dpoint_first), m_pos_2dpoint_second(pos_2dpoint_second) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH = 0, - OFFSET_PRINCIPAL_POINT_X = 1, - OFFSET_PRINCIPAL_POINT_Y = 2, - OFFSET_DISTO_K1 = 3, - OFFSET_DISTO_K2 = 4, - OFFSET_DISTO_K3 = 5 - }; - - static double distoFunctor(const std::vector & params, double r2) - { - const double k1 = params[0]; - const double k2 = params[1]; - const double k3 = params[2]; - return r2 * Square(1.+r2*(k1+r2*(k2+r2*k3))); - } - - template - void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - - //Unshift then unscale back to meters - T xd = (pt(0) - principal_point_x) / focal; - T yd = (pt(1) - principal_point_y) / focal; - T distorted_radius = sqrt(xd*xd + yd*yd); - - /*A hack to obtain undistorted point even if using automatic diff*/ - double xd_real, yd_real, k1_real, k2_real, k3_real; - xd_real = getJetValue(xd); - yd_real = getJetValue(yd); - k1_real = getJetValue(k1); - k2_real = getJetValue(k2); - k3_real = getJetValue(k3); - - /*get rescaler*/ - std::vector distortionParams = {k1_real, k2_real, k3_real}; - double distorted_radius_square = xd_real * xd_real + yd_real * yd_real; - double rescaler = ::sqrt(camera::radial_distortion::bisection_Radius_Solve(distortionParams, distorted_radius_square, distoFunctor));; - - if (distorted_radius < 1e-12) { - out(0) = xd; - out(1) = yd; - out(2) = static_cast(1.0); + ResidualErrorConstraintFunctor_PinholeRadialK3(int w, int h, const Vec3& pos_2dpoint_first, const Vec3& pos_2dpoint_second) + : m_center(double(w) * 0.5, double(h) * 0.5), + m_pos_2dpoint_first(pos_2dpoint_first), + m_pos_2dpoint_second(pos_2dpoint_second) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH = 0, + OFFSET_PRINCIPAL_POINT_X = 1, + OFFSET_PRINCIPAL_POINT_Y = 2, + OFFSET_DISTO_K1 = 3, + OFFSET_DISTO_K2 = 4, + OFFSET_DISTO_K3 = 5 + }; + + static double distoFunctor(const std::vector& params, double r2) + { + const double k1 = params[0]; + const double k2 = params[1]; + const double k3 = params[2]; + return r2 * Square(1. + r2 * (k1 + r2 * (k2 + r2 * k3))); } - else { - out(0) = (xd / distorted_radius) * static_cast(rescaler); - out(1) = (yd / distorted_radius) * static_cast(rescaler); - out(2) = static_cast(1.0); + + template + void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + + // Unshift then unscale back to meters + T xd = (pt(0) - principal_point_x) / focal; + T yd = (pt(1) - principal_point_y) / focal; + T distorted_radius = sqrt(xd * xd + yd * yd); + + /*A hack to obtain undistorted point even if using automatic diff*/ + double xd_real, yd_real, k1_real, k2_real, k3_real; + xd_real = getJetValue(xd); + yd_real = getJetValue(yd); + k1_real = getJetValue(k1); + k2_real = getJetValue(k2); + k3_real = getJetValue(k3); + + /*get rescaler*/ + std::vector distortionParams = {k1_real, k2_real, k3_real}; + double distorted_radius_square = xd_real * xd_real + yd_real * yd_real; + double rescaler = ::sqrt(camera::radial_distortion::bisection_Radius_Solve(distortionParams, distorted_radius_square, distoFunctor)); + ; + + if (distorted_radius < 1e-12) + { + out(0) = xd; + out(1) = yd; + out(2) = static_cast(1.0); + } + else + { + out(0) = (xd / distorted_radius) * static_cast(rescaler); + out(1) = (yd / distorted_radius) * static_cast(rescaler); + out(2) = static_cast(1.0); + } + } + + template + void unlift(const T* const cam_K, const Eigen::Matrix& pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + + // Project on plane + Eigen::Matrix proj_pt = pt / pt(2); + + // Apply distortion + const T r2 = proj_pt(0) * proj_pt(0) + proj_pt(1) * proj_pt(1); + const T r4 = r2 * r2; + const T r6 = r4 * r2; + const T r_coeff = (T(1) + k1 * r2 + k2 * r4 + k3 * r6); + const T x_d = proj_pt(0) * r_coeff; + const T y_d = proj_pt(1) * r_coeff; + + // Scale and shift + out(0) = x_d * focal + principal_point_x; + out(1) = y_d * focal + principal_point_y; + out(2) = static_cast(1.0); + } + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone; + + Eigen::Matrix pt3d_1; + + // From pixel to meters + lift(cam_K, m_pos_2dpoint_first, pt3d_1); + + // Build relative rotation + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + twoRone = twoRo * oneRo.transpose(); + + // Transform point + Eigen::Matrix pt3d_2_est = twoRone * pt3d_1; + + // Project back to image space in pixels + Eigen::Matrix pt2d_2_est; + unlift(cam_K, pt3d_2_est, pt2d_2_est); + + // Compute residual + Eigen::Matrix residual = pt2d_2_est - m_pos_2dpoint_second; + + out_residuals[0] = residual(0); + out_residuals[1] = residual(1); + + return true; } - } - - template - void unlift(const T* const cam_K, const Eigen::Matrix< T, 3, 1> & pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - - //Project on plane - Eigen::Matrix< T, 3, 1> proj_pt = pt / pt(2); - - //Apply distortion - const T r2 = proj_pt(0)*proj_pt(0) + proj_pt(1)*proj_pt(1); - const T r4 = r2 * r2; - const T r6 = r4 * r2; - const T r_coeff = (T(1) + k1*r2 + k2*r4 + k3*r6); - const T x_d = proj_pt(0) * r_coeff; - const T y_d = proj_pt(1) * r_coeff; - - //Scale and shift - out(0) = x_d * focal + principal_point_x; - out(1) = y_d * focal + principal_point_y; - out(2) = static_cast(1.0); - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_R1, - const T* const cam_R2, - T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone; - - Eigen::Matrix pt3d_1; - - //From pixel to meters - lift(cam_K, m_pos_2dpoint_first, pt3d_1); - - //Build relative rotation - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - twoRone = twoRo * oneRo.transpose(); - - //Transform point - Eigen::Matrix< T, 3, 1> pt3d_2_est = twoRone * pt3d_1; - - //Project back to image space in pixels - Eigen::Matrix< T, 3, 1> pt2d_2_est; - unlift(cam_K, pt3d_2_est, pt2d_2_est); - - //Compute residual - Eigen::Matrix< T, 3, 1> residual = pt2d_2_est - m_pos_2dpoint_second; - - out_residuals[0] = residual(0); - out_residuals[1] = residual(1); - - return true; - } - - Vec3 m_pos_2dpoint_first; // The 2D observation in first view - Vec3 m_pos_2dpoint_second; // The 2D observation in second view - Vec2 m_center; // Image center -}; + Vec3 m_pos_2dpoint_first; // The 2D observation in first view + Vec3 m_pos_2dpoint_second; // The 2D observation in second view + Vec2 m_center; // Image center +}; /** * @brief Ceres functor to use a pair of fisheye on a pure rotation 2D constraint. @@ -406,175 +404,176 @@ struct ResidualErrorConstraintFunctor_PinholeRadialK3 * Data parameter blocks are the following <2,7,6,6> * - 2 => dimension of the residuals, * - 4 => the intrinsic data block for the first view , - * - 3 => the camera extrinsic data block for the first view - * - 3 => the camera extrinsic data block for the second view + * - 3 => the camera extrinsic data block for the first view + * - 3 => the camera extrinsic data block for the second view * */ struct ResidualErrorConstraintFunctor_PinholeFisheye { - ResidualErrorConstraintFunctor_PinholeFisheye(int w, int h, const Vec3 & pos_2dpoint_first, const Vec3 & pos_2dpoint_second) - : m_center(double(w) * 0.5, double(h) * 0.5), m_pos_2dpoint_first(pos_2dpoint_first), m_pos_2dpoint_second(pos_2dpoint_second) - { - } - - enum { - OFFSET_FOCAL_LENGTH = 0, - OFFSET_PRINCIPAL_POINT_X = 1, - OFFSET_PRINCIPAL_POINT_Y = 2, - OFFSET_DISTO_K1 = 3, - OFFSET_DISTO_K2 = 4, - OFFSET_DISTO_K3 = 5, - OFFSET_DISTO_K4 = 6, - }; - - template - void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - const T& k4 = cam_K[OFFSET_DISTO_K4]; - - //Unshift then unscale back to meters - T xd = (pt(0) - principal_point_x) / focal; - T yd = (pt(1) - principal_point_y) / focal; - T distorted_radius = sqrt(xd*xd + yd*yd); - - /*A hack to obtain undistorted point even if using automatic diff*/ - double xd_real, yd_real, k1_real, k2_real, k3_real, k4_real; - xd_real = getJetValue(xd); - yd_real = getJetValue(yd); - k1_real = getJetValue(k1); - k2_real = getJetValue(k2); - k3_real = getJetValue(k3); - k4_real = getJetValue(k4); - - double scale = 1.0; + ResidualErrorConstraintFunctor_PinholeFisheye(int w, int h, const Vec3& pos_2dpoint_first, const Vec3& pos_2dpoint_second) + : m_center(double(w) * 0.5, double(h) * 0.5), + m_pos_2dpoint_first(pos_2dpoint_first), + m_pos_2dpoint_second(pos_2dpoint_second) + {} + + enum + { + OFFSET_FOCAL_LENGTH = 0, + OFFSET_PRINCIPAL_POINT_X = 1, + OFFSET_PRINCIPAL_POINT_Y = 2, + OFFSET_DISTO_K1 = 3, + OFFSET_DISTO_K2 = 4, + OFFSET_DISTO_K3 = 5, + OFFSET_DISTO_K4 = 6, + }; + + template + void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix& out) const { - const double eps = 1e-8; - const double theta_dist = sqrt(xd_real * xd_real + yd_real * yd_real); - if (theta_dist > eps) - { - double theta = theta_dist; - for (int j = 0; j < 10; ++j) + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + const T& k4 = cam_K[OFFSET_DISTO_K4]; + + // Unshift then unscale back to meters + T xd = (pt(0) - principal_point_x) / focal; + T yd = (pt(1) - principal_point_y) / focal; + T distorted_radius = sqrt(xd * xd + yd * yd); + + /*A hack to obtain undistorted point even if using automatic diff*/ + double xd_real, yd_real, k1_real, k2_real, k3_real, k4_real; + xd_real = getJetValue(xd); + yd_real = getJetValue(yd); + k1_real = getJetValue(k1); + k2_real = getJetValue(k2); + k3_real = getJetValue(k3); + k4_real = getJetValue(k4); + + double scale = 1.0; + { + const double eps = 1e-8; + const double theta_dist = sqrt(xd_real * xd_real + yd_real * yd_real); + if (theta_dist > eps) + { + double theta = theta_dist; + for (int j = 0; j < 10; ++j) + { + const double theta2 = theta * theta; + const double theta4 = theta2 * theta2; + const double theta6 = theta4 * theta2; + const double theta8 = theta6 * theta2; + + double theta_fix = (theta * (1.0 + k1_real * theta2 + k2_real * theta4 + k3_real * theta6 + k4_real * theta8) - theta_dist) / + (1.0 + 3.0 * k1_real * theta2 + 5.0 * k2_real * theta4 + 7.0 * k3_real * theta6 + 9.0 * k4_real * theta8); + + theta = theta - theta_fix; + } + + scale = std::tan(theta); + } + } + + if (distorted_radius < 1e-12) + { + out(0) = xd; + out(1) = yd; + out(2) = static_cast(1.0); + } + else { - const double theta2 = theta*theta; - const double theta4 = theta2*theta2; - const double theta6 = theta4*theta2; - const double theta8 = theta6*theta2; - - double theta_fix = (theta * (1.0 + k1_real * theta2 + k2_real * theta4 + k3_real * theta6 + k4_real * theta8) - theta_dist) / (1.0 + 3.0 * k1_real * theta2 + 5.0 * k2_real * theta4 + 7.0 * k3_real * theta6 + 9.0 *k4_real * theta8); - - theta = theta - theta_fix; + out(0) = (xd / distorted_radius) * static_cast(scale); + out(1) = (yd / distorted_radius) * static_cast(scale); + out(2) = static_cast(1.0); } - - scale = std::tan(theta); - } } - if (distorted_radius < 1e-12) { - out(0) = xd; - out(1) = yd; - out(2) = static_cast(1.0); + template + void unlift(const T* const cam_K, const Eigen::Matrix& pt, Eigen::Matrix& out) const + { + const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + const T& k4 = cam_K[OFFSET_DISTO_K4]; + + // Project on plane + Eigen::Matrix proj_pt = pt / pt(2); + + // Apply distortion + const T x_u = proj_pt(0); + const T y_u = proj_pt(1); + const T dist = sqrt(x_u * x_u + y_u * y_u); + const T theta = atan(dist); + const T theta2 = theta * theta; + const T theta3 = theta2 * theta; + const T theta4 = theta2 * theta2; + const T theta5 = theta4 * theta; + const T theta6 = theta3 * theta3; + const T theta7 = theta6 * theta; + const T theta8 = theta4 * theta4; + const T theta9 = theta8 * theta; + + const T theta_dist = theta + k1 * theta3 + k2 * theta5 + k3 * theta7 + k4 * theta9; + + const T inv_r = dist > T(1e-8) ? T(1.0) / dist : T(1.0); + const T cdist = dist > T(1e-8) ? theta_dist * inv_r : T(1); + + const T x_d = x_u * cdist; + const T y_d = y_u * cdist; + + // Scale and shift + out(0) = x_d * focal + principal_point_x; + out(1) = y_d * focal + principal_point_y; + out(2) = static_cast(1.0); } - else { - out(0) = (xd / distorted_radius) * static_cast(scale); - out(1) = (yd / distorted_radius) * static_cast(scale); - out(2) = static_cast(1.0); + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone; + + Eigen::Matrix pt3d_1; + + // From pixel to meters + lift(cam_K, m_pos_2dpoint_first, pt3d_1); + + // Build relative rotation + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + twoRone = twoRo * oneRo.transpose(); + + // Transform point + Eigen::Matrix pt3d_2_est = twoRone * pt3d_1; + + // Project back to image space in pixels + Eigen::Matrix pt2d_2_est; + unlift(cam_K, pt3d_2_est, pt2d_2_est); + + // Compute residual + Eigen::Matrix residual = pt2d_2_est - m_pos_2dpoint_second; + + out_residuals[0] = residual(0); + out_residuals[1] = residual(1); + + return true; } - } - - template - void unlift(const T* const cam_K, const Eigen::Matrix< T, 3, 1> & pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& focal = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - const T& k4 = cam_K[OFFSET_DISTO_K4]; - - //Project on plane - Eigen::Matrix< T, 3, 1> proj_pt = pt / pt(2); - - //Apply distortion - const T x_u = proj_pt(0); - const T y_u = proj_pt(1); - const T dist = sqrt(x_u*x_u + y_u*y_u); - const T theta = atan(dist); - const T theta2 = theta*theta; - const T theta3 = theta2*theta; - const T theta4 = theta2*theta2; - const T theta5 = theta4*theta; - const T theta6 = theta3*theta3; - const T theta7 = theta6*theta; - const T theta8 = theta4*theta4; - const T theta9 = theta8*theta; - - const T theta_dist = theta + k1*theta3 + k2*theta5 + k3*theta7 + k4*theta9; - - const T inv_r = dist > T(1e-8) ? T(1.0)/dist : T(1.0); - const T cdist = dist > T(1e-8) ? theta_dist * inv_r : T(1); - - const T x_d = x_u * cdist; - const T y_d = y_u * cdist; - - //Scale and shift - out(0) = x_d * focal + principal_point_x; - out(1) = y_d * focal + principal_point_y; - out(2) = static_cast(1.0); - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_R1, - const T* const cam_R2, - T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone; - - Eigen::Matrix< T, 3, 1> pt3d_1; - - //From pixel to meters - lift(cam_K, m_pos_2dpoint_first, pt3d_1); - - //Build relative rotation - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - twoRone = twoRo * oneRo.transpose(); - - //Transform point - Eigen::Matrix< T, 3, 1> pt3d_2_est = twoRone * pt3d_1; - - //Project back to image space in pixels - Eigen::Matrix< T, 3, 1> pt2d_2_est; - unlift(cam_K, pt3d_2_est, pt2d_2_est); - - //Compute residual - Eigen::Matrix< T, 3, 1> residual = pt2d_2_est - m_pos_2dpoint_second; - - out_residuals[0] = residual(0); - out_residuals[1] = residual(1); - - return true; - } - - Vec3 m_pos_2dpoint_first; // The 2D observation in first view - Vec3 m_pos_2dpoint_second; // The 2D observation in second view - Vec2 m_center; // Image center + + Vec3 m_pos_2dpoint_first; // The 2D observation in first view + Vec3 m_pos_2dpoint_second; // The 2D observation in second view + Vec2 m_center; // Image center }; /** @@ -583,114 +582,113 @@ struct ResidualErrorConstraintFunctor_PinholeFisheye * Data parameter blocks are the following <2,3,6,6> * - 2 => dimension of the residuals, * - 3 => the intrinsic data block for the first view [focal, principal point x, principal point y], - * - 3 => the camera extrinsic data block for the first view - * - 3 => the camera extrinsic data block for the second view + * - 3 => the camera extrinsic data block for the first view + * - 3 => the camera extrinsic data block for the second view * */ struct ResidualErrorConstraintFunctor_Equidistant { - ResidualErrorConstraintFunctor_Equidistant(int w, int h, const Vec3 & pos_2dpoint_first, const Vec3 & pos_2dpoint_second, double radius_size) - : m_center(double(w) * 0.5, double(h) * 0.5), m_pos_2dpoint_first(pos_2dpoint_first), m_pos_2dpoint_second(pos_2dpoint_second), m_radius_size(radius_size) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH = 0, - OFFSET_PRINCIPAL_POINT_X = 1, - OFFSET_PRINCIPAL_POINT_Y = 2 - }; - - template - void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - - - Eigen::Matrix< T, 2, 1> camcoords; - camcoords(0) = (pt(0) - principal_point_x) / (m_radius_size); - camcoords(1) = (pt(1) - principal_point_y) / (m_radius_size); - - - T angle_radial = atan2(camcoords(1), camcoords(0)); - T angle_Z = camcoords.norm() * 0.5 * fov; - - out(2) = cos(angle_Z); - out(0) = cos(angle_radial) /** / 1.0 / **/ * sin(angle_Z); - out(1) = sin(angle_radial) /** / 1.0 / **/ * sin(angle_Z); - } - - template - void unlift(const T* const cam_K, const Eigen::Matrix< T, 3, 1> & pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - - /* To unit sphere */ - Eigen::Matrix< T, 3, 1> pt_normalized = pt; - pt_normalized.normalize(); - - /* Compute angle with optical center */ - T angle_Z = atan2(sqrt(pt_normalized(0) * pt_normalized(0) + pt_normalized(1) * pt_normalized(1)), pt_normalized(2)); - T radius = angle_Z / (0.5 * fov); - - /* Ignore depth component and compute radial angle */ - T angle_radial = atan2(pt_normalized(1), pt_normalized(0)); - - /* radius = focal * angle_Z */ - Eigen::Matrix< T, 2, 1> proj_pt; - proj_pt(0) = cos(angle_radial) * radius; - proj_pt(1) = sin(angle_radial) * radius; - - out(0) = proj_pt(0) * m_radius_size + principal_point_x; - out(1) = proj_pt(1) * m_radius_size + principal_point_y; - out(2) = static_cast(1.0); - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone; - - Eigen::Matrix< T, 3, 1> pt3d_1; - - lift(cam_K, m_pos_2dpoint_first, pt3d_1); - - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - - twoRone = twoRo * oneRo.transpose(); - - Eigen::Matrix< T, 3, 1> pt3d_2_est = twoRone * pt3d_1; - - Eigen::Matrix< T, 3, 1> pt2d_2_est; - unlift(cam_K, pt3d_2_est, pt2d_2_est); - - Eigen::Matrix< T, 3, 1> residual = pt2d_2_est - m_pos_2dpoint_second; - - out_residuals[0] = residual(0); - out_residuals[1] = residual(1); - - return true; - } - - Vec3 m_pos_2dpoint_first; // The 2D observation in first view - Vec3 m_pos_2dpoint_second; // The 2D observation in second view - Vec2 m_center; // Image center - double m_radius_size; -}; + ResidualErrorConstraintFunctor_Equidistant(int w, int h, const Vec3& pos_2dpoint_first, const Vec3& pos_2dpoint_second, double radius_size) + : m_center(double(w) * 0.5, double(h) * 0.5), + m_pos_2dpoint_first(pos_2dpoint_first), + m_pos_2dpoint_second(pos_2dpoint_second), + m_radius_size(radius_size) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH = 0, + OFFSET_PRINCIPAL_POINT_X = 1, + OFFSET_PRINCIPAL_POINT_Y = 2 + }; + + template + void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix& out) const + { + const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + + Eigen::Matrix camcoords; + camcoords(0) = (pt(0) - principal_point_x) / (m_radius_size); + camcoords(1) = (pt(1) - principal_point_y) / (m_radius_size); + + T angle_radial = atan2(camcoords(1), camcoords(0)); + T angle_Z = camcoords.norm() * 0.5 * fov; + + out(2) = cos(angle_Z); + out(0) = cos(angle_radial) /** / 1.0 / **/ * sin(angle_Z); + out(1) = sin(angle_radial) /** / 1.0 / **/ * sin(angle_Z); + } + + template + void unlift(const T* const cam_K, const Eigen::Matrix& pt, Eigen::Matrix& out) const + { + const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + + /* To unit sphere */ + Eigen::Matrix pt_normalized = pt; + pt_normalized.normalize(); + + /* Compute angle with optical center */ + T angle_Z = atan2(sqrt(pt_normalized(0) * pt_normalized(0) + pt_normalized(1) * pt_normalized(1)), pt_normalized(2)); + T radius = angle_Z / (0.5 * fov); + + /* Ignore depth component and compute radial angle */ + T angle_radial = atan2(pt_normalized(1), pt_normalized(0)); + + /* radius = focal * angle_Z */ + Eigen::Matrix proj_pt; + proj_pt(0) = cos(angle_radial) * radius; + proj_pt(1) = sin(angle_radial) * radius; + + out(0) = proj_pt(0) * m_radius_size + principal_point_x; + out(1) = proj_pt(1) * m_radius_size + principal_point_y; + out(2) = static_cast(1.0); + } + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone; + + Eigen::Matrix pt3d_1; + + lift(cam_K, m_pos_2dpoint_first, pt3d_1); + + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + + twoRone = twoRo * oneRo.transpose(); + Eigen::Matrix pt3d_2_est = twoRone * pt3d_1; + Eigen::Matrix pt2d_2_est; + unlift(cam_K, pt3d_2_est, pt2d_2_est); + + Eigen::Matrix residual = pt2d_2_est - m_pos_2dpoint_second; + + out_residuals[0] = residual(0); + out_residuals[1] = residual(1); + + return true; + } + + Vec3 m_pos_2dpoint_first; // The 2D observation in first view + Vec3 m_pos_2dpoint_second; // The 2D observation in second view + Vec2 m_center; // Image center + double m_radius_size; +}; /** * @brief Ceres functor to use a pair of pinhole on a pure rotation 2D constraint. @@ -698,175 +696,180 @@ struct ResidualErrorConstraintFunctor_Equidistant * Data parameter blocks are the following <2,6,6,6> * - 2 => dimension of the residuals, * - 4 => the intrinsic data block for the first view [focal, principal point x, principal point y, K1, K2, K3], - * - 3 => the camera extrinsic data block for the first view - * - 3 => the camera extrinsic data block for the second view + * - 3 => the camera extrinsic data block for the first view + * - 3 => the camera extrinsic data block for the second view * */ struct ResidualErrorConstraintFunctor_EquidistantRadialK3 { - ResidualErrorConstraintFunctor_EquidistantRadialK3(int w, int h, const Vec3 & pos_2dpoint_first, const Vec3 & pos_2dpoint_second, double radius_size) - : m_center(double(w) * 0.5, double(h) * 0.5), m_pos_2dpoint_first(pos_2dpoint_first), m_pos_2dpoint_second(pos_2dpoint_second), m_radius_size(radius_size) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH = 0, - OFFSET_PRINCIPAL_POINT_X = 1, - OFFSET_PRINCIPAL_POINT_Y = 2, - OFFSET_DISTO_K1 = 3, - OFFSET_DISTO_K2 = 4, - OFFSET_DISTO_K3 = 5 - }; - - static double distoFunctor(const std::vector & params, double r2) - { - const double k1 = params[0]; - const double k2 = params[1]; - const double k3 = params[2]; - - const double r4 = r2 * r2; - const double r6 = r4 * r2; - - return r2 * Square(1.0 + k1 * r2 + k2 * r4 + k3 * r6); - } - - template - void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - - //Unshift then unscale back to meters - T xd = (pt(0) - principal_point_x) / (m_radius_size); - T yd = (pt(1) - principal_point_y) / (m_radius_size); - T distorted_radius = sqrt(xd*xd + yd*yd); - - /*A hack to obtain undistorted point even if using automatic diff*/ - double xd_real, yd_real, k1_real, k2_real, k3_real; - xd_real = getJetValue(xd); - yd_real = getJetValue(yd); - k1_real = getJetValue(k1); - k2_real = getJetValue(k2); - k3_real = getJetValue(k3); - - /*get rescaler*/ - std::vector distortionParams = {k1_real, k2_real, k3_real}; - - - Eigen::Matrix camcoords; - if (distorted_radius < 1e-12) { - camcoords(0) = xd; - camcoords(1) = yd; + ResidualErrorConstraintFunctor_EquidistantRadialK3(int w, + int h, + const Vec3& pos_2dpoint_first, + const Vec3& pos_2dpoint_second, + double radius_size) + : m_center(double(w) * 0.5, double(h) * 0.5), + m_pos_2dpoint_first(pos_2dpoint_first), + m_pos_2dpoint_second(pos_2dpoint_second), + m_radius_size(radius_size) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH = 0, + OFFSET_PRINCIPAL_POINT_X = 1, + OFFSET_PRINCIPAL_POINT_Y = 2, + OFFSET_DISTO_K1 = 3, + OFFSET_DISTO_K2 = 4, + OFFSET_DISTO_K3 = 5 + }; + + static double distoFunctor(const std::vector& params, double r2) + { + const double k1 = params[0]; + const double k2 = params[1]; + const double k3 = params[2]; + + const double r4 = r2 * r2; + const double r6 = r4 * r2; + + return r2 * Square(1.0 + k1 * r2 + k2 * r4 + k3 * r6); } - else { - double distorted_radius_square = xd_real * xd_real + yd_real * yd_real; - double rescaler = ::sqrt(camera::radial_distortion::bisection_Radius_Solve(distortionParams, distorted_radius_square, distoFunctor));; - camcoords(0) = (xd / distorted_radius) * static_cast(rescaler); - camcoords(1) = (yd / distorted_radius) * static_cast(rescaler); + + template + void lift(const T* const cam_K, const Vec3 pt, Eigen::Matrix& out) const + { + const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + + // Unshift then unscale back to meters + T xd = (pt(0) - principal_point_x) / (m_radius_size); + T yd = (pt(1) - principal_point_y) / (m_radius_size); + T distorted_radius = sqrt(xd * xd + yd * yd); + + /*A hack to obtain undistorted point even if using automatic diff*/ + double xd_real, yd_real, k1_real, k2_real, k3_real; + xd_real = getJetValue(xd); + yd_real = getJetValue(yd); + k1_real = getJetValue(k1); + k2_real = getJetValue(k2); + k3_real = getJetValue(k3); + + /*get rescaler*/ + std::vector distortionParams = {k1_real, k2_real, k3_real}; + + Eigen::Matrix camcoords; + if (distorted_radius < 1e-12) + { + camcoords(0) = xd; + camcoords(1) = yd; + } + else + { + double distorted_radius_square = xd_real * xd_real + yd_real * yd_real; + double rescaler = ::sqrt(camera::radial_distortion::bisection_Radius_Solve(distortionParams, distorted_radius_square, distoFunctor)); + ; + camcoords(0) = (xd / distorted_radius) * static_cast(rescaler); + camcoords(1) = (yd / distorted_radius) * static_cast(rescaler); + } + + T angle_radial = atan2(camcoords(1), camcoords(0)); + T angle_Z = camcoords.norm() * 0.5 * fov; + + out(2) = cos(angle_Z); + out(0) = cos(angle_radial) /** / 1.0 / **/ * sin(angle_Z); + out(1) = sin(angle_radial) /** / 1.0 / **/ * sin(angle_Z); + } + + template + void unlift(const T* const cam_K, const Eigen::Matrix& pt, Eigen::Matrix& out) const + { + const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + + /* To unit sphere */ + Eigen::Matrix pt_normalized = pt; + pt_normalized.normalize(); + + /* Compute angle with optical center */ + T angle_Z = atan2(sqrt(pt_normalized(0) * pt_normalized(0) + pt_normalized(1) * pt_normalized(1)), pt_normalized(2)); + T radius = angle_Z / (0.5 * fov); + + /* Ignore depth component and compute radial angle */ + T angle_radial = atan2(pt_normalized(1), pt_normalized(0)); + + /* radius = focal * angle_Z */ + Eigen::Matrix proj_pt; + proj_pt(0) = cos(angle_radial) * radius; + proj_pt(1) = sin(angle_radial) * radius; + + // Apply distortion + const T r = sqrt(proj_pt(0) * proj_pt(0) + proj_pt(1) * proj_pt(1)); + const T r2 = r * r; + const T r4 = r2 * r2; + const T r6 = r4 * r2; + + const T r_coeff = (T(1.0) + k1 * r2 + k2 * r4 + k3 * r6); + const T x_d = proj_pt(0) * r_coeff; + const T y_d = proj_pt(1) * r_coeff; + + // Scale and shift + out(0) = x_d * m_radius_size + principal_point_x; + out(1) = y_d * m_radius_size + principal_point_y; + out(2) = static_cast(1.0); + } + + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone; + + Eigen::Matrix pt3d_1; + + // From pixel to meters + lift(cam_K, m_pos_2dpoint_first, pt3d_1); + + // Build relative rotation + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + twoRone = twoRo * oneRo.transpose(); + + // Transform point + Eigen::Matrix pt3d_2_est = twoRone * pt3d_1; + + // Project back to image space in pixels + Eigen::Matrix pt2d_2_est; + unlift(cam_K, pt3d_2_est, pt2d_2_est); + + // Compute residual + Eigen::Matrix residual = pt2d_2_est - m_pos_2dpoint_second; + + out_residuals[0] = residual(0); + out_residuals[1] = residual(1); + + return true; } - T angle_radial = atan2(camcoords(1), camcoords(0)); - T angle_Z = camcoords.norm() * 0.5 * fov; - - out(2) = cos(angle_Z); - out(0) = cos(angle_radial) /** / 1.0 / **/ * sin(angle_Z); - out(1) = sin(angle_radial) /** / 1.0 / **/ * sin(angle_Z); - } - - template - void unlift(const T* const cam_K, const Eigen::Matrix< T, 3, 1> & pt, Eigen::Matrix< T, 3, 1> & out) const - { - const T& fov = cam_K[OFFSET_FOCAL_LENGTH]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + m_center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + m_center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - - /* To unit sphere */ - Eigen::Matrix< T, 3, 1> pt_normalized = pt; - pt_normalized.normalize(); - - /* Compute angle with optical center */ - T angle_Z = atan2(sqrt(pt_normalized(0) * pt_normalized(0) + pt_normalized(1) * pt_normalized(1)), pt_normalized(2)); - T radius = angle_Z / (0.5 * fov); - - /* Ignore depth component and compute radial angle */ - T angle_radial = atan2(pt_normalized(1), pt_normalized(0)); - - /* radius = focal * angle_Z */ - Eigen::Matrix< T, 2, 1> proj_pt; - proj_pt(0) = cos(angle_radial) * radius; - proj_pt(1) = sin(angle_radial) * radius; - - //Apply distortion - const T r = sqrt(proj_pt(0)*proj_pt(0) + proj_pt(1)*proj_pt(1)); - const T r2 = r * r; - const T r4 = r2 * r2; - const T r6 = r4 * r2; - - const T r_coeff = (T(1.0) + k1 * r2 + k2 * r4 + k3 * r6); - const T x_d = proj_pt(0) * r_coeff; - const T y_d = proj_pt(1) * r_coeff; - - //Scale and shift - out(0) = x_d * m_radius_size + principal_point_x; - out(1) = y_d * m_radius_size + principal_point_y; - out(2) = static_cast(1.0); - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_R1, - const T* const cam_R2, - T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone; - - Eigen::Matrix pt3d_1; - - //From pixel to meters - lift(cam_K, m_pos_2dpoint_first, pt3d_1); - - //Build relative rotation - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - twoRone = twoRo * oneRo.transpose(); - - //Transform point - Eigen::Matrix< T, 3, 1> pt3d_2_est = twoRone * pt3d_1; - - //Project back to image space in pixels - Eigen::Matrix< T, 3, 1> pt2d_2_est; - unlift(cam_K, pt3d_2_est, pt2d_2_est); - - //Compute residual - Eigen::Matrix< T, 3, 1> residual = pt2d_2_est - m_pos_2dpoint_second; - - out_residuals[0] = residual(0); - out_residuals[1] = residual(1); - - return true; - } - - Vec3 m_pos_2dpoint_first; // The 2D observation in first view - Vec3 m_pos_2dpoint_second; // The 2D observation in second view - Vec2 m_center; // Image center - double m_radius_size; + Vec3 m_pos_2dpoint_first; // The 2D observation in first view + Vec3 m_pos_2dpoint_second; // The 2D observation in second view + Vec2 m_center; // Image center + double m_radius_size; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/ResidualErrorFunctor.hpp b/src/aliceVision/sfm/ResidualErrorFunctor.hpp index 4c6d155ed9..0dc6f13a95 100644 --- a/src/aliceVision/sfm/ResidualErrorFunctor.hpp +++ b/src/aliceVision/sfm/ResidualErrorFunctor.hpp @@ -32,143 +32,132 @@ namespace sfm { */ struct ResidualErrorFunctor_Pinhole { - explicit ResidualErrorFunctor_Pinhole(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3 - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_u; - const T projected_y = principal_point_y + focalY * y_u; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - T pos_proj[3]; + explicit ResidualErrorFunctor_Pinhole(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum { - const T* cam_R = cam_Rt; - const T* cam_t = &cam_Rt[3]; + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3 + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const + { + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_u; + const T projected_y = principal_point_y + focalY * y_u; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + T pos_proj[3]; + + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; + + // Rotate the point according to the camera rotation + T pos_proj_tmp[3] = {pos_proj[0], pos_proj[1], pos_proj[2]}; + ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T* cam_R = subpose_Rt; - const T* cam_t = &subpose_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // T absCam_R[3]; + // T absCam_t[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- - // Rotate the point according to the camera rotation - T pos_proj_tmp[3] = { pos_proj[0], pos_proj[1], pos_proj[2] }; - ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - //T absCam_R[3]; - //T absCam_t[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; /** @@ -184,149 +173,137 @@ struct ResidualErrorFunctor_Pinhole */ struct ResidualErrorFunctor_PinholeRadialK1 { - explicit ResidualErrorFunctor_PinholeRadialK1(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_K1 = 4 - }; - - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r_coeff = (T(1) + k1*r2); - const T x_d = x_u * r_coeff; - const T y_d = y_u * r_coeff; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - T pos_proj[3]; + explicit ResidualErrorFunctor_PinholeRadialK1(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_K1 = 4 + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const + { + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r_coeff = (T(1) + k1 * r2); + const T x_d = x_u * r_coeff; + const T y_d = y_u * r_coeff; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + T pos_proj[3]; + + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + // Rotate the point according to the camera rotation + T pos_proj_tmp[3] = {pos_proj[0], pos_proj[1], pos_proj[2]}; + ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], K1 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T* cam_R = subpose_Rt; - const T* cam_t = &subpose_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- - // Rotate the point according to the camera rotation - T pos_proj_tmp[3] = { pos_proj[0], pos_proj[1], pos_proj[2] }; - ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], K1 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; /** @@ -342,148 +319,137 @@ struct ResidualErrorFunctor_PinholeRadialK1 */ struct ResidualErrorFunctor_PinholeRadialK3 { - explicit ResidualErrorFunctor_PinholeRadialK3(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_K1 = 4, - OFFSET_DISTO_K2 = 5, - OFFSET_DISTO_K3 = 6, - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r4 = r2 * r2; - const T r6 = r4 * r2; - const T r_coeff = (T(1) + k1*r2 + k2*r4 + k3*r6); - const T x_d = x_u * r_coeff; - const T y_d = y_u * r_coeff; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - // Apply external parameters (RIG pose and RIG sub-pose) - T pos_proj[3]; - - // Apply RIG pose + explicit ResidualErrorFunctor_PinholeRadialK3(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_K1 = 4, + OFFSET_DISTO_K2 = 5, + OFFSET_DISTO_K3 = 6, + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - // Rotate the point according to the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); // TODO DELI cam_Rt <-> subposeRt - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r4 = r2 * r2; + const T r6 = r4 * r2; + const T r_coeff = (T(1) + k1 * r2 + k2 * r4 + k3 * r6); + const T x_d = x_u * r_coeff; + const T y_d = y_u * r_coeff; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; } - // Apply RIG sub-pose + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = subpose_Rt; - const T * cam_t = &subpose_Rt[3]; - // Rotate the point according to the camera rotation. In-place rotation not supported by Ceres - T pos_proj_tmp[3]; - ceres::AngleAxisRotatePoint(cam_R, pos_proj, pos_proj_tmp); - - // Apply the camera translation - pos_proj[0] = pos_proj_tmp[0] + cam_t[0]; - pos_proj[1] = pos_proj_tmp[1] + cam_t[1]; - pos_proj[2] = pos_proj_tmp[2] + cam_t[2]; + // Apply external parameters (RIG pose and RIG sub-pose) + T pos_proj[3]; + + // Apply RIG pose + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + // Rotate the point according to the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); // TODO DELI cam_Rt <-> subposeRt + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Apply RIG sub-pose + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; + // Rotate the point according to the camera rotation. In-place rotation not supported by Ceres + T pos_proj_tmp[3]; + ceres::AngleAxisRotatePoint(cam_R, pos_proj, pos_proj_tmp); + + // Apply the camera translation + pos_proj[0] = pos_proj_tmp[0] + cam_t[0]; + pos_proj[1] = pos_proj_tmp[1] + cam_t[1]; + pos_proj[2] = pos_proj_tmp[2] + cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + // Apply intrinsic parameters + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - // Apply intrinsic parameters - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; + } + + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; /** @@ -499,164 +465,151 @@ struct ResidualErrorFunctor_PinholeRadialK3 */ struct ResidualErrorFunctor_PinholeBrownT2 { - explicit ResidualErrorFunctor_PinholeBrownT2(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_K1 = 4, - OFFSET_DISTO_K2 = 5, - OFFSET_DISTO_K3 = 6, - OFFSET_DISTO_T1 = 7, - OFFSET_DISTO_T2 = 8, - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - const T& t1 = cam_K[OFFSET_DISTO_T1]; - const T& t2 = cam_K[OFFSET_DISTO_T2]; - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r4 = r2 * r2; - const T r6 = r4 * r2; - const T r_coeff = (T(1) + k1*r2 + k2*r4 + k3*r6); - const T t_x = t2 * (r2 + T(2) * x_u*x_u) + T(2) * t1 * x_u * y_u; - const T t_y = t1 * (r2 + T(2) * y_u*y_u) + T(2) * t2 * x_u * y_u; - const T x_d = x_u * r_coeff + t_x; - const T y_d = y_u * r_coeff + t_y; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - T pos_proj[3]; + explicit ResidualErrorFunctor_PinholeBrownT2(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_K1 = 4, + OFFSET_DISTO_K2 = 5, + OFFSET_DISTO_K3 = 6, + OFFSET_DISTO_T1 = 7, + OFFSET_DISTO_T2 = 8, + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + const T& t1 = cam_K[OFFSET_DISTO_T1]; + const T& t2 = cam_K[OFFSET_DISTO_T2]; + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r4 = r2 * r2; + const T r6 = r4 * r2; + const T r_coeff = (T(1) + k1 * r2 + k2 * r4 + k3 * r6); + const T t_x = t2 * (r2 + T(2) * x_u * x_u) + T(2) * t1 * x_u * y_u; + const T t_y = t1 * (r2 + T(2) * y_u * y_u) + T(2) * t2 * x_u * y_u; + const T x_d = x_u * r_coeff + t_x; + const T y_d = y_u * r_coeff + t_y; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + T pos_proj[3]; + + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Rotate the point according to the camera rotation + T pos_proj_tmp[3] = {pos_proj[0], pos_proj[1], pos_proj[2]}; + ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3, t1, t2 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = subpose_Rt; - const T * cam_t = &subpose_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; - // Rotate the point according to the camera rotation - T pos_proj_tmp[3] = { pos_proj[0], pos_proj[1], pos_proj[2] }; - ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + //-- + // Apply intrinsic parameters + //-- - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3, t1, t2 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; - /** * @brief Ceres functor with constrained 3D points to use a PinholeFisheye * @@ -670,162 +623,150 @@ struct ResidualErrorFunctor_PinholeBrownT2 */ struct ResidualErrorFunctor_PinholeFisheye { - explicit ResidualErrorFunctor_PinholeFisheye(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_K1 = 4, - OFFSET_DISTO_K2 = 5, - OFFSET_DISTO_K3 = 6, - OFFSET_DISTO_K4 = 7, - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - const T& k2 = cam_K[OFFSET_DISTO_K2]; - const T& k3 = cam_K[OFFSET_DISTO_K3]; - const T& k4 = cam_K[OFFSET_DISTO_K4]; - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r = sqrt(r2); - const T theta = atan(r); - const T theta2 = theta*theta, theta3 = theta2*theta, theta4 = theta2*theta2, theta5 = theta4*theta, - theta6 = theta3*theta3, theta7 = theta6*theta, theta8 = theta4*theta4, theta9 = theta8*theta; - const T theta_dist = theta + k1*theta3 + k2*theta5 + k3*theta7 + k4*theta9; - const T inv_r = r > T(1e-8) ? T(1.0)/r : T(1.0); - const T cdist = r > T(1e-8) ? theta_dist * inv_r : T(1); - - const T x_d = x_u * cdist; - const T y_d = y_u * cdist; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - T pos_proj[3]; + explicit ResidualErrorFunctor_PinholeFisheye(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_K1 = 4, + OFFSET_DISTO_K2 = 5, + OFFSET_DISTO_K3 = 6, + OFFSET_DISTO_K4 = 7, + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + const T& k2 = cam_K[OFFSET_DISTO_K2]; + const T& k3 = cam_K[OFFSET_DISTO_K3]; + const T& k4 = cam_K[OFFSET_DISTO_K4]; + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r = sqrt(r2); + const T theta = atan(r); + const T theta2 = theta * theta, theta3 = theta2 * theta, theta4 = theta2 * theta2, theta5 = theta4 * theta, theta6 = theta3 * theta3, + theta7 = theta6 * theta, theta8 = theta4 * theta4, theta9 = theta8 * theta; + const T theta_dist = theta + k1 * theta3 + k2 * theta5 + k3 * theta7 + k4 * theta9; + const T inv_r = r > T(1e-8) ? T(1.0) / r : T(1.0); + const T cdist = r > T(1e-8) ? theta_dist * inv_r : T(1); + + const T x_d = x_u * cdist; + const T y_d = y_u * cdist; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + T pos_proj[3]; + + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; + + // Rotate the point according to the camera rotation + T pos_proj_tmp[3] = {pos_proj[0], pos_proj[1], pos_proj[2]}; + ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3, k4 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = subpose_Rt; - const T * cam_t = &subpose_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; - // Rotate the point according to the camera rotation - T pos_proj_tmp[3] = { pos_proj[0], pos_proj[1], pos_proj[2] }; - ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + //-- + // Apply intrinsic parameters + //-- - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3, k4 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; /** @@ -841,152 +782,140 @@ struct ResidualErrorFunctor_PinholeFisheye */ struct ResidualErrorFunctor_PinholeFisheye1 { - explicit ResidualErrorFunctor_PinholeFisheye1(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_K1 = 4 - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - const T& k1 = cam_K[OFFSET_DISTO_K1]; - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T r2 = x_u*x_u + y_u*y_u; - const T r = sqrt(r2); - const T r_coeff = (atan(2.0 * r * tan(0.5 * k1)) / k1) / r; - const T x_d = x_u * r_coeff; - const T y_d = y_u * r_coeff; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - T pos_proj[3]; + explicit ResidualErrorFunctor_PinholeFisheye1(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_K1 = 4 + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const + { + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + const T& k1 = cam_K[OFFSET_DISTO_K1]; + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T r2 = x_u * x_u + y_u * y_u; + const T r = sqrt(r2); + const T r_coeff = (atan(2.0 * r * tan(0.5 * k1)) / k1) / r; + const T x_d = x_u * r_coeff; + const T y_d = y_u * r_coeff; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + T pos_proj[3]; + + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Rotate the point according to the camera rotation + T pos_proj_tmp[3] = {pos_proj[0], pos_proj[1], pos_proj[2]}; + ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], K1 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = subpose_Rt; - const T * cam_t = &subpose_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; - // Rotate the point according to the camera rotation - T pos_proj_tmp[3] = { pos_proj[0], pos_proj[1], pos_proj[2] }; - ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + //-- + // Apply intrinsic parameters + //-- - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], K1 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; - /** * @brief Ceres functor to use a Pinhole3DEClassicLD * @@ -1000,170 +929,159 @@ struct ResidualErrorFunctor_PinholeFisheye1 */ struct ResidualErrorFunctor_Pinhole3DEClassicLD { - explicit ResidualErrorFunctor_Pinhole3DEClassicLD(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_DELTA = 4, - OFFSET_DISTO_IEPS = 5, - OFFSET_DISTO_MUX = 6, - OFFSET_DISTO_MUY = 7, - OFFSET_DISTO_Q = 8, - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T& delta = cam_K[OFFSET_DISTO_DELTA]; - const T& invepsilon = cam_K[OFFSET_DISTO_IEPS]; - const T& mux = cam_K[OFFSET_DISTO_MUX]; - const T& muy = cam_K[OFFSET_DISTO_MUY]; - const T& q = cam_K[OFFSET_DISTO_Q]; - - const T eps = 1.0 + cos(invepsilon); - - const T cxx = delta * eps; - const T cxy = (delta + mux) * eps; - const T cxxx = q * eps; - const T cxxy = 2.0 * q * eps; - const T cxyy = q * eps; - const T cyx = delta + muy; - const T cyy = delta; - const T cyxx = q; - const T cyxy = 2.0 * q; - const T cyyy = q; - - T x = x_u; - T y = y_u; - T xx = x * x; - T yy = y * y; - T xxxx = xx * xx; - T yyyy = yy * yy; - T xxyy = xx * yy; - - T x_d = x * (1.0 + cxx * xx + cxy * yy + cxxx * xxxx + cxxy * xxyy + cxyy * yyyy); - T y_d = y * (1.0 + cyx * xx + cyy * yy + cyxx * xxxx + cyxy * xxyy + cyyy * yyyy); - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - // Apply external parameters (RIG pose and RIG sub-pose) - T pos_proj[3]; - - // Apply RIG pose + explicit ResidualErrorFunctor_Pinhole3DEClassicLD(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - // Rotate the point according to the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); // TODO DELI cam_Rt <-> subposeRt - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_DELTA = 4, + OFFSET_DISTO_IEPS = 5, + OFFSET_DISTO_MUX = 6, + OFFSET_DISTO_MUY = 7, + OFFSET_DISTO_Q = 8, + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const + { + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T& delta = cam_K[OFFSET_DISTO_DELTA]; + const T& invepsilon = cam_K[OFFSET_DISTO_IEPS]; + const T& mux = cam_K[OFFSET_DISTO_MUX]; + const T& muy = cam_K[OFFSET_DISTO_MUY]; + const T& q = cam_K[OFFSET_DISTO_Q]; + + const T eps = 1.0 + cos(invepsilon); + + const T cxx = delta * eps; + const T cxy = (delta + mux) * eps; + const T cxxx = q * eps; + const T cxxy = 2.0 * q * eps; + const T cxyy = q * eps; + const T cyx = delta + muy; + const T cyy = delta; + const T cyxx = q; + const T cyxy = 2.0 * q; + const T cyyy = q; + + T x = x_u; + T y = y_u; + T xx = x * x; + T yy = y * y; + T xxxx = xx * xx; + T yyyy = yy * yy; + T xxyy = xx * yy; + + T x_d = x * (1.0 + cxx * xx + cxy * yy + cxxx * xxxx + cxxy * xxyy + cxyy * yyyy); + T y_d = y * (1.0 + cyx * xx + cyy * yy + cyxx * xxxx + cyxy * xxyy + cyyy * yyyy); + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; } - // Apply RIG sub-pose + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = subpose_Rt; - const T * cam_t = &subpose_Rt[3]; - // Rotate the point according to the camera rotation. In-place rotation not supported by Ceres - T pos_proj_tmp[3]; - ceres::AngleAxisRotatePoint(cam_R, pos_proj, pos_proj_tmp); - - // Apply the camera translation - pos_proj[0] = pos_proj_tmp[0] + cam_t[0]; - pos_proj[1] = pos_proj_tmp[1] + cam_t[1]; - pos_proj[2] = pos_proj_tmp[2] + cam_t[2]; + // Apply external parameters (RIG pose and RIG sub-pose) + T pos_proj[3]; + + // Apply RIG pose + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + // Rotate the point according to the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); // TODO DELI cam_Rt <-> subposeRt + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Apply RIG sub-pose + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; + // Rotate the point according to the camera rotation. In-place rotation not supported by Ceres + T pos_proj_tmp[3]; + ceres::AngleAxisRotatePoint(cam_R, pos_proj, pos_proj_tmp); + + // Apply the camera translation + pos_proj[0] = pos_proj_tmp[0] + cam_t[0]; + pos_proj[1] = pos_proj_tmp[1] + cam_t[1]; + pos_proj[2] = pos_proj_tmp[2] + cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + // Apply intrinsic parameters + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - // Apply intrinsic parameters - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const + { + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; + } + + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; /** @@ -1179,166 +1097,155 @@ struct ResidualErrorFunctor_Pinhole3DEClassicLD */ struct ResidualErrorFunctor_Pinhole3DERadial4 { - explicit ResidualErrorFunctor_Pinhole3DERadial4(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_C2 = 4, - OFFSET_DISTO_C4 = 5, - OFFSET_DISTO_U1 = 6, - OFFSET_DISTO_V1 = 7, - OFFSET_DISTO_U3 = 8, - OFFSET_DISTO_V3 = 9, - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T& c2 = cam_K[OFFSET_DISTO_C2]; - const T& c4 = cam_K[OFFSET_DISTO_C4]; - const T& u1 = cam_K[OFFSET_DISTO_U1]; - const T& v1 = cam_K[OFFSET_DISTO_V1]; - const T& u3 = cam_K[OFFSET_DISTO_U3]; - const T& v3 = cam_K[OFFSET_DISTO_V3]; - - T x = x_u; - T y = y_u; - T xx = x * x; - T yy = y * y; - T xy = x * y; - T r2 = xx + yy; - T r4 = r2 * r2; - - T p1 = 1.0 + c2 * r2 + c4 * r4; - T p2 = r2 + 2.0 * xx; - T p3 = r2 + 2.0 * yy; - T p4 = u1 + u3 * r2; - T p5 = v1 + v3 * r2; - T p6 = 2.0 * xy; - - T x_d = x * p1 + p2 * p4 + p6 * p5; - T y_d = y * p1 + p3 * p5 + p6 * p4; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * x_d; - const T projected_y = principal_point_y + focalY * y_d; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - // Apply external parameters (RIG pose and RIG sub-pose) - T pos_proj[3]; - - // Apply RIG pose + explicit ResidualErrorFunctor_Pinhole3DERadial4(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_C2 = 4, + OFFSET_DISTO_C4 = 5, + OFFSET_DISTO_U1 = 6, + OFFSET_DISTO_V1 = 7, + OFFSET_DISTO_U3 = 8, + OFFSET_DISTO_V3 = 9, + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const + { + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T& c2 = cam_K[OFFSET_DISTO_C2]; + const T& c4 = cam_K[OFFSET_DISTO_C4]; + const T& u1 = cam_K[OFFSET_DISTO_U1]; + const T& v1 = cam_K[OFFSET_DISTO_V1]; + const T& u3 = cam_K[OFFSET_DISTO_U3]; + const T& v3 = cam_K[OFFSET_DISTO_V3]; + + T x = x_u; + T y = y_u; + T xx = x * x; + T yy = y * y; + T xy = x * y; + T r2 = xx + yy; + T r4 = r2 * r2; + + T p1 = 1.0 + c2 * r2 + c4 * r4; + T p2 = r2 + 2.0 * xx; + T p3 = r2 + 2.0 * yy; + T p4 = u1 + u3 * r2; + T p5 = v1 + v3 * r2; + T p6 = 2.0 * xy; + + T x_d = x * p1 + p2 * p4 + p6 * p5; + T y_d = y * p1 + p3 * p5 + p6 * p4; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * x_d; + const T projected_y = principal_point_y + focalY * y_d; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - // Rotate the point according to the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); // TODO DELI cam_Rt <-> subposeRt - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Apply external parameters (RIG pose and RIG sub-pose) + T pos_proj[3]; + + // Apply RIG pose + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + // Rotate the point according to the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); // TODO DELI cam_Rt <-> subposeRt + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + // Apply RIG sub-pose + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; + // Rotate the point according to the camera rotation. In-place rotation not supported by Ceres + T pos_proj_tmp[3]; + ceres::AngleAxisRotatePoint(cam_R, pos_proj, pos_proj_tmp); + + // Apply the camera translation + pos_proj[0] = pos_proj_tmp[0] + cam_t[0]; + pos_proj[1] = pos_proj_tmp[1] + cam_t[1]; + pos_proj[2] = pos_proj_tmp[2] + cam_t[2]; + } + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + // Apply intrinsic parameters + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Apply RIG sub-pose + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3 ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T * cam_R = subpose_Rt; - const T * cam_t = &subpose_Rt[3]; - // Rotate the point according to the camera rotation. In-place rotation not supported by Ceres - T pos_proj_tmp[3]; - ceres::AngleAxisRotatePoint(cam_R, pos_proj, pos_proj_tmp); - - // Apply the camera translation - pos_proj[0] = pos_proj_tmp[0] + cam_t[0]; - pos_proj[1] = pos_proj_tmp[1] + cam_t[1]; - pos_proj[2] = pos_proj_tmp[2] + cam_t[2]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - // Apply intrinsic parameters - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y], k1, k2, k3 ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; /** @@ -1354,207 +1261,195 @@ struct ResidualErrorFunctor_Pinhole3DERadial4 */ struct ResidualErrorFunctor_Pinhole3DEAnamorphic4 { - explicit ResidualErrorFunctor_Pinhole3DEAnamorphic4(int w, int h, const sfmData::Observation& obs) - : _center(double(w) * 0.5, double(h) * 0.5), _obs(obs) - { - } - - // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. - enum { - OFFSET_FOCAL_LENGTH_X = 0, - OFFSET_FOCAL_LENGTH_Y = 1, - OFFSET_PRINCIPAL_POINT_X = 2, - OFFSET_PRINCIPAL_POINT_Y = 3, - OFFSET_DISTO_CX02 = 4, - OFFSET_DISTO_CY02 = 5, - OFFSET_DISTO_CX22 = 6, - OFFSET_DISTO_CY22 = 7, - OFFSET_DISTO_CX04 = 8, - OFFSET_DISTO_CY04 = 9, - OFFSET_DISTO_CX24 = 10, - OFFSET_DISTO_CY24 = 11, - OFFSET_DISTO_CX44 = 12, - OFFSET_DISTO_CY44 = 13, - OFFSET_DISTO_PHI = 14, - OFFSET_DISTO_SQX = 15, - OFFSET_DISTO_SQY = 16, - OFFSET_DISTO_PS = 17 - }; - - template - void applyIntrinsicParameters(const T* const cam_K, - const T x_u, - const T y_u, - T* out_residuals) const - { - const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; - const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; - const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); - const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); - - // Apply distortion (xd,yd) = disto(x_u,y_u) - const T& cx02 = cam_K[OFFSET_DISTO_CX02]; - const T& cy02 = cam_K[OFFSET_DISTO_CY02]; - const T& cx22 = cam_K[OFFSET_DISTO_CX22]; - const T& cy22 = cam_K[OFFSET_DISTO_CY22]; - const T& cx04 = cam_K[OFFSET_DISTO_CX04]; - const T& cy04 = cam_K[OFFSET_DISTO_CY04]; - const T& cx24 = cam_K[OFFSET_DISTO_CX24]; - const T& cy24 = cam_K[OFFSET_DISTO_CY24]; - const T& cx44 = cam_K[OFFSET_DISTO_CX44]; - const T& cy44 = cam_K[OFFSET_DISTO_CY44]; - const T& phi = cam_K[OFFSET_DISTO_PHI]; - const T& sqx = cam_K[OFFSET_DISTO_SQX]; - const T& sqy = cam_K[OFFSET_DISTO_SQY]; - - const T cphi = cos(phi); - const T sphi = sin(phi); - - const T cx_xx = cx02 + cx22; - const T cx_yy = cx02 - cx22; - const T cx_xxyy = 2.0 * cx04 - 6.0 * cx44; - const T cx_xxxx = cx04 + cx24 + cx44; - const T cx_yyyy = cx04 - cx24 + cx44; - - const T cy_xx = cy02 + cy22; - const T cy_yy = cy02 - cy22; - const T cy_xxyy = 2.0 * cy04 - 6.0 * cy44; - const T cy_xxxx = cy04 + cy24 + cy44; - const T cy_yyyy = cy04 - cy24 + cy44; - - const T x = x_u; - const T y = y_u; - - const T xr = cphi* x + sphi * y; - const T yr = -sphi * x + cphi * y; - - const T xx = xr * xr; - const T yy = yr * yr; - const T xxxx = xx * xx; - const T yyyy = yy * yy; - const T xxyy = xx * yy; - - const T xd = xr * (1.0 + xx * cx_xx + yy * cx_yy + xxxx * cx_xxxx + xxyy * cx_xxyy + yyyy * cx_yyyy); - const T yd = yr * (1.0 + xx * cy_xx + yy * cy_yy + xxxx * cy_xxxx + xxyy * cy_xxyy + yyyy * cy_yyyy); - - const T squizzed_x = xd * sqx; - const T squizzed_y = yd * sqy; - - const T np_x = cphi* squizzed_x - sphi * squizzed_y; - const T np_y = sphi* squizzed_x + cphi * squizzed_y; - - // Apply focal length and principal point to get the final image coordinates - const T projected_x = principal_point_x + focalX * np_x; - const T projected_y = principal_point_y + focalY * np_y; - - // Compute and return the error is the difference between the predicted - // and observed position - const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); - out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; - out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; - } - - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const subpose_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - T pos_proj[3]; + explicit ResidualErrorFunctor_Pinhole3DEAnamorphic4(int w, int h, const sfmData::Observation& obs) + : _center(double(w) * 0.5, double(h) * 0.5), + _obs(obs) + {} + + // Enum to map intrinsics parameters between aliceVision & ceres camera data parameter block. + enum + { + OFFSET_FOCAL_LENGTH_X = 0, + OFFSET_FOCAL_LENGTH_Y = 1, + OFFSET_PRINCIPAL_POINT_X = 2, + OFFSET_PRINCIPAL_POINT_Y = 3, + OFFSET_DISTO_CX02 = 4, + OFFSET_DISTO_CY02 = 5, + OFFSET_DISTO_CX22 = 6, + OFFSET_DISTO_CY22 = 7, + OFFSET_DISTO_CX04 = 8, + OFFSET_DISTO_CY04 = 9, + OFFSET_DISTO_CX24 = 10, + OFFSET_DISTO_CY24 = 11, + OFFSET_DISTO_CX44 = 12, + OFFSET_DISTO_CY44 = 13, + OFFSET_DISTO_PHI = 14, + OFFSET_DISTO_SQX = 15, + OFFSET_DISTO_SQY = 16, + OFFSET_DISTO_PS = 17 + }; + + template + void applyIntrinsicParameters(const T* const cam_K, const T x_u, const T y_u, T* out_residuals) const + { + const T& focalX = cam_K[OFFSET_FOCAL_LENGTH_X]; + const T& focalY = cam_K[OFFSET_FOCAL_LENGTH_Y]; + const T& principal_point_x = cam_K[OFFSET_PRINCIPAL_POINT_X] + _center(0); + const T& principal_point_y = cam_K[OFFSET_PRINCIPAL_POINT_Y] + _center(1); + + // Apply distortion (xd,yd) = disto(x_u,y_u) + const T& cx02 = cam_K[OFFSET_DISTO_CX02]; + const T& cy02 = cam_K[OFFSET_DISTO_CY02]; + const T& cx22 = cam_K[OFFSET_DISTO_CX22]; + const T& cy22 = cam_K[OFFSET_DISTO_CY22]; + const T& cx04 = cam_K[OFFSET_DISTO_CX04]; + const T& cy04 = cam_K[OFFSET_DISTO_CY04]; + const T& cx24 = cam_K[OFFSET_DISTO_CX24]; + const T& cy24 = cam_K[OFFSET_DISTO_CY24]; + const T& cx44 = cam_K[OFFSET_DISTO_CX44]; + const T& cy44 = cam_K[OFFSET_DISTO_CY44]; + const T& phi = cam_K[OFFSET_DISTO_PHI]; + const T& sqx = cam_K[OFFSET_DISTO_SQX]; + const T& sqy = cam_K[OFFSET_DISTO_SQY]; + + const T cphi = cos(phi); + const T sphi = sin(phi); + + const T cx_xx = cx02 + cx22; + const T cx_yy = cx02 - cx22; + const T cx_xxyy = 2.0 * cx04 - 6.0 * cx44; + const T cx_xxxx = cx04 + cx24 + cx44; + const T cx_yyyy = cx04 - cx24 + cx44; + + const T cy_xx = cy02 + cy22; + const T cy_yy = cy02 - cy22; + const T cy_xxyy = 2.0 * cy04 - 6.0 * cy44; + const T cy_xxxx = cy04 + cy24 + cy44; + const T cy_yyyy = cy04 - cy24 + cy44; + + const T x = x_u; + const T y = y_u; + + const T xr = cphi * x + sphi * y; + const T yr = -sphi * x + cphi * y; + + const T xx = xr * xr; + const T yy = yr * yr; + const T xxxx = xx * xx; + const T yyyy = yy * yy; + const T xxyy = xx * yy; + + const T xd = xr * (1.0 + xx * cx_xx + yy * cx_yy + xxxx * cx_xxxx + xxyy * cx_xxyy + yyyy * cx_yyyy); + const T yd = yr * (1.0 + xx * cy_xx + yy * cy_yy + xxxx * cy_xxxx + xxyy * cy_xxyy + yyyy * cy_yyyy); + + const T squizzed_x = xd * sqx; + const T squizzed_y = yd * sqy; + + const T np_x = cphi * squizzed_x - sphi * squizzed_y; + const T np_y = sphi * squizzed_x + cphi * squizzed_y; + + // Apply focal length and principal point to get the final image coordinates + const T projected_x = principal_point_x + focalX * np_x; + const T projected_y = principal_point_y + focalY * np_y; + + // Compute and return the error is the difference between the predicted + // and observed position + const T scale(_obs.scale > 0.0 ? _obs.scale : 1.0); + out_residuals[0] = (projected_x - T(_obs.x[0])) / scale; + out_residuals[1] = (projected_y - T(_obs.x[1])) / scale; + } + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const subpose_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T* cam_R = cam_Rt; - const T* cam_t = &cam_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + T pos_proj[3]; + + { + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } + + { + const T* cam_R = subpose_Rt; + const T* cam_t = &subpose_Rt[3]; + + // Rotate the point according to the camera rotation + T pos_proj_tmp[3] = {pos_proj[0], pos_proj[1], pos_proj[2]}; + ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; + } - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } + /** + * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_K, const T* const cam_Rt, const T* const pos_3dpoint, T* out_residuals) const { - const T* cam_R = subpose_Rt; - const T* cam_t = &subpose_Rt[3]; + //-- + // Apply external parameters (Pose) + //-- + + const T* cam_R = cam_Rt; + const T* cam_t = &cam_Rt[3]; + + T pos_proj[3]; + // Rotate the point according the camera rotation + ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - // Rotate the point according to the camera rotation - T pos_proj_tmp[3] = { pos_proj[0], pos_proj[1], pos_proj[2] }; - ceres::AngleAxisRotatePoint(cam_R, pos_proj_tmp, pos_proj); + // Apply the camera translation + pos_proj[0] += cam_t[0]; + pos_proj[1] += cam_t[1]; + pos_proj[2] += cam_t[2]; - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; + // Transform the point from homogeneous to euclidean (undistorted point) + const T x_u = pos_proj[0] / pos_proj[2]; + const T y_u = pos_proj[1] / pos_proj[2]; + + //-- + // Apply intrinsic parameters + //-- + + applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); + + return true; } - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - /** - * @param[in] cam_K: Camera intrinsics( focal, principal point [x,y] ) - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_K, - const T* const cam_Rt, - const T* const pos_3dpoint, - T* out_residuals) const - { - //-- - // Apply external parameters (Pose) - //-- - - const T * cam_R = cam_Rt; - const T * cam_t = &cam_Rt[3]; - - T pos_proj[3]; - // Rotate the point according the camera rotation - ceres::AngleAxisRotatePoint(cam_R, pos_3dpoint, pos_proj); - - // Apply the camera translation - pos_proj[0] += cam_t[0]; - pos_proj[1] += cam_t[1]; - pos_proj[2] += cam_t[2]; - - // Transform the point from homogeneous to euclidean (undistorted point) - const T x_u = pos_proj[0] / pos_proj[2]; - const T y_u = pos_proj[1] / pos_proj[2]; - - //-- - // Apply intrinsic parameters - //-- - - applyIntrinsicParameters(cam_K, x_u, y_u, out_residuals); - - return true; - } - - const sfmData::Observation _obs; // The 2D observation - const Vec2 _center; + const sfmData::Observation _obs; // The 2D observation + const Vec2 _center; }; - -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/ResidualErrorRotationPriorFunctor.hpp b/src/aliceVision/sfm/ResidualErrorRotationPriorFunctor.hpp index d57aef4440..c64ba881f5 100644 --- a/src/aliceVision/sfm/ResidualErrorRotationPriorFunctor.hpp +++ b/src/aliceVision/sfm/ResidualErrorRotationPriorFunctor.hpp @@ -3,8 +3,6 @@ #include #include - - namespace aliceVision { namespace sfm { @@ -13,47 +11,42 @@ namespace sfm { * * Data parameter blocks are the following <3,6,6> * - 3 => dimension of the residuals, - * - 6 => the camera extrinsic data block for the first view - * - 6 => the camera extrinsic data block for the second view + * - 6 => the camera extrinsic data block for the first view + * - 6 => the camera extrinsic data block for the second view */ struct ResidualErrorRotationPriorFunctor { - explicit ResidualErrorRotationPriorFunctor(const Eigen::Matrix3d & two_R_one) - : m_two_R_one(two_R_one) - { - } - - /** - * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: - * - 3 for rotation(angle axis), 3 for translation - * @param[in] pos_3dpoint - * @param[out] out_residuals - */ - template - bool operator()( - const T* const cam_R1, - const T* const cam_R2, - T* out_residuals) const - { - Eigen::Matrix oneRo, twoRo, twoRone, R_error; - - ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); - ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); - twoRone = twoRo * oneRo.transpose(); - R_error = twoRone * m_two_R_one.transpose(); - - ceres::RotationMatrixToAngleAxis(R_error.data(), out_residuals); - - out_residuals[0] *= 180.0 / M_PI; - out_residuals[1] *= 180.0 / M_PI; - out_residuals[2] *= 180.0 / M_PI; - - - return true; - } - - Eigen::Matrix3d m_two_R_one; + explicit ResidualErrorRotationPriorFunctor(const Eigen::Matrix3d& two_R_one) + : m_two_R_one(two_R_one) + {} + + /** + * @param[in] cam_Rt: Camera parameterized using one block of 6 parameters [R;t]: + * - 3 for rotation(angle axis), 3 for translation + * @param[in] pos_3dpoint + * @param[out] out_residuals + */ + template + bool operator()(const T* const cam_R1, const T* const cam_R2, T* out_residuals) const + { + Eigen::Matrix oneRo, twoRo, twoRone, R_error; + + ceres::AngleAxisToRotationMatrix(cam_R1, oneRo.data()); + ceres::AngleAxisToRotationMatrix(cam_R2, twoRo.data()); + twoRone = twoRo * oneRo.transpose(); + R_error = twoRone * m_two_R_one.transpose(); + + ceres::RotationMatrixToAngleAxis(R_error.data(), out_residuals); + + out_residuals[0] *= 180.0 / M_PI; + out_residuals[1] *= 180.0 / M_PI; + out_residuals[2] *= 180.0 / M_PI; + + return true; + } + + Eigen::Matrix3d m_two_R_one; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/BundleAdjustment.hpp b/src/aliceVision/sfm/bundle/BundleAdjustment.hpp index bec56cadcb..1f8b2676a4 100644 --- a/src/aliceVision/sfm/bundle/BundleAdjustment.hpp +++ b/src/aliceVision/sfm/bundle/BundleAdjustment.hpp @@ -16,7 +16,7 @@ namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { @@ -25,44 +25,45 @@ namespace sfm { */ enum class EFeatureConstraint { - BASIC = 0, - SCALE = 1 + BASIC = 0, + SCALE = 1 }; /** -*@brief convert an enum ESfMobservationConstraint to its corresponding string -* -*/ + *@brief convert an enum ESfMobservationConstraint to its corresponding string + * + */ inline std::string ESfMobservationConstraint_enumToString(EFeatureConstraint m) { - switch(m) - { - case EFeatureConstraint::BASIC: return "Basic"; - case EFeatureConstraint::SCALE: return "Scale"; - } - throw std::out_of_range("Invalid ESfMobservationConstraint enum: " + std::to_string(int(m))); + switch (m) + { + case EFeatureConstraint::BASIC: + return "Basic"; + case EFeatureConstraint::SCALE: + return "Scale"; + } + throw std::out_of_range("Invalid ESfMobservationConstraint enum: " + std::to_string(int(m))); } /** -* @brief convert a string featureConstraint to its corresponding enum featureConstraint -* @param String -* @return ESfMobservationConstraint -*/ + * @brief convert a string featureConstraint to its corresponding enum featureConstraint + * @param String + * @return ESfMobservationConstraint + */ inline EFeatureConstraint ESfMobservationConstraint_stringToEnum(const std::string& m) { - std::string featureConstraint = m; - std::transform(featureConstraint.begin(), featureConstraint.end(), featureConstraint.begin(), ::tolower); + std::string featureConstraint = m; + std::transform(featureConstraint.begin(), featureConstraint.end(), featureConstraint.begin(), ::tolower); - if(featureConstraint == "basic") return EFeatureConstraint::BASIC; - if(featureConstraint == "scale") return EFeatureConstraint::SCALE; + if (featureConstraint == "basic") + return EFeatureConstraint::BASIC; + if (featureConstraint == "scale") + return EFeatureConstraint::SCALE; - throw std::out_of_range("Invalid ESfMobservationConstraint: " + m); + throw std::out_of_range("Invalid ESfMobservationConstraint: " + m); } -inline std::ostream& operator<<(std::ostream& os, EFeatureConstraint m) -{ - return os << ESfMobservationConstraint_enumToString(m); -} +inline std::ostream& operator<<(std::ostream& os, EFeatureConstraint m) { return os << ESfMobservationConstraint_enumToString(m); } inline std::istream& operator>>(std::istream& in, EFeatureConstraint& m) { @@ -74,60 +75,59 @@ inline std::istream& operator>>(std::istream& in, EFeatureConstraint& m) class BundleAdjustment { -public: - - /** - * @brief Defines all the types of parameter adjusted during bundle adjustment. - */ - enum class EParameter : std::uint8_t - { - POSE = 0, //< The pose - INTRINSIC = 1, //< The intrinsic - LANDMARK = 2 //< The landmark - }; - - /** - * @brief Defines the state of the all parameter of the reconstruction during bundle adjustment. - */ - enum class EParameterState : std::uint8_t - { - REFINED = 0, //< will be adjusted by the BA solver - CONSTANT = 1, //< will be set as constant in the sover - IGNORED = 2 //< will not be set into the BA solver - }; - - /** - * @brief Defines all the refine options that can be used in a bundle adjustment. - */ - enum ERefineOptions - { - REFINE_NONE = 0, - REFINE_ROTATION = 1, //< refine pose rotations - REFINE_TRANSLATION = 2, //< refine pose translations - REFINE_STRUCTURE = 4, //< refine structure (i.e. 3D points) - REFINE_INTRINSICS_FOCAL = 8, //< refine the focal length - REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS = 16, //< refine the optical offset from the center - REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA = 32, //< refine the optical offset only if we have a minimum number of cameras - REFINE_INTRINSICS_DISTORTION = 64, //< refine the distortion parameters - REFINE_STRUCTURE_AS_NORMALS = 128, //< Structure lies on a sphere (Pure rotation) - /// Refine all intrinsics parameters - REFINE_INTRINSICS_ALL = REFINE_INTRINSICS_FOCAL | REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA | REFINE_INTRINSICS_DISTORTION, - /// Refine all parameters - REFINE_ALL = REFINE_ROTATION | REFINE_TRANSLATION | REFINE_INTRINSICS_ALL | REFINE_STRUCTURE, - }; - - /** - * @brief Perform a Bundle Adjustment on the SfM scene with refinement of the requested parameters - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions: choose what you want to refine - * @return false if the bundle adjustment failed else true - */ - virtual bool adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions = REFINE_ALL) = 0; - - // TODO: Use filter to say wich parameter is const or not (allow to refine only a subpart of the intrinsics or the poses) + public: + /** + * @brief Defines all the types of parameter adjusted during bundle adjustment. + */ + enum class EParameter : std::uint8_t + { + POSE = 0, //< The pose + INTRINSIC = 1, //< The intrinsic + LANDMARK = 2 //< The landmark + }; + + /** + * @brief Defines the state of the all parameter of the reconstruction during bundle adjustment. + */ + enum class EParameterState : std::uint8_t + { + REFINED = 0, //< will be adjusted by the BA solver + CONSTANT = 1, //< will be set as constant in the sover + IGNORED = 2 //< will not be set into the BA solver + }; + + /** + * @brief Defines all the refine options that can be used in a bundle adjustment. + */ + enum ERefineOptions + { + REFINE_NONE = 0, + REFINE_ROTATION = 1, //< refine pose rotations + REFINE_TRANSLATION = 2, //< refine pose translations + REFINE_STRUCTURE = 4, //< refine structure (i.e. 3D points) + REFINE_INTRINSICS_FOCAL = 8, //< refine the focal length + REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS = 16, //< refine the optical offset from the center + REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA = 32, //< refine the optical offset only if we have a minimum number of cameras + REFINE_INTRINSICS_DISTORTION = 64, //< refine the distortion parameters + REFINE_STRUCTURE_AS_NORMALS = 128, //< Structure lies on a sphere (Pure rotation) + /// Refine all intrinsics parameters + REFINE_INTRINSICS_ALL = REFINE_INTRINSICS_FOCAL | REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA | REFINE_INTRINSICS_DISTORTION, + /// Refine all parameters + REFINE_ALL = REFINE_ROTATION | REFINE_TRANSLATION | REFINE_INTRINSICS_ALL | REFINE_STRUCTURE, + }; + + /** + * @brief Perform a Bundle Adjustment on the SfM scene with refinement of the requested parameters + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions: choose what you want to refine + * @return false if the bundle adjustment failed else true + */ + virtual bool adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions = REFINE_ALL) = 0; + + // TODO: Use filter to say wich parameter is const or not (allow to refine only a subpart of the intrinsics or the poses) }; BOOST_BITMASK(BundleAdjustment::ERefineOptions) -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp b/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp index 526b702ab5..554605b049 100644 --- a/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp +++ b/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp @@ -21,7 +21,6 @@ #include #include - namespace fs = boost::filesystem; namespace aliceVision { @@ -30,163 +29,159 @@ namespace sfm { using namespace aliceVision::camera; using namespace aliceVision::geometry; -class IntrinsicsManifold : public ceres::Manifold { - public: - explicit IntrinsicsManifold(size_t parametersSize, double focalRatio, bool lockFocal, bool lockFocalRatio, bool lockCenter, bool lockDistortion) - : _ambientSize(parametersSize), - _focalRatio(focalRatio), - _lockFocal(lockFocal), - _lockFocalRatio(lockFocalRatio), - _lockCenter(lockCenter), - _lockDistortion(lockDistortion) - { - _distortionSize = _ambientSize - 4; - _tangentSize = 0; - - if (!_lockFocal) +class IntrinsicsManifold : public ceres::Manifold +{ + public: + explicit IntrinsicsManifold(size_t parametersSize, double focalRatio, bool lockFocal, bool lockFocalRatio, bool lockCenter, bool lockDistortion) + : _ambientSize(parametersSize), + _focalRatio(focalRatio), + _lockFocal(lockFocal), + _lockFocalRatio(lockFocalRatio), + _lockCenter(lockCenter), + _lockDistortion(lockDistortion) { - if (_lockFocalRatio) - { - _tangentSize += 1; - } - else - { - _tangentSize += 2; - } + _distortionSize = _ambientSize - 4; + _tangentSize = 0; + + if (!_lockFocal) + { + if (_lockFocalRatio) + { + _tangentSize += 1; + } + else + { + _tangentSize += 2; + } + } + + if (!_lockCenter) + { + _tangentSize += 2; + } + + if (!_lockDistortion) + { + _tangentSize += _distortionSize; + } } - if (!_lockCenter) - { - _tangentSize += 2; - } + virtual ~IntrinsicsManifold() = default; - if (!_lockDistortion) + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { - _tangentSize += _distortionSize; + for (int i = 0; i < _ambientSize; i++) + { + x_plus_delta[i] = x[i]; + } + + size_t posDelta = 0; + if (!_lockFocal) + { + if (_lockFocalRatio) + { + x_plus_delta[0] = x[0] + _focalRatio * delta[posDelta]; + x_plus_delta[1] = x[1] + delta[posDelta]; + posDelta++; + } + else + { + x_plus_delta[0] = x[0] + delta[posDelta]; + posDelta++; + x_plus_delta[1] = x[1] + delta[posDelta]; + posDelta++; + } + } + + if (!_lockCenter) + { + x_plus_delta[2] = x[2] + delta[posDelta]; + posDelta++; + + x_plus_delta[3] = x[3] + delta[posDelta]; + posDelta++; + } + + if (!_lockDistortion) + { + for (int i = 0; i < _distortionSize; i++) + { + x_plus_delta[4 + i] = x[4 + i] + delta[posDelta]; + posDelta++; + } + } + + return true; } - } - - virtual ~IntrinsicsManifold() = default; - - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override - { - for (int i = 0; i < _ambientSize; i++) - { - x_plus_delta[i] = x[i]; - } - - size_t posDelta = 0; - if (!_lockFocal) + bool PlusJacobian(const double* x, double* jacobian) const override { - if (_lockFocalRatio) - { - x_plus_delta[0] = x[0] + _focalRatio * delta[posDelta]; - x_plus_delta[1] = x[1] + delta[posDelta]; - posDelta++; - } - else - { - x_plus_delta[0] = x[0] + delta[posDelta]; - posDelta++; - x_plus_delta[1] = x[1] + delta[posDelta]; - posDelta++; - } + Eigen::Map> J(jacobian, AmbientSize(), TangentSize()); + + J.fill(0); + + size_t posDelta = 0; + if (!_lockFocal) + { + if (_lockFocalRatio) + { + J(0, posDelta) = _focalRatio; + J(1, posDelta) = 1.0; + posDelta++; + } + else + { + J(0, posDelta) = 1.0; + posDelta++; + J(1, posDelta) = 1.0; + posDelta++; + } + } + + if (!_lockCenter) + { + J(2, posDelta) = 1.0; + posDelta++; + + J(3, posDelta) = 1.0; + posDelta++; + } + + if (!_lockDistortion) + { + for (int i = 0; i < _distortionSize; i++) + { + J(4 + i, posDelta) = 1.0; + posDelta++; + } + } + + return true; } - if (!_lockCenter) + bool Minus(const double* y, const double* x, double* delta) const override { - x_plus_delta[2] = x[2] + delta[posDelta]; - posDelta++; - - x_plus_delta[3] = x[3] + delta[posDelta]; - posDelta++; + throw std::invalid_argument("IntrinsicsManifold::Minus() should never be called"); } - if (!_lockDistortion) + bool MinusJacobian(const double* x, double* jacobian) const override { - for (int i = 0; i < _distortionSize; i++) - { - x_plus_delta[4 + i] = x[4 + i] + delta[posDelta]; - posDelta++; - } + throw std::invalid_argument("IntrinsicsManifold::MinusJacobian() should never be called"); } - return true; - } - - bool PlusJacobian(const double* x, double* jacobian) const override - { - Eigen::Map> J(jacobian, AmbientSize(), TangentSize()); - - J.fill(0); - - size_t posDelta = 0; - if (!_lockFocal) - { - if (_lockFocalRatio) - { - J(0, posDelta) = _focalRatio; - J(1, posDelta) = 1.0; - posDelta++; - } - else - { - J(0, posDelta) = 1.0; - posDelta++; - J(1, posDelta) = 1.0; - posDelta++; - } - } + int AmbientSize() const override { return _ambientSize; } - if (!_lockCenter) - { - J(2, posDelta) = 1.0; - posDelta++; + int TangentSize() const override { return _tangentSize; } - J(3, posDelta) = 1.0; - posDelta++; - } - - if (!_lockDistortion) - { - for (int i = 0; i < _distortionSize; i++) - { - J(4 + i, posDelta) = 1.0; - posDelta++; - } - } - - return true; - } - - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("IntrinsicsManifold::Minus() should never be called"); - } - - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("IntrinsicsManifold::MinusJacobian() should never be called"); - } - - int AmbientSize() const override - { - return _ambientSize; - } - - int TangentSize() const override - { - return _tangentSize; - } - - private: - size_t _distortionSize; - size_t _ambientSize; - size_t _tangentSize; - double _focalRatio; - bool _lockFocal; - bool _lockFocalRatio; - bool _lockCenter; - bool _lockDistortion; + private: + size_t _distortionSize; + size_t _ambientSize; + size_t _tangentSize; + double _focalRatio; + bool _lockFocal; + bool _lockFocalRatio; + bool _lockCenter; + bool _lockDistortion; }; /** @@ -197,54 +192,51 @@ class IntrinsicsManifold : public ceres::Manifold { */ ceres::CostFunction* createCostFunctionFromIntrinsics(const IntrinsicBase* intrinsicPtr, const sfmData::Observation& observation) { - int w = intrinsicPtr->w(); - int h = intrinsicPtr->h(); - - // Apply undistortion to observation - sfmData::Observation obsUndistorted = observation; - const camera::IntrinsicScaleOffsetDisto* intrinsicDistortionPtr = - dynamic_cast(intrinsicPtr); - if (intrinsicDistortionPtr) - { - auto undistortion = intrinsicDistortionPtr->getUndistortion(); - if (undistortion) + int w = intrinsicPtr->w(); + int h = intrinsicPtr->h(); + + // Apply undistortion to observation + sfmData::Observation obsUndistorted = observation; + const camera::IntrinsicScaleOffsetDisto* intrinsicDistortionPtr = dynamic_cast(intrinsicPtr); + if (intrinsicDistortionPtr) + { + auto undistortion = intrinsicDistortionPtr->getUndistortion(); + if (undistortion) + { + obsUndistorted.x = undistortion->undistort(observation.x); + } + } + + switch (intrinsicPtr->getType()) { - obsUndistorted.x = undistortion->undistort(observation.x); + case EINTRINSIC::PINHOLE_CAMERA: + return new ceres::AutoDiffCostFunction(new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeRadialK1(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeRadialK3(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_Pinhole3DERadial4(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_Pinhole3DEClassicLD(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: + return new ceres::AutoDiffCostFunction(new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_BROWN: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeBrownT2(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeFisheye(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeFisheye1(w, h, obsUndistorted)); + default: + throw std::logic_error("Cannot create cost function, unrecognized intrinsic type in BA."); } - } - - switch(intrinsicPtr->getType()) - { - case EINTRINSIC::PINHOLE_CAMERA: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeRadialK1(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeRadialK3(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole3DERadial4(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole3DEClassicLD(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_BROWN: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeBrownT2(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeFisheye(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeFisheye1(w, h, obsUndistorted)); - default: - throw std::logic_error("Cannot create cost function, unrecognized intrinsic type in BA."); - } } /** @@ -255,54 +247,53 @@ ceres::CostFunction* createCostFunctionFromIntrinsics(const IntrinsicBase* intri */ ceres::CostFunction* createRigCostFunctionFromIntrinsics(const IntrinsicBase* intrinsicPtr, const sfmData::Observation& observation) { - int w = intrinsicPtr->w(); - int h = intrinsicPtr->h(); - - // Apply undistortion to observation - sfmData::Observation obsUndistorted = observation; - const camera::IntrinsicScaleOffsetDisto* intrinsicDistortionPtr = - dynamic_cast(intrinsicPtr); - if (intrinsicDistortionPtr) - { - auto undistortion = intrinsicDistortionPtr->getUndistortion(); - if (undistortion) + int w = intrinsicPtr->w(); + int h = intrinsicPtr->h(); + + // Apply undistortion to observation + sfmData::Observation obsUndistorted = observation; + const camera::IntrinsicScaleOffsetDisto* intrinsicDistortionPtr = dynamic_cast(intrinsicPtr); + if (intrinsicDistortionPtr) + { + auto undistortion = intrinsicDistortionPtr->getUndistortion(); + if (undistortion) + { + obsUndistorted.x = undistortion->undistort(observation.x); + } + } + + switch (intrinsicPtr->getType()) { - obsUndistorted.x = undistortion->undistort(observation.x); + case EINTRINSIC::PINHOLE_CAMERA: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeRadialK1(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeRadialK3(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_Pinhole3DERadial4(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_Pinhole3DEClassicLD(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_BROWN: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeBrownT2(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeFisheye(w, h, obsUndistorted)); + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: + return new ceres::AutoDiffCostFunction( + new ResidualErrorFunctor_PinholeFisheye1(w, h, obsUndistorted)); + default: + throw std::logic_error("Cannot create rig cost function, unrecognized intrinsic type in BA."); } - } - - switch(intrinsicPtr->getType()) - { - case EINTRINSIC::PINHOLE_CAMERA: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeRadialK1(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeRadialK3(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole3DERadial4(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole3DEClassicLD(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_Pinhole(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_BROWN: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeBrownT2(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeFisheye(w, h, obsUndistorted)); - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE1: - return new ceres::AutoDiffCostFunction( - new ResidualErrorFunctor_PinholeFisheye1(w, h, obsUndistorted)); - default: - throw std::logic_error("Cannot create rig cost function, unrecognized intrinsic type in BA."); - } } /** @@ -311,801 +302,807 @@ ceres::CostFunction* createRigCostFunctionFromIntrinsics(const IntrinsicBase* in * @param[in] observation The corresponding observation * @return cost functor */ -ceres::CostFunction* createConstraintsCostFunctionFromIntrinsics(const IntrinsicBase* intrinsicPtr, const Vec2& observation_first, const Vec2& observation_second) +ceres::CostFunction* createConstraintsCostFunctionFromIntrinsics(const IntrinsicBase* intrinsicPtr, + const Vec2& observation_first, + const Vec2& observation_second) { - double radius = 0.0; - const camera::Equidistant * equi = dynamic_cast(intrinsicPtr); - if (equi) { - radius = equi->getCircleRadius(); - } - - int w = intrinsicPtr->w(); - int h = intrinsicPtr->h(); - - switch(intrinsicPtr->getType()) - { - case EINTRINSIC::PINHOLE_CAMERA: - return new ceres::AutoDiffCostFunction(new ResidualErrorConstraintFunctor_Pinhole(w, h, observation_first.homogeneous(), observation_second.homogeneous())); - case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: - return new ceres::AutoDiffCostFunction(new ResidualErrorConstraintFunctor_PinholeRadialK1(w, h, observation_first.homogeneous(), observation_second.homogeneous())); - case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: - return new ceres::AutoDiffCostFunction(new ResidualErrorConstraintFunctor_PinholeRadialK3(w, h, observation_first.homogeneous(), observation_second.homogeneous())); - case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: - return new ceres::AutoDiffCostFunction(new ResidualErrorConstraintFunctor_PinholeFisheye(w, h, observation_first.homogeneous(), observation_second.homogeneous())); - case EQUIDISTANT_CAMERA: - return new ceres::AutoDiffCostFunction(new ResidualErrorConstraintFunctor_Equidistant(w, h, observation_first.homogeneous(), observation_second.homogeneous(), radius)); - case EQUIDISTANT_CAMERA_RADIAL3: - return new ceres::AutoDiffCostFunction(new ResidualErrorConstraintFunctor_EquidistantRadialK3(w, h, observation_first.homogeneous(), observation_second.homogeneous(), radius)); - default: - throw std::logic_error("Cannot create cost function, unrecognized intrinsic type in BA."); - } + double radius = 0.0; + const camera::Equidistant* equi = dynamic_cast(intrinsicPtr); + if (equi) + { + radius = equi->getCircleRadius(); + } + + int w = intrinsicPtr->w(); + int h = intrinsicPtr->h(); + + switch (intrinsicPtr->getType()) + { + case EINTRINSIC::PINHOLE_CAMERA: + return new ceres::AutoDiffCostFunction( + new ResidualErrorConstraintFunctor_Pinhole(w, h, observation_first.homogeneous(), observation_second.homogeneous())); + case EINTRINSIC::PINHOLE_CAMERA_RADIAL1: + return new ceres::AutoDiffCostFunction( + new ResidualErrorConstraintFunctor_PinholeRadialK1(w, h, observation_first.homogeneous(), observation_second.homogeneous())); + case EINTRINSIC::PINHOLE_CAMERA_RADIAL3: + return new ceres::AutoDiffCostFunction( + new ResidualErrorConstraintFunctor_PinholeRadialK3(w, h, observation_first.homogeneous(), observation_second.homogeneous())); + case EINTRINSIC::PINHOLE_CAMERA_FISHEYE: + return new ceres::AutoDiffCostFunction( + new ResidualErrorConstraintFunctor_PinholeFisheye(w, h, observation_first.homogeneous(), observation_second.homogeneous())); + case EQUIDISTANT_CAMERA: + return new ceres::AutoDiffCostFunction( + new ResidualErrorConstraintFunctor_Equidistant(w, h, observation_first.homogeneous(), observation_second.homogeneous(), radius)); + case EQUIDISTANT_CAMERA_RADIAL3: + return new ceres::AutoDiffCostFunction( + new ResidualErrorConstraintFunctor_EquidistantRadialK3( + w, h, observation_first.homogeneous(), observation_second.homogeneous(), radius)); + default: + throw std::logic_error("Cannot create cost function, unrecognized intrinsic type in BA."); + } } void BundleAdjustmentCeres::CeresOptions::setDenseBA() { - // default configuration use a DENSE representation - preconditionerType = ceres::JACOBI; - linearSolverType = ceres::DENSE_SCHUR; - sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; // not used but just to avoid a warning in ceres - ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: DENSE_SCHUR"); + // default configuration use a DENSE representation + preconditionerType = ceres::JACOBI; + linearSolverType = ceres::DENSE_SCHUR; + sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; // not used but just to avoid a warning in ceres + ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: DENSE_SCHUR"); } void BundleAdjustmentCeres::CeresOptions::setSparseBA() { - preconditionerType = ceres::JACOBI; - // if Sparse linear solver are available - // descending priority order by efficiency (SUITE_SPARSE > CX_SPARSE > EIGEN_SPARSE) - if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE)) - { - sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; - linearSolverType = ceres::SPARSE_SCHUR; - ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: SPARSE_SCHUR, SUITE_SPARSE"); - } + preconditionerType = ceres::JACOBI; + // if Sparse linear solver are available + // descending priority order by efficiency (SUITE_SPARSE > CX_SPARSE > EIGEN_SPARSE) + if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE)) + { + sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; + linearSolverType = ceres::SPARSE_SCHUR; + ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: SPARSE_SCHUR, SUITE_SPARSE"); + } #if ALICEVISION_CERES_HAS_CXSPARSE - else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE)) - { - sparseLinearAlgebraLibraryType = ceres::CX_SPARSE; - linearSolverType = ceres::SPARSE_SCHUR; - ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: SPARSE_SCHUR, CX_SPARSE"); - } + else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE)) + { + sparseLinearAlgebraLibraryType = ceres::CX_SPARSE; + linearSolverType = ceres::SPARSE_SCHUR; + ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: SPARSE_SCHUR, CX_SPARSE"); + } #endif - else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) - { - sparseLinearAlgebraLibraryType = ceres::EIGEN_SPARSE; - linearSolverType = ceres::SPARSE_SCHUR; - ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: SPARSE_SCHUR, EIGEN_SPARSE"); - } - else - { - linearSolverType = ceres::DENSE_SCHUR; - ALICEVISION_LOG_WARNING("BundleAdjustment[Ceres]: no sparse BA available, fallback to dense BA."); - } + else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) + { + sparseLinearAlgebraLibraryType = ceres::EIGEN_SPARSE; + linearSolverType = ceres::SPARSE_SCHUR; + ALICEVISION_LOG_DEBUG("BundleAdjustment[Ceres]: SPARSE_SCHUR, EIGEN_SPARSE"); + } + else + { + linearSolverType = ceres::DENSE_SCHUR; + ALICEVISION_LOG_WARNING("BundleAdjustment[Ceres]: no sparse BA available, fallback to dense BA."); + } } bool BundleAdjustmentCeres::Statistics::exportToFile(const std::string& folder, const std::string& filename) const { - std::ofstream os; - os.open((fs::path(folder) / filename).string(), std::ios::app); - - if(!os.is_open()) - { - ALICEVISION_LOG_DEBUG("Unable to open the Bundle adjustment statistics file: '" << filename << "'."); - return false; - } - - os.seekp(0, std::ios::end); // put the cursor at the end - - if(os.tellp() == std::streampos(0)) // 'tellp' return the cursor's position - { - // if the file does't exist: add a header. - os << "Time/BA(s);RefinedPose;ConstPose;IgnoredPose;" - "RefinedPts;ConstPts;IgnoredPts;" - "RefinedK;ConstK;IgnoredK;" - "ResidualBlocks;SuccessIteration;BadIteration;" - "InitRMSE;FinalRMSE;" - "d=-1;d=0;d=1;d=2;d=3;d=4;" - "d=5;d=6;d=7;d=8;d=9;d=10+;\n"; - } - - std::map> states = parametersStates; - std::size_t posesWithDistUpperThanTen = 0; - - for(const auto& it : nbCamerasPerDistance) - if (it.first >= 10) - posesWithDistUpperThanTen += it.second; - - os << time << ";" - << states[EParameter::POSE][EParameterState::REFINED] << ";" - << states[EParameter::POSE][EParameterState::CONSTANT] << ";" - << states[EParameter::POSE][EParameterState::IGNORED] << ";" - << states[EParameter::LANDMARK][EParameterState::REFINED] << ";" - << states[EParameter::LANDMARK][EParameterState::CONSTANT] << ";" - << states[EParameter::LANDMARK][EParameterState::IGNORED] << ";" - << states[EParameter::INTRINSIC][EParameterState::REFINED] << ";" - << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << ";" - << states[EParameter::INTRINSIC][EParameterState::IGNORED] << ";" - << nbResidualBlocks << ";" - << nbSuccessfullIterations << ";" - << nbUnsuccessfullIterations << ";" - << RMSEinitial << ";" - << RMSEfinal << ";"; - - for(int i = -1; i < 10; ++i) - { - auto cdIt = nbCamerasPerDistance.find(i); - if(cdIt != nbCamerasPerDistance.end()) - os << cdIt->second << ";"; - else - os << "0;"; - } - - os << posesWithDistUpperThanTen << ";\n"; - - os.close(); - return true; -} + std::ofstream os; + os.open((fs::path(folder) / filename).string(), std::ios::app); -void BundleAdjustmentCeres::Statistics::show() const -{ - std::map> states = parametersStates; - std::stringstream ss; + if (!os.is_open()) + { + ALICEVISION_LOG_DEBUG("Unable to open the Bundle adjustment statistics file: '" << filename << "'."); + return false; + } - if(!nbCamerasPerDistance.empty()) - { - std::size_t nbCamNotConnected = 0; - std::size_t nbCamDistEqZero = 0; - std::size_t nbCamDistEqOne = 0; - std::size_t nbCamDistUpperOne = 0; + os.seekp(0, std::ios::end); // put the cursor at the end - for(const auto & camdistIt : nbCamerasPerDistance) + if (os.tellp() == std::streampos(0)) // 'tellp' return the cursor's position { - if(camdistIt.first < 0) - nbCamNotConnected += camdistIt.second; - else if(camdistIt.first == 1) - nbCamDistEqZero += camdistIt.second; - else if(camdistIt.first == 1) - nbCamDistEqOne += camdistIt.second; - else if(camdistIt.first > 1) - nbCamDistUpperOne += camdistIt.second; + // if the file does't exist: add a header. + os << "Time/BA(s);RefinedPose;ConstPose;IgnoredPose;" + "RefinedPts;ConstPts;IgnoredPts;" + "RefinedK;ConstK;IgnoredK;" + "ResidualBlocks;SuccessIteration;BadIteration;" + "InitRMSE;FinalRMSE;" + "d=-1;d=0;d=1;d=2;d=3;d=4;" + "d=5;d=6;d=7;d=8;d=9;d=10+;\n"; } - ss << "\t- local strategy enabled: yes\n" - << "\t- graph-distances distribution:\n" - << "\t - not connected: " << nbCamNotConnected << " cameras\n" - << "\t - D = 0: " << nbCamDistEqZero << " cameras\n" - << "\t - D = 1: " << nbCamDistEqOne << " cameras\n" - << "\t - D > 1: " << nbCamDistUpperOne << " cameras\n"; - } - else - { - ss << "\t- local strategy enabled: no\n"; - } - - ALICEVISION_LOG_INFO("Bundle Adjustment Statistics:\n" - << ss.str() - << "\t- adjustment duration: " << time << " s\n" - << "\t- poses:\n" - << "\t - # refined: " << states[EParameter::POSE][EParameterState::REFINED] << "\n" - << "\t - # constant: " << states[EParameter::POSE][EParameterState::CONSTANT] << "\n" - << "\t - # ignored: " << states[EParameter::POSE][EParameterState::IGNORED] << "\n" - << "\t- landmarks:\n" - << "\t - # refined: " << states[EParameter::LANDMARK][EParameterState::REFINED] << "\n" - << "\t - # constant: " << states[EParameter::LANDMARK][EParameterState::CONSTANT] << "\n" - << "\t - # ignored: " << states[EParameter::LANDMARK][EParameterState::IGNORED] << "\n" - << "\t- intrinsics:\n" - << "\t - # refined: " << states[EParameter::INTRINSIC][EParameterState::REFINED] << "\n" - << "\t - # constant: " << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << "\n" - << "\t - # ignored: " << states[EParameter::INTRINSIC][EParameterState::IGNORED] << "\n" - << "\t- # residual blocks: " << nbResidualBlocks << "\n" - << "\t- # successful iterations: " << nbSuccessfullIterations << "\n" - << "\t- # unsuccessful iterations: " << nbUnsuccessfullIterations << "\n" - << "\t- initial RMSE: " << RMSEinitial << "\n" - << "\t- final RMSE: " << RMSEfinal); -} + std::map> states = parametersStates; + std::size_t posesWithDistUpperThanTen = 0; -void BundleAdjustmentCeres::setSolverOptions(ceres::Solver::Options& solverOptions) const -{ - solverOptions.preconditioner_type = _ceresOptions.preconditionerType; - solverOptions.linear_solver_type = _ceresOptions.linearSolverType; - solverOptions.sparse_linear_algebra_library_type = _ceresOptions.sparseLinearAlgebraLibraryType; - solverOptions.minimizer_progress_to_stdout = _ceresOptions.verbose; - solverOptions.logging_type = ceres::SILENT; - solverOptions.num_threads = _ceresOptions.nbThreads; - solverOptions.max_num_iterations = _ceresOptions.maxNumIterations; - /*solverOptions.function_tolerance = 1e-12; - solverOptions.gradient_tolerance = 1e-12; - solverOptions.parameter_tolerance = 1e-12;*/ - -#if CERES_VERSION_MAJOR < 2 - solverOptions.num_linear_solver_threads = _ceresOptions.nbThreads; -#endif + for (const auto& it : nbCamerasPerDistance) + if (it.first >= 10) + posesWithDistUpperThanTen += it.second; - if(_ceresOptions.useParametersOrdering) - { - // copy ParameterBlockOrdering - solverOptions.linear_solver_ordering.reset(new ceres::ParameterBlockOrdering(_linearSolverOrdering)); - } -} + os << time << ";" << states[EParameter::POSE][EParameterState::REFINED] << ";" << states[EParameter::POSE][EParameterState::CONSTANT] << ";" + << states[EParameter::POSE][EParameterState::IGNORED] << ";" << states[EParameter::LANDMARK][EParameterState::REFINED] << ";" + << states[EParameter::LANDMARK][EParameterState::CONSTANT] << ";" << states[EParameter::LANDMARK][EParameterState::IGNORED] << ";" + << states[EParameter::INTRINSIC][EParameterState::REFINED] << ";" << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << ";" + << states[EParameter::INTRINSIC][EParameterState::IGNORED] << ";" << nbResidualBlocks << ";" << nbSuccessfullIterations << ";" + << nbUnsuccessfullIterations << ";" << RMSEinitial << ";" << RMSEfinal << ";"; -void BundleAdjustmentCeres::addExtrinsicsToProblem(const sfmData::SfMData& sfmData, BundleAdjustment::ERefineOptions refineOptions, ceres::Problem& problem) -{ - const bool refineTranslation = refineOptions & BundleAdjustment::REFINE_TRANSLATION; - const bool refineRotation = refineOptions & BundleAdjustment::REFINE_ROTATION; - - const auto addPose = [&](const sfmData::CameraPose& cameraPose, bool isConstant, std::array& poseBlock) - { - const Mat3& R = cameraPose.getTransform().rotation(); - const Vec3& t = cameraPose.getTransform().translation(); - - double angleAxis[3]; - ceres::RotationMatrixToAngleAxis(static_cast(R.data()), angleAxis); - poseBlock.at(0) = angleAxis[0]; - poseBlock.at(1) = angleAxis[1]; - poseBlock.at(2) = angleAxis[2]; - poseBlock.at(3) = t(0); - poseBlock.at(4) = t(1); - poseBlock.at(5) = t(2); - - double* poseBlockPtr = poseBlock.data(); - problem.AddParameterBlock(poseBlockPtr, 6); - - // add pose parameter to the all parameters blocks pointers list - _allParametersBlocks.push_back(poseBlockPtr); - - // keep the camera extrinsics constants - if(cameraPose.isLocked() || isConstant || (!refineTranslation && !refineRotation)) + for (int i = -1; i < 10; ++i) { - // set the whole parameter block as constant. - _statistics.addState(EParameter::POSE, EParameterState::CONSTANT); - problem.SetParameterBlockConstant(poseBlockPtr); - return; + auto cdIt = nbCamerasPerDistance.find(i); + if (cdIt != nbCamerasPerDistance.end()) + os << cdIt->second << ";"; + else + os << "0;"; } - // constant parameters - std::vector constantExtrinsic; + os << posesWithDistUpperThanTen << ";\n"; - // don't refine rotations - if(!refineRotation) - { - constantExtrinsic.push_back(0); - constantExtrinsic.push_back(1); - constantExtrinsic.push_back(2); - } + os.close(); + return true; +} - // don't refine translations - if(!refineTranslation) - { - constantExtrinsic.push_back(3); - constantExtrinsic.push_back(4); - constantExtrinsic.push_back(5); - } +void BundleAdjustmentCeres::Statistics::show() const +{ + std::map> states = parametersStates; + std::stringstream ss; - // subset parametrization - if(!constantExtrinsic.empty()) + if (!nbCamerasPerDistance.empty()) { - auto* subsetManifold = new ceres::SubsetManifold(6, constantExtrinsic); - problem.SetManifold(poseBlockPtr, subsetManifold); + std::size_t nbCamNotConnected = 0; + std::size_t nbCamDistEqZero = 0; + std::size_t nbCamDistEqOne = 0; + std::size_t nbCamDistUpperOne = 0; + + for (const auto& camdistIt : nbCamerasPerDistance) + { + if (camdistIt.first < 0) + nbCamNotConnected += camdistIt.second; + else if (camdistIt.first == 1) + nbCamDistEqZero += camdistIt.second; + else if (camdistIt.first == 1) + nbCamDistEqOne += camdistIt.second; + else if (camdistIt.first > 1) + nbCamDistUpperOne += camdistIt.second; + } + + ss << "\t- local strategy enabled: yes\n" + << "\t- graph-distances distribution:\n" + << "\t - not connected: " << nbCamNotConnected << " cameras\n" + << "\t - D = 0: " << nbCamDistEqZero << " cameras\n" + << "\t - D = 1: " << nbCamDistEqOne << " cameras\n" + << "\t - D > 1: " << nbCamDistUpperOne << " cameras\n"; } - - _statistics.addState(EParameter::POSE, EParameterState::REFINED); - }; - - // setup poses data - for(const auto& posePair : sfmData.getPoses()) - { - const IndexT poseId = posePair.first; - const sfmData::CameraPose& pose = posePair.second; - - // skip camera pose set as Ignored in the Local strategy - if(getPoseState(poseId) == EParameterState::IGNORED) + else { - _statistics.addState(EParameter::POSE, EParameterState::IGNORED); - continue; + ss << "\t- local strategy enabled: no\n"; } - const bool isConstant = (getPoseState(poseId) == EParameterState::CONSTANT); + ALICEVISION_LOG_INFO("Bundle Adjustment Statistics:\n" + << ss.str() << "\t- adjustment duration: " << time << " s\n" + << "\t- poses:\n" + << "\t - # refined: " << states[EParameter::POSE][EParameterState::REFINED] << "\n" + << "\t - # constant: " << states[EParameter::POSE][EParameterState::CONSTANT] << "\n" + << "\t - # ignored: " << states[EParameter::POSE][EParameterState::IGNORED] << "\n" + << "\t- landmarks:\n" + << "\t - # refined: " << states[EParameter::LANDMARK][EParameterState::REFINED] << "\n" + << "\t - # constant: " << states[EParameter::LANDMARK][EParameterState::CONSTANT] << "\n" + << "\t - # ignored: " << states[EParameter::LANDMARK][EParameterState::IGNORED] << "\n" + << "\t- intrinsics:\n" + << "\t - # refined: " << states[EParameter::INTRINSIC][EParameterState::REFINED] << "\n" + << "\t - # constant: " << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << "\n" + << "\t - # ignored: " << states[EParameter::INTRINSIC][EParameterState::IGNORED] << "\n" + << "\t- # residual blocks: " << nbResidualBlocks << "\n" + << "\t- # successful iterations: " << nbSuccessfullIterations << "\n" + << "\t- # unsuccessful iterations: " << nbUnsuccessfullIterations << "\n" + << "\t- initial RMSE: " << RMSEinitial << "\n" + << "\t- final RMSE: " << RMSEfinal); +} - addPose(pose, isConstant, _posesBlocks[poseId]); - } +void BundleAdjustmentCeres::setSolverOptions(ceres::Solver::Options& solverOptions) const +{ + solverOptions.preconditioner_type = _ceresOptions.preconditionerType; + solverOptions.linear_solver_type = _ceresOptions.linearSolverType; + solverOptions.sparse_linear_algebra_library_type = _ceresOptions.sparseLinearAlgebraLibraryType; + solverOptions.minimizer_progress_to_stdout = _ceresOptions.verbose; + solverOptions.logging_type = ceres::SILENT; + solverOptions.num_threads = _ceresOptions.nbThreads; + solverOptions.max_num_iterations = _ceresOptions.maxNumIterations; + /*solverOptions.function_tolerance = 1e-12; + solverOptions.gradient_tolerance = 1e-12; + solverOptions.parameter_tolerance = 1e-12;*/ - // setup sub-poses data - for(const auto& rigPair : sfmData.getRigs()) - { - const IndexT rigId = rigPair.first; - const sfmData::Rig& rig = rigPair.second; - const std::size_t nbSubPoses = rig.getNbSubPoses(); +#if CERES_VERSION_MAJOR < 2 + solverOptions.num_linear_solver_threads = _ceresOptions.nbThreads; +#endif - for(std::size_t subPoseId = 0 ; subPoseId < nbSubPoses; ++subPoseId) + if (_ceresOptions.useParametersOrdering) { - const sfmData::RigSubPose& rigSubPose = rig.getSubPose(subPoseId); - - if(rigSubPose.status == sfmData::ERigSubPoseStatus::UNINITIALIZED) - continue; - - const bool isConstant = (rigSubPose.status == sfmData::ERigSubPoseStatus::CONSTANT); - - addPose(sfmData::CameraPose(rigSubPose.pose), isConstant, _rigBlocks[rigId][subPoseId]); + // copy ParameterBlockOrdering + solverOptions.linear_solver_ordering.reset(new ceres::ParameterBlockOrdering(_linearSolverOrdering)); } - } } -void BundleAdjustmentCeres::addIntrinsicsToProblem(const sfmData::SfMData& sfmData, BundleAdjustment::ERefineOptions refineOptions, ceres::Problem& problem) +void BundleAdjustmentCeres::addExtrinsicsToProblem(const sfmData::SfMData& sfmData, + BundleAdjustment::ERefineOptions refineOptions, + ceres::Problem& problem) { - const bool refineIntrinsicsOpticalCenter = (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); - const bool refineIntrinsicsFocalLength = refineOptions & REFINE_INTRINSICS_FOCAL; - const bool refineIntrinsicsDistortion = refineOptions & REFINE_INTRINSICS_DISTORTION; - const bool refineIntrinsics = refineIntrinsicsDistortion || refineIntrinsicsFocalLength || refineIntrinsicsOpticalCenter; - - std::map intrinsicsUsage; - - // count the number of reconstructed views per intrinsic - for(const auto& viewPair: sfmData.getViews()) - { - const sfmData::View& view = *(viewPair.second); - - if(intrinsicsUsage.find(view.getIntrinsicId()) == intrinsicsUsage.end()) - intrinsicsUsage[view.getIntrinsicId()] = 0; - - if(sfmData.isPoseAndIntrinsicDefined(&view)) - ++intrinsicsUsage.at(view.getIntrinsicId()); - } - - for(const auto& intrinsicPair: sfmData.getIntrinsics()) - { - const IndexT intrinsicId = intrinsicPair.first; - const auto& intrinsicPtr = intrinsicPair.second; - const auto usageIt = intrinsicsUsage.find(intrinsicId); - if(usageIt == intrinsicsUsage.end()) - // if the intrinsic is never referenced by any view, skip it - continue; - const std::size_t usageCount = usageIt->second; - - // do not refine an intrinsic does not used by any reconstructed view - if(usageCount <= 0 || getIntrinsicState(intrinsicId) == EParameterState::IGNORED) + const bool refineTranslation = refineOptions & BundleAdjustment::REFINE_TRANSLATION; + const bool refineRotation = refineOptions & BundleAdjustment::REFINE_ROTATION; + + const auto addPose = [&](const sfmData::CameraPose& cameraPose, bool isConstant, std::array& poseBlock) { + const Mat3& R = cameraPose.getTransform().rotation(); + const Vec3& t = cameraPose.getTransform().translation(); + + double angleAxis[3]; + ceres::RotationMatrixToAngleAxis(static_cast(R.data()), angleAxis); + poseBlock.at(0) = angleAxis[0]; + poseBlock.at(1) = angleAxis[1]; + poseBlock.at(2) = angleAxis[2]; + poseBlock.at(3) = t(0); + poseBlock.at(4) = t(1); + poseBlock.at(5) = t(2); + + double* poseBlockPtr = poseBlock.data(); + problem.AddParameterBlock(poseBlockPtr, 6); + + // add pose parameter to the all parameters blocks pointers list + _allParametersBlocks.push_back(poseBlockPtr); + + // keep the camera extrinsics constants + if (cameraPose.isLocked() || isConstant || (!refineTranslation && !refineRotation)) + { + // set the whole parameter block as constant. + _statistics.addState(EParameter::POSE, EParameterState::CONSTANT); + problem.SetParameterBlockConstant(poseBlockPtr); + return; + } + + // constant parameters + std::vector constantExtrinsic; + + // don't refine rotations + if (!refineRotation) + { + constantExtrinsic.push_back(0); + constantExtrinsic.push_back(1); + constantExtrinsic.push_back(2); + } + + // don't refine translations + if (!refineTranslation) + { + constantExtrinsic.push_back(3); + constantExtrinsic.push_back(4); + constantExtrinsic.push_back(5); + } + + // subset parametrization + if (!constantExtrinsic.empty()) + { + auto* subsetManifold = new ceres::SubsetManifold(6, constantExtrinsic); + problem.SetManifold(poseBlockPtr, subsetManifold); + } + + _statistics.addState(EParameter::POSE, EParameterState::REFINED); + }; + + // setup poses data + for (const auto& posePair : sfmData.getPoses()) { - _statistics.addState(EParameter::INTRINSIC, EParameterState::IGNORED); - continue; - } + const IndexT poseId = posePair.first; + const sfmData::CameraPose& pose = posePair.second; - assert(isValid(intrinsicPtr->getType())); + // skip camera pose set as Ignored in the Local strategy + if (getPoseState(poseId) == EParameterState::IGNORED) + { + _statistics.addState(EParameter::POSE, EParameterState::IGNORED); + continue; + } - std::vector& intrinsicBlock = _intrinsicsBlocks[intrinsicId]; - intrinsicBlock = intrinsicPtr->getParams(); + const bool isConstant = (getPoseState(poseId) == EParameterState::CONSTANT); - double* intrinsicBlockPtr = intrinsicBlock.data(); + addPose(pose, isConstant, _posesBlocks[poseId]); + } - problem.AddParameterBlock(intrinsicBlockPtr, intrinsicBlock.size()); + // setup sub-poses data + for (const auto& rigPair : sfmData.getRigs()) + { + const IndexT rigId = rigPair.first; + const sfmData::Rig& rig = rigPair.second; + const std::size_t nbSubPoses = rig.getNbSubPoses(); - // add intrinsic parameter to the all parameters blocks pointers list - _allParametersBlocks.push_back(intrinsicBlockPtr); + for (std::size_t subPoseId = 0; subPoseId < nbSubPoses; ++subPoseId) + { + const sfmData::RigSubPose& rigSubPose = rig.getSubPose(subPoseId); - // keep the camera intrinsic constant - if(intrinsicPtr->isLocked() || !refineIntrinsics || getIntrinsicState(intrinsicId) == EParameterState::CONSTANT) - { - // set the whole parameter block as constant. - _statistics.addState(EParameter::INTRINSIC, EParameterState::CONSTANT); - problem.SetParameterBlockConstant(intrinsicBlockPtr); - continue; - } + if (rigSubPose.status == sfmData::ERigSubPoseStatus::UNINITIALIZED) + continue; - // constant parameters - bool lockCenter = false; - bool lockFocal = false; - bool lockRatio = true; - bool lockDistortion = false; - double focalRatio = 1.0; + const bool isConstant = (rigSubPose.status == sfmData::ERigSubPoseStatus::CONSTANT); - // refine the focal length - if(refineIntrinsicsFocalLength) - { - std::shared_ptr intrinsicScaleOffset = std::dynamic_pointer_cast(intrinsicPtr); - if (intrinsicScaleOffset->getInitialScale().x() > 0 && intrinsicScaleOffset->getInitialScale().y() > 0) - { - // if we have an initial guess, we only authorize a margin around this value. - assert(intrinsicBlock.size() >= 1); - const unsigned int maxFocalError = 0.2 * std::max(intrinsicPtr->w(), intrinsicPtr->h()); // TODO : check if rounding is needed - problem.SetParameterLowerBound(intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() - maxFocalError)); - problem.SetParameterUpperBound(intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() + maxFocalError)); - problem.SetParameterLowerBound(intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() - maxFocalError)); - problem.SetParameterUpperBound(intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() + maxFocalError)); - } - else // no initial guess - { - // we don't have an initial guess, but we assume that we use - // a converging lens, so the focal length should be positive. - problem.SetParameterLowerBound(intrinsicBlockPtr, 0, 0.0); - problem.SetParameterLowerBound(intrinsicBlockPtr, 1, 0.0); - } - - focalRatio = intrinsicBlockPtr[0] / intrinsicBlockPtr[1]; - - std::shared_ptr castedcam_iso = std::dynamic_pointer_cast(intrinsicPtr); - if (castedcam_iso) - { - lockRatio = castedcam_iso->isRatioLocked(); - } - } - else - { - // set focal length as constant - lockFocal = true; + addPose(sfmData::CameraPose(rigSubPose.pose), isConstant, _rigBlocks[rigId][subPoseId]); + } } +} - // optical center - if((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || - ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA) && _minNbImagesToRefineOpticalCenter > 0 && usageCount >= _minNbImagesToRefineOpticalCenter)) - { - // refine optical center within 10% of the image size. - assert(intrinsicBlock.size() >= 3); +void BundleAdjustmentCeres::addIntrinsicsToProblem(const sfmData::SfMData& sfmData, + BundleAdjustment::ERefineOptions refineOptions, + ceres::Problem& problem) +{ + const bool refineIntrinsicsOpticalCenter = + (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); + const bool refineIntrinsicsFocalLength = refineOptions & REFINE_INTRINSICS_FOCAL; + const bool refineIntrinsicsDistortion = refineOptions & REFINE_INTRINSICS_DISTORTION; + const bool refineIntrinsics = refineIntrinsicsDistortion || refineIntrinsicsFocalLength || refineIntrinsicsOpticalCenter; - const double opticalCenterMinPercent = -0.05; - const double opticalCenterMaxPercent = 0.05; + std::map intrinsicsUsage; - // add bounds to the principal point - problem.SetParameterLowerBound(intrinsicBlockPtr, 2, opticalCenterMinPercent * intrinsicPtr->w()); - problem.SetParameterUpperBound(intrinsicBlockPtr, 2, opticalCenterMaxPercent * intrinsicPtr->w()); - problem.SetParameterLowerBound(intrinsicBlockPtr, 3, opticalCenterMinPercent * intrinsicPtr->h()); - problem.SetParameterUpperBound(intrinsicBlockPtr, 3, opticalCenterMaxPercent * intrinsicPtr->h()); - } - else + // count the number of reconstructed views per intrinsic + for (const auto& viewPair : sfmData.getViews()) { - // don't refine the optical center - lockCenter = true; + const sfmData::View& view = *(viewPair.second); + + if (intrinsicsUsage.find(view.getIntrinsicId()) == intrinsicsUsage.end()) + intrinsicsUsage[view.getIntrinsicId()] = 0; + + if (sfmData.isPoseAndIntrinsicDefined(&view)) + ++intrinsicsUsage.at(view.getIntrinsicId()); } - // lens distortion - if (!refineIntrinsicsDistortion || - intrinsicPtr->getDistortionInitializationMode() == camera::EInitMode::CALIBRATED) + for (const auto& intrinsicPair : sfmData.getIntrinsics()) { - lockDistortion = true; + const IndexT intrinsicId = intrinsicPair.first; + const auto& intrinsicPtr = intrinsicPair.second; + const auto usageIt = intrinsicsUsage.find(intrinsicId); + if (usageIt == intrinsicsUsage.end()) + // if the intrinsic is never referenced by any view, skip it + continue; + const std::size_t usageCount = usageIt->second; + + // do not refine an intrinsic does not used by any reconstructed view + if (usageCount <= 0 || getIntrinsicState(intrinsicId) == EParameterState::IGNORED) + { + _statistics.addState(EParameter::INTRINSIC, EParameterState::IGNORED); + continue; + } + + assert(isValid(intrinsicPtr->getType())); + + std::vector& intrinsicBlock = _intrinsicsBlocks[intrinsicId]; + intrinsicBlock = intrinsicPtr->getParams(); + + double* intrinsicBlockPtr = intrinsicBlock.data(); + + problem.AddParameterBlock(intrinsicBlockPtr, intrinsicBlock.size()); + + // add intrinsic parameter to the all parameters blocks pointers list + _allParametersBlocks.push_back(intrinsicBlockPtr); + + // keep the camera intrinsic constant + if (intrinsicPtr->isLocked() || !refineIntrinsics || getIntrinsicState(intrinsicId) == EParameterState::CONSTANT) + { + // set the whole parameter block as constant. + _statistics.addState(EParameter::INTRINSIC, EParameterState::CONSTANT); + problem.SetParameterBlockConstant(intrinsicBlockPtr); + continue; + } + + // constant parameters + bool lockCenter = false; + bool lockFocal = false; + bool lockRatio = true; + bool lockDistortion = false; + double focalRatio = 1.0; + + // refine the focal length + if (refineIntrinsicsFocalLength) + { + std::shared_ptr intrinsicScaleOffset = + std::dynamic_pointer_cast(intrinsicPtr); + if (intrinsicScaleOffset->getInitialScale().x() > 0 && intrinsicScaleOffset->getInitialScale().y() > 0) + { + // if we have an initial guess, we only authorize a margin around this value. + assert(intrinsicBlock.size() >= 1); + const unsigned int maxFocalError = 0.2 * std::max(intrinsicPtr->w(), intrinsicPtr->h()); // TODO : check if rounding is needed + problem.SetParameterLowerBound( + intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() - maxFocalError)); + problem.SetParameterUpperBound( + intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() + maxFocalError)); + problem.SetParameterLowerBound( + intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() - maxFocalError)); + problem.SetParameterUpperBound( + intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() + maxFocalError)); + } + else // no initial guess + { + // we don't have an initial guess, but we assume that we use + // a converging lens, so the focal length should be positive. + problem.SetParameterLowerBound(intrinsicBlockPtr, 0, 0.0); + problem.SetParameterLowerBound(intrinsicBlockPtr, 1, 0.0); + } + + focalRatio = intrinsicBlockPtr[0] / intrinsicBlockPtr[1]; + + std::shared_ptr castedcam_iso = std::dynamic_pointer_cast(intrinsicPtr); + if (castedcam_iso) + { + lockRatio = castedcam_iso->isRatioLocked(); + } + } + else + { + // set focal length as constant + lockFocal = true; + } + + // optical center + if ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || + ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA) && _minNbImagesToRefineOpticalCenter > 0 && + usageCount >= _minNbImagesToRefineOpticalCenter)) + { + // refine optical center within 10% of the image size. + assert(intrinsicBlock.size() >= 3); + + const double opticalCenterMinPercent = -0.05; + const double opticalCenterMaxPercent = 0.05; + + // add bounds to the principal point + problem.SetParameterLowerBound(intrinsicBlockPtr, 2, opticalCenterMinPercent * intrinsicPtr->w()); + problem.SetParameterUpperBound(intrinsicBlockPtr, 2, opticalCenterMaxPercent * intrinsicPtr->w()); + problem.SetParameterLowerBound(intrinsicBlockPtr, 3, opticalCenterMinPercent * intrinsicPtr->h()); + problem.SetParameterUpperBound(intrinsicBlockPtr, 3, opticalCenterMaxPercent * intrinsicPtr->h()); + } + else + { + // don't refine the optical center + lockCenter = true; + } + + // lens distortion + if (!refineIntrinsicsDistortion || intrinsicPtr->getDistortionInitializationMode() == camera::EInitMode::CALIBRATED) + { + lockDistortion = true; + } + + IntrinsicsManifold* subsetManifold = + new IntrinsicsManifold(intrinsicBlock.size(), focalRatio, lockFocal, lockRatio, lockCenter, lockDistortion); + problem.SetManifold(intrinsicBlockPtr, subsetManifold); + + _statistics.addState(EParameter::INTRINSIC, EParameterState::REFINED); } - - - IntrinsicsManifold* subsetManifold = new IntrinsicsManifold(intrinsicBlock.size(), focalRatio, - lockFocal, lockRatio, lockCenter, lockDistortion); - problem.SetManifold(intrinsicBlockPtr, subsetManifold); - - _statistics.addState(EParameter::INTRINSIC, EParameterState::REFINED); - } } void BundleAdjustmentCeres::addLandmarksToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - const bool refineStructure = refineOptions & REFINE_STRUCTURE; - - // set a LossFunction to be less penalized by false measurements. - // note: set it to NULL if you don't want use a lossFunction. - ceres::LossFunction* lossFunction = _ceresOptions.lossFunction.get(); + const bool refineStructure = refineOptions & REFINE_STRUCTURE; - // build the residual blocks corresponding to the track observations - for(const auto& landmarkPair: sfmData.getLandmarks()) - { - const IndexT landmarkId = landmarkPair.first; - const sfmData::Landmark& landmark = landmarkPair.second; + // set a LossFunction to be less penalized by false measurements. + // note: set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* lossFunction = _ceresOptions.lossFunction.get(); - // do not create a residual block if the landmark - // have been set as Ignored by the Local BA strategy - if(getLandmarkState(landmarkId) == EParameterState::IGNORED) + // build the residual blocks corresponding to the track observations + for (const auto& landmarkPair : sfmData.getLandmarks()) { - _statistics.addState(EParameter::LANDMARK, EParameterState::IGNORED); - continue; + const IndexT landmarkId = landmarkPair.first; + const sfmData::Landmark& landmark = landmarkPair.second; + + // do not create a residual block if the landmark + // have been set as Ignored by the Local BA strategy + if (getLandmarkState(landmarkId) == EParameterState::IGNORED) + { + _statistics.addState(EParameter::LANDMARK, EParameterState::IGNORED); + continue; + } + + std::array& landmarkBlock = _landmarksBlocks[landmarkId]; + for (std::size_t i = 0; i < 3; ++i) + landmarkBlock.at(i) = landmark.X(Eigen::Index(i)); + + double* landmarkBlockPtr = landmarkBlock.data(); + + // add landmark parameter to the all parameters blocks pointers list + _allParametersBlocks.push_back(landmarkBlockPtr); + + // iterate over 2D observation associated to the 3D landmark + for (const auto& observationPair : landmark.observations) + { + const sfmData::View& view = sfmData.getView(observationPair.first); + const sfmData::Observation& observation = observationPair.second; + + // each residual block takes a point and a camera as input and outputs a 2 + // dimensional residual. Internally, the cost function stores the observed + // image location and compares the reprojection against the observation. + + assert(getPoseState(view.getPoseId()) != EParameterState::IGNORED); + assert(getIntrinsicState(view.getIntrinsicId()) != EParameterState::IGNORED); + + // needed parameters to create a residual block (K, pose) + double* poseBlockPtr = _posesBlocks.at(view.getPoseId()).data(); + double* intrinsicBlockPtr = _intrinsicsBlocks.at(view.getIntrinsicId()).data(); + + // apply a specific parameter ordering: + if (_ceresOptions.useParametersOrdering) + { + _linearSolverOrdering.AddElementToGroup(landmarkBlockPtr, 0); + _linearSolverOrdering.AddElementToGroup(poseBlockPtr, 1); + _linearSolverOrdering.AddElementToGroup(intrinsicBlockPtr, 2); + } + + if (view.isPartOfRig() && !view.isPoseIndependant()) + { + ceres::CostFunction* costFunction = createRigCostFunctionFromIntrinsics(sfmData.getIntrinsicPtr(view.getIntrinsicId()), observation); + + double* rigBlockPtr = _rigBlocks.at(view.getRigId()).at(view.getSubPoseId()).data(); + _linearSolverOrdering.AddElementToGroup(rigBlockPtr, 1); + + problem.AddResidualBlock(costFunction, + lossFunction, + intrinsicBlockPtr, + poseBlockPtr, + rigBlockPtr, // subpose of the cameras rig + landmarkBlockPtr); // do we need to copy 3D point to avoid false motion, if failure ? + } + else + { + ceres::CostFunction* costFunction = createCostFunctionFromIntrinsics(sfmData.getIntrinsicPtr(view.getIntrinsicId()), observation); + + problem.AddResidualBlock(costFunction, + lossFunction, + intrinsicBlockPtr, + poseBlockPtr, + landmarkBlockPtr); // do we need to copy 3D point to avoid false motion, if failure ? + } + + if (!refineStructure || getLandmarkState(landmarkId) == EParameterState::CONSTANT) + { + // set the whole landmark parameter block as constant. + _statistics.addState(EParameter::LANDMARK, EParameterState::CONSTANT); + problem.SetParameterBlockConstant(landmarkBlockPtr); + } + else + { + _statistics.addState(EParameter::LANDMARK, EParameterState::REFINED); + } + } } - - std::array& landmarkBlock = _landmarksBlocks[landmarkId]; - for(std::size_t i = 0; i < 3; ++i) - landmarkBlock.at(i) = landmark.X(Eigen::Index(i)); - - double* landmarkBlockPtr = landmarkBlock.data(); - - // add landmark parameter to the all parameters blocks pointers list - _allParametersBlocks.push_back(landmarkBlockPtr); - - // iterate over 2D observation associated to the 3D landmark - for(const auto& observationPair: landmark.observations) - { - const sfmData::View& view = sfmData.getView(observationPair.first); - const sfmData::Observation& observation = observationPair.second; - - // each residual block takes a point and a camera as input and outputs a 2 - // dimensional residual. Internally, the cost function stores the observed - // image location and compares the reprojection against the observation. - - assert(getPoseState(view.getPoseId()) != EParameterState::IGNORED); - assert(getIntrinsicState(view.getIntrinsicId()) != EParameterState::IGNORED); - - // needed parameters to create a residual block (K, pose) - double* poseBlockPtr = _posesBlocks.at(view.getPoseId()).data(); - double* intrinsicBlockPtr = _intrinsicsBlocks.at(view.getIntrinsicId()).data(); - - // apply a specific parameter ordering: - if(_ceresOptions.useParametersOrdering) - { - _linearSolverOrdering.AddElementToGroup(landmarkBlockPtr, 0); - _linearSolverOrdering.AddElementToGroup(poseBlockPtr, 1); - _linearSolverOrdering.AddElementToGroup(intrinsicBlockPtr, 2); - } - - if(view.isPartOfRig() && !view.isPoseIndependant()) - { - ceres::CostFunction* costFunction = createRigCostFunctionFromIntrinsics(sfmData.getIntrinsicPtr(view.getIntrinsicId()), observation); - - double* rigBlockPtr = _rigBlocks.at(view.getRigId()).at(view.getSubPoseId()).data(); - _linearSolverOrdering.AddElementToGroup(rigBlockPtr, 1); - - problem.AddResidualBlock(costFunction, - lossFunction, - intrinsicBlockPtr, - poseBlockPtr, - rigBlockPtr, // subpose of the cameras rig - landmarkBlockPtr); // do we need to copy 3D point to avoid false motion, if failure ? - } - else - { - ceres::CostFunction* costFunction = createCostFunctionFromIntrinsics(sfmData.getIntrinsicPtr(view.getIntrinsicId()), observation); - - problem.AddResidualBlock(costFunction, - lossFunction, - intrinsicBlockPtr, - poseBlockPtr, - landmarkBlockPtr); //do we need to copy 3D point to avoid false motion, if failure ? - } - - if(!refineStructure || getLandmarkState(landmarkId) == EParameterState::CONSTANT) - { - // set the whole landmark parameter block as constant. - _statistics.addState(EParameter::LANDMARK, EParameterState::CONSTANT); - problem.SetParameterBlockConstant(landmarkBlockPtr); - } - else - { - _statistics.addState(EParameter::LANDMARK, EParameterState::REFINED); - } - } - } } void BundleAdjustmentCeres::addConstraints2DToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - // set a LossFunction to be less penalized by false measurements. - // note: set it to NULL if you don't want use a lossFunction. - ceres::LossFunction* lossFunction = _ceresOptions.lossFunction.get(); + // set a LossFunction to be less penalized by false measurements. + // note: set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* lossFunction = _ceresOptions.lossFunction.get(); - for (const auto & constraint : sfmData.getConstraints2D()) { - const sfmData::View& view_1 = sfmData.getView(constraint.ViewFirst); - const sfmData::View& view_2 = sfmData.getView(constraint.ViewSecond); - - assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); - assert(getIntrinsicState(view_1.getIntrinsicId()) != EParameterState::IGNORED); - assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); - assert(getIntrinsicState(view_2.getIntrinsicId()) != EParameterState::IGNORED); + for (const auto& constraint : sfmData.getConstraints2D()) + { + const sfmData::View& view_1 = sfmData.getView(constraint.ViewFirst); + const sfmData::View& view_2 = sfmData.getView(constraint.ViewSecond); - double * poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); - double * poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); + assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); + assert(getIntrinsicState(view_1.getIntrinsicId()) != EParameterState::IGNORED); + assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); + assert(getIntrinsicState(view_2.getIntrinsicId()) != EParameterState::IGNORED); - double * intrinsicBlockPtr_1 = _intrinsicsBlocks.at(view_1.getIntrinsicId()).data(); - double * intrinsicBlockPtr_2 = _intrinsicsBlocks.at(view_2.getIntrinsicId()).data(); + double* poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); + double* poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); - //For the moment assume a unique camera - assert(intrinsicBlockPtr_1 == intrinsicBlockPtr_2); + double* intrinsicBlockPtr_1 = _intrinsicsBlocks.at(view_1.getIntrinsicId()).data(); + double* intrinsicBlockPtr_2 = _intrinsicsBlocks.at(view_2.getIntrinsicId()).data(); + // For the moment assume a unique camera + assert(intrinsicBlockPtr_1 == intrinsicBlockPtr_2); - ceres::CostFunction* costFunction = createConstraintsCostFunctionFromIntrinsics(sfmData.getIntrinsicPtr(view_1.getIntrinsicId()), constraint.ObservationFirst.x, constraint.ObservationSecond.x); - problem.AddResidualBlock(costFunction, lossFunction, intrinsicBlockPtr_1, poseBlockPtr_1, poseBlockPtr_2); - } + ceres::CostFunction* costFunction = createConstraintsCostFunctionFromIntrinsics( + sfmData.getIntrinsicPtr(view_1.getIntrinsicId()), constraint.ObservationFirst.x, constraint.ObservationSecond.x); + problem.AddResidualBlock(costFunction, lossFunction, intrinsicBlockPtr_1, poseBlockPtr_1, poseBlockPtr_2); + } } void BundleAdjustmentCeres::addRotationPriorsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - // set a LossFunction to be less penalized by false measurements. - // note: set it to NULL if you don't want use a lossFunction. - ceres::LossFunction* lossFunction = nullptr; - - for (const auto & prior : sfmData.getRotationPriors()) { - const sfmData::View& view_1 = sfmData.getView(prior.ViewFirst); - const sfmData::View& view_2 = sfmData.getView(prior.ViewSecond); + // set a LossFunction to be less penalized by false measurements. + // note: set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* lossFunction = nullptr; - assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); - assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); + for (const auto& prior : sfmData.getRotationPriors()) + { + const sfmData::View& view_1 = sfmData.getView(prior.ViewFirst); + const sfmData::View& view_2 = sfmData.getView(prior.ViewSecond); - double * poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); - double * poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); + assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); + assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); + double* poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); + double* poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); - ceres::CostFunction* costFunction = new ceres::AutoDiffCostFunction(new ResidualErrorRotationPriorFunctor(prior._second_R_first)); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2); - } + ceres::CostFunction* costFunction = + new ceres::AutoDiffCostFunction(new ResidualErrorRotationPriorFunctor(prior._second_R_first)); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2); + } } -void BundleAdjustmentCeres::createProblem(const sfmData::SfMData& sfmData, - ERefineOptions refineOptions, - ceres::Problem& problem) +void BundleAdjustmentCeres::createProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - // clear previously computed data - resetProblem(); + // clear previously computed data + resetProblem(); - // ensure we are not using incompatible options - // REFINEINTRINSICS_OPTICALCENTER_ALWAYS and REFINEINTRINSICS_OPTICALCENTER_IF_ENOUGH_DATA cannot be used at the same time - assert(!((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) && (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA))); + // ensure we are not using incompatible options + // REFINEINTRINSICS_OPTICALCENTER_ALWAYS and REFINEINTRINSICS_OPTICALCENTER_IF_ENOUGH_DATA cannot be used at the same time + assert(!((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) && (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA))); - // add SfM extrincics to the Ceres problem - addExtrinsicsToProblem(sfmData, refineOptions, problem); + // add SfM extrincics to the Ceres problem + addExtrinsicsToProblem(sfmData, refineOptions, problem); - // add SfM intrinsics to the Ceres problem - addIntrinsicsToProblem(sfmData, refineOptions, problem); + // add SfM intrinsics to the Ceres problem + addIntrinsicsToProblem(sfmData, refineOptions, problem); - // add SfM landmarks to the Ceres problem - addLandmarksToProblem(sfmData, refineOptions, problem); + // add SfM landmarks to the Ceres problem + addLandmarksToProblem(sfmData, refineOptions, problem); - // add 2D constraints to the Ceres problem - addConstraints2DToProblem(sfmData, refineOptions, problem); + // add 2D constraints to the Ceres problem + addConstraints2DToProblem(sfmData, refineOptions, problem); - // add rotation priors to the Ceres problem - addRotationPriorsToProblem(sfmData, refineOptions, problem); + // add rotation priors to the Ceres problem + addRotationPriorsToProblem(sfmData, refineOptions, problem); } - void BundleAdjustmentCeres::resetProblem() +void BundleAdjustmentCeres::resetProblem() { - _statistics = Statistics(); + _statistics = Statistics(); - _allParametersBlocks.clear(); - _posesBlocks.clear(); - _intrinsicsBlocks.clear(); - _landmarksBlocks.clear(); - _rigBlocks.clear(); + _allParametersBlocks.clear(); + _posesBlocks.clear(); + _intrinsicsBlocks.clear(); + _landmarksBlocks.clear(); + _rigBlocks.clear(); - _linearSolverOrdering.Clear(); + _linearSolverOrdering.Clear(); } void BundleAdjustmentCeres::updateFromSolution(sfmData::SfMData& sfmData, ERefineOptions refineOptions) const { - const bool refinePoses = (refineOptions & REFINE_ROTATION) || (refineOptions & REFINE_TRANSLATION); - const bool refineIntrinsicsOpticalCenter = (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); - const bool refineIntrinsics = (refineOptions & REFINE_INTRINSICS_FOCAL) || (refineOptions & REFINE_INTRINSICS_DISTORTION) || refineIntrinsicsOpticalCenter; - const bool refineStructure = refineOptions & REFINE_STRUCTURE; - - // update camera poses with refined data - if(refinePoses) - { - // absolute poses - for(auto& posePair : sfmData.getPoses()) - { - const IndexT poseId = posePair.first; - - // do not update a camera pose set as Ignored or Constant in the Local strategy - if(getPoseState(poseId) != EParameterState::REFINED) - continue; - - const std::array& poseBlock = _posesBlocks.at(poseId); - - Mat3 R_refined; - ceres::AngleAxisToRotationMatrix(poseBlock.data(), R_refined.data()); - const Vec3 t_refined(poseBlock.at(3), poseBlock.at(4), poseBlock.at(5)); - - // update the pose - posePair.second.setTransform(poseFromRT(R_refined, t_refined)); - } - - // rig sub-poses - for(const auto& rigIt : _rigBlocks) + const bool refinePoses = (refineOptions & REFINE_ROTATION) || (refineOptions & REFINE_TRANSLATION); + const bool refineIntrinsicsOpticalCenter = + (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); + const bool refineIntrinsics = + (refineOptions & REFINE_INTRINSICS_FOCAL) || (refineOptions & REFINE_INTRINSICS_DISTORTION) || refineIntrinsicsOpticalCenter; + const bool refineStructure = refineOptions & REFINE_STRUCTURE; + + // update camera poses with refined data + if (refinePoses) { - sfmData::Rig& rig = sfmData.getRigs().at(rigIt.first); - - for(const auto& subPoseit : rigIt.second) - { - sfmData::RigSubPose& subPose = rig.getSubPose(subPoseit.first); - const std::array& subPoseBlock = subPoseit.second; - - Mat3 R_refined; - ceres::AngleAxisToRotationMatrix(subPoseBlock.data(), R_refined.data()); - const Vec3 t_refined(subPoseBlock.at(3), subPoseBlock.at(4), subPoseBlock.at(5)); - - // update the sub-pose - subPose.pose = poseFromRT(R_refined, t_refined); - } + // absolute poses + for (auto& posePair : sfmData.getPoses()) + { + const IndexT poseId = posePair.first; + + // do not update a camera pose set as Ignored or Constant in the Local strategy + if (getPoseState(poseId) != EParameterState::REFINED) + continue; + + const std::array& poseBlock = _posesBlocks.at(poseId); + + Mat3 R_refined; + ceres::AngleAxisToRotationMatrix(poseBlock.data(), R_refined.data()); + const Vec3 t_refined(poseBlock.at(3), poseBlock.at(4), poseBlock.at(5)); + + // update the pose + posePair.second.setTransform(poseFromRT(R_refined, t_refined)); + } + + // rig sub-poses + for (const auto& rigIt : _rigBlocks) + { + sfmData::Rig& rig = sfmData.getRigs().at(rigIt.first); + + for (const auto& subPoseit : rigIt.second) + { + sfmData::RigSubPose& subPose = rig.getSubPose(subPoseit.first); + const std::array& subPoseBlock = subPoseit.second; + + Mat3 R_refined; + ceres::AngleAxisToRotationMatrix(subPoseBlock.data(), R_refined.data()); + const Vec3 t_refined(subPoseBlock.at(3), subPoseBlock.at(4), subPoseBlock.at(5)); + + // update the sub-pose + subPose.pose = poseFromRT(R_refined, t_refined); + } + } } - } - // update camera intrinsics with refined data - if(refineIntrinsics) - { - for(const auto& intrinsicBlockPair: _intrinsicsBlocks) + // update camera intrinsics with refined data + if (refineIntrinsics) { - const IndexT intrinsicId = intrinsicBlockPair.first; + for (const auto& intrinsicBlockPair : _intrinsicsBlocks) + { + const IndexT intrinsicId = intrinsicBlockPair.first; - // do not update a camera pose set as Ignored or Constant in the Local strategy - if(getIntrinsicState(intrinsicId) != EParameterState::REFINED) - continue; + // do not update a camera pose set as Ignored or Constant in the Local strategy + if (getIntrinsicState(intrinsicId) != EParameterState::REFINED) + continue; - sfmData.getIntrinsics().at(intrinsicId)->updateFromParams(intrinsicBlockPair.second); + sfmData.getIntrinsics().at(intrinsicId)->updateFromParams(intrinsicBlockPair.second); + } } - } - // update landmarks - if(refineStructure) - { - for(const auto& landmarksBlockPair: _landmarksBlocks) + // update landmarks + if (refineStructure) { - const IndexT landmarkId = landmarksBlockPair.first; - sfmData::Landmark& landmark = sfmData.getLandmarks().at(landmarkId); - - // do not update a camera pose set as Ignored or Constant in the Local strategy - if(getLandmarkState(landmarkId) != EParameterState::REFINED) - continue; - - for(std::size_t i = 0; i < 3; ++i) - landmark.X(Eigen::Index(i))= landmarksBlockPair.second.at(i); + for (const auto& landmarksBlockPair : _landmarksBlocks) + { + const IndexT landmarkId = landmarksBlockPair.first; + sfmData::Landmark& landmark = sfmData.getLandmarks().at(landmarkId); + + // do not update a camera pose set as Ignored or Constant in the Local strategy + if (getLandmarkState(landmarkId) != EParameterState::REFINED) + continue; + + for (std::size_t i = 0; i < 3; ++i) + landmark.X(Eigen::Index(i)) = landmarksBlockPair.second.at(i); + } } - } } -void BundleAdjustmentCeres::createJacobian(const sfmData::SfMData& sfmData, - ERefineOptions refineOptions, - ceres::CRSMatrix& jacobian) +void BundleAdjustmentCeres::createJacobian(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::CRSMatrix& jacobian) { - // create problem - ceres::Problem::Options problemOptions; - problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - ceres::Problem problem(problemOptions); - createProblem(sfmData, refineOptions, problem); - - // configure Jacobian engine - double cost = 0.0; - ceres::Problem::EvaluateOptions evalOpt; - evalOpt.parameter_blocks = _allParametersBlocks; - evalOpt.num_threads = 8; - evalOpt.apply_loss_function = true; - - // create Jacobain - problem.Evaluate(evalOpt, &cost, NULL, NULL, &jacobian); + // create problem + ceres::Problem::Options problemOptions; + problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; + ceres::Problem problem(problemOptions); + createProblem(sfmData, refineOptions, problem); + + // configure Jacobian engine + double cost = 0.0; + ceres::Problem::EvaluateOptions evalOpt; + evalOpt.parameter_blocks = _allParametersBlocks; + evalOpt.num_threads = 8; + evalOpt.apply_loss_function = true; + + // create Jacobain + problem.Evaluate(evalOpt, &cost, NULL, NULL, &jacobian); } bool BundleAdjustmentCeres::adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions) { - // create problem - ceres::Problem::Options problemOptions; - problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - ceres::Problem problem(problemOptions); - createProblem(sfmData, refineOptions, problem); - - // configure a Bundle Adjustment engine and run it - // make Ceres automatically detect the bundle structure. - ceres::Solver::Options options; - setSolverOptions(options); - - // solve BA - ceres::Solver::Summary summary; - ceres::Solve(options, &problem, &summary); - - // print summary - if(_ceresOptions.summary) - ALICEVISION_LOG_INFO(summary.FullReport()); - - // solution is not usable - if(!summary.IsSolutionUsable()) - { - ALICEVISION_LOG_WARNING("Bundle Adjustment failed, the solution is not usable."); - return false; - } - - // update input sfmData with the solution - updateFromSolution(sfmData, refineOptions); - - - // store some statitics from the summary - _statistics.time = summary.total_time_in_seconds; - _statistics.nbSuccessfullIterations = summary.num_successful_steps; - _statistics.nbUnsuccessfullIterations = summary.num_unsuccessful_steps; - _statistics.nbResidualBlocks = summary.num_residuals; - _statistics.RMSEinitial = std::sqrt(summary.initial_cost / summary.num_residuals); - _statistics.RMSEfinal = std::sqrt(summary.final_cost / summary.num_residuals); - - //store distance histogram for local strategy - if(useLocalStrategy()) - _statistics.nbCamerasPerDistance = _localGraph->getDistancesHistogram(); - - return true; -} + // create problem + ceres::Problem::Options problemOptions; + problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; + ceres::Problem problem(problemOptions); + createProblem(sfmData, refineOptions, problem); + + // configure a Bundle Adjustment engine and run it + // make Ceres automatically detect the bundle structure. + ceres::Solver::Options options; + setSolverOptions(options); + + // solve BA + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + // print summary + if (_ceresOptions.summary) + ALICEVISION_LOG_INFO(summary.FullReport()); + + // solution is not usable + if (!summary.IsSolutionUsable()) + { + ALICEVISION_LOG_WARNING("Bundle Adjustment failed, the solution is not usable."); + return false; + } + + // update input sfmData with the solution + updateFromSolution(sfmData, refineOptions); -} // namespace sfm -} // namespace aliceVision + // store some statitics from the summary + _statistics.time = summary.total_time_in_seconds; + _statistics.nbSuccessfullIterations = summary.num_successful_steps; + _statistics.nbUnsuccessfullIterations = summary.num_unsuccessful_steps; + _statistics.nbResidualBlocks = summary.num_residuals; + _statistics.RMSEinitial = std::sqrt(summary.initial_cost / summary.num_residuals); + _statistics.RMSEfinal = std::sqrt(summary.final_cost / summary.num_residuals); + + // store distance histogram for local strategy + if (useLocalStrategy()) + _statistics.nbCamerasPerDistance = _localGraph->getDistancesHistogram(); + + return true; +} +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.hpp b/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.hpp index dec13fff6f..6a0a3aaa34 100644 --- a/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.hpp +++ b/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.hpp @@ -17,294 +17,276 @@ #include - namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { class BundleAdjustmentCeres : public BundleAdjustment { -public: - - /** - * @brief Contains all ceres parameters. - */ - struct CeresOptions - { - CeresOptions(bool verbose = true, bool multithreaded = true, unsigned int maxIterations = 50) - : verbose(verbose) - , nbThreads(multithreaded ? omp_get_max_threads() : 1) // set number of threads, 1 if OpenMP is not enabled - , maxNumIterations(maxIterations) + public: + /** + * @brief Contains all ceres parameters. + */ + struct CeresOptions { - setDenseBA(); // use dense BA by default - lossFunction.reset(new ceres::HuberLoss(Square(4.0))); - } + CeresOptions(bool verbose = true, bool multithreaded = true, unsigned int maxIterations = 50) + : verbose(verbose), + nbThreads(multithreaded ? omp_get_max_threads() : 1) // set number of threads, 1 if OpenMP is not enabled + , + maxNumIterations(maxIterations) + { + setDenseBA(); // use dense BA by default + lossFunction.reset(new ceres::HuberLoss(Square(4.0))); + } + + void setDenseBA(); + void setSparseBA(); + + ceres::LinearSolverType linearSolverType; + ceres::PreconditionerType preconditionerType; + ceres::SparseLinearAlgebraLibraryType sparseLinearAlgebraLibraryType; + std::shared_ptr lossFunction; + unsigned int nbThreads; + unsigned int maxNumIterations; + bool useParametersOrdering = true; + bool summary = false; + bool verbose = true; + }; + + /** + * @brief Contains all informations related to the performed bundle adjustment. + */ + struct Statistics + { + Statistics() {} + + /** + * @brief Add a parameter state + * @param[in] parameter A bundle adjustment parameter + * @param[in] state A bundle adjustment state + */ + inline void addState(EParameter parameter, EParameterState state) { ++parametersStates[parameter][state]; } + + /** + * @brief Export statistics about bundle adjustment in a CSV file + * The contents of the file have been writen such that it is easy to handle it with + * a Python script or any spreadsheets (e.g. by copy/past the full content to LibreOffice) + * @param[in] folder The folder where you want to save the statistics file + * @param[in] filename The filename of the statistics file + * @return false it cannot open the file, true if it succeed + */ + bool exportToFile(const std::string& folder, const std::string& filename = "statistics.csv") const; + + /** + * @brief Display statistics about bundle adjustment in the terminal + * Logger need to accept log level + */ + void show() const; + + // public members + + /// number of successful iterations + std::size_t nbSuccessfullIterations = 0; + /// number of unsuccessful iterations + std::size_t nbUnsuccessfullIterations = 0; + /// number of resiudal blocks in the Ceres problem + std::size_t nbResidualBlocks = 0; + /// RMSEinitial: sqrt(initial_cost / num_residuals) + double RMSEinitial = 0.0; + /// RMSEfinal: sqrt(final_cost / num_residuals) + double RMSEfinal = 0.0; + /// time spent to solve the BA (s) + double time = 0.0; + /// number of states per parameter + std::map> parametersStates; + /// The distribution of the cameras for each graph distance + std::map nbCamerasPerDistance; + }; - void setDenseBA(); - void setSparseBA(); - - ceres::LinearSolverType linearSolverType; - ceres::PreconditionerType preconditionerType; - ceres::SparseLinearAlgebraLibraryType sparseLinearAlgebraLibraryType; - std::shared_ptr lossFunction; - unsigned int nbThreads; - unsigned int maxNumIterations; - bool useParametersOrdering = true; - bool summary = false; - bool verbose = true; - }; - - /** - * @brief Contains all informations related to the performed bundle adjustment. - */ - struct Statistics - { - Statistics() + /** + * @brief Bundle adjustment constructor + * @param[in] options The user Ceres options + * @see BundleAdjustmentCeres::CeresOptions + */ + BundleAdjustmentCeres(const CeresOptions& options = CeresOptions(), int minNbImagesToRefineOpticalCenter = 3) + : _ceresOptions(options), + _minNbImagesToRefineOpticalCenter(minNbImagesToRefineOpticalCenter) {} /** - * @brief Add a parameter state - * @param[in] parameter A bundle adjustment parameter - * @param[in] state A bundle adjustment state + * @brief Create a jacobian CRSMatrix + * @param[in] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions The chosen refine flag + * @param[out] jacobian The jacobian CSRMatrix + */ + void createJacobian(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::CRSMatrix& jacobian); + + /** + * @brief Perform a Bundle Adjustment on the SfM scene with refinement of the requested parameters + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions The chosen refine flag + * @return false if the bundle adjustment failed else true + * @see BundleAdjustment::Adjust + */ + bool adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions = REFINE_ALL); + + /** + * @brief Ajust parameters according to the local reconstruction graph in order do perfomr an optimezed bundle adjustmentor + * @param[in] localGraph The Local bundle adjustment graph pointer or nullptr (will refine everything) + */ + inline void useLocalStrategyGraph(const std::shared_ptr& localGraph) { _localGraph = localGraph; } + + /** + * @brief Get bundle adjustment statistics structure + * @return statistics structure const ptr + */ + inline const Statistics& getStatistics() const { return _statistics; } + + /** + * @brief Return true if the bundle adjustment use an external local graph + * @return true if use an external local graph + */ + inline bool useLocalStrategy() const { return (_localGraph != nullptr); } + + private: + /** + * @brief Clear structures for a new problem + */ + void resetProblem(); + + /** + * @brief Set user Ceres options to the solver + * @param[in,out] solverOptions The solver options structure */ - inline void addState(EParameter parameter, EParameterState state) + void setSolverOptions(ceres::Solver::Options& solverOptions) const; + + /** + * @brief Create a parameter block for each extrinsics according to the Ceres format: [Rx, Ry, Rz, tx, ty, tz] + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addExtrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a parameter block for each intrinsic according to the Ceres format + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addIntrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a residual block for each landmarks according to the Ceres format + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addLandmarksToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a residual block for each 2D constraints + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addConstraints2DToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a residual block for each rotation priors + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addRotationPriorsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create the Ceres bundle adjustement problem with: + * - extrincics and intrinsics parameters blocks. + * - residuals blocks for each observation. + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void createProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Update The given SfMData with the solver solution + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses + * @param[in] refineOptions The chosen refine flag + */ + void updateFromSolution(sfmData::SfMData& sfmData, ERefineOptions refineOptions) const; + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific pose. + * @param[in] poseId The pose id + * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) + */ + inline BundleAdjustment::EParameterState getPoseState(IndexT poseId) const { - ++parametersStates[parameter][state]; + return (_localGraph != nullptr ? _localGraph->getPoseState(poseId) : BundleAdjustment::EParameterState::REFINED); } /** - * @brief Export statistics about bundle adjustment in a CSV file - * The contents of the file have been writen such that it is easy to handle it with - * a Python script or any spreadsheets (e.g. by copy/past the full content to LibreOffice) - * @param[in] folder The folder where you want to save the statistics file - * @param[in] filename The filename of the statistics file - * @return false it cannot open the file, true if it succeed + * @brief Return the BundleAdjustment::EParameterState for a specific intrinsic. + * @param[in] intrinsicId The intrinsic id + * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) */ - bool exportToFile(const std::string& folder, const std::string& filename = "statistics.csv") const; + inline BundleAdjustment::EParameterState getIntrinsicState(IndexT intrinsicId) const + { + return (_localGraph != nullptr ? _localGraph->getIntrinsicState(intrinsicId) : BundleAdjustment::EParameterState::REFINED); + } /** - * @brief Display statistics about bundle adjustment in the terminal - * Logger need to accept log level + * @brief Return the BundleAdjustment::EParameterState for a specific landmark. + * @param[in] landmarkId The landmark id + * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) */ - void show() const; - - // public members - - /// number of successful iterations - std::size_t nbSuccessfullIterations = 0; - /// number of unsuccessful iterations - std::size_t nbUnsuccessfullIterations = 0; - /// number of resiudal blocks in the Ceres problem - std::size_t nbResidualBlocks = 0; - /// RMSEinitial: sqrt(initial_cost / num_residuals) - double RMSEinitial = 0.0; - /// RMSEfinal: sqrt(final_cost / num_residuals) - double RMSEfinal = 0.0; - /// time spent to solve the BA (s) - double time = 0.0; - /// number of states per parameter - std::map> parametersStates; - /// The distribution of the cameras for each graph distance - std::map nbCamerasPerDistance; - }; - - /** - * @brief Bundle adjustment constructor - * @param[in] options The user Ceres options - * @see BundleAdjustmentCeres::CeresOptions - */ - BundleAdjustmentCeres(const CeresOptions& options = CeresOptions(), int minNbImagesToRefineOpticalCenter = 3) - : _ceresOptions(options) - , _minNbImagesToRefineOpticalCenter(minNbImagesToRefineOpticalCenter) - {} - - /** - * @brief Create a jacobian CRSMatrix - * @param[in] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions The chosen refine flag - * @param[out] jacobian The jacobian CSRMatrix - */ - void createJacobian(const sfmData::SfMData& sfmData, - ERefineOptions refineOptions, - ceres::CRSMatrix& jacobian); - - /** - * @brief Perform a Bundle Adjustment on the SfM scene with refinement of the requested parameters - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions The chosen refine flag - * @return false if the bundle adjustment failed else true - * @see BundleAdjustment::Adjust - */ - bool adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions = REFINE_ALL); - - /** - * @brief Ajust parameters according to the local reconstruction graph in order do perfomr an optimezed bundle adjustmentor - * @param[in] localGraph The Local bundle adjustment graph pointer or nullptr (will refine everything) - */ - inline void useLocalStrategyGraph(const std::shared_ptr& localGraph) - { - _localGraph = localGraph; - } - - /** - * @brief Get bundle adjustment statistics structure - * @return statistics structure const ptr - */ - inline const Statistics& getStatistics() const - { - return _statistics; - } - - /** - * @brief Return true if the bundle adjustment use an external local graph - * @return true if use an external local graph - */ - inline bool useLocalStrategy() const - { - return (_localGraph != nullptr); - } - -private: - - /** - * @brief Clear structures for a new problem - */ - void resetProblem(); - - /** - * @brief Set user Ceres options to the solver - * @param[in,out] solverOptions The solver options structure - */ - void setSolverOptions(ceres::Solver::Options& solverOptions) const; - - /** - * @brief Create a parameter block for each extrinsics according to the Ceres format: [Rx, Ry, Rz, tx, ty, tz] - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addExtrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a parameter block for each intrinsic according to the Ceres format - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addIntrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a residual block for each landmarks according to the Ceres format - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addLandmarksToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a residual block for each 2D constraints - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addConstraints2DToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a residual block for each rotation priors - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addRotationPriorsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create the Ceres bundle adjustement problem with: - * - extrincics and intrinsics parameters blocks. - * - residuals blocks for each observation. - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void createProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Update The given SfMData with the solver solution - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses - * @param[in] refineOptions The chosen refine flag - */ - void updateFromSolution(sfmData::SfMData& sfmData, ERefineOptions refineOptions) const; - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific pose. - * @param[in] poseId The pose id - * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) - */ - inline BundleAdjustment::EParameterState getPoseState(IndexT poseId) const - { - return (_localGraph != nullptr ? _localGraph->getPoseState(poseId) : BundleAdjustment::EParameterState::REFINED); - } - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific intrinsic. - * @param[in] intrinsicId The intrinsic id - * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) - */ - inline BundleAdjustment::EParameterState getIntrinsicState(IndexT intrinsicId) const - { - return (_localGraph != nullptr ? _localGraph->getIntrinsicState(intrinsicId) : BundleAdjustment::EParameterState::REFINED); - } - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific landmark. - * @param[in] landmarkId The landmark id - * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) - */ - inline BundleAdjustment::EParameterState getLandmarkState(IndexT landmarkId) const - { - return (_localGraph != nullptr ? _localGraph->getLandmarkState(landmarkId) : BundleAdjustment::EParameterState::REFINED); - } - - // private members - - /// use or not the local budle adjustment strategy - std::shared_ptr _localGraph = nullptr; - - /// user Ceres options to use in the solver - CeresOptions _ceresOptions; - int _minNbImagesToRefineOpticalCenter = 3; - - /// user FeatureConstraint options to use - EFeatureConstraint _featureConstraint; - - /// last adjustment iteration statisics - Statistics _statistics; - - // data wrappers for refinement - - /// all parameters blocks pointers - std::vector _allParametersBlocks; - /// poses blocks wrapper - /// block: ceres angleAxis(3) + translation(3) - HashMap> _posesBlocks; //TODO : maybe we can use boost::flat_map instead of HashMap ? - /// intrinsics blocks wrapper - /// block: intrinsics params - HashMap> _intrinsicsBlocks; - /// landmarks blocks wrapper - /// block: 3d position(3) - HashMap> _landmarksBlocks; - /// rig sub-poses blocks wrapper - /// block: ceres angleAxis(3) + translation(3) - HashMap>> _rigBlocks; - - /// hinted order for ceres to eliminate blocks when solving. - /// note: this ceres parameter is built internally and must be reset on each call to the solver. - ceres::ParameterBlockOrdering _linearSolverOrdering; + inline BundleAdjustment::EParameterState getLandmarkState(IndexT landmarkId) const + { + return (_localGraph != nullptr ? _localGraph->getLandmarkState(landmarkId) : BundleAdjustment::EParameterState::REFINED); + } + // private members + + /// use or not the local budle adjustment strategy + std::shared_ptr _localGraph = nullptr; + + /// user Ceres options to use in the solver + CeresOptions _ceresOptions; + int _minNbImagesToRefineOpticalCenter = 3; + + /// user FeatureConstraint options to use + EFeatureConstraint _featureConstraint; + + /// last adjustment iteration statisics + Statistics _statistics; + + // data wrappers for refinement + + /// all parameters blocks pointers + std::vector _allParametersBlocks; + /// poses blocks wrapper + /// block: ceres angleAxis(3) + translation(3) + HashMap> _posesBlocks; // TODO : maybe we can use boost::flat_map instead of HashMap ? + /// intrinsics blocks wrapper + /// block: intrinsics params + HashMap> _intrinsicsBlocks; + /// landmarks blocks wrapper + /// block: 3d position(3) + HashMap> _landmarksBlocks; + /// rig sub-poses blocks wrapper + /// block: ceres angleAxis(3) + translation(3) + HashMap>> _rigBlocks; + + /// hinted order for ceres to eliminate blocks when solving. + /// note: this ceres parameter is built internally and must be reset on each call to the solver. + ceres::ParameterBlockOrdering _linearSolverOrdering; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.cpp b/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.cpp index 94215bc5b9..e0b467a4fe 100644 --- a/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.cpp +++ b/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.cpp @@ -14,7 +14,6 @@ #include #include - #include #include #include @@ -28,7 +27,6 @@ #include - namespace fs = boost::filesystem; namespace aliceVision { @@ -37,782 +35,789 @@ namespace sfm { using namespace aliceVision::camera; using namespace aliceVision::geometry; - -void BundleAdjustmentSymbolicCeres::addPose(const sfmData::CameraPose& cameraPose, bool isConstant, SE3::Matrix & poseBlock, ceres::Problem& problem, bool refineTranslation, bool refineRotation) +void BundleAdjustmentSymbolicCeres::addPose(const sfmData::CameraPose& cameraPose, + bool isConstant, + SE3::Matrix& poseBlock, + ceres::Problem& problem, + bool refineTranslation, + bool refineRotation) { - const Mat3& R = cameraPose.getTransform().rotation(); - const Vec3& t = cameraPose.getTransform().translation(); - - - poseBlock = SE3::Matrix::Identity(); - poseBlock.block<3,3>(0, 0) = R; - poseBlock.block<3,1>(0, 3) = t; - double * poseBlockPtr = poseBlock.data(); - problem.AddParameterBlock(poseBlockPtr, 16); - - - // add pose parameter to the all parameters blocks pointers list - _allParametersBlocks.push_back(poseBlockPtr); - - // keep the camera extrinsics constants - if(cameraPose.isLocked() || isConstant || (!refineTranslation && !refineRotation)) - { - // set the whole parameter block as constant. - _statistics.addState(EParameter::POSE, EParameterState::CONSTANT); - - problem.SetParameterBlockConstant(poseBlockPtr); - return; - } - - if (refineRotation || refineTranslation) - { - problem.SetManifold(poseBlockPtr, new SE3ManifoldLeft(refineRotation, refineTranslation)); - } - else - { - ALICEVISION_THROW_ERROR("BundleAdjustmentSymbolicCeres: Constant extrinsics not supported at this time"); - } - - _statistics.addState(EParameter::POSE, EParameterState::REFINED); + const Mat3& R = cameraPose.getTransform().rotation(); + const Vec3& t = cameraPose.getTransform().translation(); + + poseBlock = SE3::Matrix::Identity(); + poseBlock.block<3, 3>(0, 0) = R; + poseBlock.block<3, 1>(0, 3) = t; + double* poseBlockPtr = poseBlock.data(); + problem.AddParameterBlock(poseBlockPtr, 16); + + // add pose parameter to the all parameters blocks pointers list + _allParametersBlocks.push_back(poseBlockPtr); + + // keep the camera extrinsics constants + if (cameraPose.isLocked() || isConstant || (!refineTranslation && !refineRotation)) + { + // set the whole parameter block as constant. + _statistics.addState(EParameter::POSE, EParameterState::CONSTANT); + + problem.SetParameterBlockConstant(poseBlockPtr); + return; + } + + if (refineRotation || refineTranslation) + { + problem.SetManifold(poseBlockPtr, new SE3ManifoldLeft(refineRotation, refineTranslation)); + } + else + { + ALICEVISION_THROW_ERROR("BundleAdjustmentSymbolicCeres: Constant extrinsics not supported at this time"); + } + + _statistics.addState(EParameter::POSE, EParameterState::REFINED); } void BundleAdjustmentSymbolicCeres::CeresOptions::setDenseBA() { - // default configuration use a DENSE representation - preconditionerType = ceres::JACOBI; - linearSolverType = ceres::DENSE_SCHUR; - sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; // not used but just to avoid a warning in ceres - ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: DENSE_SCHUR"); + // default configuration use a DENSE representation + preconditionerType = ceres::JACOBI; + linearSolverType = ceres::DENSE_SCHUR; + sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; // not used but just to avoid a warning in ceres + ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: DENSE_SCHUR"); } void BundleAdjustmentSymbolicCeres::CeresOptions::setSparseBA() { - preconditionerType = ceres::JACOBI; - // if Sparse linear solver are available - // descending priority order by efficiency (SUITE_SPARSE > CX_SPARSE > EIGEN_SPARSE) - if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE)) - { - sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; - linearSolverType = ceres::SPARSE_SCHUR; - ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: SPARSE_SCHUR, SUITE_SPARSE"); - } + preconditionerType = ceres::JACOBI; + // if Sparse linear solver are available + // descending priority order by efficiency (SUITE_SPARSE > CX_SPARSE > EIGEN_SPARSE) + if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::SUITE_SPARSE)) + { + sparseLinearAlgebraLibraryType = ceres::SUITE_SPARSE; + linearSolverType = ceres::SPARSE_SCHUR; + ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: SPARSE_SCHUR, SUITE_SPARSE"); + } #if ALICEVISION_CERES_HAS_CXSPARSE - else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE)) - { - sparseLinearAlgebraLibraryType = ceres::CX_SPARSE; - linearSolverType = ceres::SPARSE_SCHUR; - ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: SPARSE_SCHUR, CX_SPARSE"); - } + else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::CX_SPARSE)) + { + sparseLinearAlgebraLibraryType = ceres::CX_SPARSE; + linearSolverType = ceres::SPARSE_SCHUR; + ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: SPARSE_SCHUR, CX_SPARSE"); + } #endif - else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) - { - sparseLinearAlgebraLibraryType = ceres::EIGEN_SPARSE; - linearSolverType = ceres::SPARSE_SCHUR; - ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: SPARSE_SCHUR, EIGEN_SPARSE"); - } - else - { - linearSolverType = ceres::DENSE_SCHUR; - ALICEVISION_LOG_WARNING("BundleAdjustmentSymbolic[Ceres]: no sparse BA available, fallback to dense BA."); - } + else if (ceres::IsSparseLinearAlgebraLibraryTypeAvailable(ceres::EIGEN_SPARSE)) + { + sparseLinearAlgebraLibraryType = ceres::EIGEN_SPARSE; + linearSolverType = ceres::SPARSE_SCHUR; + ALICEVISION_LOG_DEBUG("BundleAdjustmentSymbolic[Ceres]: SPARSE_SCHUR, EIGEN_SPARSE"); + } + else + { + linearSolverType = ceres::DENSE_SCHUR; + ALICEVISION_LOG_WARNING("BundleAdjustmentSymbolic[Ceres]: no sparse BA available, fallback to dense BA."); + } } bool BundleAdjustmentSymbolicCeres::Statistics::exportToFile(const std::string& folder, const std::string& filename) const { - std::ofstream os; - os.open((fs::path(folder) / filename).string(), std::ios::app); - - if(!os.is_open()) - { - ALICEVISION_LOG_DEBUG("Unable to open the Bundle adjustment statistics file: '" << filename << "'."); - return false; - } - - os.seekp(0, std::ios::end); // put the cursor at the end - - if(os.tellp() == std::streampos(0)) // 'tellp' return the cursor's position - { - // if the file does't exist: add a header. - os << "Time/BA(s);RefinedPose;ConstPose;IgnoredPose;" - "RefinedPts;ConstPts;IgnoredPts;" - "RefinedK;ConstK;IgnoredK;" - "ResidualBlocks;SuccessIteration;BadIteration;" - "InitRMSE;FinalRMSE;" - "d=-1;d=0;d=1;d=2;d=3;d=4;" - "d=5;d=6;d=7;d=8;d=9;d=10+;\n"; - } - - std::map> states = parametersStates; - std::size_t posesWithDistUpperThanTen = 0; - - for(const auto& it : nbCamerasPerDistance) - { - if (it.first >= 10) - { - posesWithDistUpperThanTen += it.second; - } - } - - os << time << ";" - << states[EParameter::POSE][EParameterState::REFINED] << ";" - << states[EParameter::POSE][EParameterState::CONSTANT] << ";" - << states[EParameter::POSE][EParameterState::IGNORED] << ";" - << states[EParameter::LANDMARK][EParameterState::REFINED] << ";" - << states[EParameter::LANDMARK][EParameterState::CONSTANT] << ";" - << states[EParameter::LANDMARK][EParameterState::IGNORED] << ";" - << states[EParameter::INTRINSIC][EParameterState::REFINED] << ";" - << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << ";" - << states[EParameter::INTRINSIC][EParameterState::IGNORED] << ";" - << nbResidualBlocks << ";" - << nbSuccessfullIterations << ";" - << nbUnsuccessfullIterations << ";" - << RMSEinitial << ";" - << RMSEfinal << ";"; - - for(int i = -1; i < 10; ++i) - { - auto cdIt = nbCamerasPerDistance.find(i); - if(cdIt != nbCamerasPerDistance.end()) - os << cdIt->second << ";"; - else - os << "0;"; - } - - os << posesWithDistUpperThanTen << ";\n"; - - os.close(); - return true; + std::ofstream os; + os.open((fs::path(folder) / filename).string(), std::ios::app); + + if (!os.is_open()) + { + ALICEVISION_LOG_DEBUG("Unable to open the Bundle adjustment statistics file: '" << filename << "'."); + return false; + } + + os.seekp(0, std::ios::end); // put the cursor at the end + + if (os.tellp() == std::streampos(0)) // 'tellp' return the cursor's position + { + // if the file does't exist: add a header. + os << "Time/BA(s);RefinedPose;ConstPose;IgnoredPose;" + "RefinedPts;ConstPts;IgnoredPts;" + "RefinedK;ConstK;IgnoredK;" + "ResidualBlocks;SuccessIteration;BadIteration;" + "InitRMSE;FinalRMSE;" + "d=-1;d=0;d=1;d=2;d=3;d=4;" + "d=5;d=6;d=7;d=8;d=9;d=10+;\n"; + } + + std::map> states = parametersStates; + std::size_t posesWithDistUpperThanTen = 0; + + for (const auto& it : nbCamerasPerDistance) + { + if (it.first >= 10) + { + posesWithDistUpperThanTen += it.second; + } + } + + os << time << ";" << states[EParameter::POSE][EParameterState::REFINED] << ";" << states[EParameter::POSE][EParameterState::CONSTANT] << ";" + << states[EParameter::POSE][EParameterState::IGNORED] << ";" << states[EParameter::LANDMARK][EParameterState::REFINED] << ";" + << states[EParameter::LANDMARK][EParameterState::CONSTANT] << ";" << states[EParameter::LANDMARK][EParameterState::IGNORED] << ";" + << states[EParameter::INTRINSIC][EParameterState::REFINED] << ";" << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << ";" + << states[EParameter::INTRINSIC][EParameterState::IGNORED] << ";" << nbResidualBlocks << ";" << nbSuccessfullIterations << ";" + << nbUnsuccessfullIterations << ";" << RMSEinitial << ";" << RMSEfinal << ";"; + + for (int i = -1; i < 10; ++i) + { + auto cdIt = nbCamerasPerDistance.find(i); + if (cdIt != nbCamerasPerDistance.end()) + os << cdIt->second << ";"; + else + os << "0;"; + } + + os << posesWithDistUpperThanTen << ";\n"; + + os.close(); + return true; } void BundleAdjustmentSymbolicCeres::Statistics::show() const { - std::map> states = parametersStates; - std::stringstream ss; + std::map> states = parametersStates; + std::stringstream ss; - if(!nbCamerasPerDistance.empty()) - { - std::size_t nbCamNotConnected = 0; - std::size_t nbCamDistEqZero = 0; - std::size_t nbCamDistEqOne = 0; - std::size_t nbCamDistUpperOne = 0; + if (!nbCamerasPerDistance.empty()) + { + std::size_t nbCamNotConnected = 0; + std::size_t nbCamDistEqZero = 0; + std::size_t nbCamDistEqOne = 0; + std::size_t nbCamDistUpperOne = 0; - for(const auto & camdistIt : nbCamerasPerDistance) + for (const auto& camdistIt : nbCamerasPerDistance) + { + if (camdistIt.first < 0) + nbCamNotConnected += camdistIt.second; + else if (camdistIt.first == 1) + nbCamDistEqZero += camdistIt.second; + else if (camdistIt.first == 1) + nbCamDistEqOne += camdistIt.second; + else if (camdistIt.first > 1) + nbCamDistUpperOne += camdistIt.second; + } + + ss << "\t- local strategy enabled: yes\n" + << "\t- graph-distances distribution:\n" + << "\t - not connected: " << nbCamNotConnected << " cameras\n" + << "\t - D = 0: " << nbCamDistEqZero << " cameras\n" + << "\t - D = 1: " << nbCamDistEqOne << " cameras\n" + << "\t - D > 1: " << nbCamDistUpperOne << " cameras\n"; + } + else { - if(camdistIt.first < 0) - nbCamNotConnected += camdistIt.second; - else if(camdistIt.first == 1) - nbCamDistEqZero += camdistIt.second; - else if(camdistIt.first == 1) - nbCamDistEqOne += camdistIt.second; - else if(camdistIt.first > 1) - nbCamDistUpperOne += camdistIt.second; + ss << "\t- local strategy enabled: no\n"; } - ss << "\t- local strategy enabled: yes\n" - << "\t- graph-distances distribution:\n" - << "\t - not connected: " << nbCamNotConnected << " cameras\n" - << "\t - D = 0: " << nbCamDistEqZero << " cameras\n" - << "\t - D = 1: " << nbCamDistEqOne << " cameras\n" - << "\t - D > 1: " << nbCamDistUpperOne << " cameras\n"; - } - else - { - ss << "\t- local strategy enabled: no\n"; - } - - ALICEVISION_LOG_INFO("Bundle Adjustment Statistics:\n" - << ss.str() - << "\t- adjustment duration: " << time << " s\n" - << "\t- poses:\n" - << "\t - # refined: " << states[EParameter::POSE][EParameterState::REFINED] << "\n" - << "\t - # constant: " << states[EParameter::POSE][EParameterState::CONSTANT] << "\n" - << "\t - # ignored: " << states[EParameter::POSE][EParameterState::IGNORED] << "\n" - << "\t- landmarks:\n" - << "\t - # refined: " << states[EParameter::LANDMARK][EParameterState::REFINED] << "\n" - << "\t - # constant: " << states[EParameter::LANDMARK][EParameterState::CONSTANT] << "\n" - << "\t - # ignored: " << states[EParameter::LANDMARK][EParameterState::IGNORED] << "\n" - << "\t- intrinsics:\n" - << "\t - # refined: " << states[EParameter::INTRINSIC][EParameterState::REFINED] << "\n" - << "\t - # constant: " << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << "\n" - << "\t - # ignored: " << states[EParameter::INTRINSIC][EParameterState::IGNORED] << "\n" - << "\t- # residual blocks: " << nbResidualBlocks << "\n" - << "\t- # successful iterations: " << nbSuccessfullIterations << "\n" - << "\t- # unsuccessful iterations: " << nbUnsuccessfullIterations << "\n" - << "\t- initial RMSE: " << RMSEinitial << "\n" - << "\t- final RMSE: " << RMSEfinal); + ALICEVISION_LOG_INFO("Bundle Adjustment Statistics:\n" + << ss.str() << "\t- adjustment duration: " << time << " s\n" + << "\t- poses:\n" + << "\t - # refined: " << states[EParameter::POSE][EParameterState::REFINED] << "\n" + << "\t - # constant: " << states[EParameter::POSE][EParameterState::CONSTANT] << "\n" + << "\t - # ignored: " << states[EParameter::POSE][EParameterState::IGNORED] << "\n" + << "\t- landmarks:\n" + << "\t - # refined: " << states[EParameter::LANDMARK][EParameterState::REFINED] << "\n" + << "\t - # constant: " << states[EParameter::LANDMARK][EParameterState::CONSTANT] << "\n" + << "\t - # ignored: " << states[EParameter::LANDMARK][EParameterState::IGNORED] << "\n" + << "\t- intrinsics:\n" + << "\t - # refined: " << states[EParameter::INTRINSIC][EParameterState::REFINED] << "\n" + << "\t - # constant: " << states[EParameter::INTRINSIC][EParameterState::CONSTANT] << "\n" + << "\t - # ignored: " << states[EParameter::INTRINSIC][EParameterState::IGNORED] << "\n" + << "\t- # residual blocks: " << nbResidualBlocks << "\n" + << "\t- # successful iterations: " << nbSuccessfullIterations << "\n" + << "\t- # unsuccessful iterations: " << nbUnsuccessfullIterations << "\n" + << "\t- initial RMSE: " << RMSEinitial << "\n" + << "\t- final RMSE: " << RMSEfinal); } void BundleAdjustmentSymbolicCeres::setSolverOptions(ceres::Solver::Options& solverOptions) const { - solverOptions.preconditioner_type = _ceresOptions.preconditionerType; - solverOptions.linear_solver_type = _ceresOptions.linearSolverType; - solverOptions.sparse_linear_algebra_library_type = _ceresOptions.sparseLinearAlgebraLibraryType; - solverOptions.minimizer_progress_to_stdout = _ceresOptions.verbose; - solverOptions.logging_type = ceres::SILENT; - solverOptions.num_threads = _ceresOptions.nbThreads; - solverOptions.max_num_iterations = _ceresOptions.maxNumIterations; - /*solverOptions.function_tolerance = 1e-12; - solverOptions.gradient_tolerance = 1e-12; - solverOptions.parameter_tolerance = 1e-12;*/ + solverOptions.preconditioner_type = _ceresOptions.preconditionerType; + solverOptions.linear_solver_type = _ceresOptions.linearSolverType; + solverOptions.sparse_linear_algebra_library_type = _ceresOptions.sparseLinearAlgebraLibraryType; + solverOptions.minimizer_progress_to_stdout = _ceresOptions.verbose; + solverOptions.logging_type = ceres::SILENT; + solverOptions.num_threads = _ceresOptions.nbThreads; + solverOptions.max_num_iterations = _ceresOptions.maxNumIterations; + /*solverOptions.function_tolerance = 1e-12; + solverOptions.gradient_tolerance = 1e-12; + solverOptions.parameter_tolerance = 1e-12;*/ #if CERES_VERSION_MAJOR < 2 - solverOptions.num_linear_solver_threads = _ceresOptions.nbThreads; + solverOptions.num_linear_solver_threads = _ceresOptions.nbThreads; #endif - if(_ceresOptions.useParametersOrdering) - { - // copy ParameterBlockOrdering - solverOptions.linear_solver_ordering.reset(new ceres::ParameterBlockOrdering(_linearSolverOrdering)); - } + if (_ceresOptions.useParametersOrdering) + { + // copy ParameterBlockOrdering + solverOptions.linear_solver_ordering.reset(new ceres::ParameterBlockOrdering(_linearSolverOrdering)); + } } -void BundleAdjustmentSymbolicCeres::addExtrinsicsToProblem(const sfmData::SfMData& sfmData, BundleAdjustment::ERefineOptions refineOptions, ceres::Problem& problem) +void BundleAdjustmentSymbolicCeres::addExtrinsicsToProblem(const sfmData::SfMData& sfmData, + BundleAdjustment::ERefineOptions refineOptions, + ceres::Problem& problem) { - const bool refineTranslation = refineOptions & BundleAdjustment::REFINE_TRANSLATION; - const bool refineRotation = refineOptions & BundleAdjustment::REFINE_ROTATION; + const bool refineTranslation = refineOptions & BundleAdjustment::REFINE_TRANSLATION; + const bool refineRotation = refineOptions & BundleAdjustment::REFINE_ROTATION; - // setup poses data - for(const auto& posePair : sfmData.getPoses()) - { - const IndexT poseId = posePair.first; - const sfmData::CameraPose& pose = posePair.second; - - // skip camera pose set as Ignored in the Local strategy - if(getPoseState(poseId) == EParameterState::IGNORED) + // setup poses data + for (const auto& posePair : sfmData.getPoses()) { - _statistics.addState(EParameter::POSE, EParameterState::IGNORED); - continue; - } + const IndexT poseId = posePair.first; + const sfmData::CameraPose& pose = posePair.second; - const bool isConstant = (getPoseState(poseId) == EParameterState::CONSTANT); + // skip camera pose set as Ignored in the Local strategy + if (getPoseState(poseId) == EParameterState::IGNORED) + { + _statistics.addState(EParameter::POSE, EParameterState::IGNORED); + continue; + } - addPose(pose, isConstant, _posesBlocks[poseId], problem, refineTranslation, refineRotation); - } + const bool isConstant = (getPoseState(poseId) == EParameterState::CONSTANT); - // setup sub-poses data - for(const auto& rigPair : sfmData.getRigs()) - { - const IndexT rigId = rigPair.first; - const sfmData::Rig& rig = rigPair.second; - const std::size_t nbSubPoses = rig.getNbSubPoses(); + addPose(pose, isConstant, _posesBlocks[poseId], problem, refineTranslation, refineRotation); + } - for(std::size_t subPoseId = 0 ; subPoseId < nbSubPoses; ++subPoseId) + // setup sub-poses data + for (const auto& rigPair : sfmData.getRigs()) { - const sfmData::RigSubPose& rigSubPose = rig.getSubPose(subPoseId); + const IndexT rigId = rigPair.first; + const sfmData::Rig& rig = rigPair.second; + const std::size_t nbSubPoses = rig.getNbSubPoses(); - if(rigSubPose.status == sfmData::ERigSubPoseStatus::UNINITIALIZED) - continue; + for (std::size_t subPoseId = 0; subPoseId < nbSubPoses; ++subPoseId) + { + const sfmData::RigSubPose& rigSubPose = rig.getSubPose(subPoseId); - const bool isConstant = (rigSubPose.status == sfmData::ERigSubPoseStatus::CONSTANT); + if (rigSubPose.status == sfmData::ERigSubPoseStatus::UNINITIALIZED) + continue; - addPose(sfmData::CameraPose(rigSubPose.pose), isConstant, _rigBlocks[rigId][subPoseId], problem, refineTranslation, refineRotation); - } - } + const bool isConstant = (rigSubPose.status == sfmData::ERigSubPoseStatus::CONSTANT); + addPose(sfmData::CameraPose(rigSubPose.pose), isConstant, _rigBlocks[rigId][subPoseId], problem, refineTranslation, refineRotation); + } + } - //Add default rig pose - addPose(sfmData::CameraPose(), true, _rigNull, problem, refineTranslation, refineRotation); + // Add default rig pose + addPose(sfmData::CameraPose(), true, _rigNull, problem, refineTranslation, refineRotation); } -void BundleAdjustmentSymbolicCeres::addIntrinsicsToProblem(const sfmData::SfMData& sfmData, BundleAdjustment::ERefineOptions refineOptions, ceres::Problem& problem) +void BundleAdjustmentSymbolicCeres::addIntrinsicsToProblem(const sfmData::SfMData& sfmData, + BundleAdjustment::ERefineOptions refineOptions, + ceres::Problem& problem) { - const bool refineIntrinsicsOpticalCenter = (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); - const bool refineIntrinsicsFocalLength = refineOptions & REFINE_INTRINSICS_FOCAL; - const bool refineIntrinsicsDistortion = refineOptions & REFINE_INTRINSICS_DISTORTION; - const bool refineIntrinsics = refineIntrinsicsDistortion || refineIntrinsicsFocalLength || refineIntrinsicsOpticalCenter; - const bool fixFocalRatio = true; - - std::map intrinsicsUsage; - - // count the number of reconstructed views per intrinsic - for(const auto& viewPair: sfmData.getViews()) - { - const sfmData::View& view = *(viewPair.second); - - if(intrinsicsUsage.find(view.getIntrinsicId()) == intrinsicsUsage.end()) - intrinsicsUsage[view.getIntrinsicId()] = 0; - - if(sfmData.isPoseAndIntrinsicDefined(&view)) - ++intrinsicsUsage.at(view.getIntrinsicId()); - } - - for(const auto& intrinsicPair: sfmData.getIntrinsics()) - { - const IndexT intrinsicId = intrinsicPair.first; - const auto& intrinsicPtr = intrinsicPair.second; - const auto usageIt = intrinsicsUsage.find(intrinsicId); - if(usageIt == intrinsicsUsage.end()) - // if the intrinsic is never referenced by any view, skip it - continue; - const std::size_t usageCount = usageIt->second; - - // do not refine an intrinsic does not used by any reconstructed view - if(usageCount <= 0 || getIntrinsicState(intrinsicId) == EParameterState::IGNORED) + const bool refineIntrinsicsOpticalCenter = + (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); + const bool refineIntrinsicsFocalLength = refineOptions & REFINE_INTRINSICS_FOCAL; + const bool refineIntrinsicsDistortion = refineOptions & REFINE_INTRINSICS_DISTORTION; + const bool refineIntrinsics = refineIntrinsicsDistortion || refineIntrinsicsFocalLength || refineIntrinsicsOpticalCenter; + const bool fixFocalRatio = true; + + std::map intrinsicsUsage; + + // count the number of reconstructed views per intrinsic + for (const auto& viewPair : sfmData.getViews()) { - _statistics.addState(EParameter::INTRINSIC, EParameterState::IGNORED); - continue; + const sfmData::View& view = *(viewPair.second); + + if (intrinsicsUsage.find(view.getIntrinsicId()) == intrinsicsUsage.end()) + intrinsicsUsage[view.getIntrinsicId()] = 0; + + if (sfmData.isPoseAndIntrinsicDefined(&view)) + ++intrinsicsUsage.at(view.getIntrinsicId()); } - assert(isValid(intrinsicPtr->getType())); + for (const auto& intrinsicPair : sfmData.getIntrinsics()) + { + const IndexT intrinsicId = intrinsicPair.first; + const auto& intrinsicPtr = intrinsicPair.second; + const auto usageIt = intrinsicsUsage.find(intrinsicId); + if (usageIt == intrinsicsUsage.end()) + // if the intrinsic is never referenced by any view, skip it + continue; + const std::size_t usageCount = usageIt->second; + + // do not refine an intrinsic does not used by any reconstructed view + if (usageCount <= 0 || getIntrinsicState(intrinsicId) == EParameterState::IGNORED) + { + _statistics.addState(EParameter::INTRINSIC, EParameterState::IGNORED); + continue; + } - _intrinsicObjects[intrinsicId].reset(intrinsicPtr->clone()); + assert(isValid(intrinsicPtr->getType())); - std::vector& intrinsicBlock = _intrinsicsBlocks[intrinsicId]; - intrinsicBlock = intrinsicPtr->getParams(); + _intrinsicObjects[intrinsicId].reset(intrinsicPtr->clone()); - double* intrinsicBlockPtr = intrinsicBlock.data(); - problem.AddParameterBlock(intrinsicBlockPtr, intrinsicBlock.size()); + std::vector& intrinsicBlock = _intrinsicsBlocks[intrinsicId]; + intrinsicBlock = intrinsicPtr->getParams(); - // add intrinsic parameter to the all parameters blocks pointers list - _allParametersBlocks.push_back(intrinsicBlockPtr); + double* intrinsicBlockPtr = intrinsicBlock.data(); + problem.AddParameterBlock(intrinsicBlockPtr, intrinsicBlock.size()); - // keep the camera intrinsic constant - if(intrinsicPtr->isLocked() || !refineIntrinsics || getIntrinsicState(intrinsicId) == EParameterState::CONSTANT) - { - // set the whole parameter block as constant. - _statistics.addState(EParameter::INTRINSIC, EParameterState::CONSTANT); - problem.SetParameterBlockConstant(intrinsicBlockPtr); - continue; - } + // add intrinsic parameter to the all parameters blocks pointers list + _allParametersBlocks.push_back(intrinsicBlockPtr); - // constant parameters - bool lockCenter = false; - bool lockFocal = false; - bool lockRatio = true; - bool lockDistortion = false; - double focalRatio = 1.0; + // keep the camera intrinsic constant + if (intrinsicPtr->isLocked() || !refineIntrinsics || getIntrinsicState(intrinsicId) == EParameterState::CONSTANT) + { + // set the whole parameter block as constant. + _statistics.addState(EParameter::INTRINSIC, EParameterState::CONSTANT); + problem.SetParameterBlockConstant(intrinsicBlockPtr); + continue; + } - // refine the focal length - if(refineIntrinsicsFocalLength) - { - std::shared_ptr intrinsicScaleOffset = std::dynamic_pointer_cast(intrinsicPtr); - if(intrinsicScaleOffset->getInitialScale().x() > 0 && intrinsicScaleOffset->getInitialScale().y() > 0 && _ceresOptions.useFocalPrior) - { - // if we have an initial guess, we only authorize a margin around this value. - assert(intrinsicBlock.size() >= 1); - const unsigned int maxFocalError = 0.2 * std::max(intrinsicPtr->w(), intrinsicPtr->h()); // TODO : check if rounding is needed - problem.SetParameterLowerBound(intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() - maxFocalError)); - problem.SetParameterUpperBound(intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() + maxFocalError)); - problem.SetParameterLowerBound(intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() - maxFocalError)); - problem.SetParameterUpperBound(intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() + maxFocalError)); - } - else // no initial guess - { - // we don't have an initial guess, but we assume that we use - // a converging lens, so the focal length should be positive. - problem.SetParameterLowerBound(intrinsicBlockPtr, 0, 0.0); - problem.SetParameterLowerBound(intrinsicBlockPtr, 1, 0.0); - } - - focalRatio = intrinsicBlockPtr[1] / intrinsicBlockPtr[0]; - - std::shared_ptr castedcam_iso = std::dynamic_pointer_cast(intrinsicPtr); - if (castedcam_iso) - { - lockRatio = castedcam_iso->isRatioLocked(); - } - } - else - { - // set focal length as constant - lockFocal = true; - } + // constant parameters + bool lockCenter = false; + bool lockFocal = false; + bool lockRatio = true; + bool lockDistortion = false; + double focalRatio = 1.0; - const bool optional_center = ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA) && (usageCount > _minNbImagesToRefineOpticalCenter)); - if ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || optional_center) - { - // refine optical center within 10% of the image size. - assert(intrinsicBlock.size() >= 4); + // refine the focal length + if (refineIntrinsicsFocalLength) + { + std::shared_ptr intrinsicScaleOffset = + std::dynamic_pointer_cast(intrinsicPtr); + if (intrinsicScaleOffset->getInitialScale().x() > 0 && intrinsicScaleOffset->getInitialScale().y() > 0 && _ceresOptions.useFocalPrior) + { + // if we have an initial guess, we only authorize a margin around this value. + assert(intrinsicBlock.size() >= 1); + const unsigned int maxFocalError = 0.2 * std::max(intrinsicPtr->w(), intrinsicPtr->h()); // TODO : check if rounding is needed + problem.SetParameterLowerBound( + intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() - maxFocalError)); + problem.SetParameterUpperBound( + intrinsicBlockPtr, 0, static_cast(intrinsicScaleOffset->getInitialScale().x() + maxFocalError)); + problem.SetParameterLowerBound( + intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() - maxFocalError)); + problem.SetParameterUpperBound( + intrinsicBlockPtr, 1, static_cast(intrinsicScaleOffset->getInitialScale().y() + maxFocalError)); + } + else // no initial guess + { + // we don't have an initial guess, but we assume that we use + // a converging lens, so the focal length should be positive. + problem.SetParameterLowerBound(intrinsicBlockPtr, 0, 0.0); + problem.SetParameterLowerBound(intrinsicBlockPtr, 1, 0.0); + } + + focalRatio = intrinsicBlockPtr[1] / intrinsicBlockPtr[0]; + + std::shared_ptr castedcam_iso = std::dynamic_pointer_cast(intrinsicPtr); + if (castedcam_iso) + { + lockRatio = castedcam_iso->isRatioLocked(); + } + } + else + { + // set focal length as constant + lockFocal = true; + } - const double opticalCenterMinPercent = -0.05; - const double opticalCenterMaxPercent = 0.05; + const bool optional_center = + ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA) && (usageCount > _minNbImagesToRefineOpticalCenter)); + if ((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || optional_center) + { + // refine optical center within 10% of the image size. + assert(intrinsicBlock.size() >= 4); - // add bounds to the principal point - problem.SetParameterLowerBound(intrinsicBlockPtr, 2, opticalCenterMinPercent * intrinsicPtr->w()); - problem.SetParameterUpperBound(intrinsicBlockPtr, 2, opticalCenterMaxPercent * intrinsicPtr->w()); - problem.SetParameterLowerBound(intrinsicBlockPtr, 3, opticalCenterMinPercent * intrinsicPtr->h()); - problem.SetParameterUpperBound(intrinsicBlockPtr, 3, opticalCenterMaxPercent * intrinsicPtr->h()); - } - else - { - // don't refine the optical center - lockCenter = true; - } + const double opticalCenterMinPercent = -0.05; + const double opticalCenterMaxPercent = 0.05; - // lens distortion - if(!refineIntrinsicsDistortion) - { - lockDistortion = true; - } + // add bounds to the principal point + problem.SetParameterLowerBound(intrinsicBlockPtr, 2, opticalCenterMinPercent * intrinsicPtr->w()); + problem.SetParameterUpperBound(intrinsicBlockPtr, 2, opticalCenterMaxPercent * intrinsicPtr->w()); + problem.SetParameterLowerBound(intrinsicBlockPtr, 3, opticalCenterMinPercent * intrinsicPtr->h()); + problem.SetParameterUpperBound(intrinsicBlockPtr, 3, opticalCenterMaxPercent * intrinsicPtr->h()); + } + else + { + // don't refine the optical center + lockCenter = true; + } + + // lens distortion + if (!refineIntrinsicsDistortion) + { + lockDistortion = true; + } - IntrinsicsManifoldSymbolic* subsetManifold = new IntrinsicsManifoldSymbolic(intrinsicBlock.size(), focalRatio, - lockFocal, lockRatio, lockCenter, lockDistortion); - problem.SetManifold(intrinsicBlockPtr, subsetManifold); - _statistics.addState(EParameter::INTRINSIC, EParameterState::REFINED); - } + IntrinsicsManifoldSymbolic* subsetManifold = + new IntrinsicsManifoldSymbolic(intrinsicBlock.size(), focalRatio, lockFocal, lockRatio, lockCenter, lockDistortion); + problem.SetManifold(intrinsicBlockPtr, subsetManifold); + _statistics.addState(EParameter::INTRINSIC, EParameterState::REFINED); + } } void BundleAdjustmentSymbolicCeres::addLandmarksToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - const bool refineStructure = refineOptions & REFINE_STRUCTURE; - const bool refineStructureAsNormal = refineOptions & REFINE_STRUCTURE_AS_NORMALS; - - // set a LossFunction to be less penalized by false measurements. - // note: set it to NULL if you don't want use a lossFunction. - ceres::LossFunction* lossFunction = _ceresOptions.lossFunction.get(); - - // build the residual blocks corresponding to the track observations - for(const auto& landmarkPair: sfmData.getLandmarks()) - { - const IndexT landmarkId = landmarkPair.first; - const sfmData::Landmark& landmark = landmarkPair.second; - - // do not create a residual block if the landmark - // have been set as Ignored by the Local BA strategy - if(getLandmarkState(landmarkId) == EParameterState::IGNORED) - { - _statistics.addState(EParameter::LANDMARK, EParameterState::IGNORED); - continue; - } + const bool refineStructure = refineOptions & REFINE_STRUCTURE; + const bool refineStructureAsNormal = refineOptions & REFINE_STRUCTURE_AS_NORMALS; - std::array& landmarkBlock = _landmarksBlocks[landmarkId]; - for(std::size_t i = 0; i < 3; ++i) - landmarkBlock.at(i) = landmark.X(Eigen::Index(i)); + // set a LossFunction to be less penalized by false measurements. + // note: set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* lossFunction = _ceresOptions.lossFunction.get(); - double* landmarkBlockPtr = landmarkBlock.data(); - problem.AddParameterBlock(landmarkBlockPtr, 3); - if (refineStructureAsNormal) + // build the residual blocks corresponding to the track observations + for (const auto& landmarkPair : sfmData.getLandmarks()) { - problem.SetManifold(landmarkBlockPtr, new SO3Vec); - } + const IndexT landmarkId = landmarkPair.first; + const sfmData::Landmark& landmark = landmarkPair.second; - // add landmark parameter to the all parameters blocks pointers list - _allParametersBlocks.push_back(landmarkBlockPtr); + // do not create a residual block if the landmark + // have been set as Ignored by the Local BA strategy + if (getLandmarkState(landmarkId) == EParameterState::IGNORED) + { + _statistics.addState(EParameter::LANDMARK, EParameterState::IGNORED); + continue; + } - // iterate over 2D observation associated to the 3D landmark - for(const auto& observationPair: landmark.observations) - { - const sfmData::View& view = sfmData.getView(observationPair.first); - const sfmData::Observation& observation = observationPair.second; - - //Get shared object clone - const std::shared_ptr intrinsic = _intrinsicObjects[view.getIntrinsicId()]; - - // each residual block takes a point and a camera as input and outputs a 2 - // dimensional residual. Internally, the cost function stores the observed - // image location and compares the reprojection against the observation. - - assert(getPoseState(view.getPoseId()) != EParameterState::IGNORED); - assert(getIntrinsicState(view.getIntrinsicId()) != EParameterState::IGNORED); - - // needed parameters to create a residual block (K, pose) - double* poseBlockPtr = _posesBlocks.at(view.getPoseId()).data(); - double* intrinsicBlockPtr = _intrinsicsBlocks.at(view.getIntrinsicId()).data(); - - bool withRig = (view.isPartOfRig() && !view.isPoseIndependant()); - double * rigBlockPtr = nullptr; - if (withRig) { - rigBlockPtr = _rigBlocks.at(view.getRigId()).at(view.getSubPoseId()).data(); - } - else { - rigBlockPtr = _rigNull.data(); - } - - // apply a specific parameter ordering: - if(_ceresOptions.useParametersOrdering) - { - _linearSolverOrdering.AddElementToGroup(landmarkBlockPtr, 0); - _linearSolverOrdering.AddElementToGroup(poseBlockPtr, 1); - _linearSolverOrdering.AddElementToGroup(intrinsicBlockPtr, 2); - } - - if (withRig) - { - ceres::CostFunction* costFunction = new CostProjection(observation, intrinsic, withRig); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr, rigBlockPtr, intrinsicBlockPtr, landmarkBlockPtr); - } - else - { - ceres::CostFunction* costFunction = new CostProjectionSimple(observation, intrinsic); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr, intrinsicBlockPtr, landmarkBlockPtr); - } - - if(!refineStructure || getLandmarkState(landmarkId) == EParameterState::CONSTANT) - { - // set the whole landmark parameter block as constant. - _statistics.addState(EParameter::LANDMARK, EParameterState::CONSTANT); - problem.SetParameterBlockConstant(landmarkBlockPtr); - } - else - { - _statistics.addState(EParameter::LANDMARK, EParameterState::REFINED); - } + std::array& landmarkBlock = _landmarksBlocks[landmarkId]; + for (std::size_t i = 0; i < 3; ++i) + landmarkBlock.at(i) = landmark.X(Eigen::Index(i)); + + double* landmarkBlockPtr = landmarkBlock.data(); + problem.AddParameterBlock(landmarkBlockPtr, 3); + if (refineStructureAsNormal) + { + problem.SetManifold(landmarkBlockPtr, new SO3Vec); + } + + // add landmark parameter to the all parameters blocks pointers list + _allParametersBlocks.push_back(landmarkBlockPtr); + + // iterate over 2D observation associated to the 3D landmark + for (const auto& observationPair : landmark.observations) + { + const sfmData::View& view = sfmData.getView(observationPair.first); + const sfmData::Observation& observation = observationPair.second; + + // Get shared object clone + const std::shared_ptr intrinsic = _intrinsicObjects[view.getIntrinsicId()]; + + // each residual block takes a point and a camera as input and outputs a 2 + // dimensional residual. Internally, the cost function stores the observed + // image location and compares the reprojection against the observation. + + assert(getPoseState(view.getPoseId()) != EParameterState::IGNORED); + assert(getIntrinsicState(view.getIntrinsicId()) != EParameterState::IGNORED); + + // needed parameters to create a residual block (K, pose) + double* poseBlockPtr = _posesBlocks.at(view.getPoseId()).data(); + double* intrinsicBlockPtr = _intrinsicsBlocks.at(view.getIntrinsicId()).data(); + + bool withRig = (view.isPartOfRig() && !view.isPoseIndependant()); + double* rigBlockPtr = nullptr; + if (withRig) + { + rigBlockPtr = _rigBlocks.at(view.getRigId()).at(view.getSubPoseId()).data(); + } + else + { + rigBlockPtr = _rigNull.data(); + } + + // apply a specific parameter ordering: + if (_ceresOptions.useParametersOrdering) + { + _linearSolverOrdering.AddElementToGroup(landmarkBlockPtr, 0); + _linearSolverOrdering.AddElementToGroup(poseBlockPtr, 1); + _linearSolverOrdering.AddElementToGroup(intrinsicBlockPtr, 2); + } + + if (withRig) + { + ceres::CostFunction* costFunction = new CostProjection(observation, intrinsic, withRig); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr, rigBlockPtr, intrinsicBlockPtr, landmarkBlockPtr); + } + else + { + ceres::CostFunction* costFunction = new CostProjectionSimple(observation, intrinsic); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr, intrinsicBlockPtr, landmarkBlockPtr); + } + + if (!refineStructure || getLandmarkState(landmarkId) == EParameterState::CONSTANT) + { + // set the whole landmark parameter block as constant. + _statistics.addState(EParameter::LANDMARK, EParameterState::CONSTANT); + problem.SetParameterBlockConstant(landmarkBlockPtr); + } + else + { + _statistics.addState(EParameter::LANDMARK, EParameterState::REFINED); + } + } } - } } void BundleAdjustmentSymbolicCeres::addConstraints2DToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - // set a LossFunction to be less penalized by false measurements. - // note: set it to NULL if you don't want use a lossFunction. - ceres::LossFunction* lossFunction = new ceres::HuberLoss(Square(8.0)); // TODO: make the LOSS function and the parameter an option + // set a LossFunction to be less penalized by false measurements. + // note: set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* lossFunction = new ceres::HuberLoss(Square(8.0)); // TODO: make the LOSS function and the parameter an option - for (const auto & constraint : sfmData.getConstraints2D()) { - const sfmData::View& view_1 = sfmData.getView(constraint.ViewFirst); - const sfmData::View& view_2 = sfmData.getView(constraint.ViewSecond); + for (const auto& constraint : sfmData.getConstraints2D()) + { + const sfmData::View& view_1 = sfmData.getView(constraint.ViewFirst); + const sfmData::View& view_2 = sfmData.getView(constraint.ViewSecond); - assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); - assert(getIntrinsicState(view_1.getIntrinsicId()) != EParameterState::IGNORED); - assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); - assert(getIntrinsicState(view_2.getIntrinsicId()) != EParameterState::IGNORED); + assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); + assert(getIntrinsicState(view_1.getIntrinsicId()) != EParameterState::IGNORED); + assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); + assert(getIntrinsicState(view_2.getIntrinsicId()) != EParameterState::IGNORED); - /* Get pose */ - double * poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); - double * poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); + /* Get pose */ + double* poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); + double* poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); - /*Get intrinsics */ - double * intrinsicBlockPtr_1 = _intrinsicsBlocks.at(view_1.getIntrinsicId()).data(); - double * intrinsicBlockPtr_2 = _intrinsicsBlocks.at(view_2.getIntrinsicId()).data(); + /*Get intrinsics */ + double* intrinsicBlockPtr_1 = _intrinsicsBlocks.at(view_1.getIntrinsicId()).data(); + double* intrinsicBlockPtr_2 = _intrinsicsBlocks.at(view_2.getIntrinsicId()).data(); - /* For the moment assume a unique camera */ - assert(intrinsicBlockPtr_1 == intrinsicBlockPtr_2); + /* For the moment assume a unique camera */ + assert(intrinsicBlockPtr_1 == intrinsicBlockPtr_2); - std::shared_ptr intrinsic = sfmData.getIntrinsicsharedPtr(view_1.getIntrinsicId()); - std::shared_ptr equidistant = std::dynamic_pointer_cast(intrinsic); - std::shared_ptr pinhole = std::dynamic_pointer_cast(intrinsic); + std::shared_ptr intrinsic = sfmData.getIntrinsicsharedPtr(view_1.getIntrinsicId()); + std::shared_ptr equidistant = std::dynamic_pointer_cast(intrinsic); + std::shared_ptr pinhole = std::dynamic_pointer_cast(intrinsic); - if (equidistant != nullptr) - { - ceres::CostFunction* costFunction = new CostPanoramaEquidistant(constraint.ObservationFirst.x, constraint.ObservationSecond.x, equidistant); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2, intrinsicBlockPtr_1); + if (equidistant != nullptr) + { + ceres::CostFunction* costFunction = + new CostPanoramaEquidistant(constraint.ObservationFirst.x, constraint.ObservationSecond.x, equidistant); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2, intrinsicBlockPtr_1); - /* Symmetry */ - costFunction = new CostPanoramaEquidistant(constraint.ObservationSecond.x, constraint.ObservationFirst.x, equidistant); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_2, poseBlockPtr_1, intrinsicBlockPtr_1); - } - else if (pinhole != nullptr) - { - ceres::CostFunction* costFunction = new CostPanoramaPinHole(constraint.ObservationFirst.x, constraint.ObservationSecond.x, pinhole); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2, intrinsicBlockPtr_1); - /* Symmetry */ - costFunction = new CostPanoramaPinHole(constraint.ObservationSecond.x, constraint.ObservationFirst.x, pinhole); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_2, poseBlockPtr_1, intrinsicBlockPtr_1); - } - else { - ALICEVISION_LOG_ERROR("Incompatible camera for a 2D constraint"); - return; + /* Symmetry */ + costFunction = new CostPanoramaEquidistant(constraint.ObservationSecond.x, constraint.ObservationFirst.x, equidistant); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_2, poseBlockPtr_1, intrinsicBlockPtr_1); + } + else if (pinhole != nullptr) + { + ceres::CostFunction* costFunction = new CostPanoramaPinHole(constraint.ObservationFirst.x, constraint.ObservationSecond.x, pinhole); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2, intrinsicBlockPtr_1); + /* Symmetry */ + costFunction = new CostPanoramaPinHole(constraint.ObservationSecond.x, constraint.ObservationFirst.x, pinhole); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_2, poseBlockPtr_1, intrinsicBlockPtr_1); + } + else + { + ALICEVISION_LOG_ERROR("Incompatible camera for a 2D constraint"); + return; + } } - } } void BundleAdjustmentSymbolicCeres::addRotationPriorsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - // set a LossFunction to be less penalized by false measurements. - // note: set it to NULL if you don't want use a lossFunction. - ceres::LossFunction* lossFunction = nullptr; - - for (const auto & prior : sfmData.getRotationPriors()) { + // set a LossFunction to be less penalized by false measurements. + // note: set it to NULL if you don't want use a lossFunction. + ceres::LossFunction* lossFunction = nullptr; - const sfmData::View& view_1 = sfmData.getView(prior.ViewFirst); - const sfmData::View& view_2 = sfmData.getView(prior.ViewSecond); + for (const auto& prior : sfmData.getRotationPriors()) + { + const sfmData::View& view_1 = sfmData.getView(prior.ViewFirst); + const sfmData::View& view_2 = sfmData.getView(prior.ViewSecond); - assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); - assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); + assert(getPoseState(view_1.getPoseId()) != EParameterState::IGNORED); + assert(getPoseState(view_2.getPoseId()) != EParameterState::IGNORED); - double * poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); - double * poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); + double* poseBlockPtr_1 = _posesBlocks.at(view_1.getPoseId()).data(); + double* poseBlockPtr_2 = _posesBlocks.at(view_2.getPoseId()).data(); - ceres::CostFunction* costFunction = new CostRotationPrior(prior._second_R_first); - problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2); - } + ceres::CostFunction* costFunction = new CostRotationPrior(prior._second_R_first); + problem.AddResidualBlock(costFunction, lossFunction, poseBlockPtr_1, poseBlockPtr_2); + } } -void BundleAdjustmentSymbolicCeres::createProblem(const sfmData::SfMData& sfmData, - ERefineOptions refineOptions, - ceres::Problem& problem) +void BundleAdjustmentSymbolicCeres::createProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem) { - // clear previously computed data - resetProblem(); + // clear previously computed data + resetProblem(); - // ensure we are not using incompatible options - // REFINEINTRINSICS_OPTICALCENTER_ALWAYS and REFINEINTRINSICS_OPTICALCENTER_IF_ENOUGH_DATA cannot be used at the same time - assert(!((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) && (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA))); + // ensure we are not using incompatible options + // REFINEINTRINSICS_OPTICALCENTER_ALWAYS and REFINEINTRINSICS_OPTICALCENTER_IF_ENOUGH_DATA cannot be used at the same time + assert(!((refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) && (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA))); - // add SfM extrincics to the Ceres problem - addExtrinsicsToProblem(sfmData, refineOptions, problem); + // add SfM extrincics to the Ceres problem + addExtrinsicsToProblem(sfmData, refineOptions, problem); - // add SfM intrinsics to the Ceres problem - addIntrinsicsToProblem(sfmData, refineOptions, problem); + // add SfM intrinsics to the Ceres problem + addIntrinsicsToProblem(sfmData, refineOptions, problem); - // add SfM landmarks to the Ceres problem - addLandmarksToProblem(sfmData, refineOptions, problem); + // add SfM landmarks to the Ceres problem + addLandmarksToProblem(sfmData, refineOptions, problem); - // add 2D constraints to the Ceres problem - addConstraints2DToProblem(sfmData, refineOptions, problem); + // add 2D constraints to the Ceres problem + addConstraints2DToProblem(sfmData, refineOptions, problem); - // add rotation priors to the Ceres problem - addRotationPriorsToProblem(sfmData, refineOptions, problem); + // add rotation priors to the Ceres problem + addRotationPriorsToProblem(sfmData, refineOptions, problem); } void BundleAdjustmentSymbolicCeres::updateFromSolution(sfmData::SfMData& sfmData, ERefineOptions refineOptions) const { - const bool refinePoses = (refineOptions & REFINE_ROTATION) || (refineOptions & REFINE_TRANSLATION); - const bool refineIntrinsicsOpticalCenter = (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); - const bool refineIntrinsics = (refineOptions & REFINE_INTRINSICS_FOCAL) || (refineOptions & REFINE_INTRINSICS_DISTORTION) || refineIntrinsicsOpticalCenter; - const bool refineStructure = refineOptions & REFINE_STRUCTURE; - - // update camera poses with refined data - if(refinePoses) - { - // absolute poses - for(auto& posePair : sfmData.getPoses()) + const bool refinePoses = (refineOptions & REFINE_ROTATION) || (refineOptions & REFINE_TRANSLATION); + const bool refineIntrinsicsOpticalCenter = + (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS) || (refineOptions & REFINE_INTRINSICS_OPTICALOFFSET_IF_ENOUGH_DATA); + const bool refineIntrinsics = + (refineOptions & REFINE_INTRINSICS_FOCAL) || (refineOptions & REFINE_INTRINSICS_DISTORTION) || refineIntrinsicsOpticalCenter; + const bool refineStructure = refineOptions & REFINE_STRUCTURE; + + // update camera poses with refined data + if (refinePoses) { - const IndexT poseId = posePair.first; + // absolute poses + for (auto& posePair : sfmData.getPoses()) + { + const IndexT poseId = posePair.first; - // do not update a camera pose set as Ignored or Constant in the Local strategy - if(getPoseState(poseId) != EParameterState::REFINED) - continue; + // do not update a camera pose set as Ignored or Constant in the Local strategy + if (getPoseState(poseId) != EParameterState::REFINED) + continue; - const SE3::Matrix & poseBlock = _posesBlocks.at(poseId); + const SE3::Matrix& poseBlock = _posesBlocks.at(poseId); - // update the pose - posePair.second.setTransform(poseFromRT(poseBlock.block<3, 3>(0, 0), poseBlock.block<3, 1>(0, 3))); - } + // update the pose + posePair.second.setTransform(poseFromRT(poseBlock.block<3, 3>(0, 0), poseBlock.block<3, 1>(0, 3))); + } - // rig sub-poses - for(const auto& rigIt : _rigBlocks) - { - sfmData::Rig& rig = sfmData.getRigs().at(rigIt.first); + // rig sub-poses + for (const auto& rigIt : _rigBlocks) + { + sfmData::Rig& rig = sfmData.getRigs().at(rigIt.first); - for(const auto& subPoseit : rigIt.second) - { - sfmData::RigSubPose& subPose = rig.getSubPose(subPoseit.first); - const SE3::Matrix & subPoseBlock = subPoseit.second; + for (const auto& subPoseit : rigIt.second) + { + sfmData::RigSubPose& subPose = rig.getSubPose(subPoseit.first); + const SE3::Matrix& subPoseBlock = subPoseit.second; - // update the sub-pose - subPose.pose = poseFromRT(subPoseBlock.block<3, 3>(0, 0), subPoseBlock.block<3, 1>(0, 3)); - } + // update the sub-pose + subPose.pose = poseFromRT(subPoseBlock.block<3, 3>(0, 0), subPoseBlock.block<3, 1>(0, 3)); + } + } } - } - // update camera intrinsics with refined data - if(refineIntrinsics) - { - for(const auto& intrinsicBlockPair: _intrinsicsBlocks) + // update camera intrinsics with refined data + if (refineIntrinsics) { - const IndexT intrinsicId = intrinsicBlockPair.first; + for (const auto& intrinsicBlockPair : _intrinsicsBlocks) + { + const IndexT intrinsicId = intrinsicBlockPair.first; - // do not update a camera pose set as Ignored or Constant in the Local strategy - if(getIntrinsicState(intrinsicId) != EParameterState::REFINED) - continue; + // do not update a camera pose set as Ignored or Constant in the Local strategy + if (getIntrinsicState(intrinsicId) != EParameterState::REFINED) + continue; - sfmData.getIntrinsics().at(intrinsicId)->updateFromParams(intrinsicBlockPair.second); + sfmData.getIntrinsics().at(intrinsicId)->updateFromParams(intrinsicBlockPair.second); + } } - } - // update landmarks - if(refineStructure) - { - for(const auto& landmarksBlockPair: _landmarksBlocks) + // update landmarks + if (refineStructure) { - const IndexT landmarkId = landmarksBlockPair.first; - sfmData::Landmark& landmark = sfmData.getLandmarks().at(landmarkId); - - // do not update a camera pose set as Ignored or Constant in the Local strategy - if(getLandmarkState(landmarkId) != EParameterState::REFINED) { - continue; - } - - for(std::size_t i = 0; i < 3; ++i) { - landmark.X(Eigen::Index(i))= landmarksBlockPair.second.at(i); - } + for (const auto& landmarksBlockPair : _landmarksBlocks) + { + const IndexT landmarkId = landmarksBlockPair.first; + sfmData::Landmark& landmark = sfmData.getLandmarks().at(landmarkId); + + // do not update a camera pose set as Ignored or Constant in the Local strategy + if (getLandmarkState(landmarkId) != EParameterState::REFINED) + { + continue; + } + + for (std::size_t i = 0; i < 3; ++i) + { + landmark.X(Eigen::Index(i)) = landmarksBlockPair.second.at(i); + } + } } - } } -void BundleAdjustmentSymbolicCeres::createJacobian(const sfmData::SfMData& sfmData, - ERefineOptions refineOptions, - ceres::CRSMatrix& jacobian) +void BundleAdjustmentSymbolicCeres::createJacobian(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::CRSMatrix& jacobian) { - // create problem - ceres::Problem::Options problemOptions; - problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - ceres::Problem problem(problemOptions); - createProblem(sfmData, refineOptions, problem); - - // configure Jacobian engine - double cost = 0.0; - ceres::Problem::EvaluateOptions evalOpt; - evalOpt.parameter_blocks = _allParametersBlocks; - evalOpt.num_threads = 8; - evalOpt.apply_loss_function = true; - - // create Jacobain - problem.Evaluate(evalOpt, &cost, NULL, NULL, &jacobian); + // create problem + ceres::Problem::Options problemOptions; + problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; + ceres::Problem problem(problemOptions); + createProblem(sfmData, refineOptions, problem); + + // configure Jacobian engine + double cost = 0.0; + ceres::Problem::EvaluateOptions evalOpt; + evalOpt.parameter_blocks = _allParametersBlocks; + evalOpt.num_threads = 8; + evalOpt.apply_loss_function = true; + + // create Jacobain + problem.Evaluate(evalOpt, &cost, NULL, NULL, &jacobian); } bool BundleAdjustmentSymbolicCeres::adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions) { - // create problem - ceres::Problem::Options problemOptions; - problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problemOptions.evaluation_callback = this; - - ceres::Problem problem(problemOptions); - createProblem(sfmData, refineOptions, problem); - - // configure a Bundle Adjustment engine and run it - // make Ceres automatically detect the bundle structure. - ceres::Solver::Options options; - setSolverOptions(options); - - // solve BA - ceres::Solver::Summary summary; - ceres::Solve(options, &problem, &summary); - - // print summary - if(_ceresOptions.summary) { - ALICEVISION_LOG_INFO(summary.FullReport()); - } - - // solution is not usable - if(!summary.IsSolutionUsable()) - { - ALICEVISION_LOG_WARNING("Bundle Adjustment failed, the solution is not usable."); - return false; - } - - // update input sfmData with the solution - updateFromSolution(sfmData, refineOptions); - - // store some statitics from the summary - _statistics.time = summary.total_time_in_seconds; - _statistics.nbSuccessfullIterations = summary.num_successful_steps; - _statistics.nbUnsuccessfullIterations = summary.num_unsuccessful_steps; - _statistics.nbResidualBlocks = summary.num_residuals; - _statistics.RMSEinitial = std::sqrt(summary.initial_cost / summary.num_residuals); - _statistics.RMSEfinal = std::sqrt(summary.final_cost / summary.num_residuals); - - //store distance histogram for local strategy - if(useLocalStrategy()) - { - _statistics.nbCamerasPerDistance = _localGraph->getDistancesHistogram(); - } - - return true; + // create problem + ceres::Problem::Options problemOptions; + problemOptions.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; + problemOptions.evaluation_callback = this; + + ceres::Problem problem(problemOptions); + createProblem(sfmData, refineOptions, problem); + + // configure a Bundle Adjustment engine and run it + // make Ceres automatically detect the bundle structure. + ceres::Solver::Options options; + setSolverOptions(options); + + // solve BA + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + // print summary + if (_ceresOptions.summary) + { + ALICEVISION_LOG_INFO(summary.FullReport()); + } + + // solution is not usable + if (!summary.IsSolutionUsable()) + { + ALICEVISION_LOG_WARNING("Bundle Adjustment failed, the solution is not usable."); + return false; + } + + // update input sfmData with the solution + updateFromSolution(sfmData, refineOptions); + + // store some statitics from the summary + _statistics.time = summary.total_time_in_seconds; + _statistics.nbSuccessfullIterations = summary.num_successful_steps; + _statistics.nbUnsuccessfullIterations = summary.num_unsuccessful_steps; + _statistics.nbResidualBlocks = summary.num_residuals; + _statistics.RMSEinitial = std::sqrt(summary.initial_cost / summary.num_residuals); + _statistics.RMSEfinal = std::sqrt(summary.final_cost / summary.num_residuals); + + // store distance histogram for local strategy + if (useLocalStrategy()) + { + _statistics.nbCamerasPerDistance = _localGraph->getDistancesHistogram(); + } + + return true; } void BundleAdjustmentSymbolicCeres::PrepareForEvaluation(bool evaluate_jacobians, bool new_evaluation_point) { if (new_evaluation_point) { - for (const auto & pair : _intrinsicsBlocks) + for (const auto& pair : _intrinsicsBlocks) { _intrinsicObjects[pair.first]->updateFromParams(pair.second); } } } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.hpp b/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.hpp index 1980f33b20..b100b2dc15 100644 --- a/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.hpp +++ b/src/aliceVision/sfm/bundle/BundleAdjustmentSymbolicCeres.hpp @@ -21,12 +21,11 @@ #include - namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { @@ -34,300 +33,289 @@ class SymbolicEvaluationCallback; class BundleAdjustmentSymbolicCeres : public BundleAdjustment, ceres::EvaluationCallback { -public: - - /** - * @brief Contains all ceres parameters. - */ - struct CeresOptions - { - CeresOptions(bool verbose = true, bool multithreaded = true, unsigned int maxIterations = 50) - : verbose(verbose) - , nbThreads(multithreaded ? omp_get_max_threads() : 1) // set number of threads, 1 if OpenMP is not enabled - , maxNumIterations(maxIterations) + public: + /** + * @brief Contains all ceres parameters. + */ + struct CeresOptions { - setDenseBA(); // use dense BA by default - lossFunction.reset(new ceres::HuberLoss(Square(4.0))); - } + CeresOptions(bool verbose = true, bool multithreaded = true, unsigned int maxIterations = 50) + : verbose(verbose), + nbThreads(multithreaded ? omp_get_max_threads() : 1) // set number of threads, 1 if OpenMP is not enabled + , + maxNumIterations(maxIterations) + { + setDenseBA(); // use dense BA by default + lossFunction.reset(new ceres::HuberLoss(Square(4.0))); + } + + void setDenseBA(); + void setSparseBA(); + + ceres::LinearSolverType linearSolverType; + ceres::PreconditionerType preconditionerType; + ceres::SparseLinearAlgebraLibraryType sparseLinearAlgebraLibraryType; + std::shared_ptr lossFunction; + unsigned int nbThreads; + unsigned int maxNumIterations; + bool useParametersOrdering = true; + bool summary = false; + bool verbose = true; + bool useFocalPrior = true; + }; + + /** + * @brief Contains all informations related to the performed bundle adjustment. + */ + struct Statistics + { + Statistics() {} + + /** + * @brief Add a parameter state + * @param[in] parameter A bundle adjustment parameter + * @param[in] state A bundle adjustment state + */ + inline void addState(EParameter parameter, EParameterState state) { ++parametersStates[parameter][state]; } + + /** + * @brief Export statistics about bundle adjustment in a CSV file + * The contents of the file have been writen such that it is easy to handle it with + * a Python script or any spreadsheets (e.g. by copy/past the full content to LibreOffice) + * @param[in] folder The folder where you want to save the statistics file + * @param[in] filename The filename of the statistics file + * @return false it cannot open the file, true if it succeed + */ + bool exportToFile(const std::string& folder, const std::string& filename = "statistics.csv") const; + + /** + * @brief Display statistics about bundle adjustment in the terminal + * Logger need to accept log level + */ + void show() const; + + // public members + + /// number of successful iterations + std::size_t nbSuccessfullIterations = 0; + /// number of unsuccessful iterations + std::size_t nbUnsuccessfullIterations = 0; + /// number of resiudal blocks in the Ceres problem + std::size_t nbResidualBlocks = 0; + /// RMSEinitial: sqrt(initial_cost / num_residuals) + double RMSEinitial = 0.0; + /// RMSEfinal: sqrt(final_cost / num_residuals) + double RMSEfinal = 0.0; + /// time spent to solve the BA (s) + double time = 0.0; + /// number of states per parameter + std::map> parametersStates; + /// The distribution of the cameras for each graph distance + std::map nbCamerasPerDistance; + }; - void setDenseBA(); - void setSparseBA(); - - ceres::LinearSolverType linearSolverType; - ceres::PreconditionerType preconditionerType; - ceres::SparseLinearAlgebraLibraryType sparseLinearAlgebraLibraryType; - std::shared_ptr lossFunction; - unsigned int nbThreads; - unsigned int maxNumIterations; - bool useParametersOrdering = true; - bool summary = false; - bool verbose = true; - bool useFocalPrior = true; - }; - - /** - * @brief Contains all informations related to the performed bundle adjustment. - */ - struct Statistics - { - Statistics() + /** + * @brief Bundle adjustment constructor + * @param[in] options The user Ceres options + * @see BundleAdjustmentSymbolicCeres::CeresOptions + */ + BundleAdjustmentSymbolicCeres(const CeresOptions& options = CeresOptions(), int minNbImagesToRefineOpticalCenter = 3) + : _ceresOptions(options), + _minNbImagesToRefineOpticalCenter(minNbImagesToRefineOpticalCenter) {} /** - * @brief Add a parameter state - * @param[in] parameter A bundle adjustment parameter - * @param[in] state A bundle adjustment state + * @brief Create a jacobian CRSMatrix + * @param[in] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions The chosen refine flag + * @param[out] jacobian The jacobian CSRMatrix + */ + void createJacobian(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::CRSMatrix& jacobian); + + /** + * @brief Perform a Bundle Adjustment on the SfM scene with refinement of the requested parameters + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions The chosen refine flag + * @return false if the bundle adjustment failed else true + * @see BundleAdjustment::Adjust + */ + bool adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions = REFINE_ALL); + + /** + * @brief Ajust parameters according to the local reconstruction graph in order do perfomr an optimezed bundle adjustmentor + * @param[in] localGraph The Local bundle adjustment graph pointer or nullptr (will refine everything) */ - inline void addState(EParameter parameter, EParameterState state) + inline void useLocalStrategyGraph(const std::shared_ptr& localGraph) { _localGraph = localGraph; } + + /** + * @brief Get bundle adjustment statistics structure + * @return statistics structure const ptr + */ + inline const Statistics& getStatistics() const { return _statistics; } + + /** + * @brief Return true if the bundle adjustment use an external local graph + * @return true if use an external local graph + */ + inline bool useLocalStrategy() const { return (_localGraph != nullptr); } + + virtual void PrepareForEvaluation(bool evaluate_jacobians, bool new_evaluation_point); + + private: + void addPose(const sfmData::CameraPose& cameraPose, + bool isConstant, + SE3::Matrix& poseBlock, + ceres::Problem& problem, + bool refineTranslation, + bool refineRotation); + + /** + * @brief Clear structures for a new problem + */ + inline void resetProblem() { - ++parametersStates[parameter][state]; + _statistics = Statistics(); + + _allParametersBlocks.clear(); + _posesBlocks.clear(); + _intrinsicsBlocks.clear(); + _landmarksBlocks.clear(); + _rigBlocks.clear(); + _linearSolverOrdering.Clear(); } /** - * @brief Export statistics about bundle adjustment in a CSV file - * The contents of the file have been writen such that it is easy to handle it with - * a Python script or any spreadsheets (e.g. by copy/past the full content to LibreOffice) - * @param[in] folder The folder where you want to save the statistics file - * @param[in] filename The filename of the statistics file - * @return false it cannot open the file, true if it succeed + * @brief Set user Ceres options to the solver + * @param[in,out] solverOptions The solver options structure */ - bool exportToFile(const std::string& folder, const std::string& filename = "statistics.csv") const; + void setSolverOptions(ceres::Solver::Options& solverOptions) const; /** - * @brief Display statistics about bundle adjustment in the terminal - * Logger need to accept log level + * @brief Create a parameter block for each extrinsics according to the Ceres format: [Rx, Ry, Rz, tx, ty, tz] + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem */ - void show() const; - - // public members - - /// number of successful iterations - std::size_t nbSuccessfullIterations = 0; - /// number of unsuccessful iterations - std::size_t nbUnsuccessfullIterations = 0; - /// number of resiudal blocks in the Ceres problem - std::size_t nbResidualBlocks = 0; - /// RMSEinitial: sqrt(initial_cost / num_residuals) - double RMSEinitial = 0.0; - /// RMSEfinal: sqrt(final_cost / num_residuals) - double RMSEfinal = 0.0; - /// time spent to solve the BA (s) - double time = 0.0; - /// number of states per parameter - std::map> parametersStates; - /// The distribution of the cameras for each graph distance - std::map nbCamerasPerDistance; - }; - - /** - * @brief Bundle adjustment constructor - * @param[in] options The user Ceres options - * @see BundleAdjustmentSymbolicCeres::CeresOptions - */ - BundleAdjustmentSymbolicCeres(const CeresOptions& options = CeresOptions(), int minNbImagesToRefineOpticalCenter = 3) - : _ceresOptions(options) - , _minNbImagesToRefineOpticalCenter(minNbImagesToRefineOpticalCenter) - {} - - /** - * @brief Create a jacobian CRSMatrix - * @param[in] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions The chosen refine flag - * @param[out] jacobian The jacobian CSRMatrix - */ - void createJacobian(const sfmData::SfMData& sfmData, - ERefineOptions refineOptions, - ceres::CRSMatrix& jacobian); - - /** - * @brief Perform a Bundle Adjustment on the SfM scene with refinement of the requested parameters - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions The chosen refine flag - * @return false if the bundle adjustment failed else true - * @see BundleAdjustment::Adjust - */ - bool adjust(sfmData::SfMData& sfmData, ERefineOptions refineOptions = REFINE_ALL); - - /** - * @brief Ajust parameters according to the local reconstruction graph in order do perfomr an optimezed bundle adjustmentor - * @param[in] localGraph The Local bundle adjustment graph pointer or nullptr (will refine everything) - */ - inline void useLocalStrategyGraph(const std::shared_ptr& localGraph) - { - _localGraph = localGraph; - } - - /** - * @brief Get bundle adjustment statistics structure - * @return statistics structure const ptr - */ - inline const Statistics& getStatistics() const - { - return _statistics; - } - - /** - * @brief Return true if the bundle adjustment use an external local graph - * @return true if use an external local graph - */ - inline bool useLocalStrategy() const - { - return (_localGraph != nullptr); - } - - virtual void PrepareForEvaluation(bool evaluate_jacobians, bool new_evaluation_point); - -private: - - void addPose(const sfmData::CameraPose& cameraPose, bool isConstant, SE3::Matrix & poseBlock, ceres::Problem& problem, bool refineTranslation, bool refineRotation); - - /** - * @brief Clear structures for a new problem - */ - inline void resetProblem() - { - _statistics = Statistics(); - - _allParametersBlocks.clear(); - _posesBlocks.clear(); - _intrinsicsBlocks.clear(); - _landmarksBlocks.clear(); - _rigBlocks.clear(); - _linearSolverOrdering.Clear(); - } - - /** - * @brief Set user Ceres options to the solver - * @param[in,out] solverOptions The solver options structure - */ - void setSolverOptions(ceres::Solver::Options& solverOptions) const; - - /** - * @brief Create a parameter block for each extrinsics according to the Ceres format: [Rx, Ry, Rz, tx, ty, tz] - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addExtrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a parameter block for each intrinsic according to the Ceres format - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addIntrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a residual block for each landmarks according to the Ceres format - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addLandmarksToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a residual block for each 2D constraints - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addConstraints2DToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create a residual block for each rotation priors - * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void addRotationPriorsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Create the Ceres bundle adjustement problem with: - * - extrincics and intrinsics parameters blocks. - * - residuals blocks for each observation. - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction - * @param[in] refineOptions The chosen refine flag - * @param[out] problem The Ceres bundle adjustement problem - */ - void createProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); - - /** - * @brief Update The given SfMData with the solver solution - * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses - * @param[in] refineOptions The chosen refine flag - */ - void updateFromSolution(sfmData::SfMData& sfmData, ERefineOptions refineOptions) const; - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific pose. - * @param[in] poseId The pose id - * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) - */ - inline BundleAdjustment::EParameterState getPoseState(IndexT poseId) const - { - return (_localGraph != nullptr ? _localGraph->getPoseState(poseId) : BundleAdjustment::EParameterState::REFINED); - } - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific intrinsic. - * @param[in] intrinsicId The intrinsic id - * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) - */ - inline BundleAdjustment::EParameterState getIntrinsicState(IndexT intrinsicId) const - { - return (_localGraph != nullptr ? _localGraph->getIntrinsicState(intrinsicId) : BundleAdjustment::EParameterState::REFINED); - } - - /** - * @brief Return the BundleAdjustment::EParameterState for a specific landmark. - * @param[in] landmarkId The landmark id - * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) - */ - inline BundleAdjustment::EParameterState getLandmarkState(IndexT landmarkId) const - { - return (_localGraph != nullptr ? _localGraph->getLandmarkState(landmarkId) : BundleAdjustment::EParameterState::REFINED); - } - - // private members - - /// use or not the local budle adjustment strategy - std::shared_ptr _localGraph = nullptr; - - /// user Ceres options to use in the solver - CeresOptions _ceresOptions; - int _minNbImagesToRefineOpticalCenter = 3; - - /// user FeatureConstraint options to use - EFeatureConstraint _featureConstraint; - - /// last adjustment iteration statisics - Statistics _statistics; - - // data wrappers for refinement - - /// all parameters blocks pointers - std::vector _allParametersBlocks; - /// poses blocks wrapper - /// block: ceres angleAxis(3) + translation(3) - HashMap _posesBlocks; //TODO : maybe we can use boost::flat_map instead of HashMap ? - /// intrinsics blocks wrapper - /// block: intrinsics params - HashMap> _intrinsicsBlocks; - HashMap> _intrinsicObjects; - /// landmarks blocks wrapper - /// block: 3d position(3) - HashMap> _landmarksBlocks; - /// rig sub-poses blocks wrapper - /// block: ceres angleAxis(3) + translation(3) - HashMap> _rigBlocks; - ///Rig pose to use when there is no rig - SE3::Matrix _rigNull = SE3::Matrix::Identity(); - - /// hinted order for ceres to eliminate blocks when solving. - /// note: this ceres parameter is built internally and must be reset on each call to the solver. - ceres::ParameterBlockOrdering _linearSolverOrdering; + void addExtrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a parameter block for each intrinsic according to the Ceres format + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addIntrinsicsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a residual block for each landmarks according to the Ceres format + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addLandmarksToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a residual block for each 2D constraints + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addConstraints2DToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create a residual block for each rotation priors + * @param[in] sfmData The input SfMData contains all the information about the reconstruction, notably the intrinsics + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void addRotationPriorsToProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Create the Ceres bundle adjustement problem with: + * - extrincics and intrinsics parameters blocks. + * - residuals blocks for each observation. + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction + * @param[in] refineOptions The chosen refine flag + * @param[out] problem The Ceres bundle adjustement problem + */ + void createProblem(const sfmData::SfMData& sfmData, ERefineOptions refineOptions, ceres::Problem& problem); + + /** + * @brief Update The given SfMData with the solver solution + * @param[in,out] sfmData The input SfMData contains all the information about the reconstruction, notably the poses and sub-poses + * @param[in] refineOptions The chosen refine flag + */ + void updateFromSolution(sfmData::SfMData& sfmData, ERefineOptions refineOptions) const; + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific pose. + * @param[in] poseId The pose id + * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) + */ + inline BundleAdjustment::EParameterState getPoseState(IndexT poseId) const + { + return (_localGraph != nullptr ? _localGraph->getPoseState(poseId) : BundleAdjustment::EParameterState::REFINED); + } + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific intrinsic. + * @param[in] intrinsicId The intrinsic id + * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) + */ + inline BundleAdjustment::EParameterState getIntrinsicState(IndexT intrinsicId) const + { + return (_localGraph != nullptr ? _localGraph->getIntrinsicState(intrinsicId) : BundleAdjustment::EParameterState::REFINED); + } + + /** + * @brief Return the BundleAdjustment::EParameterState for a specific landmark. + * @param[in] landmarkId The landmark id + * @return BundleAdjustment::EParameterState (always REFINED if no local strategy) + */ + inline BundleAdjustment::EParameterState getLandmarkState(IndexT landmarkId) const + { + return (_localGraph != nullptr ? _localGraph->getLandmarkState(landmarkId) : BundleAdjustment::EParameterState::REFINED); + } + + // private members + + /// use or not the local budle adjustment strategy + std::shared_ptr _localGraph = nullptr; + + /// user Ceres options to use in the solver + CeresOptions _ceresOptions; + int _minNbImagesToRefineOpticalCenter = 3; + + /// user FeatureConstraint options to use + EFeatureConstraint _featureConstraint; + + /// last adjustment iteration statisics + Statistics _statistics; + + // data wrappers for refinement + + /// all parameters blocks pointers + std::vector _allParametersBlocks; + /// poses blocks wrapper + /// block: ceres angleAxis(3) + translation(3) + HashMap _posesBlocks; // TODO : maybe we can use boost::flat_map instead of HashMap ? + /// intrinsics blocks wrapper + /// block: intrinsics params + HashMap> _intrinsicsBlocks; + HashMap> _intrinsicObjects; + /// landmarks blocks wrapper + /// block: 3d position(3) + HashMap> _landmarksBlocks; + /// rig sub-poses blocks wrapper + /// block: ceres angleAxis(3) + translation(3) + HashMap> _rigBlocks; + /// Rig pose to use when there is no rig + SE3::Matrix _rigNull = SE3::Matrix::Identity(); + + /// hinted order for ceres to eliminate blocks when solving. + /// note: this ceres parameter is built internally and must be reset on each call to the solver. + ceres::ParameterBlockOrdering _linearSolverOrdering; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/bundleAdjustment_test.cpp b/src/aliceVision/sfm/bundle/bundleAdjustment_test.cpp index 7760dbf2e9..895f26b9f3 100644 --- a/src/aliceVision/sfm/bundle/bundleAdjustment_test.cpp +++ b/src/aliceVision/sfm/bundle/bundleAdjustment_test.cpp @@ -36,291 +36,291 @@ SfMData getInputScene(const NViewDataSet& d, const NViewDatasetConfigurator& con BOOST_AUTO_TEST_CASE(BUNDLE_ADJUSTMENT_EffectiveMinimization_Pinhole) { + const int nviews = 3; + const int npoints = 6; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - const int nviews = 3; - const int npoints = 6; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + // Translate the input dataset to a SfMData scene + SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - // Translate the input dataset to a SfMData scene - SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + const double dResidual_before = RMSE(sfmData); - const double dResidual_before = RMSE(sfmData); + // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) + std::shared_ptr ba_object = std::make_shared(); + BOOST_CHECK(ba_object->adjust(sfmData)); - // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) - std::shared_ptr ba_object = std::make_shared(); - BOOST_CHECK( ba_object->adjust(sfmData) ); - - const double dResidual_after = RMSE(sfmData); - BOOST_CHECK_LT(dResidual_after, dResidual_before); + const double dResidual_after = RMSE(sfmData); + BOOST_CHECK_LT(dResidual_after, dResidual_before); } BOOST_AUTO_TEST_CASE(BUNDLE_ADJUSTMENT_EffectiveMinimization_PinholeRadialK1) { - const int nviews = 3; - const int npoints = 6; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 3; + const int npoints = 6; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_RADIAL1); + // Translate the input dataset to a SfMData scene + SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_RADIAL1); - const double dResidual_before = RMSE(sfmData); + const double dResidual_before = RMSE(sfmData); - // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) - std::shared_ptr ba_object = std::make_shared(); - BOOST_CHECK( ba_object->adjust(sfmData) ); + // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) + std::shared_ptr ba_object = std::make_shared(); + BOOST_CHECK(ba_object->adjust(sfmData)); - const double dResidual_after = RMSE(sfmData); - BOOST_CHECK_LT(dResidual_after, dResidual_before); + const double dResidual_after = RMSE(sfmData); + BOOST_CHECK_LT(dResidual_after, dResidual_before); } BOOST_AUTO_TEST_CASE(BUNDLE_ADJUSTMENT_EffectiveMinimization_PinholeRadialK3) { - const int nviews = 3; - const int npoints = 6; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 3; + const int npoints = 6; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_RADIAL3); + // Translate the input dataset to a SfMData scene + SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_RADIAL3); - const double dResidual_before = RMSE(sfmData); + const double dResidual_before = RMSE(sfmData); - // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) - std::shared_ptr ba_object = std::make_shared(); - BOOST_CHECK( ba_object->adjust(sfmData) ); + // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) + std::shared_ptr ba_object = std::make_shared(); + BOOST_CHECK(ba_object->adjust(sfmData)); - const double dResidual_after = RMSE(sfmData); - BOOST_CHECK_LT(dResidual_after, dResidual_before); + const double dResidual_after = RMSE(sfmData); + BOOST_CHECK_LT(dResidual_after, dResidual_before); } BOOST_AUTO_TEST_CASE(BUNDLE_ADJUSTMENT_EffectiveMinimization_PinholeBrownT2) { - const int nviews = 3; - const int npoints = 6; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 3; + const int npoints = 6; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_BROWN); + // Translate the input dataset to a SfMData scene + SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_BROWN); - const double dResidual_before = RMSE(sfmData); + const double dResidual_before = RMSE(sfmData); - // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) - std::shared_ptr ba_object = std::make_shared(); - BOOST_CHECK( ba_object->adjust(sfmData) ); + // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) + std::shared_ptr ba_object = std::make_shared(); + BOOST_CHECK(ba_object->adjust(sfmData)); - const double dResidual_after = RMSE(sfmData); - BOOST_CHECK_LT(dResidual_after, dResidual_before); + const double dResidual_after = RMSE(sfmData); + BOOST_CHECK_LT(dResidual_after, dResidual_before); } BOOST_AUTO_TEST_CASE(BUNDLE_ADJUSTMENT_EffectiveMinimization_PinholeFisheye) { - const int nviews = 3; - const int npoints = 6; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 3; + const int npoints = 6; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_FISHEYE); + // Translate the input dataset to a SfMData scene + SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA_FISHEYE); - const double dResidual_before = RMSE(sfmData); + const double dResidual_before = RMSE(sfmData); - // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) - std::shared_ptr ba_object = std::make_shared(); - BOOST_CHECK( ba_object->adjust(sfmData) ); + // Call the BA interface and let it refine (Structure and Camera parameters [Intrinsics|Motion]) + std::shared_ptr ba_object = std::make_shared(); + BOOST_CHECK(ba_object->adjust(sfmData)); - const double dResidual_after = RMSE(sfmData); - BOOST_CHECK_LT(dResidual_after, dResidual_before); + const double dResidual_after = RMSE(sfmData); + BOOST_CHECK_LT(dResidual_after, dResidual_before); } BOOST_AUTO_TEST_CASE(LOCAL_BUNDLE_ADJUSTMENT_EffectiveMinimization_Pinhole_CamerasRing) { - const int nviews = 4; - const int npoints = 3; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - - // Translate the input dataset to a SfMData scene - SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - SfMData sfmData_notRefined = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); // used to compate which parameters are refined. - - // Transform the views scheme - // v0 - v3 - // from | X | => where each view sees all the 3D points p0, p1 and p2 - // v1 - v2 - // - // to v0 v3 p0 p1 p2 - // | | => / \ / \ / \ + const int nviews = 4; + const int npoints = 3; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + + // Translate the input dataset to a SfMData scene + SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + SfMData sfmData_notRefined = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); // used to compate which parameters are refined. + + // Transform the views scheme + // v0 - v3 + // from | X | => where each view sees all the 3D points p0, p1 and p2 + // v1 - v2 + // + // to v0 v3 p0 p1 p2 + // | | => / \ / \ / \ // v1 - v2 v0 v1 v2 v3 - // removing adequate observations: - sfmData.getLandmarks().at(0).observations.erase(2); - sfmData.getLandmarks().at(0).observations.erase(3); - sfmData.getLandmarks().at(1).observations.erase(0); - sfmData.getLandmarks().at(1).observations.erase(3); - sfmData.getLandmarks().at(2).observations.erase(0); - sfmData.getLandmarks().at(2).observations.erase(1); - - // lock common intrinsic - // if it's not locked, all views will have a distance of 1 as all views share a common intrinsic. - sfmData.getIntrinsics().begin()->second->lock(); - - track::TracksPerView tracksPerView = getLandmarksPerViews(sfmData); - - // Set the view "v0' as new (graph-distance(v0) = 0): - std::set newReconstructedViews; - newReconstructedViews.insert(0); - - const double dResidual_before = RMSE(sfmData); - - // Call the Local BA interface and let it refine - BundleAdjustmentCeres::CeresOptions options; - options.setDenseBA(); - - std::shared_ptr localBAGraph = std::make_shared(sfmData); - localBAGraph->setGraphDistanceLimit(1); // the default value is '1' - - /* DETAILS: - * With the previous reconstruction scheme & parameters: - * -- Graph-distances: - * dist(v0) == 0 [because it is set to New] - * dist(v1) == 1 [because it shares 'p0' with v0] - * dist(v2) == 2 [because it shares 'p1' with v1] - * dist(v3) == 3 [because it shares 'p2' with v2] - * -- Local BA state: (due to the graph-distance) - * state(v0) = refined [because its dist. is <= kLimitDistance] - * state(v1) = refined [because its dist. is <= kLimitDistance] - * state(v2) = constant [because its dist. is == kLimitDistance + 1] - * state(v3) = ignored [because its dist. is > kLimitDistance] - * state(p0) = refined [because it is seen by at least one refined view (v0 & v1)] - * state(p1) = refined [because it is seen by at least one refined view (v1)] - * state(p2) = ignored [because it is not seen by any refined view] - */ - - // Assign the refinement rule for all the parameters (poses, landmarks & intrinsics) according to the LBA strategy: - // 1. Add the new reconstructed views to the graph - const std::size_t kMinNbOfMatches = 1; - localBAGraph->updateGraphWithNewViews(sfmData, tracksPerView, newReconstructedViews, kMinNbOfMatches); - // 2. Compute the graph-distance between each newly reconstructed views and all the reconstructed views - localBAGraph->computeGraphDistances(sfmData, newReconstructedViews); - // 3. Use the graph-distances to assign a LBA state (Refine, Constant & Ignore) for each parameter (poses, intrinsics & landmarks) - localBAGraph->convertDistancesToStates(sfmData); - - BOOST_CHECK_EQUAL(localBAGraph->countNodes(), 4); // 4 views => 4 nodes - BOOST_CHECK_EQUAL(localBAGraph->countEdges(), 6); // landmarks connections: 6 edges created (see scheme) - - BOOST_CHECK_EQUAL(localBAGraph->getNbPosesPerState(BundleAdjustment::EParameterState::REFINED), 2); // v0 & v1 - BOOST_CHECK_EQUAL(localBAGraph->getNbPosesPerState(BundleAdjustment::EParameterState::CONSTANT), 1); // v2 - BOOST_CHECK_EQUAL(localBAGraph->getNbPosesPerState(BundleAdjustment::EParameterState::IGNORED), 1); // v3 - BOOST_CHECK_EQUAL(localBAGraph->getNbLandmarksPerState(BundleAdjustment::EParameterState::REFINED), 2); // p0 & p1 - BOOST_CHECK_EQUAL(localBAGraph->getNbLandmarksPerState(BundleAdjustment::EParameterState::CONSTANT), 0); - BOOST_CHECK_EQUAL(localBAGraph->getNbLandmarksPerState(BundleAdjustment::EParameterState::IGNORED), 1); // p2 - - std::shared_ptr BA = std::make_shared(options); - BA->useLocalStrategyGraph(localBAGraph); - BOOST_CHECK( BA->useLocalStrategy() ); - BOOST_CHECK( BA->adjust(sfmData) ); - - // Check views: - BOOST_CHECK( !(sfmData.getPose(*sfmData.getViews().at(0).get()) == sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(0).get())) ); // v0 refined - BOOST_CHECK( !(sfmData.getPose(*sfmData.getViews().at(1).get()) == sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(1).get())) ); // v1 refined - BOOST_CHECK( sfmData.getPose(*sfmData.getViews().at(2).get()) == sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(2).get()) ); // v2 constant - BOOST_CHECK( sfmData.getPose(*sfmData.getViews().at(2).get()) == sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(2).get()) ); // v2 ignored - - // Check 3D points - BOOST_CHECK( sfmData.getLandmarks()[0].X != sfmData_notRefined.getLandmarks()[0].X ); // p0 refined - BOOST_CHECK( sfmData.getLandmarks()[1].X != sfmData_notRefined.getLandmarks()[1].X ); // p1 refined - BOOST_CHECK_EQUAL( sfmData.getLandmarks()[2].X, sfmData_notRefined.getLandmarks()[2].X ); // p2 ignored - - // Not refined parameters: - BOOST_CHECK( sfmData.getLandmarks()[2].X == sfmData_notRefined.getLandmarks()[2].X ); - - const double dResidual_after = RMSE(sfmData); - BOOST_CHECK_LT(dResidual_after, dResidual_before); + // removing adequate observations: + sfmData.getLandmarks().at(0).observations.erase(2); + sfmData.getLandmarks().at(0).observations.erase(3); + sfmData.getLandmarks().at(1).observations.erase(0); + sfmData.getLandmarks().at(1).observations.erase(3); + sfmData.getLandmarks().at(2).observations.erase(0); + sfmData.getLandmarks().at(2).observations.erase(1); + + // lock common intrinsic + // if it's not locked, all views will have a distance of 1 as all views share a common intrinsic. + sfmData.getIntrinsics().begin()->second->lock(); + + track::TracksPerView tracksPerView = getLandmarksPerViews(sfmData); + + // Set the view "v0' as new (graph-distance(v0) = 0): + std::set newReconstructedViews; + newReconstructedViews.insert(0); + + const double dResidual_before = RMSE(sfmData); + + // Call the Local BA interface and let it refine + BundleAdjustmentCeres::CeresOptions options; + options.setDenseBA(); + + std::shared_ptr localBAGraph = std::make_shared(sfmData); + localBAGraph->setGraphDistanceLimit(1); // the default value is '1' + + /* DETAILS: + * With the previous reconstruction scheme & parameters: + * -- Graph-distances: + * dist(v0) == 0 [because it is set to New] + * dist(v1) == 1 [because it shares 'p0' with v0] + * dist(v2) == 2 [because it shares 'p1' with v1] + * dist(v3) == 3 [because it shares 'p2' with v2] + * -- Local BA state: (due to the graph-distance) + * state(v0) = refined [because its dist. is <= kLimitDistance] + * state(v1) = refined [because its dist. is <= kLimitDistance] + * state(v2) = constant [because its dist. is == kLimitDistance + 1] + * state(v3) = ignored [because its dist. is > kLimitDistance] + * state(p0) = refined [because it is seen by at least one refined view (v0 & v1)] + * state(p1) = refined [because it is seen by at least one refined view (v1)] + * state(p2) = ignored [because it is not seen by any refined view] + */ + + // Assign the refinement rule for all the parameters (poses, landmarks & intrinsics) according to the LBA strategy: + // 1. Add the new reconstructed views to the graph + const std::size_t kMinNbOfMatches = 1; + localBAGraph->updateGraphWithNewViews(sfmData, tracksPerView, newReconstructedViews, kMinNbOfMatches); + // 2. Compute the graph-distance between each newly reconstructed views and all the reconstructed views + localBAGraph->computeGraphDistances(sfmData, newReconstructedViews); + // 3. Use the graph-distances to assign a LBA state (Refine, Constant & Ignore) for each parameter (poses, intrinsics & landmarks) + localBAGraph->convertDistancesToStates(sfmData); + + BOOST_CHECK_EQUAL(localBAGraph->countNodes(), 4); // 4 views => 4 nodes + BOOST_CHECK_EQUAL(localBAGraph->countEdges(), 6); // landmarks connections: 6 edges created (see scheme) + + BOOST_CHECK_EQUAL(localBAGraph->getNbPosesPerState(BundleAdjustment::EParameterState::REFINED), 2); // v0 & v1 + BOOST_CHECK_EQUAL(localBAGraph->getNbPosesPerState(BundleAdjustment::EParameterState::CONSTANT), 1); // v2 + BOOST_CHECK_EQUAL(localBAGraph->getNbPosesPerState(BundleAdjustment::EParameterState::IGNORED), 1); // v3 + BOOST_CHECK_EQUAL(localBAGraph->getNbLandmarksPerState(BundleAdjustment::EParameterState::REFINED), 2); // p0 & p1 + BOOST_CHECK_EQUAL(localBAGraph->getNbLandmarksPerState(BundleAdjustment::EParameterState::CONSTANT), 0); + BOOST_CHECK_EQUAL(localBAGraph->getNbLandmarksPerState(BundleAdjustment::EParameterState::IGNORED), 1); // p2 + + std::shared_ptr BA = std::make_shared(options); + BA->useLocalStrategyGraph(localBAGraph); + BOOST_CHECK(BA->useLocalStrategy()); + BOOST_CHECK(BA->adjust(sfmData)); + + // Check views: + BOOST_CHECK( + !(sfmData.getPose(*sfmData.getViews().at(0).get()) == sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(0).get()))); // v0 refined + BOOST_CHECK( + !(sfmData.getPose(*sfmData.getViews().at(1).get()) == sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(1).get()))); // v1 refined + BOOST_CHECK(sfmData.getPose(*sfmData.getViews().at(2).get()) == + sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(2).get())); // v2 constant + BOOST_CHECK(sfmData.getPose(*sfmData.getViews().at(2).get()) == + sfmData_notRefined.getPose(*sfmData_notRefined.getViews().at(2).get())); // v2 ignored + + // Check 3D points + BOOST_CHECK(sfmData.getLandmarks()[0].X != sfmData_notRefined.getLandmarks()[0].X); // p0 refined + BOOST_CHECK(sfmData.getLandmarks()[1].X != sfmData_notRefined.getLandmarks()[1].X); // p1 refined + BOOST_CHECK_EQUAL(sfmData.getLandmarks()[2].X, sfmData_notRefined.getLandmarks()[2].X); // p2 ignored + + // Not refined parameters: + BOOST_CHECK(sfmData.getLandmarks()[2].X == sfmData_notRefined.getLandmarks()[2].X); + + const double dResidual_after = RMSE(sfmData); + BOOST_CHECK_LT(dResidual_after, dResidual_before); } /// Compute the Root Mean Square Error of the residuals -double RMSE(const SfMData & sfm_data) +double RMSE(const SfMData& sfm_data) { - // Compute residuals for each observation - std::vector vec; - for(Landmarks::const_iterator iterTracks = sfm_data.getLandmarks().begin(); - iterTracks != sfm_data.getLandmarks().end(); - ++iterTracks) - { - const Observations & observations = iterTracks->second.observations; - for(Observations::const_iterator itObs = observations.begin(); - itObs != observations.end(); ++itObs) + // Compute residuals for each observation + std::vector vec; + for (Landmarks::const_iterator iterTracks = sfm_data.getLandmarks().begin(); iterTracks != sfm_data.getLandmarks().end(); ++iterTracks) { - const View* view = sfm_data.getViews().find(itObs->first)->second.get(); - const Pose3 pose = sfm_data.getPose(*view).getTransform(); - const std::shared_ptr intrinsic = sfm_data.getIntrinsics().find(view->getIntrinsicId())->second; - const Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x); - vec.push_back( residual(0) ); - vec.push_back( residual(1) ); + const Observations& observations = iterTracks->second.observations; + for (Observations::const_iterator itObs = observations.begin(); itObs != observations.end(); ++itObs) + { + const View* view = sfm_data.getViews().find(itObs->first)->second.get(); + const Pose3 pose = sfm_data.getPose(*view).getTransform(); + const std::shared_ptr intrinsic = sfm_data.getIntrinsics().find(view->getIntrinsicId())->second; + const Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x); + vec.push_back(residual(0)); + vec.push_back(residual(1)); + } } - } - const Eigen::Map residuals(&vec[0], vec.size()); - const double RMSE = std::sqrt(residuals.squaredNorm() / vec.size()); - return RMSE; + const Eigen::Map residuals(&vec[0], vec.size()); + const double RMSE = std::sqrt(residuals.squaredNorm() / vec.size()); + return RMSE; } // Translation a synthetic scene into a valid SfMData scene. // => A synthetic scene is used: // a random noise between [-.5,.5] is added on observed data points -SfMData getInputScene(const NViewDataSet & d, const NViewDatasetConfigurator & config, EINTRINSIC eintrinsic) +SfMData getInputScene(const NViewDataSet& d, const NViewDatasetConfigurator& config, EINTRINSIC eintrinsic) { - // Translate the input dataset to a SfMData scene - SfMData sfm_data; - - // 1. Views - // 2. Poses - // 3. Intrinsic data (shared, so only one camera intrinsic is defined) - // 4. Landmarks - - const int nviews = d._C.size(); - const int npoints = d._X.cols(); - - // 1. Views - for (int i = 0; i < nviews; ++i) - { - const IndexT id_view = i, id_pose = i, id_intrinsic = 0; //(shared intrinsics) - sfm_data.getViews().emplace(i, std::make_shared("", id_view, id_intrinsic, id_pose, config._cx *2, config._cy *2)); - } - - // 2. Poses - for (int i = 0; i < nviews; ++i) - { - Pose3 pose(d._R[i], d._C[i]); - sfm_data.setPose(*sfm_data.getViews().at(i), CameraPose(pose)); - } - - // 3. Intrinsic data (shared, so only one camera intrinsic is defined) - { - const unsigned int w = config._cx *2; - const unsigned int h = config._cy *2; - sfm_data.getIntrinsics().emplace(0, createIntrinsic(eintrinsic, w, h, config._fx, config._fx, 0, 0)); - } - - // 4. Landmarks - const double unknownScale = 0.0; - for (int i = 0; i < npoints; ++i) { - - // Collect the image of point i in each frame. - Landmark landmark; - landmark.X = d._X.col(i); - for (int j = 0; j < nviews; ++j) { - Vec2 pt = d._x[j].col(i); - // => random noise between [-.5,.5] is added - pt(0) += rand()/RAND_MAX - .5; - pt(1) += rand()/RAND_MAX - .5; - - landmark.observations[j] = Observation(pt, i, unknownScale); + // Translate the input dataset to a SfMData scene + SfMData sfm_data; + + // 1. Views + // 2. Poses + // 3. Intrinsic data (shared, so only one camera intrinsic is defined) + // 4. Landmarks + + const int nviews = d._C.size(); + const int npoints = d._X.cols(); + + // 1. Views + for (int i = 0; i < nviews; ++i) + { + const IndexT id_view = i, id_pose = i, id_intrinsic = 0; //(shared intrinsics) + sfm_data.getViews().emplace(i, std::make_shared("", id_view, id_intrinsic, id_pose, config._cx * 2, config._cy * 2)); } - sfm_data.getLandmarks()[i] = landmark; - } - return sfm_data; -} + // 2. Poses + for (int i = 0; i < nviews; ++i) + { + Pose3 pose(d._R[i], d._C[i]); + sfm_data.setPose(*sfm_data.getViews().at(i), CameraPose(pose)); + } + // 3. Intrinsic data (shared, so only one camera intrinsic is defined) + { + const unsigned int w = config._cx * 2; + const unsigned int h = config._cy * 2; + sfm_data.getIntrinsics().emplace(0, createIntrinsic(eintrinsic, w, h, config._fx, config._fx, 0, 0)); + } + + // 4. Landmarks + const double unknownScale = 0.0; + for (int i = 0; i < npoints; ++i) + { + // Collect the image of point i in each frame. + Landmark landmark; + landmark.X = d._X.col(i); + for (int j = 0; j < nviews; ++j) + { + Vec2 pt = d._x[j].col(i); + // => random noise between [-.5,.5] is added + pt(0) += rand() / RAND_MAX - .5; + pt(1) += rand() / RAND_MAX - .5; + + landmark.observations[j] = Observation(pt, i, unknownScale); + } + sfm_data.getLandmarks()[i] = landmark; + } + + return sfm_data; +} diff --git a/src/aliceVision/sfm/bundle/costfunctions/panoramaEquidistant.hpp b/src/aliceVision/sfm/bundle/costfunctions/panoramaEquidistant.hpp index 99d7740c99..5f1ce7232e 100644 --- a/src/aliceVision/sfm/bundle/costfunctions/panoramaEquidistant.hpp +++ b/src/aliceVision/sfm/bundle/costfunctions/panoramaEquidistant.hpp @@ -11,94 +11,112 @@ namespace aliceVision { namespace sfm { -class CostPanoramaEquidistant : public ceres::SizedCostFunction<2, 16, 16, 7> { -public: - CostPanoramaEquidistant(Vec2 fi, Vec2 fj, std::shared_ptr & intrinsic) : _fi(fi), _fj(fj), _intrinsic(intrinsic) { - - } - - bool Evaluate(double const * const * parameters, double * residuals, double ** jacobians) const override { - - Vec2 pt_i = _fi; - Vec2 pt_j = _fj; - - const double * parameter_pose_i = parameters[0]; - const double * parameter_pose_j = parameters[1]; - const double * parameter_intrinsics = parameters[2]; - - const Eigen::Map> iTo(parameter_pose_i); - const Eigen::Map> jTo(parameter_pose_j); - - Eigen::Matrix iRo = iTo.block<3, 3>(0, 0); - Eigen::Matrix jRo = jTo.block<3, 3>(0, 0); - - _intrinsic->setScale({parameter_intrinsics[0], parameter_intrinsics[1]}); - _intrinsic->setOffset({parameter_intrinsics[2], parameter_intrinsics[3]}); - _intrinsic->setDistortionParamsFn(3, [&](auto index) { return parameter_intrinsics[4 + index]; }); - - Eigen::Matrix3d R = jRo * iRo.transpose(); - - - geometry::Pose3 T_pose3(R, Vec3({0,0,0})); - Eigen::Matrix4d T = T_pose3.getHomogeneous(); - - Vec2 pt_i_cam = _intrinsic->ima2cam(pt_i); - Vec2 pt_i_undist = _intrinsic->removeDistortion(pt_i_cam); - Vec4 pt_i_sphere = _intrinsic->toUnitSphere(pt_i_undist).homogeneous(); - - Vec2 pt_j_est = _intrinsic->project(T_pose3, pt_i_sphere, true); - - residuals[0] = pt_j_est(0) - pt_j(0); - residuals[1] = pt_j_est(1) - pt_j(1); - - if (jacobians == nullptr) { - return true; +class CostPanoramaEquidistant : public ceres::SizedCostFunction<2, 16, 16, 7> +{ + public: + CostPanoramaEquidistant(Vec2 fi, Vec2 fj, std::shared_ptr& intrinsic) + : _fi(fi), + _fj(fj), + _intrinsic(intrinsic) + {} + + bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override + { + Vec2 pt_i = _fi; + Vec2 pt_j = _fj; + + const double* parameter_pose_i = parameters[0]; + const double* parameter_pose_j = parameters[1]; + const double* parameter_intrinsics = parameters[2]; + + const Eigen::Map> iTo(parameter_pose_i); + const Eigen::Map> jTo(parameter_pose_j); + + Eigen::Matrix iRo = iTo.block<3, 3>(0, 0); + Eigen::Matrix jRo = jTo.block<3, 3>(0, 0); + + _intrinsic->setScale({parameter_intrinsics[0], parameter_intrinsics[1]}); + _intrinsic->setOffset({parameter_intrinsics[2], parameter_intrinsics[3]}); + _intrinsic->setDistortionParamsFn(3, [&](auto index) { return parameter_intrinsics[4 + index]; }); + + Eigen::Matrix3d R = jRo * iRo.transpose(); + + geometry::Pose3 T_pose3(R, Vec3({0, 0, 0})); + Eigen::Matrix4d T = T_pose3.getHomogeneous(); + + Vec2 pt_i_cam = _intrinsic->ima2cam(pt_i); + Vec2 pt_i_undist = _intrinsic->removeDistortion(pt_i_cam); + Vec4 pt_i_sphere = _intrinsic->toUnitSphere(pt_i_undist).homogeneous(); + + Vec2 pt_j_est = _intrinsic->project(T_pose3, pt_i_sphere, true); + + residuals[0] = pt_j_est(0) - pt_j(0); + residuals[1] = pt_j_est(1) - pt_j(1); + + if (jacobians == nullptr) + { + return true; + } + + if (jacobians[0] != nullptr) + { + Eigen::Map> J(jacobians[0]); + + Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * + getJacobian_AB_wrt_B<3, 3, 3>(jRo, iRo.transpose()) * getJacobian_At_wrt_A<3, 3>() * + getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), iRo); + + J.fill(0); + J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); + J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); + J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); + } + + if (jacobians[1] != nullptr) + { + Eigen::Map> J(jacobians[1]); + + Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * + getJacobian_AB_wrt_A<3, 3, 3>(jRo, iRo.transpose()) * + getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), jRo); + + J.fill(0); + J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); + J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); + J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); + } + + if (jacobians[2] != nullptr) + { + Eigen::Map> J(jacobians[2]); + + Eigen::Matrix Jhomogenous = Eigen::Matrix::Identity(); + + Eigen::Matrix Jscale = + _intrinsic->getDerivativeProjectWrtScale(T, pt_i_sphere) + + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtScale(pt_i_undist); + Eigen::Matrix Jpp = + _intrinsic->getDerivativeProjectWrtPrincipalPoint(T, pt_i_sphere) + + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * + _intrinsic->getDerivativeRemoveDistoWrtPt(pt_i_cam) * _intrinsic->getDerivativeIma2CamWrtPrincipalPoint(); + Eigen::Matrix Jdisto = + _intrinsic->getDerivativeProjectWrtDisto(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * + _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * + _intrinsic->getDerivativeRemoveDistoWrtDisto(pt_i_cam); + + J.block<2, 2>(0, 0) = Jscale; + J.block<2, 2>(0, 2) = Jpp; + J.block<2, 3>(0, 4) = Jdisto; + } + + return true; } - if (jacobians[0] != nullptr) { - Eigen::Map> J(jacobians[0]); - - Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * getJacobian_AB_wrt_B<3, 3, 3>(jRo, iRo.transpose()) * getJacobian_At_wrt_A<3, 3>() * getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), iRo); - - J.fill(0); - J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); - J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); - J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); - } - - if (jacobians[1] != nullptr) { - Eigen::Map> J(jacobians[1]); - - Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * getJacobian_AB_wrt_A<3, 3, 3>(jRo, iRo.transpose()) * getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), jRo); - - J.fill(0); - J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); - J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); - J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); - } - - if (jacobians[2] != nullptr) { - Eigen::Map> J(jacobians[2]); - - Eigen::Matrix Jhomogenous = Eigen::Matrix::Identity(); - - Eigen::Matrix Jscale = _intrinsic->getDerivativeProjectWrtScale(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtScale(pt_i_undist); - Eigen::Matrix Jpp = _intrinsic->getDerivativeProjectWrtPrincipalPoint(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * _intrinsic->getDerivativeRemoveDistoWrtPt(pt_i_cam) * _intrinsic->getDerivativeIma2CamWrtPrincipalPoint(); - Eigen::Matrix Jdisto = _intrinsic->getDerivativeProjectWrtDisto(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * _intrinsic->getDerivativeRemoveDistoWrtDisto(pt_i_cam); - - J.block<2, 2>(0, 0) = Jscale; - J.block<2, 2>(0, 2) = Jpp; - J.block<2, 3>(0, 4) = Jdisto; - } - - return true; - } - -private: - Vec2 _fi; - Vec2 _fj; - std::shared_ptr _intrinsic; + private: + Vec2 _fi; + Vec2 _fj; + std::shared_ptr _intrinsic; }; -} -} \ No newline at end of file +} // namespace sfm +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfm/bundle/costfunctions/panoramaPinhole.hpp b/src/aliceVision/sfm/bundle/costfunctions/panoramaPinhole.hpp index a8f691caf9..56cf35901e 100644 --- a/src/aliceVision/sfm/bundle/costfunctions/panoramaPinhole.hpp +++ b/src/aliceVision/sfm/bundle/costfunctions/panoramaPinhole.hpp @@ -11,105 +11,123 @@ namespace aliceVision { namespace sfm { -class CostPanoramaPinHole : public ceres::CostFunction { -public: - CostPanoramaPinHole(Vec2 fi, Vec2 fj, std::shared_ptr & intrinsic) : _fi(fi), _fj(fj), _intrinsic(intrinsic) { - - set_num_residuals(2); - - mutable_parameter_block_sizes()->push_back(16); - mutable_parameter_block_sizes()->push_back(16); - mutable_parameter_block_sizes()->push_back(intrinsic->getParams().size()); - } - - bool Evaluate(double const * const * parameters, double * residuals, double ** jacobians) const override { - - Vec2 pt_i = _fi; - Vec2 pt_j = _fj; - - const double * parameter_pose_i = parameters[0]; - const double * parameter_pose_j = parameters[1]; - const double * parameter_intrinsics = parameters[2]; - - const Eigen::Map> iTo(parameter_pose_i); - const Eigen::Map> jTo(parameter_pose_j); - - Eigen::Matrix iRo = iTo.block<3, 3>(0, 0); - Eigen::Matrix jRo = jTo.block<3, 3>(0, 0); - - _intrinsic->setScale({parameter_intrinsics[0], parameter_intrinsics[1]}); - _intrinsic->setOffset({parameter_intrinsics[2], parameter_intrinsics[3]}); - - size_t params_size = _intrinsic->getParamsSize(); - size_t disto_size = _intrinsic->getDistortionParamsSize(); - size_t offset = params_size - disto_size; - - _intrinsic->setDistortionParamsFn(disto_size, [&](auto index) +class CostPanoramaPinHole : public ceres::CostFunction +{ + public: + CostPanoramaPinHole(Vec2 fi, Vec2 fj, std::shared_ptr& intrinsic) + : _fi(fi), + _fj(fj), + _intrinsic(intrinsic) { - return parameter_intrinsics[offset + index]; - }); - - Eigen::Matrix3d R = jRo * iRo.transpose(); - geometry::Pose3 T_pose3(R, Vec3({0,0,0})); - Eigen::Matrix4d T = T_pose3.getHomogeneous(); - - Vec2 pt_i_cam = _intrinsic->ima2cam(pt_i); - Vec2 pt_i_undist = _intrinsic->removeDistortion(pt_i_cam); - Vec4 pt_i_sphere = _intrinsic->toUnitSphere(pt_i_undist).homogeneous(); - - Vec2 pt_j_est = _intrinsic->project(T_pose3, pt_i_sphere, true); - - residuals[0] = pt_j_est(0) - pt_j(0); - residuals[1] = pt_j_est(1) - pt_j(1); + set_num_residuals(2); - if (jacobians == nullptr) { - return true; + mutable_parameter_block_sizes()->push_back(16); + mutable_parameter_block_sizes()->push_back(16); + mutable_parameter_block_sizes()->push_back(intrinsic->getParams().size()); } - if (jacobians[0] != nullptr) { - Eigen::Map> J(jacobians[0]); - - Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * getJacobian_AB_wrt_B<3, 3, 3>(jRo, iRo.transpose()) * getJacobian_At_wrt_A<3, 3>() * getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), iRo); - - J.fill(0); - J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); - J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); - J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); - } - - if (jacobians[1] != nullptr) { - Eigen::Map> J(jacobians[1]); - - Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * getJacobian_AB_wrt_A<3, 3, 3>(jRo, iRo.transpose()) * getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), jRo); - - J.fill(0); - J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); - J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); - J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); - } - - if (jacobians[2] != nullptr) { - Eigen::Map> J(jacobians[2], 2, params_size); - - Eigen::Matrix Jhomogenous = Eigen::Matrix::Identity(); - - Eigen::Matrix Jscale = _intrinsic->getDerivativeProjectWrtScale(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * _intrinsic->getDerivativeRemoveDistoWrtPt(pt_i_cam) * _intrinsic->getDerivativeIma2CamWrtScale(pt_i); - Eigen::Matrix Jpp = _intrinsic->getDerivativeProjectWrtPrincipalPoint(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * _intrinsic->getDerivativeRemoveDistoWrtPt(pt_i_cam) * _intrinsic->getDerivativeIma2CamWrtPrincipalPoint(); - Eigen::Matrix Jdisto = _intrinsic->getDerivativeProjectWrtDisto(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * _intrinsic->getDerivativeRemoveDistoWrtDisto(pt_i_cam); - - J.block<2, 2>(0, 0) = Jscale; - J.block<2, 2>(0, 2) = Jpp; - J.block(0, 4, 2, disto_size) = Jdisto; + bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override + { + Vec2 pt_i = _fi; + Vec2 pt_j = _fj; + + const double* parameter_pose_i = parameters[0]; + const double* parameter_pose_j = parameters[1]; + const double* parameter_intrinsics = parameters[2]; + + const Eigen::Map> iTo(parameter_pose_i); + const Eigen::Map> jTo(parameter_pose_j); + + Eigen::Matrix iRo = iTo.block<3, 3>(0, 0); + Eigen::Matrix jRo = jTo.block<3, 3>(0, 0); + + _intrinsic->setScale({parameter_intrinsics[0], parameter_intrinsics[1]}); + _intrinsic->setOffset({parameter_intrinsics[2], parameter_intrinsics[3]}); + + size_t params_size = _intrinsic->getParamsSize(); + size_t disto_size = _intrinsic->getDistortionParamsSize(); + size_t offset = params_size - disto_size; + + _intrinsic->setDistortionParamsFn(disto_size, [&](auto index) { return parameter_intrinsics[offset + index]; }); + + Eigen::Matrix3d R = jRo * iRo.transpose(); + geometry::Pose3 T_pose3(R, Vec3({0, 0, 0})); + Eigen::Matrix4d T = T_pose3.getHomogeneous(); + + Vec2 pt_i_cam = _intrinsic->ima2cam(pt_i); + Vec2 pt_i_undist = _intrinsic->removeDistortion(pt_i_cam); + Vec4 pt_i_sphere = _intrinsic->toUnitSphere(pt_i_undist).homogeneous(); + + Vec2 pt_j_est = _intrinsic->project(T_pose3, pt_i_sphere, true); + + residuals[0] = pt_j_est(0) - pt_j(0); + residuals[1] = pt_j_est(1) - pt_j(1); + + if (jacobians == nullptr) + { + return true; + } + + if (jacobians[0] != nullptr) + { + Eigen::Map> J(jacobians[0]); + + Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * + getJacobian_AB_wrt_B<3, 3, 3>(jRo, iRo.transpose()) * getJacobian_At_wrt_A<3, 3>() * + getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), iRo); + + J.fill(0); + J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); + J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); + J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); + } + + if (jacobians[1] != nullptr) + { + Eigen::Map> J(jacobians[1]); + + Eigen::Matrix J9 = _intrinsic->getDerivativeProjectWrtRotation(T, pt_i_sphere) * + getJacobian_AB_wrt_A<3, 3, 3>(jRo, iRo.transpose()) * + getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), jRo); + + J.fill(0); + J.block<2, 3>(0, 0) = J9.block<2, 3>(0, 0); + J.block<2, 3>(0, 4) = J9.block<2, 3>(0, 3); + J.block<2, 3>(0, 8) = J9.block<2, 3>(0, 6); + } + + if (jacobians[2] != nullptr) + { + Eigen::Map> J(jacobians[2], 2, params_size); + + Eigen::Matrix Jhomogenous = Eigen::Matrix::Identity(); + + Eigen::Matrix Jscale = + _intrinsic->getDerivativeProjectWrtScale(T, pt_i_sphere) + + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * + _intrinsic->getDerivativeRemoveDistoWrtPt(pt_i_cam) * _intrinsic->getDerivativeIma2CamWrtScale(pt_i); + Eigen::Matrix Jpp = + _intrinsic->getDerivativeProjectWrtPrincipalPoint(T, pt_i_sphere) + + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * + _intrinsic->getDerivativeRemoveDistoWrtPt(pt_i_cam) * _intrinsic->getDerivativeIma2CamWrtPrincipalPoint(); + Eigen::Matrix Jdisto = + _intrinsic->getDerivativeProjectWrtDisto(T, pt_i_sphere) + _intrinsic->getDerivativeProjectWrtPoint(T, pt_i_sphere) * Jhomogenous * + _intrinsic->getDerivativetoUnitSphereWrtPoint(pt_i_undist) * + _intrinsic->getDerivativeRemoveDistoWrtDisto(pt_i_cam); + + J.block<2, 2>(0, 0) = Jscale; + J.block<2, 2>(0, 2) = Jpp; + J.block(0, 4, 2, disto_size) = Jdisto; + } + + return true; } - return true; - } - -private: - Vec2 _fi; - Vec2 _fj; - std::shared_ptr _intrinsic; + private: + Vec2 _fi; + Vec2 _fj; + std::shared_ptr _intrinsic; }; -} -} \ No newline at end of file +} // namespace sfm +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfm/bundle/costfunctions/projection.hpp b/src/aliceVision/sfm/bundle/costfunctions/projection.hpp index adf96cd4fc..d658f00dc7 100644 --- a/src/aliceVision/sfm/bundle/costfunctions/projection.hpp +++ b/src/aliceVision/sfm/bundle/costfunctions/projection.hpp @@ -11,88 +11,98 @@ namespace aliceVision { namespace sfm { -class CostProjection : public ceres::CostFunction { -public: - CostProjection(const sfmData::Observation& measured, const std::shared_ptr & intrinsics, bool withRig) : _measured(measured), _intrinsics(intrinsics), _withRig(withRig) - { - set_num_residuals(2); - - mutable_parameter_block_sizes()->push_back(16); - mutable_parameter_block_sizes()->push_back(16); - mutable_parameter_block_sizes()->push_back(intrinsics->getParams().size()); - mutable_parameter_block_sizes()->push_back(3); - } - - bool Evaluate(double const * const * parameters, double * residuals, double ** jacobians) const override - { - const double * parameter_pose = parameters[0]; - const double * parameter_rig = parameters[1]; - const double * parameter_intrinsics = parameters[2]; - const double * parameter_landmark = parameters[3]; - - const Eigen::Map rTo(parameter_pose); - const Eigen::Map cTr(parameter_rig); - const Eigen::Map pt(parameter_landmark); - - /*Update intrinsics object with estimated parameters*/ - size_t params_size = _intrinsics->getParams().size(); - std::vector params; - for (size_t param_id = 0; param_id < params_size; param_id++) { - params.push_back(parameter_intrinsics[param_id]); +class CostProjection : public ceres::CostFunction +{ + public: + CostProjection(const sfmData::Observation& measured, const std::shared_ptr& intrinsics, bool withRig) + : _measured(measured), + _intrinsics(intrinsics), + _withRig(withRig) + { + set_num_residuals(2); + + mutable_parameter_block_sizes()->push_back(16); + mutable_parameter_block_sizes()->push_back(16); + mutable_parameter_block_sizes()->push_back(intrinsics->getParams().size()); + mutable_parameter_block_sizes()->push_back(3); } - _intrinsics->updateFromParams(params); - const SE3::Matrix T = cTr * rTo; - const geometry::Pose3 T_pose3(T); + bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override + { + const double* parameter_pose = parameters[0]; + const double* parameter_rig = parameters[1]; + const double* parameter_intrinsics = parameters[2]; + const double* parameter_landmark = parameters[3]; - const Vec4 pth = pt.homogeneous(); + const Eigen::Map rTo(parameter_pose); + const Eigen::Map cTr(parameter_rig); + const Eigen::Map pt(parameter_landmark); - const Vec2 pt_est = _intrinsics->project(T_pose3, pth, true); - const double scale = (_measured.scale > 1e-12) ? _measured.scale : 1.0; + /*Update intrinsics object with estimated parameters*/ + size_t params_size = _intrinsics->getParams().size(); + std::vector params; + for (size_t param_id = 0; param_id < params_size; param_id++) + { + params.push_back(parameter_intrinsics[param_id]); + } + _intrinsics->updateFromParams(params); - residuals[0] = (pt_est(0) - _measured.x(0)) / scale; - residuals[1] = (pt_est(1) - _measured.x(1)) / scale; + const SE3::Matrix T = cTr * rTo; + const geometry::Pose3 T_pose3(T); - if (jacobians == nullptr) { - return true; - } + const Vec4 pth = pt.homogeneous(); - - Eigen::Matrix2d d_res_d_pt_est = Eigen::Matrix2d::Identity() / scale; + const Vec2 pt_est = _intrinsics->project(T_pose3, pth, true); + const double scale = (_measured.scale > 1e-12) ? _measured.scale : 1.0; - if (jacobians[0] != nullptr) { - Eigen::Map> J(jacobians[0]); + residuals[0] = (pt_est(0) - _measured.x(0)) / scale; + residuals[1] = (pt_est(1) - _measured.x(1)) / scale; - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPose(T, pth) * getJacobian_AB_wrt_B<4, 4, 4>(cTr, rTo) * getJacobian_AB_wrt_A<4, 4, 4>(Eigen::Matrix4d::Identity(), rTo); - } + if (jacobians == nullptr) + { + return true; + } - if (jacobians[1] != nullptr) { - Eigen::Map> J(jacobians[1]); - - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPose(T, pth) * getJacobian_AB_wrt_A<4, 4, 4>(cTr, rTo) * getJacobian_AB_wrt_A<4, 4, 4>(Eigen::Matrix4d::Identity(), cTr); - } + Eigen::Matrix2d d_res_d_pt_est = Eigen::Matrix2d::Identity() / scale; - if (jacobians[2] != nullptr) { - Eigen::Map> J(jacobians[2], 2, params_size); - - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtParams(T, pth); - } + if (jacobians[0] != nullptr) + { + Eigen::Map> J(jacobians[0]); - if (jacobians[3] != nullptr) { - Eigen::Map> J(jacobians[3]); + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPose(T, pth) * getJacobian_AB_wrt_B<4, 4, 4>(cTr, rTo) * + getJacobian_AB_wrt_A<4, 4, 4>(Eigen::Matrix4d::Identity(), rTo); + } + if (jacobians[1] != nullptr) + { + Eigen::Map> J(jacobians[1]); - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPoint(T, pth) * Eigen::Matrix::Identity(); - } + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPose(T, pth) * getJacobian_AB_wrt_A<4, 4, 4>(cTr, rTo) * + getJacobian_AB_wrt_A<4, 4, 4>(Eigen::Matrix4d::Identity(), cTr); + } - return true; - } + if (jacobians[2] != nullptr) + { + Eigen::Map> J(jacobians[2], 2, params_size); + + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtParams(T, pth); + } + + if (jacobians[3] != nullptr) + { + Eigen::Map> J(jacobians[3]); + + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPoint(T, pth) * Eigen::Matrix::Identity(); + } + + return true; + } -private: - const sfmData::Observation & _measured; - const std::shared_ptr _intrinsics; - bool _withRig; + private: + const sfmData::Observation& _measured; + const std::shared_ptr _intrinsics; + bool _withRig; }; -} -} \ No newline at end of file +} // namespace sfm +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfm/bundle/costfunctions/projectionSimple.hpp b/src/aliceVision/sfm/bundle/costfunctions/projectionSimple.hpp index 890679641d..3cd1a85164 100644 --- a/src/aliceVision/sfm/bundle/costfunctions/projectionSimple.hpp +++ b/src/aliceVision/sfm/bundle/costfunctions/projectionSimple.hpp @@ -11,71 +11,77 @@ namespace aliceVision { namespace sfm { -class CostProjectionSimple : public ceres::CostFunction { -public: - CostProjectionSimple(const sfmData::Observation& measured, const std::shared_ptr & intrinsics) : _measured(measured), _intrinsics(intrinsics) - { - set_num_residuals(2); +class CostProjectionSimple : public ceres::CostFunction +{ + public: + CostProjectionSimple(const sfmData::Observation& measured, const std::shared_ptr& intrinsics) + : _measured(measured), + _intrinsics(intrinsics) + { + set_num_residuals(2); + + mutable_parameter_block_sizes()->push_back(16); + mutable_parameter_block_sizes()->push_back(intrinsics->getParams().size()); + mutable_parameter_block_sizes()->push_back(3); + } - mutable_parameter_block_sizes()->push_back(16); - mutable_parameter_block_sizes()->push_back(intrinsics->getParams().size()); - mutable_parameter_block_sizes()->push_back(3); - } + bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override + { + const double* parameter_pose = parameters[0]; + const double* parameter_intrinsics = parameters[1]; + const double* parameter_landmark = parameters[2]; - bool Evaluate(double const * const * parameters, double * residuals, double ** jacobians) const override - { - const double * parameter_pose = parameters[0]; - const double * parameter_intrinsics = parameters[1]; - const double * parameter_landmark = parameters[2]; + const Eigen::Map T(parameter_pose); + const Eigen::Map pt(parameter_landmark); - const Eigen::Map T(parameter_pose); - const Eigen::Map pt(parameter_landmark); + const Vec4 pth = pt.homogeneous(); - const Vec4 pth = pt.homogeneous(); + const Vec4 cpt = T * pth; - const Vec4 cpt = T * pth; + Vec2 pt_est = _intrinsics->project(T, pth, false); + const double scale = (_measured.scale > 1e-12) ? _measured.scale : 1.0; - Vec2 pt_est = _intrinsics->project(T, pth, false); - const double scale = (_measured.scale > 1e-12) ? _measured.scale : 1.0; + residuals[0] = (pt_est(0) - _measured.x(0)) / scale; + residuals[1] = (pt_est(1) - _measured.x(1)) / scale; - residuals[0] = (pt_est(0) - _measured.x(0)) / scale; - residuals[1] = (pt_est(1) - _measured.x(1)) / scale; + if (jacobians == nullptr) + { + return true; + } - if (jacobians == nullptr) { - return true; - } + const geometry::Pose3 T_pose3(T); + size_t params_size = _intrinsics->getParams().size(); - const geometry::Pose3 T_pose3(T); - size_t params_size = _intrinsics->getParams().size(); + double d_res_d_pt_est = 1.0 / scale; - double d_res_d_pt_est = 1.0 / scale; + if (jacobians[0] != nullptr) + { + Eigen::Map> J(jacobians[0]); - if (jacobians[0] != nullptr) { - Eigen::Map> J(jacobians[0]); + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPoseLeft(T, pth); + } - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPoseLeft(T, pth); - } + if (jacobians[1] != nullptr) + { + Eigen::Map> J(jacobians[1], 2, params_size); - if (jacobians[1] != nullptr) { - Eigen::Map> J(jacobians[1], 2, params_size); - - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtParams(T, pth); - } + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtParams(T, pth); + } - if (jacobians[2] != nullptr) { - Eigen::Map> J(jacobians[2]); + if (jacobians[2] != nullptr) + { + Eigen::Map> J(jacobians[2]); - J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPoint3(T, pth); - } + J = d_res_d_pt_est * _intrinsics->getDerivativeProjectWrtPoint3(T, pth); + } - return true; - } + return true; + } -private: - const sfmData::Observation & _measured; - const std::shared_ptr _intrinsics; + private: + const sfmData::Observation& _measured; + const std::shared_ptr _intrinsics; }; - -} -} \ No newline at end of file +} // namespace sfm +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfm/bundle/costfunctions/rotationPrior.hpp b/src/aliceVision/sfm/bundle/costfunctions/rotationPrior.hpp index 4c3bbde014..4a1d8408e9 100644 --- a/src/aliceVision/sfm/bundle/costfunctions/rotationPrior.hpp +++ b/src/aliceVision/sfm/bundle/costfunctions/rotationPrior.hpp @@ -10,65 +10,72 @@ namespace aliceVision { namespace sfm { - -class CostRotationPrior : public ceres::SizedCostFunction<3, 16, 16> { -public: - explicit CostRotationPrior(const Eigen::Matrix3d & two_R_one) : _two_R_one(two_R_one) { - - } - - bool Evaluate(double const * const * parameters, double * residuals, double ** jacobians) const override { - - const double * parameter_pose_one = parameters[0]; - const double * parameter_pose_two = parameters[1]; - - const Eigen::Map> oneTo(parameter_pose_one); - const Eigen::Map> twoTo(parameter_pose_two); - - Eigen::Matrix oneRo = oneTo.block<3, 3>(0, 0); - Eigen::Matrix twoRo = twoTo.block<3, 3>(0, 0); - - Eigen::Matrix3d two_R_one_est = twoRo * oneRo.transpose(); - Eigen::Matrix3d error_R = two_R_one_est * _two_R_one.transpose(); - Eigen::Vector3d error_r = SO3::logm(error_R); - - residuals[0] = error_r(0); - residuals[1] = error_r(1); - residuals[2] = error_r(2); - - if (jacobians == nullptr) { - return true; +class CostRotationPrior : public ceres::SizedCostFunction<3, 16, 16> +{ + public: + explicit CostRotationPrior(const Eigen::Matrix3d& two_R_one) + : _two_R_one(two_R_one) + {} + + bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override + { + const double* parameter_pose_one = parameters[0]; + const double* parameter_pose_two = parameters[1]; + + const Eigen::Map> oneTo(parameter_pose_one); + const Eigen::Map> twoTo(parameter_pose_two); + + Eigen::Matrix oneRo = oneTo.block<3, 3>(0, 0); + Eigen::Matrix twoRo = twoTo.block<3, 3>(0, 0); + + Eigen::Matrix3d two_R_one_est = twoRo * oneRo.transpose(); + Eigen::Matrix3d error_R = two_R_one_est * _two_R_one.transpose(); + Eigen::Vector3d error_r = SO3::logm(error_R); + + residuals[0] = error_r(0); + residuals[1] = error_r(1); + residuals[2] = error_r(2); + + if (jacobians == nullptr) + { + return true; + } + + if (jacobians[0]) + { + Eigen::Map> J(jacobians[0]); + + Eigen::Matrix J9 = SO3::dlogmdr(error_R) * getJacobian_AB_wrt_A<3, 3, 3>(two_R_one_est, _two_R_one.transpose()) * + getJacobian_AB_wrt_B<3, 3, 3>(twoRo, oneRo.transpose()) * getJacobian_At_wrt_A<3, 3>() * + getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), oneRo); + + J.fill(0); + J.block<3, 3>(0, 0) = J9.block<3, 3>(0, 0); + J.block<3, 3>(0, 4) = J9.block<3, 3>(0, 3); + J.block<3, 3>(0, 8) = J9.block<3, 3>(0, 6); + } + + if (jacobians[1]) + { + Eigen::Map> J(jacobians[1]); + + Eigen::Matrix J9 = SO3::dlogmdr(error_R) * getJacobian_AB_wrt_A<3, 3, 3>(two_R_one_est, _two_R_one.transpose()) * + getJacobian_AB_wrt_A<3, 3, 3>(twoRo, oneRo.transpose()) * + getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), twoRo); + + J.fill(0); + J.block<3, 3>(0, 0) = J9.block<3, 3>(0, 0); + J.block<3, 3>(0, 4) = J9.block<3, 3>(0, 3); + J.block<3, 3>(0, 8) = J9.block<3, 3>(0, 6); + } + + return true; } - if (jacobians[0]) { - Eigen::Map> J(jacobians[0]); - - Eigen::Matrix J9 = SO3::dlogmdr(error_R) * getJacobian_AB_wrt_A<3, 3, 3>(two_R_one_est, _two_R_one.transpose()) * getJacobian_AB_wrt_B<3, 3, 3>(twoRo, oneRo.transpose()) * getJacobian_At_wrt_A<3, 3>() * getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), oneRo); - - J.fill(0); - J.block<3, 3>(0, 0) = J9.block<3, 3>(0, 0); - J.block<3, 3>(0, 4) = J9.block<3, 3>(0, 3); - J.block<3, 3>(0, 8) = J9.block<3, 3>(0, 6); - } - - if (jacobians[1]) { - Eigen::Map> J(jacobians[1]); - - Eigen::Matrix J9 = SO3::dlogmdr(error_R) * getJacobian_AB_wrt_A<3, 3, 3>(two_R_one_est, _two_R_one.transpose()) * getJacobian_AB_wrt_A<3, 3, 3>(twoRo, oneRo.transpose()) * getJacobian_AB_wrt_A<3, 3, 3>(Eigen::Matrix3d::Identity(), twoRo); - - J.fill(0); - J.block<3, 3>(0, 0) = J9.block<3, 3>(0, 0); - J.block<3, 3>(0, 4) = J9.block<3, 3>(0, 3); - J.block<3, 3>(0, 8) = J9.block<3, 3>(0, 6); - } - - return true; - } - -private: - Eigen::Matrix3d _two_R_one; + private: + Eigen::Matrix3d _two_R_one; }; -} -} \ No newline at end of file +} // namespace sfm +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfm/bundle/manifolds/intrinsics.hpp b/src/aliceVision/sfm/bundle/manifolds/intrinsics.hpp index e4634d01e7..7db597dcde 100644 --- a/src/aliceVision/sfm/bundle/manifolds/intrinsics.hpp +++ b/src/aliceVision/sfm/bundle/manifolds/intrinsics.hpp @@ -14,166 +14,165 @@ namespace aliceVision { namespace sfm { -class IntrinsicsManifoldSymbolic : public ceres::Manifold { - public: - explicit IntrinsicsManifoldSymbolic(size_t parametersSize, - double focalRatio, bool lockFocal, bool lockFocalRatio, - bool lockCenter, bool lockDistortion) - : _ambientSize(parametersSize), - _focalRatio(focalRatio), - _lockFocal(lockFocal), - _lockFocalRatio(lockFocalRatio), - _lockCenter(lockCenter), - _lockDistortion(lockDistortion) - { - _distortionSize = _ambientSize - 4; - _tangentSize = 0; - - if (!_lockFocal) +class IntrinsicsManifoldSymbolic : public ceres::Manifold +{ + public: + explicit IntrinsicsManifoldSymbolic(size_t parametersSize, + double focalRatio, + bool lockFocal, + bool lockFocalRatio, + bool lockCenter, + bool lockDistortion) + : _ambientSize(parametersSize), + _focalRatio(focalRatio), + _lockFocal(lockFocal), + _lockFocalRatio(lockFocalRatio), + _lockCenter(lockCenter), + _lockDistortion(lockDistortion) { - if (_lockFocalRatio) - { - _tangentSize += 1; - } - else - { - _tangentSize += 2; - } + _distortionSize = _ambientSize - 4; + _tangentSize = 0; + + if (!_lockFocal) + { + if (_lockFocalRatio) + { + _tangentSize += 1; + } + else + { + _tangentSize += 2; + } + } + + if (!_lockCenter) + { + _tangentSize += 2; + } + + if (!_lockDistortion) + { + _tangentSize += _distortionSize; + } } - if (!_lockCenter) - { - _tangentSize += 2; - } - - if (!_lockDistortion) - { - _tangentSize += _distortionSize; - } - } + virtual ~IntrinsicsManifoldSymbolic() = default; - virtual ~IntrinsicsManifoldSymbolic() = default; - - - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override - { - for (int i = 0; i < _ambientSize; i++) - { - x_plus_delta[i] = x[i]; - } - - size_t posDelta = 0; - if (!_lockFocal) + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { - if (_lockFocalRatio) - { - x_plus_delta[0] = x[0] + delta[posDelta]; - x_plus_delta[1] = x[1] + _focalRatio * delta[posDelta]; - ++posDelta; - } - else - { - x_plus_delta[0] = x[0] + delta[posDelta]; - ++posDelta; - x_plus_delta[1] = x[1] + delta[posDelta]; - ++posDelta; - } + for (int i = 0; i < _ambientSize; i++) + { + x_plus_delta[i] = x[i]; + } + + size_t posDelta = 0; + if (!_lockFocal) + { + if (_lockFocalRatio) + { + x_plus_delta[0] = x[0] + delta[posDelta]; + x_plus_delta[1] = x[1] + _focalRatio * delta[posDelta]; + ++posDelta; + } + else + { + x_plus_delta[0] = x[0] + delta[posDelta]; + ++posDelta; + x_plus_delta[1] = x[1] + delta[posDelta]; + ++posDelta; + } + } + + if (!_lockCenter) + { + x_plus_delta[2] = x[2] + delta[posDelta]; + ++posDelta; + + x_plus_delta[3] = x[3] + delta[posDelta]; + ++posDelta; + } + + if (!_lockDistortion) + { + for (int i = 0; i < _distortionSize; i++) + { + x_plus_delta[4 + i] = x[4 + i] + delta[posDelta]; + ++posDelta; + } + } + + return true; } - if (!_lockCenter) + bool PlusJacobian(const double* x, double* jacobian) const override { - x_plus_delta[2] = x[2] + delta[posDelta]; - ++posDelta; - - x_plus_delta[3] = x[3] + delta[posDelta]; - ++posDelta; + Eigen::Map> J(jacobian, AmbientSize(), TangentSize()); + + J.fill(0); + + size_t posDelta = 0; + if (!_lockFocal) + { + if (_lockFocalRatio) + { + J(0, posDelta) = 1.0; + J(1, posDelta) = _focalRatio; + ++posDelta; + } + else + { + J(0, posDelta) = 1.0; + ++posDelta; + J(1, posDelta) = 1.0; + ++posDelta; + } + } + + if (!_lockCenter) + { + J(2, posDelta) = 1.0; + ++posDelta; + + J(3, posDelta) = 1.0; + ++posDelta; + } + + if (!_lockDistortion) + { + for (int i = 0; i < _distortionSize; i++) + { + J(4 + i, posDelta) = 1.0; + ++posDelta; + } + } + + return true; } - if (!_lockDistortion) + bool Minus(const double* y, const double* x, double* delta) const override { - for (int i = 0; i < _distortionSize; i++) - { - x_plus_delta[4 + i] = x[4 + i] + delta[posDelta]; - ++posDelta; - } + throw std::invalid_argument("IntrinsicsManifold::Minus() should never be called"); } - return true; - } - - bool PlusJacobian(const double* x, double* jacobian) const override - { - Eigen::Map> J(jacobian, AmbientSize(), TangentSize()); - - J.fill(0); - - size_t posDelta = 0; - if (!_lockFocal) + bool MinusJacobian(const double* x, double* jacobian) const override { - if (_lockFocalRatio) - { - J(0, posDelta) = 1.0; - J(1, posDelta) = _focalRatio; - ++posDelta; - } - else - { - J(0, posDelta) = 1.0; - ++posDelta; - J(1, posDelta) = 1.0; - ++posDelta; - } + throw std::invalid_argument("IntrinsicsManifold::MinusJacobian() should never be called"); } - if (!_lockCenter) - { - J(2, posDelta) = 1.0; - ++posDelta; + int AmbientSize() const override { return _ambientSize; } - J(3, posDelta) = 1.0; - ++posDelta; - } - - if (!_lockDistortion) - { - for (int i = 0; i < _distortionSize; i++) - { - J(4 + i, posDelta) = 1.0; - ++posDelta; - } - } + int TangentSize() const override { return _tangentSize; } - return true; - } - - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("IntrinsicsManifold::Minus() should never be called"); - } - - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("IntrinsicsManifold::MinusJacobian() should never be called"); - } - - int AmbientSize() const override - { - return _ambientSize; - } - - int TangentSize() const override - { - return _tangentSize; - } - - private: - size_t _distortionSize; - size_t _ambientSize; - size_t _tangentSize; - double _focalRatio; - bool _lockFocal; - bool _lockFocalRatio; - bool _lockCenter; - bool _lockDistortion; + private: + size_t _distortionSize; + size_t _ambientSize; + size_t _tangentSize; + double _focalRatio; + bool _lockFocal; + bool _lockFocalRatio; + bool _lockCenter; + bool _lockDistortion; }; -} //namespace sfm -} //namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/manifolds/se3.hpp b/src/aliceVision/sfm/bundle/manifolds/se3.hpp index 6619e909ec..8c237a89bf 100644 --- a/src/aliceVision/sfm/bundle/manifolds/se3.hpp +++ b/src/aliceVision/sfm/bundle/manifolds/se3.hpp @@ -16,149 +16,143 @@ namespace aliceVision { namespace sfm { +class SE3ManifoldLeft : public ceres::Manifold +{ + public: + SE3ManifoldLeft(bool refineRotation, bool refineTranslation) + : _refineRotation(refineRotation), + _refineTranslation(refineTranslation) + {} + + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override + { + Eigen::Map> T(x); + Eigen::Map> T_result(x_plus_delta); + Eigen::Map> vec_update(delta); + Eigen::Matrix4d T_update = Eigen::Matrix4d::Identity(); -class SE3ManifoldLeft : public ceres::Manifold { -public: - SE3ManifoldLeft(bool refineRotation, bool refineTranslation) : - _refineRotation(refineRotation), - _refineTranslation(refineTranslation) - { - } - - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { - - Eigen::Map> T(x); - Eigen::Map> T_result(x_plus_delta); - Eigen::Map> vec_update(delta); - Eigen::Matrix4d T_update = Eigen::Matrix4d::Identity(); + T_update = SE3::expm(vec_update); + T_result = T_update * T; - T_update = SE3::expm(vec_update); - T_result = T_update * T; + return true; + } - return true; - } + bool PlusJacobian(const double* x, double* jacobian) const override + { + Eigen::Map> J(jacobian); - bool PlusJacobian(const double * x, double* jacobian) const override { + J.fill(0); - Eigen::Map> J(jacobian); + if (_refineRotation) + { + J(1, 2) = 1; + J(2, 1) = -1; - J.fill(0); + J(4, 2) = -1; + J(6, 0) = 1; - if (_refineRotation) - { - J(1, 2) = 1; - J(2, 1) = -1; + J(8, 1) = 1; + J(9, 0) = -1; + } - J(4, 2) = -1; - J(6, 0) = 1; + if (_refineTranslation) + { + J(12, 3) = 1; + J(13, 4) = 1; + J(14, 5) = 1; + } - J(8, 1) = 1; - J(9, 0) = -1; + return true; } - if (_refineTranslation) + bool Minus(const double* y, const double* x, double* delta) const override { - J(12, 3) = 1; - J(13, 4) = 1; - J(14, 5) = 1; + throw std::invalid_argument("SE3::Manifold::Minus() should never be called"); } - return true; - } - - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("SE3::Manifold::Minus() should never be called"); - } - - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("SE3::Manifold::MinusJacobian() should never be called"); - } + bool MinusJacobian(const double* x, double* jacobian) const override + { + throw std::invalid_argument("SE3::Manifold::MinusJacobian() should never be called"); + } - int AmbientSize() const override { - return 16; - } + int AmbientSize() const override { return 16; } - int TangentSize() const override { - return 6; - } + int TangentSize() const override { return 6; } -private: - bool _refineRotation; - bool _refineTranslation; + private: + bool _refineRotation; + bool _refineTranslation; }; +class SE3ManifoldRight : public ceres::Manifold +{ + public: + SE3ManifoldRight(bool refineRotation, bool refineTranslation) + : _refineRotation(refineRotation), + _refineTranslation(refineTranslation) + {} -class SE3ManifoldRight : public ceres::Manifold { -public: - SE3ManifoldRight(bool refineRotation, bool refineTranslation) : - _refineRotation(refineRotation), - _refineTranslation(refineTranslation) - { - } - - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override + { + Eigen::Map> T(x); + Eigen::Map> T_result(x_plus_delta); + Eigen::Map> vec_update(delta); + Eigen::Matrix4d T_update = Eigen::Matrix4d::Identity(); - Eigen::Map> T(x); - Eigen::Map> T_result(x_plus_delta); - Eigen::Map> vec_update(delta); - Eigen::Matrix4d T_update = Eigen::Matrix4d::Identity(); + T_update = SE3::expm(vec_update); + T_result = T * T_update; - T_update = SE3::expm(vec_update); - T_result = T * T_update; + return true; + } - return true; - } + bool PlusJacobian(const double* x, double* jacobian) const override + { + Eigen::Map> J(jacobian); + Eigen::Map> T(x); - bool PlusJacobian(const double * x, double* jacobian) const override { + J.fill(0); - Eigen::Map> J(jacobian); - Eigen::Map> T(x); + if (_refineRotation) + { + J(1, 2) = 1; + J(2, 1) = -1; - J.fill(0); + J(4, 2) = -1; + J(6, 0) = 1; - if (_refineRotation) - { - J(1, 2) = 1; - J(2, 1) = -1; + J(8, 1) = 1; + J(9, 0) = -1; + } - J(4, 2) = -1; - J(6, 0) = 1; + if (_refineTranslation) + { + J(12, 3) = 1; + J(13, 4) = 1; + J(14, 5) = 1; + } - J(8, 1) = 1; - J(9, 0) = -1; + return true; } - if (_refineTranslation) + bool Minus(const double* y, const double* x, double* delta) const override { - J(12, 3) = 1; - J(13, 4) = 1; - J(14, 5) = 1; + throw std::invalid_argument("SE3::Manifold::Minus() should never be called"); } - return true; - } - - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("SE3::Manifold::Minus() should never be called"); - } - - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("SE3::Manifold::MinusJacobian() should never be called"); - } + bool MinusJacobian(const double* x, double* jacobian) const override + { + throw std::invalid_argument("SE3::Manifold::MinusJacobian() should never be called"); + } - int AmbientSize() const override { - return 16; - } + int AmbientSize() const override { return 16; } - int TangentSize() const override { - return 6; - } + int TangentSize() const override { return 6; } -private: - bool _refineRotation; - bool _refineTranslation; + private: + bool _refineRotation; + bool _refineTranslation; }; -} //namespace SE3 +} // namespace sfm -} //namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/manifolds/so2.hpp b/src/aliceVision/sfm/bundle/manifolds/so2.hpp index aba1870fda..79e44a1698 100644 --- a/src/aliceVision/sfm/bundle/manifolds/so2.hpp +++ b/src/aliceVision/sfm/bundle/manifolds/so2.hpp @@ -15,49 +15,48 @@ namespace aliceVision { namespace sfm { -class SO2Manifold : public ceres::Manifold { -public: - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { +class SO2Manifold : public ceres::Manifold +{ + public: + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override + { + Eigen::Map> T(x); + Eigen::Map> T_result(x_plus_delta); + double update = delta[0]; - Eigen::Map> T(x); - Eigen::Map> T_result(x_plus_delta); - double update = delta[0]; + Eigen::Matrix2d T_update = expm(update); + T_result = T_update * T; - Eigen::Matrix2d T_update = expm(update); - T_result = T_update * T; + return true; + } - return true; - } + bool PlusJacobian(const double* x, double* jacobian) const override + { + Eigen::Map> J(jacobian); - bool PlusJacobian(const double * x, double* jacobian) const override { + J.fill(0); - Eigen::Map> J(jacobian); + J(1, 0) = 1; + J(2, 0) = -1; - J.fill(0); + return true; + } - J(1, 0) = 1; - J(2, 0) = -1; - - return true; - } + bool Minus(const double* y, const double* x, double* delta) const override + { + throw std::invalid_argument("SO3::Manifold::Minus() should never be called"); + } - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("SO3::Manifold::Minus() should never be called"); - } + bool MinusJacobian(const double* x, double* jacobian) const override + { + throw std::invalid_argument("SO3::Manifold::MinusJacobian() should never be called"); + } - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("SO3::Manifold::MinusJacobian() should never be called"); - } + int AmbientSize() const override { return 4; } - int AmbientSize() const override { - return 4; - } - - int TangentSize() const override { - return 1; - } + int TangentSize() const override { return 1; } }; -} //namespace sfm +} // namespace sfm -} //namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/manifolds/so3.hpp b/src/aliceVision/sfm/bundle/manifolds/so3.hpp index 181209201a..009b806f13 100644 --- a/src/aliceVision/sfm/bundle/manifolds/so3.hpp +++ b/src/aliceVision/sfm/bundle/manifolds/so3.hpp @@ -19,18 +19,22 @@ Compute the jacobian of the logarithm wrt changes in the rotation matrix values @param R the input rotation matrix @return the jacobian matrix (3*9 matrix) */ -inline Eigen::Matrix dlogmdr(const Eigen::Matrix3d & R) { +inline Eigen::Matrix dlogmdr(const Eigen::Matrix3d& R) +{ double p1 = R(2, 1) - R(1, 2); double p2 = R(0, 2) - R(2, 0); double p3 = R(1, 0) - R(0, 1); double costheta = (R.trace() - 1.0) / 2.0; - if (costheta > 1.0) costheta = 1.0; - else if (costheta < -1.0) costheta = -1.0; + if (costheta > 1.0) + costheta = 1.0; + else if (costheta < -1.0) + costheta = -1.0; double theta = acos(costheta); - if (fabs(theta) < std::numeric_limits::epsilon()) { + if (fabs(theta) < std::numeric_limits::epsilon()) + { Eigen::Matrix J; J.fill(0); J(0, 5) = 1; @@ -59,9 +63,8 @@ inline Eigen::Matrix dlogmdr(const Eigen::Matrix3 dpdmat(2, 1) = 1; dpdmat(2, 3) = -1; - - double dscaledtheta = -0.5 * theta * cos(theta) / (sin(theta)*sin(theta)) + 0.5 / sin(theta); - double dthetadcostheta = -1.0 / sqrt(-costheta*costheta + 1.0); + double dscaledtheta = -0.5 * theta * cos(theta) / (sin(theta) * sin(theta)) + 0.5 / sin(theta); + double dthetadcostheta = -1.0 / sqrt(-costheta * costheta + 1.0); Eigen::Matrix dcosthetadmat; dcosthetadmat << 0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5; @@ -69,68 +72,71 @@ inline Eigen::Matrix dlogmdr(const Eigen::Matrix3 return dpdmat * scale + resnoscale * dscaledmat; } -} +} // namespace SO3 namespace sfm { -class SO3Manifold : public ceres::Manifold { - public: - ~SO3Manifold() override = default; +class SO3Manifold : public ceres::Manifold +{ + public: + ~SO3Manifold() override = default; - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { - - double* ptrBase = (double*)x; - double* ptrResult = (double*)x_plus_delta; - Eigen::Map > rotation(ptrBase); - Eigen::Map > rotationResult(ptrResult); + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override + { + double* ptrBase = (double*)x; + double* ptrResult = (double*)x_plus_delta; + Eigen::Map> rotation(ptrBase); + Eigen::Map> rotationResult(ptrResult); - Eigen::Vector3d axis; - axis(0) = delta[0]; - axis(1) = delta[1]; - axis(2) = delta[2]; - double angle = axis.norm(); + Eigen::Vector3d axis; + axis(0) = delta[0]; + axis(1) = delta[1]; + axis(2) = delta[2]; + double angle = axis.norm(); - axis.normalize(); + axis.normalize(); - Eigen::AngleAxisd aa(angle, axis); - Eigen::Matrix3d Rupdate; - Rupdate = aa.toRotationMatrix(); + Eigen::AngleAxisd aa(angle, axis); + Eigen::Matrix3d Rupdate; + Rupdate = aa.toRotationMatrix(); - rotationResult = Rupdate * rotation; + rotationResult = Rupdate * rotation; - return true; - } + return true; + } - bool PlusJacobian(const double* /*x*/, double* jacobian) const override { - - Eigen::Map> J(jacobian); - //Eigen::Map> R(x); + bool PlusJacobian(const double* /*x*/, double* jacobian) const override + { + Eigen::Map> J(jacobian); + // Eigen::Map> R(x); - J.fill(0); + J.fill(0); - J(1, 2) = 1; - J(2, 1) = -1; - J(3, 2) = -1; - J(5, 0) = 1; - J(6, 1) = 1; - J(7, 0) = -1; + J(1, 2) = 1; + J(2, 1) = -1; + J(3, 2) = -1; + J(5, 0) = 1; + J(6, 1) = 1; + J(7, 0) = -1; - return true; - } + return true; + } - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("SO3::Manifold::Minus() should never be called"); - } + bool Minus(const double* y, const double* x, double* delta) const override + { + throw std::invalid_argument("SO3::Manifold::Minus() should never be called"); + } - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("SO3::Manifold::MinusJacobian() should never be called"); - } + bool MinusJacobian(const double* x, double* jacobian) const override + { + throw std::invalid_argument("SO3::Manifold::MinusJacobian() should never be called"); + } - int AmbientSize() const override { return 9; } + int AmbientSize() const override { return 9; } - int TangentSize() const override { return 3; } + int TangentSize() const override { return 3; } }; -}//namespace sfm +} // namespace sfm -} //namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/sfm/bundle/manifolds/so3vec.hpp b/src/aliceVision/sfm/bundle/manifolds/so3vec.hpp index 39c2dcb5aa..359db94ccf 100644 --- a/src/aliceVision/sfm/bundle/manifolds/so3vec.hpp +++ b/src/aliceVision/sfm/bundle/manifolds/so3vec.hpp @@ -14,142 +14,144 @@ namespace aliceVision { namespace sfm { -class SO3Vec : public ceres::Manifold { - public: - ~SO3Vec() override = default; +class SO3Vec : public ceres::Manifold +{ + public: + ~SO3Vec() override = default; - bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { - - double* ptrBase = (double*)x; - double* ptrResult = (double*)x_plus_delta; + bool Plus(const double* x, const double* delta, double* x_plus_delta) const override + { + double* ptrBase = (double*)x; + double* ptrResult = (double*)x_plus_delta; - Eigen::Vector3d n; - n(0) = x[0]; - n(1) = x[1]; - n(2) = x[2]; + Eigen::Vector3d n; + n(0) = x[0]; + n(1) = x[1]; + n(2) = x[2]; - Eigen::Vector2d update; - update(0) = delta[0]; - update(1) = delta[1]; + Eigen::Vector2d update; + update(0) = delta[0]; + update(1) = delta[1]; - //Find a point on the plane - int maxidx = 0; - double maxval = std::abs(n(0)); + // Find a point on the plane + int maxidx = 0; + double maxval = std::abs(n(0)); - if (std::abs(n(1)) > maxval) - { - maxidx = 1; - maxval = std::abs(n(1)); - } + if (std::abs(n(1)) > maxval) + { + maxidx = 1; + maxval = std::abs(n(1)); + } - if (std::abs(n(2)) > maxval) - { - maxidx = 2; - maxval = std::abs(n(2)); - } + if (std::abs(n(2)) > maxval) + { + maxidx = 2; + maxval = std::abs(n(2)); + } - double sum = 0.0; - for (int i = 0; i < 3; i++) - { - if (i != maxidx) + double sum = 0.0; + for (int i = 0; i < 3; i++) { - sum += n(i); + if (i != maxidx) + { + sum += n(i); + } } - } - Vec3 a1; - a1(0) = 1; - a1(1) = 1; - a1(2) = 1; - a1(maxidx) = -sum/n(maxidx); - a1.normalize(); + Vec3 a1; + a1(0) = 1; + a1(1) = 1; + a1(2) = 1; + a1(maxidx) = -sum / n(maxidx); + a1.normalize(); - Vec3 a2 = n.cross(a1); + Vec3 a2 = n.cross(a1); - Eigen::Matrix A; - A.col(0) = a1; - A.col(1) = a2; + Eigen::Matrix A; + A.col(0) = a1; + A.col(1) = a2; - Vec3 new_n = SO3::expm(A*update)*n; + Vec3 new_n = SO3::expm(A * update) * n; - x_plus_delta[0] = new_n(0); - x_plus_delta[1] = new_n(1); - x_plus_delta[2] = new_n(2); + x_plus_delta[0] = new_n(0); + x_plus_delta[1] = new_n(1); + x_plus_delta[2] = new_n(2); - return true; - } + return true; + } - bool PlusJacobian(const double* x, double* jacobian) const override { - - Eigen::Map> J(jacobian, AmbientSize(), TangentSize()); - J.fill(0); + bool PlusJacobian(const double* x, double* jacobian) const override + { + Eigen::Map> J(jacobian, AmbientSize(), TangentSize()); + J.fill(0); - Eigen::Vector3d n; - n(0) = x[0]; - n(1) = x[1]; - n(2) = x[2]; + Eigen::Vector3d n; + n(0) = x[0]; + n(1) = x[1]; + n(2) = x[2]; - //Find a point on the plane - int maxidx = 0; - double maxval = std::abs(n(0)); + // Find a point on the plane + int maxidx = 0; + double maxval = std::abs(n(0)); - if (std::abs(n(1)) > maxval) - { - maxidx = 1; - maxval = std::abs(n(1)); - } - - if (std::abs(n(2)) > maxval) - { - maxidx = 2; - maxval = std::abs(n(2)); - } + if (std::abs(n(1)) > maxval) + { + maxidx = 1; + maxval = std::abs(n(1)); + } - double sum = 0.0; - for (int i = 0; i < 3; i++) - { - if (i != maxidx) + if (std::abs(n(2)) > maxval) { - sum += n(i); + maxidx = 2; + maxval = std::abs(n(2)); } - } - Vec3 a1; - a1(0) = 1; - a1(1) = 1; - a1(2) = 1; - a1(maxidx) = -sum/n(maxidx); - a1.normalize(); + double sum = 0.0; + for (int i = 0; i < 3; i++) + { + if (i != maxidx) + { + sum += n(i); + } + } - Vec3 a2 = n.cross(a1); + Vec3 a1; + a1(0) = 1; + a1(1) = 1; + a1(2) = 1; + a1(maxidx) = -sum / n(maxidx); + a1.normalize(); - Eigen::Matrix A; - A.col(0) = a1; - A.col(1) = a2; + Vec3 a2 = n.cross(a1); - //d(I + [A*update]_x)*n/dupdate - //= d([A*update]_x * n)/dupdate - //= - d([n]_x * A*update)/dupdate + Eigen::Matrix A; + A.col(0) = a1; + A.col(1) = a2; - J = - SO3::skew(n) * A; + // d(I + [A*update]_x)*n/dupdate + //= d([A*update]_x * n)/dupdate + //= - d([n]_x * A*update)/dupdate + J = -SO3::skew(n) * A; - return true; - } + return true; + } - bool Minus(const double* y, const double* x, double* delta) const override { - throw std::invalid_argument("SO3::Manifold::Minus() should never be called"); - } + bool Minus(const double* y, const double* x, double* delta) const override + { + throw std::invalid_argument("SO3::Manifold::Minus() should never be called"); + } - bool MinusJacobian(const double* x, double* jacobian) const override { - throw std::invalid_argument("SO3::Manifold::MinusJacobian() should never be called"); - } + bool MinusJacobian(const double* x, double* jacobian) const override + { + throw std::invalid_argument("SO3::Manifold::MinusJacobian() should never be called"); + } - int AmbientSize() const override { return 3; } + int AmbientSize() const override { return 3; } - int TangentSize() const override { return 2; } + int TangentSize() const override { return 2; } }; -}//namespace sfm +} // namespace sfm -} //namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/sfm/filters.hpp b/src/aliceVision/sfm/filters.hpp index d3bb04e1c9..1951baa600 100644 --- a/src/aliceVision/sfm/filters.hpp +++ b/src/aliceVision/sfm/filters.hpp @@ -13,121 +13,98 @@ #include namespace aliceVision { -namespace sfm { +namespace sfm { template -inline std::set getIndexes(const IterableIndexTSequence & seq) +inline std::set getIndexes(const IterableIndexTSequence& seq) { - std::set setOut; - for(typename IterableIndexTSequence::const_iterator it = seq.begin(); it != seq.end(); ++it) - setOut.insert(it->first); - return setOut; + std::set setOut; + for (typename IterableIndexTSequence::const_iterator it = seq.begin(); it != seq.end(); ++it) + setOut.insert(it->first); + return setOut; } /// Filter the toFilter iterable sequence (keep only the element that share a common index /// with the provided Ids index list). template -inline void KeepOnlyReferencedElement( - const std::set & Ids, - T & toFilter) +inline void KeepOnlyReferencedElement(const std::set& Ids, T& toFilter) { - ALICEVISION_LOG_ERROR("Must be specialized for your type"); + ALICEVISION_LOG_ERROR("Must be specialized for your type"); } // Specialization for RelativeInfoMap -inline void KeepOnlyReferencedElement( - const std::set & set_remainingIds, - translationAveraging::RelativeInfoMap& map_relatives) +inline void KeepOnlyReferencedElement(const std::set& set_remainingIds, translationAveraging::RelativeInfoMap& map_relatives) { - translationAveraging::RelativeInfoMap map_relatives_infered; - for (translationAveraging::RelativeInfoMap::const_iterator - iter = map_relatives.begin(); - iter != map_relatives.end(); ++iter) - { - if (set_remainingIds.find(iter->first.first) != set_remainingIds.end() && - set_remainingIds.find(iter->first.second) != set_remainingIds.end()) + translationAveraging::RelativeInfoMap map_relatives_infered; + for (translationAveraging::RelativeInfoMap::const_iterator iter = map_relatives.begin(); iter != map_relatives.end(); ++iter) { - map_relatives_infered.insert(*iter); + if (set_remainingIds.find(iter->first.first) != set_remainingIds.end() && set_remainingIds.find(iter->first.second) != set_remainingIds.end()) + { + map_relatives_infered.insert(*iter); + } } - } - map_relatives.swap(map_relatives_infered); + map_relatives.swap(map_relatives_infered); } // Specialization for RelativeInfoMap template<> -inline void KeepOnlyReferencedElement( - const std::set & set_remainingIds, - rotationAveraging::RelativeRotations& relative_info) +inline void KeepOnlyReferencedElement(const std::set& set_remainingIds, rotationAveraging::RelativeRotations& relative_info) { - rotationAveraging::RelativeRotations relatives_infered; - for (rotationAveraging::RelativeRotations::const_iterator - iter = relative_info.begin(); - iter != relative_info.end(); ++iter) - { - if (set_remainingIds.find(iter->i) != set_remainingIds.end() && - set_remainingIds.find(iter->j) != set_remainingIds.end()) + rotationAveraging::RelativeRotations relatives_infered; + for (rotationAveraging::RelativeRotations::const_iterator iter = relative_info.begin(); iter != relative_info.end(); ++iter) { - relatives_infered.push_back(*iter); + if (set_remainingIds.find(iter->i) != set_remainingIds.end() && set_remainingIds.find(iter->j) != set_remainingIds.end()) + { + relatives_infered.push_back(*iter); + } } - } - relative_info.swap(relatives_infered); + relative_info.swap(relatives_infered); } // Specialization for PairwiseMatches template<> -inline void KeepOnlyReferencedElement( - const std::set & set_remainingIds, - aliceVision::matching::PairwiseMatches& map_matches) +inline void KeepOnlyReferencedElement(const std::set& set_remainingIds, aliceVision::matching::PairwiseMatches& map_matches) { - aliceVision::matching::PairwiseMatches map_matches_E_infered; - for (aliceVision::matching::PairwiseMatches::const_iterator iter = map_matches.begin(); - iter != map_matches.end(); ++iter) - { - if (set_remainingIds.find(iter->first.first) != set_remainingIds.end() && - set_remainingIds.find(iter->first.second) != set_remainingIds.end()) + aliceVision::matching::PairwiseMatches map_matches_E_infered; + for (aliceVision::matching::PairwiseMatches::const_iterator iter = map_matches.begin(); iter != map_matches.end(); ++iter) { - map_matches_E_infered.insert(*iter); + if (set_remainingIds.find(iter->first.first) != set_remainingIds.end() && set_remainingIds.find(iter->first.second) != set_remainingIds.end()) + { + map_matches_E_infered.insert(*iter); + } } - } - map_matches.swap(map_matches_E_infered); + map_matches.swap(map_matches_E_infered); } // Specialization for std::map template<> -inline void KeepOnlyReferencedElement( - const std::set & set_remainingIds, - std::map& map_Mat3) +inline void KeepOnlyReferencedElement(const std::set& set_remainingIds, std::map& map_Mat3) { - std::map map_infered; - for (std::map::const_iterator iter = map_Mat3.begin(); - iter != map_Mat3.end(); ++iter) - { - if (set_remainingIds.find(iter->first) != set_remainingIds.end()) + std::map map_infered; + for (std::map::const_iterator iter = map_Mat3.begin(); iter != map_Mat3.end(); ++iter) { - map_infered.insert(*iter); + if (set_remainingIds.find(iter->first) != set_remainingIds.end()) + { + map_infered.insert(*iter); + } } - } - map_Mat3.swap(map_infered); + map_Mat3.swap(map_infered); } // Specialization for RelativeInfoVec template<> -inline void KeepOnlyReferencedElement( - const std::set & set_remainingIds, - translationAveraging::RelativeInfoVec & relativeInfo_vec) +inline void KeepOnlyReferencedElement(const std::set& set_remainingIds, translationAveraging::RelativeInfoVec& relativeInfo_vec) { - translationAveraging::RelativeInfoVec map_infered; - for (translationAveraging::RelativeInfoVec::const_iterator iter = relativeInfo_vec.begin(); - iter != relativeInfo_vec.end(); ++iter) - { - if (set_remainingIds.find(iter->first.first) != set_remainingIds.end() && - set_remainingIds.find(iter->first.second) != set_remainingIds.end()) + translationAveraging::RelativeInfoVec map_infered; + for (translationAveraging::RelativeInfoVec::const_iterator iter = relativeInfo_vec.begin(); iter != relativeInfo_vec.end(); ++iter) { - map_infered.push_back(*iter); + if (set_remainingIds.find(iter->first.first) != set_remainingIds.end() && set_remainingIds.find(iter->first.second) != set_remainingIds.end()) + { + map_infered.push_back(*iter); + } } - } - relativeInfo_vec.swap(map_infered); + relativeInfo_vec.swap(map_infered); } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/generateReport.cpp b/src/aliceVision/sfm/generateReport.cpp index 478a258eb1..dbd74b9e91 100644 --- a/src/aliceVision/sfm/generateReport.cpp +++ b/src/aliceVision/sfm/generateReport.cpp @@ -19,161 +19,131 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace sfm { -bool generateSfMReport(const sfmData::SfMData& sfmData, - const std::string& htmlFilename) +bool generateSfMReport(const sfmData::SfMData& sfmData, const std::string& htmlFilename) { - // Compute mean,max,median residual values per View - IndexT residualCount = 0; - HashMap< IndexT, std::vector > residuals_per_view; - for(sfmData::Landmarks::const_iterator iterTracks = sfmData.getLandmarks().begin(); - iterTracks != sfmData.getLandmarks().end(); - ++iterTracks - ) - { - const sfmData::Observations & observations = iterTracks->second.observations; - for(sfmData::Observations::const_iterator itObs = observations.begin(); - itObs != observations.end(); ++itObs) + // Compute mean,max,median residual values per View + IndexT residualCount = 0; + HashMap> residuals_per_view; + for (sfmData::Landmarks::const_iterator iterTracks = sfmData.getLandmarks().begin(); iterTracks != sfmData.getLandmarks().end(); ++iterTracks) { - const sfmData::View * view = sfmData.getViews().at(itObs->first).get(); - const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); - const camera::IntrinsicBase * intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); - // Use absolute values - const Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x).array().abs(); - residuals_per_view[itObs->first].push_back(residual(0)); - residuals_per_view[itObs->first].push_back(residual(1)); - ++residualCount; + const sfmData::Observations& observations = iterTracks->second.observations; + for (sfmData::Observations::const_iterator itObs = observations.begin(); itObs != observations.end(); ++itObs) + { + const sfmData::View* view = sfmData.getViews().at(itObs->first).get(); + const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); + const camera::IntrinsicBase* intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); + // Use absolute values + const Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x).array().abs(); + residuals_per_view[itObs->first].push_back(residual(0)); + residuals_per_view[itObs->first].push_back(residual(1)); + ++residualCount; + } } - } - using namespace htmlDocument; - // extract directory from htmlFilename - const std::string sTableBegin = "", - sTableEnd = "
", - sRowBegin= "", sRowEnd = "", - sColBegin = "", sColEnd = "", - sNewLine = "
", sFullLine = "


"; - - htmlDocument::htmlDocumentStream htmlDocStream("[report] SfM reconstruction"); - htmlDocStream.pushInfo( - htmlDocument::htmlMarkup("h1", std::string("[report] SfM reconstruction"))); - htmlDocStream.pushInfo(sFullLine); - - htmlDocStream.pushInfo( "Dataset info:" + sNewLine ); - - std::ostringstream os; - os << "#views: " << sfmData.getViews().size() << sNewLine - << " #valid views: " << sfmData.getValidViews().size() << sNewLine - << " #poses: " << sfmData.getPoses().size() << sNewLine - << " #intrinsics: " << sfmData.getIntrinsics().size() << sNewLine - << " #tracks: " << sfmData.getLandmarks().size() << sNewLine - << " #residuals: " << residualCount << sNewLine; - - htmlDocStream.pushInfo( os.str() ); - htmlDocStream.pushInfo( sFullLine ); - - htmlDocStream.pushInfo( sTableBegin); - os.str(""); - os << sRowBegin - << sColBegin + "IdView" + sColEnd - << sColBegin + "Basename" + sColEnd - << sColBegin + "#Observations" + sColEnd - << sColBegin + "Residuals min" + sColEnd - << sColBegin + "Residuals median" + sColEnd - << sColBegin + "Residuals mean" + sColEnd - << sColBegin + "Residuals max" + sColEnd - << sRowEnd; - htmlDocStream.pushInfo( os.str() ); - - for(sfmData::Views::const_iterator iterV = sfmData.getViews().begin(); - iterV != sfmData.getViews().end(); - ++iterV) - { - const sfmData::View * v = iterV->second.get(); - const IndexT id_view = v->getViewId(); + using namespace htmlDocument; + // extract directory from htmlFilename + const std::string sTableBegin = "", sTableEnd = "
", sRowBegin = "", sRowEnd = "", sColBegin = "", + sColEnd = "", sNewLine = "
", sFullLine = "
"; + + htmlDocument::htmlDocumentStream htmlDocStream("[report] SfM reconstruction"); + htmlDocStream.pushInfo(htmlDocument::htmlMarkup("h1", std::string("[report] SfM reconstruction"))); + htmlDocStream.pushInfo(sFullLine); + + htmlDocStream.pushInfo("Dataset info:" + sNewLine); + + std::ostringstream os; + os << "#views: " << sfmData.getViews().size() << sNewLine << " #valid views: " << sfmData.getValidViews().size() << sNewLine + << " #poses: " << sfmData.getPoses().size() << sNewLine << " #intrinsics: " << sfmData.getIntrinsics().size() << sNewLine + << " #tracks: " << sfmData.getLandmarks().size() << sNewLine << " #residuals: " << residualCount << sNewLine; + htmlDocStream.pushInfo(os.str()); + htmlDocStream.pushInfo(sFullLine); + + htmlDocStream.pushInfo(sTableBegin); os.str(""); - os << sRowBegin - << sColBegin << id_view << sColEnd - << sColBegin + fs::path(v->getImage().getImagePath()).stem().string() + sColEnd; + os << sRowBegin << sColBegin + "IdView" + sColEnd << sColBegin + "Basename" + sColEnd << sColBegin + "#Observations" + sColEnd + << sColBegin + "Residuals min" + sColEnd << sColBegin + "Residuals median" + sColEnd << sColBegin + "Residuals mean" + sColEnd + << sColBegin + "Residuals max" + sColEnd << sRowEnd; + htmlDocStream.pushInfo(os.str()); - // IdView | basename | #Observations | residuals min | residual median | residual max - if(sfmData.isPoseAndIntrinsicDefined(v)) + for (sfmData::Views::const_iterator iterV = sfmData.getViews().begin(); iterV != sfmData.getViews().end(); ++iterV) { - if(residuals_per_view.find(id_view) != residuals_per_view.end() ) - { - const std::vector& residuals = residuals_per_view.at(id_view); - if(!residuals.empty()) + const sfmData::View* v = iterV->second.get(); + const IndexT id_view = v->getViewId(); + + os.str(""); + os << sRowBegin << sColBegin << id_view << sColEnd << sColBegin + fs::path(v->getImage().getImagePath()).stem().string() + sColEnd; + + // IdView | basename | #Observations | residuals min | residual median | residual max + if (sfmData.isPoseAndIntrinsicDefined(v)) { - BoxStats stats(residuals.begin(), residuals.end()); - os << sColBegin << residuals.size()/2 << sColEnd // #observations - << sColBegin << stats.min << sColEnd - << sColBegin << stats.median << sColEnd - << sColBegin << stats.mean << sColEnd - << sColBegin << stats.max <& residuals = residuals_per_view.at(id_view); + if (!residuals.empty()) + { + BoxStats stats(residuals.begin(), residuals.end()); + os << sColBegin << residuals.size() / 2 << sColEnd // #observations + << sColBegin << stats.min << sColEnd << sColBegin << stats.median << sColEnd << sColBegin << stats.mean << sColEnd << sColBegin + << stats.max << sColEnd; + } + } } - } + os << sRowEnd; + htmlDocStream.pushInfo(os.str()); } - os << sRowEnd; - htmlDocStream.pushInfo( os.str() ); - } - htmlDocStream.pushInfo( sTableEnd ); - htmlDocStream.pushInfo( sFullLine ); - - // combine all residual values into one vector - // export the SVG histogram - { - IndexT residualCount = 0; - for(HashMap< IndexT, std::vector >::const_iterator - it = residuals_per_view.begin(); - it != residuals_per_view.end(); - ++it) - { - residualCount += it->second.size(); - } - // Concat per view residual values into one vector - std::vector residuals(residualCount); - residualCount = 0; - for(HashMap< IndexT, std::vector >::const_iterator - it = residuals_per_view.begin(); - it != residuals_per_view.end(); - ++it) - { - std::copy(it->second.begin(), - it->second.begin()+it->second.size(), - residuals.begin()+residualCount); - residualCount += it->second.size(); - } - if(!residuals.empty()) + htmlDocStream.pushInfo(sTableEnd); + htmlDocStream.pushInfo(sFullLine); + + // combine all residual values into one vector + // export the SVG histogram { - // RMSE computation - const Eigen::Map residuals_mapping(&residuals[0], residuals.size()); - const double RMSE = std::sqrt(residuals_mapping.squaredNorm() / (double)residuals.size()); - os.str(""); - os << sFullLine << "SfM Scene RMSE: " << RMSE << sFullLine; - htmlDocStream.pushInfo(os.str()); - - const double maxRange = *max_element(residuals.begin(), residuals.end()); - utils::Histogram histo(0.0, maxRange, 100); - histo.Add(residuals.begin(), residuals.end()); - - svg::svgHisto svg_Histo; - svg_Histo.draw(histo.GetHist(), std::pair(0.f, maxRange), - (fs::path(htmlFilename).parent_path() / std::string("residuals_histogram.svg")).string(), - 600, 200); - - os.str(""); - os << sNewLine<< "Residuals histogram" << sNewLine; - os << "\n"; - htmlDocStream.pushInfo(os.str()); + IndexT residualCount = 0; + for (HashMap>::const_iterator it = residuals_per_view.begin(); it != residuals_per_view.end(); ++it) + { + residualCount += it->second.size(); + } + // Concat per view residual values into one vector + std::vector residuals(residualCount); + residualCount = 0; + for (HashMap>::const_iterator it = residuals_per_view.begin(); it != residuals_per_view.end(); ++it) + { + std::copy(it->second.begin(), it->second.begin() + it->second.size(), residuals.begin() + residualCount); + residualCount += it->second.size(); + } + if (!residuals.empty()) + { + // RMSE computation + const Eigen::Map residuals_mapping(&residuals[0], residuals.size()); + const double RMSE = std::sqrt(residuals_mapping.squaredNorm() / (double)residuals.size()); + os.str(""); + os << sFullLine << "SfM Scene RMSE: " << RMSE << sFullLine; + htmlDocStream.pushInfo(os.str()); + + const double maxRange = *max_element(residuals.begin(), residuals.end()); + utils::Histogram histo(0.0, maxRange, 100); + histo.Add(residuals.begin(), residuals.end()); + + svg::svgHisto svg_Histo; + svg_Histo.draw(histo.GetHist(), + std::pair(0.f, maxRange), + (fs::path(htmlFilename).parent_path() / std::string("residuals_histogram.svg")).string(), + 600, + 200); + + os.str(""); + os << sNewLine << "Residuals histogram" << sNewLine; + os << "\n"; + htmlDocStream.pushInfo(os.str()); + } } - } - std::ofstream htmlFileStream(htmlFilename); - htmlFileStream << htmlDocStream.getDoc(); - const bool bOk = !htmlFileStream.bad(); - return bOk; + std::ofstream htmlFileStream(htmlFilename); + htmlFileStream << htmlDocStream.getDoc(); + const bool bOk = !htmlFileStream.bad(); + return bOk; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/generateReport.hpp b/src/aliceVision/sfm/generateReport.hpp index 2e5d351330..a4fe8975a7 100644 --- a/src/aliceVision/sfm/generateReport.hpp +++ b/src/aliceVision/sfm/generateReport.hpp @@ -13,7 +13,7 @@ namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { @@ -25,5 +25,5 @@ namespace sfm { */ bool generateSfMReport(const sfmData::SfMData& sfmData, const std::string& htmlFilename); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp b/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp index a964f33c0c..382b2c1cf0 100644 --- a/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp +++ b/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp @@ -10,11 +10,9 @@ #include #include - namespace aliceVision { namespace sfm { - void retrieveMarkersId(sfmData::SfMData& sfmData) { std::set allMarkerDescTypes; @@ -31,13 +29,12 @@ void retrieveMarkersId(sfmData::SfMData& sfmData) std::set usedDescTypes = sfmData.getLandmarkDescTypes(); std::vector markerDescTypes; - std::set_intersection(allMarkerDescTypes.begin(), allMarkerDescTypes.end(), - usedDescTypes.begin(), usedDescTypes.end(), - std::back_inserter(markerDescTypes)); + std::set_intersection( + allMarkerDescTypes.begin(), allMarkerDescTypes.end(), usedDescTypes.begin(), usedDescTypes.end(), std::back_inserter(markerDescTypes)); std::set markerDescTypes_set(markerDescTypes.begin(), markerDescTypes.end()); - if(markerDescTypes.empty()) + if (markerDescTypes.empty()) return; // load the corresponding view regions @@ -62,7 +59,8 @@ void retrieveMarkersId(sfmData::SfMData& sfmData) const feature::Regions& regions = regionPerView.getRegions(obs->first, landmark.descType); const feature::CCTAG_Regions* cctagRegions = dynamic_cast(®ions); const feature::APRILTAG_Regions* apriltagRegions = dynamic_cast(®ions); - if (cctagRegions) { + if (cctagRegions) + { const auto& d = cctagRegions->Descriptors()[obs->second.id_feat]; for (int i = 0; i < d.size(); ++i) { @@ -73,7 +71,9 @@ void retrieveMarkersId(sfmData::SfMData& sfmData) break; } } - } else if (apriltagRegions) { + } + else if (apriltagRegions) + { const auto& d = apriltagRegions->Descriptors()[obs->second.id_feat]; for (int i = 0; i < d.size(); ++i) { @@ -88,6 +88,5 @@ void retrieveMarkersId(sfmData::SfMData& sfmData) } } - -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp b/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp index 3c729df6ab..77a9de7fe6 100644 --- a/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp +++ b/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp @@ -18,79 +18,61 @@ namespace sfm { void retrieveMarkersId(sfmData::SfMData& sfmData); - /** * @brief Basic Reconstruction Engine. * Process Function handle the reconstruction. */ class ReconstructionEngine { -public: - - /** - * @brief ReconstructionEngine Constructor - * @param[in] sfmData The input SfMData of the scene - * @param[in] outFolder The folder where outputs will be stored - */ - ReconstructionEngine(const sfmData::SfMData& sfmData, const std::string& outFolder) - : _outputFolder(outFolder) - , _sfmData(sfmData) - {} - - virtual ~ReconstructionEngine() {} - - /** - * @brief Reconstruction process - * @return true if the scene is reconstructed - */ - virtual bool process() = 0; - - /** - * @brief Get the scene SfMData - * @return SfMData - */ - inline const sfmData::SfMData& getSfMData() const - { - return _sfmData; - } - - /** - * @brief Get the scene SfMData - * @return SfMData - */ - inline sfmData::SfMData& getSfMData() - { - return _sfmData; - } - - /** - * @brief Colorization of the reconstructed scene - * @return true if ok - */ - inline void colorize() - { - sfmData::colorizeTracks(_sfmData); - } - - void retrieveMarkersId() - { - aliceVision::sfm::retrieveMarkersId(_sfmData); - } - - void initRandomSeed(int seed) - { - _randomNumberGenerator.seed(seed == -1 ? std::random_device()() : seed); - } - -protected: - /// Output folder where outputs will be stored - std::string _outputFolder; - /// Internal SfMData - sfmData::SfMData _sfmData; - //Random engine - std::mt19937 _randomNumberGenerator; + public: + /** + * @brief ReconstructionEngine Constructor + * @param[in] sfmData The input SfMData of the scene + * @param[in] outFolder The folder where outputs will be stored + */ + ReconstructionEngine(const sfmData::SfMData& sfmData, const std::string& outFolder) + : _outputFolder(outFolder), + _sfmData(sfmData) + {} + + virtual ~ReconstructionEngine() {} + + /** + * @brief Reconstruction process + * @return true if the scene is reconstructed + */ + virtual bool process() = 0; + + /** + * @brief Get the scene SfMData + * @return SfMData + */ + inline const sfmData::SfMData& getSfMData() const { return _sfmData; } + + /** + * @brief Get the scene SfMData + * @return SfMData + */ + inline sfmData::SfMData& getSfMData() { return _sfmData; } + + /** + * @brief Colorization of the reconstructed scene + * @return true if ok + */ + inline void colorize() { sfmData::colorizeTracks(_sfmData); } + + void retrieveMarkersId() { aliceVision::sfm::retrieveMarkersId(_sfmData); } + + void initRandomSeed(int seed) { _randomNumberGenerator.seed(seed == -1 ? std::random_device()() : seed); } + + protected: + /// Output folder where outputs will be stored + std::string _outputFolder; + /// Internal SfMData + sfmData::SfMData _sfmData; + // Random engine + std::mt19937 _randomNumberGenerator; }; - -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/RelativePoseInfo.cpp b/src/aliceVision/sfm/pipeline/RelativePoseInfo.cpp index f009e64583..0e0dc180a5 100644 --- a/src/aliceVision/sfm/pipeline/RelativePoseInfo.cpp +++ b/src/aliceVision/sfm/pipeline/RelativePoseInfo.cpp @@ -18,116 +18,113 @@ namespace aliceVision { namespace sfm { -bool estimate_Rt_fromE(const Mat3 & K1, const Mat3 & K2, - const Mat & x1, const Mat & x2, - const Mat3 & E, const std::vector & vec_inliers, - Mat3 & R, Vec3 & t) +bool estimate_Rt_fromE(const Mat3& K1, + const Mat3& K2, + const Mat& x1, + const Mat& x2, + const Mat3& E, + const std::vector& vec_inliers, + Mat3& R, + Vec3& t) { - // Accumulator to find the best solution - std::vector f(4, 0); + // Accumulator to find the best solution + std::vector f(4, 0); - std::vector Rs; // Rotation matrix. - std::vector ts; // Translation matrix. + std::vector Rs; // Rotation matrix. + std::vector ts; // Translation matrix. - // Recover best rotation and translation from E. - motionFromEssential(E, &Rs, &ts); + // Recover best rotation and translation from E. + motionFromEssential(E, &Rs, &ts); - //-> Test the 4 solutions will all the points - assert(Rs.size() == 4); - assert(ts.size() == 4); + //-> Test the 4 solutions will all the points + assert(Rs.size() == 4); + assert(ts.size() == 4); - Mat34 P1, P2; - Mat3 R1 = Mat3::Identity(); - Vec3 t1 = Vec3::Zero(); - P_from_KRt(K1, R1, t1, P1); + Mat34 P1, P2; + Mat3 R1 = Mat3::Identity(); + Vec3 t1 = Vec3::Zero(); + P_from_KRt(K1, R1, t1, P1); - for (unsigned int i = 0; i < 4; ++i) - { - const Mat3 &R2 = Rs[i]; - const Vec3 &t2 = ts[i]; - P_from_KRt(K2, R2, t2, P2); - Vec3 X; - - for (size_t k = 0; k < vec_inliers.size(); ++k) + for (unsigned int i = 0; i < 4; ++i) + { + const Mat3& R2 = Rs[i]; + const Vec3& t2 = ts[i]; + P_from_KRt(K2, R2, t2, P2); + Vec3 X; + + for (size_t k = 0; k < vec_inliers.size(); ++k) + { + const Vec2 &x1_ = x1.col(vec_inliers[k]), &x2_ = x2.col(vec_inliers[k]); + multiview::TriangulateDLT(P1, x1_, P2, x2_, X); + // Test if point is front to the two cameras. + if (Depth(R1, t1, X) > 0 && Depth(R2, t2, X) > 0) + { + ++f[i]; + } + } + } + // Check the solution: + const std::vector::iterator iter = std::max_element(f.begin(), f.end()); + if (*iter == 0) { - const Vec2 - & x1_ = x1.col(vec_inliers[k]), - & x2_ = x2.col(vec_inliers[k]); - multiview::TriangulateDLT(P1, x1_, P2, x2_, X); - // Test if point is front to the two cameras. - if (Depth(R1, t1, X) > 0 && Depth(R2, t2, X) > 0) - { - ++f[i]; - } + // There is no right solution with points in front of the cameras + return false; } - } - // Check the solution: - const std::vector::iterator iter = std::max_element(f.begin(), f.end()); - if (*iter == 0) - { - // There is no right solution with points in front of the cameras - return false; - } - - const size_t index = std::distance(f.begin(), iter); - R = Rs[index]; - t = ts[index]; - - return true; + + const size_t index = std::distance(f.begin(), iter); + R = Rs[index]; + t = ts[index]; + + return true; } -bool robustRelativePose(const Mat3& K1, const Mat3& K2, - const Mat& x1, const Mat& x2, - std::mt19937 &randomNumberGenerator, - RelativePoseInfo & relativePose_info, - const std::pair & size_ima1, - const std::pair & size_ima2, +bool robustRelativePose(const Mat3& K1, + const Mat3& K2, + const Mat& x1, + const Mat& x2, + std::mt19937& randomNumberGenerator, + RelativePoseInfo& relativePose_info, + const std::pair& size_ima1, + const std::pair& size_ima2, const size_t max_iteration_count) { - // use the 5 point solver to estimate E - using SolverT = multiview::relativePose::Essential5PSolver; - - // define the kernel - using KernelT = multiview::RelativePoseKernel_K; - - KernelT kernel(x1, size_ima1.first, size_ima1.second, - x2, size_ima2.first, size_ima2.second, K1, K2); - - - robustEstimation::Mat3Model model; - - // robustly estimation of the Essential matrix and its precision - const std::pair acRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, - relativePose_info.vec_inliers, - max_iteration_count, - &model, - relativePose_info.initial_residual_tolerance); - relativePose_info.essential_matrix = model.getMatrix(); - relativePose_info.found_residual_precision = acRansacOut.first; - - if(relativePose_info.vec_inliers.size() < kernel.getMinimumNbRequiredSamples() * ALICEVISION_MINIMUM_SAMPLES_COEF) - { - ALICEVISION_LOG_INFO("robustRelativePose: no sufficient coverage (the model does not support enough samples): " << relativePose_info.vec_inliers.size()); - return false; // no sufficient coverage (the model does not support enough samples) - } - - // estimation of the relative poses - Mat3 R; - Vec3 t; - if (!estimate_Rt_fromE( - K1, K2, x1, x2, - relativePose_info.essential_matrix, relativePose_info.vec_inliers, R, t)) - { - ALICEVISION_LOG_INFO("robustRelativePose: cannot find a valid [R|t] couple that makes the inliers in front of the camera."); - return false; // cannot find a valid [R|t] couple that makes the inliers in front of the camera. - } - - // Store [R|C] for the second camera, since the first camera is [Id|0] - relativePose_info.relativePose = geometry::Pose3(R, -R.transpose() * t); - - return true; -} + // use the 5 point solver to estimate E + using SolverT = multiview::relativePose::Essential5PSolver; + + // define the kernel + using KernelT = multiview::RelativePoseKernel_K; + + KernelT kernel(x1, size_ima1.first, size_ima1.second, x2, size_ima2.first, size_ima2.second, K1, K2); + + robustEstimation::Mat3Model model; -} // namespace sfm -} // namespace aliceVision + // robustly estimation of the Essential matrix and its precision + const std::pair acRansacOut = robustEstimation::ACRANSAC( + kernel, randomNumberGenerator, relativePose_info.vec_inliers, max_iteration_count, &model, relativePose_info.initial_residual_tolerance); + relativePose_info.essential_matrix = model.getMatrix(); + relativePose_info.found_residual_precision = acRansacOut.first; + + if (relativePose_info.vec_inliers.size() < kernel.getMinimumNbRequiredSamples() * ALICEVISION_MINIMUM_SAMPLES_COEF) + { + ALICEVISION_LOG_INFO( + "robustRelativePose: no sufficient coverage (the model does not support enough samples): " << relativePose_info.vec_inliers.size()); + return false; // no sufficient coverage (the model does not support enough samples) + } + + // estimation of the relative poses + Mat3 R; + Vec3 t; + if (!estimate_Rt_fromE(K1, K2, x1, x2, relativePose_info.essential_matrix, relativePose_info.vec_inliers, R, t)) + { + ALICEVISION_LOG_INFO("robustRelativePose: cannot find a valid [R|t] couple that makes the inliers in front of the camera."); + return false; // cannot find a valid [R|t] couple that makes the inliers in front of the camera. + } + + // Store [R|C] for the second camera, since the first camera is [Id|0] + relativePose_info.relativePose = geometry::Pose3(R, -R.transpose() * t); + + return true; +} +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/RelativePoseInfo.hpp b/src/aliceVision/sfm/pipeline/RelativePoseInfo.hpp index 074cc76ea4..c3cdef3e6f 100644 --- a/src/aliceVision/sfm/pipeline/RelativePoseInfo.hpp +++ b/src/aliceVision/sfm/pipeline/RelativePoseInfo.hpp @@ -17,38 +17,39 @@ namespace aliceVision { namespace sfm { /** -* @brief Estimate the best possible Rotation/Translation from E. -* Four are possible, keep the one with most of the point in front. -* -* @param[in] K1 camera 1 intrinsics -* @param[in] K2 camera 2 intrinsics -* @param[in] x1 camera 1 image points -* @param[in] x2 camera 2 image points -* @param[in] E essential matrix -* @param[in] vec_inliers inliers indices -* @param[out] R estimated rotation -* @param[out] t estimated translation -*/ -bool estimate_Rt_fromE -( - const Mat3 & K1, const Mat3 & K2, - const Mat & x1, const Mat & x2, - const Mat3 & E, const std::vector & vec_inliers, - Mat3 & R, Vec3 & t -); + * @brief Estimate the best possible Rotation/Translation from E. + * Four are possible, keep the one with most of the point in front. + * + * @param[in] K1 camera 1 intrinsics + * @param[in] K2 camera 2 intrinsics + * @param[in] x1 camera 1 image points + * @param[in] x2 camera 2 image points + * @param[in] E essential matrix + * @param[in] vec_inliers inliers indices + * @param[out] R estimated rotation + * @param[out] t estimated translation + */ +bool estimate_Rt_fromE(const Mat3& K1, + const Mat3& K2, + const Mat& x1, + const Mat& x2, + const Mat3& E, + const std::vector& vec_inliers, + Mat3& R, + Vec3& t); struct RelativePoseInfo { - Mat3 essential_matrix; - geometry::Pose3 relativePose; - std::vector vec_inliers; - double initial_residual_tolerance; - double found_residual_precision; + Mat3 essential_matrix; + geometry::Pose3 relativePose; + std::vector vec_inliers; + double initial_residual_tolerance; + double found_residual_precision; - RelativePoseInfo() - :initial_residual_tolerance(std::numeric_limits::infinity()), - found_residual_precision(std::numeric_limits::infinity()) - {} + RelativePoseInfo() + : initial_residual_tolerance(std::numeric_limits::infinity()), + found_residual_precision(std::numeric_limits::infinity()) + {} }; /** @@ -65,16 +66,15 @@ struct RelativePoseInfo * @param[in] size_ima2 width, height of image 2 * @param[in] max iteration count */ -bool robustRelativePose -( - const Mat3 & K1, const Mat3 & K2, - const Mat & x1, const Mat & x2, - std::mt19937 &randomNumberGenerator, - RelativePoseInfo & relativePose_info, - const std::pair & size_ima1, - const std::pair & size_ima2, - const size_t max_iteration_count = 4096 -); +bool robustRelativePose(const Mat3& K1, + const Mat3& K2, + const Mat& x1, + const Mat& x2, + std::mt19937& randomNumberGenerator, + RelativePoseInfo& relativePose_info, + const std::pair& size_ima1, + const std::pair& size_ima2, + const size_t max_iteration_count = 4096); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/RigSequence.cpp b/src/aliceVision/sfm/pipeline/RigSequence.cpp index 3f325e9c5b..bdd3f13299 100644 --- a/src/aliceVision/sfm/pipeline/RigSequence.cpp +++ b/src/aliceVision/sfm/pipeline/RigSequence.cpp @@ -18,327 +18,315 @@ using namespace sfmData; IndexT getRigPoseId(IndexT rigId, IndexT frameId) { - std::size_t rigPoseId = static_cast(rigId); - boost::hash_combine(rigPoseId, static_cast(frameId)); + std::size_t rigPoseId = static_cast(rigId); + boost::hash_combine(rigPoseId, static_cast(frameId)); - return static_cast(rigPoseId); + return static_cast(rigPoseId); } double computeCameraScore(const SfMData& sfmData, const track::TracksPerView& tracksPerView, IndexT viewId) { - std::set viewLandmarks; - { - // A. Compute 2D/3D matches - // A1. list tracks ids used by the view - const aliceVision::track::TrackIdSet& tracksIds = tracksPerView.at(static_cast(viewId)); - - // A2. intersects the track list with the reconstructed - std::set reconstructedTrackId; - std::transform(sfmData.getLandmarks().begin(), sfmData.getLandmarks().end(), - std::inserter(reconstructedTrackId, reconstructedTrackId.begin()), - stl::RetrieveKey()); - - // Get the ids of the already reconstructed tracks - std::set_intersection(tracksIds.begin(), tracksIds.end(), - reconstructedTrackId.begin(), - reconstructedTrackId.end(), - std::inserter(viewLandmarks, viewLandmarks.begin())); - - if(viewLandmarks.empty()) + std::set viewLandmarks; { - // No match. The image has no connection with already reconstructed points. - ALICEVISION_LOG_DEBUG("computeCameraScore: no reconstructed points"); - return 0.0; + // A. Compute 2D/3D matches + // A1. list tracks ids used by the view + const aliceVision::track::TrackIdSet& tracksIds = tracksPerView.at(static_cast(viewId)); + + // A2. intersects the track list with the reconstructed + std::set reconstructedTrackId; + std::transform(sfmData.getLandmarks().begin(), + sfmData.getLandmarks().end(), + std::inserter(reconstructedTrackId, reconstructedTrackId.begin()), + stl::RetrieveKey()); + + // Get the ids of the already reconstructed tracks + std::set_intersection(tracksIds.begin(), + tracksIds.end(), + reconstructedTrackId.begin(), + reconstructedTrackId.end(), + std::inserter(viewLandmarks, viewLandmarks.begin())); + + if (viewLandmarks.empty()) + { + // No match. The image has no connection with already reconstructed points. + ALICEVISION_LOG_DEBUG("computeCameraScore: no reconstructed points"); + return 0.0; + } } - } - const sfmData::View& view = sfmData.getView(viewId); - const geometry::Pose3 pose = sfmData.getPose(view).getTransform(); - const camera::IntrinsicBase * intrinsic = sfmData.getIntrinsics().at(view.getIntrinsicId()).get(); + const sfmData::View& view = sfmData.getView(viewId); + const geometry::Pose3 pose = sfmData.getPose(view).getTransform(); + const camera::IntrinsicBase* intrinsic = sfmData.getIntrinsics().at(view.getIntrinsicId()).get(); - double score = 0.0; - - for(auto landmarkId: viewLandmarks) - { - const Landmark& landmark = sfmData.getLandmarks().at(static_cast(landmarkId)); + double score = 0.0; + for (auto landmarkId : viewLandmarks) + { + const Landmark& landmark = sfmData.getLandmarks().at(static_cast(landmarkId)); - sfmData::Observations::const_iterator itObs = landmark.observations.find(viewId); + sfmData::Observations::const_iterator itObs = landmark.observations.find(viewId); - if(itObs != landmark.observations.end()) - { - const Vec2 residual = intrinsic->residual(pose, landmark.X.homogeneous(), itObs->second.x); - score += std::min(1.0 / residual.norm(), 4.0); + if (itObs != landmark.observations.end()) + { + const Vec2 residual = intrinsic->residual(pose, landmark.X.homogeneous(), itObs->second.x); + score += std::min(1.0 / residual.norm(), 4.0); + } } - } - return score; + return score; } - void RigSequence::init(const track::TracksPerView& tracksPerView) { - for(const auto& viewPair : _sfmData.getViews()) - { - const View& view = *(viewPair.second); - - if(view.isPartOfRig() && view.getRigId() == _rigId) + for (const auto& viewPair : _sfmData.getViews()) { - double score = 0.0; + const View& view = *(viewPair.second); + + if (view.isPartOfRig() && view.getRigId() == _rigId) + { + double score = 0.0; - // compute pose score, sum of inverse reprojection errors - if(_sfmData.isPoseAndIntrinsicDefined(view.getViewId())) - { - score = computeCameraScore(_sfmData, tracksPerView, view.getViewId()); + // compute pose score, sum of inverse reprojection errors + if (_sfmData.isPoseAndIntrinsicDefined(view.getViewId())) + { + score = computeCameraScore(_sfmData, tracksPerView, view.getViewId()); - // add one to the number of poses for this rig relative sub-pose - _rigInfoPerSubPose[view.getSubPoseId()].nbPose++; - } + // add one to the number of poses for this rig relative sub-pose + _rigInfoPerSubPose[view.getSubPoseId()].nbPose++; + } - // add the frame score - _rigInfoPerFrame[view.getFrameId()][view.getSubPoseId()] = {view.getViewId(), view.isPoseIndependant(), score}; + // add the frame score + _rigInfoPerFrame[view.getFrameId()][view.getSubPoseId()] = {view.getViewId(), view.isPoseIndependant(), score}; + } } - } - - // check if sub-pose are initialized - for(auto& subPoseInfoPair : _rigInfoPerSubPose) - { - const IndexT subPoseId = subPoseInfoPair.first; - SubPoseInfo& subPoseInfo = subPoseInfoPair.second; - - if(_sfmData.getRigs().at(_rigId).getSubPose(subPoseId).status != ERigSubPoseStatus::UNINITIALIZED) - subPoseInfo.isInitialized = true; - } - - ALICEVISION_LOG_INFO("Initialize rig calibration: " - << "\n\t- rig id: " << _rigId - << "\n\t- # detected sub-poses: " << _rigInfoPerSubPose.size() - << "\n\t- # detected frames: " << _rigInfoPerFrame.size()); + + // check if sub-pose are initialized + for (auto& subPoseInfoPair : _rigInfoPerSubPose) + { + const IndexT subPoseId = subPoseInfoPair.first; + SubPoseInfo& subPoseInfo = subPoseInfoPair.second; + + if (_sfmData.getRigs().at(_rigId).getSubPose(subPoseId).status != ERigSubPoseStatus::UNINITIALIZED) + subPoseInfo.isInitialized = true; + } + + ALICEVISION_LOG_INFO("Initialize rig calibration: " + << "\n\t- rig id: " << _rigId << "\n\t- # detected sub-poses: " << _rigInfoPerSubPose.size() + << "\n\t- # detected frames: " << _rigInfoPerFrame.size()); } void RigSequence::updateSfM(std::set& updatedViews) { - const Rig& rig = _sfmData.getRigs().at(_rigId); + const Rig& rig = _sfmData.getRigs().at(_rigId); - if(!rig.isFullyCalibrated()) - { - if(!rig.isInitialized()) + if (!rig.isFullyCalibrated()) { - setupRelativePoses(); - rigResection(updatedViews); + if (!rig.isInitialized()) + { + setupRelativePoses(); + rigResection(updatedViews); + } + setupRelativePoses(); } - setupRelativePoses(); - } - rigResection(updatedViews); + rigResection(updatedViews); } void RigSequence::computeScores() { - const bool rigInitialized = _rig.isInitialized(); + const bool rigInitialized = _rig.isInitialized(); - for(auto& subPoseInfoPair : _rigInfoPerSubPose) - { - const IndexT subPoseId = subPoseInfoPair.first; - SubPoseInfo& subPoseInfo = subPoseInfoPair.second; - - for(const auto& frameInfoPair : _rigInfoPerFrame) + for (auto& subPoseInfoPair : _rigInfoPerSubPose) { - const IndexT frameId = frameInfoPair.first; - const RigFrame& rigFrame = frameInfoPair.second; - - const auto currentSubPoseIt = rigFrame.find(subPoseId); - - // skip current sub-pose - if(currentSubPoseIt == rigFrame.end()) - continue; - - // compute frame score - double frameScore = 0; - for(const auto& otherPoseIt : rigFrame) - { - // if the rig is initialized, score relative to all non-independant cameras of the rig - if(rigInitialized && !otherPoseIt.second.isPoseIndependant) - continue; - - // score relative to other localized cameras of the rig - if(otherPoseIt.first != subPoseId && otherPoseIt.second.score != 0.0) - frameScore += currentSubPoseIt->second.score * otherPoseIt.second.score; - } - - // keep best frame score - if(frameScore > subPoseInfo.maxFrameScore) - { - subPoseInfo.maxFrameId = frameId; - subPoseInfo.maxFrameScore = frameScore; - } - - // compute total sub-pose score - subPoseInfo.totalScore += frameScore; + const IndexT subPoseId = subPoseInfoPair.first; + SubPoseInfo& subPoseInfo = subPoseInfoPair.second; + + for (const auto& frameInfoPair : _rigInfoPerFrame) + { + const IndexT frameId = frameInfoPair.first; + const RigFrame& rigFrame = frameInfoPair.second; + + const auto currentSubPoseIt = rigFrame.find(subPoseId); + + // skip current sub-pose + if (currentSubPoseIt == rigFrame.end()) + continue; + + // compute frame score + double frameScore = 0; + for (const auto& otherPoseIt : rigFrame) + { + // if the rig is initialized, score relative to all non-independant cameras of the rig + if (rigInitialized && !otherPoseIt.second.isPoseIndependant) + continue; + + // score relative to other localized cameras of the rig + if (otherPoseIt.first != subPoseId && otherPoseIt.second.score != 0.0) + frameScore += currentSubPoseIt->second.score * otherPoseIt.second.score; + } + + // keep best frame score + if (frameScore > subPoseInfo.maxFrameScore) + { + subPoseInfo.maxFrameId = frameId; + subPoseInfo.maxFrameScore = frameScore; + } + + // compute total sub-pose score + subPoseInfo.totalScore += frameScore; + } } - } } void RigSequence::setupRelativePoses() { - const bool rigInitialized = _rig.isInitialized(); + const bool rigInitialized = _rig.isInitialized(); - // check if new subposes could be initialized (number of poses > N) - std::vector subPosesToInitialized; - { - for(const auto& subPoseInfoPair : _rigInfoPerSubPose) + // check if new subposes could be initialized (number of poses > N) + std::vector subPosesToInitialized; { - if(subPoseInfoPair.second.nbPose > _params.minNbCamerasForCalibration && - !subPoseInfoPair.second.isInitialized) - subPosesToInitialized.emplace_back(subPoseInfoPair.first); - } + for (const auto& subPoseInfoPair : _rigInfoPerSubPose) + { + if (subPoseInfoPair.second.nbPose > _params.minNbCamerasForCalibration && !subPoseInfoPair.second.isInitialized) + subPosesToInitialized.emplace_back(subPoseInfoPair.first); + } - if(subPosesToInitialized.empty()) - { - ALICEVISION_LOG_DEBUG("Cannot initialized a new sub-poses"); - return; + if (subPosesToInitialized.empty()) + { + ALICEVISION_LOG_DEBUG("Cannot initialized a new sub-poses"); + return; + } } - } - - // compute score for each camera (rig sub-pose) at each frame - computeScores(); - - // if the rig is uninitialized, keep only the best camera - if(!rigInitialized && subPosesToInitialized.size() > 1) - { - IndexT bestSubPoseId = _rigInfoPerSubPose.begin()->first; - SubPoseInfo& bestSubPoseInfo = _rigInfoPerSubPose.begin()->second; - - ALICEVISION_LOG_DEBUG("First sub-pose chosen:" - << "\n\t- rig id: " << _rigId - << "\n\t- sub-pose id: " << bestSubPoseId - << "\n\t- nbPoses: " << bestSubPoseInfo.nbPose - << "\n\t- maxFrameId: " << bestSubPoseInfo.maxFrameId - << "\n\t- maxFrameScore: " << bestSubPoseInfo.maxFrameScore); - - // select the best camera - for(const auto& subPoseInfoPair :_rigInfoPerSubPose) + + // compute score for each camera (rig sub-pose) at each frame + computeScores(); + + // if the rig is uninitialized, keep only the best camera + if (!rigInitialized && subPosesToInitialized.size() > 1) { - if(subPoseInfoPair.second.totalScore > bestSubPoseInfo.totalScore) - { - bestSubPoseId = subPoseInfoPair.first; - bestSubPoseInfo = subPoseInfoPair.second; - } - } + IndexT bestSubPoseId = _rigInfoPerSubPose.begin()->first; + SubPoseInfo& bestSubPoseInfo = _rigInfoPerSubPose.begin()->second; - // keep only the best camera - subPosesToInitialized.clear(); - subPosesToInitialized.push_back(bestSubPoseId); - } + ALICEVISION_LOG_DEBUG("First sub-pose chosen:" + << "\n\t- rig id: " << _rigId << "\n\t- sub-pose id: " << bestSubPoseId << "\n\t- nbPoses: " << bestSubPoseInfo.nbPose + << "\n\t- maxFrameId: " << bestSubPoseInfo.maxFrameId << "\n\t- maxFrameScore: " << bestSubPoseInfo.maxFrameScore); - // compute and add the new sub-pose(s) - for(const IndexT subPoseId : subPosesToInitialized) - { - ALICEVISION_LOG_DEBUG("Add a new rig sub-pose." - << "\n\t- rig id: " << _rigId - << "\n\t- sub-pose id: " << subPoseId); + // select the best camera + for (const auto& subPoseInfoPair : _rigInfoPerSubPose) + { + if (subPoseInfoPair.second.totalScore > bestSubPoseInfo.totalScore) + { + bestSubPoseId = subPoseInfoPair.first; + bestSubPoseInfo = subPoseInfoPair.second; + } + } - SubPoseInfo& subPoseInfo = _rigInfoPerSubPose.at(subPoseId); + // keep only the best camera + subPosesToInitialized.clear(); + subPosesToInitialized.push_back(bestSubPoseId); + } - const View& bestIndependantView = _sfmData.getView(_rigInfoPerFrame.at(subPoseInfo.maxFrameId).at(subPoseId).viewId); - const geometry::Pose3& bestIndependantPose = _sfmData.getPoses().at(bestIndependantView.getPoseId()).getTransform(); + // compute and add the new sub-pose(s) + for (const IndexT subPoseId : subPosesToInitialized) + { + ALICEVISION_LOG_DEBUG("Add a new rig sub-pose." + << "\n\t- rig id: " << _rigId << "\n\t- sub-pose id: " << subPoseId); - RigSubPose& newSubPose = _rig.getSubPose(subPoseId); - newSubPose.status = ERigSubPoseStatus::ESTIMATED; + SubPoseInfo& subPoseInfo = _rigInfoPerSubPose.at(subPoseId); - subPoseInfo.isInitialized = true; + const View& bestIndependantView = _sfmData.getView(_rigInfoPerFrame.at(subPoseInfo.maxFrameId).at(subPoseId).viewId); + const geometry::Pose3& bestIndependantPose = _sfmData.getPoses().at(bestIndependantView.getPoseId()).getTransform(); - if(rigInitialized) - { - const geometry::Pose3& rigPose = _sfmData.getPoses().at(getRigPoseId(_rigId, subPoseInfo.maxFrameId)).getTransform(); - newSubPose.pose = bestIndependantPose * rigPose.inverse(); - } // else pose is Identity - } -} + RigSubPose& newSubPose = _rig.getSubPose(subPoseId); + newSubPose.status = ERigSubPoseStatus::ESTIMATED; -void RigSequence::rigResection(std::set& updatedViews) -{; - for(auto& frameInfoPair : _rigInfoPerFrame) - { - RigFrame& rigFrame = frameInfoPair.second; - const IndexT frameId = frameInfoPair.first; - const IndexT rigPoseId = getRigPoseId(_rigId, frameId); - - // if no rig pose, compute and add it - if(_sfmData.getPoses().find(rigPoseId) == _sfmData.getPoses().end()) - { - // TODO: use opengv resection for more than one valid view - IndexT bestSubPoseId = UndefinedIndexT; - double bestScore = 0.0; - - for(const auto& viewInfoPair: rigFrame) - { - const IndexT subPoseId = viewInfoPair.first; - const ViewInfo& viewInfo = viewInfoPair.second; - const auto itSubPoseInfoPair = _rigInfoPerSubPose.find(subPoseId); - - // Temporary: set the rig pose to the pose of the defined camera with the max score - if(itSubPoseInfoPair != _rigInfoPerSubPose.end() && - itSubPoseInfoPair->second.isInitialized && - viewInfo.score > bestScore) + subPoseInfo.isInitialized = true; + + if (rigInitialized) { - bestSubPoseId = subPoseId; - bestScore = viewInfo.score; - } - } - - if(bestSubPoseId == UndefinedIndexT) - { - ALICEVISION_LOG_TRACE("No sub-pose localized, skip:\n\t- frame id: " << frameId); - continue; - } - - const RigSubPose& subPose = _rig.getSubPose(bestSubPoseId); - const View& view = _sfmData.getView(rigFrame.at(bestSubPoseId).viewId); - const IndexT independantPoseId = view.getPoseId(); - const CameraPose independantPose = _sfmData.getPoses().at(independantPoseId); - const CameraPose rigPose(independantPose.getTransform() * subPose.pose.inverse()); - - ALICEVISION_LOG_DEBUG("Add rig pose:" - << "\n\t- rig pose id: " << rigPoseId - << "\n\t- frame id: " << frameId - << "\n\t- sub-pose id: " << bestSubPoseId); - - _sfmData.getPoses()[rigPoseId] = rigPose; + const geometry::Pose3& rigPose = _sfmData.getPoses().at(getRigPoseId(_rigId, subPoseInfo.maxFrameId)).getTransform(); + newSubPose.pose = bestIndependantPose * rigPose.inverse(); + } // else pose is Identity } +} - // remove independant poses and replace with rig pose - for(auto& viewInfoPair: rigFrame) +void RigSequence::rigResection(std::set& updatedViews) +{ + ; + for (auto& frameInfoPair : _rigInfoPerFrame) { - const IndexT subPoseId = viewInfoPair.first; - ViewInfo& viewInfo = viewInfoPair.second; - const auto itSubPoseInfoPair = _rigInfoPerSubPose.find(subPoseId); - - if(!viewInfo.isPoseIndependant || - itSubPoseInfoPair == _rigInfoPerSubPose.end() || - !itSubPoseInfoPair->second.isInitialized) - continue; - - View& view = _sfmData.getView(viewInfo.viewId); + RigFrame& rigFrame = frameInfoPair.second; + const IndexT frameId = frameInfoPair.first; + const IndexT rigPoseId = getRigPoseId(_rigId, frameId); - { - Poses::iterator itPose = _sfmData.getPoses().find(view.getPoseId()); - - if(itPose != _sfmData.getPoses().end()) + // if no rig pose, compute and add it + if (_sfmData.getPoses().find(rigPoseId) == _sfmData.getPoses().end()) { - _sfmData.getPoses().erase(itPose); - ALICEVISION_LOG_TRACE("Erase independant pose:" - << "\n\t- pose id: " << view.getPoseId() - << "\n\t- frame id: " << frameId - << "\n\t- sub-pose id: " << subPoseId); + // TODO: use opengv resection for more than one valid view + IndexT bestSubPoseId = UndefinedIndexT; + double bestScore = 0.0; + + for (const auto& viewInfoPair : rigFrame) + { + const IndexT subPoseId = viewInfoPair.first; + const ViewInfo& viewInfo = viewInfoPair.second; + const auto itSubPoseInfoPair = _rigInfoPerSubPose.find(subPoseId); + + // Temporary: set the rig pose to the pose of the defined camera with the max score + if (itSubPoseInfoPair != _rigInfoPerSubPose.end() && itSubPoseInfoPair->second.isInitialized && viewInfo.score > bestScore) + { + bestSubPoseId = subPoseId; + bestScore = viewInfo.score; + } + } + + if (bestSubPoseId == UndefinedIndexT) + { + ALICEVISION_LOG_TRACE("No sub-pose localized, skip:\n\t- frame id: " << frameId); + continue; + } + + const RigSubPose& subPose = _rig.getSubPose(bestSubPoseId); + const View& view = _sfmData.getView(rigFrame.at(bestSubPoseId).viewId); + const IndexT independantPoseId = view.getPoseId(); + const CameraPose independantPose = _sfmData.getPoses().at(independantPoseId); + const CameraPose rigPose(independantPose.getTransform() * subPose.pose.inverse()); + + ALICEVISION_LOG_DEBUG("Add rig pose:" + << "\n\t- rig pose id: " << rigPoseId << "\n\t- frame id: " << frameId << "\n\t- sub-pose id: " << bestSubPoseId); + + _sfmData.getPoses()[rigPoseId] = rigPose; } - } - view.setIndependantPose(false); - view.setPoseId(rigPoseId); - viewInfo.isPoseIndependant = false; - updatedViews.insert(view.getViewId()); + // remove independant poses and replace with rig pose + for (auto& viewInfoPair : rigFrame) + { + const IndexT subPoseId = viewInfoPair.first; + ViewInfo& viewInfo = viewInfoPair.second; + const auto itSubPoseInfoPair = _rigInfoPerSubPose.find(subPoseId); + + if (!viewInfo.isPoseIndependant || itSubPoseInfoPair == _rigInfoPerSubPose.end() || !itSubPoseInfoPair->second.isInitialized) + continue; + + View& view = _sfmData.getView(viewInfo.viewId); + + { + Poses::iterator itPose = _sfmData.getPoses().find(view.getPoseId()); + + if (itPose != _sfmData.getPoses().end()) + { + _sfmData.getPoses().erase(itPose); + ALICEVISION_LOG_TRACE("Erase independant pose:" + << "\n\t- pose id: " << view.getPoseId() << "\n\t- frame id: " << frameId + << "\n\t- sub-pose id: " << subPoseId); + } + } + + view.setIndependantPose(false); + view.setPoseId(rigPoseId); + viewInfo.isPoseIndependant = false; + updatedViews.insert(view.getViewId()); + } } - } } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/RigSequence.hpp b/src/aliceVision/sfm/pipeline/RigSequence.hpp index 05ddb090e3..2861c8eb1c 100644 --- a/src/aliceVision/sfm/pipeline/RigSequence.hpp +++ b/src/aliceVision/sfm/pipeline/RigSequence.hpp @@ -12,7 +12,6 @@ namespace aliceVision { namespace sfm { - struct RigParams { bool useRigConstraint = true; @@ -21,64 +20,62 @@ struct RigParams class RigSequence { -public: - - RigSequence(sfmData::SfMData& sfmData, IndexT rigId, RigParams params) - : _sfmData(sfmData) - , _rigId(rigId) - , _rig(sfmData.getRigs().at(rigId)) - , _params(params) - {} - - /** - * @brief RigSequence initialization - * build internal structures - */ - void init(const track::TracksPerView& tracksPerView); - - /** - * @brief Calibrate new possible rigs or update independent poses to rig poses - * @param[in,out] updatedViews add the updated view ids to the list - * @return set of updated views id - */ - void updateSfM(std::set& updatedViews); - -private: - - void computeScores(); - void setupRelativePoses(); - void rigResection(std::set& updatedViews); - - struct ViewInfo - { - IndexT viewId; - bool isPoseIndependant; - double score; - }; - - struct SubPoseInfo - { - std::size_t nbPose = 0; - bool isInitialized = false; - IndexT maxFrameId = UndefinedIndexT; - double maxFrameScore = 0; - double totalScore = 0; - }; - - // - using RigFrame = std::map; - // - using RigInfoPerFrame = std::map; - - IndexT _rigId; - sfmData::Rig& _rig; - sfmData::SfMData& _sfmData; - RigInfoPerFrame _rigInfoPerFrame; - std::map _rigInfoPerSubPose; - RigParams _params; + public: + RigSequence(sfmData::SfMData& sfmData, IndexT rigId, RigParams params) + : _sfmData(sfmData), + _rigId(rigId), + _rig(sfmData.getRigs().at(rigId)), + _params(params) + {} + + /** + * @brief RigSequence initialization + * build internal structures + */ + void init(const track::TracksPerView& tracksPerView); + + /** + * @brief Calibrate new possible rigs or update independent poses to rig poses + * @param[in,out] updatedViews add the updated view ids to the list + * @return set of updated views id + */ + void updateSfM(std::set& updatedViews); + + private: + void computeScores(); + void setupRelativePoses(); + void rigResection(std::set& updatedViews); + + struct ViewInfo + { + IndexT viewId; + bool isPoseIndependant; + double score; + }; + + struct SubPoseInfo + { + std::size_t nbPose = 0; + bool isInitialized = false; + IndexT maxFrameId = UndefinedIndexT; + double maxFrameScore = 0; + double totalScore = 0; + }; + + // + using RigFrame = std::map; + // + using RigInfoPerFrame = std::map; + + IndexT _rigId; + sfmData::Rig& _rig; + sfmData::SfMData& _sfmData; + RigInfoPerFrame _rigInfoPerFrame; + std::map _rigInfoPerSubPose; + RigParams _params; }; IndexT getRigPoseId(IndexT rigId, IndexT frameId); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp b/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp index c7b278d1bb..36f219798a 100644 --- a/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp +++ b/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp @@ -18,251 +18,235 @@ namespace sfm { using namespace aliceVision::rotationAveraging; -PairSet GlobalSfMRotationAveragingSolver::GetUsedPairs() const -{ - return used_pairs; -} +PairSet GlobalSfMRotationAveragingSolver::GetUsedPairs() const { return used_pairs; } -bool GlobalSfMRotationAveragingSolver::Run( - ERotationAveragingMethod eRotationAveragingMethod, - ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod, - const RelativeRotations& relativeRot_In, - const double max_angular_error, - HashMap& map_globalR -) const +bool GlobalSfMRotationAveragingSolver::Run(ERotationAveragingMethod eRotationAveragingMethod, + ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod, + const RelativeRotations& relativeRot_In, + const double max_angular_error, + HashMap& map_globalR) const { - RelativeRotations relativeRotations = relativeRot_In; - // We work on a copy, since inference can remove some relative motions + RelativeRotations relativeRotations = relativeRot_In; + // We work on a copy, since inference can remove some relative motions - ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: A) relativeRotations.size(): " << relativeRotations.size()); + ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: A) relativeRotations.size(): " << relativeRotations.size()); - //-> Test there is only one graph and at least 3 camera? - switch(eRelativeRotationInferenceMethod) - { - case(TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR): + //-> Test there is only one graph and at least 3 camera? + switch (eRelativeRotationInferenceMethod) { - //------------------- - // Triplet inference (test over the composition error) - //------------------- - PairSet pairs = getPairs(relativeRotations); - ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: pairs.size(): " << pairs.size()); - - std::vector< graph::Triplet > vec_triplets = graph::tripletListing(pairs); - ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: vec_triplets.size(): " << vec_triplets.size()); - - //-- Rejection triplet that are 'not' identity rotation (error to identity > max_angular_error) - TripletRotationRejection(max_angular_error, vec_triplets, relativeRotations); - - pairs = getPairs(relativeRotations); - const std::set set_remainingIds = graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs); - if(set_remainingIds.empty()) - return false; - KeepOnlyReferencedElement(set_remainingIds, relativeRotations); - break; + case (TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR): + { + //------------------- + // Triplet inference (test over the composition error) + //------------------- + PairSet pairs = getPairs(relativeRotations); + ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: pairs.size(): " << pairs.size()); + + std::vector vec_triplets = graph::tripletListing(pairs); + ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: vec_triplets.size(): " << vec_triplets.size()); + + //-- Rejection triplet that are 'not' identity rotation (error to identity > max_angular_error) + TripletRotationRejection(max_angular_error, vec_triplets, relativeRotations); + + pairs = getPairs(relativeRotations); + const std::set set_remainingIds = graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs); + if (set_remainingIds.empty()) + return false; + KeepOnlyReferencedElement(set_remainingIds, relativeRotations); + break; + } + default: + ALICEVISION_LOG_WARNING("Unknown relative rotation inference method: " << (int)eRelativeRotationInferenceMethod); } - default: - ALICEVISION_LOG_WARNING( - "Unknown relative rotation inference method: " << (int) eRelativeRotationInferenceMethod); - } - - // Compute contiguous index (mapping between sparse index and contiguous index) - // from ranging in [min(Id), max(Id)] to [0, nbCam] - - const PairSet pairs = getPairs(relativeRotations); - HashMap _reindexForward, _reindexBackward; - reindex(pairs, _reindexForward, _reindexBackward); - - for(RelativeRotations::iterator iter = relativeRotations.begin(); iter != relativeRotations.end(); ++iter) - { - RelativeRotation & rel = *iter; - rel.i = _reindexForward[rel.i]; - rel.j = _reindexForward[rel.j]; - } - - //- B. solve global rotation computation - bool bSuccess = false; - std::vector vec_globalR(_reindexForward.size()); - - ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: B) vec_globalR.size(): " << vec_globalR.size()); - - switch(eRotationAveragingMethod) - { - case ROTATION_AVERAGING_L2: + + // Compute contiguous index (mapping between sparse index and contiguous index) + // from ranging in [min(Id), max(Id)] to [0, nbCam] + + const PairSet pairs = getPairs(relativeRotations); + HashMap _reindexForward, _reindexBackward; + reindex(pairs, _reindexForward, _reindexBackward); + + for (RelativeRotations::iterator iter = relativeRotations.begin(); iter != relativeRotations.end(); ++iter) { - //- Solve the global rotation estimation problem: - bSuccess = rotationAveraging::l2::L2RotationAveraging( - _reindexForward.size(), - relativeRotations, - vec_globalR); - - ALICEVISION_LOG_DEBUG("rotationAveraging::l2::L2RotationAveraging: success: " << bSuccess); - //- Non linear refinement of the global rotations - if (bSuccess) - { - bSuccess = rotationAveraging::l2::L2RotationAveraging_Refine( - relativeRotations, - vec_globalR); - ALICEVISION_LOG_DEBUG("rotationAveraging::l2::L2RotationAveraging_Refine: success: " << bSuccess); - } - - // save kept pairs (restore original pose indices using the backward reindexing) - for(RelativeRotations::iterator iter = relativeRotations.begin(); iter != relativeRotations.end(); ++iter) - { - RelativeRotation & rel = *iter; - rel.i = _reindexBackward[rel.i]; - rel.j = _reindexBackward[rel.j]; - } - used_pairs = getPairs(relativeRotations); + RelativeRotation& rel = *iter; + rel.i = _reindexForward[rel.i]; + rel.j = _reindexForward[rel.j]; } - break; - case ROTATION_AVERAGING_L1: - { - using namespace aliceVision::rotationAveraging::l1; - //- Solve the global rotation estimation problem: - const size_t nMainViewID = 0; //arbitrary choice - std::vector vec_inliers; - bSuccess = rotationAveraging::l1::GlobalRotationsRobust( - relativeRotations, vec_globalR, nMainViewID, 0.0f, &vec_inliers); + //- B. solve global rotation computation + bool bSuccess = false; + std::vector vec_globalR(_reindexForward.size()); - ALICEVISION_LOG_DEBUG("rotationAveraging::l1::GlobalRotationsRobust: success: " << bSuccess); - ALICEVISION_LOG_DEBUG("inliers: " << vec_inliers); + ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver: B) vec_globalR.size(): " << vec_globalR.size()); - // save kept pairs (restore original pose indices using the backward reindexing) - for (size_t i = 0; i < vec_inliers.size(); ++i) - { - if (vec_inliers[i]) + switch (eRotationAveragingMethod) + { + case ROTATION_AVERAGING_L2: { - used_pairs.insert( - Pair(_reindexBackward[relativeRotations[i].i], - _reindexBackward[relativeRotations[i].j])); + //- Solve the global rotation estimation problem: + bSuccess = rotationAveraging::l2::L2RotationAveraging(_reindexForward.size(), relativeRotations, vec_globalR); + + ALICEVISION_LOG_DEBUG("rotationAveraging::l2::L2RotationAveraging: success: " << bSuccess); + //- Non linear refinement of the global rotations + if (bSuccess) + { + bSuccess = rotationAveraging::l2::L2RotationAveraging_Refine(relativeRotations, vec_globalR); + ALICEVISION_LOG_DEBUG("rotationAveraging::l2::L2RotationAveraging_Refine: success: " << bSuccess); + } + + // save kept pairs (restore original pose indices using the backward reindexing) + for (RelativeRotations::iterator iter = relativeRotations.begin(); iter != relativeRotations.end(); ++iter) + { + RelativeRotation& rel = *iter; + rel.i = _reindexBackward[rel.i]; + rel.j = _reindexBackward[rel.j]; + } + used_pairs = getPairs(relativeRotations); } - } + break; + case ROTATION_AVERAGING_L1: + { + using namespace aliceVision::rotationAveraging::l1; + + //- Solve the global rotation estimation problem: + const size_t nMainViewID = 0; // arbitrary choice + std::vector vec_inliers; + bSuccess = rotationAveraging::l1::GlobalRotationsRobust(relativeRotations, vec_globalR, nMainViewID, 0.0f, &vec_inliers); + + ALICEVISION_LOG_DEBUG("rotationAveraging::l1::GlobalRotationsRobust: success: " << bSuccess); + ALICEVISION_LOG_DEBUG("inliers: " << vec_inliers); + + // save kept pairs (restore original pose indices using the backward reindexing) + for (size_t i = 0; i < vec_inliers.size(); ++i) + { + if (vec_inliers[i]) + { + used_pairs.insert(Pair(_reindexBackward[relativeRotations[i].i], _reindexBackward[relativeRotations[i].j])); + } + } + } + break; + default: + ALICEVISION_LOG_DEBUG("Unknown rotation averaging method: " << (int)eRotationAveragingMethod); } - break; - default: - ALICEVISION_LOG_DEBUG( - "Unknown rotation averaging method: " << (int) eRotationAveragingMethod); - } - - if (bSuccess) - { - //-- Setup the averaged rotations - for (size_t i = 0; i < vec_globalR.size(); ++i) { - map_globalR[_reindexBackward[i]] = vec_globalR[i]; + + if (bSuccess) + { + //-- Setup the averaged rotations + for (size_t i = 0; i < vec_globalR.size(); ++i) + { + map_globalR[_reindexBackward[i]] = vec_globalR[i]; + } + } + else + { + ALICEVISION_LOG_WARNING("Global rotation solving failed."); } - } - else{ - ALICEVISION_LOG_WARNING("Global rotation solving failed."); - } - return bSuccess; + return bSuccess; } /// Reject edges of the view graph that do not produce triplets with tiny /// angular error once rotation composition have been computed. -void GlobalSfMRotationAveragingSolver::TripletRotationRejection( - const double max_angular_error, - std::vector< graph::Triplet > & vec_triplets, - RelativeRotations & relativeRotations) const +void GlobalSfMRotationAveragingSolver::TripletRotationRejection(const double max_angular_error, + std::vector& vec_triplets, + RelativeRotations& relativeRotations) const { - const size_t edges_start_count = relativeRotations.size(); + const size_t edges_start_count = relativeRotations.size(); + + RelativeRotationsMap map_relatives = getMap(relativeRotations); + RelativeRotationsMap map_relatives_validated; + + //-- + // ROTATION OUTLIERS DETECTION + //-- + + std::vector vec_triplets_validated; + vec_triplets_validated.reserve(vec_triplets.size()); - RelativeRotationsMap map_relatives = getMap(relativeRotations); - RelativeRotationsMap map_relatives_validated; + std::vector vec_errToIdentityPerTriplet; + vec_errToIdentityPerTriplet.reserve(vec_triplets.size()); + // Compute the composition error for each length 3 cycles + for (size_t i = 0; i < vec_triplets.size(); ++i) + { + const graph::Triplet& triplet = vec_triplets[i]; + const IndexT I = triplet.i, J = triplet.j, K = triplet.k; + + ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver::TripletRotationRejection: i: " << i << ", (" << I << ", " << J << ", " << K << ")."); - //-- - // ROTATION OUTLIERS DETECTION - //-- + //-- Find the three relative rotations + const Pair ij(I, J), ji(J, I); + const Mat3 RIJ = (map_relatives.count(ij)) ? map_relatives.at(ij).Rij : Mat3(map_relatives.at(ji).Rij.transpose()); - std::vector< graph::Triplet > vec_triplets_validated; - vec_triplets_validated.reserve(vec_triplets.size()); + const Pair jk(J, K), kj(K, J); + const Mat3 RJK = (map_relatives.count(jk)) ? map_relatives.at(jk).Rij : Mat3(map_relatives.at(kj).Rij.transpose()); - std::vector vec_errToIdentityPerTriplet; - vec_errToIdentityPerTriplet.reserve(vec_triplets.size()); - // Compute the composition error for each length 3 cycles - for (size_t i = 0; i < vec_triplets.size(); ++i) - { - const graph::Triplet & triplet = vec_triplets[i]; - const IndexT I = triplet.i, J = triplet.j , K = triplet.k; + const Pair ki(K, I), ik(I, K); + const Mat3 RKI = (map_relatives.count(ki)) ? map_relatives.at(ki).Rij : Mat3(map_relatives.at(ik).Rij.transpose()); - ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver::TripletRotationRejection: i: " << i << ", (" << I << ", " << J << ", " << K << ")."); + const Mat3 Rot_To_Identity = RIJ * RJK * RKI; // motion composition + const float angularErrorDegree = static_cast(radianToDegree(getRotationMagnitude(Rot_To_Identity))); + vec_errToIdentityPerTriplet.push_back(angularErrorDegree); - //-- Find the three relative rotations - const Pair ij(I,J), ji(J,I); - const Mat3 RIJ = (map_relatives.count(ij)) ? - map_relatives.at(ij).Rij : Mat3(map_relatives.at(ji).Rij.transpose()); + if (angularErrorDegree < max_angular_error) + { + vec_triplets_validated.push_back(triplet); + + if (map_relatives.count(ij)) + map_relatives_validated[ij] = map_relatives.at(ij); + else + map_relatives_validated[ji] = map_relatives.at(ji); + + if (map_relatives.count(jk)) + map_relatives_validated[jk] = map_relatives.at(jk); + else + map_relatives_validated[kj] = map_relatives.at(kj); + + if (map_relatives.count(ki)) + map_relatives_validated[ki] = map_relatives.at(ki); + else + map_relatives_validated[ik] = map_relatives.at(ik); + } + else + { + ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver::TripletRotationRejection: i: " + << i << ", angularErrorDegree: " << angularErrorDegree << ", max_angular_error: " << max_angular_error); + } + } + map_relatives = std::move(map_relatives_validated); - const Pair jk(J,K), kj(K,J); - const Mat3 RJK = (map_relatives.count(jk)) ? - map_relatives.at(jk).Rij : Mat3(map_relatives.at(kj).Rij.transpose()); + // update to keep only useful triplets + relativeRotations.clear(); + relativeRotations.reserve(map_relatives.size()); + std::transform(map_relatives.begin(), map_relatives.end(), std::back_inserter(relativeRotations), stl::RetrieveValue()); + std::transform(map_relatives.begin(), map_relatives.end(), std::inserter(used_pairs, used_pairs.begin()), stl::RetrieveKey()); - const Pair ki(K,I), ik(I,K); - const Mat3 RKI = (map_relatives.count(ki)) ? - map_relatives.at(ki).Rij : Mat3(map_relatives.at(ik).Rij.transpose()); + // Display statistics about rotation triplets error: + ALICEVISION_LOG_DEBUG("Statistics about rotation triplets:"); + ALICEVISION_LOG_DEBUG(BoxStats(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end())); - const Mat3 Rot_To_Identity = RIJ * RJK * RKI; // motion composition - const float angularErrorDegree = static_cast(radianToDegree(getRotationMagnitude(Rot_To_Identity))); - vec_errToIdentityPerTriplet.push_back(angularErrorDegree); + std::sort(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()); - if (angularErrorDegree < max_angular_error) + if (!vec_errToIdentityPerTriplet.empty()) { - vec_triplets_validated.push_back(triplet); - - if (map_relatives.count(ij)) - map_relatives_validated[ij] = map_relatives.at(ij); - else - map_relatives_validated[ji] = map_relatives.at(ji); - - if (map_relatives.count(jk)) - map_relatives_validated[jk] = map_relatives.at(jk); - else - map_relatives_validated[kj] = map_relatives.at(kj); - - if (map_relatives.count(ki)) - map_relatives_validated[ki] = map_relatives.at(ki); - else - map_relatives_validated[ik] = map_relatives.at(ik); + utils::Histogram histo(0.0f, *max_element(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()), 20); + histo.Add(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()); + ALICEVISION_LOG_DEBUG(histo.ToString()); } - else + { - ALICEVISION_LOG_DEBUG("GlobalSfMRotationAveragingSolver::TripletRotationRejection: i: " << i << ", angularErrorDegree: " << angularErrorDegree << ", max_angular_error: " << max_angular_error); + ALICEVISION_LOG_DEBUG("Triplets filtering based on composition error on unit cycles"); + ALICEVISION_LOG_DEBUG("#Triplets before: " << vec_triplets.size() + << "\n" + "#Triplets after: " + << vec_triplets_validated.size()); } - } - map_relatives = std::move(map_relatives_validated); - - // update to keep only useful triplets - relativeRotations.clear(); - relativeRotations.reserve(map_relatives.size()); - std::transform(map_relatives.begin(), map_relatives.end(), std::back_inserter(relativeRotations), stl::RetrieveValue()); - std::transform(map_relatives.begin(), map_relatives.end(), std::inserter(used_pairs, used_pairs.begin()), stl::RetrieveKey()); - - // Display statistics about rotation triplets error: - ALICEVISION_LOG_DEBUG("Statistics about rotation triplets:"); - ALICEVISION_LOG_DEBUG(BoxStats(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end())); - - std::sort(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()); - - if (!vec_errToIdentityPerTriplet.empty()) - { - utils::Histogram histo(0.0f, *max_element(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()), 20); - histo.Add(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()); - ALICEVISION_LOG_DEBUG(histo.ToString()); - } - - { - ALICEVISION_LOG_DEBUG("Triplets filtering based on composition error on unit cycles"); - ALICEVISION_LOG_DEBUG( - "#Triplets before: " << vec_triplets.size() << "\n" - "#Triplets after: " << vec_triplets_validated.size()); - } - - vec_triplets = std::move(vec_triplets_validated); - - const size_t edges_end_count = relativeRotations.size(); - ALICEVISION_LOG_DEBUG("#Edges removed by triplet inference: " << edges_start_count - edges_end_count); -} -} // namespace sfm -} // namespace aliceVision + vec_triplets = std::move(vec_triplets_validated); + + const size_t edges_end_count = relativeRotations.size(); + ALICEVISION_LOG_DEBUG("#Edges removed by triplet inference: " << edges_start_count - edges_end_count); +} +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.hpp b/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.hpp index cfa034e419..a6ba08d9be 100644 --- a/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.hpp +++ b/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.hpp @@ -16,40 +16,39 @@ namespace sfm { enum ERotationAveragingMethod { - ROTATION_AVERAGING_L1 = 1, - ROTATION_AVERAGING_L2 = 2 + ROTATION_AVERAGING_L1 = 1, + ROTATION_AVERAGING_L2 = 2 }; enum ERelativeRotationInferenceMethod { - TRIPLET_ROTATION_INFERENCE_NONE = 0, - TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR = 1 + TRIPLET_ROTATION_INFERENCE_NONE = 0, + TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR = 1 }; inline std::string ERotationAveragingMethod_enumToString(ERotationAveragingMethod eRotationAveragingMethod) { - switch(eRotationAveragingMethod) - { - case ERotationAveragingMethod::ROTATION_AVERAGING_L1: - return "L1_minimization"; - case ERotationAveragingMethod::ROTATION_AVERAGING_L2: - return "L2_minimization"; - } - throw std::out_of_range("Invalid rotation averaging method type"); + switch (eRotationAveragingMethod) + { + case ERotationAveragingMethod::ROTATION_AVERAGING_L1: + return "L1_minimization"; + case ERotationAveragingMethod::ROTATION_AVERAGING_L2: + return "L2_minimization"; + } + throw std::out_of_range("Invalid rotation averaging method type"); } inline ERotationAveragingMethod ERotationAveragingMethod_stringToEnum(const std::string& RotationAveragingMethodName) { - if(RotationAveragingMethodName == "L1_minimization") return ERotationAveragingMethod::ROTATION_AVERAGING_L1; - if(RotationAveragingMethodName == "L2_minimization") return ERotationAveragingMethod::ROTATION_AVERAGING_L2; + if (RotationAveragingMethodName == "L1_minimization") + return ERotationAveragingMethod::ROTATION_AVERAGING_L1; + if (RotationAveragingMethodName == "L2_minimization") + return ERotationAveragingMethod::ROTATION_AVERAGING_L2; - throw std::out_of_range("Invalid rotation averaging method name : '" + RotationAveragingMethodName + "'"); + throw std::out_of_range("Invalid rotation averaging method name : '" + RotationAveragingMethodName + "'"); } -inline std::ostream& operator<<(std::ostream& os, ERotationAveragingMethod e) -{ - return os << ERotationAveragingMethod_enumToString(e); -} +inline std::ostream& operator<<(std::ostream& os, ERotationAveragingMethod e) { return os << ERotationAveragingMethod_enumToString(e); } inline std::istream& operator>>(std::istream& in, ERotationAveragingMethod& rotationType) { @@ -59,8 +58,8 @@ inline std::istream& operator>>(std::istream& in, ERotationAveragingMethod& rota return in; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision #include #include @@ -71,30 +70,30 @@ namespace sfm { class GlobalSfMRotationAveragingSolver { -private: - /// pair that are considered as valid by the rotation averaging solver - mutable PairSet used_pairs; - -public: - bool Run(ERotationAveragingMethod eRotationAveragingMethod, - ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod, - const rotationAveraging::RelativeRotations& relativeRot_In, - const double max_angular_error, - HashMap& map_globalR) const; - - /** - * @brief Reject edges of the view graph that do not produce triplets with tiny - * angular error once rotation composition have been computed. - */ - void TripletRotationRejection(const double max_angular_error, - std::vector& vec_triplets, - rotationAveraging::RelativeRotations& relativeRotations) const; - /** - * @brief Return the pairs validated by the GlobalRotation routine (inference can remove some) - * @return pairs validated by the GlobalRotation routine - */ - PairSet GetUsedPairs() const; + private: + /// pair that are considered as valid by the rotation averaging solver + mutable PairSet used_pairs; + + public: + bool Run(ERotationAveragingMethod eRotationAveragingMethod, + ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod, + const rotationAveraging::RelativeRotations& relativeRot_In, + const double max_angular_error, + HashMap& map_globalR) const; + + /** + * @brief Reject edges of the view graph that do not produce triplets with tiny + * angular error once rotation composition have been computed. + */ + void TripletRotationRejection(const double max_angular_error, + std::vector& vec_triplets, + rotationAveraging::RelativeRotations& relativeRotations) const; + /** + * @brief Return the pairs validated by the GlobalRotation routine (inference can remove some) + * @return pairs validated by the GlobalRotation routine + */ + PairSet GetUsedPairs() const; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.cpp b/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.cpp index 0e34dbf97b..c232367787 100644 --- a/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.cpp +++ b/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.cpp @@ -40,749 +40,711 @@ using namespace aliceVision::sfmData; /// Use features in normalized camera frames bool GlobalSfMTranslationAveragingSolver::Run(ETranslationAveragingMethod eTranslationAveragingMethod, - SfMData& sfmData, - const feature::FeaturesPerView& normalizedFeaturesPerView, - const matching::PairwiseMatches& pairwiseMatches, - const HashMap& map_globalR, - std::mt19937 & randomNumberGenerator, - matching::PairwiseMatches& tripletWise_matches) + SfMData& sfmData, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + const HashMap& map_globalR, + std::mt19937& randomNumberGenerator, + matching::PairwiseMatches& tripletWise_matches) { - // Compute the relative translations and save them to vec_initialRijTijEstimates: - Compute_translations(sfmData, - normalizedFeaturesPerView, - pairwiseMatches, - map_globalR, - randomNumberGenerator, - tripletWise_matches); - - const bool translation = Translation_averaging(eTranslationAveragingMethod, sfmData, map_globalR); - - // Filter matches to keep only them link to view that have valid poses - // (necessary since multiple components exists before translation averaging) - std::set valid_view_ids; - for(const auto & view : sfmData.getViews()) - { - if(sfmData.isPoseAndIntrinsicDefined(view.second.get())) - valid_view_ids.insert(view.first); - } - KeepOnlyReferencedElement(valid_view_ids, tripletWise_matches); - - return translation; + // Compute the relative translations and save them to vec_initialRijTijEstimates: + Compute_translations(sfmData, normalizedFeaturesPerView, pairwiseMatches, map_globalR, randomNumberGenerator, tripletWise_matches); + + const bool translation = Translation_averaging(eTranslationAveragingMethod, sfmData, map_globalR); + + // Filter matches to keep only them link to view that have valid poses + // (necessary since multiple components exists before translation averaging) + std::set valid_view_ids; + for (const auto& view : sfmData.getViews()) + { + if (sfmData.isPoseAndIntrinsicDefined(view.second.get())) + valid_view_ids.insert(view.first); + } + KeepOnlyReferencedElement(valid_view_ids, tripletWise_matches); + + return translation; } bool GlobalSfMTranslationAveragingSolver::Translation_averaging(ETranslationAveragingMethod eTranslationAveragingMethod, - SfMData& sfmData, - const HashMap& map_globalR) + SfMData& sfmData, + const HashMap& map_globalR) { - //------------------- - //-- GLOBAL TRANSLATIONS ESTIMATION from initial triplets t_ij guess - //------------------- - - // Keep the largest Biedge connected component graph of relative translations - PairSet pairs; - std::transform(m_vec_initialRijTijEstimates.begin(), m_vec_initialRijTijEstimates.end(), - std::inserter(pairs, pairs.begin()), stl::RetrieveKey()); - const std::set set_remainingIds = - aliceVision::graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs, "./"); - KeepOnlyReferencedElement(set_remainingIds, m_vec_initialRijTijEstimates); - - { - const std::set index = translationAveraging::getIndexT(m_vec_initialRijTijEstimates); - - const size_t iNview = index.size(); - ALICEVISION_LOG_DEBUG( - "\n-------------------------------\n" - " Global translations computation:\n" - " - Ready to compute " << iNview << " global translations." << "\n" - " from #relative translations: " << m_vec_initialRijTijEstimates.size()); - - if(iNview < 3) - { - // Too tiny image set to perform motion averaging - return false; - } - //-- Update initial estimates from [minId,maxId] to range [0->Ncam] - translationAveraging::RelativeInfoVec vec_initialRijTijEstimates_cpy = m_vec_initialRijTijEstimates; - const PairSet pairs = translationAveraging::getPairs(vec_initialRijTijEstimates_cpy); - HashMap _reindexForward, _reindexBackward; - reindex(pairs, _reindexForward, _reindexBackward); - for(size_t i = 0; i < vec_initialRijTijEstimates_cpy.size(); ++i) - { - aliceVision::translationAveraging::relativeInfo & rel = vec_initialRijTijEstimates_cpy[i]; - rel.first = Pair(_reindexForward[rel.first.first], _reindexForward[rel.first.second]); - } + //------------------- + //-- GLOBAL TRANSLATIONS ESTIMATION from initial triplets t_ij guess + //------------------- - aliceVision::system::Timer timerLP_translation; + // Keep the largest Biedge connected component graph of relative translations + PairSet pairs; + std::transform(m_vec_initialRijTijEstimates.begin(), m_vec_initialRijTijEstimates.end(), std::inserter(pairs, pairs.begin()), stl::RetrieveKey()); + const std::set set_remainingIds = aliceVision::graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs, "./"); + KeepOnlyReferencedElement(set_remainingIds, m_vec_initialRijTijEstimates); - switch(eTranslationAveragingMethod) { - case TRANSLATION_AVERAGING_L1: - { - double gamma = -1.0; - std::vector vec_solution; + const std::set index = translationAveraging::getIndexT(m_vec_initialRijTijEstimates); + + const size_t iNview = index.size(); + ALICEVISION_LOG_DEBUG("\n-------------------------------\n" + " Global translations computation:\n" + " - Ready to compute " + << iNview << " global translations." + << "\n" + " from #relative translations: " + << m_vec_initialRijTijEstimates.size()); + + if (iNview < 3) { - vec_solution.resize(iNview*3 + vec_initialRijTijEstimates_cpy.size()/3 + 1); - using namespace aliceVision::linearProgramming; - #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) - MOSEKSolver solverLP(vec_solution.size()); - #else - OSI_CISolverWrapper solverLP(vec_solution.size()); - #endif - - lInfinityCV::Tifromtij_ConstraintBuilder_OneLambdaPerTrif cstBuilder(vec_initialRijTijEstimates_cpy); - - LPConstraintsSparse constraint; - //-- Setup constraint and solver - cstBuilder.Build(constraint); - solverLP.setup(constraint); - //-- - // Solving - const bool bFeasible = solverLP.solve(); - ALICEVISION_LOG_DEBUG(" Feasibility " << bFeasible); - //-- - if (bFeasible) { - solverLP.getSolution(vec_solution); - gamma = vec_solution[vec_solution.size()-1]; - } - else { - ALICEVISION_LOG_WARNING("Compute global translations: failed"); + // Too tiny image set to perform motion averaging return false; - } + } + //-- Update initial estimates from [minId,maxId] to range [0->Ncam] + translationAveraging::RelativeInfoVec vec_initialRijTijEstimates_cpy = m_vec_initialRijTijEstimates; + const PairSet pairs = translationAveraging::getPairs(vec_initialRijTijEstimates_cpy); + HashMap _reindexForward, _reindexBackward; + reindex(pairs, _reindexForward, _reindexBackward); + for (size_t i = 0; i < vec_initialRijTijEstimates_cpy.size(); ++i) + { + aliceVision::translationAveraging::relativeInfo& rel = vec_initialRijTijEstimates_cpy[i]; + rel.first = Pair(_reindexForward[rel.first.first], _reindexForward[rel.first.second]); } - const double timeLP_translation = timerLP_translation.elapsed(); - //-- Export triplet statistics: + aliceVision::system::Timer timerLP_translation; + + switch (eTranslationAveragingMethod) { + case TRANSLATION_AVERAGING_L1: + { + double gamma = -1.0; + std::vector vec_solution; + { + vec_solution.resize(iNview * 3 + vec_initialRijTijEstimates_cpy.size() / 3 + 1); + using namespace aliceVision::linearProgramming; +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_MOSEK) + MOSEKSolver solverLP(vec_solution.size()); +#else + OSI_CISolverWrapper solverLP(vec_solution.size()); +#endif - std::ostringstream os; - os << "Translation fusion statistics."; - os.str(""); - os << "-------------------------------\n" - << "-- #relative estimates: " << vec_initialRijTijEstimates_cpy.size() - << " converge with gamma: " << gamma << ".\n" - << " timing (s): " << timeLP_translation << ".\n" - << "-------------------------------" << "\n"; - ALICEVISION_LOG_DEBUG(os.str()); - } + lInfinityCV::Tifromtij_ConstraintBuilder_OneLambdaPerTrif cstBuilder(vec_initialRijTijEstimates_cpy); + + LPConstraintsSparse constraint; + //-- Setup constraint and solver + cstBuilder.Build(constraint); + solverLP.setup(constraint); + //-- + // Solving + const bool bFeasible = solverLP.solve(); + ALICEVISION_LOG_DEBUG(" Feasibility " << bFeasible); + //-- + if (bFeasible) + { + solverLP.getSolution(vec_solution); + gamma = vec_solution[vec_solution.size() - 1]; + } + else + { + ALICEVISION_LOG_WARNING("Compute global translations: failed"); + return false; + } + } - ALICEVISION_LOG_DEBUG("Found solution:\n" << vec_solution); + const double timeLP_translation = timerLP_translation.elapsed(); + //-- Export triplet statistics: + { + std::ostringstream os; + os << "Translation fusion statistics."; + os.str(""); + os << "-------------------------------\n" + << "-- #relative estimates: " << vec_initialRijTijEstimates_cpy.size() << " converge with gamma: " << gamma << ".\n" + << " timing (s): " << timeLP_translation << ".\n" + << "-------------------------------" + << "\n"; + ALICEVISION_LOG_DEBUG(os.str()); + } - std::vector vec_camTranslation(iNview*3,0); - std::copy(&vec_solution[0], &vec_solution[iNview*3], &vec_camTranslation[0]); + ALICEVISION_LOG_DEBUG("Found solution:\n" << vec_solution); - std::vector vec_camRelLambdas(&vec_solution[iNview*3], &vec_solution[iNview*3 + vec_initialRijTijEstimates_cpy.size()/3]); + std::vector vec_camTranslation(iNview * 3, 0); + std::copy(&vec_solution[0], &vec_solution[iNview * 3], &vec_camTranslation[0]); - ALICEVISION_LOG_DEBUG("cam position: " << vec_camTranslation); - ALICEVISION_LOG_DEBUG("cam Lambdas: " << vec_camRelLambdas); + std::vector vec_camRelLambdas(&vec_solution[iNview * 3], + &vec_solution[iNview * 3 + vec_initialRijTijEstimates_cpy.size() / 3]); - // Update the view poses according the found camera centers - for(std::size_t i = 0; i < iNview; ++i) - { - const Vec3 t(vec_camTranslation[i*3], vec_camTranslation[i*3+1], vec_camTranslation[i*3+2]); - const IndexT pose_id = _reindexBackward[i]; - const Mat3 & Ri = map_globalR.at(pose_id); - sfmData.setAbsolutePose(pose_id, CameraPose(Pose3(Ri, -Ri.transpose()*t))); - } - } - break; - - case TRANSLATION_AVERAGING_SOFTL1: - { - std::vector vec_translations; - if (!translationAveraging::solve_translations_problem_softl1( - vec_initialRijTijEstimates_cpy, true, iNview, vec_translations)) - { - ALICEVISION_LOG_WARNING("Compute global translations: failed"); - return false; - } + ALICEVISION_LOG_DEBUG("cam position: " << vec_camTranslation); + ALICEVISION_LOG_DEBUG("cam Lambdas: " << vec_camRelLambdas); - // A valid solution was found: - // - Update the view poses according the found camera translations - for (std::size_t i = 0; i < iNview; ++i) - { - const Vec3 & t = vec_translations[i]; - const IndexT pose_id = _reindexBackward[i]; - const Mat3 & Ri = map_globalR.at(pose_id); - sfmData.setAbsolutePose(pose_id, CameraPose(Pose3(Ri, -Ri.transpose()*t))); - } - } - break; - - case TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL: - { - std::vector vec_edges; - vec_edges.reserve(vec_initialRijTijEstimates_cpy.size() * 2); - std::vector vec_poses; - vec_poses.reserve(vec_initialRijTijEstimates_cpy.size() * 3); - std::vector vec_weights; - vec_weights.reserve(vec_initialRijTijEstimates_cpy.size()); - - for(int i=0; i < vec_initialRijTijEstimates_cpy.size(); ++i) - { - const aliceVision::translationAveraging::relativeInfo & rel = vec_initialRijTijEstimates_cpy[i]; - vec_edges.push_back(rel.first.first); - vec_edges.push_back(rel.first.second); - // Since index have been remapped - // (use the backward indexing to retrieve the second global rotation) - const IndexT secondId = _reindexBackward[rel.first.second]; - const View * view = sfmData.getViews().at(secondId).get(); - const Mat3 & Ri = map_globalR.at(view->getPoseId()); - const Vec3 direction = -(Ri.transpose() * rel.second.second.normalized()); - - vec_poses.push_back(direction(0)); - vec_poses.push_back(direction(1)); - vec_poses.push_back(direction(2)); - - vec_weights.push_back(1.0); - } + // Update the view poses according the found camera centers + for (std::size_t i = 0; i < iNview; ++i) + { + const Vec3 t(vec_camTranslation[i * 3], vec_camTranslation[i * 3 + 1], vec_camTranslation[i * 3 + 2]); + const IndexT pose_id = _reindexBackward[i]; + const Mat3& Ri = map_globalR.at(pose_id); + sfmData.setAbsolutePose(pose_id, CameraPose(Pose3(Ri, -Ri.transpose() * t))); + } + } + break; - const double function_tolerance = 1e-7, parameter_tolerance = 1e-8; - const int max_iterations = 500; - - const double loss_width = 0.0; // No loss in order to compare with TRANSLATION_AVERAGING_L1 - - std::vector X(iNview*3, 0.0); - if(!translationAveraging::solve_translations_problem_l2_chordal( - &vec_edges[0], - &vec_poses[0], - &vec_weights[0], - vec_initialRijTijEstimates_cpy.size(), - loss_width, - &X[0], - function_tolerance, - parameter_tolerance, - max_iterations)) { - ALICEVISION_LOG_WARNING("Compute global translations: failed"); - return false; - } + case TRANSLATION_AVERAGING_SOFTL1: + { + std::vector vec_translations; + if (!translationAveraging::solve_translations_problem_softl1(vec_initialRijTijEstimates_cpy, true, iNview, vec_translations)) + { + ALICEVISION_LOG_WARNING("Compute global translations: failed"); + return false; + } - // Update camera center for each view - for(std::size_t i = 0; i < iNview; ++i) - { - const Vec3 C(X[i*3], X[i*3+1], X[i*3+2]); - const IndexT pose_id = _reindexBackward[i]; // undo the reindexing - const Mat3 & Ri = map_globalR.at(pose_id); - sfmData.setAbsolutePose(pose_id, CameraPose(Pose3(Ri, C))); + // A valid solution was found: + // - Update the view poses according the found camera translations + for (std::size_t i = 0; i < iNview; ++i) + { + const Vec3& t = vec_translations[i]; + const IndexT pose_id = _reindexBackward[i]; + const Mat3& Ri = map_globalR.at(pose_id); + sfmData.setAbsolutePose(pose_id, CameraPose(Pose3(Ri, -Ri.transpose() * t))); + } + } + break; + + case TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL: + { + std::vector vec_edges; + vec_edges.reserve(vec_initialRijTijEstimates_cpy.size() * 2); + std::vector vec_poses; + vec_poses.reserve(vec_initialRijTijEstimates_cpy.size() * 3); + std::vector vec_weights; + vec_weights.reserve(vec_initialRijTijEstimates_cpy.size()); + + for (int i = 0; i < vec_initialRijTijEstimates_cpy.size(); ++i) + { + const aliceVision::translationAveraging::relativeInfo& rel = vec_initialRijTijEstimates_cpy[i]; + vec_edges.push_back(rel.first.first); + vec_edges.push_back(rel.first.second); + // Since index have been remapped + // (use the backward indexing to retrieve the second global rotation) + const IndexT secondId = _reindexBackward[rel.first.second]; + const View* view = sfmData.getViews().at(secondId).get(); + const Mat3& Ri = map_globalR.at(view->getPoseId()); + const Vec3 direction = -(Ri.transpose() * rel.second.second.normalized()); + + vec_poses.push_back(direction(0)); + vec_poses.push_back(direction(1)); + vec_poses.push_back(direction(2)); + + vec_weights.push_back(1.0); + } + + const double function_tolerance = 1e-7, parameter_tolerance = 1e-8; + const int max_iterations = 500; + + const double loss_width = 0.0; // No loss in order to compare with TRANSLATION_AVERAGING_L1 + + std::vector X(iNview * 3, 0.0); + if (!translationAveraging::solve_translations_problem_l2_chordal(&vec_edges[0], + &vec_poses[0], + &vec_weights[0], + vec_initialRijTijEstimates_cpy.size(), + loss_width, + &X[0], + function_tolerance, + parameter_tolerance, + max_iterations)) + { + ALICEVISION_LOG_WARNING("Compute global translations: failed"); + return false; + } + + // Update camera center for each view + for (std::size_t i = 0; i < iNview; ++i) + { + const Vec3 C(X[i * 3], X[i * 3 + 1], X[i * 3 + 2]); + const IndexT pose_id = _reindexBackward[i]; // undo the reindexing + const Mat3& Ri = map_globalR.at(pose_id); + sfmData.setAbsolutePose(pose_id, CameraPose(Pose3(Ri, C))); + } + } + break; + default: + { + ALICEVISION_LOG_WARNING("Unknown translation averaging method"); + return false; + } } - } - break; - default: - { - ALICEVISION_LOG_WARNING("Unknown translation averaging method"); - return false; - } } - } - return true; + return true; } void GlobalSfMTranslationAveragingSolver::Compute_translations(const SfMData& sfmData, - const feature::FeaturesPerView & normalizedFeaturesPerView, - const matching::PairwiseMatches & pairwiseMatches, - const HashMap & map_globalR, - std::mt19937 & randomNumberGenerator, - matching::PairwiseMatches & tripletWise_matches) + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + const HashMap& map_globalR, + std::mt19937& randomNumberGenerator, + matching::PairwiseMatches& tripletWise_matches) { - ALICEVISION_LOG_DEBUG( - "-------------------------------\n" - " Relative translations computation:\n" - "-------------------------------"); - - // Compute relative translations over the graph of global rotations - // thanks to an edge coverage algorithm - ComputePutativeTranslation_EdgesCoverage( - sfmData, - map_globalR, - normalizedFeaturesPerView, - pairwiseMatches, - randomNumberGenerator, - m_vec_initialRijTijEstimates, - tripletWise_matches); + ALICEVISION_LOG_DEBUG("-------------------------------\n" + " Relative translations computation:\n" + "-------------------------------"); + + // Compute relative translations over the graph of global rotations + // thanks to an edge coverage algorithm + ComputePutativeTranslation_EdgesCoverage( + sfmData, map_globalR, normalizedFeaturesPerView, pairwiseMatches, randomNumberGenerator, m_vec_initialRijTijEstimates, tripletWise_matches); } //-- Perform a trifocal estimation of the graph contained in vec_triplets with an // edge coverage algorithm. Its complexity is sub-linear in term of edges count. -void GlobalSfMTranslationAveragingSolver::ComputePutativeTranslation_EdgesCoverage(const SfMData & sfmData, - const HashMap & map_globalR, - const feature::FeaturesPerView & normalizedFeaturesPerView, - const matching::PairwiseMatches & pairwiseMatches, - std::mt19937 & randomNumberGenerator, - translationAveraging::RelativeInfoVec & vec_initialEstimates, - matching::PairwiseMatches & newpairMatches) +void GlobalSfMTranslationAveragingSolver::ComputePutativeTranslation_EdgesCoverage(const SfMData& sfmData, + const HashMap& map_globalR, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + std::mt19937& randomNumberGenerator, + translationAveraging::RelativeInfoVec& vec_initialEstimates, + matching::PairwiseMatches& newpairMatches) { - aliceVision::system::Timer timerLP_triplet; - - //-- - // Compute the relative translations using triplets of rotations over the rotation graph. - //-- - // - // 1. List plausible triplets over the global rotation pose graph Ids. - // - list all edges that have support in the rotation pose graph - // - PairSet rotation_pose_id_graph; - std::set set_pose_ids; - std::transform(map_globalR.begin(), map_globalR.end(), - std::inserter(set_pose_ids, set_pose_ids.begin()), stl::RetrieveKey()); - // List shared correspondences (pairs) between poses - for (const auto & match_iterator : pairwiseMatches) - { - const Pair pair = match_iterator.first; - const View * v1 = sfmData.getViews().at(pair.first).get(); - const View * v2 = sfmData.getViews().at(pair.second).get(); - - if (// Consider the pair iff it is supported by the rotation graph - (v1->getPoseId() != v2->getPoseId()) - && set_pose_ids.count(v1->getPoseId()) - && set_pose_ids.count(v2->getPoseId())) + aliceVision::system::Timer timerLP_triplet; + + //-- + // Compute the relative translations using triplets of rotations over the rotation graph. + //-- + // + // 1. List plausible triplets over the global rotation pose graph Ids. + // - list all edges that have support in the rotation pose graph + // + PairSet rotation_pose_id_graph; + std::set set_pose_ids; + std::transform(map_globalR.begin(), map_globalR.end(), std::inserter(set_pose_ids, set_pose_ids.begin()), stl::RetrieveKey()); + // List shared correspondences (pairs) between poses + for (const auto& match_iterator : pairwiseMatches) { - rotation_pose_id_graph.insert( - std::make_pair(v1->getPoseId(), v2->getPoseId())); - } - } - // List putative triplets (from global rotations Ids) - const std::vector< graph::Triplet > vec_triplets = - graph::tripletListing(rotation_pose_id_graph); - ALICEVISION_LOG_DEBUG("#Triplets: " << vec_triplets.size()); - - { - // Compute triplets of translations - // Avoid to cover each edge of the graph by using an edge coverage algorithm - // An estimated triplets of translation mark three edges as estimated. - - //-- precompute the number of track per triplet: - HashMap map_tracksPerTriplets; - - #pragma omp parallel for schedule(dynamic) - for (int i = 0; i < (int)vec_triplets.size(); ++i) - { - // List matches that belong to the triplet of poses - const graph::Triplet & triplet = vec_triplets[i]; - matching::PairwiseMatches map_triplet_matches; - const std::set set_triplet_pose_ids = {triplet.i, triplet.j, triplet.k}; - // List shared correspondences (pairs) between the triplet poses - for (const auto & match_iterator : pairwiseMatches) - { const Pair pair = match_iterator.first; - const View * v1 = sfmData.getViews().at(pair.first).get(); - const View * v2 = sfmData.getViews().at(pair.second).get(); - if (// Consider the pair iff it is supported by the triplet graph & 2 different pose id - (v1->getPoseId() != v2->getPoseId()) - && set_triplet_pose_ids.count(v1->getPoseId()) - && set_triplet_pose_ids.count(v2->getPoseId())) + const View* v1 = sfmData.getViews().at(pair.first).get(); + const View* v2 = sfmData.getViews().at(pair.second).get(); + + if ( // Consider the pair iff it is supported by the rotation graph + (v1->getPoseId() != v2->getPoseId()) && set_pose_ids.count(v1->getPoseId()) && set_pose_ids.count(v2->getPoseId())) { - map_triplet_matches.insert( match_iterator ); + rotation_pose_id_graph.insert(std::make_pair(v1->getPoseId(), v2->getPoseId())); } - } - // Compute tracks: - { - aliceVision::track::TracksBuilder tracksBuilder; - tracksBuilder.build(map_triplet_matches); - tracksBuilder.filter(true,3); - - #pragma omp critical - map_tracksPerTriplets[i] = tracksBuilder.nbTracks(); //count the # of matches in the UF tree - } } + // List putative triplets (from global rotations Ids) + const std::vector vec_triplets = graph::tripletListing(rotation_pose_id_graph); + ALICEVISION_LOG_DEBUG("#Triplets: " << vec_triplets.size()); - typedef Pair myEdge; - - //-- Alias (list triplet ids used per pose id edges) - std::map > map_tripletIds_perEdge; - for (size_t i = 0; i < vec_triplets.size(); ++i) { - const graph::Triplet & triplet = vec_triplets[i]; - map_tripletIds_perEdge[std::make_pair(triplet.i, triplet.j)].push_back(i); - map_tripletIds_perEdge[std::make_pair(triplet.i, triplet.k)].push_back(i); - map_tripletIds_perEdge[std::make_pair(triplet.j, triplet.k)].push_back(i); - } + // Compute triplets of translations + // Avoid to cover each edge of the graph by using an edge coverage algorithm + // An estimated triplets of translation mark three edges as estimated. - // Collect edges that are covered by the triplets - std::vector vec_edges; - std::transform(map_tripletIds_perEdge.begin(), map_tripletIds_perEdge.end(), std::back_inserter(vec_edges), stl::RetrieveKey()); + //-- precompute the number of track per triplet: + HashMap map_tracksPerTriplets; - aliceVision::sfm::MutexSet m_mutexSet; +#pragma omp parallel for schedule(dynamic) + for (int i = 0; i < (int)vec_triplets.size(); ++i) + { + // List matches that belong to the triplet of poses + const graph::Triplet& triplet = vec_triplets[i]; + matching::PairwiseMatches map_triplet_matches; + const std::set set_triplet_pose_ids = {triplet.i, triplet.j, triplet.k}; + // List shared correspondences (pairs) between the triplet poses + for (const auto& match_iterator : pairwiseMatches) + { + const Pair pair = match_iterator.first; + const View* v1 = sfmData.getViews().at(pair.first).get(); + const View* v2 = sfmData.getViews().at(pair.second).get(); + if ( // Consider the pair iff it is supported by the triplet graph & 2 different pose id + (v1->getPoseId() != v2->getPoseId()) && set_triplet_pose_ids.count(v1->getPoseId()) && set_triplet_pose_ids.count(v2->getPoseId())) + { + map_triplet_matches.insert(match_iterator); + } + } + // Compute tracks: + { + aliceVision::track::TracksBuilder tracksBuilder; + tracksBuilder.build(map_triplet_matches); + tracksBuilder.filter(true, 3); - auto progressDisplay = system::createConsoleProgressDisplay( - vec_edges.size(), std::cout, - "\nRelative translations computation (edge coverage algorithm)\n"); +#pragma omp critical + map_tracksPerTriplets[i] = tracksBuilder.nbTracks(); // count the # of matches in the UF tree + } + } - // set number of threads, 1 if openMP is not enabled - std::vector initial_estimates(omp_get_max_threads()); - const bool bVerbose = false; + typedef Pair myEdge; - #pragma omp parallel for schedule(dynamic) - for (int k = 0; k < vec_edges.size(); ++k) - { - const myEdge & edge = vec_edges[k]; - ++progressDisplay; - if (m_mutexSet.count(edge) == 0 && m_mutexSet.size() != vec_edges.size()) - { - // Find the triplets that support the given edge - const auto & vec_possibleTripletIndexes = map_tripletIds_perEdge.at(edge); - - //-- Sort the triplets according the number of track they are supporting - std::vector vec_commonTracksPerTriplets; - for (const size_t triplet_index : vec_possibleTripletIndexes) + //-- Alias (list triplet ids used per pose id edges) + std::map> map_tripletIds_perEdge; + for (size_t i = 0; i < vec_triplets.size(); ++i) { - vec_commonTracksPerTriplets.push_back(map_tracksPerTriplets[triplet_index]); + const graph::Triplet& triplet = vec_triplets[i]; + map_tripletIds_perEdge[std::make_pair(triplet.i, triplet.j)].push_back(i); + map_tripletIds_perEdge[std::make_pair(triplet.i, triplet.k)].push_back(i); + map_tripletIds_perEdge[std::make_pair(triplet.j, triplet.k)].push_back(i); } - using namespace stl::indexed_sort; - std::vector< sort_index_packet_descend < size_t, size_t> > packet_vec(vec_commonTracksPerTriplets.size()); - sort_index_helper(packet_vec, &vec_commonTracksPerTriplets[0]); + // Collect edges that are covered by the triplets + std::vector vec_edges; + std::transform(map_tripletIds_perEdge.begin(), map_tripletIds_perEdge.end(), std::back_inserter(vec_edges), stl::RetrieveKey()); - std::vector vec_triplet_ordered(vec_commonTracksPerTriplets.size(), 0); - for (size_t i = 0; i < vec_commonTracksPerTriplets.size(); ++i) { - vec_triplet_ordered[i] = vec_possibleTripletIndexes[packet_vec[i].index]; - } + aliceVision::sfm::MutexSet m_mutexSet; - // Try to solve a triplet of translations for the given edge - for (const size_t triplet_index : vec_triplet_ordered) - { - const graph::Triplet & triplet = vec_triplets[triplet_index]; + auto progressDisplay = + system::createConsoleProgressDisplay(vec_edges.size(), std::cout, "\nRelative translations computation (edge coverage algorithm)\n"); - // If the triplet is already estimated by another thread; try the next one - if (m_mutexSet.count(Pair(triplet.i, triplet.j)) && - m_mutexSet.count(Pair(triplet.i, triplet.k)) && - m_mutexSet.count(Pair(triplet.j, triplet.k))) - { - break; - } - - //-- - // Try to estimate this triplet of translations - //-- - double dPrecision = 4.0; // upper bound of the residual pixel reprojection error - - std::vector vec_tis(3); - std::vector vec_inliers; - aliceVision::track::TracksMap pose_triplet_tracks; - - const std::string sOutDirectory = "./"; - const bool bTriplet_estimation = Estimate_T_triplet( - sfmData, - map_globalR, - normalizedFeaturesPerView, - pairwiseMatches, - triplet, - randomNumberGenerator, - vec_tis, - dPrecision, - vec_inliers, - pose_triplet_tracks, - sOutDirectory); - - if (bTriplet_estimation) - { - // Since new translation edges have been computed, mark their corresponding edges as estimated - m_mutexSet.insert(std::make_pair(triplet.i, triplet.j)); - m_mutexSet.insert(std::make_pair(triplet.j, triplet.k)); - m_mutexSet.insert(std::make_pair(triplet.i, triplet.k)); - - // Compute the triplet relative motions (IJ, JK, IK) + // set number of threads, 1 if openMP is not enabled + std::vector initial_estimates(omp_get_max_threads()); + const bool bVerbose = false; + +#pragma omp parallel for schedule(dynamic) + for (int k = 0; k < vec_edges.size(); ++k) + { + const myEdge& edge = vec_edges[k]; + ++progressDisplay; + if (m_mutexSet.count(edge) == 0 && m_mutexSet.size() != vec_edges.size()) { - const Mat3 - RI = map_globalR.at(triplet.i), - RJ = map_globalR.at(triplet.j), - RK = map_globalR.at(triplet.k); - const Vec3 - ti = vec_tis[0], - tj = vec_tis[1], - tk = vec_tis[2]; - - Mat3 Rij; - Vec3 tij; - relativeCameraMotion(RI, ti, RJ, tj, &Rij, &tij); - - Mat3 Rjk; - Vec3 tjk; - relativeCameraMotion(RJ, tj, RK, tk, &Rjk, &tjk); - - Mat3 Rik; - Vec3 tik; - relativeCameraMotion(RI, ti, RK, tk, &Rik, &tik); - - // set number of threads, 1 if openMP is not enabled - const int thread_id = omp_get_thread_num(); - - initial_estimates[thread_id].emplace_back( - std::make_pair(triplet.i, triplet.j), std::make_pair(Rij, tij)); - initial_estimates[thread_id].emplace_back( - std::make_pair(triplet.j, triplet.k), std::make_pair(Rjk, tjk)); - initial_estimates[thread_id].emplace_back( - std::make_pair(triplet.i, triplet.k), std::make_pair(Rik, tik)); - - //--- ATOMIC - #pragma omp critical - { - // Add inliers as valid pairwise matches - for (std::vector::const_iterator iterInliers = vec_inliers.begin(); - iterInliers != vec_inliers.end(); ++iterInliers) + // Find the triplets that support the given edge + const auto& vec_possibleTripletIndexes = map_tripletIds_perEdge.at(edge); + + //-- Sort the triplets according the number of track they are supporting + std::vector vec_commonTracksPerTriplets; + for (const size_t triplet_index : vec_possibleTripletIndexes) { - using namespace aliceVision::track; - TracksMap::iterator it_tracks = pose_triplet_tracks.begin(); - std::advance(it_tracks, *iterInliers); - const Track & track = it_tracks->second; - - // create pairwise matches from inlier track - for (size_t index_I = 0; index_I < track.featPerView.size() ; ++index_I) - { - Track::FeatureIdPerView::const_iterator iter_I = track.featPerView.begin(); - std::advance(iter_I, index_I); - - // extract camera indexes - const size_t id_view_I = iter_I->first; - const size_t id_feat_I = iter_I->second; - - // loop on subtracks - for (size_t index_J = index_I+1; index_J < track.featPerView.size() ; ++index_J) - { - Track::FeatureIdPerView::const_iterator iter_J = track.featPerView.begin(); - std::advance(iter_J, index_J); + vec_commonTracksPerTriplets.push_back(map_tracksPerTriplets[triplet_index]); + } + + using namespace stl::indexed_sort; + std::vector> packet_vec(vec_commonTracksPerTriplets.size()); + sort_index_helper(packet_vec, &vec_commonTracksPerTriplets[0]); + + std::vector vec_triplet_ordered(vec_commonTracksPerTriplets.size(), 0); + for (size_t i = 0; i < vec_commonTracksPerTriplets.size(); ++i) + { + vec_triplet_ordered[i] = vec_possibleTripletIndexes[packet_vec[i].index]; + } - // extract camera indexes - const size_t id_view_J = iter_J->first; - const size_t id_feat_J = iter_J->second; + // Try to solve a triplet of translations for the given edge + for (const size_t triplet_index : vec_triplet_ordered) + { + const graph::Triplet& triplet = vec_triplets[triplet_index]; - newpairMatches[std::make_pair(id_view_I, id_view_J)][track.descType].emplace_back(id_feat_I, id_feat_J); + // If the triplet is already estimated by another thread; try the next one + if (m_mutexSet.count(Pair(triplet.i, triplet.j)) && m_mutexSet.count(Pair(triplet.i, triplet.k)) && + m_mutexSet.count(Pair(triplet.j, triplet.k))) + { + break; + } + + //-- + // Try to estimate this triplet of translations + //-- + double dPrecision = 4.0; // upper bound of the residual pixel reprojection error + + std::vector vec_tis(3); + std::vector vec_inliers; + aliceVision::track::TracksMap pose_triplet_tracks; + + const std::string sOutDirectory = "./"; + const bool bTriplet_estimation = Estimate_T_triplet(sfmData, + map_globalR, + normalizedFeaturesPerView, + pairwiseMatches, + triplet, + randomNumberGenerator, + vec_tis, + dPrecision, + vec_inliers, + pose_triplet_tracks, + sOutDirectory); + + if (bTriplet_estimation) + { + // Since new translation edges have been computed, mark their corresponding edges as estimated + m_mutexSet.insert(std::make_pair(triplet.i, triplet.j)); + m_mutexSet.insert(std::make_pair(triplet.j, triplet.k)); + m_mutexSet.insert(std::make_pair(triplet.i, triplet.k)); + + // Compute the triplet relative motions (IJ, JK, IK) + { + const Mat3 RI = map_globalR.at(triplet.i), RJ = map_globalR.at(triplet.j), RK = map_globalR.at(triplet.k); + const Vec3 ti = vec_tis[0], tj = vec_tis[1], tk = vec_tis[2]; + + Mat3 Rij; + Vec3 tij; + relativeCameraMotion(RI, ti, RJ, tj, &Rij, &tij); + + Mat3 Rjk; + Vec3 tjk; + relativeCameraMotion(RJ, tj, RK, tk, &Rjk, &tjk); + + Mat3 Rik; + Vec3 tik; + relativeCameraMotion(RI, ti, RK, tk, &Rik, &tik); + + // set number of threads, 1 if openMP is not enabled + const int thread_id = omp_get_thread_num(); + + initial_estimates[thread_id].emplace_back(std::make_pair(triplet.i, triplet.j), std::make_pair(Rij, tij)); + initial_estimates[thread_id].emplace_back(std::make_pair(triplet.j, triplet.k), std::make_pair(Rjk, tjk)); + initial_estimates[thread_id].emplace_back(std::make_pair(triplet.i, triplet.k), std::make_pair(Rik, tik)); + +//--- ATOMIC +#pragma omp critical + { + // Add inliers as valid pairwise matches + for (std::vector::const_iterator iterInliers = vec_inliers.begin(); iterInliers != vec_inliers.end(); + ++iterInliers) + { + using namespace aliceVision::track; + TracksMap::iterator it_tracks = pose_triplet_tracks.begin(); + std::advance(it_tracks, *iterInliers); + const Track& track = it_tracks->second; + + // create pairwise matches from inlier track + for (size_t index_I = 0; index_I < track.featPerView.size(); ++index_I) + { + Track::FeatureIdPerView::const_iterator iter_I = track.featPerView.begin(); + std::advance(iter_I, index_I); + + // extract camera indexes + const size_t id_view_I = iter_I->first; + const size_t id_feat_I = iter_I->second; + + // loop on subtracks + for (size_t index_J = index_I + 1; index_J < track.featPerView.size(); ++index_J) + { + Track::FeatureIdPerView::const_iterator iter_J = track.featPerView.begin(); + std::advance(iter_J, index_J); + + // extract camera indexes + const size_t id_view_J = iter_J->first; + const size_t id_feat_J = iter_J->second; + + newpairMatches[std::make_pair(id_view_I, id_view_J)][track.descType].emplace_back(id_feat_I, id_feat_J); + } + } + } + } + } + // Since a relative translation have been found for the edge: vec_edges[k], + // we break and start to estimate the translations for some other edges. + break; } - } } - } } - // Since a relative translation have been found for the edge: vec_edges[k], - // we break and start to estimate the translations for some other edges. - break; - } } - } + // Merge thread estimates + for (const auto vec : initial_estimates) + { + for (const auto val : vec) + { + vec_initialEstimates.emplace_back(val); + } + } } - // Merge thread estimates - for(const auto vec : initial_estimates) + + const double timeLP_triplet = timerLP_triplet.elapsed(); + ALICEVISION_LOG_DEBUG("TRIPLET COVERAGE TIMING: " << timeLP_triplet << " seconds"); + + ALICEVISION_LOG_DEBUG("-------------------------------\n" + "-- #Relative translations estimates: " + << m_vec_initialRijTijEstimates.size() / 3 << " computed from " << vec_triplets.size() + << " triplets.\n" + "-- resulting in " + << m_vec_initialRijTijEstimates.size() + << " translations estimation.\n" + "-- timing to obtain the relative translations: " + << timeLP_triplet + << " seconds.\n" + "-------------------------------"); +} + +// Robust estimation and refinement of a translation and 3D points of an image triplets. +bool GlobalSfMTranslationAveragingSolver::Estimate_T_triplet(const SfMData& sfmData, + const HashMap& map_globalR, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + const graph::Triplet& poses_id, + std::mt19937& randomNumberGenerator, + std::vector& vec_tis, + double& precision, // UpperBound of the precision found by the AContrario estimator + std::vector& vec_inliers, + aliceVision::track::TracksMap& tracks, + const std::string& outDirectory) const +{ + // List matches that belong to the triplet of poses + matching::PairwiseMatches map_triplet_matches; + const std::set set_pose_ids = {poses_id.i, poses_id.j, poses_id.k}; + // List shared correspondences (pairs) between poses + for (const auto& match_iterator : pairwiseMatches) { - for(const auto val : vec) - { - vec_initialEstimates.emplace_back(val); - } + const Pair& pair = match_iterator.first; + const View* v1 = sfmData.getViews().at(pair.first).get(); + const View* v2 = sfmData.getViews().at(pair.second).get(); + if ( // Consider the pair iff it is supported by the triplet graph & 2 different pose id + (v1->getPoseId() != v2->getPoseId()) && set_pose_ids.count(v1->getPoseId()) && set_pose_ids.count(v2->getPoseId())) + { + map_triplet_matches.insert(match_iterator); + } } - } + aliceVision::track::TracksBuilder tracksBuilder; + tracksBuilder.build(map_triplet_matches); + tracksBuilder.filter(true, 3); + tracksBuilder.exportToSTL(tracks); - const double timeLP_triplet = timerLP_triplet.elapsed(); - ALICEVISION_LOG_DEBUG("TRIPLET COVERAGE TIMING: " << timeLP_triplet << " seconds"); + if (tracks.size() < 30) + return false; - ALICEVISION_LOG_DEBUG( - "-------------------------------\n" - "-- #Relative translations estimates: " << m_vec_initialRijTijEstimates.size()/3 << - " computed from " << vec_triplets.size() << " triplets.\n" - "-- resulting in " << m_vec_initialRijTijEstimates.size() << " translations estimation.\n" - "-- timing to obtain the relative translations: " << timeLP_triplet << " seconds.\n" - "-------------------------------"); -} + // Convert data + Mat x1(2, tracks.size()); + Mat x2(2, tracks.size()); + Mat x3(2, tracks.size()); -// Robust estimation and refinement of a translation and 3D points of an image triplets. -bool GlobalSfMTranslationAveragingSolver::Estimate_T_triplet( - const SfMData& sfmData, - const HashMap& map_globalR, - const feature::FeaturesPerView& normalizedFeaturesPerView, - const matching::PairwiseMatches& pairwiseMatches, - const graph::Triplet& poses_id, - std::mt19937 & randomNumberGenerator, - std::vector& vec_tis, - double& precision, // UpperBound of the precision found by the AContrario estimator - std::vector& vec_inliers, - aliceVision::track::TracksMap& tracks, - const std::string& outDirectory) const -{ - // List matches that belong to the triplet of poses - matching::PairwiseMatches map_triplet_matches; - const std::set set_pose_ids = {poses_id.i, poses_id.j, poses_id.k}; - // List shared correspondences (pairs) between poses - for (const auto & match_iterator : pairwiseMatches) - { - const Pair& pair = match_iterator.first; - const View* v1 = sfmData.getViews().at(pair.first).get(); - const View* v2 = sfmData.getViews().at(pair.second).get(); - if (// Consider the pair iff it is supported by the triplet graph & 2 different pose id - (v1->getPoseId() != v2->getPoseId()) - && set_pose_ids.count(v1->getPoseId()) - && set_pose_ids.count(v2->getPoseId())) + Mat* xxx[3] = {&x1, &x2, &x3}; + std::set intrinsic_ids; + size_t cpt = 0; + for (track::TracksMap::const_iterator iterTracks = tracks.begin(); iterTracks != tracks.end(); ++iterTracks, ++cpt) { - map_triplet_matches.insert( match_iterator ); + const track::Track& track = iterTracks->second; + size_t index = 0; + for (track::Track::FeatureIdPerView::const_iterator iter = track.featPerView.begin(); iter != track.featPerView.end(); ++iter, ++index) + { + const size_t idx_view = iter->first; + const feature::PointFeature pt = normalizedFeaturesPerView.getFeatures(idx_view, track.descType)[iter->second]; + xxx[index]->col(cpt) = pt.coords().cast(); + const View* view = sfmData.getViews().at(idx_view).get(); + intrinsic_ids.insert(view->getIntrinsicId()); + } } - } - - aliceVision::track::TracksBuilder tracksBuilder; - tracksBuilder.build(map_triplet_matches); - tracksBuilder.filter(true,3); - tracksBuilder.exportToSTL(tracks); - - if (tracks.size() < 30) - return false; - - // Convert data - Mat x1(2, tracks.size()); - Mat x2(2, tracks.size()); - Mat x3(2, tracks.size()); - - Mat* xxx[3] = {&x1, &x2, &x3}; - std::set intrinsic_ids; - size_t cpt = 0; - for (track::TracksMap::const_iterator iterTracks = tracks.begin(); - iterTracks != tracks.end(); ++iterTracks, ++cpt) - { - const track::Track & track = iterTracks->second; - size_t index = 0; - for (track::Track::FeatureIdPerView::const_iterator iter = track.featPerView.begin(); iter != track.featPerView.end(); ++iter, ++index) + // Retrieve the smallest focal value, for threshold normalization + double min_focal = std::numeric_limits::max(); + for (const auto& ids : intrinsic_ids) { - const size_t idx_view = iter->first; - const feature::PointFeature pt = normalizedFeaturesPerView.getFeatures(idx_view, track.descType)[iter->second]; - xxx[index]->col(cpt) = pt.coords().cast(); - const View * view = sfmData.getViews().at(idx_view).get(); - intrinsic_ids.insert(view->getIntrinsicId()); + const camera::IntrinsicBase* intrinsicPtr = sfmData.getIntrinsics().at(ids).get(); + const camera::Pinhole* intrinsic = dynamic_cast(intrinsicPtr); + if (intrinsic && intrinsic->isValid()) + { + min_focal = std::min(min_focal, intrinsic->getFocalLengthPixX()); + } } - } - // Retrieve the smallest focal value, for threshold normalization - double min_focal = std::numeric_limits::max(); - for (const auto & ids : intrinsic_ids) - { - const camera::IntrinsicBase * intrinsicPtr = sfmData.getIntrinsics().at(ids).get(); - const camera::Pinhole * intrinsic = dynamic_cast< const camera::Pinhole * > (intrinsicPtr); - if (intrinsic && intrinsic->isValid()) + if (min_focal == std::numeric_limits::max()) { - min_focal = std::min(min_focal, intrinsic->getFocalLengthPixX()); + return false; } - } - if (min_focal == std::numeric_limits::max()) - { - return false; - } - - // Get rotations: - const std::vector vec_global_R_Triplet = - {map_globalR.at(poses_id.i), map_globalR.at(poses_id.j), map_globalR.at(poses_id.k)}; - - using namespace aliceVision::trifocal; - using namespace aliceVision::trifocal::kernel; - - typedef TranslationTripletKernelACRansac< - translations_Triplet_Solver, - translations_Triplet_Solver, - TrifocalTensorModel> KernelType; - const double ThresholdUpperBound = 1.0e-2; // upper bound of the pixel residual (normalized coordinates) - KernelType kernel(x1, x2, x3, vec_global_R_Triplet, Mat3::Identity(), ThresholdUpperBound); - - const size_t ORSA_ITER = 320; // max number of iterations of AC-RANSAC - - TrifocalTensorModel T; - //Note by Fabien Servant : sqrt() as it should have been Squared before ... Trying to keep the same values - const std::pair acStat = - robustEstimation::ACRANSAC(kernel, randomNumberGenerator, vec_inliers, ORSA_ITER, &T, sqrt(precision/min_focal)); - // If robust estimation fails => stop. - if (precision == std::numeric_limits::infinity()) - return false; - - // Update output parameters - precision = acStat.first * min_focal; - - vec_tis.resize(3); - Mat3 K, R; - KRt_from_P(T.P1, K, R, vec_tis[0]); - KRt_from_P(T.P2, K, R, vec_tis[1]); - KRt_from_P(T.P3, K, R, vec_tis[2]); + + // Get rotations: + const std::vector vec_global_R_Triplet = {map_globalR.at(poses_id.i), map_globalR.at(poses_id.j), map_globalR.at(poses_id.k)}; + + using namespace aliceVision::trifocal; + using namespace aliceVision::trifocal::kernel; + + typedef TranslationTripletKernelACRansac KernelType; + const double ThresholdUpperBound = 1.0e-2; // upper bound of the pixel residual (normalized coordinates) + KernelType kernel(x1, x2, x3, vec_global_R_Triplet, Mat3::Identity(), ThresholdUpperBound); + + const size_t ORSA_ITER = 320; // max number of iterations of AC-RANSAC + + TrifocalTensorModel T; + // Note by Fabien Servant : sqrt() as it should have been Squared before ... Trying to keep the same values + const std::pair acStat = + robustEstimation::ACRANSAC(kernel, randomNumberGenerator, vec_inliers, ORSA_ITER, &T, sqrt(precision / min_focal)); + // If robust estimation fails => stop. + if (precision == std::numeric_limits::infinity()) + return false; + + // Update output parameters + precision = acStat.first * min_focal; + + vec_tis.resize(3); + Mat3 K, R; + KRt_from_P(T.P1, K, R, vec_tis[0]); + KRt_from_P(T.P2, K, R, vec_tis[1]); + KRt_from_P(T.P3, K, R, vec_tis[2]); #ifdef DEBUG_TRIPLET - // compute 3D scene base on motion estimation - SfMData tiny_scene; - - // intialize poses (which are now shared by a group of images) - tiny_scene.poses[poses_id.i] = Pose3(vec_global_R_Triplet[0], -vec_global_R_Triplet[0].transpose() * vec_tis[0]); - tiny_scene.poses[poses_id.j] = Pose3(vec_global_R_Triplet[1], -vec_global_R_Triplet[1].transpose() * vec_tis[1]); - tiny_scene.poses[poses_id.k] = Pose3(vec_global_R_Triplet[2], -vec_global_R_Triplet[2].transpose() * vec_tis[2]); - - // insert views used by the relative pose pairs - for (const auto & pairIterator : map_triplet_matches ) - { - // initialize camera indexes - const IndexT I = pairIterator.first.first; - const IndexT J = pairIterator.first.second; - - // add views - tiny_scene.getViews().insert(*sfm_data.getViews().find(I)); - tiny_scene.getViews().insert(*sfm_data.getViews().find(J)); - - // add intrinsics - const View * view_I = sfm_data.getViews().at(I).get(); - const View * view_J = sfm_data.getViews().at(J).get(); - tiny_scene.getIntrinsics().insert(*sfm_data.getIntrinsics().find(view_I->getIntrinsicId())); - tiny_scene.getIntrinsics().insert(*sfm_data.getIntrinsics().find(view_J->getIntrinsicId())); - } - - // Fill sfm_data with the inliers tracks. Feed image observations: no 3D yet. - Landmarks & structure = tiny_scene.getLandmarks(); - for(size_t idx=0; idx < vec_inliers.size(); ++idx) - { - const size_t trackId = vec_inliers[idx]; - const track::submapTrack & track = tracks.at(trackId); - Observations & obs = structure[idx].obs; - for (track::Track::FeatureIdPerView::const_iterator it = track.begin(); it != track.end(); ++it) + // compute 3D scene base on motion estimation + SfMData tiny_scene; + + // intialize poses (which are now shared by a group of images) + tiny_scene.poses[poses_id.i] = Pose3(vec_global_R_Triplet[0], -vec_global_R_Triplet[0].transpose() * vec_tis[0]); + tiny_scene.poses[poses_id.j] = Pose3(vec_global_R_Triplet[1], -vec_global_R_Triplet[1].transpose() * vec_tis[1]); + tiny_scene.poses[poses_id.k] = Pose3(vec_global_R_Triplet[2], -vec_global_R_Triplet[2].transpose() * vec_tis[2]); + + // insert views used by the relative pose pairs + for (const auto& pairIterator : map_triplet_matches) { - // get view Id and feat ID - const size_t viewIndex = it->first; - const size_t featIndex = it->second; - - // initialize view and get intrinsics - const View * view = sfm_data.getViews().at(viewIndex).get(); - const camera::IntrinsicBase * cam = sfm_data.getIntrinsics().find(view->getIntrinsicId())->second.get(); - const camera::Pinhole * intrinsicPtr = dynamic_cast< const camera::Pinhole * >(cam); - - // get normalized feature - const feature::PointFeature & pt = normalizedFeaturesPerView.getFeatures(viewIndex)[featIndex]; - const Vec2 pt_unnormalized (cam->cam2ima(pt.coords().cast())); - obs[viewIndex] = Observation(pt_unnormalized, featIndex); + // initialize camera indexes + const IndexT I = pairIterator.first.first; + const IndexT J = pairIterator.first.second; + + // add views + tiny_scene.getViews().insert(*sfm_data.getViews().find(I)); + tiny_scene.getViews().insert(*sfm_data.getViews().find(J)); + + // add intrinsics + const View* view_I = sfm_data.getViews().at(I).get(); + const View* view_J = sfm_data.getViews().at(J).get(); + tiny_scene.getIntrinsics().insert(*sfm_data.getIntrinsics().find(view_I->getIntrinsicId())); + tiny_scene.getIntrinsics().insert(*sfm_data.getIntrinsics().find(view_J->getIntrinsicId())); + } + + // Fill sfm_data with the inliers tracks. Feed image observations: no 3D yet. + Landmarks& structure = tiny_scene.getLandmarks(); + for (size_t idx = 0; idx < vec_inliers.size(); ++idx) + { + const size_t trackId = vec_inliers[idx]; + const track::submapTrack& track = tracks.at(trackId); + Observations& obs = structure[idx].obs; + for (track::Track::FeatureIdPerView::const_iterator it = track.begin(); it != track.end(); ++it) + { + // get view Id and feat ID + const size_t viewIndex = it->first; + const size_t featIndex = it->second; + + // initialize view and get intrinsics + const View* view = sfm_data.getViews().at(viewIndex).get(); + const camera::IntrinsicBase* cam = sfm_data.getIntrinsics().find(view->getIntrinsicId())->second.get(); + const camera::Pinhole* intrinsicPtr = dynamic_cast(cam); + + // get normalized feature + const feature::PointFeature& pt = normalizedFeaturesPerView.getFeatures(viewIndex)[featIndex]; + const Vec2 pt_unnormalized(cam->cam2ima(pt.coords().cast())); + obs[viewIndex] = Observation(pt_unnormalized, featIndex); + } + } + + // Compute 3D landmark positions (triangulation of the observations) + { + StructureComputation_blind structure_estimator(false); + structure_estimator.triangulate(tiny_scene); + } + + // Refine structure and poses (keep intrinsic constant) + BundleAdjustmentCeres::BA_options options(false, false); + options._linear_solver_type = ceres::SPARSE_SCHUR; + BundleAdjustmentCeres bundle_adjustment_obj(options); + if (bundle_adjustment_obj.Adjust(tiny_scene, REFINE_TRANSLATION | REFINE_STRUCTURE)) + { + // export scene for visualization + std::ostringstream os; + os << poses_id.i << "_" << poses_id.j << "_" << poses_id.k << ".ply"; + Save(tiny_scene, os.str(), ESfMData(STRUCTURE | EXTRINSICS)); + + // Export refined translations + vec_tis[0] = tiny_scene.poses[poses_id.i].translation(); + vec_tis[1] = tiny_scene.poses[poses_id.j].translation(); + vec_tis[2] = tiny_scene.poses[poses_id.k].translation(); } - } - - // Compute 3D landmark positions (triangulation of the observations) - { - StructureComputation_blind structure_estimator(false); - structure_estimator.triangulate(tiny_scene); - } - - // Refine structure and poses (keep intrinsic constant) - BundleAdjustmentCeres::BA_options options(false, false); - options._linear_solver_type = ceres::SPARSE_SCHUR; - BundleAdjustmentCeres bundle_adjustment_obj(options); - if (bundle_adjustment_obj.Adjust(tiny_scene, REFINE_TRANSLATION | REFINE_STRUCTURE)) - { - // export scene for visualization - std::ostringstream os; - os << poses_id.i << "_" << poses_id.j << "_" << poses_id.k << ".ply"; - Save(tiny_scene, os.str(), ESfMData(STRUCTURE | EXTRINSICS)); - - // Export refined translations - vec_tis[0] = tiny_scene.poses[poses_id.i].translation(); - vec_tis[1] = tiny_scene.poses[poses_id.j].translation(); - vec_tis[2] = tiny_scene.poses[poses_id.k].translation(); - } #endif - // Keep the model iff it has a sufficient inlier count - const bool bTest = ( vec_inliers.size() > 30 && 0.33 * tracks.size() ); + // Keep the model iff it has a sufficient inlier count + const bool bTest = (vec_inliers.size() > 30 && 0.33 * tracks.size()); #ifdef DEBUG_TRIPLET - { - ALICEVISION_LOG_DEBUG( - "Triplet : status: " << bTest << - " AC: " << dPrecision << - " inliers % " << double(vec_inliers.size()) / tracks.size() * 100.0 << - " total putative " << tracks.size()); - } + { + ALICEVISION_LOG_DEBUG("Triplet : status: " << bTest << " AC: " << dPrecision << " inliers % " + << double(vec_inliers.size()) / tracks.size() * 100.0 << " total putative " << tracks.size()); + } #endif - return bTest; + return bTest; } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.hpp b/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.hpp index c640d6c2fb..e526897d69 100644 --- a/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.hpp +++ b/src/aliceVision/sfm/pipeline/global/GlobalSfMTranslationAveragingSolver.hpp @@ -19,38 +19,38 @@ namespace sfm { enum ETranslationAveragingMethod { - TRANSLATION_AVERAGING_L1 = 1, - TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL = 2, - TRANSLATION_AVERAGING_SOFTL1 = 3 + TRANSLATION_AVERAGING_L1 = 1, + TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL = 2, + TRANSLATION_AVERAGING_SOFTL1 = 3 }; inline std::string ETranslationAveragingMethod_enumToString(ETranslationAveragingMethod eTranslationAveragingMethod) { - switch(eTranslationAveragingMethod) - { - case ETranslationAveragingMethod::TRANSLATION_AVERAGING_L1: - return "L1_minimization"; - case ETranslationAveragingMethod::TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL: - return "L2_minimization"; - case ETranslationAveragingMethod::TRANSLATION_AVERAGING_SOFTL1: - return "L1_soft_minimization"; - } - throw std::out_of_range("Invalid translation averaging method type"); + switch (eTranslationAveragingMethod) + { + case ETranslationAveragingMethod::TRANSLATION_AVERAGING_L1: + return "L1_minimization"; + case ETranslationAveragingMethod::TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL: + return "L2_minimization"; + case ETranslationAveragingMethod::TRANSLATION_AVERAGING_SOFTL1: + return "L1_soft_minimization"; + } + throw std::out_of_range("Invalid translation averaging method type"); } inline ETranslationAveragingMethod ETranslationAveragingMethod_stringToEnum(const std::string& TranslationAveragingMethodName) { - if(TranslationAveragingMethodName == "L1_minimization") return ETranslationAveragingMethod::TRANSLATION_AVERAGING_L1; - if(TranslationAveragingMethodName == "L2_minimization") return ETranslationAveragingMethod::TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL; - if(TranslationAveragingMethodName == "L1_soft_minimization") return ETranslationAveragingMethod::TRANSLATION_AVERAGING_SOFTL1; - - throw std::out_of_range("Invalid translation averaging method name : '" + TranslationAveragingMethodName + "'"); + if (TranslationAveragingMethodName == "L1_minimization") + return ETranslationAveragingMethod::TRANSLATION_AVERAGING_L1; + if (TranslationAveragingMethodName == "L2_minimization") + return ETranslationAveragingMethod::TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL; + if (TranslationAveragingMethodName == "L1_soft_minimization") + return ETranslationAveragingMethod::TRANSLATION_AVERAGING_SOFTL1; + + throw std::out_of_range("Invalid translation averaging method name : '" + TranslationAveragingMethodName + "'"); } -inline std::ostream& operator<<(std::ostream& os, ETranslationAveragingMethod e) -{ - return os << ETranslationAveragingMethod_enumToString(e); -} +inline std::ostream& operator<<(std::ostream& os, ETranslationAveragingMethod e) { return os << ETranslationAveragingMethod_enumToString(e); } inline std::istream& operator>>(std::istream& in, ETranslationAveragingMethod& translationType) { @@ -62,62 +62,61 @@ inline std::istream& operator>>(std::istream& in, ETranslationAveragingMethod& t class GlobalSfMTranslationAveragingSolver { - translationAveraging::RelativeInfoVec m_vec_initialRijTijEstimates; - -public: - - /** - * @brief Use features in normalized camera frames - */ - bool Run(ETranslationAveragingMethod eTranslationAveragingMethod, - sfmData::SfMData& sfmData, - const feature::FeaturesPerView& normalizedFeaturesPerView, - const matching::PairwiseMatches& pairwiseMatches, - const HashMap& map_globalR, - std::mt19937 & randomNumberGenerator, - matching::PairwiseMatches& tripletWise_matches); - -private: - bool Translation_averaging(ETranslationAveragingMethod eTranslationAveragingMethod, - sfmData::SfMData& sfmData, - const HashMap & map_globalR); - - void Compute_translations(const sfmData::SfMData& sfmData, - const feature::FeaturesPerView& normalizedFeaturesPerView, - const matching::PairwiseMatches& pairwiseMatches, - const HashMap& map_globalR, - std::mt19937 & randomNumberGenerator, - matching::PairwiseMatches& tripletWise_matches); - - /** - * @brief Compute the relative translations on the rotations graph. - * Compute relative translations by using triplets of poses. - * Use an edge coverage algorithm to reduce the graph covering complexity - * Complexity: sub-linear in term of edges count. - */ - void ComputePutativeTranslation_EdgesCoverage(const sfmData::SfMData& sfmData, - const HashMap& map_globalR, - const feature::FeaturesPerView& normalizedFeaturesPerView, - const matching::PairwiseMatches& pairwiseMatches, - std::mt19937 & randomNumberGenerator, - translationAveraging::RelativeInfoVec& vec_initialEstimates, - matching::PairwiseMatches& newpairMatches); - - /** - * @brief Robust estimation and refinement of a translation and 3D points of an image triplets. - */ - bool Estimate_T_triplet(const sfmData::SfMData& sfmData, - const HashMap& map_globalR, - const feature::FeaturesPerView& normalizedFeaturesPerView, - const matching::PairwiseMatches& pairwiseMatches, - const graph::Triplet& poses_id, - std::mt19937 & randomNumberGenerator, - std::vector& vec_tis, - double& precision, // UpperBound of the precision found by the AContrario estimator - std::vector& vec_inliers, - aliceVision::track::TracksMap& rig_tracks, - const std::string& outDirectory) const; + translationAveraging::RelativeInfoVec m_vec_initialRijTijEstimates; + + public: + /** + * @brief Use features in normalized camera frames + */ + bool Run(ETranslationAveragingMethod eTranslationAveragingMethod, + sfmData::SfMData& sfmData, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + const HashMap& map_globalR, + std::mt19937& randomNumberGenerator, + matching::PairwiseMatches& tripletWise_matches); + + private: + bool Translation_averaging(ETranslationAveragingMethod eTranslationAveragingMethod, + sfmData::SfMData& sfmData, + const HashMap& map_globalR); + + void Compute_translations(const sfmData::SfMData& sfmData, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + const HashMap& map_globalR, + std::mt19937& randomNumberGenerator, + matching::PairwiseMatches& tripletWise_matches); + + /** + * @brief Compute the relative translations on the rotations graph. + * Compute relative translations by using triplets of poses. + * Use an edge coverage algorithm to reduce the graph covering complexity + * Complexity: sub-linear in term of edges count. + */ + void ComputePutativeTranslation_EdgesCoverage(const sfmData::SfMData& sfmData, + const HashMap& map_globalR, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + std::mt19937& randomNumberGenerator, + translationAveraging::RelativeInfoVec& vec_initialEstimates, + matching::PairwiseMatches& newpairMatches); + + /** + * @brief Robust estimation and refinement of a translation and 3D points of an image triplets. + */ + bool Estimate_T_triplet(const sfmData::SfMData& sfmData, + const HashMap& map_globalR, + const feature::FeaturesPerView& normalizedFeaturesPerView, + const matching::PairwiseMatches& pairwiseMatches, + const graph::Triplet& poses_id, + std::mt19937& randomNumberGenerator, + std::vector& vec_tis, + double& precision, // UpperBound of the precision found by the AContrario estimator + std::vector& vec_inliers, + aliceVision::track::TracksMap& rig_tracks, + const std::string& outDirectory) const; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/MutexSet.hpp b/src/aliceVision/sfm/pipeline/global/MutexSet.hpp index 1ce0e7360a..8b15c1a168 100644 --- a/src/aliceVision/sfm/pipeline/global/MutexSet.hpp +++ b/src/aliceVision/sfm/pipeline/global/MutexSet.hpp @@ -14,35 +14,35 @@ namespace aliceVision { namespace sfm { /// ThreadSafe Set thanks to a mutex -template -class MutexSet { - - typedef std::mutex mutexT; - typedef std::lock_guard lockGuardT; - -public: - void insert(const T& value) - { - lockGuardT guard(_mutex); - _set.insert(value); - } - - int count(const T& value) const - { - lockGuardT guard(_mutex); - return _set.count(value); - } - - std::size_t size() const - { - lockGuardT guard(_mutex); - return _set.size(); - } - -private: - std::set _set; - mutable mutexT _mutex; +template +class MutexSet +{ + typedef std::mutex mutexT; + typedef std::lock_guard lockGuardT; + + public: + void insert(const T& value) + { + lockGuardT guard(_mutex); + _set.insert(value); + } + + int count(const T& value) const + { + lockGuardT guard(_mutex); + return _set.count(value); + } + + std::size_t size() const + { + lockGuardT guard(_mutex); + return _set.size(); + } + + private: + std::set _set; + mutable mutexT _mutex; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.cpp b/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.cpp index 81912a3066..4f53dabbfd 100644 --- a/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.cpp +++ b/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.cpp @@ -22,7 +22,7 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif namespace aliceVision { @@ -36,652 +36,671 @@ using namespace aliceVision::sfmData; ReconstructionEngine_globalSfM::ReconstructionEngine_globalSfM(const SfMData& sfmData, const std::string& outDirectory, const std::string& loggingFile) - : ReconstructionEngine(sfmData, outDirectory) - , _loggingFile(loggingFile) - , _normalizedFeaturesPerView(nullptr) + : ReconstructionEngine(sfmData, outDirectory), + _loggingFile(loggingFile), + _normalizedFeaturesPerView(nullptr) { - if(!_loggingFile.empty()) - { - // setup HTML logger - _htmlDocStream = std::make_shared("GlobalReconstructionEngine SFM report."); - _htmlDocStream->pushInfo(htmlDocument::htmlMarkup("h1", std::string("ReconstructionEngine_globalSfM"))); - _htmlDocStream->pushInfo("
"); - _htmlDocStream->pushInfo( "Dataset info:"); - _htmlDocStream->pushInfo( "Views count: " + htmlDocument::toString( sfmData.getViews().size()) + "
"); - } - - // Set default motion Averaging methods - _eRotationAveragingMethod = ROTATION_AVERAGING_L2; - _eTranslationAveragingMethod = TRANSLATION_AVERAGING_L1; + if (!_loggingFile.empty()) + { + // setup HTML logger + _htmlDocStream = std::make_shared("GlobalReconstructionEngine SFM report."); + _htmlDocStream->pushInfo(htmlDocument::htmlMarkup("h1", std::string("ReconstructionEngine_globalSfM"))); + _htmlDocStream->pushInfo("
"); + _htmlDocStream->pushInfo("Dataset info:"); + _htmlDocStream->pushInfo("Views count: " + htmlDocument::toString(sfmData.getViews().size()) + "
"); + } + + // Set default motion Averaging methods + _eRotationAveragingMethod = ROTATION_AVERAGING_L2; + _eTranslationAveragingMethod = TRANSLATION_AVERAGING_L1; } ReconstructionEngine_globalSfM::~ReconstructionEngine_globalSfM() { - if(!_loggingFile.empty()) - { - // Save the reconstruction Log - std::ofstream htmlFileStream(_loggingFile); - htmlFileStream << _htmlDocStream->getDoc(); - } + if (!_loggingFile.empty()) + { + // Save the reconstruction Log + std::ofstream htmlFileStream(_loggingFile); + htmlFileStream << _htmlDocStream->getDoc(); + } } void ReconstructionEngine_globalSfM::SetFeaturesProvider(feature::FeaturesPerView* featuresPerView) { - _featuresPerView = featuresPerView; - - // Copy features and save a normalized version - _normalizedFeaturesPerView = std::make_shared(*featuresPerView); - #pragma omp parallel - for(MapFeaturesPerView::iterator iter = _normalizedFeaturesPerView->getData().begin(); - iter != _normalizedFeaturesPerView->getData().end(); ++iter) - { - #pragma omp single nowait + _featuresPerView = featuresPerView; + + // Copy features and save a normalized version + _normalizedFeaturesPerView = std::make_shared(*featuresPerView); +#pragma omp parallel + for (MapFeaturesPerView::iterator iter = _normalizedFeaturesPerView->getData().begin(); iter != _normalizedFeaturesPerView->getData().end(); + ++iter) { - // get the related view & camera intrinsic and compute the corresponding bearing vectors - const View * view = _sfmData.getViews().at(iter->first).get(); - if(_sfmData.getIntrinsics().count(view->getIntrinsicId())) - { - const std::shared_ptr cam = _sfmData.getIntrinsics().find(view->getIntrinsicId())->second; - for(auto& iterFeatPerDesc: iter->second) +#pragma omp single nowait { - for (PointFeatures::iterator iterPt = iterFeatPerDesc.second.begin(); - iterPt != iterFeatPerDesc.second.end(); ++iterPt) - { - const Vec2 pt = iterPt->coords().cast(); - const Vec3 bearingVector = cam->toUnitSphere(cam->removeDistortion(cam->ima2cam(pt))); - iterPt->coords() << (bearingVector.head(2) / bearingVector(2)).cast(); - } + // get the related view & camera intrinsic and compute the corresponding bearing vectors + const View* view = _sfmData.getViews().at(iter->first).get(); + if (_sfmData.getIntrinsics().count(view->getIntrinsicId())) + { + const std::shared_ptr cam = _sfmData.getIntrinsics().find(view->getIntrinsicId())->second; + for (auto& iterFeatPerDesc : iter->second) + { + for (PointFeatures::iterator iterPt = iterFeatPerDesc.second.begin(); iterPt != iterFeatPerDesc.second.end(); ++iterPt) + { + const Vec2 pt = iterPt->coords().cast(); + const Vec3 bearingVector = cam->toUnitSphere(cam->removeDistortion(cam->ima2cam(pt))); + iterPt->coords() << (bearingVector.head(2) / bearingVector(2)).cast(); + } + } + } } - } } - } } -void ReconstructionEngine_globalSfM::SetMatchesProvider(matching::PairwiseMatches* provider) -{ - _pairwiseMatches = provider; -} +void ReconstructionEngine_globalSfM::SetMatchesProvider(matching::PairwiseMatches* provider) { _pairwiseMatches = provider; } void ReconstructionEngine_globalSfM::SetRotationAveragingMethod(ERotationAveragingMethod eRotationAveragingMethod) { - _eRotationAveragingMethod = eRotationAveragingMethod; + _eRotationAveragingMethod = eRotationAveragingMethod; } void ReconstructionEngine_globalSfM::SetTranslationAveragingMethod(ETranslationAveragingMethod eTranslationAveragingMethod) { - _eTranslationAveragingMethod = eTranslationAveragingMethod; + _eTranslationAveragingMethod = eTranslationAveragingMethod; } bool ReconstructionEngine_globalSfM::process() { - // keep only the largest biedge connected subgraph - { - const PairSet pairs = matching::getImagePairs(*_pairwiseMatches); - const std::set set_remainingIds = graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs, _outputFolder); - if(set_remainingIds.empty()) + // keep only the largest biedge connected subgraph + { + const PairSet pairs = matching::getImagePairs(*_pairwiseMatches); + const std::set set_remainingIds = graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs, _outputFolder); + if (set_remainingIds.empty()) + { + ALICEVISION_LOG_DEBUG("Invalid input image graph for global SfM"); + return false; + } + KeepOnlyReferencedElement(set_remainingIds, *_pairwiseMatches); + } + + aliceVision::rotationAveraging::RelativeRotations relatives_R; + Compute_Relative_Rotations(relatives_R); + + HashMap global_rotations; + if (!Compute_Global_Rotations(relatives_R, global_rotations)) + { + ALICEVISION_LOG_WARNING("GlobalSfM:: Rotation Averaging failure!"); + return false; + } + matching::PairwiseMatches tripletWise_matches; + if (!Compute_Global_Translations(global_rotations, tripletWise_matches)) { - ALICEVISION_LOG_DEBUG("Invalid input image graph for global SfM"); - return false; + ALICEVISION_LOG_WARNING("GlobalSfM:: Translation Averaging failure!"); + return false; } - KeepOnlyReferencedElement(set_remainingIds, *_pairwiseMatches); - } - - aliceVision::rotationAveraging::RelativeRotations relatives_R; - Compute_Relative_Rotations(relatives_R); - - HashMap global_rotations; - if(!Compute_Global_Rotations(relatives_R, global_rotations)) - { - ALICEVISION_LOG_WARNING("GlobalSfM:: Rotation Averaging failure!"); - return false; - } - matching::PairwiseMatches tripletWise_matches; - if(!Compute_Global_Translations(global_rotations, tripletWise_matches)) - { - ALICEVISION_LOG_WARNING("GlobalSfM:: Translation Averaging failure!"); - return false; - } - if(!Compute_Initial_Structure(tripletWise_matches)) - { - ALICEVISION_LOG_WARNING("GlobalSfM:: Cannot initialize an initial structure!"); - return false; - } - if(!Adjust()) - { - ALICEVISION_LOG_WARNING("GlobalSfM:: Non-linear adjustment failure!"); - return false; - } - - //-- Export statistics about the SfM process - if (!_loggingFile.empty()) - { - using namespace htmlDocument; - std::ostringstream os; - os << "Structure from Motion statistics."; - _htmlDocStream->pushInfo("
"); - _htmlDocStream->pushInfo(htmlMarkup("h1",os.str())); - - os.str(""); - os << "-------------------------------" << "
" - << "-- View count: " << _sfmData.getViews().size() << "
" - << "-- Intrinsic count: " << _sfmData.getIntrinsics().size() << "
" - << "-- Pose count: " << _sfmData.getPoses().size() << "
" - << "-- Track count: " << _sfmData.getLandmarks().size() << "
" - << "-------------------------------" << "
"; - _htmlDocStream->pushInfo(os.str()); - } - - return true; + if (!Compute_Initial_Structure(tripletWise_matches)) + { + ALICEVISION_LOG_WARNING("GlobalSfM:: Cannot initialize an initial structure!"); + return false; + } + if (!Adjust()) + { + ALICEVISION_LOG_WARNING("GlobalSfM:: Non-linear adjustment failure!"); + return false; + } + + //-- Export statistics about the SfM process + if (!_loggingFile.empty()) + { + using namespace htmlDocument; + std::ostringstream os; + os << "Structure from Motion statistics."; + _htmlDocStream->pushInfo("
"); + _htmlDocStream->pushInfo(htmlMarkup("h1", os.str())); + + os.str(""); + os << "-------------------------------" + << "
" + << "-- View count: " << _sfmData.getViews().size() << "
" + << "-- Intrinsic count: " << _sfmData.getIntrinsics().size() << "
" + << "-- Pose count: " << _sfmData.getPoses().size() << "
" + << "-- Track count: " << _sfmData.getLandmarks().size() << "
" + << "-------------------------------" + << "
"; + _htmlDocStream->pushInfo(os.str()); + } + + return true; } /// Compute from relative rotations the global rotations of the camera poses bool ReconstructionEngine_globalSfM::Compute_Global_Rotations(const rotationAveraging::RelativeRotations& relatives_R, HashMap& global_rotations) { - if(relatives_R.empty()) - return false; - // Log statistics about the relative rotation graph - { - std::set set_pose_ids; - for (const auto & relative_R : relatives_R) + if (relatives_R.empty()) + return false; + // Log statistics about the relative rotation graph { - set_pose_ids.insert(relative_R.i); - set_pose_ids.insert(relative_R.j); - } + std::set set_pose_ids; + for (const auto& relative_R : relatives_R) + { + set_pose_ids.insert(relative_R.i); + set_pose_ids.insert(relative_R.j); + } - ALICEVISION_LOG_DEBUG("Global rotations computation: " << "\n" - "\t- relative rotations: " << relatives_R.size() << "\n" - "\t- global rotations: " << set_pose_ids.size()); - } + ALICEVISION_LOG_DEBUG("Global rotations computation: " + << "\n" + "\t- relative rotations: " + << relatives_R.size() + << "\n" + "\t- global rotations: " + << set_pose_ids.size()); + } - // Global Rotation solver: - const ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod = TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR; //TRIPLET_ROTATION_INFERENCE_NONE; + // Global Rotation solver: + const ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod = + TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR; // TRIPLET_ROTATION_INFERENCE_NONE; - GlobalSfMRotationAveragingSolver rotationAveraging_solver; - //-- Rejection triplet that are 'not' identity rotation (error to identity > 5°) - const bool b_rotationAveraging = rotationAveraging_solver.Run(_eRotationAveragingMethod, eRelativeRotationInferenceMethod, relatives_R, 5.0, global_rotations); + GlobalSfMRotationAveragingSolver rotationAveraging_solver; + //-- Rejection triplet that are 'not' identity rotation (error to identity > 5°) + const bool b_rotationAveraging = + rotationAveraging_solver.Run(_eRotationAveragingMethod, eRelativeRotationInferenceMethod, relatives_R, 5.0, global_rotations); - ALICEVISION_LOG_DEBUG("Found #global_rotations: " << global_rotations.size()); + ALICEVISION_LOG_DEBUG("Found #global_rotations: " << global_rotations.size()); - if(b_rotationAveraging) - { - // Log input graph to the HTML report - if(!_loggingFile.empty() && !_outputFolder.empty()) + if (b_rotationAveraging) { - // Log a relative pose graph - { - std::set set_pose_ids; - PairSet relative_pose_pairs; - for(const auto & view : _sfmData.getViews()) + // Log input graph to the HTML report + if (!_loggingFile.empty() && !_outputFolder.empty()) { - const IndexT pose_id = view.second->getPoseId(); - set_pose_ids.insert(pose_id); + // Log a relative pose graph + { + std::set set_pose_ids; + PairSet relative_pose_pairs; + for (const auto& view : _sfmData.getViews()) + { + const IndexT pose_id = view.second->getPoseId(); + set_pose_ids.insert(pose_id); + } + const std::string sGraph_name = "global_relative_rotation_pose_graph_final"; + graph::indexedGraph putativeGraph(set_pose_ids, rotationAveraging_solver.GetUsedPairs()); + graph::exportToGraphvizData((fs::path(_outputFolder) / (sGraph_name + ".dot")).string(), putativeGraph.g); + + /* + using namespace htmlDocument; + std::ostringstream os; + os << "
" << sGraph_name << "
" + << "\n"; + _htmlDocStream->pushInfo(os.str()); + */ + } } - const std::string sGraph_name = "global_relative_rotation_pose_graph_final"; - graph::indexedGraph putativeGraph(set_pose_ids, rotationAveraging_solver.GetUsedPairs()); - graph::exportToGraphvizData((fs::path(_outputFolder) / (sGraph_name + ".dot")).string(), putativeGraph.g); - - /* - using namespace htmlDocument; - std::ostringstream os; - os << "
" << sGraph_name << "
" - << "\n"; - _htmlDocStream->pushInfo(os.str()); - */ - } } - } - return b_rotationAveraging; + return b_rotationAveraging; } /// Compute/refine relative translations and compute global translations bool ReconstructionEngine_globalSfM::Compute_Global_Translations(const HashMap& global_rotations, matching::PairwiseMatches& tripletWise_matches) { - // Translation averaging (compute translations & update them to a global common coordinates system) - GlobalSfMTranslationAveragingSolver translation_averaging_solver; - const bool bTranslationAveraging = translation_averaging_solver.Run( - _eTranslationAveragingMethod, - _sfmData, - *_normalizedFeaturesPerView.get(), - *_pairwiseMatches, - global_rotations, - _randomNumberGenerator, - tripletWise_matches); - - if(!_loggingFile.empty()) - { - sfmDataIO::Save(_sfmData,(fs::path(_loggingFile).parent_path() / "cameraPath_translation_averaging.ply").string(), sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS)); - } - - return bTranslationAveraging; + // Translation averaging (compute translations & update them to a global common coordinates system) + GlobalSfMTranslationAveragingSolver translation_averaging_solver; + const bool bTranslationAveraging = translation_averaging_solver.Run(_eTranslationAveragingMethod, + _sfmData, + *_normalizedFeaturesPerView.get(), + *_pairwiseMatches, + global_rotations, + _randomNumberGenerator, + tripletWise_matches); + + if (!_loggingFile.empty()) + { + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "cameraPath_translation_averaging.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS)); + } + + return bTranslationAveraging; } /// Compute the initial structure of the scene bool ReconstructionEngine_globalSfM::Compute_Initial_Structure(matching::PairwiseMatches& tripletWise_matches) { - // Build tracks from selected triplets (Union of all the validated triplet tracks (_tripletWise_matches)) - { - using namespace aliceVision::track; - TracksBuilder tracksBuilder; -#ifdef USE_ALL_VALID_MATCHES // not used by default - matching::PairwiseMatches pose_supported_matches; - for (const auto & pairwiseMatchesIt : *_pairwiseMatches) + // Build tracks from selected triplets (Union of all the validated triplet tracks (_tripletWise_matches)) { - const View * vI = _sfm_data.getViews().at(pairwiseMatchesIt.first.first).get(); - const View * vJ = _sfm_data.getViews().at(pairwiseMatchesIt.first.second).get(); - if (_sfm_data.isPoseAndIntrinsicDefined(vI) && _sfm_data.isPoseAndIntrinsicDefined(vJ)) - { - pose_supported_matches.insert(pairwiseMatchesIt); - } - } - tracksBuilder.build(pose_supported_matches); + using namespace aliceVision::track; + TracksBuilder tracksBuilder; +#ifdef USE_ALL_VALID_MATCHES // not used by default + matching::PairwiseMatches pose_supported_matches; + for (const auto& pairwiseMatchesIt : *_pairwiseMatches) + { + const View* vI = _sfm_data.getViews().at(pairwiseMatchesIt.first.first).get(); + const View* vJ = _sfm_data.getViews().at(pairwiseMatchesIt.first.second).get(); + if (_sfm_data.isPoseAndIntrinsicDefined(vI) && _sfm_data.isPoseAndIntrinsicDefined(vJ)) + { + pose_supported_matches.insert(pairwiseMatchesIt); + } + } + tracksBuilder.build(pose_supported_matches); #else - // Use triplet validated matches - tracksBuilder.build(tripletWise_matches); + // Use triplet validated matches + tracksBuilder.build(tripletWise_matches); #endif - tracksBuilder.filter(true,3); - TracksMap map_selectedTracks; // reconstructed track (visibility per 3D point) - tracksBuilder.exportToSTL(map_selectedTracks); - - // Fill sfm_data with the computed tracks (no 3D yet) - Landmarks & structure = _sfmData.getLandmarks(); - IndexT idx(0); - for (TracksMap::const_iterator itTracks = map_selectedTracks.begin(); - itTracks != map_selectedTracks.end(); - ++itTracks, ++idx) - { - const Track & track = itTracks->second; - Landmark& newLandmark = structure[idx]; - newLandmark.descType = track.descType; - Observations & obs = newLandmark.observations; - for (Track::FeatureIdPerView::const_iterator it = track.featPerView.begin(); it != track.featPerView.end(); ++it) - { - const size_t imaIndex = it->first; - const size_t featIndex = it->second; - const PointFeature & pt = _featuresPerView->getFeatures(imaIndex, track.descType)[featIndex]; - - const double scale = (_featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : pt.scale(); - obs[imaIndex] = Observation(pt.coords().cast(), featIndex, scale); - } - } + tracksBuilder.filter(true, 3); + TracksMap map_selectedTracks; // reconstructed track (visibility per 3D point) + tracksBuilder.exportToSTL(map_selectedTracks); + + // Fill sfm_data with the computed tracks (no 3D yet) + Landmarks& structure = _sfmData.getLandmarks(); + IndexT idx(0); + for (TracksMap::const_iterator itTracks = map_selectedTracks.begin(); itTracks != map_selectedTracks.end(); ++itTracks, ++idx) + { + const Track& track = itTracks->second; + Landmark& newLandmark = structure[idx]; + newLandmark.descType = track.descType; + Observations& obs = newLandmark.observations; + for (Track::FeatureIdPerView::const_iterator it = track.featPerView.begin(); it != track.featPerView.end(); ++it) + { + const size_t imaIndex = it->first; + const size_t featIndex = it->second; + const PointFeature& pt = _featuresPerView->getFeatures(imaIndex, track.descType)[featIndex]; + + const double scale = (_featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : pt.scale(); + obs[imaIndex] = Observation(pt.coords().cast(), featIndex, scale); + } + } - ALICEVISION_LOG_DEBUG("Track stats"); - { - std::ostringstream osTrack; - //-- Display stats: - // - number of images - // - number of tracks - std::set set_imagesId; - imageIdInTracks(map_selectedTracks, set_imagesId); - osTrack << "------------------" << "\n" - << "-- Tracks Stats --" << "\n" - << " Tracks number: " << tracksBuilder.nbTracks() << "\n" - << " Images Id: " << "\n"; - std::copy(set_imagesId.begin(), - set_imagesId.end(), - std::ostream_iterator(osTrack, ", ")); - osTrack << "\n------------------" << "\n"; - - std::map map_Occurence_TrackLength; - tracksLength(map_selectedTracks, map_Occurence_TrackLength); - osTrack << "TrackLength, Occurrence" << "\n"; - for (std::map::const_iterator iter = map_Occurence_TrackLength.begin(); - iter != map_Occurence_TrackLength.end(); ++iter) { - osTrack << "\t" << iter->first << "\t" << iter->second << "\n"; - } - osTrack << "\n"; - ALICEVISION_LOG_DEBUG(osTrack.str()); + ALICEVISION_LOG_DEBUG("Track stats"); + { + std::ostringstream osTrack; + //-- Display stats: + // - number of images + // - number of tracks + std::set set_imagesId; + imageIdInTracks(map_selectedTracks, set_imagesId); + osTrack << "------------------" + << "\n" + << "-- Tracks Stats --" + << "\n" + << " Tracks number: " << tracksBuilder.nbTracks() << "\n" + << " Images Id: " + << "\n"; + std::copy(set_imagesId.begin(), set_imagesId.end(), std::ostream_iterator(osTrack, ", ")); + osTrack << "\n------------------" + << "\n"; + + std::map map_Occurence_TrackLength; + tracksLength(map_selectedTracks, map_Occurence_TrackLength); + osTrack << "TrackLength, Occurrence" + << "\n"; + for (std::map::const_iterator iter = map_Occurence_TrackLength.begin(); iter != map_Occurence_TrackLength.end(); ++iter) + { + osTrack << "\t" << iter->first << "\t" << iter->second << "\n"; + } + osTrack << "\n"; + ALICEVISION_LOG_DEBUG(osTrack.str()); + } } - } - // Compute 3D position of the landmark of the structure by triangulation of the observations - { - aliceVision::system::Timer timer; + // Compute 3D position of the landmark of the structure by triangulation of the observations + { + aliceVision::system::Timer timer; - const IndexT trackCountBefore = _sfmData.getLandmarks().size(); - StructureComputation_blind structure_estimator(true); - structure_estimator.triangulate(_sfmData, _randomNumberGenerator); + const IndexT trackCountBefore = _sfmData.getLandmarks().size(); + StructureComputation_blind structure_estimator(true); + structure_estimator.triangulate(_sfmData, _randomNumberGenerator); - ALICEVISION_LOG_DEBUG("#removed tracks (invalid triangulation): " << - trackCountBefore - IndexT(_sfmData.getLandmarks().size())); - ALICEVISION_LOG_DEBUG(" Triangulation took (s): " << timer.elapsed()); + ALICEVISION_LOG_DEBUG("#removed tracks (invalid triangulation): " << trackCountBefore - IndexT(_sfmData.getLandmarks().size())); + ALICEVISION_LOG_DEBUG(" Triangulation took (s): " << timer.elapsed()); - // Export initial structure - if (!_loggingFile.empty()) - { - sfmDataIO::Save(_sfmData, - (fs::path(_loggingFile).parent_path() / "initial_structure.ply").string(), - sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + // Export initial structure + if (!_loggingFile.empty()) + { + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "initial_structure.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + } } - } - return !_sfmData.getLandmarks().empty(); + return !_sfmData.getLandmarks().empty(); } // Adjust the scene (& remove outliers) bool ReconstructionEngine_globalSfM::Adjust() { - // refine sfm scene (in a 3 iteration process (free the parameters regarding their incertainty order)): - BundleAdjustmentCeres::CeresOptions options; - options.useParametersOrdering = false; // disable parameters ordering - - BundleAdjustmentCeres BA(options); - // - refine only Structure and translations - bool success = BA.adjust(_sfmData, BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE); - if(success) - { - if(!_loggingFile.empty()) - sfmDataIO::Save(_sfmData, (fs::path(_loggingFile).parent_path() / "structure_00_refine_T_Xi.ply").string(), sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); - - // refine only structure and rotations & translations - success = BA.adjust(_sfmData, BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE); - - if(success && !_loggingFile.empty()) - sfmDataIO::Save(_sfmData, (fs::path(_loggingFile).parent_path() / "structure_01_refine_RT_Xi.ply").string(), sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); - } - - if(success && !_lockAllIntrinsics) - { - // refine all: Structure, motion:{rotations, translations} and optics:{intrinsics} - success = BA.adjust(_sfmData, BundleAdjustment::REFINE_ALL); - if(success && !_loggingFile.empty()) - sfmDataIO::Save(_sfmData, (fs::path(_loggingFile).parent_path() / "structure_02_refine_KRT_Xi.ply").string(), sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); - } - - // Remove outliers (max_angle, residual error) - const size_t pointcount_initial = _sfmData.getLandmarks().size(); - RemoveOutliers_PixelResidualError(_sfmData, _featureConstraint, 4.0); - const size_t pointcount_pixelresidual_filter = _sfmData.getLandmarks().size(); - RemoveOutliers_AngleError(_sfmData, 2.0); - const size_t pointcount_angular_filter = _sfmData.getLandmarks().size(); - ALICEVISION_LOG_DEBUG("Outlier removal (remaining points):\n" - "\t- # landmarks initial: " << pointcount_initial << "\n" - "\t- # landmarks after pixel residual filter: " << pointcount_pixelresidual_filter << "\n" - "\t- # landmarks after angular filter: " << pointcount_angular_filter); - - if(!_loggingFile.empty()) - sfmDataIO::Save(_sfmData, (fs::path(_loggingFile).parent_path() / "structure_03_outlier_removed.ply").string(), sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); - - // check that poses & intrinsic cover some measures (after outlier removal) - const IndexT minPointPerPose = 12; // 6 min - const IndexT minTrackLength = 3; // 2 min todo param@L - - if (eraseUnstablePosesAndObservations(_sfmData, minPointPerPose, minTrackLength)) - { - // TODO: must ensure that track graph is producing a single connected component - - const size_t pointcount_cleaning = _sfmData.getLandmarks().size(); - ALICEVISION_LOG_DEBUG("# landmarks after eraseUnstablePosesAndObservations: " << pointcount_cleaning); - } - - BundleAdjustment::ERefineOptions refineOptions = BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE; - if(!_lockAllIntrinsics) - refineOptions |= BundleAdjustment::REFINE_INTRINSICS_ALL; - success = BA.adjust(_sfmData, refineOptions); - - if(success && !_loggingFile.empty()) - sfmDataIO::Save(_sfmData, (fs::path(_loggingFile).parent_path() / "structure_04_outlier_removed.ply").string(), sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); - - return success; + // refine sfm scene (in a 3 iteration process (free the parameters regarding their incertainty order)): + BundleAdjustmentCeres::CeresOptions options; + options.useParametersOrdering = false; // disable parameters ordering + + BundleAdjustmentCeres BA(options); + // - refine only Structure and translations + bool success = BA.adjust(_sfmData, BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE); + if (success) + { + if (!_loggingFile.empty()) + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "structure_00_refine_T_Xi.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + + // refine only structure and rotations & translations + success = BA.adjust(_sfmData, BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE); + + if (success && !_loggingFile.empty()) + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "structure_01_refine_RT_Xi.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + } + + if (success && !_lockAllIntrinsics) + { + // refine all: Structure, motion:{rotations, translations} and optics:{intrinsics} + success = BA.adjust(_sfmData, BundleAdjustment::REFINE_ALL); + if (success && !_loggingFile.empty()) + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "structure_02_refine_KRT_Xi.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + } + + // Remove outliers (max_angle, residual error) + const size_t pointcount_initial = _sfmData.getLandmarks().size(); + RemoveOutliers_PixelResidualError(_sfmData, _featureConstraint, 4.0); + const size_t pointcount_pixelresidual_filter = _sfmData.getLandmarks().size(); + RemoveOutliers_AngleError(_sfmData, 2.0); + const size_t pointcount_angular_filter = _sfmData.getLandmarks().size(); + ALICEVISION_LOG_DEBUG("Outlier removal (remaining points):\n" + "\t- # landmarks initial: " + << pointcount_initial + << "\n" + "\t- # landmarks after pixel residual filter: " + << pointcount_pixelresidual_filter + << "\n" + "\t- # landmarks after angular filter: " + << pointcount_angular_filter); + + if (!_loggingFile.empty()) + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "structure_03_outlier_removed.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + + // check that poses & intrinsic cover some measures (after outlier removal) + const IndexT minPointPerPose = 12; // 6 min + const IndexT minTrackLength = 3; // 2 min todo param@L + + if (eraseUnstablePosesAndObservations(_sfmData, minPointPerPose, minTrackLength)) + { + // TODO: must ensure that track graph is producing a single connected component + + const size_t pointcount_cleaning = _sfmData.getLandmarks().size(); + ALICEVISION_LOG_DEBUG("# landmarks after eraseUnstablePosesAndObservations: " << pointcount_cleaning); + } + + BundleAdjustment::ERefineOptions refineOptions = + BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE; + if (!_lockAllIntrinsics) + refineOptions |= BundleAdjustment::REFINE_INTRINSICS_ALL; + success = BA.adjust(_sfmData, refineOptions); + + if (success && !_loggingFile.empty()) + sfmDataIO::Save(_sfmData, + (fs::path(_loggingFile).parent_path() / "structure_04_outlier_removed.ply").string(), + sfmDataIO::ESfMData(sfmDataIO::EXTRINSICS | sfmDataIO::STRUCTURE)); + + return success; } void ReconstructionEngine_globalSfM::Compute_Relative_Rotations(rotationAveraging::RelativeRotations& vec_relatives_R) { - // - // Build the Relative pose graph from matches: - // - /// pairwise view relation between poseIds - typedef std::map PoseWiseMatches; - - // List shared correspondences (pairs) between poses - PoseWiseMatches poseWiseMatches; - for (matching::PairwiseMatches::const_iterator iterMatches = _pairwiseMatches->begin(); - iterMatches != _pairwiseMatches->end(); ++iterMatches) - { - const Pair pair = iterMatches->first; - const View* v1 = _sfmData.getViews().at(pair.first).get(); - const View* v2 = _sfmData.getViews().at(pair.second).get(); - poseWiseMatches[Pair(v1->getPoseId(), v2->getPoseId())].insert(pair); - } - - auto progressDisplay = system::createConsoleProgressDisplay(poseWiseMatches.size(), std::cout, - "\n- Relative pose computation -\n" ); - #pragma omp parallel for schedule(dynamic) - // Compute the relative pose from pairwise point matches: - for (int i = 0; i < poseWiseMatches.size(); ++i) - { - ++progressDisplay; + // + // Build the Relative pose graph from matches: + // + /// pairwise view relation between poseIds + typedef std::map PoseWiseMatches; + + // List shared correspondences (pairs) between poses + PoseWiseMatches poseWiseMatches; + for (matching::PairwiseMatches::const_iterator iterMatches = _pairwiseMatches->begin(); iterMatches != _pairwiseMatches->end(); ++iterMatches) { - PoseWiseMatches::const_iterator iter (poseWiseMatches.begin()); - std::advance(iter, i); - const auto& relative_pose_iterator(*iter); - const Pair relative_pose_pair = relative_pose_iterator.first; - const PairSet& match_pairs = relative_pose_iterator.second; - - // If a pair has the same ID, discard it - if (relative_pose_pair.first == relative_pose_pair.second) - { - continue; - } - - // Select common bearing vectors - if (match_pairs.size() > 1) - { - ALICEVISION_LOG_WARNING("Compute relative pose between more than two view is not supported"); - continue; - } - - const Pair pairIterator = *(match_pairs.begin()); - - const IndexT I = pairIterator.first; - const IndexT J = pairIterator.second; - - const View* view_I = _sfmData.getViews().at(I).get(); - const View* view_J = _sfmData.getViews().at(J).get(); - - // Check that valid cameras are existing for the pair of view - if (_sfmData.getIntrinsics().count(view_I->getIntrinsicId()) == 0 || - _sfmData.getIntrinsics().count(view_J->getIntrinsicId()) == 0) - continue; - - // Setup corresponding bearing vector - const matching::MatchesPerDescType & matchesPerDesc = _pairwiseMatches->at(pairIterator); - const std::size_t nbBearing = matchesPerDesc.getNbAllMatches(); - std::size_t iBearing = 0; - Mat x1(2, nbBearing), x2(2, nbBearing); - - for(const auto& matchesPerDescIt: matchesPerDesc) - { - const feature::EImageDescriberType descType = matchesPerDescIt.first; - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - const matching::IndMatches & matches = matchesPerDescIt.second; - - for (const auto & match : matches) + const Pair pair = iterMatches->first; + const View* v1 = _sfmData.getViews().at(pair.first).get(); + const View* v2 = _sfmData.getViews().at(pair.second).get(); + poseWiseMatches[Pair(v1->getPoseId(), v2->getPoseId())].insert(pair); + } + + auto progressDisplay = system::createConsoleProgressDisplay(poseWiseMatches.size(), std::cout, "\n- Relative pose computation -\n"); +#pragma omp parallel for schedule(dynamic) + // Compute the relative pose from pairwise point matches: + for (int i = 0; i < poseWiseMatches.size(); ++i) + { + ++progressDisplay; { - x1.col(iBearing) = _normalizedFeaturesPerView->getFeatures(I, descType)[match._i].coords().cast(); - x2.col(iBearing++) = _normalizedFeaturesPerView->getFeatures(J, descType)[match._j].coords().cast(); + PoseWiseMatches::const_iterator iter(poseWiseMatches.begin()); + std::advance(iter, i); + const auto& relative_pose_iterator(*iter); + const Pair relative_pose_pair = relative_pose_iterator.first; + const PairSet& match_pairs = relative_pose_iterator.second; + + // If a pair has the same ID, discard it + if (relative_pose_pair.first == relative_pose_pair.second) + { + continue; + } + + // Select common bearing vectors + if (match_pairs.size() > 1) + { + ALICEVISION_LOG_WARNING("Compute relative pose between more than two view is not supported"); + continue; + } + + const Pair pairIterator = *(match_pairs.begin()); + + const IndexT I = pairIterator.first; + const IndexT J = pairIterator.second; + + const View* view_I = _sfmData.getViews().at(I).get(); + const View* view_J = _sfmData.getViews().at(J).get(); + + // Check that valid cameras are existing for the pair of view + if (_sfmData.getIntrinsics().count(view_I->getIntrinsicId()) == 0 || _sfmData.getIntrinsics().count(view_J->getIntrinsicId()) == 0) + continue; + + // Setup corresponding bearing vector + const matching::MatchesPerDescType& matchesPerDesc = _pairwiseMatches->at(pairIterator); + const std::size_t nbBearing = matchesPerDesc.getNbAllMatches(); + std::size_t iBearing = 0; + Mat x1(2, nbBearing), x2(2, nbBearing); + + for (const auto& matchesPerDescIt : matchesPerDesc) + { + const feature::EImageDescriberType descType = matchesPerDescIt.first; + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + const matching::IndMatches& matches = matchesPerDescIt.second; + + for (const auto& match : matches) + { + x1.col(iBearing) = _normalizedFeaturesPerView->getFeatures(I, descType)[match._i].coords().cast(); + x2.col(iBearing++) = _normalizedFeaturesPerView->getFeatures(J, descType)[match._j].coords().cast(); + } + } + assert(nbBearing == iBearing); + + std::shared_ptr cam_I = _sfmData.getIntrinsics().at(view_I->getIntrinsicId()); + std::shared_ptr camIPinHole = std::dynamic_pointer_cast(cam_I); + if (!camIPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in Compute_Relative_Rotations"); + continue; + } + + std::shared_ptr cam_J = _sfmData.getIntrinsics().at(view_J->getIntrinsicId()); + std::shared_ptr camJPinHole = std::dynamic_pointer_cast(cam_J); + if (!camJPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in Compute_Relative_Rotations"); + continue; + } + + RelativePoseInfo relativePose_info; + // Compute max authorized error as geometric mean of camera plane tolerated residual error + // Note by Fabien Servant : double sqrt as before it was considered to be squared ... + relativePose_info.initial_residual_tolerance = + std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); + + // Since we use normalized features, we will use unit image size and intrinsic matrix: + const std::pair imageSize(1., 1.); + const Mat3 K = Mat3::Identity(); + + if (!robustRelativePose(K, K, x1, x2, _randomNumberGenerator, relativePose_info, imageSize, imageSize, 256)) + { + continue; + } + + const bool refineUsingBA = true; + if (refineUsingBA) + { + // Refine the defined scene + SfMData tinyScene; + tinyScene.getViews().insert(*_sfmData.getViews().find(view_I->getViewId())); + tinyScene.getViews().insert(*_sfmData.getViews().find(view_J->getViewId())); + tinyScene.getIntrinsics().insert(*_sfmData.getIntrinsics().find(view_I->getIntrinsicId())); + tinyScene.getIntrinsics().insert(*_sfmData.getIntrinsics().find(view_J->getIntrinsicId())); + + // Init poses + const Pose3& poseI = Pose3(Mat3::Identity(), Vec3::Zero()); + const Pose3& poseJ = relativePose_info.relativePose; + + tinyScene.setPose(*view_I, CameraPose(poseI)); + tinyScene.setPose(*view_J, CameraPose(poseJ)); + + // Init structure + const Mat34 P1 = camIPinHole->getProjectiveEquivalent(poseI); + const Mat34 P2 = camJPinHole->getProjectiveEquivalent(poseJ); + Landmarks& landmarks = tinyScene.getLandmarks(); + + size_t landmarkId = 0; + for (const auto& matchesPerDescIt : matchesPerDesc) + { + const feature::EImageDescriberType descType = matchesPerDescIt.first; + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + if (descType == feature::EImageDescriberType::UNINITIALIZED) + throw std::logic_error("descType UNINITIALIZED"); + const matching::IndMatches& matches = matchesPerDescIt.second; + for (const matching::IndMatch& match : matches) + { + const PointFeature& p1 = _featuresPerView->getFeatures(I, descType)[match._i]; + const PointFeature& p2 = _featuresPerView->getFeatures(J, descType)[match._j]; + const Vec2 x1_ = p1.coords().cast(); + const Vec2 x2_ = p2.coords().cast(); + Vec3 X; + multiview::TriangulateDLT(P1, x1_, P2, x2_, X); + Observations obs; + const double scaleI = (_featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : p1.scale(); + const double scaleJ = (_featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : p2.scale(); + obs[view_I->getViewId()] = Observation(x1_, match._i, scaleI); + obs[view_J->getViewId()] = Observation(x2_, match._j, scaleJ); + Landmark& newLandmark = landmarks[landmarkId++]; + newLandmark.descType = descType; + newLandmark.observations = obs; + newLandmark.X = X; + } + } + // - refine only Structure and Rotations & translations (keep intrinsic constant) + BundleAdjustmentCeres::CeresOptions options(false, false); + options.linearSolverType = ceres::DENSE_SCHUR; + BundleAdjustmentCeres bundle_adjustment_obj(options); + if (bundle_adjustment_obj.adjust( + tinyScene, BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE)) + { + // --> to debug: save relative pair geometry on disk + // std::ostringstream os; + // os << relative_pose_pair.first << "_" << relative_pose_pair.second << ".ply"; + // Save(tiny_scene, os.str(), ESfMData(STRUCTURE | EXTRINSICS)); + // + + const geometry::Pose3 poseI = tinyScene.getPose(*view_I).getTransform(); + const geometry::Pose3 poseJ = tinyScene.getPose(*view_J).getTransform(); + + const Mat3 R1 = poseI.rotation(); + const Mat3 R2 = poseJ.rotation(); + const Vec3 t1 = poseI.translation(); + const Vec3 t2 = poseJ.translation(); + // Compute relative motion and save it + Mat3 Rrel; + Vec3 trel; + relativeCameraMotion(R1, t1, R2, t2, &Rrel, &trel); + // Update found relative pose + relativePose_info.relativePose = Pose3(Rrel, -Rrel.transpose() * trel); + } + } +#pragma omp critical + { + // Add the relative rotation to the relative 'rotation' pose graph + using namespace aliceVision::rotationAveraging; + vec_relatives_R.emplace_back(relative_pose_pair.first, + relative_pose_pair.second, + relativePose_info.relativePose.rotation(), + relativePose_info.vec_inliers.size()); + } } - } - assert(nbBearing == iBearing); - - - std::shared_ptr cam_I = _sfmData.getIntrinsics().at(view_I->getIntrinsicId()); - std::shared_ptr camIPinHole = std::dynamic_pointer_cast(cam_I); - if (!camIPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in Compute_Relative_Rotations"); - continue; - } - - std::shared_ptr cam_J = _sfmData.getIntrinsics().at(view_J->getIntrinsicId()); - std::shared_ptr camJPinHole = std::dynamic_pointer_cast(cam_J); - if (!camJPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in Compute_Relative_Rotations"); - continue; - } - - RelativePoseInfo relativePose_info; - // Compute max authorized error as geometric mean of camera plane tolerated residual error - // Note by Fabien Servant : double sqrt as before it was considered to be squared ... - relativePose_info.initial_residual_tolerance = std::sqrt(std::sqrt( - cam_I->imagePlaneToCameraPlaneError(2.5) * - cam_J->imagePlaneToCameraPlaneError(2.5))); - - // Since we use normalized features, we will use unit image size and intrinsic matrix: - const std::pair imageSize(1., 1.); - const Mat3 K = Mat3::Identity(); - - - if(!robustRelativePose(K, K, x1, x2, _randomNumberGenerator, relativePose_info, imageSize, imageSize, 256)) - { - continue; - } - - const bool refineUsingBA = true; - if(refineUsingBA) - { - // Refine the defined scene - SfMData tinyScene; - tinyScene.getViews().insert(*_sfmData.getViews().find(view_I->getViewId())); - tinyScene.getViews().insert(*_sfmData.getViews().find(view_J->getViewId())); - tinyScene.getIntrinsics().insert(*_sfmData.getIntrinsics().find(view_I->getIntrinsicId())); - tinyScene.getIntrinsics().insert(*_sfmData.getIntrinsics().find(view_J->getIntrinsicId())); - - // Init poses - const Pose3& poseI = Pose3(Mat3::Identity(), Vec3::Zero()); - const Pose3& poseJ = relativePose_info.relativePose; - - tinyScene.setPose(*view_I, CameraPose(poseI)); - tinyScene.setPose(*view_J, CameraPose(poseJ)); - - // Init structure - const Mat34 P1 = camIPinHole->getProjectiveEquivalent(poseI); - const Mat34 P2 = camJPinHole->getProjectiveEquivalent(poseJ); - Landmarks & landmarks = tinyScene.getLandmarks(); - - size_t landmarkId = 0; - for(const auto& matchesPerDescIt: matchesPerDesc) + } // for all relative pose + + // Re-weight rotation in [0,1] + if (vec_relatives_R.size() > 1) + { + std::vector vec_count; + vec_count.reserve(vec_relatives_R.size()); + for (const auto& relative_rotation_info : vec_relatives_R) { - const feature::EImageDescriberType descType = matchesPerDescIt.first; - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - if(descType == feature::EImageDescriberType::UNINITIALIZED) - throw std::logic_error("descType UNINITIALIZED"); - const matching::IndMatches & matches = matchesPerDescIt.second; - for (const matching::IndMatch& match: matches) - { - const PointFeature& p1 = _featuresPerView->getFeatures(I, descType)[match._i]; - const PointFeature& p2 = _featuresPerView->getFeatures(J, descType)[match._j]; - const Vec2 x1_ = p1.coords().cast(); - const Vec2 x2_ = p2.coords().cast(); - Vec3 X; - multiview::TriangulateDLT(P1, x1_, P2, x2_, X); - Observations obs; - const double scaleI = (_featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : p1.scale(); - const double scaleJ = (_featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : p2.scale(); - obs[view_I->getViewId()] = Observation(x1_, match._i, scaleI); - obs[view_J->getViewId()] = Observation(x2_, match._j, scaleJ); - Landmark& newLandmark = landmarks[landmarkId++]; - newLandmark.descType = descType; - newLandmark.observations = obs; - newLandmark.X = X; - } + vec_count.push_back(relative_rotation_info.weight); } - // - refine only Structure and Rotations & translations (keep intrinsic constant) - BundleAdjustmentCeres::CeresOptions options(false, false); - options.linearSolverType = ceres::DENSE_SCHUR; - BundleAdjustmentCeres bundle_adjustment_obj(options); - if(bundle_adjustment_obj.adjust(tinyScene, BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE)) + std::partial_sort(vec_count.begin(), vec_count.begin() + vec_count.size() / 2, vec_count.end()); + // const float thTrustPair = vec_count[vec_count.size() / 2]; + for (auto& relative_rotation_info : vec_relatives_R) { - // --> to debug: save relative pair geometry on disk - // std::ostringstream os; - // os << relative_pose_pair.first << "_" << relative_pose_pair.second << ".ply"; - // Save(tiny_scene, os.str(), ESfMData(STRUCTURE | EXTRINSICS)); - // - - const geometry::Pose3 poseI = tinyScene.getPose(*view_I).getTransform(); - const geometry::Pose3 poseJ = tinyScene.getPose(*view_J).getTransform(); - - const Mat3 R1 = poseI.rotation(); - const Mat3 R2 = poseJ.rotation(); - const Vec3 t1 = poseI.translation(); - const Vec3 t2 = poseJ.translation(); - // Compute relative motion and save it - Mat3 Rrel; - Vec3 trel; - relativeCameraMotion(R1, t1, R2, t2, &Rrel, &trel); - // Update found relative pose - relativePose_info.relativePose = Pose3(Rrel, -Rrel.transpose() * trel); + relative_rotation_info.weight = std::min(relative_rotation_info.weight, 1.f); } - } - #pragma omp critical - { - // Add the relative rotation to the relative 'rotation' pose graph - using namespace aliceVision::rotationAveraging; - vec_relatives_R.emplace_back( - relative_pose_pair.first, relative_pose_pair.second, - relativePose_info.relativePose.rotation(), relativePose_info.vec_inliers.size()); - } } - } // for all relative pose - - // Re-weight rotation in [0,1] - if (vec_relatives_R.size() > 1) - { - std::vector vec_count; - vec_count.reserve(vec_relatives_R.size()); - for(const auto & relative_rotation_info : vec_relatives_R) - { - vec_count.push_back(relative_rotation_info.weight); - } - std::partial_sort(vec_count.begin(), vec_count.begin() + vec_count.size() / 2, vec_count.end()); - // const float thTrustPair = vec_count[vec_count.size() / 2]; - for(auto & relative_rotation_info : vec_relatives_R) - { - relative_rotation_info.weight = std::min(relative_rotation_info.weight, 1.f); - } - } - // Log input graph to the HTML report - if (!_loggingFile.empty() && !_outputFolder.empty()) - { - // Log a relative view graph + // Log input graph to the HTML report + if (!_loggingFile.empty() && !_outputFolder.empty()) { - std::set set_ViewIds; - std::transform(_sfmData.getViews().begin(), _sfmData.getViews().end(), std::inserter(set_ViewIds, set_ViewIds.begin()), stl::RetrieveKey()); - graph::indexedGraph putativeGraph(set_ViewIds, getImagePairs(*_pairwiseMatches)); - graph::exportToGraphvizData((fs::path(_outputFolder) / "global_relative_rotation_view_graph.dot").string(), putativeGraph.g); - } + // Log a relative view graph + { + std::set set_ViewIds; + std::transform( + _sfmData.getViews().begin(), _sfmData.getViews().end(), std::inserter(set_ViewIds, set_ViewIds.begin()), stl::RetrieveKey()); + graph::indexedGraph putativeGraph(set_ViewIds, getImagePairs(*_pairwiseMatches)); + graph::exportToGraphvizData((fs::path(_outputFolder) / "global_relative_rotation_view_graph.dot").string(), putativeGraph.g); + } - // Log a relative pose graph - { - std::set set_pose_ids; - PairSet relative_pose_pairs; - for(const auto& relative_R : vec_relatives_R) - { - const Pair relative_pose_indices(relative_R.i, relative_R.j); - relative_pose_pairs.insert(relative_pose_indices); - set_pose_ids.insert(relative_R.i); - set_pose_ids.insert(relative_R.j); - } - const std::string sGraph_name = "global_relative_rotation_pose_graph"; - graph::indexedGraph putativeGraph(set_pose_ids, relative_pose_pairs); - graph::exportToGraphvizData((fs::path(_outputFolder) / (sGraph_name + ".dot")).string(), putativeGraph.g); - /* - using namespace htmlDocument; - std::ostringstream os; - - os << "
" << "global_relative_rotation_pose_graph" << "
" - << "\n"; - _htmlDocStream->pushInfo(os.str()); - */ + // Log a relative pose graph + { + std::set set_pose_ids; + PairSet relative_pose_pairs; + for (const auto& relative_R : vec_relatives_R) + { + const Pair relative_pose_indices(relative_R.i, relative_R.j); + relative_pose_pairs.insert(relative_pose_indices); + set_pose_ids.insert(relative_R.i); + set_pose_ids.insert(relative_R.j); + } + const std::string sGraph_name = "global_relative_rotation_pose_graph"; + graph::indexedGraph putativeGraph(set_pose_ids, relative_pose_pairs); + graph::exportToGraphvizData((fs::path(_outputFolder) / (sGraph_name + ".dot")).string(), putativeGraph.g); + /* + using namespace htmlDocument; + std::ostringstream os; + + os << "
" << "global_relative_rotation_pose_graph" << "
" + << "\n"; + _htmlDocStream->pushInfo(os.str()); + */ + } } - } } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.hpp b/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.hpp index 2c30b00b88..24754afe5f 100644 --- a/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.hpp +++ b/src/aliceVision/sfm/pipeline/global/ReconstructionEngine_globalSfM.hpp @@ -13,66 +13,61 @@ #include -namespace aliceVision{ -namespace sfm{ +namespace aliceVision { +namespace sfm { /// Global SfM Pipeline Reconstruction Engine. /// - Method: Global Fusion of Relative Motions. class ReconstructionEngine_globalSfM : public ReconstructionEngine { -public: + public: + ReconstructionEngine_globalSfM(const sfmData::SfMData& sfmData, const std::string& outDirectory, const std::string& loggingFile = ""); - ReconstructionEngine_globalSfM(const sfmData::SfMData& sfmData, - const std::string& outDirectory, - const std::string& loggingFile = ""); + ~ReconstructionEngine_globalSfM(); - ~ReconstructionEngine_globalSfM(); + void SetFeaturesProvider(feature::FeaturesPerView* featuresPerView); + void SetMatchesProvider(matching::PairwiseMatches* provider); - void SetFeaturesProvider(feature::FeaturesPerView* featuresPerView); - void SetMatchesProvider(matching::PairwiseMatches* provider); + void SetRotationAveragingMethod(ERotationAveragingMethod eRotationAveragingMethod); + void SetTranslationAveragingMethod(ETranslationAveragingMethod eTranslationAveragingMethod); - void SetRotationAveragingMethod(ERotationAveragingMethod eRotationAveragingMethod); - void SetTranslationAveragingMethod(ETranslationAveragingMethod eTranslationAveragingMethod); + void setLockAllIntrinsics(bool v) { _lockAllIntrinsics = v; } - void setLockAllIntrinsics(bool v) { _lockAllIntrinsics = v; } + virtual bool process(); - virtual bool process(); + protected: + /// Compute from relative rotations the global rotations of the camera poses + bool Compute_Global_Rotations(const aliceVision::rotationAveraging::RelativeRotations& vec_relatives_R, HashMap& map_globalR); -protected: - /// Compute from relative rotations the global rotations of the camera poses - bool Compute_Global_Rotations(const aliceVision::rotationAveraging::RelativeRotations& vec_relatives_R, - HashMap& map_globalR); + /// Compute/refine relative translations and compute global translations + bool Compute_Global_Translations(const HashMap& global_rotations, matching::PairwiseMatches& tripletWise_matches); - /// Compute/refine relative translations and compute global translations - bool Compute_Global_Translations(const HashMap& global_rotations, - matching::PairwiseMatches& tripletWise_matches); + /// Compute the initial structure of the scene + bool Compute_Initial_Structure(matching::PairwiseMatches& tripletWise_matches); - /// Compute the initial structure of the scene - bool Compute_Initial_Structure(matching::PairwiseMatches& tripletWise_matches); + /// Adjust the scene (& remove outliers) + bool Adjust(); - /// Adjust the scene (& remove outliers) - bool Adjust(); + private: + /// Compute relative rotations + void Compute_Relative_Rotations(aliceVision::rotationAveraging::RelativeRotations& vec_relatives_R); -private: - /// Compute relative rotations - void Compute_Relative_Rotations(aliceVision::rotationAveraging::RelativeRotations& vec_relatives_R); + // Logger + std::shared_ptr _htmlDocStream; + std::string _loggingFile; - // Logger - std::shared_ptr _htmlDocStream; - std::string _loggingFile; + // Parameter + ERotationAveragingMethod _eRotationAveragingMethod; + ETranslationAveragingMethod _eTranslationAveragingMethod; + bool _lockAllIntrinsics = false; + EFeatureConstraint _featureConstraint = EFeatureConstraint::BASIC; - // Parameter - ERotationAveragingMethod _eRotationAveragingMethod; - ETranslationAveragingMethod _eTranslationAveragingMethod; - bool _lockAllIntrinsics = false; - EFeatureConstraint _featureConstraint = EFeatureConstraint::BASIC; + // Data provider + feature::FeaturesPerView* _featuresPerView; + matching::PairwiseMatches* _pairwiseMatches; - // Data provider - feature::FeaturesPerView* _featuresPerView; - matching::PairwiseMatches* _pairwiseMatches; - - std::shared_ptr _normalizedFeaturesPerView; + std::shared_ptr _normalizedFeaturesPerView; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/TranslationTripletKernelACRansac.hpp b/src/aliceVision/sfm/pipeline/global/TranslationTripletKernelACRansac.hpp index 94506513b8..45bbf64f0b 100644 --- a/src/aliceVision/sfm/pipeline/global/TranslationTripletKernelACRansac.hpp +++ b/src/aliceVision/sfm/pipeline/global/TranslationTripletKernelACRansac.hpp @@ -19,155 +19,127 @@ namespace sfm { using namespace aliceVision::trifocal::kernel; /// AContrario Kernel to solve a translation triplet & structure problem -template -class TranslationTripletKernelACRansac - : robustEstimation::IRansacKernel +template +class TranslationTripletKernelACRansac : robustEstimation::IRansacKernel { -public: - using SolverT = SolverT_; - using ErrorT = SolverT_; - using ModelT = ModelT_; - - TranslationTripletKernelACRansac(const Mat& x1, - const Mat& x2, - const Mat& x3, - const std::vector& vec_KRi, - const Mat3& K, - const double ThresholdUpperBound) - : _x1(x1) - , _x2(x2) - , _x3(x3) - , _vecKR(vec_KRi) - , _K(K) - , _thresholdUpperBound(ThresholdUpperBound) - , _logalpha0(log10(M_PI)) - , _Kinv(K.inverse()) - { - // Normalize points by inverse(K) - robustEstimation::applyTransformationToPoints(_x1, _Kinv, &_x1n); - robustEstimation::applyTransformationToPoints(_x2, _Kinv, &_x2n); - robustEstimation::applyTransformationToPoints(_x3, _Kinv, &_x3n); - - _vecKR[0] = _Kinv * _vecKR[0]; - _vecKR[1] = _Kinv * _vecKR[1]; - _vecKR[2] = _Kinv * _vecKR[2]; - } - /** - * @brief Return the minimum number of required samples for the solver - * @return minimum number of required samples - */ - inline std::size_t getMinimumNbRequiredSamples() const override - { - return _kernelSolver.getMinimumNbRequiredSamples(); - } - - /** - * @brief Return the maximum number of models for the solver - * @return maximum number of models - */ - inline std::size_t getMaximumNbModels() const override - { - return _kernelSolver.getMaximumNbModels(); - } - - void fit(const std::vector& samples, std::vector& models) const override - { - - // create a model from the points - _kernelSolver.solve(buildSubsetMatrix(_x1n, samples), - buildSubsetMatrix(_x2n, samples), - buildSubsetMatrix(_x3n, samples), - _vecKR, models, _thresholdUpperBound); - } - - /** - * @brief error - * @param sample - * @param model - * @return - */ - double error(std::size_t sample, const ModelT_& model) const override - { - return _errorEstimator.error(model, _x1n.col(sample), _x2n.col(sample), _x3n.col(sample)); - } - - void errors(const ModelT_& model, std::vector& errors) const override - { - for(std::size_t sample = 0; sample < _x1n.cols(); ++sample) - errors[sample] = error(sample, model); - } - - std::size_t nbSamples() const override - { - return _x1n.cols(); - } - - void unnormalize(ModelT_& model) const override - { - // unnormalize model from the computed conditioning. - model.P1 = _K * model.P1; - model.P2 = _K * model.P2; - model.P3 = _K * model.P3; - } - - double logalpha0() const override - { - return _logalpha0; - } - - double errorVectorDimension() const override - { - return 2.0; - } - - Mat3 normalizer1() const override - { - return _Kinv; - } - - double thresholdNormalizer() const override - { - return 1.0; - } - - double unormalizeError(double val) const override - { - return std::sqrt(val) / _Kinv(0,0); - } - - std::size_t getMinimumNbRequiredSamplesLS() const - { - throw std::logic_error("TranslationTripletKernelACRansac cannot be used in LO_RANSAC."); - return 0; - } - - void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const - { - throw std::logic_error("TranslationTripletKernelACRansac cannot be used in LO_RANSAC."); - } - - void computeWeights(const ModelT& model, const std::vector& inliers, std::vector & weights, const double eps = 0.001) const - { - throw std::logic_error("TranslationTripletKernelACRansac cannot be used in LO_RANSAC."); - } - -private: - const Mat& _x1; - const Mat& _x2; - const Mat& _x3; - Mat _x1n; - Mat _x2n; - Mat _x3n; - const Mat3 _Kinv, _K; - const double _logalpha0; - const double _thresholdUpperBound; - std::vector _vecKR; - - /// two view solver - const SolverT _kernelSolver = SolverT(); - /// solver error estimation - const ErrorT _errorEstimator = ErrorT(); + public: + using SolverT = SolverT_; + using ErrorT = SolverT_; + using ModelT = ModelT_; + + TranslationTripletKernelACRansac(const Mat& x1, + const Mat& x2, + const Mat& x3, + const std::vector& vec_KRi, + const Mat3& K, + const double ThresholdUpperBound) + : _x1(x1), + _x2(x2), + _x3(x3), + _vecKR(vec_KRi), + _K(K), + _thresholdUpperBound(ThresholdUpperBound), + _logalpha0(log10(M_PI)), + _Kinv(K.inverse()) + { + // Normalize points by inverse(K) + robustEstimation::applyTransformationToPoints(_x1, _Kinv, &_x1n); + robustEstimation::applyTransformationToPoints(_x2, _Kinv, &_x2n); + robustEstimation::applyTransformationToPoints(_x3, _Kinv, &_x3n); + + _vecKR[0] = _Kinv * _vecKR[0]; + _vecKR[1] = _Kinv * _vecKR[1]; + _vecKR[2] = _Kinv * _vecKR[2]; + } + /** + * @brief Return the minimum number of required samples for the solver + * @return minimum number of required samples + */ + inline std::size_t getMinimumNbRequiredSamples() const override { return _kernelSolver.getMinimumNbRequiredSamples(); } + + /** + * @brief Return the maximum number of models for the solver + * @return maximum number of models + */ + inline std::size_t getMaximumNbModels() const override { return _kernelSolver.getMaximumNbModels(); } + + void fit(const std::vector& samples, std::vector& models) const override + { + // create a model from the points + _kernelSolver.solve( + buildSubsetMatrix(_x1n, samples), buildSubsetMatrix(_x2n, samples), buildSubsetMatrix(_x3n, samples), _vecKR, models, _thresholdUpperBound); + } + + /** + * @brief error + * @param sample + * @param model + * @return + */ + double error(std::size_t sample, const ModelT_& model) const override + { + return _errorEstimator.error(model, _x1n.col(sample), _x2n.col(sample), _x3n.col(sample)); + } + + void errors(const ModelT_& model, std::vector& errors) const override + { + for (std::size_t sample = 0; sample < _x1n.cols(); ++sample) + errors[sample] = error(sample, model); + } + + std::size_t nbSamples() const override { return _x1n.cols(); } + + void unnormalize(ModelT_& model) const override + { + // unnormalize model from the computed conditioning. + model.P1 = _K * model.P1; + model.P2 = _K * model.P2; + model.P3 = _K * model.P3; + } + + double logalpha0() const override { return _logalpha0; } + + double errorVectorDimension() const override { return 2.0; } + + Mat3 normalizer1() const override { return _Kinv; } + + double thresholdNormalizer() const override { return 1.0; } + + double unormalizeError(double val) const override { return std::sqrt(val) / _Kinv(0, 0); } + + std::size_t getMinimumNbRequiredSamplesLS() const + { + throw std::logic_error("TranslationTripletKernelACRansac cannot be used in LO_RANSAC."); + return 0; + } + + void fitLS(const std::vector& inliers, std::vector& models, const std::vector* weights = nullptr) const + { + throw std::logic_error("TranslationTripletKernelACRansac cannot be used in LO_RANSAC."); + } + + void computeWeights(const ModelT& model, const std::vector& inliers, std::vector& weights, const double eps = 0.001) const + { + throw std::logic_error("TranslationTripletKernelACRansac cannot be used in LO_RANSAC."); + } + + private: + const Mat& _x1; + const Mat& _x2; + const Mat& _x3; + Mat _x1n; + Mat _x2n; + Mat _x3n; + const Mat3 _Kinv, _K; + const double _logalpha0; + const double _thresholdUpperBound; + std::vector _vecKR; + + /// two view solver + const SolverT _kernelSolver = SolverT(); + /// solver error estimation + const ErrorT _errorEstimator = ErrorT(); }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/global/globalSfM_test.cpp b/src/aliceVision/sfm/pipeline/global/globalSfM_test.cpp index 654cc152c5..eef630fcd5 100644 --- a/src/aliceVision/sfm/pipeline/global/globalSfM_test.cpp +++ b/src/aliceVision/sfm/pipeline/global/globalSfM_test.cpp @@ -40,208 +40,196 @@ namespace fs = boost::filesystem; // - the desired number of poses are found. BOOST_AUTO_TEST_CASE(GLOBAL_SFM_RotationAveragingL2_TranslationAveragingL1) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const int nviews = 6; - const int npoints = 64; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 6; + const int npoints = 64; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); - ReconstructionEngine_globalSfM sfmEngine( - sfmData2, - "./", - "./Reconstruction_Report.html"); + ReconstructionEngine_globalSfM sfmEngine(sfmData2, "./", "./Reconstruction_Report.html"); - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - // Configure data provider (Features and Matches) - sfmEngine.SetFeaturesProvider(&featuresPerView); - sfmEngine.SetMatchesProvider(&pairwiseMatches); + // Configure data provider (Features and Matches) + sfmEngine.SetFeaturesProvider(&featuresPerView); + sfmEngine.SetMatchesProvider(&pairwiseMatches); - // Configure reconstruction parameters - sfmEngine.setLockAllIntrinsics(true); + // Configure reconstruction parameters + sfmEngine.setLockAllIntrinsics(true); - // Configure motion averaging method - sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L2); - sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_L1); + // Configure motion averaging method + sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L2); + sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_L1); - BOOST_CHECK (sfmEngine.process()); + BOOST_CHECK(sfmEngine.process()); - const double residual = RMSE(sfmEngine.getSfMData()); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK(residual < 0.5); - BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); - BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); + const double residual = RMSE(sfmEngine.getSfMData()); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK(residual < 0.5); + BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); + BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); } BOOST_AUTO_TEST_CASE(GLOBAL_SFM_RotationAveragingL1_TranslationAveragingL1) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const int nviews = 6; - const int npoints = 64; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 6; + const int npoints = 64; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); - ReconstructionEngine_globalSfM sfmEngine( - sfmData2, - "./", - "./Reconstruction_Report.html"); + ReconstructionEngine_globalSfM sfmEngine(sfmData2, "./", "./Reconstruction_Report.html"); - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - // Configure data provider (Features and Matches) - sfmEngine.SetFeaturesProvider(&featuresPerView); - sfmEngine.SetMatchesProvider(&pairwiseMatches); + // Configure data provider (Features and Matches) + sfmEngine.SetFeaturesProvider(&featuresPerView); + sfmEngine.SetMatchesProvider(&pairwiseMatches); - // Configure reconstruction parameters - sfmEngine.setLockAllIntrinsics(true); + // Configure reconstruction parameters + sfmEngine.setLockAllIntrinsics(true); - // Configure motion averaging method - sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L1); - sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_L1); + // Configure motion averaging method + sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L1); + sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_L1); - BOOST_CHECK (sfmEngine.process()); + BOOST_CHECK(sfmEngine.process()); - const double residual = RMSE(sfmEngine.getSfMData()); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK(residual < 0.5); - BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); - BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); + const double residual = RMSE(sfmEngine.getSfMData()); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK(residual < 0.5); + BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); + BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); } BOOST_AUTO_TEST_CASE(GLOBAL_SFM_RotationAveragingL2_TranslationAveragingL2_Chordal) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const int nviews = 6; - const int npoints = 64; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 6; + const int npoints = 64; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); - ReconstructionEngine_globalSfM sfmEngine( - sfmData2, - "./", - "./Reconstruction_Report.html"); + ReconstructionEngine_globalSfM sfmEngine(sfmData2, "./", "./Reconstruction_Report.html"); - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - // Configure data provider (Features and Matches) - sfmEngine.SetFeaturesProvider(&featuresPerView); - sfmEngine.SetMatchesProvider(&pairwiseMatches); + // Configure data provider (Features and Matches) + sfmEngine.SetFeaturesProvider(&featuresPerView); + sfmEngine.SetMatchesProvider(&pairwiseMatches); - // Configure reconstruction parameters - sfmEngine.setLockAllIntrinsics(true); + // Configure reconstruction parameters + sfmEngine.setLockAllIntrinsics(true); - // Configure motion averaging method - sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L2); - sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL); + // Configure motion averaging method + sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L2); + sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL); - BOOST_CHECK (sfmEngine.process()); + BOOST_CHECK(sfmEngine.process()); - const double residual = RMSE(sfmEngine.getSfMData()); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK(residual < 0.5); - BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); - BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); + const double residual = RMSE(sfmEngine.getSfMData()); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK(residual < 0.5); + BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); + BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); } BOOST_AUTO_TEST_CASE(GLOBAL_SFM_RotationAveragingL2_TranslationAveragingSoftL1) { - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const int nviews = 6; - const int npoints = 64; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + const int nviews = 6; + const int npoints = 64; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); - ReconstructionEngine_globalSfM sfmEngine( - sfmData2, - "./", - "./Reconstruction_Report.html"); + ReconstructionEngine_globalSfM sfmEngine(sfmData2, "./", "./Reconstruction_Report.html"); - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - // Configure data provider (Features and Matches) - sfmEngine.SetFeaturesProvider(&featuresPerView); - sfmEngine.SetMatchesProvider(&pairwiseMatches); + // Configure data provider (Features and Matches) + sfmEngine.SetFeaturesProvider(&featuresPerView); + sfmEngine.SetMatchesProvider(&pairwiseMatches); - // Configure reconstruction parameters - sfmEngine.setLockAllIntrinsics(true); + // Configure reconstruction parameters + sfmEngine.setLockAllIntrinsics(true); - // Configure motion averaging method - sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L2); - sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_SOFTL1); + // Configure motion averaging method + sfmEngine.SetRotationAveragingMethod(ROTATION_AVERAGING_L2); + sfmEngine.SetTranslationAveragingMethod(TRANSLATION_AVERAGING_SOFTL1); - BOOST_CHECK (sfmEngine.process()); + BOOST_CHECK(sfmEngine.process()); - const double residual = RMSE(sfmEngine.getSfMData()); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK(residual < 0.5); - BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); - BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); + const double residual = RMSE(sfmEngine.getSfMData()); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK(residual < 0.5); + BOOST_CHECK(sfmEngine.getSfMData().getPoses().size() == nviews); + BOOST_CHECK(sfmEngine.getSfMData().getLandmarks().size() == npoints); } diff --git a/src/aliceVision/sfm/pipeline/global/reindexGlobalSfM.hpp b/src/aliceVision/sfm/pipeline/global/reindexGlobalSfM.hpp index 498a014ed8..5c2887e092 100644 --- a/src/aliceVision/sfm/pipeline/global/reindexGlobalSfM.hpp +++ b/src/aliceVision/sfm/pipeline/global/reindexGlobalSfM.hpp @@ -8,7 +8,7 @@ #pragma once namespace aliceVision { -namespace sfm{ +namespace sfm { /// association of Ids to a contiguous set of Ids template @@ -16,35 +16,35 @@ void reindex(const IterablePairs& pairs, HashMap& _reindexForward, HashMap& _reindexBackward) { - typedef std::pair PairT; + typedef std::pair PairT; - // get an unique set of Ids - std::set _uniqueId; + // get an unique set of Ids + std::set _uniqueId; - for(typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) - { - _uniqueId.insert(iter->first); - _uniqueId.insert(iter->second); - } - - // build the Forward and Backward mapping - for(typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) - { - if(_reindexForward.find(iter->first) == _reindexForward.end()) + for (typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) { - const size_t dist = std::distance(_uniqueId.begin(), _uniqueId.find(iter->first)); - _reindexForward[iter->first] = dist; - _reindexBackward[dist] = iter->first; + _uniqueId.insert(iter->first); + _uniqueId.insert(iter->second); } - if(_reindexForward.find(iter->second) == _reindexForward.end()) + // build the Forward and Backward mapping + for (typename IterablePairs::const_iterator iter = pairs.begin(); iter != pairs.end(); ++iter) { - const size_t dist = std::distance(_uniqueId.begin(), _uniqueId.find(iter->second)); - _reindexForward[iter->second] = dist; - _reindexBackward[dist] = iter->second; + if (_reindexForward.find(iter->first) == _reindexForward.end()) + { + const size_t dist = std::distance(_uniqueId.begin(), _uniqueId.find(iter->first)); + _reindexForward[iter->first] = dist; + _reindexBackward[dist] = iter->first; + } + + if (_reindexForward.find(iter->second) == _reindexForward.end()) + { + const size_t dist = std::distance(_uniqueId.begin(), _uniqueId.find(iter->second)); + _reindexForward[iter->second] = dist; + _reindexBackward[dist] = iter->second; + } } - } } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.cpp b/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.cpp index 958f9dd8db..d3a80fb023 100644 --- a/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.cpp +++ b/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.cpp @@ -25,141 +25,159 @@ namespace sfm { bool SfMLocalizer::Localize(const Pair& imageSize, const camera::IntrinsicBase* optionalIntrinsics, - std::mt19937 &randomNumberGenerator, + std::mt19937& randomNumberGenerator, ImageLocalizerMatchData& resectionData, geometry::Pose3& pose, robustEstimation::ERobustEstimator estimator) { - // compute the camera pose (resectioning) - - Mat34 P; - resectionData.vec_inliers.clear(); - - // setup the admissible upper bound residual error - const double precision = - resectionData.error_max == std::numeric_limits::infinity() ? - std::numeric_limits::infinity() : - resectionData.error_max; - - std::size_t minimumSamples = 0; - const camera::Pinhole* pinholeCam = dynamic_cast(optionalIntrinsics); - - if(pinholeCam == nullptr || !pinholeCam->isValid()) - { - // classic resection (try to compute the entire P matrix) - using SolverT = multiview::resection::Resection6PSolver; - using KernelT = multiview::ResectionKernel; - - const KernelT kernel(resectionData.pt2D, resectionData.pt3D, imageSize.first, imageSize.second); - - minimumSamples = kernel.getMinimumNbRequiredSamples(); - - // robust estimation of the Projection matrix and its precision - robustEstimation::Mat34Model model; - const std::pair ACRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, resectionData.vec_inliers, resectionData.max_iteration, &model, precision); - P = model.getMatrix(); - // update the upper bound precision of the model found by AC-RANSAC - resectionData.error_max = ACRansacOut.first; - } - else - { - // undistort the points if the camera has a distortion model - Mat pt2Dundistorted; - const bool hasDistortion = pinholeCam->hasDistortion(); - if(hasDistortion) - { - const std::size_t numPts = resectionData.pt2D.cols(); - pt2Dundistorted = Mat2X(2, numPts); - for(std::size_t iPoint = 0; iPoint < numPts; ++iPoint) - { - pt2Dundistorted.col(iPoint) = pinholeCam->get_ud_pixel(resectionData.pt2D.col(iPoint)); - } - } + // compute the camera pose (resectioning) + + Mat34 P; + resectionData.vec_inliers.clear(); + + // setup the admissible upper bound residual error + const double precision = + resectionData.error_max == std::numeric_limits::infinity() ? std::numeric_limits::infinity() : resectionData.error_max; - switch(estimator) + std::size_t minimumSamples = 0; + const camera::Pinhole* pinholeCam = dynamic_cast(optionalIntrinsics); + + if (pinholeCam == nullptr || !pinholeCam->isValid()) { - case robustEstimation::ERobustEstimator::ACRANSAC: - { - // since K calibration matrix is known, compute only [R|t] - using SolverT = multiview::resection::P3PSolver; - using KernelT = multiview::ResectionKernel_K; + // classic resection (try to compute the entire P matrix) + using SolverT = multiview::resection::Resection6PSolver; + using KernelT = multiview::ResectionKernel; - // otherwise we just pass the input points - const KernelT kernel = KernelT(hasDistortion ? pt2Dundistorted : resectionData.pt2D, resectionData.pt3D, pinholeCam->K()); + const KernelT kernel(resectionData.pt2D, resectionData.pt3D, imageSize.first, imageSize.second); minimumSamples = kernel.getMinimumNbRequiredSamples(); // robust estimation of the Projection matrix and its precision robustEstimation::Mat34Model model; - const std::pair ACRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, resectionData.vec_inliers, resectionData.max_iteration, &model, precision); - + const std::pair ACRansacOut = + robustEstimation::ACRANSAC(kernel, randomNumberGenerator, resectionData.vec_inliers, resectionData.max_iteration, &model, precision); P = model.getMatrix(); - // update the upper bound precision of the model found by AC-RANSAC resectionData.error_max = ACRansacOut.first; - break; - } - - case robustEstimation::ERobustEstimator::LORANSAC: - { - - // just a safeguard - if(resectionData.error_max == std::numeric_limits::infinity()) + } + else + { + // undistort the points if the camera has a distortion model + Mat pt2Dundistorted; + const bool hasDistortion = pinholeCam->hasDistortion(); + if (hasDistortion) { - // switch to a default value - resectionData.error_max = 4.0; - ALICEVISION_LOG_DEBUG("LORansac: error was set to infinity, a default value of " - << resectionData.error_max << " is going to be used"); + const std::size_t numPts = resectionData.pt2D.cols(); + pt2Dundistorted = Mat2X(2, numPts); + for (std::size_t iPoint = 0; iPoint < numPts; ++iPoint) + { + pt2Dundistorted.col(iPoint) = pinholeCam->get_ud_pixel(resectionData.pt2D.col(iPoint)); + } } - // use the P3P solver for generating the model - using SolverT = multiview::resection::P3PSolver; - using SolverLsT = multiview::resection::Resection6PSolver; - - // use the six point algorithm as Least square solution to refine the model - using KernelT = multiview::ResectionKernel_K; - - // otherwise we just pass the input points - const KernelT kernel = KernelT(hasDistortion ? pt2Dundistorted : resectionData.pt2D, resectionData.pt3D, pinholeCam->K()); - - minimumSamples = kernel.getMinimumNbRequiredSamples(); - - // this is just stupid and ugly, the threshold should be always give as pixel - // value, the scorer should be not aware of the fact that we treat squared errors - // and normalization inside the kernel - // @todo refactor, maybe move scorer directly inside the kernel - const double threshold = resectionData.error_max * resectionData.error_max * (kernel.thresholdNormalizer() * kernel.thresholdNormalizer()); - robustEstimation::ScoreEvaluator scorer(threshold); - - const robustEstimation::Mat34Model model = robustEstimation::LO_RANSAC(kernel, scorer, randomNumberGenerator, &resectionData.vec_inliers); - P = model.getMatrix(); + switch (estimator) + { + case robustEstimation::ERobustEstimator::ACRANSAC: + { + // since K calibration matrix is known, compute only [R|t] + using SolverT = multiview::resection::P3PSolver; + using KernelT = multiview::ResectionKernel_K; + + // otherwise we just pass the input points + const KernelT kernel = KernelT(hasDistortion ? pt2Dundistorted : resectionData.pt2D, resectionData.pt3D, pinholeCam->K()); + + minimumSamples = kernel.getMinimumNbRequiredSamples(); + + // robust estimation of the Projection matrix and its precision + robustEstimation::Mat34Model model; + const std::pair ACRansacOut = robustEstimation::ACRANSAC( + kernel, randomNumberGenerator, resectionData.vec_inliers, resectionData.max_iteration, &model, precision); + + P = model.getMatrix(); + + // update the upper bound precision of the model found by AC-RANSAC + resectionData.error_max = ACRansacOut.first; + break; + } + + case robustEstimation::ERobustEstimator::LORANSAC: + { + // just a safeguard + if (resectionData.error_max == std::numeric_limits::infinity()) + { + // switch to a default value + resectionData.error_max = 4.0; + ALICEVISION_LOG_DEBUG("LORansac: error was set to infinity, a default value of " << resectionData.error_max + << " is going to be used"); + } + + // use the P3P solver for generating the model + using SolverT = multiview::resection::P3PSolver; + using SolverLsT = multiview::resection::Resection6PSolver; + + // use the six point algorithm as Least square solution to refine the model + using KernelT = multiview::ResectionKernel_K; + + // otherwise we just pass the input points + const KernelT kernel = KernelT(hasDistortion ? pt2Dundistorted : resectionData.pt2D, resectionData.pt3D, pinholeCam->K()); + + minimumSamples = kernel.getMinimumNbRequiredSamples(); + + // this is just stupid and ugly, the threshold should be always give as pixel + // value, the scorer should be not aware of the fact that we treat squared errors + // and normalization inside the kernel + // @todo refactor, maybe move scorer directly inside the kernel + const double threshold = + resectionData.error_max * resectionData.error_max * (kernel.thresholdNormalizer() * kernel.thresholdNormalizer()); + robustEstimation::ScoreEvaluator scorer(threshold); + + const robustEstimation::Mat34Model model = + robustEstimation::LO_RANSAC(kernel, scorer, randomNumberGenerator, &resectionData.vec_inliers); + P = model.getMatrix(); + + break; + } + + default: + throw std::runtime_error("[SfMLocalizer::localize] Only ACRansac and LORansac are supported!"); + } + } - break; - } + const bool resection = matching::hasStrongSupport(resectionData.vec_inliers, resectionData.vec_descType, minimumSamples); - default: - throw std::runtime_error("[SfMLocalizer::localize] Only ACRansac and LORansac are supported!"); + if (resection) + { + resectionData.projection_matrix = P; + Mat3 K, R; + Vec3 t; + KRt_from_P(P, K, R, t); + pose = geometry::Pose3(R, -R.transpose() * t); } - } - - const bool resection = matching::hasStrongSupport(resectionData.vec_inliers, resectionData.vec_descType, minimumSamples); - - if(resection) - { - resectionData.projection_matrix = P; - Mat3 K, R; - Vec3 t; - KRt_from_P(P, K, R, t); - pose = geometry::Pose3(R, -R.transpose() * t); - } - - ALICEVISION_LOG_TRACE("Robust Resection information:\n" - "\t- resection status: " << resection << "\n" - "\t- threshold (error max): " << resectionData.error_max << "\n" - "\t- # points used for resection: " << resectionData.pt2D.cols() << "\n" - "\t- # points validated by robust resection: " << resectionData.vec_inliers.size()); - - return resection; + + ALICEVISION_LOG_TRACE("Robust Resection information:\n" + "\t- resection status: " + << resection + << "\n" + "\t- threshold (error max): " + << resectionData.error_max + << "\n" + "\t- # points used for resection: " + << resectionData.pt2D.cols() + << "\n" + "\t- # points validated by robust resection: " + << resectionData.vec_inliers.size()); + + return resection; } bool SfMLocalizer::RefinePose(camera::IntrinsicBase* intrinsics, @@ -168,51 +186,51 @@ bool SfMLocalizer::RefinePose(camera::IntrinsicBase* intrinsics, bool refinePose, bool refineIntrinsic) { - // Setup a tiny SfM scene with the corresponding 2D-3D data - sfmData::SfMData tinyScene; + // Setup a tiny SfM scene with the corresponding 2D-3D data + sfmData::SfMData tinyScene; - // view - std::shared_ptr view = std::make_shared("", 0, 0, 0); - tinyScene.getViews().insert(std::make_pair(0, view)); + // view + std::shared_ptr view = std::make_shared("", 0, 0, 0); + tinyScene.getViews().insert(std::make_pair(0, view)); - // pose - tinyScene.setPose(*view, sfmData::CameraPose(pose)); + // pose + tinyScene.setPose(*view, sfmData::CameraPose(pose)); - // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) - std::shared_ptr localIntrinsics(intrinsics->clone()); - tinyScene.getIntrinsics().emplace(0, localIntrinsics); + // intrinsic (the shared_ptr does not take the ownership, will not release the input pointer) + std::shared_ptr localIntrinsics(intrinsics->clone()); + tinyScene.getIntrinsics().emplace(0, localIntrinsics); - const double unknownScale = 0.0; - // structure data (2D-3D correspondences) - for(std::size_t i = 0; i < matchingData.vec_inliers.size(); ++i) - { - const std::size_t idx = matchingData.vec_inliers[i]; - sfmData::Landmark landmark; - landmark.X = matchingData.pt3D.col(idx); - landmark.observations[0] = sfmData::Observation(matchingData.pt2D.col(idx), UndefinedIndexT, unknownScale); // TODO-SCALE - tinyScene.getLandmarks()[i] = std::move(landmark); - } + const double unknownScale = 0.0; + // structure data (2D-3D correspondences) + for (std::size_t i = 0; i < matchingData.vec_inliers.size(); ++i) + { + const std::size_t idx = matchingData.vec_inliers[i]; + sfmData::Landmark landmark; + landmark.X = matchingData.pt3D.col(idx); + landmark.observations[0] = sfmData::Observation(matchingData.pt2D.col(idx), UndefinedIndexT, unknownScale); // TODO-SCALE + tinyScene.getLandmarks()[i] = std::move(landmark); + } - BundleAdjustmentCeres BA; - BundleAdjustment::ERefineOptions refineOptions = BundleAdjustment::REFINE_NONE; + BundleAdjustmentCeres BA; + BundleAdjustment::ERefineOptions refineOptions = BundleAdjustment::REFINE_NONE; - if(refinePose) - refineOptions |= BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION; - if(refineIntrinsic) - refineOptions |= BundleAdjustment::REFINE_INTRINSICS_ALL; + if (refinePose) + refineOptions |= BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION; + if (refineIntrinsic) + refineOptions |= BundleAdjustment::REFINE_INTRINSICS_ALL; - const bool success = BA.adjust(tinyScene, refineOptions); + const bool success = BA.adjust(tinyScene, refineOptions); - if(!success) - return false; + if (!success) + return false; - pose = tinyScene.getPose(*view).getTransform(); + pose = tinyScene.getPose(*view).getTransform(); - if(refineIntrinsic) - intrinsics->assign(*localIntrinsics); + if (refineIntrinsic) + intrinsics->assign(*localIntrinsics); - return true; + return true; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.hpp b/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.hpp index 579ea407c1..5452af3af3 100644 --- a/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.hpp +++ b/src/aliceVision/sfm/pipeline/localization/SfMLocalizer.hpp @@ -20,71 +20,70 @@ namespace sfm { struct ImageLocalizerMatchData { - /// 3x4 matrix represented the estimated camera pose. - Mat34 projection_matrix; - - /// 3xN matrix storing all the 3D points whose images have been found - /// in the query view through the feature matching procedure. - Mat pt3D; - - /// 2xN matrix storing all 2D distorted points associated to 3D points (pt3D) - /// found through the feature matching procedure. - Mat pt2D; - - /// pt2D and pt3D have the same number of columns. - /// Index mask for both pt3D and pt2D whose elements - /// represent the column indices of inliers in pt2D - /// and pt3D. - std::vector vec_inliers; + /// 3x4 matrix represented the estimated camera pose. + Mat34 projection_matrix; - std::vector vec_descType; - - /// Upper bound pixel(s) tolerance for residual errors - double error_max = std::numeric_limits::infinity(); - size_t max_iteration = 4096; + /// 3xN matrix storing all the 3D points whose images have been found + /// in the query view through the feature matching procedure. + Mat pt3D; + + /// 2xN matrix storing all 2D distorted points associated to 3D points (pt3D) + /// found through the feature matching procedure. + Mat pt2D; + + /// pt2D and pt3D have the same number of columns. + /// Index mask for both pt3D and pt2D whose elements + /// represent the column indices of inliers in pt2D + /// and pt3D. + std::vector vec_inliers; + + std::vector vec_descType; + + /// Upper bound pixel(s) tolerance for residual errors + double error_max = std::numeric_limits::infinity(); + size_t max_iteration = 4096; }; class SfMLocalizer { -public: - virtual ~SfMLocalizer() = default; + public: + virtual ~SfMLocalizer() = default; - /** - * @brief Try to localize an image from known 2D-3D matches - * - * @param[in] imageSize the w,h image size - * @param[in] optionalIntrinsics camera intrinsic if known (else nullptr) - * @param[in,out] resectionData matching data (with filled 2D-3D correspondences). - * The 2D points are supposed to be the original distorted image points - * @param[out] pose found pose - * @param[in] estimator The type of robust estimator to use. The only supported - * frameworks are ERobustEstimator::ACRANSAC and ERobustEstimator::LORANSAC. - * @return True if a putative pose has been estimated - */ - static bool Localize(const Pair& imageSize, - const camera::IntrinsicBase* optionalIntrinsics, - std::mt19937 &randomNumberGenerator, - ImageLocalizerMatchData& resectionData, - geometry::Pose3& pose, - robustEstimation::ERobustEstimator estimator = robustEstimation::ERobustEstimator::ACRANSAC); - - /** - * @brief Refine a pose according 2D-3D matching & camera model data - * - * @param[in,out] intrinsics Camera model - * @param[in,out] pose Camera pose - * @param[in] matchingData Corresponding 2D-3D data - * @param[in] refinePose tell if pose must be refined - * @param[in] refineIntrinsic tell if intrinsics must be refined - * @return True if the refinement decreased the RMSE pixel residual error - */ - static bool RefinePose(camera::IntrinsicBase* intrinsics, + /** + * @brief Try to localize an image from known 2D-3D matches + * + * @param[in] imageSize the w,h image size + * @param[in] optionalIntrinsics camera intrinsic if known (else nullptr) + * @param[in,out] resectionData matching data (with filled 2D-3D correspondences). + * The 2D points are supposed to be the original distorted image points + * @param[out] pose found pose + * @param[in] estimator The type of robust estimator to use. The only supported + * frameworks are ERobustEstimator::ACRANSAC and ERobustEstimator::LORANSAC. + * @return True if a putative pose has been estimated + */ + static bool Localize(const Pair& imageSize, + const camera::IntrinsicBase* optionalIntrinsics, + std::mt19937& randomNumberGenerator, + ImageLocalizerMatchData& resectionData, geometry::Pose3& pose, - const ImageLocalizerMatchData& matchingData, - bool refinePose, - bool refineIntrinsic); -}; + robustEstimation::ERobustEstimator estimator = robustEstimation::ERobustEstimator::ACRANSAC); + /** + * @brief Refine a pose according 2D-3D matching & camera model data + * + * @param[in,out] intrinsics Camera model + * @param[in,out] pose Camera pose + * @param[in] matchingData Corresponding 2D-3D data + * @param[in] refinePose tell if pose must be refined + * @param[in] refineIntrinsic tell if intrinsics must be refined + * @return True if the refinement decreased the RMSE pixel residual error + */ + static bool RefinePose(camera::IntrinsicBase* intrinsics, + geometry::Pose3& pose, + const ImageLocalizerMatchData& matchingData, + bool refinePose, + bool refineIntrinsic); +}; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/pairwiseMatchesIO.hpp b/src/aliceVision/sfm/pipeline/pairwiseMatchesIO.hpp index 71da3f46df..64b77f3fc5 100644 --- a/src/aliceVision/sfm/pipeline/pairwiseMatchesIO.hpp +++ b/src/aliceVision/sfm/pipeline/pairwiseMatchesIO.hpp @@ -29,43 +29,42 @@ namespace sfm { * @param[in] maxNbMatches Maximum number of matches per image pair (and per feature type), 0 = no limit * @param[in] useOnlyMatchesFromFolder If enabled, don't use sfmData matches folders */ -inline bool loadPairwiseMatches( - matching::PairwiseMatches& out_pairwiseMatches, - const sfmData::SfMData& sfmData, - const std::vector& folders, - const std::vector& descTypes, - const int maxNbMatches = 0, - const int minNbMatches = 0, - bool useOnlyMatchesFromFolder = false) +inline bool loadPairwiseMatches(matching::PairwiseMatches& out_pairwiseMatches, + const sfmData::SfMData& sfmData, + const std::vector& folders, + const std::vector& descTypes, + const int maxNbMatches = 0, + const int minNbMatches = 0, + bool useOnlyMatchesFromFolder = false) { - std::vector matchesFolders; + std::vector matchesFolders; - ALICEVISION_LOG_DEBUG("List of provided match folders:"); - for (auto it = folders.begin(); it != folders.end(); ++it) - ALICEVISION_LOG_DEBUG("\t - " << *it); + ALICEVISION_LOG_DEBUG("List of provided match folders:"); + for (auto it = folders.begin(); it != folders.end(); ++it) + ALICEVISION_LOG_DEBUG("\t - " << *it); - if(!useOnlyMatchesFromFolder) - matchesFolders = sfmData.getMatchesFolders(); - else - ALICEVISION_LOG_DEBUG("Load only matches from given folder."); + if (!useOnlyMatchesFromFolder) + matchesFolders = sfmData.getMatchesFolders(); + else + ALICEVISION_LOG_DEBUG("Load only matches from given folder."); - matchesFolders.insert(matchesFolders.end(), folders.begin(), folders.end()); + matchesFolders.insert(matchesFolders.end(), folders.begin(), folders.end()); - ALICEVISION_LOG_DEBUG("List of match folders to load:"); - for (auto it = matchesFolders.begin(); it != matchesFolders.end(); ++it) - ALICEVISION_LOG_DEBUG("\t - " << *it); + ALICEVISION_LOG_DEBUG("List of match folders to load:"); + for (auto it = matchesFolders.begin(); it != matchesFolders.end(); ++it) + ALICEVISION_LOG_DEBUG("\t - " << *it); - ALICEVISION_LOG_DEBUG("Loading matches"); - if (!matching::Load(out_pairwiseMatches, sfmData.getViewsKeys(), matchesFolders, descTypes, maxNbMatches, minNbMatches)) - { - std::stringstream ss("Unable to read the matches file(s) from:\n"); - for(const std::string& folder : matchesFolders) - ss << "\t- " << folder << "\n"; - ALICEVISION_LOG_WARNING(ss.str()); - return false; - } - return true; + ALICEVISION_LOG_DEBUG("Loading matches"); + if (!matching::Load(out_pairwiseMatches, sfmData.getViewsKeys(), matchesFolders, descTypes, maxNbMatches, minNbMatches)) + { + std::stringstream ss("Unable to read the matches file(s) from:\n"); + for (const std::string& folder : matchesFolders) + ss << "\t- " << folder << "\n"; + ALICEVISION_LOG_WARNING(ss.str()); + return false; + } + return true; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.cpp b/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.cpp index 76adcf38bd..bfbb85ab95 100644 --- a/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.cpp +++ b/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.cpp @@ -34,11 +34,10 @@ #include - #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif namespace aliceVision { @@ -49,50 +48,46 @@ using namespace aliceVision::geometry; using namespace aliceVision::feature; using namespace aliceVision::sfmData; -bool robustRelativeRotation_fromE( - const Mat3 & K1, const Mat3 & K2, - const Mat & x1, const Mat & x2, - const std::pair & size_ima1, - const std::pair & size_ima2, - std::mt19937 &randomNumberGenerator, - RelativePoseInfo & relativePose_info, - const size_t max_iteration_count) +bool robustRelativeRotation_fromE(const Mat3& K1, + const Mat3& K2, + const Mat& x1, + const Mat& x2, + const std::pair& size_ima1, + const std::pair& size_ima2, + std::mt19937& randomNumberGenerator, + RelativePoseInfo& relativePose_info, + const size_t max_iteration_count) { // Use the 5 point solver to estimate E // Define the AContrario adaptor - using KernelType = multiview::RelativePoseKernel_K< - multiview::relativePose::Essential5PSolver, - multiview::relativePose::FundamentalSymmetricEpipolarDistanceError, - robustEstimation::Mat3Model>; + using KernelType = multiview::RelativePoseKernel_K; - KernelType kernel(x1, size_ima1.first, size_ima1.second, - x2, size_ima2.first, size_ima2.second, K1, K2); + KernelType kernel(x1, size_ima1.first, size_ima1.second, x2, size_ima2.first, size_ima2.second, K1, K2); // Robustly estimation of the Essential matrix and its precision robustEstimation::Mat3Model model; - const std::pair acRansacOut = robustEstimation::ACRANSAC(kernel, randomNumberGenerator, - relativePose_info.vec_inliers, - max_iteration_count, &model, - relativePose_info.initial_residual_tolerance); + const std::pair acRansacOut = robustEstimation::ACRANSAC( + kernel, randomNumberGenerator, relativePose_info.vec_inliers, max_iteration_count, &model, relativePose_info.initial_residual_tolerance); relativePose_info.essential_matrix = model.getMatrix(); relativePose_info.found_residual_precision = acRansacOut.first; - if (relativePose_info.vec_inliers.size() < kernel.getMinimumNbRequiredSamples() * 2.5); // consider ALICEVISION_MINIMUM_SAMPLES_COEF + if (relativePose_info.vec_inliers.size() < kernel.getMinimumNbRequiredSamples() * 2.5) + ; // consider ALICEVISION_MINIMUM_SAMPLES_COEF { - ALICEVISION_LOG_INFO("robustRelativePose: no sufficient coverage (the model does not support enough samples): " - << relativePose_info.vec_inliers.size()); - return false; // no sufficient coverage (the model does not support enough samples) + ALICEVISION_LOG_INFO( + "robustRelativePose: no sufficient coverage (the model does not support enough samples): " << relativePose_info.vec_inliers.size()); + return false; // no sufficient coverage (the model does not support enough samples) } // estimation of the relative poses Mat3 R; Vec3 t; - if (!estimate_Rt_fromE( - K1, K2, x1, x2, - relativePose_info.essential_matrix, relativePose_info.vec_inliers, R, t)) + if (!estimate_Rt_fromE(K1, K2, x1, x2, relativePose_info.essential_matrix, relativePose_info.vec_inliers, R, t)) { ALICEVISION_LOG_INFO("robustRelativePose: cannot find a valid [R|t] couple that makes the inliers in front of the camera."); - return false; // cannot find a valid [R|t] couple that makes the inliers in front of the camera. + return false; // cannot find a valid [R|t] couple that makes the inliers in front of the camera. } t = Vec3(0.0, 0.0, 0.0); @@ -101,14 +96,13 @@ bool robustRelativeRotation_fromE( return true; } - /** * @brief Decompose a homography given known calibration matrices, assuming a pure rotation between the two views. * It is supposed that \f$ x_2 \sim H x_1 \f$ with \f$ H = K_2 * R * K_1^{-1} \f$ * @param[in] homography 3x3 homography matrix H. * @return The 3x3 rotation matrix corresponding to the pure rotation between the views. */ -aliceVision::Mat3 decomposePureRotationHomography(const Mat3 &homography) +aliceVision::Mat3 decomposePureRotationHomography(const Mat3& homography) { // compute the scale factor lambda that makes det(lambda*G) = 1 const auto lambda = std::pow(1 / homography.determinant(), 1 / 3); @@ -118,7 +112,7 @@ aliceVision::Mat3 decomposePureRotationHomography(const Mat3 &homography) // compute and return the closest rotation matrix Eigen::JacobiSVD usv(rotation, Eigen::ComputeFullU | Eigen::ComputeFullV); - const auto &u = usv.matrixU(); + const auto& u = usv.matrixU(); const auto vt = usv.matrixV().transpose(); return u * vt; @@ -134,29 +128,29 @@ aliceVision::Mat3 decomposePureRotationHomography(const Mat3 &homography) * @param[out] vec_inliers The inliers satisfying the homography as a list of indices. * @return the status of the estimation. */ -aliceVision::EstimationStatus robustHomographyEstimationAC(const Mat2X &x1, - const Mat2X &x2, - const std::pair &imgSize1, - const std::pair &imgSize2, - std::mt19937 &randomNumberGenerator, - Mat3 &H, - std::vector &vec_inliers) +aliceVision::EstimationStatus robustHomographyEstimationAC(const Mat2X& x1, + const Mat2X& x2, + const std::pair& imgSize1, + const std::pair& imgSize2, + std::mt19937& randomNumberGenerator, + Mat3& H, + std::vector& vec_inliers) { - using KernelType = multiview::RelativePoseKernel< - multiview::relativePose::Homography4PSolver, - multiview::relativePose::HomographyAsymmetricError, - multiview::UnnormalizerI, - robustEstimation::Mat3Model>; - - KernelType kernel(x1, imgSize1.first, imgSize1.second, x2, imgSize2.first, imgSize2.second, false); // configure as point to point error model. - + using KernelType = multiview::RelativePoseKernel; + + KernelType kernel(x1, imgSize1.first, imgSize1.second, x2, imgSize2.first, imgSize2.second, false); // configure as point to point error model. + robustEstimation::Mat3Model model; - /*const std::pair ACRansacOut = */ robustEstimation::ACRANSAC(kernel, randomNumberGenerator, vec_inliers, 1024, &model, std::numeric_limits::infinity()); + /*const std::pair ACRansacOut = */ robustEstimation::ACRANSAC( + kernel, randomNumberGenerator, vec_inliers, 1024, &model, std::numeric_limits::infinity()); H = model.getMatrix(); const bool valid{!vec_inliers.empty()}; - const bool hasStrongSupport{vec_inliers.size() > kernel.getMinimumNbRequiredSamples() * 2.5}; // consider ALICEVISION_MINIMUM_SAMPLES_COEF + const bool hasStrongSupport{vec_inliers.size() > kernel.getMinimumNbRequiredSamples() * 2.5}; // consider ALICEVISION_MINIMUM_SAMPLES_COEF return {valid, hasStrongSupport}; } @@ -171,36 +165,47 @@ aliceVision::EstimationStatus robustHomographyEstimationAC(const Mat2X &x1, * @param[out] vec_inliers The inliers satisfying the rotation as a list of indices. * @return the status of the estimation. */ -aliceVision::EstimationStatus robustRotationEstimationAC(const Mat &x1, const Mat &x2, const std::pair &imgSize1, const std::pair &imgSize2, std::mt19937 &randomNumberGenerator, Mat3 &R, std::vector &vec_inliers) +aliceVision::EstimationStatus robustRotationEstimationAC(const Mat& x1, + const Mat& x2, + const std::pair& imgSize1, + const std::pair& imgSize2, + std::mt19937& randomNumberGenerator, + Mat3& R, + std::vector& vec_inliers) { // using KernelType = multiview::RelativePoseKernel< - using KernelType = multiview::RelativePoseSphericalKernel< - multiview::relativePose::Rotation3PSolver, - multiview::relativePose::RotationError, - robustEstimation::Mat3Model>; + using KernelType = multiview:: + RelativePoseSphericalKernel; - // KernelType kernel(x1, imgSize1.first, imgSize1.second, x2, imgSize2.first, imgSize2.second, false); // configure as point to point error model. - KernelType kernel(x1, x2); // configure as point to point error model. + // KernelType kernel(x1, imgSize1.first, imgSize1.second, x2, imgSize2.first, imgSize2.second, false); // configure as point to point error + // model. + KernelType kernel(x1, x2); // configure as point to point error model. robustEstimation::Mat3Model model; - /*const std::pair ACRansacOut =*/robustEstimation::ACRANSAC(kernel, randomNumberGenerator, vec_inliers, 1024, &model, std::numeric_limits::infinity()); + /*const std::pair ACRansacOut =*/robustEstimation::ACRANSAC( + kernel, randomNumberGenerator, vec_inliers, 1024, &model, std::numeric_limits::infinity()); R = model.getMatrix(); const bool valid{!vec_inliers.empty()}; - const bool hasStrongSupport{vec_inliers.size() > kernel.getMinimumNbRequiredSamples() * 2.5}; // consider ALICEVISION_MINIMUM_SAMPLES_COEF + const bool hasStrongSupport{vec_inliers.size() > kernel.getMinimumNbRequiredSamples() * 2.5}; // consider ALICEVISION_MINIMUM_SAMPLES_COEF return {valid, hasStrongSupport}; } - -bool robustRelativeRotation_fromH(const Mat2X &x1, const Mat2X &x2, const std::pair &imgSize1, const std::pair &imgSize2, std::mt19937 &randomNumberGenerator, RelativeRotationInfo &relativeRotationInfo, const size_t max_iteration_count) +bool robustRelativeRotation_fromH(const Mat2X& x1, + const Mat2X& x2, + const std::pair& imgSize1, + const std::pair& imgSize2, + std::mt19937& randomNumberGenerator, + RelativeRotationInfo& relativeRotationInfo, + const size_t max_iteration_count) { std::vector vec_inliers{}; // estimate the homography - const auto status = robustHomographyEstimationAC(x1, x2, imgSize1, imgSize2, randomNumberGenerator, - relativeRotationInfo._homography, relativeRotationInfo._inliers); + const auto status = robustHomographyEstimationAC( + x1, x2, imgSize1, imgSize2, randomNumberGenerator, relativeRotationInfo._homography, relativeRotationInfo._inliers); if (!status.isValid && !status.hasStrongSupport) { return false; @@ -211,13 +216,18 @@ bool robustRelativeRotation_fromH(const Mat2X &x1, const Mat2X &x2, const std::p return true; } -bool robustRelativeRotation_fromR(const Mat &x1, const Mat &x2, const std::pair &imgSize1, const std::pair &imgSize2, std::mt19937 &randomNumberGenerator, RelativeRotationInfo &relativeRotationInfo, const size_t max_iteration_count) +bool robustRelativeRotation_fromR(const Mat& x1, + const Mat& x2, + const std::pair& imgSize1, + const std::pair& imgSize2, + std::mt19937& randomNumberGenerator, + RelativeRotationInfo& relativeRotationInfo, + const size_t max_iteration_count) { std::vector vec_inliers{}; - const auto status = robustRotationEstimationAC(x1, x2, imgSize1, imgSize2, randomNumberGenerator, - relativeRotationInfo._relativeRotation, - relativeRotationInfo._inliers); + const auto status = robustRotationEstimationAC( + x1, x2, imgSize1, imgSize2, randomNumberGenerator, relativeRotationInfo._relativeRotation, relativeRotationInfo._inliers); if (!status.isValid && !status.hasStrongSupport) { return false; @@ -226,29 +236,28 @@ bool robustRelativeRotation_fromR(const Mat &x1, const Mat &x2, const std::pair< return true; } - ReconstructionEngine_panorama::ReconstructionEngine_panorama(const SfMData& sfmData, const ReconstructionEngine_panorama::Params& params, const std::string& outDirectory, const std::string& loggingFile) - : ReconstructionEngine(sfmData, outDirectory) - , _params(params) - , _loggingFile(loggingFile) + : ReconstructionEngine(sfmData, outDirectory), + _params(params), + _loggingFile(loggingFile) { - if(!_loggingFile.empty()) + if (!_loggingFile.empty()) { // setup HTML logger _htmlDocStream = std::make_shared("GlobalReconstructionEngine SFM report."); _htmlDocStream->pushInfo(htmlDocument::htmlMarkup("h1", std::string("ReconstructionEngine_globalSfM"))); _htmlDocStream->pushInfo("
"); - _htmlDocStream->pushInfo( "Dataset info:"); - _htmlDocStream->pushInfo( "Views count: " + htmlDocument::toString( sfmData.getViews().size()) + "
"); + _htmlDocStream->pushInfo("Dataset info:"); + _htmlDocStream->pushInfo("Views count: " + htmlDocument::toString(sfmData.getViews().size()) + "
"); } } ReconstructionEngine_panorama::~ReconstructionEngine_panorama() { - if(!_loggingFile.empty()) + if (!_loggingFile.empty()) { // Save the reconstruction Log std::ofstream htmlFileStream(_loggingFile); @@ -256,23 +265,16 @@ ReconstructionEngine_panorama::~ReconstructionEngine_panorama() } } -void ReconstructionEngine_panorama::SetFeaturesProvider(feature::FeaturesPerView* featuresPerView) -{ - _featuresPerView = featuresPerView; -} +void ReconstructionEngine_panorama::SetFeaturesProvider(feature::FeaturesPerView* featuresPerView) { _featuresPerView = featuresPerView; } -void ReconstructionEngine_panorama::SetMatchesProvider(matching::PairwiseMatches* provider) -{ - _pairwiseMatches = provider; -} +void ReconstructionEngine_panorama::SetMatchesProvider(matching::PairwiseMatches* provider) { _pairwiseMatches = provider; } void ReconstructionEngine_panorama::filterMatches() { // keep only the largest biedge connected subgraph const PairSet pairs = matching::getImagePairs(*_pairwiseMatches); - const std::set set_remainingIds = - graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs, _outputFolder); - if(set_remainingIds.empty()) + const std::set set_remainingIds = graph::CleanGraph_KeepLargestBiEdge_Nodes(pairs, _outputFolder); + if (set_remainingIds.empty()) { ALICEVISION_LOG_DEBUG("Invalid input image graph for panorama, no remaining match after filtering."); } @@ -287,18 +289,18 @@ bool ReconstructionEngine_panorama::process() Compute_Relative_Rotations(relatives_R); HashMap global_rotations; - if(!Compute_Global_Rotations(relatives_R, global_rotations)) + if (!Compute_Global_Rotations(relatives_R, global_rotations)) { ALICEVISION_LOG_WARNING("Panorama: Rotation Averaging failure!"); return false; } // we set translation vector to zero - for(const auto& gR: global_rotations) + for (const auto& gR : global_rotations) { const Vec3 t(0.0, 0.0, 0.0); const IndexT poseId = gR.first; - const Mat3 & Ri = gR.second; + const Mat3& Ri = gR.second; _sfmData.setAbsolutePose(poseId, CameraPose(Pose3(Ri, t))); } @@ -309,15 +311,17 @@ bool ReconstructionEngine_panorama::process() std::ostringstream os; os << "Structure from Motion statistics."; _htmlDocStream->pushInfo("
"); - _htmlDocStream->pushInfo(htmlMarkup("h1",os.str())); + _htmlDocStream->pushInfo(htmlMarkup("h1", os.str())); os.str(""); - os << "-------------------------------" << "
" - << "-- View count: " << _sfmData.getViews().size() << "
" - << "-- Intrinsic count: " << _sfmData.getIntrinsics().size() << "
" - << "-- Pose count: " << _sfmData.getPoses().size() << "
" - << "-- Track count: " << _sfmData.getLandmarks().size() << "
" - << "-------------------------------" << "
"; + os << "-------------------------------" + << "
" + << "-- View count: " << _sfmData.getViews().size() << "
" + << "-- Intrinsic count: " << _sfmData.getIntrinsics().size() << "
" + << "-- Pose count: " << _sfmData.getPoses().size() << "
" + << "-- Track count: " << _sfmData.getLandmarks().size() << "
" + << "-------------------------------" + << "
"; _htmlDocStream->pushInfo(os.str()); } @@ -325,9 +329,10 @@ bool ReconstructionEngine_panorama::process() } /// Compute from relative rotations the global rotations of the camera poses -bool ReconstructionEngine_panorama::Compute_Global_Rotations(const rotationAveraging::RelativeRotations& relatives_R, HashMap& global_rotations) +bool ReconstructionEngine_panorama::Compute_Global_Rotations(const rotationAveraging::RelativeRotations& relatives_R, + HashMap& global_rotations) { - if(relatives_R.empty()) + if (relatives_R.empty()) { return false; } @@ -335,43 +340,49 @@ bool ReconstructionEngine_panorama::Compute_Global_Rotations(const rotationAvera rotationAveraging::RelativeRotations local_relatives_R = relatives_R; std::set set_pose_ids; - for (const auto & relative_R : local_relatives_R) + for (const auto& relative_R : local_relatives_R) { set_pose_ids.insert(relative_R.i); set_pose_ids.insert(relative_R.j); } - ALICEVISION_LOG_INFO("Global rotations computation: " << "\n" - "\t- relative rotations: " << relatives_R.size() << "\n" - "\t- global rotations: " << set_pose_ids.size()); + ALICEVISION_LOG_INFO("Global rotations computation: " + << "\n" + "\t- relative rotations: " + << relatives_R.size() + << "\n" + "\t- global rotations: " + << set_pose_ids.size()); // Global Rotation solver - const ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod = TRIPLET_ROTATION_INFERENCE_NONE; // TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR; + const ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod = + TRIPLET_ROTATION_INFERENCE_NONE; // TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR; GlobalSfMRotationAveragingSolver rotationAveraging_solver; //-- Rejection triplet that are 'not' identity rotation (error to identity > 50deg) - const bool b_rotationAveraging = rotationAveraging_solver.Run(_params.eRotationAveragingMethod, eRelativeRotationInferenceMethod, local_relatives_R, _params.maxAngularError, global_rotations); + const bool b_rotationAveraging = rotationAveraging_solver.Run( + _params.eRotationAveragingMethod, eRelativeRotationInferenceMethod, local_relatives_R, _params.maxAngularError, global_rotations); ALICEVISION_LOG_DEBUG("Found #global_rotations: " << global_rotations.size()); - if(b_rotationAveraging) + if (b_rotationAveraging) { // Log input graph to the HTML report - if(!_loggingFile.empty() && !_outputFolder.empty()) + if (!_loggingFile.empty() && !_outputFolder.empty()) { - // Log a relative pose graph - { - std::set set_pose_ids; - PairSet relative_pose_pairs; - for(const auto & view : _sfmData.getViews()) + // Log a relative pose graph { - const IndexT pose_id = view.second->getPoseId(); - set_pose_ids.insert(pose_id); + std::set set_pose_ids; + PairSet relative_pose_pairs; + for (const auto& view : _sfmData.getViews()) + { + const IndexT pose_id = view.second->getPoseId(); + set_pose_ids.insert(pose_id); + } + const std::string sGraph_name = "global_relative_rotation_pose_graph_final"; + graph::indexedGraph putativeGraph(set_pose_ids, rotationAveraging_solver.GetUsedPairs()); + graph::exportToGraphvizData((fs::path(_outputFolder) / (sGraph_name + ".dot")).string(), putativeGraph.g); } - const std::string sGraph_name = "global_relative_rotation_pose_graph_final"; - graph::indexedGraph putativeGraph(set_pose_ids, rotationAveraging_solver.GetUsedPairs()); - graph::exportToGraphvizData((fs::path(_outputFolder) / (sGraph_name + ".dot")).string(), putativeGraph.g); - } } } @@ -390,7 +401,7 @@ bool ReconstructionEngine_panorama::Adjust() // Start bundle with rotation only BundleAdjustmentSymbolicCeres BA(options); bool success = BA.adjust(_sfmData, BundleAdjustmentSymbolicCeres::REFINE_ROTATION); - if(success) + if (success) { ALICEVISION_LOG_INFO("Rotations successfully refined."); } @@ -400,17 +411,16 @@ bool ReconstructionEngine_panorama::Adjust() return false; } - if(_params.lockAllIntrinsics) + if (_params.lockAllIntrinsics) { // Do not modify intrinsic camera parameters return true; } - if(_params.intermediateRefineWithFocal) + if (_params.intermediateRefineWithFocal) { - success = BA.adjust(_sfmData, BundleAdjustmentSymbolicCeres::REFINE_ROTATION | - BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_FOCAL); - if(success) + success = BA.adjust(_sfmData, BundleAdjustmentSymbolicCeres::REFINE_ROTATION | BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_FOCAL); + if (success) { ALICEVISION_LOG_INFO("Bundle successfully refined: Rotation + Focal"); } @@ -420,12 +430,12 @@ bool ReconstructionEngine_panorama::Adjust() return false; } } - if(_params.intermediateRefineWithFocalDist) + if (_params.intermediateRefineWithFocalDist) { - success = BA.adjust(_sfmData, BundleAdjustmentSymbolicCeres::REFINE_ROTATION | - BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_FOCAL | - BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_DISTORTION); - if(success) + success = BA.adjust(_sfmData, + BundleAdjustmentSymbolicCeres::REFINE_ROTATION | BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_FOCAL | + BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_DISTORTION); + if (success) { ALICEVISION_LOG_INFO("Bundle successfully refined: Rotation + Focal + Distortion"); } @@ -437,11 +447,11 @@ bool ReconstructionEngine_panorama::Adjust() } // Minimize All - success = BA.adjust(_sfmData, BundleAdjustmentSymbolicCeres::REFINE_ROTATION | - BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_FOCAL | - BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_DISTORTION | - BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS); - if(success) + success = BA.adjust(_sfmData, + BundleAdjustmentSymbolicCeres::REFINE_ROTATION | BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_FOCAL | + BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_DISTORTION | + BundleAdjustmentSymbolicCeres::REFINE_INTRINSICS_OPTICALOFFSET_ALWAYS); + if (success) { ALICEVISION_LOG_INFO("Bundle successfully refined: Rotation + Focal + Optical Center + Distortion"); } @@ -451,22 +461,22 @@ bool ReconstructionEngine_panorama::Adjust() return false; } - //If we have priors - if(_rotationPriors.size() == _sfmData.getViews().size()) + // If we have priors + if (_rotationPriors.size() == _sfmData.getViews().size()) { - //Remove all matches in sfm - sfm::Constraints2D & constraints2d = _sfmData.getConstraints2D(); + // Remove all matches in sfm + sfm::Constraints2D& constraints2d = _sfmData.getConstraints2D(); constraints2d.clear(); - //Put back the rotations from priors and reestimate + // Put back the rotations from priors and reestimate _sfmData.getPoses() = _rotationPriors; - //Add matches but filters out those which are too far from the prior rotation + // Add matches but filters out those which are too far from the prior rotation addConstraints2DWithKnownRotation(); // Minimize Rotation success = BA.adjust(_sfmData, BundleAdjustmentSymbolicCeres::REFINE_ROTATION); - if(success) + if (success) { ALICEVISION_LOG_INFO("Bundle successfully refined: Rotation after cleaning outliers"); } @@ -482,16 +492,16 @@ bool ReconstructionEngine_panorama::Adjust() bool ReconstructionEngine_panorama::addConstraints2DWithKnownRotation() { - sfm::Constraints2D & constraints2d = _sfmData.getConstraints2D(); - - //Check all matches - for (const auto & matchesForView : *_pairwiseMatches) + sfm::Constraints2D& constraints2d = _sfmData.getConstraints2D(); + + // Check all matches + for (const auto& matchesForView : *_pairwiseMatches) { IndexT viewI = matchesForView.first.first; IndexT viewJ = matchesForView.first.second; - const sfmData::View & vI = _sfmData.getView(viewI); - const sfmData::View & vJ = _sfmData.getView(viewJ); + const sfmData::View& vI = _sfmData.getView(viewI); + const sfmData::View& vJ = _sfmData.getView(viewJ); sfmData::CameraPose iTo = _sfmData.getAbsolutePose(vI.getPoseId()); sfmData::CameraPose jTo = _sfmData.getAbsolutePose(vJ.getPoseId()); @@ -504,25 +514,27 @@ bool ReconstructionEngine_panorama::addConstraints2DWithKnownRotation() Mat3 iRj = iRo * jRo.transpose(); - for (const auto & matchesPerDesc : matchesForView.second) + for (const auto& matchesPerDesc : matchesForView.second) { const feature::EImageDescriberType descType = matchesPerDesc.first; const feature::PointFeatures& feats_I = _featuresPerView->getFeatures(viewI, descType); const feature::PointFeatures& feats_J = _featuresPerView->getFeatures(viewJ, descType); - for (const auto & indMatch : matchesPerDesc.second) + for (const auto& indMatch : matchesPerDesc.second) { - const feature::PointFeature & feat_I = feats_I[indMatch._i]; - const feature::PointFeature & feat_J = feats_J[indMatch._j]; + const feature::PointFeature& feat_I = feats_I[indMatch._i]; + const feature::PointFeature& feat_J = feats_J[indMatch._j]; Vec2 ptViewI = feats_I[indMatch._i].coords().cast(); Vec2 ptViewJ = feats_J[indMatch._j].coords().cast(); - //Compute vectors in 3D - const Vec3 bearingVector_I = intrinsicI->toUnitSphere(intrinsicI->removeDistortion(intrinsicI->ima2cam(feat_I.coords().cast()))); - const Vec3 bearingVector_J = intrinsicJ->toUnitSphere(intrinsicJ->removeDistortion(intrinsicJ->ima2cam(feat_J.coords().cast()))); + // Compute vectors in 3D + const Vec3 bearingVector_I = + intrinsicI->toUnitSphere(intrinsicI->removeDistortion(intrinsicI->ima2cam(feat_I.coords().cast()))); + const Vec3 bearingVector_J = + intrinsicJ->toUnitSphere(intrinsicJ->removeDistortion(intrinsicJ->ima2cam(feat_J.coords().cast()))); - //Check angular difference + // Check angular difference double cangle = (iRj * bearingVector_J).dot(bearingVector_I); double angle = acos(cangle); if (angle > _params.maxAngleToPriorRefined * M_PI / 180.0) @@ -530,7 +542,11 @@ bool ReconstructionEngine_panorama::addConstraints2DWithKnownRotation() continue; } - const sfm::Constraint2D constraint(viewI, sfm::Observation(ptViewI, indMatch._i, feat_I.scale()), viewJ, sfm::Observation(ptViewJ, indMatch._j, feat_J.scale()), descType); + const sfm::Constraint2D constraint(viewI, + sfm::Observation(ptViewI, indMatch._i, feat_I.scale()), + viewJ, + sfm::Observation(ptViewJ, indMatch._j, feat_J.scale()), + descType); constraints2d.push_back(constraint); } } @@ -547,16 +563,15 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging /// pairwise view relation between poseIds typedef std::map PoseWiseMatches; - sfmData::RotationPriors & rotationpriors = _sfmData.getRotationPriors(); - for (auto & iter_v1 :_sfmData.getViews()) + sfmData::RotationPriors& rotationpriors = _sfmData.getRotationPriors(); + for (auto& iter_v1 : _sfmData.getViews()) { - if (!_sfmData.isPoseAndIntrinsicDefined(iter_v1.first)) { continue; } - for (auto & iter_v2 :_sfmData.getViews()) + for (auto& iter_v2 : _sfmData.getViews()) { if (iter_v1.first == iter_v2.first) { @@ -587,8 +602,7 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging // List shared correspondences (pairs) between poses PoseWiseMatches poseWiseMatches; - for (matching::PairwiseMatches::const_iterator iterMatches = _pairwiseMatches->begin(); - iterMatches != _pairwiseMatches->end(); ++iterMatches) + for (matching::PairwiseMatches::const_iterator iterMatches = _pairwiseMatches->begin(); iterMatches != _pairwiseMatches->end(); ++iterMatches) { const Pair pair = iterMatches->first; const View* v1 = _sfmData.getViews().at(pair.first).get(); @@ -596,7 +610,7 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging poseWiseMatches[Pair(v1->getPoseId(), v2->getPoseId())].insert(pair); } - sfm::Constraints2D & constraints2d = _sfmData.getConstraints2D(); + sfm::Constraints2D& constraints2d = _sfmData.getConstraints2D(); std::map connection_size; ALICEVISION_LOG_INFO("Relative pose computation:"); @@ -604,7 +618,7 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging for (int i = 0; i < poseWiseMatches.size(); ++i) { { - PoseWiseMatches::const_iterator iter (poseWiseMatches.begin()); + PoseWiseMatches::const_iterator iter(poseWiseMatches.begin()); std::advance(iter, i); const auto& relative_pose_iterator(*iter); const Pair relative_pose_pair = relative_pose_iterator.first; @@ -622,8 +636,8 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging if (match_pairs.size() > 1) { ALICEVISION_LOG_INFO("Compute relative pose: multiple views sharing the same pose. " - << match_pairs.size() << " image matching pairs between the 2 poses (" - << relative_pose_pair.first << ", " << relative_pose_pair.second << ")."); + << match_pairs.size() << " image matching pairs between the 2 poses (" << relative_pose_pair.first << ", " + << relative_pose_pair.second << ")."); } const Pair pairIterator = *(match_pairs.begin()); @@ -657,7 +671,7 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging // Build a list of pairs in meters std::size_t nbBearing = 0; - for(const auto& match_pair: match_pairs) + for (const auto& match_pair : match_pairs) { const matching::MatchesPerDescType& matchesPerDesc = _pairwiseMatches->at(match_pair); nbBearing += matchesPerDesc.getNbAllMatches(); @@ -675,10 +689,10 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging x2 = Mat(2, nbBearing); } - for(const auto& match_pair : match_pairs) + for (const auto& match_pair : match_pairs) { const matching::MatchesPerDescType& matchesPerDesc = _pairwiseMatches->at(match_pair); - for(const auto& matchesPerDescIt : matchesPerDesc) + for (const auto& matchesPerDescIt : matchesPerDesc) { const feature::EImageDescriberType descType = matchesPerDescIt.first; assert(descType != feature::EImageDescriberType::UNINITIALIZED); @@ -687,17 +701,15 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging const feature::PointFeatures& feats_I = _featuresPerView->getFeatures(I, descType); const feature::PointFeatures& feats_J = _featuresPerView->getFeatures(J, descType); - for(const auto& match : matches) + for (const auto& match : matches) { const feature::PointFeature& feat_I = feats_I[match._i]; const feature::PointFeature& feat_J = feats_J[match._j]; - const Vec3 bearingVector_I = - cam_I->toUnitSphere(cam_I->removeDistortion(cam_I->ima2cam(feat_I.coords().cast()))); - const Vec3 bearingVector_J = - cam_J->toUnitSphere(cam_J->removeDistortion(cam_J->ima2cam(feat_J.coords().cast()))); + const Vec3 bearingVector_I = cam_I->toUnitSphere(cam_I->removeDistortion(cam_I->ima2cam(feat_I.coords().cast()))); + const Vec3 bearingVector_J = cam_J->toUnitSphere(cam_J->removeDistortion(cam_J->ima2cam(feat_J.coords().cast()))); - if(useSpherical) + if (useSpherical) { x1.col(iBearing) = bearingVector_I; x2.col(iBearing++) = bearingVector_J; @@ -715,19 +727,19 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging RelativePoseInfo relativePose_info; // Compute max authorized error as geometric mean of camera plane tolerated residual error relativePose_info.initial_residual_tolerance = - std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); + std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); // Since we use normalized features, we will use unit image size and intrinsic matrix: const std::pair imageSize(1., 1.); - const Mat3 K = Mat3::Identity(); + const Mat3 K = Mat3::Identity(); - switch(_params.eRelativeRotationMethod) + switch (_params.eRelativeRotationMethod) { case RELATIVE_ROTATION_FROM_E: { - if(!robustRelativeRotation_fromE(K, K, x1, x2, imageSize, imageSize, _randomNumberGenerator, relativePose_info)) + if (!robustRelativeRotation_fromE(K, K, x1, x2, imageSize, imageSize, _randomNumberGenerator, relativePose_info)) { - ALICEVISION_LOG_INFO("Relative pose computation: i: " << i << ", (" << I << ", " << J <<") => FAILED"); + ALICEVISION_LOG_INFO("Relative pose computation: i: " << i << ", (" << I << ", " << J << ") => FAILED"); continue; } } @@ -736,11 +748,11 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging { RelativeRotationInfo relativeRotation_info; relativeRotation_info._initialResidualTolerance = - std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); + std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); - if(!robustRelativeRotation_fromH(x1, x2, imageSize, imageSize, _randomNumberGenerator, relativeRotation_info)) + if (!robustRelativeRotation_fromH(x1, x2, imageSize, imageSize, _randomNumberGenerator, relativeRotation_info)) { - ALICEVISION_LOG_INFO("Relative pose computation: i: " << i << ", (" << I << ", " << J <<") => FAILED"); + ALICEVISION_LOG_INFO("Relative pose computation: i: " << i << ", (" << I << ", " << J << ") => FAILED"); continue; } @@ -754,11 +766,11 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging { RelativeRotationInfo relativeRotation_info; relativeRotation_info._initialResidualTolerance = - std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); + std::sqrt(std::sqrt(cam_I->imagePlaneToCameraPlaneError(2.5) * cam_J->imagePlaneToCameraPlaneError(2.5))); - if(!robustRelativeRotation_fromR(x1, x2, imageSize, imageSize, _randomNumberGenerator, relativeRotation_info)) + if (!robustRelativeRotation_fromR(x1, x2, imageSize, imageSize, _randomNumberGenerator, relativeRotation_info)) { - ALICEVISION_LOG_INFO("Relative pose computation: i: " << i << ", (" << I << ", " << J <<") => FAILED"); + ALICEVISION_LOG_INFO("Relative pose computation: i: " << i << ", (" << I << ", " << J << ") => FAILED"); ALICEVISION_LOG_INFO("I: " << view_I->getImage().getImagePath() << ", J: " << view_J->getImage().getImagePath()); continue; } @@ -770,7 +782,8 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging } break; default: - ALICEVISION_LOG_DEBUG("Unknown relative rotation method: " << ERelativeRotationMethod_enumToString(_params.eRelativeRotationMethod)); + ALICEVISION_LOG_DEBUG( + "Unknown relative rotation method: " << ERelativeRotationMethod_enumToString(_params.eRelativeRotationMethod)); } // If an existing prior on rotation exists, then make sure the found detected rotation is not stupid @@ -815,20 +828,20 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging size_t index = 0; size_t index_inlier = 0; - for(const auto& match_pair : match_pairs) + for (const auto& match_pair : match_pairs) { const matching::MatchesPerDescType& matchesPerDesc = _pairwiseMatches->at(match_pair); - for(const auto& matchesPerDescIt : matchesPerDesc) + for (const auto& matchesPerDescIt : matchesPerDesc) { const feature::EImageDescriberType descType = matchesPerDescIt.first; const matching::IndMatches& matches = matchesPerDescIt.second; - for(const auto& match : matches) + for (const auto& match : matches) { - if(index_inlier >= relativePose_info.vec_inliers.size()) + if (index_inlier >= relativePose_info.vec_inliers.size()) break; size_t next_inlier = relativePose_info.vec_inliers[index_inlier]; - if(index == next_inlier) + if (index == next_inlier) { Vec2 pt1 = _featuresPerView->getFeatures(I, descType)[match._i].coords().cast(); Vec2 pt2 = _featuresPerView->getFeatures(J, descType)[match._j].coords().cast(); @@ -836,8 +849,8 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging const PointFeature& pI = _featuresPerView->getFeatures(I, descType)[match._i]; const PointFeature& pJ = _featuresPerView->getFeatures(J, descType)[match._j]; - const sfm::Constraint2D constraint(I, sfm::Observation(pt1, match._i, pI.scale()), J, - sfm::Observation(pt2, match._j, pJ.scale()), descType); + const sfm::Constraint2D constraint( + I, sfm::Observation(pt1, match._i, pI.scale()), J, sfm::Observation(pt2, match._j, pJ.scale()), descType); constraints2d.push_back(constraint); ++index_inlier; @@ -853,18 +866,18 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging { // Add the relative rotation to the relative 'rotation' pose graph using namespace aliceVision::rotationAveraging; - vec_relatives_R.emplace_back(relative_pose_pair.first, relative_pose_pair.second, - relativePose_info.relativePose.rotation(), weight); + vec_relatives_R.emplace_back(relative_pose_pair.first, relative_pose_pair.second, relativePose_info.relativePose.rotation(), weight); } } - } // for all relative pose + } // for all relative pose // Debug result ALICEVISION_LOG_DEBUG("Compute_Relative_Rotations: vec_relatives_R.size(): " << vec_relatives_R.size()); - for(rotationAveraging::RelativeRotation& rotation: vec_relatives_R) + for (rotationAveraging::RelativeRotation& rotation : vec_relatives_R) { - ALICEVISION_LOG_DEBUG("Relative_Rotation:\n" << "i: " << rotation.i << ", j: " << rotation.j - << ", weight: " << rotation.weight << "\n" << "Rij" << rotation.Rij); + ALICEVISION_LOG_DEBUG("Relative_Rotation:\n" + << "i: " << rotation.i << ", j: " << rotation.j << ", weight: " << rotation.weight << "\n" + << "Rij" << rotation.Rij); } // Log input graph to the HTML report @@ -873,8 +886,8 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging // Log a relative view graph { std::set set_ViewIds; - std::transform(_sfmData.getViews().begin(), _sfmData.getViews().end(), - std::inserter(set_ViewIds, set_ViewIds.begin()), stl::RetrieveKey()); + std::transform( + _sfmData.getViews().begin(), _sfmData.getViews().end(), std::inserter(set_ViewIds, set_ViewIds.begin()), stl::RetrieveKey()); graph::indexedGraph putativeGraph(set_ViewIds, getImagePairs(*_pairwiseMatches)); graph::exportToGraphvizData((fs::path(_outputFolder) / "global_relative_rotation_view_graph.dot").string(), putativeGraph.g); } @@ -883,7 +896,7 @@ void ReconstructionEngine_panorama::Compute_Relative_Rotations(rotationAveraging { std::set set_pose_ids; PairSet relative_pose_pairs; - for(const auto& relative_R : vec_relatives_R) + for (const auto& relative_R : vec_relatives_R) { const Pair relative_pose_indices(relative_R.i, relative_R.j); relative_pose_pairs.insert(relative_pose_indices); @@ -903,15 +916,15 @@ bool ReconstructionEngine_panorama::buildLandmarks() _sfmData.getLandmarks().clear(); size_t count = 0; - for (const sfmData::Constraint2D & c : _sfmData.getConstraints2D()) + for (const sfmData::Constraint2D& c : _sfmData.getConstraints2D()) { // Retrieve camera parameters - const sfmData::View & v1 = _sfmData.getView(c.ViewFirst); + const sfmData::View& v1 = _sfmData.getView(c.ViewFirst); const std::shared_ptr cam1 = _sfmData.getIntrinsicsharedPtr(v1.getIntrinsicId()); const sfmData::CameraPose pose1 = _sfmData.getPose(v1); const Vec3 wpt1 = cam1->backproject(c.ObservationFirst.x, true, pose1.getTransform(), 1.0); - const sfmData::View & v2 = _sfmData.getView(c.ViewSecond); + const sfmData::View& v2 = _sfmData.getView(c.ViewSecond); const std::shared_ptr cam2 = _sfmData.getIntrinsicsharedPtr(v2.getIntrinsicId()); const sfmData::CameraPose pose2 = _sfmData.getPose(v2); const Vec3 wpt2 = cam2->backproject(c.ObservationSecond.x, true, pose2.getTransform(), 1.0); @@ -929,6 +942,5 @@ bool ReconstructionEngine_panorama::buildLandmarks() return true; } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.hpp b/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.hpp index d8efa37121..70a53bcbc9 100644 --- a/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.hpp +++ b/src/aliceVision/sfm/pipeline/panorama/ReconstructionEngine_panorama.hpp @@ -13,8 +13,8 @@ #include -namespace aliceVision{ -namespace sfm{ +namespace aliceVision { +namespace sfm { enum ERelativeRotationMethod { @@ -25,11 +25,14 @@ enum ERelativeRotationMethod inline std::string ERelativeRotationMethod_enumToString(const ERelativeRotationMethod rotationMethod) { - switch(rotationMethod) + switch (rotationMethod) { - case ERelativeRotationMethod::RELATIVE_ROTATION_FROM_E: return "essential_matrix"; - case ERelativeRotationMethod::RELATIVE_ROTATION_FROM_R: return "rotation_matrix"; - case ERelativeRotationMethod::RELATIVE_ROTATION_FROM_H: return "homography_matrix"; + case ERelativeRotationMethod::RELATIVE_ROTATION_FROM_E: + return "essential_matrix"; + case ERelativeRotationMethod::RELATIVE_ROTATION_FROM_R: + return "rotation_matrix"; + case ERelativeRotationMethod::RELATIVE_ROTATION_FROM_H: + return "homography_matrix"; } throw std::out_of_range("Invalid method name enum"); } @@ -39,9 +42,12 @@ inline ERelativeRotationMethod ERelativeRotationMethod_stringToEnum(const std::s std::string methodName = rotationMethodName; std::transform(methodName.begin(), methodName.end(), methodName.begin(), ::tolower); - if (methodName == "essential_matrix") return ERelativeRotationMethod::RELATIVE_ROTATION_FROM_E; - if (methodName == "rotation_matrix") return ERelativeRotationMethod::RELATIVE_ROTATION_FROM_R; - if (methodName == "homography_matrix") return ERelativeRotationMethod::RELATIVE_ROTATION_FROM_H; + if (methodName == "essential_matrix") + return ERelativeRotationMethod::RELATIVE_ROTATION_FROM_E; + if (methodName == "rotation_matrix") + return ERelativeRotationMethod::RELATIVE_ROTATION_FROM_R; + if (methodName == "homography_matrix") + return ERelativeRotationMethod::RELATIVE_ROTATION_FROM_H; throw std::out_of_range("Invalid method name : '" + rotationMethodName + "'"); } @@ -60,7 +66,6 @@ inline std::istream& operator>>(std::istream& in, ERelativeRotationMethod& rotat return in; } - /** * @brief A struct containing the information of the relative rotation. */ @@ -81,10 +86,8 @@ struct RelativeRotationInfo double _initialResidualTolerance{std::numeric_limits::infinity()}; /// the estimated threshold found by acransac. double _foundResidualPrecision{std::numeric_limits::infinity()}; - }; - /** * @brief Estimate the relative pose between two views. * @param[in] K1 3x3 calibration matrix of the first view. @@ -98,12 +101,14 @@ struct RelativeRotationInfo * @param[in] maxIterationCount Max number of iteration for the ransac process. * @return true if a homography has been estimated. */ -bool robustRelativeRotation_fromE(const Mat3 & K1, const Mat3 & K2, - const Mat & x1, const Mat & x2, - const std::pair & size_ima1, - const std::pair & size_ima2, - std::mt19937 &randomNumberGenerator, - RelativePoseInfo & relativePose_info, +bool robustRelativeRotation_fromE(const Mat3& K1, + const Mat3& K2, + const Mat& x1, + const Mat& x2, + const std::pair& size_ima1, + const std::pair& size_ima2, + std::mt19937& randomNumberGenerator, + RelativePoseInfo& relativePose_info, const size_t max_iteration_count = 4096); /** @@ -117,11 +122,12 @@ bool robustRelativeRotation_fromE(const Mat3 & K1, const Mat3 & K2, * @param[in] maxIterationCount Max number of iteration for the ransac process. * @return true if a homography has been estimated. */ -bool robustRelativeRotation_fromH(const Mat2X &x1, const Mat2X &x2, - const std::pair &imgSize1, - const std::pair &imgSize2, - std::mt19937 &randomNumberGenerator, - RelativeRotationInfo &relativeRotationInfo, +bool robustRelativeRotation_fromH(const Mat2X& x1, + const Mat2X& x2, + const std::pair& imgSize1, + const std::pair& imgSize2, + std::mt19937& randomNumberGenerator, + RelativeRotationInfo& relativeRotationInfo, const size_t max_iteration_count = 4096); /** @@ -135,32 +141,32 @@ bool robustRelativeRotation_fromH(const Mat2X &x1, const Mat2X &x2, * @param[in] maxIterationCount Max number of iteration for the ransac process. * @return true if a homography has been estimated. */ -bool robustRelativeRotation_fromR(const Mat &x1, const Mat &x2, - const std::pair &imgSize1, - const std::pair &imgSize2, - std::mt19937 &randomNumberGenerator, - RelativeRotationInfo &relativeRotationInfo, +bool robustRelativeRotation_fromR(const Mat& x1, + const Mat& x2, + const std::pair& imgSize1, + const std::pair& imgSize2, + std::mt19937& randomNumberGenerator, + RelativeRotationInfo& relativeRotationInfo, const size_t max_iteration_count = 4096); - /** * Panorama Pipeline Reconstruction Engine. * The method is based on the Global SfM but with no translations between cameras. */ class ReconstructionEngine_panorama : public ReconstructionEngine { -public: + public: struct Params { ERotationAveragingMethod eRotationAveragingMethod = ROTATION_AVERAGING_L2; ERelativeRotationMethod eRelativeRotationMethod = RELATIVE_ROTATION_FROM_E; bool lockAllIntrinsics = false; bool rotationAveragingWeighting = true; - double maxAngleToPrior = 5.0; //< max angle to input prior before refinement in degree - double maxAngleToPriorRefined = 2.0; //< max angle to input prior after refinement in degree - double maxAngularError = 100.0; //< max angular error in degree (in global rotation averaging) - bool intermediateRefineWithFocal = false; //< intermediate refine with rotation+focal - bool intermediateRefineWithFocalDist = false; //< intermediate refine with rotation+focal+distortion + double maxAngleToPrior = 5.0; //< max angle to input prior before refinement in degree + double maxAngleToPriorRefined = 2.0; //< max angle to input prior after refinement in degree + double maxAngularError = 100.0; //< max angular error in degree (in global rotation averaging) + bool intermediateRefineWithFocal = false; //< intermediate refine with rotation+focal + bool intermediateRefineWithFocalDist = false; //< intermediate refine with rotation+focal+distortion }; ReconstructionEngine_panorama(const sfmData::SfMData& sfmData, const Params& params, @@ -181,19 +187,19 @@ class ReconstructionEngine_panorama : public ReconstructionEngine bool buildLandmarks(); -protected: + protected: /// Compute from relative rotations the global rotations of the camera poses bool Compute_Global_Rotations(const aliceVision::rotationAveraging::RelativeRotations& vec_relatives_R, HashMap& map_globalR); -public: + public: /// Adjust the scene (& remove outliers) bool Adjust(); -private: + private: /// Compute relative rotations void Compute_Relative_Rotations(aliceVision::rotationAveraging::RelativeRotations& vec_relatives_R); bool addConstraints2DWithKnownRotation(); - + // Logger std::shared_ptr _htmlDocStream; std::string _loggingFile; @@ -208,5 +214,5 @@ class ReconstructionEngine_panorama : public ReconstructionEngine sfmData::Poses _rotationPriors; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_outliers_test.cpp b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_outliers_test.cpp index 2a27a85074..f2871fc33e 100644 --- a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_outliers_test.cpp +++ b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_outliers_test.cpp @@ -13,10 +13,8 @@ BOOST_AUTO_TEST_CASE(PANORAMA_SFM_EQUIDISTANT_OUTLIERS) { - auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, - 1920, 1080, 1357.0, 1357.0, 0, 0); - auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, - 1920, 1080, 1000.0, 1000.0, 40, -20); + auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, 1920, 1080, 1357.0, 1357.0, 0, 0); + auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, 1920, 1080, 1000.0, 1000.0, 40, -20); test_panorama(intrinsic_gt, intrinsic_est, 0.3); } diff --git a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_test.cpp b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_test.cpp index 021e7a01c6..7ad7bea3c0 100644 --- a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_test.cpp +++ b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_equidistant_test.cpp @@ -13,10 +13,8 @@ BOOST_AUTO_TEST_CASE(PANORAMA_SFM_EQUIDISTANT) { - auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, - 1920, 1080, 1357.0, 1357.0, 0, 0); - auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, - 1920, 1080, 1200.0, 1200.0, 40, -20); + auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, 1920, 1080, 1357.0, 1357.0, 0, 0); + auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, 1920, 1080, 1200.0, 1200.0, 40, -20); test_panorama(intrinsic_gt, intrinsic_est, 0.0); } diff --git a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_outliers_test.cpp b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_outliers_test.cpp index f4506b7a7b..93a447ce42 100644 --- a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_outliers_test.cpp +++ b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_outliers_test.cpp @@ -13,10 +13,8 @@ BOOST_AUTO_TEST_CASE(PANORAMA_SFM_RADIAL3_OUTLIERS) { - auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - 1920, 1080, 1357.0, 1357.0, 0, 0); - auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - 1920, 1080, 1000.0, 1000.0, 40, -20); + auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, 1920, 1080, 1357.0, 1357.0, 0, 0); + auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, 1920, 1080, 1000.0, 1000.0, 40, -20); test_panorama(intrinsic_gt, intrinsic_est, 0.3); } diff --git a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_test.cpp b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_test.cpp index 44db485f2e..1cc3a93723 100644 --- a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_test.cpp +++ b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_radial3_test.cpp @@ -13,10 +13,8 @@ BOOST_AUTO_TEST_CASE(PANORAMA_SFM_RADIAL3) { - auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - 1920, 1080, 1357.0, 1357.0, 0, 0); - auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - 1920, 1080, 1200.0, 1200.0, 40, -20); + auto intrinsic_gt = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, 1920, 1080, 1357.0, 1357.0, 0, 0); + auto intrinsic_est = camera::createIntrinsic(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, 1920, 1080, 1200.0, 1200.0, 40, -20); test_panorama(intrinsic_gt, intrinsic_est, 0.0); } diff --git a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_test_common.hpp b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_test_common.hpp index 6cc8427a1e..8d90ab43a1 100644 --- a/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_test_common.hpp +++ b/src/aliceVision/sfm/pipeline/panorama/panoramaSfM_test_common.hpp @@ -22,129 +22,139 @@ using namespace aliceVision; // - Create a rotation matrix between two views // - Create two matrices of calibration for two views -void test_panorama(std::shared_ptr & intrinsic_gt, std::shared_ptr & intrinsic_noisy, double ratio_inliers) { - - sfmData::SfMData sfmdata; - sfmdata.getIntrinsics().emplace(0, intrinsic_noisy); - - /*Create cameras */ - std::vector poses_gt; - size_t count = 0; - for (double latitude = - M_PI_2; latitude <= M_PI_2; latitude += 0.8) { - for (double longitude = - M_PI; longitude <= M_PI; longitude += 0.5) { - - Eigen::Matrix3d matLongitude = Eigen::AngleAxisd(longitude, Vec3(0,1,0)).toRotationMatrix(); - Eigen::Matrix3d matLatitude = Eigen::AngleAxisd(latitude, Vec3(1,0,0)).toRotationMatrix(); - Eigen::Matrix3d R = matLongitude * matLatitude; - geometry::Pose3 pose(R, Vec3(0,0,0)); - poses_gt.push_back(pose); - - std::shared_ptr v = std::make_shared("fakeimg.png", count, 0, count, 1920, 1080); - sfmdata.getViews().emplace(count, v); - count++; - } - } - - - feature::FeaturesPerView fpv; - matching::PairwiseMatches matches; - - std::mt19937 mt; - std::uniform_real_distribution dist(0.0, 1.0); - std::normal_distribution noise(0.0, 100.0); - - for (double latitude = - M_PI_2; latitude <= M_PI_2; latitude += 0.02) { - for (double longitude = - M_PI; longitude <= M_PI; longitude += 0.02) { - - const double Px = cos(latitude) * sin(longitude); - const double Py = sin(latitude); - const double Pz = cos(latitude) * cos(longitude); - - Vec3 pt(Px, Py, Pz); - - std::vector> observations; - - /*Project this point on all cameras*/ - for (int idPose = 0; idPose < poses_gt.size(); idPose++) { - - geometry::Pose3 pose = poses_gt[idPose]; - - Vec3 ray = pose(pt); - if (!intrinsic_gt->isVisibleRay(ray)) { - continue; - } - - Vec2 im = intrinsic_gt->project(pose, pt.homogeneous(), true); - double dice = dist(mt); - if (dice < ratio_inliers) { - //Outlier: generate large error - im(0) += noise(mt); - im(1) += noise(mt); +void test_panorama(std::shared_ptr& intrinsic_gt, + std::shared_ptr& intrinsic_noisy, + double ratio_inliers) +{ + sfmData::SfMData sfmdata; + sfmdata.getIntrinsics().emplace(0, intrinsic_noisy); + + /*Create cameras */ + std::vector poses_gt; + size_t count = 0; + for (double latitude = -M_PI_2; latitude <= M_PI_2; latitude += 0.8) + { + for (double longitude = -M_PI; longitude <= M_PI; longitude += 0.5) + { + Eigen::Matrix3d matLongitude = Eigen::AngleAxisd(longitude, Vec3(0, 1, 0)).toRotationMatrix(); + Eigen::Matrix3d matLatitude = Eigen::AngleAxisd(latitude, Vec3(1, 0, 0)).toRotationMatrix(); + Eigen::Matrix3d R = matLongitude * matLatitude; + geometry::Pose3 pose(R, Vec3(0, 0, 0)); + poses_gt.push_back(pose); + + std::shared_ptr v = std::make_shared("fakeimg.png", count, 0, count, 1920, 1080); + sfmdata.getViews().emplace(count, v); + count++; } - if (!intrinsic_gt->isVisible(im)) { - continue; - } - - /*If it is visible, store a fake feature */ - feature::MapFeaturesPerView & map = fpv.getData(); - feature::PointFeatures & pfs = map[idPose][feature::EImageDescriberType::UNKNOWN]; - - /*Also store the index of this feature for this view*/ - observations.push_back(std::make_pair(idPose, pfs.size())); - - feature::PointFeature pf(im(0), im(1), 1.0, 0.0); - pfs.push_back(pf); - } - - for (size_t idObs1 = 0; idObs1 < observations.size(); idObs1++) { - for (size_t idObs2 = idObs1 + 1; idObs2 < observations.size(); idObs2++) { - - size_t idPose1 = observations[idObs1].first; - size_t idPose2 = observations[idObs2].first; - - Pair p = std::make_pair(idPose1, idPose2); - - matching::IndMatch match(observations[idObs1].second, observations[idObs2].second); + } - matching::IndMatches & vec_matches = matches[p][feature::EImageDescriberType::UNKNOWN]; - vec_matches.push_back(match); + feature::FeaturesPerView fpv; + matching::PairwiseMatches matches; + + std::mt19937 mt; + std::uniform_real_distribution dist(0.0, 1.0); + std::normal_distribution noise(0.0, 100.0); + + for (double latitude = -M_PI_2; latitude <= M_PI_2; latitude += 0.02) + { + for (double longitude = -M_PI; longitude <= M_PI; longitude += 0.02) + { + const double Px = cos(latitude) * sin(longitude); + const double Py = sin(latitude); + const double Pz = cos(latitude) * cos(longitude); + + Vec3 pt(Px, Py, Pz); + + std::vector> observations; + + /*Project this point on all cameras*/ + for (int idPose = 0; idPose < poses_gt.size(); idPose++) + { + geometry::Pose3 pose = poses_gt[idPose]; + + Vec3 ray = pose(pt); + if (!intrinsic_gt->isVisibleRay(ray)) + { + continue; + } + + Vec2 im = intrinsic_gt->project(pose, pt.homogeneous(), true); + double dice = dist(mt); + if (dice < ratio_inliers) + { + // Outlier: generate large error + im(0) += noise(mt); + im(1) += noise(mt); + } + if (!intrinsic_gt->isVisible(im)) + { + continue; + } + + /*If it is visible, store a fake feature */ + feature::MapFeaturesPerView& map = fpv.getData(); + feature::PointFeatures& pfs = map[idPose][feature::EImageDescriberType::UNKNOWN]; + + /*Also store the index of this feature for this view*/ + observations.push_back(std::make_pair(idPose, pfs.size())); + + feature::PointFeature pf(im(0), im(1), 1.0, 0.0); + pfs.push_back(pf); + } + + for (size_t idObs1 = 0; idObs1 < observations.size(); idObs1++) + { + for (size_t idObs2 = idObs1 + 1; idObs2 < observations.size(); idObs2++) + { + size_t idPose1 = observations[idObs1].first; + size_t idPose2 = observations[idObs2].first; + + Pair p = std::make_pair(idPose1, idPose2); + + matching::IndMatch match(observations[idObs1].second, observations[idObs2].second); + + matching::IndMatches& vec_matches = matches[p][feature::EImageDescriberType::UNKNOWN]; + vec_matches.push_back(match); + } + } } - } } - } - sfm::ReconstructionEngine_panorama::Params params; - params.eRelativeRotationMethod = sfm::RELATIVE_ROTATION_FROM_R; + sfm::ReconstructionEngine_panorama::Params params; + params.eRelativeRotationMethod = sfm::RELATIVE_ROTATION_FROM_R; - sfm::ReconstructionEngine_panorama pano(sfmdata, params, ""); - pano.SetFeaturesProvider(&fpv); - pano.SetMatchesProvider(&matches); + sfm::ReconstructionEngine_panorama pano(sfmdata, params, ""); + pano.SetFeaturesProvider(&fpv); + pano.SetMatchesProvider(&matches); - if (!pano.process()) { - BOOST_TEST_FAIL("Panorama processing failed"); - return; - } + if (!pano.process()) + { + BOOST_TEST_FAIL("Panorama processing failed"); + return; + } - if (!pano.Adjust()) { - BOOST_TEST_FAIL("Panorama adjustment failed"); - return; - } + if (!pano.Adjust()) + { + BOOST_TEST_FAIL("Panorama adjustment failed"); + return; + } - sfmdata = pano.getSfMData(); + sfmdata = pano.getSfMData(); - geometry::Pose3 zRw_gt = poses_gt[0]; - geometry::Pose3 zRw_est = sfmdata.getPoses()[0].getTransform(); + geometry::Pose3 zRw_gt = poses_gt[0]; + geometry::Pose3 zRw_est = sfmdata.getPoses()[0].getTransform(); - for (int idPose = 0; idPose < poses_gt.size(); idPose++) { - geometry::Pose3 cRw_gt = poses_gt[idPose]; - geometry::Pose3 cRw_est = sfmdata.getPoses()[idPose].getTransform(); + for (int idPose = 0; idPose < poses_gt.size(); idPose++) + { + geometry::Pose3 cRw_gt = poses_gt[idPose]; + geometry::Pose3 cRw_est = sfmdata.getPoses()[idPose].getTransform(); - Eigen::Matrix3d cRz_gt = cRw_gt.rotation() * zRw_gt.rotation().transpose(); - Eigen::Matrix3d cRz_est = cRw_est.rotation() * zRw_est.rotation().transpose(); + Eigen::Matrix3d cRz_gt = cRw_gt.rotation() * zRw_gt.rotation().transpose(); + Eigen::Matrix3d cRz_est = cRw_est.rotation() * zRw_est.rotation().transpose(); - Eigen::Matrix3d delta = cRz_gt * cRz_est.transpose(); + Eigen::Matrix3d delta = cRz_gt * cRz_est.transpose(); - Eigen::AngleAxisd aa; - aa.fromRotationMatrix(delta); - BOOST_CHECK_LT(aa.angle(), 1e-2); - } + Eigen::AngleAxisd aa; + aa.fromRotationMatrix(delta); + BOOST_CHECK_LT(aa.angle(), 1e-2); + } } diff --git a/src/aliceVision/sfm/pipeline/regionsIO.cpp b/src/aliceVision/sfm/pipeline/regionsIO.cpp index 421c67ff86..f808ed9e62 100644 --- a/src/aliceVision/sfm/pipeline/regionsIO.cpp +++ b/src/aliceVision/sfm/pipeline/regionsIO.cpp @@ -20,277 +20,271 @@ namespace sfm { using namespace sfmData; -std::unique_ptr loadRegions(const std::vector& folders, - IndexT viewId, - const feature::ImageDescriber& imageDescriber) +std::unique_ptr loadRegions(const std::vector& folders, IndexT viewId, const feature::ImageDescriber& imageDescriber) { - assert(!folders.empty()); + assert(!folders.empty()); - const std::string imageDescriberTypeName = feature::EImageDescriberType_enumToString(imageDescriber.getDescriberType()); - const std::string basename = std::to_string(viewId); + const std::string imageDescriberTypeName = feature::EImageDescriberType_enumToString(imageDescriber.getDescriberType()); + const std::string basename = std::to_string(viewId); - std::string featFilename; - std::string descFilename; + std::string featFilename; + std::string descFilename; - for(const std::string& folder : folders) - { - const fs::path featPath = fs::path(folder) / std::string(basename + "." + imageDescriberTypeName + ".feat"); - const fs::path descPath = fs::path(folder) / std::string(basename + "." + imageDescriberTypeName + ".desc"); + for (const std::string& folder : folders) + { + const fs::path featPath = fs::path(folder) / std::string(basename + "." + imageDescriberTypeName + ".feat"); + const fs::path descPath = fs::path(folder) / std::string(basename + "." + imageDescriberTypeName + ".desc"); + + if (fs::exists(featPath) && fs::exists(descPath)) + { + featFilename = featPath.string(); + descFilename = descPath.string(); + } + } + + if (featFilename.empty() || descFilename.empty()) + throw std::runtime_error("Can't find view " + basename + " region files"); + + ALICEVISION_LOG_TRACE("Features filename: " << featFilename); + ALICEVISION_LOG_TRACE("Descriptors filename: " << descFilename); - if(fs::exists(featPath) && fs::exists(descPath)) + std::unique_ptr regionsPtr; + imageDescriber.allocate(regionsPtr); + + try + { + regionsPtr->Load(featFilename, descFilename); + } + catch (const std::exception& e) { - featFilename = featPath.string(); - descFilename = descPath.string(); + std::stringstream ss; + ss << "Invalid " << imageDescriberTypeName << " regions files for the view " << basename << " : \n"; + ss << "\t- Features file : " << featFilename << "\n"; + ss << "\t- Descriptors file: " << descFilename << "\n"; + ss << "\t " << e.what() << "\n"; + ALICEVISION_LOG_ERROR(ss.str()); + + throw std::runtime_error(e.what()); } - } - - if(featFilename.empty() || descFilename.empty()) - throw std::runtime_error("Can't find view " + basename + " region files"); - - ALICEVISION_LOG_TRACE("Features filename: " << featFilename); - ALICEVISION_LOG_TRACE("Descriptors filename: " << descFilename); - - std::unique_ptr regionsPtr; - imageDescriber.allocate(regionsPtr); - - try - { - regionsPtr->Load(featFilename, descFilename); - } - catch(const std::exception& e) - { - std::stringstream ss; - ss << "Invalid " << imageDescriberTypeName << " regions files for the view " << basename << " : \n"; - ss << "\t- Features file : " << featFilename << "\n"; - ss << "\t- Descriptors file: " << descFilename << "\n"; - ss << "\t " << e.what() << "\n"; - ALICEVISION_LOG_ERROR(ss.str()); - - throw std::runtime_error(e.what()); - } - - ALICEVISION_LOG_TRACE("Region count: " << regionsPtr->RegionCount()); - return regionsPtr; + + ALICEVISION_LOG_TRACE("Region count: " << regionsPtr->RegionCount()); + return regionsPtr; } -std::unique_ptr loadFeatures(const std::vector& folders, - IndexT viewId, - const feature::ImageDescriber& imageDescriber) +std::unique_ptr loadFeatures(const std::vector& folders, IndexT viewId, const feature::ImageDescriber& imageDescriber) { - assert(!folders.empty()); + assert(!folders.empty()); + + const std::string imageDescriberTypeName = feature::EImageDescriberType_enumToString(imageDescriber.getDescriberType()); + const std::string basename = std::to_string(viewId); + + std::string featFilename; - const std::string imageDescriberTypeName = feature::EImageDescriberType_enumToString(imageDescriber.getDescriberType()); - const std::string basename = std::to_string(viewId); + // build up a set with normalized paths to remove duplicates + std::set foldersSet; + for (const auto& folder : folders) + { + if (fs::exists(folder)) + { + foldersSet.insert(fs::canonical(folder).string()); + } + } + + for (const auto& folder : foldersSet) + { + const fs::path featPath = fs::path(folder) / std::string(basename + "." + imageDescriberTypeName + ".feat"); + if (fs::exists(featPath)) + featFilename = featPath.string(); + } - std::string featFilename; + if (featFilename.empty()) + throw std::runtime_error("Can't find view " + basename + " features file"); - // build up a set with normalized paths to remove duplicates - std::set foldersSet; - for(const auto& folder : folders) - { - if(fs::exists(folder)) + ALICEVISION_LOG_DEBUG("Features filename: " << featFilename); + + std::unique_ptr regionsPtr; + imageDescriber.allocate(regionsPtr); + + try + { + regionsPtr->LoadFeatures(featFilename); + } + catch (const std::exception& e) { - foldersSet.insert(fs::canonical(folder).string()); + std::stringstream ss; + ss << "Invalid " << imageDescriberTypeName << " features file for the view " << basename << " : \n"; + ss << "\t- Features file : " << featFilename << "\n"; + ss << "\t " << e.what() << "\n"; + ALICEVISION_LOG_ERROR(ss.str()); + + throw std::runtime_error(e.what()); } - } - - for(const auto& folder : foldersSet) - { - const fs::path featPath = fs::path(folder) / std::string(basename + "." + imageDescriberTypeName + ".feat"); - if(fs::exists(featPath)) - featFilename = featPath.string(); - } - - if(featFilename.empty()) - throw std::runtime_error("Can't find view " + basename + " features file"); - - ALICEVISION_LOG_DEBUG("Features filename: " << featFilename); - - std::unique_ptr regionsPtr; - imageDescriber.allocate(regionsPtr); - - try - { - regionsPtr->LoadFeatures(featFilename); - } - catch(const std::exception& e) - { - std::stringstream ss; - ss << "Invalid " << imageDescriberTypeName << " features file for the view " << basename << " : \n"; - ss << "\t- Features file : " << featFilename << "\n"; - ss << "\t " << e.what() << "\n"; - ALICEVISION_LOG_ERROR(ss.str()); - - throw std::runtime_error(e.what()); - } - - ALICEVISION_LOG_TRACE("Feature count: " << regionsPtr->RegionCount()); - return regionsPtr; + + ALICEVISION_LOG_TRACE("Feature count: " << regionsPtr->RegionCount()); + return regionsPtr; } bool loadFeaturesPerDescPerView(std::vector>>& featuresPerDescPerView, - const std::vector& viewIds, const std::vector& folders, + const std::vector& viewIds, + const std::vector& folders, const std::vector& imageDescriberTypes) { - if(folders.empty()) - { - ALICEVISION_LOG_ERROR("Cannot load features, no folders provided"); - return false; - } - if(viewIds.empty()) - { - ALICEVISION_LOG_ERROR("Cannot load features, no view ids provided"); - return false; - } - if(imageDescriberTypes.empty()) - { - ALICEVISION_LOG_ERROR("Cannot load features, no image desciber types provided"); - return false; - } - - std::vector> imageDescribers; - imageDescribers.resize(imageDescriberTypes.size()); - - for(std::size_t i = 0; i < imageDescriberTypes.size(); ++i) - imageDescribers.at(i) = createImageDescriber(imageDescriberTypes.at(i)); - - featuresPerDescPerView.resize(imageDescribers.size()); - - std::atomic_bool loadingSuccess(true); - - for(int descIdx = 0; descIdx < imageDescribers.size(); ++descIdx) - { - std::vector>& featuresPerView = featuresPerDescPerView.at(descIdx); - featuresPerView.resize(viewIds.size()); + if (folders.empty()) + { + ALICEVISION_LOG_ERROR("Cannot load features, no folders provided"); + return false; + } + if (viewIds.empty()) + { + ALICEVISION_LOG_ERROR("Cannot load features, no view ids provided"); + return false; + } + if (imageDescriberTypes.empty()) + { + ALICEVISION_LOG_ERROR("Cannot load features, no image desciber types provided"); + return false; + } -#pragma omp parallel for - for(int viewIdx = 0; viewIdx < viewIds.size(); ++viewIdx) + std::vector> imageDescribers; + imageDescribers.resize(imageDescriberTypes.size()); + + for (std::size_t i = 0; i < imageDescriberTypes.size(); ++i) + imageDescribers.at(i) = createImageDescriber(imageDescriberTypes.at(i)); + + featuresPerDescPerView.resize(imageDescribers.size()); + + std::atomic_bool loadingSuccess(true); + + for (int descIdx = 0; descIdx < imageDescribers.size(); ++descIdx) { - try - { - featuresPerView.at(viewIdx) = loadFeatures(folders, viewIds.at(viewIdx), *imageDescribers.at(descIdx)); - } - catch(const std::exception& e) - { - loadingSuccess = false; - } + std::vector>& featuresPerView = featuresPerDescPerView.at(descIdx); + featuresPerView.resize(viewIds.size()); + +#pragma omp parallel for + for (int viewIdx = 0; viewIdx < viewIds.size(); ++viewIdx) + { + try + { + featuresPerView.at(viewIdx) = loadFeatures(folders, viewIds.at(viewIdx), *imageDescribers.at(descIdx)); + } + catch (const std::exception& e) + { + loadingSuccess = false; + } + } } - } - return loadingSuccess; + return loadingSuccess; } bool loadRegionsPerView(feature::RegionsPerView& regionsPerView, - const SfMData& sfmData, - const std::vector& folders, - const std::vector& imageDescriberTypes, - const std::set& viewIdFilter) + const SfMData& sfmData, + const std::vector& folders, + const std::vector& imageDescriberTypes, + const std::set& viewIdFilter) { - std::vector featuresFolders = sfmData.getFeaturesFolders(); // add sfm features folders - featuresFolders.insert(featuresFolders.end(), folders.begin(), folders.end()); // add user features folders - auto last = std::unique(featuresFolders.begin(), featuresFolders.end()); - featuresFolders.erase(last, featuresFolders.end()); + std::vector featuresFolders = sfmData.getFeaturesFolders(); // add sfm features folders + featuresFolders.insert(featuresFolders.end(), folders.begin(), folders.end()); // add user features folders + auto last = std::unique(featuresFolders.begin(), featuresFolders.end()); + featuresFolders.erase(last, featuresFolders.end()); - auto progressDisplay = - system::createConsoleProgressDisplay(sfmData.getViews().size() * imageDescriberTypes.size(), - std::cout, "Loading regions\n"); + auto progressDisplay = + system::createConsoleProgressDisplay(sfmData.getViews().size() * imageDescriberTypes.size(), std::cout, "Loading regions\n"); - std::atomic_bool invalid(false); + std::atomic_bool invalid(false); - std::vector> imageDescribers; - imageDescribers.resize(imageDescriberTypes.size()); + std::vector> imageDescribers; + imageDescribers.resize(imageDescriberTypes.size()); - for(std::size_t i = 0; i < imageDescriberTypes.size(); ++i) - imageDescribers.at(i) = createImageDescriber(imageDescriberTypes.at(i)); + for (std::size_t i = 0; i < imageDescriberTypes.size(); ++i) + imageDescribers.at(i) = createImageDescriber(imageDescriberTypes.at(i)); #pragma omp parallel num_threads(3) - for(auto iter = sfmData.getViews().begin(); iter != sfmData.getViews().end() && !invalid; ++iter) - { + for (auto iter = sfmData.getViews().begin(); iter != sfmData.getViews().end() && !invalid; ++iter) + { #pragma omp single nowait - { - for(std::size_t i = 0; i < imageDescriberTypes.size(); ++i) - { - if(viewIdFilter.empty() || viewIdFilter.find(iter->second.get()->getViewId()) != viewIdFilter.end()) - { - std::unique_ptr regionsPtr; - try - { - regionsPtr = loadRegions(featuresFolders, iter->second.get()->getViewId(), *(imageDescribers.at(i))); - } - catch(const std::exception&) - { - invalid = true; - continue; - } + { + for (std::size_t i = 0; i < imageDescriberTypes.size(); ++i) + { + if (viewIdFilter.empty() || viewIdFilter.find(iter->second.get()->getViewId()) != viewIdFilter.end()) + { + std::unique_ptr regionsPtr; + try + { + regionsPtr = loadRegions(featuresFolders, iter->second.get()->getViewId(), *(imageDescribers.at(i))); + } + catch (const std::exception&) + { + invalid = true; + continue; + } #pragma omp critical - { - regionsPerView.addRegions(iter->second.get()->getViewId(), imageDescriberTypes.at(i), regionsPtr.release()); - ++progressDisplay; - } - } - } - } - } - return !invalid; + { + regionsPerView.addRegions(iter->second.get()->getViewId(), imageDescriberTypes.at(i), regionsPtr.release()); + ++progressDisplay; + } + } + } + } + } + return !invalid; } - bool loadFeaturesPerView(feature::FeaturesPerView& featuresPerView, - const SfMData& sfmData, - const std::vector& folders, - const std::vector& imageDescriberTypes) + const SfMData& sfmData, + const std::vector& folders, + const std::vector& imageDescriberTypes) { - std::vector featuresFolders = sfmData.getFeaturesFolders(); // add sfm features folders - featuresFolders.insert(featuresFolders.end(), folders.begin(), folders.end()); // add user features folders + std::vector featuresFolders = sfmData.getFeaturesFolders(); // add sfm features folders + featuresFolders.insert(featuresFolders.end(), folders.begin(), folders.end()); // add user features folders - ALICEVISION_LOG_DEBUG("List of provided feature folders:"); - for (auto it = folders.begin(); it != folders.end(); ++it) - ALICEVISION_LOG_DEBUG("\t - " << *it); + ALICEVISION_LOG_DEBUG("List of provided feature folders:"); + for (auto it = folders.begin(); it != folders.end(); ++it) + ALICEVISION_LOG_DEBUG("\t - " << *it); - ALICEVISION_LOG_DEBUG("List of feature folders to load:"); - for (auto it = featuresFolders.begin(); it != featuresFolders.end(); ++it) - ALICEVISION_LOG_DEBUG("\t - " << *it); + ALICEVISION_LOG_DEBUG("List of feature folders to load:"); + for (auto it = featuresFolders.begin(); it != featuresFolders.end(); ++it) + ALICEVISION_LOG_DEBUG("\t - " << *it); - auto progressDisplay = system::createConsoleProgressDisplay(sfmData.getViews().size(), std::cout, - "Loading features\n"); + auto progressDisplay = system::createConsoleProgressDisplay(sfmData.getViews().size(), std::cout, "Loading features\n"); - // read for each view the corresponding features and store them as PointFeatures - std::atomic_bool invalid(false); + // read for each view the corresponding features and store them as PointFeatures + std::atomic_bool invalid(false); - std::vector< std::unique_ptr > imageDescribers; - imageDescribers.resize(imageDescriberTypes.size()); + std::vector> imageDescribers; + imageDescribers.resize(imageDescriberTypes.size()); - for(std::size_t i = 0; i < imageDescriberTypes.size(); ++i) - imageDescribers.at(i) = createImageDescriber(imageDescriberTypes.at(i)); + for (std::size_t i = 0; i < imageDescriberTypes.size(); ++i) + imageDescribers.at(i) = createImageDescriber(imageDescriberTypes.at(i)); #pragma omp parallel - for (auto iter = sfmData.getViews().begin(); (iter != sfmData.getViews().end()) && (!invalid); ++iter) - { -#pragma omp single nowait + for (auto iter = sfmData.getViews().begin(); (iter != sfmData.getViews().end()) && (!invalid); ++iter) { - for(std::size_t i = 0; i < imageDescriberTypes.size(); ++i) - { - std::unique_ptr regionsPtr; - try - { - regionsPtr = loadFeatures(featuresFolders, iter->second.get()->getViewId(), *imageDescribers.at(i)); - } - catch(const std::exception&) +#pragma omp single nowait { - invalid = true; - continue; - } + for (std::size_t i = 0; i < imageDescriberTypes.size(); ++i) + { + std::unique_ptr regionsPtr; + try + { + regionsPtr = loadFeatures(featuresFolders, iter->second.get()->getViewId(), *imageDescribers.at(i)); + } + catch (const std::exception&) + { + invalid = true; + continue; + } #pragma omp critical - { - // save loaded Features as PointFeature - featuresPerView.addFeatures(iter->second.get()->getViewId(), imageDescriberTypes[i], regionsPtr->GetRegionsPositions()); - ++progressDisplay; + { + // save loaded Features as PointFeature + featuresPerView.addFeatures(iter->second.get()->getViewId(), imageDescriberTypes[i], regionsPtr->GetRegionsPositions()); + ++progressDisplay; + } + } } - } } - } - return !invalid; + return !invalid; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/regionsIO.hpp b/src/aliceVision/sfm/pipeline/regionsIO.hpp index 2279f4f012..37047732c6 100644 --- a/src/aliceVision/sfm/pipeline/regionsIO.hpp +++ b/src/aliceVision/sfm/pipeline/regionsIO.hpp @@ -46,7 +46,8 @@ std::unique_ptr loadFeatures(const std::vector& f * @return true if the features are correctlty loaded */ bool loadFeaturesPerDescPerView(std::vector>>& featuresPerDescPerView, - const std::vector& viewIds, const std::vector& folders, + const std::vector& viewIds, + const std::vector& folders, const std::vector& imageDescriberTypes); /** @@ -77,5 +78,5 @@ bool loadFeaturesPerView(feature::FeaturesPerView& featuresPerView, const std::vector& folders, const std::vector& imageDescriberTypes); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/relativePoses.hpp b/src/aliceVision/sfm/pipeline/relativePoses.hpp index 05a610527b..d6101cbc12 100644 --- a/src/aliceVision/sfm/pipeline/relativePoses.hpp +++ b/src/aliceVision/sfm/pipeline/relativePoses.hpp @@ -9,51 +9,47 @@ #include #include -namespace Eigen +namespace Eigen { +template +Eigen::Matrix tag_invoke(boost::json::value_to_tag>, boost::json::value const& jv) { - template - Eigen::Matrix tag_invoke(boost::json::value_to_tag>, boost::json::value const& jv) - { - Eigen::Matrix ret; - - std::vector buf = boost::json::value_to>(jv); + Eigen::Matrix ret; + + std::vector buf = boost::json::value_to>(jv); - int pos = 0; - for (int i = 0; i < M; i ++) + int pos = 0; + for (int i = 0; i < M; i++) + { + for (int j = 0; j < N; j++) { - for (int j = 0; j < N; j++) - { - ret(i, j) = buf[pos]; - pos++; - } + ret(i, j) = buf[pos]; + pos++; } - - return ret; } - template - void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, Eigen::Matrix const& input) - { - std::vector buf; + return ret; +} + +template +void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, Eigen::Matrix const& input) +{ + std::vector buf; - for (int i = 0; i < M; i ++) + for (int i = 0; i < M; i++) + { + for (int j = 0; j < N; j++) { - for (int j = 0; j < N; j++) - { - buf.push_back(input(i, j)); - } + buf.push_back(input(i, j)); } - - - jv = boost::json::value_from>(std::move(buf)); } + jv = boost::json::value_from>(std::move(buf)); } -namespace aliceVision -{ -namespace sfm -{ +} // namespace Eigen + +namespace aliceVision { +namespace sfm { struct ReconstructedPair { @@ -64,16 +60,13 @@ struct ReconstructedPair double score; }; - void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, sfm::ReconstructedPair const& input) { - jv = { - {"reference", input.reference}, - {"next", input.next}, - {"R", boost::json::value_from(SO3::logm(input.R))}, - {"t", boost::json::value_from(input.t)}, - {"score", boost::json::value_from(input.score)} - }; + jv = {{"reference", input.reference}, + {"next", input.next}, + {"R", boost::json::value_from(SO3::logm(input.R))}, + {"t", boost::json::value_from(input.t)}, + {"score", boost::json::value_from(input.score)}}; } ReconstructedPair tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) @@ -91,5 +84,5 @@ ReconstructedPair tag_invoke(boost::json::value_to_tag, boost return ret; } -} -} \ No newline at end of file +} // namespace sfm +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp index f98f2587bf..ca8c0542b8 100644 --- a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp +++ b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp @@ -42,10 +42,10 @@ #include #ifdef _MSC_VER -#pragma warning( once : 4267 ) //warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data + #pragma warning(once : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'const int', possible loss of data #endif -//#define ALICEVISION_NEXTBESTVIEW_WITHOUT_SCORE +// #define ALICEVISION_NEXTBESTVIEW_WITHOUT_SCORE namespace aliceVision { namespace sfm { @@ -66,375 +66,378 @@ using namespace aliceVision::sfmData; * @param[out] tracksPyramidPerView: * Precomputed list of pyramid cells ID for each track in each view. */ -void computeTracksPyramidPerView( - const track::TracksPerView& tracksPerView, - const track::TracksMap& map_tracks, - const Views& views, - const feature::FeaturesPerView& featuresProvider, - const std::size_t pyramidBase, - const std::size_t pyramidDepth, - track::TracksPyramidPerView& tracksPyramidPerView) +void computeTracksPyramidPerView(const track::TracksPerView& tracksPerView, + const track::TracksMap& map_tracks, + const Views& views, + const feature::FeaturesPerView& featuresProvider, + const std::size_t pyramidBase, + const std::size_t pyramidDepth, + track::TracksPyramidPerView& tracksPyramidPerView) { - std::vector widthPerLevel(pyramidDepth); - std::vector startPerLevel(pyramidDepth); - std::size_t start = 0; - for(std::size_t level = 0; level < pyramidDepth; ++level) - { - startPerLevel[level] = start; - widthPerLevel[level] = std::pow(pyramidBase, level+1); - start += Square(widthPerLevel[level]); - } - - tracksPyramidPerView.reserve(tracksPerView.size()); - for(const auto& viewTracks: tracksPerView) - { - auto& trackPyramid = tracksPyramidPerView[viewTracks.first]; - // reserve 500 tracks in each view - trackPyramid.reserve(500 * pyramidDepth); - } - - for(const auto& viewTracks: tracksPerView) - { - const auto viewId = viewTracks.first; - auto& tracksPyramidIndex = tracksPyramidPerView[viewId]; - const View& view = *views.at(viewId).get(); - std::vector cellWidthPerLevel(pyramidDepth); - std::vector cellHeightPerLevel(pyramidDepth); - for(std::size_t level = 0; level < pyramidDepth; ++level) - { - cellWidthPerLevel[level] = (double)view.getImage().getWidth() / (double)widthPerLevel[level]; - cellHeightPerLevel[level] = (double)view.getImage().getHeight() / (double)widthPerLevel[level]; + std::vector widthPerLevel(pyramidDepth); + std::vector startPerLevel(pyramidDepth); + std::size_t start = 0; + for (std::size_t level = 0; level < pyramidDepth; ++level) + { + startPerLevel[level] = start; + widthPerLevel[level] = std::pow(pyramidBase, level + 1); + start += Square(widthPerLevel[level]); + } + + tracksPyramidPerView.reserve(tracksPerView.size()); + for (const auto& viewTracks : tracksPerView) + { + auto& trackPyramid = tracksPyramidPerView[viewTracks.first]; + // reserve 500 tracks in each view + trackPyramid.reserve(500 * pyramidDepth); } - for(std::size_t i = 0; i < viewTracks.second.size(); ++i) - { - const std::size_t trackId = viewTracks.second[i]; - const track::Track& track = map_tracks.at(trackId); - const std::size_t featIndex = track.featPerView.at(viewId); - const auto& feature = featuresProvider.getFeatures(viewId, track.descType)[featIndex]; - - for(std::size_t level = 0; level < pyramidDepth; ++level) - { - std::size_t xCell = std::floor(std::max(feature.x(), 0.0f) / cellWidthPerLevel[level]); - std::size_t yCell = std::floor(std::max(feature.y(), 0.0f) / cellHeightPerLevel[level]); - xCell = std::min(xCell, widthPerLevel[level] - 1); - yCell = std::min(yCell, widthPerLevel[level] - 1); - const std::size_t levelIndex = xCell + yCell * widthPerLevel[level]; - assert(levelIndex < Square(widthPerLevel[level])); - tracksPyramidIndex[trackId * pyramidDepth + level] = startPerLevel[level] + levelIndex; - } + + for (const auto& viewTracks : tracksPerView) + { + const auto viewId = viewTracks.first; + auto& tracksPyramidIndex = tracksPyramidPerView[viewId]; + const View& view = *views.at(viewId).get(); + std::vector cellWidthPerLevel(pyramidDepth); + std::vector cellHeightPerLevel(pyramidDepth); + for (std::size_t level = 0; level < pyramidDepth; ++level) + { + cellWidthPerLevel[level] = (double)view.getImage().getWidth() / (double)widthPerLevel[level]; + cellHeightPerLevel[level] = (double)view.getImage().getHeight() / (double)widthPerLevel[level]; + } + for (std::size_t i = 0; i < viewTracks.second.size(); ++i) + { + const std::size_t trackId = viewTracks.second[i]; + const track::Track& track = map_tracks.at(trackId); + const std::size_t featIndex = track.featPerView.at(viewId); + const auto& feature = featuresProvider.getFeatures(viewId, track.descType)[featIndex]; + + for (std::size_t level = 0; level < pyramidDepth; ++level) + { + std::size_t xCell = std::floor(std::max(feature.x(), 0.0f) / cellWidthPerLevel[level]); + std::size_t yCell = std::floor(std::max(feature.y(), 0.0f) / cellHeightPerLevel[level]); + xCell = std::min(xCell, widthPerLevel[level] - 1); + yCell = std::min(yCell, widthPerLevel[level] - 1); + const std::size_t levelIndex = xCell + yCell * widthPerLevel[level]; + assert(levelIndex < Square(widthPerLevel[level])); + tracksPyramidIndex[trackId * pyramidDepth + level] = startPerLevel[level] + levelIndex; + } + } } - } } -ReconstructionEngine_sequentialSfM::ReconstructionEngine_sequentialSfM( - const SfMData& sfmData, - const Params& params, - const std::string& outputFolder, - const std::string& loggingFile) +ReconstructionEngine_sequentialSfM::ReconstructionEngine_sequentialSfM(const SfMData& sfmData, + const Params& params, + const std::string& outputFolder, + const std::string& loggingFile) : ReconstructionEngine(sfmData, outputFolder), _params(params), _htmlLogFile(loggingFile), _sfmStepFolder((fs::path(outputFolder) / "intermediate_steps").string()) { - if (_params.useLocalBundleAdjustment) - { - _localStrategyGraph = std::make_shared(_sfmData); if (_params.useLocalBundleAdjustment) - _localStrategyGraph->setGraphDistanceLimit(_params.localBundelAdjustementGraphDistanceLimit); - } - - // setup HTML logger - if(!_htmlLogFile.empty()) - { - _htmlDocStream = std::make_shared("[log] Sequential SfM reconstruction"); - _htmlDocStream->pushInfo(htmlDocument::htmlMarkup("h1", std::string("[log] Sequential SfM reconstruction"))); - _htmlDocStream->pushInfo("
"); - _htmlDocStream->pushInfo("Dataset info:"); - _htmlDocStream->pushInfo("Views count: " + htmlDocument::toString( sfmData.getViews().size()) + "
"); - } - - // create sfm intermediate step folder - if(!fs::exists(_sfmStepFolder) && _params.logIntermediateSteps) - fs::create_directory(_sfmStepFolder); + { + _localStrategyGraph = std::make_shared(_sfmData); + if (_params.useLocalBundleAdjustment) + _localStrategyGraph->setGraphDistanceLimit(_params.localBundelAdjustementGraphDistanceLimit); + } + + // setup HTML logger + if (!_htmlLogFile.empty()) + { + _htmlDocStream = std::make_shared("[log] Sequential SfM reconstruction"); + _htmlDocStream->pushInfo(htmlDocument::htmlMarkup("h1", std::string("[log] Sequential SfM reconstruction"))); + _htmlDocStream->pushInfo("
"); + _htmlDocStream->pushInfo("Dataset info:"); + _htmlDocStream->pushInfo("Views count: " + htmlDocument::toString(sfmData.getViews().size()) + "
"); + } + + // create sfm intermediate step folder + if (!fs::exists(_sfmStepFolder) && _params.logIntermediateSteps) + fs::create_directory(_sfmStepFolder); } bool ReconstructionEngine_sequentialSfM::process() { - initializePyramidScoring(); - - if(fuseMatchesIntoTracks() == 0) - { - throw std::runtime_error("No valid tracks."); - } - - if (!_sfmData.getLandmarks().empty()) - { - if (_sfmData.getPoses().empty()) - throw std::runtime_error("You cannot have landmarks without valid poses."); - - // If we have already reconstructed landmarks, we need to recognize the corresponding tracks - // and update the landmarkIds accordingly. - // Note: each landmark has a corresponding track with the same id (landmarkId == trackId). - remapLandmarkIdsToTrackIds(); - - if (_params.useLocalBundleAdjustment) - { - const std::set reconstructedViews = _sfmData.getValidViews(); - if (!reconstructedViews.empty()) - { - // Add the reconstructed views to the LocalBA graph - _localStrategyGraph->updateGraphWithNewViews(_sfmData, _map_tracksPerView, reconstructedViews, _params.kMinNbOfMatches); - _localStrategyGraph->updateRigEdgesToTheGraph(_sfmData); - } - } - } - - // initial pair choice - if(_sfmData.getPoses().empty()) - { - std::vector initialImagePairCandidates = getInitialImagePairsCandidates(); - createInitialReconstruction(initialImagePairCandidates); - } - else - { - // If we don't have any landmark, we need to triangulate them from the known poses. - // But even if we already have landmarks, we need to try to triangulate new points with the current set of parameters. - std::set prevReconstructedViews = _sfmData.getValidViews(); - - triangulate({}, prevReconstructedViews); - bundleAdjustment(prevReconstructedViews); - - // The optimization could allow the triangulation of new landmarks - triangulate({}, prevReconstructedViews); - bundleAdjustment(prevReconstructedViews); - } - - // reconstruction - const double elapsedTime = incrementalReconstruction(); - - exportStatistics(elapsedTime); - - return !_sfmData.getPoses().empty(); + initializePyramidScoring(); + + if (fuseMatchesIntoTracks() == 0) + { + throw std::runtime_error("No valid tracks."); + } + + if (!_sfmData.getLandmarks().empty()) + { + if (_sfmData.getPoses().empty()) + throw std::runtime_error("You cannot have landmarks without valid poses."); + + // If we have already reconstructed landmarks, we need to recognize the corresponding tracks + // and update the landmarkIds accordingly. + // Note: each landmark has a corresponding track with the same id (landmarkId == trackId). + remapLandmarkIdsToTrackIds(); + + if (_params.useLocalBundleAdjustment) + { + const std::set reconstructedViews = _sfmData.getValidViews(); + if (!reconstructedViews.empty()) + { + // Add the reconstructed views to the LocalBA graph + _localStrategyGraph->updateGraphWithNewViews(_sfmData, _map_tracksPerView, reconstructedViews, _params.kMinNbOfMatches); + _localStrategyGraph->updateRigEdgesToTheGraph(_sfmData); + } + } + } + + // initial pair choice + if (_sfmData.getPoses().empty()) + { + std::vector initialImagePairCandidates = getInitialImagePairsCandidates(); + createInitialReconstruction(initialImagePairCandidates); + } + else + { + // If we don't have any landmark, we need to triangulate them from the known poses. + // But even if we already have landmarks, we need to try to triangulate new points with the current set of parameters. + std::set prevReconstructedViews = _sfmData.getValidViews(); + + triangulate({}, prevReconstructedViews); + bundleAdjustment(prevReconstructedViews); + + // The optimization could allow the triangulation of new landmarks + triangulate({}, prevReconstructedViews); + bundleAdjustment(prevReconstructedViews); + } + + // reconstruction + const double elapsedTime = incrementalReconstruction(); + + exportStatistics(elapsedTime); + + return !_sfmData.getPoses().empty(); } void ReconstructionEngine_sequentialSfM::initializePyramidScoring() { - // update cache values - if(_pyramidWeights.size() != _params.pyramidDepth) - { - _pyramidWeights.resize(_params.pyramidDepth); - std::size_t maxWeight = 0; - for(std::size_t level = 0; level < _params.pyramidDepth; ++level) - { - std::size_t nbCells = Square(std::pow(_params.pyramidBase, level+1)); - // We use a different weighting strategy than [Schonberger 2016]. - // They use w = 2^l with l={1...L} (even if there is a typo in the text where they say to use w=2^{2*l}. - // We prefer to give more importance to the first levels of the pyramid, so: - // w = 2^{L-l} with L the number of levels in the pyramid. - _pyramidWeights[level] = std::pow(2.0, (_params.pyramidDepth-(level+1))); - maxWeight += nbCells * _pyramidWeights[level]; + // update cache values + if (_pyramidWeights.size() != _params.pyramidDepth) + { + _pyramidWeights.resize(_params.pyramidDepth); + std::size_t maxWeight = 0; + for (std::size_t level = 0; level < _params.pyramidDepth; ++level) + { + std::size_t nbCells = Square(std::pow(_params.pyramidBase, level + 1)); + // We use a different weighting strategy than [Schonberger 2016]. + // They use w = 2^l with l={1...L} (even if there is a typo in the text where they say to use w=2^{2*l}. + // We prefer to give more importance to the first levels of the pyramid, so: + // w = 2^{L-l} with L the number of levels in the pyramid. + _pyramidWeights[level] = std::pow(2.0, (_params.pyramidDepth - (level + 1))); + maxWeight += nbCells * _pyramidWeights[level]; + } + _pyramidThreshold = maxWeight * 0.2; } - _pyramidThreshold = maxWeight * 0.2; - } } std::size_t ReconstructionEngine_sequentialSfM::fuseMatchesIntoTracks() { - // compute tracks from matches - track::TracksBuilder tracksBuilder; + // compute tracks from matches + track::TracksBuilder tracksBuilder; - { - // list of features matches for each couple of images - const aliceVision::matching::PairwiseMatches& matches = *_pairwiseMatches; + { + // list of features matches for each couple of images + const aliceVision::matching::PairwiseMatches& matches = *_pairwiseMatches; - ALICEVISION_LOG_DEBUG("Track building"); - tracksBuilder.build(matches); + ALICEVISION_LOG_DEBUG("Track building"); + tracksBuilder.build(matches); - ALICEVISION_LOG_DEBUG("Track filtering"); - tracksBuilder.filter(_params.filterTrackForks, _params.minInputTrackLength); + ALICEVISION_LOG_DEBUG("Track filtering"); + tracksBuilder.filter(_params.filterTrackForks, _params.minInputTrackLength); - ALICEVISION_LOG_DEBUG("Track export to internal structure"); - // build tracks with STL compliant type - tracksBuilder.exportToSTL(_map_tracks); - ALICEVISION_LOG_DEBUG("Build tracks per view"); + ALICEVISION_LOG_DEBUG("Track export to internal structure"); + // build tracks with STL compliant type + tracksBuilder.exportToSTL(_map_tracks); + ALICEVISION_LOG_DEBUG("Build tracks per view"); - // Init tracksPerView to have an entry in the map for each view (even if there is no track at all) - for(const auto& viewIt: _sfmData.getViews()) - { - // create an entry in the map - _map_tracksPerView[viewIt.first]; - } - track::computeTracksPerView(_map_tracks, _map_tracksPerView); - ALICEVISION_LOG_DEBUG("Build tracks pyramid per view"); - computeTracksPyramidPerView( - _map_tracksPerView, _map_tracks, _sfmData.getViews(), *_featuresPerView, _params.pyramidBase, _params.pyramidDepth, _map_featsPyramidPerView); - - // display stats - { - std::set imagesId; - track::imageIdInTracks(_map_tracksPerView, imagesId); - - ALICEVISION_LOG_INFO("Fuse matches into tracks: " << std::endl - << "\t- # tracks: " << tracksBuilder.nbTracks() << std::endl - << "\t- # images in tracks: " << imagesId.size()); - - std::map map_Occurence_TrackLength; - track::tracksLength(_map_tracks, map_Occurence_TrackLength); - ALICEVISION_LOG_INFO("TrackLength, Occurrence"); - for(const auto& iter: map_Occurence_TrackLength) - { - // add input tracks histogram - _jsonLogTree.add("sfm.inputtracks_histogram." + std::to_string(iter.first), iter.second); - ALICEVISION_LOG_INFO("\t" << iter.first << "\t" << iter.second); - } + // Init tracksPerView to have an entry in the map for each view (even if there is no track at all) + for (const auto& viewIt : _sfmData.getViews()) + { + // create an entry in the map + _map_tracksPerView[viewIt.first]; + } + track::computeTracksPerView(_map_tracks, _map_tracksPerView); + ALICEVISION_LOG_DEBUG("Build tracks pyramid per view"); + computeTracksPyramidPerView(_map_tracksPerView, + _map_tracks, + _sfmData.getViews(), + *_featuresPerView, + _params.pyramidBase, + _params.pyramidDepth, + _map_featsPyramidPerView); + + // display stats + { + std::set imagesId; + track::imageIdInTracks(_map_tracksPerView, imagesId); + + ALICEVISION_LOG_INFO("Fuse matches into tracks: " << std::endl + << "\t- # tracks: " << tracksBuilder.nbTracks() << std::endl + << "\t- # images in tracks: " << imagesId.size()); + + std::map map_Occurence_TrackLength; + track::tracksLength(_map_tracks, map_Occurence_TrackLength); + ALICEVISION_LOG_INFO("TrackLength, Occurrence"); + for (const auto& iter : map_Occurence_TrackLength) + { + // add input tracks histogram + _jsonLogTree.add("sfm.inputtracks_histogram." + std::to_string(iter.first), iter.second); + ALICEVISION_LOG_INFO("\t" << iter.first << "\t" << iter.second); + } + } } - } - return _map_tracks.size(); + return _map_tracks.size(); } std::vector ReconstructionEngine_sequentialSfM::getInitialImagePairsCandidates() { - std::vector initialImagePairCandidates; - - if(_params.userInitialImagePair.first == UndefinedIndexT || _params.userInitialImagePair.second == UndefinedIndexT) - { - IndexT filterViewId = UndefinedIndexT; - - if(_params.userInitialImagePair.first != UndefinedIndexT) - filterViewId = _params.userInitialImagePair.first; - else if(_params.userInitialImagePair.second != UndefinedIndexT) - filterViewId = _params.userInitialImagePair.second; - - if(!getBestInitialImagePairs(initialImagePairCandidates, filterViewId)) - throw std::runtime_error("No valid initial pair found automatically."); - } - else - { - initialImagePairCandidates.emplace_back(_params.userInitialImagePair); - } - - return initialImagePairCandidates; + std::vector initialImagePairCandidates; + + if (_params.userInitialImagePair.first == UndefinedIndexT || _params.userInitialImagePair.second == UndefinedIndexT) + { + IndexT filterViewId = UndefinedIndexT; + + if (_params.userInitialImagePair.first != UndefinedIndexT) + filterViewId = _params.userInitialImagePair.first; + else if (_params.userInitialImagePair.second != UndefinedIndexT) + filterViewId = _params.userInitialImagePair.second; + + if (!getBestInitialImagePairs(initialImagePairCandidates, filterViewId)) + throw std::runtime_error("No valid initial pair found automatically."); + } + else + { + initialImagePairCandidates.emplace_back(_params.userInitialImagePair); + } + + return initialImagePairCandidates; } void ReconstructionEngine_sequentialSfM::createInitialReconstruction(const std::vector& initialImagePairCandidates) { - // initial pair Essential Matrix and [R|t] estimation. - for(const auto& initialPairCandidate: initialImagePairCandidates) - { - if(makeInitialPair3D(initialPairCandidate)) + // initial pair Essential Matrix and [R|t] estimation. + for (const auto& initialPairCandidate : initialImagePairCandidates) { - // successfully found an initial image pair - ALICEVISION_LOG_INFO("Initial pair is: " << initialPairCandidate.first << ", " << initialPairCandidate.second); + if (makeInitialPair3D(initialPairCandidate)) + { + // successfully found an initial image pair + ALICEVISION_LOG_INFO("Initial pair is: " << initialPairCandidate.first << ", " << initialPairCandidate.second); - std::set updatedViews; - updatedViews.insert(initialPairCandidate.first); - updatedViews.insert(initialPairCandidate.second); + std::set updatedViews; + updatedViews.insert(initialPairCandidate.first); + updatedViews.insert(initialPairCandidate.second); - return; + return; + } } - } - throw std::runtime_error("Initialization failed after trying all possible initial image pairs."); + throw std::runtime_error("Initialization failed after trying all possible initial image pairs."); } void ReconstructionEngine_sequentialSfM::registerChanges(std::set& linkedViews, const std::set& newReconstructedViews) { - linkedViews.clear(); - - const sfmData::Landmarks & landmarks = _sfmData.getLandmarks(); - for (IndexT id : newReconstructedViews) - { - const track::TrackIdSet & setTracks = _map_tracksPerView[id]; - - for (auto idTrack : setTracks) - { - //Check that this track is indeed a landmark - if (landmarks.find(idTrack) == landmarks.end()) - { - continue; - } - - //Check that this view is a registered observation of this landmark (confirmed track) - const Landmark & l = landmarks.at(idTrack); - if (l.observations.find(id) == l.observations.end()) - { - continue; - } - - const track::Track & track = _map_tracks[idTrack]; - for (const auto & pairFeat : track.featPerView) - { - IndexT oview = pairFeat.first; - if (oview == id) - { - continue; - } - - if (_sfmData.isPoseAndIntrinsicDefined(oview)) - { - continue; - } + linkedViews.clear(); + + const sfmData::Landmarks& landmarks = _sfmData.getLandmarks(); + for (IndexT id : newReconstructedViews) + { + const track::TrackIdSet& setTracks = _map_tracksPerView[id]; + + for (auto idTrack : setTracks) + { + // Check that this track is indeed a landmark + if (landmarks.find(idTrack) == landmarks.end()) + { + continue; + } + + // Check that this view is a registered observation of this landmark (confirmed track) + const Landmark& l = landmarks.at(idTrack); + if (l.observations.find(id) == l.observations.end()) + { + continue; + } - linkedViews.insert(oview); - } + const track::Track& track = _map_tracks[idTrack]; + for (const auto& pairFeat : track.featPerView) + { + IndexT oview = pairFeat.first; + if (oview == id) + { + continue; + } + + if (_sfmData.isPoseAndIntrinsicDefined(oview)) + { + continue; + } + + linkedViews.insert(oview); + } + } } - } } void ReconstructionEngine_sequentialSfM::remapLandmarkIdsToTrackIds() { - using namespace track; - - // get unmap landmarks - Landmarks landmarks; + using namespace track; - // clear sfmData structure and store them locally - std::swap(landmarks, _sfmData.getLandmarks()); + // get unmap landmarks + Landmarks landmarks; - // builds landmarks temporary comparison structure - // ObsKey - // ObsToLandmark - using ObsKey = std::tuple; - using ObsToLandmark = std::map; + // clear sfmData structure and store them locally + std::swap(landmarks, _sfmData.getLandmarks()); - ObsToLandmark obsToLandmark; + // builds landmarks temporary comparison structure + // ObsKey + // ObsToLandmark + using ObsKey = std::tuple; + using ObsToLandmark = std::map; - ALICEVISION_LOG_DEBUG("Builds landmarks temporary comparison structure"); + ObsToLandmark obsToLandmark; - for(const auto& landmarkPair : landmarks) - { - const IndexT landmarkId = landmarkPair.first; - const IndexT firstViewId = landmarkPair.second.observations.begin()->first; - const IndexT firstFeatureId = landmarkPair.second.observations.begin()->second.id_feat; - const feature::EImageDescriberType descType = landmarkPair.second.descType; + ALICEVISION_LOG_DEBUG("Builds landmarks temporary comparison structure"); - obsToLandmark.emplace(ObsKey(firstViewId, firstFeatureId, descType), landmarkId); - } + for (const auto& landmarkPair : landmarks) + { + const IndexT landmarkId = landmarkPair.first; + const IndexT firstViewId = landmarkPair.second.observations.begin()->first; + const IndexT firstFeatureId = landmarkPair.second.observations.begin()->second.id_feat; + const feature::EImageDescriberType descType = landmarkPair.second.descType; - ALICEVISION_LOG_DEBUG("Find corresponding landmark id per track id"); + obsToLandmark.emplace(ObsKey(firstViewId, firstFeatureId, descType), landmarkId); + } - // find corresponding landmark id per track id - for(const auto& trackPair : _map_tracks) - { - const IndexT trackId = trackPair.first; - const Track& track = trackPair.second; + ALICEVISION_LOG_DEBUG("Find corresponding landmark id per track id"); - for(const auto& featView : track.featPerView) + // find corresponding landmark id per track id + for (const auto& trackPair : _map_tracks) { - const ObsToLandmark::const_iterator it = obsToLandmark.find(ObsKey(featView.first, featView.second, track.descType)); + const IndexT trackId = trackPair.first; + const Track& track = trackPair.second; + + for (const auto& featView : track.featPerView) + { + const ObsToLandmark::const_iterator it = obsToLandmark.find(ObsKey(featView.first, featView.second, track.descType)); - if(it != obsToLandmark.end()) - { - // re-insert the landmark with the new id - _sfmData.getLandmarks().emplace(trackId, landmarks.find(it->second)->second); - break; //one landmark per track - } + if (it != obsToLandmark.end()) + { + // re-insert the landmark with the new id + _sfmData.getLandmarks().emplace(trackId, landmarks.find(it->second)->second); + break; // one landmark per track + } + } } - } - ALICEVISION_LOG_INFO("Landmark ids to track ids remapping: " << std::endl - << "\t- # tracks: " << _map_tracks.size() << std::endl - << "\t- # input landmarks: " << landmarks.size() << std::endl - << "\t- # output landmarks: " << _sfmData.getLandmarks().size()); + ALICEVISION_LOG_INFO("Landmark ids to track ids remapping: " << std::endl + << "\t- # tracks: " << _map_tracks.size() << std::endl + << "\t- # input landmarks: " << landmarks.size() << std::endl + << "\t- # output landmarks: " << _sfmData.getLandmarks().size()); } double ReconstructionEngine_sequentialSfM::incrementalReconstruction() @@ -452,19 +455,19 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() std::vector bestViewCandidates; // get all viewIds and max resection id - for(const auto& viewPair : _sfmData.getViews()) + for (const auto& viewPair : _sfmData.getViews()) { IndexT viewId = viewPair.second->getViewId(); IndexT viewResectionId = viewPair.second->getResectionId(); // Create a list of remaining views to estimate - if(!_sfmData.isPoseAndIntrinsicDefined(viewId)) + if (!_sfmData.isPoseAndIntrinsicDefined(viewId)) { viewsToVisit.insert(viewId); } // Make sure we can use the higher resectionIds - if(viewResectionId != UndefinedIndexT && viewResectionId > resectionId) + if (viewResectionId != UndefinedIndexT && viewResectionId > resectionId) { resectionId = viewResectionId + 1; } @@ -475,7 +478,7 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() std::stringstream ss; ss << "Begin Incremental Reconstruction:" << std::endl; - if(_sfmData.getViews().size() == viewsToVisit.size()) + if (_sfmData.getViews().size() == viewsToVisit.size()) { ss << "\t- mode: SfM creation" << std::endl; } @@ -498,45 +501,41 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() // Compute intersection of available views and views with potential changes nbValidPoses = _sfmData.getPoses().size(); - ALICEVISION_LOG_INFO("Incremental Reconstruction start iteration " - << globalIteration << ":" << std::endl - << "\t- # number of resection groups: " << resectionId << std::endl - << "\t- # number of poses: " << nbValidPoses << std::endl - << "\t- # number of landmarks: " << _sfmData.getLandmarks().size() << std::endl); + ALICEVISION_LOG_INFO("Incremental Reconstruction start iteration " << globalIteration << ":" << std::endl + << "\t- # number of resection groups: " << resectionId << std::endl + << "\t- # number of poses: " << nbValidPoses << std::endl + << "\t- # number of landmarks: " << _sfmData.getLandmarks().size() + << std::endl); - for(auto v : potentials) + for (auto v : potentials) { - if(!_sfmData.isPoseAndIntrinsicDefined(v)) + if (!_sfmData.isPoseAndIntrinsicDefined(v)) { viewsToVisit.insert(v); } } potentials.clear(); - // get set of reconstructed views std::set prevReconstructedViews = _sfmData.getValidViews(); // compute robust resection of remaining images - while(findNextBestViews(bestViewCandidates, viewsToVisit)) + while (findNextBestViews(bestViewCandidates, viewsToVisit)) { ALICEVISION_LOG_INFO("Update Reconstruction:" << std::endl << "\t- resection id: " << resectionId << std::endl - << "\t- # images in the resection group: " - << bestViewCandidates.size() << std::endl + << "\t- # images in the resection group: " << bestViewCandidates.size() << std::endl << "\t- # images remaining: " << viewsToVisit.size()); - - // Erase reconstructed views from list of available views asap - for(auto v : bestViewCandidates) + for (auto v : bestViewCandidates) { viewsToVisit.erase(v); } - //Return the difference between reconstructed views and prevReconstructedViews + // Return the difference between reconstructed views and prevReconstructedViews std::set newReconstructedViews = resection(resectionId, bestViewCandidates, prevReconstructedViews); - if(newReconstructedViews.empty()) + if (newReconstructedViews.empty()) { continue; } @@ -556,7 +555,7 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() } // No bundle if we did not accumulate enough resectionned views - if(newReconstructedViews.size() < minimalResectionedViewsForBundle && viewsToVisit.size() > 0) + if (newReconstructedViews.size() < minimalResectionedViewsForBundle && viewsToVisit.size() > 0) { continue; } @@ -564,33 +563,31 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() triangulate(prevReconstructedViews, newReconstructedViews); bundleAdjustment(newReconstructedViews); - //Only update prevReconstructedViews after the resectioned views have been refined + // Only update prevReconstructedViews after the resectioned views have been refined prevReconstructedViews = _sfmData.getValidViews(); // Compute the connected views to inform we have new information ! registerChanges(linkedViewIds, newReconstructedViews); - std::set_union(potentials.begin(), potentials.end(), linkedViewIds.begin(), linkedViewIds.end(), std::inserter(potentials, potentials.end())); + std::set_union( + potentials.begin(), potentials.end(), linkedViewIds.begin(), linkedViewIds.end(), std::inserter(potentials, potentials.end())); // scene logging for visual debug - if(_params.logIntermediateSteps && (resectionId % 3) == 0) + if (_params.logIntermediateSteps && (resectionId % 3) == 0) { auto chrono_start = std::chrono::steady_clock::now(); std::ostringstream os; os << "sfm_" << std::setw(8) << std::setfill('0') << resectionId; - sfmDataIO::Save(_sfmData, - (fs::path(_sfmStepFolder) / (os.str() + _params.sfmStepFileExtension)).string(), - _params.sfmStepFilter); - ALICEVISION_LOG_DEBUG("Save of file " << os.str() << " took " - << std::chrono::duration_cast( - std::chrono::steady_clock::now() - chrono_start) - .count() - << " msec."); + sfmDataIO::Save(_sfmData, (fs::path(_sfmStepFolder) / (os.str() + _params.sfmStepFileExtension)).string(), _params.sfmStepFilter); + ALICEVISION_LOG_DEBUG( + "Save of file " << os.str() << " took " + << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() + << " msec."); } ++resectionId; } - if(_params.rig.useRigConstraint && !_sfmData.getRigs().empty()) + if (_params.rig.useRigConstraint && !_sfmData.getRigs().empty()) { ALICEVISION_LOG_INFO("Rig(s) calibration start"); @@ -599,7 +596,7 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() calibrateRigs(updatedViews); // update rig edges in the local BA graph - if(_params.useLocalBundleAdjustment) + if (_params.useLocalBundleAdjustment) _localStrategyGraph->updateRigEdgesToTheGraph(_sfmData); // after rig calibration, camera may have moved by replacing independant poses by a rig pose with a common @@ -615,900 +612,907 @@ double ReconstructionEngine_sequentialSfM::incrementalReconstruction() ALICEVISION_LOG_WARNING("Rig calibration finished:\n\t- # updated views: " << updatedViews.size()); } ++globalIteration; - } - while(nbValidPoses != _sfmData.getPoses().size()); + } while (nbValidPoses != _sfmData.getPoses().size()); - ALICEVISION_LOG_INFO("Incremental Reconstruction completed with " - << globalIteration << " iterations:" << std::endl - << "\t- # number of resection groups: " << resectionId << std::endl - << "\t- # number of poses: " << nbValidPoses << std::endl - << "\t- # number of landmarks: " << _sfmData.getLandmarks().size() << std::endl); + ALICEVISION_LOG_INFO("Incremental Reconstruction completed with " << globalIteration << " iterations:" << std::endl + << "\t- # number of resection groups: " << resectionId << std::endl + << "\t- # number of poses: " << nbValidPoses << std::endl + << "\t- # number of landmarks: " << _sfmData.getLandmarks().size() + << std::endl); return timer.elapsed(); } - std::set ReconstructionEngine_sequentialSfM::resection(IndexT resectionId, - const std::vector& bestViewIds, - const std::set& prevReconstructedViews) +std::set ReconstructionEngine_sequentialSfM::resection(IndexT resectionId, + const std::vector& bestViewIds, + const std::set& prevReconstructedViews) { - auto chrono_start = std::chrono::steady_clock::now(); + auto chrono_start = std::chrono::steady_clock::now(); - // add images to the 3D reconstruction + // add images to the 3D reconstruction #pragma omp parallel for - for(int i = 0; i < bestViewIds.size(); ++i) - { - const IndexT viewId = bestViewIds.at(i); - const View& view = *_sfmData.getViews().at(viewId); - - if(view.isPartOfRig()) - { - // some views can become indirectly localized when the sub-pose becomes defined - if(_sfmData.isPoseAndIntrinsicDefined(view.getViewId())) - { - ALICEVISION_LOG_DEBUG("Resection of image " << i << " was skipped." << std::endl - << "View indirectly localized, sub-pose and pose already defined." << std::endl - << "\t- view id: " << viewId << std::endl - << "\t- rig id: " << view.getRigId() << std::endl - << "\t- sub-pose id: " << view.getSubPoseId()); - - continue; - } - - // we cannot localize a view if it is part of an initialized rig with unknown rig pose and unknown sub-pose - const bool knownPose = _sfmData.existsPose(view); - const Rig& rig = _sfmData.getRig(view); - const RigSubPose& subpose = rig.getSubPose(view.getSubPoseId()); - - if(rig.isInitialized() && !knownPose && (subpose.status == ERigSubPoseStatus::UNINITIALIZED)) - { - ALICEVISION_LOG_DEBUG("Resection of image " << i << " was skipped." << std::endl - << "Rig initialized but unkown pose and sub-pose." << std::endl - << "\t- view id: " << viewId << std::endl - << "\t- rig id: " << view.getRigId() << std::endl - << "\t- sub-pose id: " << view.getSubPoseId()); - - continue; - } - } + for (int i = 0; i < bestViewIds.size(); ++i) + { + const IndexT viewId = bestViewIds.at(i); + const View& view = *_sfmData.getViews().at(viewId); + + if (view.isPartOfRig()) + { + // some views can become indirectly localized when the sub-pose becomes defined + if (_sfmData.isPoseAndIntrinsicDefined(view.getViewId())) + { + ALICEVISION_LOG_DEBUG("Resection of image " << i << " was skipped." << std::endl + << "View indirectly localized, sub-pose and pose already defined." << std::endl + << "\t- view id: " << viewId << std::endl + << "\t- rig id: " << view.getRigId() << std::endl + << "\t- sub-pose id: " << view.getSubPoseId()); + + continue; + } - ResectionData newResectionData; - newResectionData.error_max = _params.localizerEstimatorError; - newResectionData.max_iteration = _params.localizerEstimatorMaxIterations; - const bool hasResected = computeResection(viewId, newResectionData); + // we cannot localize a view if it is part of an initialized rig with unknown rig pose and unknown sub-pose + const bool knownPose = _sfmData.existsPose(view); + const Rig& rig = _sfmData.getRig(view); + const RigSubPose& subpose = rig.getSubPose(view.getSubPoseId()); + + if (rig.isInitialized() && !knownPose && (subpose.status == ERigSubPoseStatus::UNINITIALIZED)) + { + ALICEVISION_LOG_DEBUG("Resection of image " << i << " was skipped." << std::endl + << "Rig initialized but unkown pose and sub-pose." << std::endl + << "\t- view id: " << viewId << std::endl + << "\t- rig id: " << view.getRigId() << std::endl + << "\t- sub-pose id: " << view.getSubPoseId()); + + continue; + } + } + + ResectionData newResectionData; + newResectionData.error_max = _params.localizerEstimatorError; + newResectionData.max_iteration = _params.localizerEstimatorMaxIterations; + const bool hasResected = computeResection(viewId, newResectionData); #pragma omp critical - { - if(hasResected) - { - updateScene(viewId, newResectionData); - ALICEVISION_LOG_DEBUG("Resection of image " << i << " ( view id: " << viewId << " ) succeed."); - _sfmData.getViews().at(viewId)->setResectionId(resectionId); - } - else - { - ALICEVISION_LOG_DEBUG("Resection of image " << i << " ( view id: " << viewId << " ) was not possible."); - } + { + if (hasResected) + { + updateScene(viewId, newResectionData); + ALICEVISION_LOG_DEBUG("Resection of image " << i << " ( view id: " << viewId << " ) succeed."); + _sfmData.getViews().at(viewId)->setResectionId(resectionId); + } + else + { + ALICEVISION_LOG_DEBUG("Resection of image " << i << " ( view id: " << viewId << " ) was not possible."); + } + } } - } - - ALICEVISION_LOG_DEBUG("Resection of " << bestViewIds.size() << " new images took " << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() << " msec."); - // get new reconstructed views - std::set newReconstructedViews; - { - // get reconstructed views after resection - const std::set reconstructedViews = _sfmData.getValidViews(); + ALICEVISION_LOG_DEBUG( + "Resection of " << bestViewIds.size() << " new images took " + << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() << " msec."); - std::set_difference( - reconstructedViews.begin(), - reconstructedViews.end(), - prevReconstructedViews.begin(), - prevReconstructedViews.end(), - std::inserter(newReconstructedViews, newReconstructedViews.end())); - } + // get new reconstructed views + std::set newReconstructedViews; + { + // get reconstructed views after resection + const std::set reconstructedViews = _sfmData.getValidViews(); + + std::set_difference(reconstructedViews.begin(), + reconstructedViews.end(), + prevReconstructedViews.begin(), + prevReconstructedViews.end(), + std::inserter(newReconstructedViews, newReconstructedViews.end())); + } - return newReconstructedViews; + return newReconstructedViews; } void ReconstructionEngine_sequentialSfM::triangulate(const std::set& prevReconstructedViews, const std::set& newReconstructedViews) { - auto chrono_start = std::chrono::steady_clock::now(); + auto chrono_start = std::chrono::steady_clock::now(); - // allow to use to the old triangulatation algorithm (using 2 views only) - if(_params.minNbObservationsForTriangulation == 0) - triangulate_2Views(_sfmData, prevReconstructedViews, newReconstructedViews); - else - triangulate_multiViewsLORANSAC(_sfmData, prevReconstructedViews, newReconstructedViews); + // allow to use to the old triangulatation algorithm (using 2 views only) + if (_params.minNbObservationsForTriangulation == 0) + triangulate_2Views(_sfmData, prevReconstructedViews, newReconstructedViews); + else + triangulate_multiViewsLORANSAC(_sfmData, prevReconstructedViews, newReconstructedViews); - ALICEVISION_LOG_DEBUG("Triangulation of the " << newReconstructedViews.size() << " newly reconstructed views took " << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() << " msec."); + ALICEVISION_LOG_DEBUG( + "Triangulation of the " << newReconstructedViews.size() << " newly reconstructed views took " + << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() + << " msec."); } bool ReconstructionEngine_sequentialSfM::bundleAdjustment(std::set& newReconstructedViews, bool isInitialPair) { - ALICEVISION_LOG_INFO("Bundle adjustment start."); - auto chronoStart = std::chrono::steady_clock::now(); - - BundleAdjustmentCeres::CeresOptions options; - BundleAdjustment::ERefineOptions refineOptions = BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE; - - if(!isInitialPair && !_params.lockAllIntrinsics) - refineOptions |= BundleAdjustment::REFINE_INTRINSICS_ALL; - - const int nbOutliersThreshold = - (isInitialPair) ? 0 : _params.bundleAdjustmentMaxOutliers; - std::size_t iteration = 0; - std::size_t nbOutliers = 0; - bool enableLocalStrategy = false; - - // enable Sparse solver and local strategy - if(_sfmData.getPoses().size() > 100) - { - options.setSparseBA(); - if(_params.useLocalBundleAdjustment) // local strategy enable if more than 100 poses - enableLocalStrategy = true; - } - else - { - options.setDenseBA(); - } - - // add the new reconstructed views to the graph - if(_params.useLocalBundleAdjustment) - _localStrategyGraph->updateGraphWithNewViews(_sfmData, _map_tracksPerView, newReconstructedViews, _params.kMinNbOfMatches); - - - if(enableLocalStrategy) - { - // compute the graph-distance between each newly reconstructed views and all the reconstructed views - _localStrategyGraph->computeGraphDistances(_sfmData, newReconstructedViews); - - // use the graph-distances to assign a state (Refine, Constant & Ignore) for each parameter (poses, intrinsics & landmarks) - _localStrategyGraph->convertDistancesToStates(_sfmData); - - const std::size_t nbRefinedPoses = _localStrategyGraph->getNbPosesPerState(BundleAdjustment::EParameterState::REFINED); - const std::size_t nbConstantPoses = _localStrategyGraph->getNbPosesPerState(BundleAdjustment::EParameterState::CONSTANT); - - // restore the Dense linear solver type if the number of cameras in the solver is <= 20 - if(nbRefinedPoses + nbConstantPoses <= 20) - options.setDenseBA(); - - // parameters are refined only if the number of cameras to refine is > to the number of newly added cameras. - // - if they are equal: it means that none of the new cameras is connected to the local BA graph, - // so the refinement would be done on those cameras only, without any constant parameters. - // - the number of cameras to refine cannot be < to the number of newly added cameras (set to 'refine' by default) - if((nbRefinedPoses <= newReconstructedViews.size()) && _sfmData.getRigs().empty()) - { - ALICEVISION_LOG_INFO("Local bundle adjustment: the new cameras are not connected to the rest of the graph" - " (nbRefinedPoses: " << nbRefinedPoses << ", newReconstructedViews.size(): " << newReconstructedViews.size() << ")."); - } - } + ALICEVISION_LOG_INFO("Bundle adjustment start."); + auto chronoStart = std::chrono::steady_clock::now(); - BundleAdjustmentCeres BA(options, _params.minNbCamerasToRefinePrincipalPoint); + BundleAdjustmentCeres::CeresOptions options; + BundleAdjustment::ERefineOptions refineOptions = + BundleAdjustment::REFINE_ROTATION | BundleAdjustment::REFINE_TRANSLATION | BundleAdjustment::REFINE_STRUCTURE; - // give the local strategy graph is local strategy is enable - if(enableLocalStrategy) - BA.useLocalStrategyGraph(_localStrategyGraph); + if (!isInitialPair && !_params.lockAllIntrinsics) + refineOptions |= BundleAdjustment::REFINE_INTRINSICS_ALL; - // perform BA until all point are under the given precision - do - { - ALICEVISION_LOG_INFO("Start bundle adjustment iteration: " << iteration); - auto chronoItStart = std::chrono::steady_clock::now(); + const int nbOutliersThreshold = (isInitialPair) ? 0 : _params.bundleAdjustmentMaxOutliers; + std::size_t iteration = 0; + std::size_t nbOutliers = 0; + bool enableLocalStrategy = false; - // bundle adjustment iteration + // enable Sparse solver and local strategy + if (_sfmData.getPoses().size() > 100) { - const bool success = BA.adjust(_sfmData, refineOptions); + options.setSparseBA(); + if (_params.useLocalBundleAdjustment) // local strategy enable if more than 100 poses + enableLocalStrategy = true; + } + else + { + options.setDenseBA(); + } - if(!success) - return false; // not usable solution + // add the new reconstructed views to the graph + if (_params.useLocalBundleAdjustment) + _localStrategyGraph->updateGraphWithNewViews(_sfmData, _map_tracksPerView, newReconstructedViews, _params.kMinNbOfMatches); - // save the current focal lengths values (for each intrinsic) in the history - if(_params.useLocalBundleAdjustment) - _localStrategyGraph->saveIntrinsicsToHistory(_sfmData); + if (enableLocalStrategy) + { + // compute the graph-distance between each newly reconstructed views and all the reconstructed views + _localStrategyGraph->computeGraphDistances(_sfmData, newReconstructedViews); - // export and print information about the refinement - const BundleAdjustmentCeres::Statistics& statistics = BA.getStatistics(); - statistics.exportToFile(_outputFolder, "bundle_adjustment.csv"); - statistics.show(); - } + // use the graph-distances to assign a state (Refine, Constant & Ignore) for each parameter (poses, intrinsics & landmarks) + _localStrategyGraph->convertDistancesToStates(_sfmData); - nbOutliers = removeOutliers(); + const std::size_t nbRefinedPoses = _localStrategyGraph->getNbPosesPerState(BundleAdjustment::EParameterState::REFINED); + const std::size_t nbConstantPoses = _localStrategyGraph->getNbPosesPerState(BundleAdjustment::EParameterState::CONSTANT); - std::set removedViewsIdIteration; - eraseUnstablePosesAndObservations(this->_sfmData, _params.minPointsPerPose, _params.minTrackLength, &removedViewsIdIteration); + // restore the Dense linear solver type if the number of cameras in the solver is <= 20 + if (nbRefinedPoses + nbConstantPoses <= 20) + options.setDenseBA(); - for(IndexT v : removedViewsIdIteration) - newReconstructedViews.erase(v); + // parameters are refined only if the number of cameras to refine is > to the number of newly added cameras. + // - if they are equal: it means that none of the new cameras is connected to the local BA graph, + // so the refinement would be done on those cameras only, without any constant parameters. + // - the number of cameras to refine cannot be < to the number of newly added cameras (set to 'refine' by default) + if ((nbRefinedPoses <= newReconstructedViews.size()) && _sfmData.getRigs().empty()) + { + ALICEVISION_LOG_INFO("Local bundle adjustment: the new cameras are not connected to the rest of the graph" + " (nbRefinedPoses: " + << nbRefinedPoses << ", newReconstructedViews.size(): " << newReconstructedViews.size() << ")."); + } + } + + BundleAdjustmentCeres BA(options, _params.minNbCamerasToRefinePrincipalPoint); - if(_params.useLocalBundleAdjustment && !removedViewsIdIteration.empty()) + // give the local strategy graph is local strategy is enable + if (enableLocalStrategy) + BA.useLocalStrategyGraph(_localStrategyGraph); + + // perform BA until all point are under the given precision + do { - // remove views from localBA graph - _localStrategyGraph->removeViews(_sfmData, removedViewsIdIteration); - ALICEVISION_LOG_DEBUG("Views removed from the local BA graph: " << removedViewsIdIteration); - } + ALICEVISION_LOG_INFO("Start bundle adjustment iteration: " << iteration); + auto chronoItStart = std::chrono::steady_clock::now(); - ALICEVISION_LOG_INFO("Bundle adjustment iteration: " << iteration << " took " << std::chrono::duration_cast(std::chrono::steady_clock::now() - chronoItStart).count() << " msec."); - ++iteration; - } - while(nbOutliersThreshold >= 0 && nbOutliers > nbOutliersThreshold); + // bundle adjustment iteration + { + const bool success = BA.adjust(_sfmData, refineOptions); + + if (!success) + return false; // not usable solution + + // save the current focal lengths values (for each intrinsic) in the history + if (_params.useLocalBundleAdjustment) + _localStrategyGraph->saveIntrinsicsToHistory(_sfmData); + + // export and print information about the refinement + const BundleAdjustmentCeres::Statistics& statistics = BA.getStatistics(); + statistics.exportToFile(_outputFolder, "bundle_adjustment.csv"); + statistics.show(); + } + + nbOutliers = removeOutliers(); + + std::set removedViewsIdIteration; + eraseUnstablePosesAndObservations(this->_sfmData, _params.minPointsPerPose, _params.minTrackLength, &removedViewsIdIteration); - ALICEVISION_LOG_INFO("Bundle adjustment with " << iteration << " iterations took " << std::chrono::duration_cast(std::chrono::steady_clock::now() - chronoStart).count() << " msec."); - return true; + for (IndexT v : removedViewsIdIteration) + newReconstructedViews.erase(v); + + if (_params.useLocalBundleAdjustment && !removedViewsIdIteration.empty()) + { + // remove views from localBA graph + _localStrategyGraph->removeViews(_sfmData, removedViewsIdIteration); + ALICEVISION_LOG_DEBUG("Views removed from the local BA graph: " << removedViewsIdIteration); + } + + ALICEVISION_LOG_INFO("Bundle adjustment iteration: " + << iteration << " took " + << std::chrono::duration_cast(std::chrono::steady_clock::now() - chronoItStart).count() + << " msec."); + ++iteration; + } while (nbOutliersThreshold >= 0 && nbOutliers > nbOutliersThreshold); + + ALICEVISION_LOG_INFO( + "Bundle adjustment with " << iteration << " iterations took " + << std::chrono::duration_cast(std::chrono::steady_clock::now() - chronoStart).count() + << " msec."); + return true; } void ReconstructionEngine_sequentialSfM::exportStatistics(double reconstructionTime) { - const double residual = RMSE(_sfmData); - const std::size_t nbValidViews = _sfmData.getValidViews().size(); - - ALICEVISION_LOG_INFO("Structure from Motion statistics:" << std::endl - << "\t- # input images: " << _sfmData.getViews().size() << std::endl - << "\t- # cameras calibrated: " << nbValidViews << std::endl - << "\t- # poses: " << _sfmData.getPoses().size() << std::endl - << "\t- # landmarks: " << _sfmData.getLandmarks().size() << std::endl - << "\t- elapsed time: " << reconstructionTime << std::endl - << "\t- residual RMSE: " << residual); - - std::map descTypeUsage = _sfmData.getLandmarkDescTypesUsages(); - for(const auto& d: descTypeUsage) - { - ALICEVISION_LOG_INFO(" - # " << EImageDescriberType_enumToString(d.first) << ": " << d.second); - } - - // residual histogram - utils::Histogram residualHistogram; - { - BoxStats residualStats; - computeResidualsHistogram(_sfmData, residualStats, &residualHistogram); - ALICEVISION_LOG_DEBUG( - "\t- # Landmarks: " << _sfmData.getLandmarks().size() << std::endl << - "\t- Residual min: " << residualStats.min << std::endl << - "\t- Residual median: " << residualStats.median << std::endl << - "\t- Residual max: " << residualStats.max << std::endl << - "\t- Residual mean: " << residualStats.mean << std::endl << - "\t- Residual first quartile: " << residualStats.firstQuartile << std::endl << - "\t- Residual third quartile: " << residualStats.thirdQuartile); - ALICEVISION_LOG_INFO("Histogram of residuals:" << residualHistogram.ToString("", 2)); - } - - // tracks lengths histogram - utils::Histogram observationsLengthHistogram; - { - BoxStats observationsLengthStats; - int overallNbObservations = 0; - computeObservationsLengthsHistogram(_sfmData, observationsLengthStats, overallNbObservations, &observationsLengthHistogram); - ALICEVISION_LOG_INFO("# landmarks: " << _sfmData.getLandmarks().size()); - ALICEVISION_LOG_INFO("# overall observations: " << overallNbObservations); - ALICEVISION_LOG_INFO("Landmarks observations length min: " << observationsLengthStats.min << ", mean: " << observationsLengthStats.mean << ", median: " << observationsLengthStats.median << ", max: " << observationsLengthStats.max); - ALICEVISION_LOG_INFO("Histogram of observations length:" << observationsLengthHistogram.ToString("", 6)); - } - - // nb landmarks per view histogram - utils::Histogram landmarksPerViewHistogram; - { - BoxStats landmarksPerViewStats; - computeLandmarksPerViewHistogram(_sfmData, landmarksPerViewStats, &landmarksPerViewHistogram); - ALICEVISION_LOG_INFO("Landmarks per view min: " << landmarksPerViewStats.min << ", mean: " << landmarksPerViewStats.mean << ", median: " << landmarksPerViewStats.median << ", max: " << landmarksPerViewStats.max); - ALICEVISION_LOG_INFO("Histogram of nb landmarks per view:" << landmarksPerViewHistogram.ToString("", 3)); - } - - // html log file - if(!_htmlLogFile.empty()) - { - using namespace htmlDocument; - - std::ostringstream os("Structure from Motion process finished."); - _htmlDocStream->pushInfo("
"); - _htmlDocStream->pushInfo(htmlMarkup("h3",os.str())); - - os.str(""); - os << "Structure from Motion statistics:" - << "
- # input images: " << _sfmData.getViews().size() - << "
- # camera calibrated: " << nbValidViews - << "
- # poses: " << _sfmData.getPoses().size() << std::endl - << "
- # landmarks: " << _sfmData.getLandmarks().size() - << "
- elapsed time: " << reconstructionTime - << "
- residual RMSE: " << residual; - - _htmlDocStream->pushInfo(os.str()); - _htmlDocStream->pushInfo(htmlMarkup("h2","Histogram of reprojection-residuals")); - - const std::vector xBin = residualHistogram.GetXbinsValue(); - _htmlDocStream->pushXYChart(xBin, residualHistogram.GetHist(),"3DtoImageResiduals"); - - const std::vector xBinTracks = observationsLengthHistogram.GetXbinsValue(); - _htmlDocStream->pushXYChart(xBinTracks, observationsLengthHistogram.GetHist(),"3DtoTracksSize"); - - // save the reconstruction Log - std::ofstream htmlFileStream(_htmlLogFile); - htmlFileStream << _htmlDocStream->getDoc(); - } - - // json log file - { - // put nb images, nb poses, nb points - _jsonLogTree.put("sfm.views", _sfmData.getViews().size()); - _jsonLogTree.put("sfm.validViews", _sfmData.getValidViews().size()); - _jsonLogTree.put("sfm.poses", _sfmData.getPoses().size()); - _jsonLogTree.put("sfm.points", _sfmData.getLandmarks().size()); - _jsonLogTree.put("sfm.residual", residual); - - // add observations histogram - std::map obsHistogram; - for (const auto& iterTracks : _sfmData.getLandmarks()) - { - const Observations& obs = iterTracks.second.observations; - if(obsHistogram.count(obs.size())) - obsHistogram[obs.size()]++; - else - obsHistogram[obs.size()] = 1; + const double residual = RMSE(_sfmData); + const std::size_t nbValidViews = _sfmData.getValidViews().size(); + + ALICEVISION_LOG_INFO("Structure from Motion statistics:" << std::endl + << "\t- # input images: " << _sfmData.getViews().size() << std::endl + << "\t- # cameras calibrated: " << nbValidViews << std::endl + << "\t- # poses: " << _sfmData.getPoses().size() << std::endl + << "\t- # landmarks: " << _sfmData.getLandmarks().size() << std::endl + << "\t- elapsed time: " << reconstructionTime << std::endl + << "\t- residual RMSE: " << residual); + + std::map descTypeUsage = _sfmData.getLandmarkDescTypesUsages(); + for (const auto& d : descTypeUsage) + { + ALICEVISION_LOG_INFO(" - # " << EImageDescriberType_enumToString(d.first) << ": " << d.second); + } + + // residual histogram + utils::Histogram residualHistogram; + { + BoxStats residualStats; + computeResidualsHistogram(_sfmData, residualStats, &residualHistogram); + ALICEVISION_LOG_DEBUG("\t- # Landmarks: " << _sfmData.getLandmarks().size() << std::endl + << "\t- Residual min: " << residualStats.min << std::endl + << "\t- Residual median: " << residualStats.median << std::endl + << "\t- Residual max: " << residualStats.max << std::endl + << "\t- Residual mean: " << residualStats.mean << std::endl + << "\t- Residual first quartile: " << residualStats.firstQuartile << std::endl + << "\t- Residual third quartile: " << residualStats.thirdQuartile); + ALICEVISION_LOG_INFO("Histogram of residuals:" << residualHistogram.ToString("", 2)); + } + + // tracks lengths histogram + utils::Histogram observationsLengthHistogram; + { + BoxStats observationsLengthStats; + int overallNbObservations = 0; + computeObservationsLengthsHistogram(_sfmData, observationsLengthStats, overallNbObservations, &observationsLengthHistogram); + ALICEVISION_LOG_INFO("# landmarks: " << _sfmData.getLandmarks().size()); + ALICEVISION_LOG_INFO("# overall observations: " << overallNbObservations); + ALICEVISION_LOG_INFO("Landmarks observations length min: " << observationsLengthStats.min << ", mean: " << observationsLengthStats.mean + << ", median: " << observationsLengthStats.median + << ", max: " << observationsLengthStats.max); + ALICEVISION_LOG_INFO("Histogram of observations length:" << observationsLengthHistogram.ToString("", 6)); + } + + // nb landmarks per view histogram + utils::Histogram landmarksPerViewHistogram; + { + BoxStats landmarksPerViewStats; + computeLandmarksPerViewHistogram(_sfmData, landmarksPerViewStats, &landmarksPerViewHistogram); + ALICEVISION_LOG_INFO("Landmarks per view min: " << landmarksPerViewStats.min << ", mean: " << landmarksPerViewStats.mean + << ", median: " << landmarksPerViewStats.median << ", max: " << landmarksPerViewStats.max); + ALICEVISION_LOG_INFO("Histogram of nb landmarks per view:" << landmarksPerViewHistogram.ToString("", 3)); + } + + // html log file + if (!_htmlLogFile.empty()) + { + using namespace htmlDocument; + + std::ostringstream os("Structure from Motion process finished."); + _htmlDocStream->pushInfo("
"); + _htmlDocStream->pushInfo(htmlMarkup("h3", os.str())); + + os.str(""); + os << "Structure from Motion statistics:" + << "
- # input images: " << _sfmData.getViews().size() << "
- # camera calibrated: " << nbValidViews + << "
- # poses: " << _sfmData.getPoses().size() << std::endl + << "
- # landmarks: " << _sfmData.getLandmarks().size() << "
- elapsed time: " << reconstructionTime + << "
- residual RMSE: " << residual; + + _htmlDocStream->pushInfo(os.str()); + _htmlDocStream->pushInfo(htmlMarkup("h2", "Histogram of reprojection-residuals")); + + const std::vector xBin = residualHistogram.GetXbinsValue(); + _htmlDocStream->pushXYChart(xBin, residualHistogram.GetHist(), "3DtoImageResiduals"); + + const std::vector xBinTracks = observationsLengthHistogram.GetXbinsValue(); + _htmlDocStream->pushXYChart(xBinTracks, observationsLengthHistogram.GetHist(), "3DtoTracksSize"); + + // save the reconstruction Log + std::ofstream htmlFileStream(_htmlLogFile); + htmlFileStream << _htmlDocStream->getDoc(); } - for(std::size_t i = 2; i < obsHistogram.size(); ++i) - _jsonLogTree.add("sfm.observationsHistogram." + std::to_string(i), obsHistogram[i]); + // json log file + { + // put nb images, nb poses, nb points + _jsonLogTree.put("sfm.views", _sfmData.getViews().size()); + _jsonLogTree.put("sfm.validViews", _sfmData.getValidViews().size()); + _jsonLogTree.put("sfm.poses", _sfmData.getPoses().size()); + _jsonLogTree.put("sfm.points", _sfmData.getLandmarks().size()); + _jsonLogTree.put("sfm.residual", residual); + + // add observations histogram + std::map obsHistogram; + for (const auto& iterTracks : _sfmData.getLandmarks()) + { + const Observations& obs = iterTracks.second.observations; + if (obsHistogram.count(obs.size())) + obsHistogram[obs.size()]++; + else + obsHistogram[obs.size()] = 1; + } + + for (std::size_t i = 2; i < obsHistogram.size(); ++i) + _jsonLogTree.add("sfm.observationsHistogram." + std::to_string(i), obsHistogram[i]); - _jsonLogTree.put("sfm.time", reconstructionTime); // process time - _jsonLogTree.put("hardware.cpu.freq", system::cpu_clock_by_os()); // cpu frequency - _jsonLogTree.put("hardware.cpu.cores", system::get_total_cpus()); // cpu cores - _jsonLogTree.put("hardware.ram.size", system::getMemoryInfo().totalRam); // ram size + _jsonLogTree.put("sfm.time", reconstructionTime); // process time + _jsonLogTree.put("hardware.cpu.freq", system::cpu_clock_by_os()); // cpu frequency + _jsonLogTree.put("hardware.cpu.cores", system::get_total_cpus()); // cpu cores + _jsonLogTree.put("hardware.ram.size", system::getMemoryInfo().totalRam); // ram size - // write json on disk - pt::write_json((fs::path(_outputFolder) / "stats.json").string(), _jsonLogTree); - } + // write json on disk + pt::write_json((fs::path(_outputFolder) / "stats.json").string(), _jsonLogTree); + } - // (optional) export the intrinsics history values to a csv file. - if(_params.useLocalBundleAdjustment) - _localStrategyGraph->exportIntrinsicsHistory(_outputFolder, "intrinsics_history.csv"); + // (optional) export the intrinsics history values to a csv file. + if (_params.useLocalBundleAdjustment) + _localStrategyGraph->exportIntrinsicsHistory(_outputFolder, "intrinsics_history.csv"); } void ReconstructionEngine_sequentialSfM::calibrateRigs(std::set& updatedViews) { - for(const std::pair& rigPair : _sfmData.getRigs()) - { - RigSequence sequence(_sfmData, rigPair.first, _params.rig); - sequence.init(_map_tracksPerView); - sequence.updateSfM(updatedViews); - } + for (const std::pair& rigPair : _sfmData.getRigs()) + { + RigSequence sequence(_sfmData, rigPair.first, _params.rig); + sequence.init(_map_tracksPerView); + sequence.updateSfM(updatedViews); + } } -bool ReconstructionEngine_sequentialSfM::findConnectedViews( - std::vector& out_connectedViews, - const std::set& remainingViewIds) const +bool ReconstructionEngine_sequentialSfM::findConnectedViews(std::vector& out_connectedViews, + const std::set& remainingViewIds) const { - out_connectedViews.clear(); + out_connectedViews.clear(); - if (remainingViewIds.empty() || _sfmData.getLandmarks().empty()) - return false; + if (remainingViewIds.empty() || _sfmData.getLandmarks().empty()) + return false; - // Collect tracksIds - std::set reconstructed_trackId; - std::transform(_sfmData.getLandmarks().begin(), _sfmData.getLandmarks().end(), - std::inserter(reconstructed_trackId, reconstructed_trackId.begin()), - stl::RetrieveKey()); + // Collect tracksIds + std::set reconstructed_trackId; + std::transform(_sfmData.getLandmarks().begin(), + _sfmData.getLandmarks().end(), + std::inserter(reconstructed_trackId, reconstructed_trackId.begin()), + stl::RetrieveKey()); - const std::set reconstructedIntrinsics = _sfmData.getReconstructedIntrinsics(); + const std::set reconstructedIntrinsics = _sfmData.getReconstructedIntrinsics(); #pragma omp parallel for - for(int i = 0; i < remainingViewIds.size(); ++i) - { - std::set::const_iterator iter = remainingViewIds.cbegin(); - std::advance(iter, i); - const IndexT viewId = *iter; - const IndexT intrinsicId = _sfmData.getViews().at(viewId)->getIntrinsicId(); - const bool isIntrinsicsReconstructed = reconstructedIntrinsics.count(intrinsicId); - - // Compute 2D - 3D possible content - const aliceVision::track::TrackIdSet& set_tracksIds = _map_tracksPerView.at(viewId); - if (set_tracksIds.empty()) - continue; - - // Check if the view is part of a rig + for (int i = 0; i < remainingViewIds.size(); ++i) { - const View& view = *_sfmData.getViews().at(viewId); + std::set::const_iterator iter = remainingViewIds.cbegin(); + std::advance(iter, i); + const IndexT viewId = *iter; + const IndexT intrinsicId = _sfmData.getViews().at(viewId)->getIntrinsicId(); + const bool isIntrinsicsReconstructed = reconstructedIntrinsics.count(intrinsicId); + + // Compute 2D - 3D possible content + const aliceVision::track::TrackIdSet& set_tracksIds = _map_tracksPerView.at(viewId); + if (set_tracksIds.empty()) + continue; - if(view.isPartOfRig()) - { - // Some views can become indirectly localized when the sub-pose becomes defined - if(_sfmData.isPoseAndIntrinsicDefined(view.getViewId())) + // Check if the view is part of a rig { - continue; - } + const View& view = *_sfmData.getViews().at(viewId); - // We cannot localize a view if it is part of an initialized RIG with unknown Rig Pose - const bool knownPose = _sfmData.existsPose(view); - const Rig& rig = _sfmData.getRig(view); - const RigSubPose& subpose = rig.getSubPose(view.getSubPoseId()); + if (view.isPartOfRig()) + { + // Some views can become indirectly localized when the sub-pose becomes defined + if (_sfmData.isPoseAndIntrinsicDefined(view.getViewId())) + { + continue; + } + + // We cannot localize a view if it is part of an initialized RIG with unknown Rig Pose + const bool knownPose = _sfmData.existsPose(view); + const Rig& rig = _sfmData.getRig(view); + const RigSubPose& subpose = rig.getSubPose(view.getSubPoseId()); + + if (rig.isInitialized() && !knownPose && (subpose.status == ERigSubPoseStatus::UNINITIALIZED)) + { + continue; + } + } + } - if(rig.isInitialized() && - !knownPose && - (subpose.status == ERigSubPoseStatus::UNINITIALIZED)) + // Count the common possible putative point + // with the already 3D reconstructed trackId + std::vector vec_trackIdForResection; + vec_trackIdForResection.reserve(set_tracksIds.size()); + std::set_intersection(set_tracksIds.begin(), + set_tracksIds.end(), + reconstructed_trackId.begin(), + reconstructed_trackId.end(), + std::back_inserter(vec_trackIdForResection)); + // Compute an image score based on the number of matches to the 3D scene + // and the repartition of these features in the image. + std::size_t score = computeCandidateImageScore(viewId, vec_trackIdForResection); +#pragma omp critical { - continue; + out_connectedViews.emplace_back(viewId, vec_trackIdForResection.size(), score, isIntrinsicsReconstructed); } - } } - // Count the common possible putative point - // with the already 3D reconstructed trackId - std::vector vec_trackIdForResection; - vec_trackIdForResection.reserve(set_tracksIds.size()); - std::set_intersection(set_tracksIds.begin(), set_tracksIds.end(), - reconstructed_trackId.begin(), - reconstructed_trackId.end(), - std::back_inserter(vec_trackIdForResection)); - // Compute an image score based on the number of matches to the 3D scene - // and the repartition of these features in the image. - std::size_t score = computeCandidateImageScore(viewId, vec_trackIdForResection); -#pragma omp critical - { - out_connectedViews.emplace_back(viewId, vec_trackIdForResection.size(), score, isIntrinsicsReconstructed); - } - } - - // Sort by the image score - std::sort(out_connectedViews.begin(), out_connectedViews.end(), - [](const ViewConnectionScore& t1, const ViewConnectionScore& t2) { - return std::get<2>(t1) > std::get<2>(t2); - }); - return !out_connectedViews.empty(); + // Sort by the image score + std::sort(out_connectedViews.begin(), out_connectedViews.end(), [](const ViewConnectionScore& t1, const ViewConnectionScore& t2) { + return std::get<2>(t1) > std::get<2>(t2); + }); + return !out_connectedViews.empty(); } -bool ReconstructionEngine_sequentialSfM::findNextBestViews( - std::vector & out_selectedViewIds, - const std::set& remainingViewIds) const +bool ReconstructionEngine_sequentialSfM::findNextBestViews(std::vector& out_selectedViewIds, const std::set& remainingViewIds) const { - out_selectedViewIds.clear(); - auto chrono_start = std::chrono::steady_clock::now(); - std::vector vec_viewsScore; - if(!findConnectedViews(vec_viewsScore, remainingViewIds)) - { - ALICEVISION_LOG_DEBUG("FindConnectedViews does not find connected new views "); - return false; - } - - // Impose a minimal number of points to ensure that it makes sense to try the pose estimation. - static const std::size_t minPointsThreshold = 30; - - ALICEVISION_LOG_DEBUG("findNextBestViews -- Scores (features): "); - // print the 30 best scores - for(std::size_t i = 0; i < vec_viewsScore.size() && i < 30; ++i) - { - ALICEVISION_LOG_DEBUG_OBJ << std::get<2>(vec_viewsScore[i]) << "(" << std::get<1>(vec_viewsScore[i]) << "), "; - } - ALICEVISION_LOG_DEBUG_OBJ << std::endl; - - // If the list is empty or if the list contains images with no correspondences - // -> (no resection will be possible) - if (vec_viewsScore.empty() || std::get<1>(vec_viewsScore[0]) == 0) - { - ALICEVISION_LOG_DEBUG("Failed to find next best views :"); - if(vec_viewsScore.empty()) - { - ALICEVISION_LOG_DEBUG("No putative image."); + out_selectedViewIds.clear(); + auto chrono_start = std::chrono::steady_clock::now(); + std::vector vec_viewsScore; + if (!findConnectedViews(vec_viewsScore, remainingViewIds)) + { + ALICEVISION_LOG_DEBUG("FindConnectedViews does not find connected new views "); + return false; } - else + + // Impose a minimal number of points to ensure that it makes sense to try the pose estimation. + static const std::size_t minPointsThreshold = 30; + + ALICEVISION_LOG_DEBUG("findNextBestViews -- Scores (features): "); + // print the 30 best scores + for (std::size_t i = 0; i < vec_viewsScore.size() && i < 30; ++i) { - ALICEVISION_LOG_DEBUG_OBJ << "Not enough point in the putative images: "; - for(auto v: vec_viewsScore) - ALICEVISION_LOG_DEBUG_OBJ << std::get<1>(v) << ", "; + ALICEVISION_LOG_DEBUG_OBJ << std::get<2>(vec_viewsScore[i]) << "(" << std::get<1>(vec_viewsScore[i]) << "), "; } ALICEVISION_LOG_DEBUG_OBJ << std::endl; - // All remaining images cannot be used for pose estimation - return false; - } - // Add the image view index with the best score - out_selectedViewIds.push_back(std::get<0>(vec_viewsScore[0])); + // If the list is empty or if the list contains images with no correspondences + // -> (no resection will be possible) + if (vec_viewsScore.empty() || std::get<1>(vec_viewsScore[0]) == 0) + { + ALICEVISION_LOG_DEBUG("Failed to find next best views :"); + if (vec_viewsScore.empty()) + { + ALICEVISION_LOG_DEBUG("No putative image."); + } + else + { + ALICEVISION_LOG_DEBUG_OBJ << "Not enough point in the putative images: "; + for (auto v : vec_viewsScore) + ALICEVISION_LOG_DEBUG_OBJ << std::get<1>(v) << ", "; + } + ALICEVISION_LOG_DEBUG_OBJ << std::endl; + // All remaining images cannot be used for pose estimation + return false; + } - #ifdef ALICEVISION_NEXTBESTVIEW_WITHOUT_SCORE + // Add the image view index with the best score + out_selectedViewIds.push_back(std::get<0>(vec_viewsScore[0])); + +#ifdef ALICEVISION_NEXTBESTVIEW_WITHOUT_SCORE static const float dThresholdGroup = 0.75f; // Number of 2D-3D correspondences for the best view. const IndexT bestScore = std::get<2>(vec_viewsScore[0]); // Add all the image view indexes that have at least N% of the score of the best image. const size_t scoreThreshold = dThresholdGroup * bestScore; - #else +#else const std::size_t scoreThreshold = _pyramidThreshold; - #endif - - for (std::size_t i = 1; - i < vec_viewsScore.size() && - std::get<1>(vec_viewsScore[i]) > minPointsThreshold && // ensure min number of points - std::get<2>(vec_viewsScore[i]) > scoreThreshold; // ensure score level - ++i) - { - out_selectedViewIds.push_back(std::get<0>(vec_viewsScore[i])); - if(!std::get<3>(vec_viewsScore[i])) - { - // If we add a new intrinsic, it is a sensitive stage in the process, - // so it is better to perform a Bundle Adjustment just after. - break; +#endif + + for (std::size_t i = 1; i < vec_viewsScore.size() && std::get<1>(vec_viewsScore[i]) > minPointsThreshold && // ensure min number of points + std::get<2>(vec_viewsScore[i]) > scoreThreshold; // ensure score level + ++i) + { + out_selectedViewIds.push_back(std::get<0>(vec_viewsScore[i])); + if (!std::get<3>(vec_viewsScore[i])) + { + // If we add a new intrinsic, it is a sensitive stage in the process, + // so it is better to perform a Bundle Adjustment just after. + break; + } + } + + // If the number of cameras is less than nbFirstUnstableCameras, then the bundle adjustment should be performed + // every time a new camera is added: it is not expensive as there is very little data and it gives more stable results. + if (_sfmData.getPoses().size() < _params.nbFirstUnstableCameras && !out_selectedViewIds.empty()) + { + // add images one by one to reconstruct the first cameras + ALICEVISION_LOG_DEBUG("findNextBestViews: beginning of the incremental SfM" + << std::endl + << "Only the first image of the resection group is used." << std::endl + << "\t- image view id : " << out_selectedViewIds.front() << std::endl + << "\t- # unstable poses : " << _sfmData.getPoses().size() << " / " << _params.nbFirstUnstableCameras << std::endl); + + out_selectedViewIds.resize(1); } - } - - // If the number of cameras is less than nbFirstUnstableCameras, then the bundle adjustment should be performed - // every time a new camera is added: it is not expensive as there is very little data and it gives more stable results. - if(_sfmData.getPoses().size() < _params.nbFirstUnstableCameras && - !out_selectedViewIds.empty()) - { - // add images one by one to reconstruct the first cameras - ALICEVISION_LOG_DEBUG("findNextBestViews: beginning of the incremental SfM" << std::endl - << "Only the first image of the resection group is used." << std::endl - << "\t- image view id : " << out_selectedViewIds.front() << std::endl - << "\t- # unstable poses : " << _sfmData.getPoses().size() << " / " << _params.nbFirstUnstableCameras << std::endl); - - out_selectedViewIds.resize(1); - } - - // No more than maxImagesPerGroup cameras should be added at once without performing the bundle adjustment (if set to - // 0, then there is no limit on the number of views that can be added at once) - if(_params.maxImagesPerGroup > 0 && out_selectedViewIds.size() > _params.maxImagesPerGroup) - out_selectedViewIds.resize(_params.maxImagesPerGroup); - - ALICEVISION_LOG_DEBUG( - "Find next best views took: " << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() << " msec\n" - "\t# images : " << out_selectedViewIds.size() << "\n" - "\t- scores: from " << std::get<2>(vec_viewsScore.front()) << " to " << std::get<2>(vec_viewsScore[out_selectedViewIds.size()-1]) << " (threshold was " << scoreThreshold << ")\n" - "\t- features: from " << std::get<1>(vec_viewsScore.front()) << " to " << std::get<1>(vec_viewsScore[out_selectedViewIds.size()-1]) << " (threshold was " << minPointsThreshold << ")"); - - return (!out_selectedViewIds.empty()); + + // No more than maxImagesPerGroup cameras should be added at once without performing the bundle adjustment (if set to + // 0, then there is no limit on the number of views that can be added at once) + if (_params.maxImagesPerGroup > 0 && out_selectedViewIds.size() > _params.maxImagesPerGroup) + out_selectedViewIds.resize(_params.maxImagesPerGroup); + + ALICEVISION_LOG_DEBUG("Find next best views took: " + << std::chrono::duration_cast(std::chrono::steady_clock::now() - chrono_start).count() + << " msec\n" + "\t# images : " + << out_selectedViewIds.size() + << "\n" + "\t- scores: from " + << std::get<2>(vec_viewsScore.front()) << " to " << std::get<2>(vec_viewsScore[out_selectedViewIds.size() - 1]) + << " (threshold was " << scoreThreshold + << ")\n" + "\t- features: from " + << std::get<1>(vec_viewsScore.front()) << " to " << std::get<1>(vec_viewsScore[out_selectedViewIds.size() - 1]) + << " (threshold was " << minPointsThreshold << ")"); + + return (!out_selectedViewIds.empty()); } bool ReconstructionEngine_sequentialSfM::makeInitialPair3D(const Pair& currentPair) { - // compute robust Essential matrix for ImageId [I,J] - // use min max to have I < J - const std::size_t I = std::min(currentPair.first, currentPair.second); - const std::size_t J = std::max(currentPair.first, currentPair.second); - - // a. assert we have valid pinhole cameras - const View& viewI = _sfmData.getView(I); - const Intrinsics::const_iterator itIntrinsicI = _sfmData.getIntrinsics().find(viewI.getIntrinsicId()); - const View& viewJ = _sfmData.getView(J); - const Intrinsics::const_iterator itIntrinsicJ = _sfmData.getIntrinsics().find(viewJ.getIntrinsicId()); - - ALICEVISION_LOG_INFO("Initial pair is:\n" - "\t- [A] view id: " << I << ", filepath: " << viewI.getImage().getImagePath() << "\n" - "\t- [B] view id: " << J << ", filepath: " << viewJ.getImage().getImagePath()); - - if(itIntrinsicI == _sfmData.getIntrinsics().end() || - itIntrinsicJ == _sfmData.getIntrinsics().end() ) - { - ALICEVISION_LOG_WARNING("Can't find initial image pair intrinsics: " << viewI.getIntrinsicId() << ", " << viewJ.getIntrinsicId()); - return false; - } + // compute robust Essential matrix for ImageId [I,J] + // use min max to have I < J + const std::size_t I = std::min(currentPair.first, currentPair.second); + const std::size_t J = std::max(currentPair.first, currentPair.second); + + // a. assert we have valid pinhole cameras + const View& viewI = _sfmData.getView(I); + const Intrinsics::const_iterator itIntrinsicI = _sfmData.getIntrinsics().find(viewI.getIntrinsicId()); + const View& viewJ = _sfmData.getView(J); + const Intrinsics::const_iterator itIntrinsicJ = _sfmData.getIntrinsics().find(viewJ.getIntrinsicId()); + + ALICEVISION_LOG_INFO("Initial pair is:\n" + "\t- [A] view id: " + << I << ", filepath: " << viewI.getImage().getImagePath() + << "\n" + "\t- [B] view id: " + << J << ", filepath: " << viewJ.getImage().getImagePath()); + + if (itIntrinsicI == _sfmData.getIntrinsics().end() || itIntrinsicJ == _sfmData.getIntrinsics().end()) + { + ALICEVISION_LOG_WARNING("Can't find initial image pair intrinsics: " << viewI.getIntrinsicId() << ", " << viewJ.getIntrinsicId()); + return false; + } - const Pinhole* camI = dynamic_cast(itIntrinsicI->second.get()); - const Pinhole* camJ = dynamic_cast(itIntrinsicJ->second.get()); + const Pinhole* camI = dynamic_cast(itIntrinsicI->second.get()); + const Pinhole* camJ = dynamic_cast(itIntrinsicJ->second.get()); - if(camI == nullptr || camJ == nullptr || !camI->isValid() || !camJ->isValid()) - { - ALICEVISION_LOG_WARNING("Can't find initial image pair intrinsics (NULL ptr): " << viewI.getIntrinsicId() << ", " << viewJ.getIntrinsicId()); - return false; - } - - // b. get common features between the two views - // use the track to have a more dense match correspondence set - aliceVision::track::TracksMap commonTracks; - track::getCommonTracksInImagesFast({I, J}, _map_tracks, _map_tracksPerView, commonTracks); - - // copy point to arrays - const std::size_t n = commonTracks.size(); - Mat xI(2,n), xJ(2,n); - std::size_t cptIndex = 0; - for (aliceVision::track::TracksMap::const_iterator - iterT = commonTracks.begin(); iterT != commonTracks.end(); - ++iterT, ++cptIndex) - { - assert(iterT->second.featPerView.size() == 2); - auto iter = iterT->second.featPerView.begin(); - const std::size_t i = iter->second; - const std::size_t j = (++iter)->second; - - Vec2 feat = _featuresPerView->getFeatures(I, iterT->second.descType)[i].coords().cast(); - xI.col(cptIndex) = camI->get_ud_pixel(feat); - feat = _featuresPerView->getFeatures(J, iterT->second.descType)[j].coords().cast(); - xJ.col(cptIndex) = camJ->get_ud_pixel(feat); - } - ALICEVISION_LOG_INFO(n << " matches in the image pair for the initial pose estimation."); - - // c. robust estimation of the relative pose - RelativePoseInfo relativePoseInfo; - const std::pair imageSizeI(camI->w(), camI->h()); - const std::pair imageSizeJ(camJ->w(), camJ->h()); - - if(!robustRelativePose(camI->K(), camJ->K(), xI, xJ, _randomNumberGenerator, relativePoseInfo, imageSizeI, imageSizeJ, 4096)) - { - ALICEVISION_LOG_WARNING("Robust estimation failed to compute E for this pair"); - return false; - } + if (camI == nullptr || camJ == nullptr || !camI->isValid() || !camJ->isValid()) + { + ALICEVISION_LOG_WARNING("Can't find initial image pair intrinsics (NULL ptr): " << viewI.getIntrinsicId() << ", " << viewJ.getIntrinsicId()); + return false; + } - ALICEVISION_LOG_DEBUG("A-Contrario initial pair residual: " << relativePoseInfo.found_residual_precision); + // b. get common features between the two views + // use the track to have a more dense match correspondence set + aliceVision::track::TracksMap commonTracks; + track::getCommonTracksInImagesFast({I, J}, _map_tracks, _map_tracksPerView, commonTracks); - // bound min precision at 1 pix. - relativePoseInfo.found_residual_precision = std::max(relativePoseInfo.found_residual_precision, 1.0); - { - // initialize poses - const Pose3& initPoseI = Pose3(Mat3::Identity(), Vec3::Zero()); - const Pose3& initPoseJ = relativePoseInfo.relativePose; + // copy point to arrays + const std::size_t n = commonTracks.size(); + Mat xI(2, n), xJ(2, n); + std::size_t cptIndex = 0; + for (aliceVision::track::TracksMap::const_iterator iterT = commonTracks.begin(); iterT != commonTracks.end(); ++iterT, ++cptIndex) + { + assert(iterT->second.featPerView.size() == 2); + auto iter = iterT->second.featPerView.begin(); + const std::size_t i = iter->second; + const std::size_t j = (++iter)->second; + + Vec2 feat = _featuresPerView->getFeatures(I, iterT->second.descType)[i].coords().cast(); + xI.col(cptIndex) = camI->get_ud_pixel(feat); + feat = _featuresPerView->getFeatures(J, iterT->second.descType)[j].coords().cast(); + xJ.col(cptIndex) = camJ->get_ud_pixel(feat); + } + ALICEVISION_LOG_INFO(n << " matches in the image pair for the initial pose estimation."); - _sfmData.setPose(viewI, CameraPose(initPoseI)); - _sfmData.setPose(viewJ, CameraPose(initPoseJ)); + // c. robust estimation of the relative pose + RelativePoseInfo relativePoseInfo; + const std::pair imageSizeI(camI->w(), camI->h()); + const std::pair imageSizeJ(camJ->w(), camJ->h()); - // triangulate - const std::set prevImageIndex = {static_cast(I)}; - const std::set newImageIndex = {static_cast(J)}; - triangulate_2Views(_sfmData, prevImageIndex, newImageIndex); + if (!robustRelativePose(camI->K(), camJ->K(), xI, xJ, _randomNumberGenerator, relativePoseInfo, imageSizeI, imageSizeJ, 4096)) + { + ALICEVISION_LOG_WARNING("Robust estimation failed to compute E for this pair"); + return false; + } + + ALICEVISION_LOG_DEBUG("A-Contrario initial pair residual: " << relativePoseInfo.found_residual_precision); - // refine only structure & rotations & translations (keep intrinsic constant) + // bound min precision at 1 pix. + relativePoseInfo.found_residual_precision = std::max(relativePoseInfo.found_residual_precision, 1.0); { - std::set newReconstructedViews = {static_cast(I), static_cast(J)}; - const bool isInitialPair = true; - const bool success = bundleAdjustment(newReconstructedViews, isInitialPair); + // initialize poses + const Pose3& initPoseI = Pose3(Mat3::Identity(), Vec3::Zero()); + const Pose3& initPoseJ = relativePoseInfo.relativePose; - if(!success) - { - // bundle adjustment solution is not usable - // because it can failed after multiple iterations - // we need to clear poses & rigs & landmarks - _sfmData.getPoses().clear(); - _sfmData.getLandmarks().clear(); - _sfmData.resetRigs(); + _sfmData.setPose(viewI, CameraPose(initPoseI)); + _sfmData.setPose(viewJ, CameraPose(initPoseJ)); - // this initial pair is not usable - return false; - } - } + // triangulate + const std::set prevImageIndex = {static_cast(I)}; + const std::set newImageIndex = {static_cast(J)}; + triangulate_2Views(_sfmData, prevImageIndex, newImageIndex); - // save outlier residual information - utils::Histogram residualHistogram; - BoxStats residualStats; - computeResidualsHistogram(_sfmData, residualStats, &residualHistogram); - ALICEVISION_LOG_DEBUG("MSE Residual initial pair inlier: " << residualStats.mean); - - if(!_htmlLogFile.empty()) - { - using namespace htmlDocument; - _htmlDocStream->pushInfo(htmlMarkup("h3","Essential Matrix.")); - std::ostringstream os; - os << std::endl - << "Robust Essential matrix:" << "
" - << "-> View I:
id: " << I << "
image path: " << viewI.getImage().getImagePath() << "
" - << "-> View J:
id: " << J << "
image path: " << viewJ.getImage().getImagePath() << "

" - << "- Threshold: " << relativePoseInfo.found_residual_precision << "
" - << "- Resection status: OK
" - << "- # points used for robust Essential matrix estimation: " << xI.cols() << "
" - << "- # points validated by robust estimation: " << _sfmData.getLandmarks().size() << "
" - << "- % points validated: " << _sfmData.getLandmarks().size()/static_cast(xI.cols()) << "
"; - _htmlDocStream->pushInfo(os.str()); - - _htmlDocStream->pushInfo(htmlMarkup("h3", - "Initial triangulation - Residual of the robust estimation.
Thresholded at: " - + toString(relativePoseInfo.found_residual_precision))); - - _htmlDocStream->pushInfo(htmlMarkup("h3","Histogram of residuals")); - - std::vector xBin = residualHistogram.GetXbinsValue(); - std::pair< std::pair, std::pair > range = - autoJSXGraphViewport(xBin, residualHistogram.GetHist()); - - htmlDocument::JSXGraphWrapper jsxGraph; - jsxGraph.init("InitialPairTriangulationKeptInfo",600,300); - jsxGraph.addXYChart(xBin, residualHistogram.GetHist(), "line,point"); - jsxGraph.addLine(relativePoseInfo.found_residual_precision, 0, - relativePoseInfo.found_residual_precision, residualHistogram.GetHist().front()); - jsxGraph.UnsuspendUpdate(); - jsxGraph.setViewport(range); - jsxGraph.close(); - - _htmlDocStream->pushInfo(jsxGraph.toStr()); - _htmlDocStream->pushInfo("
"); - - std::ofstream htmlFileStream((fs::path(_outputFolder) / _htmlLogFile).string()); - htmlFileStream << _htmlDocStream->getDoc(); + // refine only structure & rotations & translations (keep intrinsic constant) + { + std::set newReconstructedViews = {static_cast(I), static_cast(J)}; + const bool isInitialPair = true; + const bool success = bundleAdjustment(newReconstructedViews, isInitialPair); + + if (!success) + { + // bundle adjustment solution is not usable + // because it can failed after multiple iterations + // we need to clear poses & rigs & landmarks + _sfmData.getPoses().clear(); + _sfmData.getLandmarks().clear(); + _sfmData.resetRigs(); + + // this initial pair is not usable + return false; + } + } + + // save outlier residual information + utils::Histogram residualHistogram; + BoxStats residualStats; + computeResidualsHistogram(_sfmData, residualStats, &residualHistogram); + ALICEVISION_LOG_DEBUG("MSE Residual initial pair inlier: " << residualStats.mean); + + if (!_htmlLogFile.empty()) + { + using namespace htmlDocument; + _htmlDocStream->pushInfo(htmlMarkup("h3", "Essential Matrix.")); + std::ostringstream os; + os << std::endl + << "Robust Essential matrix:" + << "
" + << "-> View I:
id: " << I << "
image path: " << viewI.getImage().getImagePath() << "
" + << "-> View J:
id: " << J << "
image path: " << viewJ.getImage().getImagePath() << "

" + << "- Threshold: " << relativePoseInfo.found_residual_precision << "
" + << "- Resection status: OK
" + << "- # points used for robust Essential matrix estimation: " << xI.cols() << "
" + << "- # points validated by robust estimation: " << _sfmData.getLandmarks().size() << "
" + << "- % points validated: " << _sfmData.getLandmarks().size() / static_cast(xI.cols()) << "
"; + _htmlDocStream->pushInfo(os.str()); + + _htmlDocStream->pushInfo(htmlMarkup("h3", + "Initial triangulation - Residual of the robust estimation.
Thresholded at: " + + toString(relativePoseInfo.found_residual_precision))); + + _htmlDocStream->pushInfo(htmlMarkup("h3", "Histogram of residuals")); + + std::vector xBin = residualHistogram.GetXbinsValue(); + std::pair, std::pair> range = autoJSXGraphViewport(xBin, residualHistogram.GetHist()); + + htmlDocument::JSXGraphWrapper jsxGraph; + jsxGraph.init("InitialPairTriangulationKeptInfo", 600, 300); + jsxGraph.addXYChart(xBin, residualHistogram.GetHist(), "line,point"); + jsxGraph.addLine( + relativePoseInfo.found_residual_precision, 0, relativePoseInfo.found_residual_precision, residualHistogram.GetHist().front()); + jsxGraph.UnsuspendUpdate(); + jsxGraph.setViewport(range); + jsxGraph.close(); + + _htmlDocStream->pushInfo(jsxGraph.toStr()); + _htmlDocStream->pushInfo("
"); + + std::ofstream htmlFileStream((fs::path(_outputFolder) / _htmlLogFile).string()); + htmlFileStream << _htmlDocStream->getDoc(); + } } - } - return !_sfmData.getLandmarks().empty(); + return !_sfmData.getLandmarks().empty(); } -bool ReconstructionEngine_sequentialSfM::getBestInitialImagePairs(std::vector& out_bestImagePairs, IndexT filterViewId) { - // From the k view pairs with the highest number of verified matches - // select a pair that have the largest baseline (mean angle between its bearing vectors). - - const unsigned iMin_inliers_count = 100; - // Use a min angle limit to ensure quality of the geometric evaluation. - const float fRequired_min_angle = _params.minAngleInitialPair; - // Use a max angle limit to ensure good matching quality. - const float fLimit_max_angle = _params.maxAngleInitialPair; - - // List Views that support valid intrinsic (view that could be used for Essential matrix computation) - std::set valid_views; - for(const auto& it : _sfmData.getViews()) - { - - const View * v = it.second.get(); - if (_sfmData.getIntrinsics().count(v->getIntrinsicId()) && - _sfmData.getIntrinsics().at(v->getIntrinsicId())->isValid()) - valid_views.insert(v->getViewId()); - } - - if (valid_views.size() < 2) - { - ALICEVISION_LOG_WARNING("Failed to find an initial pair automatically. There is no view with valid intrinsics."); - return false; - } - - if(filterViewId != UndefinedIndexT) - ALICEVISION_LOG_INFO("Selection of an initial pair with one given view id: " << filterViewId << "."); - - /// ImagePairScore contains - typedef std::tuple ImagePairScore; - std::vector bestImagePairs; - bestImagePairs.reserve(_pairwiseMatches->size()); - - // Compute the relative pose & the 'baseline score' - auto progressDisplay = system::createConsoleProgressDisplay(_pairwiseMatches->size(), std::cout, - "Automatic selection of an initial pair:\n" ); +bool ReconstructionEngine_sequentialSfM::getBestInitialImagePairs(std::vector& out_bestImagePairs, IndexT filterViewId) +{ + // From the k view pairs with the highest number of verified matches + // select a pair that have the largest baseline (mean angle between its bearing vectors). + + const unsigned iMin_inliers_count = 100; + // Use a min angle limit to ensure quality of the geometric evaluation. + const float fRequired_min_angle = _params.minAngleInitialPair; + // Use a max angle limit to ensure good matching quality. + const float fLimit_max_angle = _params.maxAngleInitialPair; + + // List Views that support valid intrinsic (view that could be used for Essential matrix computation) + std::set valid_views; + for (const auto& it : _sfmData.getViews()) + { + const View* v = it.second.get(); + if (_sfmData.getIntrinsics().count(v->getIntrinsicId()) && _sfmData.getIntrinsics().at(v->getIntrinsicId())->isValid()) + valid_views.insert(v->getViewId()); + } + + if (valid_views.size() < 2) + { + ALICEVISION_LOG_WARNING("Failed to find an initial pair automatically. There is no view with valid intrinsics."); + return false; + } + + if (filterViewId != UndefinedIndexT) + ALICEVISION_LOG_INFO("Selection of an initial pair with one given view id: " << filterViewId << "."); + + /// ImagePairScore contains + typedef std::tuple ImagePairScore; + std::vector bestImagePairs; + bestImagePairs.reserve(_pairwiseMatches->size()); + + // Compute the relative pose & the 'baseline score' + auto progressDisplay = system::createConsoleProgressDisplay(_pairwiseMatches->size(), std::cout, "Automatic selection of an initial pair:\n"); #pragma omp parallel for schedule(dynamic) - for (int i = 0; i < _pairwiseMatches->size(); ++i) - { - matching::PairwiseMatches::const_iterator iter = _pairwiseMatches->begin(); - std::advance(iter, i); - - ++progressDisplay; - - const Pair current_pair = iter->first; - - const IndexT I = std::min(current_pair.first, current_pair.second); - const IndexT J = std::max(current_pair.first, current_pair.second); - - if (filterViewId != UndefinedIndexT && filterViewId != I && filterViewId != J) - continue; - - if (!valid_views.count(I) || !valid_views.count(J)) - continue; - - const View* viewI = _sfmData.getViews().at(I).get(); - const Intrinsics::const_iterator iterIntrinsic_I = _sfmData.getIntrinsics().find(viewI->getIntrinsicId()); - const View* viewJ = _sfmData.getViews().at(J).get(); - const Intrinsics::const_iterator iterIntrinsic_J = _sfmData.getIntrinsics().find(viewJ->getIntrinsicId()); - - const Pinhole* camI = dynamic_cast(iterIntrinsic_I->second.get()); - const Pinhole* camJ = dynamic_cast(iterIntrinsic_J->second.get()); - if (camI == nullptr || camJ == nullptr) - continue; - - aliceVision::track::TracksMap map_tracksCommon; - const std::set set_imageIndex= {I, J}; - track::getCommonTracksInImagesFast(set_imageIndex, _map_tracks, _map_tracksPerView, map_tracksCommon); - - // Copy points correspondences to arrays for relative pose estimation - const size_t n = map_tracksCommon.size(); - ALICEVISION_LOG_DEBUG("Automatic initial pair choice test - I: " << I << ", J: " << J << ", common tracks: " << n); - Mat xI(2,n), xJ(2,n); - size_t cptIndex = 0; - std::vector commonTracksIds(n); - for (aliceVision::track::TracksMap::const_iterator - iterT = map_tracksCommon.begin(); iterT != map_tracksCommon.end(); - ++iterT, ++cptIndex) - { - auto iter = iterT->second.featPerView.begin(); - const size_t i = iter->second; - const size_t j = (++iter)->second; - commonTracksIds[cptIndex] = iterT->first; - - const auto& viewI = _featuresPerView->getFeatures(I, iterT->second.descType); - const auto& viewJ = _featuresPerView->getFeatures(J, iterT->second.descType); - - Vec2 feat = viewI[i].coords().cast(); - xI.col(cptIndex) = camI->get_ud_pixel(feat); - feat = viewJ[j].coords().cast(); - xJ.col(cptIndex) = camJ->get_ud_pixel(feat); + for (int i = 0; i < _pairwiseMatches->size(); ++i) + { + matching::PairwiseMatches::const_iterator iter = _pairwiseMatches->begin(); + std::advance(iter, i); + + ++progressDisplay; + + const Pair current_pair = iter->first; + + const IndexT I = std::min(current_pair.first, current_pair.second); + const IndexT J = std::max(current_pair.first, current_pair.second); + + if (filterViewId != UndefinedIndexT && filterViewId != I && filterViewId != J) + continue; + + if (!valid_views.count(I) || !valid_views.count(J)) + continue; + + const View* viewI = _sfmData.getViews().at(I).get(); + const Intrinsics::const_iterator iterIntrinsic_I = _sfmData.getIntrinsics().find(viewI->getIntrinsicId()); + const View* viewJ = _sfmData.getViews().at(J).get(); + const Intrinsics::const_iterator iterIntrinsic_J = _sfmData.getIntrinsics().find(viewJ->getIntrinsicId()); + + const Pinhole* camI = dynamic_cast(iterIntrinsic_I->second.get()); + const Pinhole* camJ = dynamic_cast(iterIntrinsic_J->second.get()); + if (camI == nullptr || camJ == nullptr) + continue; + + aliceVision::track::TracksMap map_tracksCommon; + const std::set set_imageIndex = {I, J}; + track::getCommonTracksInImagesFast(set_imageIndex, _map_tracks, _map_tracksPerView, map_tracksCommon); + + // Copy points correspondences to arrays for relative pose estimation + const size_t n = map_tracksCommon.size(); + ALICEVISION_LOG_DEBUG("Automatic initial pair choice test - I: " << I << ", J: " << J << ", common tracks: " << n); + Mat xI(2, n), xJ(2, n); + size_t cptIndex = 0; + std::vector commonTracksIds(n); + for (aliceVision::track::TracksMap::const_iterator iterT = map_tracksCommon.begin(); iterT != map_tracksCommon.end(); ++iterT, ++cptIndex) + { + auto iter = iterT->second.featPerView.begin(); + const size_t i = iter->second; + const size_t j = (++iter)->second; + commonTracksIds[cptIndex] = iterT->first; + + const auto& viewI = _featuresPerView->getFeatures(I, iterT->second.descType); + const auto& viewJ = _featuresPerView->getFeatures(J, iterT->second.descType); + + Vec2 feat = viewI[i].coords().cast(); + xI.col(cptIndex) = camI->get_ud_pixel(feat); + feat = viewJ[j].coords().cast(); + xJ.col(cptIndex) = camJ->get_ud_pixel(feat); + } + + // Robust estimation of the relative pose + RelativePoseInfo relativePose_info; + relativePose_info.initial_residual_tolerance = 4.0; + + const bool relativePoseSuccess = robustRelativePose(camI->K(), + camJ->K(), + xI, + xJ, + _randomNumberGenerator, + relativePose_info, + std::make_pair(camI->w(), camI->h()), + std::make_pair(camJ->w(), camJ->h()), + 1024); + + if (relativePoseSuccess && relativePose_info.vec_inliers.size() > iMin_inliers_count) + { + // Triangulate inliers & compute angle between bearing vectors + std::vector vec_angles(relativePose_info.vec_inliers.size()); + std::vector validCommonTracksIds(relativePose_info.vec_inliers.size()); + const Pose3 pose_I = Pose3(Mat3::Identity(), Vec3::Zero()); + const Pose3 pose_J = relativePose_info.relativePose; + const Mat34 PI = camI->getProjectiveEquivalent(pose_I); + const Mat34 PJ = camJ->getProjectiveEquivalent(pose_J); + std::size_t i = 0; + for (const size_t inlier_idx : relativePose_info.vec_inliers) + { + Vec3 X; + multiview::TriangulateDLT(PI, xI.col(inlier_idx), PJ, xJ.col(inlier_idx), X); + IndexT trackId = commonTracksIds[inlier_idx]; + auto iter = map_tracksCommon[trackId].featPerView.begin(); + const Vec2 featI = _featuresPerView->getFeatures(I, map_tracksCommon[trackId].descType)[iter->second].coords().cast(); + const Vec2 featJ = _featuresPerView->getFeatures(J, map_tracksCommon[trackId].descType)[(++iter)->second].coords().cast(); + vec_angles[i] = angleBetweenRays(pose_I, camI, pose_J, camJ, featI, featJ); + validCommonTracksIds[i] = trackId; + ++i; + } + // Compute the median triangulation angle + const unsigned median_index = vec_angles.size() / 2; + std::nth_element(vec_angles.begin(), vec_angles.begin() + median_index, vec_angles.end()); + const float scoring_angle = vec_angles[median_index]; + const double imagePairScore = + std::min(computeCandidateImageScore(I, validCommonTracksIds), computeCandidateImageScore(J, validCommonTracksIds)); + double score = scoring_angle * imagePairScore; + + // If the image pair is outside the reasonable angle range: [fRequired_min_angle;fLimit_max_angle] + // we put it in negative to ensure that image pairs with reasonable angle will win, + // but keep the score ordering. + if (scoring_angle < fRequired_min_angle || scoring_angle > fLimit_max_angle) + score = -1.0 / score; + +#pragma omp critical + bestImagePairs.emplace_back(score, imagePairScore, scoring_angle, relativePose_info.vec_inliers.size(), current_pair); + } + } + // We print the N best scores and return the best one. + const std::size_t nBestScores = std::min(std::size_t(50), bestImagePairs.size()); + std::sort(bestImagePairs.begin(), bestImagePairs.end(), std::greater()); + ALICEVISION_LOG_DEBUG(bestImagePairs.size() << " possible image pairs. " << nBestScores << " best possibles image pairs are:"); + ALICEVISION_LOG_DEBUG(boost::format("%=25s | %=15s | %=15s | %=15s | %=15s") % "Pair" % "Score" % "ImagePairScore" % "Angle" % "NbMatches"); + ALICEVISION_LOG_DEBUG(std::string(25 + 15 * 4 + 3 * 4, '-')); + for (std::size_t i = 0; i < nBestScores; ++i) + { + const ImagePairScore& s = bestImagePairs[i]; + const Pair& currPair = std::get<4>(s); + const std::string pairIdx = std::to_string(currPair.first) + ", " + std::to_string(currPair.second); + ALICEVISION_LOG_DEBUG(boost::format("%=25s | %+15.1f | %+15.1f | %+15.1f | %+15f") % pairIdx % std::get<0>(s) % std::get<1>(s) % + std::get<2>(s) % std::get<3>(s)); } - - // Robust estimation of the relative pose - RelativePoseInfo relativePose_info; - relativePose_info.initial_residual_tolerance = 4.0; - - const bool relativePoseSuccess = robustRelativePose( - camI->K(), camJ->K(), - xI, xJ, _randomNumberGenerator, relativePose_info, - std::make_pair(camI->w(), camI->h()), std::make_pair(camJ->w(), camJ->h()), - 1024); - - if (relativePoseSuccess && relativePose_info.vec_inliers.size() > iMin_inliers_count) - { - // Triangulate inliers & compute angle between bearing vectors - std::vector vec_angles(relativePose_info.vec_inliers.size()); - std::vector validCommonTracksIds(relativePose_info.vec_inliers.size()); - const Pose3 pose_I = Pose3(Mat3::Identity(), Vec3::Zero()); - const Pose3 pose_J = relativePose_info.relativePose; - const Mat34 PI = camI->getProjectiveEquivalent(pose_I); - const Mat34 PJ = camJ->getProjectiveEquivalent(pose_J); - std::size_t i = 0; - for (const size_t inlier_idx: relativePose_info.vec_inliers) - { - Vec3 X; - multiview::TriangulateDLT(PI, xI.col(inlier_idx), PJ, xJ.col(inlier_idx), X); - IndexT trackId = commonTracksIds[inlier_idx]; - auto iter = map_tracksCommon[trackId].featPerView.begin(); - const Vec2 featI = _featuresPerView->getFeatures(I, map_tracksCommon[trackId].descType)[iter->second].coords().cast(); - const Vec2 featJ = _featuresPerView->getFeatures(J, map_tracksCommon[trackId].descType)[(++iter)->second].coords().cast(); - vec_angles[i] = angleBetweenRays(pose_I, camI, pose_J, camJ, featI, featJ); - validCommonTracksIds[i] = trackId; - ++i; - } - // Compute the median triangulation angle - const unsigned median_index = vec_angles.size() / 2; - std::nth_element( - vec_angles.begin(), - vec_angles.begin() + median_index, - vec_angles.end()); - const float scoring_angle = vec_angles[median_index]; - const double imagePairScore = std::min(computeCandidateImageScore(I, validCommonTracksIds), computeCandidateImageScore(J, validCommonTracksIds)); - double score = scoring_angle * imagePairScore; - - // If the image pair is outside the reasonable angle range: [fRequired_min_angle;fLimit_max_angle] - // we put it in negative to ensure that image pairs with reasonable angle will win, - // but keep the score ordering. - if (scoring_angle < fRequired_min_angle || - scoring_angle > fLimit_max_angle) - score = - 1.0 / score; - - #pragma omp critical - bestImagePairs.emplace_back(score, imagePairScore, scoring_angle, relativePose_info.vec_inliers.size(), current_pair); + if (bestImagePairs.empty()) + { + ALICEVISION_LOG_ERROR("No valid initial pair found automatically."); + return false; } - } - // We print the N best scores and return the best one. - const std::size_t nBestScores = std::min(std::size_t(50), bestImagePairs.size()); - std::sort(bestImagePairs.begin(), bestImagePairs.end(), std::greater()); - ALICEVISION_LOG_DEBUG(bestImagePairs.size() << " possible image pairs. " << nBestScores << " best possibles image pairs are:"); - ALICEVISION_LOG_DEBUG(boost::format("%=25s | %=15s | %=15s | %=15s | %=15s") % "Pair" % "Score" % "ImagePairScore" % "Angle" % "NbMatches"); - ALICEVISION_LOG_DEBUG(std::string(25+15*4+3*4, '-')); - for(std::size_t i = 0; i < nBestScores; ++i) - { - const ImagePairScore& s = bestImagePairs[i]; - const Pair& currPair = std::get<4>(s); - const std::string pairIdx = std::to_string(currPair.first) + ", " + std::to_string(currPair.second); - ALICEVISION_LOG_DEBUG(boost::format("%=25s | %+15.1f | %+15.1f | %+15.1f | %+15f") % pairIdx % std::get<0>(s) % std::get<1>(s) % std::get<2>(s) % std::get<3>(s)); - } - if (bestImagePairs.empty()) - { - ALICEVISION_LOG_ERROR("No valid initial pair found automatically."); - return false; - } - out_bestImagePairs.reserve(bestImagePairs.size()); - for(const auto& imagePair: bestImagePairs) - out_bestImagePairs.push_back(std::get<4>(imagePair)); - - return true; + out_bestImagePairs.reserve(bestImagePairs.size()); + for (const auto& imagePair : bestImagePairs) + out_bestImagePairs.push_back(std::get<4>(imagePair)); + + return true; } std::size_t ReconstructionEngine_sequentialSfM::computeCandidateImageScore(IndexT viewId, const std::vector& trackIds) const { #ifdef ALICEVISION_NEXTBESTVIEW_WITHOUT_SCORE - return trackIds.size(); + return trackIds.size(); #else - std::size_t score = 0; - // The number of cells of the pyramid grid represent the score - // and ensure a proper repartition of features in images. - const auto& featsPyramid = _map_featsPyramidPerView.at(viewId); - for(std::size_t level = 0; level < _params.pyramidDepth; ++level) - { - std::set featIndexes; // Set of grid cell indexes in the pyramid - for(IndexT trackId: trackIds) - { - std::size_t pyramidIndex = featsPyramid.at(trackId * _params.pyramidDepth + level); - featIndexes.insert(pyramidIndex); + std::size_t score = 0; + // The number of cells of the pyramid grid represent the score + // and ensure a proper repartition of features in images. + const auto& featsPyramid = _map_featsPyramidPerView.at(viewId); + for (std::size_t level = 0; level < _params.pyramidDepth; ++level) + { + std::set featIndexes; // Set of grid cell indexes in the pyramid + for (IndexT trackId : trackIds) + { + std::size_t pyramidIndex = featsPyramid.at(trackId * _params.pyramidDepth + level); + featIndexes.insert(pyramidIndex); + } + score += featIndexes.size() * _pyramidWeights[level]; } - score += featIndexes.size() * _pyramidWeights[level]; - } - return score; + return score; #endif } - /** * @brief Add one image to the 3D reconstruction. To the resectioning of * the camera. @@ -1522,227 +1526,219 @@ std::size_t ReconstructionEngine_sequentialSfM::computeCandidateImageScore(Index */ bool ReconstructionEngine_sequentialSfM::computeResection(const IndexT viewId, ResectionData& resectionData) { - // A. Compute 2D/3D matches - // A1. list tracks ids used by the view - const aliceVision::track::TrackIdSet& set_tracksIds = _map_tracksPerView.at(viewId); - - // A2. intersects the track list with the reconstructed - std::set reconstructed_trackId; - std::transform(_sfmData.getLandmarks().begin(), _sfmData.getLandmarks().end(), - std::inserter(reconstructed_trackId, reconstructed_trackId.begin()), - stl::RetrieveKey()); - - // Get the ids of the already reconstructed tracks - std::set_intersection(set_tracksIds.begin(), set_tracksIds.end(), - reconstructed_trackId.begin(), - reconstructed_trackId.end(), - std::inserter(resectionData.tracksId, resectionData.tracksId.begin())); - - if (resectionData.tracksId.empty()) - { - // No match. The image has no connection with already reconstructed points. - ALICEVISION_LOG_DEBUG("Resection failed as there is no connection with already reconstructed points"); - return false; - } - - // Get back featId associated to a tracksID already reconstructed. - // These 2D/3D associations will be used for the resection. - getFeatureIdInViewPerTrack(_map_tracks, - resectionData.tracksId, - viewId, - &resectionData.featuresId); - - // Localize the image inside the SfM reconstruction - resectionData.pt2D.resize(2, resectionData.tracksId.size()); - resectionData.pt3D.resize(3, resectionData.tracksId.size()); - resectionData.vec_descType.resize(resectionData.tracksId.size()); - - // B. Look if intrinsic data is known or not - std::shared_ptr view_I = _sfmData.getViews().at(viewId); - std::shared_ptr intrinsics = _sfmData.getIntrinsicsharedPtr(view_I->getIntrinsicId()); - if(intrinsics == nullptr) - { - throw std::runtime_error("Intrinsic " + std::to_string(view_I->getIntrinsicId()) + " is not initialized, all intrinsics should be initialized" ); + // A. Compute 2D/3D matches + // A1. list tracks ids used by the view + const aliceVision::track::TrackIdSet& set_tracksIds = _map_tracksPerView.at(viewId); + + // A2. intersects the track list with the reconstructed + std::set reconstructed_trackId; + std::transform(_sfmData.getLandmarks().begin(), + _sfmData.getLandmarks().end(), + std::inserter(reconstructed_trackId, reconstructed_trackId.begin()), + stl::RetrieveKey()); + + // Get the ids of the already reconstructed tracks + std::set_intersection(set_tracksIds.begin(), + set_tracksIds.end(), + reconstructed_trackId.begin(), + reconstructed_trackId.end(), + std::inserter(resectionData.tracksId, resectionData.tracksId.begin())); + + if (resectionData.tracksId.empty()) + { + // No match. The image has no connection with already reconstructed points. + ALICEVISION_LOG_DEBUG("Resection failed as there is no connection with already reconstructed points"); + return false; } - - std::size_t cpt = 0; - std::set::const_iterator iterTrackId = resectionData.tracksId.begin(); - for (std::vector::const_iterator iterfeatId = resectionData.featuresId.begin(); - iterfeatId != resectionData.featuresId.end(); - ++iterfeatId, ++iterTrackId, ++cpt) - { - const feature::EImageDescriberType descType = iterfeatId->first; - resectionData.pt3D.col(cpt) = _sfmData.getLandmarks().at(*iterTrackId).X; - resectionData.pt2D.col(cpt) = _featuresPerView->getFeatures(viewId, descType)[iterfeatId->second].coords().cast(); - resectionData.vec_descType.at(cpt) = descType; - } - - // C. Do the resectioning: compute the camera pose. - ALICEVISION_LOG_INFO("[" << _sfmData.getValidViews().size()+1 << "/" << _sfmData.getViews().size() << "] Robust Resection of view: " << viewId); - - const bool bResection = sfm::SfMLocalizer::Localize( - Pair(view_I->getImage().getWidth(), view_I->getImage().getHeight()), - intrinsics.get(), - _randomNumberGenerator, - resectionData, - resectionData.pose, - _params.localizerEstimator - ); - - if (!_htmlLogFile.empty()) - { - using namespace htmlDocument; - std::ostringstream os; - os << "Robust resection of view " << viewId << ":
"; - _htmlDocStream->pushInfo(htmlMarkup("h4",os.str())); - - os.str(""); - os << std::endl - << "- Image path: " << view_I->getImage().getImagePath() << "
" - << "- Threshold (error max): " << resectionData.error_max << "
" - << "- Resection status: " << (bResection ? "OK" : "FAILED") << "
" - << "- # points used for Resection: " << resectionData.featuresId.size() << "
" - << "- # points validated by robust estimation: " << resectionData.vec_inliers.size() << "
" - << "- % points validated: " - << resectionData.vec_inliers.size()/static_cast(resectionData.featuresId.size()) << "
"; - - _htmlDocStream->pushInfo(os.str()); - } - - if (!bResection) - { - ALICEVISION_LOG_INFO("Resection of view " << viewId << " failed."); - return false; - } - // D. Refine the pose of the found camera. - // We use a local scene with only the 3D points and the new camera. - { - const std::set reconstructedIntrinsics = _sfmData.getReconstructedIntrinsics(); - // If we use a camera intrinsic for the first time we need to refine it. - const bool intrinsicsFirstUsage = (reconstructedIntrinsics.count(view_I->getIntrinsicId()) == 0); - - if(!sfm::SfMLocalizer::RefinePose( - intrinsics.get(), - resectionData.pose, - resectionData, - true, - intrinsicsFirstUsage)) - { - ALICEVISION_LOG_INFO("Resection of view " << viewId << " failed during pose refinement."); - return false; + // Get back featId associated to a tracksID already reconstructed. + // These 2D/3D associations will be used for the resection. + getFeatureIdInViewPerTrack(_map_tracks, resectionData.tracksId, viewId, &resectionData.featuresId); + + // Localize the image inside the SfM reconstruction + resectionData.pt2D.resize(2, resectionData.tracksId.size()); + resectionData.pt3D.resize(3, resectionData.tracksId.size()); + resectionData.vec_descType.resize(resectionData.tracksId.size()); + + // B. Look if intrinsic data is known or not + std::shared_ptr view_I = _sfmData.getViews().at(viewId); + std::shared_ptr intrinsics = _sfmData.getIntrinsicsharedPtr(view_I->getIntrinsicId()); + if (intrinsics == nullptr) + { + throw std::runtime_error("Intrinsic " + std::to_string(view_I->getIntrinsicId()) + + " is not initialized, all intrinsics should be initialized"); + } + + std::size_t cpt = 0; + std::set::const_iterator iterTrackId = resectionData.tracksId.begin(); + for (std::vector::const_iterator iterfeatId = resectionData.featuresId.begin(); iterfeatId != resectionData.featuresId.end(); + ++iterfeatId, ++iterTrackId, ++cpt) + { + const feature::EImageDescriberType descType = iterfeatId->first; + resectionData.pt3D.col(cpt) = _sfmData.getLandmarks().at(*iterTrackId).X; + resectionData.pt2D.col(cpt) = _featuresPerView->getFeatures(viewId, descType)[iterfeatId->second].coords().cast(); + resectionData.vec_descType.at(cpt) = descType; + } + + // C. Do the resectioning: compute the camera pose. + ALICEVISION_LOG_INFO("[" << _sfmData.getValidViews().size() + 1 << "/" << _sfmData.getViews().size() << "] Robust Resection of view: " << viewId); + + const bool bResection = sfm::SfMLocalizer::Localize(Pair(view_I->getImage().getWidth(), view_I->getImage().getHeight()), + intrinsics.get(), + _randomNumberGenerator, + resectionData, + resectionData.pose, + _params.localizerEstimator); + + if (!_htmlLogFile.empty()) + { + using namespace htmlDocument; + std::ostringstream os; + os << "Robust resection of view " << viewId << ":
"; + _htmlDocStream->pushInfo(htmlMarkup("h4", os.str())); + + os.str(""); + os << std::endl + << "- Image path: " << view_I->getImage().getImagePath() << "
" + << "- Threshold (error max): " << resectionData.error_max << "
" + << "- Resection status: " << (bResection ? "OK" : "FAILED") << "
" + << "- # points used for Resection: " << resectionData.featuresId.size() << "
" + << "- # points validated by robust estimation: " << resectionData.vec_inliers.size() << "
" + << "- % points validated: " << resectionData.vec_inliers.size() / static_cast(resectionData.featuresId.size()) << "
"; + + _htmlDocStream->pushInfo(os.str()); } - } - return true; + + if (!bResection) + { + ALICEVISION_LOG_INFO("Resection of view " << viewId << " failed."); + return false; + } + + // D. Refine the pose of the found camera. + // We use a local scene with only the 3D points and the new camera. + { + const std::set reconstructedIntrinsics = _sfmData.getReconstructedIntrinsics(); + // If we use a camera intrinsic for the first time we need to refine it. + const bool intrinsicsFirstUsage = (reconstructedIntrinsics.count(view_I->getIntrinsicId()) == 0); + + if (!sfm::SfMLocalizer::RefinePose(intrinsics.get(), resectionData.pose, resectionData, true, intrinsicsFirstUsage)) + { + ALICEVISION_LOG_INFO("Resection of view " << viewId << " failed during pose refinement."); + return false; + } + } + return true; } void ReconstructionEngine_sequentialSfM::updateScene(const IndexT viewIndex, const ResectionData& resectionData) -{ - // A. Update the global scene with the new found camera pose, intrinsic (if not defined) - - // update the view pose or rig pose/sub-pose - _map_ACThreshold.insert(std::make_pair(viewIndex, resectionData.error_max)); - - const View& view = *_sfmData.getViews().at(viewIndex); - _sfmData.setPose(view, CameraPose(resectionData.pose)); - - std::shared_ptr intrinsics = _sfmData.getIntrinsicsharedPtr(view.getIntrinsicId()); - - // B. Update the observations into the global scene structure - // - Add the new 2D observations to the reconstructed tracks - std::set::const_iterator iterTrackId = resectionData.tracksId.begin(); - for (std::size_t i = 0; i < resectionData.pt2D.cols(); ++i, ++iterTrackId) - { - const Vec3 X = resectionData.pt3D.col(i); - const Vec2 x = resectionData.pt2D.col(i); - const Vec2 residual = intrinsics->residual(resectionData.pose, X.homogeneous(), x); - if (residual.norm() < resectionData.error_max && - resectionData.pose.depth(X) > 0) - { - Landmark& landmark = _sfmData.getLandmarks()[*iterTrackId]; - const IndexT idFeat = resectionData.featuresId[i].second; - const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : _featuresPerView->getFeatures(viewIndex, landmark.descType)[idFeat].scale(); - // Inlier, add the point to the reconstructed track - landmark.observations[viewIndex] = Observation(x, idFeat, scale); +{ + // A. Update the global scene with the new found camera pose, intrinsic (if not defined) + + // update the view pose or rig pose/sub-pose + _map_ACThreshold.insert(std::make_pair(viewIndex, resectionData.error_max)); + + const View& view = *_sfmData.getViews().at(viewIndex); + _sfmData.setPose(view, CameraPose(resectionData.pose)); + + std::shared_ptr intrinsics = _sfmData.getIntrinsicsharedPtr(view.getIntrinsicId()); + + // B. Update the observations into the global scene structure + // - Add the new 2D observations to the reconstructed tracks + std::set::const_iterator iterTrackId = resectionData.tracksId.begin(); + for (std::size_t i = 0; i < resectionData.pt2D.cols(); ++i, ++iterTrackId) + { + const Vec3 X = resectionData.pt3D.col(i); + const Vec2 x = resectionData.pt2D.col(i); + const Vec2 residual = intrinsics->residual(resectionData.pose, X.homogeneous(), x); + if (residual.norm() < resectionData.error_max && resectionData.pose.depth(X) > 0) + { + Landmark& landmark = _sfmData.getLandmarks()[*iterTrackId]; + const IndexT idFeat = resectionData.featuresId[i].second; + const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) + ? 0.0 + : _featuresPerView->getFeatures(viewIndex, landmark.descType)[idFeat].scale(); + // Inlier, add the point to the reconstructed track + landmark.observations[viewIndex] = Observation(x, idFeat, scale); + } } - } } -bool ReconstructionEngine_sequentialSfM::checkChieralities( - const Vec3& pt3D, - const std::set & viewsId, - const SfMData& scene) +bool ReconstructionEngine_sequentialSfM::checkChieralities(const Vec3& pt3D, const std::set& viewsId, const SfMData& scene) { - for (const IndexT & viewId : viewsId) - { - const View* view = scene.getViews().at(viewId).get(); - const Pose3 pose = scene.getPose(*view).getTransform(); - // Check that the point is in front of all the cameras. - if (pose.depth(pt3D) < 0) - return false; - } - return true; + for (const IndexT& viewId : viewsId) + { + const View* view = scene.getViews().at(viewId).get(); + const Pose3 pose = scene.getPose(*view).getTransform(); + // Check that the point is in front of all the cameras. + if (pose.depth(pt3D) < 0) + return false; + } + return true; } -bool ReconstructionEngine_sequentialSfM::checkAngles(const Vec3 &pt3D, const std::set &viewsId, const SfMData &scene, const double &kMinAngle) -{ - for (const std::size_t & viewIdA : viewsId) - { - for (const std::size_t & viewIdB : viewsId) - { - if (viewIdA < viewIdB) - { - double angle_deg = angleBetweenRays(scene.getPose(*scene.getViews().at(viewIdA).get()).getTransform(), - scene.getPose(*scene.getViews().at(viewIdB).get()).getTransform(), - pt3D); - if (angle_deg >= kMinAngle) - return true; - } +bool ReconstructionEngine_sequentialSfM::checkAngles(const Vec3& pt3D, const std::set& viewsId, const SfMData& scene, const double& kMinAngle) +{ + for (const std::size_t& viewIdA : viewsId) + { + for (const std::size_t& viewIdB : viewsId) + { + if (viewIdA < viewIdB) + { + double angle_deg = angleBetweenRays(scene.getPose(*scene.getViews().at(viewIdA).get()).getTransform(), + scene.getPose(*scene.getViews().at(viewIdB).get()).getTransform(), + pt3D); + if (angle_deg >= kMinAngle) + return true; + } + } } - } - return false; + return false; } -void ReconstructionEngine_sequentialSfM::getTracksToTriangulate(const std::set& previousReconstructedViews, - const std::set& newReconstructedViews, - std::map> & mapTracksToTriangulate) const +void ReconstructionEngine_sequentialSfM::getTracksToTriangulate(const std::set& previousReconstructedViews, + const std::set& newReconstructedViews, + std::map>& mapTracksToTriangulate) const { - std::set allReconstructedViews; - allReconstructedViews.insert(previousReconstructedViews.begin(), previousReconstructedViews.end()); - allReconstructedViews.insert(newReconstructedViews.begin(), newReconstructedViews.end()); - - std::set allTracksInNewViews; - track::getTracksInImagesFast(newReconstructedViews, _map_tracksPerView, allTracksInNewViews); - - std::set::iterator it; + std::set allReconstructedViews; + allReconstructedViews.insert(previousReconstructedViews.begin(), previousReconstructedViews.end()); + allReconstructedViews.insert(newReconstructedViews.begin(), newReconstructedViews.end()); + + std::set allTracksInNewViews; + track::getTracksInImagesFast(newReconstructedViews, _map_tracksPerView, allTracksInNewViews); + + std::set::iterator it; #pragma omp parallel private(it) - { - for (it = allTracksInNewViews.begin(); it != allTracksInNewViews.end(); ++it) { -#pragma omp single nowait - { - const std::size_t trackId = *it; - - const track::Track& track = _map_tracks.at(trackId); - - std::set allViewsSharingTheTrack; - std::transform(track.featPerView.begin(), track.featPerView.end(), - std::inserter(allViewsSharingTheTrack, allViewsSharingTheTrack.begin()), - stl::RetrieveKey()); - - std::set allReconstructedViewsSharingTheTrack; - std::set_intersection(allViewsSharingTheTrack.begin(), allViewsSharingTheTrack.end(), - allReconstructedViews.begin(), allReconstructedViews.end(), - std::inserter(allReconstructedViewsSharingTheTrack, allReconstructedViewsSharingTheTrack.begin())); - - if (allReconstructedViewsSharingTheTrack.size() >= _params.minNbObservationsForTriangulation) + for (it = allTracksInNewViews.begin(); it != allTracksInNewViews.end(); ++it) { -#pragma omp critical - mapTracksToTriangulate[trackId] = allReconstructedViewsSharingTheTrack; +#pragma omp single nowait + { + const std::size_t trackId = *it; + + const track::Track& track = _map_tracks.at(trackId); + + std::set allViewsSharingTheTrack; + std::transform(track.featPerView.begin(), + track.featPerView.end(), + std::inserter(allViewsSharingTheTrack, allViewsSharingTheTrack.begin()), + stl::RetrieveKey()); + + std::set allReconstructedViewsSharingTheTrack; + std::set_intersection(allViewsSharingTheTrack.begin(), + allViewsSharingTheTrack.end(), + allReconstructedViews.begin(), + allReconstructedViews.end(), + std::inserter(allReconstructedViewsSharingTheTrack, allReconstructedViewsSharingTheTrack.begin())); + + if (allReconstructedViewsSharingTheTrack.size() >= _params.minNbObservationsForTriangulation) + { +#pragma omp critical + mapTracksToTriangulate[trackId] = allReconstructedViewsSharingTheTrack; + } + } } - } } - } } namespace { @@ -1758,16 +1754,15 @@ struct ObservationData bool isEmpty() const { return cam == nullptr; } }; -ObservationData getObservationData(const SfMData& scene, - feature::FeaturesPerView* featuresPerView, - IndexT viewId, const track::Track& track) +ObservationData getObservationData(const SfMData& scene, feature::FeaturesPerView* featuresPerView, IndexT viewId, const track::Track& track) { const View* view = scene.getViews().at(viewId).get(); std::shared_ptr cam = scene.getIntrinsics().at(view->getIntrinsicId()); std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); - if (!camPinHole) { + if (!camPinHole) + { ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate_multiViewsLORANSAC"); return {nullptr, {}, {}, {}, {}}; } @@ -1777,350 +1772,349 @@ ObservationData getObservationData(const SfMData& scene, const auto& feature = featuresPerView->getFeatures(viewId, track.descType)[track.featPerView.at(viewId)]; Vec2 x = feature.coords().cast(); - Vec2 xUd = cam->get_ud_pixel(x); // undistorted 2D point + Vec2 xUd = cam->get_ud_pixel(x); // undistorted 2D point return {camPinHole, pose, P, x, xUd}; } -} // namespace +} // namespace -void ReconstructionEngine_sequentialSfM::triangulate_multiViewsLORANSAC(SfMData& scene, const std::set& previousReconstructedViews, const std::set& newReconstructedViews) +void ReconstructionEngine_sequentialSfM::triangulate_multiViewsLORANSAC(SfMData& scene, + const std::set& previousReconstructedViews, + const std::set& newReconstructedViews) { - ALICEVISION_LOG_DEBUG("Triangulating (mode: multi-view LO-RANSAC)... "); - - // -- Identify the track to triangulate : - // This map contains all the tracks that will be triangulated (for the first time, or not) - // These tracks are seen by at least one new reconstructed view. - std::map> mapTracksToTriangulate; // - getTracksToTriangulate(previousReconstructedViews, newReconstructedViews, mapTracksToTriangulate); - - std::vector setTracksId; // - std::transform(mapTracksToTriangulate.begin(), mapTracksToTriangulate.end(), - std::inserter(setTracksId, setTracksId.begin()), - stl::RetrieveKey()); - -#pragma omp parallel for - for (int i = 0; i < setTracksId.size(); i++) // each track (already reconstructed or not) - { - const IndexT trackId = setTracksId.at(i); - bool isValidTrack = true; - const track::Track& track = _map_tracks.at(trackId); - std::set& observations = mapTracksToTriangulate.at(trackId); // all the posed views possessing the track - - // The track needs to be seen by a min. number of views to be triangulated - if (observations.size() < _params.minNbObservationsForTriangulation) - continue; - - Vec3 X_euclidean = Vec3::Zero(); - std::set inliers; - - if (observations.size() == 2) - { - /* -------------------------------------------- - * 2 observations : triangulation using DLT - * -------------------------------------------- */ - - inliers = observations; - - // -- Prepare: - IndexT I = *(observations.begin()); - IndexT J = *(observations.rbegin()); - - const auto oi = getObservationData(scene, _featuresPerView, I, track); - const auto oj = getObservationData(scene, _featuresPerView, J, track); - - if (oi.isEmpty() || oj.isEmpty()) { - continue; - } - - // -- Triangulate: - multiview::TriangulateDLT(oi.P, oi.xUd, oj.P, oj.xUd, X_euclidean); - - // -- Check: - // - angle (small angle leads imprecise triangulation) - // - positive depth - // - residual values - // TODO assert(acThresholdIt != _map_ACThreshold.end()); - const auto& acThresholdItI = _map_ACThreshold.find(I); - const auto& acThresholdItJ = _map_ACThreshold.find(J); - const double& acThresholdI = (acThresholdItI != _map_ACThreshold.end()) ? acThresholdItI->second : 4.0; - const double& acThresholdJ = (acThresholdItJ != _map_ACThreshold.end()) ? acThresholdItJ->second : 4.0; - - if (angleBetweenRays(oi.pose, oi.cam.get(), oj.pose, oj.cam.get(), oi.x, oj.x) < _params.minAngleForTriangulation || - oi.pose.depth(X_euclidean) < 0 || - oj.pose.depth(X_euclidean) < 0 || - oi.cam->residual(oi.pose, X_euclidean.homogeneous(), oi.x).norm() > acThresholdI || - oj.cam->residual(oj.pose, X_euclidean.homogeneous(), oj.x).norm() > acThresholdJ) - isValidTrack = false; - } - else - { - /* ------------------------------------------------------- - * N obsevations (N>2) : triangulation using LORANSAC - * ------------------------------------------------------- */ - - // -- Prepare: - std::vector features; // undistorted 2D features (one per pose) - std::vector Ps; // projective matrices (one per pose) - { + ALICEVISION_LOG_DEBUG("Triangulating (mode: multi-view LO-RANSAC)... "); + + // -- Identify the track to triangulate : + // This map contains all the tracks that will be triangulated (for the first time, or not) + // These tracks are seen by at least one new reconstructed view. + std::map> mapTracksToTriangulate; // + getTracksToTriangulate(previousReconstructedViews, newReconstructedViews, mapTracksToTriangulate); + + std::vector setTracksId; // + std::transform(mapTracksToTriangulate.begin(), mapTracksToTriangulate.end(), std::inserter(setTracksId, setTracksId.begin()), stl::RetrieveKey()); + +#pragma omp parallel for + for (int i = 0; i < setTracksId.size(); i++) // each track (already reconstructed or not) + { + const IndexT trackId = setTracksId.at(i); + bool isValidTrack = true; const track::Track& track = _map_tracks.at(trackId); - - int i = 0; - for (const IndexT& viewId : observations) - { - const auto o = getObservationData(scene, _featuresPerView, viewId, track); + std::set& observations = mapTracksToTriangulate.at(trackId); // all the posed views possessing the track - if (o.isEmpty()) { + // The track needs to be seen by a min. number of views to be triangulated + if (observations.size() < _params.minNbObservationsForTriangulation) continue; - } - features.push_back(o.xUd); - Ps.push_back(o.P); - i++; - } - } - - // -- Triangulate: - Vec4 X_homogeneous = Vec4::Zero(); - std::vector inliersIndex; - - multiview::TriangulateNViewLORANSAC(features, Ps, _randomNumberGenerator, X_homogeneous, &inliersIndex, 8.0); - - homogeneousToEuclidean(X_homogeneous, X_euclidean); - - // observations = {350, 380, 442} | inliersIndex = [0, 1] | inliers = {350, 380} - for (const auto & id : inliersIndex) - inliers.insert(*std::next(observations.begin(), id)); - - // -- Check: - // - nb of cameras validing the track - // - angle (small angle leads imprecise triangulation) - // - positive depth (chierality) - if (inliers.size() < _params.minNbObservationsForTriangulation || - !checkAngles(X_euclidean, inliers, scene, _params.minAngleForTriangulation) || - !checkChieralities(X_euclidean, inliers, scene)) - isValidTrack = false; - } - - // -- Add the tringulated point to the scene - if (isValidTrack) - { - Landmark landmark; - landmark.X = X_euclidean; - landmark.descType = track.descType; - for (const IndexT & viewId : inliers) // add inliers as observations - { - const Vec2 x = _featuresPerView->getFeatures(viewId, track.descType)[track.featPerView.at(viewId)].coords().cast(); - const feature::PointFeature& p = _featuresPerView->getFeatures(viewId, track.descType)[track.featPerView.at(viewId)]; - const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : p.scale(); - landmark.observations[viewId] = Observation(x, track.featPerView.at(viewId), scale); - } -#pragma omp critical - { - scene.getLandmarks()[trackId] = landmark; - } - } - else - { -#pragma omp critical - { - if (scene.getLandmarks().find(trackId) != scene.getLandmarks().end()) - scene.getLandmarks().erase(trackId); - } - } - } // for all shared tracks -} + Vec3 X_euclidean = Vec3::Zero(); + std::set inliers; -void ReconstructionEngine_sequentialSfM::triangulate_2Views(SfMData& scene, const std::set& previousReconstructedViews, const std::set& newReconstructedViews) -{ - { - std::vector intersection; - std::set_intersection( - newReconstructedViews.begin(), - newReconstructedViews.end(), - previousReconstructedViews.begin(), - previousReconstructedViews.end(), - std::back_inserter(intersection)); - - assert(intersection.empty()); - } - - std::set allReconstructedViews; - allReconstructedViews.insert(previousReconstructedViews.begin(), previousReconstructedViews.end()); - allReconstructedViews.insert(newReconstructedViews.begin(), newReconstructedViews.end()); + if (observations.size() == 2) + { + /* -------------------------------------------- + * 2 observations : triangulation using DLT + * -------------------------------------------- */ -#pragma omp parallel for schedule(dynamic) - for (ptrdiff_t i = 0; i < static_cast(allReconstructedViews.size()); ++i) - { - std::set::const_iterator iter = allReconstructedViews.begin(); - std::advance(iter, i); - const IndexT indexAll = *iter; - - for(IndexT indexNew: newReconstructedViews) - { - if(indexAll == indexNew) - continue; - - const std::size_t I = std::min((IndexT)indexNew, indexAll); - const std::size_t J = std::max((IndexT)indexNew, indexAll); - - // Find track correspondences between I and J - const std::set set_viewIndex = { I, J }; - track::TracksMap map_tracksCommonIJ; - track::getCommonTracksInImagesFast(set_viewIndex, _map_tracks, _map_tracksPerView, map_tracksCommonIJ); - - const View* viewI = scene.getViews().at(I).get(); - const View* viewJ = scene.getViews().at(J).get(); - - std::shared_ptr camI = scene.getIntrinsics().at(viewI->getIntrinsicId()); - std::shared_ptr camIPinHole = std::dynamic_pointer_cast(camI); - if (!camIPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate_multiViewsLORANSAC"); - continue; - } + inliers = observations; - std::shared_ptr camJ = scene.getIntrinsics().at(viewJ->getIntrinsicId()); - std::shared_ptr camJPinHole = std::dynamic_pointer_cast(camJ); - if (!camJPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate_multiViewsLORANSAC"); - continue; - } - - - const Pose3 poseI = scene.getPose(*viewI).getTransform(); - const Pose3 poseJ = scene.getPose(*viewJ).getTransform(); - - std::size_t new_putative_track = 0, new_added_track = 0, extented_track = 0; - for (const std::pair& trackIt : map_tracksCommonIJ) - { - const std::size_t trackId = trackIt.first; - const track::Track & track = trackIt.second; - - const Vec2 xI = _featuresPerView->getFeatures(I, track.descType)[track.featPerView.at(I)].coords().cast(); - const Vec2 xJ = _featuresPerView->getFeatures(J, track.descType)[track.featPerView.at(J)].coords().cast(); - - const feature::PointFeature& featI = _featuresPerView->getFeatures(I, track.descType)[track.featPerView.at(I)]; - const feature::PointFeature& featJ = _featuresPerView->getFeatures(J, track.descType)[track.featPerView.at(J)]; - // test if the track already exists in 3D - bool trackIdExists; -#pragma omp critical + // -- Prepare: + IndexT I = *(observations.begin()); + IndexT J = *(observations.rbegin()); + + const auto oi = getObservationData(scene, _featuresPerView, I, track); + const auto oj = getObservationData(scene, _featuresPerView, J, track); + + if (oi.isEmpty() || oj.isEmpty()) + { + continue; + } + + // -- Triangulate: + multiview::TriangulateDLT(oi.P, oi.xUd, oj.P, oj.xUd, X_euclidean); + + // -- Check: + // - angle (small angle leads imprecise triangulation) + // - positive depth + // - residual values + // TODO assert(acThresholdIt != _map_ACThreshold.end()); + const auto& acThresholdItI = _map_ACThreshold.find(I); + const auto& acThresholdItJ = _map_ACThreshold.find(J); + const double& acThresholdI = (acThresholdItI != _map_ACThreshold.end()) ? acThresholdItI->second : 4.0; + const double& acThresholdJ = (acThresholdItJ != _map_ACThreshold.end()) ? acThresholdItJ->second : 4.0; + + if (angleBetweenRays(oi.pose, oi.cam.get(), oj.pose, oj.cam.get(), oi.x, oj.x) < _params.minAngleForTriangulation || + oi.pose.depth(X_euclidean) < 0 || oj.pose.depth(X_euclidean) < 0 || + oi.cam->residual(oi.pose, X_euclidean.homogeneous(), oi.x).norm() > acThresholdI || + oj.cam->residual(oj.pose, X_euclidean.homogeneous(), oj.x).norm() > acThresholdJ) + isValidTrack = false; + } + else { - trackIdExists = scene.getLandmarks().find(trackId) != scene.getLandmarks().end(); + /* ------------------------------------------------------- + * N obsevations (N>2) : triangulation using LORANSAC + * ------------------------------------------------------- */ + + // -- Prepare: + std::vector features; // undistorted 2D features (one per pose) + std::vector Ps; // projective matrices (one per pose) + { + const track::Track& track = _map_tracks.at(trackId); + + int i = 0; + for (const IndexT& viewId : observations) + { + const auto o = getObservationData(scene, _featuresPerView, viewId, track); + + if (o.isEmpty()) + { + continue; + } + + features.push_back(o.xUd); + Ps.push_back(o.P); + i++; + } + } + + // -- Triangulate: + Vec4 X_homogeneous = Vec4::Zero(); + std::vector inliersIndex; + + multiview::TriangulateNViewLORANSAC(features, Ps, _randomNumberGenerator, X_homogeneous, &inliersIndex, 8.0); + + homogeneousToEuclidean(X_homogeneous, X_euclidean); + + // observations = {350, 380, 442} | inliersIndex = [0, 1] | inliers = {350, 380} + for (const auto& id : inliersIndex) + inliers.insert(*std::next(observations.begin(), id)); + + // -- Check: + // - nb of cameras validing the track + // - angle (small angle leads imprecise triangulation) + // - positive depth (chierality) + if (inliers.size() < _params.minNbObservationsForTriangulation || + !checkAngles(X_euclidean, inliers, scene, _params.minAngleForTriangulation) || !checkChieralities(X_euclidean, inliers, scene)) + isValidTrack = false; } - if (trackIdExists) + + // -- Add the tringulated point to the scene + if (isValidTrack) { - // 3D point triangulated before, only add image observation if needed -#pragma omp critical - { - Landmark& landmark = scene.getLandmarks().at(trackId); - if (landmark.observations.count(I) == 0) + Landmark landmark; + landmark.X = X_euclidean; + landmark.descType = track.descType; + for (const IndexT& viewId : inliers) // add inliers as observations { - const Vec2 residual = camI->residual(poseI, landmark.X.homogeneous(), xI); - // TODO: scale in residual - const auto& acThresholdIt = _map_ACThreshold.find(I); - // TODO assert(acThresholdIt != _map_ACThreshold.end()); - const double acThreshold = (acThresholdIt != _map_ACThreshold.end()) ? acThresholdIt->second : 4.0; - if (poseI.depth(landmark.X) > 0 && residual.norm() < std::max(4.0, acThreshold)) - { - const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featI.scale(); - landmark.observations[I] = Observation(xI, track.featPerView.at(I), scale); - ++extented_track; - } + const Vec2 x = _featuresPerView->getFeatures(viewId, track.descType)[track.featPerView.at(viewId)].coords().cast(); + const feature::PointFeature& p = _featuresPerView->getFeatures(viewId, track.descType)[track.featPerView.at(viewId)]; + const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : p.scale(); + landmark.observations[viewId] = Observation(x, track.featPerView.at(viewId), scale); } - if (landmark.observations.count(J) == 0) +#pragma omp critical { - const Vec2 residual = camJ->residual(poseJ, landmark.X.homogeneous(), xJ); - const auto& acThresholdIt = _map_ACThreshold.find(J); - // TODO assert(acThresholdIt != _map_ACThreshold.end()); - const double acThreshold = (acThresholdIt != _map_ACThreshold.end()) ? acThresholdIt->second : 4.0; - if (poseJ.depth(landmark.X) > 0 && residual.norm() < std::max(4.0, acThreshold)) - { - const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featJ.scale(); - landmark.observations[J] = Observation(xJ, track.featPerView.at(J), scale); - ++extented_track; - } + scene.getLandmarks()[trackId] = landmark; } - } } else { - // A new 3D point must be added -#pragma omp critical - { - ++new_putative_track; - } - - Vec3 X_euclidean = Vec3::Zero(); - const Vec2 xI_ud = camI->get_ud_pixel(xI); - const Vec2 xJ_ud = camJ->get_ud_pixel(xJ); - const Mat34 pI = camIPinHole->getProjectiveEquivalent(poseI); - const Mat34 pJ = camJPinHole->getProjectiveEquivalent(poseJ); - - multiview::TriangulateDLT(pI, xI_ud, pJ, xJ_ud, X_euclidean); - - // Check triangulation results - // - Check angle (small angle leads imprecise triangulation) - // - Check positive depth - // - Check residual values - const double angle = angleBetweenRays(poseI, camI.get(), poseJ, camJ.get(), xI, xJ); - const Vec2 residualI = camI->residual(poseI, X_euclidean.homogeneous(), xI); - const Vec2 residualJ = camJ->residual(poseJ, X_euclidean.homogeneous(), xJ); - - // TODO assert(acThresholdIt != _map_ACThreshold.end()); - - const auto& acThresholdItI = _map_ACThreshold.find(I); - const auto& acThresholdItJ = _map_ACThreshold.find(J); - - const double& acThresholdI = (acThresholdItI != _map_ACThreshold.end()) ? acThresholdItI->second : 4.0; - const double& acThresholdJ = (acThresholdItJ != _map_ACThreshold.end()) ? acThresholdItJ->second : 4.0; - - if (angle > _params.minAngleForTriangulation && - poseI.depth(X_euclidean) > 0 && - poseJ.depth(X_euclidean) > 0 && - residualI.norm() < acThresholdI && - residualJ.norm() < acThresholdJ) - { #pragma omp critical { - // Add a new track - Landmark & landmark = scene.getLandmarks()[trackId]; - landmark.X = X_euclidean; - landmark.descType = track.descType; - - const double scaleI = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featI.scale(); - const double scaleJ = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featJ.scale(); - landmark.observations[I] = Observation(xI, track.featPerView.at(I), scaleI); - landmark.observations[J] = Observation(xJ, track.featPerView.at(J), scaleJ); - - ++new_added_track; - } // critical - } // 3D point is valid - } // else (New 3D point) - }// for all correspondences + if (scene.getLandmarks().find(trackId) != scene.getLandmarks().end()) + scene.getLandmarks().erase(trackId); + } + } + } // for all shared tracks +} + +void ReconstructionEngine_sequentialSfM::triangulate_2Views(SfMData& scene, + const std::set& previousReconstructedViews, + const std::set& newReconstructedViews) +{ + { + std::vector intersection; + std::set_intersection(newReconstructedViews.begin(), + newReconstructedViews.end(), + previousReconstructedViews.begin(), + previousReconstructedViews.end(), + std::back_inserter(intersection)); + + assert(intersection.empty()); } -// #pragma omp critical -// if (!map_tracksCommonIJ.empty()) -// { -// ALICEVISION_LOG_DEBUG("--Triangulated 3D points [" << I << "-" << J << "]:\n" -// "\t#Track extented: " << extented_track << "\n" -// "\t#Validated/#Possible: " << new_added_track << "/" << new_putative_track << "\n" -// "\t#3DPoint for the entire scene: " << scene.getLandmarks().size()); -// } - } + std::set allReconstructedViews; + allReconstructedViews.insert(previousReconstructedViews.begin(), previousReconstructedViews.end()); + allReconstructedViews.insert(newReconstructedViews.begin(), newReconstructedViews.end()); + +#pragma omp parallel for schedule(dynamic) + for (ptrdiff_t i = 0; i < static_cast(allReconstructedViews.size()); ++i) + { + std::set::const_iterator iter = allReconstructedViews.begin(); + std::advance(iter, i); + const IndexT indexAll = *iter; + + for (IndexT indexNew : newReconstructedViews) + { + if (indexAll == indexNew) + continue; + + const std::size_t I = std::min((IndexT)indexNew, indexAll); + const std::size_t J = std::max((IndexT)indexNew, indexAll); + + // Find track correspondences between I and J + const std::set set_viewIndex = {I, J}; + track::TracksMap map_tracksCommonIJ; + track::getCommonTracksInImagesFast(set_viewIndex, _map_tracks, _map_tracksPerView, map_tracksCommonIJ); + + const View* viewI = scene.getViews().at(I).get(); + const View* viewJ = scene.getViews().at(J).get(); + + std::shared_ptr camI = scene.getIntrinsics().at(viewI->getIntrinsicId()); + std::shared_ptr camIPinHole = std::dynamic_pointer_cast(camI); + if (!camIPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate_multiViewsLORANSAC"); + continue; + } + + std::shared_ptr camJ = scene.getIntrinsics().at(viewJ->getIntrinsicId()); + std::shared_ptr camJPinHole = std::dynamic_pointer_cast(camJ); + if (!camJPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate_multiViewsLORANSAC"); + continue; + } + + const Pose3 poseI = scene.getPose(*viewI).getTransform(); + const Pose3 poseJ = scene.getPose(*viewJ).getTransform(); + + std::size_t new_putative_track = 0, new_added_track = 0, extented_track = 0; + for (const std::pair& trackIt : map_tracksCommonIJ) + { + const std::size_t trackId = trackIt.first; + const track::Track& track = trackIt.second; + + const Vec2 xI = _featuresPerView->getFeatures(I, track.descType)[track.featPerView.at(I)].coords().cast(); + const Vec2 xJ = _featuresPerView->getFeatures(J, track.descType)[track.featPerView.at(J)].coords().cast(); + + const feature::PointFeature& featI = _featuresPerView->getFeatures(I, track.descType)[track.featPerView.at(I)]; + const feature::PointFeature& featJ = _featuresPerView->getFeatures(J, track.descType)[track.featPerView.at(J)]; + // test if the track already exists in 3D + bool trackIdExists; +#pragma omp critical + { + trackIdExists = scene.getLandmarks().find(trackId) != scene.getLandmarks().end(); + } + if (trackIdExists) + { + // 3D point triangulated before, only add image observation if needed +#pragma omp critical + { + Landmark& landmark = scene.getLandmarks().at(trackId); + if (landmark.observations.count(I) == 0) + { + const Vec2 residual = camI->residual(poseI, landmark.X.homogeneous(), xI); + // TODO: scale in residual + const auto& acThresholdIt = _map_ACThreshold.find(I); + // TODO assert(acThresholdIt != _map_ACThreshold.end()); + const double acThreshold = (acThresholdIt != _map_ACThreshold.end()) ? acThresholdIt->second : 4.0; + if (poseI.depth(landmark.X) > 0 && residual.norm() < std::max(4.0, acThreshold)) + { + const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featI.scale(); + landmark.observations[I] = Observation(xI, track.featPerView.at(I), scale); + ++extented_track; + } + } + if (landmark.observations.count(J) == 0) + { + const Vec2 residual = camJ->residual(poseJ, landmark.X.homogeneous(), xJ); + const auto& acThresholdIt = _map_ACThreshold.find(J); + // TODO assert(acThresholdIt != _map_ACThreshold.end()); + const double acThreshold = (acThresholdIt != _map_ACThreshold.end()) ? acThresholdIt->second : 4.0; + if (poseJ.depth(landmark.X) > 0 && residual.norm() < std::max(4.0, acThreshold)) + { + const double scale = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featJ.scale(); + landmark.observations[J] = Observation(xJ, track.featPerView.at(J), scale); + ++extented_track; + } + } + } + } + else + { + // A new 3D point must be added +#pragma omp critical + { + ++new_putative_track; + } + + Vec3 X_euclidean = Vec3::Zero(); + const Vec2 xI_ud = camI->get_ud_pixel(xI); + const Vec2 xJ_ud = camJ->get_ud_pixel(xJ); + const Mat34 pI = camIPinHole->getProjectiveEquivalent(poseI); + const Mat34 pJ = camJPinHole->getProjectiveEquivalent(poseJ); + + multiview::TriangulateDLT(pI, xI_ud, pJ, xJ_ud, X_euclidean); + + // Check triangulation results + // - Check angle (small angle leads imprecise triangulation) + // - Check positive depth + // - Check residual values + const double angle = angleBetweenRays(poseI, camI.get(), poseJ, camJ.get(), xI, xJ); + const Vec2 residualI = camI->residual(poseI, X_euclidean.homogeneous(), xI); + const Vec2 residualJ = camJ->residual(poseJ, X_euclidean.homogeneous(), xJ); + + // TODO assert(acThresholdIt != _map_ACThreshold.end()); + + const auto& acThresholdItI = _map_ACThreshold.find(I); + const auto& acThresholdItJ = _map_ACThreshold.find(J); + + const double& acThresholdI = (acThresholdItI != _map_ACThreshold.end()) ? acThresholdItI->second : 4.0; + const double& acThresholdJ = (acThresholdItJ != _map_ACThreshold.end()) ? acThresholdItJ->second : 4.0; + + if (angle > _params.minAngleForTriangulation && poseI.depth(X_euclidean) > 0 && poseJ.depth(X_euclidean) > 0 && + residualI.norm() < acThresholdI && residualJ.norm() < acThresholdJ) + { +#pragma omp critical + { + // Add a new track + Landmark& landmark = scene.getLandmarks()[trackId]; + landmark.X = X_euclidean; + landmark.descType = track.descType; + + const double scaleI = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featI.scale(); + const double scaleJ = (_params.featureConstraint == EFeatureConstraint::BASIC) ? 0.0 : featJ.scale(); + landmark.observations[I] = Observation(xI, track.featPerView.at(I), scaleI); + landmark.observations[J] = Observation(xJ, track.featPerView.at(J), scaleJ); + + ++new_added_track; + } // critical + } // 3D point is valid + } // else (New 3D point) + } // for all correspondences + } + + // #pragma omp critical + // if (!map_tracksCommonIJ.empty()) + // { + // ALICEVISION_LOG_DEBUG("--Triangulated 3D points [" << I << "-" << J << "]:\n" + // "\t#Track extented: " << extented_track << "\n" + // "\t#Validated/#Possible: " << new_added_track << "/" << new_putative_track << "\n" + // "\t#3DPoint for the entire scene: " << scene.getLandmarks().size()); + // } + } } std::size_t ReconstructionEngine_sequentialSfM::removeOutliers() { - const std::size_t nbOutliersResidualErr = RemoveOutliers_PixelResidualError(_sfmData, _params.featureConstraint, _params.maxReprojectionError, 2); - const std::size_t nbOutliersAngleErr = RemoveOutliers_AngleError(_sfmData, _params.minAngleForLandmark); + const std::size_t nbOutliersResidualErr = RemoveOutliers_PixelResidualError(_sfmData, _params.featureConstraint, _params.maxReprojectionError, 2); + const std::size_t nbOutliersAngleErr = RemoveOutliers_AngleError(_sfmData, _params.minAngleForLandmark); - ALICEVISION_LOG_INFO("Remove outliers: " << std::endl - << "\t- # outliers residual error: " << nbOutliersResidualErr << std::endl - << "\t- # outliers angular error: " << nbOutliersAngleErr); + ALICEVISION_LOG_INFO("Remove outliers: " << std::endl + << "\t- # outliers residual error: " << nbOutliersResidualErr << std::endl + << "\t- # outliers angular error: " << nbOutliersAngleErr); - return nbOutliersResidualErr + nbOutliersAngleErr; + return nbOutliersResidualErr + nbOutliersAngleErr; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.hpp b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.hpp index fa8989095a..483fd11d8b 100644 --- a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.hpp +++ b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.hpp @@ -35,380 +35,363 @@ typedef std::tuple ViewConnectionScore; */ class ReconstructionEngine_sequentialSfM : public ReconstructionEngine { -public: - struct Params - { - Pair userInitialImagePair = { UndefinedIndexT, UndefinedIndexT }; - int minInputTrackLength = 2; - int minTrackLength = 2; - int minPointsPerPose = 30; - bool useLocalBundleAdjustment = false; - int localBundelAdjustementGraphDistanceLimit = 1; - - /// Dump current status of the scene every 3 resections - bool logIntermediateSteps = false; - - RigParams rig; - - /// Has fixed Intrinsics - bool lockAllIntrinsics = false; - int minNbCamerasToRefinePrincipalPoint = 3; - - /// minimum number of obersvations to triangulate a 3d point. - std::size_t minNbObservationsForTriangulation = 2; - /// a 3D point must have at least 2 obervations not too much aligned. - double minAngleForTriangulation = 3.0; - double minAngleForLandmark = 2.0; - double maxReprojectionError = 4.0; - EFeatureConstraint featureConstraint = EFeatureConstraint::BASIC; - float minAngleInitialPair = 5.0f; - float maxAngleInitialPair = 40.0f; - bool filterTrackForks = true; - robustEstimation::ERobustEstimator localizerEstimator = robustEstimation::ERobustEstimator::ACRANSAC; - double localizerEstimatorError = std::numeric_limits::infinity(); - std::size_t localizerEstimatorMaxIterations = 4096; + public: + struct Params + { + Pair userInitialImagePair = {UndefinedIndexT, UndefinedIndexT}; + int minInputTrackLength = 2; + int minTrackLength = 2; + int minPointsPerPose = 30; + bool useLocalBundleAdjustment = false; + int localBundelAdjustementGraphDistanceLimit = 1; + + /// Dump current status of the scene every 3 resections + bool logIntermediateSteps = false; + + RigParams rig; + + /// Has fixed Intrinsics + bool lockAllIntrinsics = false; + int minNbCamerasToRefinePrincipalPoint = 3; + + /// minimum number of obersvations to triangulate a 3d point. + std::size_t minNbObservationsForTriangulation = 2; + /// a 3D point must have at least 2 obervations not too much aligned. + double minAngleForTriangulation = 3.0; + double minAngleForLandmark = 2.0; + double maxReprojectionError = 4.0; + EFeatureConstraint featureConstraint = EFeatureConstraint::BASIC; + float minAngleInitialPair = 5.0f; + float maxAngleInitialPair = 40.0f; + bool filterTrackForks = true; + robustEstimation::ERobustEstimator localizerEstimator = robustEstimation::ERobustEstimator::ACRANSAC; + double localizerEstimatorError = std::numeric_limits::infinity(); + std::size_t localizerEstimatorMaxIterations = 4096; + + // Pyramid scoring + + const int pyramidBase = 2; + const int pyramidDepth = 5; + + // Bundle Adjustment triggering + + /// The beginning of the incremental SfM is a well known risky and + /// unstable step which has a big impact on the final result. + /// The Bundle Adjustment is an intensive computing step so we only use it + /// every N cameras. + /// We make an exception for the first 'nbFirstUnstableCameras' cameras + /// and perform a BA for each camera because it makes the results + /// more stable and it's quite cheap because we have few data. + std::size_t nbFirstUnstableCameras = 30; + + /// Limit to a maximum number of cameras added to ensure that + /// we don't add too much data in one step without bundle adjustment. + std::size_t maxImagesPerGroup = 30; + + /// Threshold for the maximum number of outliers allowed at the end of a BA iteration. + /// If the limit is not met, another BA iteration is performed. + /// Using a negative value for this threshold will disable BA iterations. + int bundleAdjustmentMaxOutliers = 50; + + // Local Bundle Adjustment data + + /// The minimum number of shared matches to create an edge between two views (nodes) + const std::size_t kMinNbOfMatches = 50; + + // Intermediate reconstructions + /// extension of the intermediate reconstruction files + std::string sfmStepFileExtension = ".ply"; + /// filter for the intermediate reconstruction files + sfmDataIO::ESfMData sfmStepFilter = + sfmDataIO::ESfMData(sfmDataIO::VIEWS | sfmDataIO::EXTRINSICS | sfmDataIO::INTRINSICS | sfmDataIO::STRUCTURE | sfmDataIO::OBSERVATIONS); + }; + + public: + ReconstructionEngine_sequentialSfM(const sfmData::SfMData& sfmData, + const Params& params, + const std::string& outputFolder, + const std::string& loggingFile = ""); + + void setFeatures(feature::FeaturesPerView* featuresPerView) { _featuresPerView = featuresPerView; } + + void setMatches(matching::PairwiseMatches* pairwiseMatches) { _pairwiseMatches = pairwiseMatches; } + + /** + * @brief Process the entire incremental reconstruction + * @return true if done + */ + virtual bool process(); + + /** + * @brief Initialize pyramid scoring + */ + void initializePyramidScoring(); + + /** + * @brief Initialize tracks + * @return number of traks + */ + std::size_t fuseMatchesIntoTracks(); + + /** + * @brief Get all initial pair candidates + * @return pair list + */ + std::vector getInitialImagePairsCandidates(); + + /** + * @brief Try all initial pair candidates in order to create an initial reconstruction + * @param initialPairCandidate The list of all initial pair candidates + */ + void createInitialReconstruction(const std::vector& initialImagePairCandidates); + + /** + * @brief If we have already reconstructed landmarks in a previous reconstruction, + * we need to recognize the corresponding tracks and update the landmarkIds accordingly. + */ + void remapLandmarkIdsToTrackIds(); + + /** + * @brief Loop of reconstruction updates + * @return the duration of the incremental reconstruction + */ + double incrementalReconstruction(); + + /** + * @brief Update the reconstruction with a new resection group of images + * @param[in] resectionId The resection id + * @param[in] bestViewIds The best remaining view ids + * @param[in] prevReconstructedViews The previously reconstructed view ids + * @return new reconstructed view ids + */ + std::set resection(IndexT resectionId, const std::vector& bestViewIds, const std::set& prevReconstructedViews); + + /** + * @brief triangulate + * @param[in] prevReconstructedViews The previously reconstructed view ids + * @param[in] newReconstructedViews The newly reconstructed view ids + */ + void triangulate(const std::set& prevReconstructedViews, const std::set& newReconstructedViews); + + /** + * @brief bundleAdjustment + * @param[in,out] newReconstructedViews The newly reconstructed view ids + * @param[in] isInitialPair If true use fixed intrinsics an no nbOutliersThreshold + * @return true if the bundle adjustment solution is usable + */ + bool bundleAdjustment(std::set& newReconstructedViews, bool isInitialPair = false); + + /** + * @brief Export and print statistics of a complete reconstruction + * @param[in] reconstructionTime The duration of the reconstruction + */ + void exportStatistics(double reconstructionTime); + + /** + * @brief calibrateRigs + * @param[in,out] updatedViews add the updated view ids to the list + * @return updatedViews view ids + */ + void calibrateRigs(std::set& updatedViews); + + /** + * @brief Return all the images containing matches with already reconstructed 3D points. + * The images are sorted by a score based on the number of features id shared with + * the reconstruction and the repartition of these points in the image. + * + * @param[out] out_connectedViews: output list of view IDs connected with the 3D reconstruction. + * @param[in] remainingViewIds: input list of remaining view IDs in which we will search for connected views. + * @return False if there is no view connected. + */ + bool findConnectedViews(std::vector& out_connectedViews, const std::set& remainingViewIds) const; + + /** + * @brief Estimate the best images on which we can compute the resectioning safely. + * The images are sorted by a score based on the number of features id shared with + * the reconstruction and the repartition of these points in the image. + * + * @param[out] out_selectedViewIds: output list of view IDs we can use for resectioning. + * @param[in] remainingViewIds: input list of remaining view IDs in which we will search for the best ones for resectioning. + * @return False if there is no possible resection. + */ + bool findNextBestViews(std::vector& out_selectedViewIds, const std::set& remainingViewIds) const; + + private: + struct ResectionData : ImageLocalizerMatchData + { + /// tracks index for resection + std::set tracksId; + /// features index for resection + std::vector featuresId; + /// pose estimated by the resection + geometry::Pose3 pose; + }; + + /** + * @brief Compute the initial 3D seed (First camera t=0; R=Id, second estimated by 5 point algorithm) + * @param[in] initialPair + * @return + */ + bool makeInitialPair3D(const Pair& initialPair); + + /** + * @brief Automatic initial pair selection (based on a 'baseline' computation score) + * @param[out] out_bestImagePairs + * @param[in] filterViewId If defined, each output pairs must contain filterViewId + * @return + */ + bool getBestInitialImagePairs(std::vector& out_bestImagePairs, IndexT filterViewId = UndefinedIndexT); + + /** + * @brief Compute a score of the view for a subset of features. This is + * used for the next best view choice. + * + * The score is based on a pyramid which allows to compute a weighting + * strategy to promote a good repartition in the image (instead of relying + * only on the number of features). + * Inspired by [Schonberger 2016]: + * "Structure-from-Motion Revisited", Johannes L. Schonberger, Jan-Michael Frahm + * + * http://people.inf.ethz.ch/jschoenb/papers/schoenberger2016sfm.pdf + * We don't use the same weighting strategy. The weighting choice + * is not justified in the paper. + * + * @param[in] viewId: the ID of the view + * @param[in] trackIds: set of track IDs contained in viewId + * @return the computed score + */ + std::size_t computeCandidateImageScore(IndexT viewId, const std::vector& trackIds) const; + + /** + * @brief Apply the resection on a single view. + * @param[in] viewIndex: image index to add to the reconstruction. + * @param[out] resectionData: contains the result (P) and all the data used during the resection. + * @return false if resection failed + */ + bool computeResection(const IndexT viewIndex, ResectionData& resectionData); + + /** + * @brief Update the global scene with the new found camera pose, intrinsic (if not defined) and + * Update its observations into the global scene structure. + * @param[in] viewIndex: image index added to the reconstruction. + * @param[in] resectionData: contains the camera pose and all data used during the resection. + */ + void updateScene(const IndexT viewIndex, const ResectionData& resectionData); + + /** + * @brief Triangulate new possible 2D tracks + * List tracks that share content with this view and add observations and new 3D track if required. + * @param previousReconstructedViews + * @param newReconstructedViews + */ + void triangulate_2Views(sfmData::SfMData& scene, + const std::set& previousReconstructedViews, + const std::set& newReconstructedViews); + + /** + * @brief Triangulate new possible 2D tracks + * List tracks that share content with this view and run a multiview triangulation on them, using the Lo-RANSAC algorithm. + * @param[in,out] scene All the data about the 3D reconstruction. + * @param[in] previousReconstructedViews The list of the old reconstructed views (views index). + * @param[in] newReconstructedViews The list of the new reconstructed views (views index). + */ + void triangulate_multiViewsLORANSAC(sfmData::SfMData& scene, + const std::set& previousReconstructedViews, + const std::set& newReconstructedViews); + + /** + * @brief Check if a 3D points is well located in front of a set of views. + * @param[in] pt3D A 3D point (euclidian coordinates) + * @param[in] viewsId A set of views index + * @param[in] scene All the data about the 3D reconstruction. + * @return false if the 3D points is located behind one view (or more), else \c true. + */ + bool checkChieralities(const Vec3& pt3D, const std::set& viewsId, const sfmData::SfMData& scene); + + /** + * @brief Check if the maximal angle formed by a 3D points and 2 views exceeds a min. angle, among a set of views. + * @param[in] pt3D A 3D point (euclidian coordinates) + * @param[in] viewsId A set of views index + * @param[in] scene All the data about the 3D reconstruction. + * @param[in] kMinAngle The angle limit. + * @return false if the maximal angle does not exceed the limit, else \c true. + */ + bool checkAngles(const Vec3& pt3D, const std::set& viewsId, const sfmData::SfMData& scene, const double& kMinAngle); + + /** + * @brief Select the candidate tracks for the next triangulation step. + * @details A track is considered as triangulable if it is visible by at least one new reconsutructed + * view and at least \c _minNbObservationsForTriangulation (new and previous) reconstructed view. + * @param[in] previousReconstructedViews The old reconstructed views. + * @param[in] newReconstructedViews The newly reconstructed views. + * @param[out] mapTracksToTriangulate A map with the tracks to triangulate and the observations to do it. + */ + void getTracksToTriangulate(const std::set& previousReconstructedViews, + const std::set& newReconstructedViews, + std::map>& mapTracksToTriangulate) const; + + /** + * @brief Loop over the reconstructed views, and for each landmark of the reconstructed views, + * loop over their tracks to detect which views may have new information using this newly reconstructed views + * + * @param linkedViews output non renconstructed views + * @param newReconstructedViews a list of reconstructed views to analyse + */ + void registerChanges(std::set& linkedViews, const std::set& newReconstructedViews); + + /** + * @brief Remove observation/tracks that have: + * - too large residual error + * - too small angular value + * + * @param[in] precision + * @return number of removed outliers + */ + std::size_t removeOutliers(); + + private: + // Parameters + Params _params; + + // Data providers + + feature::FeaturesPerView* _featuresPerView; + matching::PairwiseMatches* _pairwiseMatches; // Pyramid scoring - const int pyramidBase = 2; - const int pyramidDepth = 5; + /// internal cache of precomputed values for the weighting of the pyramid levels + std::vector _pyramidWeights; + int _pyramidThreshold; - // Bundle Adjustment triggering + // Temporary data - /// The beginning of the incremental SfM is a well known risky and - /// unstable step which has a big impact on the final result. - /// The Bundle Adjustment is an intensive computing step so we only use it - /// every N cameras. - /// We make an exception for the first 'nbFirstUnstableCameras' cameras - /// and perform a BA for each camera because it makes the results - /// more stable and it's quite cheap because we have few data. - std::size_t nbFirstUnstableCameras = 30; + /// Putative landmark tracks (visibility per potential 3D point) + track::TracksMap _map_tracks; + /// Putative tracks per view + track::TracksPerView _map_tracksPerView; + /// Precomputed pyramid index for each trackId of each viewId. + track::TracksPyramidPerView _map_featsPyramidPerView; + /// Per camera confidence (A contrario estimated threshold error) + HashMap _map_ACThreshold; - /// Limit to a maximum number of cameras added to ensure that - /// we don't add too much data in one step without bundle adjustment. - std::size_t maxImagesPerGroup = 30; + // Local Bundle Adjustment data - /// Threshold for the maximum number of outliers allowed at the end of a BA iteration. - /// If the limit is not met, another BA iteration is performed. - /// Using a negative value for this threshold will disable BA iterations. - int bundleAdjustmentMaxOutliers = 50; + /// Contains all the data used by the Local BA approach + std::shared_ptr _localStrategyGraph; - // Local Bundle Adjustment data + // Log - /// The minimum number of shared matches to create an edge between two views (nodes) - const std::size_t kMinNbOfMatches = 50; - - // Intermediate reconstructions - /// extension of the intermediate reconstruction files - std::string sfmStepFileExtension = ".ply"; - /// filter for the intermediate reconstruction files - sfmDataIO::ESfMData sfmStepFilter = sfmDataIO::ESfMData( - sfmDataIO::VIEWS | - sfmDataIO::EXTRINSICS | - sfmDataIO::INTRINSICS | - sfmDataIO::STRUCTURE | - sfmDataIO::OBSERVATIONS); - }; - -public: - - ReconstructionEngine_sequentialSfM(const sfmData::SfMData& sfmData, - const Params& params, - const std::string& outputFolder, - const std::string& loggingFile = ""); - - void setFeatures(feature::FeaturesPerView* featuresPerView) - { - _featuresPerView = featuresPerView; - } - - void setMatches(matching::PairwiseMatches* pairwiseMatches) - { - _pairwiseMatches = pairwiseMatches; - } - - /** - * @brief Process the entire incremental reconstruction - * @return true if done - */ - virtual bool process(); - - /** - * @brief Initialize pyramid scoring - */ - void initializePyramidScoring(); - - /** - * @brief Initialize tracks - * @return number of traks - */ - std::size_t fuseMatchesIntoTracks(); - - /** - * @brief Get all initial pair candidates - * @return pair list - */ - std::vector getInitialImagePairsCandidates(); - - /** - * @brief Try all initial pair candidates in order to create an initial reconstruction - * @param initialPairCandidate The list of all initial pair candidates - */ - void createInitialReconstruction(const std::vector& initialImagePairCandidates); - - /** - * @brief If we have already reconstructed landmarks in a previous reconstruction, - * we need to recognize the corresponding tracks and update the landmarkIds accordingly. - */ - void remapLandmarkIdsToTrackIds(); - - /** - * @brief Loop of reconstruction updates - * @return the duration of the incremental reconstruction - */ - double incrementalReconstruction(); - - /** - * @brief Update the reconstruction with a new resection group of images - * @param[in] resectionId The resection id - * @param[in] bestViewIds The best remaining view ids - * @param[in] prevReconstructedViews The previously reconstructed view ids - * @return new reconstructed view ids - */ - std::set resection(IndexT resectionId, - const std::vector& bestViewIds, - const std::set& prevReconstructedViews); - - /** - * @brief triangulate - * @param[in] prevReconstructedViews The previously reconstructed view ids - * @param[in] newReconstructedViews The newly reconstructed view ids - */ - void triangulate(const std::set& prevReconstructedViews, - const std::set& newReconstructedViews); - - /** - * @brief bundleAdjustment - * @param[in,out] newReconstructedViews The newly reconstructed view ids - * @param[in] isInitialPair If true use fixed intrinsics an no nbOutliersThreshold - * @return true if the bundle adjustment solution is usable - */ - bool bundleAdjustment(std::set& newReconstructedViews, bool isInitialPair = false); - - /** - * @brief Export and print statistics of a complete reconstruction - * @param[in] reconstructionTime The duration of the reconstruction - */ - void exportStatistics(double reconstructionTime); - - - /** - * @brief calibrateRigs - * @param[in,out] updatedViews add the updated view ids to the list - * @return updatedViews view ids - */ - void calibrateRigs(std::set& updatedViews); - - /** - * @brief Return all the images containing matches with already reconstructed 3D points. - * The images are sorted by a score based on the number of features id shared with - * the reconstruction and the repartition of these points in the image. - * - * @param[out] out_connectedViews: output list of view IDs connected with the 3D reconstruction. - * @param[in] remainingViewIds: input list of remaining view IDs in which we will search for connected views. - * @return False if there is no view connected. - */ - bool findConnectedViews(std::vector& out_connectedViews, - const std::set& remainingViewIds) const; - - /** - * @brief Estimate the best images on which we can compute the resectioning safely. - * The images are sorted by a score based on the number of features id shared with - * the reconstruction and the repartition of these points in the image. - * - * @param[out] out_selectedViewIds: output list of view IDs we can use for resectioning. - * @param[in] remainingViewIds: input list of remaining view IDs in which we will search for the best ones for resectioning. - * @return False if there is no possible resection. - */ - bool findNextBestViews(std::vector& out_selectedViewIds, - const std::set& remainingViewIds) const; - -private: - - struct ResectionData : ImageLocalizerMatchData - { - /// tracks index for resection - std::set tracksId; - /// features index for resection - std::vector featuresId; - /// pose estimated by the resection - geometry::Pose3 pose; - }; - - /** - * @brief Compute the initial 3D seed (First camera t=0; R=Id, second estimated by 5 point algorithm) - * @param[in] initialPair - * @return - */ - bool makeInitialPair3D(const Pair& initialPair); - - /** - * @brief Automatic initial pair selection (based on a 'baseline' computation score) - * @param[out] out_bestImagePairs - * @param[in] filterViewId If defined, each output pairs must contain filterViewId - * @return - */ - bool getBestInitialImagePairs(std::vector& out_bestImagePairs, IndexT filterViewId = UndefinedIndexT); - - /** - * @brief Compute a score of the view for a subset of features. This is - * used for the next best view choice. - * - * The score is based on a pyramid which allows to compute a weighting - * strategy to promote a good repartition in the image (instead of relying - * only on the number of features). - * Inspired by [Schonberger 2016]: - * "Structure-from-Motion Revisited", Johannes L. Schonberger, Jan-Michael Frahm - * - * http://people.inf.ethz.ch/jschoenb/papers/schoenberger2016sfm.pdf - * We don't use the same weighting strategy. The weighting choice - * is not justified in the paper. - * - * @param[in] viewId: the ID of the view - * @param[in] trackIds: set of track IDs contained in viewId - * @return the computed score - */ - std::size_t computeCandidateImageScore(IndexT viewId, const std::vector& trackIds) const; - - /** - * @brief Apply the resection on a single view. - * @param[in] viewIndex: image index to add to the reconstruction. - * @param[out] resectionData: contains the result (P) and all the data used during the resection. - * @return false if resection failed - */ - bool computeResection(const IndexT viewIndex, ResectionData& resectionData); - - /** - * @brief Update the global scene with the new found camera pose, intrinsic (if not defined) and - * Update its observations into the global scene structure. - * @param[in] viewIndex: image index added to the reconstruction. - * @param[in] resectionData: contains the camera pose and all data used during the resection. - */ - void updateScene(const IndexT viewIndex, const ResectionData& resectionData); - - /** - * @brief Triangulate new possible 2D tracks - * List tracks that share content with this view and add observations and new 3D track if required. - * @param previousReconstructedViews - * @param newReconstructedViews - */ - void triangulate_2Views(sfmData::SfMData& scene, const std::set& previousReconstructedViews, const std::set& newReconstructedViews); - - /** - * @brief Triangulate new possible 2D tracks - * List tracks that share content with this view and run a multiview triangulation on them, using the Lo-RANSAC algorithm. - * @param[in,out] scene All the data about the 3D reconstruction. - * @param[in] previousReconstructedViews The list of the old reconstructed views (views index). - * @param[in] newReconstructedViews The list of the new reconstructed views (views index). - */ - void triangulate_multiViewsLORANSAC(sfmData::SfMData& scene, const std::set& previousReconstructedViews, const std::set& newReconstructedViews); - - /** - * @brief Check if a 3D points is well located in front of a set of views. - * @param[in] pt3D A 3D point (euclidian coordinates) - * @param[in] viewsId A set of views index - * @param[in] scene All the data about the 3D reconstruction. - * @return false if the 3D points is located behind one view (or more), else \c true. - */ - bool checkChieralities(const Vec3& pt3D, const std::set& viewsId, const sfmData::SfMData& scene); - - /** - * @brief Check if the maximal angle formed by a 3D points and 2 views exceeds a min. angle, among a set of views. - * @param[in] pt3D A 3D point (euclidian coordinates) - * @param[in] viewsId A set of views index - * @param[in] scene All the data about the 3D reconstruction. - * @param[in] kMinAngle The angle limit. - * @return false if the maximal angle does not exceed the limit, else \c true. - */ - bool checkAngles(const Vec3& pt3D, const std::set& viewsId, const sfmData::SfMData& scene, const double& kMinAngle); - - /** - * @brief Select the candidate tracks for the next triangulation step. - * @details A track is considered as triangulable if it is visible by at least one new reconsutructed - * view and at least \c _minNbObservationsForTriangulation (new and previous) reconstructed view. - * @param[in] previousReconstructedViews The old reconstructed views. - * @param[in] newReconstructedViews The newly reconstructed views. - * @param[out] mapTracksToTriangulate A map with the tracks to triangulate and the observations to do it. - */ - void getTracksToTriangulate( - const std::set& previousReconstructedViews, - const std::set& newReconstructedViews, - std::map > & mapTracksToTriangulate) const; - - /** - * @brief Loop over the reconstructed views, and for each landmark of the reconstructed views, - * loop over their tracks to detect which views may have new information using this newly reconstructed views - * - * @param linkedViews output non renconstructed views - * @param newReconstructedViews a list of reconstructed views to analyse - */ - void registerChanges(std::set& linkedViews, const std::set& newReconstructedViews); - - /** - * @brief Remove observation/tracks that have: - * - too large residual error - * - too small angular value - * - * @param[in] precision - * @return number of removed outliers - */ - std::size_t removeOutliers(); - -private: - - // Parameters - Params _params; - - // Data providers - - feature::FeaturesPerView* _featuresPerView; - matching::PairwiseMatches* _pairwiseMatches; - - // Pyramid scoring - - /// internal cache of precomputed values for the weighting of the pyramid levels - std::vector _pyramidWeights; - int _pyramidThreshold; - - // Temporary data - - /// Putative landmark tracks (visibility per potential 3D point) - track::TracksMap _map_tracks; - /// Putative tracks per view - track::TracksPerView _map_tracksPerView; - /// Precomputed pyramid index for each trackId of each viewId. - track::TracksPyramidPerView _map_featsPyramidPerView; - /// Per camera confidence (A contrario estimated threshold error) - HashMap _map_ACThreshold; - - // Local Bundle Adjustment data - - /// Contains all the data used by the Local BA approach - std::shared_ptr _localStrategyGraph; - - // Log - - /// sfm intermediate reconstruction files - const std::string _sfmStepFolder; - - /// HTML logger - std::shared_ptr _htmlDocStream; - /// HTML log file - std::string _htmlLogFile; - /// property tree for json stats export - pt::ptree _jsonLogTree; -}; + /// sfm intermediate reconstruction files + const std::string _sfmStepFolder; -} // namespace sfm -} // namespace aliceVision + /// HTML logger + std::shared_ptr _htmlDocStream; + /// HTML log file + std::string _htmlLogFile; + /// property tree for json stats export + pt::ptree _jsonLogTree; +}; +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/sequential/sequentialSfM_test.cpp b/src/aliceVision/sfm/pipeline/sequential/sequentialSfM_test.cpp index 1f1a52758b..c7ee7f2928 100644 --- a/src/aliceVision/sfm/pipeline/sequential/sequentialSfM_test.cpp +++ b/src/aliceVision/sfm/pipeline/sequential/sequentialSfM_test.cpp @@ -37,161 +37,146 @@ using namespace aliceVision::sfmData; // Test a scene where all the camera intrinsics are known BOOST_AUTO_TEST_CASE(SEQUENTIAL_SFM_Known_Intrinsics) { - const int nviews = 6; - const int npoints = 128; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); - - ReconstructionEngine_sequentialSfM::Params sfmParams; - sfmParams.userInitialImagePair = Pair(0, 1); - sfmParams.lockAllIntrinsics = true; - - ReconstructionEngine_sequentialSfM sfmEngine( - sfmData2, - sfmParams, - "./", - "./Reconstruction_Report.html"); - - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); - - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - - // Configure data provider (Features and Matches) - sfmEngine.setFeatures(&featuresPerView); - sfmEngine.setMatches(&pairwiseMatches); - - BOOST_CHECK (sfmEngine.process()); - - const double residual = RMSE(sfmEngine.getSfMData()); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK_LT(residual, 0.5); - BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getPoses().size(), nviews); - BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getLandmarks().size(), npoints); + const int nviews = 6; + const int npoints = 128; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); + + ReconstructionEngine_sequentialSfM::Params sfmParams; + sfmParams.userInitialImagePair = Pair(0, 1); + sfmParams.lockAllIntrinsics = true; + + ReconstructionEngine_sequentialSfM sfmEngine(sfmData2, sfmParams, "./", "./Reconstruction_Report.html"); + + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); + + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + + // Configure data provider (Features and Matches) + sfmEngine.setFeatures(&featuresPerView); + sfmEngine.setMatches(&pairwiseMatches); + + BOOST_CHECK(sfmEngine.process()); + + const double residual = RMSE(sfmEngine.getSfMData()); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK_LT(residual, 0.5); + BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getPoses().size(), nviews); + BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getLandmarks().size(), npoints); } // Test a scene where only the two first camera have known intrinsics BOOST_AUTO_TEST_CASE(SEQUENTIAL_SFM_Partially_Known_Intrinsics) { - const int nviews = 6; - const int npoints = 256; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); - - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); - - // The first two views will have valid intrinsics. - // The third one will have an invalid intrinsic (unknown focal length) - { - // Create the intrinsic with unknown focal length - sfmData2.getIntrinsics().emplace(1, camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - config._cx*2, config._cy*2, -1, -1, 0, 0)); - // The 3rd view use this invalid intrinsic - sfmData2.getViews().at(2)->setIntrinsicId(1); - } - - ReconstructionEngine_sequentialSfM::Params sfmParams; - // sfmParams.userInitialImagePair = Pair(0, 1); // test automatic selection of initial pair - sfmParams.lockAllIntrinsics = true; - - ReconstructionEngine_sequentialSfM sfmEngine( - sfmData2, - sfmParams, - "./", - "./Reconstruction_Report.html"); - - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); - - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - - // Configure data provider (Features and Matches) - sfmEngine.setFeatures(&featuresPerView); - sfmEngine.setMatches(&pairwiseMatches); - - BOOST_CHECK (sfmEngine.process()); - - const SfMData& finalSfMData = sfmEngine.getSfMData(); - const double residual = RMSE(finalSfMData); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK_LT(residual, 0.5); - BOOST_CHECK_EQUAL(nviews, finalSfMData.getPoses().size()); - BOOST_CHECK_EQUAL(npoints, finalSfMData.getLandmarks().size()); - BOOST_CHECK_NE(reinterpret_cast(finalSfMData.getIntrinsics().at(0).get())->getFocalLengthPixX(), - reinterpret_cast(finalSfMData.getIntrinsics().at(1).get())->getFocalLengthPixX()); + const int nviews = 6; + const int npoints = 256; + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nviews, npoints, config); + + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); + + // The first two views will have valid intrinsics. + // The third one will have an invalid intrinsic (unknown focal length) + { + // Create the intrinsic with unknown focal length + sfmData2.getIntrinsics().emplace(1, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, config._cx * 2, config._cy * 2, -1, -1, 0, 0)); + // The 3rd view use this invalid intrinsic + sfmData2.getViews().at(2)->setIntrinsicId(1); + } + + ReconstructionEngine_sequentialSfM::Params sfmParams; + // sfmParams.userInitialImagePair = Pair(0, 1); // test automatic selection of initial pair + sfmParams.lockAllIntrinsics = true; + + ReconstructionEngine_sequentialSfM sfmEngine(sfmData2, sfmParams, "./", "./Reconstruction_Report.html"); + + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); + + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + + // Configure data provider (Features and Matches) + sfmEngine.setFeatures(&featuresPerView); + sfmEngine.setMatches(&pairwiseMatches); + + BOOST_CHECK(sfmEngine.process()); + + const SfMData& finalSfMData = sfmEngine.getSfMData(); + const double residual = RMSE(finalSfMData); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK_LT(residual, 0.5); + BOOST_CHECK_EQUAL(nviews, finalSfMData.getPoses().size()); + BOOST_CHECK_EQUAL(npoints, finalSfMData.getLandmarks().size()); + BOOST_CHECK_NE(reinterpret_cast(finalSfMData.getIntrinsics().at(0).get())->getFocalLengthPixX(), + reinterpret_cast(finalSfMData.getIntrinsics().at(1).get())->getFocalLengthPixX()); } BOOST_AUTO_TEST_CASE(SEQUENTIAL_SFM_Known_Rig) { - const int nbPoses = 10; - const int nbPoints = 128; + const int nbPoses = 10; + const int nbPoints = 128; - const NViewDatasetConfigurator config; - const NViewDataSet d = NRealisticCamerasRing(nbPoses, nbPoints, config); + const NViewDatasetConfigurator config; + const NViewDataSet d = NRealisticCamerasRing(nbPoses, nbPoints, config); - // Translate the input dataset to a SfMData scene - const SfMData sfmData = getInputRigScene(d, config, EINTRINSIC::PINHOLE_CAMERA); + // Translate the input dataset to a SfMData scene + const SfMData sfmData = getInputRigScene(d, config, EINTRINSIC::PINHOLE_CAMERA); - // Remove poses and structure - SfMData sfmData2 = sfmData; - sfmData2.getPoses().clear(); - sfmData2.getLandmarks().clear(); + // Remove poses and structure + SfMData sfmData2 = sfmData; + sfmData2.getPoses().clear(); + sfmData2.getLandmarks().clear(); - ReconstructionEngine_sequentialSfM::Params sfmParams; - sfmParams.userInitialImagePair = Pair(0, 2); - sfmParams.lockAllIntrinsics = true; + ReconstructionEngine_sequentialSfM::Params sfmParams; + sfmParams.userInitialImagePair = Pair(0, 2); + sfmParams.lockAllIntrinsics = true; - ReconstructionEngine_sequentialSfM sfmEngine( - sfmData2, - sfmParams, - "./", - "./Reconstruction_Report.html"); + ReconstructionEngine_sequentialSfM sfmEngine(sfmData2, sfmParams, "./", "./Reconstruction_Report.html"); - // Add a tiny noise in 2D observations to make data more realistic - std::normal_distribution distribution(0.0,0.5); + // Add a tiny noise in 2D observations to make data more realistic + std::normal_distribution distribution(0.0, 0.5); - // Configure the featuresPerView & the matches_provider from the synthetic dataset - feature::FeaturesPerView featuresPerView; - generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); + // Configure the featuresPerView & the matches_provider from the synthetic dataset + feature::FeaturesPerView featuresPerView; + generateSyntheticFeatures(featuresPerView, feature::EImageDescriberType::UNKNOWN, sfmData, distribution); - matching::PairwiseMatches pairwiseMatches; - generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); + matching::PairwiseMatches pairwiseMatches; + generateSyntheticMatches(pairwiseMatches, sfmData, feature::EImageDescriberType::UNKNOWN); - // Configure data provider (Features and Matches) - sfmEngine.setFeatures(&featuresPerView); - sfmEngine.setMatches(&pairwiseMatches); + // Configure data provider (Features and Matches) + sfmEngine.setFeatures(&featuresPerView); + sfmEngine.setMatches(&pairwiseMatches); - BOOST_CHECK (sfmEngine.process()); + BOOST_CHECK(sfmEngine.process()); - const double residual = RMSE(sfmEngine.getSfMData()); - ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); - BOOST_CHECK_LT(residual, 0.5); - BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getPoses().size(), nbPoses); - BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getLandmarks().size(), nbPoints); + const double residual = RMSE(sfmEngine.getSfMData()); + ALICEVISION_LOG_DEBUG("RMSE residual: " << residual); + BOOST_CHECK_LT(residual, 0.5); + BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getPoses().size(), nbPoses); + BOOST_CHECK_EQUAL(sfmEngine.getSfMData().getLandmarks().size(), nbPoints); } - diff --git a/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.cpp b/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.cpp index 41b2b20ab5..01c3f7b046 100644 --- a/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.cpp +++ b/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.cpp @@ -28,280 +28,269 @@ using namespace aliceVision::sfmData; /// Camera pair epipole (Projection of camera center 2 in the image plane 1) inline Vec3 epipole_from_P(const Mat34& P1, const Pose3& P2) { - const Vec3 c = P2.center(); - Vec4 center; - center << c(0), c(1), c(2), 1.0; - return P1*center; + const Vec3 c = P2.center(); + Vec4 center; + center << c(0), c(1), c(2), 1.0; + return P1 * center; } /// Export point feature based vector to a matrix [(x,y)'T, (x,y)'T] /// Use the camera intrinsics in order to get undistorted pixel coordinates -template -void PointsToMat( - const IntrinsicBase * cam, - const PointFeatures & vec_feats, - MatT & m) +template +void PointsToMat(const IntrinsicBase* cam, const PointFeatures& vec_feats, MatT& m) { - m.resize(2, vec_feats.size()); - typedef typename MatT::Scalar Scalar; // Output matrix type - - size_t i = 0; - for( PointFeatures::const_iterator iter = vec_feats.begin(); - iter != vec_feats.end(); ++iter, ++i) - { - if (cam && cam->isValid()) - m.col(i) = cam->get_ud_pixel(Vec2(iter->x(), iter->y())); - else - m.col(i) << iter->x(), iter->y(); - } + m.resize(2, vec_feats.size()); + typedef typename MatT::Scalar Scalar; // Output matrix type + + size_t i = 0; + for (PointFeatures::const_iterator iter = vec_feats.begin(); iter != vec_feats.end(); ++iter, ++i) + { + if (cam && cam->isValid()) + m.col(i) = cam->get_ud_pixel(Vec2(iter->x(), iter->y())); + else + m.col(i) << iter->x(), iter->y(); + } } /// Use geometry of the views to compute a putative structure from features and descriptors. void StructureEstimationFromKnownPoses::run(SfMData& sfmData, - const PairSet& pairs, - const feature::RegionsPerView& regionsPerView, - std::mt19937 &randomNumberGenerator, - double geometricErrorMax) + const PairSet& pairs, + const feature::RegionsPerView& regionsPerView, + std::mt19937& randomNumberGenerator, + double geometricErrorMax) { - sfmData.getLandmarks().clear(); + sfmData.getLandmarks().clear(); - match(sfmData, pairs, regionsPerView, geometricErrorMax); - filter(sfmData, pairs, regionsPerView); - triangulate(sfmData, regionsPerView, randomNumberGenerator); + match(sfmData, pairs, regionsPerView, geometricErrorMax); + filter(sfmData, pairs, regionsPerView); + triangulate(sfmData, regionsPerView, randomNumberGenerator); } // #define ALICEVISION_EXHAUSTIVE_MATCHING /// Use guided matching to find corresponding 2-view correspondences void StructureEstimationFromKnownPoses::match(const SfMData& sfmData, - const PairSet& pairs, - const feature::RegionsPerView& regionsPerView, - double geometricErrorMax) + const PairSet& pairs, + const feature::RegionsPerView& regionsPerView, + double geometricErrorMax) { - auto progressDisplay = system::createConsoleProgressDisplay(pairs.size(), std::cout, - "Compute pairwise fundamental guided matching:\n" ); + auto progressDisplay = system::createConsoleProgressDisplay(pairs.size(), std::cout, "Compute pairwise fundamental guided matching:\n"); - #pragma omp parallel - for (PairSet::const_iterator it = pairs.begin(); it != pairs.end(); ++it) - { - #pragma omp single nowait +#pragma omp parallel + for (PairSet::const_iterator it = pairs.begin(); it != pairs.end(); ++it) { - // -- - // Perform GUIDED MATCHING - // -- - // Use the computed model to check valid correspondences - // - by considering geometric error and descriptor distance ratio. - - const View * viewL = sfmData.getViews().at(it->first).get(); - const Pose3 poseL = sfmData.getPose(*viewL).getTransform(); - const Intrinsics::const_iterator iterIntrinsicL = sfmData.getIntrinsics().find(viewL->getIntrinsicId()); - const View * viewR = sfmData.getViews().at(it->second).get(); - const Pose3 poseR = sfmData.getPose(*viewR).getTransform(); - const Intrinsics::const_iterator iterIntrinsicR = sfmData.getIntrinsics().find(viewR->getIntrinsicId()); - - if (sfmData.getIntrinsics().count(viewL->getIntrinsicId()) != 0 || - sfmData.getIntrinsics().count(viewR->getIntrinsicId()) != 0) - { - std::shared_ptr camL = iterIntrinsicL->second; - std::shared_ptr pinHoleCamL = std::dynamic_pointer_cast(camL); - if (!pinHoleCamL) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in match"); - } - - std::shared_ptr camR = iterIntrinsicR->second; - std::shared_ptr pinHoleCamR = std::dynamic_pointer_cast(camR); - if (!pinHoleCamL) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in match"); - } - - const Mat34 P_L = pinHoleCamL->getProjectiveEquivalent(poseL); - const Mat34 P_R = pinHoleCamR->getProjectiveEquivalent(poseR); - - const Mat3 F_lr = F_from_P(P_L, P_R); - std::vector commonDescTypes = regionsPerView.getCommonDescTypes(*it); - - matching::MatchesPerDescType allImagePairMatches; - for(feature::EImageDescriberType descType: commonDescTypes) - { - std::vector matches; +#pragma omp single nowait + { + // -- + // Perform GUIDED MATCHING + // -- + // Use the computed model to check valid correspondences + // - by considering geometric error and descriptor distance ratio. + + const View* viewL = sfmData.getViews().at(it->first).get(); + const Pose3 poseL = sfmData.getPose(*viewL).getTransform(); + const Intrinsics::const_iterator iterIntrinsicL = sfmData.getIntrinsics().find(viewL->getIntrinsicId()); + const View* viewR = sfmData.getViews().at(it->second).get(); + const Pose3 poseR = sfmData.getPose(*viewR).getTransform(); + const Intrinsics::const_iterator iterIntrinsicR = sfmData.getIntrinsics().find(viewR->getIntrinsicId()); + + if (sfmData.getIntrinsics().count(viewL->getIntrinsicId()) != 0 || sfmData.getIntrinsics().count(viewR->getIntrinsicId()) != 0) + { + std::shared_ptr camL = iterIntrinsicL->second; + std::shared_ptr pinHoleCamL = std::dynamic_pointer_cast(camL); + if (!pinHoleCamL) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in match"); + } + + std::shared_ptr camR = iterIntrinsicR->second; + std::shared_ptr pinHoleCamR = std::dynamic_pointer_cast(camR); + if (!pinHoleCamL) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in match"); + } + + const Mat34 P_L = pinHoleCamL->getProjectiveEquivalent(poseL); + const Mat34 P_R = pinHoleCamR->getProjectiveEquivalent(poseR); + + const Mat3 F_lr = F_from_P(P_L, P_R); + std::vector commonDescTypes = regionsPerView.getCommonDescTypes(*it); + + matching::MatchesPerDescType allImagePairMatches; + for (feature::EImageDescriberType descType : commonDescTypes) + { + std::vector matches; #ifdef ALICEVISION_EXHAUSTIVE_MATCHING - matching::guidedMatching - - ( - F_lr, - iterIntrinsicL->second.get(), - regionsPerView.getRegions(it->first, descType), - iterIntrinsicR->second.get(), - regionsPerView.getRegions(it->second, descType), - // descType, - Square(thresholdF), Square(0.8), - matches - ); - #else - const Vec3 epipole2 = epipole_from_P(P_R, poseL); - - //const feature::Regions& regions = regionsPerView.getRegions(it->first); - matching::guidedMatchingFundamentalFast - ( - F_lr, - epipole2, - iterIntrinsicL->second.get(), - regionsPerView.getRegions(it->first, descType), - iterIntrinsicR->second.get(), - regionsPerView.getRegions(it->second, descType), - iterIntrinsicR->second->w(), iterIntrinsicR->second->h(), - //descType, - Square(geometricErrorMax), Square(0.8), - matches - ); - #endif - allImagePairMatches[descType] = matches; - } - - #pragma omp critical - { - ++progressDisplay; - _putativeMatches[*it] = allImagePairMatches; - } - } + matching::guidedMatching( + F_lr, + iterIntrinsicL->second.get(), + regionsPerView.getRegions(it->first, descType), + iterIntrinsicR->second.get(), + regionsPerView.getRegions(it->second, descType), + // descType, + Square(thresholdF), + Square(0.8), + matches); +#else + const Vec3 epipole2 = epipole_from_P(P_R, poseL); + + // const feature::Regions& regions = regionsPerView.getRegions(it->first); + matching::guidedMatchingFundamentalFast( + F_lr, + epipole2, + iterIntrinsicL->second.get(), + regionsPerView.getRegions(it->first, descType), + iterIntrinsicR->second.get(), + regionsPerView.getRegions(it->second, descType), + iterIntrinsicR->second->w(), + iterIntrinsicR->second->h(), + // descType, + Square(geometricErrorMax), + Square(0.8), + matches); +#endif + allImagePairMatches[descType] = matches; + } + +#pragma omp critical + { + ++progressDisplay; + _putativeMatches[*it] = allImagePairMatches; + } + } + } } - } } /// Filter inconsistent correspondences by using 3-view correspondences on view triplets -void StructureEstimationFromKnownPoses::filter( - const SfMData& sfmData, - const PairSet& pairs, - const feature::RegionsPerView& regionsPerView) +void StructureEstimationFromKnownPoses::filter(const SfMData& sfmData, const PairSet& pairs, const feature::RegionsPerView& regionsPerView) { - // Compute triplets - // Triangulate triplet tracks - // - keep valid one - - typedef std::vector< graph::Triplet > Triplets; - const Triplets triplets = graph::tripletListing(pairs); - - auto progressDisplay = system::createConsoleProgressDisplay(triplets.size(), std::cout, - "Per triplet tracks validation (discard spurious correspondences):\n" ); - #pragma omp parallel - for( Triplets::const_iterator it = triplets.begin(); it != triplets.end(); ++it) - { - #pragma omp single nowait - { - ++progressDisplay; + // Compute triplets + // Triangulate triplet tracks + // - keep valid one - const graph::Triplet & triplet = *it; - const IndexT I = triplet.i, J = triplet.j , K = triplet.k; + typedef std::vector Triplets; + const Triplets triplets = graph::tripletListing(pairs); - track::TracksMap map_tracksCommon; - track::TracksBuilder tracksBuilder; - { - matching::PairwiseMatches map_matchesIJK; - if (_putativeMatches.count(std::make_pair(I,J))) - map_matchesIJK.insert(*_putativeMatches.find(std::make_pair(I,J))); - - if (_putativeMatches.count(std::make_pair(I,K))) - map_matchesIJK.insert(*_putativeMatches.find(std::make_pair(I,K))); + auto progressDisplay = + system::createConsoleProgressDisplay(triplets.size(), std::cout, "Per triplet tracks validation (discard spurious correspondences):\n"); +#pragma omp parallel + for (Triplets::const_iterator it = triplets.begin(); it != triplets.end(); ++it) + { +#pragma omp single nowait + { + ++progressDisplay; - if (_putativeMatches.count(std::make_pair(J,K))) - map_matchesIJK.insert(*_putativeMatches.find(std::make_pair(J,K))); + const graph::Triplet& triplet = *it; + const IndexT I = triplet.i, J = triplet.j, K = triplet.k; - if (map_matchesIJK.size() >= 2) { - tracksBuilder.build(map_matchesIJK); - tracksBuilder.filter(true,3, false); - tracksBuilder.exportToSTL(map_tracksCommon); - } - - // Triangulate the tracks - for (track::TracksMap::const_iterator iterTracks = map_tracksCommon.begin(); - iterTracks != map_tracksCommon.end(); ++iterTracks) { - { - const track::Track & subTrack = iterTracks->second; - multiview::Triangulation trianObj; - for (auto iter = subTrack.featPerView.begin(); iter != subTrack.featPerView.end(); ++iter) + track::TracksMap map_tracksCommon; + track::TracksBuilder tracksBuilder; { - const size_t imaIndex = iter->first; - const size_t featIndex = iter->second; - const View * view = sfmData.getViews().at(imaIndex).get(); - - std::shared_ptr cam = sfmData.getIntrinsics().at(view->getIntrinsicId()); - std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); - if (!camPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in filter"); - continue; - } - - const Pose3 pose = sfmData.getPose(*view).getTransform(); - const Vec2 pt = regionsPerView.getRegions(imaIndex, subTrack.descType).GetRegionPosition(featIndex); - trianObj.add(camPinHole->getProjectiveEquivalent(pose), cam->get_ud_pixel(pt)); + matching::PairwiseMatches map_matchesIJK; + if (_putativeMatches.count(std::make_pair(I, J))) + map_matchesIJK.insert(*_putativeMatches.find(std::make_pair(I, J))); + + if (_putativeMatches.count(std::make_pair(I, K))) + map_matchesIJK.insert(*_putativeMatches.find(std::make_pair(I, K))); + + if (_putativeMatches.count(std::make_pair(J, K))) + map_matchesIJK.insert(*_putativeMatches.find(std::make_pair(J, K))); + + if (map_matchesIJK.size() >= 2) + { + tracksBuilder.build(map_matchesIJK); + tracksBuilder.filter(true, 3, false); + tracksBuilder.exportToSTL(map_tracksCommon); + } + + // Triangulate the tracks + for (track::TracksMap::const_iterator iterTracks = map_tracksCommon.begin(); iterTracks != map_tracksCommon.end(); ++iterTracks) + { + { + const track::Track& subTrack = iterTracks->second; + multiview::Triangulation trianObj; + for (auto iter = subTrack.featPerView.begin(); iter != subTrack.featPerView.end(); ++iter) + { + const size_t imaIndex = iter->first; + const size_t featIndex = iter->second; + const View* view = sfmData.getViews().at(imaIndex).get(); + + std::shared_ptr cam = sfmData.getIntrinsics().at(view->getIntrinsicId()); + std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); + if (!camPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in filter"); + continue; + } + + const Pose3 pose = sfmData.getPose(*view).getTransform(); + const Vec2 pt = regionsPerView.getRegions(imaIndex, subTrack.descType).GetRegionPosition(featIndex); + trianObj.add(camPinHole->getProjectiveEquivalent(pose), cam->get_ud_pixel(pt)); + } + const Vec3 Xs = trianObj.compute(); + if (trianObj.minDepth() > 0 && trianObj.error() / (double)trianObj.size() < 4.0) + // TODO: Add an angular check ? + { +#pragma omp critical + { + track::Track::FeatureIdPerView::const_iterator iterI, iterJ, iterK; + iterI = iterJ = iterK = subTrack.featPerView.begin(); + std::advance(iterJ, 1); + std::advance(iterK, 2); + + _tripletMatches[std::make_pair(I, J)][subTrack.descType].emplace_back(iterI->second, iterJ->second); + _tripletMatches[std::make_pair(J, K)][subTrack.descType].emplace_back(iterJ->second, iterK->second); + _tripletMatches[std::make_pair(I, K)][subTrack.descType].emplace_back(iterI->second, iterK->second); + } + } + } + } } - const Vec3 Xs = trianObj.compute(); - if (trianObj.minDepth() > 0 && trianObj.error()/(double)trianObj.size() < 4.0) - // TODO: Add an angular check ? - { - #pragma omp critical - { - track::Track::FeatureIdPerView::const_iterator iterI, iterJ, iterK; - iterI = iterJ = iterK = subTrack.featPerView.begin(); - std::advance(iterJ,1); - std::advance(iterK,2); - - _tripletMatches[std::make_pair(I,J)][subTrack.descType].emplace_back(iterI->second, iterJ->second); - _tripletMatches[std::make_pair(J,K)][subTrack.descType].emplace_back(iterJ->second, iterK->second); - _tripletMatches[std::make_pair(I,K)][subTrack.descType].emplace_back(iterI->second, iterK->second); - } - } - } } - } } - } - // Clear putatives matches since they are no longer required - matching::PairwiseMatches().swap(_putativeMatches); + // Clear putatives matches since they are no longer required + matching::PairwiseMatches().swap(_putativeMatches); } /// Init & triangulate landmark observations from validated 3-view correspondences -void StructureEstimationFromKnownPoses::triangulate( - SfMData& sfmData, - const feature::RegionsPerView& regionsPerView, - std::mt19937 &randomNumberGenerator) +void StructureEstimationFromKnownPoses::triangulate(SfMData& sfmData, + const feature::RegionsPerView& regionsPerView, + std::mt19937& randomNumberGenerator) { - track::TracksMap map_tracksCommon; - track::TracksBuilder tracksBuilder; - tracksBuilder.build(_tripletMatches); - tracksBuilder.filter(true,3); - tracksBuilder.exportToSTL(map_tracksCommon); - matching::PairwiseMatches().swap(_tripletMatches); - - // Generate new Structure tracks - sfmData.getLandmarks().clear(); - - // Fill sfm_data with the computed tracks (no 3D yet) - Landmarks & structure = sfmData.getLandmarks(); - IndexT idx(0); - for (track::TracksMap::const_iterator itTracks = map_tracksCommon.begin(); - itTracks != map_tracksCommon.end(); - ++itTracks, ++idx) - { - const track::Track & track = itTracks->second; - structure[idx] = Landmark(track.descType); - Observations & observations = structure.at(idx).observations; - for (auto it = track.featPerView.begin(); it != track.featPerView.end(); ++it) + track::TracksMap map_tracksCommon; + track::TracksBuilder tracksBuilder; + tracksBuilder.build(_tripletMatches); + tracksBuilder.filter(true, 3); + tracksBuilder.exportToSTL(map_tracksCommon); + matching::PairwiseMatches().swap(_tripletMatches); + + // Generate new Structure tracks + sfmData.getLandmarks().clear(); + + // Fill sfm_data with the computed tracks (no 3D yet) + Landmarks& structure = sfmData.getLandmarks(); + IndexT idx(0); + for (track::TracksMap::const_iterator itTracks = map_tracksCommon.begin(); itTracks != map_tracksCommon.end(); ++itTracks, ++idx) { - const size_t imaIndex = it->first; - const size_t featIndex = it->second; - const feature::Regions& regions = regionsPerView.getRegions(imaIndex, track.descType); - const feature::PointFeature& feat = regions.Features()[featIndex]; - - observations[imaIndex] = Observation(feat.coords().cast(), featIndex, feat.scale()); + const track::Track& track = itTracks->second; + structure[idx] = Landmark(track.descType); + Observations& observations = structure.at(idx).observations; + for (auto it = track.featPerView.begin(); it != track.featPerView.end(); ++it) + { + const size_t imaIndex = it->first; + const size_t featIndex = it->second; + const feature::Regions& regions = regionsPerView.getRegions(imaIndex, track.descType); + const feature::PointFeature& feat = regions.Features()[featIndex]; + + observations[imaIndex] = Observation(feat.coords().cast(), featIndex, feat.scale()); + } } - } - // Triangulate them using a robust triangulation scheme - StructureComputation_robust structure_estimator(true); - structure_estimator.triangulate(sfmData, randomNumberGenerator); + // Triangulate them using a robust triangulation scheme + StructureComputation_robust structure_estimator(true); + structure_estimator.triangulate(sfmData, randomNumberGenerator); } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.hpp b/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.hpp index 7cb8c6d3cc..1a57a977ce 100644 --- a/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.hpp +++ b/src/aliceVision/sfm/pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.hpp @@ -16,45 +16,33 @@ namespace sfm { class StructureEstimationFromKnownPoses { -public: - - /// Use geometry of the views to compute a putative structure from features and descriptors. - void run(sfmData::SfMData& sfmData, - const PairSet& pairs, - const feature::RegionsPerView& regionsPerView, - std::mt19937 &randomNumberGenerator, - double geometricErrorMax); - -public: - - /// Use guided matching to find corresponding 2-view correspondences - void match(const sfmData::SfMData& sfmData, - const PairSet& pairs, - const feature::RegionsPerView& regionsPerView, - double geometricErrorMax); - - /// Filter inconsistent correspondences by using 3-view correspondences on view triplets - void filter( - const sfmData::SfMData& sfmData, - const PairSet& pairs, - const feature::RegionsPerView& regionsPerView); - - /// Init & triangulate landmark observations from validated 3-view correspondences - void triangulate( - sfmData::SfMData& sfmData, - const feature::RegionsPerView& regionsPerView, - std::mt19937 &randomNumberGenerator); - - const matching::PairwiseMatches& getPutativesMatches() const { return _putativeMatches; } - -private: - //-- - // DATA (temporary) - //-- - matching::PairwiseMatches _putativeMatches; - matching::PairwiseMatches _tripletMatches; + public: + /// Use geometry of the views to compute a putative structure from features and descriptors. + void run(sfmData::SfMData& sfmData, + const PairSet& pairs, + const feature::RegionsPerView& regionsPerView, + std::mt19937& randomNumberGenerator, + double geometricErrorMax); + + public: + /// Use guided matching to find corresponding 2-view correspondences + void match(const sfmData::SfMData& sfmData, const PairSet& pairs, const feature::RegionsPerView& regionsPerView, double geometricErrorMax); + + /// Filter inconsistent correspondences by using 3-view correspondences on view triplets + void filter(const sfmData::SfMData& sfmData, const PairSet& pairs, const feature::RegionsPerView& regionsPerView); + + /// Init & triangulate landmark observations from validated 3-view correspondences + void triangulate(sfmData::SfMData& sfmData, const feature::RegionsPerView& regionsPerView, std::mt19937& randomNumberGenerator); + + const matching::PairwiseMatches& getPutativesMatches() const { return _putativeMatches; } + + private: + //-- + // DATA (temporary) + //-- + matching::PairwiseMatches _putativeMatches; + matching::PairwiseMatches _tripletMatches; }; -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/sfmFilters.cpp b/src/aliceVision/sfm/sfmFilters.cpp index 3a6d6d158d..35a37e642c 100644 --- a/src/aliceVision/sfm/sfmFilters.cpp +++ b/src/aliceVision/sfm/sfmFilters.cpp @@ -21,234 +21,236 @@ IndexT RemoveOutliers_PixelResidualError(sfmData::SfMData& sfmData, const double dThresholdPixel, const unsigned int minTrackLength) { - IndexT outlier_count = 0; - sfmData::Landmarks::iterator iterTracks = sfmData.getLandmarks().begin(); + IndexT outlier_count = 0; + sfmData::Landmarks::iterator iterTracks = sfmData.getLandmarks().begin(); + while (iterTracks != sfmData.getLandmarks().end()) + { + sfmData::Observations& observations = iterTracks->second.observations; + sfmData::Observations::iterator itObs = observations.begin(); - while(iterTracks != sfmData.getLandmarks().end()) - { - sfmData::Observations & observations = iterTracks->second.observations; - sfmData::Observations::iterator itObs = observations.begin(); + while (itObs != observations.end()) + { + const sfmData::View* view = sfmData.getViews().at(itObs->first).get(); + const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); + const camera::IntrinsicBase* intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); + + Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x); + if (featureConstraint == EFeatureConstraint::SCALE && itObs->second.scale > 0.0) + { + // Apply the scale of the feature to get a residual value + // relative to the feature precision. + residual /= itObs->second.scale; + } + + if ((pose.depth(iterTracks->second.X) < 0) || (residual.norm() > dThresholdPixel)) + { + ++outlier_count; + itObs = observations.erase(itObs); + } + else + ++itObs; + } - while(itObs != observations.end()) - { - const sfmData::View * view = sfmData.getViews().at(itObs->first).get(); - const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); - const camera::IntrinsicBase * intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); - - Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x); - if(featureConstraint == EFeatureConstraint::SCALE && itObs->second.scale > 0.0) - { - // Apply the scale of the feature to get a residual value - // relative to the feature precision. - residual /= itObs->second.scale; - } - - if((pose.depth(iterTracks->second.X) < 0) || (residual.norm() > dThresholdPixel)) - { - ++outlier_count; - itObs = observations.erase(itObs); - } - else - ++itObs; + if (observations.empty() || observations.size() < minTrackLength) + iterTracks = sfmData.getLandmarks().erase(iterTracks); + else + ++iterTracks; } - - if (observations.empty() || observations.size() < minTrackLength) - iterTracks = sfmData.getLandmarks().erase(iterTracks); - else - ++iterTracks; - } - return outlier_count; + return outlier_count; } IndexT RemoveOutliers_AngleError(sfmData::SfMData& sfmData, const double dMinAcceptedAngle) { - // note that smallest accepted angle => largest accepted cos(angle) - const double dMaxAcceptedCosAngle = std::cos(degreeToRadian(dMinAcceptedAngle)); - - using LandmarksKeysVec = std::vector; - LandmarksKeysVec v_keys; v_keys.reserve(sfmData.getLandmarks().size()); - std::transform(sfmData.getLandmarks().cbegin(), sfmData.getLandmarks().cend(), std::back_inserter(v_keys), stl::RetrieveKey()); - - LandmarksKeysVec toErase; - - #pragma omp parallel for - for (int landmarkIndex = 0; landmarkIndex < v_keys.size(); ++landmarkIndex) - { - const sfmData::Observations &observations = sfmData.getLandmarks().at(v_keys[landmarkIndex]).observations; - - // create matrix for observation directions from camera to point - Mat3X viewDirections(3, observations.size()); - Mat3X::Index i; - sfmData::Observations::const_iterator itObs; - - // Greedy algorithm almost always finds an acceptable angle in 1-5 iterations (if it exists). - // It works by greedily chasing the first larger view angle found from the current greedy index. - // View angles have a spatial distribution, so greedily jumping over larger and larger angles - // forces the greedy index towards the outside of the distribution. - double dGreedyCos = 1.1; - Mat3X::Index greedyI = 0; - - - // fill matrix, optimistically checking each new entry against col(greedyI) - for(itObs = observations.begin(), i = 0; itObs != observations.end(); ++itObs, ++i) - { - const sfmData::View * view = sfmData.getViews().at(itObs->first).get(); - const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); - const camera::IntrinsicBase * intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); - - viewDirections.col(i) = applyIntrinsicExtrinsic(pose, intrinsic, itObs->second.x); - - double dCosAngle = viewDirections.col(i).transpose() * viewDirections.col(greedyI); - if (dCosAngle < dMaxAcceptedCosAngle) - { - break; - } - else if (dCosAngle < dGreedyCos) - { - dGreedyCos = dCosAngle; - greedyI = i; - } - } + // note that smallest accepted angle => largest accepted cos(angle) + const double dMaxAcceptedCosAngle = std::cos(degreeToRadian(dMinAcceptedAngle)); - // early exit, acceptable angle found - if (itObs != observations.end()) - { - continue; - } + using LandmarksKeysVec = std::vector; + LandmarksKeysVec v_keys; + v_keys.reserve(sfmData.getLandmarks().size()); + std::transform(sfmData.getLandmarks().cbegin(), sfmData.getLandmarks().cend(), std::back_inserter(v_keys), stl::RetrieveKey()); - // Switch to O(n^2) exhaustive search. - // Although this is an O(n^2) loop, in practice it will almost always break very early. - // - // - Default value of dMinAcceptedAngle is 2 degrees. Any larger angle breaks. - // - For landmarks with small number of views, n^2 is negligible. - // - For landmarks with large number of views, backwards iteration means - // all view directions as considered as early as possible, - // making it difficult for a small angle to hide between views. - // - for(i = viewDirections.cols() - 1; i > 0; i -= 1) + LandmarksKeysVec toErase; + +#pragma omp parallel for + for (int landmarkIndex = 0; landmarkIndex < v_keys.size(); ++landmarkIndex) { - // Compute and find minimum cosAngle between viewDirections[i] and all viewDirections[0:i]. - // Single statement can allow Eigen optimizations - const double dMinCosAngle = (viewDirections.col(i).transpose() * viewDirections.leftCols(i)).minCoeff(); - if (dMinCosAngle < dMaxAcceptedCosAngle) { - break; - } + const sfmData::Observations& observations = sfmData.getLandmarks().at(v_keys[landmarkIndex]).observations; + + // create matrix for observation directions from camera to point + Mat3X viewDirections(3, observations.size()); + Mat3X::Index i; + sfmData::Observations::const_iterator itObs; + + // Greedy algorithm almost always finds an acceptable angle in 1-5 iterations (if it exists). + // It works by greedily chasing the first larger view angle found from the current greedy index. + // View angles have a spatial distribution, so greedily jumping over larger and larger angles + // forces the greedy index towards the outside of the distribution. + double dGreedyCos = 1.1; + Mat3X::Index greedyI = 0; + + // fill matrix, optimistically checking each new entry against col(greedyI) + for (itObs = observations.begin(), i = 0; itObs != observations.end(); ++itObs, ++i) + { + const sfmData::View* view = sfmData.getViews().at(itObs->first).get(); + const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); + const camera::IntrinsicBase* intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); + + viewDirections.col(i) = applyIntrinsicExtrinsic(pose, intrinsic, itObs->second.x); + + double dCosAngle = viewDirections.col(i).transpose() * viewDirections.col(greedyI); + if (dCosAngle < dMaxAcceptedCosAngle) + { + break; + } + else if (dCosAngle < dGreedyCos) + { + dGreedyCos = dCosAngle; + greedyI = i; + } + } + + // early exit, acceptable angle found + if (itObs != observations.end()) + { + continue; + } + + // Switch to O(n^2) exhaustive search. + // Although this is an O(n^2) loop, in practice it will almost always break very early. + // + // - Default value of dMinAcceptedAngle is 2 degrees. Any larger angle breaks. + // - For landmarks with small number of views, n^2 is negligible. + // - For landmarks with large number of views, backwards iteration means + // all view directions as considered as early as possible, + // making it difficult for a small angle to hide between views. + // + for (i = viewDirections.cols() - 1; i > 0; i -= 1) + { + // Compute and find minimum cosAngle between viewDirections[i] and all viewDirections[0:i]. + // Single statement can allow Eigen optimizations + const double dMinCosAngle = (viewDirections.col(i).transpose() * viewDirections.leftCols(i)).minCoeff(); + if (dMinCosAngle < dMaxAcceptedCosAngle) + { + break; + } + } + + // acceptable angle not found + if (i == 0) + { +#pragma omp critical + toErase.push_back(v_keys[landmarkIndex]); + } } - // acceptable angle not found - if (i == 0) + for (IndexT key : toErase) { - #pragma omp critical - toErase.push_back(v_keys[landmarkIndex]); + sfmData.getLandmarks().erase(key); } - } - - for (IndexT key : toErase) - { - sfmData.getLandmarks().erase(key); - } - return toErase.size(); + return toErase.size(); } bool eraseUnstablePoses(sfmData::SfMData& sfmData, const IndexT min_points_per_pose, std::set* outRemovedViewsId) { - IndexT removed_elements = 0; - const sfmData::Landmarks & landmarks = sfmData.getLandmarks(); + IndexT removed_elements = 0; + const sfmData::Landmarks& landmarks = sfmData.getLandmarks(); - // Count the observation poses occurrence - HashMap posesCount; + // Count the observation poses occurrence + HashMap posesCount; - // Init with 0 count, undefined rig id (in order to be able to remove non referenced elements) - for(sfmData::Poses::const_iterator itPoses = sfmData.getPoses().begin(); itPoses != sfmData.getPoses().end(); ++itPoses) - posesCount[itPoses->first] = 0; + // Init with 0 count, undefined rig id (in order to be able to remove non referenced elements) + for (sfmData::Poses::const_iterator itPoses = sfmData.getPoses().begin(); itPoses != sfmData.getPoses().end(); ++itPoses) + posesCount[itPoses->first] = 0; - // Count occurrence of the poses in the Landmark observations - for(sfmData::Landmarks::const_iterator itLandmarks = landmarks.begin(); itLandmarks != landmarks.end(); ++itLandmarks) - { - const sfmData::Observations & observations = itLandmarks->second.observations; - for(sfmData::Observations::const_iterator itObs = observations.begin(); itObs != observations.end(); ++itObs) + // Count occurrence of the poses in the Landmark observations + for (sfmData::Landmarks::const_iterator itLandmarks = landmarks.begin(); itLandmarks != landmarks.end(); ++itLandmarks) { - const IndexT viewId = itObs->first; - const sfmData::View * v = sfmData.getViews().at(viewId).get(); - const auto poseInfoIt = posesCount.find(v->getPoseId()); - - if(poseInfoIt != posesCount.end()) - poseInfoIt->second++; - else // all pose should be defined in map_PoseId_Count - throw std::runtime_error(std::string("eraseUnstablePoses: found unknown pose id referenced by a view.\n\t- view id: ") - + std::to_string(v->getViewId()) + std::string("\n\t- pose id: ") + std::to_string(v->getPoseId())); + const sfmData::Observations& observations = itLandmarks->second.observations; + for (sfmData::Observations::const_iterator itObs = observations.begin(); itObs != observations.end(); ++itObs) + { + const IndexT viewId = itObs->first; + const sfmData::View* v = sfmData.getViews().at(viewId).get(); + const auto poseInfoIt = posesCount.find(v->getPoseId()); + + if (poseInfoIt != posesCount.end()) + poseInfoIt->second++; + else // all pose should be defined in map_PoseId_Count + throw std::runtime_error(std::string("eraseUnstablePoses: found unknown pose id referenced by a view.\n\t- view id: ") + + std::to_string(v->getViewId()) + std::string("\n\t- pose id: ") + std::to_string(v->getPoseId())); + } } - } - // If usage count is smaller than the threshold, remove the Pose - for(HashMap::const_iterator it = posesCount.begin(); it != posesCount.end(); ++it) - { - if(it->second < min_points_per_pose) + // If usage count is smaller than the threshold, remove the Pose + for (HashMap::const_iterator it = posesCount.begin(); it != posesCount.end(); ++it) { - sfmData.erasePose(it->first, true); // no throw - - for(auto& viewPair : sfmData.getViews()) - { - if(viewPair.second->getPoseId() == it->first) + if (it->second < min_points_per_pose) { - if(viewPair.second->isPartOfRig()) - { - // the pose is now independant - viewPair.second->setPoseId(viewPair.first); - viewPair.second->setIndependantPose(true); - } - - // add view id to the removedViewsId set - if(outRemovedViewsId != NULL) - outRemovedViewsId->insert(viewPair.first); + sfmData.erasePose(it->first, true); // no throw + + for (auto& viewPair : sfmData.getViews()) + { + if (viewPair.second->getPoseId() == it->first) + { + if (viewPair.second->isPartOfRig()) + { + // the pose is now independant + viewPair.second->setPoseId(viewPair.first); + viewPair.second->setIndependantPose(true); + } + + // add view id to the removedViewsId set + if (outRemovedViewsId != NULL) + outRemovedViewsId->insert(viewPair.first); + } + } + ++removed_elements; } - } - ++removed_elements; } - } - if(removed_elements) - ALICEVISION_LOG_DEBUG("eraseUnstablePoses: " << removed_elements); - return removed_elements > 0; + if (removed_elements) + ALICEVISION_LOG_DEBUG("eraseUnstablePoses: " << removed_elements); + return removed_elements > 0; } bool eraseObservationsWithMissingPoses(sfmData::SfMData& sfmData, const IndexT min_points_per_landmark) { - IndexT removed_elements = 0; - - std::set reconstructedPoseIndexes; - std::transform(sfmData.getPoses().begin(), sfmData.getPoses().end(), - std::inserter(reconstructedPoseIndexes, reconstructedPoseIndexes.begin()), stl::RetrieveKey()); + IndexT removed_elements = 0; - // For each landmark: - // - Check if we need to keep the observations & the track - sfmData::Landmarks::iterator itLandmarks = sfmData.getLandmarks().begin(); + std::set reconstructedPoseIndexes; + std::transform(sfmData.getPoses().begin(), + sfmData.getPoses().end(), + std::inserter(reconstructedPoseIndexes, reconstructedPoseIndexes.begin()), + stl::RetrieveKey()); - while(itLandmarks != sfmData.getLandmarks().end()) - { - sfmData::Observations& observations = itLandmarks->second.observations; - sfmData::Observations::iterator itObs = observations.begin(); + // For each landmark: + // - Check if we need to keep the observations & the track + sfmData::Landmarks::iterator itLandmarks = sfmData.getLandmarks().begin(); - while (itObs != observations.end()) + while (itLandmarks != sfmData.getLandmarks().end()) { - const IndexT ViewId = itObs->first; - const sfmData::View* v = sfmData.getViews().at(ViewId).get(); - if(reconstructedPoseIndexes.count(v->getPoseId()) == 0) - { - itObs = observations.erase(itObs); - ++removed_elements; - } - else - ++itObs; - } + sfmData::Observations& observations = itLandmarks->second.observations; + sfmData::Observations::iterator itObs = observations.begin(); + + while (itObs != observations.end()) + { + const IndexT ViewId = itObs->first; + const sfmData::View* v = sfmData.getViews().at(ViewId).get(); + if (reconstructedPoseIndexes.count(v->getPoseId()) == 0) + { + itObs = observations.erase(itObs); + ++removed_elements; + } + else + ++itObs; + } - if(observations.empty() || observations.size() < min_points_per_landmark) - itLandmarks = sfmData.getLandmarks().erase(itLandmarks); - else - ++itLandmarks; - } - return removed_elements > 0; + if (observations.empty() || observations.size() < min_points_per_landmark) + itLandmarks = sfmData.getLandmarks().erase(itLandmarks); + else + ++itLandmarks; + } + return removed_elements > 0; } /// Remove unstable content from analysis of the sfm_data structure @@ -257,27 +259,26 @@ bool eraseUnstablePosesAndObservations(sfmData::SfMData& sfmData, const IndexT min_points_per_landmark, std::set* outRemovedViewsId) { - IndexT removeIteration = 0; - bool removedContent = false; - bool removedPoses = false; - bool removedObservations = false; - do - { - removedContent = false; - if(eraseUnstablePoses(sfmData, min_points_per_pose, outRemovedViewsId)) + IndexT removeIteration = 0; + bool removedContent = false; + bool removedPoses = false; + bool removedObservations = false; + do { - removedPoses = true; - removedContent = eraseObservationsWithMissingPoses(sfmData, min_points_per_landmark); - if(removedContent) - removedObservations = true; - // Erase some observations can make some Poses index disappear so perform the process in a loop - } - removeIteration += removedContent ? 1 : 0; - } - while(removedContent); + removedContent = false; + if (eraseUnstablePoses(sfmData, min_points_per_pose, outRemovedViewsId)) + { + removedPoses = true; + removedContent = eraseObservationsWithMissingPoses(sfmData, min_points_per_landmark); + if (removedContent) + removedObservations = true; + // Erase some observations can make some Poses index disappear so perform the process in a loop + } + removeIteration += removedContent ? 1 : 0; + } while (removedContent); - return removedPoses || removedObservations; + return removedPoses || removedObservations; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/sfmFilters.hpp b/src/aliceVision/sfm/sfmFilters.hpp index 0bdd6eed53..1ce5d81ebc 100644 --- a/src/aliceVision/sfm/sfmFilters.hpp +++ b/src/aliceVision/sfm/sfmFilters.hpp @@ -14,21 +14,21 @@ namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { /// Filter a list of pair: Keep only the pair that are defined in index list -template +template inline PairSet Pair_filter(const IterablePairs& pairs, const IterableIndex& index) { - PairSet kept_pairs; - for (auto& it : pairs) - { - if (index.count(it.first) > 0 && index.count(it.second) > 0) - kept_pairs.insert(it); - } - return kept_pairs; + PairSet kept_pairs; + for (auto& it : pairs) + { + if (index.count(it.first) > 0 && index.count(it.second) > 0) + kept_pairs.insert(it); + } + return kept_pairs; } /// Remove observations with too large reprojection error. @@ -42,15 +42,15 @@ IndexT RemoveOutliers_PixelResidualError(sfmData::SfMData& sfmData, // Return the number of removed tracks IndexT RemoveOutliers_AngleError(sfmData::SfMData& sfmData, const double dMinAcceptedAngle); -bool eraseUnstablePoses(sfmData::SfMData& sfmData, const IndexT min_points_per_pose, std::set *outRemovedViewsId = NULL); +bool eraseUnstablePoses(sfmData::SfMData& sfmData, const IndexT min_points_per_pose, std::set* outRemovedViewsId = NULL); bool eraseObservationsWithMissingPoses(sfmData::SfMData& sfmData, const IndexT min_points_per_landmark); /// Remove unstable content from analysis of the sfm_data structure bool eraseUnstablePosesAndObservations(sfmData::SfMData& sfmData, const IndexT min_points_per_pose = 6, - const IndexT min_points_per_landmark = 2, - std::set *outRemovedViewsId = NULL); + const IndexT min_points_per_landmark = 2, + std::set* outRemovedViewsId = NULL); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/sfmStatistics.cpp b/src/aliceVision/sfm/sfmStatistics.cpp index 8185f2de8d..8e0de96781 100644 --- a/src/aliceVision/sfm/sfmStatistics.cpp +++ b/src/aliceVision/sfm/sfmStatistics.cpp @@ -12,113 +12,119 @@ #include #include - namespace aliceVision { namespace sfm { -void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram, const std::set& specificViews) +void computeResidualsHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + utils::Histogram* out_histogram, + const std::set& specificViews) { - { - // Init output params - out_stats = BoxStats(); - if (out_histogram) { - *out_histogram = utils::Histogram(); + // Init output params + out_stats = BoxStats(); + if (out_histogram) + { + *out_histogram = utils::Histogram(); + } } - } - if (sfmData.getLandmarks().empty()) - return; - - // Collect residuals for each observation - std::vector vec_residuals; - vec_residuals.reserve(sfmData.getLandmarks().size()); - - for(const auto &track : sfmData.getLandmarks()) - { - const aliceVision::sfmData::Observations & observations = track.second.observations; - for(const auto& obs: observations) + if (sfmData.getLandmarks().empty()) + return; + + // Collect residuals for each observation + std::vector vec_residuals; + vec_residuals.reserve(sfmData.getLandmarks().size()); + + for (const auto& track : sfmData.getLandmarks()) { - if(!specificViews.empty()) - { - if(specificViews.count(obs.first) == 0) - continue; - } - const sfmData::View& view = sfmData.getView(obs.first); - const aliceVision::geometry::Pose3 pose = sfmData.getPose(view).getTransform(); - const std::shared_ptr intrinsic = sfmData.getIntrinsics().find(view.getIntrinsicId())->second; - const Vec2 residual = intrinsic->residual(pose, track.second.X.homogeneous(), obs.second.x); - vec_residuals.push_back(residual.norm()); - //ALICEVISION_LOG_INFO("[AliceVision] sfmtstatistics::computeResidualsHistogram track: " << track.first << ", residual: " << residual.norm()); + const aliceVision::sfmData::Observations& observations = track.second.observations; + for (const auto& obs : observations) + { + if (!specificViews.empty()) + { + if (specificViews.count(obs.first) == 0) + continue; + } + const sfmData::View& view = sfmData.getView(obs.first); + const aliceVision::geometry::Pose3 pose = sfmData.getPose(view).getTransform(); + const std::shared_ptr intrinsic = sfmData.getIntrinsics().find(view.getIntrinsicId())->second; + const Vec2 residual = intrinsic->residual(pose, track.second.X.homogeneous(), obs.second.x); + vec_residuals.push_back(residual.norm()); + // ALICEVISION_LOG_INFO("[AliceVision] sfmtstatistics::computeResidualsHistogram track: " << track.first << ", residual: " << + // residual.norm()); + } } - } - // ALICEVISION_LOG_INFO("[AliceVision] sfmtstatistics::computeResidualsHistogram vec_residuals.size(): " << vec_residuals.size()); + // ALICEVISION_LOG_INFO("[AliceVision] sfmtstatistics::computeResidualsHistogram vec_residuals.size(): " << vec_residuals.size()); - if(vec_residuals.empty()) - return; + if (vec_residuals.empty()) + return; - out_stats = BoxStats(vec_residuals.begin(), vec_residuals.end()); + out_stats = BoxStats(vec_residuals.begin(), vec_residuals.end()); - if (out_histogram) - { - *out_histogram = utils::Histogram(0.0, std::ceil(out_stats.max), std::ceil(out_stats.max)*2); - out_histogram->Add(vec_residuals.begin(), vec_residuals.end()); - } + if (out_histogram) + { + *out_histogram = utils::Histogram(0.0, std::ceil(out_stats.max), std::ceil(out_stats.max) * 2); + out_histogram->Add(vec_residuals.begin(), vec_residuals.end()); + } } - -void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, int& overallNbObservations, utils::Histogram* out_histogram, const std::set& specificViews) +void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + int& overallNbObservations, + utils::Histogram* out_histogram, + const std::set& specificViews) { - { - // Init output params - out_stats = BoxStats(); - if (out_histogram) { - *out_histogram = utils::Histogram(); + // Init output params + out_stats = BoxStats(); + if (out_histogram) + { + *out_histogram = utils::Histogram(); + } } - } - if (sfmData.getLandmarks().empty()) - return; - - // Collect tracks size: number of 2D observations per 3D points - std::vector nbObservations; - nbObservations.reserve(sfmData.getLandmarks().size()); - - for(const auto& landmark : sfmData.getLandmarks()) - { - const aliceVision::sfmData::Observations & observations = landmark.second.observations; - if (!specificViews.empty()) + if (sfmData.getLandmarks().empty()) + return; + + // Collect tracks size: number of 2D observations per 3D points + std::vector nbObservations; + nbObservations.reserve(sfmData.getLandmarks().size()); + + for (const auto& landmark : sfmData.getLandmarks()) { - int nbObsSpecificViews = 0; - for(const auto& obs: observations) + const aliceVision::sfmData::Observations& observations = landmark.second.observations; + if (!specificViews.empty()) { - if(specificViews.count(obs.first) == 1) + int nbObsSpecificViews = 0; + for (const auto& obs : observations) + { + if (specificViews.count(obs.first) == 1) + { + ++nbObsSpecificViews; + } + } + if (nbObsSpecificViews > 0) { - ++nbObsSpecificViews; + nbObservations.push_back(observations.size()); } } - if(nbObsSpecificViews > 0) + else { nbObservations.push_back(observations.size()); } + overallNbObservations += observations.size(); } - else - { - nbObservations.push_back(observations.size()); - } - overallNbObservations += observations.size(); - } - if(nbObservations.empty()) - return; + if (nbObservations.empty()) + return; - out_stats = BoxStats(nbObservations.begin(), nbObservations.end()); + out_stats = BoxStats(nbObservations.begin(), nbObservations.end()); - if (out_histogram) - { - *out_histogram = utils::Histogram(out_stats.min, out_stats.max + 1, out_stats.max - out_stats.min + 1); - out_histogram->Add(nbObservations.begin(), nbObservations.end()); - } + if (out_histogram) + { + *out_histogram = utils::Histogram(out_stats.min, out_stats.max + 1, out_stats.max - out_stats.min + 1); + out_histogram->Add(nbObservations.begin(), nbObservations.end()); + } } void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram) @@ -131,29 +137,29 @@ void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats< *out_histogram = utils::Histogram(); } } - if(sfmData.getLandmarks().empty()) + if (sfmData.getLandmarks().empty()) return; std::map nbLandmarksPerView; - for(const auto& landmark: sfmData.getLandmarks()) + for (const auto& landmark : sfmData.getLandmarks()) { - for(const auto& obsIt: landmark.second.observations) + for (const auto& obsIt : landmark.second.observations) { const auto& viewId = obsIt.first; auto it = nbLandmarksPerView.find(viewId); - if(it != nbLandmarksPerView.end()) + if (it != nbLandmarksPerView.end()) ++(it->second); else it->second = 1; } } - if(nbLandmarksPerView.empty()) + if (nbLandmarksPerView.empty()) return; // Collect tracks size: number of 2D observations per 3D points std::vector nbLandmarksPerViewVec; - for(const auto& it: nbLandmarksPerView) + for (const auto& it : nbLandmarksPerView) nbLandmarksPerViewVec.push_back(it.second); out_stats = BoxStats(nbLandmarksPerViewVec.begin(), nbLandmarksPerViewVec.end()); @@ -166,39 +172,37 @@ void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats< } } - void computeLandmarksPerView(const sfmData::SfMData& sfmData, std::vector& out_nbLandmarksPerView) { { out_nbLandmarksPerView.clear(); } - if(sfmData.getLandmarks().empty()) + if (sfmData.getLandmarks().empty()) return; std::map nbLandmarksPerView; - - for(const auto& landmark : sfmData.getLandmarks()) + for (const auto& landmark : sfmData.getLandmarks()) { - const aliceVision::sfmData::Observations & observations = landmark.second.observations; - for(const auto& obs: observations) + const aliceVision::sfmData::Observations& observations = landmark.second.observations; + for (const auto& obs : observations) { auto it = nbLandmarksPerView.find(obs.first); - if(it != nbLandmarksPerView.end()) + if (it != nbLandmarksPerView.end()) ++(it->second); else nbLandmarksPerView[obs.first] = 1; } } - if(nbLandmarksPerView.empty()) + if (nbLandmarksPerView.empty()) return; out_nbLandmarksPerView.reserve(nbLandmarksPerView.size()); - for(const auto& viewIt: sfmData.getViews()) + for (const auto& viewIt : sfmData.getViews()) { const auto it = nbLandmarksPerView.find(viewIt.first); int nbLandmarks = 0; - if(it != nbLandmarksPerView.end()) + if (it != nbLandmarksPerView.end()) nbLandmarks = it->second; out_nbLandmarksPerView.push_back(nbLandmarks); } @@ -211,7 +215,7 @@ void computeFeatMatchPerView(const sfmData::SfMData& sfmData, std::vector& out_stats, utils::Histogram* out_histogram, const std::set& specificViews) +void computeScaleHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + utils::Histogram* out_histogram, + const std::set& specificViews) { { - // Init output params - out_stats = BoxStats(); - if (out_histogram) - { - *out_histogram = utils::Histogram(); - } + // Init output params + out_stats = BoxStats(); + if (out_histogram) + { + *out_histogram = utils::Histogram(); + } } - if(sfmData.getLandmarks().empty()) - return; + if (sfmData.getLandmarks().empty()) + return; // Collect tracks size: number of 2D observations per 3D points std::vector vec_scaleObservations; vec_scaleObservations.reserve(sfmData.getLandmarks().size()); - for(const auto& landmark: sfmData.getLandmarks()) + for (const auto& landmark : sfmData.getLandmarks()) { - const aliceVision::sfmData::Observations & observations = landmark.second.observations; + const aliceVision::sfmData::Observations& observations = landmark.second.observations; - for(const auto& obs: observations) + for (const auto& obs : observations) { - if(!specificViews.empty()) + if (!specificViews.empty()) { - if(specificViews.count(obs.first) == 0) + if (specificViews.count(obs.first) == 0) continue; } vec_scaleObservations.push_back(obs.second.scale); } } - if(vec_scaleObservations.empty()) + if (vec_scaleObservations.empty()) return; out_stats = BoxStats(vec_scaleObservations.begin(), vec_scaleObservations.end()); if (out_histogram) { - size_t maxValue = std::ceil(out_stats.max); - *out_histogram = utils::Histogram(0.0, double(maxValue), maxValue +1); - out_histogram->Add(vec_scaleObservations.begin(), vec_scaleObservations.end()); + size_t maxValue = std::ceil(out_stats.max); + *out_histogram = utils::Histogram(0.0, double(maxValue), maxValue + 1); + out_histogram->Add(vec_scaleObservations.begin(), vec_scaleObservations.end()); } } -void computeResidualsPerView(const sfmData::SfMData& sfmData, int& nbViews, std::vector& nbResidualsPerViewMin, - std::vector& nbResidualsPerViewMax, std::vector& nbResidualsPerViewMean, - std::vector& nbResidualsPerViewMedian, std::vector& nbResidualsPerViewFirstQuartile, - std::vector& nbResidualsPerViewThirdQuartile) +void computeResidualsPerView(const sfmData::SfMData& sfmData, + int& nbViews, + std::vector& nbResidualsPerViewMin, + std::vector& nbResidualsPerViewMax, + std::vector& nbResidualsPerViewMean, + std::vector& nbResidualsPerViewMedian, + std::vector& nbResidualsPerViewFirstQuartile, + std::vector& nbResidualsPerViewThirdQuartile) { - if(sfmData.getLandmarks().empty()) - return; + if (sfmData.getLandmarks().empty()) + return; nbViews = sfmData.getViews().size(); nbResidualsPerViewMin.resize(nbViews); @@ -321,34 +332,35 @@ void computeResidualsPerView(const sfmData::SfMData& sfmData, int& nbViews, std: // Collect residuals (number of residuals per 3D points) of all landmarks visible in each view std::map> residualsPerView; - for(const auto &landmark : sfmData.getLandmarks()) + for (const auto& landmark : sfmData.getLandmarks()) { - const aliceVision::sfmData::Observations & observations = landmark.second.observations; - for(const auto& obs: observations) - { - const sfmData::View& view = sfmData.getView(obs.first); - const aliceVision::geometry::Pose3 pose = sfmData.getPose(view).getTransform(); - const std::shared_ptr intrinsic = sfmData.getIntrinsics().find(view.getIntrinsicId())->second; - const Vec2 residual = intrinsic->residual(pose, landmark.second.X.homogeneous(), obs.second.x); - residualsPerView[obs.first].push_back(residual.norm()); - } + const aliceVision::sfmData::Observations& observations = landmark.second.observations; + for (const auto& obs : observations) + { + const sfmData::View& view = sfmData.getView(obs.first); + const aliceVision::geometry::Pose3 pose = sfmData.getPose(view).getTransform(); + const std::shared_ptr intrinsic = sfmData.getIntrinsics().find(view.getIntrinsicId())->second; + const Vec2 residual = intrinsic->residual(pose, landmark.second.X.homogeneous(), obs.second.x); + residualsPerView[obs.first].push_back(residual.norm()); + } } std::vector viewKeys; - for(const auto& v: sfmData.getViews()) + for (const auto& v : sfmData.getViews()) viewKeys.push_back(v.first); - #pragma omp parallel for - for(int viewIdx = 0; viewIdx < nbViews; ++viewIdx) +#pragma omp parallel for + for (int viewIdx = 0; viewIdx < nbViews; ++viewIdx) { const IndexT viewId = viewKeys[viewIdx]; const auto it = residualsPerView.find(viewId); - if(it == residualsPerView.end()) + if (it == residualsPerView.end()) continue; const std::vector& residuals = it->second; BoxStats residualStats(residuals.begin(), residuals.end()); - utils::Histogram residual_histogram = utils::Histogram(residualStats.min, residualStats.max+1, residualStats.max - residualStats.min +1); + utils::Histogram residual_histogram = + utils::Histogram(residualStats.min, residualStats.max + 1, residualStats.max - residualStats.min + 1); residual_histogram.Add(residuals.begin(), residuals.end()); nbResidualsPerViewMin[viewIdx] = residualStats.min; @@ -358,16 +370,19 @@ void computeResidualsPerView(const sfmData::SfMData& sfmData, int& nbViews, std: nbResidualsPerViewFirstQuartile[viewIdx] = residualStats.firstQuartile; nbResidualsPerViewThirdQuartile[viewIdx] = residualStats.thirdQuartile; } - } -void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, int& nbViews, std::vector& nbObservationsLengthsPerViewMin, - std::vector& nbObservationsLengthsPerViewMax, std::vector& nbObservationsLengthsPerViewMean, - std::vector& nbObservationsLengthsPerViewMedian, std::vector& nbObservationsLengthsPerViewFirstQuartile, +void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, + int& nbViews, + std::vector& nbObservationsLengthsPerViewMin, + std::vector& nbObservationsLengthsPerViewMax, + std::vector& nbObservationsLengthsPerViewMean, + std::vector& nbObservationsLengthsPerViewMedian, + std::vector& nbObservationsLengthsPerViewFirstQuartile, std::vector& nbObservationsLengthsPerViewThirdQuartile) { - if(sfmData.getLandmarks().empty()) - return; + if (sfmData.getLandmarks().empty()) + return; nbViews = sfmData.getViews().size(); nbObservationsLengthsPerViewMin.resize(nbViews); @@ -380,26 +395,27 @@ void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, int& nbV // Collect observations length (number of 2D observations per 3D points) of all landmarks visible in each view std::map> observationLengthsPerView; - for(const auto& landmark : sfmData.getLandmarks()) + for (const auto& landmark : sfmData.getLandmarks()) { - const aliceVision::sfmData::Observations & observations = landmark.second.observations; - for(const auto& obs: observations) + const aliceVision::sfmData::Observations& observations = landmark.second.observations; + for (const auto& obs : observations) { observationLengthsPerView[obs.first].push_back(observations.size()); } } std::vector viewKeys; - for(const auto& v: sfmData.getViews()) + for (const auto& v : sfmData.getViews()) viewKeys.push_back(v.first); - #pragma omp parallel for - for(int viewIdx = 0; viewIdx < nbViews; ++viewIdx) +#pragma omp parallel for + for (int viewIdx = 0; viewIdx < nbViews; ++viewIdx) { const IndexT viewId = viewKeys[viewIdx]; const std::vector& nbObservations = observationLengthsPerView[viewId]; BoxStats observationsLengthsStats(nbObservations.begin(), nbObservations.end()); - utils::Histogram observationsLengths_histogram(observationsLengthsStats.min, observationsLengthsStats.max + 1, observationsLengthsStats.max - observationsLengthsStats.min + 1); + utils::Histogram observationsLengths_histogram( + observationsLengthsStats.min, observationsLengthsStats.max + 1, observationsLengthsStats.max - observationsLengthsStats.min + 1); observationsLengths_histogram.Add(nbObservations.begin(), nbObservations.end()); nbObservationsLengthsPerViewMin[viewIdx] = observationsLengthsStats.min; @@ -409,8 +425,7 @@ void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, int& nbV nbObservationsLengthsPerViewFirstQuartile[viewIdx] = observationsLengthsStats.firstQuartile; nbObservationsLengthsPerViewThirdQuartile[viewIdx] = observationsLengthsStats.thirdQuartile; } - } -} -} +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/sfmStatistics.hpp b/src/aliceVision/sfm/sfmStatistics.hpp index 6483137ea1..afe0af0cec 100644 --- a/src/aliceVision/sfm/sfmStatistics.hpp +++ b/src/aliceVision/sfm/sfmStatistics.hpp @@ -14,7 +14,6 @@ #include - namespace aliceVision { namespace sfm { @@ -25,7 +24,10 @@ namespace sfm { * @param[out] out_histogram : histogram of the number of points for each residual value (0-4 px) * @param[in] specificViews: Limit stats to specific views. If empty, compute stats for all views. */ -void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram, const std::set& specificViews = std::set()); +void computeResidualsHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + utils::Histogram* out_histogram, + const std::set& specificViews = std::set()); /** * @brief Compute histogram of observations lengths @@ -34,7 +36,11 @@ void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats * @param[out] observationsLengthHistogram : histogram of the number of points for each observation length * @param[in] specificViews: Limit stats to specific views. If empty, compute stats for all views */ -void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, int& overallNbObservations, utils::Histogram* observationsLengthHistogram, const std::set& specificViews = std::set()); +void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + int& overallNbObservations, + utils::Histogram* observationsLengthHistogram, + const std::set& specificViews = std::set()); /** * @brief Compute histogram of the number of landmarks per view @@ -42,7 +48,9 @@ void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxSta * @param[out] out_stats: stats containing the landmarks * @param[out] landmarksPerViewHistogram: histogram of the number of landmarks for each view */ -void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* landmarksPerViewHistogram); +void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + utils::Histogram* landmarksPerViewHistogram); /** * @brief Compute landmarks per view @@ -66,7 +74,10 @@ void computeFeatMatchPerView(const sfmData::SfMData& sfmData, std::vector& out_stats, utils::Histogram* scaleHistogram, const std::set& specificViews = std::set()); +void computeScaleHistogram(const sfmData::SfMData& sfmData, + BoxStats& out_stats, + utils::Histogram* scaleHistogram, + const std::set& specificViews = std::set()); /** * @brief Compute different stats of residuals per view @@ -79,10 +90,14 @@ void computeScaleHistogram(const sfmData::SfMData& sfmData, BoxStats& ou * @param[out] nbResidualsPerViewFirstQuartile: vector containing the first quartile residuals values * @param[out] nbResidualsPerViewThirdQuartile: vector containing the third quartile residuals values */ -void computeResidualsPerView(const sfmData::SfMData& sfmData, int& nbViews, std::vector& nbResidualsPerViewMin, - std::vector& nbResidualsPerViewMax, std::vector& nbResidualsPerViewMean, - std::vector& nbResidualsPerViewMedian, std::vector& nbResidualsPerViewFirstQuartile, - std::vector& nbResidualsPerViewThirdQuartile); +void computeResidualsPerView(const sfmData::SfMData& sfmData, + int& nbViews, + std::vector& nbResidualsPerViewMin, + std::vector& nbResidualsPerViewMax, + std::vector& nbResidualsPerViewMean, + std::vector& nbResidualsPerViewMedian, + std::vector& nbResidualsPerViewFirstQuartile, + std::vector& nbResidualsPerViewThirdQuartile); /** * @brief Compute different stats of observations lengths per view @@ -95,11 +110,14 @@ void computeResidualsPerView(const sfmData::SfMData& sfmData, int& nbViews, std: * @param[out] nbResidualsPerViewFirstQuartile: vector containing the first quartile observations lengths * @param[out] nbResidualsPerViewThirdQuartile: vector containing the third quartile observations lengths */ -void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, int& nbViews, std::vector& nbObservationsLengthsPerViewMin, - std::vector& nbObservationsLengthsPerViewMax, std::vector& nbObservationsLengthsPerViewMean, - std::vector& nbObservationsLengthsPerViewMedian, std::vector& nbResidualsPerViewFirstQuartile, - std::vector& nbResidualsPerViewThirdQuartile); - -} -} +void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, + int& nbViews, + std::vector& nbObservationsLengthsPerViewMin, + std::vector& nbObservationsLengthsPerViewMax, + std::vector& nbObservationsLengthsPerViewMean, + std::vector& nbObservationsLengthsPerViewMedian, + std::vector& nbResidualsPerViewFirstQuartile, + std::vector& nbResidualsPerViewThirdQuartile); +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/sfmTriangulation.cpp b/src/aliceVision/sfm/sfmTriangulation.cpp index 2158d755cc..17fca92341 100644 --- a/src/aliceVision/sfm/sfmTriangulation.cpp +++ b/src/aliceVision/sfm/sfmTriangulation.cpp @@ -28,128 +28,123 @@ StructureComputation_blind::StructureComputation_blind(bool verbose) : StructureComputation_basis(verbose) {} -void StructureComputation_blind::triangulate(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const +void StructureComputation_blind::triangulate(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const { - std::deque rejectedId; - system::ProgressDisplay progressDisplay; - if (_bConsoleVerbose) - progressDisplay = system::createConsoleProgressDisplay(sfmData.getLandmarks().size(), std::cout, - "Blind triangulation progress:\n"); + std::deque rejectedId; + system::ProgressDisplay progressDisplay; + if (_bConsoleVerbose) + progressDisplay = system::createConsoleProgressDisplay(sfmData.getLandmarks().size(), std::cout, "Blind triangulation progress:\n"); - #pragma omp parallel - for(sfmData::Landmarks::iterator iterTracks = sfmData.getLandmarks().begin(); - iterTracks != sfmData.getLandmarks().end(); - ++iterTracks) - { - #pragma omp single nowait +#pragma omp parallel + for (sfmData::Landmarks::iterator iterTracks = sfmData.getLandmarks().begin(); iterTracks != sfmData.getLandmarks().end(); ++iterTracks) { - if (_bConsoleVerbose) - { - ++(progressDisplay); - } - // Triangulate each landmark - multiview::Triangulation trianObj; - const sfmData::Observations & observations = iterTracks->second.observations; - for(const auto& itObs : observations) - { - const sfmData::View * view = sfmData.getViews().at(itObs.first).get(); - if (sfmData.isPoseAndIntrinsicDefined(view)) +#pragma omp single nowait { - std::shared_ptr cam = sfmData.getIntrinsics().at(view->getIntrinsicId()); - std::shared_ptr pinHoleCam = std::dynamic_pointer_cast(cam); - if (!pinHoleCam) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate"); - continue; - } + if (_bConsoleVerbose) + { + ++(progressDisplay); + } + // Triangulate each landmark + multiview::Triangulation trianObj; + const sfmData::Observations& observations = iterTracks->second.observations; + for (const auto& itObs : observations) + { + const sfmData::View* view = sfmData.getViews().at(itObs.first).get(); + if (sfmData.isPoseAndIntrinsicDefined(view)) + { + std::shared_ptr cam = sfmData.getIntrinsics().at(view->getIntrinsicId()); + std::shared_ptr pinHoleCam = std::dynamic_pointer_cast(cam); + if (!pinHoleCam) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in triangulate"); + continue; + } - const Pose3 pose = sfmData.getPose(*view).getTransform(); - trianObj.add( - pinHoleCam->getProjectiveEquivalent(pose), - cam->get_ud_pixel(itObs.second.x)); - } - } - if (trianObj.size() < 2) - { - #pragma omp critical - { - rejectedId.push_front(iterTracks->first); - } - } - else - { - // Compute the 3D point - const Vec3 X = trianObj.compute(); - if (trianObj.minDepth() > 0) // Keep the point only if it have a positive depth - { - iterTracks->second.X = X; + const Pose3 pose = sfmData.getPose(*view).getTransform(); + trianObj.add(pinHoleCam->getProjectiveEquivalent(pose), cam->get_ud_pixel(itObs.second.x)); + } + } + if (trianObj.size() < 2) + { +#pragma omp critical + { + rejectedId.push_front(iterTracks->first); + } + } + else + { + // Compute the 3D point + const Vec3 X = trianObj.compute(); + if (trianObj.minDepth() > 0) // Keep the point only if it have a positive depth + { + iterTracks->second.X = X; + } + else + { +#pragma omp critical + { + rejectedId.push_front(iterTracks->first); + } + } + } } - else - { - #pragma omp critical - { - rejectedId.push_front(iterTracks->first); - } - } - } } - } - // Erase the unsuccessful triangulated tracks - for (auto& it : rejectedId) - { - sfmData.getLandmarks().erase(it); - } + // Erase the unsuccessful triangulated tracks + for (auto& it : rejectedId) + { + sfmData.getLandmarks().erase(it); + } } StructureComputation_robust::StructureComputation_robust(bool verbose) : StructureComputation_basis(verbose) {} -void StructureComputation_robust::triangulate(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const +void StructureComputation_robust::triangulate(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const { - robust_triangulation(sfmData, randomNumberGenerator); + robust_triangulation(sfmData, randomNumberGenerator); } /// Robust triangulation of track data contained in the structure /// All observations must have View with valid Intrinsic and Pose data /// Invalid landmark are removed. -void StructureComputation_robust::robust_triangulation(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const +void StructureComputation_robust::robust_triangulation(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const { - std::deque rejectedId; + std::deque rejectedId; - system::ProgressDisplay progressDisplay; - if (_bConsoleVerbose) - progressDisplay = system::createConsoleProgressDisplay(sfmData.getLandmarks().size(), std::cout, - "Robust triangulation progress:\n"); + system::ProgressDisplay progressDisplay; + if (_bConsoleVerbose) + progressDisplay = system::createConsoleProgressDisplay(sfmData.getLandmarks().size(), std::cout, "Robust triangulation progress:\n"); - #pragma omp parallel - for(sfmData::Landmarks::iterator iterTracks = sfmData.getLandmarks().begin(); - iterTracks != sfmData.getLandmarks().end(); - ++iterTracks) - { - #pragma omp single nowait +#pragma omp parallel + for (sfmData::Landmarks::iterator iterTracks = sfmData.getLandmarks().begin(); iterTracks != sfmData.getLandmarks().end(); ++iterTracks) { - if (_bConsoleVerbose) - { - ++(progressDisplay); - } - Vec3 X; - if (robust_triangulation(sfmData, iterTracks->second.observations, randomNumberGenerator, X)) { - iterTracks->second.X = X; - } - else { - iterTracks->second.X = Vec3::Zero(); - #pragma omp critical +#pragma omp single nowait { - rejectedId.push_front(iterTracks->first); + if (_bConsoleVerbose) + { + ++(progressDisplay); + } + Vec3 X; + if (robust_triangulation(sfmData, iterTracks->second.observations, randomNumberGenerator, X)) + { + iterTracks->second.X = X; + } + else + { + iterTracks->second.X = Vec3::Zero(); +#pragma omp critical + { + rejectedId.push_front(iterTracks->first); + } + } } - } } - } - // Erase the unsuccessful triangulated tracks - for(auto& it : rejectedId) - { - sfmData.getLandmarks().erase(it); - } + // Erase the unsuccessful triangulated tracks + for (auto& it : rejectedId) + { + sfmData.getLandmarks().erase(it); + } } /// Robustly try to estimate the best 3D point using a ransac Scheme @@ -157,116 +152,115 @@ void StructureComputation_robust::robust_triangulation(sfmData::SfMData& sfmData /// Return true for a successful triangulation bool StructureComputation_robust::robust_triangulation(const sfmData::SfMData& sfmData, const sfmData::Observations& observations, - std::mt19937 & randomNumberGenerator, + std::mt19937& randomNumberGenerator, Vec3& X, const IndexT min_required_inliers, const IndexT min_sample_index) const { - if (observations.size() < 3) - { - return false; - } + if (observations.size() < 3) + { + return false; + } - const double dThresholdPixel = 4.0; // TODO: make this parameter customizable + const double dThresholdPixel = 4.0; // TODO: make this parameter customizable - const IndexT nbIter = observations.size(); // TODO: automatic computation of the number of iterations? + const IndexT nbIter = observations.size(); // TODO: automatic computation of the number of iterations? - // - Ransac variables - Vec3 best_model; - std::set best_inlier_set; - double best_error = std::numeric_limits::max(); + // - Ransac variables + Vec3 best_model; + std::set best_inlier_set; + double best_error = std::numeric_limits::max(); - // - Ransac loop - for(IndexT i = 0; i < nbIter; ++i) - { - std::set samples; - robustEstimation::uniformSample(randomNumberGenerator, std::min(std::size_t(min_sample_index), observations.size()), observations.size(), samples); + // - Ransac loop + for (IndexT i = 0; i < nbIter; ++i) + { + std::set samples; + robustEstimation::uniformSample( + randomNumberGenerator, std::min(std::size_t(min_sample_index), observations.size()), observations.size(), samples); - // Hypothesis generation. - const Vec3 current_model = track_sample_triangulation(sfmData, observations, samples); + // Hypothesis generation. + const Vec3 current_model = track_sample_triangulation(sfmData, observations, samples); - // Test validity of the hypothesis - // - chierality (for the samples) - // - residual error + // Test validity of the hypothesis + // - chierality (for the samples) + // - residual error - // Chierality (Check the point is in front of the sampled cameras) - bool bChierality = true; + // Chierality (Check the point is in front of the sampled cameras) + bool bChierality = true; - for(auto& it : samples) - { - sfmData::Observations::const_iterator itObs = observations.begin(); - std::advance(itObs, it); - const sfmData::View * view = sfmData.getViews().at(itObs->first).get(); - const IntrinsicBase * cam = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); - const Pose3 pose = sfmData.getPose(*view).getTransform(); - const double z = pose.depth(current_model); // TODO: cam->depth(pose(X)); - bChierality &= z > 0; - } + for (auto& it : samples) + { + sfmData::Observations::const_iterator itObs = observations.begin(); + std::advance(itObs, it); + const sfmData::View* view = sfmData.getViews().at(itObs->first).get(); + const IntrinsicBase* cam = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); + const Pose3 pose = sfmData.getPose(*view).getTransform(); + const double z = pose.depth(current_model); // TODO: cam->depth(pose(X)); + bChierality &= z > 0; + } - if (!bChierality) - continue; + if (!bChierality) + continue; - std::set inlier_set; - double current_error = 0.0; - - // Classification as inlier/outlier according pixel residual errors. - for (const auto& itObs : observations) - { - const sfmData::View * view = sfmData.getViews().at(itObs.first).get(); - const IntrinsicBase * intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); - const Pose3 pose = sfmData.getPose(*view).getTransform(); - const Vec2 residual = intrinsic->residual(pose, current_model.homogeneous(), itObs.second.x); - const double residual_d = residual.norm(); + std::set inlier_set; + double current_error = 0.0; - if (residual_d < dThresholdPixel) - { - inlier_set.insert(itObs.first); - current_error += residual_d; - } - else - { - current_error += dThresholdPixel; - } - } - // Does the hypothesis is the best one we have seen and have sufficient inliers. - if (current_error < best_error && inlier_set.size() >= min_required_inliers) - { - X = best_model = current_model; - best_inlier_set = inlier_set; - best_error = current_error; + // Classification as inlier/outlier according pixel residual errors. + for (const auto& itObs : observations) + { + const sfmData::View* view = sfmData.getViews().at(itObs.first).get(); + const IntrinsicBase* intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()).get(); + const Pose3 pose = sfmData.getPose(*view).getTransform(); + const Vec2 residual = intrinsic->residual(pose, current_model.homogeneous(), itObs.second.x); + const double residual_d = residual.norm(); + + if (residual_d < dThresholdPixel) + { + inlier_set.insert(itObs.first); + current_error += residual_d; + } + else + { + current_error += dThresholdPixel; + } + } + // Does the hypothesis is the best one we have seen and have sufficient inliers. + if (current_error < best_error && inlier_set.size() >= min_required_inliers) + { + X = best_model = current_model; + best_inlier_set = inlier_set; + best_error = current_error; + } } - } - return !best_inlier_set.empty(); + return !best_inlier_set.empty(); } - /// Triangulate a given track from a selection of observations Vec3 StructureComputation_robust::track_sample_triangulation(const sfmData::SfMData& sfmData, const sfmData::Observations& observations, const std::set& samples) const { - multiview::Triangulation trianObj; - for (const IndexT idx : samples) - { - assert(idx < observations.size()); - sfmData::Observations::const_iterator itObs = observations.begin(); - std::advance(itObs, idx); - const sfmData::View * view = sfmData.getViews().at(itObs->first).get(); + multiview::Triangulation trianObj; + for (const IndexT idx : samples) + { + assert(idx < observations.size()); + sfmData::Observations::const_iterator itObs = observations.begin(); + std::advance(itObs, idx); + const sfmData::View* view = sfmData.getViews().at(itObs->first).get(); - std::shared_ptr cam = sfmData.getIntrinsics().at(view->getIntrinsicId()); - std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); - if (!camPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in filter"); - return Vec3(); - } + std::shared_ptr cam = sfmData.getIntrinsics().at(view->getIntrinsicId()); + std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); + if (!camPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in filter"); + return Vec3(); + } - const Pose3 pose = sfmData.getPose(*view).getTransform(); - trianObj.add( - camPinHole->getProjectiveEquivalent(pose), - cam->get_ud_pixel(itObs->second.x)); - } - return trianObj.compute(); + const Pose3 pose = sfmData.getPose(*view).getTransform(); + trianObj.add(camPinHole->getProjectiveEquivalent(pose), cam->get_ud_pixel(itObs->second.x)); + } + return trianObj.compute(); } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/sfmTriangulation.hpp b/src/aliceVision/sfm/sfmTriangulation.hpp index 5ddda9a83b..05ff09edbf 100644 --- a/src/aliceVision/sfm/sfmTriangulation.hpp +++ b/src/aliceVision/sfm/sfmTriangulation.hpp @@ -17,55 +17,54 @@ namespace sfm { /// in the SfMData scene structure. struct StructureComputation_basis { - bool _bConsoleVerbose; + bool _bConsoleVerbose; - StructureComputation_basis(bool verbose = false); + StructureComputation_basis(bool verbose = false); - virtual void triangulate(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const = 0; + virtual void triangulate(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const = 0; }; - /// Triangulation of track data contained in the structure of a SfMData scene. // Use a blind estimation: // - Triangulate tracks using all observations // - Inlier/Outlier classification is done by a cheirality test -struct StructureComputation_blind: public StructureComputation_basis +struct StructureComputation_blind : public StructureComputation_basis { - StructureComputation_blind(bool verbose = false); + StructureComputation_blind(bool verbose = false); - virtual void triangulate(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const; + virtual void triangulate(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const; }; /// Triangulation of track data contained in the structure of a SfMData scene. // Use a robust estimation: // - Triangulate tracks using a RANSAC scheme // - Check cheirality and a pixel residual error (TODO: make it a parameter) -struct StructureComputation_robust: public StructureComputation_basis +struct StructureComputation_robust : public StructureComputation_basis { - StructureComputation_robust(bool verbose = false); + StructureComputation_robust(bool verbose = false); - virtual void triangulate(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const; + virtual void triangulate(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const; - /// Robust triangulation of track data contained in the structure - /// All observations must have View with valid Intrinsic and Pose data - /// Invalid landmark are removed. - void robust_triangulation(sfmData::SfMData& sfmData, std::mt19937 & randomNumberGenerator) const; + /// Robust triangulation of track data contained in the structure + /// All observations must have View with valid Intrinsic and Pose data + /// Invalid landmark are removed. + void robust_triangulation(sfmData::SfMData& sfmData, std::mt19937& randomNumberGenerator) const; - /// Robustly try to estimate the best 3D point using a ransac Scheme - /// Return true for a successful triangulation - bool robust_triangulation(const sfmData::SfMData& sfmData, - const sfmData::Observations& observations, - std::mt19937 & randomNumberGenerator, - Vec3& X, - const IndexT min_required_inliers = 3, - const IndexT min_sample_index = 3) const; + /// Robustly try to estimate the best 3D point using a ransac Scheme + /// Return true for a successful triangulation + bool robust_triangulation(const sfmData::SfMData& sfmData, + const sfmData::Observations& observations, + std::mt19937& randomNumberGenerator, + Vec3& X, + const IndexT min_required_inliers = 3, + const IndexT min_sample_index = 3) const; -private: - /// Triangulate a given track from a selection of observations - Vec3 track_sample_triangulation(const sfmData::SfMData& sfmData, - const sfmData::Observations& observations, - const std::set& samples) const; + private: + /// Triangulate a given track from a selection of observations + Vec3 track_sample_triangulation(const sfmData::SfMData& sfmData, + const sfmData::Observations& observations, + const std::set& samples) const; }; -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/alignment.cpp b/src/aliceVision/sfm/utils/alignment.cpp index a2096ec20d..5116a7fbac 100644 --- a/src/aliceVision/sfm/utils/alignment.cpp +++ b/src/aliceVision/sfm/utils/alignment.cpp @@ -24,20 +24,18 @@ #include - namespace bacc = boost::accumulators; namespace aliceVision { namespace sfm { - std::istream& operator>>(std::istream& in, MarkerWithCoord& marker) { std::string token; in >> token; std::vector markerCoord; boost::split(markerCoord, token, boost::algorithm::is_any_of(":=")); - if(markerCoord.size() != 2) + if (markerCoord.size() != 2) throw std::invalid_argument("Failed to parse MarkerWithCoord from: " + token); marker.id = boost::lexical_cast(markerCoord.front()); @@ -60,12 +58,12 @@ std::ostream& operator<<(std::ostream& os, const MarkerWithCoord& marker) } bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::vector>& commonViewIds, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t) + const sfmData::SfMData& sfmDataB, + const std::vector>& commonViewIds, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t) { assert(out_S != nullptr); assert(out_R != nullptr); @@ -113,7 +111,8 @@ bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, randomNumberGenerator, S, t, R, inliers, true)) return false; - ALICEVISION_LOG_DEBUG("There are " << reconstructedCommonViewIds.size() << " common cameras and " << inliers.size() << " were used to compute the similarity transform."); + ALICEVISION_LOG_DEBUG("There are " << reconstructedCommonViewIds.size() << " common cameras and " << inliers.size() + << " were used to compute the similarity transform."); *out_S = S; *out_R = R; @@ -123,11 +122,11 @@ bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, } bool computeSimilarityFromCommonCameras_viewId(const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t) + const sfmData::SfMData& sfmDataB, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t) { assert(out_S != nullptr); assert(out_R != nullptr); @@ -145,13 +144,12 @@ bool computeSimilarityFromCommonCameras_viewId(const sfmData::SfMData& sfmDataA, return computeSimilarityFromCommonViews(sfmDataA, sfmDataB, commonViewIds_pairs, randomNumberGenerator, out_S, out_R, out_t); } -bool computeSimilarityFromCommonCameras_poseId( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::mt19937 & randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t) +bool computeSimilarityFromCommonCameras_poseId(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t) { assert(out_S != nullptr); assert(out_R != nullptr); @@ -192,7 +190,8 @@ bool computeSimilarityFromCommonCameras_poseId( if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, randomNumberGenerator, S, t, R, inliers, true)) return false; - ALICEVISION_LOG_DEBUG("There are " << commonPoseIds.size() << " common camera poses and " << inliers.size() << " were used to compute the similarity transform."); + ALICEVISION_LOG_DEBUG("There are " << commonPoseIds.size() << " common camera poses and " << inliers.size() + << " were used to compute the similarity transform."); *out_S = S; *out_R = R; @@ -201,10 +200,7 @@ bool computeSimilarityFromCommonCameras_poseId( return true; } - -std::map retrieveMatchingFilepath( - const sfmData::SfMData& sfmData, - const std::string& filePatternMatching) +std::map retrieveMatchingFilepath(const sfmData::SfMData& sfmData, const std::string& filePatternMatching) { std::set duplicates; std::map uniqueFileparts; @@ -222,7 +218,7 @@ std::map retrieveMatchingFilepath( std::smatch matches; if (std::regex_match(imagePath, matches, re)) { - for(int i = 1; i < matches.size(); ++i) + for (int i = 1; i < matches.size(); ++i) { const std::ssub_match& submatch = matches[i]; cumulatedValues += submatch.str(); @@ -247,11 +243,10 @@ std::map retrieveMatchingFilepath( return uniqueFileparts; } -void matchViewsByFilePattern( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::string& filePatternMatching, - std::vector>& out_commonViewIds) +void matchViewsByFilePattern(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + std::vector>& out_commonViewIds) { out_commonViewIds.clear(); std::map filepathValuesA = retrieveMatchingFilepath(sfmDataA, filePatternMatching); @@ -259,14 +254,12 @@ void matchViewsByFilePattern( using P = std::pair; std::vector

commonMetadataValues; - std::set_intersection( - filepathValuesA.begin(), filepathValuesA.end(), // already sorted - filepathValuesB.begin(), filepathValuesB.end(), // already sorted - std::back_inserter(commonMetadataValues), - [](const P& p1, const P& p2) { - return p1.first < p2.first; - } - ); + std::set_intersection(filepathValuesA.begin(), + filepathValuesA.end(), // already sorted + filepathValuesB.begin(), + filepathValuesB.end(), // already sorted + std::back_inserter(commonMetadataValues), + [](const P& p1, const P& p2) { return p1.first < p2.first; }); for (const P& p : commonMetadataValues) { @@ -274,15 +267,13 @@ void matchViewsByFilePattern( } } - -bool computeSimilarityFromCommonCameras_imageFileMatching( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::string& filePatternMatching, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t) +bool computeSimilarityFromCommonCameras_imageFileMatching(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t) { assert(out_S != nullptr); assert(out_R != nullptr); @@ -296,11 +287,7 @@ bool computeSimilarityFromCommonCameras_imageFileMatching( return computeSimilarityFromCommonViews(sfmDataA, sfmDataB, commonViewIds, randomNumberGenerator, out_S, out_R, out_t); } - - -std::map retrieveUniqueMetadataValues( - const sfmData::SfMData& sfmData, - const std::vector& metadataList) +std::map retrieveUniqueMetadataValues(const sfmData::SfMData& sfmData, const std::vector& metadataList) { std::set duplicates; std::map uniqueMetadataValues; @@ -325,18 +312,17 @@ std::map retrieveUniqueMetadataValues( uniqueMetadataValues[cumulatedValues] = viewIt.first; } } - for (const std::string& d: duplicates) + for (const std::string& d : duplicates) { uniqueMetadataValues.erase(d); } return uniqueMetadataValues; } -void matchViewsByMetadataMatching( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::vector& metadataList, - std::vector>& out_commonViewIds) +void matchViewsByMetadataMatching(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + std::vector>& out_commonViewIds) { out_commonViewIds.clear(); std::map metadataValuesA = retrieveUniqueMetadataValues(sfmDataA, metadataList); @@ -344,28 +330,25 @@ void matchViewsByMetadataMatching( using P = std::pair; std::vector

commonMetadataValues; - std::set_intersection( - metadataValuesA.begin(), metadataValuesA.end(), // already sorted - metadataValuesB.begin(), metadataValuesB.end(), // already sorted - std::back_inserter(commonMetadataValues), - [](const P& p1, const P& p2) { - return p1.first < p2.first; - } - ); + std::set_intersection(metadataValuesA.begin(), + metadataValuesA.end(), // already sorted + metadataValuesB.begin(), + metadataValuesB.end(), // already sorted + std::back_inserter(commonMetadataValues), + [](const P& p1, const P& p2) { return p1.first < p2.first; }); for (const P& p : commonMetadataValues) { out_commonViewIds.emplace_back(metadataValuesA.at(p.first), metadataValuesB.at(p.first)); } } -bool computeSimilarityFromCommonCameras_metadataMatching( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::vector& metadataList, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t) +bool computeSimilarityFromCommonCameras_metadataMatching(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t) { assert(out_S != nullptr); assert(out_R != nullptr); @@ -379,7 +362,6 @@ bool computeSimilarityFromCommonCameras_metadataMatching( return computeSimilarityFromCommonViews(sfmDataA, sfmDataB, commonViewIds, randomNumberGenerator, out_S, out_R, out_t); } - /** * @return map, landmarkId> */ @@ -399,7 +381,7 @@ std::map, IndexT> getUniqueMarkers( } else { - duplicates.insert(p); // multiple usages + duplicates.insert(p); // multiple usages } } } @@ -410,14 +392,12 @@ std::map, IndexT> getUniqueMarkers( return markers; } - -bool computeSimilarityFromCommonMarkers( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::mt19937 & randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t) +bool computeSimilarityFromCommonMarkers(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t) { assert(out_S != nullptr); assert(out_R != nullptr); @@ -428,14 +408,12 @@ bool computeSimilarityFromCommonMarkers( using P = std::pair, IndexT>; std::vector

commonMarkers; - std::set_intersection( - markers_A.begin(), markers_A.end(), // already sorted - markers_B.begin(), markers_B.end(), // already sorted - std::back_inserter(commonMarkers), - [](const P& p1, const P& p2) { - return p1.first < p2.first; - } - ); + std::set_intersection(markers_A.begin(), + markers_A.end(), // already sorted + markers_B.begin(), + markers_B.end(), // already sorted + std::back_inserter(commonMarkers), + [](const P& p1, const P& p2) { return p1.first < p2.first; }); ALICEVISION_LOG_DEBUG("Found " << commonMarkers.size() << " common markers."); if (commonMarkers.empty()) @@ -480,7 +458,8 @@ bool computeSimilarityFromCommonMarkers( if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, randomNumberGenerator, S, t, R, inliers, true)) return false; - ALICEVISION_LOG_DEBUG("There are " << commonLandmarks.size() << " common markers and " << inliers.size() << " were used to compute the similarity transform."); + ALICEVISION_LOG_DEBUG("There are " << commonLandmarks.size() << " common markers and " << inliers.size() + << " were used to compute the similarity transform."); *out_S = S; *out_R = R; @@ -494,25 +473,21 @@ bool computeSimilarityFromCommonMarkers( */ double orientationToRotationDegree(sfmData::EEXIFOrientation orientation) { - switch(orientation) + switch (orientation) { - case sfmData::EEXIFOrientation::RIGHT: // 8 - return 90.0; // CCW - case sfmData::EEXIFOrientation::LEFT: // 6 - return 270.0; // CCW - case sfmData::EEXIFOrientation::UPSIDEDOWN: // 3 + case sfmData::EEXIFOrientation::RIGHT: // 8 + return 90.0; // CCW + case sfmData::EEXIFOrientation::LEFT: // 6 + return 270.0; // CCW + case sfmData::EEXIFOrientation::UPSIDEDOWN: // 3 return 180.0; case sfmData::EEXIFOrientation::NONE: - default: - return 0.0; + default: return 0.0; } return 0.0; } -void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, - double& out_S, - Mat3& out_R, - Vec3& out_t) +void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, double& out_S, Mat3& out_R, Vec3& out_t) { out_S = 1.0; out_R = Mat3::Identity(); @@ -525,11 +500,11 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, Eigen::Vector3d meanRy = Eigen::Vector3d::Zero(); std::size_t validPoses = 0; - for(auto& viewIt : sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { const sfmData::View& view = *viewIt.second.get(); - if(sfmData.isPoseAndIntrinsicDefined(&view)) + if (sfmData.isPoseAndIntrinsicDefined(&view)) { const sfmData::EEXIFOrientation orientation = view.getImage().getMetadataOrientation(); const sfmData::CameraPose camPose = sfmData.getPose(view); @@ -537,13 +512,13 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, // Rotation of image Mat3 R_image = Eigen::AngleAxisd(degreeToRadian(orientationToRotationDegree(orientation)), Vec3(0, 0, 1)).toRotationMatrix(); - + Eigen::Vector3d oriented_X = R_image * Eigen::Vector3d::UnitX(); Eigen::Vector3d oriented_Y = R_image * Eigen::Vector3d::UnitY(); - //The X direction is in the "viewed" image - //If we use the raw X, it will be in the image without the orientation - //We need to use this orientation to make sure the X spans the horizontal plane. + // The X direction is in the "viewed" image + // If we use the raw X, it will be in the image without the orientation + // We need to use this orientation to make sure the X spans the horizontal plane. const Eigen::Vector3d rX = p.rotation().transpose() * oriented_X; const Eigen::Vector3d rY = p.rotation().transpose() * oriented_Y; @@ -554,7 +529,7 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, ++validPoses; } } - if(validPoses == 0) + if (validPoses == 0) { return; } @@ -567,11 +542,11 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, double rms = 0.0; // Compute covariance matrix of the rotation X component Eigen::Matrix3d C = Eigen::Matrix3d::Zero(); - for(auto& viewIt : sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { const sfmData::View& view = *viewIt.second.get(); - if(sfmData.isPoseAndIntrinsicDefined(&view)) + if (sfmData.isPoseAndIntrinsicDefined(&view)) { const sfmData::EEXIFOrientation orientation = view.getImage().getMetadataOrientation(); const sfmData::CameraPose camPose = sfmData.getPose(view); @@ -593,17 +568,17 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, } Eigen::EigenSolver solver(C, true); - - //Warning, eigenvalues are not sorted ... + + // Warning, eigenvalues are not sorted ... Vec3 evalues = solver.eigenvalues().real(); Vec3 aevalues = evalues.cwiseAbs(); - + std::vector indices(evalues.size()); std::iota(indices.begin(), indices.end(), 0); std::sort(indices.begin(), indices.end(), [&](int i, int j) { return evalues[i] < evalues[j]; }); int minCol = indices[0]; - //Make sure we have a clear unique small eigen value + // Make sure we have a clear unique small eigen value double ratio1 = evalues[indices[2]] / evalues[indices[0]]; double ratio2 = evalues[indices[2]] / evalues[indices[1]]; double unicity = ratio1 / ratio2; @@ -612,12 +587,12 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, ALICEVISION_LOG_DEBUG("computeNewCoordinateSystemFromCamerasXAxis: eigenvalues: " << solver.eigenvalues()); ALICEVISION_LOG_DEBUG("computeNewCoordinateSystemFromCamerasXAxis: eigenvectors: " << solver.eigenvectors()); ALICEVISION_LOG_DEBUG("computeNewCoordinateSystemFromCamerasXAxis: unicity: " << unicity); - ALICEVISION_LOG_DEBUG("computeNewCoordinateSystemFromCamerasXAxis: largest eigen value: " << largest); + ALICEVISION_LOG_DEBUG("computeNewCoordinateSystemFromCamerasXAxis: largest eigen value: " << largest); // We assume that the X axis of all or majority of the cameras are on a plane. // The covariance is a flat ellipsoid and the min axis is our candidate Y axis. Eigen::Vector3d nullestSpace = solver.eigenvectors().col(minCol).real(); - Eigen::Vector3d referenceAxis = Eigen::Vector3d::UnitY(); + Eigen::Vector3d referenceAxis = Eigen::Vector3d::UnitY(); if (std::abs(unicity) < 10.0 || largest < 1e-2) { @@ -631,47 +606,43 @@ void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, const bool inverseDirection = (d < 0.0); // We have an ambiguity on the Y direction, so if our Y axis is not aligned with the Y axis of the scene // we inverse the axis. - if(inverseDirection) + if (inverseDirection) { nullestSpace = -nullestSpace; out_R = Matrix3d(Quaterniond().setFromTwoVectors(nullestSpace, referenceAxis)); } - + out_S = 1.0; out_t = -out_R * meanCameraCenter; } -void computeNewCoordinateSystemFromCameras(const sfmData::SfMData& sfmData, - double& out_S, - Mat3& out_R, - Vec3& out_t) +void computeNewCoordinateSystemFromCameras(const sfmData::SfMData& sfmData, double& out_S, Mat3& out_R, Vec3& out_t) { const std::size_t nbCameras = sfmData.getPoses().size(); - Mat3X vCamCenter(3,nbCameras); + Mat3X vCamCenter(3, nbCameras); // Compute the mean of the point cloud Vec3 meanCameraCenter = Vec3::Zero(); Vec3::Index ncol = 0; - for (const auto & pose : sfmData.getPoses()) + for (const auto& pose : sfmData.getPoses()) { const Vec3 center = pose.second.getTransform().center(); vCamCenter.col(ncol) = center; - meanCameraCenter += center; + meanCameraCenter += center; ++ncol; } meanCameraCenter /= nbCameras; - // Compute standard deviation double stddev = 0; for (Vec3::Index i = 0; i < vCamCenter.cols(); ++i) { Vec3 camCenterMean = vCamCenter.col(i) - meanCameraCenter; - stddev += camCenterMean.transpose() * camCenterMean; + stddev += camCenterMean.transpose() * camCenterMean; } stddev /= nbCameras; - if (stddev < 1e-12) //If there is no translation change + if (stddev < 1e-12) // If there is no translation change { stddev = 1.0; } @@ -694,12 +665,12 @@ void computeNewCoordinateSystemFromCameras(const sfmData::SfMData& sfmData, } // We want ideal normal to be the Y axis - out_R = Matrix3d(Quaterniond().setFromTwoVectors(n, Eigen::Vector3d::UnitY())); + out_R = Matrix3d(Quaterniond().setFromTwoVectors(n, Eigen::Vector3d::UnitY())); out_S = 1.0 / sqrt(stddev); - out_t = - out_S * out_R * meanCameraCenter; + out_t = -out_S * out_R * meanCameraCenter; } -IndexT getViewIdFromExpression(const sfmData::SfMData& sfmData, const std::string & camName) +IndexT getViewIdFromExpression(const sfmData::SfMData& sfmData, const std::string& camName) { IndexT viewId = -1; @@ -709,9 +680,9 @@ IndexT getViewIdFromExpression(const sfmData::SfMData& sfmData, const std::strin { viewId = boost::lexical_cast(camName); if (!sfmData.getViews().count(viewId)) - { + { bool found = false; - //check if this view is an ancestor of a view + // check if this view is an ancestor of a view for (auto pv : sfmData.getViews()) { for (auto ancestor : pv.second->getAncestors()) @@ -736,17 +707,17 @@ IndexT getViewIdFromExpression(const sfmData::SfMData& sfmData, const std::strin } } } - catch(const boost::bad_lexical_cast &) + catch (const boost::bad_lexical_cast&) { viewId = -1; } - if(viewId == -1) + if (viewId == -1) { - for(const auto & view : sfmData.getViews()) + for (const auto& view : sfmData.getViews()) { const std::string path = view.second->getImage().getImagePath(); - if(std::regex_match(path, cameraRegex)) + if (std::regex_match(path, cameraRegex)) { viewId = view.second->getViewId(); break; @@ -754,10 +725,10 @@ IndexT getViewIdFromExpression(const sfmData::SfMData& sfmData, const std::strin } } - if(viewId == -1) - throw std::invalid_argument("The camera name \"" + camName + "\" is not found in the sfmData."); - else if(!sfmData.isPoseAndIntrinsicDefined(viewId)) - throw std::invalid_argument("The camera \"" + camName + "\" exists in the sfmData but is not reconstructed."); + if (viewId == -1) + throw std::invalid_argument("The camera name \"" + camName + "\" is not found in the sfmData."); + else if (!sfmData.isPoseAndIntrinsicDefined(viewId)) + throw std::invalid_argument("The camera \"" + camName + "\" exists in the sfmData but is not reconstructed."); return viewId; } @@ -767,7 +738,7 @@ IndexT getCenterCameraView(const sfmData::SfMData& sfmData) using namespace boost::accumulators; accumulator_set> accX, accY, accZ; - for(auto& pose: sfmData.getPoses()) + for (auto& pose : sfmData.getPoses()) { const auto& c = pose.second.getTransform().center(); accX(c(0)); @@ -778,15 +749,15 @@ IndexT getCenterCameraView(const sfmData::SfMData& sfmData) double minDist = std::numeric_limits::max(); IndexT centerViewId = UndefinedIndexT; - for(auto& viewIt: sfmData.getViews()) + for (auto& viewIt : sfmData.getViews()) { const sfmData::View& v = *viewIt.second; - if(!sfmData.isPoseAndIntrinsicDefined(&v)) + if (!sfmData.isPoseAndIntrinsicDefined(&v)) continue; const auto& pose = sfmData.getPose(v); const double dist = (pose.getTransform().center() - camerasCenter).norm(); - if(dist < minDist) + if (dist < minDist) { minDist = dist; centerViewId = v.getViewId(); @@ -809,23 +780,22 @@ void computeNewCoordinateSystemFromSingleCamera(const sfmData::SfMData& sfmData, } void computeNewCoordinateSystemFromLandmarks(const sfmData::SfMData& sfmData, - const std::vector& imageDescriberTypes, - double& out_S, - Mat3& out_R, - Vec3& out_t) + const std::vector& imageDescriberTypes, + double& out_S, + Mat3& out_R, + Vec3& out_t) { Mat3X vX(3, sfmData.getLandmarks().size()); std::size_t landmarksCount = 0; - Vec3 meanPoints = Vec3::Zero(3,1); + Vec3 meanPoints = Vec3::Zero(3, 1); std::size_t nbMeanLandmarks = 0; - for(const auto& landmark : sfmData.getLandmarks()) + for (const auto& landmark : sfmData.getLandmarks()) { - if(!imageDescriberTypes.empty() && - std::find(imageDescriberTypes.begin(), imageDescriberTypes.end(), - landmark.second.descType) == imageDescriberTypes.end()) + if (!imageDescriberTypes.empty() && + std::find(imageDescriberTypes.begin(), imageDescriberTypes.end(), landmark.second.descType) == imageDescriberTypes.end()) { continue; } @@ -843,7 +813,7 @@ void computeNewCoordinateSystemFromLandmarks(const sfmData::SfMData& sfmData, AccumulatorMax accDist(tag::tail::cache_size = cacheSize); // Center the point cloud in [0;0;0] - for(int i = 0; i < landmarksCount; ++i) + for (int i = 0; i < landmarksCount; ++i) { vX.col(i) -= meanPoints; accDist(vX.col(i).norm()); @@ -851,12 +821,12 @@ void computeNewCoordinateSystemFromLandmarks(const sfmData::SfMData& sfmData, // Perform an svd over vX*vXT (var-covar) const Mat3 dum = vX.leftCols(nbMeanLandmarks) * vX.leftCols(nbMeanLandmarks).transpose(); - Eigen::JacobiSVD svd(dum,Eigen::ComputeFullV|Eigen::ComputeFullU); + Eigen::JacobiSVD svd(dum, Eigen::ComputeFullV | Eigen::ComputeFullU); Mat3 U = svd.matrixU(); // Check whether the determinant is negative in order to keep // a direct coordinate system - if(U.determinant() < 0) + if (U.determinant() < 0) { U.col(2) = -U.col(2); } @@ -865,28 +835,26 @@ void computeNewCoordinateSystemFromLandmarks(const sfmData::SfMData& sfmData, out_S = (distMax > 0.00001 ? 1.0 / distMax : 1.0); out_R = U.transpose(); - out_R = Eigen::AngleAxisd(degreeToRadian(90.0), Vec3(1,0,0)) * out_R; - out_t = - out_S * out_R * meanPoints; + out_R = Eigen::AngleAxisd(degreeToRadian(90.0), Vec3(1, 0, 0)) * out_R; + out_t = -out_S * out_R * meanPoints; } - bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmData, - const feature::EImageDescriberType& imageDescriberType, - const std::vector& markers, - bool withScaling, - double& out_S, - Mat3& out_R, - Vec3& out_t - ) + const feature::EImageDescriberType& imageDescriberType, + const std::vector& markers, + bool withScaling, + double& out_S, + Mat3& out_R, + Vec3& out_t) { std::vector landmarksIds(markers.size(), -1); int maxLandmarkIdx = 0; for (const auto& landmarkIt : sfmData.getLandmarks()) { - if(landmarkIt.first > maxLandmarkIdx) + if (landmarkIt.first > maxLandmarkIdx) maxLandmarkIdx = landmarkIt.first; - if(landmarkIt.second.descType != imageDescriberType) + if (landmarkIt.second.descType != imageDescriberType) continue; for (int i = 0; i < markers.size(); ++i) { @@ -928,9 +896,11 @@ bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmDa return geometry::decomposeRTS(RTS, out_S, out_t, out_R); } - - -bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, std::mt19937 &randomNumberGenerator, double& out_S, Mat3& out_R, Vec3& out_t) +bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, + std::mt19937& randomNumberGenerator, + double& out_S, + Mat3& out_R, + Vec3& out_t) { std::vector gpsPositions{}; std::vector centers{}; @@ -938,14 +908,15 @@ bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, std: centers.reserve(sfmData.getPoses().size()); // for each reconstructed view - for(const auto& v : sfmData.getViews()) + for (const auto& v : sfmData.getViews()) { const auto viewID = v.first; const auto& view = v.second; // skip no pose - if(!(sfmData.isPoseAndIntrinsicDefined(viewID) && view->getImage().hasGpsMetadata())) + if (!(sfmData.isPoseAndIntrinsicDefined(viewID) && view->getImage().hasGpsMetadata())) { - ALICEVISION_LOG_TRACE("Skipping view " << viewID << " because pose " << sfmData.isPoseAndIntrinsicDefined(viewID) << " and gps " << view->getImage().hasGpsMetadata()); + ALICEVISION_LOG_TRACE("Skipping view " << viewID << " because pose " << sfmData.isPoseAndIntrinsicDefined(viewID) << " and gps " + << view->getImage().hasGpsMetadata()); continue; } // extract the gps position @@ -955,7 +926,7 @@ bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, std: } // if enough data try to find the transformation - if(gpsPositions.size() < 4) + if (gpsPositions.size() < 4) { ALICEVISION_LOG_INFO("Not enough points to estimate the rototranslation to align the gps"); return false; @@ -964,7 +935,7 @@ bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, std: Mat x1(3, centers.size()); Mat x2(3, gpsPositions.size()); - for(Mat::Index i = 0; i < gpsPositions.size(); ++i) + for (Mat::Index i = 0; i < gpsPositions.size(); ++i) { x1.col(i) = centers[i]; x2.col(i) = gpsPositions[i]; @@ -975,13 +946,13 @@ bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, std: return aliceVision::geometry::ACRansac_FindRTS(x1, x2, randomNumberGenerator, out_S, out_t, out_R, inliers, refine); } -void getRotationNullifyX(Eigen::Matrix3d & out_R, const Eigen::Matrix3d & R) +void getRotationNullifyX(Eigen::Matrix3d& out_R, const Eigen::Matrix3d& R) { Eigen::Vector3d alignmentVector = R.transpose() * Eigen::Vector3d::UnitZ(); getRotationNullifyX(out_R, alignmentVector); } -void getRotationNullifyX(Eigen::Matrix3d & out_R, const Eigen::Vector3d & pt) +void getRotationNullifyX(Eigen::Matrix3d& out_R, const Eigen::Vector3d& pt) { /* 0 = [cos(x) 0 -sin(x)][X] @@ -993,9 +964,9 @@ void getRotationNullifyX(Eigen::Matrix3d & out_R, const Eigen::Vector3d & pt) tan(x) = X/Z x = atan2(X, Z) */ - + double angle = std::atan2(pt(0), pt(2)); - out_R = Eigen::AngleAxisd(angle, Vec3(0,-1,0)).toRotationMatrix(); + out_R = Eigen::AngleAxisd(angle, Vec3(0, -1, 0)).toRotationMatrix(); } Vec3 computeCameraCentersMean(const sfmData::SfMData& sfmData) @@ -1003,7 +974,7 @@ Vec3 computeCameraCentersMean(const sfmData::SfMData& sfmData) // Compute the mean of the point cloud Vec3 center = Vec3::Zero(); size_t count = 0; - const auto & poses = sfmData.getPoses(); + const auto& poses = sfmData.getPoses(); for (auto v : sfmData.getViews()) { @@ -1013,9 +984,9 @@ Vec3 computeCameraCentersMean(const sfmData::SfMData& sfmData) } const IndexT poseId = v.second->getPoseId(); - const auto & pose = poses.at(poseId); + const auto& pose = poses.at(poseId); - center += pose.getTransform().center(); + center += pose.getTransform().center(); count++; } @@ -1023,11 +994,11 @@ Vec3 computeCameraCentersMean(const sfmData::SfMData& sfmData) return center; } -void computeCentersVarCov(const sfmData::SfMData& sfmData, const Vec3 & mean, Eigen::Matrix3d & varCov, size_t & count) +void computeCentersVarCov(const sfmData::SfMData& sfmData, const Vec3& mean, Eigen::Matrix3d& varCov, size_t& count) { // Compute the mean of the point cloud varCov = Eigen::Matrix3d::Zero(); - const auto & poses = sfmData.getPoses(); + const auto& poses = sfmData.getPoses(); count = 0; for (auto v : sfmData.getViews()) @@ -1038,9 +1009,9 @@ void computeCentersVarCov(const sfmData::SfMData& sfmData, const Vec3 & mean, Ei } const IndexT poseId = v.second->getPoseId(); - const auto & pose = poses.at(poseId); + const auto& pose = poses.at(poseId); - Vec3 centered = pose.getTransform().center() - mean; + Vec3 centered = pose.getTransform().center() - mean; varCov += centered * centered.transpose(); count++; @@ -1054,7 +1025,7 @@ void computeNewCoordinateSystemGroundAuto(const sfmData::SfMData& sfmData, Vec3& // Collect landmark positions for ground detection // Note: the Y axis is pointing down, therefore +Y is the direction to the ground std::vector points; - for (auto & plandmark: sfmData.getLandmarks()) + for (auto& plandmark : sfmData.getLandmarks()) { // Filter out landmarks with not enough observations if (plandmark.second.observations.size() < 3) @@ -1066,7 +1037,7 @@ void computeNewCoordinateSystemGroundAuto(const sfmData::SfMData& sfmData, Vec3& // This filtering step assumes that cameras should not be underneath the ground level const Vec3 X = plandmark.second.X; bool foundUnder = false; - for (const auto & pObs : plandmark.second.observations) + for (const auto& pObs : plandmark.second.observations) { const IndexT viewId = pObs.first; const Vec3 camCenter = sfmData.getPose(sfmData.getView(viewId)).getTransform().center(); @@ -1090,14 +1061,12 @@ void computeNewCoordinateSystemGroundAuto(const sfmData::SfMData& sfmData, Vec3& ALICEVISION_LOG_WARNING("Ground detection failed as there is no valid point"); return; } - + // Filter out statistical noise // and take lowest point as the ground level const double noiseRatio = 1e-4; std::size_t idxGround = static_cast(static_cast(points.size()) * noiseRatio); - std::nth_element( - points.begin(), points.begin() + idxGround, points.end(), - [](const Vec3 & pt1, const Vec3 & pt2) { return (pt1(1) > pt2(1)); }); + std::nth_element(points.begin(), points.begin() + idxGround, points.end(), [](const Vec3& pt1, const Vec3& pt2) { return (pt1(1) > pt2(1)); }); const double Yground = points[idxGround](1); out_t = {0.0, -Yground, 0.0}; @@ -1105,19 +1074,17 @@ void computeNewCoordinateSystemGroundAuto(const sfmData::SfMData& sfmData, Vec3& void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out_S, Mat3& out_R, Vec3& out_t) { - //For reference, the update is - //landmark.second.X = S * R * landmark.second.X + t; - //pose._center = S * R * _center + t; + // For reference, the update is + // landmark.second.X = S * R * landmark.second.X + t; + // pose._center = S * R * _center + t; - //Align with Xaxis, only modify out_R + // Align with Xaxis, only modify out_R sfm::computeNewCoordinateSystemFromCamerasXAxis(sfmData, out_S, out_R, out_t); - ALICEVISION_LOG_INFO("X axis rotation:" << std::endl - << out_R - ); + ALICEVISION_LOG_INFO("X axis rotation:" << std::endl << out_R); + + // Compute camera statistics - //Compute camera statistics - Eigen::Matrix3d covCamBase; size_t count; const Vec3 mean = computeCameraCentersMean(sfmData); @@ -1125,7 +1092,7 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out ALICEVISION_LOG_INFO("Initial point cloud center: " << mean.transpose()); - //By default, scale to get unit rms + // By default, scale to get unit rms const double rms = sqrt(covCamBase.trace() / double(count)); out_S = 1.0 / rms; if (rms < 1e-12) @@ -1135,15 +1102,15 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out ALICEVISION_LOG_INFO("Initial point cloud scale: " << rms); - //By default, center all the camera such that their mean is 0 - out_t = - out_S * out_R * mean; + // By default, center all the camera such that their mean is 0 + out_t = -out_S * out_R * mean; - //Get pairs of gps/camera positions + // Get pairs of gps/camera positions const size_t minimalGpsMeasuresCount = 10; const double gpsVariance = 4.0; const double distVariance = gpsVariance * 2.0; - const auto & poses = sfmData.getPoses(); + const auto& poses = sfmData.getPoses(); std::list> list_pairs; for (const auto v : sfmData.getViews()) { @@ -1158,7 +1125,7 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out } const IndexT poseId = v.second->getPoseId(); - const auto & pose = poses.at(poseId); + const auto& pose = poses.at(poseId); const Vec3 camCoordinates = pose.getTransform().center(); const Vec3 gpsCoordinates = v.second->getImage().getGpsPositionFromMetadata(); @@ -1175,13 +1142,14 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out if (list_pairs.size() < minimalGpsMeasuresCount) { ALICEVISION_LOG_INFO("Not enough GPS information available to use it " - "(GPS image pairs: " << list_pairs.size() << ", minimal GPS measures count: " << minimalGpsMeasuresCount << ")."); + "(GPS image pairs: " + << list_pairs.size() << ", minimal GPS measures count: " << minimalGpsMeasuresCount << ")."); return; } - + Vec3 camCoordinatesSum = Vec3::Zero(); Vec3 gpsCoordinatesSum = Vec3::Zero(); - for (const auto & pair : list_pairs) + for (const auto& pair : list_pairs) { camCoordinatesSum += pair.first; gpsCoordinatesSum += pair.second; @@ -1192,7 +1160,7 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out Eigen::Matrix3d camCov = Eigen::Matrix3d::Zero(); Eigen::Matrix3d gpsCov = Eigen::Matrix3d::Zero(); - for (const auto & pair : list_pairs) + for (const auto& pair : list_pairs) { Vec3 centeredCam = pair.first - camCoordinatesSum; camCov += centeredCam * centeredCam.transpose(); @@ -1200,21 +1168,22 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out gpsCov += centeredGps * centeredGps.transpose(); } - //Make sure that gps + // Make sure that gps const double var = gpsCov.trace(); if (var < distVariance) { ALICEVISION_LOG_INFO("Scene is too small to use GPS information " - "(dataset variance: " << var << ", min variance: " << distVariance << ")."); + "(dataset variance: " + << var << ", min variance: " << distVariance << ")."); return; } - + ALICEVISION_LOG_INFO("GPS point cloud scale: " << gpsCov.trace()); ALICEVISION_LOG_INFO("Linked cameras centers scale: " << camCov.trace()); out_S = sqrt(gpsCov.trace()) / sqrt(camCov.trace()); - out_t = - out_S * out_R * mean; + out_t = -out_S * out_R * mean; - //Try to align gps and camera point + // Try to align gps and camera point double gpsS; Vec3 gpst; Mat3 gpsR; @@ -1224,9 +1193,9 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out return; } - //Rotate to align north with Z=1 + // Rotate to align north with Z=1 const Vec3 northPole = WGS84ToCartesian({90.0, 0.0, 0.0}); - const Vec3 camera_northpole = gpsR.transpose()*(northPole - gpst) * (1.0 / gpsS); + const Vec3 camera_northpole = gpsR.transpose() * (northPole - gpst) * (1.0 / gpsS); Vec3 aligned_camera_northpole = out_R * camera_northpole; Mat3 nullifyX; @@ -1235,9 +1204,8 @@ void computeNewCoordinateSystemAuto(const sfmData::SfMData& sfmData, double& out getRotationNullifyX(nullifyX, aligned_camera_northpole); out_R = nullifyX * out_R; - out_t = - out_S * out_R * mean; + out_t = -out_S * out_R * mean; } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/alignment.hpp b/src/aliceVision/sfm/utils/alignment.hpp index af90444cf1..f7641e653d 100644 --- a/src/aliceVision/sfm/utils/alignment.hpp +++ b/src/aliceVision/sfm/utils/alignment.hpp @@ -13,41 +13,34 @@ namespace aliceVision { namespace sfm { -inline void getCommonViews(const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::vector& outIndexes) +inline void getCommonViews(const sfmData::SfMData& sfmDataA, const sfmData::SfMData& sfmDataB, std::vector& outIndexes) { - for(const auto& viewA: sfmDataA.getViews()) + for (const auto& viewA : sfmDataA.getViews()) { - if(sfmDataB.getViews().find(viewA.first) != sfmDataB.getViews().end()) + if (sfmDataB.getViews().find(viewA.first) != sfmDataB.getViews().end()) { outIndexes.push_back(viewA.first); } } } -inline void getCommonViewsWithPoses(const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::vector& outIndexes) +inline void getCommonViewsWithPoses(const sfmData::SfMData& sfmDataA, const sfmData::SfMData& sfmDataB, std::vector& outIndexes) { - for(const auto& viewA: sfmDataA.getViews()) + for (const auto& viewA : sfmDataA.getViews()) { - // check there is a view with the same ID and both of them have pose and + // check there is a view with the same ID and both of them have pose and // intrinsics defined - if(!sfmDataA.isPoseAndIntrinsicDefined(viewA.second.get())) + if (!sfmDataA.isPoseAndIntrinsicDefined(viewA.second.get())) continue; - if(sfmDataB.getViews().find(viewA.first) != sfmDataB.getViews().end() && - sfmDataB.isPoseAndIntrinsicDefined(viewA.first)) + if (sfmDataB.getViews().find(viewA.first) != sfmDataB.getViews().end() && sfmDataB.isPoseAndIntrinsicDefined(viewA.first)) { outIndexes.push_back(viewA.first); } } } -inline void getCommonPoseId(const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::vector& outIndexes) +inline void getCommonPoseId(const sfmData::SfMData& sfmDataA, const sfmData::SfMData& sfmDataB, std::vector& outIndexes) { for (const auto& poseA : sfmDataA.getPoses()) { @@ -58,20 +51,15 @@ inline void getCommonPoseId(const sfmData::SfMData& sfmDataA, } } +void matchViewsByFilePattern(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + std::vector>& out_commonViewIds); -void matchViewsByFilePattern( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::string& filePatternMatching, - std::vector>& out_commonViewIds); - - -void matchViewsByMetadataMatching( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::vector& metadataList, - std::vector>& out_commonViewIds); - +void matchViewsByMetadataMatching(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + std::vector>& out_commonViewIds); /** * @brief Compute a 5DOF rigid transform between the two set of cameras based on common viewIds. @@ -85,58 +73,52 @@ void matchViewsByMetadataMatching( * @return true if it finds a similarity transformation */ bool computeSimilarityFromCommonCameras_viewId(const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::mt19937 & randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t); + const sfmData::SfMData& sfmDataB, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t); /** -* @brief Compute a 5DOF rigid transform between the two set of cameras based on common poseIds. -* -* @param[in] sfmDataA -* @param[in] sfmDataB -* @param[in] randomNumberGenerator random number generator -* @param[out] out_S output scale factor -* @param[out] out_R output rotation 3x3 matrix -* @param[out] out_t output translation vector -* @return true if it finds a similarity transformation -*/ -bool computeSimilarityFromCommonCameras_poseId( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::mt19937 & randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t); - -bool computeSimilarityFromCommonCameras_imageFileMatching( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::string& filePatternMatching, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t); - -bool computeSimilarityFromCommonCameras_metadataMatching( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - const std::vector& metadataList, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t); - - -bool computeSimilarityFromCommonMarkers( - const sfmData::SfMData& sfmDataA, - const sfmData::SfMData& sfmDataB, - std::mt19937 &randomNumberGenerator, - double* out_S, - Mat3* out_R, - Vec3* out_t); - + * @brief Compute a 5DOF rigid transform between the two set of cameras based on common poseIds. + * + * @param[in] sfmDataA + * @param[in] sfmDataB + * @param[in] randomNumberGenerator random number generator + * @param[out] out_S output scale factor + * @param[out] out_R output rotation 3x3 matrix + * @param[out] out_t output translation vector + * @return true if it finds a similarity transformation + */ +bool computeSimilarityFromCommonCameras_poseId(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t); + +bool computeSimilarityFromCommonCameras_imageFileMatching(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t); + +bool computeSimilarityFromCommonCameras_metadataMatching(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t); + +bool computeSimilarityFromCommonMarkers(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + std::mt19937& randomNumberGenerator, + double* out_S, + Mat3* out_R, + Vec3* out_t); /** * @brief Apply a transformation the given SfMData @@ -146,12 +128,9 @@ bool computeSimilarityFromCommonMarkers( * @param R rotation * @param t translation */ -inline void applyTransform(sfmData::SfMData& sfmData, - const double S, - const Mat3& R, - const Vec3& t) +inline void applyTransform(sfmData::SfMData& sfmData, const double S, const Mat3& R, const Vec3& t) { - for(auto& poseIt: sfmData.getPoses()) + for (auto& poseIt : sfmData.getPoses()) { geometry::Pose3 pose = poseIt.second.getTransform(); pose = pose.transformSRt(S, R, t); @@ -166,7 +145,7 @@ inline void applyTransform(sfmData::SfMData& sfmData, } } - for(auto& landmark: sfmData.getLandmarks()) + for (auto& landmark : sfmData.getLandmarks()) { landmark.second.X = S * R * landmark.second.X + t; } @@ -184,8 +163,7 @@ inline void applyTransform(sfmData::SfMData& sfmData, * @param[out] out_R rotation * @param[out] out_t translation */ -void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, double& out_S, Mat3& out_R, - Vec3& out_t); +void computeNewCoordinateSystemFromCamerasXAxis(const sfmData::SfMData& sfmData, double& out_S, Mat3& out_R, Vec3& out_t); /** * @brief Compute the new coordinate system in the given SfM so that the mean @@ -221,10 +199,7 @@ void computeNewCoordinateSystemGroundAuto(const sfmData::SfMData& sfmData, Vec3& * @param[out] out_R rotation * @param[out] out_t translation */ -void computeNewCoordinateSystemFromCameras(const sfmData::SfMData& sfmData, - double& out_S, - Mat3& out_R, - Vec3& out_t); +void computeNewCoordinateSystemFromCameras(const sfmData::SfMData& sfmData, double& out_S, Mat3& out_R, Vec3& out_t); /** * @brief Compute the new coordinate system in the given reconstruction so that a landmark (e.g. artificial) @@ -255,10 +230,11 @@ void computeNewCoordinateSystemFromLandmarks(const sfmData::SfMData& sfmData, * @param[out] out_t the translation. * @return false if no reliable transformation can be computed or the sfmdata does not contain gps metadata, true otherwise. */ -bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, std::mt19937 &randomNumberGenerator, - double& out_S, - Mat3& out_R, - Vec3& out_t); +bool computeNewCoordinateSystemFromGpsData(const sfmData::SfMData& sfmData, + std::mt19937& randomNumberGenerator, + double& out_S, + Mat3& out_R, + Vec3& out_t); /** * @brief Retrieve the View Id from a string expression (integer with the view id or filename of the input image). @@ -283,11 +259,7 @@ IndexT getCenterCameraView(const sfmData::SfMData& sfmData); * @param[out] out_R rotation * @param[out] out_t translation */ -void computeNewCoordinateSystemFromSingleCamera(const sfmData::SfMData& sfmData, - const IndexT viewId, - double& out_S, - Mat3& out_R, - Vec3& out_t); +void computeNewCoordinateSystemFromSingleCamera(const sfmData::SfMData& sfmData, const IndexT viewId, double& out_S, Mat3& out_R, Vec3& out_t); /** * @brief Compute the cameras centers mean @@ -295,17 +267,17 @@ void computeNewCoordinateSystemFromSingleCamera(const sfmData::SfMData& sfmData, * @param[in] sfmData * @param[out] mean output mean of centers */ -void computeCentersMean(const sfmData::SfMData& sfmData, Vec3 & center); +void computeCentersMean(const sfmData::SfMData& sfmData, Vec3& center); /** * @brief Compute the cameras centers standard deviation * - * @param[in] sfmData + * @param[in] sfmData * @param[in] mean input mean of the poses centers * @param[out] varCov variance covariance of the poses centers * @param[out] count poses centers count */ -void computeCentersVarCov(const sfmData::SfMData& sfmData, const Vec3 & mean, Eigen::Matrix3d & varCov, size_t & count); +void computeCentersVarCov(const sfmData::SfMData& sfmData, const Vec3& mean, Eigen::Matrix3d& varCov, size_t& count); struct MarkerWithCoord { @@ -318,23 +290,23 @@ std::istream& operator>>(std::istream& in, MarkerWithCoord& marker); std::ostream& operator<<(std::ostream& os, const MarkerWithCoord& marker); /** -* @brief Compute a new coordinate system so that markers are aligned with the target coordinates. -* -* @param[in] sfmData -* @param[in] imageDescriberType -* @param[in] markers: markers id associated to a target 3D coordinate -* @param[in] withScaling -* @param[out] out_S scale -* @param[out] out_R rotation -* @param[out] out_t translation -*/ + * @brief Compute a new coordinate system so that markers are aligned with the target coordinates. + * + * @param[in] sfmData + * @param[in] imageDescriberType + * @param[in] markers: markers id associated to a target 3D coordinate + * @param[in] withScaling + * @param[out] out_S scale + * @param[out] out_R rotation + * @param[out] out_t translation + */ bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmData, - const feature::EImageDescriberType& imageDescriberType, - const std::vector& markers, - bool withScaling, - double& out_S, - Mat3& out_R, - Vec3& out_t); + const feature::EImageDescriberType& imageDescriberType, + const std::vector& markers, + bool withScaling, + double& out_S, + Mat3& out_R, + Vec3& out_t); /** * @brief Compute the 3D rotation matrix such that "R.t() * unit_z" @@ -343,7 +315,7 @@ bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmDa * @param[out] out_R the result rotation matrix * @param[in] R the input rotation matrix */ -void getRotationNullifyX(Eigen::Matrix3d & out_R, const Eigen::Matrix3d & R); +void getRotationNullifyX(Eigen::Matrix3d& out_R, const Eigen::Matrix3d& R); /** * @brief Compute the 3D rotation matrix such that "pt" @@ -352,7 +324,7 @@ void getRotationNullifyX(Eigen::Matrix3d & out_R, const Eigen::Matrix3d & R); * @param[out] out_R the result rotation matrix * @param[in] pt the input point to nullify on X */ -void getRotationNullifyX(Eigen::Matrix3d & out_R, const Eigen::Vector3d & pt); +void getRotationNullifyX(Eigen::Matrix3d& out_R, const Eigen::Vector3d& pt); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/alignment_test.cpp b/src/aliceVision/sfm/utils/alignment_test.cpp index 32294b78ab..742658623e 100644 --- a/src/aliceVision/sfm/utils/alignment_test.cpp +++ b/src/aliceVision/sfm/utils/alignment_test.cpp @@ -80,8 +80,7 @@ BOOST_AUTO_TEST_CASE(ALIGMENT_CamerasXAxis_checkRotation) double aS = 1.0; const Vec3 rAngles = Vec3::Random() * M_PI; - const Mat3 aR(Eigen::AngleAxisd(rAngles(0), Vec3::UnitX()) * - Eigen::AngleAxisd(rAngles(1), Vec3::UnitY()) * + const Mat3 aR(Eigen::AngleAxisd(rAngles(0), Vec3::UnitX()) * Eigen::AngleAxisd(rAngles(1), Vec3::UnitY()) * Eigen::AngleAxisd(rAngles(2), Vec3::UnitZ())); const Vec3 at = Vec3::Zero(); applyTransform(sfmData, aS, aR, at); @@ -97,10 +96,10 @@ BOOST_AUTO_TEST_CASE(ALIGMENT_CamerasXAxis_checkRotation) applyTransform(sfmDataCorrected, 1.0, bR, bt); ALICEVISION_LOG_INFO("aR: " << aR); ALICEVISION_LOG_INFO("bR: " << bR); - for(const auto& pose: sfmDataCorrected.getPoses()) + for (const auto& pose : sfmDataCorrected.getPoses()) { Vec3 camY(pose.second.getTransform().rotation() * Vec3::UnitY()); - //EXPECT_MATRIX_NEAR(camY, -Vec3::UnitY(), 1e-3); + // EXPECT_MATRIX_NEAR(camY, -Vec3::UnitY(), 1e-3); ALICEVISION_LOG_INFO("camY: " << camY); } // Mat3 res = bR * aR; @@ -140,57 +139,58 @@ BOOST_AUTO_TEST_CASE(ALIGMENT_CamerasXAxis_checkRotation) // Translation a synthetic scene into a valid SfMData scene. // => A synthetic scene is used: // a random noise between [-.5,.5] is added on observed data points -SfMData getInputScene(const NViewDataSet & d, const NViewDatasetConfigurator & config, EINTRINSIC eintrinsic) +SfMData getInputScene(const NViewDataSet& d, const NViewDatasetConfigurator& config, EINTRINSIC eintrinsic) { - // Translate the input dataset to a SfMData scene - SfMData sfm_data; - - // 1. Views - // 2. Poses - // 3. Intrinsic data (shared, so only one camera intrinsic is defined) - // 4. Landmarks - - const int nviews = d._C.size(); - const int npoints = d._X.cols(); - - // 1. Views - for (int i = 0; i < nviews; ++i) - { - const IndexT id_view = i, id_pose = i, id_intrinsic = 0; //(shared intrinsics) - sfm_data.getViews().emplace(i, std::make_shared("", id_view, id_intrinsic, id_pose, config._cx *2, config._cy *2)); - } - - // 2. Poses - for (int i = 0; i < nviews; ++i) - { - Pose3 pose(d._R[i], d._C[i]); - sfm_data.setPose(*sfm_data.getViews().at(i), CameraPose(pose)); - } - - // 3. Intrinsic data (shared, so only one camera intrinsic is defined) - { - const unsigned int w = config._cx *2; - const unsigned int h = config._cy *2; - sfm_data.getIntrinsics().emplace(0, createIntrinsic(eintrinsic, w, h, config._fx, config._cx, config._cy)); - } - - // 4. Landmarks - const double unknownScale = 0.0; - for (int i = 0; i < npoints; ++i) { - - // Collect the image of point i in each frame. - Landmark landmark; - landmark.X = d._X.col(i); - for (int j = 0; j < nviews; ++j) { - Vec2 pt = d._x[j].col(i); - // => random noise between [-.5,.5] is added - pt(0) += rand()/RAND_MAX - .5; - pt(1) += rand()/RAND_MAX - .5; - - landmark.observations[j] = Observation(pt, i, unknownScale); + // Translate the input dataset to a SfMData scene + SfMData sfm_data; + + // 1. Views + // 2. Poses + // 3. Intrinsic data (shared, so only one camera intrinsic is defined) + // 4. Landmarks + + const int nviews = d._C.size(); + const int npoints = d._X.cols(); + + // 1. Views + for (int i = 0; i < nviews; ++i) + { + const IndexT id_view = i, id_pose = i, id_intrinsic = 0; //(shared intrinsics) + sfm_data.getViews().emplace(i, std::make_shared("", id_view, id_intrinsic, id_pose, config._cx * 2, config._cy * 2)); + } + + // 2. Poses + for (int i = 0; i < nviews; ++i) + { + Pose3 pose(d._R[i], d._C[i]); + sfm_data.setPose(*sfm_data.getViews().at(i), CameraPose(pose)); + } + + // 3. Intrinsic data (shared, so only one camera intrinsic is defined) + { + const unsigned int w = config._cx * 2; + const unsigned int h = config._cy * 2; + sfm_data.getIntrinsics().emplace(0, createIntrinsic(eintrinsic, w, h, config._fx, config._cx, config._cy)); + } + + // 4. Landmarks + const double unknownScale = 0.0; + for (int i = 0; i < npoints; ++i) + { + // Collect the image of point i in each frame. + Landmark landmark; + landmark.X = d._X.col(i); + for (int j = 0; j < nviews; ++j) + { + Vec2 pt = d._x[j].col(i); + // => random noise between [-.5,.5] is added + pt(0) += rand() / RAND_MAX - .5; + pt(1) += rand() / RAND_MAX - .5; + + landmark.observations[j] = Observation(pt, i, unknownScale); + } + sfm_data.getLandmarks()[i] = landmark; } - sfm_data.getLandmarks()[i] = landmark; - } - return sfm_data; + return sfm_data; } diff --git a/src/aliceVision/sfm/utils/statistics.cpp b/src/aliceVision/sfm/utils/statistics.cpp index 41f11e0c48..d5fd210efc 100644 --- a/src/aliceVision/sfm/utils/statistics.cpp +++ b/src/aliceVision/sfm/utils/statistics.cpp @@ -13,31 +13,27 @@ namespace sfm { double RMSE(const sfmData::SfMData& sfmData) { - // Compute residuals for each observation - std::vector vec; - for(sfmData::Landmarks::const_iterator iterTracks = sfmData.getLandmarks().begin(); - iterTracks != sfmData.getLandmarks().end(); - ++iterTracks) - { - const sfmData::Observations& obs = iterTracks->second.observations; - for(sfmData::Observations::const_iterator itObs = obs.begin(); - itObs != obs.end(); ++itObs) + // Compute residuals for each observation + std::vector vec; + for (sfmData::Landmarks::const_iterator iterTracks = sfmData.getLandmarks().begin(); iterTracks != sfmData.getLandmarks().end(); ++iterTracks) { - const sfmData::View* view = sfmData.getViews().find(itObs->first)->second.get(); - const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); - const std::shared_ptr intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()); - const Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x); - vec.push_back(residual(0)); - vec.push_back(residual(1)); + const sfmData::Observations& obs = iterTracks->second.observations; + for (sfmData::Observations::const_iterator itObs = obs.begin(); itObs != obs.end(); ++itObs) + { + const sfmData::View* view = sfmData.getViews().find(itObs->first)->second.get(); + const geometry::Pose3 pose = sfmData.getPose(*view).getTransform(); + const std::shared_ptr intrinsic = sfmData.getIntrinsics().at(view->getIntrinsicId()); + const Vec2 residual = intrinsic->residual(pose, iterTracks->second.X.homogeneous(), itObs->second.x); + vec.push_back(residual(0)); + vec.push_back(residual(1)); + } } - } - if(vec.empty()) - return -1.0; - const Eigen::Map residuals(&vec[0], vec.size()); - const double RMSE = std::sqrt(residuals.squaredNorm() / vec.size()); - return RMSE; + if (vec.empty()) + return -1.0; + const Eigen::Map residuals(&vec[0], vec.size()); + const double RMSE = std::sqrt(residuals.squaredNorm() / vec.size()); + return RMSE; } -} // namespace sfm -} // namespace aliceVision - +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/statistics.hpp b/src/aliceVision/sfm/utils/statistics.hpp index 01ea90225d..9bb040cbcd 100644 --- a/src/aliceVision/sfm/utils/statistics.hpp +++ b/src/aliceVision/sfm/utils/statistics.hpp @@ -11,7 +11,7 @@ namespace aliceVision { namespace sfmData { class SfMData; -} // namespace sfmData +} // namespace sfmData namespace sfm { @@ -22,5 +22,5 @@ namespace sfm { */ double RMSE(const sfmData::SfMData& sfmData); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/syntheticScene.cpp b/src/aliceVision/sfm/utils/syntheticScene.cpp index 924488733e..ca959d6c17 100644 --- a/src/aliceVision/sfm/utils/syntheticScene.cpp +++ b/src/aliceVision/sfm/utils/syntheticScene.cpp @@ -14,168 +14,164 @@ namespace aliceVision { namespace sfm { -void generateSyntheticMatches(matching::PairwiseMatches& out_pairwiseMatches, - const sfmData::SfMData& sfmData, - feature::EImageDescriberType descType) +void generateSyntheticMatches(matching::PairwiseMatches& out_pairwiseMatches, const sfmData::SfMData& sfmData, feature::EImageDescriberType descType) { - for(const auto& it: sfmData.getLandmarks()) - { - const sfmData::Landmark& landmark = it.second; - const std::size_t limitMatches = std::min(std::size_t(3), landmark.observations.size()); - - for(auto obsItI = landmark.observations.begin(); obsItI != landmark.observations.end(); ++obsItI) + for (const auto& it : sfmData.getLandmarks()) { - const sfmData::Observation& obsI = obsItI->second; - // We don't need matches between all observations. - // We will limit to matches between 3 observations of the same landmark. - // At the end of the reconstruction process, they should be be fused again into one landmark. - auto obsItJ = obsItI; - for(std::size_t j = 1; j < limitMatches; ++j) - { - ++obsItJ; - if(obsItJ == landmark.observations.end()) - obsItJ = landmark.observations.begin(); - - const sfmData::Observation& obsJ = obsItJ->second; - - out_pairwiseMatches[Pair(obsItI->first, obsItJ->first)][descType].emplace_back(obsItI->second.id_feat, obsItJ->second.id_feat); - } + const sfmData::Landmark& landmark = it.second; + const std::size_t limitMatches = std::min(std::size_t(3), landmark.observations.size()); + + for (auto obsItI = landmark.observations.begin(); obsItI != landmark.observations.end(); ++obsItI) + { + const sfmData::Observation& obsI = obsItI->second; + // We don't need matches between all observations. + // We will limit to matches between 3 observations of the same landmark. + // At the end of the reconstruction process, they should be be fused again into one landmark. + auto obsItJ = obsItI; + for (std::size_t j = 1; j < limitMatches; ++j) + { + ++obsItJ; + if (obsItJ == landmark.observations.end()) + obsItJ = landmark.observations.begin(); + + const sfmData::Observation& obsJ = obsItJ->second; + + out_pairwiseMatches[Pair(obsItI->first, obsItJ->first)][descType].emplace_back(obsItI->second.id_feat, obsItJ->second.id_feat); + } + } } - } } -sfmData::SfMData getInputScene(const NViewDataSet& d, - const NViewDatasetConfigurator& config, - camera::EINTRINSIC eintrinsic) +sfmData::SfMData getInputScene(const NViewDataSet& d, const NViewDatasetConfigurator& config, camera::EINTRINSIC eintrinsic) { - // Translate the input dataset to a SfMData scene - sfmData::SfMData sfmData; - - // 1. Views - // 2. Poses - // 3. Intrinsic data (shared, so only one camera intrinsic is defined) - // 4. Landmarks - - const int nviews = d._C.size(); - const int npoints = d._X.cols(); - - // 1. Views - for(int i = 0; i < nviews; ++i) - { - const IndexT viewId = i, poseId = i, intrinsicId = 0; //(shared intrinsics) - sfmData.getViews().emplace(i, std::make_shared("", viewId, intrinsicId, poseId, config._cx * 2, config._cy * 2)); - } - - // 2. Poses - for(int i = 0; i < nviews; ++i) - { - sfmData.setPose(*sfmData.getViews().at(i), sfmData::CameraPose(geometry::Pose3(d._R[i], d._C[i]))); - } - - // 3. Intrinsic data (shared, so only one camera intrinsic is defined) - { - const unsigned int w = config._cx *2; - const unsigned int h = config._cy *2; - sfmData.getIntrinsics().emplace(0, camera::createIntrinsic(eintrinsic, w, h, config._fx, config._fx)); - } - - // 4. Landmarks - const double unknownScale = 0.0; - for(int i = 0; i < npoints; ++i) - { - // Collect the image of point i in each frame. - sfmData::Landmark landmark; - landmark.X = d._X.col(i); - for (int j = 0; j < nviews; ++j) { - const Vec2 pt = d._x[j].col(i); - landmark.observations[j] = sfmData::Observation(pt, i, unknownScale); + // Translate the input dataset to a SfMData scene + sfmData::SfMData sfmData; + + // 1. Views + // 2. Poses + // 3. Intrinsic data (shared, so only one camera intrinsic is defined) + // 4. Landmarks + + const int nviews = d._C.size(); + const int npoints = d._X.cols(); + + // 1. Views + for (int i = 0; i < nviews; ++i) + { + const IndexT viewId = i, poseId = i, intrinsicId = 0; //(shared intrinsics) + sfmData.getViews().emplace(i, std::make_shared("", viewId, intrinsicId, poseId, config._cx * 2, config._cy * 2)); + } + + // 2. Poses + for (int i = 0; i < nviews; ++i) + { + sfmData.setPose(*sfmData.getViews().at(i), sfmData::CameraPose(geometry::Pose3(d._R[i], d._C[i]))); + } + + // 3. Intrinsic data (shared, so only one camera intrinsic is defined) + { + const unsigned int w = config._cx * 2; + const unsigned int h = config._cy * 2; + sfmData.getIntrinsics().emplace(0, camera::createIntrinsic(eintrinsic, w, h, config._fx, config._fx)); } - sfmData.getLandmarks()[i] = landmark; - } - return sfmData; + // 4. Landmarks + const double unknownScale = 0.0; + for (int i = 0; i < npoints; ++i) + { + // Collect the image of point i in each frame. + sfmData::Landmark landmark; + landmark.X = d._X.col(i); + for (int j = 0; j < nviews; ++j) + { + const Vec2 pt = d._x[j].col(i); + landmark.observations[j] = sfmData::Observation(pt, i, unknownScale); + } + sfmData.getLandmarks()[i] = landmark; + } + + return sfmData; } -sfmData::SfMData getInputRigScene(const NViewDataSet& d, - const NViewDatasetConfigurator& config, - camera::EINTRINSIC eintrinsic) +sfmData::SfMData getInputRigScene(const NViewDataSet& d, const NViewDatasetConfigurator& config, camera::EINTRINSIC eintrinsic) { - // 1. Rig - // 2. Views - // 3. Poses - // 4. Intrinsic data (shared, so only one camera intrinsic is defined) - // 5. Landmarks - - // Translate the input dataset to a SfMData scene - sfmData::SfMData sfmData; - - const std::size_t nbPoses = d._C.size(); - const std::size_t nbPoints = d._X.cols(); - - // 1. Rig - const IndexT rigId = 0; - const std::size_t nbSubposes = 2; - sfmData.getRigs().emplace(rigId, sfmData::Rig(nbSubposes)); - sfmData::Rig& rig = sfmData.getRigs().at(rigId); - rig.getSubPose(0) = sfmData::RigSubPose(geometry::Pose3(Mat3::Identity(), Vec3(-0.01, 0, 0)), sfmData::ERigSubPoseStatus::CONSTANT); - rig.getSubPose(1) = sfmData::RigSubPose(geometry::Pose3(Mat3::Identity(), Vec3(+0.01, 0, 0)), sfmData::ERigSubPoseStatus::CONSTANT); - - // 2. Views - for(std::size_t poseId = 0; poseId < nbPoses; ++poseId) - { - for(std::size_t subposeI = 0; subposeI < nbSubposes; ++subposeI) + // 1. Rig + // 2. Views + // 3. Poses + // 4. Intrinsic data (shared, so only one camera intrinsic is defined) + // 5. Landmarks + + // Translate the input dataset to a SfMData scene + sfmData::SfMData sfmData; + + const std::size_t nbPoses = d._C.size(); + const std::size_t nbPoints = d._X.cols(); + + // 1. Rig + const IndexT rigId = 0; + const std::size_t nbSubposes = 2; + sfmData.getRigs().emplace(rigId, sfmData::Rig(nbSubposes)); + sfmData::Rig& rig = sfmData.getRigs().at(rigId); + rig.getSubPose(0) = sfmData::RigSubPose(geometry::Pose3(Mat3::Identity(), Vec3(-0.01, 0, 0)), sfmData::ERigSubPoseStatus::CONSTANT); + rig.getSubPose(1) = sfmData::RigSubPose(geometry::Pose3(Mat3::Identity(), Vec3(+0.01, 0, 0)), sfmData::ERigSubPoseStatus::CONSTANT); + + // 2. Views + for (std::size_t poseId = 0; poseId < nbPoses; ++poseId) + { + for (std::size_t subposeI = 0; subposeI < nbSubposes; ++subposeI) + { + const IndexT viewId = poseId * nbSubposes + subposeI; + const IndexT intrinsicId = 0; //(shared intrinsics) + + auto viewPtr = std::make_shared("", viewId, intrinsicId, poseId, config._cx * 2, config._cy * 2, rigId, subposeI); + viewPtr->setFrameId(poseId); + viewPtr->setIndependantPose(false); + sfmData.getViews().emplace(viewId, viewPtr); + } + } + const std::size_t nbViews = sfmData.getViews().size(); + + // 3. Poses + for (int poseId = 0; poseId < nbPoses; ++poseId) { - const IndexT viewId = poseId * nbSubposes + subposeI; - const IndexT intrinsicId = 0; //(shared intrinsics) + sfmData.setAbsolutePose(static_cast(poseId), sfmData::CameraPose(geometry::Pose3(d._R[poseId], d._C[poseId]))); + } - auto viewPtr = std::make_shared("", viewId, intrinsicId, poseId, config._cx * 2, config._cy * 2, rigId, subposeI); - viewPtr->setFrameId(poseId); - viewPtr->setIndependantPose(false); - sfmData.getViews().emplace(viewId, viewPtr); + // 4. Intrinsic data (shared, so only one camera intrinsic is defined) + { + const unsigned int w = config._cx * 2; + const unsigned int h = config._cy * 2; + sfmData.getIntrinsics().emplace(0, camera::createIntrinsic(eintrinsic, w, h, config._fx, config._fx)); } - } - const std::size_t nbViews = sfmData.getViews().size(); - - // 3. Poses - for(int poseId = 0; poseId < nbPoses; ++poseId) - { - sfmData.setAbsolutePose(static_cast(poseId), sfmData::CameraPose(geometry::Pose3(d._R[poseId], d._C[poseId]))); - } - - // 4. Intrinsic data (shared, so only one camera intrinsic is defined) - { - const unsigned int w = config._cx * 2; - const unsigned int h = config._cy * 2; - sfmData.getIntrinsics().emplace(0, camera::createIntrinsic(eintrinsic, w, h, config._fx, config._fx)); - } - - // 5. Landmarks - const double unknownScale = 0.0; - for(int landmarkId = 0; landmarkId < nbPoints; ++landmarkId) - { - // Collect the image of point i in each frame. - sfmData::Landmark landmark; - landmark.X = d._X.col(landmarkId); - for(int viewId = 0; viewId < nbViews; ++viewId) + + // 5. Landmarks + const double unknownScale = 0.0; + for (int landmarkId = 0; landmarkId < nbPoints; ++landmarkId) { - const sfmData::View& view = *sfmData.getViews().at(viewId); - const geometry::Pose3 camPose = sfmData.getPose(view).getTransform(); - - std::shared_ptr cam = sfmData.getIntrinsics().at(0); - std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); - if (!camPinHole) { - ALICEVISION_LOG_ERROR("Camera is not pinhole in getInputRigScene"); - continue; - } - - const Vec2 pt = project(camPinHole->getProjectiveEquivalent(camPose), landmark.X); - landmark.observations[viewId] = sfmData::Observation(pt, landmarkId, unknownScale); + // Collect the image of point i in each frame. + sfmData::Landmark landmark; + landmark.X = d._X.col(landmarkId); + for (int viewId = 0; viewId < nbViews; ++viewId) + { + const sfmData::View& view = *sfmData.getViews().at(viewId); + const geometry::Pose3 camPose = sfmData.getPose(view).getTransform(); + + std::shared_ptr cam = sfmData.getIntrinsics().at(0); + std::shared_ptr camPinHole = std::dynamic_pointer_cast(cam); + if (!camPinHole) + { + ALICEVISION_LOG_ERROR("Camera is not pinhole in getInputRigScene"); + continue; + } + + const Vec2 pt = project(camPinHole->getProjectiveEquivalent(camPose), landmark.X); + landmark.observations[viewId] = sfmData::Observation(pt, landmarkId, unknownScale); + } + sfmData.getLandmarks()[landmarkId] = landmark; } - sfmData.getLandmarks()[landmarkId] = landmark; - } - return sfmData; + return sfmData; } -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/syntheticScene.hpp b/src/aliceVision/sfm/utils/syntheticScene.hpp index a51654fea0..571eab46c0 100644 --- a/src/aliceVision/sfm/utils/syntheticScene.hpp +++ b/src/aliceVision/sfm/utils/syntheticScene.hpp @@ -22,57 +22,58 @@ namespace sfm { * @param[in] descType * @param[in] noise */ -template +template void generateSyntheticFeatures(feature::FeaturesPerView& out_featuresPerView, feature::EImageDescriberType descType, const sfmData::SfMData& sfmData, NoiseGenerator& noise) { - assert(descType != feature::EImageDescriberType::UNINITIALIZED); - std::default_random_engine generator; + assert(descType != feature::EImageDescriberType::UNINITIALIZED); + std::default_random_engine generator; - // precompute output feature vectors size and resize - { - std::map nbFeatPerView; - for(const auto& it: sfmData.getViews()) + // precompute output feature vectors size and resize { - nbFeatPerView[it.first] = 0; - } - for(const auto& it: sfmData.getLandmarks()) - { - const sfmData::Landmark& landmark = it.second; + std::map nbFeatPerView; + for (const auto& it : sfmData.getViews()) + { + nbFeatPerView[it.first] = 0; + } + for (const auto& it : sfmData.getLandmarks()) + { + const sfmData::Landmark& landmark = it.second; - for(const auto& obsIt: landmark.observations) - { - const IndexT viewId = obsIt.first; - const sfmData::Observation& obs = obsIt.second; - nbFeatPerView[viewId] = std::max(nbFeatPerView[viewId], std::size_t(obs.id_feat+1)); - } - } - for(auto& it: nbFeatPerView) - { - // create Point Features vectors at the right size - feature::PointFeatures pointFeatures(it.second); - out_featuresPerView.addFeatures(it.first, descType, pointFeatures); + for (const auto& obsIt : landmark.observations) + { + const IndexT viewId = obsIt.first; + const sfmData::Observation& obs = obsIt.second; + nbFeatPerView[viewId] = std::max(nbFeatPerView[viewId], std::size_t(obs.id_feat + 1)); + } + } + for (auto& it : nbFeatPerView) + { + // create Point Features vectors at the right size + feature::PointFeatures pointFeatures(it.second); + out_featuresPerView.addFeatures(it.first, descType, pointFeatures); + } } - } - // Use arbitrary values for feature scale and orientation - const float scale = 0.0f; - const float orientation = 0.0f; - - // Fill with the observation values - for(const auto& it: sfmData.getLandmarks()) - { - const sfmData::Landmark& landmark = it.second; + // Use arbitrary values for feature scale and orientation + const float scale = 0.0f; + const float orientation = 0.0f; - for(const auto& obsIt: landmark.observations) + // Fill with the observation values + for (const auto& it : sfmData.getLandmarks()) { - const IndexT viewId = obsIt.first; - const sfmData::Observation& obs = obsIt.second; + const sfmData::Landmark& landmark = it.second; + + for (const auto& obsIt : landmark.observations) + { + const IndexT viewId = obsIt.first; + const sfmData::Observation& obs = obsIt.second; - out_featuresPerView.getFeaturesPerDesc(viewId)[descType][obs.id_feat] = feature::PointFeature(obs.x(0) + noise(generator), obs.x(1) + noise(generator), scale, orientation); + out_featuresPerView.getFeaturesPerDesc(viewId)[descType][obs.id_feat] = + feature::PointFeature(obs.x(0) + noise(generator), obs.x(1) + noise(generator), scale, orientation); + } } - } } /** @@ -91,5 +92,5 @@ sfmData::SfMData getInputScene(const NViewDataSet& d, const NViewDatasetConfigur // As only one intrinsic is defined we used shared intrinsic sfmData::SfMData getInputRigScene(const NViewDataSet& d, const NViewDatasetConfigurator& config, camera::EINTRINSIC eintrinsic); -} // namespace sfm -} // namespace aliceVision +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/CameraPose.hpp b/src/aliceVision/sfmData/CameraPose.hpp index 135b9a410a..60715eba21 100644 --- a/src/aliceVision/sfmData/CameraPose.hpp +++ b/src/aliceVision/sfmData/CameraPose.hpp @@ -13,84 +13,64 @@ namespace sfmData { class CameraPose { -public: + public: + /** + * @brief CameraPose default constructor + */ + CameraPose() = default; - /** - * @brief CameraPose default constructor - */ - CameraPose() = default; + /** + * @brief CameraPose constructor + * @param[in] transform The camera initial 3d transformation + * @param[in] locked If enable the camera pose is locked + */ + explicit CameraPose(const geometry::Pose3& transform, bool locked = false) + : _transform(transform), + _locked(locked) + {} - /** - * @brief CameraPose constructor - * @param[in] transform The camera initial 3d transformation - * @param[in] locked If enable the camera pose is locked - */ - explicit CameraPose(const geometry::Pose3& transform, bool locked = false) - : _transform(transform) - , _locked(locked) - {} + /** + * @brief Get the 3d transformation of the camera + * @return 3d transformation + */ + inline const geometry::Pose3& getTransform() const { return _transform; } - /** - * @brief Get the 3d transformation of the camera - * @return 3d transformation - */ - inline const geometry::Pose3& getTransform() const - { - return _transform; - } + /** + * @brief Get the lock state of the camera + * @return true if the camera pose is locked + */ + inline bool isLocked() const { return _locked; } - /** - * @brief Get the lock state of the camera - * @return true if the camera pose is locked - */ - inline bool isLocked() const - { - return _locked; - } + /** + * @brief operator == + */ + inline bool operator==(const CameraPose& other) const { return (_transform == other._transform && _locked == other._locked); } - /** - * @brief operator == - */ - inline bool operator==(const CameraPose& other) const - { - return (_transform == other._transform && - _locked == other._locked); - } + /** + * @brief Set the 3d transformation of the camera + * @param[in] 3d transformation + */ + inline void setTransform(const geometry::Pose3& transform) { _transform = transform; } - /** - * @brief Set the 3d transformation of the camera - * @param[in] 3d transformation - */ - inline void setTransform(const geometry::Pose3& transform) - { - _transform = transform; - } + /** + * @brief lock the camera pose + */ + inline void lock() { _locked = true; } - /** - * @brief lock the camera pose - */ - inline void lock() - { - _locked = true; - } + /** + * @brief unlock the camera pose + */ + inline void unlock() { _locked = false; } - /** - * @brief unlock the camera pose - */ - inline void unlock() - { - _locked = false; - } + private: + /// camera 3d transformation + geometry::Pose3 _transform; + /// camera lock + bool _locked = false; -private: - /// camera 3d transformation - geometry::Pose3 _transform; - /// camera lock - bool _locked = false; - -public: - EIGEN_MAKE_ALIGNED_OPERATOR_NEW + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/Constraint2D.hpp b/src/aliceVision/sfmData/Constraint2D.hpp index 89c50696a6..371071c8b3 100644 --- a/src/aliceVision/sfmData/Constraint2D.hpp +++ b/src/aliceVision/sfmData/Constraint2D.hpp @@ -17,32 +17,33 @@ namespace sfmData { */ struct Constraint2D { - Constraint2D() = default; - - Constraint2D(IndexT view_first, const Observation & observation_first, - IndexT view_second, const Observation & observation_second, feature::EImageDescriberType descType_) - : ViewFirst(view_first) - , ObservationFirst(observation_first) - , ViewSecond(view_second) - , ObservationSecond(observation_second) - , descType(descType_) - {} - - feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; - IndexT ViewFirst = UndefinedIndexT; - IndexT ViewSecond = UndefinedIndexT; - Observation ObservationFirst; - Observation ObservationSecond; - - bool operator==(const Constraint2D& other) const - { - return (ViewFirst == other.ViewFirst) && - (ViewSecond == other.ViewSecond) && - (ObservationFirst == other.ObservationFirst) && - (ObservationSecond == other.ObservationSecond); - } - inline bool operator!=(const Constraint2D& other) const { return !(*this == other); } + Constraint2D() = default; + + Constraint2D(IndexT view_first, + const Observation& observation_first, + IndexT view_second, + const Observation& observation_second, + feature::EImageDescriberType descType_) + : ViewFirst(view_first), + ObservationFirst(observation_first), + ViewSecond(view_second), + ObservationSecond(observation_second), + descType(descType_) + {} + + feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; + IndexT ViewFirst = UndefinedIndexT; + IndexT ViewSecond = UndefinedIndexT; + Observation ObservationFirst; + Observation ObservationSecond; + + bool operator==(const Constraint2D& other) const + { + return (ViewFirst == other.ViewFirst) && (ViewSecond == other.ViewSecond) && (ObservationFirst == other.ObservationFirst) && + (ObservationSecond == other.ObservationSecond); + } + inline bool operator!=(const Constraint2D& other) const { return !(*this == other); } }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/HashMapPtr.hpp b/src/aliceVision/sfmData/HashMapPtr.hpp index c4fa84f152..2329c08a33 100644 --- a/src/aliceVision/sfmData/HashMapPtr.hpp +++ b/src/aliceVision/sfmData/HashMapPtr.hpp @@ -10,15 +10,15 @@ namespace aliceVision { namespace sfmData { -template +template class HashMapPtr : public HashMap> { -public: + public: /** * We don't want the user to assume the object is created when the index does not exist in the map - */ - std::shared_ptr & operator[](const IndexT & index) = delete; + */ + std::shared_ptr& operator[](const IndexT& index) = delete; }; -} -} \ No newline at end of file +} // namespace sfmData +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfmData/Landmark.hpp b/src/aliceVision/sfmData/Landmark.hpp index e8eb3d9d22..719b983b38 100644 --- a/src/aliceVision/sfmData/Landmark.hpp +++ b/src/aliceVision/sfmData/Landmark.hpp @@ -21,22 +21,18 @@ namespace sfmData { */ struct Observation { - Observation() {} - Observation(const Vec2 & p, IndexT idFeat, double scale_) - : x(p) - , id_feat(idFeat) - , scale(scale_) - {} + Observation() {} + Observation(const Vec2& p, IndexT idFeat, double scale_) + : x(p), + id_feat(idFeat), + scale(scale_) + {} - Vec2 x; - IndexT id_feat = UndefinedIndexT; - double scale = 0.0; + Vec2 x; + IndexT id_feat = UndefinedIndexT; + double scale = 0.0; - bool operator==(const Observation& other) const - { - return AreVecNearEqual(x, other.x, 1e-6) && - id_feat == other.id_feat; - } + bool operator==(const Observation& other) const { return AreVecNearEqual(x, other.x, 1e-6) && id_feat == other.id_feat; } }; /// Observations are indexed by their View_id @@ -47,32 +43,32 @@ typedef stl::flat_map Observations; */ struct Landmark { - Landmark() = default; - explicit Landmark(feature::EImageDescriberType descType): descType(descType) {} - Landmark(const Vec3& pos3d, - feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED, - const Observations& observations = Observations(), - const image::RGBColor &color = image::WHITE) - : X(pos3d) - , descType(descType) - , observations(observations) - , rgb(color) - {} + Landmark() = default; + explicit Landmark(feature::EImageDescriberType descType) + : descType(descType) + {} + Landmark(const Vec3& pos3d, + feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED, + const Observations& observations = Observations(), + const image::RGBColor& color = image::WHITE) + : X(pos3d), + descType(descType), + observations(observations), + rgb(color) + {} - Vec3 X; - feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; - Observations observations; - image::RGBColor rgb = image::WHITE; //!> the color associated to the point - - bool operator==(const Landmark& other) const - { - return AreVecNearEqual(X, other.X, 1e-3) && - AreVecNearEqual(rgb, other.rgb, 1e-3) && - observations == other.observations && - descType == other.descType; - } - inline bool operator!=(const Landmark& other) const { return !(*this == other); } + Vec3 X; + feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; + Observations observations; + image::RGBColor rgb = image::WHITE; //!> the color associated to the point + + bool operator==(const Landmark& other) const + { + return AreVecNearEqual(X, other.X, 1e-3) && AreVecNearEqual(rgb, other.rgb, 1e-3) && observations == other.observations && + descType == other.descType; + } + inline bool operator!=(const Landmark& other) const { return !(*this == other); } }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/Rig.hpp b/src/aliceVision/sfmData/Rig.hpp index 3834414a33..5aec74c9d9 100644 --- a/src/aliceVision/sfmData/Rig.hpp +++ b/src/aliceVision/sfmData/Rig.hpp @@ -20,11 +20,11 @@ namespace sfmData { /** * @brief Rig sub-pose status enum */ -enum class ERigSubPoseStatus: std::uint8_t +enum class ERigSubPoseStatus : std::uint8_t { - UNINITIALIZED = 0 - , ESTIMATED = 1 - , CONSTANT = 2 + UNINITIALIZED = 0, + ESTIMATED = 1, + CONSTANT = 2 }; /** @@ -34,13 +34,16 @@ enum class ERigSubPoseStatus: std::uint8_t */ inline std::string ERigSubPoseStatus_enumToString(ERigSubPoseStatus rigSubPoseStatus) { - switch(rigSubPoseStatus) - { - case ERigSubPoseStatus::UNINITIALIZED: return "uninitialized"; - case ERigSubPoseStatus::ESTIMATED: return "estimated"; - case ERigSubPoseStatus::CONSTANT: return "constant"; - } - throw std::out_of_range("Invalid rigSubPoseStatus enum"); + switch (rigSubPoseStatus) + { + case ERigSubPoseStatus::UNINITIALIZED: + return "uninitialized"; + case ERigSubPoseStatus::ESTIMATED: + return "estimated"; + case ERigSubPoseStatus::CONSTANT: + return "constant"; + } + throw std::out_of_range("Invalid rigSubPoseStatus enum"); } /** @@ -50,168 +53,142 @@ inline std::string ERigSubPoseStatus_enumToString(ERigSubPoseStatus rigSubPoseSt */ inline ERigSubPoseStatus ERigSubPoseStatus_stringToEnum(const std::string& rigSubPoseStatus) { - std::string status = rigSubPoseStatus; - std::transform(status.begin(), status.end(), status.begin(), ::tolower); //tolower + std::string status = rigSubPoseStatus; + std::transform(status.begin(), status.end(), status.begin(), ::tolower); // tolower - if(status == "uninitialized") return ERigSubPoseStatus::UNINITIALIZED; - if(status == "estimated") return ERigSubPoseStatus::ESTIMATED; - if(status == "constant") return ERigSubPoseStatus::CONSTANT; + if (status == "uninitialized") + return ERigSubPoseStatus::UNINITIALIZED; + if (status == "estimated") + return ERigSubPoseStatus::ESTIMATED; + if (status == "constant") + return ERigSubPoseStatus::CONSTANT; - throw std::out_of_range("Invalid rigSubPoseStatus : " + rigSubPoseStatus); + throw std::out_of_range("Invalid rigSubPoseStatus : " + rigSubPoseStatus); } struct RigSubPose { - /// status of the sub-pose - ERigSubPoseStatus status = ERigSubPoseStatus::UNINITIALIZED; - /// relative pose of the sub-pose - geometry::Pose3 pose; - - /** - * @brief RigSubPose constructor - * @param pose The relative pose of the sub-pose - * @param status The status of the sub-pose - */ - RigSubPose(const geometry::Pose3& pose = geometry::Pose3(), - ERigSubPoseStatus status = ERigSubPoseStatus::UNINITIALIZED) - : pose(pose) - , status(status) - {} - - /** - * @brief operator == - * @param other the other RigSubPose - * @return true if the current RigSubPose and the other are identical. - */ - bool operator==(const RigSubPose& other) const - { - return (status == other.status && - pose == other.pose); - } + /// status of the sub-pose + ERigSubPoseStatus status = ERigSubPoseStatus::UNINITIALIZED; + /// relative pose of the sub-pose + geometry::Pose3 pose; + + /** + * @brief RigSubPose constructor + * @param pose The relative pose of the sub-pose + * @param status The status of the sub-pose + */ + RigSubPose(const geometry::Pose3& pose = geometry::Pose3(), ERigSubPoseStatus status = ERigSubPoseStatus::UNINITIALIZED) + : pose(pose), + status(status) + {} + + /** + * @brief operator == + * @param other the other RigSubPose + * @return true if the current RigSubPose and the other are identical. + */ + bool operator==(const RigSubPose& other) const { return (status == other.status && pose == other.pose); } }; class Rig { -public: - - /** - * @brief Rig constructor - * @param nbSubPoses The number of sub-poses of the rig - */ - explicit Rig(unsigned int nbSubPoses = 0) - { - _subPoses.resize(nbSubPoses); - } - - - /** - * @brief operator == - * @param other the other Rig - * @return true if the current rig and the other are identical. - */ - bool operator==(const Rig& other) const - { - return _subPoses == other._subPoses; - } - - /** - * @brief Check if the rig has at least one sub-pose initialized - * @return true if at least one subpose initialized - */ - bool isInitialized() const - { - for(const RigSubPose& subPose : _subPoses) + public: + /** + * @brief Rig constructor + * @param nbSubPoses The number of sub-poses of the rig + */ + explicit Rig(unsigned int nbSubPoses = 0) { _subPoses.resize(nbSubPoses); } + + /** + * @brief operator == + * @param other the other Rig + * @return true if the current rig and the other are identical. + */ + bool operator==(const Rig& other) const { return _subPoses == other._subPoses; } + + /** + * @brief Check if the rig has at least one sub-pose initialized + * @return true if at least one subpose initialized + */ + bool isInitialized() const { - if(subPose.status != ERigSubPoseStatus::UNINITIALIZED) - return true; + for (const RigSubPose& subPose : _subPoses) + { + if (subPose.status != ERigSubPoseStatus::UNINITIALIZED) + return true; + } + return false; } - return false; - } - bool isFullyCalibrated() const - { - for(const RigSubPose& subPose : _subPoses) + bool isFullyCalibrated() const { - if(subPose.status == ERigSubPoseStatus::UNINITIALIZED) - return false; + for (const RigSubPose& subPose : _subPoses) + { + if (subPose.status == ERigSubPoseStatus::UNINITIALIZED) + return false; + } + return true; } - return true; - } - - /** - * @brief Get the number of sub-poses in the rig - * @return number of sub-poses in the rig - */ - std::size_t getNbSubPoses() const - { - return _subPoses.size(); - } - - /** - * @brief Get the sub-poses const vector - * @return rig sub-poses - */ - const std::vector& getSubPoses() const - { - return _subPoses; - } - - /** - * @brief Get the sub-poses const vector - * @return rig sub-poses - */ - std::vector& getSubPoses() - { - return _subPoses; - } - /** - * @brief Get the sub-pose for the given sub-pose index - * @param index The sub-pose index - * @return corresponding rig sub-pose - */ - const RigSubPose& getSubPose(IndexT index) const - { - return _subPoses.at(index); - } - - /** - * @brief Get the sub-pose for the given sub-pose index - * @param index The sub-pose index - * @return corresponding rig sub-pose - */ - RigSubPose& getSubPose(IndexT index) - { - return _subPoses.at(index); - } - - /** - * @brief Set the given sub-pose for the given sub-pose index - * @param index The sub-pose index - * @param rigSubPose The rig sub-pose - */ - void setSubPose(IndexT index, const RigSubPose& rigSubPose) - { - assert(_subPoses.size() > index); - _subPoses[index] = rigSubPose; - } - - /** - * @brief Reset all sub-poses parameters - */ - void reset() - { - for(RigSubPose& subPose : _subPoses) + + /** + * @brief Get the number of sub-poses in the rig + * @return number of sub-poses in the rig + */ + std::size_t getNbSubPoses() const { return _subPoses.size(); } + + /** + * @brief Get the sub-poses const vector + * @return rig sub-poses + */ + const std::vector& getSubPoses() const { return _subPoses; } + + /** + * @brief Get the sub-poses const vector + * @return rig sub-poses + */ + std::vector& getSubPoses() { return _subPoses; } + /** + * @brief Get the sub-pose for the given sub-pose index + * @param index The sub-pose index + * @return corresponding rig sub-pose + */ + const RigSubPose& getSubPose(IndexT index) const { return _subPoses.at(index); } + + /** + * @brief Get the sub-pose for the given sub-pose index + * @param index The sub-pose index + * @return corresponding rig sub-pose + */ + RigSubPose& getSubPose(IndexT index) { return _subPoses.at(index); } + + /** + * @brief Set the given sub-pose for the given sub-pose index + * @param index The sub-pose index + * @param rigSubPose The rig sub-pose + */ + void setSubPose(IndexT index, const RigSubPose& rigSubPose) { - subPose.status = ERigSubPoseStatus::UNINITIALIZED; - subPose.pose = geometry::Pose3(); + assert(_subPoses.size() > index); + _subPoses[index] = rigSubPose; } - } -private: + /** + * @brief Reset all sub-poses parameters + */ + void reset() + { + for (RigSubPose& subPose : _subPoses) + { + subPose.status = ERigSubPoseStatus::UNINITIALIZED; + subPose.pose = geometry::Pose3(); + } + } - /// rig sub-poses - std::vector _subPoses; + private: + /// rig sub-poses + std::vector _subPoses; }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/RotationPrior.hpp b/src/aliceVision/sfmData/RotationPrior.hpp index 9fe8573ac3..0987248d8c 100644 --- a/src/aliceVision/sfmData/RotationPrior.hpp +++ b/src/aliceVision/sfmData/RotationPrior.hpp @@ -17,27 +17,25 @@ namespace sfmData { */ struct RotationPrior { - RotationPrior() = default; - - RotationPrior(IndexT view_first = UndefinedIndexT, - IndexT view_second = UndefinedIndexT, - Eigen::Matrix3d second_R_first = Eigen::Matrix3d::Identity()) - : ViewFirst(view_first), - ViewSecond(view_second), - _second_R_first(second_R_first) - {} - - IndexT ViewFirst; - IndexT ViewSecond; - Eigen::Matrix3d _second_R_first; - - bool operator==(const RotationPrior& other) const - { - return (ViewFirst == other.ViewFirst) && - (ViewSecond == other.ViewSecond) && - (_second_R_first == other._second_R_first); - } + RotationPrior() = default; + + RotationPrior(IndexT view_first = UndefinedIndexT, + IndexT view_second = UndefinedIndexT, + Eigen::Matrix3d second_R_first = Eigen::Matrix3d::Identity()) + : ViewFirst(view_first), + ViewSecond(view_second), + _second_R_first(second_R_first) + {} + + IndexT ViewFirst; + IndexT ViewSecond; + Eigen::Matrix3d _second_R_first; + + bool operator==(const RotationPrior& other) const + { + return (ViewFirst == other.ViewFirst) && (ViewSecond == other.ViewSecond) && (_second_R_first == other._second_R_first); + } }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/SfMData.cpp b/src/aliceVision/sfmData/SfMData.cpp index f663b19a1a..01e988b131 100644 --- a/src/aliceVision/sfmData/SfMData.cpp +++ b/src/aliceVision/sfmData/SfMData.cpp @@ -20,13 +20,9 @@ using namespace aliceVision::image; namespace fs = boost::filesystem; -SfMData::SfMData() -{ -} +SfMData::SfMData() {} -SfMData::~SfMData() -{ -} +SfMData::~SfMData() {} bool SfMData::operator==(const SfMData& other) const { @@ -118,7 +114,7 @@ std::vector toAbsoluteFolders(const std::vector& folde // Else, convert relative paths to absolute paths std::vector absolutePaths; absolutePaths.reserve(folders.size()); - for (const auto& folder: folders) + for (const auto& folder : folders) { const fs::path f = fs::absolute(folder, fs::path(absolutePath).parent_path()); if (fs::exists(f)) @@ -134,7 +130,7 @@ std::vector toAbsoluteFolders(const std::vector& folde return absolutePaths; } -/** +/** * @brief Add paths contained in \p folders to \p dst as relative paths to \p absolutePath. * Paths already present in \p dst are omitted. * @param[in] dst list in which paths should be added @@ -143,7 +139,7 @@ std::vector toAbsoluteFolders(const std::vector& folde */ void addAsRelativeFolders(std::vector& dst, const std::vector& folders, const std::string& absolutePath) { - for (auto folderPath: folders) + for (auto folderPath : folders) { // If absolutePath is set, convert to relative path if (!absolutePath.empty() && fs::path(folderPath).is_absolute()) @@ -158,25 +154,13 @@ void addAsRelativeFolders(std::vector& dst, const std::vector SfMData::getFeaturesFolders() const -{ - return toAbsoluteFolders(_featuresFolders, _absolutePath); -} +std::vector SfMData::getFeaturesFolders() const { return toAbsoluteFolders(_featuresFolders, _absolutePath); } -std::vector SfMData::getMatchesFolders() const -{ - return toAbsoluteFolders(_matchesFolders, _absolutePath); -} +std::vector SfMData::getMatchesFolders() const { return toAbsoluteFolders(_matchesFolders, _absolutePath); } -void SfMData::addFeaturesFolders(const std::vector& folders) -{ - addAsRelativeFolders(_featuresFolders, folders, _absolutePath); -} +void SfMData::addFeaturesFolders(const std::vector& folders) { addAsRelativeFolders(_featuresFolders, folders, _absolutePath); } -void SfMData::addMatchesFolders(const std::vector& folders) -{ - addAsRelativeFolders(_matchesFolders, folders, _absolutePath); -} +void SfMData::addMatchesFolders(const std::vector& folders) { addAsRelativeFolders(_matchesFolders, folders, _absolutePath); } void SfMData::setAbsolutePath(const std::string& path) { @@ -196,7 +180,7 @@ std::set SfMData::getValidViews() const std::set valid_idx; for (Views::const_iterator it = _views.begin(); it != _views.end(); ++it) { - const View * v = it->second.get(); + const View* v = it->second.get(); if (isPoseAndIntrinsicDefined(v)) { valid_idx.insert(v->getViewId()); @@ -210,7 +194,7 @@ std::set SfMData::getReconstructedIntrinsics() const std::set valid_idx; for (Views::const_iterator it = _views.begin(); it != _views.end(); ++it) { - const View * v = it->second.get(); + const View* v = it->second.get(); if (isPoseAndIntrinsicDefined(v)) { valid_idx.insert(v->getIntrinsicId()); @@ -250,7 +234,6 @@ void SfMData::setPose(const View& view, const CameraPose& absolutePose) throw std::runtime_error("SfMData::setPose: dependant view pose not part of an initialized rig."); } - void SfMData::combine(const SfMData& sfmData) { if (!_rigs.empty() && !sfmData._rigs.empty()) @@ -311,8 +294,8 @@ LandmarksPerView getLandmarksPerViews(const SfMData& sfmData) } } - // Sort landmark Ids in each view - #pragma omp parallel for +// Sort landmark Ids in each view +#pragma omp parallel for for (int i = 0; i < landmarksPerView.size(); ++i) { LandmarksPerView::iterator it = landmarksPerView.begin(); @@ -323,5 +306,5 @@ LandmarksPerView getLandmarksPerViews(const SfMData& sfmData) return landmarksPerView; } -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/SfMData.hpp b/src/aliceVision/sfmData/SfMData.hpp index be547a4edc..52dc54baf7 100644 --- a/src/aliceVision/sfmData/SfMData.hpp +++ b/src/aliceVision/sfmData/SfMData.hpp @@ -45,10 +45,10 @@ using PosesUncertainty = HashMap; /// Define uncertainty per landmark using LandmarksUncertainty = HashMap; -///Define a collection of constraints +/// Define a collection of constraints using Constraints2D = std::vector; -///Define a collection of rotation priors +/// Define a collection of rotation priors using RotationPriors = std::vector; /** @@ -57,7 +57,7 @@ using RotationPriors = std::vector; */ class SfMData { -public: + public: /// Uncertainty per pose PosesUncertainty _posesUncertainty; /// Uncertainty per landmark @@ -82,68 +82,62 @@ class SfMData * @brief Get views * @return views */ - const Views& getViews() const {return _views;} - Views& getViews() {return _views;} + const Views& getViews() const { return _views; } + Views& getViews() { return _views; } /** * @brief Get poses * @return poses - */ - const Poses& getPoses() const {return _poses;} - Poses& getPoses() {return _poses;} + */ + const Poses& getPoses() const { return _poses; } + Poses& getPoses() { return _poses; } /** * @brief Get rigs * @return rigs */ - const Rigs& getRigs() const {return _rigs;} - Rigs& getRigs() {return _rigs;} + const Rigs& getRigs() const { return _rigs; } + Rigs& getRigs() { return _rigs; } /** * @brief Get intrinsics * @return intrinsics */ - const Intrinsics& getIntrinsics() const {return _intrinsics;} - Intrinsics& getIntrinsics() {return _intrinsics;} + const Intrinsics& getIntrinsics() const { return _intrinsics; } + Intrinsics& getIntrinsics() { return _intrinsics; } /** * @brief Get landmarks * @return landmarks */ - const Landmarks& getLandmarks() const {return _structure;} - Landmarks& getLandmarks() {return _structure;} + const Landmarks& getLandmarks() const { return _structure; } + Landmarks& getLandmarks() { return _structure; } /** * @brief Get Constraints2D * @return Constraints2D */ - const Constraints2D& getConstraints2D() const {return constraints2d;} - Constraints2D& getConstraints2D() {return constraints2d;} + const Constraints2D& getConstraints2D() const { return constraints2d; } + Constraints2D& getConstraints2D() { return constraints2d; } /** * @brief Get RotationPriors * @return RotationPriors */ - const RotationPriors& getRotationPriors() const {return rotationpriors;} - RotationPriors& getRotationPriors() {return rotationpriors;} + const RotationPriors& getRotationPriors() const { return rotationpriors; } + RotationPriors& getRotationPriors() { return rotationpriors; } /** * @brief Get relative features folder paths * @return features folders paths */ - const std::vector& getRelativeFeaturesFolders() const - { - return _featuresFolders; - } + const std::vector& getRelativeFeaturesFolders() const { return _featuresFolders; } /** * @brief Get relative matches folder paths * @return matches folder paths */ - const std::vector& getRelativeMatchesFolders() const - { - return _matchesFolders; - } + const std::vector& getRelativeMatchesFolders() const { return _matchesFolders; } /** * @brief Get absolute features folder paths @@ -186,7 +180,7 @@ class SfMData */ camera::IntrinsicBase* getIntrinsicPtr(IndexT intrinsicId) { - if(_intrinsics.count(intrinsicId)) + if (_intrinsics.count(intrinsicId)) return _intrinsics.at(intrinsicId).get(); return nullptr; } @@ -197,7 +191,7 @@ class SfMData */ std::shared_ptr getIntrinsicsharedPtr(IndexT intrinsicId) { - if(_intrinsics.count(intrinsicId)) + if (_intrinsics.count(intrinsicId)) return _intrinsics.at(intrinsicId); return nullptr; } @@ -208,7 +202,7 @@ class SfMData */ const std::shared_ptr getIntrinsicsharedPtr(IndexT intrinsicId) const { - if(_intrinsics.count(intrinsicId)) + if (_intrinsics.count(intrinsicId)) return _intrinsics.at(intrinsicId); return nullptr; } @@ -220,7 +214,7 @@ class SfMData std::set getViewsKeys() const { std::set viewKeys; - for (auto v: _views) + for (auto v : _views) viewKeys.insert(v.first); return viewKeys; } @@ -234,13 +228,9 @@ class SfMData { if (view == nullptr) return false; - return ( - view->getIntrinsicId() != UndefinedIndexT && - view->getPoseId() != UndefinedIndexT && - (!view->isPartOfRig() || view->isPoseIndependant() || getRigSubPose(*view).status != ERigSubPoseStatus::UNINITIALIZED) && - _intrinsics.find(view->getIntrinsicId()) != _intrinsics.end() && - _poses.find(view->getPoseId()) != _poses.end() - ); + return (view->getIntrinsicId() != UndefinedIndexT && view->getPoseId() != UndefinedIndexT && + (!view->isPartOfRig() || view->isPoseIndependant() || getRigSubPose(*view).status != ERigSubPoseStatus::UNINITIALIZED) && + _intrinsics.find(view->getIntrinsicId()) != _intrinsics.end() && _poses.find(view->getPoseId()) != _poses.end()); } /** @@ -248,60 +238,42 @@ class SfMData * @param[in] viewID The given viewID * @return true if intrinsic and pose defined */ - bool isPoseAndIntrinsicDefined(IndexT viewId) const - { - return isPoseAndIntrinsicDefined(_views.at(viewId).get()); - } + bool isPoseAndIntrinsicDefined(IndexT viewId) const { return isPoseAndIntrinsicDefined(_views.at(viewId).get()); } /** * @brief Check if the given view has an existing pose * @param[in] view The given view * @return true if the pose exists */ - bool existsPose(const View& view) const - { - return (_poses.find(view.getPoseId()) != _poses.end()); - } + bool existsPose(const View& view) const { return (_poses.find(view.getPoseId()) != _poses.end()); } /** * @brief Gives the view of the input view id. * @param[in] viewId The given view id * @return the corresponding view reference */ - View& getView(IndexT viewId) - { - return *(_views.at(viewId)); - } + View& getView(IndexT viewId) { return *(_views.at(viewId)); } /** * @brief Gives the view of the input view id. * @param[in] viewId The given view id * @return the corresponding view ptr */ - View::ptr getViewPtr(IndexT viewId) - { - return _views.at(viewId).get(); - } + View::ptr getViewPtr(IndexT viewId) { return _views.at(viewId).get(); } /** * @brief Gives the view of the input view id. * @param[in] viewId The given view id * @return the corresponding view ptr */ - View::sptr getViewSharedPtr(IndexT viewId) - { - return _views.at(viewId); - } + View::sptr getViewSharedPtr(IndexT viewId) { return _views.at(viewId); } /** * @brief Gives the view of the input view id. * @param[in] viewId The given view id * @return the corresponding view reference */ - const View& getView(IndexT viewId) const - { - return *(_views.at(viewId)); - } + const View& getView(IndexT viewId) const { return *(_views.at(viewId)); } /** * @brief Gives the pose of the input view. If this view is part of a rig, it returns rigPose + rigSubPose. @@ -331,10 +303,7 @@ class SfMData * @brief Gives the pose with the given pose id. * @param[in] poseId The given pose id */ - const CameraPose& getAbsolutePose(IndexT poseId) const - { - return _poses.at(poseId); - } + const CameraPose& getAbsolutePose(IndexT poseId) const { return _poses.at(poseId); } /** * @brief Get the rig of the given view @@ -383,7 +352,7 @@ class SfMData std::vector cameraExposureList; cameraExposureList.reserve(_views.size()); - for(const auto& view : _views) + for (const auto& view : _views) { const ExposureSetting ce = view.second->getImage().getCameraExposureSetting(); if (ce.isPartiallyDefined()) @@ -394,26 +363,23 @@ class SfMData } } - std::nth_element(cameraExposureList.begin(), cameraExposureList.begin() + cameraExposureList.size()/2, cameraExposureList.end()); - const ExposureSetting& ceMedian = cameraExposureList[cameraExposureList.size()/2]; + std::nth_element(cameraExposureList.begin(), cameraExposureList.begin() + cameraExposureList.size() / 2, cameraExposureList.end()); + const ExposureSetting& ceMedian = cameraExposureList[cameraExposureList.size() / 2]; return ceMedian; } /** * @brief Add the given \p folder to features folders. - * @note If SfmData's absolutePath has been set, + * @note If SfmData's absolutePath has been set, * an absolute path will be converted to a relative one. * @param[in] folder path to a folder containing features */ - inline void addFeaturesFolder(const std::string& folder) - { - addFeaturesFolders({folder}); - } + inline void addFeaturesFolder(const std::string& folder) { addFeaturesFolders({folder}); } /** * @brief Add the given \p folders to features folders. - * @note If SfmData's absolutePath has been set, + * @note If SfmData's absolutePath has been set, * absolute paths will be converted to relative ones. * @param[in] folders paths to folders containing features */ @@ -421,18 +387,15 @@ class SfMData /** * @brief Add the given \p folder to matches folders. - * @note If SfmData's absolutePath has been set, + * @note If SfmData's absolutePath has been set, * an absolute path will be converted to a relative one. * @param[in] folder path to a folder containing matches */ - inline void addMatchesFolder(const std::string& folder) - { - addMatchesFolders({folder}); - } + inline void addMatchesFolder(const std::string& folder) { addMatchesFolders({folder}); } /** * @brief Add the given \p folders to matches folders. - * @note If SfmData's absolutePath has been set, + * @note If SfmData's absolutePath has been set, * absolute paths will be converted to relative ones. * @param[in] folders paths to folders containing matches */ @@ -440,7 +403,7 @@ class SfMData /** * @brief Replace the current features folders by the given ones. - * @note If SfmData's absolutePath has been set, + * @note If SfmData's absolutePath has been set, * absolute paths will be converted to relative ones. * @param[in] folders paths to folders containing features */ @@ -452,7 +415,7 @@ class SfMData /** * @brief Replace the current matches folders by the given ones. - * @note If SfmData's absolutePath has been set, + * @note If SfmData's absolutePath has been set, * absolute paths will be converted to relative ones. * @param[in] folders paths to folders containing matches */ @@ -464,7 +427,7 @@ class SfMData /** * @brief Set the SfMData file absolute path. - * @note Internal relative features/matches folders will be remapped + * @note Internal relative features/matches folders will be remapped * to be relative to the new absolute \p path. * @param[in] path The absolute path to the SfMData file folder */ @@ -478,16 +441,12 @@ class SfMData */ void setPose(const View& view, const CameraPose& pose); - /** * @brief Set the given pose for the given poseId * @param[in] poseId The given poseId * @param[in] pose The given pose */ - void setAbsolutePose(IndexT poseId, const CameraPose& pose) - { - _poses[poseId] = pose; - } + void setAbsolutePose(IndexT poseId, const CameraPose& pose) { _poses[poseId] = pose; } /** * @brief Erase yhe pose for the given poseId @@ -496,7 +455,7 @@ class SfMData */ void erasePose(IndexT poseId, bool noThrow = false) { - auto it =_poses.find(poseId); + auto it = _poses.find(poseId); if (it != _poses.end()) _poses.erase(it); else if (!noThrow) @@ -521,7 +480,7 @@ class SfMData void clear(); -private: + private: /// Structure (3D points with their 2D observations) Landmarks _structure; /// Considered camera intrinsics (indexed by view.getIntrinsicId()) @@ -544,10 +503,7 @@ class SfMData * @param[in] view The given view * @return Rig pose of the given camera view */ - const CameraPose& getRigPose(const View& view) const - { - return _poses.at(view.getPoseId()); - } + const CameraPose& getRigPose(const View& view) const { return _poses.at(view.getPoseId()); } /** * @brief Get Rig subPose of a given camera view @@ -566,10 +522,7 @@ class SfMData * @param[in] view The given view * @return Rig pose of the given camera view */ - CameraPose& getRigPose(const View& view) - { - return _poses.at(view.getPoseId()); - } + CameraPose& getRigPose(const View& view) { return _poses.at(view.getPoseId()); } /** * @brief Get Rig subPose of a given camera view @@ -589,5 +542,5 @@ using LandmarksPerView = stl::flat_map; LandmarksPerView getLandmarksPerViews(const SfMData& sfmData); -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/View.cpp b/src/aliceVision/sfmData/View.cpp index 6edd72cfaf..a69891a405 100644 --- a/src/aliceVision/sfmData/View.cpp +++ b/src/aliceVision/sfmData/View.cpp @@ -19,10 +19,5 @@ #include namespace aliceVision { -namespace sfmData { - - - - -} // namespace sfmData -} // namespace aliceVision +namespace sfmData {} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/View.hpp b/src/aliceVision/sfmData/View.hpp index 55f0799df6..1af8af28f2 100644 --- a/src/aliceVision/sfmData/View.hpp +++ b/src/aliceVision/sfmData/View.hpp @@ -31,292 +31,223 @@ namespace sfmData { */ class View { -public: - using sptr = std::shared_ptr; - using ptr = View*; - -public: - - /** - * @brief View Constructor - * @param[in] imagePath The image path on disk - * @param[in] viewId The view id (use unique index) - * @param[in] intrinsicId The intrinsic id - * @param[in] poseId The pose id (or the rig pose id) - * @param[in] width The image width - * @param[in] height The image height - * @param[in] rigId The rig id (or undefined) - * @param[in] subPoseId The sub-pose id (or undefined) - * @param[in] metadata The image metadata - */ - View(const std::string& imagePath = "", - IndexT viewId = UndefinedIndexT, - IndexT intrinsicId = UndefinedIndexT, - IndexT poseId = UndefinedIndexT, - std::size_t width = 0, - std::size_t height = 0, - IndexT rigId = UndefinedIndexT, - IndexT subPoseId = UndefinedIndexT, - const std::map& metadata = std::map()) - : _viewId(viewId) - , _intrinsicId(intrinsicId) - , _poseId(poseId) - , _rigId(rigId) - , _subPoseId(subPoseId) - , _image(new ImageInfo(imagePath, width, height)) - {} - - /** - * @brief Shallow View Copy - * return a pointer to a new View - */ - View * shallowClone() - { - return new View(*this); - } - - /** - * @brief Deep View Copy - * return a pointer to a new View - */ - View * clone() - { - View * v = new View(*this); - v->_image = std::make_shared(*this->_image); - - return v; - } - - - - bool operator==(const View& other) const - { - // image paths can be different - return _viewId == other._viewId && - _intrinsicId == other._intrinsicId && - _poseId == other._poseId && - _rigId == other._rigId && - _subPoseId == other._subPoseId; - } - - inline bool operator!=(const View& other) const { return !(*this == other); } - - /** - * @brief Get Image object - * @return an object - */ - const ImageInfo & getImage() const - { - return *_image; - } - - /** - * @brief Get Image object - * @return an object - */ - ImageInfo & getImage() - { - return *_image; - } - - /** - * @brief Get the view id - * @return view id - */ - IndexT getViewId() const - { - return _viewId; - } - - /** - * @brief Get the intrinsic id - * @return intrinsic id - */ - IndexT getIntrinsicId() const - { - return _intrinsicId; - } - - /** - * @brief Get the pose id - * @return pose id - */ - IndexT getPoseId() const - { - return _poseId; - } - - /** - * @brief Get the rig id - * @return rig id or undefined - */ - IndexT getRigId() const - { - return _rigId; - } - - /** - * @brief Get the sub-pose id - * @return sup-pose id or undefined - */ - IndexT getSubPoseId() const - { - return _subPoseId; - } - - /** - * @brief Get the frame id - * @return frame id - */ - IndexT getFrameId() const - { - return _frameId; - } - - /** - * @brief Get the resection id - * @return resection id - */ - IndexT getResectionId() const - { - return _resectionId; - } - - /** - * @brief Return if true or false the view is part of a rig - * @return true if the view is part of a rig - */ - bool isPartOfRig() const - { - return _rigId != UndefinedIndexT; - } - - /** - * @brief If the view is part of a camera rig, the camera can be a sub-pose of the rig pose but can also be temporarily solved independently. - * @return true if the view is not part of a rig. - * true if the view is part of a rig and the camera is solved separately. - * false if the view is part of a rig and the camera is solved as a sub-pose of the rig pose. - */ - bool isPoseIndependant() const - { - return (!isPartOfRig() || _isPoseIndependent); - } - - - /** - * @brief Set the given view id - * @param[in] viewId The given view id - */ - void setViewId(IndexT viewId) - { - _viewId = viewId; - } - - /** - * @brief Set the given intrinsic id - * @param[in] intrinsicId The given intrinsic id - */ - void setIntrinsicId(IndexT intrinsicId) - { - _intrinsicId = intrinsicId; - } - - /** - * @brief Set the given pose id - * @param[in] poseId The given pose id - */ - void setPoseId(IndexT poseId) - { - _poseId = poseId; - } - - /** - * @brief setIndependantPose - * @param independant - */ - void setIndependantPose(bool independent) - { - _isPoseIndependent = independent; - } - - /** - * @brief Set the given rig id and the given sub-pose id - * @param[in] rigId The given rig id - * @param[in] subPoseId The given sub-pose id - */ - void setRigAndSubPoseId(IndexT rigId, IndexT subPoseId) - { - _rigId = rigId; - _subPoseId = subPoseId; - } - - - /** - * @brief Set the given frame id - * @param[in] frame The given frame id - */ - void setFrameId(IndexT frameId) - { - _frameId = frameId; - } - - /** - * @brief Get the list of viewID referencing the source views called "Ancestors" - * If an image is generated from multiple input images, "Ancestors" allows to keep track of the viewIDs of the original inputs views. - * For instance, the generated view can come from the fusion of multiple LDR images into one HDR image, the fusion from multi-focus - * stacking to get a fully focused image, fusion of images with multiple lighting to get a more diffuse lighting, etc. - * @return list of viewID of the ancestors - * @param[in] viewId the view ancestor id - */ - void addAncestor(IndexT viewId) - { - _ancestors.push_back(viewId); - } - - /** - * @Brief get all ancestors for this view - * @return ancestors - */ - const std::vector & getAncestors() const - { - return _ancestors; - } - - /** - * @brief Set the given resection id - * @param[in] resectionId The given resection id - */ - void setResectionId(IndexT resectionId) - { - _resectionId = resectionId; - } - -private: - View(const View & v) = default; - -private: - - /// view id - IndexT _viewId; - /// intrinsics id - IndexT _intrinsicId; - /// either the pose of the rig or the pose of the camera if there's no rig - IndexT _poseId; - /// corresponding rig id or undefined - IndexT _rigId; - /// corresponding sub-pose id or undefined - IndexT _subPoseId; - /// corresponding frame id for synchronized views - IndexT _frameId = UndefinedIndexT; - /// resection id - IndexT _resectionId = UndefinedIndexT; - /// pose independent of other view(s) - bool _isPoseIndependent = true; - /// list of ancestors - std::vector _ancestors; - /// Link to imageinfo - std::shared_ptr _image; + public: + using sptr = std::shared_ptr; + using ptr = View*; + + public: + /** + * @brief View Constructor + * @param[in] imagePath The image path on disk + * @param[in] viewId The view id (use unique index) + * @param[in] intrinsicId The intrinsic id + * @param[in] poseId The pose id (or the rig pose id) + * @param[in] width The image width + * @param[in] height The image height + * @param[in] rigId The rig id (or undefined) + * @param[in] subPoseId The sub-pose id (or undefined) + * @param[in] metadata The image metadata + */ + View(const std::string& imagePath = "", + IndexT viewId = UndefinedIndexT, + IndexT intrinsicId = UndefinedIndexT, + IndexT poseId = UndefinedIndexT, + std::size_t width = 0, + std::size_t height = 0, + IndexT rigId = UndefinedIndexT, + IndexT subPoseId = UndefinedIndexT, + const std::map& metadata = std::map()) + : _viewId(viewId), + _intrinsicId(intrinsicId), + _poseId(poseId), + _rigId(rigId), + _subPoseId(subPoseId), + _image(new ImageInfo(imagePath, width, height)) + {} + + /** + * @brief Shallow View Copy + * return a pointer to a new View + */ + View* shallowClone() { return new View(*this); } + + /** + * @brief Deep View Copy + * return a pointer to a new View + */ + View* clone() + { + View* v = new View(*this); + v->_image = std::make_shared(*this->_image); + + return v; + } + + bool operator==(const View& other) const + { + // image paths can be different + return _viewId == other._viewId && _intrinsicId == other._intrinsicId && _poseId == other._poseId && _rigId == other._rigId && + _subPoseId == other._subPoseId; + } + + inline bool operator!=(const View& other) const { return !(*this == other); } + + /** + * @brief Get Image object + * @return an object + */ + const ImageInfo& getImage() const { return *_image; } + + /** + * @brief Get Image object + * @return an object + */ + ImageInfo& getImage() { return *_image; } + + /** + * @brief Get the view id + * @return view id + */ + IndexT getViewId() const { return _viewId; } + + /** + * @brief Get the intrinsic id + * @return intrinsic id + */ + IndexT getIntrinsicId() const { return _intrinsicId; } + + /** + * @brief Get the pose id + * @return pose id + */ + IndexT getPoseId() const { return _poseId; } + + /** + * @brief Get the rig id + * @return rig id or undefined + */ + IndexT getRigId() const { return _rigId; } + + /** + * @brief Get the sub-pose id + * @return sup-pose id or undefined + */ + IndexT getSubPoseId() const { return _subPoseId; } + + /** + * @brief Get the frame id + * @return frame id + */ + IndexT getFrameId() const { return _frameId; } + + /** + * @brief Get the resection id + * @return resection id + */ + IndexT getResectionId() const { return _resectionId; } + + /** + * @brief Return if true or false the view is part of a rig + * @return true if the view is part of a rig + */ + bool isPartOfRig() const { return _rigId != UndefinedIndexT; } + + /** + * @brief If the view is part of a camera rig, the camera can be a sub-pose of the rig pose but can also be temporarily solved independently. + * @return true if the view is not part of a rig. + * true if the view is part of a rig and the camera is solved separately. + * false if the view is part of a rig and the camera is solved as a sub-pose of the rig pose. + */ + bool isPoseIndependant() const { return (!isPartOfRig() || _isPoseIndependent); } + + /** + * @brief Set the given view id + * @param[in] viewId The given view id + */ + void setViewId(IndexT viewId) { _viewId = viewId; } + + /** + * @brief Set the given intrinsic id + * @param[in] intrinsicId The given intrinsic id + */ + void setIntrinsicId(IndexT intrinsicId) { _intrinsicId = intrinsicId; } + + /** + * @brief Set the given pose id + * @param[in] poseId The given pose id + */ + void setPoseId(IndexT poseId) { _poseId = poseId; } + + /** + * @brief setIndependantPose + * @param independant + */ + void setIndependantPose(bool independent) { _isPoseIndependent = independent; } + + /** + * @brief Set the given rig id and the given sub-pose id + * @param[in] rigId The given rig id + * @param[in] subPoseId The given sub-pose id + */ + void setRigAndSubPoseId(IndexT rigId, IndexT subPoseId) + { + _rigId = rigId; + _subPoseId = subPoseId; + } + + /** + * @brief Set the given frame id + * @param[in] frame The given frame id + */ + void setFrameId(IndexT frameId) { _frameId = frameId; } + + /** + * @brief Get the list of viewID referencing the source views called "Ancestors" + * If an image is generated from multiple input images, "Ancestors" allows to keep track of the viewIDs of the original inputs views. + * For instance, the generated view can come from the fusion of multiple LDR images into one HDR image, the fusion from multi-focus + * stacking to get a fully focused image, fusion of images with multiple lighting to get a more diffuse lighting, etc. + * @return list of viewID of the ancestors + * @param[in] viewId the view ancestor id + */ + void addAncestor(IndexT viewId) { _ancestors.push_back(viewId); } + + /** + * @Brief get all ancestors for this view + * @return ancestors + */ + const std::vector& getAncestors() const { return _ancestors; } + + /** + * @brief Set the given resection id + * @param[in] resectionId The given resection id + */ + void setResectionId(IndexT resectionId) { _resectionId = resectionId; } + + private: + View(const View& v) = default; + + private: + /// view id + IndexT _viewId; + /// intrinsics id + IndexT _intrinsicId; + /// either the pose of the rig or the pose of the camera if there's no rig + IndexT _poseId; + /// corresponding rig id or undefined + IndexT _rigId; + /// corresponding sub-pose id or undefined + IndexT _subPoseId; + /// corresponding frame id for synchronized views + IndexT _frameId = UndefinedIndexT; + /// resection id + IndexT _resectionId = UndefinedIndexT; + /// pose independent of other view(s) + bool _isPoseIndependent = true; + /// list of ancestors + std::vector _ancestors; + /// Link to imageinfo + std::shared_ptr _image; }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/colorize.cpp b/src/aliceVision/sfmData/colorize.cpp index 70047bc24a..abcc3a0b45 100644 --- a/src/aliceVision/sfmData/colorize.cpp +++ b/src/aliceVision/sfmData/colorize.cpp @@ -22,107 +22,105 @@ namespace sfmData { void colorizeTracks(SfMData& sfmData) { - auto progressDisplay = system::createConsoleProgressDisplay(sfmData.getLandmarks().size(), std::cout, - "\nCompute scene structure color\n"); - - std::vector> remainingLandmarksToColor; - remainingLandmarksToColor.reserve(sfmData.getLandmarks().size()); - - for(auto& landmarkPair : sfmData.getLandmarks()) - remainingLandmarksToColor.push_back(landmarkPair.second); - - struct ViewInfo - { - ViewInfo(IndexT viewId, std::size_t cardinal) - : viewId(viewId) - , cardinal(cardinal) - {} - - IndexT viewId; - std::size_t cardinal; - std::vector> landmarks; - }; - - std::vector sortedViewsCardinal; - sortedViewsCardinal.reserve(sfmData.getViews().size()); - { - // create cardinal per viewId map - std::map viewsCardinalMap; // - for(const auto& landmarkPair : sfmData.getLandmarks()) - { - const Observations& observations = landmarkPair.second.observations; - for(const auto& observationPair : observations) - ++viewsCardinalMap[observationPair.first]; // TODO: 0 - } - - // copy key-value pairs from the map to the vector - for(const auto& cardinalPair : viewsCardinalMap) - sortedViewsCardinal.push_back(ViewInfo(cardinalPair.first, cardinalPair.second)); + auto progressDisplay = system::createConsoleProgressDisplay(sfmData.getLandmarks().size(), std::cout, "\nCompute scene structure color\n"); - // sort the vector, biggest cardinality first - std::sort(sortedViewsCardinal.begin(), - sortedViewsCardinal.end(), - [] (const ViewInfo& l, const ViewInfo& r) { return l.cardinal > r.cardinal; }); - } + std::vector> remainingLandmarksToColor; + remainingLandmarksToColor.reserve(sfmData.getLandmarks().size()); - // assign each landmark to a view - for(ViewInfo& viewCardinal : sortedViewsCardinal) - { - std::vector> toKeep; - const IndexT viewId = viewCardinal.viewId; + for (auto& landmarkPair : sfmData.getLandmarks()) + remainingLandmarksToColor.push_back(landmarkPair.second); - for(int i = 0; i < remainingLandmarksToColor.size(); ++i) + struct ViewInfo { - Landmark& landmark = remainingLandmarksToColor.at(i); - auto it = landmark.observations.find(viewId); - if(it != landmark.observations.end()) - { - viewCardinal.landmarks.push_back(landmark); - } - else - { - toKeep.push_back(landmark); - } + ViewInfo(IndexT viewId, std::size_t cardinal) + : viewId(viewId), + cardinal(cardinal) + {} + + IndexT viewId; + std::size_t cardinal; + std::vector> landmarks; + }; + + std::vector sortedViewsCardinal; + sortedViewsCardinal.reserve(sfmData.getViews().size()); + { + // create cardinal per viewId map + std::map viewsCardinalMap; // + for (const auto& landmarkPair : sfmData.getLandmarks()) + { + const Observations& observations = landmarkPair.second.observations; + for (const auto& observationPair : observations) + ++viewsCardinalMap[observationPair.first]; // TODO: 0 + } + + // copy key-value pairs from the map to the vector + for (const auto& cardinalPair : viewsCardinalMap) + sortedViewsCardinal.push_back(ViewInfo(cardinalPair.first, cardinalPair.second)); + + // sort the vector, biggest cardinality first + std::sort( + sortedViewsCardinal.begin(), sortedViewsCardinal.end(), [](const ViewInfo& l, const ViewInfo& r) { return l.cardinal > r.cardinal; }); } - std::swap(toKeep, remainingLandmarksToColor); - if(remainingLandmarksToColor.empty()) - break; - } + // assign each landmark to a view + for (ViewInfo& viewCardinal : sortedViewsCardinal) + { + std::vector> toKeep; + const IndexT viewId = viewCardinal.viewId; + + for (int i = 0; i < remainingLandmarksToColor.size(); ++i) + { + Landmark& landmark = remainingLandmarksToColor.at(i); + auto it = landmark.observations.find(viewId); + if (it != landmark.observations.end()) + { + viewCardinal.landmarks.push_back(landmark); + } + else + { + toKeep.push_back(landmark); + } + } + std::swap(toKeep, remainingLandmarksToColor); + + if (remainingLandmarksToColor.empty()) + break; + } - std::random_device randomDevice; - std::mt19937 rng(randomDevice()); + std::random_device randomDevice; + std::mt19937 rng(randomDevice()); - // create an unsorted index container - std::vector unsortedIndexes(sortedViewsCardinal.size()) ; - std::iota(std::begin(unsortedIndexes), std::end(unsortedIndexes), 0); - std::shuffle(unsortedIndexes.begin(), unsortedIndexes.end(), rng); + // create an unsorted index container + std::vector unsortedIndexes(sortedViewsCardinal.size()); + std::iota(std::begin(unsortedIndexes), std::end(unsortedIndexes), 0); + std::shuffle(unsortedIndexes.begin(), unsortedIndexes.end(), rng); - // landmark colorization + // landmark colorization #pragma omp parallel for - for(int i = 0; i < unsortedIndexes.size(); ++i) - { - const ViewInfo& viewCardinal = sortedViewsCardinal.at(unsortedIndexes.at(i)); - if(!viewCardinal.landmarks.empty()) + for (int i = 0; i < unsortedIndexes.size(); ++i) { - const View& view = sfmData.getView(viewCardinal.viewId); - image::Image image; - image::readImage(view.getImage().getImagePath(), image, image::EImageColorSpace::SRGB); - - for(Landmark& landmark : viewCardinal.landmarks) - { - // color the point - Vec2 pt = landmark.observations.at(view.getViewId()).x; - // clamp the pixel position if the feature/marker center is outside the image. - pt.x() = clamp(pt.x(), 0.0, static_cast(image.Width() - 1)); - pt.y() = clamp(pt.y(), 0.0, static_cast(image.Height() - 1)); - landmark.rgb = image(pt.y(), pt.x()); - } - - progressDisplay += viewCardinal.landmarks.size(); + const ViewInfo& viewCardinal = sortedViewsCardinal.at(unsortedIndexes.at(i)); + if (!viewCardinal.landmarks.empty()) + { + const View& view = sfmData.getView(viewCardinal.viewId); + image::Image image; + image::readImage(view.getImage().getImagePath(), image, image::EImageColorSpace::SRGB); + + for (Landmark& landmark : viewCardinal.landmarks) + { + // color the point + Vec2 pt = landmark.observations.at(view.getViewId()).x; + // clamp the pixel position if the feature/marker center is outside the image. + pt.x() = clamp(pt.x(), 0.0, static_cast(image.Width() - 1)); + pt.y() = clamp(pt.y(), 0.0, static_cast(image.Height() - 1)); + landmark.rgb = image(pt.y(), pt.x()); + } + + progressDisplay += viewCardinal.landmarks.size(); + } } - } } -} // namespace sfm -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/colorize.hpp b/src/aliceVision/sfmData/colorize.hpp index 02d74fa06f..962f876c7a 100644 --- a/src/aliceVision/sfmData/colorize.hpp +++ b/src/aliceVision/sfmData/colorize.hpp @@ -20,5 +20,5 @@ class SfMData; */ void colorizeTracks(SfMData& sfmData); -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/exif.cpp b/src/aliceVision/sfmData/exif.cpp index fcb7a27554..737cbff9d9 100644 --- a/src/aliceVision/sfmData/exif.cpp +++ b/src/aliceVision/sfmData/exif.cpp @@ -11,41 +11,27 @@ namespace aliceVision { namespace sfmData { +std::string GPSExifTags::latitude() { return "GPS:Latitude"; } -std::string GPSExifTags::latitude() -{ - return "GPS:Latitude"; -} +std::string GPSExifTags::latitudeRef() { return "GPS:LatitudeRef"; } -std::string GPSExifTags::latitudeRef() -{ - return "GPS:LatitudeRef"; -} +std::string GPSExifTags::longitude() { return "GPS:Longitude"; } -std::string GPSExifTags::longitude() -{ - return "GPS:Longitude"; -} - -std::string GPSExifTags::longitudeRef() -{ - return "GPS:LongitudeRef"; -} +std::string GPSExifTags::longitudeRef() { return "GPS:LongitudeRef"; } std::vector GPSExifTags::all() { - return {GPSExifTags::latitude(), GPSExifTags::latitudeRef(), GPSExifTags::longitude(), GPSExifTags::longitudeRef(), GPSExifTags::altitude(), GPSExifTags::altitudeRef()}; + return {GPSExifTags::latitude(), + GPSExifTags::latitudeRef(), + GPSExifTags::longitude(), + GPSExifTags::longitudeRef(), + GPSExifTags::altitude(), + GPSExifTags::altitudeRef()}; } -std::string GPSExifTags::altitude() -{ - return "GPS:Altitude"; -} +std::string GPSExifTags::altitude() { return "GPS:Altitude"; } -std::string GPSExifTags::altitudeRef() -{ - return "GPS:AltitudeRef"; -} +std::string GPSExifTags::altitudeRef() { return "GPS:AltitudeRef"; } -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/exif.hpp b/src/aliceVision/sfmData/exif.hpp index 7932cba260..bb8c9be7ca 100644 --- a/src/aliceVision/sfmData/exif.hpp +++ b/src/aliceVision/sfmData/exif.hpp @@ -10,11 +10,8 @@ #include #include - -namespace aliceVision -{ -namespace sfmData -{ +namespace aliceVision { +namespace sfmData { /** * @brief EXIF Orientation to names @@ -22,15 +19,15 @@ namespace sfmData */ enum class EEXIFOrientation { - NONE = 1 - , REVERSED = 2 - , UPSIDEDOWN = 3 - , UPSIDEDOWN_REVERSED = 4 - , LEFT_REVERSED = 5 - , LEFT = 6 - , RIGHT_REVERSED = 7 - , RIGHT = 8 - , UNKNOWN = -1 + NONE = 1, + REVERSED = 2, + UPSIDEDOWN = 3, + UPSIDEDOWN_REVERSED = 4, + LEFT_REVERSED = 5, + LEFT = 6, + RIGHT_REVERSED = 7, + RIGHT = 8, + UNKNOWN = -1 }; struct GPSExifTags @@ -44,5 +41,5 @@ struct GPSExifTags static std::vector all(); }; -} -} \ No newline at end of file +} // namespace sfmData +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfmData/exposureSetting.hpp b/src/aliceVision/sfmData/exposureSetting.hpp index 38a8373a84..a484e5275c 100644 --- a/src/aliceVision/sfmData/exposureSetting.hpp +++ b/src/aliceVision/sfmData/exposureSetting.hpp @@ -12,16 +12,16 @@ namespace aliceVision { namespace sfmData { -class ExposureSetting { -public: +class ExposureSetting +{ + public: ExposureSetting() {} ExposureSetting(double shutter, double fnumber, double iso) - : _shutter(shutter) - , _fnumber(fnumber) - , _iso(iso) - { - } + : _shutter(shutter), + _fnumber(fnumber), + _iso(iso) + {} double _shutter{-1.0}; double _fnumber{-1.0}; @@ -31,26 +31,22 @@ class ExposureSetting { bool hasFNumber() const { return _fnumber > 0.0 && std::isnormal(_fnumber); } bool hasISO() const { return _iso > 0.0 && std::isnormal(_iso); } - bool isFullyDefined() const { - return hasShutter() && hasFNumber() && hasISO(); - } + bool isFullyDefined() const { return hasShutter() && hasFNumber() && hasISO(); } - bool isPartiallyDefined() const { - return hasShutter() || hasFNumber(); - } + bool isPartiallyDefined() const { return hasShutter() || hasFNumber(); } double getExposure(const double referenceISO = 100.0, const double referenceFNumber = 1.0) const { const bool validShutter = hasShutter(); const bool validFNumber = hasFNumber(); - if(!validShutter && !validFNumber) + if (!validShutter && !validFNumber) return -1.0; const bool validRefFNumber = referenceFNumber > 0.0 && std::isnormal(referenceFNumber); double shutter = _shutter; - if(!validShutter) + if (!validShutter) { shutter = 1.0 / 200.0; } @@ -58,15 +54,15 @@ class ExposureSetting { // Usually we should get a valid shutter speed, but we could have invalid fnumber. // For instance, if there is a connection problem between the lens and the camera, all lens related option like fnumber could be invalid. // In this particular case, the exposure should rely only on the shutter speed. - if(!validFNumber) + if (!validFNumber) { - if(validRefFNumber) + if (validRefFNumber) fnumber = referenceFNumber; else fnumber = 2.0; } double lReferenceFNumber = referenceFNumber; - if(!validRefFNumber) + if (!validRefFNumber) { lReferenceFNumber = fnumber; } @@ -82,7 +78,7 @@ class ExposureSetting { aperture2 = sqrt(iso1 / iso2) */ double iso_2_aperture = 1.0; - if(iso > 1e-6 && referenceISO > 1e-6) + if (iso > 1e-6 && referenceISO > 1e-6) { // Need to have both iso and reference iso to use it iso_2_aperture = std::sqrt(iso / referenceISO); @@ -115,7 +111,7 @@ class ExposureSetting { bool operator==(const ExposureSetting& other) const { return getExposure() == other.getExposure(); } }; -inline std::ostream& operator<<( std::ostream& os, const ExposureSetting& s) +inline std::ostream& operator<<(std::ostream& os, const ExposureSetting& s) { os << "shutter: " << s._shutter << ", fnumber: " << s._fnumber << ", iso: " << s._iso; return os; @@ -123,20 +119,20 @@ inline std::ostream& operator<<( std::ostream& os, const ExposureSetting& s) inline bool hasComparableExposures(const std::vector& exposuresSetting) { - if(exposuresSetting.size() < 2) + if (exposuresSetting.size() < 2) return false; const bool hasShutter = exposuresSetting.front().hasShutter(); const bool hasFNumber = exposuresSetting.front().hasFNumber(); const bool hasISO = exposuresSetting.front().hasISO(); - for(std::size_t i = 1; i < exposuresSetting.size(); ++i) + for (std::size_t i = 1; i < exposuresSetting.size(); ++i) { const ExposureSetting& s = exposuresSetting[i]; - if(hasShutter != s.hasShutter()) + if (hasShutter != s.hasShutter()) return false; - if(hasFNumber != s.hasFNumber()) + if (hasFNumber != s.hasFNumber()) return false; - if(hasISO != s.hasISO()) + if (hasISO != s.hasISO()) return false; } return true; @@ -146,12 +142,12 @@ inline std::vector getExposures(const std::vector& expo { std::vector output; output.reserve(exposuresSetting.size()); - for(const ExposureSetting& exp: exposuresSetting) + for (const ExposureSetting& exp : exposuresSetting) { output.push_back(exp.getExposure()); } return output; } -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/imageInfo.cpp b/src/aliceVision/sfmData/imageInfo.cpp index 35d5fb016c..0122ae37ce 100644 --- a/src/aliceVision/sfmData/imageInfo.cpp +++ b/src/aliceVision/sfmData/imageInfo.cpp @@ -13,29 +13,26 @@ namespace aliceVision { namespace sfmData { -double ImageInfo::getEv() const -{ - return std::log2(1.0 / getCameraExposureSetting().getExposure()); -} +double ImageInfo::getEv() const { return std::log2(1.0 / getCameraExposureSetting().getExposure()); } std::map::const_iterator ImageInfo::findMetadataIterator(const std::string& name) const { auto it = _metadata.find(name); - if(it != _metadata.end()) + if (it != _metadata.end()) return it; std::string nameLower = name; boost::algorithm::to_lower(nameLower); - for(auto mIt = _metadata.begin(); mIt != _metadata.end(); ++mIt) + for (auto mIt = _metadata.begin(); mIt != _metadata.end(); ++mIt) { std::string key = mIt->first; boost::algorithm::to_lower(key); - if(key.size() > name.size()) + if (key.size() > name.size()) { auto delimiterIt = key.find_last_of("/:"); - if(delimiterIt != std::string::npos) + if (delimiterIt != std::string::npos) key = key.substr(delimiterIt + 1); } - if(key == nameLower) + if (key == nameLower) { return mIt; } @@ -45,10 +42,7 @@ std::map::const_iterator ImageInfo::findMetadataIterat bool ImageInfo::hasMetadata(const std::vector& names) const { - return std::any_of(names.cbegin(), names.cend(), - [this](const std::string& name){ - return (findMetadataIterator(name) != _metadata.end()); - }); + return std::any_of(names.cbegin(), names.cend(), [this](const std::string& name) { return (findMetadataIterator(name) != _metadata.end()); }); } bool ImageInfo::hasDigitMetadata(const std::vector& names, bool isPositive) const @@ -56,11 +50,11 @@ bool ImageInfo::hasDigitMetadata(const std::vector& names, bool isP bool hasDigitMetadata = false; double value = -1.0; - for(const std::string& name : names) + for (const std::string& name : names) { const auto it = findMetadataIterator(name); - if(it == _metadata.end() || it->second.empty()) + if (it == _metadata.end() || it->second.empty()) { continue; } @@ -71,7 +65,7 @@ bool ImageInfo::hasDigitMetadata(const std::vector& names, bool isP hasDigitMetadata = true; break; } - catch(std::exception&) + catch (std::exception&) { value = -1.0; } @@ -82,10 +76,10 @@ bool ImageInfo::hasDigitMetadata(const std::vector& names, bool isP const std::string& ImageInfo::getMetadata(const std::vector& names) const { static const std::string emptyString; - for(const std::string& name : names) + for (const std::string& name : names) { const auto it = findMetadataIterator(name); - if(it != _metadata.end()) + if (it != _metadata.end()) return it->second; } return emptyString; @@ -98,7 +92,7 @@ double ImageInfo::readRealNumber(const std::string& str) const std::smatch m; std::regex pattern_frac("([0-9]+)\\/([0-9]+)"); - if(!std::regex_search(str, m, pattern_frac)) + if (!std::regex_search(str, m, pattern_frac)) { return std::stod(str); } @@ -106,12 +100,12 @@ double ImageInfo::readRealNumber(const std::string& str) const const int num = std::stoi(m[1].str()); const int denum = std::stoi(m[2].str()); - if(denum != 0) + if (denum != 0) return double(num) / double(denum); else return 0.0; } - catch(std::exception&) + catch (std::exception&) { return -1.0; } @@ -137,13 +131,13 @@ bool ImageInfo::getDoubleMetadata(const std::vector& names, double& int ImageInfo::getIntMetadata(const std::vector& names) const { const std::string value = getMetadata(names); - if(value.empty()) + if (value.empty()) return -1; try { return std::stoi(value); } - catch(std::exception&) + catch (std::exception&) { return -1; } @@ -152,7 +146,7 @@ int ImageInfo::getIntMetadata(const std::vector& names) const bool ImageInfo::hasGpsMetadata() const { const auto tags = GPSExifTags::all(); - return std::all_of(tags.cbegin(), tags.cend(), [this](const std::string& t){ return hasMetadata({t}); }); + return std::all_of(tags.cbegin(), tags.cend(), [this](const std::string& t) { return hasMetadata({t}); }); } Vec3 ImageInfo::getGpsPositionFromMetadata() const @@ -187,8 +181,12 @@ Vec3 ImageInfo::getGpsPositionWGS84FromMetadata() const return {lat, lon, alt}; } -int ImageInfo::getSensorSize(const std::vector& sensorDatabase, double& sensorWidth, double& sensorHeight, - double& focalLengthmm, camera::EInitMode& intrinsicInitMode, bool verbose) +int ImageInfo::getSensorSize(const std::vector& sensorDatabase, + double& sensorWidth, + double& sensorHeight, + double& focalLengthmm, + camera::EInitMode& intrinsicInitMode, + bool verbose) { int errCode = 0; @@ -232,7 +230,8 @@ int ImageInfo::getSensorSize(const std::vector& sensorDatab << "\t- image camera model: " << model << std::endl << "\t- sensor database camera brand: " << datasheet._brand << std::endl << "\t- sensor database camera model: " << datasheet._model << std::endl - << "\t- sensor database camera sensor width: " << datasheet._sensorWidth << " mm"); + << "\t- sensor database camera sensor width: " << datasheet._sensorWidth + << " mm"); ALICEVISION_LOG_WARNING("Please check and correct camera model(s) name in the sensor database." << std::endl); } } @@ -262,7 +261,7 @@ int ImageInfo::getSensorSize(const std::vector& sensorDatab { // no sensorWidth but valid focalLength and valid focalLengthIn35mm, so deduce // sensorWith approximation - const double sensorDiag = (focalLengthmm * diag24x36) / focalIn35mm; // 43.3 is the diagonal of 35mm film + const double sensorDiag = (focalLengthmm * diag24x36) / focalIn35mm; // 43.3 is the diagonal of 35mm film sensorWidth = sensorDiag * std::sqrt(1.0 / (1.0 + invRatio * invRatio)); sensorWidthSource = ESensorWidthSource::FROM_METADATA_ESTIMATION; } @@ -370,5 +369,5 @@ int ImageInfo::getSensorSize(const std::vector& sensorDatab return errCode; } -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/imageInfo.hpp b/src/aliceVision/sfmData/imageInfo.hpp index 57461ec23f..6df50089a8 100644 --- a/src/aliceVision/sfmData/imageInfo.hpp +++ b/src/aliceVision/sfmData/imageInfo.hpp @@ -8,7 +8,6 @@ #include - #include #include #include @@ -23,602 +22,543 @@ namespace sfmData { class ImageInfo { -public: - - /** - * @brief Image Constructor - * @param[in] imagePath The image path on disk - * @param[in] width The image width - * @param[in] height The image height - * @param[in] metadata The image metadata - */ - ImageInfo(const std::string& imagePath = "", - std::size_t width = 0, - std::size_t height = 0, - const std::map& metadata = std::map()) - : _imagePath(imagePath) - , _width(width) - , _height(height) - , _metadata(metadata) - {} - - bool operator==(const ImageInfo& other) const - { - // image paths can be different - return - _width == other._width && - _height == other._height; - } - - inline bool operator!=(const ImageInfo& other) const { return !(*this == other); } - - /** - * @brief Get view image path - * @return image path - */ - const std::string& getImagePath() const - { - return _imagePath; - } - - /** - * @brief Get view image width - * @return image width - */ - std::size_t getWidth() const - { - return _width; - } - - /** - * @brief Get view image height - * @return image height - */ - std::size_t getHeight() const - { - return _height; - } - - /** - * @brief Get view image size - * @return image size - */ - std::pair getImgSize() const - { - return {_width, _height}; - } - - /** - * @brief Get the Camera Exposure Setting value. - * For the same scene, this value is linearly proportional to the amount of light captured by the camera according to - * the shooting parameters (shutter speed, f-number, iso). - */ - ExposureSetting getCameraExposureSetting() const - { - return ExposureSetting( - getMetadataShutter(), - getMetadataFNumber(), - getMetadataISO()); - } - - /** - * @brief Get the Exposure Value. EV is a number that represents a combination of a camera's shutter speed and - * f-number, such that all combinations that yield the same exposure have the same EV. - * It progresses in a linear sequence as camera exposure is changed in power-of-2 steps. - */ - double getEv() const; - - /** - * @brief Get an iterator on the map of metadata from a given name. - */ - std::map::const_iterator findMetadataIterator(const std::string& name) const; - - /** - * @brief Return true if the given metadata name exists - * @param[in] names List of possible names for the metadata - * @return true if the corresponding metadata value exists - */ - bool hasMetadata(const std::vector& names) const; - - /** - * @brief Return true if the metadata for longitude and latitude exist. - * It checks that all the tags from GPSExifTags exists - * @return true if GPS data is available - */ - bool hasGpsMetadata() const; - - /** - * @brief Return true if the given metadata name exists and is a digit - * @param[in] names List of possible names for the metadata - * @param[in] isPositive true if the metadata must be positive - * @return true if the corresponding metadata value exists - */ - bool hasDigitMetadata(const std::vector& names, bool isPositive = true) const; - - /** - * @brief Get the metadata value as a string - * @param[in] names List of possible names for the metadata - * @return the metadata value as a string or an empty string if it does not exist - */ - const std::string& getMetadata(const std::vector& names) const; - - /** - * @brief Read a floating point value from a string. It support an integer, a floating point value or a fraction. - * @param[in] str string with the number to evaluate - * @return the extracted floating point value or -1.0 if it fails to convert the string - */ - double readRealNumber(const std::string& str) const; - - /** - * @brief Get the metadata value as a double - * @param[in] names List of possible names for the metadata - * @return the metadata value as a double or -1.0 if it does not exist - */ - double getDoubleMetadata(const std::vector& names) const; - - /** - * @brief Get the metadata value as a double - * @param[in] names List of possible names for the metadata - * @param[in] val Data to be set with the metadata value - * @return true if the metadata is found or false if it does not exist - */ - bool getDoubleMetadata(const std::vector& names, double& val) const; - - /** - * @brief Get the metadata value as an integer - * @param[in] names List of possible names for the metadata - * @return the metadata value as an integer or -1 if it does not exist - */ - int getIntMetadata(const std::vector& names) const; - - /** - * @brief Get the corresponding "Make" metadata value - * @return the metadata value string or "" if no corresponding value - */ - const std::string& getMetadataMake() const - { - return getMetadata({"Make", "cameraMake", "camera make"}); - } - - /** - * @brief Get the corresponding "Model" metadata value - * @return the metadata value string or "" if no corresponding value - */ - const std::string& getMetadataModel() const - { - return getMetadata({"Model", "cameraModel", "cameraModelName", "camera model", "camera model name"}); - } - - /** - * @brief Get the corresponding "BodySerialNumber" metadata value - * @return the metadata value string or "" if no corresponding value - */ - const std::string& getMetadataBodySerialNumber() const - { - return getMetadata({"Exif:BodySerialNumber", "cameraSerialNumber", "SerialNumber", "Serial Number"}); - } - - /** - * @brief Get the corresponding "LensModel" metadata value - * @return the metadata value string or "" if no corresponding value - */ - const std::string& getMetadataLensModel() const - { - return getMetadata({ "Exif:LensModel", "lensModel", "lens model" }); - } - - /** - * @brief Get the corresponding "LensID" metadata value - * @return the metadata value -1 if no corresponding value - */ - int getMetadataLensID() const - { - return getIntMetadata({ "Exif:LensID", "lensID", "lensType"}); - } - - /** - * @brief Get the corresponding "LensSerialNumber" metadata value - * @return the metadata value string or "" if no corresponding value - */ - const std::string& getMetadataLensSerialNumber() const - { - return getMetadata({ "Exif:LensSerialNumber", "lensSerialNumber", "lens serial number" }); - } - - /** - * @brief Get the corresponding "FocalLength" metadata value - * @return the metadata value float or -1 if no corresponding value - */ - double getMetadataFocalLength() const - { - return getDoubleMetadata({"Exif:FocalLength", "focalLength", "focal length"}); - } - - /** - * @brief Get the corresponding "ExposureTime" (shutter) metadata value - * @return the metadata value float or -1 if no corresponding value - */ - double getMetadataShutter() const - { - return getDoubleMetadata({"ExposureTime", "Shutter Speed Value"}); - } - - /** - * @brief Get the corresponding "FNumber" (relative aperture) metadata value - * @return the metadata value float or -1 if no corresponding value - */ - double getMetadataFNumber() const - { - if(hasDigitMetadata({"FNumber"})) - { - return getDoubleMetadata({"FNumber"}); - } - if (hasDigitMetadata({"ApertureValue", "Aperture Value"})) - { - const double aperture = getDoubleMetadata({"ApertureValue", "Aperture Value"}); - // fnumber = 2^(aperture/2) - return std::pow(2.0, aperture / 2.0); - } - return -1; - } - - /** + public: + /** + * @brief Image Constructor + * @param[in] imagePath The image path on disk + * @param[in] width The image width + * @param[in] height The image height + * @param[in] metadata The image metadata + */ + ImageInfo(const std::string& imagePath = "", + std::size_t width = 0, + std::size_t height = 0, + const std::map& metadata = std::map()) + : _imagePath(imagePath), + _width(width), + _height(height), + _metadata(metadata) + {} + + bool operator==(const ImageInfo& other) const + { + // image paths can be different + return _width == other._width && _height == other._height; + } + + inline bool operator!=(const ImageInfo& other) const { return !(*this == other); } + + /** + * @brief Get view image path + * @return image path + */ + const std::string& getImagePath() const { return _imagePath; } + + /** + * @brief Get view image width + * @return image width + */ + std::size_t getWidth() const { return _width; } + + /** + * @brief Get view image height + * @return image height + */ + std::size_t getHeight() const { return _height; } + + /** + * @brief Get view image size + * @return image size + */ + std::pair getImgSize() const { return {_width, _height}; } + + /** + * @brief Get the Camera Exposure Setting value. + * For the same scene, this value is linearly proportional to the amount of light captured by the camera according to + * the shooting parameters (shutter speed, f-number, iso). + */ + ExposureSetting getCameraExposureSetting() const { return ExposureSetting(getMetadataShutter(), getMetadataFNumber(), getMetadataISO()); } + + /** + * @brief Get the Exposure Value. EV is a number that represents a combination of a camera's shutter speed and + * f-number, such that all combinations that yield the same exposure have the same EV. + * It progresses in a linear sequence as camera exposure is changed in power-of-2 steps. + */ + double getEv() const; + + /** + * @brief Get an iterator on the map of metadata from a given name. + */ + std::map::const_iterator findMetadataIterator(const std::string& name) const; + + /** + * @brief Return true if the given metadata name exists + * @param[in] names List of possible names for the metadata + * @return true if the corresponding metadata value exists + */ + bool hasMetadata(const std::vector& names) const; + + /** + * @brief Return true if the metadata for longitude and latitude exist. + * It checks that all the tags from GPSExifTags exists + * @return true if GPS data is available + */ + bool hasGpsMetadata() const; + + /** + * @brief Return true if the given metadata name exists and is a digit + * @param[in] names List of possible names for the metadata + * @param[in] isPositive true if the metadata must be positive + * @return true if the corresponding metadata value exists + */ + bool hasDigitMetadata(const std::vector& names, bool isPositive = true) const; + + /** + * @brief Get the metadata value as a string + * @param[in] names List of possible names for the metadata + * @return the metadata value as a string or an empty string if it does not exist + */ + const std::string& getMetadata(const std::vector& names) const; + + /** + * @brief Read a floating point value from a string. It support an integer, a floating point value or a fraction. + * @param[in] str string with the number to evaluate + * @return the extracted floating point value or -1.0 if it fails to convert the string + */ + double readRealNumber(const std::string& str) const; + + /** + * @brief Get the metadata value as a double + * @param[in] names List of possible names for the metadata + * @return the metadata value as a double or -1.0 if it does not exist + */ + double getDoubleMetadata(const std::vector& names) const; + + /** + * @brief Get the metadata value as a double + * @param[in] names List of possible names for the metadata + * @param[in] val Data to be set with the metadata value + * @return true if the metadata is found or false if it does not exist + */ + bool getDoubleMetadata(const std::vector& names, double& val) const; + + /** + * @brief Get the metadata value as an integer + * @param[in] names List of possible names for the metadata + * @return the metadata value as an integer or -1 if it does not exist + */ + int getIntMetadata(const std::vector& names) const; + + /** + * @brief Get the corresponding "Make" metadata value + * @return the metadata value string or "" if no corresponding value + */ + const std::string& getMetadataMake() const { return getMetadata({"Make", "cameraMake", "camera make"}); } + + /** + * @brief Get the corresponding "Model" metadata value + * @return the metadata value string or "" if no corresponding value + */ + const std::string& getMetadataModel() const + { + return getMetadata({"Model", "cameraModel", "cameraModelName", "camera model", "camera model name"}); + } + + /** + * @brief Get the corresponding "BodySerialNumber" metadata value + * @return the metadata value string or "" if no corresponding value + */ + const std::string& getMetadataBodySerialNumber() const + { + return getMetadata({"Exif:BodySerialNumber", "cameraSerialNumber", "SerialNumber", "Serial Number"}); + } + + /** + * @brief Get the corresponding "LensModel" metadata value + * @return the metadata value string or "" if no corresponding value + */ + const std::string& getMetadataLensModel() const { return getMetadata({"Exif:LensModel", "lensModel", "lens model"}); } + + /** + * @brief Get the corresponding "LensID" metadata value + * @return the metadata value -1 if no corresponding value + */ + int getMetadataLensID() const { return getIntMetadata({"Exif:LensID", "lensID", "lensType"}); } + + /** + * @brief Get the corresponding "LensSerialNumber" metadata value + * @return the metadata value string or "" if no corresponding value + */ + const std::string& getMetadataLensSerialNumber() const + { + return getMetadata({"Exif:LensSerialNumber", "lensSerialNumber", "lens serial number"}); + } + + /** + * @brief Get the corresponding "FocalLength" metadata value + * @return the metadata value float or -1 if no corresponding value + */ + double getMetadataFocalLength() const { return getDoubleMetadata({"Exif:FocalLength", "focalLength", "focal length"}); } + + /** + * @brief Get the corresponding "ExposureTime" (shutter) metadata value + * @return the metadata value float or -1 if no corresponding value + */ + double getMetadataShutter() const { return getDoubleMetadata({"ExposureTime", "Shutter Speed Value"}); } + + /** + * @brief Get the corresponding "FNumber" (relative aperture) metadata value + * @return the metadata value float or -1 if no corresponding value + */ + double getMetadataFNumber() const + { + if (hasDigitMetadata({"FNumber"})) + { + return getDoubleMetadata({"FNumber"}); + } + if (hasDigitMetadata({"ApertureValue", "Aperture Value"})) + { + const double aperture = getDoubleMetadata({"ApertureValue", "Aperture Value"}); + // fnumber = 2^(aperture/2) + return std::pow(2.0, aperture / 2.0); + } + return -1; + } + + /** * @brief Get the corresponding "PhotographicSensitivity" (ISO) metadata value * @return the metadata value int or -1 if no corresponding value */ - double getMetadataISO() const - { - return getDoubleMetadata({"Exif:PhotographicSensitivity", "PhotographicSensitivity", "Photographic Sensitivity", "ISO"}); - } - - /** - * @brief Get the corresponding "Orientation" metadata value - * @return the enum EEXIFOrientation - */ - EEXIFOrientation getMetadataOrientation() const - { - const int orientation = getIntMetadata({"Exif:Orientation", "Orientation"}); - if(orientation < 0) - return EEXIFOrientation::UNKNOWN; - return static_cast(orientation); - } - - /** - * @brief Get the gps position in the absolute cartesian reference system. - * @return The position x, y, z as a three dimensional vector. - */ - Vec3 getGpsPositionFromMetadata() const; - - /** - * @brief Get the gps position in the WGS84 reference system. - * @param[out] lat the latitude - * @param[out] lon the longitude - * @param[out] alt the altitude - */ - void getGpsPositionWGS84FromMetadata(double& lat, double& lon, double& alt) const; - - /** - * @brief Get the gps position in the WGS84 reference system as a vector. - * @return A three dimensional vector with latitude, logitude and altitude. - */ - Vec3 getGpsPositionWGS84FromMetadata() const; - - const std::string& getColorProfileFileName() const - { - return getMetadata({ "AliceVision:DCP:colorProfileFileName" }); - } - - const std::string& getRawColorInterpretation() const - { - return getMetadata({ "AliceVision:rawColorInterpretation" }); - } - - const std::vector getCameraMultiplicators() const - { - const std::string cam_mul = getMetadata({ "raw:cam_mul" }); - std::vector v_mult; - - size_t last = 0; - size_t next = 0; - while ((next = cam_mul.find(" ", last)) != std::string::npos) - { - v_mult.push_back(std::stoi(cam_mul.substr(last, next - last))); - last = next + 1; - } - v_mult.push_back(std::stoi(cam_mul.substr(last))); - - return v_mult; - } - - const bool getVignettingParams(std::vector& v_vignParam) const - { - v_vignParam.clear(); - bool valid = true; - double value; - - valid = valid && getDoubleMetadata({"AliceVision:VignParamFocX"}, value); - v_vignParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:VignParamFocY"}, value); - v_vignParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:VignParamCenterX"}, value); - v_vignParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:VignParamCenterY"}, value); - v_vignParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:VignParam1"}, value); - v_vignParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:VignParam2"}, value); - v_vignParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:VignParam3"}, value); - v_vignParam.push_back(static_cast(value)); - return valid; - } - - const bool getChromaticAberrationParams(std::vector& v_caGParam, std::vector& v_caBGParam, std::vector& v_caRGParam) const - { - v_caGParam.clear(); - v_caBGParam.clear(); - v_caRGParam.clear(); - bool valid = true; - double value; - - valid = valid && getDoubleMetadata({"AliceVision:CAGreenFocX"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CAGreenFocY"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CAGreenCenterX"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CAGreenCenterY"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CAGreenParam1"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CAGreenParam2"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CAGreenParam3"}, value); - v_caGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenFocX"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenFocY"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenCenterX"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenCenterY"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenParam1"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenParam2"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenParam3"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenScaleFactor"}, value); - v_caBGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenFocX"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenFocY"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenCenterX"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenCenterY"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenParam1"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenParam2"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenParam3"}, value); - v_caRGParam.push_back(static_cast(value)); - valid = valid && getDoubleMetadata({"AliceVision:CARedGreenScaleFactor"}, value); - v_caRGParam.push_back(static_cast(value)); - - return valid; - } - - const bool hasMetadataDateTimeOriginal() const - { - return hasMetadata( - {"Exif:DateTimeOriginal", "DateTimeOriginal", "DateTime", "Date Time", "Create Date", "ctime"}); - } - - const std::string& getMetadataDateTimeOriginal() const - { - return getMetadata({"Exif:DateTimeOriginal", "DateTimeOriginal", "DateTime", "Date Time", "Create Date", "ctime"}); - } - - int64_t getMetadataDateTimestamp() const { - - std::smatch sm; - std::string dtstring = getMetadataDateTimeOriginal(); - std::regex regex("([\\d]+):([\\d]+):([\\d]+) ([\\d]+):([\\d]+):([\\d]+)"); - - if (!std::regex_match(dtstring, sm, regex)) { - return -1; + double getMetadataISO() const + { + return getDoubleMetadata({"Exif:PhotographicSensitivity", "PhotographicSensitivity", "Photographic Sensitivity", "ISO"}); } - - int64_t year = std::stoi(sm[1]); - int64_t month = std::stoi(sm[2]); - int64_t day = std::stoi(sm[3]); - int64_t hour = std::stoi(sm[4]); - int64_t minutes = std::stoi(sm[5]); - int64_t seconds = std::stoi(sm[6]); - int64_t timecode = ((((((((year * 12) + month) * 31) + day) * 24) + hour) * 60 + minutes) * 60) + seconds; - - return timecode; - } - - double getSensorWidth() const { return getDoubleMetadata({"AliceVision:SensorWidth"}); } - - double getSensorHeight() const { return getDoubleMetadata({"AliceVision:SensorHeight"}); } - - /** - * @brief Get the view metadata structure - * @return the view metadata - */ - const std::map& getMetadata() const - { - return _metadata; - } - - /** - * @brief Set the given view image path - * @param[in] imagePath The given view image path - */ - void setImagePath(const std::string& imagePath) - { - _imagePath = imagePath; - } - - /** - * @brief Set the given view image width - * @param[in] width The given view image width - */ - void setWidth(std::size_t width) - { - _width = width; - } - - /** - * @brief Set the given view image height - * @param[in] height The given view image height - */ - void setHeight(std::size_t height) - { - _height = height; - } - - - /** - * @brief Set view metadata - * @param[in] metadata The metadata map - */ - void setMetadata(const std::map& metadata) - { - _metadata = metadata; - } - - /** - * @brief Add view metadata - * @param[in] key The metadata key - * @param[in] value The metadata value - */ - void addMetadata(const std::string& key, const std::string& value) - { - _metadata[key] = value; - } - - /** - * @brief Add DCP info in metadata - * @param[in] dcpProf The DCP color profile - */ - void addDCPMetadata(image::DCPProfile& dcpProf) - { - addMetadata("AliceVision:DCP:colorProfileFileName", dcpProf.info.filename); - - addMetadata("AliceVision:DCP:Temp1", std::to_string(dcpProf.info.temperature_1)); - addMetadata("AliceVision:DCP:Temp2", std::to_string(dcpProf.info.temperature_2)); - - const int colorMatrixNumber = (dcpProf.info.has_color_matrix_1 && dcpProf.info.has_color_matrix_2) ? 2 : - (dcpProf.info.has_color_matrix_1 ? 1 : 0); - addMetadata("AliceVision:DCP:ColorMatrixNumber", std::to_string(colorMatrixNumber)); - - const int forwardMatrixNumber = (dcpProf.info.has_forward_matrix_1 && dcpProf.info.has_forward_matrix_2) ? 2 : - (dcpProf.info.has_forward_matrix_1 ? 1 : 0); - addMetadata("AliceVision:DCP:ForwardMatrixNumber", std::to_string(forwardMatrixNumber)); - - const int calibMatrixNumber = (dcpProf.info.has_camera_calibration_1 && dcpProf.info.has_camera_calibration_2) ? 2 : - (dcpProf.info.has_camera_calibration_1 ? 1 : 0); - addMetadata("AliceVision:DCP:CameraCalibrationMatrixNumber", std::to_string(calibMatrixNumber)); - - std::vector v_strColorMatrix; - dcpProf.getMatricesAsStrings("color", v_strColorMatrix); - for (int k = 0; k < v_strColorMatrix.size(); k++) - { - addMetadata("AliceVision:DCP:ColorMat" + std::to_string(k + 1), v_strColorMatrix[k]); - } - - std::vector v_strForwardMatrix; - dcpProf.getMatricesAsStrings("forward", v_strForwardMatrix); - for (int k = 0; k < v_strForwardMatrix.size(); k++) - { - addMetadata("AliceVision:DCP:ForwardMat" + std::to_string(k + 1), v_strForwardMatrix[k]); - } - - std::vector v_strCalibMatrix; - dcpProf.getMatricesAsStrings("calib", v_strCalibMatrix); - for (int k = 0; k < v_strCalibMatrix.size(); k++) - { - addMetadata("AliceVision:DCP:CameraCalibrationMat" + std::to_string(k + 1), v_strCalibMatrix[k]); - } - } - - - /** - * @brief Add vignetting model parameters in metadata - * @param[in] The lens data extracted from a LCP file - */ - void addVignettingMetadata(LensParam& lensParam) - { - addMetadata("AliceVision:VignParamFocX", std::to_string(lensParam.vignParams.FocalLengthX)); - addMetadata("AliceVision:VignParamFocY", std::to_string(lensParam.vignParams.FocalLengthY)); - addMetadata("AliceVision:VignParamCenterX", std::to_string(lensParam.vignParams.ImageXCenter)); - addMetadata("AliceVision:VignParamCenterY", std::to_string(lensParam.vignParams.ImageYCenter)); - addMetadata("AliceVision:VignParam1", std::to_string(lensParam.vignParams.VignetteModelParam1)); - addMetadata("AliceVision:VignParam2", std::to_string(lensParam.vignParams.VignetteModelParam2)); - addMetadata("AliceVision:VignParam3", std::to_string(lensParam.vignParams.VignetteModelParam3)); - } - - /** - * @brief Add chromatic model parameters in metadata - * @param[in] The lens data extracted from a LCP file - */ - void addChromaticMetadata(LensParam& lensParam) - { - addMetadata("AliceVision:CAGreenFocX", std::to_string(lensParam.ChromaticGreenParams.FocalLengthX)); - addMetadata("AliceVision:CAGreenFocY", std::to_string(lensParam.ChromaticGreenParams.FocalLengthY)); - addMetadata("AliceVision:CAGreenCenterX", std::to_string(lensParam.ChromaticGreenParams.ImageXCenter)); - addMetadata("AliceVision:CAGreenCenterY", std::to_string(lensParam.ChromaticGreenParams.ImageYCenter)); - addMetadata("AliceVision:CAGreenParam1", std::to_string(lensParam.ChromaticGreenParams.RadialDistortParam1)); - addMetadata("AliceVision:CAGreenParam2", std::to_string(lensParam.ChromaticGreenParams.RadialDistortParam2)); - addMetadata("AliceVision:CAGreenParam3", std::to_string(lensParam.ChromaticGreenParams.RadialDistortParam3)); - addMetadata("AliceVision:CABlueGreenFocX", std::to_string(lensParam.ChromaticBlueGreenParams.FocalLengthX)); - addMetadata("AliceVision:CABlueGreenFocY", std::to_string(lensParam.ChromaticBlueGreenParams.FocalLengthY)); - addMetadata("AliceVision:CABlueGreenCenterX", std::to_string(lensParam.ChromaticBlueGreenParams.ImageXCenter)); - addMetadata("AliceVision:CABlueGreenCenterY", std::to_string(lensParam.ChromaticBlueGreenParams.ImageYCenter)); - addMetadata("AliceVision:CABlueGreenParam1", std::to_string(lensParam.ChromaticBlueGreenParams.RadialDistortParam1)); - addMetadata("AliceVision:CABlueGreenParam2", std::to_string(lensParam.ChromaticBlueGreenParams.RadialDistortParam2)); - addMetadata("AliceVision:CABlueGreenParam3", std::to_string(lensParam.ChromaticBlueGreenParams.RadialDistortParam3)); - addMetadata("AliceVision:CABlueGreenScaleFactor", std::to_string(lensParam.ChromaticBlueGreenParams.ScaleFactor)); - addMetadata("AliceVision:CARedGreenFocX", std::to_string(lensParam.ChromaticRedGreenParams.FocalLengthX)); - addMetadata("AliceVision:CARedGreenFocY", std::to_string(lensParam.ChromaticRedGreenParams.FocalLengthY)); - addMetadata("AliceVision:CARedGreenCenterX", std::to_string(lensParam.ChromaticRedGreenParams.ImageXCenter)); - addMetadata("AliceVision:CARedGreenCenterY", std::to_string(lensParam.ChromaticRedGreenParams.ImageYCenter)); - addMetadata("AliceVision:CARedGreenParam1", std::to_string(lensParam.ChromaticRedGreenParams.RadialDistortParam1)); - addMetadata("AliceVision:CARedGreenParam2", std::to_string(lensParam.ChromaticRedGreenParams.RadialDistortParam2)); - addMetadata("AliceVision:CARedGreenParam3", std::to_string(lensParam.ChromaticRedGreenParams.RadialDistortParam3)); - addMetadata("AliceVision:CARedGreenScaleFactor", std::to_string(lensParam.ChromaticRedGreenParams.ScaleFactor)); - } - - /** - * @brief Get sensor size by combining info in metadata and in sensor database - * @param[in] sensorDatabase The sensor database - * @param[out] sensorWidth The sensor width - * @param[out] sensorHeight The sensor height - * @param[out] focalLengthmm The focal length - * @param[out] intrinsicInitMode The intrinsic init mode - * @param[in] verbose Enable verbosity - * @return An Error or Warning code: 1 - Unknown sensor, 2 - No metadata, 3 - Unsure sensor, 4 - Computation from 35mm Focal - */ - int getSensorSize(const std::vector& sensorDatabase, double& sensorWidth, double& sensorHeight, double& focalLengthmm, camera::EInitMode& intrinsicInitMode, - bool verbose = false); - -private: - - /// image path on disk - std::string _imagePath; - /// image width - std::size_t _width; - /// image height - std::size_t _height; - /// map for metadata - std::map _metadata; + + /** + * @brief Get the corresponding "Orientation" metadata value + * @return the enum EEXIFOrientation + */ + EEXIFOrientation getMetadataOrientation() const + { + const int orientation = getIntMetadata({"Exif:Orientation", "Orientation"}); + if (orientation < 0) + return EEXIFOrientation::UNKNOWN; + return static_cast(orientation); + } + + /** + * @brief Get the gps position in the absolute cartesian reference system. + * @return The position x, y, z as a three dimensional vector. + */ + Vec3 getGpsPositionFromMetadata() const; + + /** + * @brief Get the gps position in the WGS84 reference system. + * @param[out] lat the latitude + * @param[out] lon the longitude + * @param[out] alt the altitude + */ + void getGpsPositionWGS84FromMetadata(double& lat, double& lon, double& alt) const; + + /** + * @brief Get the gps position in the WGS84 reference system as a vector. + * @return A three dimensional vector with latitude, logitude and altitude. + */ + Vec3 getGpsPositionWGS84FromMetadata() const; + + const std::string& getColorProfileFileName() const { return getMetadata({"AliceVision:DCP:colorProfileFileName"}); } + + const std::string& getRawColorInterpretation() const { return getMetadata({"AliceVision:rawColorInterpretation"}); } + + const std::vector getCameraMultiplicators() const + { + const std::string cam_mul = getMetadata({"raw:cam_mul"}); + std::vector v_mult; + + size_t last = 0; + size_t next = 0; + while ((next = cam_mul.find(" ", last)) != std::string::npos) + { + v_mult.push_back(std::stoi(cam_mul.substr(last, next - last))); + last = next + 1; + } + v_mult.push_back(std::stoi(cam_mul.substr(last))); + + return v_mult; + } + + const bool getVignettingParams(std::vector& v_vignParam) const + { + v_vignParam.clear(); + bool valid = true; + double value; + + valid = valid && getDoubleMetadata({"AliceVision:VignParamFocX"}, value); + v_vignParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:VignParamFocY"}, value); + v_vignParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:VignParamCenterX"}, value); + v_vignParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:VignParamCenterY"}, value); + v_vignParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:VignParam1"}, value); + v_vignParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:VignParam2"}, value); + v_vignParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:VignParam3"}, value); + v_vignParam.push_back(static_cast(value)); + return valid; + } + + const bool getChromaticAberrationParams(std::vector& v_caGParam, std::vector& v_caBGParam, std::vector& v_caRGParam) const + { + v_caGParam.clear(); + v_caBGParam.clear(); + v_caRGParam.clear(); + bool valid = true; + double value; + + valid = valid && getDoubleMetadata({"AliceVision:CAGreenFocX"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CAGreenFocY"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CAGreenCenterX"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CAGreenCenterY"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CAGreenParam1"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CAGreenParam2"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CAGreenParam3"}, value); + v_caGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenFocX"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenFocY"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenCenterX"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenCenterY"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenParam1"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenParam2"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenParam3"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CABlueGreenScaleFactor"}, value); + v_caBGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenFocX"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenFocY"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenCenterX"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenCenterY"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenParam1"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenParam2"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenParam3"}, value); + v_caRGParam.push_back(static_cast(value)); + valid = valid && getDoubleMetadata({"AliceVision:CARedGreenScaleFactor"}, value); + v_caRGParam.push_back(static_cast(value)); + + return valid; + } + + const bool hasMetadataDateTimeOriginal() const + { + return hasMetadata({"Exif:DateTimeOriginal", "DateTimeOriginal", "DateTime", "Date Time", "Create Date", "ctime"}); + } + + const std::string& getMetadataDateTimeOriginal() const + { + return getMetadata({"Exif:DateTimeOriginal", "DateTimeOriginal", "DateTime", "Date Time", "Create Date", "ctime"}); + } + + int64_t getMetadataDateTimestamp() const + { + std::smatch sm; + std::string dtstring = getMetadataDateTimeOriginal(); + std::regex regex("([\\d]+):([\\d]+):([\\d]+) ([\\d]+):([\\d]+):([\\d]+)"); + + if (!std::regex_match(dtstring, sm, regex)) + { + return -1; + } + + int64_t year = std::stoi(sm[1]); + int64_t month = std::stoi(sm[2]); + int64_t day = std::stoi(sm[3]); + int64_t hour = std::stoi(sm[4]); + int64_t minutes = std::stoi(sm[5]); + int64_t seconds = std::stoi(sm[6]); + int64_t timecode = ((((((((year * 12) + month) * 31) + day) * 24) + hour) * 60 + minutes) * 60) + seconds; + + return timecode; + } + + double getSensorWidth() const { return getDoubleMetadata({"AliceVision:SensorWidth"}); } + + double getSensorHeight() const { return getDoubleMetadata({"AliceVision:SensorHeight"}); } + + /** + * @brief Get the view metadata structure + * @return the view metadata + */ + const std::map& getMetadata() const { return _metadata; } + + /** + * @brief Set the given view image path + * @param[in] imagePath The given view image path + */ + void setImagePath(const std::string& imagePath) { _imagePath = imagePath; } + + /** + * @brief Set the given view image width + * @param[in] width The given view image width + */ + void setWidth(std::size_t width) { _width = width; } + + /** + * @brief Set the given view image height + * @param[in] height The given view image height + */ + void setHeight(std::size_t height) { _height = height; } + + /** + * @brief Set view metadata + * @param[in] metadata The metadata map + */ + void setMetadata(const std::map& metadata) { _metadata = metadata; } + + /** + * @brief Add view metadata + * @param[in] key The metadata key + * @param[in] value The metadata value + */ + void addMetadata(const std::string& key, const std::string& value) { _metadata[key] = value; } + + /** + * @brief Add DCP info in metadata + * @param[in] dcpProf The DCP color profile + */ + void addDCPMetadata(image::DCPProfile& dcpProf) + { + addMetadata("AliceVision:DCP:colorProfileFileName", dcpProf.info.filename); + + addMetadata("AliceVision:DCP:Temp1", std::to_string(dcpProf.info.temperature_1)); + addMetadata("AliceVision:DCP:Temp2", std::to_string(dcpProf.info.temperature_2)); + + const int colorMatrixNumber = + (dcpProf.info.has_color_matrix_1 && dcpProf.info.has_color_matrix_2) ? 2 : (dcpProf.info.has_color_matrix_1 ? 1 : 0); + addMetadata("AliceVision:DCP:ColorMatrixNumber", std::to_string(colorMatrixNumber)); + + const int forwardMatrixNumber = + (dcpProf.info.has_forward_matrix_1 && dcpProf.info.has_forward_matrix_2) ? 2 : (dcpProf.info.has_forward_matrix_1 ? 1 : 0); + addMetadata("AliceVision:DCP:ForwardMatrixNumber", std::to_string(forwardMatrixNumber)); + + const int calibMatrixNumber = + (dcpProf.info.has_camera_calibration_1 && dcpProf.info.has_camera_calibration_2) ? 2 : (dcpProf.info.has_camera_calibration_1 ? 1 : 0); + addMetadata("AliceVision:DCP:CameraCalibrationMatrixNumber", std::to_string(calibMatrixNumber)); + + std::vector v_strColorMatrix; + dcpProf.getMatricesAsStrings("color", v_strColorMatrix); + for (int k = 0; k < v_strColorMatrix.size(); k++) + { + addMetadata("AliceVision:DCP:ColorMat" + std::to_string(k + 1), v_strColorMatrix[k]); + } + + std::vector v_strForwardMatrix; + dcpProf.getMatricesAsStrings("forward", v_strForwardMatrix); + for (int k = 0; k < v_strForwardMatrix.size(); k++) + { + addMetadata("AliceVision:DCP:ForwardMat" + std::to_string(k + 1), v_strForwardMatrix[k]); + } + + std::vector v_strCalibMatrix; + dcpProf.getMatricesAsStrings("calib", v_strCalibMatrix); + for (int k = 0; k < v_strCalibMatrix.size(); k++) + { + addMetadata("AliceVision:DCP:CameraCalibrationMat" + std::to_string(k + 1), v_strCalibMatrix[k]); + } + } + + /** + * @brief Add vignetting model parameters in metadata + * @param[in] The lens data extracted from a LCP file + */ + void addVignettingMetadata(LensParam& lensParam) + { + addMetadata("AliceVision:VignParamFocX", std::to_string(lensParam.vignParams.FocalLengthX)); + addMetadata("AliceVision:VignParamFocY", std::to_string(lensParam.vignParams.FocalLengthY)); + addMetadata("AliceVision:VignParamCenterX", std::to_string(lensParam.vignParams.ImageXCenter)); + addMetadata("AliceVision:VignParamCenterY", std::to_string(lensParam.vignParams.ImageYCenter)); + addMetadata("AliceVision:VignParam1", std::to_string(lensParam.vignParams.VignetteModelParam1)); + addMetadata("AliceVision:VignParam2", std::to_string(lensParam.vignParams.VignetteModelParam2)); + addMetadata("AliceVision:VignParam3", std::to_string(lensParam.vignParams.VignetteModelParam3)); + } + + /** + * @brief Add chromatic model parameters in metadata + * @param[in] The lens data extracted from a LCP file + */ + void addChromaticMetadata(LensParam& lensParam) + { + addMetadata("AliceVision:CAGreenFocX", std::to_string(lensParam.ChromaticGreenParams.FocalLengthX)); + addMetadata("AliceVision:CAGreenFocY", std::to_string(lensParam.ChromaticGreenParams.FocalLengthY)); + addMetadata("AliceVision:CAGreenCenterX", std::to_string(lensParam.ChromaticGreenParams.ImageXCenter)); + addMetadata("AliceVision:CAGreenCenterY", std::to_string(lensParam.ChromaticGreenParams.ImageYCenter)); + addMetadata("AliceVision:CAGreenParam1", std::to_string(lensParam.ChromaticGreenParams.RadialDistortParam1)); + addMetadata("AliceVision:CAGreenParam2", std::to_string(lensParam.ChromaticGreenParams.RadialDistortParam2)); + addMetadata("AliceVision:CAGreenParam3", std::to_string(lensParam.ChromaticGreenParams.RadialDistortParam3)); + addMetadata("AliceVision:CABlueGreenFocX", std::to_string(lensParam.ChromaticBlueGreenParams.FocalLengthX)); + addMetadata("AliceVision:CABlueGreenFocY", std::to_string(lensParam.ChromaticBlueGreenParams.FocalLengthY)); + addMetadata("AliceVision:CABlueGreenCenterX", std::to_string(lensParam.ChromaticBlueGreenParams.ImageXCenter)); + addMetadata("AliceVision:CABlueGreenCenterY", std::to_string(lensParam.ChromaticBlueGreenParams.ImageYCenter)); + addMetadata("AliceVision:CABlueGreenParam1", std::to_string(lensParam.ChromaticBlueGreenParams.RadialDistortParam1)); + addMetadata("AliceVision:CABlueGreenParam2", std::to_string(lensParam.ChromaticBlueGreenParams.RadialDistortParam2)); + addMetadata("AliceVision:CABlueGreenParam3", std::to_string(lensParam.ChromaticBlueGreenParams.RadialDistortParam3)); + addMetadata("AliceVision:CABlueGreenScaleFactor", std::to_string(lensParam.ChromaticBlueGreenParams.ScaleFactor)); + addMetadata("AliceVision:CARedGreenFocX", std::to_string(lensParam.ChromaticRedGreenParams.FocalLengthX)); + addMetadata("AliceVision:CARedGreenFocY", std::to_string(lensParam.ChromaticRedGreenParams.FocalLengthY)); + addMetadata("AliceVision:CARedGreenCenterX", std::to_string(lensParam.ChromaticRedGreenParams.ImageXCenter)); + addMetadata("AliceVision:CARedGreenCenterY", std::to_string(lensParam.ChromaticRedGreenParams.ImageYCenter)); + addMetadata("AliceVision:CARedGreenParam1", std::to_string(lensParam.ChromaticRedGreenParams.RadialDistortParam1)); + addMetadata("AliceVision:CARedGreenParam2", std::to_string(lensParam.ChromaticRedGreenParams.RadialDistortParam2)); + addMetadata("AliceVision:CARedGreenParam3", std::to_string(lensParam.ChromaticRedGreenParams.RadialDistortParam3)); + addMetadata("AliceVision:CARedGreenScaleFactor", std::to_string(lensParam.ChromaticRedGreenParams.ScaleFactor)); + } + + /** + * @brief Get sensor size by combining info in metadata and in sensor database + * @param[in] sensorDatabase The sensor database + * @param[out] sensorWidth The sensor width + * @param[out] sensorHeight The sensor height + * @param[out] focalLengthmm The focal length + * @param[out] intrinsicInitMode The intrinsic init mode + * @param[in] verbose Enable verbosity + * @return An Error or Warning code: 1 - Unknown sensor, 2 - No metadata, 3 - Unsure sensor, 4 - Computation from 35mm Focal + */ + int getSensorSize(const std::vector& sensorDatabase, + double& sensorWidth, + double& sensorHeight, + double& focalLengthmm, + camera::EInitMode& intrinsicInitMode, + bool verbose = false); + + private: + /// image path on disk + std::string _imagePath; + /// image width + std::size_t _width; + /// image height + std::size_t _height; + /// map for metadata + std::map _metadata; }; -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/sfmData_test.cpp b/src/aliceVision/sfmData/sfmData_test.cpp index 369a121f40..20cf4aee2d 100644 --- a/src/aliceVision/sfmData/sfmData_test.cpp +++ b/src/aliceVision/sfmData/sfmData_test.cpp @@ -11,43 +11,42 @@ namespace fs = boost::filesystem; BOOST_AUTO_TEST_CASE(SfMData_InternalFolders) { - const std::string filename = "InternalFolders.sfm"; - sfmData::SfMData sfmData; - - // add relative features/matches folders with duplicates - std::string refFolder(".."); - sfmData.addFeaturesFolders({refFolder, refFolder}); - sfmData.addMatchesFolders({refFolder, refFolder}); - auto featuresFolders = sfmData.getFeaturesFolders(); - auto matchesFolders = sfmData.getMatchesFolders(); - // ensure duplicates were removed - BOOST_CHECK_EQUAL(featuresFolders.size(), 1); - BOOST_CHECK_EQUAL(matchesFolders.size(), 1); - // sfmData has no absolute path set, folders are still in relative form - BOOST_CHECK_EQUAL(featuresFolders[0], refFolder); - BOOST_CHECK_EQUAL(matchesFolders[0], refFolder); - - // set absolutePath to current filename - sfmData.setAbsolutePath(fs::absolute(filename).string()); - featuresFolders = sfmData.getFeaturesFolders(); - matchesFolders = sfmData.getMatchesFolders(); - // internal folders were kept... - BOOST_CHECK_EQUAL(featuresFolders.size(), 1); - BOOST_CHECK_EQUAL(matchesFolders.size(), 1); - // ... and are now absolute paths - BOOST_CHECK(fs::path(featuresFolders[0]).is_absolute()); - BOOST_CHECK(fs::equivalent(featuresFolders[0], refFolder)); - BOOST_CHECK(fs::path(matchesFolders[0]).is_absolute()); - BOOST_CHECK(fs::equivalent(matchesFolders[0], refFolder)); - - // update sfm absolute path to be in parent/parent folder - fs::path otherFolder = fs::path("../.."); - std::string updatedFilename = ( otherFolder / filename).string(); - sfmData.setAbsolutePath(updatedFilename); - // internal folders still reference the same folder as before - BOOST_CHECK(fs::equivalent(featuresFolders[0], refFolder)); - BOOST_CHECK(fs::equivalent(matchesFolders[0], refFolder)); - BOOST_CHECK_EQUAL(sfmData.getRelativeFeaturesFolders()[0], fs::relative(refFolder, otherFolder)); - BOOST_CHECK_EQUAL(sfmData.getRelativeMatchesFolders()[0], fs::relative(refFolder, otherFolder)); + const std::string filename = "InternalFolders.sfm"; + sfmData::SfMData sfmData; + + // add relative features/matches folders with duplicates + std::string refFolder(".."); + sfmData.addFeaturesFolders({refFolder, refFolder}); + sfmData.addMatchesFolders({refFolder, refFolder}); + auto featuresFolders = sfmData.getFeaturesFolders(); + auto matchesFolders = sfmData.getMatchesFolders(); + // ensure duplicates were removed + BOOST_CHECK_EQUAL(featuresFolders.size(), 1); + BOOST_CHECK_EQUAL(matchesFolders.size(), 1); + // sfmData has no absolute path set, folders are still in relative form + BOOST_CHECK_EQUAL(featuresFolders[0], refFolder); + BOOST_CHECK_EQUAL(matchesFolders[0], refFolder); + + // set absolutePath to current filename + sfmData.setAbsolutePath(fs::absolute(filename).string()); + featuresFolders = sfmData.getFeaturesFolders(); + matchesFolders = sfmData.getMatchesFolders(); + // internal folders were kept... + BOOST_CHECK_EQUAL(featuresFolders.size(), 1); + BOOST_CHECK_EQUAL(matchesFolders.size(), 1); + // ... and are now absolute paths + BOOST_CHECK(fs::path(featuresFolders[0]).is_absolute()); + BOOST_CHECK(fs::equivalent(featuresFolders[0], refFolder)); + BOOST_CHECK(fs::path(matchesFolders[0]).is_absolute()); + BOOST_CHECK(fs::equivalent(matchesFolders[0], refFolder)); + + // update sfm absolute path to be in parent/parent folder + fs::path otherFolder = fs::path("../.."); + std::string updatedFilename = (otherFolder / filename).string(); + sfmData.setAbsolutePath(updatedFilename); + // internal folders still reference the same folder as before + BOOST_CHECK(fs::equivalent(featuresFolders[0], refFolder)); + BOOST_CHECK(fs::equivalent(matchesFolders[0], refFolder)); + BOOST_CHECK_EQUAL(sfmData.getRelativeFeaturesFolders()[0], fs::relative(refFolder, otherFolder)); + BOOST_CHECK_EQUAL(sfmData.getRelativeMatchesFolders()[0], fs::relative(refFolder, otherFolder)); } - diff --git a/src/aliceVision/sfmData/uid.cpp b/src/aliceVision/sfmData/uid.cpp index d8a3b3f805..dce12bb45f 100644 --- a/src/aliceVision/sfmData/uid.cpp +++ b/src/aliceVision/sfmData/uid.cpp @@ -10,10 +10,9 @@ #include #include -#include +#include #include - namespace fs = boost::filesystem; namespace aliceVision { @@ -21,176 +20,174 @@ namespace sfmData { std::size_t computeViewUID(const View& view) { - std::size_t uid = 0; - const fs::path imagePath = view.getImage().getImagePath(); - - { - std::string ext = imagePath.extension().string(); - boost::to_lower(ext); - stl::hash_combine(uid, ext); - } - - const std::string& bodySerialNumber = view.getImage().getMetadataBodySerialNumber(); - const std::string& lensSerialNumber = view.getImage().getMetadataLensSerialNumber(); - - const bool hasImageUniqueID = view.getImage().hasMetadata({"Exif:ImageUniqueID", "ImageUniqueID"}); - if(hasImageUniqueID) - { - stl::hash_combine(uid, view.getImage().getMetadata({"Exif:ImageUniqueID", "ImageUniqueID"})); - } - else - { - // No uid in the metadata to identify the image, fallback to the filename. - // File extension is already used in the uid, so add the stem. - std::string stem = imagePath.stem().string(); - boost::to_lower(stem); - stl::hash_combine(uid, stem); - } - - if(!bodySerialNumber.empty() || !lensSerialNumber.empty()) - { - stl::hash_combine(uid, bodySerialNumber); - stl::hash_combine(uid, lensSerialNumber); - } - else if(!hasImageUniqueID) - { - // No metadata to identify the device, fallback to the folder path of the file. - // The image will NOT be relocatable in this particular case. - stl::hash_combine(uid, imagePath.parent_path().generic_string()); - } - - bool hasTimeOrCounterMetadata = false; - if(view.getImage().hasMetadataDateTimeOriginal()) - { - stl::hash_combine(uid, view.getImage().getMetadataDateTimeOriginal()); - stl::hash_combine(uid, view.getImage().getMetadata({"Exif:SubsecTimeOriginal", "SubsecTimeOriginal"})); - hasTimeOrCounterMetadata = true; - } - - if(view.getImage().hasMetadata({"imageCounter"})) - { - // if the view is from a video camera - stl::hash_combine(uid, view.getImage().getMetadata({"imageCounter"})); - hasTimeOrCounterMetadata = true; - } - - if(hasTimeOrCounterMetadata) - { - // already added to the uid - } - else if(view.getImage().hasMetadata({"DateTime"})) - { - // if no original date/time, fallback to the file date/time - stl::hash_combine(uid, view.getImage().getMetadata({"DateTime"})); - } - else - { - // if no original date/time, fallback to the file date/time - std::time_t t = fs::last_write_time(imagePath); - stl::hash_combine(uid, t); - } - - // cannot use view.getWidth() and view.getHeight() directly - // because ground truth tests and previous version datasets - // view UID use EXIF width and height (or 0) - - if(view.getImage().hasMetadata({"Exif:PixelXDimension", "PixelXDimension"})) - stl::hash_combine(uid, std::stoi(view.getImage().getMetadata({"Exif:PixelXDimension", "PixelXDimension"}))); - - if(view.getImage().hasMetadata({"Exif:PixelYDimension", "PixelYDimension"})) - stl::hash_combine(uid, std::stoi(view.getImage().getMetadata({"Exif:PixelYDimension", "PixelYDimension"}))); - - // limit to integer to maximize compatibility (like Alembic in Maya) - uid = std::abs((int) uid); - - return uid; -} + std::size_t uid = 0; + const fs::path imagePath = view.getImage().getImagePath(); -void updateStructureWithNewUID(Landmarks &landmarks, const std::map &oldIdToNew) -{ - // update the id in the visibility of each 3D point - for(auto &iter : landmarks) - { - Landmark& currentLandmark = iter.second; - - // the new observations where to copy the existing ones - // (needed as the key of the map is the idview) - Observations newObservations; - - for(const auto &iterObs : currentLandmark.observations) { - const auto idview = iterObs.first; - const Observation &obs = iterObs.second; + std::string ext = imagePath.extension().string(); + boost::to_lower(ext); + stl::hash_combine(uid, ext); + } - newObservations.emplace(oldIdToNew.at(idview), obs); + const std::string& bodySerialNumber = view.getImage().getMetadataBodySerialNumber(); + const std::string& lensSerialNumber = view.getImage().getMetadataLensSerialNumber(); + + const bool hasImageUniqueID = view.getImage().hasMetadata({"Exif:ImageUniqueID", "ImageUniqueID"}); + if (hasImageUniqueID) + { + stl::hash_combine(uid, view.getImage().getMetadata({"Exif:ImageUniqueID", "ImageUniqueID"})); + } + else + { + // No uid in the metadata to identify the image, fallback to the filename. + // File extension is already used in the uid, so add the stem. + std::string stem = imagePath.stem().string(); + boost::to_lower(stem); + stl::hash_combine(uid, stem); + } + + if (!bodySerialNumber.empty() || !lensSerialNumber.empty()) + { + stl::hash_combine(uid, bodySerialNumber); + stl::hash_combine(uid, lensSerialNumber); + } + else if (!hasImageUniqueID) + { + // No metadata to identify the device, fallback to the folder path of the file. + // The image will NOT be relocatable in this particular case. + stl::hash_combine(uid, imagePath.parent_path().generic_string()); } - - assert(currentLandmark.observations.size() == newObservations.size()); - currentLandmark.observations.swap(newObservations); - } -} + bool hasTimeOrCounterMetadata = false; + if (view.getImage().hasMetadataDateTimeOriginal()) + { + stl::hash_combine(uid, view.getImage().getMetadataDateTimeOriginal()); + stl::hash_combine(uid, view.getImage().getMetadata({"Exif:SubsecTimeOriginal", "SubsecTimeOriginal"})); + hasTimeOrCounterMetadata = true; + } -void sanityCheckLandmarks(const Landmarks &landmarks, const Views &views) -{ - for(const auto &iter : landmarks) - { - const Landmark& currentLandmark = iter.second; - for(const auto &iterObs : currentLandmark.observations) + if (view.getImage().hasMetadata({"imageCounter"})) { - const auto idview = iterObs.first; + // if the view is from a video camera + stl::hash_combine(uid, view.getImage().getMetadata({"imageCounter"})); + hasTimeOrCounterMetadata = true; + } - // there must be a view with that id (in the map) and the view must have - // the same id (the member) - assert(views.count(idview) == 1); - assert(views.at(idview)->getViewId() == idview); + if (hasTimeOrCounterMetadata) + { + // already added to the uid } - } + else if (view.getImage().hasMetadata({"DateTime"})) + { + // if no original date/time, fallback to the file date/time + stl::hash_combine(uid, view.getImage().getMetadata({"DateTime"})); + } + else + { + // if no original date/time, fallback to the file date/time + std::time_t t = fs::last_write_time(imagePath); + stl::hash_combine(uid, t); + } + + // cannot use view.getWidth() and view.getHeight() directly + // because ground truth tests and previous version datasets + // view UID use EXIF width and height (or 0) + + if (view.getImage().hasMetadata({"Exif:PixelXDimension", "PixelXDimension"})) + stl::hash_combine(uid, std::stoi(view.getImage().getMetadata({"Exif:PixelXDimension", "PixelXDimension"}))); + + if (view.getImage().hasMetadata({"Exif:PixelYDimension", "PixelYDimension"})) + stl::hash_combine(uid, std::stoi(view.getImage().getMetadata({"Exif:PixelYDimension", "PixelYDimension"}))); + + // limit to integer to maximize compatibility (like Alembic in Maya) + uid = std::abs((int)uid); + + return uid; } -void regenerateUID(SfMData &sfmdata, std::map &oldIdToNew, bool sanityCheck) +void updateStructureWithNewUID(Landmarks& landmarks, const std::map& oldIdToNew) { - // if the views are empty, nothing to be done. - if(sfmdata.getViews().empty()) - return; - - regenerateViewUIDs(sfmdata.getViews(), oldIdToNew); - - if(!sanityCheck) - return; - - sanityCheckLandmarks(sfmdata.getLandmarks(), sfmdata.getViews()); -} + // update the id in the visibility of each 3D point + for (auto& iter : landmarks) + { + Landmark& currentLandmark = iter.second; + // the new observations where to copy the existing ones + // (needed as the key of the map is the idview) + Observations newObservations; -void regenerateViewUIDs(Views &views, std::map &oldIdToNew) + for (const auto& iterObs : currentLandmark.observations) + { + const auto idview = iterObs.first; + const Observation& obs = iterObs.second; + + newObservations.emplace(oldIdToNew.at(idview), obs); + } + + assert(currentLandmark.observations.size() == newObservations.size()); + currentLandmark.observations.swap(newObservations); + } +} + +void sanityCheckLandmarks(const Landmarks& landmarks, const Views& views) { - // if the views are empty, nothing to be done. - if(views.empty()) - return; - - Views newViews; - - for(auto const &iter : views) - { - const View& currentView = *iter.second.get(); - - // compute the view UID - const std::size_t uid = computeViewUID(currentView); - - // update the mapping - assert(oldIdToNew.count(currentView.getViewId()) == 0); - oldIdToNew.emplace(currentView.getViewId(), uid); - - // add the view to the new map using the uid as key and change the id - assert(newViews.count(uid) == 0); - newViews.emplace(uid, iter.second); - newViews.at(uid)->setViewId(uid); - } - - assert(newViews.size() == views.size()); - views.swap(newViews); + for (const auto& iter : landmarks) + { + const Landmark& currentLandmark = iter.second; + for (const auto& iterObs : currentLandmark.observations) + { + const auto idview = iterObs.first; + + // there must be a view with that id (in the map) and the view must have + // the same id (the member) + assert(views.count(idview) == 1); + assert(views.at(idview)->getViewId() == idview); + } + } } +void regenerateUID(SfMData& sfmdata, std::map& oldIdToNew, bool sanityCheck) +{ + // if the views are empty, nothing to be done. + if (sfmdata.getViews().empty()) + return; + + regenerateViewUIDs(sfmdata.getViews(), oldIdToNew); + + if (!sanityCheck) + return; + + sanityCheckLandmarks(sfmdata.getLandmarks(), sfmdata.getViews()); } + +void regenerateViewUIDs(Views& views, std::map& oldIdToNew) +{ + // if the views are empty, nothing to be done. + if (views.empty()) + return; + + Views newViews; + + for (auto const& iter : views) + { + const View& currentView = *iter.second.get(); + + // compute the view UID + const std::size_t uid = computeViewUID(currentView); + + // update the mapping + assert(oldIdToNew.count(currentView.getViewId()) == 0); + oldIdToNew.emplace(currentView.getViewId(), uid); + + // add the view to the new map using the uid as key and change the id + assert(newViews.count(uid) == 0); + newViews.emplace(uid, iter.second); + newViews.at(uid)->setViewId(uid); + } + + assert(newViews.size() == views.size()); + views.swap(newViews); } + +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/uid.hpp b/src/aliceVision/sfmData/uid.hpp index 661242129d..b58b0d6cc2 100644 --- a/src/aliceVision/sfmData/uid.hpp +++ b/src/aliceVision/sfmData/uid.hpp @@ -22,43 +22,42 @@ namespace sfmData { std::size_t computeViewUID(const View& view); /** - * @brief Update all viewID referenced in the observation of each landmark according + * @brief Update all viewID referenced in the observation of each landmark according * to the provided mapping. - * + * * @param[in,out] landmarks The landmarks to update. * @param[in] oldIdToNew The mapping between the old ID and the reconmputed UID. */ -void updateStructureWithNewUID(Landmarks &landmarks, const std::map &oldIdToNew); - +void updateStructureWithNewUID(Landmarks& landmarks, const std::map& oldIdToNew); /** * @brief It perform a sanity check on a list of Landmarks and check if the viewIDs - * in the observations of each landmark has a corresponding ID in the list of views. + * in the observations of each landmark has a corresponding ID in the list of views. * @param[in] landmarks A list of landmarks. * @param[in] views A list of views. */ -void sanityCheckLandmarks(const Landmarks &landmarks, const Views &views); +void sanityCheckLandmarks(const Landmarks& landmarks, const Views& views); /** - * @brief Recompute the UID from the metadata of the original input images and + * @brief Recompute the UID from the metadata of the original input images and * modify the ID if it's not the same. - * + * * @param[in,out] sfmdata The sfmdata scene for which to recompute the UID. - * @param[out] oldIdToNew A map that holds the mapping between the old ID and the + * @param[out] oldIdToNew A map that holds the mapping between the old ID and the * reconmputed UID. - * @param[in] sanityCheck Enable a sanity check at the end to assure that the + * @param[in] sanityCheck Enable a sanity check at the end to assure that the * observations of 3D points and the control points have been correctly updated. */ -void regenerateUID(SfMData &sfmdata, std::map &oldIdToNew, bool sanityCheck = false); +void regenerateUID(SfMData& sfmdata, std::map& oldIdToNew, bool sanityCheck = false); /** * @brief Update all viewID of a list of view by replacing them with the UID. - * + * * @param[in,out] views The list of views to update - * @param[out] oldIdToNew oldIdToNew A map that holds the mapping between the + * @param[out] oldIdToNew oldIdToNew A map that holds the mapping between the * old ID and the reconmputed UID. */ -void regenerateViewUIDs(Views &views, std::map &oldIdToNew); +void regenerateViewUIDs(Views& views, std::map& oldIdToNew); -} // namespace sfmData -} // namespace aliceVision +} // namespace sfmData +} // namespace aliceVision diff --git a/src/aliceVision/sfmData/view_test.cpp b/src/aliceVision/sfmData/view_test.cpp index ebe807e6ca..9f36740ea5 100644 --- a/src/aliceVision/sfmData/view_test.cpp +++ b/src/aliceVision/sfmData/view_test.cpp @@ -11,52 +11,52 @@ namespace fs = boost::filesystem; BOOST_AUTO_TEST_CASE(View_Metadata) { - { - sfmData::View view; + { + sfmData::View view; - view.getImage().addMetadata("key", "value"); - view.getImage().addMetadata("exif/2/FocalLength", "50"); + view.getImage().addMetadata("key", "value"); + view.getImage().addMetadata("exif/2/FocalLength", "50"); - BOOST_CHECK(view.getImage().hasMetadata({"key"})); - BOOST_CHECK_EQUAL(view.getImage().getMetadata({"key"}), "value"); + BOOST_CHECK(view.getImage().hasMetadata({"key"})); + BOOST_CHECK_EQUAL(view.getImage().getMetadata({"key"}), "value"); - BOOST_CHECK(view.getImage().hasMetadata({"FocalLength"})); - BOOST_CHECK_EQUAL(view.getImage().getMetadata({"FocalLength"}), "50"); + BOOST_CHECK(view.getImage().hasMetadata({"FocalLength"})); + BOOST_CHECK_EQUAL(view.getImage().getMetadata({"FocalLength"}), "50"); - BOOST_CHECK(!view.getImage().hasMetadata({"DoesNotExist"})); - BOOST_CHECK_EQUAL(view.getImage().getMetadata({"DoesNotExist"}), ""); + BOOST_CHECK(!view.getImage().hasMetadata({"DoesNotExist"})); + BOOST_CHECK_EQUAL(view.getImage().getMetadata({"DoesNotExist"}), ""); - BOOST_CHECK_EQUAL(view.getImage().getMetadataFocalLength(), 50.0); - } + BOOST_CHECK_EQUAL(view.getImage().getMetadataFocalLength(), 50.0); + } - { - sfmData::View view; + { + sfmData::View view; - view.getImage().addMetadata("Exif:FocalLength", "50"); + view.getImage().addMetadata("Exif:FocalLength", "50"); - BOOST_CHECK(view.getImage().hasMetadata({"FocalLength"})); - BOOST_CHECK_EQUAL(view.getImage().getMetadata({"focalLength"}), "50"); + BOOST_CHECK(view.getImage().hasMetadata({"FocalLength"})); + BOOST_CHECK_EQUAL(view.getImage().getMetadata({"focalLength"}), "50"); - BOOST_CHECK_EQUAL(view.getImage().getMetadataFocalLength(), 50.0); - } + BOOST_CHECK_EQUAL(view.getImage().getMetadataFocalLength(), 50.0); + } - { - sfmData::View view; + { + sfmData::View view; - view.getImage().addMetadata("test:FOCALLENGTH", "50/10"); + view.getImage().addMetadata("test:FOCALLENGTH", "50/10"); - BOOST_CHECK(view.getImage().hasMetadata({"FocalLength"})); - BOOST_CHECK_EQUAL(view.getImage().getMetadata({"focalLength"}), "50/10"); + BOOST_CHECK(view.getImage().hasMetadata({"FocalLength"})); + BOOST_CHECK_EQUAL(view.getImage().getMetadata({"focalLength"}), "50/10"); - BOOST_CHECK_EQUAL(view.getImage().getMetadataFocalLength(), 5.0); - } + BOOST_CHECK_EQUAL(view.getImage().getMetadataFocalLength(), 5.0); + } { sfmData::View view; BOOST_CHECK_THROW(view.getImage().getGpsPositionFromMetadata(), std::out_of_range); - for(const auto& t : sfmData::GPSExifTags::all()) + for (const auto& t : sfmData::GPSExifTags::all()) { BOOST_CHECK(!view.getImage().hasGpsMetadata()); view.getImage().addMetadata(t, "100.6"); @@ -64,4 +64,3 @@ BOOST_AUTO_TEST_CASE(View_Metadata) BOOST_CHECK(view.getImage().hasGpsMetadata()); } } - diff --git a/src/aliceVision/sfmDataIO/AlembicExporter.cpp b/src/aliceVision/sfmDataIO/AlembicExporter.cpp index 5d9bc53862..dbb112e314 100644 --- a/src/aliceVision/sfmDataIO/AlembicExporter.cpp +++ b/src/aliceVision/sfmDataIO/AlembicExporter.cpp @@ -27,244 +27,244 @@ using namespace Alembic::AbcGeom; struct AlembicExporter::DataImpl { explicit DataImpl(const std::string& filename) - : _archive(Alembic::AbcCoreOgawa::WriteArchive(), filename) - , _topObj(_archive, Alembic::Abc::kTop) - { - // create MVG hierarchy - _mvgRoot = Alembic::AbcGeom::OXform(_topObj, "mvgRoot"); - _mvgCameras = Alembic::AbcGeom::OXform(_mvgRoot, "mvgCameras"); - _mvgCamerasUndefined = Alembic::AbcGeom::OXform(_mvgRoot, "mvgCamerasUndefined"); - _mvgCloud = Alembic::AbcGeom::OXform(_mvgRoot, "mvgCloud"); - _mvgPointCloud = Alembic::AbcGeom::OXform(_mvgCloud, "mvgPointCloud"); - - // add version as custom property - const std::vector<::uint32_t> abcVersion = {ALICEVISION_SFMDATAIO_VERSION_MAJOR, ALICEVISION_SFMDATAIO_VERSION_MINOR, ALICEVISION_SFMDATAIO_VERSION_REVISION}; - const std::vector<::uint32_t> aliceVisionVersion = {ALICEVISION_VERSION_MAJOR, ALICEVISION_VERSION_MINOR, ALICEVISION_VERSION_REVISION}; - - auto userProps = _mvgRoot.getProperties(); - - OUInt32ArrayProperty(userProps, "mvg_ABC_version").set(abcVersion); - OUInt32ArrayProperty(userProps, "mvg_aliceVision_version").set(aliceVisionVersion); - - // hide mvgCamerasUndefined - Alembic::AbcGeom::CreateVisibilityProperty(_mvgCamerasUndefined, 0).set(Alembic::AbcGeom::kVisibilityHidden); - } - - /** - * @brief Add a camera - * @param[in] name The camera identifier - * @param[in] view The corresponding view - * @param[in] pose The camera pose (nullptr if undefined) - * @param[in] intrinsic The camera intrinsic (nullptr if undefined) - * @param[in,out] parent The Alembic parent node - */ - void addCamera(const std::string& name, - const sfmData::View& view, - const sfmData::CameraPose* pose = nullptr, - std::shared_ptr intrinsic = nullptr, - const Vec6* uncertainty = nullptr, - Alembic::Abc::OObject* parent = nullptr); - - Alembic::Abc::OArchive _archive; - Alembic::Abc::OObject _topObj; - Alembic::AbcGeom::OXform _mvgRoot; - Alembic::AbcGeom::OXform _mvgCameras; - Alembic::AbcGeom::OXform _mvgCamerasUndefined; - Alembic::AbcGeom::OXform _mvgCloud; - Alembic::AbcGeom::OXform _mvgPointCloud; - Alembic::AbcGeom::OXform _xform; - Alembic::AbcGeom::OCamera _camObj; - Alembic::AbcGeom::OUInt32ArrayProperty _propSensorSize_pix; - Alembic::AbcGeom::OStringProperty _imagePlane; - Alembic::AbcGeom::OUInt32Property _propViewId; - Alembic::AbcGeom::OUInt32Property _propIntrinsicId; - Alembic::AbcGeom::OStringProperty _mvgIntrinsicType; - Alembic::AbcGeom::ODoubleArrayProperty _mvgIntrinsicParams; -}; + : _archive(Alembic::AbcCoreOgawa::WriteArchive(), filename), + _topObj(_archive, Alembic::Abc::kTop) + { + // create MVG hierarchy + _mvgRoot = Alembic::AbcGeom::OXform(_topObj, "mvgRoot"); + _mvgCameras = Alembic::AbcGeom::OXform(_mvgRoot, "mvgCameras"); + _mvgCamerasUndefined = Alembic::AbcGeom::OXform(_mvgRoot, "mvgCamerasUndefined"); + _mvgCloud = Alembic::AbcGeom::OXform(_mvgRoot, "mvgCloud"); + _mvgPointCloud = Alembic::AbcGeom::OXform(_mvgCloud, "mvgPointCloud"); + // add version as custom property + const std::vector<::uint32_t> abcVersion = { + ALICEVISION_SFMDATAIO_VERSION_MAJOR, ALICEVISION_SFMDATAIO_VERSION_MINOR, ALICEVISION_SFMDATAIO_VERSION_REVISION}; + const std::vector<::uint32_t> aliceVisionVersion = {ALICEVISION_VERSION_MAJOR, ALICEVISION_VERSION_MINOR, ALICEVISION_VERSION_REVISION}; -void AlembicExporter::DataImpl::addCamera(const std::string& name, - const sfmData::View& view, - const sfmData::CameraPose* pose, - std::shared_ptr intrinsic, - const Vec6* uncertainty, - Alembic::Abc::OObject* parent) -{ - if(parent == nullptr) - parent = &_mvgCameras; + auto userProps = _mvgRoot.getProperties(); - std::stringstream ssLabel; - ssLabel << "camxform_" << std::setfill('0') << std::setw(5) << view.getResectionId() << "_" << view.getPoseId(); - ssLabel << "_" << name << "_" << view.getViewId(); + OUInt32ArrayProperty(userProps, "mvg_ABC_version").set(abcVersion); + OUInt32ArrayProperty(userProps, "mvg_aliceVision_version").set(aliceVisionVersion); - Alembic::AbcGeom::OXform xform(*parent, ssLabel.str()); - OCamera camObj(xform, "camera_" + ssLabel.str()); + // hide mvgCamerasUndefined + Alembic::AbcGeom::CreateVisibilityProperty(_mvgCamerasUndefined, 0).set(Alembic::AbcGeom::kVisibilityHidden); + } - auto userProps = camObj.getSchema().getUserProperties(); + /** + * @brief Add a camera + * @param[in] name The camera identifier + * @param[in] view The corresponding view + * @param[in] pose The camera pose (nullptr if undefined) + * @param[in] intrinsic The camera intrinsic (nullptr if undefined) + * @param[in,out] parent The Alembic parent node + */ + void addCamera(const std::string& name, + const sfmData::View& view, + const sfmData::CameraPose* pose = nullptr, + std::shared_ptr intrinsic = nullptr, + const Vec6* uncertainty = nullptr, + Alembic::Abc::OObject* parent = nullptr); + + Alembic::Abc::OArchive _archive; + Alembic::Abc::OObject _topObj; + Alembic::AbcGeom::OXform _mvgRoot; + Alembic::AbcGeom::OXform _mvgCameras; + Alembic::AbcGeom::OXform _mvgCamerasUndefined; + Alembic::AbcGeom::OXform _mvgCloud; + Alembic::AbcGeom::OXform _mvgPointCloud; + Alembic::AbcGeom::OXform _xform; + Alembic::AbcGeom::OCamera _camObj; + Alembic::AbcGeom::OUInt32ArrayProperty _propSensorSize_pix; + Alembic::AbcGeom::OStringProperty _imagePlane; + Alembic::AbcGeom::OUInt32Property _propViewId; + Alembic::AbcGeom::OUInt32Property _propIntrinsicId; + Alembic::AbcGeom::OStringProperty _mvgIntrinsicType; + Alembic::AbcGeom::ODoubleArrayProperty _mvgIntrinsicParams; +}; - XformSample xformsample; +void AlembicExporter::DataImpl::addCamera(const std::string& name, + const sfmData::View& view, + const sfmData::CameraPose* pose, + std::shared_ptr intrinsic, + const Vec6* uncertainty, + Alembic::Abc::OObject* parent) +{ + if (parent == nullptr) + parent = &_mvgCameras; - // set camera pose - if(pose != nullptr) - { - OBoolProperty(userProps, "mvg_poseLocked").set(pose->isLocked()); + std::stringstream ssLabel; + ssLabel << "camxform_" << std::setfill('0') << std::setw(5) << view.getResectionId() << "_" << view.getPoseId(); + ssLabel << "_" << name << "_" << view.getViewId(); - // Convert from computer vision convention to computer graphics (opengl-like) - Eigen::Matrix4d M = Eigen::Matrix4d::Identity(); - M(1, 1) = -1; - M(2, 2) = -1; + Alembic::AbcGeom::OXform xform(*parent, ssLabel.str()); + OCamera camObj(xform, "camera_" + ssLabel.str()); - Eigen::Matrix4d T = Eigen::Matrix4d::Identity(); - T.block<3, 3>(0, 0) = pose->getTransform().rotation(); - T.block<3, 1>(0, 3) = pose->getTransform().translation(); + auto userProps = camObj.getSchema().getUserProperties(); - Eigen::Matrix4d T2 = (M * T * M).inverse(); - + XformSample xformsample; - // compensate translation with rotation - // build transform matrix - Abc::M44d xformMatrix; - for (int i = 0; i < 4; i++) + // set camera pose + if (pose != nullptr) { - for (int j = 0; j < 4; j++) - { - xformMatrix[j][i] = T2(i, j); - } - } + OBoolProperty(userProps, "mvg_poseLocked").set(pose->isLocked()); - xformsample.setMatrix(xformMatrix); - } + // Convert from computer vision convention to computer graphics (opengl-like) + Eigen::Matrix4d M = Eigen::Matrix4d::Identity(); + M(1, 1) = -1; + M(2, 2) = -1; - xform.getSchema().set(xformsample); + Eigen::Matrix4d T = Eigen::Matrix4d::Identity(); + T.block<3, 3>(0, 0) = pose->getTransform().rotation(); + T.block<3, 1>(0, 3) = pose->getTransform().translation(); - // set view custom properties - if(!view.getImage().getImagePath().empty()) - OStringProperty(userProps, "mvg_imagePath").set(view.getImage().getImagePath()); + Eigen::Matrix4d T2 = (M * T * M).inverse(); - OUInt32Property(userProps, "mvg_viewId").set(view.getViewId()); - OUInt32Property(userProps, "mvg_poseId").set(view.getPoseId()); - OUInt32Property(userProps, "mvg_intrinsicId").set(view.getIntrinsicId()); - OUInt32Property(userProps, "mvg_resectionId").set(view.getResectionId()); + // compensate translation with rotation + // build transform matrix + Abc::M44d xformMatrix; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + xformMatrix[j][i] = T2(i, j); + } + } - if(view.isPartOfRig()) - { - OUInt32Property(userProps, "mvg_rigId").set(view.getRigId()); - OUInt32Property(userProps, "mvg_subPoseId").set(view.getSubPoseId()); - } + xformsample.setMatrix(xformMatrix); + } - if(view.getFrameId() != UndefinedIndexT) - OUInt32Property(userProps, "mvg_frameId").set(view.getFrameId()); + xform.getSchema().set(xformsample); - if(view.isPoseIndependant() == false) - OBoolProperty(userProps, "mvg_poseIndependant").set(view.isPoseIndependant()); + // set view custom properties + if (!view.getImage().getImagePath().empty()) + OStringProperty(userProps, "mvg_imagePath").set(view.getImage().getImagePath()); - // set view metadata - { - std::vector rawMetadata(view.getImage().getMetadata().size() * 2); - auto it = view.getImage().getMetadata().cbegin(); + OUInt32Property(userProps, "mvg_viewId").set(view.getViewId()); + OUInt32Property(userProps, "mvg_poseId").set(view.getPoseId()); + OUInt32Property(userProps, "mvg_intrinsicId").set(view.getIntrinsicId()); + OUInt32Property(userProps, "mvg_resectionId").set(view.getResectionId()); - for(std::size_t i = 0; i < rawMetadata.size(); i+=2) + if (view.isPartOfRig()) { - rawMetadata.at(i) = it->first; - rawMetadata.at(i + 1) = it->second; - std::advance(it,1); + OUInt32Property(userProps, "mvg_rigId").set(view.getRigId()); + OUInt32Property(userProps, "mvg_subPoseId").set(view.getSubPoseId()); } - OStringArrayProperty(userProps, "mvg_metadata").set(rawMetadata); - } - - //Export ancestors - OUInt32ArrayProperty(userProps, "mvg_ancestorsParams").set(view.getAncestors()); - // set intrinsic properties - std::shared_ptr intrinsicCasted = std::dynamic_pointer_cast(intrinsic); - if(intrinsicCasted) - { - CameraSample camSample; + if (view.getFrameId() != UndefinedIndexT) + OUInt32Property(userProps, "mvg_frameId").set(view.getFrameId()); - // Take the max of the image size to handle the case where the image is in portrait mode - const float imgWidth = intrinsicCasted->w(); - const float imgHeight = intrinsicCasted->h(); - const float sensorWidth = intrinsicCasted->sensorWidth(); - const float sensorHeight = intrinsicCasted->sensorHeight(); - const float sensorWidth_pix = std::max(imgWidth, imgHeight); - const float focalLengthX_pix = static_cast(intrinsicCasted->getScale()(0)); - const float focalLengthY_pix = static_cast(intrinsicCasted->getScale()(1)); - const float focalLength_mm = sensorWidth * focalLengthX_pix / sensorWidth_pix; - const float squeeze = focalLengthX_pix / focalLengthY_pix; - const float pix2mm = sensorWidth / sensorWidth_pix; + if (view.isPoseIndependant() == false) + OBoolProperty(userProps, "mvg_poseIndependant").set(view.isPoseIndependant()); - // aliceVision: origin is (top,left) corner and orientation is (bottom,right) - // ABC: origin is centered and orientation is (up,right) - // Following values are in cm, hence the 0.1 multiplier - const float haperture_cm = static_cast(0.1 * imgWidth * pix2mm); - const float vaperture_cm = static_cast(0.1 * imgHeight * pix2mm); + // set view metadata + { + std::vector rawMetadata(view.getImage().getMetadata().size() * 2); + auto it = view.getImage().getMetadata().cbegin(); - camSample.setFocalLength(focalLength_mm); - camSample.setHorizontalAperture(haperture_cm); - camSample.setVerticalAperture(vaperture_cm); - camSample.setLensSqueezeRatio(squeeze); + for (std::size_t i = 0; i < rawMetadata.size(); i += 2) + { + rawMetadata.at(i) = it->first; + rawMetadata.at(i + 1) = it->second; + std::advance(it, 1); + } + OStringArrayProperty(userProps, "mvg_metadata").set(rawMetadata); + } - // Add sensor width (largest image side) in pixels as custom property - std::vector<::uint32_t> sensorSize_pix = {intrinsicCasted->w(), intrinsicCasted->h()}; - std::vector sensorSize_mm = {sensorWidth, sensorHeight}; + // Export ancestors + OUInt32ArrayProperty(userProps, "mvg_ancestorsParams").set(view.getAncestors()); - double initialFocalLength = (intrinsicCasted->getInitialScale().x() > 0)?(intrinsicCasted->getInitialScale().x() * sensorWidth / double(intrinsicCasted->w())):-1; + // set intrinsic properties + std::shared_ptr intrinsicCasted = std::dynamic_pointer_cast(intrinsic); + if (intrinsicCasted) + { + CameraSample camSample; + + // Take the max of the image size to handle the case where the image is in portrait mode + const float imgWidth = intrinsicCasted->w(); + const float imgHeight = intrinsicCasted->h(); + const float sensorWidth = intrinsicCasted->sensorWidth(); + const float sensorHeight = intrinsicCasted->sensorHeight(); + const float sensorWidth_pix = std::max(imgWidth, imgHeight); + const float focalLengthX_pix = static_cast(intrinsicCasted->getScale()(0)); + const float focalLengthY_pix = static_cast(intrinsicCasted->getScale()(1)); + const float focalLength_mm = sensorWidth * focalLengthX_pix / sensorWidth_pix; + const float squeeze = focalLengthX_pix / focalLengthY_pix; + const float pix2mm = sensorWidth / sensorWidth_pix; + + // aliceVision: origin is (top,left) corner and orientation is (bottom,right) + // ABC: origin is centered and orientation is (up,right) + // Following values are in cm, hence the 0.1 multiplier + const float haperture_cm = static_cast(0.1 * imgWidth * pix2mm); + const float vaperture_cm = static_cast(0.1 * imgHeight * pix2mm); + + camSample.setFocalLength(focalLength_mm); + camSample.setHorizontalAperture(haperture_cm); + camSample.setVerticalAperture(vaperture_cm); + camSample.setLensSqueezeRatio(squeeze); + + // Add sensor width (largest image side) in pixels as custom property + std::vector<::uint32_t> sensorSize_pix = {intrinsicCasted->w(), intrinsicCasted->h()}; + std::vector sensorSize_mm = {sensorWidth, sensorHeight}; + + double initialFocalLength = + (intrinsicCasted->getInitialScale().x() > 0) ? (intrinsicCasted->getInitialScale().x() * sensorWidth / double(intrinsicCasted->w())) : -1; + + OUInt32ArrayProperty(userProps, "mvg_sensorSizePix").set(sensorSize_pix); + ODoubleArrayProperty(userProps, "mvg_sensorSizeMm").set(sensorSize_mm); + OStringProperty(userProps, "mvg_intrinsicType").set(intrinsicCasted->getTypeStr()); + OStringProperty(userProps, "mvg_intrinsicInitializationMode").set(camera::EInitMode_enumToString(intrinsicCasted->getInitializationMode())); + ODoubleProperty(userProps, "mvg_initialFocalLength").set(initialFocalLength); + OBoolProperty(userProps, "mvg_intrinsicLocked").set(intrinsicCasted->isLocked()); + OBoolProperty(userProps, "mvg_intrinsicPixelRatioLocked").set(intrinsicCasted->isRatioLocked()); + OStringProperty(userProps, "mvg_intrinsicDistortionInitializationMode") + .set(camera::EInitMode_enumToString(intrinsicCasted->getDistortionInitializationMode())); + + // Intrinsic parameters + { + Vec2 scale = intrinsicCasted->getScale(); + Vec2 offset = intrinsicCasted->getOffset(); + std::vector params = {scale(0), scale(1), offset(0), offset(1)}; + ODoubleArrayProperty(userProps, "mvg_intrinsicParams").set(params); + } + // Distortion parameters + std::shared_ptr distortion = intrinsicCasted->getDistortion(); + if (distortion) + { + ODoubleArrayProperty(userProps, "mvg_distortionParams").set(distortion->getParameters()); + } + // Undistortion parameters and offset + std::shared_ptr undistortion = intrinsicCasted->getUndistortion(); + if (undistortion) + { + ODoubleArrayProperty(userProps, "mvg_undistortionParams").set(undistortion->getParameters()); + ODoubleProperty(userProps, "mvg_undistortionOffsetX").set(undistortion->getOffset().x()); + ODoubleProperty(userProps, "mvg_undistortionOffsetY").set(undistortion->getOffset().y()); + } - OUInt32ArrayProperty(userProps, "mvg_sensorSizePix").set(sensorSize_pix); - ODoubleArrayProperty(userProps, "mvg_sensorSizeMm").set(sensorSize_mm); - OStringProperty(userProps, "mvg_intrinsicType").set(intrinsicCasted->getTypeStr()); - OStringProperty(userProps, "mvg_intrinsicInitializationMode").set(camera::EInitMode_enumToString(intrinsicCasted->getInitializationMode())); - ODoubleProperty(userProps, "mvg_initialFocalLength").set(initialFocalLength); - OBoolProperty(userProps, "mvg_intrinsicLocked").set(intrinsicCasted->isLocked()); - OBoolProperty(userProps, "mvg_intrinsicPixelRatioLocked").set(intrinsicCasted->isRatioLocked()); - OStringProperty(userProps, "mvg_intrinsicDistortionInitializationMode").set( - camera::EInitMode_enumToString(intrinsicCasted->getDistortionInitializationMode())); + camObj.getSchema().set(camSample); + } - // Intrinsic parameters + std::shared_ptr intrinsicEquiCasted = std::dynamic_pointer_cast(intrinsic); + if (intrinsicEquiCasted) { - Vec2 scale = intrinsicCasted->getScale(); - Vec2 offset = intrinsicCasted->getOffset(); - std::vector params = {scale(0), scale(1), offset(0), offset(1)}; - ODoubleArrayProperty(userProps, "mvg_intrinsicParams").set(params); + ODoubleProperty(userProps, "mvg_fisheyeCircleCenterX").set(intrinsicEquiCasted->getCircleCenterX()); + ODoubleProperty(userProps, "mvg_fisheyeCircleCenterY").set(intrinsicEquiCasted->getCircleCenterY()); + ODoubleProperty(userProps, "mvg_fisheyeCircleRadius").set(intrinsicEquiCasted->getCircleRadius()); } - // Distortion parameters - std::shared_ptr distortion = intrinsicCasted->getDistortion(); - if (distortion) + + if (uncertainty) { - ODoubleArrayProperty(userProps, "mvg_distortionParams").set(distortion->getParameters()); + std::vector uncertaintyParams(uncertainty->data(), uncertainty->data() + 6); + ODoubleArrayProperty mvg_uncertaintyParams(userProps, "mvg_uncertaintyEigenValues"); + mvg_uncertaintyParams.set(uncertaintyParams); } - // Undistortion parameters and offset - std::shared_ptr undistortion = intrinsicCasted->getUndistortion(); - if (undistortion) + + if (pose == nullptr || intrinsicCasted == nullptr) { - ODoubleArrayProperty(userProps, "mvg_undistortionParams").set(undistortion->getParameters()); - ODoubleProperty(userProps, "mvg_undistortionOffsetX").set(undistortion->getOffset().x()); - ODoubleProperty(userProps, "mvg_undistortionOffsetY").set(undistortion->getOffset().y()); + // hide camera + Alembic::AbcGeom::CreateVisibilityProperty(xform, 0).set(Alembic::AbcGeom::kVisibilityHidden); } - - camObj.getSchema().set(camSample); - } - - std::shared_ptr intrinsicEquiCasted = std::dynamic_pointer_cast(intrinsic); - if(intrinsicEquiCasted) - { - ODoubleProperty(userProps, "mvg_fisheyeCircleCenterX").set(intrinsicEquiCasted->getCircleCenterX()); - ODoubleProperty(userProps, "mvg_fisheyeCircleCenterY").set(intrinsicEquiCasted->getCircleCenterY()); - ODoubleProperty(userProps, "mvg_fisheyeCircleRadius").set(intrinsicEquiCasted->getCircleRadius()); - } - - if(uncertainty) - { - std::vector uncertaintyParams(uncertainty->data(), uncertainty->data()+6); - ODoubleArrayProperty mvg_uncertaintyParams(userProps, "mvg_uncertaintyEigenValues"); - mvg_uncertaintyParams.set(uncertaintyParams); - } - - if(pose == nullptr || intrinsicCasted == nullptr) - { - // hide camera - Alembic::AbcGeom::CreateVisibilityProperty(xform, 0).set(Alembic::AbcGeom::kVisibilityHidden); - } } AlembicExporter::AlembicExporter(const std::string& filename) @@ -273,269 +273,269 @@ AlembicExporter::AlembicExporter(const std::string& filename) AlembicExporter::~AlembicExporter() = default; -std::string AlembicExporter::getFilename() const -{ - return _dataImpl->_archive.getName(); -} +std::string AlembicExporter::getFilename() const { return _dataImpl->_archive.getName(); } void AlembicExporter::addSfM(const sfmData::SfMData& sfmData, ESfMData flagsPart) { - OCompoundProperty userProps = _dataImpl->_mvgRoot.getProperties(); - - OStringArrayProperty(userProps, "mvg_featuresFolders").set(sfmData.getRelativeFeaturesFolders()); - OStringArrayProperty(userProps, "mvg_matchesFolders").set(sfmData.getRelativeMatchesFolders()); - - if(flagsPart & ESfMData::STRUCTURE) - { - const sfmData::LandmarksUncertainty noUncertainty; - - addLandmarks(sfmData.getLandmarks(), - (flagsPart & ESfMData::LANDMARKS_UNCERTAINTY) ? sfmData._landmarksUncertainty : noUncertainty, - ((flagsPart & ESfMData::OBSERVATIONS || flagsPart & ESfMData::OBSERVATIONS_WITH_FEATURES)), (flagsPart & ESfMData::OBSERVATIONS_WITH_FEATURES)); - } + OCompoundProperty userProps = _dataImpl->_mvgRoot.getProperties(); - if(flagsPart & ESfMData::VIEWS || - flagsPart & ESfMData::EXTRINSICS) - { - std::map>> rigsViewIds; //map> + OStringArrayProperty(userProps, "mvg_featuresFolders").set(sfmData.getRelativeFeaturesFolders()); + OStringArrayProperty(userProps, "mvg_matchesFolders").set(sfmData.getRelativeMatchesFolders()); - // save all single views - for(const auto& viewPair : sfmData.getViews()) + if (flagsPart & ESfMData::STRUCTURE) { - const sfmData::View& view = *(viewPair.second); - - if(view.isPartOfRig() && !view.isPoseIndependant()) - { - // save rigId, poseId, viewId in a temporary structure, will process later - rigsViewIds[view.getRigId()][view.getPoseId()].push_back(view.getViewId()); - continue; - } - addSfMSingleCamera(sfmData, view, flagsPart); + const sfmData::LandmarksUncertainty noUncertainty; + + addLandmarks(sfmData.getLandmarks(), + (flagsPart & ESfMData::LANDMARKS_UNCERTAINTY) ? sfmData._landmarksUncertainty : noUncertainty, + ((flagsPart & ESfMData::OBSERVATIONS || flagsPart & ESfMData::OBSERVATIONS_WITH_FEATURES)), + (flagsPart & ESfMData::OBSERVATIONS_WITH_FEATURES)); } - // save rigs views - for(const auto& rigPair : rigsViewIds) + if (flagsPart & ESfMData::VIEWS || flagsPart & ESfMData::EXTRINSICS) { - for(const auto& poseViewIds : rigPair.second) - addSfMCameraRig(sfmData, rigPair.first, poseViewIds.second, flagsPart); // add one camera rig per rig pose + std::map>> rigsViewIds; // map> + + // save all single views + for (const auto& viewPair : sfmData.getViews()) + { + const sfmData::View& view = *(viewPair.second); + + if (view.isPartOfRig() && !view.isPoseIndependant()) + { + // save rigId, poseId, viewId in a temporary structure, will process later + rigsViewIds[view.getRigId()][view.getPoseId()].push_back(view.getViewId()); + continue; + } + addSfMSingleCamera(sfmData, view, flagsPart); + } + + // save rigs views + for (const auto& rigPair : rigsViewIds) + { + for (const auto& poseViewIds : rigPair.second) + addSfMCameraRig(sfmData, rigPair.first, poseViewIds.second, flagsPart); // add one camera rig per rig pose + } } - } } -void AlembicExporter::addSfMSingleCamera(const sfmData::SfMData& sfmData, const sfmData::View& view, - ESfMData flagsPart) +void AlembicExporter::addSfMSingleCamera(const sfmData::SfMData& sfmData, const sfmData::View& view, ESfMData flagsPart) { - const std::string name = fs::path(view.getImage().getImagePath()).stem().string(); - const sfmData::CameraPose* pose = ((flagsPart & ESfMData::EXTRINSICS) && sfmData.existsPose(view)) ? &(sfmData.getPoses().at(view.getPoseId())) : nullptr; - const std::shared_ptr intrinsic = (flagsPart & ESfMData::INTRINSICS) ? sfmData.getIntrinsicsharedPtr(view.getIntrinsicId()) : nullptr; - - if(sfmData.isPoseAndIntrinsicDefined(&view) && (flagsPart & ESfMData::EXTRINSICS)) - _dataImpl->addCamera(name, view, pose, intrinsic, nullptr, &_dataImpl->_mvgCameras); - else - _dataImpl->addCamera(name, view, pose, intrinsic, nullptr, &_dataImpl->_mvgCamerasUndefined); + const std::string name = fs::path(view.getImage().getImagePath()).stem().string(); + const sfmData::CameraPose* pose = + ((flagsPart & ESfMData::EXTRINSICS) && sfmData.existsPose(view)) ? &(sfmData.getPoses().at(view.getPoseId())) : nullptr; + const std::shared_ptr intrinsic = + (flagsPart & ESfMData::INTRINSICS) ? sfmData.getIntrinsicsharedPtr(view.getIntrinsicId()) : nullptr; + + if (sfmData.isPoseAndIntrinsicDefined(&view) && (flagsPart & ESfMData::EXTRINSICS)) + _dataImpl->addCamera(name, view, pose, intrinsic, nullptr, &_dataImpl->_mvgCameras); + else + _dataImpl->addCamera(name, view, pose, intrinsic, nullptr, &_dataImpl->_mvgCamerasUndefined); } -void AlembicExporter::addSfMCameraRig(const sfmData::SfMData& sfmData, IndexT rigId, const std::vector& viewIds, - ESfMData flagsPart) +void AlembicExporter::addSfMCameraRig(const sfmData::SfMData& sfmData, IndexT rigId, const std::vector& viewIds, ESfMData flagsPart) { - const sfmData::Rig& rig = sfmData.getRigs().at(rigId); - const std::size_t nbSubPoses = rig.getNbSubPoses(); - - const sfmData::View& firstView = *(sfmData.getViews().at(viewIds.front())); + const sfmData::Rig& rig = sfmData.getRigs().at(rigId); + const std::size_t nbSubPoses = rig.getNbSubPoses(); - XformSample xformsample; - const IndexT rigPoseId = firstView.getPoseId(); - bool rigPoseLocked = false; + const sfmData::View& firstView = *(sfmData.getViews().at(viewIds.front())); - if(sfmData.getPoses().find(rigPoseId) != sfmData.getPoses().end()) - { - // rig pose - const sfmData::CameraPose& rigPose = sfmData.getAbsolutePose(rigPoseId); - const geometry::Pose3& rigTransform = rigPose.getTransform(); - rigPoseLocked = rigPose.isLocked(); + XformSample xformsample; + const IndexT rigPoseId = firstView.getPoseId(); + bool rigPoseLocked = false; - Eigen::Matrix4d M = Eigen::Matrix4d::Identity(); - M(1, 1) = -1; - M(2, 2) = -1; + if (sfmData.getPoses().find(rigPoseId) != sfmData.getPoses().end()) + { + // rig pose + const sfmData::CameraPose& rigPose = sfmData.getAbsolutePose(rigPoseId); + const geometry::Pose3& rigTransform = rigPose.getTransform(); + rigPoseLocked = rigPose.isLocked(); - Eigen::Matrix4d T = Eigen::Matrix4d::Identity(); - T.block<3, 3>(0, 0) = rigPose.getTransform().rotation(); - T.block<3, 1>(0, 3) = rigPose.getTransform().translation(); + Eigen::Matrix4d M = Eigen::Matrix4d::Identity(); + M(1, 1) = -1; + M(2, 2) = -1; - Eigen::Matrix4d T2 = (M * T * M).inverse(); + Eigen::Matrix4d T = Eigen::Matrix4d::Identity(); + T.block<3, 3>(0, 0) = rigPose.getTransform().rotation(); + T.block<3, 1>(0, 3) = rigPose.getTransform().translation(); + Eigen::Matrix4d T2 = (M * T * M).inverse(); - // compensate translation with rotation - // build transform matrix - Abc::M44d xformMatrix; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) + // compensate translation with rotation + // build transform matrix + Abc::M44d xformMatrix; + for (int i = 0; i < 4; i++) { - xformMatrix[j][i] = T2(i, j); + for (int j = 0; j < 4; j++) + { + xformMatrix[j][i] = T2(i, j); + } } - } - - xformsample.setMatrix(xformMatrix); - } - std::stringstream ssLabel; - ssLabel << "rigxform_" << std::setfill('0') << std::setw(5) << rigId << "_" << rigPoseId; + xformsample.setMatrix(xformMatrix); + } - std::map rigObj; - for(const IndexT viewId : viewIds) - { - const sfmData::View& view = *(sfmData.getViews().at(viewId)); - const sfmData::RigSubPose& rigSubPose = rig.getSubPose(view.getSubPoseId()); - const bool isReconstructed = (rigSubPose.status != sfmData::ERigSubPoseStatus::UNINITIALIZED); - const std::string name = fs::path(view.getImage().getImagePath()).stem().string(); - const std::shared_ptr intrinsic = (flagsPart & ESfMData::INTRINSICS) ? sfmData.getIntrinsicsharedPtr(view.getIntrinsicId()) : nullptr; - std::unique_ptr subPosePtr; + std::stringstream ssLabel; + ssLabel << "rigxform_" << std::setfill('0') << std::setw(5) << rigId << "_" << rigPoseId; - if(isReconstructed && (flagsPart & ESfMData::EXTRINSICS)) + std::map rigObj; + for (const IndexT viewId : viewIds) { - subPosePtr = std::unique_ptr(new sfmData::CameraPose(rigSubPose.pose)); - } + const sfmData::View& view = *(sfmData.getViews().at(viewId)); + const sfmData::RigSubPose& rigSubPose = rig.getSubPose(view.getSubPoseId()); + const bool isReconstructed = (rigSubPose.status != sfmData::ERigSubPoseStatus::UNINITIALIZED); + const std::string name = fs::path(view.getImage().getImagePath()).stem().string(); + const std::shared_ptr intrinsic = + (flagsPart & ESfMData::INTRINSICS) ? sfmData.getIntrinsicsharedPtr(view.getIntrinsicId()) : nullptr; + std::unique_ptr subPosePtr; + + if (isReconstructed && (flagsPart & ESfMData::EXTRINSICS)) + { + subPosePtr = std::unique_ptr(new sfmData::CameraPose(rigSubPose.pose)); + } - Alembic::Abc::OObject& parent = isReconstructed ? _dataImpl->_mvgCameras : _dataImpl->_mvgCamerasUndefined; + Alembic::Abc::OObject& parent = isReconstructed ? _dataImpl->_mvgCameras : _dataImpl->_mvgCamerasUndefined; - if(rigObj.find(isReconstructed) == rigObj.end()) - { - // The first time we declare a view, we have to create a RIG entry. - // The RIG entry will be different if the view is reconstructed or not. - rigObj[isReconstructed] = Alembic::AbcGeom::OXform(parent, ssLabel.str()); - auto schema = rigObj.at(isReconstructed).getSchema(); - schema.set(xformsample); - { - auto userProps = schema.getUserProperties(); - OUInt32Property(userProps, "mvg_rigId").set(rigId); - OUInt32Property(userProps, "mvg_poseId").set(rigPoseId); - OUInt16Property(userProps, "mvg_nbSubPoses").set(nbSubPoses); - OBoolProperty(userProps, "mvg_rigPoseLocked").set(rigPoseLocked); - } + if (rigObj.find(isReconstructed) == rigObj.end()) + { + // The first time we declare a view, we have to create a RIG entry. + // The RIG entry will be different if the view is reconstructed or not. + rigObj[isReconstructed] = Alembic::AbcGeom::OXform(parent, ssLabel.str()); + auto schema = rigObj.at(isReconstructed).getSchema(); + schema.set(xformsample); + { + auto userProps = schema.getUserProperties(); + OUInt32Property(userProps, "mvg_rigId").set(rigId); + OUInt32Property(userProps, "mvg_poseId").set(rigPoseId); + OUInt16Property(userProps, "mvg_nbSubPoses").set(nbSubPoses); + OBoolProperty(userProps, "mvg_rigPoseLocked").set(rigPoseLocked); + } + } + _dataImpl->addCamera(name, view, subPosePtr.get(), intrinsic, nullptr, &(rigObj.at(isReconstructed))); } - _dataImpl->addCamera(name, view, subPosePtr.get(), intrinsic, nullptr, &(rigObj.at(isReconstructed))); - } } -void AlembicExporter::addLandmarks(const sfmData::Landmarks& landmarks, const sfmData::LandmarksUncertainty& landmarksUncertainty, bool withVisibility, bool withFeatures) +void AlembicExporter::addLandmarks(const sfmData::Landmarks& landmarks, + const sfmData::LandmarksUncertainty& landmarksUncertainty, + bool withVisibility, + bool withFeatures) { - if(landmarks.empty()) - return; + if (landmarks.empty()) + return; - // Fill vector with the values taken from AliceVision - std::vector positions; - std::vector colors; - std::vector descTypes; - positions.reserve(landmarks.size()); - descTypes.reserve(landmarks.size()); + // Fill vector with the values taken from AliceVision + std::vector positions; + std::vector colors; + std::vector descTypes; + positions.reserve(landmarks.size()); + descTypes.reserve(landmarks.size()); - // For all the 3d points in the hash_map - for(const auto& landmark : landmarks) - { - const Vec3& pt = landmark.second.X; - const image::RGBColor& color = landmark.second.rgb; - // convert position from computer vision convention to computer graphics (opengl-like) - positions.emplace_back(pt[0], -pt[1], -pt[2]); - colors.emplace_back(color.r()/255.f, color.g()/255.f, color.b()/255.f); - descTypes.emplace_back(static_cast(landmark.second.descType)); - } + // For all the 3d points in the hash_map + for (const auto& landmark : landmarks) + { + const Vec3& pt = landmark.second.X; + const image::RGBColor& color = landmark.second.rgb; + // convert position from computer vision convention to computer graphics (opengl-like) + positions.emplace_back(pt[0], -pt[1], -pt[2]); + colors.emplace_back(color.r() / 255.f, color.g() / 255.f, color.b() / 255.f); + descTypes.emplace_back(static_cast(landmark.second.descType)); + } - std::vector ids(positions.size()); - std::iota(begin(ids), end(ids), 0); + std::vector ids(positions.size()); + std::iota(begin(ids), end(ids), 0); - OPoints partsOut(_dataImpl->_mvgPointCloud, "particleShape1"); - OPointsSchema& pSchema = partsOut.getSchema(); + OPoints partsOut(_dataImpl->_mvgPointCloud, "particleShape1"); + OPointsSchema& pSchema = partsOut.getSchema(); - OPointsSchema::Sample psamp(std::move(V3fArraySample(positions)), std::move(UInt64ArraySample(ids))); - pSchema.set(psamp); + OPointsSchema::Sample psamp(std::move(V3fArraySample(positions)), std::move(UInt64ArraySample(ids))); + pSchema.set(psamp); - OCompoundProperty arbGeom = pSchema.getArbGeomParams(); + OCompoundProperty arbGeom = pSchema.getArbGeomParams(); - C3fArraySample cval_samp(&colors[0], colors.size()); - OC3fGeomParam::Sample color_samp(cval_samp, kVertexScope); + C3fArraySample cval_samp(&colors[0], colors.size()); + OC3fGeomParam::Sample color_samp(cval_samp, kVertexScope); - OC3fGeomParam rgbOut(arbGeom, "color", false, kVertexScope, 1); - rgbOut.set(color_samp); + OC3fGeomParam rgbOut(arbGeom, "color", false, kVertexScope, 1); + rgbOut.set(color_samp); - OCompoundProperty userProps = pSchema.getUserProperties(); + OCompoundProperty userProps = pSchema.getUserProperties(); - OUInt32ArrayProperty(userProps, "mvg_describerType").set(descTypes); + OUInt32ArrayProperty(userProps, "mvg_describerType").set(descTypes); - if(withVisibility) - { - std::vector<::uint32_t> visibilitySize; - visibilitySize.reserve(positions.size()); - for(const auto& landmark : landmarks) + if (withVisibility) { - visibilitySize.emplace_back(landmark.second.observations.size()); - } - std::size_t nbObservations = std::accumulate(visibilitySize.begin(), visibilitySize.end(), 0); - - // Use std::vector<::uint32_t> and std::vector instead of std::vector and std::vector - // Because Maya don't import them correctly - std::vector<::uint32_t> visibilityViewId; - std::vector<::uint32_t> visibilityFeatId; - visibilityViewId.reserve(nbObservations); - - std::vector featPos2d; - std::vector featScale; - if(withFeatures) - { - featPos2d.reserve(nbObservations*2); - visibilityFeatId.reserve(nbObservations); - featScale.reserve(nbObservations); - } + std::vector<::uint32_t> visibilitySize; + visibilitySize.reserve(positions.size()); + for (const auto& landmark : landmarks) + { + visibilitySize.emplace_back(landmark.second.observations.size()); + } + std::size_t nbObservations = std::accumulate(visibilitySize.begin(), visibilitySize.end(), 0); - for (const auto& landmark : landmarks) - { - const sfmData::Observations& observations = landmark.second.observations; - for(const auto& vObs: observations ) - { - const sfmData::Observation& obs = vObs.second; + // Use std::vector<::uint32_t> and std::vector instead of std::vector and std::vector + // Because Maya don't import them correctly + std::vector<::uint32_t> visibilityViewId; + std::vector<::uint32_t> visibilityFeatId; + visibilityViewId.reserve(nbObservations); - // viewId - visibilityViewId.emplace_back(vObs.first); + std::vector featPos2d; + std::vector featScale; + if (withFeatures) + { + featPos2d.reserve(nbObservations * 2); + visibilityFeatId.reserve(nbObservations); + featScale.reserve(nbObservations); + } - if(withFeatures) + for (const auto& landmark : landmarks) { - // featureId - visibilityFeatId.emplace_back(obs.id_feat); + const sfmData::Observations& observations = landmark.second.observations; + for (const auto& vObs : observations) + { + const sfmData::Observation& obs = vObs.second; + + // viewId + visibilityViewId.emplace_back(vObs.first); + + if (withFeatures) + { + // featureId + visibilityFeatId.emplace_back(obs.id_feat); + + // feature 2D position (x, y)) + featPos2d.emplace_back(obs.x[0]); + featPos2d.emplace_back(obs.x[1]); + + featScale.emplace_back(obs.scale); + } + } + } - // feature 2D position (x, y)) - featPos2d.emplace_back(obs.x[0]); - featPos2d.emplace_back(obs.x[1]); + OUInt32ArrayProperty(userProps, "mvg_visibilitySize").set(visibilitySize); + OUInt32ArrayProperty(userProps, "mvg_visibilityViewId").set(visibilityViewId); - featScale.emplace_back(obs.scale); + if (withFeatures) + { + OUInt32ArrayProperty(userProps, "mvg_visibilityFeatId").set(visibilityFeatId); + OFloatArrayProperty(userProps, "mvg_visibilityFeatPos").set(featPos2d); // feature position (x,y) + OFloatArrayProperty(userProps, "mvg_visibilityFeatScale").set(featScale); } - } } - - OUInt32ArrayProperty(userProps, "mvg_visibilitySize" ).set(visibilitySize); - OUInt32ArrayProperty(userProps, "mvg_visibilityViewId" ).set(visibilityViewId); - - if(withFeatures) + if (!landmarksUncertainty.empty()) { - OUInt32ArrayProperty(userProps, "mvg_visibilityFeatId" ).set(visibilityFeatId); - OFloatArrayProperty(userProps, "mvg_visibilityFeatPos" ).set(featPos2d); // feature position (x,y) - OFloatArrayProperty(userProps, "mvg_visibilityFeatScale" ).set(featScale); - } - } - if(!landmarksUncertainty.empty()) - { - std::vector uncertainties; + std::vector uncertainties; - std::size_t indexLandmark = 0; - for(sfmData::Landmarks::const_iterator itLandmark = landmarks.begin(); itLandmark != landmarks.end(); ++itLandmark, ++indexLandmark) - { - const IndexT idLandmark = itLandmark->first; - const Vec3& u = landmarksUncertainty.at(idLandmark); - uncertainties.emplace_back(u[0], u[1], u[2]); + std::size_t indexLandmark = 0; + for (sfmData::Landmarks::const_iterator itLandmark = landmarks.begin(); itLandmark != landmarks.end(); ++itLandmark, ++indexLandmark) + { + const IndexT idLandmark = itLandmark->first; + const Vec3& u = landmarksUncertainty.at(idLandmark); + uncertainties.emplace_back(u[0], u[1], u[2]); + } + // Uncertainty eigen values (x,y,z) + OV3dArrayProperty propUncertainty(userProps, "mvg_uncertaintyEigenValues"); + propUncertainty.set(uncertainties); } - // Uncertainty eigen values (x,y,z) - OV3dArrayProperty propUncertainty(userProps, "mvg_uncertaintyEigenValues"); - propUncertainty.set(uncertainties); - } } void AlembicExporter::addCamera(const std::string& name, @@ -544,45 +544,45 @@ void AlembicExporter::addCamera(const std::string& name, std::shared_ptr intrinsic, const Vec6* uncertainty) { - _dataImpl->addCamera(name, view, pose, intrinsic, uncertainty); + _dataImpl->addCamera(name, view, pose, intrinsic, uncertainty); } void AlembicExporter::initAnimatedCamera(const std::string& cameraName, std::size_t startFrame) { - // Sample the time in order to have one keyframe every frame - // nb: it HAS TO be attached to EACH keyframed properties - TimeSamplingPtr tsp( new TimeSampling(1.0 / 24.0, startFrame / 24.0) ); - - // Create the camera transform object - std::stringstream ss; - ss << cameraName; - _dataImpl->_xform = Alembic::AbcGeom::OXform(_dataImpl->_mvgCameras, "animxform_" + ss.str()); - _dataImpl->_xform.getSchema().setTimeSampling(tsp); - - // Create the camera parameters object (intrinsics & custom properties) - _dataImpl->_camObj = OCamera(_dataImpl->_xform, "animcam_" + ss.str()); - _dataImpl->_camObj.getSchema().setTimeSampling(tsp); - - // Add the custom properties - auto userProps = _dataImpl->_camObj.getSchema().getUserProperties(); - // Sensor size - _dataImpl->_propSensorSize_pix = OUInt32ArrayProperty(userProps, "mvg_sensorSizePix"); - _dataImpl->_propSensorSize_pix.setTimeSampling(tsp); - // Image path - _dataImpl->_imagePlane = OStringProperty(userProps, "mvg_imagePath"); - _dataImpl->_imagePlane.setTimeSampling(tsp); - // View id - _dataImpl->_propViewId = OUInt32Property(userProps, "mvg_viewId"); - _dataImpl->_propViewId.setTimeSampling(tsp); - // Intrinsic id - _dataImpl->_propIntrinsicId = OUInt32Property(userProps, "mvg_intrinsicId"); - _dataImpl->_propIntrinsicId.setTimeSampling(tsp); - // Intrinsic type (ex: PINHOLE_CAMERA_RADIAL3) - _dataImpl->_mvgIntrinsicType = OStringProperty(userProps, "mvg_intrinsicType"); - _dataImpl->_mvgIntrinsicType.setTimeSampling(tsp); - // Intrinsic parameters - _dataImpl->_mvgIntrinsicParams = ODoubleArrayProperty(userProps, "mvg_intrinsicParams"); - _dataImpl->_mvgIntrinsicParams.setTimeSampling(tsp); + // Sample the time in order to have one keyframe every frame + // nb: it HAS TO be attached to EACH keyframed properties + TimeSamplingPtr tsp(new TimeSampling(1.0 / 24.0, startFrame / 24.0)); + + // Create the camera transform object + std::stringstream ss; + ss << cameraName; + _dataImpl->_xform = Alembic::AbcGeom::OXform(_dataImpl->_mvgCameras, "animxform_" + ss.str()); + _dataImpl->_xform.getSchema().setTimeSampling(tsp); + + // Create the camera parameters object (intrinsics & custom properties) + _dataImpl->_camObj = OCamera(_dataImpl->_xform, "animcam_" + ss.str()); + _dataImpl->_camObj.getSchema().setTimeSampling(tsp); + + // Add the custom properties + auto userProps = _dataImpl->_camObj.getSchema().getUserProperties(); + // Sensor size + _dataImpl->_propSensorSize_pix = OUInt32ArrayProperty(userProps, "mvg_sensorSizePix"); + _dataImpl->_propSensorSize_pix.setTimeSampling(tsp); + // Image path + _dataImpl->_imagePlane = OStringProperty(userProps, "mvg_imagePath"); + _dataImpl->_imagePlane.setTimeSampling(tsp); + // View id + _dataImpl->_propViewId = OUInt32Property(userProps, "mvg_viewId"); + _dataImpl->_propViewId.setTimeSampling(tsp); + // Intrinsic id + _dataImpl->_propIntrinsicId = OUInt32Property(userProps, "mvg_intrinsicId"); + _dataImpl->_propIntrinsicId.setTimeSampling(tsp); + // Intrinsic type (ex: PINHOLE_CAMERA_RADIAL3) + _dataImpl->_mvgIntrinsicType = OStringProperty(userProps, "mvg_intrinsicType"); + _dataImpl->_mvgIntrinsicType.setTimeSampling(tsp); + // Intrinsic parameters + _dataImpl->_mvgIntrinsicParams = ODoubleArrayProperty(userProps, "mvg_intrinsicParams"); + _dataImpl->_mvgIntrinsicParams.setTimeSampling(tsp); } void AlembicExporter::addCameraKeyframe(const geometry::Pose3& pose, @@ -616,14 +616,14 @@ void AlembicExporter::addCameraKeyframe(const geometry::Pose3& pose, // Create the XformSample XformSample xformsample; xformsample.setMatrix(xformMatrix); - + // Attach it to the schema of the OXform _dataImpl->_xform.getSchema().set(xformsample); - + // Camera intrinsic parameters CameraSample camSample; - // Take the max of the image size to handle the case where the image is in portrait mode + // Take the max of the image size to handle the case where the image is in portrait mode const float imgWidth = cam->w(); const float imgHeight = cam->h(); const float sensorWidth_pix = std::max(imgWidth, imgHeight); @@ -640,11 +640,11 @@ void AlembicExporter::addCameraKeyframe(const geometry::Pose3& pose, camSample.setFocalLength(focalLength_mm); camSample.setHorizontalAperture(haperture_cm); camSample.setVerticalAperture(vaperture_cm); - + // Add sensor size in pixels as custom property std::vector<::uint32_t> sensorSize_pix = {cam->w(), cam->h()}; _dataImpl->_propSensorSize_pix.set(sensorSize_pix); - + // Set custom attributes // Image path _dataImpl->_imagePlane.set(imagePath); @@ -658,24 +658,24 @@ void AlembicExporter::addCameraKeyframe(const geometry::Pose3& pose, // Intrinsic parameters std::vector intrinsicParams = cam->getParams(); _dataImpl->_mvgIntrinsicParams.set(intrinsicParams); - + // Attach intrinsic parameters to camera object _dataImpl->_camObj.getSchema().set(camSample); } void AlembicExporter::jumpKeyframe(const std::string& imagePath) { - if(_dataImpl->_xform.getSchema().getNumSamples() == 0) - { - camera::Pinhole default_intrinsic; - this->addCameraKeyframe(geometry::Pose3(), &default_intrinsic, imagePath, 0, 0); - } - else - { - _dataImpl->_xform.getSchema().setFromPrevious(); - _dataImpl->_camObj.getSchema().setFromPrevious(); - } + if (_dataImpl->_xform.getSchema().getNumSamples() == 0) + { + camera::Pinhole default_intrinsic; + this->addCameraKeyframe(geometry::Pose3(), &default_intrinsic, imagePath, 0, 0); + } + else + { + _dataImpl->_xform.getSchema().setFromPrevious(); + _dataImpl->_camObj.getSchema().setFromPrevious(); + } } -} //namespace sfmDataIO -} //namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/AlembicExporter.hpp b/src/aliceVision/sfmDataIO/AlembicExporter.hpp index 0154ba777d..097e0a9171 100644 --- a/src/aliceVision/sfmDataIO/AlembicExporter.hpp +++ b/src/aliceVision/sfmDataIO/AlembicExporter.hpp @@ -21,101 +21,95 @@ namespace sfmDataIO { class AlembicExporter { -public: - - explicit AlembicExporter(const std::string& filename); - - ~AlembicExporter(); - - /** - * @brief Return the filename associated to the alembic file. - * @return the filename associated to the alembic file. - */ - std::string getFilename() const; - - /** - * @brief Add SfM Data - * @param[in] sfmdata SfMData container - * @param[in] flagsPart filter the elements to add - */ - void addSfM(const sfmData::SfMData& sfmdata, ESfMData flagsPart = ESfMData::ALL); - - /** - * @brief Add a SfM single camera - * @param sfmData The corresponding view scene - * @param view The corresponding view - */ - void addSfMSingleCamera(const sfmData::SfMData& sfmData, const sfmData::View& view, ESfMData flagsPart); - - /** - * @brief Add a SfM camera rig - * @param sfmData The corresponding rig scene - * @param rigId The rig Id in the scene - * @param viewIds The sub-pose view ids in the scene - */ - void addSfMCameraRig(const sfmData::SfMData& sfmData, - IndexT rigId, - const std::vector& viewIds, - ESfMData flagsPart); - - /** - * @brief Add a set of 3d points - * @param[in] points The 3D points to add - */ - void addLandmarks(const sfmData::Landmarks& points, - const sfmData::LandmarksUncertainty& landmarksUncertainty = sfmData::LandmarksUncertainty(), - bool withVisibility = true, - bool withFeatures = true); - - /** - * @brief Add a camera - * @param[in] name The camera identifier - * @param[in] view The corresponding view - * @param[in] pose The camera pose (nullptr if undefined) - * @param[in] intrinsic The camera intrinsic (nullptr if undefined) - * @param[in] uncertainty The camera uncertainty values (nullptr if undefined) - * @param[in,out] parent The Alembic parent node - */ - void addCamera(const std::string& name, - const sfmData::View& view, - const sfmData::CameraPose* pose = nullptr, - std::shared_ptr intrinsic = nullptr, - const Vec6* uncertainty = nullptr); - - /** - * @brief Add a keyframe to the animated camera - * @param[in] pose The camera pose - * @param[in] cam The camera intrinsics parameters - * @param[in] imagePath The localized image path - * @param[in] viewId View id - * @param[in] intrinsicId Intrinsic id - * @param[in] sensorWidthMM Width of the sensor in millimeters - */ - void addCameraKeyframe(const geometry::Pose3& pose, - const camera::Pinhole* cam, - const std::string& imagePath, - IndexT viewId, - IndexT intrinsicId, - float sensorWidthMM=36.0); - - /** - * @brief Initiate an animated camera - * @param[in] name The camera identifier - */ - void initAnimatedCamera(const std::string& name, std::size_t startFrame = 1); - - - - /** - * @brief Register keyframe on the previous values - * @param[in] imagePath The Path to the image - */ - void jumpKeyframe(const std::string& imagePath = std::string()); - -private: - struct DataImpl; - std::unique_ptr _dataImpl; + public: + explicit AlembicExporter(const std::string& filename); + + ~AlembicExporter(); + + /** + * @brief Return the filename associated to the alembic file. + * @return the filename associated to the alembic file. + */ + std::string getFilename() const; + + /** + * @brief Add SfM Data + * @param[in] sfmdata SfMData container + * @param[in] flagsPart filter the elements to add + */ + void addSfM(const sfmData::SfMData& sfmdata, ESfMData flagsPart = ESfMData::ALL); + + /** + * @brief Add a SfM single camera + * @param sfmData The corresponding view scene + * @param view The corresponding view + */ + void addSfMSingleCamera(const sfmData::SfMData& sfmData, const sfmData::View& view, ESfMData flagsPart); + + /** + * @brief Add a SfM camera rig + * @param sfmData The corresponding rig scene + * @param rigId The rig Id in the scene + * @param viewIds The sub-pose view ids in the scene + */ + void addSfMCameraRig(const sfmData::SfMData& sfmData, IndexT rigId, const std::vector& viewIds, ESfMData flagsPart); + + /** + * @brief Add a set of 3d points + * @param[in] points The 3D points to add + */ + void addLandmarks(const sfmData::Landmarks& points, + const sfmData::LandmarksUncertainty& landmarksUncertainty = sfmData::LandmarksUncertainty(), + bool withVisibility = true, + bool withFeatures = true); + + /** + * @brief Add a camera + * @param[in] name The camera identifier + * @param[in] view The corresponding view + * @param[in] pose The camera pose (nullptr if undefined) + * @param[in] intrinsic The camera intrinsic (nullptr if undefined) + * @param[in] uncertainty The camera uncertainty values (nullptr if undefined) + * @param[in,out] parent The Alembic parent node + */ + void addCamera(const std::string& name, + const sfmData::View& view, + const sfmData::CameraPose* pose = nullptr, + std::shared_ptr intrinsic = nullptr, + const Vec6* uncertainty = nullptr); + + /** + * @brief Add a keyframe to the animated camera + * @param[in] pose The camera pose + * @param[in] cam The camera intrinsics parameters + * @param[in] imagePath The localized image path + * @param[in] viewId View id + * @param[in] intrinsicId Intrinsic id + * @param[in] sensorWidthMM Width of the sensor in millimeters + */ + void addCameraKeyframe(const geometry::Pose3& pose, + const camera::Pinhole* cam, + const std::string& imagePath, + IndexT viewId, + IndexT intrinsicId, + float sensorWidthMM = 36.0); + + /** + * @brief Initiate an animated camera + * @param[in] name The camera identifier + */ + void initAnimatedCamera(const std::string& name, std::size_t startFrame = 1); + + /** + * @brief Register keyframe on the previous values + * @param[in] imagePath The Path to the image + */ + void jumpKeyframe(const std::string& imagePath = std::string()); + + private: + struct DataImpl; + std::unique_ptr _dataImpl; }; -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/AlembicImporter.cpp b/src/aliceVision/sfmDataIO/AlembicImporter.cpp index e3e947176d..b01ccdc7df 100644 --- a/src/aliceVision/sfmDataIO/AlembicImporter.cpp +++ b/src/aliceVision/sfmDataIO/AlembicImporter.cpp @@ -22,24 +22,24 @@ using namespace Alembic::AbcGeom; template void getAbcArrayProp(ICompoundProperty& userProps, const std::string& id, index_t sampleFrame, T& outputArray) { - typedef typename AbcArrayProperty::sample_ptr_type sample_ptr_type; + typedef typename AbcArrayProperty::sample_ptr_type sample_ptr_type; - AbcArrayProperty prop(userProps, id); - sample_ptr_type sample; - prop.get(sample, ISampleSelector(sampleFrame)); - outputArray.assign(sample->get(), sample->get()+sample->size()); + AbcArrayProperty prop(userProps, id); + sample_ptr_type sample; + prop.get(sample, ISampleSelector(sampleFrame)); + outputArray.assign(sample->get(), sample->get() + sample->size()); } void getAbcArrayProp_uint(ICompoundProperty& userProps, const std::string& id, index_t sampleFrame, std::vector& outputArray) { - try - { - getAbcArrayProp(userProps, id, sampleFrame, outputArray); - } - catch(Alembic::Util::Exception&) - { - getAbcArrayProp(userProps, id, sampleFrame, outputArray); - } + try + { + getAbcArrayProp(userProps, id, sampleFrame, outputArray); + } + catch (Alembic::Util::Exception&) + { + getAbcArrayProp(userProps, id, sampleFrame, outputArray); + } } /** @@ -53,913 +53,904 @@ void getAbcArrayProp_uint(ICompoundProperty& userProps, const std::string& id, i * @return value */ template -typename AbcProperty::traits_type::value_type getAbcProp(ICompoundProperty& userProps, const Alembic::Abc::PropertyHeader& propHeader, const std::string& id, index_t sampleFrame) +typename AbcProperty::traits_type::value_type getAbcProp(ICompoundProperty& userProps, + const Alembic::Abc::PropertyHeader& propHeader, + const std::string& id, + index_t sampleFrame) { - typedef typename AbcProperty::traits_type traits_type; - typedef typename traits_type::value_type value_type; - typedef typename Alembic::Abc::ITypedArrayProperty array_type; - typedef typename array_type::sample_ptr_type array_sample_ptr_type; - - // Maya transforms everything into arrays - if(propHeader.isArray()) - { - Alembic::Abc::ITypedArrayProperty prop(userProps, id); - array_sample_ptr_type sample; - prop.get(sample, ISampleSelector(sampleFrame)); - return (*sample)[0]; - } - else - { - value_type v; - AbcProperty prop(userProps, id); - prop.get(v, ISampleSelector(sampleFrame)); - return v; - } + typedef typename AbcProperty::traits_type traits_type; + typedef typename traits_type::value_type value_type; + typedef typename Alembic::Abc::ITypedArrayProperty array_type; + typedef typename array_type::sample_ptr_type array_sample_ptr_type; + + // Maya transforms everything into arrays + if (propHeader.isArray()) + { + Alembic::Abc::ITypedArrayProperty prop(userProps, id); + array_sample_ptr_type sample; + prop.get(sample, ISampleSelector(sampleFrame)); + return (*sample)[0]; + } + else + { + value_type v; + AbcProperty prop(userProps, id); + prop.get(v, ISampleSelector(sampleFrame)); + return v; + } } std::size_t getAbcProp_uint(ICompoundProperty& userProps, const Alembic::Abc::PropertyHeader& propHeader, const std::string& id, index_t sampleFrame) { - try - { - return getAbcProp(userProps, propHeader, id, sampleFrame); - } - catch(Alembic::Util::Exception&) - { - return getAbcProp(userProps, propHeader, id, sampleFrame); - } + try + { + return getAbcProp(userProps, propHeader, id, sampleFrame); + } + catch (Alembic::Util::Exception&) + { + return getAbcProp(userProps, propHeader, id, sampleFrame); + } } template inline ICompoundProperty getAbcUserProperties(ABCSCHEMA& schema) { - ICompoundProperty userProps = schema.getUserProperties(); - if(userProps && userProps.getNumProperties() != 0) - return userProps; + ICompoundProperty userProps = schema.getUserProperties(); + if (userProps && userProps.getNumProperties() != 0) + return userProps; - // Maya always use ArbGeomParams instead of user properties. - return schema.getArbGeomParams(); + // Maya always use ArbGeomParams instead of user properties. + return schema.getArbGeomParams(); } - struct AV_UInt32ArraySamplePtr { - bool _isUnsigned = true; - UInt32ArraySamplePtr _v_uint; - Int32ArraySamplePtr _v_int; + bool _isUnsigned = true; + UInt32ArraySamplePtr _v_uint; + Int32ArraySamplePtr _v_int; - AV_UInt32ArraySamplePtr() = default; - template - AV_UInt32ArraySamplePtr(const T& userProps, const char* s) - { - read(userProps, s); - } - - template - void read(const T& userProps, const char* s) - { - try { - IUInt32ArrayProperty propVisibilitySize_uint(userProps, s); - propVisibilitySize_uint.get(_v_uint); + AV_UInt32ArraySamplePtr() = default; + template + AV_UInt32ArraySamplePtr(const T& userProps, const char* s) + { + read(userProps, s); } - catch(Alembic::Util::Exception&) + + template + void read(const T& userProps, const char* s) { - _isUnsigned = false; - IInt32ArrayProperty propVisibilitySize_int(userProps, s); - propVisibilitySize_int.get(_v_int); + try + { + IUInt32ArrayProperty propVisibilitySize_uint(userProps, s); + propVisibilitySize_uint.get(_v_uint); + } + catch (Alembic::Util::Exception&) + { + _isUnsigned = false; + IInt32ArrayProperty propVisibilitySize_int(userProps, s); + propVisibilitySize_int.get(_v_int); + } } - } - std::size_t size() const - { - return _isUnsigned ? _v_uint->size() : _v_int->size(); - } + std::size_t size() const { return _isUnsigned ? _v_uint->size() : _v_int->size(); } - std::size_t operator[](const std::size_t& i) - { - return _isUnsigned ? (*_v_uint)[i] : (*_v_int)[i]; - } + std::size_t operator[](const std::size_t& i) { return _isUnsigned ? (*_v_uint)[i] : (*_v_int)[i]; } - void reset() - { - if(_isUnsigned) - { - _v_uint->reset(); - } - else + void reset() { - _v_int->reset(); + if (_isUnsigned) + { + _v_uint->reset(); + } + else + { + _v_int->reset(); + } } - } - operator bool() const - { - return _isUnsigned ? bool(_v_uint) : bool(_v_int); - } + operator bool() const { return _isUnsigned ? bool(_v_uint) : bool(_v_int); } }; -bool readPointCloud(const Version & abcVersion, IObject iObj, M44d mat, sfmData::SfMData &sfmdata, ESfMData flags_part) +bool readPointCloud(const Version& abcVersion, IObject iObj, M44d mat, sfmData::SfMData& sfmdata, ESfMData flags_part) { - using namespace aliceVision::geometry; + using namespace aliceVision::geometry; - IPoints points(iObj, kWrapExisting); - IPointsSchema& ms = points.getSchema(); - P3fArraySamplePtr positions = ms.getValue().getPositions(); + IPoints points(iObj, kWrapExisting); + IPointsSchema& ms = points.getSchema(); + P3fArraySamplePtr positions = ms.getValue().getPositions(); - ICompoundProperty userProps = getAbcUserProperties(ms); - ICompoundProperty arbGeom = ms.getArbGeomParams(); + ICompoundProperty userProps = getAbcUserProperties(ms); + ICompoundProperty arbGeom = ms.getArbGeomParams(); - C3fArraySamplePtr sampleColors; - if(arbGeom && arbGeom.getPropertyHeader("color")) - { - IC3fArrayProperty propColor(arbGeom, "color"); - propColor.get(sampleColors); - if(sampleColors->size() != positions->size()) + C3fArraySamplePtr sampleColors; + if (arbGeom && arbGeom.getPropertyHeader("color")) { - ALICEVISION_LOG_WARNING("[Alembic Importer] Colors will be ignored. Color vector size: " << sampleColors->size() << ", positions vector size: " << positions->size()); - sampleColors.reset(); + IC3fArrayProperty propColor(arbGeom, "color"); + propColor.get(sampleColors); + if (sampleColors->size() != positions->size()) + { + ALICEVISION_LOG_WARNING("[Alembic Importer] Colors will be ignored. Color vector size: " + << sampleColors->size() << ", positions vector size: " << positions->size()); + sampleColors.reset(); + } } - } - AV_UInt32ArraySamplePtr sampleDescs; - if(userProps && userProps.getPropertyHeader("mvg_describerType")) - { - sampleDescs.read(userProps, "mvg_describerType"); - if(sampleDescs.size() != positions->size()) + AV_UInt32ArraySamplePtr sampleDescs; + if (userProps && userProps.getPropertyHeader("mvg_describerType")) { - ALICEVISION_LOG_WARNING("[Alembic Importer] Describer type will be ignored. describerType vector size: " << sampleDescs.size() << ", positions vector size: " << positions->size()); - sampleDescs.reset(); + sampleDescs.read(userProps, "mvg_describerType"); + if (sampleDescs.size() != positions->size()) + { + ALICEVISION_LOG_WARNING("[Alembic Importer] Describer type will be ignored. describerType vector size: " + << sampleDescs.size() << ", positions vector size: " << positions->size()); + sampleDescs.reset(); + } } - } - // Number of points before adding the Alembic data - const std::size_t nbPointsInit = sfmdata.getLandmarks().size(); - for(std::size_t point3d_i = 0; - point3d_i < positions->size(); - ++point3d_i) - { - const P3fArraySamplePtr::element_type::value_type & pos_i = positions->get()[point3d_i]; + // Number of points before adding the Alembic data + const std::size_t nbPointsInit = sfmdata.getLandmarks().size(); + for (std::size_t point3d_i = 0; point3d_i < positions->size(); ++point3d_i) + { + const P3fArraySamplePtr::element_type::value_type& pos_i = positions->get()[point3d_i]; + sfmData::Landmark& landmark = sfmdata.getLandmarks()[nbPointsInit + point3d_i]; - sfmData::Landmark& landmark = sfmdata.getLandmarks()[nbPointsInit + point3d_i]; - - if (abcVersion < Version(1, 2, 3)) - { - landmark = sfmData::Landmark(Vec3(pos_i.x, pos_i.y, pos_i.z), feature::EImageDescriberType::UNKNOWN); - } - else - { - // convert from computer graphics convention (opengl-like) to computer vision - landmark = sfmData::Landmark(Vec3(pos_i.x, -pos_i.y, -pos_i.z), feature::EImageDescriberType::UNKNOWN); - } + if (abcVersion < Version(1, 2, 3)) + { + landmark = sfmData::Landmark(Vec3(pos_i.x, pos_i.y, pos_i.z), feature::EImageDescriberType::UNKNOWN); + } + else + { + // convert from computer graphics convention (opengl-like) to computer vision + landmark = sfmData::Landmark(Vec3(pos_i.x, -pos_i.y, -pos_i.z), feature::EImageDescriberType::UNKNOWN); + } - if(sampleColors) - { - const P3fArraySamplePtr::element_type::value_type & color_i = sampleColors->get()[point3d_i]; - landmark.rgb = image::RGBColor(static_cast(color_i[0] * 255.0f), - static_cast(color_i[1] * 255.0f), - static_cast(color_i[2] * 255.0f) - ); + if (sampleColors) + { + const P3fArraySamplePtr::element_type::value_type& color_i = sampleColors->get()[point3d_i]; + landmark.rgb = image::RGBColor(static_cast(color_i[0] * 255.0f), + static_cast(color_i[1] * 255.0f), + static_cast(color_i[2] * 255.0f)); + } + + if (sampleDescs) + { + const std::size_t descType_i = sampleDescs[point3d_i]; + landmark.descType = static_cast(descType_i); + } } - if(sampleDescs) + // for compatibility with files generated with a previous version + if (userProps && userProps.getPropertyHeader("mvg_visibilitySize") && userProps.getPropertyHeader("mvg_visibilityIds") && + userProps.getPropertyHeader("mvg_visibilityFeatPos") && + (flags_part & ESfMData::OBSERVATIONS || flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES)) { - const std::size_t descType_i = sampleDescs[point3d_i]; - landmark.descType = static_cast(descType_i); - } - } + AV_UInt32ArraySamplePtr sampleVisibilitySize(userProps, "mvg_visibilitySize"); + AV_UInt32ArraySamplePtr sampleVisibilityIds(userProps, "mvg_visibilityIds"); - // for compatibility with files generated with a previous version - if(userProps && - userProps.getPropertyHeader("mvg_visibilitySize") && - userProps.getPropertyHeader("mvg_visibilityIds") && - userProps.getPropertyHeader("mvg_visibilityFeatPos") && - (flags_part & ESfMData::OBSERVATIONS || flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES)) - { - AV_UInt32ArraySamplePtr sampleVisibilitySize(userProps, "mvg_visibilitySize"); - AV_UInt32ArraySamplePtr sampleVisibilityIds(userProps, "mvg_visibilityIds"); + FloatArraySamplePtr sampleFeatPos2d; + IFloatArrayProperty propFeatPos2d(userProps, "mvg_visibilityFeatPos"); + propFeatPos2d.get(sampleFeatPos2d); - FloatArraySamplePtr sampleFeatPos2d; - IFloatArrayProperty propFeatPos2d(userProps, "mvg_visibilityFeatPos"); - propFeatPos2d.get(sampleFeatPos2d); + if (positions->size() != sampleVisibilitySize.size()) + { + ALICEVISION_LOG_ERROR("Alembic Error: number of observations per 3D point should be identical to the number of 2D features.\n" + "# observations per 3D point: " + << sampleVisibilitySize.size() + << ".\n" + "# 3D points: " + << positions->size() << "."); + return false; + } + if (sampleVisibilityIds.size() != sampleFeatPos2d->size()) + { + ALICEVISION_LOG_ERROR("Alembic Error: visibility Ids and features 2D pos should have the same size.\n" + "# visibility Ids: " + << sampleVisibilityIds.size() + << ".\n" + "# features 2D pos: " + << sampleFeatPos2d->size() << "."); + return false; + } - if(positions->size() != sampleVisibilitySize.size()) - { - ALICEVISION_LOG_ERROR("Alembic Error: number of observations per 3D point should be identical to the number of 2D features.\n" - "# observations per 3D point: " << sampleVisibilitySize.size() << ".\n" - "# 3D points: " << positions->size() << "."); - return false; - } - if(sampleVisibilityIds.size() != sampleFeatPos2d->size()) - { - ALICEVISION_LOG_ERROR("Alembic Error: visibility Ids and features 2D pos should have the same size.\n" - "# visibility Ids: " << sampleVisibilityIds.size() << ".\n" - "# features 2D pos: " << sampleFeatPos2d->size() << "."); - return false; + std::size_t obsGlobal_i = 0; + for (std::size_t point3d_i = 0; point3d_i < positions->size(); ++point3d_i) + { + sfmData::Landmark& landmark = sfmdata.getLandmarks()[nbPointsInit + point3d_i]; + // Number of observation for this 3d point + const std::size_t visibilitySize = sampleVisibilitySize[point3d_i]; + + for (std::size_t obs_i = 0; obs_i < visibilitySize * 2; obs_i += 2, obsGlobal_i += 2) + { + const int viewID = sampleVisibilityIds[obsGlobal_i]; + const int featID = sampleVisibilityIds[obsGlobal_i + 1]; + sfmData::Observation& observations = landmark.observations[viewID]; + observations.id_feat = featID; + + const float posX = (*sampleFeatPos2d)[obsGlobal_i]; + const float posY = (*sampleFeatPos2d)[obsGlobal_i + 1]; + observations.x[0] = posX; + observations.x[1] = posY; + } + } } - std::size_t obsGlobal_i = 0; - for(std::size_t point3d_i = 0; - point3d_i < positions->size(); - ++point3d_i) + if (userProps && userProps.getPropertyHeader("mvg_visibilitySize") && userProps.getPropertyHeader("mvg_visibilityViewId") && + (flags_part & ESfMData::OBSERVATIONS || flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES)) { - sfmData::Landmark& landmark = sfmdata.getLandmarks()[nbPointsInit + point3d_i]; - // Number of observation for this 3d point - const std::size_t visibilitySize = sampleVisibilitySize[point3d_i]; + AV_UInt32ArraySamplePtr sampleVisibilitySize(userProps, "mvg_visibilitySize"); + AV_UInt32ArraySamplePtr sampleVisibilityViewId(userProps, "mvg_visibilityViewId"); - for(std::size_t obs_i = 0; - obs_i < visibilitySize*2; - obs_i+=2, obsGlobal_i+=2) - { + if (positions->size() != sampleVisibilitySize.size()) + { + ALICEVISION_LOG_ERROR("Alembic Error: number of observations per 3D point should be identical to the number of 2D features.\n" + "# observations per 3D point: " + << sampleVisibilitySize.size() + << ".\n" + "# 3D points: " + << positions->size() << "."); + return false; + } - const int viewID = sampleVisibilityIds[obsGlobal_i]; - const int featID = sampleVisibilityIds[obsGlobal_i+1]; - sfmData::Observation& observations = landmark.observations[viewID]; - observations.id_feat = featID; + AV_UInt32ArraySamplePtr sampleVisibilityFeatId; + FloatArraySamplePtr sampleVisibilityFeatPos; + FloatArraySamplePtr sampleVisibilityFeatScale; - const float posX = (*sampleFeatPos2d)[obsGlobal_i]; - const float posY = (*sampleFeatPos2d)[obsGlobal_i+1]; - observations.x[0] = posX; - observations.x[1] = posY; - } - } - } + if (userProps.getPropertyHeader("mvg_visibilityFeatId") && userProps.getPropertyHeader("mvg_visibilityFeatPos") && + flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES) + { + sampleVisibilityFeatId.read(userProps, "mvg_visibilityFeatId"); + + IFloatArrayProperty propVisibilityFeatPos(userProps, "mvg_visibilityFeatPos"); + propVisibilityFeatPos.get(sampleVisibilityFeatPos); + + if (userProps && userProps.getPropertyHeader("mvg_visibilityFeatScale")) + { + IFloatArrayProperty propVisibilityFeatScale(userProps, "mvg_visibilityFeatScale"); + propVisibilityFeatScale.get(sampleVisibilityFeatScale); + } + + if (sampleVisibilityViewId.size() != sampleVisibilityFeatId.size() || + 2 * sampleVisibilityViewId.size() != sampleVisibilityFeatPos->size()) + { + ALICEVISION_LOG_ERROR("Alembic Error: visibility Ids and features id / 2D pos should have the same size.\n" + "# view Ids: " + << sampleVisibilityViewId.size() + << ".\n" + "# features id: " + << sampleVisibilityFeatId.size() + << ".\n" + "# features 2D pos: " + << sampleVisibilityFeatPos->size() << "."); + return false; + } + } + else + { + ALICEVISION_LOG_WARNING("Alembic LOAD: NO OBSERVATIONS_WITH_FEATURES: " + << ", mvg_visibilityFeatId: " << long(userProps.getPropertyHeader("mvg_visibilityFeatId")) + << ", mvg_visibilityFeatPos: " << long(userProps.getPropertyHeader("mvg_visibilityFeatPos")) + << ", OBSERVATIONS_WITH_FEATURES flag: " << bool(flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES)); + } - if(userProps && - userProps.getPropertyHeader("mvg_visibilitySize") && - userProps.getPropertyHeader("mvg_visibilityViewId") && - (flags_part & ESfMData::OBSERVATIONS || flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES)) - { - AV_UInt32ArraySamplePtr sampleVisibilitySize(userProps, "mvg_visibilitySize"); - AV_UInt32ArraySamplePtr sampleVisibilityViewId(userProps, "mvg_visibilityViewId"); + const bool hasFeatures = bool(sampleVisibilityFeatId) && (sampleVisibilityFeatId.size() > 0); - if(positions->size() != sampleVisibilitySize.size()) - { - ALICEVISION_LOG_ERROR("Alembic Error: number of observations per 3D point should be identical to the number of 2D features.\n" - "# observations per 3D point: " << sampleVisibilitySize.size() << ".\n" - "# 3D points: " << positions->size() << "."); - return false; + std::size_t obsGlobalIndex = 0; + for (std::size_t point3d_i = 0; point3d_i < positions->size(); ++point3d_i) + { + const int landmarkId = nbPointsInit + point3d_i; + sfmData::Landmark& landmark = sfmdata.getLandmarks()[landmarkId]; + + // Number of observation for this 3d point + const std::size_t visibilitySize = sampleVisibilitySize[point3d_i]; + + for (std::size_t obs_i = 0; obs_i < visibilitySize; ++obs_i, ++obsGlobalIndex) + { + const int viewId = sampleVisibilityViewId[obsGlobalIndex]; + + if (hasFeatures) + { + const std::size_t featId = sampleVisibilityFeatId[obsGlobalIndex]; + sfmData::Observation& observation = landmark.observations[viewId]; + observation.id_feat = featId; + + const float posX = (*sampleVisibilityFeatPos)[2 * obsGlobalIndex]; + const float posY = (*sampleVisibilityFeatPos)[2 * obsGlobalIndex + 1]; + observation.x[0] = posX; + observation.x[1] = posY; + + // for compatibility with previous version without scale + if (sampleVisibilityFeatScale) + { + observation.scale = (*sampleVisibilityFeatScale)[obsGlobalIndex]; + } + } + else + { + landmark.observations[viewId] = sfmData::Observation(); + } + } + } } - AV_UInt32ArraySamplePtr sampleVisibilityFeatId; - FloatArraySamplePtr sampleVisibilityFeatPos; - FloatArraySamplePtr sampleVisibilityFeatScale; + return true; +} + +bool readCamera(const Version& abcVersion, + const ICamera& camera, + const M44d& mat, + sfmData::SfMData& sfmData, + ESfMData flagsPart, + const index_t sampleFrame = 0, + bool isReconstructed = true) +{ + using namespace aliceVision::geometry; + using namespace aliceVision::camera; - if(userProps.getPropertyHeader("mvg_visibilityFeatId") && - userProps.getPropertyHeader("mvg_visibilityFeatPos") && - flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES) + ICameraSchema cs = camera.getSchema(); + CameraSample camSample; + if (sampleFrame == 0) + camSample = cs.getValue(); + else + camSample = cs.getValue(ISampleSelector(sampleFrame)); + + // Check if we have an associated image plane + ICompoundProperty userProps = getAbcUserProperties(cs); + std::string imagePath; + std::vector sensorSize_pix = {0, 0}; + std::vector sensorSize_mm = {0, 0}; + std::string mvg_intrinsicType = EINTRINSIC_enumToString(EINTRINSIC::PINHOLE_CAMERA); + std::string mvg_intrinsicInitializationMode = EInitMode_enumToString(EInitMode::NONE); + std::string mvg_intrinsicDistortionInitializationMode = EInitMode_enumToString(EInitMode::NONE); + std::vector mvg_intrinsicParams; + std::vector mvg_ancestorsParams; + Vec2 initialFocalLengthPix = {-1, -1}; + double fisheyeCenterX = 0.0; + double fisheyeCenterY = 0.0; + double fisheyeRadius = 1.0; + std::vector rawMetadata; + IndexT viewId = sfmData.getViews().size(); + IndexT poseId = sfmData.getViews().size(); + IndexT intrinsicId = sfmData.getIntrinsics().size(); + IndexT rigId = UndefinedIndexT; + IndexT subPoseId = UndefinedIndexT; + IndexT frameId = UndefinedIndexT; + IndexT resectionId = UndefinedIndexT; + bool intrinsicLocked = false; + bool poseLocked = false; + bool poseIndependant = true; + bool lockRatio = true; + std::vector distortionParams; + std::vector undistortionParams; + Vec2 undistortionOffset = {0, 0}; + + if (userProps) { - sampleVisibilityFeatId.read(userProps, "mvg_visibilityFeatId"); + if ((flagsPart & ESfMData::VIEWS) || (flagsPart & ESfMData::INTRINSICS) || (flagsPart & ESfMData::EXTRINSICS)) + { + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_imagePath")) + imagePath = getAbcProp(userProps, *propHeader, "mvg_imagePath", sampleFrame); + + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_viewId")) + { + viewId = getAbcProp_uint(userProps, *propHeader, "mvg_viewId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_poseId")) + { + poseId = getAbcProp_uint(userProps, *propHeader, "mvg_poseId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_intrinsicId")) + { + intrinsicId = getAbcProp_uint(userProps, *propHeader, "mvg_intrinsicId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_rigId")) + { + rigId = getAbcProp_uint(userProps, *propHeader, "mvg_rigId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_subPoseId")) + { + subPoseId = getAbcProp_uint(userProps, *propHeader, "mvg_subPoseId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_frameId")) + { + frameId = getAbcProp_uint(userProps, *propHeader, "mvg_frameId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_resectionId")) + { + resectionId = getAbcProp_uint(userProps, *propHeader, "mvg_resectionId", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_intrinsicLocked")) + { + intrinsicLocked = getAbcProp(userProps, *propHeader, "mvg_intrinsicLocked", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_intrinsicPixelRatioLocked")) + { + lockRatio = getAbcProp(userProps, *propHeader, "mvg_intrinsicPixelRatioLocked", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_poseLocked")) + { + poseLocked = getAbcProp(userProps, *propHeader, "mvg_poseLocked", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_poseIndependant")) + { + poseIndependant = getAbcProp(userProps, *propHeader, "mvg_poseIndependant", sampleFrame); + } + if (userProps.getPropertyHeader("mvg_metadata")) + { + getAbcArrayProp(userProps, "mvg_metadata", sampleFrame, rawMetadata); + if (rawMetadata.size() % 2 != 0) + { + ALICEVISION_THROW_ERROR("[Alembic] 'metadata' property is supposed to be key/values. Number of values is " + + std::to_string(rawMetadata.size()) + "."); + } + } + if (userProps.getPropertyHeader("mvg_sensorSizePix")) + { + getAbcArrayProp_uint(userProps, "mvg_sensorSizePix", sampleFrame, sensorSize_pix); + if (sensorSize_pix.size() != 2) + { + ALICEVISION_THROW_ERROR("[Alembic] 'sensorSizePix' property is supposed to be 2 values. Number of values is " + + std::to_string(sensorSize_pix.size()) + "."); + } + } + if (userProps.getPropertyHeader("mvg_sensorSizeMm")) + { + getAbcArrayProp(userProps, "mvg_sensorSizeMm", sampleFrame, sensorSize_mm); + if (sensorSize_mm.size() != 2) + { + ALICEVISION_THROW_ERROR("[Alembic] 'sensorSizeMm' property is supposed to be 2 values. Number of values is " + + std::to_string(sensorSize_mm.size()) + "."); + } + } + else + { + sensorSize_mm = {24.0, 36.0}; + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_intrinsicType")) + { + mvg_intrinsicType = getAbcProp(userProps, *propHeader, "mvg_intrinsicType", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_intrinsicInitializationMode")) + { + mvg_intrinsicInitializationMode = + getAbcProp(userProps, *propHeader, "mvg_intrinsicInitializationMode", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_intrinsicDistortionInitializationMode")) + { + mvg_intrinsicDistortionInitializationMode = + getAbcProp(userProps, *propHeader, "mvg_intrinsicDistortionInitializationMode", sampleFrame); + } + // For compatibility with versions < 1.2 (value was in pixels) + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_initialFocalLengthPix")) + { + initialFocalLengthPix(0) = + getAbcProp(userProps, *propHeader, "mvg_initialFocalLengthPix", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_initialFocalLength")) + { + initialFocalLengthPix(0) = (sensorSize_pix.at(0) / sensorSize_mm[0]) * + getAbcProp(userProps, *propHeader, "mvg_initialFocalLength", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_fisheyeCircleCenterX")) + { + fisheyeCenterX = getAbcProp(userProps, *propHeader, "mvg_fisheyeCircleCenterX", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_fisheyeCircleCenterY")) + { + fisheyeCenterY = getAbcProp(userProps, *propHeader, "mvg_fisheyeCircleCenterY", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_fisheyeCircleRadius")) + { + fisheyeRadius = getAbcProp(userProps, *propHeader, "mvg_fisheyeCircleRadius", sampleFrame); + } + if (userProps.getPropertyHeader("mvg_intrinsicParams")) + { + Alembic::Abc::IDoubleArrayProperty prop(userProps, "mvg_intrinsicParams"); + Alembic::Abc::IDoubleArrayProperty::sample_ptr_type sample; + prop.get(sample, ISampleSelector(sampleFrame)); + mvg_intrinsicParams.assign(sample->get(), sample->get() + sample->size()); + } + if (userProps.getPropertyHeader("mvg_ancestorsParams")) + { + Alembic::Abc::IUInt32ArrayProperty prop(userProps, "mvg_ancestorsParams"); + Alembic::Abc::IUInt32ArrayProperty::sample_ptr_type sample; + prop.get(sample, ISampleSelector(sampleFrame)); + mvg_ancestorsParams.assign(sample->get(), sample->get() + sample->size()); + } + if (userProps.getPropertyHeader("mvg_distortionParams")) + { + // Distortion parameters + Alembic::Abc::IDoubleArrayProperty prop(userProps, "mvg_distortionParams"); + Alembic::Abc::IDoubleArrayProperty::sample_ptr_type sample; + prop.get(sample, ISampleSelector(sampleFrame)); + distortionParams.assign(sample->get(), sample->get() + sample->size()); + } + if (userProps.getPropertyHeader("mvg_undistortionParams")) + { + // Undistortion parameters + Alembic::Abc::IDoubleArrayProperty prop(userProps, "mvg_undistortionParams"); + Alembic::Abc::IDoubleArrayProperty::sample_ptr_type sample; + prop.get(sample, ISampleSelector(sampleFrame)); + undistortionParams.assign(sample->get(), sample->get() + sample->size()); + // Undistortion offset + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_undistortionOffsetX")) + { + undistortionOffset(0) = getAbcProp(userProps, *propHeader, "mvg_undistortionOffsetX", sampleFrame); + } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_undistortionOffsetY")) + { + undistortionOffset(1) = getAbcProp(userProps, *propHeader, "mvg_undistortionOffsetY", sampleFrame); + } + } + } + } - IFloatArrayProperty propVisibilityFeatPos(userProps, "mvg_visibilityFeatPos"); - propVisibilityFeatPos.get(sampleVisibilityFeatPos); + if (flagsPart & ESfMData::INTRINSICS) + { + // get known values from alembic + // const float haperture_cm = camSample.getHorizontalAperture(); + // const float vaperture_cm = camSample.getVerticalAperture(); + // compute other needed values + // const float sensorWidth_mm = std::max(vaperture_cm, haperture_cm) * 10.0; + // const float mm2pix = sensorSize_pix.at(0) / sensorWidth_mm; + // imgWidth = haperture_cm * 10.0 * mm2pix; + // imgHeight = vaperture_cm * 10.0 * mm2pix; + + // create intrinsic parameters object + std::shared_ptr intrinsic = createIntrinsic( + /*intrinsic type*/ EINTRINSIC_stringToEnum(mvg_intrinsicType), + /*width*/ sensorSize_pix.at(0), + /*height*/ sensorSize_pix.at(1)); + + intrinsic->setSensorWidth(sensorSize_mm.at(0)); + intrinsic->setSensorHeight(sensorSize_mm.at(1)); + intrinsic->importFromParams(mvg_intrinsicParams, abcVersion); + intrinsic->setInitializationMode(EInitMode_stringToEnum(mvg_intrinsicInitializationMode)); + intrinsic->setDistortionInitializationMode(EInitMode_stringToEnum(mvg_intrinsicDistortionInitializationMode)); + + std::shared_ptr intrinsicCasted = std::dynamic_pointer_cast(intrinsic); + if (intrinsicCasted) + { + // fy_pix = fx_pix * fy/fx + initialFocalLengthPix(1) = + (initialFocalLengthPix(0) > 0) ? initialFocalLengthPix(0) * mvg_intrinsicParams[1] / mvg_intrinsicParams[0] : -1; + intrinsicCasted->setInitialScale(initialFocalLengthPix); + intrinsicCasted->setRatioLocked(lockRatio); + std::shared_ptr distortion = intrinsicCasted->getDistortion(); + if (distortion) + { + distortion->setParameters(distortionParams); + } + std::shared_ptr undistortion = intrinsicCasted->getUndistortion(); + if (undistortion) + { + undistortion->setParameters(undistortionParams); + undistortion->setOffset(undistortionOffset); + } + } - if(userProps && userProps.getPropertyHeader("mvg_visibilityFeatScale")) - { - IFloatArrayProperty propVisibilityFeatScale(userProps, "mvg_visibilityFeatScale"); - propVisibilityFeatScale.get(sampleVisibilityFeatScale); - } + std::shared_ptr casted = std::dynamic_pointer_cast(intrinsic); + if (casted) + { + casted->setCircleCenterX(fisheyeCenterX); + casted->setCircleCenterY(fisheyeCenterY); + casted->setCircleRadius(fisheyeRadius); + } - if(sampleVisibilityViewId.size() != sampleVisibilityFeatId.size() || - 2*sampleVisibilityViewId.size() != sampleVisibilityFeatPos->size()) - { - ALICEVISION_LOG_ERROR("Alembic Error: visibility Ids and features id / 2D pos should have the same size.\n" - "# view Ids: " << sampleVisibilityViewId.size() << ".\n" - "# features id: " << sampleVisibilityFeatId.size() << ".\n" - "# features 2D pos: " << sampleVisibilityFeatPos->size() << "."); - return false; - } + if (intrinsicLocked) + intrinsic->lock(); + else + intrinsic->unlock(); - } - else { - ALICEVISION_LOG_WARNING("Alembic LOAD: NO OBSERVATIONS_WITH_FEATURES: " - << ", mvg_visibilityFeatId: " << long(userProps.getPropertyHeader("mvg_visibilityFeatId")) - << ", mvg_visibilityFeatPos: " << long(userProps.getPropertyHeader("mvg_visibilityFeatPos")) - << ", OBSERVATIONS_WITH_FEATURES flag: " << bool(flags_part & ESfMData::OBSERVATIONS_WITH_FEATURES) - ); + sfmData.getIntrinsics().emplace(intrinsicId, intrinsic); } - const bool hasFeatures = bool(sampleVisibilityFeatId) && (sampleVisibilityFeatId.size() > 0); - - std::size_t obsGlobalIndex = 0; - for(std::size_t point3d_i = 0; point3d_i < positions->size(); ++point3d_i) + // add imported data to the SfMData container TODO use UID + // this view is incomplete if no flag VIEWS + std::shared_ptr view = + std::make_shared(imagePath, viewId, intrinsicId, poseId, sensorSize_pix.at(0), sensorSize_pix.at(1), rigId, subPoseId); + if (flagsPart & ESfMData::VIEWS) { - const int landmarkId = nbPointsInit + point3d_i; - sfmData::Landmark& landmark = sfmdata.getLandmarks()[landmarkId]; + view->setResectionId(resectionId); + view->setFrameId(frameId); + view->setIndependantPose(poseIndependant); - // Number of observation for this 3d point - const std::size_t visibilitySize = sampleVisibilitySize[point3d_i]; + // set metadata + for (std::size_t i = 0; i < rawMetadata.size(); i += 2) + { + view->getImage().addMetadata(rawMetadata.at(i), rawMetadata.at(i + 1)); + } - for(std::size_t obs_i = 0; obs_i < visibilitySize; ++obs_i, ++obsGlobalIndex) - { - const int viewId = sampleVisibilityViewId[obsGlobalIndex]; + for (IndexT val : mvg_ancestorsParams) + { + view->addAncestor(val); + } - if(hasFeatures) + sfmData.getViews().emplace(viewId, view); + } + + if ((flagsPart & ESfMData::EXTRINSICS) && isReconstructed) + { + Mat4 T = Mat4::Identity(); + for (int i = 0; i < 4; i++) { - const std::size_t featId = sampleVisibilityFeatId[obsGlobalIndex]; - sfmData::Observation& observation = landmark.observations[viewId]; - observation.id_feat = featId; + for (int j = 0; j < 4; j++) + { + T(i, j) = mat[j][i]; + } + } - const float posX = (*sampleVisibilityFeatPos)[2 * obsGlobalIndex]; - const float posY = (*sampleVisibilityFeatPos)[2 * obsGlobalIndex + 1]; - observation.x[0] = posX; - observation.x[1] = posY; + // convert from computer graphics convention (opengl-like) to computer vision + Mat4 M = Mat4::Identity(); + M(1, 1) = -1.0; + M(2, 2) = -1.0; - // for compatibility with previous version without scale - if(sampleVisibilityFeatScale) - { - observation.scale = (*sampleVisibilityFeatScale)[obsGlobalIndex]; - } + Mat4 T2; + if (abcVersion < Version(1, 2, 3)) + { + T2 = (T * M).inverse(); } else { - landmark.observations[viewId] = sfmData::Observation(); + T2 = (M * T * M).inverse(); } - } + Pose3 pose(T2); + + if (view->isPartOfRig() && !view->isPoseIndependant()) + { + sfmData::Rig& rig = sfmData.getRigs()[view->getRigId()]; + std::vector& sp = rig.getSubPoses(); + if (view->getSubPoseId() >= sp.size()) + sp.resize(view->getSubPoseId() + 1); + sfmData::RigSubPose& subPose = rig.getSubPose(view->getSubPoseId()); + if (subPose.status == sfmData::ERigSubPoseStatus::UNINITIALIZED) + { + subPose.status = sfmData::ERigSubPoseStatus::ESTIMATED; + subPose.pose = pose; + } + } + else + { + sfmData.setPose(*view, sfmData::CameraPose(pose, poseLocked)); + } } - } - return true; + return true; } -bool readCamera(const Version & abcVersion, const ICamera& camera, const M44d& mat, sfmData::SfMData& sfmData, - ESfMData flagsPart, const index_t sampleFrame = 0, bool isReconstructed = true) +bool readXform(const Version& abcVersion, IXform& xform, M44d& mat, sfmData::SfMData& sfmData, ESfMData flagsPart, bool isReconstructed = true) { - using namespace aliceVision::geometry; - using namespace aliceVision::camera; - - ICameraSchema cs = camera.getSchema(); - CameraSample camSample; - if(sampleFrame == 0) - camSample = cs.getValue(); - else - camSample = cs.getValue(ISampleSelector(sampleFrame)); - - // Check if we have an associated image plane - ICompoundProperty userProps = getAbcUserProperties(cs); - std::string imagePath; - std::vector sensorSize_pix = {0, 0}; - std::vector sensorSize_mm = {0, 0}; - std::string mvg_intrinsicType = EINTRINSIC_enumToString(EINTRINSIC::PINHOLE_CAMERA); - std::string mvg_intrinsicInitializationMode = EInitMode_enumToString(EInitMode::NONE); - std::string mvg_intrinsicDistortionInitializationMode = EInitMode_enumToString(EInitMode::NONE); - std::vector mvg_intrinsicParams; - std::vector mvg_ancestorsParams; - Vec2 initialFocalLengthPix = {-1, -1}; - double fisheyeCenterX = 0.0; - double fisheyeCenterY = 0.0; - double fisheyeRadius = 1.0; - std::vector rawMetadata; - IndexT viewId = sfmData.getViews().size(); - IndexT poseId = sfmData.getViews().size(); - IndexT intrinsicId = sfmData.getIntrinsics().size(); - IndexT rigId = UndefinedIndexT; - IndexT subPoseId = UndefinedIndexT; - IndexT frameId = UndefinedIndexT; - IndexT resectionId = UndefinedIndexT; - bool intrinsicLocked = false; - bool poseLocked = false; - bool poseIndependant = true; - bool lockRatio = true; - std::vector distortionParams; - std::vector undistortionParams; - Vec2 undistortionOffset = {0, 0}; - - if(userProps) - { - if((flagsPart & ESfMData::VIEWS) || (flagsPart & ESfMData::INTRINSICS) || (flagsPart & ESfMData::EXTRINSICS)) - { - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_imagePath")) - imagePath = getAbcProp(userProps, *propHeader, "mvg_imagePath", sampleFrame); - - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_viewId")) - { - viewId = getAbcProp_uint(userProps, *propHeader, "mvg_viewId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_poseId")) - { - poseId = getAbcProp_uint(userProps, *propHeader, "mvg_poseId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_intrinsicId")) - { - intrinsicId = getAbcProp_uint(userProps, *propHeader, "mvg_intrinsicId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_rigId")) - { - rigId = getAbcProp_uint(userProps, *propHeader, "mvg_rigId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_subPoseId")) - { - subPoseId = getAbcProp_uint(userProps, *propHeader, "mvg_subPoseId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_frameId")) - { - frameId = getAbcProp_uint(userProps, *propHeader, "mvg_frameId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_resectionId")) - { - resectionId = getAbcProp_uint(userProps, *propHeader, "mvg_resectionId", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_intrinsicLocked")) - { - intrinsicLocked = getAbcProp(userProps, *propHeader, "mvg_intrinsicLocked", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_intrinsicPixelRatioLocked")) - { - lockRatio = getAbcProp(userProps, *propHeader, "mvg_intrinsicPixelRatioLocked", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_poseLocked")) - { - poseLocked = getAbcProp(userProps, *propHeader, "mvg_poseLocked", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_poseIndependant")) - { - poseIndependant = getAbcProp(userProps, *propHeader, "mvg_poseIndependant", sampleFrame); - } - if(userProps.getPropertyHeader("mvg_metadata")) - { - getAbcArrayProp(userProps, "mvg_metadata", sampleFrame, rawMetadata); - if(rawMetadata.size() % 2 != 0) - { - ALICEVISION_THROW_ERROR("[Alembic] 'metadata' property is supposed to be key/values. Number of values is " + std::to_string(rawMetadata.size()) + "."); - } - } - if(userProps.getPropertyHeader("mvg_sensorSizePix")) - { - getAbcArrayProp_uint(userProps, "mvg_sensorSizePix", sampleFrame, sensorSize_pix); - if(sensorSize_pix.size() != 2) - { - ALICEVISION_THROW_ERROR("[Alembic] 'sensorSizePix' property is supposed to be 2 values. Number of values is " + std::to_string(sensorSize_pix.size()) + "."); - } - } - if(userProps.getPropertyHeader("mvg_sensorSizeMm")) - { - getAbcArrayProp(userProps, "mvg_sensorSizeMm", sampleFrame, sensorSize_mm); - if(sensorSize_mm.size() != 2) - { - ALICEVISION_THROW_ERROR("[Alembic] 'sensorSizeMm' property is supposed to be 2 values. Number of values is " + std::to_string(sensorSize_mm.size()) + "."); - } - } - else - { - sensorSize_mm = {24.0, 36.0}; - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_intrinsicType")) - { - mvg_intrinsicType = getAbcProp(userProps, *propHeader, "mvg_intrinsicType", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_intrinsicInitializationMode")) - { - mvg_intrinsicInitializationMode = getAbcProp(userProps, *propHeader, "mvg_intrinsicInitializationMode", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_intrinsicDistortionInitializationMode")) - { - mvg_intrinsicDistortionInitializationMode = getAbcProp(userProps, *propHeader, "mvg_intrinsicDistortionInitializationMode", sampleFrame); - } - // For compatibility with versions < 1.2 (value was in pixels) - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_initialFocalLengthPix")) - { - initialFocalLengthPix(0) = getAbcProp(userProps, *propHeader, "mvg_initialFocalLengthPix", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_initialFocalLength")) - { - initialFocalLengthPix(0) = (sensorSize_pix.at(0) / sensorSize_mm[0]) * getAbcProp(userProps, *propHeader, "mvg_initialFocalLength", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_fisheyeCircleCenterX")) - { - fisheyeCenterX = getAbcProp(userProps, *propHeader, "mvg_fisheyeCircleCenterX", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_fisheyeCircleCenterY")) - { - fisheyeCenterY = getAbcProp(userProps, *propHeader, "mvg_fisheyeCircleCenterY", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_fisheyeCircleRadius")) - { - fisheyeRadius = getAbcProp(userProps, *propHeader, "mvg_fisheyeCircleRadius", sampleFrame); - } - if(userProps.getPropertyHeader("mvg_intrinsicParams")) - { - Alembic::Abc::IDoubleArrayProperty prop(userProps, "mvg_intrinsicParams"); - Alembic::Abc::IDoubleArrayProperty::sample_ptr_type sample; - prop.get(sample, ISampleSelector(sampleFrame)); - mvg_intrinsicParams.assign(sample->get(), sample->get()+sample->size()); - } - if(userProps.getPropertyHeader("mvg_ancestorsParams")) - { - Alembic::Abc::IUInt32ArrayProperty prop(userProps, "mvg_ancestorsParams"); - Alembic::Abc::IUInt32ArrayProperty::sample_ptr_type sample; - prop.get(sample, ISampleSelector(sampleFrame)); - mvg_ancestorsParams.assign(sample->get(), sample->get()+sample->size()); - } - if(userProps.getPropertyHeader("mvg_distortionParams")) - { - // Distortion parameters - Alembic::Abc::IDoubleArrayProperty prop(userProps, "mvg_distortionParams"); - Alembic::Abc::IDoubleArrayProperty::sample_ptr_type sample; - prop.get(sample, ISampleSelector(sampleFrame)); - distortionParams.assign(sample->get(), sample->get()+sample->size()); - } - if(userProps.getPropertyHeader("mvg_undistortionParams")) - { - // Undistortion parameters - Alembic::Abc::IDoubleArrayProperty prop(userProps, "mvg_undistortionParams"); - Alembic::Abc::IDoubleArrayProperty::sample_ptr_type sample; - prop.get(sample, ISampleSelector(sampleFrame)); - undistortionParams.assign(sample->get(), sample->get()+sample->size()); - // Undistortion offset - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_undistortionOffsetX")) - { - undistortionOffset(0) = getAbcProp(userProps, *propHeader, "mvg_undistortionOffsetX", sampleFrame); - } - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_undistortionOffsetY")) - { - undistortionOffset(1) = getAbcProp(userProps, *propHeader, "mvg_undistortionOffsetY", sampleFrame); - } - } - } - } - - if(flagsPart & ESfMData::INTRINSICS) - { - // get known values from alembic - // const float haperture_cm = camSample.getHorizontalAperture(); - // const float vaperture_cm = camSample.getVerticalAperture(); - // compute other needed values - // const float sensorWidth_mm = std::max(vaperture_cm, haperture_cm) * 10.0; - // const float mm2pix = sensorSize_pix.at(0) / sensorWidth_mm; - // imgWidth = haperture_cm * 10.0 * mm2pix; - // imgHeight = vaperture_cm * 10.0 * mm2pix; - - // create intrinsic parameters object - std::shared_ptr intrinsic = createIntrinsic( - /*intrinsic type*/ EINTRINSIC_stringToEnum(mvg_intrinsicType), - /*width*/ sensorSize_pix.at(0), - /*height*/ sensorSize_pix.at(1)); - - intrinsic->setSensorWidth(sensorSize_mm.at(0)); - intrinsic->setSensorHeight(sensorSize_mm.at(1)); - intrinsic->importFromParams(mvg_intrinsicParams, abcVersion); - intrinsic->setInitializationMode(EInitMode_stringToEnum(mvg_intrinsicInitializationMode)); - intrinsic->setDistortionInitializationMode(EInitMode_stringToEnum(mvg_intrinsicDistortionInitializationMode)); - - std::shared_ptr intrinsicCasted = std::dynamic_pointer_cast(intrinsic); - if (intrinsicCasted) - { - // fy_pix = fx_pix * fy/fx - initialFocalLengthPix(1) = (initialFocalLengthPix(0) > 0)? initialFocalLengthPix(0) * mvg_intrinsicParams[1] / mvg_intrinsicParams[0] : -1; - intrinsicCasted->setInitialScale(initialFocalLengthPix); - intrinsicCasted->setRatioLocked(lockRatio); - std::shared_ptr distortion = intrinsicCasted->getDistortion(); - if (distortion) - { - distortion->setParameters(distortionParams); - } - std::shared_ptr undistortion = intrinsicCasted->getUndistortion(); - if (undistortion) - { - undistortion->setParameters(undistortionParams); - undistortion->setOffset(undistortionOffset); - } - } - - std::shared_ptr casted = std::dynamic_pointer_cast(intrinsic); - if (casted) - { - casted->setCircleCenterX(fisheyeCenterX); - casted->setCircleCenterY(fisheyeCenterY); - casted->setCircleRadius(fisheyeRadius); - } - - if(intrinsicLocked) - intrinsic->lock(); - else - intrinsic->unlock(); + using namespace aliceVision::geometry; + using namespace aliceVision::camera; - sfmData.getIntrinsics().emplace(intrinsicId, intrinsic); - } + IXformSchema schema = xform.getSchema(); + XformSample xsample; - // add imported data to the SfMData container TODO use UID - // this view is incomplete if no flag VIEWS - std::shared_ptr view = std::make_shared(imagePath, - viewId, - intrinsicId, - poseId, - sensorSize_pix.at(0), - sensorSize_pix.at(1), - rigId, - subPoseId); - if(flagsPart & ESfMData::VIEWS) - { - view->setResectionId(resectionId); - view->setFrameId(frameId); - view->setIndependantPose(poseIndependant); + schema.get(xsample); - // set metadata - for(std::size_t i = 0; i < rawMetadata.size(); i+=2) + // If we have an animated camera we handle it with the xform here + if (xform.getSchema().getNumSamples() != 1) { - view->getImage().addMetadata(rawMetadata.at(i), rawMetadata.at(i + 1)); + ALICEVISION_LOG_DEBUG(xform.getSchema().getNumSamples() << " samples found in this animated xform."); + for (index_t frame = 0; frame < xform.getSchema().getNumSamples(); ++frame) + { + xform.getSchema().get(xsample, ISampleSelector(frame)); + readCamera(abcVersion, ICamera(xform.getChild(0), kWrapExisting), mat * xsample.getMatrix(), sfmData, flagsPart, frame, isReconstructed); + } + return true; } - for (IndexT val : mvg_ancestorsParams) - { - view->addAncestor(val); - } + mat *= xsample.getMatrix(); - sfmData.getViews().emplace(viewId, view); - } + ICompoundProperty userProps = getAbcUserProperties(schema); - if((flagsPart & ESfMData::EXTRINSICS) && - isReconstructed) - { - Mat4 T = Mat4::Identity(); - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - T(i, j) = mat[j][i]; - } - } - - // convert from computer graphics convention (opengl-like) to computer vision - Mat4 M = Mat4::Identity(); - M(1, 1) = -1.0; - M(2, 2) = -1.0; + // Check if it is a rig node + IndexT rigId = UndefinedIndexT; + IndexT poseId = UndefinedIndexT; + std::size_t nbSubPoses = 0; + bool rigPoseLocked = false; - Mat4 T2; - if (abcVersion < Version(1, 2, 3)) + if (userProps) { - T2 = (T * M).inverse(); - } - else - { - T2 = (M * T * M).inverse(); - } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_rigId")) + { + try + { + rigId = getAbcProp(userProps, *propHeader, "mvg_rigId", 0); + } + catch (Alembic::Util::Exception&) + { + rigId = getAbcProp(userProps, *propHeader, "mvg_rigId", 0); + } + } + + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_poseId")) + { + try + { + poseId = getAbcProp(userProps, *propHeader, "mvg_poseId", 0); + } + catch (Alembic::Util::Exception&) + { + poseId = getAbcProp(userProps, *propHeader, "mvg_poseId", 0); + } + } - Pose3 pose(T2); + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_nbSubPoses")) + { + try + { + nbSubPoses = getAbcProp(userProps, *propHeader, "mvg_nbSubPoses", 0); + } + catch (Alembic::Util::Exception&) + { + nbSubPoses = getAbcProp(userProps, *propHeader, "mvg_nbSubPoses", 0); + } + } - if(view->isPartOfRig() && !view->isPoseIndependant()) - { - sfmData::Rig& rig = sfmData.getRigs()[view->getRigId()]; - std::vector& sp = rig.getSubPoses(); - if(view->getSubPoseId() >= sp.size()) - sp.resize(view->getSubPoseId()+1); - sfmData::RigSubPose& subPose = rig.getSubPose(view->getSubPoseId()); - if(subPose.status == sfmData::ERigSubPoseStatus::UNINITIALIZED) - { - subPose.status = sfmData::ERigSubPoseStatus::ESTIMATED; - subPose.pose = pose; - } + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_rigPoseLocked")) + { + rigPoseLocked = getAbcProp(userProps, *propHeader, "mvg_rigPoseLocked", 0); + } } - else + + if ((rigId == UndefinedIndexT) && (poseId == UndefinedIndexT)) { - sfmData.setPose(*view, sfmData::CameraPose(pose, poseLocked)); + return true; // not a rig } - } - return true; -} + if ((flagsPart & ESfMData::EXTRINSICS) && isReconstructed) + { + Mat4 T = Mat4::Identity(); + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + T(i, j) = mat[j][i]; + } + } -bool readXform(const Version & abcVersion, IXform& xform, M44d& mat, sfmData::SfMData& sfmData, - ESfMData flagsPart, bool isReconstructed = true) -{ - using namespace aliceVision::geometry; - using namespace aliceVision::camera; + Mat4 M = Mat4::Identity(); + M(1, 1) = -1.0; + M(2, 2) = -1.0; - IXformSchema schema = xform.getSchema(); - XformSample xsample; + Mat4 T2; + if (abcVersion < Version(1, 2, 3)) + { + T2 = T.inverse(); + } + else + { + T2 = (M * T * M).inverse(); + } - schema.get(xsample); + Pose3 pose(T2); - // If we have an animated camera we handle it with the xform here - if(xform.getSchema().getNumSamples() != 1) - { - ALICEVISION_LOG_DEBUG(xform.getSchema().getNumSamples() << " samples found in this animated xform."); - for(index_t frame = 0; frame < xform.getSchema().getNumSamples(); ++frame) - { - xform.getSchema().get(xsample, ISampleSelector(frame)); - readCamera(abcVersion, ICamera(xform.getChild(0), kWrapExisting), mat * xsample.getMatrix(), sfmData, flagsPart, frame, isReconstructed); + if (sfmData.getPoses().find(poseId) == sfmData.getPoses().end()) + { + sfmData.getPoses().emplace(poseId, sfmData::CameraPose(pose, rigPoseLocked)); + } } + + if ((rigId != UndefinedIndexT) && sfmData.getRigs().find(rigId) == sfmData.getRigs().end()) + sfmData.getRigs().emplace(rigId, sfmData::Rig(nbSubPoses)); + + mat.makeIdentity(); return true; - } - - mat *= xsample.getMatrix(); - - ICompoundProperty userProps = getAbcUserProperties(schema); - - // Check if it is a rig node - IndexT rigId = UndefinedIndexT; - IndexT poseId = UndefinedIndexT; - std::size_t nbSubPoses = 0; - bool rigPoseLocked = false; - - if(userProps) - { - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_rigId")) - { - try - { - rigId = getAbcProp(userProps, *propHeader, "mvg_rigId", 0); - } - catch(Alembic::Util::Exception&) - { - rigId = getAbcProp(userProps, *propHeader, "mvg_rigId", 0); - } - } - - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_poseId")) - { - try - { - poseId = getAbcProp(userProps, *propHeader, "mvg_poseId", 0); - } - catch(Alembic::Util::Exception&) - { - poseId = getAbcProp(userProps, *propHeader, "mvg_poseId", 0); - } - } - - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_nbSubPoses")) - { - try - { - nbSubPoses = getAbcProp(userProps, *propHeader, "mvg_nbSubPoses", 0); - } - catch(Alembic::Util::Exception&) - { - nbSubPoses = getAbcProp(userProps, *propHeader, "mvg_nbSubPoses", 0); - } - } - - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_rigPoseLocked")) - { - rigPoseLocked = getAbcProp(userProps, *propHeader, "mvg_rigPoseLocked", 0); - } - } - - if((rigId == UndefinedIndexT) && (poseId == UndefinedIndexT)) - { - return true; //not a rig - } - - if((flagsPart & ESfMData::EXTRINSICS) && isReconstructed) - { - Mat4 T = Mat4::Identity(); - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - T(i, j) = mat[j][i]; - } - } - - - Mat4 M = Mat4::Identity(); - M(1, 1) = -1.0; - M(2, 2) = -1.0; - - Mat4 T2; - if (abcVersion < Version(1, 2, 3)) - { - T2 = T.inverse(); - } - else - { - T2 = (M * T * M).inverse(); - } - - Pose3 pose(T2); - - if (sfmData.getPoses().find(poseId) == sfmData.getPoses().end()) - { - sfmData.getPoses().emplace(poseId, sfmData::CameraPose(pose, rigPoseLocked)); - } - } - - if((rigId != UndefinedIndexT) && sfmData.getRigs().find(rigId) == sfmData.getRigs().end()) - sfmData.getRigs().emplace(rigId, sfmData::Rig(nbSubPoses)); - - mat.makeIdentity(); - return true; } // Top down read of 3d objects -void visitObject(const Version& abcVersion, IObject iObj, M44d mat, sfmData::SfMData& sfmdata, - ESfMData flagsPart, bool isReconstructed = true) +void visitObject(const Version& abcVersion, IObject iObj, M44d mat, sfmData::SfMData& sfmdata, ESfMData flagsPart, bool isReconstructed = true) { - // ALICEVISION_LOG_DEBUG("ABC visit: " << iObj.getFullName()); - if(iObj.getName() == "mvgCamerasUndefined") - isReconstructed = false; - - const MetaData& md = iObj.getMetaData(); - if(IPoints::matches(md) && (flagsPart & ESfMData::STRUCTURE)) - { - readPointCloud(abcVersion, iObj, mat, sfmdata, flagsPart); - } - else if(IXform::matches(md)) - { - IXform xform(iObj, kWrapExisting); - readXform(abcVersion, xform, mat, sfmdata, flagsPart, isReconstructed); - } - else if(ICamera::matches(md) && ((flagsPart & ESfMData::VIEWS) || - (flagsPart & ESfMData::INTRINSICS) || - (flagsPart & ESfMData::EXTRINSICS))) - { - ICamera check_cam(iObj, kWrapExisting); - // If it's not an animated camera we add it here - if(check_cam.getSchema().getNumSamples() == 1) - { - readCamera(abcVersion, check_cam, mat, sfmdata, flagsPart, 0, isReconstructed); - } - } - - // Recurse - for(std::size_t i = 0; i < iObj.getNumChildren(); i++) - { - visitObject(abcVersion, iObj.getChild(i), mat, sfmdata, flagsPart, isReconstructed); - } + // ALICEVISION_LOG_DEBUG("ABC visit: " << iObj.getFullName()); + if (iObj.getName() == "mvgCamerasUndefined") + isReconstructed = false; + + const MetaData& md = iObj.getMetaData(); + if (IPoints::matches(md) && (flagsPart & ESfMData::STRUCTURE)) + { + readPointCloud(abcVersion, iObj, mat, sfmdata, flagsPart); + } + else if (IXform::matches(md)) + { + IXform xform(iObj, kWrapExisting); + readXform(abcVersion, xform, mat, sfmdata, flagsPart, isReconstructed); + } + else if (ICamera::matches(md) && ((flagsPart & ESfMData::VIEWS) || (flagsPart & ESfMData::INTRINSICS) || (flagsPart & ESfMData::EXTRINSICS))) + { + ICamera check_cam(iObj, kWrapExisting); + // If it's not an animated camera we add it here + if (check_cam.getSchema().getNumSamples() == 1) + { + readCamera(abcVersion, check_cam, mat, sfmdata, flagsPart, 0, isReconstructed); + } + } + + // Recurse + for (std::size_t i = 0; i < iObj.getNumChildren(); i++) + { + visitObject(abcVersion, iObj.getChild(i), mat, sfmdata, flagsPart, isReconstructed); + } } struct AlembicImporter::DataImpl { - DataImpl(const std::string& filename) - { - Alembic::AbcCoreFactory::IFactory factory; - Alembic::AbcCoreFactory::IFactory::CoreType coreType; - Abc::IArchive archive = factory.getArchive(filename, coreType); - - if(!archive.valid()) - throw std::runtime_error("Can't open '" + filename + "' : Alembic file is not valid."); - - _rootEntity = archive.getTop(); - _filename = filename; - } - - IObject _rootEntity; - std::string _filename; + DataImpl(const std::string& filename) + { + Alembic::AbcCoreFactory::IFactory factory; + Alembic::AbcCoreFactory::IFactory::CoreType coreType; + Abc::IArchive archive = factory.getArchive(filename, coreType); + + if (!archive.valid()) + throw std::runtime_error("Can't open '" + filename + "' : Alembic file is not valid."); + + _rootEntity = archive.getTop(); + _filename = filename; + } + + IObject _rootEntity; + std::string _filename; }; -AlembicImporter::AlembicImporter(const std::string& filename) -{ - _dataImpl.reset(new DataImpl(filename)); -} +AlembicImporter::AlembicImporter(const std::string& filename) { _dataImpl.reset(new DataImpl(filename)); } -AlembicImporter::~AlembicImporter() -{} +AlembicImporter::~AlembicImporter() {} void AlembicImporter::populateSfM(sfmData::SfMData& sfmdata, ESfMData flagsPart) { - const index_t sampleFrame = 0; - IObject rootObj = _dataImpl->_rootEntity.getChild("mvgRoot"); - ICompoundProperty userProps = rootObj.getProperties(); - - // set SfMData folder absolute path - sfmdata.setAbsolutePath(_dataImpl->_filename); - - std::vector<::uint32_t> vecAbcVersion = {0, 0, 0}; - - if(const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_ABC_version")) - { - getAbcArrayProp_uint(userProps, "mvg_ABC_version", sampleFrame, vecAbcVersion); - } - // Old versions were using only major,minor - if(vecAbcVersion.size() < 3) - { - vecAbcVersion.resize(3, 0); - } - - Version abcVersion(vecAbcVersion[0], vecAbcVersion[1], vecAbcVersion[2]); - - if(userProps.getPropertyHeader("mvg_featuresFolders")) - { - std::vector featuresFolders; - getAbcArrayProp(userProps, "mvg_featuresFolders", sampleFrame, featuresFolders); - sfmdata.setFeaturesFolders(featuresFolders); - } - - if(userProps.getPropertyHeader("mvg_matchesFolders")) - { - std::vector matchesFolders; - getAbcArrayProp(userProps, "mvg_matchesFolders", sampleFrame, matchesFolders); - sfmdata.setMatchesFolders(matchesFolders); - } - - // keep compatibility with single folder for feature and matching - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_featureFolder")) - { - const std::string featuresFolder = getAbcProp(userProps, *propHeader, "mvg_featureFolder", sampleFrame); - sfmdata.addFeaturesFolder(featuresFolder); - } - - if(const Alembic::Abc::PropertyHeader *propHeader = userProps.getPropertyHeader("mvg_matchingFolder")) - { - const std::string matchesFolder = getAbcProp(userProps, *propHeader, "mvg_matchingFolder", sampleFrame); - sfmdata.addMatchesFolder(matchesFolder); - } - - // TODO : handle the case where the archive wasn't correctly opened - M44d xformMat; - visitObject(abcVersion, _dataImpl->_rootEntity, xformMat, sfmdata, flagsPart); - - // TODO: fusion of common intrinsics + const index_t sampleFrame = 0; + IObject rootObj = _dataImpl->_rootEntity.getChild("mvgRoot"); + ICompoundProperty userProps = rootObj.getProperties(); + + // set SfMData folder absolute path + sfmdata.setAbsolutePath(_dataImpl->_filename); + + std::vector<::uint32_t> vecAbcVersion = {0, 0, 0}; + + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_ABC_version")) + { + getAbcArrayProp_uint(userProps, "mvg_ABC_version", sampleFrame, vecAbcVersion); + } + // Old versions were using only major,minor + if (vecAbcVersion.size() < 3) + { + vecAbcVersion.resize(3, 0); + } + + Version abcVersion(vecAbcVersion[0], vecAbcVersion[1], vecAbcVersion[2]); + + if (userProps.getPropertyHeader("mvg_featuresFolders")) + { + std::vector featuresFolders; + getAbcArrayProp(userProps, "mvg_featuresFolders", sampleFrame, featuresFolders); + sfmdata.setFeaturesFolders(featuresFolders); + } + + if (userProps.getPropertyHeader("mvg_matchesFolders")) + { + std::vector matchesFolders; + getAbcArrayProp(userProps, "mvg_matchesFolders", sampleFrame, matchesFolders); + sfmdata.setMatchesFolders(matchesFolders); + } + + // keep compatibility with single folder for feature and matching + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_featureFolder")) + { + const std::string featuresFolder = getAbcProp(userProps, *propHeader, "mvg_featureFolder", sampleFrame); + sfmdata.addFeaturesFolder(featuresFolder); + } + + if (const Alembic::Abc::PropertyHeader* propHeader = userProps.getPropertyHeader("mvg_matchingFolder")) + { + const std::string matchesFolder = getAbcProp(userProps, *propHeader, "mvg_matchingFolder", sampleFrame); + sfmdata.addMatchesFolder(matchesFolder); + } + + // TODO : handle the case where the archive wasn't correctly opened + M44d xformMat; + visitObject(abcVersion, _dataImpl->_rootEntity, xformMat, sfmdata, flagsPart); + + // TODO: fusion of common intrinsics } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/AlembicImporter.hpp b/src/aliceVision/sfmDataIO/AlembicImporter.hpp index 72b9211baa..8c1e91de89 100644 --- a/src/aliceVision/sfmDataIO/AlembicImporter.hpp +++ b/src/aliceVision/sfmDataIO/AlembicImporter.hpp @@ -14,25 +14,24 @@ namespace aliceVision { namespace sfmDataIO { -class AlembicImporter +class AlembicImporter { -public: + public: + explicit AlembicImporter(const std::string& filename); - explicit AlembicImporter(const std::string& filename); + ~AlembicImporter(); - ~AlembicImporter(); + /** + * @brief populate a SfMData from the alembic file + * @param[out] sfmData The output SfMData + * @param[in] flagsPart + */ + void populateSfM(sfmData::SfMData& sfmdata, ESfMData flagsPart = ESfMData::ALL); - /** - * @brief populate a SfMData from the alembic file - * @param[out] sfmData The output SfMData - * @param[in] flagsPart - */ - void populateSfM(sfmData::SfMData& sfmdata, ESfMData flagsPart = ESfMData::ALL); - -private: - struct DataImpl; - std::unique_ptr _dataImpl; + private: + struct DataImpl; + std::unique_ptr _dataImpl; }; -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/alembicIO_test.cpp b/src/aliceVision/sfmDataIO/alembicIO_test.cpp index 19b0477374..a9bf054742 100644 --- a/src/aliceVision/sfmDataIO/alembicIO_test.cpp +++ b/src/aliceVision/sfmDataIO/alembicIO_test.cpp @@ -23,128 +23,112 @@ using namespace aliceVision::sfmDataIO; // Create a SfM scene with desired count of views & poses & intrinsic (shared or not) // Add a 3D point with observation in 2 view (just in order to have non empty data) -SfMData createTestScene(IndexT singleViewsCount, - IndexT pointCount, - IndexT rigCount, - IndexT subPoseCount, - bool bSharedIntrinsic) +SfMData createTestScene(IndexT singleViewsCount, IndexT pointCount, IndexT rigCount, IndexT subPoseCount, bool bSharedIntrinsic) { - SfMData sfm_data; - - std::srand(time(nullptr)); - - for(IndexT i = 0; i < singleViewsCount; ++i) - { - // Add views - std::ostringstream os; - os << "dataset/" << i << ".jpg"; - const IndexT id_view = i, id_pose = i; - const IndexT id_intrinsic = bSharedIntrinsic ? 0 : i; //(shared or not intrinsics) - - std::shared_ptr view = std::make_shared(os.str(),id_view, id_intrinsic, id_pose, 1000, 1000); - view->getImage().addMetadata("A","A"); - view->getImage().addMetadata("B","B"); - view->getImage().addMetadata("C","C"); - sfm_data.getViews().emplace(id_view, view); - - // Add poses - const Mat3 r = SO3::expm(Vec3::Random()); - const Vec3 c = Vec3::Random(); - sfm_data.setPose(*view, CameraPose(geometry::Pose3(r, c))); - - // Add intrinsics - if(bSharedIntrinsic) - { - if(i == 0) - { - sfm_data.getIntrinsics().emplace(0, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - 1000, 1000, 36.0, 36.0, std::rand()%10000, std::rand()%10000)); - } - } - else - { - sfm_data.getIntrinsics().emplace(i, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - 1000, 1000, 36.0, 36.0, std::rand()%10000, std::rand()%10000)); - } - } + SfMData sfm_data; + std::srand(time(nullptr)); - std::size_t nbIntrinsics = (bSharedIntrinsic ? 1 : singleViewsCount); - std::size_t nbPoses = singleViewsCount; - std::size_t nbViews = singleViewsCount; - const std::size_t nbRigPoses = 5; + for (IndexT i = 0; i < singleViewsCount; ++i) + { + // Add views + std::ostringstream os; + os << "dataset/" << i << ".jpg"; + const IndexT id_view = i, id_pose = i; + const IndexT id_intrinsic = bSharedIntrinsic ? 0 : i; //(shared or not intrinsics) - for(IndexT rigId = 0; rigId < rigCount; ++rigId) - { - sfm_data.getRigs().emplace(rigId, Rig(subPoseCount)); - Rig& rig = sfm_data.getRigs().at(rigId); + std::shared_ptr view = std::make_shared(os.str(), id_view, id_intrinsic, id_pose, 1000, 1000); + view->getImage().addMetadata("A", "A"); + view->getImage().addMetadata("B", "B"); + view->getImage().addMetadata("C", "C"); + sfm_data.getViews().emplace(id_view, view); - for(IndexT subPoseId = 0; subPoseId < subPoseCount; ++subPoseId) - { - { + // Add poses const Mat3 r = SO3::expm(Vec3::Random()); const Vec3 c = Vec3::Random(); - rig.setSubPose(subPoseId, RigSubPose(geometry::Pose3(r, c), ERigSubPoseStatus::ESTIMATED)); - } - - sfm_data.getIntrinsics().emplace(nbIntrinsics + subPoseId, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - 1000, 1000, 36.0, 36.0, std::rand()%10000, std::rand()%10000)); + sfm_data.setPose(*view, CameraPose(geometry::Pose3(r, c))); - for(std::size_t pose = 0; pose < nbRigPoses; ++pose) - { - std::ostringstream os; - os << "dataset/rig_" << rigId << "_" << subPoseId << "_" << pose << ".jpg"; - - std::shared_ptr view = std::make_shared(os.str(), - nbViews, - nbIntrinsics + subPoseId, - nbPoses + pose, - 1000, - 1000, - rigId, - subPoseId); - - if(subPoseId == 0) + // Add intrinsics + if (bSharedIntrinsic) { - const Mat3 r = SO3::expm(Vec3::Random()); - const Vec3 c = Vec3::Random(); - sfm_data.setPose(*view, CameraPose(geometry::Pose3(r, c))); + if (i == 0) + { + sfm_data.getIntrinsics().emplace( + 0, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, 1000, 1000, 36.0, 36.0, std::rand() % 10000, std::rand() % 10000)); + } } + else + { + sfm_data.getIntrinsics().emplace( + i, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, 1000, 1000, 36.0, 36.0, std::rand() % 10000, std::rand() % 10000)); + } + } - view->setFrameId(nbPoses + pose); - view->setIndependantPose(false); + std::size_t nbIntrinsics = (bSharedIntrinsic ? 1 : singleViewsCount); + std::size_t nbPoses = singleViewsCount; + std::size_t nbViews = singleViewsCount; + const std::size_t nbRigPoses = 5; - sfm_data.getViews().emplace(nbViews, view); - ++nbViews; - } + for (IndexT rigId = 0; rigId < rigCount; ++rigId) + { + sfm_data.getRigs().emplace(rigId, Rig(subPoseCount)); + Rig& rig = sfm_data.getRigs().at(rigId); + + for (IndexT subPoseId = 0; subPoseId < subPoseCount; ++subPoseId) + { + { + const Mat3 r = SO3::expm(Vec3::Random()); + const Vec3 c = Vec3::Random(); + rig.setSubPose(subPoseId, RigSubPose(geometry::Pose3(r, c), ERigSubPoseStatus::ESTIMATED)); + } + + sfm_data.getIntrinsics().emplace( + nbIntrinsics + subPoseId, + camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, 1000, 1000, 36.0, 36.0, std::rand() % 10000, std::rand() % 10000)); + + for (std::size_t pose = 0; pose < nbRigPoses; ++pose) + { + std::ostringstream os; + os << "dataset/rig_" << rigId << "_" << subPoseId << "_" << pose << ".jpg"; + + std::shared_ptr view = + std::make_shared(os.str(), nbViews, nbIntrinsics + subPoseId, nbPoses + pose, 1000, 1000, rigId, subPoseId); + + if (subPoseId == 0) + { + const Mat3 r = SO3::expm(Vec3::Random()); + const Vec3 c = Vec3::Random(); + sfm_data.setPose(*view, CameraPose(geometry::Pose3(r, c))); + } + + view->setFrameId(nbPoses + pose); + view->setIndependantPose(false); + + sfm_data.getViews().emplace(nbViews, view); + ++nbViews; + } + } + nbPoses += nbRigPoses; + nbIntrinsics += subPoseCount; + } + + const double unknownScale = 0.0; + // Fill with not meaningful tracks + for (IndexT i = 0; i < pointCount; ++i) + { + // Add structure + Observations observations; + observations[0] = Observation(Vec2(std::rand() % 10000, std::rand() % 10000), 0, unknownScale); + observations[1] = Observation(Vec2(std::rand() % 10000, std::rand() % 10000), 1, unknownScale); + sfm_data.getLandmarks()[i].observations = observations; + sfm_data.getLandmarks()[i].X = Vec3(std::rand() % 10000, std::rand() % 10000, std::rand() % 10000); + sfm_data.getLandmarks()[i].rgb = image::RGBColor((std::rand() % 1000) / 1000.0, (std::rand() % 1000) / 1000.0, (std::rand() % 1000) / 1000.0); + sfm_data.getLandmarks()[i].descType = feature::EImageDescriberType::SIFT; + + // Add control points } - nbPoses += nbRigPoses; - nbIntrinsics += subPoseCount; - } - - const double unknownScale = 0.0; - // Fill with not meaningful tracks - for(IndexT i = 0; i < pointCount; ++i) - { - // Add structure - Observations observations; - observations[0] = Observation( Vec2(std::rand() % 10000, std::rand() % 10000), 0, unknownScale); - observations[1] = Observation( Vec2(std::rand() % 10000, std::rand() % 10000), 1, unknownScale); - sfm_data.getLandmarks()[i].observations = observations; - sfm_data.getLandmarks()[i].X = Vec3(std::rand() % 10000, std::rand() % 10000, std::rand() % 10000); - sfm_data.getLandmarks()[i].rgb = image::RGBColor((std::rand() % 1000) / 1000.0, (std::rand() % 1000) / 1000.0, (std::rand() % 1000) / 1000.0); - sfm_data.getLandmarks()[i].descType = feature::EImageDescriberType::SIFT; - - // Add control points - } - - return sfm_data; + + return sfm_data; } //----------------- @@ -155,23 +139,21 @@ SfMData createTestScene(IndexT singleViewsCount, // - Import to Alembic // - Import to .json //----------------- -BOOST_AUTO_TEST_CASE(AlembicImporter_importExport) { +BOOST_AUTO_TEST_CASE(AlembicImporter_importExport) +{ makeRandomOperationsReproducible(); int flags = ALL; // Create a random scene const SfMData sfmData = createTestScene(5, 50, 2, 3, true); - + // JSON -> JSON // Export as JSON const std::string jsonFile = "importExport.sfm"; { - BOOST_CHECK(Save( - sfmData, - jsonFile, - ESfMData(flags))); + BOOST_CHECK(Save(sfmData, jsonFile, ESfMData(flags))); } // Reload @@ -180,16 +162,13 @@ BOOST_AUTO_TEST_CASE(AlembicImporter_importExport) { BOOST_CHECK(Load(sfmJsonToJson, jsonFile, ESfMData(flags))); BOOST_CHECK(sfmData == sfmJsonToJson); } - + // ABC -> ABC // Export as ABC const std::string abcFile = "abcToAbc.abc"; { - BOOST_CHECK(Save( - sfmData, - abcFile, - ESfMData(flags))); + BOOST_CHECK(Save(sfmData, abcFile, ESfMData(flags))); } // Reload @@ -197,20 +176,14 @@ BOOST_AUTO_TEST_CASE(AlembicImporter_importExport) { { BOOST_CHECK(Load(sfmAbcToAbc, abcFile, ESfMData(flags))); std::string abcFile2 = "abcToJson.sfm"; - BOOST_CHECK(Save( - sfmAbcToAbc, - abcFile2, - ESfMData(flags))); + BOOST_CHECK(Save(sfmAbcToAbc, abcFile2, ESfMData(flags))); BOOST_CHECK(sfmData == sfmAbcToAbc); } // Export as ABC const std::string abcFile2 = "abcToAbc2.abc"; { - BOOST_CHECK(Save( - sfmAbcToAbc, - abcFile2, - ESfMData(flags))); + BOOST_CHECK(Save(sfmAbcToAbc, abcFile2, ESfMData(flags))); } // JSON -> ABC -> ABC -> JSON @@ -218,67 +191,54 @@ BOOST_AUTO_TEST_CASE(AlembicImporter_importExport) { // Export as JSON const std::string jsonFile3 = "jsonToABC.sfm"; { - BOOST_CHECK(Save( - sfmData, - jsonFile3, - ESfMData(flags))); + BOOST_CHECK(Save(sfmData, jsonFile3, ESfMData(flags))); } // Reload SfMData sfmJsonToABC; { BOOST_CHECK(Load(sfmJsonToABC, jsonFile3, ESfMData(flags))); - BOOST_CHECK_EQUAL( sfmData.getViews().size(), sfmJsonToABC.getViews().size()); - BOOST_CHECK_EQUAL( sfmData.getPoses().size(), sfmJsonToABC.getPoses().size()); - BOOST_CHECK_EQUAL( sfmData.getIntrinsics().size(), sfmJsonToABC.getIntrinsics().size()); - BOOST_CHECK_EQUAL( sfmData.getLandmarks().size(), sfmJsonToABC.getLandmarks().size()); + BOOST_CHECK_EQUAL(sfmData.getViews().size(), sfmJsonToABC.getViews().size()); + BOOST_CHECK_EQUAL(sfmData.getPoses().size(), sfmJsonToABC.getPoses().size()); + BOOST_CHECK_EQUAL(sfmData.getIntrinsics().size(), sfmJsonToABC.getIntrinsics().size()); + BOOST_CHECK_EQUAL(sfmData.getLandmarks().size(), sfmJsonToABC.getLandmarks().size()); } // Export as ABC const std::string abcFile3 = "jsonToABC.abc"; { - BOOST_CHECK(Save( - sfmJsonToABC, - abcFile3, - ESfMData(flags))); + BOOST_CHECK(Save(sfmJsonToABC, abcFile3, ESfMData(flags))); } // Reload SfMData sfmJsonToABC2; { BOOST_CHECK(Load(sfmJsonToABC2, abcFile3, ESfMData(flags))); - BOOST_CHECK_EQUAL( sfmData.getViews().size(), sfmJsonToABC2.getViews().size()); - BOOST_CHECK_EQUAL( sfmData.getPoses().size(), sfmJsonToABC2.getPoses().size()); - BOOST_CHECK_EQUAL( sfmData.getIntrinsics().size(), sfmJsonToABC2.getIntrinsics().size()); - BOOST_CHECK_EQUAL( sfmData.getLandmarks().size(), sfmJsonToABC2.getLandmarks().size()); + BOOST_CHECK_EQUAL(sfmData.getViews().size(), sfmJsonToABC2.getViews().size()); + BOOST_CHECK_EQUAL(sfmData.getPoses().size(), sfmJsonToABC2.getPoses().size()); + BOOST_CHECK_EQUAL(sfmData.getIntrinsics().size(), sfmJsonToABC2.getIntrinsics().size()); + BOOST_CHECK_EQUAL(sfmData.getLandmarks().size(), sfmJsonToABC2.getLandmarks().size()); } // Export as ABC const std::string abcFile4 = "jsonToABC2.abc"; { - BOOST_CHECK(Save( - sfmJsonToABC2, - abcFile4, - ESfMData(flags))); + BOOST_CHECK(Save(sfmJsonToABC2, abcFile4, ESfMData(flags))); } // Reload SfMData sfmJsonToABC3; { BOOST_CHECK(Load(sfmJsonToABC3, abcFile4, ESfMData(flags))); - BOOST_CHECK_EQUAL( sfmData.getViews().size(), sfmJsonToABC3.getViews().size()); - BOOST_CHECK_EQUAL( sfmData.getPoses().size(), sfmJsonToABC3.getPoses().size()); - BOOST_CHECK_EQUAL( sfmData.getIntrinsics().size(), sfmJsonToABC3.getIntrinsics().size()); - BOOST_CHECK_EQUAL( sfmData.getLandmarks().size(), sfmJsonToABC3.getLandmarks().size()); + BOOST_CHECK_EQUAL(sfmData.getViews().size(), sfmJsonToABC3.getViews().size()); + BOOST_CHECK_EQUAL(sfmData.getPoses().size(), sfmJsonToABC3.getPoses().size()); + BOOST_CHECK_EQUAL(sfmData.getIntrinsics().size(), sfmJsonToABC3.getIntrinsics().size()); + BOOST_CHECK_EQUAL(sfmData.getLandmarks().size(), sfmJsonToABC3.getLandmarks().size()); } // Export as JSON const std::string jsonFile4 = "jsonToABC2.sfm"; { - BOOST_CHECK(Save( - sfmJsonToABC3, - jsonFile4, - ESfMData(flags))); + BOOST_CHECK(Save(sfmJsonToABC3, jsonFile4, ESfMData(flags))); } - } diff --git a/src/aliceVision/sfmDataIO/bafIO.cpp b/src/aliceVision/sfmDataIO/bafIO.cpp index a36cc1d23b..9abf6c8332 100644 --- a/src/aliceVision/sfmDataIO/bafIO.cpp +++ b/src/aliceVision/sfmDataIO/bafIO.cpp @@ -16,112 +16,91 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace sfmDataIO { -bool saveBAF( - const sfmData::SfMData& sfmData, - const std::string& filename, - ESfMData partFlag) +bool saveBAF(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag) { - std::ofstream stream(filename); - if (!stream.is_open()) - return false; - - bool bOk = false; - { - stream - << sfmData.getIntrinsics().size() << '\n' - << sfmData.getViews().size() << '\n' - << sfmData.getLandmarks().size() << '\n'; + std::ofstream stream(filename); + if (!stream.is_open()) + return false; - const sfmData::Intrinsics& intrinsics = sfmData.getIntrinsics(); - for (sfmData::Intrinsics::const_iterator iterIntrinsic = intrinsics.begin(); - iterIntrinsic != intrinsics.end(); ++iterIntrinsic) + bool bOk = false; { - //get params - const std::vector intrinsicsParams = iterIntrinsic->second.get()->getParams(); - std::copy(intrinsicsParams.begin(), intrinsicsParams.end(), - std::ostream_iterator(stream, " ")); - stream << '\n'; - } + stream << sfmData.getIntrinsics().size() << '\n' << sfmData.getViews().size() << '\n' << sfmData.getLandmarks().size() << '\n'; - const sfmData::Poses& poses = sfmData.getPoses(); - for (sfmData::Views::const_iterator iterV = sfmData.getViews().begin(); - iterV != sfmData.getViews().end(); - ++ iterV) - { - const sfmData::View* view = iterV->second.get(); - if (!sfmData.isPoseAndIntrinsicDefined(view)) - { - const Mat3 R = Mat3::Identity(); - const double * rotation = R.data(); - std::copy(rotation, rotation+9, std::ostream_iterator(stream, " ")); - const Vec3 C = Vec3::Zero(); - const double * center = C.data(); - std::copy(center, center+3, std::ostream_iterator(stream, " ")); - stream << '\n'; - } - else - { - // [Rotation col major 3x3; camera center 3x1] - const double * rotation = poses.at(view->getPoseId()).getTransform().rotation().data(); - std::copy(rotation, rotation+9, std::ostream_iterator(stream, " ")); - const double * center = poses.at(view->getPoseId()).getTransform().center().data(); - std::copy(center, center+3, std::ostream_iterator(stream, " ")); - stream << '\n'; - } - } + const sfmData::Intrinsics& intrinsics = sfmData.getIntrinsics(); + for (sfmData::Intrinsics::const_iterator iterIntrinsic = intrinsics.begin(); iterIntrinsic != intrinsics.end(); ++iterIntrinsic) + { + // get params + const std::vector intrinsicsParams = iterIntrinsic->second.get()->getParams(); + std::copy(intrinsicsParams.begin(), intrinsicsParams.end(), std::ostream_iterator(stream, " ")); + stream << '\n'; + } - const sfmData::Landmarks& landmarks = sfmData.getLandmarks(); - for (sfmData::Landmarks::const_iterator iterLandmarks = landmarks.begin(); - iterLandmarks != landmarks.end(); - ++iterLandmarks) - { - // Export visibility information - // X Y Z #observations id_cam id_pose x y ... - const double * X = iterLandmarks->second.X.data(); - std::copy(X, X+3, std::ostream_iterator(stream, " ")); - const sfmData::Observations& observations = iterLandmarks->second.observations; - stream << observations.size() << " "; - for (sfmData::Observations::const_iterator iterOb = observations.begin(); - iterOb != observations.end(); ++iterOb) - { - const IndexT id_view = iterOb->first; - const sfmData::View * v = sfmData.getViews().at(id_view).get(); - stream - << v->getIntrinsicId() << ' ' - << v->getPoseId() << ' ' - << iterOb->second.x(0) << ' ' << iterOb->second.x(1) << ' '; - } - stream << '\n'; - } + const sfmData::Poses& poses = sfmData.getPoses(); + for (sfmData::Views::const_iterator iterV = sfmData.getViews().begin(); iterV != sfmData.getViews().end(); ++iterV) + { + const sfmData::View* view = iterV->second.get(); + if (!sfmData.isPoseAndIntrinsicDefined(view)) + { + const Mat3 R = Mat3::Identity(); + const double* rotation = R.data(); + std::copy(rotation, rotation + 9, std::ostream_iterator(stream, " ")); + const Vec3 C = Vec3::Zero(); + const double* center = C.data(); + std::copy(center, center + 3, std::ostream_iterator(stream, " ")); + stream << '\n'; + } + else + { + // [Rotation col major 3x3; camera center 3x1] + const double* rotation = poses.at(view->getPoseId()).getTransform().rotation().data(); + std::copy(rotation, rotation + 9, std::ostream_iterator(stream, " ")); + const double* center = poses.at(view->getPoseId()).getTransform().center().data(); + std::copy(center, center + 3, std::ostream_iterator(stream, " ")); + stream << '\n'; + } + } - stream.flush(); - bOk = stream.good(); - stream.close(); - } + const sfmData::Landmarks& landmarks = sfmData.getLandmarks(); + for (sfmData::Landmarks::const_iterator iterLandmarks = landmarks.begin(); iterLandmarks != landmarks.end(); ++iterLandmarks) + { + // Export visibility information + // X Y Z #observations id_cam id_pose x y ... + const double* X = iterLandmarks->second.X.data(); + std::copy(X, X + 3, std::ostream_iterator(stream, " ")); + const sfmData::Observations& observations = iterLandmarks->second.observations; + stream << observations.size() << " "; + for (sfmData::Observations::const_iterator iterOb = observations.begin(); iterOb != observations.end(); ++iterOb) + { + const IndexT id_view = iterOb->first; + const sfmData::View* v = sfmData.getViews().at(id_view).get(); + stream << v->getIntrinsicId() << ' ' << v->getPoseId() << ' ' << iterOb->second.x(0) << ' ' << iterOb->second.x(1) << ' '; + } + stream << '\n'; + } - // Export View filenames & ids as an imgList.txt file - { - const std::string sFile = (fs::path(filename).parent_path() / (fs::path(filename).stem().string() + "_imgList.txt")).string(); + stream.flush(); + bOk = stream.good(); + stream.close(); + } - stream.open(sFile); - if (!stream.is_open()) - return false; - for (sfmData::Views::const_iterator iterV = sfmData.getViews().begin(); - iterV != sfmData.getViews().end(); - ++ iterV) + // Export View filenames & ids as an imgList.txt file { - const std::string sView_filename = iterV->second->getImage().getImagePath(); - stream - << sView_filename - << ' ' << iterV->second->getIntrinsicId() - << ' ' << iterV->second->getPoseId() << "\n"; + const std::string sFile = (fs::path(filename).parent_path() / (fs::path(filename).stem().string() + "_imgList.txt")).string(); + + stream.open(sFile); + if (!stream.is_open()) + return false; + for (sfmData::Views::const_iterator iterV = sfmData.getViews().begin(); iterV != sfmData.getViews().end(); ++iterV) + { + const std::string sView_filename = iterV->second->getImage().getImagePath(); + stream << sView_filename << ' ' << iterV->second->getIntrinsicId() << ' ' << iterV->second->getPoseId() << "\n"; + } + stream.flush(); + bOk = stream.good(); + stream.close(); } - stream.flush(); - bOk = stream.good(); - stream.close(); - } - return bOk; + return bOk; } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/bafIO.hpp b/src/aliceVision/sfmDataIO/bafIO.hpp index 150f18db79..33246655c7 100644 --- a/src/aliceVision/sfmDataIO/bafIO.hpp +++ b/src/aliceVision/sfmDataIO/bafIO.hpp @@ -37,9 +37,7 @@ namespace sfmDataIO { * @param[in] partFlag The ESfMData save flag * @return true if completed */ -bool saveBAF(const sfmData::SfMData& sfmData, - const std::string& filename, - ESfMData partFlag); +bool saveBAF(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/colmap.cpp b/src/aliceVision/sfmDataIO/colmap.cpp index a18cd32f77..0e509ffdd7 100644 --- a/src/aliceVision/sfmDataIO/colmap.cpp +++ b/src/aliceVision/sfmDataIO/colmap.cpp @@ -15,11 +15,11 @@ namespace fs = boost::filesystem; -namespace aliceVision{ -namespace sfmDataIO{ +namespace aliceVision { +namespace sfmDataIO { ColmapConfig::ColmapConfig(const std::string& basePath) - : _basePath(basePath) + : _basePath(basePath) { _sparseDirectory = (fs::path(_basePath) / fs::path("sparse/0")).string(); _denseDirectory = (fs::path(_basePath) / fs::path("dense/0")).string(); @@ -31,13 +31,15 @@ ColmapConfig::ColmapConfig(const std::string& basePath) const std::set& colmapCompatibleIntrinsics() { - static const std::set compatibleIntrinsics{ - camera::PINHOLE_CAMERA, camera::PINHOLE_CAMERA_RADIAL1, camera::PINHOLE_CAMERA_RADIAL3, - camera::PINHOLE_CAMERA_BROWN, camera::PINHOLE_CAMERA_FISHEYE, camera::PINHOLE_CAMERA_FISHEYE1}; + static const std::set compatibleIntrinsics{camera::PINHOLE_CAMERA, + camera::PINHOLE_CAMERA_RADIAL1, + camera::PINHOLE_CAMERA_RADIAL3, + camera::PINHOLE_CAMERA_BROWN, + camera::PINHOLE_CAMERA_FISHEYE, + camera::PINHOLE_CAMERA_FISHEYE1}; return compatibleIntrinsics; } - bool isColmapCompatible(camera::EINTRINSIC intrinsicType) { const auto compatible = colmapCompatibleIntrinsics(); @@ -47,19 +49,19 @@ bool isColmapCompatible(camera::EINTRINSIC intrinsicType) CompatibleList getColmapCompatibleIntrinsics(const sfmData::SfMData& sfMData) { CompatibleList compatible; - for(const auto& iter : sfMData.getIntrinsics()) + for (const auto& iter : sfMData.getIntrinsics()) { const auto& intrinsics = iter.second; const auto intrID = iter.first; const auto intrType = intrinsics->getType(); - if(isColmapCompatible(intrType)) + if (isColmapCompatible(intrType)) { compatible.insert(intrID); } else { ALICEVISION_LOG_INFO("The intrinsics " << intrID << " is of type " << intrType - << " which is not supported in Colmap and it will be removed"); + << " which is not supported in Colmap and it will be removed"); } } return compatible; @@ -70,15 +72,15 @@ CompatibleList getColmapCompatibleViews(const sfmData::SfMData& sfmData) const auto compatibleIntrinsics = getColmapCompatibleIntrinsics(sfmData); CompatibleList compatibleViews; - for(const auto& iter : sfmData.getViews()) + for (const auto& iter : sfmData.getViews()) { const auto& view = iter.second; const auto viewID = iter.first; - if(!sfmData.isPoseAndIntrinsicDefined(view.get())) + if (!sfmData.isPoseAndIntrinsicDefined(view.get())) { continue; } - if(compatibleIntrinsics.count(view->getIntrinsicId()) > 0) + if (compatibleIntrinsics.count(view->getIntrinsicId()) > 0) { compatibleViews.insert(viewID); } @@ -91,7 +93,7 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar std::stringstream intrString; camera::EINTRINSIC current_type = intrinsic->getType(); - switch(current_type) + switch (current_type) { case camera::PINHOLE_CAMERA: // PINHOLE_CAMERA corresponds to Colmap's PINHOLE @@ -101,9 +103,8 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar intrString << intrinsicsID << " " << "PINHOLE" - << " " << pinhole_intrinsic->w() << " " << pinhole_intrinsic->h() << " " - << pinhole_intrinsic->getFocalLengthPixX() << " " << pinhole_intrinsic->getFocalLengthPixY() - << " " << pinhole_intrinsic->getPrincipalPoint().x() << " " + << " " << pinhole_intrinsic->w() << " " << pinhole_intrinsic->h() << " " << pinhole_intrinsic->getFocalLengthPixX() << " " + << pinhole_intrinsic->getFocalLengthPixY() << " " << pinhole_intrinsic->getPrincipalPoint().x() << " " << pinhole_intrinsic->getPrincipalPoint().y() << "\n"; } break; @@ -116,10 +117,8 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar intrString << intrinsicsID << " " << "FULL_OPENCV" << " " << pinhole_intrinsic_radial->w() << " " << pinhole_intrinsic_radial->h() << " " - << pinhole_intrinsic_radial->getFocalLengthPixX() << " " - << pinhole_intrinsic_radial->getFocalLengthPixY() << " " - << pinhole_intrinsic_radial->getPrincipalPoint().x() << " " - << pinhole_intrinsic_radial->getPrincipalPoint().y() << " " + << pinhole_intrinsic_radial->getFocalLengthPixX() << " " << pinhole_intrinsic_radial->getFocalLengthPixY() << " " + << pinhole_intrinsic_radial->getPrincipalPoint().x() << " " << pinhole_intrinsic_radial->getPrincipalPoint().y() << " " << pinhole_intrinsic_radial->getParams().at(4) << " " // k2, p1, p2, k3, k4, k5, k6 @@ -136,12 +135,9 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar intrString << intrinsicsID << " " << "FULL_OPENCV" << " " << pinhole_intrinsic_radial->w() << " " << pinhole_intrinsic_radial->h() << " " - << pinhole_intrinsic_radial->getFocalLengthPixX() << " " - << pinhole_intrinsic_radial->getFocalLengthPixY() << " " - << pinhole_intrinsic_radial->getPrincipalPoint().x() << " " - << pinhole_intrinsic_radial->getPrincipalPoint().y() << " " - << pinhole_intrinsic_radial->getParams().at(4) << " " - << pinhole_intrinsic_radial->getParams().at(5) + << pinhole_intrinsic_radial->getFocalLengthPixX() << " " << pinhole_intrinsic_radial->getFocalLengthPixY() << " " + << pinhole_intrinsic_radial->getPrincipalPoint().x() << " " << pinhole_intrinsic_radial->getPrincipalPoint().y() << " " + << pinhole_intrinsic_radial->getParams().at(4) << " " << pinhole_intrinsic_radial->getParams().at(5) << " " // tangential params p1 and p2 << 0.0 << " " << 0.0 @@ -161,17 +157,13 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar intrString << intrinsicsID << " " << "FULL_OPENCV" - << " " << pinholeCameraBrownIntrinsics->w() << " " << pinholeCameraBrownIntrinsics->h() - << " " << pinholeCameraBrownIntrinsics->getFocalLengthPixX() << " " - << pinholeCameraBrownIntrinsics->getFocalLengthPixY() << " " - << pinholeCameraBrownIntrinsics->getPrincipalPoint().x() << " " - << pinholeCameraBrownIntrinsics->getPrincipalPoint().y() << " " - << pinholeCameraBrownIntrinsics->getParams().at(4) << " " - << pinholeCameraBrownIntrinsics->getParams().at(5) + << " " << pinholeCameraBrownIntrinsics->w() << " " << pinholeCameraBrownIntrinsics->h() << " " + << pinholeCameraBrownIntrinsics->getFocalLengthPixX() << " " << pinholeCameraBrownIntrinsics->getFocalLengthPixY() << " " + << pinholeCameraBrownIntrinsics->getPrincipalPoint().x() << " " << pinholeCameraBrownIntrinsics->getPrincipalPoint().y() + << " " << pinholeCameraBrownIntrinsics->getParams().at(4) << " " << pinholeCameraBrownIntrinsics->getParams().at(5) << " " // tangential params p1 and p2 - << pinholeCameraBrownIntrinsics->getParams().at(7) << " " - << pinholeCameraBrownIntrinsics->getParams().at(8) + << pinholeCameraBrownIntrinsics->getParams().at(7) << " " << pinholeCameraBrownIntrinsics->getParams().at(8) << " " // k3 << pinholeCameraBrownIntrinsics->getParams().at(6) @@ -189,14 +181,10 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar intrString << intrinsicsID << " " << "OPENCV_FISHEYE" << " " << pinholeIntrinsicFisheye->w() << " " << pinholeIntrinsicFisheye->h() << " " - << pinholeIntrinsicFisheye->getFocalLengthPixX() << " " - << pinholeIntrinsicFisheye->getFocalLengthPixY() << " " - << pinholeIntrinsicFisheye->getPrincipalPoint().x() << " " - << pinholeIntrinsicFisheye->getPrincipalPoint().y() << " " - << pinholeIntrinsicFisheye->getParams().at(4) << " " - << pinholeIntrinsicFisheye->getParams().at(5) << " " - << pinholeIntrinsicFisheye->getParams().at(6) << " " - << pinholeIntrinsicFisheye->getParams().at(7) << "\n"; + << pinholeIntrinsicFisheye->getFocalLengthPixX() << " " << pinholeIntrinsicFisheye->getFocalLengthPixY() << " " + << pinholeIntrinsicFisheye->getPrincipalPoint().x() << " " << pinholeIntrinsicFisheye->getPrincipalPoint().y() << " " + << pinholeIntrinsicFisheye->getParams().at(4) << " " << pinholeIntrinsicFisheye->getParams().at(5) << " " + << pinholeIntrinsicFisheye->getParams().at(6) << " " << pinholeIntrinsicFisheye->getParams().at(7) << "\n"; } break; case camera::PINHOLE_CAMERA_FISHEYE1: @@ -208,16 +196,14 @@ std::string convertIntrinsicsToColmapString(const IndexT intrinsicsID, std::shar intrString << intrinsicsID << " " << "FOV" << " " << pinholeIntrinsicFisheye->w() << " " << pinholeIntrinsicFisheye->h() << " " - << pinholeIntrinsicFisheye->getFocalLengthPixX() << " " - << pinholeIntrinsicFisheye->getFocalLengthPixY() << " " - << pinholeIntrinsicFisheye->getPrincipalPoint().x() << " " - << pinholeIntrinsicFisheye->getPrincipalPoint().y() << " " + << pinholeIntrinsicFisheye->getFocalLengthPixX() << " " << pinholeIntrinsicFisheye->getFocalLengthPixY() << " " + << pinholeIntrinsicFisheye->getPrincipalPoint().x() << " " << pinholeIntrinsicFisheye->getPrincipalPoint().y() << " " << pinholeIntrinsicFisheye->getParams().at(4) << "\n"; } break; default: - throw std::invalid_argument("The intrinsics " + EINTRINSIC_enumToString(current_type) + " for camera " + - std::to_string(intrinsicsID) + " are not supported in Colmap"); + throw std::invalid_argument("The intrinsics " + EINTRINSIC_enumToString(current_type) + " for camera " + std::to_string(intrinsicsID) + + " are not supported in Colmap"); } return intrString.str(); } @@ -231,7 +217,7 @@ void generateColmapCamerasTxtFile(const sfmData::SfMData& sfmData, const std::st std::ofstream outfile(filename); - if(!outfile) + if (!outfile) { ALICEVISION_LOG_ERROR("Unable to create the cameras file " << filename); throw std::runtime_error("Unable to create the cameras file " + filename); @@ -242,12 +228,12 @@ void generateColmapCamerasTxtFile(const sfmData::SfMData& sfmData, const std::st std::vector camera_lines; - for(const auto& iter : sfmData.getIntrinsics()) + for (const auto& iter : sfmData.getIntrinsics()) { const IndexT intrID = iter.first; std::shared_ptr intrinsic = iter.second; - if(isColmapCompatible(intrinsic->getType())) + if (isColmapCompatible(intrinsic->getType())) { outfile << convertIntrinsicsToColmapString(intrID, intrinsic); } @@ -258,17 +244,17 @@ PerViewVisibility computePerViewVisibility(const sfmData::SfMData& sfmData, cons { PerViewVisibility perCameraVisibility; - for(const auto& land : sfmData.getLandmarks()) + for (const auto& land : sfmData.getLandmarks()) { const IndexT landID = land.first; const auto& observations = land.second.observations; - for(const auto& iter : observations) + for (const auto& iter : observations) { const IndexT viewID = iter.first; const auto& obs = iter.second; - if(viewSelections.find(viewID) != viewSelections.end()) + if (viewSelections.find(viewID) != viewSelections.end()) { // for the current viewID add the feature point and its associate 3D point's ID perCameraVisibility[viewID][landID] = obs.x; @@ -278,8 +264,7 @@ PerViewVisibility computePerViewVisibility(const sfmData::SfMData& sfmData, cons return perCameraVisibility; } -void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, - const std::string& filename) +void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, const std::string& filename) { // adapted from Colmap's Reconstruction::WriteImagesText() // An images.txt file has the following format @@ -289,7 +274,7 @@ void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const Compatib std::ofstream outfile(filename); - if(!outfile) + if (!outfile) { ALICEVISION_LOG_ERROR("Unable to create the image file " << filename); throw std::runtime_error("Unable to create the image file " + filename); @@ -303,12 +288,12 @@ void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const Compatib const auto perViewVisibility = computePerViewVisibility(sfmData, viewSelections); // for each view to export add a line with the pose and the intrinsics ID and another with point visibility - for(const auto& iter : sfmData.getViews()) + for (const auto& iter : sfmData.getViews()) { const auto view = iter.second.get(); const auto viewID = view->getViewId(); - if(viewSelections.find(viewID) == viewSelections.end()) + if (viewSelections.find(viewID) == viewSelections.end()) { continue; } @@ -324,10 +309,10 @@ void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const Compatib //"# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME\n" //"# POINTS2D[] as (X, Y, POINT3D_ID)\n" - outfile << viewID << " " << quat.w() << " " << quat.x() << " " << quat.y() << " " << quat.z() << " " << tra[0] - << " " << tra[1] << " " << tra[2] << " " << intrID << " " << imageFilename << "\n"; + outfile << viewID << " " << quat.w() << " " << quat.x() << " " << quat.y() << " " << quat.z() << " " << tra[0] << " " << tra[1] << " " + << tra[2] << " " << intrID << " " << imageFilename << "\n"; - for(const auto& obs : perViewVisibility.at(viewID)) + for (const auto& obs : perViewVisibility.at(viewID)) { const auto& id = obs.first; const auto& pts = obs.second; @@ -337,10 +322,9 @@ void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const Compatib } } -void copyImagesFromSfmData(const sfmData::SfMData& sfmData, const std::string& destinationFolder, - const CompatibleList selection) +void copyImagesFromSfmData(const sfmData::SfMData& sfmData, const std::string& destinationFolder, const CompatibleList selection) { - for(const auto viewId : selection) + for (const auto viewId : selection) { const auto& view = sfmData.getView(viewId); const auto& from = fs::path(view.getImage().getImagePath()); @@ -349,18 +333,16 @@ void copyImagesFromSfmData(const sfmData::SfMData& sfmData, const std::string& d } } -void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, - const std::string& filename) +void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, const std::string& filename) { // adapted from Colmap's Reconstruction::WritePoints3DText() // The points3D.txt file has the following header - static const std::string points3dHeader = - "# 3D point list with one line of data per point:\n" - "# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)\n"; + static const std::string points3dHeader = "# 3D point list with one line of data per point:\n" + "# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)\n"; std::ofstream outfile(filename); - if(!outfile) + if (!outfile) { ALICEVISION_LOG_ERROR("Unable to create the 3D point file " << filename); throw std::runtime_error("Unable to create the 3D point file " + filename); @@ -370,23 +352,22 @@ void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const Compat // default reprojection error (not used) const double defaultError{-1.0}; - for(const auto& iter : sfmData.getLandmarks()) + for (const auto& iter : sfmData.getLandmarks()) { const IndexT id = iter.first; const Vec3 exportPoint = iter.second.X; const auto pointColor = iter.second.rgb; - outfile << id << " " << exportPoint.x() << " " << exportPoint.y() << " " << exportPoint.z() << " " - << static_cast(pointColor.r()) << " " << static_cast(pointColor.g()) << " " - << static_cast(pointColor.b()) << " "; + outfile << id << " " << exportPoint.x() << " " << exportPoint.y() << " " << exportPoint.z() << " " << static_cast(pointColor.r()) << " " + << static_cast(pointColor.g()) << " " << static_cast(pointColor.b()) << " "; outfile << defaultError; - for(const auto& itObs : iter.second.observations) + for (const auto& itObs : iter.second.observations) { const IndexT viewId = itObs.first; const IndexT featId = itObs.second.id_feat; - if(viewSelections.find(viewId) != viewSelections.end()) + if (viewSelections.find(viewId) != viewSelections.end()) { outfile << " " << viewId << " " << featId; } @@ -395,8 +376,7 @@ void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const Compat } } -void generateColmapSceneFiles(const sfmData::SfMData& sfmData, const CompatibleList& viewsSelection, - const ColmapConfig& colmapParams) +void generateColmapSceneFiles(const sfmData::SfMData& sfmData, const CompatibleList& viewsSelection, const ColmapConfig& colmapParams) { generateColmapCamerasTxtFile(sfmData, colmapParams._camerasTxtPath); generateColmapImagesTxtFile(sfmData, viewsSelection, colmapParams._imagesTxtPath); @@ -406,7 +386,7 @@ void generateColmapSceneFiles(const sfmData::SfMData& sfmData, const CompatibleL void create_directories(const std::string& directory) { const bool ok = fs::create_directories(directory); - if(!ok) + if (!ok) { ALICEVISION_LOG_ERROR("Cannot create directory " << directory); throw std::runtime_error("Cannot create directory " + directory); @@ -417,7 +397,7 @@ void convertToColmapScene(const sfmData::SfMData& sfmData, const std::string& co { // retrieve the views that are compatible with Colmap and that can be exported const auto views2export = getColmapCompatibleViews(sfmData); - if(views2export.empty()) + if (views2export.empty()) { ALICEVISION_LOG_WARNING("No camera in the scene is compatible with Colmap"); return; @@ -434,7 +414,7 @@ void convertToColmapScene(const sfmData::SfMData& sfmData, const std::string& co create_directories(colmapParams._denseDirectory); create_directories(colmapParams._imagesDirectory); - if(copyImages) + if (copyImages) { ALICEVISION_LOG_INFO("Copying source images..."); copyImagesFromSfmData(sfmData, colmapParams._imagesDirectory, views2export); @@ -444,5 +424,5 @@ void convertToColmapScene(const sfmData::SfMData& sfmData, const std::string& co generateColmapSceneFiles(sfmData, views2export, colmapParams); } -} -} +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/colmap.hpp b/src/aliceVision/sfmDataIO/colmap.hpp index 106fc0d11c..6c351cca85 100644 --- a/src/aliceVision/sfmDataIO/colmap.hpp +++ b/src/aliceVision/sfmDataIO/colmap.hpp @@ -14,8 +14,8 @@ #include #include -namespace aliceVision{ -namespace sfmDataIO{ +namespace aliceVision { +namespace sfmDataIO { using CompatibleList = std::unordered_set; @@ -108,8 +108,7 @@ PerViewVisibility computePerViewVisibility(const sfmData::SfMData& sfmData, cons * @param[in] viewSelections a selection of view IDs that have compatible intrinsics with Colmap. * @param[in] filename the filename where to save the scene (usually a images.txt file) */ -void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, - const std::string& filename); +void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, const std::string& filename); /** * @brief Given an sfm scene and a selection of its views that are compatible with Colmap, it copies the source images to @@ -118,8 +117,7 @@ void generateColmapImagesTxtFile(const sfmData::SfMData& sfmData, const Compatib * @param[in] destinationFolder the folder where to copy the images. * @param[in] selection a selection of view IDs that have compatible intrinsics with Colmap. */ -void copyImagesFromSfmData(const sfmData::SfMData& sfmData, const std::string& destinationFolder, - const CompatibleList selection); +void copyImagesFromSfmData(const sfmData::SfMData& sfmData, const std::string& destinationFolder, const CompatibleList selection); /** * @brief Given an sfm scene and a selection of its views that are compatible with Colmap, it generates the points3d.txt @@ -128,8 +126,7 @@ void copyImagesFromSfmData(const sfmData::SfMData& sfmData, const std::string& d * @param[in] viewSelections a selection of view IDs that have compatible intrinsics with Colmap. * @param[in] filename the filename where to save the points (usually a points3d.txt file) */ -void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, - const std::string& filename); +void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const CompatibleList& viewSelections, const std::string& filename); /** * @brief Given an sfm scene and a selection of its views that are compatible with Colmap, it generates all the Colmap @@ -138,8 +135,7 @@ void generateColmapPoints3DTxtFile(const sfmData::SfMData& sfmData, const Compat * @param viewsSelection a selection of view IDs that have compatible intrinsics with Colmap. * @param colmapParams the configuration data for the Colmap scene. */ -void generateColmapSceneFiles(const sfmData::SfMData& sfmData, const CompatibleList& viewsSelection, - const ColmapConfig& colmapParams); +void generateColmapSceneFiles(const sfmData::SfMData& sfmData, const CompatibleList& viewsSelection, const ColmapConfig& colmapParams); /** * @brief iven an sfm scene it generate the folder structure and all the files in Colmap format. @@ -151,5 +147,5 @@ void generateColmapSceneFiles(const sfmData::SfMData& sfmData, const CompatibleL */ void convertToColmapScene(const sfmData::SfMData& sfmData, const std::string& colmapBaseDir, bool copyImages); -} -} \ No newline at end of file +} // namespace sfmDataIO +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfmDataIO/colmap_test.cpp b/src/aliceVision/sfmDataIO/colmap_test.cpp index 0650ed1f5a..9f4be1d869 100644 --- a/src/aliceVision/sfmDataIO/colmap_test.cpp +++ b/src/aliceVision/sfmDataIO/colmap_test.cpp @@ -6,7 +6,6 @@ #define BOOST_TEST_MODULE colmap - #include #include @@ -18,25 +17,24 @@ using namespace aliceVision; BOOST_AUTO_TEST_CASE(colmap_isCompatible) { - const std::map compatibility { - {camera::EINTRINSIC::PINHOLE_CAMERA, true}, - {camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL1, true}, - {camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, true}, - {camera::EINTRINSIC::PINHOLE_CAMERA_BROWN, true}, - {camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE, true}, - {camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE1, true}, - {camera::EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4, false}, - {camera::EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD, false}, - {camera::EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4, false}, - {camera::EINTRINSIC::EQUIDISTANT_CAMERA, false}, - {camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, false}, + const std::map compatibility{ + {camera::EINTRINSIC::PINHOLE_CAMERA, true}, + {camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL1, true}, + {camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, true}, + {camera::EINTRINSIC::PINHOLE_CAMERA_BROWN, true}, + {camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE, true}, + {camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE1, true}, + {camera::EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4, false}, + {camera::EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD, false}, + {camera::EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4, false}, + {camera::EINTRINSIC::EQUIDISTANT_CAMERA, false}, + {camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, false}, }; - for(const auto& c : compatibility) + for (const auto& c : compatibility) { BOOST_CHECK(sfmDataIO::isColmapCompatible(c.first) == c.second); } - } BOOST_AUTO_TEST_CASE(colmap_convertIntrinsicsToColmapString) @@ -45,72 +43,57 @@ BOOST_AUTO_TEST_CASE(colmap_convertIntrinsicsToColmapString) auto& intrTest = sfmTest.getIntrinsics(); BOOST_CHECK(intrTest.empty()); // add some compatible intrinsics - intrTest.emplace(10, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54)); - intrTest.emplace(11, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL1, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.02078})); - intrTest.emplace(12, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.02078, 0.1705, -0.00714})); - intrTest.emplace(13, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_BROWN, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.02078, 0.1705, -0.00714, 0.00134, -0.000542})); - intrTest.emplace(14, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.02078, 0.1705, -0.00714, 0.00134})); + intrTest.emplace(10, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, 1920, 1080, 1548.76, 1547.32, 992.36, 549.54)); + intrTest.emplace(11, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL1, 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, {-0.02078})); + intrTest.emplace( + 12, + camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, {-0.02078, 0.1705, -0.00714})); + intrTest.emplace( + 13, + camera::createPinhole( + camera::EINTRINSIC::PINHOLE_CAMERA_BROWN, 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, {-0.02078, 0.1705, -0.00714, 0.00134, -0.000542})); + intrTest.emplace( + 14, + camera::createPinhole( + camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE, 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, {-0.02078, 0.1705, -0.00714, 0.00134})); intrTest.emplace(15, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE1, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.000542})); + camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE1, 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, {-0.000542})); // add some incompatible intrinsics - intrTest.emplace(20, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4, - 1920, 1080, 0., 0., 0., 0.)); + intrTest.emplace(20, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA_3DEANAMORPHIC4, 1920, 1080, 0., 0., 0., 0.)); intrTest.emplace(21, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.02078, 0.1705, -0.00714, 0.00134, -0.000542})); + camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA_3DECLASSICLD, + 1920, + 1080, + 1548.76, + 1547.32, + 992.36, + 549.54, + {-0.02078, 0.1705, -0.00714, 0.00134, -0.000542})); intrTest.emplace(22, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4, - 1920, 1080, 1548.76, 1547.32, 992.36, 549.54, - {-0.02078, 0.1705, -0.00714, 0.00134, -0.00714, 0.00134})); - intrTest.emplace(23, - camera::createEquidistant( - camera::EINTRINSIC::EQUIDISTANT_CAMERA, - 1920, 1080, 1548.76, 549.54, -0.02078)); - intrTest.emplace(24, - camera::createEquidistant( - camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, - 1920, 1080, 1548.76, 549.54, -0.02078, - {0.1705, -0.00714, 0.00134})); + camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA_3DERADIAL4, + 1920, + 1080, + 1548.76, + 1547.32, + 992.36, + 549.54, + {-0.02078, 0.1705, -0.00714, 0.00134, -0.00714, 0.00134})); + intrTest.emplace(23, camera::createEquidistant(camera::EINTRINSIC::EQUIDISTANT_CAMERA, 1920, 1080, 1548.76, 549.54, -0.02078)); + intrTest.emplace( + 24, + camera::createEquidistant(camera::EINTRINSIC::EQUIDISTANT_CAMERA_RADIAL3, 1920, 1080, 1548.76, 549.54, -0.02078, {0.1705, -0.00714, 0.00134})); // reference for each intrinsic ID the relevant expected string const std::map stringRef{ - {10, "10 PINHOLE 1920 1080 1548.76 1547.32 1952.36 1089.54\n"}, - {11, "11 FULL_OPENCV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n"}, - {12, "12 FULL_OPENCV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.1705 0 0 -0.00714 0 0 0\n"}, - {13, "13 FULL_OPENCV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.1705 0.00134 -0.000542 -0.00714 0 0 0\n"}, - {14, "14 OPENCV_FISHEYE 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.1705 -0.00714 0.00134\n"}, - {15, "15 FOV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.000542\n"} - }; + {10, "10 PINHOLE 1920 1080 1548.76 1547.32 1952.36 1089.54\n"}, + {11, "11 FULL_OPENCV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n"}, + {12, "12 FULL_OPENCV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.1705 0 0 -0.00714 0 0 0\n"}, + {13, "13 FULL_OPENCV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.1705 0.00134 -0.000542 -0.00714 0 0 0\n"}, + {14, "14 OPENCV_FISHEYE 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.02078 0.1705 -0.00714 0.00134\n"}, + {15, "15 FOV 1920 1080 1548.76 1547.32 1952.36 1089.54 -0.000542\n"}}; // test the string conversion - for(const auto& toTest : stringRef) + for (const auto& toTest : stringRef) { const auto id = toTest.first; const auto& ref = toTest.second; @@ -119,11 +102,11 @@ BOOST_AUTO_TEST_CASE(colmap_convertIntrinsicsToColmapString) } // test throw if not compatible - for(const auto& item : intrTest) + for (const auto& item : intrTest) { const auto id = item.first; const auto& intr = item.second; - if(id >= 20) + if (id >= 20) { BOOST_CHECK_THROW(sfmDataIO::convertIntrinsicsToColmapString(id, intr), std::invalid_argument); } @@ -137,7 +120,7 @@ BOOST_AUTO_TEST_CASE(colmap_convertIntrinsicsToColmapString) { const auto compIntr = sfmDataIO::getColmapCompatibleIntrinsics(sfmTest); BOOST_CHECK(compIntr.size() == 6); - for(const auto& id : compIntr) + for (const auto& id : compIntr) { BOOST_CHECK(id < 20); } @@ -146,10 +129,10 @@ BOOST_AUTO_TEST_CASE(colmap_convertIntrinsicsToColmapString) // test compatible views { // add some fake view for each intrinsics and test get compatible views - for(const auto& item : intrTest) + for (const auto& item : intrTest) { const auto intrID = item.first; - for(IndexT cam = 0; cam < 5; ++cam) + for (IndexT cam = 0; cam < 5; ++cam) { const auto camID = intrID * 10 + cam; sfmTest.getViews().emplace(camID, std::make_shared("", cam, intrID, camID)); @@ -160,7 +143,7 @@ BOOST_AUTO_TEST_CASE(colmap_convertIntrinsicsToColmapString) const auto compatibleViews = sfmDataIO::getColmapCompatibleViews(sfmTest); BOOST_CHECK(compatibleViews.size() == 30); // check the retrieved views are compatible - for(const auto& id : compatibleViews) + for (const auto& id : compatibleViews) { const auto intrID = sfmTest.getViews().at(id)->getIntrinsicId(); BOOST_CHECK(sfmDataIO::isColmapCompatible(sfmTest.getIntrinsics().at(intrID)->getType())); diff --git a/src/aliceVision/sfmDataIO/gtIO.cpp b/src/aliceVision/sfmDataIO/gtIO.cpp index ca276e226a..904a3aa876 100644 --- a/src/aliceVision/sfmDataIO/gtIO.cpp +++ b/src/aliceVision/sfmDataIO/gtIO.cpp @@ -22,102 +22,96 @@ namespace sfmDataIO { bool read_aliceVision_Camera(const std::string& camName, camera::Pinhole& cam, geometry::Pose3& pose) { - std::vector val; - if (fs::extension(camName) == ".bin") - { - std::ifstream in(camName, std::ios::in|std::ios::binary); - if (!in.is_open()) + std::vector val; + if (fs::extension(camName) == ".bin") { - ALICEVISION_LOG_ERROR("Failed to open file '" << camName << "' for reading"); - return false; + std::ifstream in(camName, std::ios::in | std::ios::binary); + if (!in.is_open()) + { + ALICEVISION_LOG_ERROR("Failed to open file '" << camName << "' for reading"); + return false; + } + val.resize(12); + in.read((char*)&val[0], (std::streamsize)12 * sizeof(double)); + if (in.fail()) + { + val.clear(); + return false; + } } - val.resize(12); - in.read((char*)&val[0],(std::streamsize)12*sizeof(double)); - if (in.fail()) + else { - val.clear(); - return false; + std::ifstream ifs; + ifs.open(camName, std::ifstream::in); + if (!ifs.is_open()) + { + ALICEVISION_LOG_ERROR("Failed to open file '" << camName << "' for reading"); + return false; + } + while (ifs.good()) + { + double valT; + ifs >> valT; + if (!ifs.fail()) + val.push_back(valT); + } } - } - else - { - std::ifstream ifs; - ifs.open( camName, std::ifstream::in); - if (!ifs.is_open()) { - ALICEVISION_LOG_ERROR("Failed to open file '" << camName << "' for reading"); - return false; + + // Update the camera from file value + Mat34 P; + if (fs::extension(camName) == ".bin") + { + P << val[0], val[3], val[6], val[9], val[1], val[4], val[7], val[10], val[2], val[5], val[8], val[11]; } - while (ifs.good()) + else { - double valT; - ifs >> valT; - if (!ifs.fail()) - val.push_back(valT); + P << val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8], val[9], val[10], val[11]; } - } - - //Update the camera from file value - Mat34 P; - if (fs::extension(camName) == ".bin") - { - P << val[0], val[3], val[6], val[9], - val[1], val[4], val[7], val[10], - val[2], val[5], val[8], val[11]; - } - else - { - P << val[0], val[1], val[2], val[3], - val[4], val[5], val[6], val[7], - val[8], val[9], val[10], val[11]; - } - Mat3 K, R; - Vec3 t; - KRt_from_P(P, K, R, t); - cam = camera::Pinhole(0,0,K); - // K.transpose() is applied to give [R t] to the constructor instead of P = K [R t] - Mat4 T = Mat4::Identity(); - T.block<3, 4>(0, 0) = K.transpose() * P; - pose = geometry::Pose3(T); - return true; + Mat3 K, R; + Vec3 t; + KRt_from_P(P, K, R, t); + cam = camera::Pinhole(0, 0, K); + // K.transpose() is applied to give [R t] to the constructor instead of P = K [R t] + Mat4 T = Mat4::Identity(); + T.block<3, 4>(0, 0) = K.transpose() * P; + pose = geometry::Pose3(T); + return true; } bool read_Strecha_Camera(const std::string& camName, camera::Pinhole& cam, geometry::Pose3& pose) { - std::ifstream ifs; - ifs.open(camName, std::ifstream::in); - if (!ifs.is_open()) { - ALICEVISION_LOG_ERROR("Failed to open file '" << camName << "' for reading"); - return false; - } - std::vector val; - while (ifs.good() && !ifs.eof()) - { - double valT; - ifs >> valT; - if (!ifs.fail()) - val.push_back(valT); - } - - if (!(val.size() == 3*3 +3 +3*3 +3 + 3 || val.size() == 26)) //Strecha cam - { - ALICEVISION_LOG_ERROR("File " << camName << " is not in Stretcha format ! "); - return false; - } - Mat3 K, R; - K << val[0], val[1], val[2], - val[3], val[4], val[5], - val[6], val[7], val[8]; - R << val[12], val[13], val[14], - val[15], val[16], val[17], - val[18], val[19], val[20]; - - Vec3 C (val[21], val[22], val[23]); - // R need to be transposed - cam = camera::Pinhole(0,0,K); - cam.setWidth(val[24]); - cam.setHeight(val[25]); - pose = geometry::Pose3(R.transpose(), C); - return true; + std::ifstream ifs; + ifs.open(camName, std::ifstream::in); + if (!ifs.is_open()) + { + ALICEVISION_LOG_ERROR("Failed to open file '" << camName << "' for reading"); + return false; + } + std::vector val; + while (ifs.good() && !ifs.eof()) + { + double valT; + ifs >> valT; + if (!ifs.fail()) + val.push_back(valT); + } + + if (!(val.size() == 3 * 3 + 3 + 3 * 3 + 3 + 3 || val.size() == 26)) // Strecha cam + { + ALICEVISION_LOG_ERROR("File " << camName << " is not in Stretcha format ! "); + return false; + } + Mat3 K, R; + K << val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8]; + R << val[12], val[13], val[14], val[15], val[16], val[17], val[18], val[19], val[20]; + + Vec3 C(val[21], val[22], val[23]); + // R need to be transposed + cam = camera::Pinhole(0, 0, K); + cam.setWidth(val[24]); + cam.setHeight(val[25]); + pose = geometry::Pose3(R.transpose(), C); + return true; } /** @@ -129,102 +123,101 @@ bool read_Strecha_Camera(const std::string& camName, camera::Pinhole& cam, geome **/ bool readGt(const std::string& rootPath, sfmData::SfMData& sfmData, bool useUID) { - // IF GT_Folder exists, perform evaluation of the quality of rotation estimates - const std::string sGTPath = (fs::path(rootPath) / "gt_dense_cameras").string(); - if (!fs::is_directory(sGTPath)) - { - ALICEVISION_LOG_WARNING("There is no valid GT data to read from " << sGTPath); - return false; - } - - // Switch between case to choose the file reader according to the file types in GT path - bool (*fcnReadCamPtr)(const std::string &, camera::Pinhole &, geometry::Pose3&); - std::string suffix; - - const boost::regex binFilter(".*.bin"); - const boost::regex camFilter(".*.camera"); - - std::vector binFiles; - std::vector camFiles; - - boost::filesystem::directory_iterator endItr; - for(boost::filesystem::directory_iterator i(sGTPath); i != endItr; ++i) - { - if(!boost::filesystem::is_regular_file(i->status())) - continue; - - boost::smatch what; - - if(boost::regex_match(i->path().filename().string(), what, binFilter)) - { - binFiles.push_back(i->path().filename().string()); - continue; - } - - if(boost::regex_match(i->path().filename().string(), what, camFilter)) - { - camFiles.push_back(i->path().filename().string()); - } - } - - std::vector& vec_camfilenames = binFiles; - - if(!binFiles.empty()) - { - ALICEVISION_LOG_TRACE("Using aliceVision Camera"); - fcnReadCamPtr = &read_aliceVision_Camera; - suffix = "bin"; - } - else if(!camFiles.empty()) - { - ALICEVISION_LOG_TRACE("Using Strechas Camera"); - fcnReadCamPtr = &read_Strecha_Camera; - suffix = "camera"; - vec_camfilenames = camFiles; - } - else - { - throw std::logic_error(std::string("No camera found in ") + sGTPath); - } - - ALICEVISION_LOG_TRACE("Read rotation and translation estimates"); - const std::string imgPath = (fs::path(rootPath) / "images").string(); - std::map< std::string, Mat3 > map_R_gt; - //Try to read .suffix camera (parse camera names) - - std::sort(vec_camfilenames.begin(), vec_camfilenames.end()); - if (vec_camfilenames.empty()) - { - ALICEVISION_LOG_WARNING("No camera found in " << sGTPath); - return false; - } - IndexT index = 0; - for (std::vector::const_iterator iter = vec_camfilenames.begin(); - iter != vec_camfilenames.end(); ++iter, ++index) - { - geometry::Pose3 pose; - std::shared_ptr pinholeIntrinsic = camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA); - bool loaded = fcnReadCamPtr((fs::path(sGTPath) / *iter).string(), *pinholeIntrinsic.get(), pose); - if (!loaded) + // IF GT_Folder exists, perform evaluation of the quality of rotation estimates + const std::string sGTPath = (fs::path(rootPath) / "gt_dense_cameras").string(); + if (!fs::is_directory(sGTPath)) + { + ALICEVISION_LOG_WARNING("There is no valid GT data to read from " << sGTPath); + return false; + } + + // Switch between case to choose the file reader according to the file types in GT path + bool (*fcnReadCamPtr)(const std::string&, camera::Pinhole&, geometry::Pose3&); + std::string suffix; + + const boost::regex binFilter(".*.bin"); + const boost::regex camFilter(".*.camera"); + + std::vector binFiles; + std::vector camFiles; + + boost::filesystem::directory_iterator endItr; + for (boost::filesystem::directory_iterator i(sGTPath); i != endItr; ++i) { - ALICEVISION_LOG_ERROR("Failed to load: " << *iter); - return false; + if (!boost::filesystem::is_regular_file(i->status())) + continue; + + boost::smatch what; + + if (boost::regex_match(i->path().filename().string(), what, binFilter)) + { + binFiles.push_back(i->path().filename().string()); + continue; + } + + if (boost::regex_match(i->path().filename().string(), what, camFilter)) + { + camFiles.push_back(i->path().filename().string()); + } } - const std::string imgName = fs::path(*iter).stem().string(); - const std::string imgFile = (fs::path(imgPath) / imgName).string(); + std::vector& vec_camfilenames = binFiles; - std::shared_ptr viewPtr = std::make_shared(imgFile, UndefinedIndexT, index); + if (!binFiles.empty()) + { + ALICEVISION_LOG_TRACE("Using aliceVision Camera"); + fcnReadCamPtr = &read_aliceVision_Camera; + suffix = "bin"; + } + else if (!camFiles.empty()) + { + ALICEVISION_LOG_TRACE("Using Strechas Camera"); + fcnReadCamPtr = &read_Strecha_Camera; + suffix = "camera"; + vec_camfilenames = camFiles; + } + else + { + throw std::logic_error(std::string("No camera found in ") + sGTPath); + } - updateIncompleteView(*viewPtr, EViewIdMethod::FILENAME, ".*?(\\d+)"); + ALICEVISION_LOG_TRACE("Read rotation and translation estimates"); + const std::string imgPath = (fs::path(rootPath) / "images").string(); + std::map map_R_gt; + // Try to read .suffix camera (parse camera names) - // Update intrinsics with width and height of image - sfmData.getViews().emplace(viewPtr->getViewId(), viewPtr); - sfmData.setPose(*sfmData.getViews().at(viewPtr->getViewId()), sfmData::CameraPose(pose)); - sfmData.getIntrinsics().emplace(index, pinholeIntrinsic); - } - return true; + std::sort(vec_camfilenames.begin(), vec_camfilenames.end()); + if (vec_camfilenames.empty()) + { + ALICEVISION_LOG_WARNING("No camera found in " << sGTPath); + return false; + } + IndexT index = 0; + for (std::vector::const_iterator iter = vec_camfilenames.begin(); iter != vec_camfilenames.end(); ++iter, ++index) + { + geometry::Pose3 pose; + std::shared_ptr pinholeIntrinsic = camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA); + bool loaded = fcnReadCamPtr((fs::path(sGTPath) / *iter).string(), *pinholeIntrinsic.get(), pose); + if (!loaded) + { + ALICEVISION_LOG_ERROR("Failed to load: " << *iter); + return false; + } + + const std::string imgName = fs::path(*iter).stem().string(); + const std::string imgFile = (fs::path(imgPath) / imgName).string(); + + std::shared_ptr viewPtr = std::make_shared(imgFile, UndefinedIndexT, index); + + updateIncompleteView(*viewPtr, EViewIdMethod::FILENAME, ".*?(\\d+)"); + + // Update intrinsics with width and height of image + sfmData.getViews().emplace(viewPtr->getViewId(), viewPtr); + sfmData.setPose(*sfmData.getViews().at(viewPtr->getViewId()), sfmData::CameraPose(pose)); + sfmData.getIntrinsics().emplace(index, pinholeIntrinsic); + } + return true; } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/gtIO.hpp b/src/aliceVision/sfmDataIO/gtIO.hpp index d1911f120a..40ff7dbd59 100644 --- a/src/aliceVision/sfmDataIO/gtIO.hpp +++ b/src/aliceVision/sfmDataIO/gtIO.hpp @@ -29,5 +29,5 @@ bool read_Strecha_Camera(const std::string& camName, camera::Pinhole& cam, geome **/ bool readGt(const std::string& rootPath, sfmData::SfMData& sfmData, bool useUID = true); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/jsonIO.cpp b/src/aliceVision/sfmDataIO/jsonIO.cpp index 4c90f0a96c..c4013a3580 100644 --- a/src/aliceVision/sfmDataIO/jsonIO.cpp +++ b/src/aliceVision/sfmDataIO/jsonIO.cpp @@ -18,731 +18,740 @@ namespace sfmDataIO { void saveView(const std::string& name, const sfmData::View& view, bpt::ptree& parentTree) { - bpt::ptree viewTree; + bpt::ptree viewTree; - if(view.getViewId() != UndefinedIndexT) - viewTree.put("viewId", view.getViewId()); + if (view.getViewId() != UndefinedIndexT) + viewTree.put("viewId", view.getViewId()); - if(view.getPoseId() != UndefinedIndexT) - viewTree.put("poseId", view.getPoseId()); + if (view.getPoseId() != UndefinedIndexT) + viewTree.put("poseId", view.getPoseId()); - if(view.isPartOfRig()) - { - viewTree.put("rigId", view.getRigId()); - viewTree.put("subPoseId", view.getSubPoseId()); - } - - if(view.getFrameId() != UndefinedIndexT) - viewTree.put("frameId", view.getFrameId()); + if (view.isPartOfRig()) + { + viewTree.put("rigId", view.getRigId()); + viewTree.put("subPoseId", view.getSubPoseId()); + } - if(view.getIntrinsicId() != UndefinedIndexT) - viewTree.put("intrinsicId", view.getIntrinsicId()); + if (view.getFrameId() != UndefinedIndexT) + viewTree.put("frameId", view.getFrameId()); - if(view.getResectionId() != UndefinedIndexT) - viewTree.put("resectionId", view.getResectionId()); + if (view.getIntrinsicId() != UndefinedIndexT) + viewTree.put("intrinsicId", view.getIntrinsicId()); - if(view.isPoseIndependant() == false) - viewTree.put("isPoseIndependant", view.isPoseIndependant()); + if (view.getResectionId() != UndefinedIndexT) + viewTree.put("resectionId", view.getResectionId()); - viewTree.put("path", view.getImage().getImagePath()); - viewTree.put("width", view.getImage().getWidth()); - viewTree.put("height", view.getImage().getHeight()); + if (view.isPoseIndependant() == false) + viewTree.put("isPoseIndependant", view.isPoseIndependant()); - // metadata - { - bpt::ptree metadataTree; + viewTree.put("path", view.getImage().getImagePath()); + viewTree.put("width", view.getImage().getWidth()); + viewTree.put("height", view.getImage().getHeight()); - for(const auto& metadataPair : view.getImage().getMetadata()) - metadataTree.put(metadataPair.first, metadataPair.second); + // metadata + { + bpt::ptree metadataTree; - viewTree.add_child("metadata", metadataTree); - } + for (const auto& metadataPair : view.getImage().getMetadata()) + metadataTree.put(metadataPair.first, metadataPair.second); - // ancestors - if (!view.getAncestors().empty()) - { - bpt::ptree ancestorsTree; + viewTree.add_child("metadata", metadataTree); + } - for(const auto & ancestor : view.getAncestors()) + // ancestors + if (!view.getAncestors().empty()) { - bpt::ptree ancestorTree; - ancestorTree.put("", ancestor); - ancestorsTree.push_back(std::make_pair("", ancestorTree)); - } + bpt::ptree ancestorsTree; - viewTree.add_child("ancestors", ancestorsTree); - } + for (const auto& ancestor : view.getAncestors()) + { + bpt::ptree ancestorTree; + ancestorTree.put("", ancestor); + ancestorsTree.push_back(std::make_pair("", ancestorTree)); + } - parentTree.push_back(std::make_pair(name, viewTree)); + viewTree.add_child("ancestors", ancestorsTree); + } + + parentTree.push_back(std::make_pair(name, viewTree)); } void loadView(sfmData::View& view, bpt::ptree& viewTree) { - view.setViewId(viewTree.get("viewId", UndefinedIndexT)); - view.setPoseId(viewTree.get("poseId", UndefinedIndexT)); + view.setViewId(viewTree.get("viewId", UndefinedIndexT)); + view.setPoseId(viewTree.get("poseId", UndefinedIndexT)); - if(viewTree.count("rigId")) - { - view.setRigAndSubPoseId( - viewTree.get("rigId"), - viewTree.get("subPoseId")); - } + if (viewTree.count("rigId")) + { + view.setRigAndSubPoseId(viewTree.get("rigId"), viewTree.get("subPoseId")); + } - view.setFrameId(viewTree.get("frameId", UndefinedIndexT)); - view.setIntrinsicId(viewTree.get("intrinsicId", UndefinedIndexT)); - view.setResectionId(viewTree.get("resectionId", UndefinedIndexT)); - view.setIndependantPose(viewTree.get("isPoseIndependant", true)); + view.setFrameId(viewTree.get("frameId", UndefinedIndexT)); + view.setIntrinsicId(viewTree.get("intrinsicId", UndefinedIndexT)); + view.setResectionId(viewTree.get("resectionId", UndefinedIndexT)); + view.setIndependantPose(viewTree.get("isPoseIndependant", true)); - view.getImage().setImagePath(viewTree.get("path")); - view.getImage().setWidth(viewTree.get("width", 0)); - view.getImage().setHeight(viewTree.get("height", 0)); + view.getImage().setImagePath(viewTree.get("path")); + view.getImage().setWidth(viewTree.get("width", 0)); + view.getImage().setHeight(viewTree.get("height", 0)); - if (viewTree.count("ancestors")) - { - for (bpt::ptree::value_type &ancestor : viewTree.get_child("ancestors")) + if (viewTree.count("ancestors")) { - view.addAncestor(ancestor.second.get_value()); + for (bpt::ptree::value_type& ancestor : viewTree.get_child("ancestors")) + { + view.addAncestor(ancestor.second.get_value()); + } } - } - - // metadata - if(viewTree.count("metadata")) - for(bpt::ptree::value_type &metaDataNode : viewTree.get_child("metadata")) - view.getImage().addMetadata(metaDataNode.first, metaDataNode.second.data()); + // metadata + if (viewTree.count("metadata")) + for (bpt::ptree::value_type& metaDataNode : viewTree.get_child("metadata")) + view.getImage().addMetadata(metaDataNode.first, metaDataNode.second.data()); } void saveIntrinsic(const std::string& name, IndexT intrinsicId, const std::shared_ptr& intrinsic, bpt::ptree& parentTree) { - bpt::ptree intrinsicTree; - - camera::EINTRINSIC intrinsicType = intrinsic->getType(); - - intrinsicTree.put("intrinsicId", intrinsicId); - intrinsicTree.put("width", intrinsic->w()); - intrinsicTree.put("height", intrinsic->h()); - intrinsicTree.put("sensorWidth", intrinsic->sensorWidth()); - intrinsicTree.put("sensorHeight", intrinsic->sensorHeight()); - intrinsicTree.put("serialNumber", intrinsic->serialNumber()); - intrinsicTree.put("type", camera::EINTRINSIC_enumToString(intrinsicType)); - intrinsicTree.put("initializationMode", camera::EInitMode_enumToString(intrinsic->getInitializationMode())); - - std::shared_ptr intrinsicScaleOffset = std::dynamic_pointer_cast(intrinsic); - if (intrinsicScaleOffset) - { - - const double initialFocalLengthMM = (intrinsicScaleOffset->getInitialScale().x() > 0) ? intrinsicScaleOffset->sensorWidth() * intrinsicScaleOffset->getInitialScale().x() / double(intrinsic->w()): -1; - const double focalLengthMM = intrinsicScaleOffset->sensorWidth() * intrinsicScaleOffset->getScale().x() / double(intrinsic->w()); - const double focalRatio = intrinsicScaleOffset->getScale().x() / intrinsicScaleOffset->getScale().y(); - const double pixelAspectRatio = 1.0 / focalRatio; - - intrinsicTree.put("initialFocalLength", initialFocalLengthMM); - intrinsicTree.put("focalLength", focalLengthMM); - intrinsicTree.put("pixelRatio", pixelAspectRatio); - intrinsicTree.put("pixelRatioLocked", intrinsicScaleOffset->isRatioLocked()); - - saveMatrix("principalPoint", intrinsicScaleOffset->getOffset(), intrinsicTree); - } - - std::shared_ptr intrinsicScaleOffsetDisto = std::dynamic_pointer_cast(intrinsic); - if (intrinsicScaleOffsetDisto) - { - bpt::ptree distParamsTree; - - std::shared_ptr distortionObject = intrinsicScaleOffsetDisto->getDistortion(); - if (distortionObject) - { - for (double param : distortionObject->getParameters()) + bpt::ptree intrinsicTree; + + camera::EINTRINSIC intrinsicType = intrinsic->getType(); + + intrinsicTree.put("intrinsicId", intrinsicId); + intrinsicTree.put("width", intrinsic->w()); + intrinsicTree.put("height", intrinsic->h()); + intrinsicTree.put("sensorWidth", intrinsic->sensorWidth()); + intrinsicTree.put("sensorHeight", intrinsic->sensorHeight()); + intrinsicTree.put("serialNumber", intrinsic->serialNumber()); + intrinsicTree.put("type", camera::EINTRINSIC_enumToString(intrinsicType)); + intrinsicTree.put("initializationMode", camera::EInitMode_enumToString(intrinsic->getInitializationMode())); + + std::shared_ptr intrinsicScaleOffset = std::dynamic_pointer_cast(intrinsic); + if (intrinsicScaleOffset) + { + const double initialFocalLengthMM = + (intrinsicScaleOffset->getInitialScale().x() > 0) + ? intrinsicScaleOffset->sensorWidth() * intrinsicScaleOffset->getInitialScale().x() / double(intrinsic->w()) + : -1; + const double focalLengthMM = intrinsicScaleOffset->sensorWidth() * intrinsicScaleOffset->getScale().x() / double(intrinsic->w()); + const double focalRatio = intrinsicScaleOffset->getScale().x() / intrinsicScaleOffset->getScale().y(); + const double pixelAspectRatio = 1.0 / focalRatio; + + intrinsicTree.put("initialFocalLength", initialFocalLengthMM); + intrinsicTree.put("focalLength", focalLengthMM); + intrinsicTree.put("pixelRatio", pixelAspectRatio); + intrinsicTree.put("pixelRatioLocked", intrinsicScaleOffset->isRatioLocked()); + + saveMatrix("principalPoint", intrinsicScaleOffset->getOffset(), intrinsicTree); + } + + std::shared_ptr intrinsicScaleOffsetDisto = + std::dynamic_pointer_cast(intrinsic); + if (intrinsicScaleOffsetDisto) + { + bpt::ptree distParamsTree; + + std::shared_ptr distortionObject = intrinsicScaleOffsetDisto->getDistortion(); + if (distortionObject) + { + for (double param : distortionObject->getParameters()) + { + bpt::ptree paramTree; + paramTree.put("", param); + distParamsTree.push_back(std::make_pair("", paramTree)); + } + } + intrinsicTree.put("distortionInitializationMode", + camera::EInitMode_enumToString(intrinsicScaleOffsetDisto->getDistortionInitializationMode())); + + intrinsicTree.add_child("distortionParams", distParamsTree); + + bpt::ptree undistParamsTree; + + std::shared_ptr undistortionObject = intrinsicScaleOffsetDisto->getUndistortion(); + if (undistortionObject) + { + saveMatrix("undistortionOffset", undistortionObject->getOffset(), intrinsicTree); + + for (double param : undistortionObject->getParameters()) + { + bpt::ptree paramTree; + paramTree.put("", param); + undistParamsTree.push_back(std::make_pair("", paramTree)); + } + } + else { - bpt::ptree paramTree; - paramTree.put("", param); - distParamsTree.push_back(std::make_pair("", paramTree)); + saveMatrix("undistortionOffset", Vec2{0.0, 0.0}, intrinsicTree); } + + intrinsicTree.add_child("undistortionParams", undistParamsTree); + } + + std::shared_ptr intrinsicEquidistant = std::dynamic_pointer_cast(intrinsic); + if (intrinsicEquidistant) + { + intrinsicTree.put("fisheyeCircleCenterX", intrinsicEquidistant->getCircleCenterX()); + intrinsicTree.put("fisheyeCircleCenterY", intrinsicEquidistant->getCircleCenterY()); + intrinsicTree.put("fisheyeCircleRadius", intrinsicEquidistant->getCircleRadius()); } - intrinsicTree.put("distortionInitializationMode", camera::EInitMode_enumToString(intrinsicScaleOffsetDisto->getDistortionInitializationMode())); - intrinsicTree.add_child("distortionParams", distParamsTree); + intrinsicTree.put("locked", intrinsic->isLocked()); - bpt::ptree undistParamsTree; + parentTree.push_back(std::make_pair(name, intrinsicTree)); +} - std::shared_ptr undistortionObject = intrinsicScaleOffsetDisto->getUndistortion(); - if (undistortionObject) +void loadIntrinsic(const Version& version, IndexT& intrinsicId, std::shared_ptr& intrinsic, bpt::ptree& intrinsicTree) +{ + intrinsicId = intrinsicTree.get("intrinsicId"); + const unsigned int width = intrinsicTree.get("width"); + const unsigned int height = intrinsicTree.get("height"); + const double sensorWidth = intrinsicTree.get("sensorWidth", 36.0); + const double sensorHeight = intrinsicTree.get("sensorHeight", 24.0); + const camera::EINTRINSIC intrinsicType = camera::EINTRINSIC_stringToEnum(intrinsicTree.get("type")); + const camera::EInitMode initializationMode = camera::EInitMode_stringToEnum( + intrinsicTree.get("initializationMode", camera::EInitMode_enumToString(camera::EInitMode::CALIBRATED))); + + // principal point + Vec2 principalPoint; + loadMatrix("principalPoint", principalPoint, intrinsicTree); + + if (version < Version(1, 2, 1)) { - saveMatrix("undistortionOffset", undistortionObject->getOffset(), intrinsicTree); + principalPoint[0] -= (double(width) / 2.0); + principalPoint[1] -= (double(height) / 2.0); + } - for (double param : undistortionObject->getParameters()) - { - bpt::ptree paramTree; - paramTree.put("", param); - undistParamsTree.push_back(std::make_pair("", paramTree)); - } + // Focal length + Vec2 pxFocalLength; + if (version < Version(1, 2, 0)) + { + pxFocalLength(0) = intrinsicTree.get("pxFocalLength", -1); + // Only one focal value for X and Y in previous versions + pxFocalLength(1) = pxFocalLength(0); } - else + else if (version < Version(1, 2, 2)) // version >= 1.2 { - saveMatrix("undistortionOffset", Vec2{0.0, 0.0}, intrinsicTree); + loadMatrix("pxFocalLength", pxFocalLength, intrinsicTree); + } + else if (version < Version(1, 2, 5)) + { + const double fmm = intrinsicTree.get("focalLength", 1.0); + // pixelRatio field was actually storing the focalRatio before version 1.2.5 + const double focalRatio = intrinsicTree.get("pixelRatio", 1.0); + + const double fx = (fmm / sensorWidth) * double(width); + const double fy = fx / focalRatio; + + pxFocalLength(0) = fx; + pxFocalLength(1) = fy; } + else + { + const double fmm = intrinsicTree.get("focalLength", 1.0); + const double pixelAspectRatio = intrinsicTree.get("pixelRatio", 1.0); - intrinsicTree.add_child("undistortionParams", undistParamsTree); - } + const double focalRatio = 1.0 / pixelAspectRatio; + const double fx = (fmm / sensorWidth) * double(width); + const double fy = fx / focalRatio; - std::shared_ptr intrinsicEquidistant = std::dynamic_pointer_cast(intrinsic); - if (intrinsicEquidistant) - { - intrinsicTree.put("fisheyeCircleCenterX", intrinsicEquidistant->getCircleCenterX()); - intrinsicTree.put("fisheyeCircleCenterY", intrinsicEquidistant->getCircleCenterY()); - intrinsicTree.put("fisheyeCircleRadius", intrinsicEquidistant->getCircleRadius()); - } + pxFocalLength(0) = fx; + pxFocalLength(1) = fy; + } - intrinsicTree.put("locked", intrinsic->isLocked()); + // pinhole parameters + intrinsic = camera::createIntrinsic(intrinsicType, width, height, pxFocalLength(0), pxFocalLength(1), principalPoint(0), principalPoint(1)); - parentTree.push_back(std::make_pair(name, intrinsicTree)); - -} + intrinsic->setSerialNumber(intrinsicTree.get("serialNumber")); + intrinsic->setInitializationMode(initializationMode); + intrinsic->setSensorWidth(sensorWidth); + intrinsic->setSensorHeight(sensorHeight); -void loadIntrinsic(const Version & version, IndexT& intrinsicId, std::shared_ptr& intrinsic, - bpt::ptree& intrinsicTree) -{ - intrinsicId = intrinsicTree.get("intrinsicId"); - const unsigned int width = intrinsicTree.get("width"); - const unsigned int height = intrinsicTree.get("height"); - const double sensorWidth = intrinsicTree.get("sensorWidth", 36.0); - const double sensorHeight = intrinsicTree.get("sensorHeight", 24.0); - const camera::EINTRINSIC intrinsicType = camera::EINTRINSIC_stringToEnum(intrinsicTree.get("type")); - const camera::EInitMode initializationMode = camera::EInitMode_stringToEnum(intrinsicTree.get("initializationMode", camera::EInitMode_enumToString(camera::EInitMode::CALIBRATED))); - - // principal point - Vec2 principalPoint; - loadMatrix("principalPoint", principalPoint, intrinsicTree); - - if (version < Version(1,2,1)) - { - principalPoint[0] -= (double(width) / 2.0); - principalPoint[1] -= (double(height) / 2.0); - } - - // Focal length - Vec2 pxFocalLength; - if (version < Version(1,2,0)) - { - pxFocalLength(0) = intrinsicTree.get("pxFocalLength", -1); - // Only one focal value for X and Y in previous versions - pxFocalLength(1) = pxFocalLength(0); - } - else if (version < Version(1,2,2)) // version >= 1.2 - { - loadMatrix("pxFocalLength", pxFocalLength, intrinsicTree); - } - else if (version < Version(1,2,5)) - { - const double fmm = intrinsicTree.get("focalLength", 1.0); - // pixelRatio field was actually storing the focalRatio before version 1.2.5 - const double focalRatio = intrinsicTree.get("pixelRatio", 1.0); - - const double fx = (fmm / sensorWidth) * double(width); - const double fy = fx / focalRatio; - - pxFocalLength(0) = fx; - pxFocalLength(1) = fy; - } - else - { - const double fmm = intrinsicTree.get("focalLength", 1.0); - const double pixelAspectRatio = intrinsicTree.get("pixelRatio", 1.0); - - const double focalRatio = 1.0 / pixelAspectRatio; - const double fx = (fmm / sensorWidth) * double(width); - const double fy = fx / focalRatio; - - pxFocalLength(0) = fx; - pxFocalLength(1) = fy; - } - - // pinhole parameters - intrinsic = camera::createIntrinsic(intrinsicType, width, height, pxFocalLength(0), pxFocalLength(1), principalPoint(0), principalPoint(1)); - - intrinsic->setSerialNumber(intrinsicTree.get("serialNumber")); - intrinsic->setInitializationMode(initializationMode); - intrinsic->setSensorWidth(sensorWidth); - intrinsic->setSensorHeight(sensorHeight); - - // intrinsic lock - if(intrinsicTree.get("locked", false)) { - intrinsic->lock(); - } - else { - intrinsic->unlock(); - } - - std::shared_ptr intrinsicWithScale = std::dynamic_pointer_cast(intrinsic); - if (intrinsicWithScale != nullptr) { - - if (version < Version(1, 2, 2)) - { - Vec2 initialFocalLengthPx; - initialFocalLengthPx(0) = intrinsicTree.get("pxInitialFocalLength"); - initialFocalLengthPx(1) = (initialFocalLengthPx(0) > 0)?initialFocalLengthPx(0) * pxFocalLength(1) / pxFocalLength(0):-1; - intrinsicWithScale->setInitialScale(initialFocalLengthPx); - } - else - { - double initialFocalLengthMM = intrinsicTree.get("initialFocalLength"); - - Vec2 initialFocalLengthPx; - initialFocalLengthPx(0) = (initialFocalLengthMM / sensorWidth) * double(width); - initialFocalLengthPx(1) = (initialFocalLengthPx(0) > 0)?initialFocalLengthPx(0) * pxFocalLength(1) / pxFocalLength(0):-1; - - intrinsicWithScale->setInitialScale(initialFocalLengthPx); - intrinsicWithScale->setRatioLocked(intrinsicTree.get("pixelRatioLocked")); - } - } - - // Load distortion - std::shared_ptr intrinsicWithDistoEnabled = std::dynamic_pointer_cast(intrinsic); - if (intrinsicWithDistoEnabled != nullptr) - { - const camera::EInitMode distortionInitializationMode = - camera::EInitMode_stringToEnum( - intrinsicTree.get("distortionInitializationMode", camera::EInitMode_enumToString(camera::EInitMode::NONE))); - - intrinsicWithDistoEnabled->setDistortionInitializationMode(distortionInitializationMode); - - std::shared_ptr distortionObject = intrinsicWithDistoEnabled->getDistortion(); - if (distortionObject) - { - std::vector distortionParams; - for (bpt::ptree::value_type ¶mNode : intrinsicTree.get_child("distortionParams")) + // intrinsic lock + if (intrinsicTree.get("locked", false)) + { + intrinsic->lock(); + } + else + { + intrinsic->unlock(); + } + + std::shared_ptr intrinsicWithScale = std::dynamic_pointer_cast(intrinsic); + if (intrinsicWithScale != nullptr) + { + if (version < Version(1, 2, 2)) { - distortionParams.emplace_back(paramNode.second.get_value()); + Vec2 initialFocalLengthPx; + initialFocalLengthPx(0) = intrinsicTree.get("pxInitialFocalLength"); + initialFocalLengthPx(1) = (initialFocalLengthPx(0) > 0) ? initialFocalLengthPx(0) * pxFocalLength(1) / pxFocalLength(0) : -1; + intrinsicWithScale->setInitialScale(initialFocalLengthPx); } - - // ensure that we have the right number of params - if (distortionParams.size() == distortionObject->getParameters().size()) + else { - distortionObject->setParameters(distortionParams); + double initialFocalLengthMM = intrinsicTree.get("initialFocalLength"); + + Vec2 initialFocalLengthPx; + initialFocalLengthPx(0) = (initialFocalLengthMM / sensorWidth) * double(width); + initialFocalLengthPx(1) = (initialFocalLengthPx(0) > 0) ? initialFocalLengthPx(0) * pxFocalLength(1) / pxFocalLength(0) : -1; + + intrinsicWithScale->setInitialScale(initialFocalLengthPx); + intrinsicWithScale->setRatioLocked(intrinsicTree.get("pixelRatioLocked")); } } - std::shared_ptr undistortionObject = intrinsicWithDistoEnabled->getUndistortion(); - if (undistortionObject) + // Load distortion + std::shared_ptr intrinsicWithDistoEnabled = + std::dynamic_pointer_cast(intrinsic); + if (intrinsicWithDistoEnabled != nullptr) { - std::vector undistortionParams; - for (bpt::ptree::value_type ¶mNode : intrinsicTree.get_child("undistortionParams")) + const camera::EInitMode distortionInitializationMode = camera::EInitMode_stringToEnum( + intrinsicTree.get("distortionInitializationMode", camera::EInitMode_enumToString(camera::EInitMode::NONE))); + + intrinsicWithDistoEnabled->setDistortionInitializationMode(distortionInitializationMode); + + std::shared_ptr distortionObject = intrinsicWithDistoEnabled->getDistortion(); + if (distortionObject) { - undistortionParams.emplace_back(paramNode.second.get_value()); + std::vector distortionParams; + for (bpt::ptree::value_type& paramNode : intrinsicTree.get_child("distortionParams")) + { + distortionParams.emplace_back(paramNode.second.get_value()); + } + + // ensure that we have the right number of params + if (distortionParams.size() == distortionObject->getParameters().size()) + { + distortionObject->setParameters(distortionParams); + } } - // ensure that we have the right number of params - if (undistortionParams.size() == undistortionObject->getParameters().size()) + std::shared_ptr undistortionObject = intrinsicWithDistoEnabled->getUndistortion(); + if (undistortionObject) { - undistortionObject->setParameters(undistortionParams); - Vec2 offset; - loadMatrix("undistortionOffset", offset, intrinsicTree); - undistortionObject->setOffset(offset); + std::vector undistortionParams; + for (bpt::ptree::value_type& paramNode : intrinsicTree.get_child("undistortionParams")) + { + undistortionParams.emplace_back(paramNode.second.get_value()); + } + + // ensure that we have the right number of params + if (undistortionParams.size() == undistortionObject->getParameters().size()) + { + undistortionObject->setParameters(undistortionParams); + Vec2 offset; + loadMatrix("undistortionOffset", offset, intrinsicTree); + undistortionObject->setOffset(offset); + } } } - } - // Load Equidistant params - std::shared_ptr intrinsicEquidistant = std::dynamic_pointer_cast(intrinsic); - if (intrinsicEquidistant != nullptr) - { - intrinsicEquidistant->setCircleCenterX(intrinsicTree.get("fisheyeCircleCenterX", 0.0)); - intrinsicEquidistant->setCircleCenterY(intrinsicTree.get("fisheyeCircleCenterY", 0.0)); - intrinsicEquidistant->setCircleRadius(intrinsicTree.get("fisheyeCircleRadius", 1.0)); - } + // Load Equidistant params + std::shared_ptr intrinsicEquidistant = std::dynamic_pointer_cast(intrinsic); + if (intrinsicEquidistant != nullptr) + { + intrinsicEquidistant->setCircleCenterX(intrinsicTree.get("fisheyeCircleCenterX", 0.0)); + intrinsicEquidistant->setCircleCenterY(intrinsicTree.get("fisheyeCircleCenterY", 0.0)); + intrinsicEquidistant->setCircleRadius(intrinsicTree.get("fisheyeCircleRadius", 1.0)); + } } void saveRig(const std::string& name, IndexT rigId, const sfmData::Rig& rig, bpt::ptree& parentTree) { - bpt::ptree rigTree; + bpt::ptree rigTree; - rigTree.put("rigId", rigId); + rigTree.put("rigId", rigId); - bpt::ptree rigSubPosesTree; + bpt::ptree rigSubPosesTree; - for(const auto& rigSubPose : rig.getSubPoses()) - { - bpt::ptree rigSubPoseTree; + for (const auto& rigSubPose : rig.getSubPoses()) + { + bpt::ptree rigSubPoseTree; - rigSubPoseTree.put("status", sfmData::ERigSubPoseStatus_enumToString(rigSubPose.status)); - savePose3("pose", rigSubPose.pose, rigSubPoseTree); + rigSubPoseTree.put("status", sfmData::ERigSubPoseStatus_enumToString(rigSubPose.status)); + savePose3("pose", rigSubPose.pose, rigSubPoseTree); - rigSubPosesTree.push_back(std::make_pair("", rigSubPoseTree)); - } + rigSubPosesTree.push_back(std::make_pair("", rigSubPoseTree)); + } - rigTree.add_child("subPoses", rigSubPosesTree); + rigTree.add_child("subPoses", rigSubPosesTree); - parentTree.push_back(std::make_pair(name, rigTree)); + parentTree.push_back(std::make_pair(name, rigTree)); } - void loadRig(IndexT& rigId, sfmData::Rig& rig, bpt::ptree& rigTree) { - rigId = rigTree.get("rigId"); - rig = sfmData::Rig(rigTree.get_child("subPoses").size()); - int subPoseId = 0; + rigId = rigTree.get("rigId"); + rig = sfmData::Rig(rigTree.get_child("subPoses").size()); + int subPoseId = 0; - for(bpt::ptree::value_type& subPoseNode : rigTree.get_child("subPoses")) - { - bpt::ptree& subPoseTree = subPoseNode.second; + for (bpt::ptree::value_type& subPoseNode : rigTree.get_child("subPoses")) + { + bpt::ptree& subPoseTree = subPoseNode.second; - sfmData::RigSubPose subPose; + sfmData::RigSubPose subPose; - subPose.status = sfmData::ERigSubPoseStatus_stringToEnum(subPoseTree.get("status")); - loadPose3("pose", subPose.pose, subPoseTree); + subPose.status = sfmData::ERigSubPoseStatus_stringToEnum(subPoseTree.get("status")); + loadPose3("pose", subPose.pose, subPoseTree); - rig.setSubPose(subPoseId++, subPose); - } + rig.setSubPose(subPoseId++, subPose); + } } -void saveLandmark(const std::string& name, IndexT landmarkId, const sfmData::Landmark& landmark, bpt::ptree& parentTree, bool saveObservations, bool saveFeatures) +void saveLandmark(const std::string& name, + IndexT landmarkId, + const sfmData::Landmark& landmark, + bpt::ptree& parentTree, + bool saveObservations, + bool saveFeatures) { - bpt::ptree landmarkTree; + bpt::ptree landmarkTree; - landmarkTree.put("landmarkId", landmarkId); - landmarkTree.put("descType", feature::EImageDescriberType_enumToString(landmark.descType)); + landmarkTree.put("landmarkId", landmarkId); + landmarkTree.put("descType", feature::EImageDescriberType_enumToString(landmark.descType)); - saveMatrix("color", landmark.rgb, landmarkTree); - saveMatrix("X", landmark.X, landmarkTree); + saveMatrix("color", landmark.rgb, landmarkTree); + saveMatrix("X", landmark.X, landmarkTree); - // observations - if(saveObservations) - { - bpt::ptree observationsTree; - for(const auto& obsPair : landmark.observations) + // observations + if (saveObservations) { - bpt::ptree obsTree; + bpt::ptree observationsTree; + for (const auto& obsPair : landmark.observations) + { + bpt::ptree obsTree; - const sfmData::Observation& observation = obsPair.second; + const sfmData::Observation& observation = obsPair.second; - obsTree.put("observationId", obsPair.first); + obsTree.put("observationId", obsPair.first); - // features - if(saveFeatures) - { - obsTree.put("featureId", observation.id_feat); - saveMatrix("x", observation.x, obsTree); - obsTree.put("scale", observation.scale); - } + // features + if (saveFeatures) + { + obsTree.put("featureId", observation.id_feat); + saveMatrix("x", observation.x, obsTree); + obsTree.put("scale", observation.scale); + } - observationsTree.push_back(std::make_pair("", obsTree)); - } + observationsTree.push_back(std::make_pair("", obsTree)); + } - landmarkTree.add_child("observations", observationsTree); - } + landmarkTree.add_child("observations", observationsTree); + } - parentTree.push_back(std::make_pair(name, landmarkTree)); + parentTree.push_back(std::make_pair(name, landmarkTree)); } void loadLandmark(IndexT& landmarkId, sfmData::Landmark& landmark, bpt::ptree& landmarkTree, bool loadObservations, bool loadFeatures) { - landmarkId = landmarkTree.get("landmarkId"); - landmark.descType = feature::EImageDescriberType_stringToEnum(landmarkTree.get("descType")); + landmarkId = landmarkTree.get("landmarkId"); + landmark.descType = feature::EImageDescriberType_stringToEnum(landmarkTree.get("descType")); - loadMatrix("color", landmark.rgb, landmarkTree); - loadMatrix("X", landmark.X, landmarkTree); + loadMatrix("color", landmark.rgb, landmarkTree); + loadMatrix("X", landmark.X, landmarkTree); - // observations - if(loadObservations) - { - for(bpt::ptree::value_type &obsNode : landmarkTree.get_child("observations")) + // observations + if (loadObservations) { - bpt::ptree& obsTree = obsNode.second; + for (bpt::ptree::value_type& obsNode : landmarkTree.get_child("observations")) + { + bpt::ptree& obsTree = obsNode.second; - sfmData::Observation observation; + sfmData::Observation observation; - if(loadFeatures) - { - observation.id_feat = obsTree.get("featureId"); - loadMatrix("x", observation.x, obsTree); - observation.scale = obsTree.get("scale", 0.0); - } + if (loadFeatures) + { + observation.id_feat = obsTree.get("featureId"); + loadMatrix("x", observation.x, obsTree); + observation.scale = obsTree.get("scale", 0.0); + } - landmark.observations.emplace(obsTree.get("observationId"), observation); + landmark.observations.emplace(obsTree.get("observationId"), observation); + } } - } } - bool saveJSON(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag) { - const Vec3i version = {ALICEVISION_SFMDATAIO_VERSION_MAJOR, ALICEVISION_SFMDATAIO_VERSION_MINOR, ALICEVISION_SFMDATAIO_VERSION_REVISION}; - - // save flags - const bool saveViews = (partFlag & VIEWS) == VIEWS; - const bool saveIntrinsics = (partFlag & INTRINSICS) == INTRINSICS; - const bool saveExtrinsics = (partFlag & EXTRINSICS) == EXTRINSICS; - const bool saveStructure = (partFlag & STRUCTURE) == STRUCTURE; - const bool saveFeatures = (partFlag & OBSERVATIONS_WITH_FEATURES) == OBSERVATIONS_WITH_FEATURES; - const bool saveObservations = saveFeatures || ((partFlag & OBSERVATIONS) == OBSERVATIONS); + const Vec3i version = {ALICEVISION_SFMDATAIO_VERSION_MAJOR, ALICEVISION_SFMDATAIO_VERSION_MINOR, ALICEVISION_SFMDATAIO_VERSION_REVISION}; - // main tree - bpt::ptree fileTree; + // save flags + const bool saveViews = (partFlag & VIEWS) == VIEWS; + const bool saveIntrinsics = (partFlag & INTRINSICS) == INTRINSICS; + const bool saveExtrinsics = (partFlag & EXTRINSICS) == EXTRINSICS; + const bool saveStructure = (partFlag & STRUCTURE) == STRUCTURE; + const bool saveFeatures = (partFlag & OBSERVATIONS_WITH_FEATURES) == OBSERVATIONS_WITH_FEATURES; + const bool saveObservations = saveFeatures || ((partFlag & OBSERVATIONS) == OBSERVATIONS); - // file version - saveMatrix("version", version, fileTree); + // main tree + bpt::ptree fileTree; - // folders - if(!sfmData.getRelativeFeaturesFolders().empty()) - { - bpt::ptree featureFoldersTree; + // file version + saveMatrix("version", version, fileTree); - for(const std::string& featuresFolder : sfmData.getRelativeFeaturesFolders()) + // folders + if (!sfmData.getRelativeFeaturesFolders().empty()) { - bpt::ptree featureFolderTree; - featureFolderTree.put("", featuresFolder); - featureFoldersTree.push_back(std::make_pair("", featureFolderTree)); - } + bpt::ptree featureFoldersTree; - fileTree.add_child("featuresFolders", featureFoldersTree); - } + for (const std::string& featuresFolder : sfmData.getRelativeFeaturesFolders()) + { + bpt::ptree featureFolderTree; + featureFolderTree.put("", featuresFolder); + featureFoldersTree.push_back(std::make_pair("", featureFolderTree)); + } - if(!sfmData.getRelativeMatchesFolders().empty()) - { - bpt::ptree matchingFoldersTree; + fileTree.add_child("featuresFolders", featureFoldersTree); + } - for(const std::string& matchesFolder : sfmData.getRelativeMatchesFolders()) + if (!sfmData.getRelativeMatchesFolders().empty()) { - bpt::ptree matchingFolderTree; - matchingFolderTree.put("", matchesFolder); - matchingFoldersTree.push_back(std::make_pair("", matchingFolderTree)); - } + bpt::ptree matchingFoldersTree; - fileTree.add_child("matchesFolders", matchingFoldersTree); - } + for (const std::string& matchesFolder : sfmData.getRelativeMatchesFolders()) + { + bpt::ptree matchingFolderTree; + matchingFolderTree.put("", matchesFolder); + matchingFoldersTree.push_back(std::make_pair("", matchingFolderTree)); + } - // views - if(saveViews && !sfmData.getViews().empty()) - { - bpt::ptree viewsTree; + fileTree.add_child("matchesFolders", matchingFoldersTree); + } - for(const auto& viewPair : sfmData.getViews()) - saveView("", *(viewPair.second), viewsTree); + // views + if (saveViews && !sfmData.getViews().empty()) + { + bpt::ptree viewsTree; - fileTree.add_child("views", viewsTree); - } + for (const auto& viewPair : sfmData.getViews()) + saveView("", *(viewPair.second), viewsTree); - // intrinsics - if(saveIntrinsics && !sfmData.getIntrinsics().empty()) - { - bpt::ptree intrinsicsTree; + fileTree.add_child("views", viewsTree); + } - for(const auto& intrinsicPair : sfmData.getIntrinsics()) - saveIntrinsic("", intrinsicPair.first, intrinsicPair.second, intrinsicsTree); + // intrinsics + if (saveIntrinsics && !sfmData.getIntrinsics().empty()) + { + bpt::ptree intrinsicsTree; + + for (const auto& intrinsicPair : sfmData.getIntrinsics()) + saveIntrinsic("", intrinsicPair.first, intrinsicPair.second, intrinsicsTree); - fileTree.add_child("intrinsics", intrinsicsTree); - } + fileTree.add_child("intrinsics", intrinsicsTree); + } - //extrinsics - if(saveExtrinsics) - { - // poses - if(!sfmData.getPoses().empty()) + // extrinsics + if (saveExtrinsics) { - bpt::ptree posesTree; + // poses + if (!sfmData.getPoses().empty()) + { + bpt::ptree posesTree; - for(const auto& posePair : sfmData.getPoses()) - { - bpt::ptree poseTree; + for (const auto& posePair : sfmData.getPoses()) + { + bpt::ptree poseTree; - poseTree.put("poseId", posePair.first); - saveCameraPose("pose", posePair.second, poseTree); - posesTree.push_back(std::make_pair("", poseTree)); - } + poseTree.put("poseId", posePair.first); + saveCameraPose("pose", posePair.second, poseTree); + posesTree.push_back(std::make_pair("", poseTree)); + } - fileTree.add_child("poses", posesTree); - } + fileTree.add_child("poses", posesTree); + } - // rigs - if(!sfmData.getRigs().empty()) - { - bpt::ptree rigsTree; + // rigs + if (!sfmData.getRigs().empty()) + { + bpt::ptree rigsTree; - for(const auto& rigPair : sfmData.getRigs()) - saveRig("", rigPair.first, rigPair.second, rigsTree); + for (const auto& rigPair : sfmData.getRigs()) + saveRig("", rigPair.first, rigPair.second, rigsTree); - fileTree.add_child("rigs", rigsTree); + fileTree.add_child("rigs", rigsTree); + } } - } - // structure - if(saveStructure && !sfmData.getLandmarks().empty()) - { - bpt::ptree structureTree; + // structure + if (saveStructure && !sfmData.getLandmarks().empty()) + { + bpt::ptree structureTree; - for(const auto& structurePair : sfmData.getLandmarks()) - saveLandmark("", structurePair.first, structurePair.second, structureTree, saveObservations, saveFeatures); + for (const auto& structurePair : sfmData.getLandmarks()) + saveLandmark("", structurePair.first, structurePair.second, structureTree, saveObservations, saveFeatures); - fileTree.add_child("structure", structureTree); - } + fileTree.add_child("structure", structureTree); + } - // write the json file with the tree + // write the json file with the tree - bpt::write_json(filename, fileTree); + bpt::write_json(filename, fileTree); - return true; + return true; } -bool loadJSON(sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag, bool incompleteViews, - EViewIdMethod viewIdMethod, const std::string& viewIdRegex) +bool loadJSON(sfmData::SfMData& sfmData, + const std::string& filename, + ESfMData partFlag, + bool incompleteViews, + EViewIdMethod viewIdMethod, + const std::string& viewIdRegex) { - Version version; + Version version; - // load flags - const bool loadViews = (partFlag & VIEWS) == VIEWS; - const bool loadIntrinsics = (partFlag & INTRINSICS) == INTRINSICS; - const bool loadExtrinsics = (partFlag & EXTRINSICS) == EXTRINSICS; - const bool loadStructure = (partFlag & STRUCTURE) == STRUCTURE; - const bool loadFeatures = (partFlag & OBSERVATIONS_WITH_FEATURES) == OBSERVATIONS_WITH_FEATURES; - const bool loadObservations = loadFeatures || ((partFlag & OBSERVATIONS) == OBSERVATIONS); + // load flags + const bool loadViews = (partFlag & VIEWS) == VIEWS; + const bool loadIntrinsics = (partFlag & INTRINSICS) == INTRINSICS; + const bool loadExtrinsics = (partFlag & EXTRINSICS) == EXTRINSICS; + const bool loadStructure = (partFlag & STRUCTURE) == STRUCTURE; + const bool loadFeatures = (partFlag & OBSERVATIONS_WITH_FEATURES) == OBSERVATIONS_WITH_FEATURES; + const bool loadObservations = loadFeatures || ((partFlag & OBSERVATIONS) == OBSERVATIONS); - // main tree - bpt::ptree fileTree; + // main tree + bpt::ptree fileTree; - // read the json file and initialize the tree - bpt::read_json(filename, fileTree); + // read the json file and initialize the tree + bpt::read_json(filename, fileTree); - // version - { - Vec3i v; - loadMatrix("version", v, fileTree); - version = v; - } - - // folders - if(fileTree.count("featuresFolders")) - for(bpt::ptree::value_type& featureFolderNode : fileTree.get_child("featuresFolders")) - sfmData.addFeaturesFolder(featureFolderNode.second.get_value()); + // version + { + Vec3i v; + loadMatrix("version", v, fileTree); + version = v; + } - if(fileTree.count("matchesFolders")) - for(bpt::ptree::value_type& matchingFolderNode : fileTree.get_child("matchesFolders")) - sfmData.addMatchesFolder(matchingFolderNode.second.get_value()); + // folders + if (fileTree.count("featuresFolders")) + for (bpt::ptree::value_type& featureFolderNode : fileTree.get_child("featuresFolders")) + sfmData.addFeaturesFolder(featureFolderNode.second.get_value()); - // intrinsics - if(loadIntrinsics && fileTree.count("intrinsics")) - { - sfmData::Intrinsics& intrinsics = sfmData.getIntrinsics(); + if (fileTree.count("matchesFolders")) + for (bpt::ptree::value_type& matchingFolderNode : fileTree.get_child("matchesFolders")) + sfmData.addMatchesFolder(matchingFolderNode.second.get_value()); - for(bpt::ptree::value_type &intrinsicNode : fileTree.get_child("intrinsics")) + // intrinsics + if (loadIntrinsics && fileTree.count("intrinsics")) { - IndexT intrinsicId; - std::shared_ptr intrinsic; + sfmData::Intrinsics& intrinsics = sfmData.getIntrinsics(); - loadIntrinsic(version, intrinsicId, intrinsic, intrinsicNode.second); + for (bpt::ptree::value_type& intrinsicNode : fileTree.get_child("intrinsics")) + { + IndexT intrinsicId; + std::shared_ptr intrinsic; - intrinsics.emplace(intrinsicId, intrinsic); - } - } + loadIntrinsic(version, intrinsicId, intrinsic, intrinsicNode.second); - // views - if(loadViews && fileTree.count("views")) - { - sfmData::Views& views = sfmData.getViews(); + intrinsics.emplace(intrinsicId, intrinsic); + } + } - if(incompleteViews) + // views + if (loadViews && fileTree.count("views")) { - auto children = fileTree.get_child("views"); - // update incomplete views - #pragma omp parallel for - for(int index = 0; index < children.size(); index++) - { - auto it = children.begin(); - std::advance(it, index); + sfmData::Views& views = sfmData.getViews(); - auto view = std::make_shared(); - loadView(*view, it->second); - - // if we have the intrinsics and the view has an valid associated intrinsics - // update the width and height field of View (they are mirrored) - if (loadIntrinsics && view->getIntrinsicId() != UndefinedIndexT) + if (incompleteViews) { - const auto intrinsics = sfmData.getIntrinsicPtr(view->getIntrinsicId()); - - if(intrinsics == nullptr) - { - throw std::logic_error("View " + std::to_string(view->getViewId()) - + " has a intrinsics id " + std::to_string(view->getIntrinsicId()) - + " that cannot be found or the intrinsics are not correctly " - "loaded from the json file."); - } - - view->getImage().setWidth(intrinsics->w()); - view->getImage().setHeight(intrinsics->h()); + auto children = fileTree.get_child("views"); + // update incomplete views +#pragma omp parallel for + for (int index = 0; index < children.size(); index++) + { + auto it = children.begin(); + std::advance(it, index); + + auto view = std::make_shared(); + loadView(*view, it->second); + + // if we have the intrinsics and the view has an valid associated intrinsics + // update the width and height field of View (they are mirrored) + if (loadIntrinsics && view->getIntrinsicId() != UndefinedIndexT) + { + const auto intrinsics = sfmData.getIntrinsicPtr(view->getIntrinsicId()); + + if (intrinsics == nullptr) + { + throw std::logic_error("View " + std::to_string(view->getViewId()) + " has a intrinsics id " + + std::to_string(view->getIntrinsicId()) + + " that cannot be found or the intrinsics are not correctly " + "loaded from the json file."); + } + + view->getImage().setWidth(intrinsics->w()); + view->getImage().setHeight(intrinsics->h()); + } + updateIncompleteView(*view, viewIdMethod, viewIdRegex); + +#pragma omp critical + { + views.emplace(view->getViewId(), view); + } + } } - updateIncompleteView(*view, viewIdMethod, viewIdRegex); - - #pragma omp critical + else { - views.emplace(view->getViewId(), view); + // store directly in the SfMData views map + for (bpt::ptree::value_type& viewNode : fileTree.get_child("views")) + { + auto view = std::make_shared(); + loadView(*view, viewNode.second); + views.emplace(view->getViewId(), view); + } } - } - } - else - { - // store directly in the SfMData views map - for(bpt::ptree::value_type &viewNode : fileTree.get_child("views")) - { - auto view = std::make_shared(); - loadView(*view, viewNode.second); - views.emplace(view->getViewId(), view); - } } - } - // extrinsics - if(loadExtrinsics) - { - // poses - if(fileTree.count("poses")) + // extrinsics + if (loadExtrinsics) { - sfmData::Poses& poses = sfmData.getPoses(); + // poses + if (fileTree.count("poses")) + { + sfmData::Poses& poses = sfmData.getPoses(); - for(bpt::ptree::value_type &poseNode : fileTree.get_child("poses")) - { - bpt::ptree& poseTree = poseNode.second; - sfmData::CameraPose pose; + for (bpt::ptree::value_type& poseNode : fileTree.get_child("poses")) + { + bpt::ptree& poseTree = poseNode.second; + sfmData::CameraPose pose; - loadCameraPose("pose", pose, poseTree); + loadCameraPose("pose", pose, poseTree); - poses.emplace(poseTree.get("poseId"), pose); - } - } + poses.emplace(poseTree.get("poseId"), pose); + } + } - // rigs - if(fileTree.count("rigs")) - { - sfmData::Rigs& rigs = sfmData.getRigs(); + // rigs + if (fileTree.count("rigs")) + { + sfmData::Rigs& rigs = sfmData.getRigs(); - for(bpt::ptree::value_type &rigNode : fileTree.get_child("rigs")) - { - IndexT rigId; - sfmData::Rig rig; + for (bpt::ptree::value_type& rigNode : fileTree.get_child("rigs")) + { + IndexT rigId; + sfmData::Rig rig; - loadRig(rigId, rig, rigNode.second); + loadRig(rigId, rig, rigNode.second); - rigs.emplace(rigId, rig); - } + rigs.emplace(rigId, rig); + } + } } - } - - // structure - if(loadStructure && fileTree.count("structure")) - { - sfmData::Landmarks& structure = sfmData.getLandmarks(); - for(bpt::ptree::value_type &landmarkNode : fileTree.get_child("structure")) + // structure + if (loadStructure && fileTree.count("structure")) { - IndexT landmarkId; - sfmData::Landmark landmark; + sfmData::Landmarks& structure = sfmData.getLandmarks(); - loadLandmark(landmarkId, landmark, landmarkNode.second, loadObservations, loadFeatures); + for (bpt::ptree::value_type& landmarkNode : fileTree.get_child("structure")) + { + IndexT landmarkId; + sfmData::Landmark landmark; + + loadLandmark(landmarkId, landmark, landmarkNode.second, loadObservations, loadFeatures); - structure.emplace(landmarkId, landmark); + structure.emplace(landmarkId, landmark); + } } - } - return true; + return true; } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/jsonIO.hpp b/src/aliceVision/sfmDataIO/jsonIO.hpp index 264aada1e7..1baa257d7d 100644 --- a/src/aliceVision/sfmDataIO/jsonIO.hpp +++ b/src/aliceVision/sfmDataIO/jsonIO.hpp @@ -27,17 +27,17 @@ namespace bpt = boost::property_tree; template inline void saveMatrix(const std::string& name, const Eigen::MatrixBase& matrix, bpt::ptree& parentTree) { - bpt::ptree matrixTree; + bpt::ptree matrixTree; - const int size = matrix.size(); - for(int i = 0; i < size; ++i) - { - bpt::ptree cellTree; - cellTree.put("", matrix(i)); - matrixTree.push_back(std::make_pair("", cellTree)); - } + const int size = matrix.size(); + for (int i = 0; i < size; ++i) + { + bpt::ptree cellTree; + cellTree.put("", matrix(i)); + matrixTree.push_back(std::make_pair("", cellTree)); + } - parentTree.add_child(name, matrixTree); + parentTree.add_child(name, matrixTree); } /** @@ -49,17 +49,17 @@ inline void saveMatrix(const std::string& name, const Eigen::MatrixBase template inline void loadMatrix(const std::string& name, Eigen::MatrixBase& matrix, bpt::ptree& matrixTree) { - const int size = matrix.size(); - int i = 0; + const int size = matrix.size(); + int i = 0; - for(bpt::ptree::value_type &cellNode : matrixTree.get_child(name)) - { - if(i > size) - throw std::out_of_range("Invalid matrix / vector type for : " + name); + for (bpt::ptree::value_type& cellNode : matrixTree.get_child(name)) + { + if (i > size) + throw std::out_of_range("Invalid matrix / vector type for : " + name); - matrix(i) = cellNode.second.get_value(); - ++i; - } + matrix(i) = cellNode.second.get_value(); + ++i; + } } /** @@ -70,12 +70,12 @@ inline void loadMatrix(const std::string& name, Eigen::MatrixBase& matr */ inline void savePose3(const std::string& name, const geometry::Pose3& pose, bpt::ptree& parentTree) { - bpt::ptree pose3Tree; + bpt::ptree pose3Tree; - saveMatrix("rotation", pose.rotation(), pose3Tree); - saveMatrix("center", pose.center(), pose3Tree); + saveMatrix("rotation", pose.rotation(), pose3Tree); + saveMatrix("center", pose.center(), pose3Tree); - parentTree.add_child(name, pose3Tree); + parentTree.add_child(name, pose3Tree); } /** @@ -86,13 +86,13 @@ inline void savePose3(const std::string& name, const geometry::Pose3& pose, bpt: */ inline void loadPose3(const std::string& name, geometry::Pose3& pose, bpt::ptree& pose3Tree) { - Mat3 rotation; - Vec3 center; + Mat3 rotation; + Vec3 center; - loadMatrix(name + ".rotation", rotation, pose3Tree); - loadMatrix(name + ".center", center, pose3Tree); + loadMatrix(name + ".rotation", rotation, pose3Tree); + loadMatrix(name + ".center", center, pose3Tree); - pose = geometry::Pose3(rotation, center); + pose = geometry::Pose3(rotation, center); } /** @@ -103,12 +103,13 @@ inline void loadPose3(const std::string& name, geometry::Pose3& pose, bpt::ptree */ inline void saveCameraPose(const std::string& name, const sfmData::CameraPose& cameraPose, bpt::ptree& parentTree) { - bpt::ptree cameraPoseTree; + bpt::ptree cameraPoseTree; - savePose3("transform", cameraPose.getTransform(), cameraPoseTree); - cameraPoseTree.put("locked", static_cast(cameraPose.isLocked())); // convert bool to integer to avoid using "true/false" in exported file instead of "1/0". + savePose3("transform", cameraPose.getTransform(), cameraPoseTree); + cameraPoseTree.put( + "locked", static_cast(cameraPose.isLocked())); // convert bool to integer to avoid using "true/false" in exported file instead of "1/0". - parentTree.add_child(name, cameraPoseTree); + parentTree.add_child(name, cameraPoseTree); } /** @@ -119,15 +120,15 @@ inline void saveCameraPose(const std::string& name, const sfmData::CameraPose& c */ inline void loadCameraPose(const std::string& name, sfmData::CameraPose& cameraPose, bpt::ptree& cameraPoseTree) { - geometry::Pose3 pose; + geometry::Pose3 pose; - loadPose3(name + ".transform", pose, cameraPoseTree); - cameraPose.setTransform(pose); + loadPose3(name + ".transform", pose, cameraPoseTree); + cameraPose.setTransform(pose); - if(cameraPoseTree.get("locked", false)) - cameraPose.lock(); - else - cameraPose.unlock(); + if (cameraPoseTree.get("locked", false)) + cameraPose.lock(); + else + cameraPose.unlock(); } /** @@ -161,8 +162,7 @@ void saveIntrinsic(const std::string& name, IndexT intrinsicId, const std::share * @param[out] intrinsic The output Intrinsic * @param intrinsicTree The input tree */ -void loadIntrinsic(const Version& version, IndexT& intrinsicId, std::shared_ptr& intrinsic, - bpt::ptree& intrinsicTree); +void loadIntrinsic(const Version& version, IndexT& intrinsicId, std::shared_ptr& intrinsic, bpt::ptree& intrinsicTree); /** * @brief Save a Rig in a boost property tree. @@ -192,7 +192,12 @@ void loadRig(IndexT& rigId, sfmData::Rig& rig, bpt::ptree& rigTree); * @param[in] saveObservations Save landmark observations (default: true) * @param[in] saveFeatures Save landmark observations features (default: true) */ -void saveLandmark(const std::string& name, IndexT landmarkId, const sfmData::Landmark& landmark, bpt::ptree& parentTree, bool saveObservations = true, bool saveFeatures = true); +void saveLandmark(const std::string& name, + IndexT landmarkId, + const sfmData::Landmark& landmark, + bpt::ptree& parentTree, + bool saveObservations = true, + bool saveFeatures = true); /** * @brief Load a Landmark from a boost property tree. @@ -225,8 +230,12 @@ bool saveJSON(const sfmData::SfMData& sfmData, const std::string& filename, ESfM * @param[in] viewIdRegex Optional regex used when viewIdMethod is FILENAME * @return true if completed */ -bool loadJSON(sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag, bool incompleteViews = false, - EViewIdMethod viewIdMethod = EViewIdMethod::METADATA, const std::string& viewIdRegex = ""); +bool loadJSON(sfmData::SfMData& sfmData, + const std::string& filename, + ESfMData partFlag, + bool incompleteViews = false, + EViewIdMethod viewIdMethod = EViewIdMethod::METADATA, + const std::string& viewIdRegex = ""); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/middlebury.cpp b/src/aliceVision/sfmDataIO/middlebury.cpp index 3158f4bfaa..dcdd204f22 100644 --- a/src/aliceVision/sfmDataIO/middlebury.cpp +++ b/src/aliceVision/sfmDataIO/middlebury.cpp @@ -20,15 +20,14 @@ namespace bfs = boost::filesystem; Mat3 extractMat3FromVec(const std::vector& entries, std::size_t offset) { // we are supposed to read 9 elements, so the offset must be coherent with the vector size - if(offset + 9 > entries.size()) + if (offset + 9 > entries.size()) { - ALICEVISION_LOG_ERROR("The vector has " << entries.size() - << " elements, tried to read out of bounds (offset: " << offset); + ALICEVISION_LOG_ERROR("The vector has " << entries.size() << " elements, tried to read out of bounds (offset: " << offset); throw std::out_of_range("Trying to read out of the bounds of the vector"); } Mat3 rotation; const auto lastIdx{offset + 8}; - for(std::size_t i = offset; i <= lastIdx; ++i) + for (std::size_t i = offset; i <= lastIdx; ++i) { const double val = std::stod(entries[i]); const auto row = (i - offset) / 3; @@ -38,8 +37,7 @@ Mat3 extractMat3FromVec(const std::vector& entries, std::size_t off return rotation; } -void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3& matK, Mat3& rotation, - Vec3& translation) +void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3& matK, Mat3& rotation, Vec3& translation) { std::vector entries{}; @@ -47,12 +45,12 @@ void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3 std::string entry; const char spaceChar{' '}; // tokenize the line extracting all the entries separated by a space - while(std::getline(sstream, entry, spaceChar)) + while (std::getline(sstream, entry, spaceChar)) { entries.push_back(entry); ALICEVISION_LOG_TRACE(entry); } - if(entries.size() != 22) + if (entries.size() != 22) { ALICEVISION_LOG_ERROR("read " << entries.size() << " entries, expected " << 22 << ". Incorrect file format"); throw std::runtime_error("Error while reading the camera parameters, incorrect number of entries"); @@ -71,11 +69,15 @@ void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3 translation(2) = std::stod(entries[21]); } -sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std::string& basePath, - bool uniqueIntrinsics, bool importPoses, bool lockIntrinsics, bool lockPoses) +sfmData::SfMData middleburySceneToSfmData(const std::string& filename, + const std::string& basePath, + bool uniqueIntrinsics, + bool importPoses, + bool lockIntrinsics, + bool lockPoses) { std::ifstream infile(filename); - if(!infile.is_open()) + if (!infile.is_open()) { ALICEVISION_LOG_ERROR("Unable to open " << filename); throw std::runtime_error("Unable to open " + filename); @@ -96,7 +98,7 @@ sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std IndexT poseId = importPoses ? 0 : UndefinedIndexT; // parse all the other lines - while(std::getline(infile, line)) + while (std::getline(infile, line)) { std::string imageName; Mat3 matK; @@ -110,12 +112,12 @@ sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std image::readImageSize(imagePath, imageWidth, imageHeight); // if uniqueIntrinsics do it once, otherwise always - if((uniqueIntrinsics && scene.getIntrinsics().empty()) || !uniqueIntrinsics) + if ((uniqueIntrinsics && scene.getIntrinsics().empty()) || !uniqueIntrinsics) { ALICEVISION_LOG_DEBUG("matK " << matK); // add the intrinsics scene.getIntrinsics().insert({intrinsicsId, std::make_shared(imageWidth, imageHeight, matK)}); - if(lockIntrinsics) + if (lockIntrinsics) { scene.getIntrinsics().at(intrinsicsId)->lock(); } @@ -123,7 +125,7 @@ sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std ALICEVISION_LOG_DEBUG("rotation " << rotation); ALICEVISION_LOG_DEBUG("translation " << translation); - if(importPoses) + if (importPoses) { // add the pose entry const auto pose = geometry::poseFromRT(rotation, translation); @@ -131,32 +133,30 @@ sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std } // add view - scene.getViews().insert({viewId, std::make_shared(imagePath, viewId, intrinsicsId, poseId, - imageWidth, imageHeight)}); + scene.getViews().insert({viewId, std::make_shared(imagePath, viewId, intrinsicsId, poseId, imageWidth, imageHeight)}); // update the intrinsics id only if not unique - if(!uniqueIntrinsics) + if (!uniqueIntrinsics) { ++intrinsicsId; } ++viewId; - if(importPoses) + if (importPoses) { ++poseId; } } // just a safe guard - if(scene.getViews().size() != numViews) + if (scene.getViews().size() != numViews) { ALICEVISION_LOG_ERROR("Read " << scene.getViews().size() << " views, expected " << numViews); throw std::runtime_error("Unexpected number of cameras read"); } - ALICEVISION_LOG_INFO("Scene contains: " << scene.getIntrinsics().size() << " intrinsics, " - << scene.getViews().size() << " views, " << scene.getPoses().size() - << " poses"); + ALICEVISION_LOG_INFO("Scene contains: " << scene.getIntrinsics().size() << " intrinsics, " << scene.getViews().size() << " views, " + << scene.getPoses().size() << " poses"); return scene; } -} -} \ No newline at end of file +} // namespace sfmDataIO +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfmDataIO/middlebury.hpp b/src/aliceVision/sfmDataIO/middlebury.hpp index 179b7338d3..2e92b1ac4b 100644 --- a/src/aliceVision/sfmDataIO/middlebury.hpp +++ b/src/aliceVision/sfmDataIO/middlebury.hpp @@ -14,7 +14,6 @@ namespace aliceVision { namespace sfmDataIO { - /** * @brief Read the file containing the cameras (intrinsics and pose) in the middlebury format * @param[in] filename middlebury file (e.g. temple_par.txt) @@ -25,8 +24,12 @@ namespace sfmDataIO { * @param[in] lockPoses set the poses to locked (i.e. they cannot change during e.g. structure from motion) * @return the corresponding SfMData representation of the scene */ -sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std::string& basePath, - bool uniqueIntrinsics, bool importPoses, bool lockIntrinsics, bool lockPoses); +sfmData::SfMData middleburySceneToSfmData(const std::string& filename, + const std::string& basePath, + bool uniqueIntrinsics, + bool importPoses, + bool lockIntrinsics, + bool lockPoses); /** * @brief Parse a line of the middlebury file containing the calibration, the pose and the image name @@ -36,8 +39,7 @@ sfmData::SfMData middleburySceneToSfmData(const std::string& filename, const std * @param[out] rotation the rotation matrix of the pose * @param[out] translation the translation vector of the pose */ -void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3& matK, Mat3& rotation, - Vec3& translation); +void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3& matK, Mat3& rotation, Vec3& translation); /** * @brief Helper function to parse the list of entries parsed from the file. @@ -47,5 +49,5 @@ void parseMiddleburyCamera(const std::string& line, std::string& imageName, Mat3 */ Mat3 extractMat3FromVec(const std::vector& entries, std::size_t offset); -} -} \ No newline at end of file +} // namespace sfmDataIO +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/sfmDataIO/middlebury_test.cpp b/src/aliceVision/sfmDataIO/middlebury_test.cpp index 91ef894e2d..e26b56b85d 100644 --- a/src/aliceVision/sfmDataIO/middlebury_test.cpp +++ b/src/aliceVision/sfmDataIO/middlebury_test.cpp @@ -26,13 +26,20 @@ BOOST_AUTO_TEST_CASE(middlebury_parse) "-0.93780781035583538000 -0.12966197244580752000 -0.32203149494584499000 " "0.06997376920479636600 -0.01263011688236693700 0.56781167778967301000"}; const std::string imageNameGT{"temple0299.png"}; - const auto matKGT = (Mat3() << 1520.400000, 0.000000, 302.320000, - 0.000000, 1525.900000, 246.870000, - 0.000000, 0.000000, 1.000000).finished(); - const auto rotationGT= (Mat3() << 0.10793659924390045000, -0.99055951509200579000, 0.08450761861721302300, - -0.32994878599451571000, 0.04449292035991074500, 0.94294972223262863000, - -0.93780781035583538000, -0.12966197244580752000, -0.32203149494584499000).finished();; - const auto translationGT= (Vec3() << 0.06997376920479636600, -0.01263011688236693700, 0.56781167778967301000).finished();; + const auto matKGT = (Mat3() << 1520.400000, 0.000000, 302.320000, 0.000000, 1525.900000, 246.870000, 0.000000, 0.000000, 1.000000).finished(); + const auto rotationGT = (Mat3() << 0.10793659924390045000, + -0.99055951509200579000, + 0.08450761861721302300, + -0.32994878599451571000, + 0.04449292035991074500, + 0.94294972223262863000, + -0.93780781035583538000, + -0.12966197244580752000, + -0.32203149494584499000) + .finished(); + ; + const auto translationGT = (Vec3() << 0.06997376920479636600, -0.01263011688236693700, 0.56781167778967301000).finished(); + ; std::string imageName; Mat3 matK; @@ -45,6 +52,4 @@ BOOST_AUTO_TEST_CASE(middlebury_parse) EXPECT_MATRIX_CLOSE_FRACTION(matK, matKGT, threshold); EXPECT_MATRIX_CLOSE_FRACTION(rotation, rotationGT, threshold); EXPECT_MATRIX_CLOSE_FRACTION(translation, translationGT, threshold); - - } \ No newline at end of file diff --git a/src/aliceVision/sfmDataIO/plyIO.cpp b/src/aliceVision/sfmDataIO/plyIO.cpp index 880353b825..7e112c6af8 100644 --- a/src/aliceVision/sfmDataIO/plyIO.cpp +++ b/src/aliceVision/sfmDataIO/plyIO.cpp @@ -12,78 +12,71 @@ namespace aliceVision { namespace sfmDataIO { -bool savePLY( - const sfmData::SfMData& sfmData, - const std::string& filename, - ESfMData partFlag) +bool savePLY(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag) { - const bool b_structure = (partFlag & STRUCTURE) == STRUCTURE; - const bool b_extrinsics = (partFlag & EXTRINSICS) == EXTRINSICS; + const bool b_structure = (partFlag & STRUCTURE) == STRUCTURE; + const bool b_extrinsics = (partFlag & EXTRINSICS) == EXTRINSICS; - if (!(b_structure || b_extrinsics)) - return false; + if (!(b_structure || b_extrinsics)) + return false; - //Create the stream and check it is ok - std::ofstream stream(filename); - if (!stream.is_open()) - return false; + // Create the stream and check it is ok + std::ofstream stream(filename); + if (!stream.is_open()) + return false; - bool bOk = false; - { - // Count how many views having valid poses: - IndexT view_with_pose_count = 0; - if (b_extrinsics) + bool bOk = false; { - for (const auto& view : sfmData.getViews()) - { - view_with_pose_count += sfmData.isPoseAndIntrinsicDefined(view.second.get()); - } - } - stream << "ply" - << '\n' << "format ascii 1.0" - << '\n' << "element vertex " - // Vertex count: (#landmark + #view_with_valid_pose) - << ((b_structure ? sfmData.getLandmarks().size() : 0) + - view_with_pose_count) - << '\n' << "property float x" - << '\n' << "property float y" - << '\n' << "property float z" - << '\n' << "property uchar red" - << '\n' << "property uchar green" - << '\n' << "property uchar blue" - << '\n' << "end_header" << std::endl; + // Count how many views having valid poses: + IndexT view_with_pose_count = 0; + if (b_extrinsics) + { + for (const auto& view : sfmData.getViews()) + { + view_with_pose_count += sfmData.isPoseAndIntrinsicDefined(view.second.get()); + } + } + stream << "ply" << '\n' + << "format ascii 1.0" << '\n' + << "element vertex " + // Vertex count: (#landmark + #view_with_valid_pose) + << ((b_structure ? sfmData.getLandmarks().size() : 0) + view_with_pose_count) << '\n' + << "property float x" << '\n' + << "property float y" << '\n' + << "property float z" << '\n' + << "property uchar red" << '\n' + << "property uchar green" << '\n' + << "property uchar blue" << '\n' + << "end_header" << std::endl; - if (b_extrinsics) - { - for (const auto& view : sfmData.getViews()) + if (b_extrinsics) { - if (sfmData.isPoseAndIntrinsicDefined(view.second.get())) - { - const geometry::Pose3 pose = sfmData.getPose(*(view.second.get())).getTransform(); - stream << pose.center().transpose() - << " 0 255 0" << "\n"; - } + for (const auto& view : sfmData.getViews()) + { + if (sfmData.isPoseAndIntrinsicDefined(view.second.get())) + { + const geometry::Pose3 pose = sfmData.getPose(*(view.second.get())).getTransform(); + stream << pose.center().transpose() << " 0 255 0" + << "\n"; + } + } } - } - if (b_structure) - { - const sfmData::Landmarks& landmarks = sfmData.getLandmarks(); - for (sfmData::Landmarks::const_iterator iterLandmarks = landmarks.begin(); - iterLandmarks != landmarks.end(); - ++iterLandmarks) { - stream << iterLandmarks->second.X.transpose() << " " - << (int)iterLandmarks->second.rgb.r() << " " - << (int)iterLandmarks->second.rgb.g() << " " - << (int)iterLandmarks->second.rgb.b() << "\n"; + if (b_structure) + { + const sfmData::Landmarks& landmarks = sfmData.getLandmarks(); + for (sfmData::Landmarks::const_iterator iterLandmarks = landmarks.begin(); iterLandmarks != landmarks.end(); ++iterLandmarks) + { + stream << iterLandmarks->second.X.transpose() << " " << (int)iterLandmarks->second.rgb.r() << " " + << (int)iterLandmarks->second.rgb.g() << " " << (int)iterLandmarks->second.rgb.b() << "\n"; + } } - } - stream.flush(); - bOk = stream.good(); - stream.close(); - } - return bOk; + stream.flush(); + bOk = stream.good(); + stream.close(); + } + return bOk; } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/plyIO.hpp b/src/aliceVision/sfmDataIO/plyIO.hpp index e686803b88..82db6b2001 100644 --- a/src/aliceVision/sfmDataIO/plyIO.hpp +++ b/src/aliceVision/sfmDataIO/plyIO.hpp @@ -21,9 +21,7 @@ namespace sfmDataIO { * @param[in] partFlag The ESfMData save flag * @return true if completed */ -bool savePLY(const sfmData::SfMData& sfmData, - const std::string& filename, - ESfMData partFlag); +bool savePLY(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/sceneSample.cpp b/src/aliceVision/sfmDataIO/sceneSample.cpp index be001a5f12..0ced2fb090 100644 --- a/src/aliceVision/sfmDataIO/sceneSample.cpp +++ b/src/aliceVision/sfmDataIO/sceneSample.cpp @@ -9,17 +9,17 @@ namespace aliceVision { namespace sfmDataIO { -void generateSampleScene(sfmData::SfMData & output) +void generateSampleScene(sfmData::SfMData& output) { // Generate points on a cube IndexT idpt = 0; - for(int x = -10; x <= 10; ++x) + for (int x = -10; x <= 10; ++x) { - for(int y = -10; y <= 10; ++y) + for (int y = -10; y <= 10; ++y) { - for(int z = -10; z <= 10; ++z) + for (int z = -10; z <= 10; ++z) { - output.getLandmarks().emplace(idpt, sfmData::Landmark(Vec3(x,y,z), feature::EImageDescriberType::UNKNOWN)); + output.getLandmarks().emplace(idpt, sfmData::Landmark(Vec3(x, y, z), feature::EImageDescriberType::UNKNOWN)); ++idpt; } } @@ -32,30 +32,25 @@ void generateSampleScene(sfmData::SfMData & output) const double offsetX = -26; const double offsetY = 16; output.getIntrinsics().emplace( - 0, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA, - w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY)); + 0, camera::createPinhole(camera::EINTRINSIC::PINHOLE_CAMERA, w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY)); output.getIntrinsics().emplace( - 1, - camera::createPinhole( - camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY, - {0.1, 0.05, -0.001})); + 1, + camera::createPinhole( + camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, w, h, focalLengthPixX, focalLengthPixY, offsetX, offsetY, {0.1, 0.05, -0.001})); // Generate poses on another cube IndexT idpose = 0; IndexT idview = 0; - for(int x = -1; x <= 1; ++x) + for (int x = -1; x <= 1; ++x) { - for(int y = -1; y <= 1; ++y) + for (int y = -1; y <= 1; ++y) { - for(int z = -1; z <= 1; ++z) + for (int z = -1; z <= 1; ++z) { const Eigen::Vector3d thetau(x, y, z); const Eigen::AngleAxis aa(thetau.norm(), thetau.normalized()); - output.getPoses().emplace(idpose, geometry::Pose3(aa.toRotationMatrix(), Vec3(x,y,z))); + output.getPoses().emplace(idpose, geometry::Pose3(aa.toRotationMatrix(), Vec3(x, y, z))); for (const auto itIntrinsic : output.getIntrinsics()) { @@ -69,5 +64,5 @@ void generateSampleScene(sfmData::SfMData & output) } } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/sceneSample.hpp b/src/aliceVision/sfmDataIO/sceneSample.hpp index d95a168239..506b43cb2a 100644 --- a/src/aliceVision/sfmDataIO/sceneSample.hpp +++ b/src/aliceVision/sfmDataIO/sceneSample.hpp @@ -15,7 +15,7 @@ namespace sfmDataIO { * @brief Create an SfmData with some arbitrary content. * This is used in unit tests to validate read/write sfmData files. */ -void generateSampleScene(sfmData::SfMData & output); +void generateSampleScene(sfmData::SfMData& output); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/sfmDataIO.cpp b/src/aliceVision/sfmDataIO/sfmDataIO.cpp index f7da385bdb..fd1aa002d7 100644 --- a/src/aliceVision/sfmDataIO/sfmDataIO.cpp +++ b/src/aliceVision/sfmDataIO/sfmDataIO.cpp @@ -14,8 +14,8 @@ #include #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_ALEMBIC) -#include -#include + #include + #include #endif #include @@ -25,163 +25,164 @@ namespace fs = boost::filesystem; namespace aliceVision { namespace sfmDataIO { -///Check that each view references a declared intrinsic +/// Check that each view references a declared intrinsic bool ValidIds(const sfmData::SfMData& sfmData, ESfMData partFlag) { - const bool bCheck_Intrinsic = (partFlag & INTRINSICS); - const bool bCheck_Rig = (partFlag & EXTRINSICS); - - // Collect intrinsic IDs declared - std::set intrinsicIdsDeclared; - transform(sfmData.getIntrinsics().begin(), sfmData.getIntrinsics().end(), - std::inserter(intrinsicIdsDeclared, intrinsicIdsDeclared.begin()), stl::RetrieveKey()); - - // Collect rig IDs declared - std::set rigIdsDeclared; - transform(sfmData.getRigs().begin(), sfmData.getRigs().end(), - std::inserter(rigIdsDeclared, rigIdsDeclared.begin()), stl::RetrieveKey()); - - // Collect intrinsic IDs referenced from views - std::set intrinsicIdsReferenced; - for(const auto& v: sfmData.getViews()) - { - const IndexT id_intrinsic = v.second.get()->getIntrinsicId(); - intrinsicIdsReferenced.insert(id_intrinsic); - } - - // Collect rig IDs referenced from views - std::set rigIdsReferenced; - for(const auto& v: sfmData.getViews()) - { - const IndexT id_rig = v.second.get()->getRigId(); - rigIdsReferenced.insert(id_rig); - } - - // We may have some views with undefined intrinsic/rig, - // so erase the UndefinedIndex value if exist. - intrinsicIdsReferenced.erase(UndefinedIndexT); - rigIdsReferenced.erase(UndefinedIndexT); - - // Check if defined intrinsics are at least connected to views - bool bRet = true; - if(bCheck_Intrinsic && intrinsicIdsDeclared != intrinsicIdsReferenced) - { - ALICEVISION_LOG_WARNING("The number of intrinsics is incoherent:"); - ALICEVISION_LOG_WARNING(intrinsicIdsDeclared.size() << " intrinsics declared and " << intrinsicIdsReferenced.size() << " intrinsics used."); - std::set undefinedIntrinsicIds; - // undefinedIntrinsicIds = intrinsicIdsReferenced - intrinsicIdsDeclared - std::set_difference(intrinsicIdsReferenced.begin(), intrinsicIdsReferenced.end(), - intrinsicIdsDeclared.begin(), intrinsicIdsDeclared.end(), - std::inserter(undefinedIntrinsicIds, undefinedIntrinsicIds.begin())); - // If undefinedIntrinsicIds is not empty, - // some intrinsics are used in Views but never declared. - // So the file structure is invalid and may create troubles. - if(!undefinedIntrinsicIds.empty()) - bRet = false; // error - } - - // Check if defined rigs are at least connected to views - if(bCheck_Rig && rigIdsDeclared != rigIdsReferenced) - { - ALICEVISION_LOG_WARNING("The number of rigs is incoherent:"); - ALICEVISION_LOG_WARNING(rigIdsDeclared.size() << " rigs declared and " << rigIdsReferenced.size() << " rigs used."); - std::set undefinedRigIds; - // undefinedRigIds = rigIdsReferenced - rigIdsDeclared - std::set_difference(rigIdsReferenced.begin(), rigIdsReferenced.end(), - rigIdsDeclared.begin(), rigIdsDeclared.end(), - std::inserter(undefinedRigIds, undefinedRigIds.begin())); - // If undefinedRigIds is not empty, - // some rigs are used in Views but never declared. - // So the file structure is invalid and may create troubles. - if(!undefinedRigIds.empty()) - bRet = false; // error - } - - return bRet; + const bool bCheck_Intrinsic = (partFlag & INTRINSICS); + const bool bCheck_Rig = (partFlag & EXTRINSICS); + + // Collect intrinsic IDs declared + std::set intrinsicIdsDeclared; + transform(sfmData.getIntrinsics().begin(), + sfmData.getIntrinsics().end(), + std::inserter(intrinsicIdsDeclared, intrinsicIdsDeclared.begin()), + stl::RetrieveKey()); + + // Collect rig IDs declared + std::set rigIdsDeclared; + transform(sfmData.getRigs().begin(), sfmData.getRigs().end(), std::inserter(rigIdsDeclared, rigIdsDeclared.begin()), stl::RetrieveKey()); + + // Collect intrinsic IDs referenced from views + std::set intrinsicIdsReferenced; + for (const auto& v : sfmData.getViews()) + { + const IndexT id_intrinsic = v.second.get()->getIntrinsicId(); + intrinsicIdsReferenced.insert(id_intrinsic); + } + + // Collect rig IDs referenced from views + std::set rigIdsReferenced; + for (const auto& v : sfmData.getViews()) + { + const IndexT id_rig = v.second.get()->getRigId(); + rigIdsReferenced.insert(id_rig); + } + + // We may have some views with undefined intrinsic/rig, + // so erase the UndefinedIndex value if exist. + intrinsicIdsReferenced.erase(UndefinedIndexT); + rigIdsReferenced.erase(UndefinedIndexT); + + // Check if defined intrinsics are at least connected to views + bool bRet = true; + if (bCheck_Intrinsic && intrinsicIdsDeclared != intrinsicIdsReferenced) + { + ALICEVISION_LOG_WARNING("The number of intrinsics is incoherent:"); + ALICEVISION_LOG_WARNING(intrinsicIdsDeclared.size() << " intrinsics declared and " << intrinsicIdsReferenced.size() << " intrinsics used."); + std::set undefinedIntrinsicIds; + // undefinedIntrinsicIds = intrinsicIdsReferenced - intrinsicIdsDeclared + std::set_difference(intrinsicIdsReferenced.begin(), + intrinsicIdsReferenced.end(), + intrinsicIdsDeclared.begin(), + intrinsicIdsDeclared.end(), + std::inserter(undefinedIntrinsicIds, undefinedIntrinsicIds.begin())); + // If undefinedIntrinsicIds is not empty, + // some intrinsics are used in Views but never declared. + // So the file structure is invalid and may create troubles. + if (!undefinedIntrinsicIds.empty()) + bRet = false; // error + } + + // Check if defined rigs are at least connected to views + if (bCheck_Rig && rigIdsDeclared != rigIdsReferenced) + { + ALICEVISION_LOG_WARNING("The number of rigs is incoherent:"); + ALICEVISION_LOG_WARNING(rigIdsDeclared.size() << " rigs declared and " << rigIdsReferenced.size() << " rigs used."); + std::set undefinedRigIds; + // undefinedRigIds = rigIdsReferenced - rigIdsDeclared + std::set_difference(rigIdsReferenced.begin(), + rigIdsReferenced.end(), + rigIdsDeclared.begin(), + rigIdsDeclared.end(), + std::inserter(undefinedRigIds, undefinedRigIds.begin())); + // If undefinedRigIds is not empty, + // some rigs are used in Views but never declared. + // So the file structure is invalid and may create troubles. + if (!undefinedRigIds.empty()) + bRet = false; // error + } + + return bRet; } bool Load(sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag) { - const std::string extension = fs::extension(filename); - bool status = false; - - if(extension == ".sfm" || extension == ".json") // JSON File - { - status = loadJSON(sfmData, filename, partFlag); - } - else if (extension == ".abc") // Alembic - { + const std::string extension = fs::extension(filename); + bool status = false; + + if (extension == ".sfm" || extension == ".json") // JSON File + { + status = loadJSON(sfmData, filename, partFlag); + } + else if (extension == ".abc") // Alembic + { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_ALEMBIC) - AlembicImporter(filename).populateSfM(sfmData, partFlag); - status = true; + AlembicImporter(filename).populateSfM(sfmData, partFlag); + status = true; #else - ALICEVISION_THROW_ERROR("Cannot load the ABC file: \"" << filename << "\", AliceVision is built without Alembic support."); + ALICEVISION_THROW_ERROR("Cannot load the ABC file: \"" << filename << "\", AliceVision is built without Alembic support."); #endif - } - else if(fs::is_directory(filename)) - { - status = readGt(filename, sfmData); - } - else // It is not a folder or known format, return false - { - ALICEVISION_LOG_ERROR("Unknown input SfM data format: '" << extension << "'"); - return false; - } - - if(status) - sfmData.setAbsolutePath(filename); - - // Assert that loaded intrinsics are linked to valid view - if(status && (partFlag & VIEWS) && (partFlag & INTRINSICS)) - return ValidIds(sfmData, partFlag); - - return status; + } + else if (fs::is_directory(filename)) + { + status = readGt(filename, sfmData); + } + else // It is not a folder or known format, return false + { + ALICEVISION_LOG_ERROR("Unknown input SfM data format: '" << extension << "'"); + return false; + } + + if (status) + sfmData.setAbsolutePath(filename); + + // Assert that loaded intrinsics are linked to valid view + if (status && (partFlag & VIEWS) && (partFlag & INTRINSICS)) + return ValidIds(sfmData, partFlag); + + return status; } bool Save(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag) { - const fs::path bPath = fs::path(filename); - const std::string extension = bPath.extension().string(); - const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + extension; - bool status = false; - - if(extension == ".sfm" || extension == ".json") // JSON File - { - status = saveJSON(sfmData, tmpPath, partFlag); - } - else if(extension == ".ply") // Polygon File - { - status = savePLY(sfmData, tmpPath, partFlag); - } - else if (extension == ".baf") // Bundle Adjustment File - { - status = saveBAF(sfmData, tmpPath, partFlag); - } - else if (extension == ".abc") // Alembic - { + const fs::path bPath = fs::path(filename); + const std::string extension = bPath.extension().string(); + const std::string tmpPath = (bPath.parent_path() / bPath.stem()).string() + "." + fs::unique_path().string() + extension; + bool status = false; + + if (extension == ".sfm" || extension == ".json") // JSON File + { + status = saveJSON(sfmData, tmpPath, partFlag); + } + else if (extension == ".ply") // Polygon File + { + status = savePLY(sfmData, tmpPath, partFlag); + } + else if (extension == ".baf") // Bundle Adjustment File + { + status = saveBAF(sfmData, tmpPath, partFlag); + } + else if (extension == ".abc") // Alembic + { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_ALEMBIC) - AlembicExporter(tmpPath).addSfM(sfmData, partFlag); - status = true; + AlembicExporter(tmpPath).addSfM(sfmData, partFlag); + status = true; #else - ALICEVISION_THROW_ERROR("Cannot save the ABC file: \"" << filename - << "\", AliceVision is built without Alembic support."); + ALICEVISION_THROW_ERROR("Cannot save the ABC file: \"" << filename << "\", AliceVision is built without Alembic support."); #endif - } - else - { - ALICEVISION_LOG_ERROR("Cannot save the SfM data file: '" << filename << "'." - << std::endl << "The file extension is not recognized."); - return false; - } - - // rename temporary filename - if(status) - fs::rename(tmpPath, filename); - - return status; + } + else + { + ALICEVISION_LOG_ERROR("Cannot save the SfM data file: '" << filename << "'." << std::endl << "The file extension is not recognized."); + return false; + } + + // rename temporary filename + if (status) + fs::rename(tmpPath, filename); + + return status; } -} // namespace sfmDataIO -} // namespace aliceVision - - +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/sfmDataIO.hpp b/src/aliceVision/sfmDataIO/sfmDataIO.hpp index 5c560d24eb..cee0940def 100644 --- a/src/aliceVision/sfmDataIO/sfmDataIO.hpp +++ b/src/aliceVision/sfmDataIO/sfmDataIO.hpp @@ -14,30 +14,29 @@ #define ALICEVISION_SFMDATAIO_VERSION_MINOR 2 #define ALICEVISION_SFMDATAIO_VERSION_REVISION 5 - // AliceVision version as a string; for example "0.9.0". -#define ALICEVISION_SFMDATAIO_VERSION_STRING ALICEVISION_TO_STRING(ALICEVISION_SFMDATAIO_VERSION_MAJOR) "." \ - ALICEVISION_TO_STRING(ALICEVISION_SFMDATAIO_VERSION_MINOR) "." \ - ALICEVISION_TO_STRING(ALICEVISION_SFMDATAIO_VERSION_REVISION) +#define ALICEVISION_SFMDATAIO_VERSION_STRING \ + ALICEVISION_TO_STRING(ALICEVISION_SFMDATAIO_VERSION_MAJOR) \ + "." ALICEVISION_TO_STRING(ALICEVISION_SFMDATAIO_VERSION_MINOR) "." ALICEVISION_TO_STRING(ALICEVISION_SFMDATAIO_VERSION_REVISION) namespace aliceVision { namespace sfmDataIO { enum ESfMData { - VIEWS = 1, - EXTRINSICS = 2, - INTRINSICS = 4, - STRUCTURE = 8, - OBSERVATIONS = 16, - OBSERVATIONS_WITH_FEATURES = 32, - LANDMARKS_UNCERTAINTY = 64, - POSES_UNCERTAINTY = 128, - CONSTRAINTS2D = 256, - - UNCERTAINTY = LANDMARKS_UNCERTAINTY | POSES_UNCERTAINTY, - ALL_DENSE = VIEWS | EXTRINSICS | INTRINSICS | STRUCTURE | OBSERVATIONS | CONSTRAINTS2D, - ALL = VIEWS | EXTRINSICS | INTRINSICS | STRUCTURE | OBSERVATIONS | OBSERVATIONS_WITH_FEATURES | UNCERTAINTY | CONSTRAINTS2D + VIEWS = 1, + EXTRINSICS = 2, + INTRINSICS = 4, + STRUCTURE = 8, + OBSERVATIONS = 16, + OBSERVATIONS_WITH_FEATURES = 32, + LANDMARKS_UNCERTAINTY = 64, + POSES_UNCERTAINTY = 128, + CONSTRAINTS2D = 256, + + UNCERTAINTY = LANDMARKS_UNCERTAINTY | POSES_UNCERTAINTY, + ALL_DENSE = VIEWS | EXTRINSICS | INTRINSICS | STRUCTURE | OBSERVATIONS | CONSTRAINTS2D, + ALL = VIEWS | EXTRINSICS | INTRINSICS | STRUCTURE | OBSERVATIONS | OBSERVATIONS_WITH_FEATURES | UNCERTAINTY | CONSTRAINTS2D }; /// check that each pose have a valid intrinsic and pose id in the existing View ids @@ -49,5 +48,5 @@ bool Load(sfmData::SfMData& sfmData, const std::string& filename, ESfMData partF /// save SfMData SfM scene to a file bool Save(const sfmData::SfMData& sfmData, const std::string& filename, ESfMData partFlag); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/sfmDataIOCompatibility_test.cpp b/src/aliceVision/sfmDataIO/sfmDataIOCompatibility_test.cpp index 4e40f27d0f..15a6a42a5d 100644 --- a/src/aliceVision/sfmDataIO/sfmDataIOCompatibility_test.cpp +++ b/src/aliceVision/sfmDataIO/sfmDataIOCompatibility_test.cpp @@ -30,15 +30,13 @@ BOOST_AUTO_TEST_CASE(Compatibility_generate_files_current_version) fs::path pathSource(__FILE__); { - fs::path outputPath = - pathSource.parent_path() / "compatibilityData" / - "scene_v" BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MAJOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MINOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_REVISION) ".json"; + fs::path outputPath = pathSource.parent_path() / "compatibilityData" / + "scene_v" BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MAJOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MINOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_REVISION) ".json"; BOOST_CHECK(sfmDataIO::Save(sfmData, outputPath.string(), ESfMData::ALL)); } { - fs::path outputPath = - pathSource.parent_path() / "compatibilityData" / - "scene_v" BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MAJOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MINOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_REVISION) ".abc"; + fs::path outputPath = pathSource.parent_path() / "compatibilityData" / + "scene_v" BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MAJOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_MINOR) "." BOOST_PP_STRINGIZE(ALICEVISION_SFMDATAIO_VERSION_REVISION) ".abc"; BOOST_CHECK(sfmDataIO::Save(sfmData, outputPath.string(), ESfMData::ALL)); } } @@ -54,7 +52,7 @@ BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_0) sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_0) @@ -71,8 +69,8 @@ BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_0) BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_1) { - +BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_1) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); @@ -82,11 +80,11 @@ BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_1) { sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_1) { - +BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_1) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); @@ -96,11 +94,11 @@ BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_1) { sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_2) { - +BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_2) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); @@ -110,11 +108,11 @@ BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_2) { sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_2) { - +BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_2) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); @@ -124,67 +122,65 @@ BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_2) { sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_3) { - +BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_3) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); fs::path pathSource(__FILE__); fs::path toLoad = pathSource.parent_path() / "compatibilityData" / "scene_v1.2.3.abc"; - //TODO when we will have files to compare + // TODO when we will have files to compare sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_3) { - +BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_3) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); fs::path pathSource(__FILE__); fs::path toLoad = pathSource.parent_path() / "compatibilityData" / "scene_v1.2.3.json"; - //TODO when we will have files to compare + // TODO when we will have files to compare sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_4) { - +BOOST_AUTO_TEST_CASE(Compatibility_abc_1_2_4) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); fs::path pathSource(__FILE__); fs::path toLoad = pathSource.parent_path() / "compatibilityData" / "scene_v1.2.4.abc"; - //TODO when we will have files to compare + // TODO when we will have files to compare sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } -BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_4) { - +BOOST_AUTO_TEST_CASE(Compatibility_json_1_2_4) +{ sfmData::SfMData sfmData; generateSampleScene(sfmData); fs::path pathSource(__FILE__); fs::path toLoad = pathSource.parent_path() / "compatibilityData" / "scene_v1.2.4.json"; - //TODO when we will have files to compare + // TODO when we will have files to compare sfmData::SfMData sfmDataLoad; BOOST_CHECK(sfmDataIO::Load(sfmDataLoad, toLoad.string(), ESfMData::ALL)); - BOOST_CHECK(sfmData == sfmDataLoad); + BOOST_CHECK(sfmData == sfmDataLoad); } - - diff --git a/src/aliceVision/sfmDataIO/sfmDataIO_test.cpp b/src/aliceVision/sfmDataIO/sfmDataIO_test.cpp index 92ef61864c..c54eb1ee75 100644 --- a/src/aliceVision/sfmDataIO/sfmDataIO_test.cpp +++ b/src/aliceVision/sfmDataIO/sfmDataIO_test.cpp @@ -30,43 +30,43 @@ namespace fs = boost::filesystem; // Add a 3D point with observation in 2 view (just in order to have non empty data) sfmData::SfMData createTestScene(std::size_t viewsCount = 2, std::size_t observationCount = 2, bool sharedIntrinsic = true) { - sfmData::SfMData sfmData; + sfmData::SfMData sfmData; - for(IndexT i = 0; i < viewsCount; ++i) - { - // Add views - std::ostringstream os; - os << "dataset/" << i << ".jpg"; - const IndexT viewId = i, poseId = i; - const IndexT intrinsicId = sharedIntrinsic ? 0 : i; //(shared or not intrinsics) + for (IndexT i = 0; i < viewsCount; ++i) + { + // Add views + std::ostringstream os; + os << "dataset/" << i << ".jpg"; + const IndexT viewId = i, poseId = i; + const IndexT intrinsicId = sharedIntrinsic ? 0 : i; //(shared or not intrinsics) - std::shared_ptr view = std::make_shared(os.str(),viewId, intrinsicId, poseId, 1500, 1000); + std::shared_ptr view = std::make_shared(os.str(), viewId, intrinsicId, poseId, 1500, 1000); - sfmData.getViews().emplace(viewId, view); + sfmData.getViews().emplace(viewId, view); - // Add poses - sfmData.setPose(*view, sfmData::CameraPose()); + // Add poses + sfmData.setPose(*view, sfmData::CameraPose()); - // Add intrinsics - if(!sharedIntrinsic || (i == 0)) - { - sfmData.getIntrinsics().emplace(i, std::make_shared(1500, 1000, 700, 600, 10, -20)); + // Add intrinsics + if (!sharedIntrinsic || (i == 0)) + { + sfmData.getIntrinsics().emplace(i, std::make_shared(1500, 1000, 700, 600, 10, -20)); + } } - } - // Fill with not meaningful tracks - sfmData::Observations observations; - const double unknownScale = 0.0; - for(std::size_t i = 0; i < observationCount; ++i) - { - observations[i] = sfmData::Observation( Vec2(i,i), i, unknownScale); - } + // Fill with not meaningful tracks + sfmData::Observations observations; + const double unknownScale = 0.0; + for (std::size_t i = 0; i < observationCount; ++i) + { + observations[i] = sfmData::Observation(Vec2(i, i), i, unknownScale); + } - sfmData.getLandmarks()[0].observations = observations; - sfmData.getLandmarks()[0].X = Vec3(11,22,33); - sfmData.getLandmarks()[0].descType = feature::EImageDescriberType::SIFT; + sfmData.getLandmarks()[0].observations = observations; + sfmData.getLandmarks()[0].X = Vec3(11, 22, 33); + sfmData.getLandmarks()[0].descType = feature::EImageDescriberType::SIFT; - return sfmData; + return sfmData; } BOOST_AUTO_TEST_CASE(SfMData_IO_SAVE_LOAD) @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(SfMData_IO_SAVE_LOAD) ext_Type.push_back("abc"); #endif - for(int i = 0; i < ext_Type.size(); ++i) + for (int i = 0; i < ext_Type.size(); ++i) { std::ostringstream os; os << "SAVE_LOAD" @@ -147,7 +147,7 @@ BOOST_AUTO_TEST_CASE(SfMData_IO_SAVE_LOAD) BOOST_TEST_CONTEXT("LOAD (subparts: INTRINSICS | EXTRINSICS), file format: " << ext_Type[i]) { - const sfmData::SfMData sfmData = createTestScene(2, 2, false); // 2 intrinsics group here + const sfmData::SfMData sfmData = createTestScene(2, 2, false); // 2 intrinsics group here BOOST_CHECK(Save(sfmData, filename, ALL)); sfmData::SfMData sfmDataLoad; ESfMData flags_part = ESfMData(ESfMData::INTRINSICS | ESfMData::EXTRINSICS); @@ -211,18 +211,19 @@ BOOST_AUTO_TEST_CASE(SfMData_IO_BigFile) { } */ -BOOST_AUTO_TEST_CASE(SfMData_IO_SAVE_PLY) { - - // SAVE as PLY - { - std::ostringstream os; - os << "SAVE_LOAD" << ".ply"; - const std::string filename = os.str(); - ALICEVISION_LOG_DEBUG("Testing:" << filename); +BOOST_AUTO_TEST_CASE(SfMData_IO_SAVE_PLY) +{ + // SAVE as PLY + { + std::ostringstream os; + os << "SAVE_LOAD" + << ".ply"; + const std::string filename = os.str(); + ALICEVISION_LOG_DEBUG("Testing:" << filename); - const sfmData::SfMData sfmData = createTestScene(2, 2, true); - ESfMData flags_part = ESfMData(EXTRINSICS | STRUCTURE); - BOOST_CHECK( Save(sfmData, filename, flags_part) ); - BOOST_CHECK( fs::is_regular_file(filename) ); - } + const sfmData::SfMData sfmData = createTestScene(2, 2, true); + ESfMData flags_part = ESfMData(EXTRINSICS | STRUCTURE); + BOOST_CHECK(Save(sfmData, filename, flags_part)); + BOOST_CHECK(fs::is_regular_file(filename)); + } } diff --git a/src/aliceVision/sfmDataIO/viewIO.cpp b/src/aliceVision/sfmDataIO/viewIO.cpp index 219491f254..932292e6c2 100644 --- a/src/aliceVision/sfmDataIO/viewIO.cpp +++ b/src/aliceVision/sfmDataIO/viewIO.cpp @@ -23,11 +23,8 @@ namespace sfmDataIO { void updateIncompleteView(sfmData::View& view, EViewIdMethod viewIdMethod, const std::string& viewIdRegex) { // check if the view is complete - if(view.getViewId() != UndefinedIndexT && - view.getIntrinsicId() != UndefinedIndexT && - view.getPoseId() == view.getViewId() && - view.getImage().getHeight() > 0 && - view.getImage().getWidth() > 0) + if (view.getViewId() != UndefinedIndexT && view.getIntrinsicId() != UndefinedIndexT && view.getPoseId() == view.getViewId() && + view.getImage().getHeight() > 0 && view.getImage().getWidth() > 0) return; int width, height; @@ -37,20 +34,20 @@ void updateIncompleteView(sfmData::View& view, EViewIdMethod viewIdMethod, const view.getImage().setHeight(height); // reset metadata - if(view.getImage().getMetadata().empty()) + if (view.getImage().getMetadata().empty()) view.getImage().setMetadata(image::getMapFromMetadata(metadata)); // Reset viewId - if(view.getViewId() == UndefinedIndexT) + if (view.getViewId() == UndefinedIndexT) { - if(viewIdMethod == EViewIdMethod::FILENAME) + if (viewIdMethod == EViewIdMethod::FILENAME) { std::regex re; try { re = viewIdRegex; } - catch(const std::regex_error& e) + catch (const std::regex_error& e) { throw std::invalid_argument("Invalid regex conversion, your regexfilename '" + viewIdRegex + "' may be invalid."); } @@ -60,24 +57,28 @@ void updateIncompleteView(sfmData::View& view, EViewIdMethod viewIdMethod, const std::smatch match; std::regex_search(filename, match, re); - if(match.size() == 2) + if (match.size() == 2) { try { const IndexT id(std::stoul(match.str(1))); view.setViewId(id); } - catch(std::invalid_argument& e) + catch (std::invalid_argument& e) { - ALICEVISION_LOG_ERROR("ViewId captured in the filename '" << filename << "' can't be converted to a number. " - "The regex '" << viewIdRegex << "' is probably incorrect."); + ALICEVISION_LOG_ERROR("ViewId captured in the filename '" << filename + << "' can't be converted to a number. " + "The regex '" + << viewIdRegex << "' is probably incorrect."); throw; } } else { - ALICEVISION_LOG_ERROR("The Regex '" << viewIdRegex << "' must match a unique number in the filename " << filename << "' to be used as viewId."); - throw std::invalid_argument("The Regex '" + viewIdRegex + "' must match a unique number in the filename " + filename + "' to be used as viewId."); + ALICEVISION_LOG_ERROR("The Regex '" << viewIdRegex << "' must match a unique number in the filename " << filename + << "' to be used as viewId."); + throw std::invalid_argument("The Regex '" + viewIdRegex + "' must match a unique number in the filename " + filename + + "' to be used as viewId."); } } else @@ -87,37 +88,37 @@ void updateIncompleteView(sfmData::View& view, EViewIdMethod viewIdMethod, const } } - if(view.getPoseId() == UndefinedIndexT) + if (view.getPoseId() == UndefinedIndexT) { // check if the rig poseId id is defined - if(view.isPartOfRig()) + if (view.isPartOfRig()) { ALICEVISION_LOG_ERROR("Error: Can't find poseId for'" << fs::path(view.getImage().getImagePath()).filename().string() - << "' marked as part of a rig." << std::endl); - throw std::invalid_argument("Error: Can't find poseId for'" + fs::path(view.getImage().getImagePath()).filename().string() - + "' marked as part of a rig."); + << "' marked as part of a rig." << std::endl); + throw std::invalid_argument("Error: Can't find poseId for'" + fs::path(view.getImage().getImagePath()).filename().string() + + "' marked as part of a rig."); } else view.setPoseId(view.getViewId()); } - else if((!view.isPartOfRig()) && (view.getPoseId() != view.getViewId())) + else if ((!view.isPartOfRig()) && (view.getPoseId() != view.getViewId())) { - ALICEVISION_LOG_WARNING("PoseId and viewId are different for image '" << fs::path(view.getImage().getImagePath()).filename().string() - << "'." << std::endl); + ALICEVISION_LOG_WARNING("PoseId and viewId are different for image '" << fs::path(view.getImage().getImagePath()).filename().string() << "'." + << std::endl); } } -std::shared_ptr getViewIntrinsic( - const sfmData::View& view, - double mmFocalLength, - double sensorWidth, - double defaultFocalLength, - double defaultFieldOfView, - double defaultFocalRatio, - double defaultOffsetX, double defaultOffsetY, - LensParam* lensParam, - camera::EINTRINSIC defaultIntrinsicType, - camera::EINTRINSIC allowedEintrinsics) +std::shared_ptr getViewIntrinsic(const sfmData::View& view, + double mmFocalLength, + double sensorWidth, + double defaultFocalLength, + double defaultFieldOfView, + double defaultFocalRatio, + double defaultOffsetX, + double defaultOffsetY, + LensParam* lensParam, + camera::EINTRINSIC defaultIntrinsicType, + camera::EINTRINSIC allowedEintrinsics) { // can't combine defaultFocalLengthPx and defaultFieldOfView assert(defaultFocalLength < 0 || defaultFieldOfView < 0); @@ -153,7 +154,8 @@ std::shared_ptr getViewIntrinsic( bool isResized = false; - if (view.getImage().hasMetadata({"Exif:PixelXDimension", "PixelXDimension"}) && view.getImage().hasMetadata({"Exif:PixelYDimension", "PixelYDimension"})) // has dimension metadata + if (view.getImage().hasMetadata({"Exif:PixelXDimension", "PixelXDimension"}) && + view.getImage().hasMetadata({"Exif:PixelYDimension", "PixelYDimension"})) // has dimension metadata { // check if the image is resized int exifWidth = std::stoi(view.getImage().getMetadata({"Exif:PixelXDimension", "PixelXDimension"})); @@ -161,17 +163,14 @@ std::shared_ptr getViewIntrinsic( // if metadata is rotated if (exifWidth == view.getImage().getHeight() && exifHeight == view.getImage().getWidth()) - std::swap(exifWidth, exifHeight); + std::swap(exifWidth, exifHeight); - if (exifWidth > 0 && exifHeight > 0 && - (exifWidth != view.getImage().getWidth() || exifHeight != view.getImage().getHeight())) + if (exifWidth > 0 && exifHeight > 0 && (exifWidth != view.getImage().getWidth() || exifHeight != view.getImage().getHeight())) { - ALICEVISION_LOG_WARNING("Resized image detected: " << fs::path(view.getImage().getImagePath()).filename().string() - << std::endl - << "\t- real image size: " << view.getImage().getWidth() - << "x" << view.getImage().getHeight() << std::endl - << "\t- image size from exif metadata is: " << exifWidth - << "x" << exifHeight << std::endl); + ALICEVISION_LOG_WARNING("Resized image detected: " + << fs::path(view.getImage().getImagePath()).filename().string() << std::endl + << "\t- real image size: " << view.getImage().getWidth() << "x" << view.getImage().getHeight() << std::endl + << "\t- image size from exif metadata is: " << exifWidth << "x" << exifHeight << std::endl); isResized = true; } } @@ -180,8 +179,8 @@ std::shared_ptr getViewIntrinsic( if (mmFocalLength <= 0.0) { ALICEVISION_LOG_WARNING("Image '" << fs::path(view.getImage().getImagePath()).filename().string() - << "' focal length (in mm) metadata is missing." << std::endl - << "Can't compute focal length, use default." << std::endl); + << "' focal length (in mm) metadata is missing." << std::endl + << "Can't compute focal length, use default." << std::endl); } else { @@ -204,11 +203,9 @@ std::shared_ptr getViewIntrinsic( // choose intrinsic type camera::EINTRINSIC lcpIntrinsicType = - (lensParam == nullptr || lensParam->isEmpty()) ? - camera::EINTRINSIC::UNKNOWN : - (lensParam->isFisheye() ? - camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE : - camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3); + (lensParam == nullptr || lensParam->isEmpty()) + ? camera::EINTRINSIC::UNKNOWN + : (lensParam->isFisheye() ? camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE : camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3); if (cameraBrand == "Custom") { @@ -226,13 +223,12 @@ std::shared_ptr getViewIntrinsic( else if (intrinsicType == camera::EINTRINSIC::UNKNOWN) { // Choose a default camera model if no default type - static const std::initializer_list intrinsicsPriorities = - {camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, - camera::EINTRINSIC::PINHOLE_CAMERA_BROWN, - camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL1, - camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE, - camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE1, - camera::EINTRINSIC::PINHOLE_CAMERA}; + static const std::initializer_list intrinsicsPriorities = {camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3, + camera::EINTRINSIC::PINHOLE_CAMERA_BROWN, + camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL1, + camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE, + camera::EINTRINSIC::PINHOLE_CAMERA_FISHEYE1, + camera::EINTRINSIC::PINHOLE_CAMERA}; for (const auto& e : intrinsicsPriorities) { @@ -251,17 +247,18 @@ std::shared_ptr getViewIntrinsic( } // create the desired intrinsic - std::shared_ptr intrinsic = - camera::createIntrinsic( - /*camera*/ intrinsicType, - /*dimensions*/ view.getImage().getWidth(), view.getImage().getHeight(), - /*focal length*/ pxFocalLength, pxFocalLength / focalRatio, - /*offset*/ 0, 0); + std::shared_ptr intrinsic = camera::createIntrinsic( + /*camera*/ intrinsicType, + /*dimensions*/ view.getImage().getWidth(), + view.getImage().getHeight(), + /*focal length*/ pxFocalLength, + pxFocalLength / focalRatio, + /*offset*/ 0, + 0); if (hasFocalLengthInput) { - std::shared_ptr intrinsicScaleOffset = - std::dynamic_pointer_cast(intrinsic); + std::shared_ptr intrinsicScaleOffset = std::dynamic_pointer_cast(intrinsic); if (intrinsicScaleOffset) { @@ -332,17 +329,17 @@ bool extractNumberFromFileStem(const std::string& imagePathStem, IndexT& number, { // check if the image stem contains a number // regexFrame: ^(.*\D)?([0-9]+)([\-_\.].*[[:alpha:]].*)?$ - std::regex regexFrame("^(.*\\D)?" // the optional prefix which ends with a non digit character - "([0-9]+)" // the number - "([\\-_\\.]" // the suffix starts with a separator - ".*[[:alpha:]].*" // at least one letter in the suffix - ")?$" // suffix is optional + std::regex regexFrame("^(.*\\D)?" // the optional prefix which ends with a non digit character + "([0-9]+)" // the number + "([\\-_\\.]" // the suffix starts with a separator + ".*[[:alpha:]].*" // at least one letter in the suffix + ")?$" // suffix is optional ); std::smatch matches; bool containsNumber = std::regex_search(imagePathStem, matches, regexFrame); - if(containsNumber) + if (containsNumber) { prefix = matches[1]; suffix = matches[3]; @@ -360,5 +357,5 @@ bool extractNumberFromFileStem(const std::string& imagePathStem, IndexT& number, return containsNumber; } -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/viewIO.hpp b/src/aliceVision/sfmDataIO/viewIO.hpp index 0499c9bc1f..31e89d440f 100644 --- a/src/aliceVision/sfmDataIO/viewIO.hpp +++ b/src/aliceVision/sfmDataIO/viewIO.hpp @@ -26,26 +26,27 @@ enum class EViewIdMethod inline std::string EViewIdMethod_enumToString(EViewIdMethod viewIdMethod) { - switch(viewIdMethod) + switch (viewIdMethod) { - case EViewIdMethod::METADATA: return "metadata"; - case EViewIdMethod::FILENAME: return "filename"; + case EViewIdMethod::METADATA: + return "metadata"; + case EViewIdMethod::FILENAME: + return "filename"; } throw std::out_of_range("Invalid ViewIdMethod type Enum: " + std::to_string(int(viewIdMethod))); } inline EViewIdMethod EViewIdMethod_stringToEnum(const std::string& viewIdMethod) { - if(viewIdMethod == "metadata") return EViewIdMethod::METADATA; - if(viewIdMethod == "filename") return EViewIdMethod::FILENAME; + if (viewIdMethod == "metadata") + return EViewIdMethod::METADATA; + if (viewIdMethod == "filename") + return EViewIdMethod::FILENAME; throw std::out_of_range("Invalid ViewIdMethod type string " + viewIdMethod); } -inline std::ostream& operator<<(std::ostream& os, EViewIdMethod s) -{ - return os << EViewIdMethod_enumToString(s); -} +inline std::ostream& operator<<(std::ostream& os, EViewIdMethod s) { return os << EViewIdMethod_enumToString(s); } inline std::istream& operator>>(std::istream& in, EViewIdMethod& s) { @@ -74,48 +75,43 @@ void updateIncompleteView(sfmData::View& view, EViewIdMethod viewIdMethod = EVie * @param[in] defaultOffsetX * @param[in] defaultOffsetY * @param[in] lensParam Lens data from LCP file - * @param[in] defaultIntrinsicType (unknown by default) + * @param[in] defaultIntrinsicType (unknown by default) * @param[in] allowedEintrinsics The intrinsics values that can be attributed * @return shared_ptr IntrinsicBase */ -std::shared_ptr getViewIntrinsic( - const sfmData::View& view, - double mmFocalLength = -1.0, - double sensorWidth = -1, - double defaultFocalLength = -1, - double defaultFieldOfView = -1, - double defaultFocalRatio = 1.0, - double defaultOffsetX = 0.0, double defaultOffsetY = 0.0, - LensParam *lensParam = nullptr, - camera::EINTRINSIC defaultIntrinsicType = camera::EINTRINSIC::UNKNOWN, - camera::EINTRINSIC allowedEintrinsics = camera::EINTRINSIC::VALID_CAMERA_MODEL); +std::shared_ptr getViewIntrinsic(const sfmData::View& view, + double mmFocalLength = -1.0, + double sensorWidth = -1, + double defaultFocalLength = -1, + double defaultFieldOfView = -1, + double defaultFocalRatio = 1.0, + double defaultOffsetX = 0.0, + double defaultOffsetY = 0.0, + LensParam* lensParam = nullptr, + camera::EINTRINSIC defaultIntrinsicType = camera::EINTRINSIC::UNKNOWN, + camera::EINTRINSIC allowedEintrinsics = camera::EINTRINSIC::VALID_CAMERA_MODEL); /** -* @brief Allows you to retrieve the files paths corresponding to a view by searching through a list of folders. -* Filename must be the same or equal to the viewId. -* @param[in] the view -* @param[in] the folder list -* @return the list of paths to the corresponding view if found in the folders, otherwise returns an empty list. -*/ + * @brief Allows you to retrieve the files paths corresponding to a view by searching through a list of folders. + * Filename must be the same or equal to the viewId. + * @param[in] the view + * @param[in] the folder list + * @return the list of paths to the corresponding view if found in the folders, otherwise returns an empty list. + */ std::vector viewPathsFromFolders(const sfmData::View& view, const std::vector& folders); - /* -* @brief Allows to detect if an image filename (stripped of its extension) contains a number. -* This function can be used to detect if an image is part of an image sequence from the analysis of its filename. -* Expected pattern: (optional prefix which ends with a non digit character)(a number)(optional suffix with at least one letter which starts with a separator ('.', '-', '_')) -* Examples: -* IMG01234 -* IMG_01234.cam -* C4M0123-A -* 01234 -* @param[in] imagePathStem the image filename stripped of its extension -* @param[out] number the detected number (or UndefinedIndexT) -* @param[out] prefix the detected prefix (or empty) -* @param[out] suffix the detected suffix (or empty) -* @return true if the image filename (stripped of its extension) contains a number -*/ + * @brief Allows to detect if an image filename (stripped of its extension) contains a number. + * This function can be used to detect if an image is part of an image sequence from the analysis of its filename. + * Expected pattern: (optional prefix which ends with a non digit character)(a number)(optional suffix with at least one letter which starts + * with a separator ('.', '-', '_')) Examples: IMG01234 IMG_01234.cam C4M0123-A 01234 + * @param[in] imagePathStem the image filename stripped of its extension + * @param[out] number the detected number (or UndefinedIndexT) + * @param[out] prefix the detected prefix (or empty) + * @param[out] suffix the detected suffix (or empty) + * @return true if the image filename (stripped of its extension) contains a number + */ bool extractNumberFromFileStem(const std::string& imagePathStem, IndexT& number, std::string& prefix, std::string& suffix); -} // namespace sfmDataIO -} // namespace aliceVision +} // namespace sfmDataIO +} // namespace aliceVision diff --git a/src/aliceVision/sfmMvsUtils/visibility.cpp b/src/aliceVision/sfmMvsUtils/visibility.cpp index 16ceda5abb..b595a43905 100644 --- a/src/aliceVision/sfmMvsUtils/visibility.cpp +++ b/src/aliceVision/sfmMvsUtils/visibility.cpp @@ -14,11 +14,10 @@ #include #include - namespace aliceVision { namespace mvsUtils { -void createRefMeshFromDenseSfMData(mesh::Mesh & refMesh, const sfmData::SfMData & sfmData, const mvsUtils::MultiViewParams & mp) +void createRefMeshFromDenseSfMData(mesh::Mesh& refMesh, const sfmData::SfMData& sfmData, const mvsUtils::MultiViewParams& mp) { mesh::PointsVisibility& refVisibilities = refMesh.pointsVisibilities; const std::size_t nbPoints = sfmData.getLandmarks().size(); @@ -38,5 +37,5 @@ void createRefMeshFromDenseSfMData(mesh::Mesh & refMesh, const sfmData::SfMData } } -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/sfmMvsUtils/visibility.hpp b/src/aliceVision/sfmMvsUtils/visibility.hpp index 17f55e3aca..5d7cec49b8 100644 --- a/src/aliceVision/sfmMvsUtils/visibility.hpp +++ b/src/aliceVision/sfmMvsUtils/visibility.hpp @@ -6,7 +6,6 @@ #pragma once - namespace aliceVision { namespace mesh { @@ -22,5 +21,5 @@ class MultiViewParams; void createRefMeshFromDenseSfMData(mesh::Mesh& outRefMesh, const sfmData::SfMData& sfmData, const mvsUtils::MultiViewParams& mp); -} // namespace mvsUtils -} // namespace aliceVision +} // namespace mvsUtils +} // namespace aliceVision diff --git a/src/aliceVision/sphereDetection/sphereDetection.cpp b/src/aliceVision/sphereDetection/sphereDetection.cpp index 966164842f..e53efb0712 100644 --- a/src/aliceVision/sphereDetection/sphereDetection.cpp +++ b/src/aliceVision/sphereDetection/sphereDetection.cpp @@ -73,8 +73,7 @@ void modelExplore(Ort::Session& session) ALICEVISION_LOG_DEBUG(" Type : " << inputType); std::vector inputShape = inputInfo2.GetShape(); - size_t inputSize = - std::accumulate(begin(inputShape), end(inputShape), 1, std::multiplies()); + size_t inputSize = std::accumulate(begin(inputShape), end(inputShape), 1, std::multiplies()); ALICEVISION_LOG_DEBUG(" Shape: " << inputShape); ALICEVISION_LOG_DEBUG(" Size : " << inputSize); } @@ -98,8 +97,7 @@ void modelExplore(Ort::Session& session) ALICEVISION_LOG_DEBUG(" Type: " << outputType); std::vector outputShape = outputInfo2.GetShape(); - const size_t outputSize = - std::accumulate(begin(outputShape), end(outputShape), 1, std::multiplies()); + const size_t outputSize = std::accumulate(begin(outputShape), end(outputShape), 1, std::multiplies()); ALICEVISION_LOG_DEBUG(" Shape: " << outputShape); ALICEVISION_LOG_DEBUG(" Size: " << outputSize); } @@ -124,8 +122,7 @@ Prediction predict(Ort::Session& session, const fs::path imagePath, const float // Inference on CPU // TODO: use GPU - Ort::MemoryInfo memoryInfo = - Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); + Ort::MemoryInfo memoryInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); // Initialize input tensor std::vector inputShape = {1, 3, imageAlice.Height(), imageAlice.Width()}; @@ -135,19 +132,15 @@ Prediction predict(Ort::Session& session, const fs::path imagePath, const float // Create input data std::vector inputData; - inputData.push_back( - Ort::Value::CreateTensor( - memoryInfo, inputTensor.data(), inputSize, inputShape.data(), inputShape.size() - ) - ); + inputData.push_back(Ort::Value::CreateTensor(memoryInfo, inputTensor.data(), inputSize, inputShape.data(), inputShape.size())); // Select inputs and outputs std::vector inputNames{"input"}; std::vector outputNames{"boxes", "scores", "masks"}; // Run the inference - auto output = session.Run(Ort::RunOptions{nullptr}, inputNames.data(), inputData.data(), inputNames.size(), - outputNames.data(), outputNames.size()); + auto output = + session.Run(Ort::RunOptions{nullptr}, inputNames.data(), inputData.data(), inputNames.size(), outputNames.data(), outputNames.size()); // Get pointers to outputs float* bboxesPtr = output.at(0).GetTensorMutableData(); @@ -158,7 +151,7 @@ Prediction predict(Ort::Session& session, const fs::path imagePath, const float const auto shape = infos.GetShape(); // Get scores of detections - std::vector allScores = { scoresPtr, scoresPtr + shape[0] }; + std::vector allScores = {scoresPtr, scoresPtr + shape[0]}; // Initialize arrays std::vector> bboxes; @@ -179,11 +172,10 @@ Prediction predict(Ort::Session& session, const fs::path imagePath, const float } } - return Prediction { bboxes, scores, imageOpencvShape }; + return Prediction{bboxes, scores, imageOpencvShape}; } -void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs::path outputPath, - const float minScore) +void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs::path outputPath, const float minScore) { // Main tree bpt::ptree fileTree; @@ -234,8 +226,7 @@ void sphereDetection(const sfmData::SfMData& sfmData, Ort::Session& session, fs: bpt::write_json(outputPath.append("detection.json").string(), fileTree); } -void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, - fs::path outputPath) +void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, fs::path outputPath) { // Main tree bpt::ptree fileTree; @@ -262,5 +253,5 @@ void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, - fs::path outputPath); +void writeManualSphereJSON(const sfmData::SfMData& sfmData, const std::array& sphereParam, fs::path outputPath); -} -} +} // namespace sphereDetection +} // namespace aliceVision diff --git a/src/aliceVision/stl/DynamicBitset.hpp b/src/aliceVision/stl/DynamicBitset.hpp index b238dcdfa5..a82383cb1d 100644 --- a/src/aliceVision/stl/DynamicBitset.hpp +++ b/src/aliceVision/stl/DynamicBitset.hpp @@ -13,39 +13,41 @@ #include #include -namespace stl +namespace stl { + +/** + * Class derived from the Boost dynamic_bitset<> implementation. + * Distributed under the Boost Software License, Version 1.0. + * (http://www.boost.org/LICENSE_1_0.txt) + */ +struct dynamic_bitset { - - /** - * Class derived from the Boost dynamic_bitset<> implementation. - * Distributed under the Boost Software License, Version 1.0. - * (http://www.boost.org/LICENSE_1_0.txt) - */ - struct dynamic_bitset - { typedef unsigned char BlockType; static const int bits_per_block = (std::numeric_limits::digits); // Reference over a single bit of a sub-block class reference { - BlockType & m_block; - const BlockType m_mask; - - public: - reference(BlockType & b, BlockType pos) - :m_block(b), - m_mask((assert(pos < bits_per_block), BlockType(1) << pos) - ) - { } - - operator bool() const { return (m_block & m_mask) != 0; } - reference& operator=(bool x) { do_assign(x); return *this; } // for b[i] = x - - void do_set() { m_block |= m_mask; } - void do_reset() { m_block &= ~m_mask; } - void do_flip() { m_block ^= m_mask; } - void do_assign(bool x) { x ? do_set() : do_reset(); } + BlockType& m_block; + const BlockType m_mask; + + public: + reference(BlockType& b, BlockType pos) + : m_block(b), + m_mask((assert(pos < bits_per_block), BlockType(1) << pos)) + {} + + operator bool() const { return (m_block & m_mask) != 0; } + reference& operator=(bool x) + { + do_assign(x); + return *this; + } // for b[i] = x + + void do_set() { m_block |= m_mask; } + void do_reset() { m_block &= ~m_mask; } + void do_flip() { m_block ^= m_mask; } + void do_assign(bool x) { x ? do_set() : do_reset(); } }; size_t block_index(size_t pos) const { return pos / bits_per_block; } @@ -64,27 +66,23 @@ namespace stl // Constructor dynamic_bitset(size_t num_bits = 0) { - vec_bits.resize(calc_num_blocks(num_bits)); - m_num_bits = num_bits; + vec_bits.resize(calc_num_blocks(num_bits)); + m_num_bits = num_bits; } dynamic_bitset& reset() { - std::fill(vec_bits.begin(), vec_bits.end(), BlockType(0)); - return *this; + std::fill(vec_bits.begin(), vec_bits.end(), BlockType(0)); + return *this; } - const BlockType * data() const { return &vec_bits[0]; } + const BlockType* data() const { return &vec_bits[0]; } private: - inline size_t calc_num_blocks(size_t num_bits) - { - return num_bits / bits_per_block - + static_cast(num_bits % bits_per_block != 0); - } + inline size_t calc_num_blocks(size_t num_bits) { return num_bits / bits_per_block + static_cast(num_bits % bits_per_block != 0); } // DATA std::vector vec_bits; size_t m_num_bits; - }; -} // namespace stl +}; +} // namespace stl diff --git a/src/aliceVision/stl/FlatMap.hpp b/src/aliceVision/stl/FlatMap.hpp index 9cedf758bf..b90a42b798 100644 --- a/src/aliceVision/stl/FlatMap.hpp +++ b/src/aliceVision/stl/FlatMap.hpp @@ -9,11 +9,7 @@ #include -namespace stl -{ - template - ,class Allocator = std::allocator > > - using flat_map = boost::container::flat_map; +namespace stl { +template, class Allocator = std::allocator>> +using flat_map = boost::container::flat_map; } diff --git a/src/aliceVision/stl/FlatSet.hpp b/src/aliceVision/stl/FlatSet.hpp index 9711d73d1d..808bfc6783 100644 --- a/src/aliceVision/stl/FlatSet.hpp +++ b/src/aliceVision/stl/FlatSet.hpp @@ -9,10 +9,7 @@ #include -namespace stl -{ - template - ,class Allocator = std::allocator > - using flat_set = boost::container::flat_set; +namespace stl { +template, class Allocator = std::allocator> +using flat_set = boost::container::flat_set; } diff --git a/src/aliceVision/stl/bitmask.hpp b/src/aliceVision/stl/bitmask.hpp index 4f9a768c6b..8fb8496360 100644 --- a/src/aliceVision/stl/bitmask.hpp +++ b/src/aliceVision/stl/bitmask.hpp @@ -9,31 +9,44 @@ #define ALICEVISION_STL_BITMASK_H // Taken from boost -#define ALICEVISION_BITMASK(Bitmask) \ - \ - inline Bitmask operator| (Bitmask x , Bitmask y ) \ - { return static_cast( static_cast(x) \ - | static_cast(y)); } \ - \ - inline Bitmask operator& (Bitmask x , Bitmask y ) \ - { return static_cast( static_cast(x) \ - & static_cast(y)); } \ - \ - inline Bitmask operator^ (Bitmask x , Bitmask y ) \ - { return static_cast( static_cast(x) \ - ^ static_cast(y)); } \ - \ - inline Bitmask operator~ (Bitmask x ) \ - { return static_cast(~static_cast(x)); } \ - \ - inline Bitmask & operator&=(Bitmask & x , Bitmask y) \ - { x = x & y ; return x ; } \ - \ - inline Bitmask & operator|=(Bitmask & x , Bitmask y) \ - { x = x | y ; return x ; } \ - \ - inline Bitmask & operator^=(Bitmask & x , Bitmask y) \ - { x = x ^ y ; return x ; } - +#define ALICEVISION_BITMASK(Bitmask) \ + \ + inline Bitmask operator|(Bitmask x, Bitmask y) \ + { \ + return static_cast(static_cast(x) | static_cast(y)); \ + } \ + \ + inline Bitmask operator&(Bitmask x, Bitmask y) \ + { \ + return static_cast(static_cast(x) & static_cast(y)); \ + } \ + \ + inline Bitmask operator^(Bitmask x, Bitmask y) \ + { \ + return static_cast(static_cast(x) ^ static_cast(y)); \ + } \ + \ + inline Bitmask operator~(Bitmask x) \ + { \ + return static_cast(~static_cast(x)); \ + } \ + \ + inline Bitmask& operator&=(Bitmask& x, Bitmask y) \ + { \ + x = x & y; \ + return x; \ + } \ + \ + inline Bitmask& operator|=(Bitmask& x, Bitmask y) \ + { \ + x = x | y; \ + return x; \ + } \ + \ + inline Bitmask& operator^=(Bitmask& x, Bitmask y) \ + { \ + x = x ^ y; \ + return x; \ + } #endif // ALICEVISION_STL_HASH_H diff --git a/src/aliceVision/stl/dynamicBitset_test.cpp b/src/aliceVision/stl/dynamicBitset_test.cpp index bbb8b4240d..fbc6d29636 100644 --- a/src/aliceVision/stl/dynamicBitset_test.cpp +++ b/src/aliceVision/stl/dynamicBitset_test.cpp @@ -14,59 +14,59 @@ BOOST_AUTO_TEST_CASE(DYNAMIC_BITSET_InitAndReset_64) { - using namespace stl; + using namespace stl; - const int nbBits = 64; - dynamic_bitset mybitset(nbBits); + const int nbBits = 64; + dynamic_bitset mybitset(nbBits); - // Check that there is nbBits bit stored - BOOST_CHECK_EQUAL(64, mybitset.size()); - // Check that there is just the necessary count of BlockType allocated for storage - BOOST_CHECK_EQUAL(64/dynamic_bitset::bits_per_block, mybitset.num_blocks()); - - // Set some bits to 1 - for (int i = 0; i < mybitset.size(); i+=2) - mybitset[i] = true; + // Check that there is nbBits bit stored + BOOST_CHECK_EQUAL(64, mybitset.size()); + // Check that there is just the necessary count of BlockType allocated for storage + BOOST_CHECK_EQUAL(64 / dynamic_bitset::bits_per_block, mybitset.num_blocks()); - // Check that some bits have been correctly set to 1 - for (int i = 0; i < mybitset.size(); ++i) - { - BOOST_CHECK_EQUAL(!(i%2), mybitset[i]); - } - - // Reset the value to 0 - mybitset.reset(); - for (int i = 0; i < mybitset.size(); ++i) - { - BOOST_CHECK_EQUAL(false, mybitset[i]); - } + // Set some bits to 1 + for (int i = 0; i < mybitset.size(); i += 2) + mybitset[i] = true; + + // Check that some bits have been correctly set to 1 + for (int i = 0; i < mybitset.size(); ++i) + { + BOOST_CHECK_EQUAL(!(i % 2), mybitset[i]); + } + + // Reset the value to 0 + mybitset.reset(); + for (int i = 0; i < mybitset.size(); ++i) + { + BOOST_CHECK_EQUAL(false, mybitset[i]); + } } // Create a dynamic_bitset that is shorter than the internal used bit container BOOST_AUTO_TEST_CASE(DYNAMIC_BITSET_InitAndReset_4) { - using namespace stl; + using namespace stl; + + const int nbBits = 4; + dynamic_bitset mybitset(nbBits); + + BOOST_CHECK_EQUAL(4, mybitset.size()); + BOOST_CHECK_EQUAL(1, mybitset.num_blocks()); - const int nbBits = 4; - dynamic_bitset mybitset(nbBits); - - BOOST_CHECK_EQUAL(4, mybitset.size()); - BOOST_CHECK_EQUAL(1, mybitset.num_blocks()); + // Set some bits to 1 + for (int i = 0; i < mybitset.size(); i += 2) + mybitset[i] = true; - // Set some bits to 1 - for (int i = 0; i < mybitset.size(); i+=2) - mybitset[i] = true; + // Check that some bits have been correctly set to 1 + for (int i = 0; i < mybitset.size(); ++i) + { + BOOST_CHECK_EQUAL(!(i % 2), mybitset[i]); + } - // Check that some bits have been correctly set to 1 - for (int i = 0; i < mybitset.size(); ++i) - { - BOOST_CHECK_EQUAL(!(i%2), mybitset[i]); - } - - // Reset the value to 0 - mybitset.reset(); - for (int i = 0; i < mybitset.size(); ++i) - { - BOOST_CHECK_EQUAL(false, mybitset[i]); - } + // Reset the value to 0 + mybitset.reset(); + for (int i = 0; i < mybitset.size(); ++i) + { + BOOST_CHECK_EQUAL(false, mybitset[i]); + } } diff --git a/src/aliceVision/stl/hash.hpp b/src/aliceVision/stl/hash.hpp index fc07ecf9a9..9736871c95 100644 --- a/src/aliceVision/stl/hash.hpp +++ b/src/aliceVision/stl/hash.hpp @@ -10,18 +10,17 @@ #include -namespace stl -{ +namespace stl { // Combine hashing value // http://www.boost.org/doc/libs/1_37_0/doc/html/hash/reference.html#boost.hash_combine -template +template inline void hash_combine(std::size_t& seed, const T& v) { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } -} // namespace stl +} // namespace stl #endif // ALICEVISION_STL_HASH_H diff --git a/src/aliceVision/stl/indexedSort.hpp b/src/aliceVision/stl/indexedSort.hpp index 5e6a0b78ff..be046f037a 100644 --- a/src/aliceVision/stl/indexedSort.hpp +++ b/src/aliceVision/stl/indexedSort.hpp @@ -8,59 +8,53 @@ #ifndef ALICEVISION_STL_INDEXED_SORT_H #define ALICEVISION_STL_INDEXED_SORT_H -namespace stl +namespace stl { +namespace indexed_sort { +template +struct sort_index_packet_ascend { -namespace indexed_sort -{ - template - struct sort_index_packet_ascend { T1 val; T2 index; - }; +}; - template - struct sort_index_packet_descend { +template +struct sort_index_packet_descend +{ T1 val; T2 index; - }; +}; - template - inline - bool - operator< (const sort_index_packet_ascend& A, - const sort_index_packet_ascend& B) { +template +inline bool operator<(const sort_index_packet_ascend& A, const sort_index_packet_ascend& B) +{ return A.val < B.val; - } +} - template - inline - bool - operator< (const sort_index_packet_descend& A, - const sort_index_packet_descend& B) { +template +inline bool operator<(const sort_index_packet_descend& A, const sort_index_packet_descend& B) +{ return A.val > B.val; - } +} - /// Sort by default all indexed value, else sort only the NN smallest element of the indexed array. - template - void - inline - sort_index_helper(std::vector& packet_vec, - const eT* in_mem, int NN = -1) { +/// Sort by default all indexed value, else sort only the NN smallest element of the indexed array. +template +void inline sort_index_helper(std::vector& packet_vec, const eT* in_mem, int NN = -1) +{ const size_t n_elem = packet_vec.size(); - for(size_t i=0; i - typename T::first_type operator()(const T & keyValuePair) const - { - return keyValuePair.first; - } + template + typename T::first_type operator()(const T& keyValuePair) const + { + return keyValuePair.first; + } }; /// Allow to select the Values of a map. struct RetrieveValue { - template - typename T::second_type operator()(const T & keyValuePair) const - { - return keyValuePair.second; - } + template + typename T::second_type operator()(const T& keyValuePair) const + { + return keyValuePair.second; + } }; -} +} // namespace stl namespace aliceVision { @@ -56,7 +56,7 @@ template const typename Map::mapped_type& map_get_with_default(const Map& m, const typename Map::key_type& key, const typename Map::mapped_type& defval) { auto it = m.find(key); - if(it == m.end()) + if (it == m.end()) return defval; return it->second; } @@ -64,9 +64,9 @@ template bool map_has_non_empty_value(const Map& m, const typename Map::key_type& key) { auto it = m.find(key); - if(it == m.end()) + if (it == m.end()) return false; return !it->second.empty(); } -} +} // namespace aliceVision diff --git a/src/aliceVision/stl/regex.hpp b/src/aliceVision/stl/regex.hpp index b213d87d3e..bc99c226c0 100644 --- a/src/aliceVision/stl/regex.hpp +++ b/src/aliceVision/stl/regex.hpp @@ -16,8 +16,8 @@ inline std::regex simpleFilterToRegex(const std::string& simpleFilter) filterToRegex = std::regex_replace(filterToRegex, std::regex("/"), std::string("\\/")); filterToRegex = std::regex_replace(filterToRegex, std::regex("\\*"), std::string(".*")); filterToRegex = std::regex_replace(filterToRegex, std::regex("\\?"), std::string(".")); - filterToRegex = std::regex_replace(filterToRegex, std::regex("\\@"), std::string("[0-9]+")); // one @ correspond to one or more digits - filterToRegex = std::regex_replace(filterToRegex, std::regex("\\#"), std::string("[0-9]")); // each # in pattern correspond to a digit + filterToRegex = std::regex_replace(filterToRegex, std::regex("\\@"), std::string("[0-9]+")); // one @ correspond to one or more digits + filterToRegex = std::regex_replace(filterToRegex, std::regex("\\#"), std::string("[0-9]")); // each # in pattern correspond to a digit ALICEVISION_LOG_TRACE("filterToRegex: " << filterToRegex); return std::regex(filterToRegex); @@ -36,5 +36,4 @@ inline std::regex simpleFilterToRegex_noThrow(const std::string& simpleFilter) } } -} - +} // namespace aliceVision diff --git a/src/aliceVision/system/Logger.cpp b/src/aliceVision/system/Logger.cpp index 2838ef41a6..5fc2d9f7d8 100644 --- a/src/aliceVision/system/Logger.cpp +++ b/src/aliceVision/system/Logger.cpp @@ -22,11 +22,11 @@ #include #if BOOST_VERSION >= 105600 -#include + #include #elif BOOST_VERSION >= 105500 -#include + #include #else -#include + #include #endif namespace aliceVision { @@ -34,14 +34,20 @@ namespace system { std::string EVerboseLevel_enumToString(EVerboseLevel verboseLevel) { - switch(verboseLevel) + switch (verboseLevel) { - case EVerboseLevel::Fatal: return "fatal"; - case EVerboseLevel::Error: return "error"; - case EVerboseLevel::Warning: return "warning"; - case EVerboseLevel::Info: return "info"; - case EVerboseLevel::Debug: return "debug"; - case EVerboseLevel::Trace: return "trace"; + case EVerboseLevel::Fatal: + return "fatal"; + case EVerboseLevel::Error: + return "error"; + case EVerboseLevel::Warning: + return "warning"; + case EVerboseLevel::Info: + return "info"; + case EVerboseLevel::Debug: + return "debug"; + case EVerboseLevel::Trace: + return "trace"; } throw std::out_of_range("Invalid verbose level enum"); } @@ -50,12 +56,18 @@ EVerboseLevel EVerboseLevel_stringToEnum(std::string verboseLevel) { boost::to_lower(verboseLevel); - if(verboseLevel == "fatal") return EVerboseLevel::Fatal; - if(verboseLevel == "error") return EVerboseLevel::Error; - if(verboseLevel == "warning") return EVerboseLevel::Warning; - if(verboseLevel == "info") return EVerboseLevel::Info; - if(verboseLevel == "debug") return EVerboseLevel::Debug; - if(verboseLevel == "trace") return EVerboseLevel::Trace; + if (verboseLevel == "fatal") + return EVerboseLevel::Fatal; + if (verboseLevel == "error") + return EVerboseLevel::Error; + if (verboseLevel == "warning") + return EVerboseLevel::Warning; + if (verboseLevel == "info") + return EVerboseLevel::Info; + if (verboseLevel == "debug") + return EVerboseLevel::Debug; + if (verboseLevel == "trace") + return EVerboseLevel::Trace; throw std::out_of_range("Invalid verbose level : '" + verboseLevel + "'"); } @@ -78,92 +90,96 @@ std::shared_ptr Logger::_instance = nullptr; Logger::Logger() { - namespace expr = boost::log::expressions; - namespace sinks = boost::log::sinks; - using sink_t = sinks::synchronous_sink; + namespace expr = boost::log::expressions; + namespace sinks = boost::log::sinks; + using sink_t = sinks::synchronous_sink; #if BOOST_VERSION >= 105600 - using boost::null_deleter; + using boost::null_deleter; #elif BOOST_VERSION >= 105500 - using null_deleter = boost::empty_deleter; + using null_deleter = boost::empty_deleter; #else - using null_deleter = boost::log::empty_deleter; + using null_deleter = boost::log::empty_deleter; #endif - boost::shared_ptr sink; + boost::shared_ptr sink; - { - // create a backend and attach a stream to it - boost::shared_ptr backend = boost::make_shared(); - backend->add_stream(boost::shared_ptr(&std::clog, null_deleter())); - // backend->add_stream( boost::shared_ptr< std::ostream >( new std::ofstream("sample.log") ) ); + { + // create a backend and attach a stream to it + boost::shared_ptr backend = boost::make_shared(); + backend->add_stream(boost::shared_ptr(&std::clog, null_deleter())); + // backend->add_stream( boost::shared_ptr< std::ostream >( new std::ofstream("sample.log") ) ); - // enable auto-flushing after each log record written - backend->auto_flush(true); + // enable auto-flushing after each log record written + backend->auto_flush(true); - // wrap it into the frontend and register in the core. - sink = boost::make_shared(backend); - } + // wrap it into the frontend and register in the core. + sink = boost::make_shared(backend); + } - sink->reset_formatter(); + sink->reset_formatter(); - // specify format of the log records - sink->set_formatter(expr::stream - << "[" << expr::format_date_time("TimeStamp","%H:%M:%S.%f") << "]" - << "[" << boost::log::trivial::severity << "]" - << " " << expr::smessage); + // specify format of the log records + sink->set_formatter(expr::stream << "[" << expr::format_date_time("TimeStamp", "%H:%M:%S.%f") << "]" + << "[" << boost::log::trivial::severity << "]" + << " " << expr::smessage); - // register the sink in the logging core - boost::log::core::get()->add_sink(sink); + // register the sink in the logging core + boost::log::core::get()->add_sink(sink); - boost::log::add_common_attributes(); + boost::log::add_common_attributes(); - const char* envLevel = std::getenv("ALICEVISION_LOG_LEVEL"); + const char* envLevel = std::getenv("ALICEVISION_LOG_LEVEL"); - if(envLevel == NULL) - setLogLevel(getDefaultVerboseLevel()); - else - setLogLevel(envLevel); + if (envLevel == NULL) + setLogLevel(getDefaultVerboseLevel()); + else + setLogLevel(envLevel); } std::shared_ptr Logger::get() { - if(_instance == nullptr) - _instance.reset(new Logger()); - return _instance; + if (_instance == nullptr) + _instance.reset(new Logger()); + return _instance; } -EVerboseLevel Logger::getDefaultVerboseLevel() -{ - return EVerboseLevel::Info; -} +EVerboseLevel Logger::getDefaultVerboseLevel() { return EVerboseLevel::Info; } void Logger::setLogLevel(const EVerboseLevel level) { - switch(level) - { - case EVerboseLevel::Fatal: setLogLevel(boost::log::trivial::fatal); break; - case EVerboseLevel::Error: setLogLevel(boost::log::trivial::error); break; - case EVerboseLevel::Warning: setLogLevel(boost::log::trivial::warning); break; - case EVerboseLevel::Info: setLogLevel(boost::log::trivial::info); break; - case EVerboseLevel::Debug: setLogLevel(boost::log::trivial::debug); break; - case EVerboseLevel::Trace: setLogLevel(boost::log::trivial::trace); break; - default: - setLogLevel(getDefaultVerboseLevel()); - ALICEVISION_LOG_WARNING("Unrecognized log level enum '" << level << "', fallback to '" << getDefaultVerboseLevel() << "'."); - break; - } + switch (level) + { + case EVerboseLevel::Fatal: + setLogLevel(boost::log::trivial::fatal); + break; + case EVerboseLevel::Error: + setLogLevel(boost::log::trivial::error); + break; + case EVerboseLevel::Warning: + setLogLevel(boost::log::trivial::warning); + break; + case EVerboseLevel::Info: + setLogLevel(boost::log::trivial::info); + break; + case EVerboseLevel::Debug: + setLogLevel(boost::log::trivial::debug); + break; + case EVerboseLevel::Trace: + setLogLevel(boost::log::trivial::trace); + break; + default: + setLogLevel(getDefaultVerboseLevel()); + ALICEVISION_LOG_WARNING("Unrecognized log level enum '" << level << "', fallback to '" << getDefaultVerboseLevel() << "'."); + break; + } } -void Logger::setLogLevel(const std::string& level) -{ - setLogLevel(EVerboseLevel_stringToEnum(level)); -} +void Logger::setLogLevel(const std::string& level) { setLogLevel(EVerboseLevel_stringToEnum(level)); } void Logger::setLogLevel(const boost::log::trivial::severity_level level) { - boost::log::core::get()->set_filter(boost::log::trivial::severity >= level); - + boost::log::core::get()->set_filter(boost::log::trivial::severity >= level); } -} // namespace system -} // namespace aliceVision +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/Logger.hpp b/src/aliceVision/system/Logger.hpp index 6294a7c10f..2f4bb654c7 100644 --- a/src/aliceVision/system/Logger.hpp +++ b/src/aliceVision/system/Logger.hpp @@ -35,15 +35,14 @@ #define ALICEVISION_LOG_ERROR(a) ALICEVISION_LOG(ALICEVISION_LOG_ERROR_OBJ, a) #define ALICEVISION_LOG_FATAL(a) ALICEVISION_LOG(ALICEVISION_LOG_FATAL_OBJ, a) -#define ALICEVISION_THROW(EXCEPTION, x) \ -{ \ - std::stringstream s; \ - s << x; \ - throw EXCEPTION(s.str()); \ -} +#define ALICEVISION_THROW(EXCEPTION, x) \ + { \ + std::stringstream s; \ + s << x; \ + throw EXCEPTION(s.str()); \ + } #define ALICEVISION_THROW_ERROR(x) ALICEVISION_THROW(std::runtime_error, x) - namespace aliceVision { namespace system { @@ -77,47 +76,45 @@ std::istream& operator>>(std::istream& in, EVerboseLevel& verboseLevel); class Logger { -public: - - /** - * @brief get Logger instance - * @return instance - */ - static std::shared_ptr get(); - - /** - * @brief get default verbose level - * @return default verbose level - */ - static EVerboseLevel getDefaultVerboseLevel(); - - /** - * @brief set Logger level with EVerboseLevel enum - * @param level EVerboseLevel enum - */ - void setLogLevel(const EVerboseLevel level); - - /** - * @brief set Logger level with string - * @param level string - */ - void setLogLevel(const std::string& level); - -private: - - /** - * @brief Logger private constructor - */ - Logger(); - - /** - * @brief setLogLevel with boost severity level - * @param level boost severity level - */ - void setLogLevel(const boost::log::trivial::severity_level level); - - static std::shared_ptr _instance; + public: + /** + * @brief get Logger instance + * @return instance + */ + static std::shared_ptr get(); + + /** + * @brief get default verbose level + * @return default verbose level + */ + static EVerboseLevel getDefaultVerboseLevel(); + + /** + * @brief set Logger level with EVerboseLevel enum + * @param level EVerboseLevel enum + */ + void setLogLevel(const EVerboseLevel level); + + /** + * @brief set Logger level with string + * @param level string + */ + void setLogLevel(const std::string& level); + + private: + /** + * @brief Logger private constructor + */ + Logger(); + + /** + * @brief setLogLevel with boost severity level + * @param level boost severity level + */ + void setLogLevel(const boost::log::trivial::severity_level level); + + static std::shared_ptr _instance; }; -} // namespace system -} // namespace aliceVision +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/Logger_test.cpp b/src/aliceVision/system/Logger_test.cpp index 04c3e69559..8b9db851f9 100644 --- a/src/aliceVision/system/Logger_test.cpp +++ b/src/aliceVision/system/Logger_test.cpp @@ -17,21 +17,12 @@ BOOST_AUTO_TEST_CASE(Logger_Enums) { using namespace aliceVision::system; - const std::array allLevels{EVerboseLevel::Trace, - EVerboseLevel::Debug, - EVerboseLevel::Info, - EVerboseLevel::Warning, - EVerboseLevel::Error , - EVerboseLevel::Fatal}; - - const std::array allStringLevels{"trace", - "debug", - "info", - "warning", - "error" , - "fatal"}; - - for(std::size_t i = 0; i < allLevels.size(); ++i) + const std::array allLevels{ + EVerboseLevel::Trace, EVerboseLevel::Debug, EVerboseLevel::Info, EVerboseLevel::Warning, EVerboseLevel::Error, EVerboseLevel::Fatal}; + + const std::array allStringLevels{"trace", "debug", "info", "warning", "error", "fatal"}; + + for (std::size_t i = 0; i < allLevels.size(); ++i) { BOOST_CHECK(allLevels[i] == EVerboseLevel_stringToEnum(allStringLevels[i])); BOOST_CHECK(allLevels[i] == EVerboseLevel_stringToEnum(boost::to_upper_copy(allStringLevels[i]))); diff --git a/src/aliceVision/system/MemoryInfo.cpp b/src/aliceVision/system/MemoryInfo.cpp index 452f53a506..f5b3edb2e1 100644 --- a/src/aliceVision/system/MemoryInfo.cpp +++ b/src/aliceVision/system/MemoryInfo.cpp @@ -12,24 +12,23 @@ #include #if defined(__WINDOWS__) -#include + #include #elif defined(__LINUX__) -#include -#include -#include + #include + #include + #include #elif defined(__APPLE__) -#include -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include + #include #else -#warning "System unrecognized. Can't found memory infos." -#include + #warning "System unrecognized. Can't found memory infos." + #include #endif - namespace aliceVision { namespace system { @@ -38,20 +37,25 @@ unsigned long linuxGetAvailableRam() { std::string token; std::ifstream file("/proc/meminfo"); - while(file >> token) { - if(token == "MemAvailable:") { + while (file >> token) + { + if (token == "MemAvailable:") + { unsigned long mem; - if(file >> mem) { + if (file >> mem) + { // read in kB and convert to bytes return mem * 1024; - } else { + } + else + { return 0; } } // ignore rest of the line file.ignore(std::numeric_limits::max(), '\n'); } - return 0; // nothing found + return 0; // nothing found } #endif @@ -78,7 +82,7 @@ MemoryInfo getMemoryInfo() infos.freeRam = sys_info.freeram * sys_info.mem_unit; infos.availableRam = linuxGetAvailableRam(); - if(infos.availableRam == 0) + if (infos.availableRam == 0) infos.availableRam = infos.freeRam; // infos.sharedRam = sys_info.sharedram * sys_info.mem_unit; @@ -92,7 +96,7 @@ MemoryInfo getMemoryInfo() size_t miblen = sizeof(mib) / sizeof(mib[0]); // Total physical memory. - if(sysctl(mib, miblen, &physmem, &len, NULL, 0) == 0 && len == sizeof(physmem)) + if (sysctl(mib, miblen, &physmem, &len, NULL, 0) == 0 && len == sizeof(physmem)) infos.totalRam = physmem; // Virtual memory. @@ -100,7 +104,7 @@ MemoryInfo getMemoryInfo() mib[1] = VM_SWAPUSAGE; struct xsw_usage swap; len = sizeof(struct xsw_usage); - if(sysctl(mib, miblen, &swap, &len, NULL, 0) == 0) + if (sysctl(mib, miblen, &swap, &len, NULL, 0) == 0) { infos.totalSwap = swap.xsu_total; infos.freeSwap = swap.xsu_avail; @@ -111,8 +115,8 @@ MemoryInfo getMemoryInfo() vm_size_t page_size; vm_statistics_data_t vm_stat; mach_msg_type_number_t count = sizeof(vm_stat) / sizeof(natural_t); - if(KERN_SUCCESS == host_page_size(stat_port, &page_size) && - KERN_SUCCESS == host_statistics(stat_port, HOST_VM_INFO, (host_info_t)&vm_stat, &count)) + if (KERN_SUCCESS == host_page_size(stat_port, &page_size) && + KERN_SUCCESS == host_statistics(stat_port, HOST_VM_INFO, (host_info_t)&vm_stat, &count)) { // uint64_t used = ((int64_t)vm_stat.active_count + (int64_t)vm_stat.inactive_count + (int64_t)vm_stat.wire_count) * // (int64_t)page_size; @@ -131,17 +135,14 @@ MemoryInfo getMemoryInfo() std::ostream& operator<<(std::ostream& os, const MemoryInfo& infos) { - const double convertionGb = std::pow(2,30); - os << std::setw(5) - << "\t- Total RAM: " << (infos.totalRam / convertionGb) << " GB" << std::endl - << "\t- Free RAM: " << (infos.freeRam / convertionGb) << " GB" << std::endl - << "\t- Available RAM: " << (infos.availableRam / convertionGb) << " GB" << std::endl - << "\t- Total swap: " << (infos.totalSwap / convertionGb) << " GB" << std::endl - << "\t- Free swap: " << (infos.freeSwap / convertionGb) << " GB" << std::endl; - return os; + const double convertionGb = std::pow(2, 30); + os << std::setw(5) << "\t- Total RAM: " << (infos.totalRam / convertionGb) << " GB" << std::endl + << "\t- Free RAM: " << (infos.freeRam / convertionGb) << " GB" << std::endl + << "\t- Available RAM: " << (infos.availableRam / convertionGb) << " GB" << std::endl + << "\t- Total swap: " << (infos.totalSwap / convertionGb) << " GB" << std::endl + << "\t- Free swap: " << (infos.freeSwap / convertionGb) << " GB" << std::endl; + return os; } -} -} - - +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/MemoryInfo.hpp b/src/aliceVision/system/MemoryInfo.hpp index 4ecc2aeabf..e302130831 100644 --- a/src/aliceVision/system/MemoryInfo.hpp +++ b/src/aliceVision/system/MemoryInfo.hpp @@ -27,6 +27,5 @@ MemoryInfo getMemoryInfo(); std::ostream& operator<<(std::ostream& os, const MemoryInfo& infos); -} -} - +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/ProgressDisplay.cpp b/src/aliceVision/system/ProgressDisplay.cpp index 68b95a8a6e..e054c11ad8 100644 --- a/src/aliceVision/system/ProgressDisplay.cpp +++ b/src/aliceVision/system/ProgressDisplay.cpp @@ -13,34 +13,33 @@ namespace system { ProgressDisplayImpl::~ProgressDisplayImpl() = default; -class ProgressDisplayImplEmpty : public ProgressDisplayImpl { -public: +class ProgressDisplayImplEmpty : public ProgressDisplayImpl +{ + public: void restart(unsigned long expectedCount) override {} void increment(unsigned long count) override {} unsigned long count() override { return 0; } unsigned long expectedCount() override { return 0; } }; -ProgressDisplay::ProgressDisplay() : _impl{std::make_shared()} +ProgressDisplay::ProgressDisplay() + : _impl{std::make_shared()} {} -class ProgressDisplayImplBoostProgress : public ProgressDisplayImpl { -public: +class ProgressDisplayImplBoostProgress : public ProgressDisplayImpl +{ + public: ProgressDisplayImplBoostProgress(unsigned long expectedCount, std::ostream& os, const std::string& s1, const std::string& s2, - const std::string& s3) : - _display{expectedCount, os, s1, s2, s3} - { - } + const std::string& s3) + : _display{expectedCount, os, s1, s2, s3} + {} ~ProgressDisplayImplBoostProgress() override = default; - void restart(unsigned long expectedCount) override - { - _display.restart(expectedCount); - } + void restart(unsigned long expectedCount) override { _display.restart(expectedCount); } void increment(unsigned long count) override { @@ -54,17 +53,13 @@ class ProgressDisplayImplBoostProgress : public ProgressDisplayImpl { return _display.count(); } - unsigned long expectedCount() override - { - return _display.expected_count(); - } + unsigned long expectedCount() override { return _display.expected_count(); } -private: + private: std::mutex _mutex; boost::timer::progress_display _display; }; - ProgressDisplay createConsoleProgressDisplay(unsigned long expectedCount, std::ostream& os, const std::string& s1, @@ -75,5 +70,5 @@ ProgressDisplay createConsoleProgressDisplay(unsigned long expectedCount, return ProgressDisplay(impl); } -} // namespace system -} // namespace aliceVision +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/ProgressDisplay.hpp b/src/aliceVision/system/ProgressDisplay.hpp index 3b074f4e6e..7397b3bffd 100644 --- a/src/aliceVision/system/ProgressDisplay.hpp +++ b/src/aliceVision/system/ProgressDisplay.hpp @@ -13,8 +13,9 @@ namespace aliceVision { namespace system { -class ProgressDisplayImpl { -public: +class ProgressDisplayImpl +{ + public: virtual ~ProgressDisplayImpl(); virtual void restart(unsigned long expectedCount) = 0; virtual void increment(unsigned long count) = 0; @@ -30,50 +31,38 @@ class ProgressDisplayImpl { * * For ease of use value semantics are exposed. */ -class ProgressDisplay { -public: +class ProgressDisplay +{ + public: ProgressDisplay(); - ProgressDisplay(const std::shared_ptr& impl) : _impl{impl} {} + ProgressDisplay(const std::shared_ptr& impl) + : _impl{impl} + {} - void restart(unsigned long expectedCount) - { - _impl->restart(expectedCount); - } + void restart(unsigned long expectedCount) { _impl->restart(expectedCount); } // Thread safe with respect to other calls to operator++ and to calls to count() - void operator++() - { - _impl->increment(1); - } + void operator++() { _impl->increment(1); } // Thread safe with respect to other calls to operator++ and to calls to count() - void operator+=(unsigned long increment) - { - _impl->increment(increment); - } + void operator+=(unsigned long increment) { _impl->increment(increment); } // Thread safe with respect to calls to operator++ - unsigned long count() - { - return _impl->count(); - } + unsigned long count() { return _impl->count(); } // Thread safe - unsigned long expectedCount() - { - return _impl->expectedCount(); - } + unsigned long expectedCount() { return _impl->expectedCount(); } -private: + private: std::shared_ptr _impl; }; /// Creates console-based progress bar ProgressDisplay createConsoleProgressDisplay(unsigned long expectedCount, std::ostream& os, - const std::string& s1 = "\n", //leading strings + const std::string& s1 = "\n", // leading strings const std::string& s2 = "", const std::string& s3 = ""); -} // namespace system -} // namespace aliceVision +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/Timer.cpp b/src/aliceVision/system/Timer.cpp index 778956033d..1accbb1ff9 100644 --- a/src/aliceVision/system/Timer.cpp +++ b/src/aliceVision/system/Timer.cpp @@ -10,80 +10,68 @@ #include #ifdef _WIN32 -# include + #include #endif namespace aliceVision { namespace system { -Timer::Timer() -{ - reset(); -} +Timer::Timer() { reset(); } -void Timer::reset() -{ - start_ = std::chrono::high_resolution_clock::now(); -} +void Timer::reset() { start_ = std::chrono::high_resolution_clock::now(); } -double Timer::elapsed() const -{ - return elapsedMs() / 1000.; -} +double Timer::elapsed() const { return elapsedMs() / 1000.; } double Timer::elapsedMs() const { - const auto end_ = std::chrono::high_resolution_clock::now(); - return std::chrono::duration_cast(end_ - start_).count(); + const auto end_ = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end_ - start_).count(); } -std::ostream& operator << (std::ostream& str, const Timer& t) -{ - return str << t.elapsed() << " s elapsed"; -} +std::ostream& operator<<(std::ostream& str, const Timer& t) { return str << t.elapsed() << " s elapsed"; } std::string prettyTime(double durationMs) { - std::string out; + std::string out; - const auto msecs = fmod(durationMs, 1000); - durationMs /= 1000.; - const std::size_t secs = std::size_t(fmod(durationMs, 60)); - durationMs /= 60.; - const std::size_t mins = std::size_t(fmod(durationMs, 60)); - durationMs /= 60.; - const std::size_t hours = std::size_t(fmod(durationMs, 24)); - durationMs /= 24.; - const std::size_t days = durationMs; + const auto msecs = fmod(durationMs, 1000); + durationMs /= 1000.; + const std::size_t secs = std::size_t(fmod(durationMs, 60)); + durationMs /= 60.; + const std::size_t mins = std::size_t(fmod(durationMs, 60)); + durationMs /= 60.; + const std::size_t hours = std::size_t(fmod(durationMs, 24)); + durationMs /= 24.; + const std::size_t days = durationMs; - bool printed_earlier = false; - if(days >= 1) - { - printed_earlier = true; - out += (std::to_string(days) + "d "); - } - if(printed_earlier || hours >= 1) - { - printed_earlier = true; - out += (std::to_string(hours) + "h "); - } - if(printed_earlier || mins >= 1) - { - printed_earlier = true; - out += (std::to_string(mins) + "m "); - } - if(printed_earlier || secs >= 1) - { - printed_earlier = true; - out += (std::to_string(secs) + "s "); - } - if(printed_earlier || msecs >= 1) - { - printed_earlier = true; - out += (std::to_string(msecs) + "ms"); - } - return out; + bool printed_earlier = false; + if (days >= 1) + { + printed_earlier = true; + out += (std::to_string(days) + "d "); + } + if (printed_earlier || hours >= 1) + { + printed_earlier = true; + out += (std::to_string(hours) + "h "); + } + if (printed_earlier || mins >= 1) + { + printed_earlier = true; + out += (std::to_string(mins) + "m "); + } + if (printed_earlier || secs >= 1) + { + printed_earlier = true; + out += (std::to_string(secs) + "s "); + } + if (printed_earlier || msecs >= 1) + { + printed_earlier = true; + out += (std::to_string(msecs) + "ms"); + } + return out; } -} // namespace system -} // namespace aliceVision +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/Timer.hpp b/src/aliceVision/system/Timer.hpp index dc4628a771..e6e0f52211 100644 --- a/src/aliceVision/system/Timer.hpp +++ b/src/aliceVision/system/Timer.hpp @@ -16,13 +16,13 @@ namespace aliceVision { namespace system { - /** - * @brief Timer class with microsecond accuracy. - * Adapted from DO++, a basic set of libraries in C++ for computer vision, licensed under MPL2. - * See https://do-cv.github.io/sara - */ - class Timer - { +/** + * @brief Timer class with microsecond accuracy. + * Adapted from DO++, a basic set of libraries in C++ for computer vision, licensed under MPL2. + * See https://do-cv.github.io/sara + */ +class Timer +{ public: //! Default constructor Timer(); @@ -32,25 +32,24 @@ namespace system { double elapsed() const; //! Returns the elapsed time in milliseconds. double elapsedMs() const; - private: + private: std::chrono::high_resolution_clock::time_point start_; - }; - - // print the elapsed time - std::ostream& operator << (std::ostream&, const Timer&); - +}; + +// print the elapsed time +std::ostream& operator<<(std::ostream&, const Timer&); + /** * @brief Prints the duration in the format #d #h #m #s #ms starting from the non-zero * most significant entity (ie it does not print #d if d is 0 and so on...). - * + * * @param durationMs the duration in milliseconds. * @return a formatted string - */ + */ std::string prettyTime(double durationMs); -} // namespace system -} // namespace aliceVision - -#endif // ALICEVISION_SYSTEM_TIMER_HPP +} // namespace system +} // namespace aliceVision +#endif // ALICEVISION_SYSTEM_TIMER_HPP diff --git a/src/aliceVision/system/cpu.cpp b/src/aliceVision/system/cpu.cpp index ed87bbe86a..2b65fe5c3c 100644 --- a/src/aliceVision/system/cpu.cpp +++ b/src/aliceVision/system/cpu.cpp @@ -8,159 +8,166 @@ #include "system.hpp" #ifdef __WINDOWS__ -#include + #include namespace aliceVision { namespace system { int cpu_clock_by_os(void) { - HKEY key; - DWORD result; - DWORD size = 4; - - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &key) != ERROR_SUCCESS) - return -1; - - if (RegQueryValueEx(key, TEXT("~MHz"), NULL, NULL, (LPBYTE) &result, (LPDWORD) &size) != ERROR_SUCCESS) { - RegCloseKey(key); - return -1; - } - RegCloseKey(key); - - return (int)result; + HKEY key; + DWORD result; + DWORD size = 4; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &key) != ERROR_SUCCESS) + return -1; + + if (RegQueryValueEx(key, TEXT("~MHz"), NULL, NULL, (LPBYTE)&result, (LPDWORD)&size) != ERROR_SUCCESS) + { + RegCloseKey(key); + return -1; + } + RegCloseKey(key); + + return (int)result; } -}} +} // namespace system +} // namespace aliceVision #else -#ifdef __APPLE__ -#include -#include + #ifdef __APPLE__ + #include + #include /* Assuming Mac OS X with hw.cpufrequency sysctl */ namespace aliceVision { namespace system { int cpu_clock_by_os(void) { - long long result = -1; - size_t size = sizeof(result); - if (sysctlbyname("hw.cpufrequency", &result, &size, NULL, 0)) - return -1; - return (int) (result / (long long) 1000000); + long long result = -1; + size_t size = sizeof(result); + if (sysctlbyname("hw.cpufrequency", &result, &size, NULL, 0)) + return -1; + return (int)(result / (long long)1000000); } -}} -#else -#include -#include -#include +} // namespace system +} // namespace aliceVision + #else + #include + #include + #include namespace aliceVision { namespace system { /* Assuming Linux with /proc/cpuinfo */ int cpu_clock_by_os(void) { - FILE *f; - char line[1024], *s; - int result; - - f = fopen("/proc/cpuinfo", "rt"); - if (!f) return -1; - - while (fgets(line, sizeof(line), f)) { - if (!strncmp(line, "cpu MHz", 7)) { - s = strchr(line, ':'); - if (s && 1 == sscanf(s, ":%d.", &result)) { - fclose(f); - return result; - } - } - } - fclose(f); - return -1; + FILE* f; + char line[1024], *s; + int result; + + f = fopen("/proc/cpuinfo", "rt"); + if (!f) + return -1; + + while (fgets(line, sizeof(line), f)) + { + if (!strncmp(line, "cpu MHz", 7)) + { + s = strchr(line, ':'); + if (s && 1 == sscanf(s, ":%d.", &result)) + { + fclose(f); + return result; + } + } + } + fclose(f); + return -1; } -}} -#endif /* __APPLE__ */ -#endif /* _WIN32 */ - +} // namespace system +} // namespace aliceVision + #endif /* __APPLE__ */ +#endif /* _WIN32 */ /* get_total_cpus() system specific code: uses OS routines to determine total number of CPUs */ #ifdef __APPLE__ -#include -#include -#include -#include + #include + #include + #include + #include namespace aliceVision { namespace system { int get_total_cpus(void) { - kern_return_t kr; - host_basic_info_data_t basic_info; - host_info_t info = (host_info_t)&basic_info; - host_flavor_t flavor = HOST_BASIC_INFO; - mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; - kr = host_info(mach_host_self(), flavor, info, &count); - if (kr != KERN_SUCCESS) return 1; - return basic_info.avail_cpus; + kern_return_t kr; + host_basic_info_data_t basic_info; + host_info_t info = (host_info_t)&basic_info; + host_flavor_t flavor = HOST_BASIC_INFO; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + kr = host_info(mach_host_self(), flavor, info, &count); + if (kr != KERN_SUCCESS) + return 1; + return basic_info.avail_cpus; } -}} -#define GET_TOTAL_CPUS_DEFINED +} // namespace system +} // namespace aliceVision + #define GET_TOTAL_CPUS_DEFINED #endif #ifdef __WINDOWS__ -#include + #include namespace aliceVision { namespace system { int get_total_cpus(void) { - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - return system_info.dwNumberOfProcessors; + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + return system_info.dwNumberOfProcessors; } -}} -#define GET_TOTAL_CPUS_DEFINED +} // namespace system +} // namespace aliceVision + #define GET_TOTAL_CPUS_DEFINED #endif #if defined linux || defined __linux__ || defined __sun -#include -#include + #include + #include namespace aliceVision { namespace system { -int get_total_cpus(void) -{ - return sysconf(_SC_NPROCESSORS_ONLN); -} -}} -#define GET_TOTAL_CPUS_DEFINED +int get_total_cpus(void) { return sysconf(_SC_NPROCESSORS_ONLN); } +} // namespace system +} // namespace aliceVision + #define GET_TOTAL_CPUS_DEFINED #endif #if defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ || defined __bsdi__ || defined __QNX__ -#include -#include + #include + #include namespace aliceVision { namespace system { int get_total_cpus(void) { - int mib[2] = { CTL_HW, HW_NCPU }; - int ncpus; - size_t len = sizeof(ncpus); - if (sysctl(mib, 2, &ncpus, &len, (void *) 0, 0) != 0) return 1; - return ncpus; + int mib[2] = {CTL_HW, HW_NCPU}; + int ncpus; + size_t len = sizeof(ncpus); + if (sysctl(mib, 2, &ncpus, &len, (void*)0, 0) != 0) + return 1; + return ncpus; } -}} -#define GET_TOTAL_CPUS_DEFINED +} // namespace system +} // namespace aliceVision + #define GET_TOTAL_CPUS_DEFINED #endif #ifndef GET_TOTAL_CPUS_DEFINED namespace aliceVision { namespace system { -int get_total_cpus(void) -{ - return 1; -} -}} +int get_total_cpus(void) { return 1; } +} // namespace system +} // namespace aliceVision #endif /* GET_TOTAL_CPUS_DEFINED */ - diff --git a/src/aliceVision/system/cpu.hpp b/src/aliceVision/system/cpu.hpp index ee98a8f1e2..4d2050dfa8 100644 --- a/src/aliceVision/system/cpu.hpp +++ b/src/aliceVision/system/cpu.hpp @@ -22,7 +22,5 @@ int cpu_clock_by_os(); */ int get_total_cpus(); -} -} - - +} // namespace system +} // namespace aliceVision diff --git a/src/aliceVision/system/hardwareContext.cpp b/src/aliceVision/system/hardwareContext.cpp index cd797a57ff..a40dbf631e 100644 --- a/src/aliceVision/system/hardwareContext.cpp +++ b/src/aliceVision/system/hardwareContext.cpp @@ -6,11 +6,10 @@ namespace aliceVision { - void HardwareContext::displayHardware() { std::cout << "Hardware : " << std::endl; - + std::cout << "\tDetected core count : " << system::get_total_cpus() << std::endl; if (_maxUserCoresAvailable < std::numeric_limits::max()) @@ -21,8 +20,8 @@ void HardwareContext::displayHardware() std::cout << "\tOpenMP will use " << omp_get_max_threads() << " cores" << std::endl; auto meminfo = system::getMemoryInfo(); - - std::cout << "\tDetected available memory : " << meminfo.availableRam / (1024 * 1024) << " Mo" << std::endl; + + std::cout << "\tDetected available memory : " << meminfo.availableRam / (1024 * 1024) << " Mo" << std::endl; if (_maxUserMemoryAvailable < std::numeric_limits::max()) { @@ -33,17 +32,17 @@ void HardwareContext::displayHardware() } unsigned int HardwareContext::getMaxThreads() const -{ - //Get hardware limit on threads +{ + // Get hardware limit on threads unsigned int count = system::get_total_cpus(); - //Get User max threads + // Get User max threads if (count > _maxUserCoresAvailable) { count = _maxUserCoresAvailable; } - //Get User limit max threads + // Get User limit max threads if (_limitUserCores > 0 && count > _limitUserCores) { count = _limitUserCores; @@ -52,7 +51,7 @@ unsigned int HardwareContext::getMaxThreads() const return count; } -size_t HardwareContext::getMaxMemory() const +size_t HardwareContext::getMaxMemory() const { auto meminfo = system::getMemoryInfo(); @@ -62,4 +61,4 @@ size_t HardwareContext::getMaxMemory() const return ret; } -} \ No newline at end of file +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/system/hardwareContext.hpp b/src/aliceVision/system/hardwareContext.hpp index c7142bb02d..e4a060e183 100644 --- a/src/aliceVision/system/hardwareContext.hpp +++ b/src/aliceVision/system/hardwareContext.hpp @@ -9,50 +9,34 @@ #include "Logger.hpp" #include "Timer.hpp" - namespace aliceVision { class HardwareContext { -public: + public: void displayHardware(); - size_t getUserMaxMemoryAvailable() const - { - return _maxUserMemoryAvailable; - } + size_t getUserMaxMemoryAvailable() const { return _maxUserMemoryAvailable; } - void setUserMaxMemoryAvailable(size_t val) - { - _maxUserMemoryAvailable = val; - } + void setUserMaxMemoryAvailable(size_t val) { _maxUserMemoryAvailable = val; } - unsigned int getUserMaxCoresAvailable() const - { - return _maxUserCoresAvailable; - } + unsigned int getUserMaxCoresAvailable() const { return _maxUserCoresAvailable; } - void setUserMaxCoresAvailable(unsigned int val) - { - _maxUserCoresAvailable = val; - } + void setUserMaxCoresAvailable(unsigned int val) { _maxUserCoresAvailable = val; } - void setUserCoresLimit(unsigned int coresLimit) - { - _limitUserCores = coresLimit; - } + void setUserCoresLimit(unsigned int coresLimit) { _limitUserCores = coresLimit; } unsigned int getMaxThreads() const; /** - * @brief compute the maximum memory available + * @brief compute the maximum memory available * @return the size in bytes */ size_t getMaxMemory() const; -private: + private: /** - * @brief This is the maximum memory available + * @brief This is the maximum memory available * This information is passed to the application through command line parameters * if we want to override the system defined information (E.g. cgroups) */ @@ -72,4 +56,4 @@ class HardwareContext unsigned int _limitUserCores = std::numeric_limits::max(); }; -} +} // namespace aliceVision diff --git a/src/aliceVision/system/main.hpp b/src/aliceVision/system/main.hpp index 48d032dee9..951b58c4d6 100644 --- a/src/aliceVision/system/main.hpp +++ b/src/aliceVision/system/main.hpp @@ -36,11 +36,11 @@ int main(int argc, char* argv[]) { return aliceVision_main(argc, argv); } - catch(const std::exception& e) + catch (const std::exception& e) { ALICEVISION_LOG_FATAL(e.what()); } - catch(...) + catch (...) { ALICEVISION_LOG_FATAL("Unknown exception"); } diff --git a/src/aliceVision/system/nvtx.cpp b/src/aliceVision/system/nvtx.cpp index 2b5bc77937..3f8b085f66 100644 --- a/src/aliceVision/system/nvtx.cpp +++ b/src/aliceVision/system/nvtx.cpp @@ -1,24 +1,20 @@ #ifdef ALICEVISION_USE_NVTX -#include -#include -#include + #include + #include + #include -#include "aliceVision/system/nvtx.hpp" + #include "aliceVision/system/nvtx.hpp" -void nvtxPushA( const char* label, const char* file, int line ) +void nvtxPushA(const char* label, const char* file, int line) { - boost::filesystem::path in( file ); + boost::filesystem::path in(file); std::ostringstream ostr; ostr << label << " " << in.filename() << ":" << line; - nvtxRangePushA( ostr.str().c_str() ); + nvtxRangePushA(ostr.str().c_str()); } -void nvtxPop ( const char* ) -{ - nvtxRangePop( ); -} +void nvtxPop(const char*) { nvtxRangePop(); } #endif /* ALICEVISION_USE_NVTX */ - diff --git a/src/aliceVision/system/nvtx.hpp b/src/aliceVision/system/nvtx.hpp index 93bd56f4a0..68f5fb65a3 100644 --- a/src/aliceVision/system/nvtx.hpp +++ b/src/aliceVision/system/nvtx.hpp @@ -1,11 +1,11 @@ #pragma once -#define nvtxPush(a) nvtxPushA(a,__FILE__,__LINE__) +#define nvtxPush(a) nvtxPushA(a, __FILE__, __LINE__) #ifdef ALICEVISION_USE_NVTX -void nvtxPushA( const char* label, const char* file, int line ); -void nvtxPop ( const char* label ); +void nvtxPushA(const char* label, const char* file, int line); +void nvtxPop(const char* label); #else -inline void nvtxPushA( const char*, const char*, int ) { } -inline void nvtxPop ( const char* ) { } +inline void nvtxPushA(const char*, const char*, int) {} +inline void nvtxPop(const char*) {} #endif diff --git a/src/aliceVision/system/system.hpp b/src/aliceVision/system/system.hpp index 782aff6fe2..ac42875e4e 100644 --- a/src/aliceVision/system/system.hpp +++ b/src/aliceVision/system/system.hpp @@ -8,35 +8,33 @@ #if defined(linux) || defined(__linux) || defined(LINUX) || defined(_LINUX) || defined(__LINUX__) -#ifndef __LINUX__ -#define __LINUX__ -#endif + #ifndef __LINUX__ + #define __LINUX__ + #endif -#ifndef __UNIX__ -#define __UNIX__ -#endif + #ifndef __UNIX__ + #define __UNIX__ + #endif -#elif defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) || defined(MACOS) || \ - defined(MACOSX) || defined(__MACOS__) +#elif defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) || defined(MACOS) || defined(MACOSX) || defined(__MACOS__) -#ifndef __MACOS__ -#define __MACOS__ -#endif + #ifndef __MACOS__ + #define __MACOS__ + #endif -#ifndef __UNIX__ -#define __UNIX__ -#endif + #ifndef __UNIX__ + #define __UNIX__ + #endif -#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \ - defined(__TOS_WIN__) || defined(WINDOWS) || defined(_WINDOWS) || defined(__WINDOWS__) +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(__TOS_WIN__) || \ + defined(WINDOWS) || defined(_WINDOWS) || defined(__WINDOWS__) -#ifndef __WINDOWS__ -#define __WINDOWS__ -#endif + #ifndef __WINDOWS__ + #define __WINDOWS__ + #endif #else -#warning "Your operating system is not recognized." + #warning "Your operating system is not recognized." #endif - diff --git a/src/aliceVision/track/Track.hpp b/src/aliceVision/track/Track.hpp index 2fd456c9e5..5d12c9b234 100644 --- a/src/aliceVision/track/Track.hpp +++ b/src/aliceVision/track/Track.hpp @@ -28,27 +28,26 @@ using namespace aliceVision::matching; using FeatureId = std::pair; - /** * @brief KeypointId is a unique ID for a feature in a view. */ struct KeypointId { - KeypointId(){} - KeypointId(feature::EImageDescriberType type, std::size_t index) - : descType(type) - , featIndex(index) - {} - - bool operator<(const KeypointId& other) const - { - if(descType == other.descType) - return featIndex < other.featIndex; - return descType < other.descType; - } - - feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; - std::size_t featIndex = 0; + KeypointId() {} + KeypointId(feature::EImageDescriberType type, std::size_t index) + : descType(type), + featIndex(index) + {} + + bool operator<(const KeypointId& other) const + { + if (descType == other.descType) + return featIndex < other.featIndex; + return descType < other.descType; + } + + feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; + std::size_t featIndex = 0; }; inline std::ostream& operator<<(std::ostream& os, const KeypointId& k) @@ -57,22 +56,21 @@ inline std::ostream& operator<<(std::ostream& os, const KeypointId& k) return os; } - /** * @brief A Track is a feature visible accross multiple views. * Tracks are generated by the fusion of all matches accross all images. */ struct Track { - /// Data structure to store a track: collection of {ViewId, FeatureId} - using FeatureIdPerView = stl::flat_map; + /// Data structure to store a track: collection of {ViewId, FeatureId} + using FeatureIdPerView = stl::flat_map; - Track() {} + Track() {} - /// Descriptor type - feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; - /// Collection of matched features between views: {ViewId, FeatureId} - FeatureIdPerView featPerView; + /// Descriptor type + feature::EImageDescriberType descType = feature::EImageDescriberType::UNINITIALIZED; + /// Collection of matched features between views: {ViewId, FeatureId} + FeatureIdPerView featPerView; }; /// A track is a collection of {trackId, Track} @@ -93,14 +91,13 @@ using TrackIdSet = std::vector; * and we go on for increasing values of l so that e.g. the first cell of the pyramid at l=2 has position K^2, the second K^2 + 1 etc... * So in general the i-th cell of the pyramid at level l has position P= \sum_{j=1...l-1} K_j^2 + i */ -using TracksPyramidPerView = stl::flat_map >; +using TracksPyramidPerView = stl::flat_map>; /** * @brief TracksPerView is a list of visible track ids for each view. * TracksPerView contains > */ -using TracksPerView = stl::flat_map; - +using TracksPerView = stl::flat_map; -} // namespace track -} // namespace aliceVision +} // namespace track +} // namespace aliceVision diff --git a/src/aliceVision/track/TracksBuilder.cpp b/src/aliceVision/track/TracksBuilder.cpp index 21981907cd..0030defc1a 100644 --- a/src/aliceVision/track/TracksBuilder.cpp +++ b/src/aliceVision/track/TracksBuilder.cpp @@ -10,7 +10,6 @@ #include #include - namespace aliceVision { namespace track { @@ -20,195 +19,185 @@ using namespace lemon; /// IndexedFeaturePair is: map using IndexedFeaturePair = std::pair; using IndexMap = lemon::ListDigraph::NodeMap; -using UnionFindObject = lemon::UnionFindEnum< IndexMap >; +using UnionFindObject = lemon::UnionFindEnum; -using MapNodeToIndex = stl::flat_map< lemon::ListDigraph::Node, IndexedFeaturePair>; -using MapIndexToNode = stl::flat_map< IndexedFeaturePair, lemon::ListDigraph::Node >; +using MapNodeToIndex = stl::flat_map; +using MapIndexToNode = stl::flat_map; struct TracksBuilderData { - /// graph container to create the node - lemon::ListDigraph graph; - /// node to index map - MapNodeToIndex map_nodeToIndex; - std::unique_ptr index; - std::unique_ptr tracksUF; - - const UnionFindObject& getUnionFindEnum() const - { - return *tracksUF; - } - - const MapNodeToIndex& getReverseMap() const - { - return map_nodeToIndex; - } + /// graph container to create the node + lemon::ListDigraph graph; + /// node to index map + MapNodeToIndex map_nodeToIndex; + std::unique_ptr index; + std::unique_ptr tracksUF; + + const UnionFindObject& getUnionFindEnum() const { return *tracksUF; } + + const MapNodeToIndex& getReverseMap() const { return map_nodeToIndex; } }; -TracksBuilder::TracksBuilder() -{ - _d.reset(new TracksBuilderData()); -} +TracksBuilder::TracksBuilder() { _d.reset(new TracksBuilderData()); } TracksBuilder::~TracksBuilder() = default; void TracksBuilder::build(const PairwiseMatches& pairwiseMatches) { - typedef std::set SetIndexedPair; + typedef std::set SetIndexedPair; + + // set of all features of all images: (imageIndex, featureIndex) + SetIndexedPair allFeatures; - // set of all features of all images: (imageIndex, featureIndex) - SetIndexedPair allFeatures; + // for each couple of images make the union according the pair matches + for (const auto& matchesPerDescIt : pairwiseMatches) + { + const std::size_t& I = matchesPerDescIt.first.first; + const std::size_t& J = matchesPerDescIt.first.second; + const MatchesPerDescType& matchesPerDesc = matchesPerDescIt.second; + + for (const auto& matchesIt : matchesPerDesc) + { + const feature::EImageDescriberType descType = matchesIt.first; + const IndMatches& matches = matchesIt.second; + // we have correspondences between I and J image index. + for (const IndMatch& m : matches) + { + IndexedFeaturePair pairI(I, KeypointId(descType, m._i)); + IndexedFeaturePair pairJ(J, KeypointId(descType, m._j)); + allFeatures.insert(pairI); + allFeatures.insert(pairJ); + } + } + } - // for each couple of images make the union according the pair matches - for(const auto& matchesPerDescIt: pairwiseMatches) - { - const std::size_t& I = matchesPerDescIt.first.first; - const std::size_t& J = matchesPerDescIt.first.second; - const MatchesPerDescType& matchesPerDesc = matchesPerDescIt.second; + // build the node indirection for each referenced feature + MapIndexToNode map_indexToNode; + map_indexToNode.reserve(allFeatures.size()); + _d->map_nodeToIndex.reserve(allFeatures.size()); - for(const auto& matchesIt: matchesPerDesc) + for (const IndexedFeaturePair& featPair : allFeatures) { - const feature::EImageDescriberType descType = matchesIt.first; - const IndMatches& matches = matchesIt.second; - // we have correspondences between I and J image index. - for(const IndMatch& m: matches) - { - IndexedFeaturePair pairI(I, KeypointId(descType, m._i)); - IndexedFeaturePair pairJ(J, KeypointId(descType, m._j)); - allFeatures.insert(pairI); - allFeatures.insert(pairJ); - } + lemon::ListDigraph::Node node = _d->graph.addNode(); + map_indexToNode.insert(std::make_pair(featPair, node)); + _d->map_nodeToIndex.insert(std::make_pair(node, featPair)); } - } - - // build the node indirection for each referenced feature - MapIndexToNode map_indexToNode; - map_indexToNode.reserve(allFeatures.size()); - _d->map_nodeToIndex.reserve(allFeatures.size()); - - for(const IndexedFeaturePair& featPair: allFeatures) - { - lemon::ListDigraph::Node node = _d->graph.addNode(); - map_indexToNode.insert(std::make_pair(featPair, node)); - _d->map_nodeToIndex.insert(std::make_pair(node, featPair)); - } - - // add the element of myset to the UnionFind insert method. - _d->index.reset(new IndexMap(_d->graph)); - _d->tracksUF.reset(new UnionFindObject(*_d->index)); - - for(ListDigraph::NodeIt it(_d->graph); it != INVALID; ++it) - { - _d->tracksUF->insert(it); - } - - // make the union according the pair matches - for(const auto& matchesPerDescIt: pairwiseMatches) - { - const std::size_t& I = matchesPerDescIt.first.first; - const std::size_t& J = matchesPerDescIt.first.second; - const MatchesPerDescType& matchesPerDesc = matchesPerDescIt.second; - - for(const auto& matchesIt: matchesPerDesc) + + // add the element of myset to the UnionFind insert method. + _d->index.reset(new IndexMap(_d->graph)); + _d->tracksUF.reset(new UnionFindObject(*_d->index)); + + for (ListDigraph::NodeIt it(_d->graph); it != INVALID; ++it) { - const feature::EImageDescriberType descType = matchesIt.first; - const IndMatches& matches = matchesIt.second; - // we have correspondences between I and J image index. - for(const IndMatch& m: matches) - { - IndexedFeaturePair pairI(I, KeypointId(descType, m._i)); - IndexedFeaturePair pairJ(J, KeypointId(descType, m._j)); - _d->tracksUF->join(map_indexToNode[pairI], map_indexToNode[pairJ]); - } + _d->tracksUF->insert(it); + } + + // make the union according the pair matches + for (const auto& matchesPerDescIt : pairwiseMatches) + { + const std::size_t& I = matchesPerDescIt.first.first; + const std::size_t& J = matchesPerDescIt.first.second; + const MatchesPerDescType& matchesPerDesc = matchesPerDescIt.second; + + for (const auto& matchesIt : matchesPerDesc) + { + const feature::EImageDescriberType descType = matchesIt.first; + const IndMatches& matches = matchesIt.second; + // we have correspondences between I and J image index. + for (const IndMatch& m : matches) + { + IndexedFeaturePair pairI(I, KeypointId(descType, m._i)); + IndexedFeaturePair pairJ(J, KeypointId(descType, m._j)); + _d->tracksUF->join(map_indexToNode[pairI], map_indexToNode[pairJ]); + } + } } - } } void TracksBuilder::filter(bool clearForks, std::size_t minTrackLength, bool multithreaded) { - // remove bad tracks: - // - track that are too short, - // - track with id conflicts (many times the same image index) - if(!clearForks && minTrackLength == 0) - return; - - std::set set_classToErase; + // remove bad tracks: + // - track that are too short, + // - track with id conflicts (many times the same image index) + if (!clearForks && minTrackLength == 0) + return; -#pragma omp parallel if(multithreaded) - for(lemon::UnionFindEnum::ClassIt cit(*_d->tracksUF); cit != INVALID; ++cit) - { + std::set set_classToErase; -#pragma omp single nowait +#pragma omp parallel if (multithreaded) + for (lemon::UnionFindEnum::ClassIt cit(*_d->tracksUF); cit != INVALID; ++cit) { - std::size_t cpt = 0; - std::set myset; - for(lemon::UnionFindEnum::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) - { - myset.insert(_d->map_nodeToIndex[iit].first); - ++cpt; - } - if((clearForks && myset.size() != cpt) || myset.size() < minTrackLength) - { +#pragma omp single nowait + { + std::size_t cpt = 0; + std::set myset; + for (lemon::UnionFindEnum::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) + { + myset.insert(_d->map_nodeToIndex[iit].first); + ++cpt; + } + if ((clearForks && myset.size() != cpt) || myset.size() < minTrackLength) + { #pragma omp critical - set_classToErase.insert(cit.operator int()); - } + set_classToErase.insert(cit.operator int()); + } + } } - } - std::for_each(set_classToErase.begin(), set_classToErase.end(), - [&](int toErase){ _d->tracksUF->eraseClass(toErase); }); + std::for_each(set_classToErase.begin(), set_classToErase.end(), [&](int toErase) { _d->tracksUF->eraseClass(toErase); }); } bool TracksBuilder::exportToStream(std::ostream& os) { - std::size_t cpt = 0; - for(lemon::UnionFindEnum< IndexMap >::ClassIt cit(*_d->tracksUF); cit != INVALID; ++cit) - { - os << "Class: " << cpt++ << std::endl; - std::size_t cptTrackLength = 0; - for(lemon::UnionFindEnum< IndexMap >::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) - { - ++cptTrackLength; - } - os << "\t" << "track length: " << cptTrackLength << std::endl; - - for(lemon::UnionFindEnum< IndexMap >::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) + std::size_t cpt = 0; + for (lemon::UnionFindEnum::ClassIt cit(*_d->tracksUF); cit != INVALID; ++cit) { - os << _d->map_nodeToIndex[ iit ].first << " " << _d->map_nodeToIndex[ iit ].second << std::endl; + os << "Class: " << cpt++ << std::endl; + std::size_t cptTrackLength = 0; + for (lemon::UnionFindEnum::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) + { + ++cptTrackLength; + } + os << "\t" + << "track length: " << cptTrackLength << std::endl; + + for (lemon::UnionFindEnum::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) + { + os << _d->map_nodeToIndex[iit].first << " " << _d->map_nodeToIndex[iit].second << std::endl; + } } - } - return os.good(); + return os.good(); } void TracksBuilder::exportToSTL(TracksMap& allTracks) const { - allTracks.clear(); - - std::size_t trackIndex = 0; - for(lemon::UnionFindEnum< IndexMap >::ClassIt cit(*_d->tracksUF); cit != INVALID; ++cit, ++trackIndex) - { - // create the output track - std::pair ret = allTracks.insert(std::make_pair(trackIndex, Track())); - - Track& outTrack = ret.first->second; + allTracks.clear(); - for(lemon::UnionFindEnum< IndexMap >::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) + std::size_t trackIndex = 0; + for (lemon::UnionFindEnum::ClassIt cit(*_d->tracksUF); cit != INVALID; ++cit, ++trackIndex) { - const IndexedFeaturePair & currentPair = _d->map_nodeToIndex.at(iit); - // all descType inside the track will be the same - outTrack.descType = currentPair.second.descType; - outTrack.featPerView[currentPair.first] = currentPair.second.featIndex; + // create the output track + std::pair ret = allTracks.insert(std::make_pair(trackIndex, Track())); + + Track& outTrack = ret.first->second; + + for (lemon::UnionFindEnum::ItemIt iit(*_d->tracksUF, cit); iit != INVALID; ++iit) + { + const IndexedFeaturePair& currentPair = _d->map_nodeToIndex.at(iit); + // all descType inside the track will be the same + outTrack.descType = currentPair.second.descType; + outTrack.featPerView[currentPair.first] = currentPair.second.featIndex; + } } - } } std::size_t TracksBuilder::nbTracks() const { std::size_t cpt = 0; - for(lemon::UnionFindEnum< IndexMap >::ClassIt cit(*_d->tracksUF); cit != lemon::INVALID; ++cit) + for (lemon::UnionFindEnum::ClassIt cit(*_d->tracksUF); cit != lemon::INVALID; ++cit) ++cpt; return cpt; } -} // namespace track -} // namespace aliceVision +} // namespace track +} // namespace aliceVision diff --git a/src/aliceVision/track/TracksBuilder.hpp b/src/aliceVision/track/TracksBuilder.hpp index 108ecbce8d..d9f60a8a1d 100644 --- a/src/aliceVision/track/TracksBuilder.hpp +++ b/src/aliceVision/track/TracksBuilder.hpp @@ -10,7 +10,6 @@ #include - namespace aliceVision { namespace track { @@ -44,46 +43,46 @@ struct TracksBuilderData; */ class TracksBuilder { -public: + public: TracksBuilder(); ~TracksBuilder(); /** - * @brief Build tracks for a given series of pairWise matches - * @param[in] pairwiseMatches PairWise matches - */ + * @brief Build tracks for a given series of pairWise matches + * @param[in] pairwiseMatches PairWise matches + */ void build(const PairwiseMatches& pairwiseMatches); /** - * @brief Remove bad tracks (too short or track with ids collision) - * @param[in] clearForks: remove tracks with multiple observation in a single image - * @param[in] minTrackLength: minimal number of observations to keep the track - * @param[in] multithreaded Is multithreaded - */ + * @brief Remove bad tracks (too short or track with ids collision) + * @param[in] clearForks: remove tracks with multiple observation in a single image + * @param[in] minTrackLength: minimal number of observations to keep the track + * @param[in] multithreaded Is multithreaded + */ void filter(bool clearForks = true, std::size_t minTrackLength = 2, bool multithreaded = true); /** - * @brief Export data of tracks to stream - * @param[out] os char output stream - * @return true if no error flag are set - */ + * @brief Export data of tracks to stream + * @param[out] os char output stream + * @return true if no error flag are set + */ bool exportToStream(std::ostream& os); /** - * @brief Export tracks as a map (each entry is a sequence of imageId and keypointId): - * {TrackIndex => {(imageIndex, keypointId), ... ,(imageIndex, keypointId)} - */ + * @brief Export tracks as a map (each entry is a sequence of imageId and keypointId): + * {TrackIndex => {(imageIndex, keypointId), ... ,(imageIndex, keypointId)} + */ void exportToSTL(TracksMap& allTracks) const; /** - * @brief Return the number of connected set in the UnionFind structure (tree forest) - * @return number of connected set in the UnionFind structure - */ + * @brief Return the number of connected set in the UnionFind structure (tree forest) + * @return number of connected set in the UnionFind structure + */ std::size_t nbTracks() const; -private: + private: std::unique_ptr _d; }; -} // namespace track -} // namespace aliceVision +} // namespace track +} // namespace aliceVision diff --git a/src/aliceVision/track/trackIO.cpp b/src/aliceVision/track/trackIO.cpp index c99c707737..d25429f631 100644 --- a/src/aliceVision/track/trackIO.cpp +++ b/src/aliceVision/track/trackIO.cpp @@ -11,10 +11,7 @@ namespace track { void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, aliceVision::track::Track const& input) { - jv = { - {"descType", EImageDescriberType_enumToString(input.descType)} , - {"featPerView", boost::json::value_from(input.featPerView)} - }; + jv = {{"descType", EImageDescriberType_enumToString(input.descType)}, {"featPerView", boost::json::value_from(input.featPerView)}}; } aliceVision::track::Track tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) @@ -28,5 +25,5 @@ aliceVision::track::Track tag_invoke(boost::json::value_to_tag stl::flat_map flat_map_value_to(const boost::json::value& jv) { stl::flat_map ret; - + const boost::json::array obj = jv.as_array(); - for (const auto & item: obj) + for (const auto& item : obj) { const boost::json::array inner = item.as_array(); ret.insert({boost::json::value_to(inner[0]), boost::json::value_to(inner[1])}); @@ -39,5 +39,5 @@ void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, alic */ aliceVision::track::Track tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); -} // namespace track -} // namespace aliceVision +} // namespace track +} // namespace aliceVision diff --git a/src/aliceVision/track/track_test.cpp b/src/aliceVision/track/track_test.cpp index f6d95aa1d5..acca9c7515 100644 --- a/src/aliceVision/track/track_test.cpp +++ b/src/aliceVision/track/track_test.cpp @@ -21,210 +21,201 @@ using namespace aliceVision::feature; using namespace aliceVision::track; using namespace aliceVision::matching; - -BOOST_AUTO_TEST_CASE(Track_Simple) { - - // Create some tracks for image (A,B,C) - // {A,B,C} imageId will be {0,1,2} - // For those image link some features id depicted below - //A B C - //0 -> 0 -> 0 - //1 -> 1 -> 6 - //2 -> 3 - - // Create the input pairwise correspondences - PairwiseMatches map_pairwisematches; - - const IndMatch testAB[] = {IndMatch(0,0), IndMatch(1,1), IndMatch(2,3)}; - const IndMatch testBC[] = {IndMatch(0,0), IndMatch(1,6)}; - - const std::vector ab(testAB, testAB+3); - const std::vector bc(testBC, testBC+2); - const int A = 0; - const int B = 1; - const int C = 2; - map_pairwisematches[ std::make_pair(A, B) ][EImageDescriberType::UNKNOWN] = ab; - map_pairwisematches[ std::make_pair(B, C) ][EImageDescriberType::UNKNOWN] = bc; - - //-- Build tracks using the interface tracksbuilder - TracksBuilder trackBuilder; - trackBuilder.build( map_pairwisematches ); - - trackBuilder.exportToStream(std::cout); - - TracksMap map_tracks; - trackBuilder.exportToSTL(map_tracks); - - //------------------- - // Unit Test check - //------------------- - - //0, {(0,0) (1,0) (2,0)} - //1, {(0,1) (1,1) (2,6)} - //2, {(0,2) (1,3)} - const std::pair GT_Tracks[] = - { - std::make_pair(0,0), std::make_pair(1,0), std::make_pair(2,0), - std::make_pair(0,1), std::make_pair(1,1), std::make_pair(2,6), - std::make_pair(0,2), std::make_pair(1,3) - }; - - BOOST_CHECK_EQUAL(3, map_tracks.size()); - std::size_t cpt = 0, i = 0; - for (TracksMap::const_iterator iterT = map_tracks.begin(); - iterT != map_tracks.end(); - ++iterT, ++i) - { - BOOST_CHECK_EQUAL(i, iterT->first); - for (auto iter = iterT->second.featPerView.begin(); - iter != iterT->second.featPerView.end(); - ++iter) +BOOST_AUTO_TEST_CASE(Track_Simple) +{ + // Create some tracks for image (A,B,C) + // {A,B,C} imageId will be {0,1,2} + // For those image link some features id depicted below + // A B C + // 0 -> 0 -> 0 + // 1 -> 1 -> 6 + // 2 -> 3 + + // Create the input pairwise correspondences + PairwiseMatches map_pairwisematches; + + const IndMatch testAB[] = {IndMatch(0, 0), IndMatch(1, 1), IndMatch(2, 3)}; + const IndMatch testBC[] = {IndMatch(0, 0), IndMatch(1, 6)}; + + const std::vector ab(testAB, testAB + 3); + const std::vector bc(testBC, testBC + 2); + const int A = 0; + const int B = 1; + const int C = 2; + map_pairwisematches[std::make_pair(A, B)][EImageDescriberType::UNKNOWN] = ab; + map_pairwisematches[std::make_pair(B, C)][EImageDescriberType::UNKNOWN] = bc; + + //-- Build tracks using the interface tracksbuilder + TracksBuilder trackBuilder; + trackBuilder.build(map_pairwisematches); + + trackBuilder.exportToStream(std::cout); + + TracksMap map_tracks; + trackBuilder.exportToSTL(map_tracks); + + //------------------- + // Unit Test check + //------------------- + + // 0, {(0,0) (1,0) (2,0)} + // 1, {(0,1) (1,1) (2,6)} + // 2, {(0,2) (1,3)} + const std::pair GT_Tracks[] = {std::make_pair(0, 0), + std::make_pair(1, 0), + std::make_pair(2, 0), + std::make_pair(0, 1), + std::make_pair(1, 1), + std::make_pair(2, 6), + std::make_pair(0, 2), + std::make_pair(1, 3)}; + + BOOST_CHECK_EQUAL(3, map_tracks.size()); + std::size_t cpt = 0, i = 0; + for (TracksMap::const_iterator iterT = map_tracks.begin(); iterT != map_tracks.end(); ++iterT, ++i) { - BOOST_CHECK( GT_Tracks[cpt] == std::make_pair(iter->first, iter->second)); - ++cpt; + BOOST_CHECK_EQUAL(i, iterT->first); + for (auto iter = iterT->second.featPerView.begin(); iter != iterT->second.featPerView.end(); ++iter) + { + BOOST_CHECK(GT_Tracks[cpt] == std::make_pair(iter->first, iter->second)); + ++cpt; + } } - } } -BOOST_AUTO_TEST_CASE(Track_filter_3viewAtLeast) { - - // - //A B C - //0 -> 0 -> 0 - //1 -> 1 -> 6 - //2 -> 3 - // - - // Create the input pairwise correspondences - PairwiseMatches map_pairwisematches; - - IndMatch testAB[] = {IndMatch(0,0), IndMatch(1,1), IndMatch(2,3)}; - IndMatch testBC[] = {IndMatch(0,0), IndMatch(1,6)}; - - - std::vector ab(testAB, testAB+3); - std::vector bc(testBC, testBC+2); - const int A = 0; - const int B = 1; - const int C = 2; - map_pairwisematches[ std::make_pair(A,B) ][EImageDescriberType::UNKNOWN] = ab; - map_pairwisematches[ std::make_pair(B,C) ][EImageDescriberType::UNKNOWN] = bc; - - //-- Build tracks using the interface tracksbuilder - TracksBuilder trackBuilder; - trackBuilder.build( map_pairwisematches ); - BOOST_CHECK_EQUAL(3, trackBuilder.nbTracks()); - trackBuilder.filter(true, 3); - BOOST_CHECK_EQUAL(2, trackBuilder.nbTracks()); +BOOST_AUTO_TEST_CASE(Track_filter_3viewAtLeast) +{ + // + // A B C + // 0 -> 0 -> 0 + // 1 -> 1 -> 6 + // 2 -> 3 + // + + // Create the input pairwise correspondences + PairwiseMatches map_pairwisematches; + + IndMatch testAB[] = {IndMatch(0, 0), IndMatch(1, 1), IndMatch(2, 3)}; + IndMatch testBC[] = {IndMatch(0, 0), IndMatch(1, 6)}; + + std::vector ab(testAB, testAB + 3); + std::vector bc(testBC, testBC + 2); + const int A = 0; + const int B = 1; + const int C = 2; + map_pairwisematches[std::make_pair(A, B)][EImageDescriberType::UNKNOWN] = ab; + map_pairwisematches[std::make_pair(B, C)][EImageDescriberType::UNKNOWN] = bc; + + //-- Build tracks using the interface tracksbuilder + TracksBuilder trackBuilder; + trackBuilder.build(map_pairwisematches); + BOOST_CHECK_EQUAL(3, trackBuilder.nbTracks()); + trackBuilder.filter(true, 3); + BOOST_CHECK_EQUAL(2, trackBuilder.nbTracks()); } -BOOST_AUTO_TEST_CASE(Track_Conflict) { - - // - //A B C - //0 -> 0 -> 0 - //1 -> 1 -> 6 - //{2 -> 3 -> 2 - // 3 -> 8 } This track must be deleted, index 3 appears two times - // - - // Create the input pairwise correspondences - PairwiseMatches map_pairwisematches; - - const IndMatch testAB[] = {IndMatch(0,0), IndMatch(1,1), IndMatch(2,3)}; - const IndMatch testBC[] = {IndMatch(0,0), IndMatch(1,6), IndMatch(3,2), IndMatch(3,8)}; - - std::vector ab(testAB, testAB+3); - std::vector bc(testBC, testBC+4); - const int A = 0; - const int B = 1; - const int C = 2; - map_pairwisematches[ std::make_pair(A,B) ][EImageDescriberType::UNKNOWN] = ab; - map_pairwisematches[ std::make_pair(B,C) ][EImageDescriberType::UNKNOWN] = bc; - - //-- Build tracks using the interface tracksbuilder - TracksBuilder trackBuilder; - trackBuilder.build( map_pairwisematches ); - - BOOST_CHECK_EQUAL(3, trackBuilder.nbTracks()); - trackBuilder.filter(true, 2); // Key feature tested here to kill the conflicted track - BOOST_CHECK_EQUAL(2, trackBuilder.nbTracks()); - - TracksMap map_tracks; - trackBuilder.exportToSTL(map_tracks); - - //------------------- - // Unit Test check - //------------------- - - //0, {(0,0) (1,0) (2,0)} - //1, {(0,1) (1,1) (2,6)} - const std::pair GT_Tracks[] = - {std::make_pair(0,0), std::make_pair(1,0), std::make_pair(2,0), - std::make_pair(0,1), std::make_pair(1,1), std::make_pair(2,6)}; - - BOOST_CHECK_EQUAL(2, map_tracks.size()); - std::size_t cpt = 0, i = 0; - for (TracksMap::const_iterator iterT = map_tracks.begin(); - iterT != map_tracks.end(); - ++iterT, ++i) - { - BOOST_CHECK_EQUAL(i, iterT->first); - for (auto iter = iterT->second.featPerView.begin(); - iter != iterT->second.featPerView.end(); - ++iter) +BOOST_AUTO_TEST_CASE(Track_Conflict) +{ + // + // A B C + // 0 -> 0 -> 0 + // 1 -> 1 -> 6 + //{2 -> 3 -> 2 + // 3 -> 8 } This track must be deleted, index 3 appears two times + // + + // Create the input pairwise correspondences + PairwiseMatches map_pairwisematches; + + const IndMatch testAB[] = {IndMatch(0, 0), IndMatch(1, 1), IndMatch(2, 3)}; + const IndMatch testBC[] = {IndMatch(0, 0), IndMatch(1, 6), IndMatch(3, 2), IndMatch(3, 8)}; + + std::vector ab(testAB, testAB + 3); + std::vector bc(testBC, testBC + 4); + const int A = 0; + const int B = 1; + const int C = 2; + map_pairwisematches[std::make_pair(A, B)][EImageDescriberType::UNKNOWN] = ab; + map_pairwisematches[std::make_pair(B, C)][EImageDescriberType::UNKNOWN] = bc; + + //-- Build tracks using the interface tracksbuilder + TracksBuilder trackBuilder; + trackBuilder.build(map_pairwisematches); + + BOOST_CHECK_EQUAL(3, trackBuilder.nbTracks()); + trackBuilder.filter(true, 2); // Key feature tested here to kill the conflicted track + BOOST_CHECK_EQUAL(2, trackBuilder.nbTracks()); + + TracksMap map_tracks; + trackBuilder.exportToSTL(map_tracks); + + //------------------- + // Unit Test check + //------------------- + + // 0, {(0,0) (1,0) (2,0)} + // 1, {(0,1) (1,1) (2,6)} + const std::pair GT_Tracks[] = { + std::make_pair(0, 0), std::make_pair(1, 0), std::make_pair(2, 0), std::make_pair(0, 1), std::make_pair(1, 1), std::make_pair(2, 6)}; + + BOOST_CHECK_EQUAL(2, map_tracks.size()); + std::size_t cpt = 0, i = 0; + for (TracksMap::const_iterator iterT = map_tracks.begin(); iterT != map_tracks.end(); ++iterT, ++i) { - BOOST_CHECK( GT_Tracks[cpt] == std::make_pair(iter->first, iter->second)); - ++cpt; + BOOST_CHECK_EQUAL(i, iterT->first); + for (auto iter = iterT->second.featPerView.begin(); iter != iterT->second.featPerView.end(); ++iter) + { + BOOST_CHECK(GT_Tracks[cpt] == std::make_pair(iter->first, iter->second)); + ++cpt; + } } - } } BOOST_AUTO_TEST_CASE(Track_GetCommonTracksInImages) { - { - std::set set_imageIndex {15, 20}; - TracksPerView map_tracksPerView; - - std::vector base{1,2,3,4}; - map_tracksPerView[10] = base; - map_tracksPerView[15] = base; - map_tracksPerView[20] = base; - map_tracksPerView[40] = base; - map_tracksPerView[15].push_back(5); - map_tracksPerView[20].push_back(6); - - std::set set_visibleTracks; - getCommonTracksInImages(set_imageIndex, map_tracksPerView, set_visibleTracks); - BOOST_CHECK_EQUAL(base.size(), set_visibleTracks.size()); - set_visibleTracks.clear(); - // test non-existing view index - getCommonTracksInImages({15, 50}, map_tracksPerView, set_visibleTracks); - BOOST_CHECK(set_visibleTracks.empty()); - } - { - std::set set_imageIndex {15, 20, 10, 40}; - TracksPerView map_tracksPerView; - - std::vector base{1,2,3,4}; - map_tracksPerView[10] = base; - map_tracksPerView[15] = base; - map_tracksPerView[20] = base; - map_tracksPerView[40] = base; - - map_tracksPerView[10].push_back(100); - map_tracksPerView[15].push_back(100); - map_tracksPerView[20].push_back(100); - - map_tracksPerView[15].push_back(200); - map_tracksPerView[20].push_back(200); - map_tracksPerView[40].push_back(200); - - map_tracksPerView[15].push_back(5); - map_tracksPerView[20].push_back(6); - - std::set set_visibleTracks; - getCommonTracksInImages(set_imageIndex, map_tracksPerView, set_visibleTracks); - BOOST_CHECK_EQUAL(base.size(), set_visibleTracks.size()); - } + { + std::set set_imageIndex{15, 20}; + TracksPerView map_tracksPerView; + + std::vector base{1, 2, 3, 4}; + map_tracksPerView[10] = base; + map_tracksPerView[15] = base; + map_tracksPerView[20] = base; + map_tracksPerView[40] = base; + map_tracksPerView[15].push_back(5); + map_tracksPerView[20].push_back(6); + + std::set set_visibleTracks; + getCommonTracksInImages(set_imageIndex, map_tracksPerView, set_visibleTracks); + BOOST_CHECK_EQUAL(base.size(), set_visibleTracks.size()); + set_visibleTracks.clear(); + // test non-existing view index + getCommonTracksInImages({15, 50}, map_tracksPerView, set_visibleTracks); + BOOST_CHECK(set_visibleTracks.empty()); + } + { + std::set set_imageIndex{15, 20, 10, 40}; + TracksPerView map_tracksPerView; + + std::vector base{1, 2, 3, 4}; + map_tracksPerView[10] = base; + map_tracksPerView[15] = base; + map_tracksPerView[20] = base; + map_tracksPerView[40] = base; + + map_tracksPerView[10].push_back(100); + map_tracksPerView[15].push_back(100); + map_tracksPerView[20].push_back(100); + + map_tracksPerView[15].push_back(200); + map_tracksPerView[20].push_back(200); + map_tracksPerView[40].push_back(200); + + map_tracksPerView[15].push_back(5); + map_tracksPerView[20].push_back(6); + + std::set set_visibleTracks; + getCommonTracksInImages(set_imageIndex, map_tracksPerView, set_visibleTracks); + BOOST_CHECK_EQUAL(base.size(), set_visibleTracks.size()); + } } diff --git a/src/aliceVision/track/tracksUtils.cpp b/src/aliceVision/track/tracksUtils.cpp index 988bcc529e..b915c54e8a 100644 --- a/src/aliceVision/track/tracksUtils.cpp +++ b/src/aliceVision/track/tracksUtils.cpp @@ -9,84 +9,76 @@ #include - namespace aliceVision { namespace track { using namespace aliceVision::matching; -bool getCommonTracksInImages(const std::set& imageIndexes, - const TracksMap& tracksIn, - TracksMap& map_tracksOut) +bool getCommonTracksInImages(const std::set& imageIndexes, const TracksMap& tracksIn, TracksMap& map_tracksOut) { - assert(!imageIndexes.empty()); - map_tracksOut.clear(); - - // go along the tracks - for(auto& trackIn: tracksIn) - { - // look if the track contains the provided view index & save the point ids - Track map_temp; - for(std::size_t imageIndex: imageIndexes) + assert(!imageIndexes.empty()); + map_tracksOut.clear(); + + // go along the tracks + for (auto& trackIn : tracksIn) { - auto iterSearch = trackIn.second.featPerView.find(imageIndex); - if (iterSearch == trackIn.second.featPerView.end()) - break; // at least one request image is not in the track - map_temp.featPerView[iterSearch->first] = iterSearch->second; - map_temp.descType = trackIn.second.descType; + // look if the track contains the provided view index & save the point ids + Track map_temp; + for (std::size_t imageIndex : imageIndexes) + { + auto iterSearch = trackIn.second.featPerView.find(imageIndex); + if (iterSearch == trackIn.second.featPerView.end()) + break; // at least one request image is not in the track + map_temp.featPerView[iterSearch->first] = iterSearch->second; + map_temp.descType = trackIn.second.descType; + } + // if we have a feature for each input image + // we can add it to the output tracks. + if (map_temp.featPerView.size() == imageIndexes.size()) + map_tracksOut[trackIn.first] = std::move(map_temp); } - // if we have a feature for each input image - // we can add it to the output tracks. - if(map_temp.featPerView.size() == imageIndexes.size()) - map_tracksOut[trackIn.first] = std::move(map_temp); - } - return !map_tracksOut.empty(); + return !map_tracksOut.empty(); } - -void getCommonTracksInImages(const std::set& imageIndexes, - const TracksPerView& tracksPerView, - std::set& visibleTracks) +void getCommonTracksInImages(const std::set& imageIndexes, const TracksPerView& tracksPerView, std::set& visibleTracks) { - assert(!imageIndexes.empty()); - visibleTracks.clear(); - - // take the first image id - std::set::const_iterator it = imageIndexes.cbegin(); - { - TracksPerView::const_iterator tracksPerViewIt = tracksPerView.find(*it); - // if there are no tracks for the first view just return as there will not be - // any common track - if(tracksPerViewIt == tracksPerView.end()) + assert(!imageIndexes.empty()); + visibleTracks.clear(); + + // take the first image id + std::set::const_iterator it = imageIndexes.cbegin(); { - // one image is not present in the tracksPerView, so there is no track in common - visibleTracks.clear(); - return; + TracksPerView::const_iterator tracksPerViewIt = tracksPerView.find(*it); + // if there are no tracks for the first view just return as there will not be + // any common track + if (tracksPerViewIt == tracksPerView.end()) + { + // one image is not present in the tracksPerView, so there is no track in common + visibleTracks.clear(); + return; + } + const TrackIdSet& imageTracks = tracksPerViewIt->second; + // copy all the visible tracks by the first image + visibleTracks.insert(imageTracks.cbegin(), imageTracks.cend()); } - const TrackIdSet& imageTracks = tracksPerViewIt->second; - // copy all the visible tracks by the first image - visibleTracks.insert(imageTracks.cbegin(), imageTracks.cend()); - } - ++it; - // for each of the remaining view - for(; it != imageIndexes.cend(); ++it) - { - // if there are no tracks for this view just return - TracksPerView::const_iterator tracksPerViewIt = tracksPerView.find(*it); - if(tracksPerViewIt == tracksPerView.end()) + ++it; + // for each of the remaining view + for (; it != imageIndexes.cend(); ++it) { - // one image is not present in the tracksPerView, so there is no track in common - visibleTracks.clear(); - return; + // if there are no tracks for this view just return + TracksPerView::const_iterator tracksPerViewIt = tracksPerView.find(*it); + if (tracksPerViewIt == tracksPerView.end()) + { + // one image is not present in the tracksPerView, so there is no track in common + visibleTracks.clear(); + return; + } + const TrackIdSet& imageTracks = tracksPerViewIt->second; + std::set tmp; + std::set_intersection( + visibleTracks.cbegin(), visibleTracks.cend(), imageTracks.cbegin(), imageTracks.cend(), std::inserter(tmp, tmp.begin())); + visibleTracks.swap(tmp); } - const TrackIdSet& imageTracks = tracksPerViewIt->second; - std::set tmp; - std::set_intersection( - visibleTracks.cbegin(), visibleTracks.cend(), - imageTracks.cbegin(), imageTracks.cend(), - std::inserter(tmp, tmp.begin())); - visibleTracks.swap(tmp); - } } bool getCommonTracksInImagesFast(const std::set& imageIndexes, @@ -94,193 +86,175 @@ bool getCommonTracksInImagesFast(const std::set& imageIndexes, const TracksPerView& tracksPerView, TracksMap& tracksOut) { - assert(!imageIndexes.empty()); - tracksOut.clear(); - - std::set set_visibleTracks; - getCommonTracksInImages(imageIndexes, tracksPerView, set_visibleTracks); - - // go along the tracks - for(std::size_t visibleTrack: set_visibleTracks) - { - TracksMap::const_iterator itTrackIn = tracksIn.find(visibleTrack); - if(itTrackIn == tracksIn.end()) - continue; - const Track& trackFeatsIn = itTrackIn->second; - Track& trackFeatsOut = tracksOut[visibleTrack]; - trackFeatsOut.descType = trackFeatsIn.descType; - for(std::size_t imageIndex: imageIndexes) + assert(!imageIndexes.empty()); + tracksOut.clear(); + + std::set set_visibleTracks; + getCommonTracksInImages(imageIndexes, tracksPerView, set_visibleTracks); + + // go along the tracks + for (std::size_t visibleTrack : set_visibleTracks) { - const auto trackFeatsInIt = trackFeatsIn.featPerView.find(imageIndex); - if(trackFeatsInIt != trackFeatsIn.featPerView.end()) - trackFeatsOut.featPerView[imageIndex] = trackFeatsInIt->second; + TracksMap::const_iterator itTrackIn = tracksIn.find(visibleTrack); + if (itTrackIn == tracksIn.end()) + continue; + const Track& trackFeatsIn = itTrackIn->second; + Track& trackFeatsOut = tracksOut[visibleTrack]; + trackFeatsOut.descType = trackFeatsIn.descType; + for (std::size_t imageIndex : imageIndexes) + { + const auto trackFeatsInIt = trackFeatsIn.featPerView.find(imageIndex); + if (trackFeatsInIt != trackFeatsIn.featPerView.end()) + trackFeatsOut.featPerView[imageIndex] = trackFeatsInIt->second; + } + assert(trackFeatsOut.featPerView.size() == imageIndexes.size()); } - assert(trackFeatsOut.featPerView.size() == imageIndexes.size()); - } - return !tracksOut.empty(); + return !tracksOut.empty(); } -void getTracksInImages(const std::set& imagesId, - const TracksMap& tracks, - std::set& tracksId) +void getTracksInImages(const std::set& imagesId, const TracksMap& tracks, std::set& tracksId) { - tracksId.clear(); - for(const std::size_t id : imagesId) - { - std::set currentImageTracks; - getTracksInImage(id, tracks, currentImageTracks); - tracksId.insert(currentImageTracks.begin(), currentImageTracks.end()); - } + tracksId.clear(); + for (const std::size_t id : imagesId) + { + std::set currentImageTracks; + getTracksInImage(id, tracks, currentImageTracks); + tracksId.insert(currentImageTracks.begin(), currentImageTracks.end()); + } } -void getTracksInImagesFast(const std::set& imagesId, - const TracksPerView& tracksPerView, - std::set& tracksIds) +void getTracksInImagesFast(const std::set& imagesId, const TracksPerView& tracksPerView, std::set& tracksIds) { - tracksIds.clear(); - for(const std::size_t id : imagesId) - { - std::set currentImageTracks; - getTracksInImageFast(id, tracksPerView, currentImageTracks); - tracksIds.insert(currentImageTracks.begin(), currentImageTracks.end()); - } + tracksIds.clear(); + for (const std::size_t id : imagesId) + { + std::set currentImageTracks; + getTracksInImageFast(id, tracksPerView, currentImageTracks); + tracksIds.insert(currentImageTracks.begin(), currentImageTracks.end()); + } } -void getTracksInImage(const std::size_t& imageIndex, - const TracksMap& tracks, - std::set& tracksIds) +void getTracksInImage(const std::size_t& imageIndex, const TracksMap& tracks, std::set& tracksIds) { - tracksIds.clear(); - for(auto& track: tracks) - { - const auto iterSearch = track.second.featPerView.find(imageIndex); - if(iterSearch != track.second.featPerView.end()) - tracksIds.insert(track.first); - } + tracksIds.clear(); + for (auto& track : tracks) + { + const auto iterSearch = track.second.featPerView.find(imageIndex); + if (iterSearch != track.second.featPerView.end()) + tracksIds.insert(track.first); + } } -void getTracksInImageFast(const std::size_t& imageId, - const TracksPerView& tracksPerView, - std::set& tracksIds) +void getTracksInImageFast(const std::size_t& imageId, const TracksPerView& tracksPerView, std::set& tracksIds) { - if(tracksPerView.find(imageId) == tracksPerView.end()) - return; + if (tracksPerView.find(imageId) == tracksPerView.end()) + return; - const TrackIdSet& imageTracks = tracksPerView.at(imageId); - tracksIds.clear(); - tracksIds.insert(imageTracks.cbegin(), imageTracks.cend()); + const TrackIdSet& imageTracks = tracksPerView.at(imageId); + tracksIds.clear(); + tracksIds.insert(imageTracks.cbegin(), imageTracks.cend()); } void computeTracksPerView(const TracksMap& tracks, TracksPerView& tracksPerView) { - for(const auto& track: tracks) - { - for(const auto& feat: track.second.featPerView) + for (const auto& track : tracks) { - TrackIdSet& tracksSet = tracksPerView[feat.first]; - if(tracksSet.empty()) - tracksSet.reserve(1000); - tracksSet.push_back(track.first); + for (const auto& feat : track.second.featPerView) + { + TrackIdSet& tracksSet = tracksPerView[feat.first]; + if (tracksSet.empty()) + tracksSet.reserve(1000); + tracksSet.push_back(track.first); + } } - } - // sort tracks Ids in each view + // sort tracks Ids in each view #pragma omp parallel for - for(int i = 0; i < tracksPerView.size(); ++i) - { - TracksPerView::iterator it = tracksPerView.begin(); - std::advance(it, i); - std::sort(it->second.begin(), it->second.end()); - } + for (int i = 0; i < tracksPerView.size(); ++i) + { + TracksPerView::iterator it = tracksPerView.begin(); + std::advance(it, i); + std::sort(it->second.begin(), it->second.end()); + } } -void getTracksIdVector(const TracksMap& tracks, - std::set* tracksIds) +void getTracksIdVector(const TracksMap& tracks, std::set* tracksIds) { - tracksIds->clear(); - for (TracksMap::const_iterator iterT = tracks.begin(); iterT != tracks.end(); ++iterT) - tracksIds->insert(iterT->first); + tracksIds->clear(); + for (TracksMap::const_iterator iterT = tracks.begin(); iterT != tracks.end(); ++iterT) + tracksIds->insert(iterT->first); } -bool getFeatureIdInViewPerTrack(const TracksMap& allTracks, - const std::set& trackIds, - IndexT viewId, - std::vector* out_featId) +bool getFeatureIdInViewPerTrack(const TracksMap& allTracks, const std::set& trackIds, IndexT viewId, std::vector* out_featId) { - for(std::size_t trackId: trackIds) - { - TracksMap::const_iterator iterT = allTracks.find(trackId); - - // ignore it if the track doesn't exist - if(iterT == allTracks.end()) - continue; - - // try to find imageIndex - const Track& map_ref = iterT->second; - auto iterSearch = map_ref.featPerView.find(viewId); - if(iterSearch != map_ref.featPerView.end()) - out_featId->emplace_back(map_ref.descType, iterSearch->second); - } - return !out_featId->empty(); + for (std::size_t trackId : trackIds) + { + TracksMap::const_iterator iterT = allTracks.find(trackId); + + // ignore it if the track doesn't exist + if (iterT == allTracks.end()) + continue; + + // try to find imageIndex + const Track& map_ref = iterT->second; + auto iterSearch = map_ref.featPerView.find(viewId); + if (iterSearch != map_ref.featPerView.end()) + out_featId->emplace_back(map_ref.descType, iterSearch->second); + } + return !out_featId->empty(); } -void tracksToIndexedMatches(const TracksMap& tracks, - const std::vector& filterIndex, - std::vector* out_index) +void tracksToIndexedMatches(const TracksMap& tracks, const std::vector& filterIndex, std::vector* out_index) { + std::vector& vec_indexref = *out_index; + vec_indexref.clear(); - std::vector& vec_indexref = *out_index; - vec_indexref.clear(); - - for(std::size_t i = 0; i < filterIndex.size(); ++i) - { - // retrieve the track information from the current index i. - TracksMap::const_iterator itF = std::find_if(tracks.begin(), tracks.end(), FunctorMapFirstEqual(filterIndex[i])); + for (std::size_t i = 0; i < filterIndex.size(); ++i) + { + // retrieve the track information from the current index i. + TracksMap::const_iterator itF = std::find_if(tracks.begin(), tracks.end(), FunctorMapFirstEqual(filterIndex[i])); - // the current track. - const Track& map_ref = itF->second; + // the current track. + const Track& map_ref = itF->second; - // check we have 2 elements for a track. - assert(map_ref.featPerView.size() == 2); + // check we have 2 elements for a track. + assert(map_ref.featPerView.size() == 2); - const IndexT indexI = (map_ref.featPerView.begin())->second; - const IndexT indexJ = (++map_ref.featPerView.begin())->second; + const IndexT indexI = (map_ref.featPerView.begin())->second; + const IndexT indexJ = (++map_ref.featPerView.begin())->second; - vec_indexref.emplace_back(indexI, indexJ); - } + vec_indexref.emplace_back(indexI, indexJ); + } } -void tracksLength(const TracksMap& tracks, - std::map& occurenceTrackLength) +void tracksLength(const TracksMap& tracks, std::map& occurenceTrackLength) { - for(TracksMap::const_iterator iterT = tracks.begin(); iterT != tracks.end(); ++iterT) - { - const std::size_t trLength = iterT->second.featPerView.size(); - - if(occurenceTrackLength.end() == occurenceTrackLength.find(trLength)) - occurenceTrackLength[trLength] = 1; - else - occurenceTrackLength[trLength] += 1; - } + for (TracksMap::const_iterator iterT = tracks.begin(); iterT != tracks.end(); ++iterT) + { + const std::size_t trLength = iterT->second.featPerView.size(); + + if (occurenceTrackLength.end() == occurenceTrackLength.find(trLength)) + occurenceTrackLength[trLength] = 1; + else + occurenceTrackLength[trLength] += 1; + } } -void imageIdInTracks(const TracksPerView& tracksPerView, - std::set& imagesId) +void imageIdInTracks(const TracksPerView& tracksPerView, std::set& imagesId) { - for(const auto& viewTracks: tracksPerView) - imagesId.insert(viewTracks.first); + for (const auto& viewTracks : tracksPerView) + imagesId.insert(viewTracks.first); } -void imageIdInTracks(const TracksMap& tracks, - std::set& imagesId) +void imageIdInTracks(const TracksMap& tracks, std::set& imagesId) { - for (TracksMap::const_iterator iterT = tracks.begin(); iterT != tracks.end(); ++iterT) - { - const Track& map_ref = iterT->second; - for(auto iter = map_ref.featPerView.begin(); iter != map_ref.featPerView.end(); ++iter) - imagesId.insert(iter->first); - } + for (TracksMap::const_iterator iterT = tracks.begin(); iterT != tracks.end(); ++iterT) + { + const Track& map_ref = iterT->second; + for (auto iter = map_ref.featPerView.begin(); iter != map_ref.featPerView.end(); ++iter) + imagesId.insert(iter->first); + } } -} // namespace track -} // namespace aliceVision +} // namespace track +} // namespace aliceVision diff --git a/src/aliceVision/track/tracksUtils.hpp b/src/aliceVision/track/tracksUtils.hpp index 9fcc56efec..8c54eff2fb 100644 --- a/src/aliceVision/track/tracksUtils.hpp +++ b/src/aliceVision/track/tracksUtils.hpp @@ -8,7 +8,6 @@ #pragma once #include - namespace aliceVision { namespace track { @@ -18,20 +17,16 @@ namespace track { * @param[in] tracksIn: all tracks of the scene * @param[out] tracksOut: output with only the common tracks */ -bool getCommonTracksInImages(const std::set& imageIndexes, - const TracksMap& tracksIn, - TracksMap & tracksOut); - +bool getCommonTracksInImages(const std::set& imageIndexes, const TracksMap& tracksIn, TracksMap& tracksOut); + /** * @brief Find common tracks among a set of images. * @param[in] imageIndexes: set of images we are looking for common tracks. * @param[in] tracksPerView: for each view it contains the list of visible tracks. *The tracks ids must be ordered*. * @param[out] visibleTracks: output with only the common tracks. */ -void getCommonTracksInImages(const std::set& imageIndexes, - const TracksPerView& tracksPerView, - std::set& visibleTracks); - +void getCommonTracksInImages(const std::set& imageIndexes, const TracksPerView& tracksPerView, std::set& visibleTracks); + /** * @brief Find common tracks among images. * @param[in] imageIndexes: set of images we are looking for common tracks. @@ -40,19 +35,17 @@ void getCommonTracksInImages(const std::set& imageIndexes, * @param[out] tracksOut: output with only the common tracks. */ bool getCommonTracksInImagesFast(const std::set& imageIndexes, - const TracksMap& tracksIn, - const TracksPerView& tracksPerView, - TracksMap& tracksOut); - + const TracksMap& tracksIn, + const TracksPerView& tracksPerView, + TracksMap& tracksOut); + /** * @brief Find all the visible tracks from a set of images. * @param[in] imagesId set of images we are looking for tracks. * @param[in] tracks all tracks of the scene. * @param[out] tracksId the tracks in the images */ -void getTracksInImages(const std::set& imagesId, - const TracksMap& tracks, - std::set& tracksId); +void getTracksInImages(const std::set& imagesId, const TracksMap& tracks, std::set& tracksId); /** * @brief Find all the visible tracks from a set of images. @@ -60,9 +53,7 @@ void getTracksInImages(const std::set& imagesId, * @param[in] tracksPerView for each view the id of the visible tracks. * @param[out] tracksId the tracks in the images */ -void getTracksInImagesFast(const std::set& imagesId, - const TracksPerView& tracksPerView, - std::set& tracksIds); +void getTracksInImagesFast(const std::set& imagesId, const TracksPerView& tracksPerView, std::set& tracksIds); /** * @brief Find all the visible tracks from a single image. @@ -70,9 +61,7 @@ void getTracksInImagesFast(const std::set& imagesId, * @param[in] tracks all tracks of the scene. * @param[out] tracksIds the tracks in the image */ -void getTracksInImage(const std::size_t& imageIndex, - const TracksMap& tracks, - std::set& tracksIds); +void getTracksInImage(const std::size_t& imageIndex, const TracksMap& tracks, std::set& tracksIds); /** * @brief Find all the visible tracks from a set of images. @@ -80,9 +69,7 @@ void getTracksInImage(const std::size_t& imageIndex, * @param[in] map_tracksPerView for each view the id of the visible tracks. * @param[out] tracksIds the tracks in the images */ -void getTracksInImageFast(const std::size_t& imageId, - const TracksPerView& tracksPerView, - std::set& tracksIds); +void getTracksInImageFast(const std::size_t& imageId, const TracksPerView& tracksPerView, std::set& tracksIds); /** * @brief Compute the number of tracks for each view @@ -96,8 +83,7 @@ void computeTracksPerView(const TracksMap& tracks, TracksPerView& tracksPerView) * @param[in] tracks all tracks of the scene as a map {trackId, track} * @param[out] tracksIds the tracks in the images */ -void getTracksIdVector(const TracksMap& tracks, - std::set* tracksIds); +void getTracksIdVector(const TracksMap& tracks, std::set* tracksIds); /** * @brief Get feature id (with associated describer type) in the specified view for each TrackId @@ -107,22 +93,16 @@ void getTracksIdVector(const TracksMap& tracks, * @param[out] out_featId the number of features in the image as a vector * @return true if the vector of features Ids is not empty */ -bool getFeatureIdInViewPerTrack(const TracksMap& allTracks, - const std::set& trackIds, - IndexT viewId, - std::vector* out_featId); - +bool getFeatureIdInViewPerTrack(const TracksMap& allTracks, const std::set& trackIds, IndexT viewId, std::vector* out_featId); struct FunctorMapFirstEqual { - std::size_t id; + std::size_t id; - explicit FunctorMapFirstEqual(std::size_t val) : id(val) { }; + explicit FunctorMapFirstEqual(std::size_t val) + : id(val){}; - bool operator()(const std::pair & val) const - { - return ( id == val.first); - } + bool operator()(const std::pair& val) const { return (id == val.first); } }; /** @@ -138,33 +118,28 @@ struct FunctorMapFirstEqual * @warning The input tracks must be composed of only two images index. * @warning Image index are considered sorted (increasing order). */ -void tracksToIndexedMatches(const TracksMap& tracks, - const std::vector& filterIndex, - std::vector* out_index); +void tracksToIndexedMatches(const TracksMap& tracks, const std::vector& filterIndex, std::vector* out_index); /** * @brief Return the occurrence of tracks length. * @param[in] tracks all tracks of the scene as a map {trackId, track} * @param[out] occurenceTrackLength : the occurence length of each trackId in the scene */ -void tracksLength(const TracksMap& tracks, - std::map& occurenceTrackLength); +void tracksLength(const TracksMap& tracks, std::map& occurenceTrackLength); /** * @brief Return a set containing the image Id considered in the tracks container. * @param[in] tracksPerView the visible tracks as a map {viewID, vector} * @param[out] imagesId set of images considered in the visible tracks container. */ -void imageIdInTracks(const TracksPerView& tracksPerView, - std::set& imagesId); +void imageIdInTracks(const TracksPerView& tracksPerView, std::set& imagesId); /** * @brief Return a set containing the image Id considered in the tracks container. * @param[in] tracks all tracks of the scene as a map {trackId, track} * @param[out] imagesId set of images considered in the tracks container. */ -void imageIdInTracks(const TracksMap& tracks, - std::set& imagesId); +void imageIdInTracks(const TracksMap& tracks, std::set& imagesId); -} // namespace track -} // namespace aliceVision +} // namespace track +} // namespace aliceVision diff --git a/src/aliceVision/types.hpp b/src/aliceVision/types.hpp index 4eff5bc4d9..7e907258d9 100644 --- a/src/aliceVision/types.hpp +++ b/src/aliceVision/types.hpp @@ -15,7 +15,7 @@ #include #ifdef ALICEVISION_STD_UNORDERED_MAP -#include + #include #endif namespace aliceVision { @@ -23,7 +23,7 @@ namespace aliceVision { typedef uint32_t IndexT; static const IndexT UndefinedIndexT = std::numeric_limits::max(); -typedef std::pair Pair; +typedef std::pair Pair; typedef std::set PairSet; typedef std::vector PairVec; @@ -32,19 +32,18 @@ template using HashMap = std::unordered_map; #else template -using HashMap = std::map, Eigen::aligned_allocator > >; +using HashMap = std::map, Eigen::aligned_allocator>>; #endif - struct EstimationStatus { - EstimationStatus(bool valid, bool strongSupport) - : isValid(valid) - , hasStrongSupport(strongSupport) - {} + EstimationStatus(bool valid, bool strongSupport) + : isValid(valid), + hasStrongSupport(strongSupport) + {} - bool isValid = false; - bool hasStrongSupport = false; + bool isValid = false; + bool hasStrongSupport = false; }; -} // namespace aliceVision +} // namespace aliceVision diff --git a/src/aliceVision/unitTest.hpp b/src/aliceVision/unitTest.hpp index d82d73873e..782cf0da88 100644 --- a/src/aliceVision/unitTest.hpp +++ b/src/aliceVision/unitTest.hpp @@ -10,73 +10,85 @@ #include - #include -#define EXPECT_MATRIX_CLOSE_FRACTION(a, b, tolerance) \ -{ \ - bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ - BOOST_CHECK_EQUAL(a.rows(),b.rows()); \ - BOOST_CHECK_EQUAL(a.cols(),b.cols()); \ - if (dims_match) { \ - for (int r = 0; r < a.rows(); ++r) { \ - for (int c = 0; c < a.cols(); ++c) { \ - BOOST_CHECK_CLOSE_FRACTION(a(r, c), b(r, c), tolerance); \ - } \ - } \ - } \ -} +#define EXPECT_MATRIX_CLOSE_FRACTION(a, b, tolerance) \ + { \ + bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ + BOOST_CHECK_EQUAL(a.rows(), b.rows()); \ + BOOST_CHECK_EQUAL(a.cols(), b.cols()); \ + if (dims_match) \ + { \ + for (int r = 0; r < a.rows(); ++r) \ + { \ + for (int c = 0; c < a.cols(); ++c) \ + { \ + BOOST_CHECK_CLOSE_FRACTION(a(r, c), b(r, c), tolerance); \ + } \ + } \ + } \ + } -#define EXPECT_MATRIX_NEAR(a, b, tolerance) \ -{ \ - bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ - BOOST_CHECK_EQUAL(a.rows(),b.rows()); \ - BOOST_CHECK_EQUAL(a.cols(),b.cols()); \ - if (dims_match) { \ - for (int r = 0; r < a.rows(); ++r) { \ - for (int c = 0; c < a.cols(); ++c) { \ - BOOST_CHECK_SMALL(a(r, c)-b(r, c), tolerance); \ - } \ - } \ - } \ -} +#define EXPECT_MATRIX_NEAR(a, b, tolerance) \ + { \ + bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ + BOOST_CHECK_EQUAL(a.rows(), b.rows()); \ + BOOST_CHECK_EQUAL(a.cols(), b.cols()); \ + if (dims_match) \ + { \ + for (int r = 0; r < a.rows(); ++r) \ + { \ + for (int c = 0; c < a.cols(); ++c) \ + { \ + BOOST_CHECK_SMALL(a(r, c) - b(r, c), tolerance); \ + } \ + } \ + } \ + } -#define EXPECT_MATRIX_NEAR_ZERO(a, tolerance) \ -{ \ - for (int r = 0; r < a.rows(); ++r) { \ - for (int c = 0; c < a.cols(); ++c) { \ - BOOST_CHECK_SMALL(a(r, c), tolerance) \ - } \ - } \ -} +#define EXPECT_MATRIX_NEAR_ZERO(a, tolerance) \ + { \ + for (int r = 0; r < a.rows(); ++r) \ + { \ + for (int c = 0; c < a.cols(); ++c) \ + { \ + BOOST_CHECK_SMALL(a(r, c), tolerance) \ + } \ + } \ + } -#define EXPECT_MATRIX_EQ(a, b) \ -{ \ - bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ - BOOST_CHECK_EQUAL(a.rows(),b.rows()); \ - BOOST_CHECK_EQUAL(a.cols(),b.cols()); \ - if (dims_match) { \ - for (int r = 0; r < a.rows(); ++r) { \ - for (int c = 0; c < a.cols(); ++c) { \ - BOOST_CHECK_EQUAL(a(r, c), b(r, c)) \ - } \ - } \ - } \ -} +#define EXPECT_MATRIX_EQ(a, b) \ + { \ + bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ + BOOST_CHECK_EQUAL(a.rows(), b.rows()); \ + BOOST_CHECK_EQUAL(a.cols(), b.cols()); \ + if (dims_match) \ + { \ + for (int r = 0; r < a.rows(); ++r) \ + { \ + for (int c = 0; c < a.cols(); ++c) \ + { \ + BOOST_CHECK_EQUAL(a(r, c), b(r, c)) \ + } \ + } \ + } \ + } -#define EXPECT_EQ(a, b) BOOST_CHECK_EQUAL(a,b); +#define EXPECT_EQ(a, b) BOOST_CHECK_EQUAL(a, b); // Check that sin(angle(a, b)) < tolerance. -#define EXPECT_MATRIX_PROP(a, b, tolerance) \ -{ \ - bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ - BOOST_CHECK_EQUAL(a.rows(), b.rows()); \ - BOOST_CHECK_EQUAL(a.cols(), b.cols()); \ - if (dims_match) { \ - double c = CosinusBetweenMatrices(a, b); \ - if (c * c < 1) { \ - double s = sqrt(1 - c * c); \ - BOOST_CHECK_SMALL(s, tolerance); \ - } \ - } \ -} +#define EXPECT_MATRIX_PROP(a, b, tolerance) \ + { \ + bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ + BOOST_CHECK_EQUAL(a.rows(), b.rows()); \ + BOOST_CHECK_EQUAL(a.cols(), b.cols()); \ + if (dims_match) \ + { \ + double c = CosinusBetweenMatrices(a, b); \ + if (c * c < 1) \ + { \ + double s = sqrt(1 - c * c); \ + BOOST_CHECK_SMALL(s, tolerance); \ + } \ + } \ + } diff --git a/src/aliceVision/utils/Histogram.hpp b/src/aliceVision/utils/Histogram.hpp index 651300d3d6..2a3fe3b10e 100644 --- a/src/aliceVision/utils/Histogram.hpp +++ b/src/aliceVision/utils/Histogram.hpp @@ -35,109 +35,94 @@ namespace utils { * @author Pierre MOULON base on work from Jansson Consulting * 2009-06-30, updated 2011-06-17 and 2011-08-03 Dedicated to the public domain. */ -template +template class Histogram { -public: - // Construct a histogram that can count - // within a range of values. - // All bins of the histogram are set to zero. - Histogram( - const T& Start= T(0), - const T& End=T(1), - const size_t& nBins = 10): - Start(Start), - End(End), - nBins_by_interval(nBins/(End-Start)), - nBins(nBins), - overflow(0), - underflow(0) - { - freq.resize(nBins, 0); - } + public: + // Construct a histogram that can count + // within a range of values. + // All bins of the histogram are set to zero. + Histogram(const T& Start = T(0), const T& End = T(1), const size_t& nBins = 10) + : Start(Start), + End(End), + nBins_by_interval(nBins / (End - Start)), + nBins(nBins), + overflow(0), + underflow(0) + { + freq.resize(nBins, 0); + } - // Construct a histogram from a sequence of data - template - void Add(DataInputIterator begin, DataInputIterator end) - { - for (DataInputIterator iter = begin; iter != end; ++iter) - Add(static_cast(*iter)); - } + // Construct a histogram from a sequence of data + template + void Add(DataInputIterator begin, DataInputIterator end) + { + for (DataInputIterator iter = begin; iter != end; ++iter) + Add(static_cast(*iter)); + } - // Increase the count for the bin that holds a - // value that is in range for this histogram or - // the under-/overflow count if it is not in range. - void Add(const T& x) - { - if( x < Start ) - ++underflow; - else if( x > End ) - ++overflow; - else + // Increase the count for the bin that holds a + // value that is in range for this histogram or + // the under-/overflow count if it is not in range. + void Add(const T& x) { - const size_t i( - static_cast( - (x-Start)*nBins_by_interval) ); - // clamp for the particular case when (x == End) - ++freq[std::min(i, nBins-1)]; + if (x < Start) + ++underflow; + else if (x > End) + ++overflow; + else + { + const size_t i(static_cast((x - Start) * nBins_by_interval)); + // clamp for the particular case when (x == End) + ++freq[std::min(i, nBins - 1)]; + } } - } - // Get the sum of all counts in the histogram. - size_t GetTotalCount() const - { - return std::accumulate(freq.begin(), freq.end(), 0.0); - } - // Get the overflow count. - size_t GetOverflow() const - { - return overflow; - } - // Get the underflow count. - size_t GetUnderflow() const - { - return underflow; - } - // Get frequencies - const std::vector & GetHist() const { return freq; } - std::vector & GetHist() { return freq; } + // Get the sum of all counts in the histogram. + size_t GetTotalCount() const { return std::accumulate(freq.begin(), freq.end(), 0.0); } + // Get the overflow count. + size_t GetOverflow() const { return overflow; } + // Get the underflow count. + size_t GetUnderflow() const { return underflow; } + // Get frequencies + const std::vector& GetHist() const { return freq; } + std::vector& GetHist() { return freq; } - // Get XbinsValue - std::vector GetXbinsValue() const - { - std::vector vec_XbinValue(nBins, T(0)); - double val = (End-Start)/static_cast(nBins-1); - for (size_t i = 0; i < nBins; ++i) - vec_XbinValue[i] = (val*static_cast(i) + Start); - return vec_XbinValue; - } - // Get start - double GetStart() const {return Start;} - // Get End - double GetEnd() const {return End;} + // Get XbinsValue + std::vector GetXbinsValue() const + { + std::vector vec_XbinValue(nBins, T(0)); + double val = (End - Start) / static_cast(nBins - 1); + for (size_t i = 0; i < nBins; ++i) + vec_XbinValue[i] = (val * static_cast(i) + Start); + return vec_XbinValue; + } + // Get start + double GetStart() const { return Start; } + // Get End + double GetEnd() const { return End; } - // Text display of the histogram - template - std::string ToString(const std::string & sTitle = "", int precision = 3) const - { - std::ostringstream os; - os << std::endl << sTitle << std::endl; - const size_t n = freq.size(); - for (size_t i = 0; i < n; ++i) + // Text display of the histogram + template + std::string ToString(const std::string& sTitle = "", int precision = 3) const { - os << std::setprecision(precision) - << KeyDataType(Start + static_cast(End-Start)/n*static_cast(i)) - << "\t|\t" << ValueDataType(freq[i]) << "\n"; + std::ostringstream os; + os << std::endl << sTitle << std::endl; + const size_t n = freq.size(); + for (size_t i = 0; i < n; ++i) + { + os << std::setprecision(precision) << KeyDataType(Start + static_cast(End - Start) / n * static_cast(i)) << "\t|\t" + << ValueDataType(freq[i]) << "\n"; + } + os << std::setprecision(precision) << KeyDataType(End) << std::endl; + return os.str(); } - os << std::setprecision(precision) << KeyDataType(End) << std::endl; - return os.str(); - } -private: - double Start, End, nBins_by_interval; - size_t nBins; // number of bins - std::vector freq; // histogram - size_t overflow, underflow; //count under/over flow + private: + double Start, End, nBins_by_interval; + size_t nBins; // number of bins + std::vector freq; // histogram + size_t overflow, underflow; // count under/over flow }; -} // namespace utils -} // namespace aliceVision +} // namespace utils +} // namespace aliceVision diff --git a/src/aliceVision/utils/convert.hpp b/src/aliceVision/utils/convert.hpp index bd5627f279..3c84fd4341 100644 --- a/src/aliceVision/utils/convert.hpp +++ b/src/aliceVision/utils/convert.hpp @@ -15,10 +15,10 @@ namespace utils { inline std::string toStringZeroPadded(std::size_t i, std::size_t zeroPadding) { - std::stringstream ss; - ss << std::setw(zeroPadding) << std::setfill('0') << i; - return ss.str(); + std::stringstream ss; + ss << std::setw(zeroPadding) << std::setfill('0') << i; + return ss.str(); } -} // namespace utils -} // namespace aliceVision +} // namespace utils +} // namespace aliceVision diff --git a/src/aliceVision/utils/filesIO.hpp b/src/aliceVision/utils/filesIO.hpp index d1f7fd5351..3eed8825d4 100644 --- a/src/aliceVision/utils/filesIO.hpp +++ b/src/aliceVision/utils/filesIO.hpp @@ -23,19 +23,19 @@ namespace utils { * @return the paths list to the corresponding files if they validate the predicate, otherwise it returns an empty list. */ inline std::vector getFilesPathsFromFolder(const std::string& folder, - const std::function& predicate) + const std::function& predicate) { // Get all files paths in folder std::vector paths; // If the path isn't a folder path - if(!fs::is_directory(folder)) + if (!fs::is_directory(folder)) throw std::invalid_argument("The path '" + folder + "' is not a valid folder path."); - for(const auto& pathIt : fs::directory_iterator(folder)) + for (const auto& pathIt : fs::directory_iterator(folder)) { const fs::path path = pathIt.path(); - if(is_regular_file(path) && predicate(path)) + if (is_regular_file(path) && predicate(path)) paths.push_back(path.generic_string()); } @@ -49,10 +49,10 @@ inline std::vector getFilesPathsFromFolder(const std::string& folde * @return the paths list to the corresponding files if they validate the predicate, otherwise it returns an empty list. */ inline std::vector getFilesPathsFromFolders(const std::vector& folders, - const std::function& predicate) + const std::function& predicate) { std::vector paths; - for(const std::string& folder : folders) + for (const std::string& folder : folders) { const std::vector subPaths = getFilesPathsFromFolder(folder, predicate); paths.insert(paths.end(), subPaths.begin(), subPaths.end()); @@ -61,5 +61,5 @@ inline std::vector getFilesPathsFromFolders(const std::vector namespace aliceVision { -namespace utils -{ - /** - * @brief Create regex from a string filter (supported regex: '#' matches a single digit, '@' one or more digits, '?' one character and '*' zero or more) +namespace utils { +/** + * @brief Create regex from a string filter (supported regex: '#' matches a single digit, '@' one or more digits, '?' one character and '*' zero or + * more) * @param[in] str - Input string filter * @return the resulting regex */ inline std::regex filterToRegex(std::string str) { - boost::replace_all(str, ".", "\\."); // escape "." - boost::replace_all(str, "@", "[0-9]+"); // one @ correspond to one or more digits - boost::replace_all(str, "#", "[0-9]"); // each # in pattern correspond to a digit + boost::replace_all(str, ".", "\\."); // escape "." + boost::replace_all(str, "@", "[0-9]+"); // one @ correspond to one or more digits + boost::replace_all(str, "#", "[0-9]"); // each # in pattern correspond to a digit boost::replace_all(str, "*", "(.*)"); boost::replace_all(str, "?", "(.)"); std::regex regexFilter; @@ -33,7 +33,7 @@ inline std::regex filterToRegex(std::string str) { regexFilter = std::regex(str); } - catch(const std::regex_error& e) + catch (const std::regex_error& e) { ALICEVISION_LOG_ERROR("[filterToRegex] regex_error caught: " << std::string(e.what())); throw std::invalid_argument("Invalid regex conversion, your input filter may be invalid.\nGenerated regex: '" + str + "'"); @@ -54,5 +54,5 @@ inline void filterStrings(std::vector& strVec, const std::string& f strVec.erase(std::remove_if(begin, end, [®ex](const std::string& str) { return !std::regex_match(str, regex); }), end); } -} // namespace utils -} // namespace aliceVision \ No newline at end of file +} // namespace utils +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/utils/regexFilter_test.cpp b/src/aliceVision/utils/regexFilter_test.cpp index d7fadade7c..9d3e9656ab 100644 --- a/src/aliceVision/utils/regexFilter_test.cpp +++ b/src/aliceVision/utils/regexFilter_test.cpp @@ -18,16 +18,13 @@ using namespace aliceVision; BOOST_AUTO_TEST_CASE(utils_regexMatching) { - const std::vector exemplePaths - { - "C:/Users/img_0001.jpg", "C:/Users/img_0002.jpg", "C:/Users/img_0003.jpg", "C:/Users/img_0004.jpg", - "C:/Users/img_0005.jpg", "C:/Users/img_0006.jpg", "C:/Users/img_0007.jpg", "C:/Users/img_0008.jpg", - "C:/Users/img_0009.jpg", "C:/Users/img_0010.jpg", "C:/Users/img_0011.jpg", "C:/Users/img_0012.jpg", + const std::vector exemplePaths{"C:/Users/img_0001.jpg", "C:/Users/img_0002.jpg", "C:/Users/img_0003.jpg", "C:/Users/img_0004.jpg", + "C:/Users/img_0005.jpg", "C:/Users/img_0006.jpg", "C:/Users/img_0007.jpg", "C:/Users/img_0008.jpg", + "C:/Users/img_0009.jpg", "C:/Users/img_0010.jpg", "C:/Users/img_0011.jpg", "C:/Users/img_0012.jpg", - "C:/Users/000001.png", "C:/Users/000002.png", "C:/Users/000003.png", "C:/Users/000004.png", + "C:/Users/000001.png", "C:/Users/000002.png", "C:/Users/000003.png", "C:/Users/000004.png", - "C:/Users/test00.exr", "C:/Users/test01.exr", "C:/Users/test02.exr", "C:/Users/test03.exr" - }; + "C:/Users/test00.exr", "C:/Users/test01.exr", "C:/Users/test02.exr", "C:/Users/test03.exr"}; { std::vector test; diff --git a/src/aliceVision/version.hpp b/src/aliceVision/version.hpp index e47a2a1d69..990db92811 100644 --- a/src/aliceVision/version.hpp +++ b/src/aliceVision/version.hpp @@ -21,28 +21,26 @@ #define ALICEVISION_TO_STRING(x) ALICEVISION_TO_STRING_HELPER(x) // AliceVision version as a string; for example "0.9.0". -#define ALICEVISION_VERSION_STRING ALICEVISION_TO_STRING(ALICEVISION_VERSION_MAJOR) "." \ - ALICEVISION_TO_STRING(ALICEVISION_VERSION_MINOR) "." \ - ALICEVISION_TO_STRING(ALICEVISION_VERSION_REVISION) +#define ALICEVISION_VERSION_STRING \ + ALICEVISION_TO_STRING(ALICEVISION_VERSION_MAJOR) \ + "." ALICEVISION_TO_STRING(ALICEVISION_VERSION_MINOR) "." ALICEVISION_TO_STRING(ALICEVISION_VERSION_REVISION) namespace aliceVision { class Version { -public: + public: Version() - : _v(Vec3i::Zero()) + : _v(Vec3i::Zero()) {} - explicit Version(const Vec3i & v) - : _v(v) - { - } + explicit Version(const Vec3i& v) + : _v(v) + {} Version(int major, int minor, int micro) - : _v(major, minor, micro) - { - } + : _v(major, minor, micro) + {} Version& operator=(const Vec3i& other) { @@ -63,13 +61,13 @@ class Version { return false; } - } + } return false; } -private: + private: Vec3i _v; }; -} +} // namespace aliceVision diff --git a/src/aliceVision/voctree/Database.cpp b/src/aliceVision/voctree/Database.cpp index 04837275d7..b973c01b61 100644 --- a/src/aliceVision/voctree/Database.cpp +++ b/src/aliceVision/voctree/Database.cpp @@ -13,23 +13,23 @@ #include #include -namespace aliceVision{ -namespace voctree{ +namespace aliceVision { +namespace voctree { -std::ostream& operator<<(std::ostream& os, const SparseHistogram &dv) +std::ostream& operator<<(std::ostream& os, const SparseHistogram& dv) { - for( const auto &e : dv ) - { - os << e.first << ", " << e.second.size() << "; "; - } - os << "\n"; - return os; + for (const auto& e : dv) + { + os << e.first << ", " << e.second.size() << "; "; + } + os << "\n"; + return os; } std::ostream& operator<<(std::ostream& os, const DocMatches& matches) { os << "[ "; - for (const auto &e : matches) + for (const auto& e : matches) { os << e.id << ", " << e.score << "; "; } @@ -38,59 +38,60 @@ std::ostream& operator<<(std::ostream& os, const DocMatches& matches) } Database::Database(uint32_t num_words) -: word_files_(num_words), -word_weights_( num_words, 1.0f ) { } + : word_files_(num_words), + word_weights_(num_words, 1.0f) +{} DocId Database::insert(DocId doc_id, const SparseHistogram& document) { - // Ensure that the new document to insert is not already there. - assert(database_.find(doc_id) == database_.end()); - - // For each word, retrieve its inverted file and increment the count for doc_id. - for(SparseHistogram::const_iterator it = document.begin(), end = document.end(); it != end; ++it) - { - Word word = it->first; - InvertedFile& file = word_files_[word]; - if(file.empty() || file.back().id != doc_id) - file.push_back(WordFrequency(doc_id, it->second.size())); - else - file.back().count += it->second.size(); - } + // Ensure that the new document to insert is not already there. + assert(database_.find(doc_id) == database_.end()); + + // For each word, retrieve its inverted file and increment the count for doc_id. + for (SparseHistogram::const_iterator it = document.begin(), end = document.end(); it != end; ++it) + { + Word word = it->first; + InvertedFile& file = word_files_[word]; + if (file.empty() || file.back().id != doc_id) + file.push_back(WordFrequency(doc_id, it->second.size())); + else + file.back().count += it->second.size(); + } - database_[doc_id] = document; + database_[doc_id] = document; - return doc_id; + return doc_id; } void Database::sanityCheck(std::size_t N, std::map& matches) const { - // if N is equal to zero - if(N == 0) - { - // retrieve all the matchings - N = this->size(); - } - else - { - // otherwise always take the min between N and the number of documents - // in the database - N = std::min(N, this->size()); - } - - matches.clear(); - // since we already know the size of the vectors, in order to parallelize the - // query allocate the whole memory - auto display = system::createConsoleProgressDisplay(database_.size(), std::cout); - - //#pragma omp parallel for default(none) shared(database_) - for(const auto &doc : database_) - { - std::vector m; - find(doc.second, N, m); - // matches.emplace_back( m ); - matches[doc.first] = m; - ++display; - } + // if N is equal to zero + if (N == 0) + { + // retrieve all the matchings + N = this->size(); + } + else + { + // otherwise always take the min between N and the number of documents + // in the database + N = std::min(N, this->size()); + } + + matches.clear(); + // since we already know the size of the vectors, in order to parallelize the + // query allocate the whole memory + auto display = system::createConsoleProgressDisplay(database_.size(), std::cout); + + // #pragma omp parallel for default(none) shared(database_) + for (const auto& doc : database_) + { + std::vector m; + find(doc.second, N, m); + // matches.emplace_back( m ); + matches[doc.first] = m; + ++display; + } } /** @@ -101,14 +102,14 @@ void Database::sanityCheck(std::size_t N, std::map& mat * @param[out] matches IDs and scores for the top N matching database documents. * @param[in] distanceMethod the method used to compute distance between histograms. */ -void Database::find(const std::vector& document, std::size_t N, std::vector& matches, const std::string &distanceMethod) const +void Database::find(const std::vector& document, std::size_t N, std::vector& matches, const std::string& distanceMethod) const { - SparseHistogram query; - // from the list of visual words associated with each feature in the document/image - // generate the (sparse) histogram of the visual words - computeSparseHistogram(document, query); + SparseHistogram query; + // from the list of visual words associated with each feature in the document/image + // generate the (sparse) histogram of the visual words + computeSparseHistogram(document, query); - find( query, N, matches, distanceMethod); + find(query, N, matches, distanceMethod); } /** @@ -119,11 +120,11 @@ void Database::find(const std::vector& document, std::size_t N, std::vecto * @param[out] matches IDs and scores for the top N matching database documents. * @param[in] distanceMethod the method used to compute distance between histograms. */ -void Database::find( const SparseHistogram& query, std::size_t N, std::vector& matches, const std::string &distanceMethod) const +void Database::find(const SparseHistogram& query, std::size_t N, std::vector& matches, const std::string& distanceMethod) const { matches.clear(); matches.reserve(database_.size()); - for(const auto& document : database_) + for (const auto& document : database_) { // for each document/image in the database compute the distance between the // histograms of the query image and the others @@ -143,52 +144,52 @@ void Database::find( const SparseHistogram& query, std::size_t N, std::vector #include -namespace aliceVision{ -namespace voctree{ +namespace aliceVision { +namespace voctree { /** * @brief Struct representing a single database match. @@ -23,30 +23,20 @@ namespace voctree{ */ struct DocMatch { - DocId id{UndefinedIndexT}; - float score{0.0f}; - - DocMatch() = default; - DocMatch(DocId _id, float _score) - : id(_id) - , score(_score) - {} - - /// Allows sorting DocMatches in best-to-worst order with std::sort. - bool operator<(const DocMatch& other) const - { - return score < other.score; - } - - bool operator==(const DocMatch& other) const - { - return id == other.id && - score == other.score; - } - bool operator!=(const DocMatch& other) const - { - return !(*this == other); - } + DocId id{UndefinedIndexT}; + float score{0.0f}; + + DocMatch() = default; + DocMatch(DocId _id, float _score) + : id(_id), + score(_score) + {} + + /// Allows sorting DocMatches in best-to-worst order with std::sort. + bool operator<(const DocMatch& other) const { return score < other.score; } + + bool operator==(const DocMatch& other) const { return id == other.id && score == other.score; } + bool operator!=(const DocMatch& other) const { return !(*this == other); } }; typedef std::vector DocMatches; @@ -59,113 +49,115 @@ std::ostream& operator<<(std::ostream& os, const DocMatches& matches); */ class Database { -public: - /** - * @brief Constructor - * - * If computing weights for a new vocabulary, \c num_words should be the size of the vocabulary. - * If calling loadWeights(), it can be left zero. - */ - explicit Database(uint32_t num_words = 0); - - /** - * @brief Insert a new document. - * - * @param doc_id Unique ID of the new document to insert - * @param document The set of quantized words in a document/image. - * \return An ID representing the inserted document. - */ - DocId insert(DocId doc_id, const SparseHistogram& document); - - /** - * @brief Perform a sanity check of the database by querying each document - * of the database and finding its top N matches - * - * @param[in] N The number of matches to return. - * @param[out] matches IDs and scores for the top N matching database documents. - */ - void sanityCheck(std::size_t N, std::map& matches) const; - - /** - * @brief Find the top N matches in the database for the query document. - * - * @param[in] document The query document, a set of quantized words. - * @param[in] N The number of matches to return. - * @param[in] distanceMethod distance method (norm L1, etc.) - * @param[out] matches IDs and scores for the top N matching database documents. - */ - void find(const std::vector& document, std::size_t N, std::vector& matches, const std::string &distanceMethod = "strongCommonPoints") const; - + public: /** - * @brief Find the top N matches in the database for the query document. - * - * @param[in] query The query document, a normalized set of quantized words. - * @param[int] N The number of matches to return. - * @param[in] distanceMethod distance method (norm L1, etc.) - * @param[out] matches IDs and scores for the top N matching database documents. - */ - void find(const SparseHistogram& query, std::size_t N, std::vector& matches, const std::string &distanceMethod = "strongCommonPoints") const; - - /** - * @brief Compute the TF-IDF weights of all the words. To be called after inserting a corpus of - * training examples into the database. - * - * @param default_weight The default weight of a word that appears in none of the training documents. - */ - void computeTfIdfWeights(float default_weight = 1.0f); - - /** - * @brief Return the size of the database in terms of number of documents - * @return the number of documents - */ - std::size_t size() const; - - /// Save the vocabulary word weights to a file. - void saveWeights(const std::string& file) const; - /// Load the vocabulary word weights from a file. - void loadWeights(const std::string& file); - - // Save weights and documents - //void save(const std::string& file) const; - //void load(const std::string& file); - - const SparseHistogramPerImage& getSparseHistogramPerImage() const - { - return database_; - } - -private: - - struct WordFrequency - { - DocId id; - uint32_t count; - - WordFrequency() = default; - WordFrequency(DocId _id, uint32_t _count) - : id(_id) - , count(_count) - {} - }; + * @brief Constructor + * + * If computing weights for a new vocabulary, \c num_words should be the size of the vocabulary. + * If calling loadWeights(), it can be left zero. + */ + explicit Database(uint32_t num_words = 0); + + /** + * @brief Insert a new document. + * + * @param doc_id Unique ID of the new document to insert + * @param document The set of quantized words in a document/image. + * \return An ID representing the inserted document. + */ + DocId insert(DocId doc_id, const SparseHistogram& document); + + /** + * @brief Perform a sanity check of the database by querying each document + * of the database and finding its top N matches + * + * @param[in] N The number of matches to return. + * @param[out] matches IDs and scores for the top N matching database documents. + */ + void sanityCheck(std::size_t N, std::map& matches) const; + + /** + * @brief Find the top N matches in the database for the query document. + * + * @param[in] document The query document, a set of quantized words. + * @param[in] N The number of matches to return. + * @param[in] distanceMethod distance method (norm L1, etc.) + * @param[out] matches IDs and scores for the top N matching database documents. + */ + void find(const std::vector& document, + std::size_t N, + std::vector& matches, + const std::string& distanceMethod = "strongCommonPoints") const; + + /** + * @brief Find the top N matches in the database for the query document. + * + * @param[in] query The query document, a normalized set of quantized words. + * @param[int] N The number of matches to return. + * @param[in] distanceMethod distance method (norm L1, etc.) + * @param[out] matches IDs and scores for the top N matching database documents. + */ + void find(const SparseHistogram& query, + std::size_t N, + std::vector& matches, + const std::string& distanceMethod = "strongCommonPoints") const; + + /** + * @brief Compute the TF-IDF weights of all the words. To be called after inserting a corpus of + * training examples into the database. + * + * @param default_weight The default weight of a word that appears in none of the training documents. + */ + void computeTfIdfWeights(float default_weight = 1.0f); + + /** + * @brief Return the size of the database in terms of number of documents + * @return the number of documents + */ + std::size_t size() const; + + /// Save the vocabulary word weights to a file. + void saveWeights(const std::string& file) const; + /// Load the vocabulary word weights from a file. + void loadWeights(const std::string& file); - // Stored in increasing order by DocId - typedef std::vector InvertedFile; + // Save weights and documents + // void save(const std::string& file) const; + // void load(const std::string& file); - /// @todo Use sorted vector? - // typedef std::vector< std::pair > DocumentVector; - - friend std::ostream& operator<<(std::ostream& os, const SparseHistogram& dv); + const SparseHistogramPerImage& getSparseHistogramPerImage() const { return database_; } - std::vector word_files_; - std::vector word_weights_; - SparseHistogramPerImage database_; // Precomputed for inserted documents + private: + struct WordFrequency + { + DocId id; + uint32_t count; - /** - * Normalize a document vector representing the histogram of visual words for a given image - * @param[in/out] v the unnormalized histogram of visual words - */ - void normalize(SparseHistogram& v) const; + WordFrequency() = default; + WordFrequency(DocId _id, uint32_t _count) + : id(_id), + count(_count) + {} + }; + + // Stored in increasing order by DocId + typedef std::vector InvertedFile; + + /// @todo Use sorted vector? + // typedef std::vector< std::pair > DocumentVector; + + friend std::ostream& operator<<(std::ostream& os, const SparseHistogram& dv); + + std::vector word_files_; + std::vector word_weights_; + SparseHistogramPerImage database_; // Precomputed for inserted documents + + /** + * Normalize a document vector representing the histogram of visual words for a given image + * @param[in/out] v the unnormalized histogram of visual words + */ + void normalize(SparseHistogram& v) const; }; -}//namespace voctree -}//namespace aliceVision +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/DefaultAllocator.hpp b/src/aliceVision/voctree/DefaultAllocator.hpp index 2a6e2fb405..9430a40b5d 100644 --- a/src/aliceVision/voctree/DefaultAllocator.hpp +++ b/src/aliceVision/voctree/DefaultAllocator.hpp @@ -20,16 +20,16 @@ namespace voctree { template struct DefaultAllocator { - typedef std::allocator type; + typedef std::allocator type; }; // Specialization to use aligned allocator for Eigen::Matrix types. template -struct DefaultAllocator< Eigen::Matrix > +struct DefaultAllocator> { - typedef Eigen::aligned_allocator > type; + typedef Eigen::aligned_allocator> type; }; -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/MutableVocabularyTree.hpp b/src/aliceVision/voctree/MutableVocabularyTree.hpp index ca7af50368..448001d5cd 100644 --- a/src/aliceVision/voctree/MutableVocabularyTree.hpp +++ b/src/aliceVision/voctree/MutableVocabularyTree.hpp @@ -17,49 +17,31 @@ namespace voctree { * * When loading and using an existing vocabulary tree, use VocabularyTree instead. */ -template class Distance = L2, -class FeatureAllocator = typename DefaultAllocator::type> +template class Distance = L2, class FeatureAllocator = typename DefaultAllocator::type> class MutableVocabularyTree : public VocabularyTree { - typedef VocabularyTree BaseClass; + typedef VocabularyTree BaseClass; -public: - MutableVocabularyTree() - { - } + public: + MutableVocabularyTree() {} - void setSize(uint32_t levels, uint32_t splits) - { - this->levels_ = levels; - this->k_ = splits; - this->setNodeCounts(); - } + void setSize(uint32_t levels, uint32_t splits) + { + this->levels_ = levels; + this->k_ = splits; + this->setNodeCounts(); + } - uint32_t nodes() const - { - return this->word_start_ + this->num_words_; - } + uint32_t nodes() const { return this->word_start_ + this->num_words_; } - std::vector& centers() - { - return this->centers_; - } + std::vector& centers() { return this->centers_; } - const std::vector& centers() const - { - return this->centers_; - } + const std::vector& centers() const { return this->centers_; } - std::vector& validCenters() - { - return this->valid_centers_; - } + std::vector& validCenters() { return this->valid_centers_; } - const std::vector& validCenters() const - { - return this->valid_centers_; - } + const std::vector& validCenters() const { return this->valid_centers_; } }; -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/SimpleKmeans.hpp b/src/aliceVision/voctree/SimpleKmeans.hpp index 8d34049121..9a22970838 100644 --- a/src/aliceVision/voctree/SimpleKmeans.hpp +++ b/src/aliceVision/voctree/SimpleKmeans.hpp @@ -24,184 +24,193 @@ #include #include -namespace aliceVision{ -namespace voctree{ +namespace aliceVision { +namespace voctree { /** * @brief Initializer for K-means that randomly selects k features as the cluster centers. */ struct InitRandom { - - template - void operator()(const std::vector& features, size_t k, std::vector& centers, Distance distance, const int verbose = 0) - { - ALICEVISION_LOG_DEBUG("#\t\tRandom initialization"); - // Construct a random permutation of the features using a Fisher-Yates shuffle - std::vector features_perm = features; - for(size_t i = features.size(); i > 1; --i) + template + void operator()(const std::vector& features, + size_t k, + std::vector& centers, + Distance distance, + const int verbose = 0) { - size_t k = rand() % i; - std::swap(features_perm[i - 1], features_perm[k]); + ALICEVISION_LOG_DEBUG("#\t\tRandom initialization"); + // Construct a random permutation of the features using a Fisher-Yates shuffle + std::vector features_perm = features; + for (size_t i = features.size(); i > 1; --i) + { + size_t k = rand() % i; + std::swap(features_perm[i - 1], features_perm[k]); + } + // Take the first k permuted features as the initial centers + for (size_t i = 0; i < centers.size(); ++i) + centers[i] = *features_perm[i]; } - // Take the first k permuted features as the initial centers - for(size_t i = 0; i < centers.size(); ++i) - centers[i] = *features_perm[i]; - } }; /** * @brief Initializer for K-means using the K-means++ algorithm. - * - * Arthur, D. and Vassilvitskii, S. (2007). "k-means++: the advantages of - * careful seeding" Proceedings of the 18th annual ACM-SIAM symposium on - * Discrete algorithms. Society for Industrial and Applied Mathematics + * + * Arthur, D. and Vassilvitskii, S. (2007). "k-means++: the advantages of + * careful seeding" Proceedings of the 18th annual ACM-SIAM symposium on + * Discrete algorithms. Society for Industrial and Applied Mathematics * Philadelphia, PA, USA. pp. 1027–1035. */ struct InitKmeanspp { - - template - void operator()(const std::vector& features, size_t k, std::vector& centers, Distance distance, const int verbose = 0) - { - typedef typename Distance::result_type squared_distance_type; - - int numTrials = 5; - squared_distance_type currSum = 0; - - // Algorithm: - // 1. Choose one center uniformly at random from among the data points. - // 2. For each data point x, compute D(x), the distance between x and the nearest - // center that has already been chosen. - // 3. Add one new data point as a center. Each point x is chosen with probability - // proportional to D(x)^2. - // 4. Repeat Steps 2 and 3 until k centers have been chosen. - if(verbose > 0) ALICEVISION_LOG_DEBUG("Kmeanspp initialization"); - - centers.clear(); - centers.resize(k); - - auto threadCount = std::min(numTrials, omp_get_max_threads()); - - std::vector dists(features.size(), std::numeric_limits::max()); - std::vector distsTempBest(features.size(), std::numeric_limits::max()); - std::vector> threadDistsTemp(threadCount); - for (int i = 0; i < threadCount; ++i) + template + void operator()(const std::vector& features, + size_t k, + std::vector& centers, + Distance distance, + const int verbose = 0) { - // Data will be overwritten, can be initialized to anything. - threadDistsTemp[i].resize(features.size()); - } - - typename std::vector::iterator dstiter; - typename std::vector::const_iterator featiter; - - // 1. Choose a random center - size_t randCenter = rand() % features.size(); - - // add it to the centers - centers[0] = *features[ randCenter ]; - - if(verbose > 2) ALICEVISION_LOG_DEBUG("First center picked randomly " << randCenter << ": " << centers[0]); - - // compute the distances - for(dstiter = dists.begin(), featiter = features.begin(); dstiter != dists.end(); ++dstiter, ++featiter) - { - *dstiter = distance(*(*featiter), centers[0]); - currSum += *dstiter; - } + typedef typename Distance::result_type squared_distance_type; + + int numTrials = 5; + squared_distance_type currSum = 0; + + // Algorithm: + // 1. Choose one center uniformly at random from among the data points. + // 2. For each data point x, compute D(x), the distance between x and the nearest + // center that has already been chosen. + // 3. Add one new data point as a center. Each point x is chosen with probability + // proportional to D(x)^2. + // 4. Repeat Steps 2 and 3 until k centers have been chosen. + if (verbose > 0) + ALICEVISION_LOG_DEBUG("Kmeanspp initialization"); + + centers.clear(); + centers.resize(k); + + auto threadCount = std::min(numTrials, omp_get_max_threads()); + + std::vector dists(features.size(), std::numeric_limits::max()); + std::vector distsTempBest(features.size(), std::numeric_limits::max()); + std::vector> threadDistsTemp(threadCount); + for (int i = 0; i < threadCount; ++i) + { + // Data will be overwritten, can be initialized to anything. + threadDistsTemp[i].resize(features.size()); + } - std::mutex bestSumMutex; + typename std::vector::iterator dstiter; + typename std::vector::const_iterator featiter; - std::vector trialPercs(numTrials); + // 1. Choose a random center + size_t randCenter = rand() % features.size(); - // iterate k-1 times - for(int i = 1; i < k; ++i) - { - if(verbose > 1) ALICEVISION_LOG_DEBUG("Finding initial center " << i + 1); + // add it to the centers + centers[0] = *features[randCenter]; - squared_distance_type bestSum = std::numeric_limits::max(); - std::size_t bestCenter = -1; + if (verbose > 2) + ALICEVISION_LOG_DEBUG("First center picked randomly " << randCenter << ": " << centers[0]); - for (auto& perc : trialPercs) - { - perc = (float)std::rand() / RAND_MAX; - } - - //make it a little bit more robust and try several guesses - // choose the one with the global minimal distance -#pragma omp parallel for num_threads(threadCount) - for(int j = 0; j < numTrials; ++j) - { - // draw an element from 0 to currSum - // in order to choose a point with a probability proportional to D(x)^2 - // let's compute the overall sum of D(x)^2 and draw a number between - // 0 and this sum, then start compute the sum from the first element again - // until the partial sum is greater than the number drawn: the - // the previous element is what we are looking for - const float perc = trialPercs[j]; - squared_distance_type partial = (squared_distance_type)(currSum * perc); - // look for the element that cap the partial sum that has been - // drawn - dstiter = dists.begin(); - while((partial > 0) && (dstiter != dists.end())) + // compute the distances + for (dstiter = dists.begin(), featiter = features.begin(); dstiter != dists.end(); ++dstiter, ++featiter) { - assert(dstiter != dists.end()); - // safeguard against unsigned types that do not allow negative numbers - if(partial > *dstiter) - { - partial -= *dstiter; - } - else - { - partial = 0; - } - ++dstiter; + *dstiter = distance(*(*featiter), centers[0]); + currSum += *dstiter; } - // get the index - std::size_t featidx; - if(dstiter == dists.end()) - featidx = features.size() - 1; - else - featidx = dstiter - dists.begin(); + std::mutex bestSumMutex; - // 2. compute the distance of each feature from the current center - squared_distance_type distSum = 0; + std::vector trialPercs(numTrials); - auto& distsTemp = threadDistsTemp[omp_get_thread_num()]; - - Feature newCenter = *features[ featidx ]; - #pragma omp parallel for reduction(+:distSum) num_threads(omp_get_max_threads() / threadCount) - for(ptrdiff_t it = 0; it < static_cast(features.size()); ++it) + // iterate k-1 times + for (int i = 1; i < k; ++i) { - distsTemp[it] = std::min(distance(*(features[it]), newCenter), dists[it]); - distSum += distsTemp[it]; - } - if(verbose > 2) ALICEVISION_LOG_DEBUG("trial " << j << " found feat " << featidx << ": " << *features[ featidx ] << " with sum: " << distSum); + if (verbose > 1) + ALICEVISION_LOG_DEBUG("Finding initial center " << i + 1); - { - std::lock_guard lock(bestSumMutex); - if (distSum < bestSum) + squared_distance_type bestSum = std::numeric_limits::max(); + std::size_t bestCenter = -1; + + for (auto& perc : trialPercs) { - // save the best so far - bestSum = distSum; - bestCenter = featidx; - std::swap(distsTemp, distsTempBest); + perc = (float)std::rand() / RAND_MAX; } - } - } - if(verbose > 2) ALICEVISION_LOG_DEBUG("feature found feat " << bestCenter << ": " << *features[ bestCenter ]); - - // 3. add new data - centers[i] = *features[ bestCenter ]; - currSum = bestSum; - std::swap(dists, distsTempBest); + // make it a little bit more robust and try several guesses + // choose the one with the global minimal distance +#pragma omp parallel for num_threads(threadCount) + for (int j = 0; j < numTrials; ++j) + { + // draw an element from 0 to currSum + // in order to choose a point with a probability proportional to D(x)^2 + // let's compute the overall sum of D(x)^2 and draw a number between + // 0 and this sum, then start compute the sum from the first element again + // until the partial sum is greater than the number drawn: the + // the previous element is what we are looking for + const float perc = trialPercs[j]; + squared_distance_type partial = (squared_distance_type)(currSum * perc); + // look for the element that cap the partial sum that has been + // drawn + dstiter = dists.begin(); + while ((partial > 0) && (dstiter != dists.end())) + { + assert(dstiter != dists.end()); + // safeguard against unsigned types that do not allow negative numbers + if (partial > *dstiter) + { + partial -= *dstiter; + } + else + { + partial = 0; + } + ++dstiter; + } + + // get the index + std::size_t featidx; + if (dstiter == dists.end()) + featidx = features.size() - 1; + else + featidx = dstiter - dists.begin(); + + // 2. compute the distance of each feature from the current center + squared_distance_type distSum = 0; + + auto& distsTemp = threadDistsTemp[omp_get_thread_num()]; + + Feature newCenter = *features[featidx]; +#pragma omp parallel for reduction(+ : distSum) num_threads(omp_get_max_threads() / threadCount) + for (ptrdiff_t it = 0; it < static_cast(features.size()); ++it) + { + distsTemp[it] = std::min(distance(*(features[it]), newCenter), dists[it]); + distSum += distsTemp[it]; + } + if (verbose > 2) + ALICEVISION_LOG_DEBUG("trial " << j << " found feat " << featidx << ": " << *features[featidx] << " with sum: " << distSum); + + { + std::lock_guard lock(bestSumMutex); + if (distSum < bestSum) + { + // save the best so far + bestSum = distSum; + bestCenter = featidx; + std::swap(distsTemp, distsTempBest); + } + } + } + if (verbose > 2) + ALICEVISION_LOG_DEBUG("feature found feat " << bestCenter << ": " << *features[bestCenter]); + // 3. add new data + centers[i] = *features[bestCenter]; + currSum = bestSum; + std::swap(dists, distsTempBest); + } + if (verbose > 1) + ALICEVISION_LOG_DEBUG("Done!"); } - if(verbose > 1) ALICEVISION_LOG_DEBUG("Done!"); - - } }; /** @@ -209,73 +218,77 @@ struct InitKmeanspp */ struct InitGiven { - - template - void operator()(const std::vector& features, std::size_t k, std::vector& centers, Distance distance, const int verbose = 0) - { - // Do nothing! - } + template + void operator()(const std::vector& features, + std::size_t k, + std::vector& centers, + Distance distance, + const int verbose = 0) + { + // Do nothing! + } }; template -inline void printFeat(const Feature &f) +inline void printFeat(const Feature& f) { - ALICEVISION_LOG_DEBUG(f); + ALICEVISION_LOG_DEBUG(f); } template::type> -void printFeatVector(const std::vector &f) +void printFeatVector(const std::vector& f) { - for(std::size_t j = 0; j < f.size(); ++j) - { - printFeat(f[j]); - } + for (std::size_t j = 0; j < f.size(); ++j) + { + printFeat(f[j]); + } } /** * Check for "strange values" (nan or large values) in a feature, if some * of these values are found it prints them out and in the end it returns false - * + * * @param f the feature to check (supposed to have a size() method) * @param str somethig to write as debug * @return true if everything is ok */ template -bool checkElements(const Feature &f, const char* str) +bool checkElements(const Feature& f, const char* str) { - bool correct = true; - // here we are supposing that Feature has a size method... (bleah!) - for(std::size_t i = 0; i < f.size(); ++i) - { - if(f[i] > 10e6 || std::isnan(f[i])) + bool correct = true; + // here we are supposing that Feature has a size method... (bleah!) + for (std::size_t i = 0; i < f.size(); ++i) { - correct = false; - printf("%s\t%.3f %ld", str, (float) f[i], i); + if (f[i] > 10e6 || std::isnan(f[i])) + { + correct = false; + printf("%s\t%.3f %ld", str, (float)f[i], i); + } } - } - if(!correct) printf("\n"); - return correct; + if (!correct) + printf("\n"); + return correct; } /** * Check for "strange values" (nan or large values) in a set of feature, if some * of these values are found it prints them out and in the end it returns false - * + * * @param f the vector of feature to check * @param str omethig to write as debug * @return true if everything is ok * @see checkElements( const Feature &f, const char* str ) */ template::type> -bool checkVectorElements(const std::vector &f, const char* str) +bool checkVectorElements(const std::vector& f, const char* str) { - bool correct = true; - for(std::size_t i = 0; i < f.size(); ++i) - { - if(!checkElements(f[i], str)) - correct = false; - } - return correct; + bool correct = true; + for (std::size_t i = 0; i < f.size(); ++i) + { + if (!checkElements(f[i], str)) + correct = false; + } + return correct; } /** @@ -283,283 +296,272 @@ bool checkVectorElements(const std::vector &f, const * * The standard Lloyd's algorithm is used. By default, cluster centers are initialized randomly. */ -template, - class FeatureAllocator = typename DefaultAllocator::type> +template, class FeatureAllocator = typename DefaultAllocator::type> class SimpleKmeans { -public: - typedef typename Distance::result_type squared_distance_type; - typedef boost::function&, std::size_t, std::vector&, Distance, const int verbose) > Initializer; - - /** - * @brief Constructor - * - * @param zero Object representing zero in the feature space - * @param d Functor for calculating squared distance - * - * @todo FeatureAllocator parameter - */ - SimpleKmeans(const Feature& zero = Feature(), Distance d = Distance(), const int verbose = 0); - - /// Set function object used to choose initial cluster centers. - - void setInitMethod(const Initializer& init) - { - choose_centers_ = init; - } - - std::size_t getMaxIterations() const - { - return max_iterations_; - } - - void setMaxIterations(std::size_t iters) - { - max_iterations_ = iters; - } - - std::size_t getRestarts() const - { - return restarts_; - } - - void setRestarts(std::size_t restarts) - { - restarts_ = restarts; - } - - int getVerbose() const - { - return verbose_; - } - - void setVerbose(const int verboseLevel) - { - verbose_ = verboseLevel; - } - - /** - * @brief Partition a set of features into k clusters. - * - * @param features The features to be clustered. - * @param k The number of clusters. - * @param[out] centers A set of k cluster centers. - * @param[out] membership Cluster assignment for each feature - */ - squared_distance_type cluster(const std::vector& features, std::size_t k, - std::vector& centers, - std::vector& membership) const; - - /** - * @brief Partition a set of features into k clusters. - * - * This version is more convenient for hierarchical clustering, as you do not have to copy - * feature objects. - * - * @param features The features to be clustered. - * @param k The number of clusters. - * @param[out] centers A set of k cluster centers. - * @param[out] membership Cluster assignment for each feature - */ - squared_distance_type clusterPointers(const std::vector& features, std::size_t k, - std::vector& centers, - std::vector& membership) const; - -private: - - squared_distance_type clusterOnce(const std::vector& features, std::size_t k, - std::vector& centers, - std::vector& membership) const; - - Feature zero_; - Distance distance_; - Initializer choose_centers_; - std::size_t max_iterations_; - std::size_t restarts_; - int verbose_; + public: + typedef typename Distance::result_type squared_distance_type; + typedef boost::function&, std::size_t, std::vector&, Distance, const int verbose)> + Initializer; + + /** + * @brief Constructor + * + * @param zero Object representing zero in the feature space + * @param d Functor for calculating squared distance + * + * @todo FeatureAllocator parameter + */ + SimpleKmeans(const Feature& zero = Feature(), Distance d = Distance(), const int verbose = 0); + + /// Set function object used to choose initial cluster centers. + + void setInitMethod(const Initializer& init) { choose_centers_ = init; } + + std::size_t getMaxIterations() const { return max_iterations_; } + + void setMaxIterations(std::size_t iters) { max_iterations_ = iters; } + + std::size_t getRestarts() const { return restarts_; } + + void setRestarts(std::size_t restarts) { restarts_ = restarts; } + + int getVerbose() const { return verbose_; } + + void setVerbose(const int verboseLevel) { verbose_ = verboseLevel; } + + /** + * @brief Partition a set of features into k clusters. + * + * @param features The features to be clustered. + * @param k The number of clusters. + * @param[out] centers A set of k cluster centers. + * @param[out] membership Cluster assignment for each feature + */ + squared_distance_type cluster(const std::vector& features, + std::size_t k, + std::vector& centers, + std::vector& membership) const; + + /** + * @brief Partition a set of features into k clusters. + * + * This version is more convenient for hierarchical clustering, as you do not have to copy + * feature objects. + * + * @param features The features to be clustered. + * @param k The number of clusters. + * @param[out] centers A set of k cluster centers. + * @param[out] membership Cluster assignment for each feature + */ + squared_distance_type clusterPointers(const std::vector& features, + std::size_t k, + std::vector& centers, + std::vector& membership) const; + + private: + squared_distance_type clusterOnce(const std::vector& features, + std::size_t k, + std::vector& centers, + std::vector& membership) const; + + Feature zero_; + Distance distance_; + Initializer choose_centers_; + std::size_t max_iterations_; + std::size_t restarts_; + int verbose_; }; -template < class Feature, class Distance, class FeatureAllocator > +template SimpleKmeans::SimpleKmeans(const Feature& zero, Distance d, const int verbose) -: zero_(zero), -distance_(d), -// choose_centers_( InitRandom( ) ), -choose_centers_(InitKmeanspp()), -max_iterations_(100), -verbose_(verbose), -restarts_(1) + : zero_(zero), + distance_(d), + // choose_centers_( InitRandom( ) ), + choose_centers_(InitKmeanspp()), + max_iterations_(100), + verbose_(verbose), + restarts_(1) +{} + +template +typename SimpleKmeans::squared_distance_type SimpleKmeans::cluster( + const std::vector& features, + size_t k, + std::vector& centers, + std::vector& membership) const { + std::vector feature_ptrs; + feature_ptrs.reserve(features.size()); + BOOST_FOREACH (const Feature& f, features) + feature_ptrs.push_back(const_cast(&f)); + return clusterPointers(feature_ptrs, k, centers, membership); } -template < class Feature, class Distance, class FeatureAllocator > -typename SimpleKmeans::squared_distance_type -SimpleKmeans::cluster(const std::vector& features, size_t k, - std::vector& centers, - std::vector& membership) const +template +typename SimpleKmeans::squared_distance_type SimpleKmeans::clusterPointers( + const std::vector& features, + size_t k, + std::vector& centers, + std::vector& membership) const { - std::vector feature_ptrs; - feature_ptrs.reserve(features.size()); - BOOST_FOREACH(const Feature& f, features) - feature_ptrs.push_back(const_cast (&f)); - return clusterPointers(feature_ptrs, k, centers, membership); -} + std::vector new_centers(centers); + new_centers.resize(k); + std::vector new_membership(features.size()); -template < class Feature, class Distance, class FeatureAllocator > -typename SimpleKmeans::squared_distance_type -SimpleKmeans::clusterPointers(const std::vector& features, size_t k, - std::vector& centers, - std::vector& membership) const -{ - std::vector new_centers(centers); - new_centers.resize(k); - std::vector new_membership(features.size()); - - squared_distance_type least_sse = std::numeric_limits::max(); - assert(restarts_ > 0); - for(std::size_t starts = 0; starts < restarts_; ++starts) - { - if(verbose_ > 0) ALICEVISION_LOG_DEBUG("Trial " << starts + 1 << "/" << restarts_); - choose_centers_(features, k, new_centers, distance_, verbose_); - squared_distance_type sse = clusterOnce(features, k, new_centers, new_membership); - if(verbose_ > 0) ALICEVISION_LOG_DEBUG("End of Trial " << starts + 1 << "/" << restarts_); - if(sse < least_sse) + squared_distance_type least_sse = std::numeric_limits::max(); + assert(restarts_ > 0); + for (std::size_t starts = 0; starts < restarts_; ++starts) { - least_sse = sse; - centers = new_centers; - membership = new_membership; + if (verbose_ > 0) + ALICEVISION_LOG_DEBUG("Trial " << starts + 1 << "/" << restarts_); + choose_centers_(features, k, new_centers, distance_, verbose_); + squared_distance_type sse = clusterOnce(features, k, new_centers, new_membership); + if (verbose_ > 0) + ALICEVISION_LOG_DEBUG("End of Trial " << starts + 1 << "/" << restarts_); + if (sse < least_sse) + { + least_sse = sse; + centers = new_centers; + membership = new_membership; + } } - } - - assert(least_sse != std::numeric_limits::max()); - assert(membership.size() >= features.size()); - return least_sse; + + assert(least_sse != std::numeric_limits::max()); + assert(membership.size() >= features.size()); + return least_sse; } -template < class Feature, class Distance, class FeatureAllocator > -typename SimpleKmeans::squared_distance_type -SimpleKmeans::clusterOnce(const std::vector& features, std::size_t k, - std::vector& centers, - std::vector& membership) const +template +typename SimpleKmeans::squared_distance_type SimpleKmeans::clusterOnce( + const std::vector& features, + std::size_t k, + std::vector& centers, + std::vector& membership) const { - typedef typename std::vector::value_type centerType; - typedef typename Distance::value_type feature_value_type; - - std::vector new_center_counts(k); - std::vector new_centers(k); - std::vector centersLocks(k); - squared_distance_type max_center_shift = std::numeric_limits::max(); - - if(verbose_ > 0) ALICEVISION_LOG_DEBUG("Iterations"); - for(std::size_t iter = 0; iter < max_iterations_; ++iter) - { - if(verbose_ > 0) ALICEVISION_LOG_DEBUG("*"); - // Zero out new centers and counts - std::fill(new_center_counts.begin(), new_center_counts.end(), 0); - // for(std::size_t i = 0; i < k; checkElements(new_centers[i++], "bef")); - std::fill(new_centers.begin(), new_centers.end(), zero_); - // for(std::size_t i = 0; i < k; checkElements(new_centers[i++], "aft")); - assert(checkVectorElements(new_centers, "newcenters init")); - bool is_stable = true; - - // On small problems enabling multithreading does much more harm than good because thread - // creation is relatively expensive. - // TODO: Ideally a thread pool would be created before the first iteration and the iterations - // would reuse the existing threads. - bool enableMultithreading = features.size() * k > 1000000; - - // Assign data objects to current centers - #pragma omp parallel for shared( new_centers, new_center_counts, features, centers, membership) if(enableMultithreading) - for(ptrdiff_t i = 0; i < static_cast(features.size()); ++i) + typedef typename std::vector::value_type centerType; + typedef typename Distance::value_type feature_value_type; + + std::vector new_center_counts(k); + std::vector new_centers(k); + std::vector centersLocks(k); + squared_distance_type max_center_shift = std::numeric_limits::max(); + + if (verbose_ > 0) + ALICEVISION_LOG_DEBUG("Iterations"); + for (std::size_t iter = 0; iter < max_iterations_; ++iter) { - squared_distance_type d_min = std::numeric_limits::max(); - unsigned int nearest = 0; - bool found = false; - - // @todo if k is large, let's say k>100 use FLAAN to retrieve the - // cluster center - - // Find the nearest cluster center to feature i - for(unsigned int j = 0; j < k; ++j) - { - squared_distance_type distance = distance_(*features[i], centers[j]); - // printf("\t\tdistance %f\n", (float)distance); - // PrintFeat(*features[i] ); - if(distance < d_min) + if (verbose_ > 0) + ALICEVISION_LOG_DEBUG("*"); + // Zero out new centers and counts + std::fill(new_center_counts.begin(), new_center_counts.end(), 0); + // for(std::size_t i = 0; i < k; checkElements(new_centers[i++], "bef")); + std::fill(new_centers.begin(), new_centers.end(), zero_); + // for(std::size_t i = 0; i < k; checkElements(new_centers[i++], "aft")); + assert(checkVectorElements(new_centers, "newcenters init")); + bool is_stable = true; + + // On small problems enabling multithreading does much more harm than good because thread + // creation is relatively expensive. + // TODO: Ideally a thread pool would be created before the first iteration and the iterations + // would reuse the existing threads. + bool enableMultithreading = features.size() * k > 1000000; + +// Assign data objects to current centers +#pragma omp parallel for shared(new_centers, new_center_counts, features, centers, membership) if (enableMultithreading) + for (ptrdiff_t i = 0; i < static_cast(features.size()); ++i) { - d_min = distance; - nearest = j; - found = true; + squared_distance_type d_min = std::numeric_limits::max(); + unsigned int nearest = 0; + bool found = false; + + // @todo if k is large, let's say k>100 use FLAAN to retrieve the + // cluster center + + // Find the nearest cluster center to feature i + for (unsigned int j = 0; j < k; ++j) + { + squared_distance_type distance = distance_(*features[i], centers[j]); + // printf("\t\tdistance %f\n", (float)distance); + // PrintFeat(*features[i] ); + if (distance < d_min) + { + d_min = distance; + nearest = j; + found = true; + } + // printf("\t distance %f %f\n", (double)distance, (double)d_min); + } + assert(found); + // Assign feature i to the cluster it is nearest to + if (membership[i] != nearest) + { + is_stable = false; + membership[i] = nearest; + } + // Accumulate the cluster center and its membership count + // printf("\t nearest %d\n", nearest); + // checkElements(*features[i], "feat"); + { + std::lock_guard lock{centersLocks[nearest]}; + new_centers[nearest] += *features[i]; + // checkElements(new_centers[nearest], "sum"); + // printf("\t new_centers[nearest] += *features[i];\n"); + ++new_center_counts[nearest]; + // printf("\taccumulate\n"); + } + } // for + + if (is_stable) + break; + + if (iter > 0) + max_center_shift = 0; + // Assign new centers + for (std::size_t i = 0; i < k; ++i) + { + if (new_center_counts[i] > 0) + { + // printf("%d - %d\n", i, new_center_counts[i] ); + // PrintFeat(new_centers[i] ); + // checkElements(new_centers[i], "bef"); + new_centers[i] = new_centers[i] / new_center_counts[i]; + + squared_distance_type shift = distance_(new_centers[i], centers[i]); + + max_center_shift = std::max(max_center_shift, shift); + + centers[i] = new_centers[i]; + // centers[i] = new_centers[i] / new_center_counts[i]; + // checkElements(new_centers[i], "aft"); + // PrintFeat(centers[i] ); + } + else + { + // Choose a new center randomly from the input features + // @todo use a better strategy like taking splitting the largest cluster + unsigned int index = rand() % features.size(); + centers[i] = *features[index]; + ALICEVISION_LOG_DEBUG("Choosing a new center: " << index); + } } - // printf("\t distance %f %f\n", (double)distance, (double)d_min); - } - assert(found); - // Assign feature i to the cluster it is nearest to - if(membership[i] != nearest) - { - is_stable = false; - membership[i] = nearest; - } - // Accumulate the cluster center and its membership count - // printf("\t nearest %d\n", nearest); - // checkElements(*features[i], "feat"); - { - std::lock_guard lock{centersLocks[nearest]}; - new_centers[nearest] += *features[i]; - // checkElements(new_centers[nearest], "sum"); - // printf("\t new_centers[nearest] += *features[i];\n"); - ++new_center_counts[nearest]; - // printf("\taccumulate\n"); - } - }//for - - if(is_stable) break; - - if(iter > 0) - max_center_shift = 0; - // Assign new centers - for(std::size_t i = 0; i < k; ++i) + // ALICEVISION_LOG_DEBUG("max_center_shift: " << max_center_shift); + if (max_center_shift <= 10e-10) + break; + } + if (verbose_ > 0) + ALICEVISION_LOG_DEBUG(""); + + // Return the sum squared error + /// @todo Kahan summation? + squared_distance_type sse = squared_distance_type(0); + assert(features.size() > 0); + for (std::size_t i = 0; i < features.size(); ++i) { - if(new_center_counts[i] > 0) - { - // printf("%d - %d\n", i, new_center_counts[i] ); - // PrintFeat(new_centers[i] ); - // checkElements(new_centers[i], "bef"); - new_centers[i] = new_centers[i] / new_center_counts[i]; - - squared_distance_type shift = distance_(new_centers[i], centers[i]); - - max_center_shift = std::max(max_center_shift, shift); - - centers[i] = new_centers[i]; - // centers[i] = new_centers[i] / new_center_counts[i]; - // checkElements(new_centers[i], "aft"); - // PrintFeat(centers[i] ); - } - else - { - // Choose a new center randomly from the input features - // @todo use a better strategy like taking splitting the largest cluster - unsigned int index = rand() % features.size(); - centers[i] = *features[index]; - ALICEVISION_LOG_DEBUG("Choosing a new center: " << index); - } + sse += distance_(*features[i], centers[membership[i]]); } - // ALICEVISION_LOG_DEBUG("max_center_shift: " << max_center_shift); - if(max_center_shift <= 10e-10) break; - } - if(verbose_ > 0) ALICEVISION_LOG_DEBUG(""); - - // Return the sum squared error - /// @todo Kahan summation? - squared_distance_type sse = squared_distance_type(0); - assert(features.size() > 0); - for(std::size_t i = 0; i < features.size(); ++i) - { - sse += distance_(*features[i], centers[membership[i]]); - } - return sse; + return sse; } -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/TreeBuilder.hpp b/src/aliceVision/voctree/TreeBuilder.hpp index 7d711b4a4b..aedfadfa48 100644 --- a/src/aliceVision/voctree/TreeBuilder.hpp +++ b/src/aliceVision/voctree/TreeBuilder.hpp @@ -9,7 +9,7 @@ #include "MutableVocabularyTree.hpp" #include "SimpleKmeans.hpp" #include -//#include //DEBUG +// #include //DEBUG namespace aliceVision { namespace voctree { @@ -18,159 +18,149 @@ namespace voctree { * @brief Class for building a new vocabulary by hierarchically clustering * a set of training features. */ -template class DistanceT = L2, - class FeatureAllocator = typename DefaultAllocator::type> +template class DistanceT = L2, class FeatureAllocator = typename DefaultAllocator::type> class TreeBuilder { -public: - typedef MutableVocabularyTree Tree; - typedef DistanceT Distance; - typedef SimpleKmeans Kmeans; - typedef std::vector FeatureVector; - - /** - * @brief Constructor - * - * @param zero Object representing zero in the feature space - * @param d Functor for calculating squared distance - */ - TreeBuilder(const Feature& zero = Feature(), Distance d = Distance(), unsigned char verbose = 0); - - /** - * @brief Build a new vocabulary tree. - * - * The number of words in the resulting vocabulary is at most k ^ levels. - * - * @param training_features The set of training features to cluster. - * @param k The branching factor, or max children of any node. - * @param levels The number of levels in the tree. - */ - void build(const FeatureVector& training_features, uint32_t k, uint32_t levels); - - /// Get the built vocabulary tree. - - const Tree& tree() const - { - return tree_; - } - - /// Get the k-means clusterer. - - Kmeans& kmeans() - { - return kmeans_; - } - /// Get the k-means clusterer. - - const Kmeans& kmeans() const - { - return kmeans_; - } - - void setVerbose(unsigned char level) - { - verbose_ = level; - kmeans_.setVerbose(level); - } - - unsigned char getVerbose() const - { - return verbose_; - } - -protected: - Tree tree_; - Kmeans kmeans_; - Feature zero_; -private: - unsigned char verbose_; + public: + typedef MutableVocabularyTree Tree; + typedef DistanceT Distance; + typedef SimpleKmeans Kmeans; + typedef std::vector FeatureVector; + + /** + * @brief Constructor + * + * @param zero Object representing zero in the feature space + * @param d Functor for calculating squared distance + */ + TreeBuilder(const Feature& zero = Feature(), Distance d = Distance(), unsigned char verbose = 0); + + /** + * @brief Build a new vocabulary tree. + * + * The number of words in the resulting vocabulary is at most k ^ levels. + * + * @param training_features The set of training features to cluster. + * @param k The branching factor, or max children of any node. + * @param levels The number of levels in the tree. + */ + void build(const FeatureVector& training_features, uint32_t k, uint32_t levels); + + /// Get the built vocabulary tree. + + const Tree& tree() const { return tree_; } + + /// Get the k-means clusterer. + + Kmeans& kmeans() { return kmeans_; } + /// Get the k-means clusterer. + + const Kmeans& kmeans() const { return kmeans_; } + + void setVerbose(unsigned char level) + { + verbose_ = level; + kmeans_.setVerbose(level); + } + + unsigned char getVerbose() const { return verbose_; } + + protected: + Tree tree_; + Kmeans kmeans_; + Feature zero_; + + private: + unsigned char verbose_; }; template class DistanceT, class FeatureAllocator> TreeBuilder::TreeBuilder(const Feature& zero, Distance d, unsigned char verbose) -: kmeans_(zero, d, verbose), -zero_(zero), -verbose_(verbose) -{ -} + : kmeans_(zero, d, verbose), + zero_(zero), + verbose_(verbose) +{} template class DistanceT, class FeatureAllocator> -void TreeBuilder::build(const FeatureVector& training_features, - uint32_t k, uint32_t levels) +void TreeBuilder::build(const FeatureVector& training_features, uint32_t k, uint32_t levels) { - // Initial setup and memory allocation for the tree - tree_.clear(); - tree_.setSize(levels, k); - tree_.centers().reserve(tree_.nodes()); - tree_.validCenters().reserve(tree_.nodes()); - - // We keep a queue of disjoint feature subsets to cluster. - // Feature* is used to avoid copying features. - std::deque< std::vector > subset_queue(1); - - { - // At first the queue contains one "subset" containing all the features. - std::vector &feature_ptrs = subset_queue.front(); - feature_ptrs.reserve(training_features.size()); - for(const Feature& f: training_features) - { - feature_ptrs.push_back(const_cast (&f)); - } - } - FeatureVector centers; // always size k - for(uint32_t level = 0; level < levels; ++level) - { - if(verbose_) printf("# Level %u\n", level); - std::vector membership; - - for(std::size_t i = 0, ie = subset_queue.size(); i < ie; ++i) + // Initial setup and memory allocation for the tree + tree_.clear(); + tree_.setSize(levels, k); + tree_.centers().reserve(tree_.nodes()); + tree_.validCenters().reserve(tree_.nodes()); + + // We keep a queue of disjoint feature subsets to cluster. + // Feature* is used to avoid copying features. + std::deque> subset_queue(1); + { - std::vector &subset = subset_queue.front(); - if(verbose_ > 1) printf("#\tClustering subset %lu/%lu of size %lu\n", i + 1, ie, subset.size()); - - // If the subset already has k or fewer elements, just use those as the centers. - if(subset.size() <= k) - { - if(verbose_ > 2) printf("#\tno need to cluster %lu elements\n", subset.size()); - for(std::size_t j = 0; j < subset.size(); ++j) + // At first the queue contains one "subset" containing all the features. + std::vector& feature_ptrs = subset_queue.front(); + feature_ptrs.reserve(training_features.size()); + for (const Feature& f : training_features) { - tree_.centers().push_back(*subset[j]); - tree_.validCenters().push_back(1); + feature_ptrs.push_back(const_cast(&f)); } - // Mark non-existent centers as invalid. - tree_.centers().insert(tree_.centers().end(), k - subset.size(), zero_); - tree_.validCenters().insert(tree_.validCenters().end(), k - subset.size(), 0); - - // Push k empty subsets into the queue so all children get marked invalid. - subset_queue.pop_front(); - subset_queue.insert(subset_queue.end(), k, std::vector()); - } - else - { - // Cluster the current subset into k centers. - if(verbose_ > 2) printf("#\tclustering the current subset of %lu elements into %d centers\n", subset.size(), k); - kmeans_.clusterPointers(subset, k, centers, membership); - // Add the centers and mark them as valid. - tree_.centers().insert(tree_.centers().end(), centers.begin(), centers.end()); - tree_.validCenters().insert(tree_.validCenters().end(), k, 1); - // Partition the current subset into k new subsets based on the cluster assignments. - std::vector< std::vector > new_subsets(k); - assert(membership.size() >= subset.size()); - for(std::size_t j = 0; j < subset.size(); ++j) + } + FeatureVector centers; // always size k + for (uint32_t level = 0; level < levels; ++level) + { + if (verbose_) + printf("# Level %u\n", level); + std::vector membership; + + for (std::size_t i = 0, ie = subset_queue.size(); i < ie; ++i) { - assert(membership[j] < k); - assert(membership[j] < new_subsets.size()); - new_subsets[ membership[j] ].push_back(subset[j]); + std::vector& subset = subset_queue.front(); + if (verbose_ > 1) + printf("#\tClustering subset %lu/%lu of size %lu\n", i + 1, ie, subset.size()); + + // If the subset already has k or fewer elements, just use those as the centers. + if (subset.size() <= k) + { + if (verbose_ > 2) + printf("#\tno need to cluster %lu elements\n", subset.size()); + for (std::size_t j = 0; j < subset.size(); ++j) + { + tree_.centers().push_back(*subset[j]); + tree_.validCenters().push_back(1); + } + // Mark non-existent centers as invalid. + tree_.centers().insert(tree_.centers().end(), k - subset.size(), zero_); + tree_.validCenters().insert(tree_.validCenters().end(), k - subset.size(), 0); + + // Push k empty subsets into the queue so all children get marked invalid. + subset_queue.pop_front(); + subset_queue.insert(subset_queue.end(), k, std::vector()); + } + else + { + // Cluster the current subset into k centers. + if (verbose_ > 2) + printf("#\tclustering the current subset of %lu elements into %d centers\n", subset.size(), k); + kmeans_.clusterPointers(subset, k, centers, membership); + // Add the centers and mark them as valid. + tree_.centers().insert(tree_.centers().end(), centers.begin(), centers.end()); + tree_.validCenters().insert(tree_.validCenters().end(), k, 1); + // Partition the current subset into k new subsets based on the cluster assignments. + std::vector> new_subsets(k); + assert(membership.size() >= subset.size()); + for (std::size_t j = 0; j < subset.size(); ++j) + { + assert(membership[j] < k); + assert(membership[j] < new_subsets.size()); + new_subsets[membership[j]].push_back(subset[j]); + } + // Update the queue + subset_queue.pop_front(); + subset_queue.insert(subset_queue.end(), new_subsets.begin(), new_subsets.end()); + } } - // Update the queue - subset_queue.pop_front(); - subset_queue.insert(subset_queue.end(), new_subsets.begin(), new_subsets.end()); - } + if (verbose_) + printf("# centers so far = %lu\n", tree_.centers().size()); } - if(verbose_) printf("# centers so far = %lu\n", tree_.centers().size()); - } } -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/VocabularyTree.cpp b/src/aliceVision/voctree/VocabularyTree.cpp index b5f051a5a1..86b6ae538a 100644 --- a/src/aliceVision/voctree/VocabularyTree.cpp +++ b/src/aliceVision/voctree/VocabularyTree.cpp @@ -13,247 +13,246 @@ namespace voctree { std::ostream& operator<<(std::ostream& os, const Document& doc) { - os << "[ "; - for (const auto &w : doc) - { - os << w << ", "; - } - os << "];\n"; - return os; + os << "[ "; + for (const auto& w : doc) + { + os << w << ", "; + } + os << "];\n"; + return os; } -float sparseDistance(const SparseHistogram& v1, const SparseHistogram& v2, const std::string &distanceMethod, const std::vector& word_weights) +float sparseDistance(const SparseHistogram& v1, const SparseHistogram& v2, const std::string& distanceMethod, const std::vector& word_weights) { + float distance{0.0f}; + const float epsilon{0.001f}; - float distance{0.0f}; - const float epsilon{0.001f}; - - auto i1 = v1.cbegin(); - auto i1e = v1.cend(); - auto i2 = v2.cbegin(); - auto i2e = v2.cend(); - - if(distanceMethod == "classic") - { - while(i1 != i1e && i2 != i2e) - { - if(i2->first < i1->first) - { - distance += i2->second.size(); - ++i2; - } - else if(i1->first < i2->first) - { - distance += i1->second.size(); - ++i1; - } - else - { - const auto val = std::minmax(i1->second.size(), i2->second.size()); - distance += static_cast(val.second - val.first); - ++i1; - ++i2; - } - } + auto i1 = v1.cbegin(); + auto i1e = v1.cend(); + auto i2 = v2.cbegin(); + auto i2e = v2.cend(); - while(i1 != i1e) + if (distanceMethod == "classic") { - distance += i1->second.size(); - ++i1; - } + while (i1 != i1e && i2 != i2e) + { + if (i2->first < i1->first) + { + distance += i2->second.size(); + ++i2; + } + else if (i1->first < i2->first) + { + distance += i1->second.size(); + ++i1; + } + else + { + const auto val = std::minmax(i1->second.size(), i2->second.size()); + distance += static_cast(val.second - val.first); + ++i1; + ++i2; + } + } - while(i2 != i2e) - { - distance += i2->second.size(); - ++i2; - } - } - - else if(distanceMethod == "commonPoints") - { - float score{0.f}; - float N1{0.f}; - float N2{0.f}; - - while(i1 != i1e && i2 != i2e) - { - if(i2->first < i1->first) - { - N2 += i2->second.size(); - ++i2; - } - else if(i1->first < i2->first) - { - N1 += i1->second.size(); - ++i1; - } - else - { - score += std::min(i1->second.size(), i2->second.size()); - N1 += i1->second.size(); - N2 += i2->second.size(); - ++i1; - ++i2; - } - } + while (i1 != i1e) + { + distance += i1->second.size(); + ++i1; + } - while(i1 != i1e) - { - N1 += i1->second.size(); - ++i1; + while (i2 != i2e) + { + distance += i2->second.size(); + ++i2; + } } - while(i2 != i2e) + else if (distanceMethod == "commonPoints") { - N2 += i2->second.size(); - ++i2; - } - - distance = - score; - } - - else if(distanceMethod == "strongCommonPoints") - { - float score{0.f}; - float N1{0.f}; - float N2{0.f}; - - while(i1 != i1e && i2 != i2e) - { - if(i2->first < i1->first) - { - N2 += i2->second.size(); - ++i2; - } - else if(i1->first < i2->first) - { - N1 += i1->second.size(); - ++i1; - } - else - { - if( ( fabs(i1->second.size() - 1.f) < epsilon ) && ( fabs(i2->second.size() - 1.f) < epsilon) ) + float score{0.f}; + float N1{0.f}; + float N2{0.f}; + + while (i1 != i1e && i2 != i2e) { - score += 1; - N1 += 1; - N2 += 1; + if (i2->first < i1->first) + { + N2 += i2->second.size(); + ++i2; + } + else if (i1->first < i2->first) + { + N1 += i1->second.size(); + ++i1; + } + else + { + score += std::min(i1->second.size(), i2->second.size()); + N1 += i1->second.size(); + N2 += i2->second.size(); + ++i1; + ++i2; + } } - ++i1; - ++i2; - } - } - while(i1 != i1e) - { - N1 += i1->second.size(); - ++i1; - } + while (i1 != i1e) + { + N1 += i1->second.size(); + ++i1; + } - while(i2 != i2e) - { - N2 += i2->second.size(); - ++i2; - } - - distance = - score; - } - - else if(distanceMethod == "weightedStrongCommonPoints") - { - float score{0.f}; - float N1{0.f}; - float N2{0.f}; - - while(i1 != i1e && i2 != i2e) - { - if(i2->first < i1->first) - { - N2 += i2->second.size()*word_weights[i2->first]; - ++i2; - } - else if(i1->first < i2->first) - { - N1 += i1->second.size()*word_weights[i1->first]; - ++i1; - } - if( ( fabs(i1->second.size() - 1.f) < epsilon ) && ( fabs(i2->second.size() - 1.f) < epsilon) ) + while (i2 != i2e) { - score += word_weights[i1->first]; - N1 += word_weights[i1->first]; - N2 += word_weights[i2->first]; + N2 += i2->second.size(); + ++i2; } - ++i1; - ++i2; - } - while(i1 != i1e) - { - N1 += i1->second.size()*word_weights[i1->first]; - ++i1; + distance = -score; } - while(i2 != i2e) + else if (distanceMethod == "strongCommonPoints") { - N2 += i2->second.size()*word_weights[i2->first]; - ++i2; + float score{0.f}; + float N1{0.f}; + float N2{0.f}; + + while (i1 != i1e && i2 != i2e) + { + if (i2->first < i1->first) + { + N2 += i2->second.size(); + ++i2; + } + else if (i1->first < i2->first) + { + N1 += i1->second.size(); + ++i1; + } + else + { + if ((fabs(i1->second.size() - 1.f) < epsilon) && (fabs(i2->second.size() - 1.f) < epsilon)) + { + score += 1; + N1 += 1; + N2 += 1; + } + ++i1; + ++i2; + } + } + + while (i1 != i1e) + { + N1 += i1->second.size(); + ++i1; + } + + while (i2 != i2e) + { + N2 += i2->second.size(); + ++i2; + } + + distance = -score; } - - distance = - score; - } - - else if(distanceMethod == "inversedWeightedCommonPoints") - { - float score{0.f}; - float N1{0.f}; - float N2{0.f}; - std::map counter; - - while(i1 != i1e && i2 != i2e) + + else if (distanceMethod == "weightedStrongCommonPoints") { - if(i2->first < i1->first) - { - N2 += i2->second.size() / word_weights[i2->first]; - ++i2; - } - else if(i1->first < i2->first) - { - N1 += i1->second.size() / word_weights[i1->first]; - ++i1; - } - else - { - counter[i1->first] += std::min(i1->second.size(), i2->second.size()); - N1 += i1->second.size() / word_weights[i1->first]; - N2 += i2->second.size() / word_weights[i2->first]; - ++i1; - ++i2; - } + float score{0.f}; + float N1{0.f}; + float N2{0.f}; + + while (i1 != i1e && i2 != i2e) + { + if (i2->first < i1->first) + { + N2 += i2->second.size() * word_weights[i2->first]; + ++i2; + } + else if (i1->first < i2->first) + { + N1 += i1->second.size() * word_weights[i1->first]; + ++i1; + } + if ((fabs(i1->second.size() - 1.f) < epsilon) && (fabs(i2->second.size() - 1.f) < epsilon)) + { + score += word_weights[i1->first]; + N1 += word_weights[i1->first]; + N2 += word_weights[i2->first]; + } + ++i1; + ++i2; + } + + while (i1 != i1e) + { + N1 += i1->second.size() * word_weights[i1->first]; + ++i1; + } + + while (i2 != i2e) + { + N2 += i2->second.size() * word_weights[i2->first]; + ++i2; + } + + distance = -score; } - while(i1 != i1e) + else if (distanceMethod == "inversedWeightedCommonPoints") { - N1 += i1->second.size() / word_weights[i1->first]; - ++i1; - } + float score{0.f}; + float N1{0.f}; + float N2{0.f}; + std::map counter; + + while (i1 != i1e && i2 != i2e) + { + if (i2->first < i1->first) + { + N2 += i2->second.size() / word_weights[i2->first]; + ++i2; + } + else if (i1->first < i2->first) + { + N1 += i1->second.size() / word_weights[i1->first]; + ++i1; + } + else + { + counter[i1->first] += std::min(i1->second.size(), i2->second.size()); + N1 += i1->second.size() / word_weights[i1->first]; + N2 += i2->second.size() / word_weights[i2->first]; + ++i1; + ++i2; + } + } - while(i2 != i2e) + while (i1 != i1e) + { + N1 += i1->second.size() / word_weights[i1->first]; + ++i1; + } + + while (i2 != i2e) + { + N2 += i2->second.size() / word_weights[i2->first]; + ++i2; + } + + for (const auto elem : counter) + score += (1.f / elem.second) * word_weights[elem.first]; + + distance = -score; + } + else { - N2 += i2->second.size() / word_weights[i2->first]; - ++i2; + throw std::invalid_argument("distance method " + distanceMethod + " unknown!"); } - - for(const auto elem : counter) - score += (1.f/ elem.second) * word_weights[elem.first]; - - distance = - score; - } - else - { - throw std::invalid_argument("distance method "+ distanceMethod +" unknown!"); - } - - return distance; + + return distance; } -} //namespace voctree -} //namespace aliceVision +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/VocabularyTree.hpp b/src/aliceVision/voctree/VocabularyTree.hpp index 05f2fbe655..28300a6c3f 100644 --- a/src/aliceVision/voctree/VocabularyTree.hpp +++ b/src/aliceVision/voctree/VocabularyTree.hpp @@ -25,7 +25,6 @@ #include #include - namespace aliceVision { namespace voctree { @@ -34,58 +33,57 @@ typedef int32_t Word; typedef IndexT DocId; typedef std::vector Document; -typedef std::map > SparseHistogram; +typedef std::map> SparseHistogram; typedef std::map SparseHistogramPerImage; -std::ostream& operator<<(std::ostream& os, const Document &doc); +std::ostream& operator<<(std::ostream& os, const Document& doc); /** - * Given a list of visual words associated to the features of a document it computes the + * Given a list of visual words associated to the features of a document it computes the * vector of unique weighted visual words - * + * * @param[in] document a list of (possibly repeated) visual words * @param[out] v the vector of visual words (ie the visual word histogram of the image) */ inline void computeSparseHistogram(const std::vector& document, SparseHistogram& v) { - // for each visual word in the list - for(std::size_t i = 0; i < document.size(); ++i) - { - // update its weighted count inside the map - // the map v contains only the visual words that are associated to some features - // the visual words in v are unique unlikely the document - Word word = document[i]; - v[word].push_back(i); - } + // for each visual word in the list + for (std::size_t i = 0; i < document.size(); ++i) + { + // update its weighted count inside the map + // the map v contains only the visual words that are associated to some features + // the visual words in v are unique unlikely the document + Word word = document[i]; + v[word].push_back(i); + } } class IVocabularyTree { -public: - virtual ~IVocabularyTree() = 0; - - /// Save vocabulary to a file. - virtual void save(const std::string& file) const = 0; - /// Load vocabulary from a file. - virtual void load(const std::string& file) = 0; - - /** - * @brief Create a SparseHistogram from a blind vector of descriptors. - * @param blindDescriptors - * @return - */ - virtual SparseHistogram quantizeToSparse(const void* blindDescriptors) const = 0; - - /// Get the depth (number of levels) of the tree. - virtual uint32_t levels() const = 0; - /// Get the branching factor (max splits at each node) of the tree. - virtual uint32_t splits() const = 0; - /// Get the number of words the tree contains. - virtual uint32_t words() const = 0; - - /// Clears vocabulary, leaving an empty tree. - virtual void clear() = 0; - + public: + virtual ~IVocabularyTree() = 0; + + /// Save vocabulary to a file. + virtual void save(const std::string& file) const = 0; + /// Load vocabulary from a file. + virtual void load(const std::string& file) = 0; + + /** + * @brief Create a SparseHistogram from a blind vector of descriptors. + * @param blindDescriptors + * @return + */ + virtual SparseHistogram quantizeToSparse(const void* blindDescriptors) const = 0; + + /// Get the depth (number of levels) of the tree. + virtual uint32_t levels() const = 0; + /// Get the branching factor (max splits at each node) of the tree. + virtual uint32_t splits() const = 0; + /// Get the number of words the tree contains. + virtual uint32_t words() const = 0; + + /// Clears vocabulary, leaving an empty tree. + virtual void clear() = 0; }; inline IVocabularyTree::~IVocabularyTree() {} @@ -102,294 +100,311 @@ inline IVocabularyTree::~IVocabularyTree() {} * * \c FeatureAllocator is an STL-compatible allocator used to allocate Features internally. */ -template class Distance = L2, // TODO: rename Feature into Descriptor -class FeatureAllocator = typename DefaultAllocator::type> +template class Distance = L2, // TODO: rename Feature into Descriptor + class FeatureAllocator = typename DefaultAllocator::type> class VocabularyTree : public IVocabularyTree { -public: - VocabularyTree(); - - /** - * @brief Create from vocabulary file. - * @param file vocabulary file path - */ - VocabularyTree(const std::string& file); - - /// Quantizes a feature into a discrete word. - template - Word quantize(const DescriptorT& feature) const; - - /// Quantizes a set of features into visual words. - template - std::vector quantize(const std::vector& features) const; - - /// Quantizes a set of features into sparse histogram of visual words. - template - SparseHistogram quantizeToSparse(const std::vector& features) const; - - SparseHistogram quantizeToSparse(const void* blindDescriptors) const override - { - const std::vector* descriptors = static_cast*>(blindDescriptors); - return quantizeToSparse(*descriptors); - } - - /// Get the depth (number of levels) of the tree. - uint32_t levels() const override; - /// Get the branching factor (max splits at each node) of the tree. - uint32_t splits() const override; - /// Get the number of words the tree contains. - uint32_t words() const override; - - /// Clears vocabulary, leaving an empty tree. - void clear() override; - - /// Save vocabulary to a file. - void save(const std::string& file) const override; - /// Load vocabulary from a file. - void load(const std::string& file) override; - - bool operator==(const VocabularyTree& other) const - { - return (centers_ == other.centers_) && - (valid_centers_ == other.valid_centers_) && - (k_ == other.k_) && - (levels_ == other.levels_) && - (num_words_ == other.num_words_) && - (word_start_ == other.word_start_); - } - -protected: - std::vector centers_; - std::vector valid_centers_; /// @todo Consider bit-vector - - uint32_t k_; // splits, or branching factor - uint32_t levels_; - uint32_t num_words_; // number of leaf nodes - uint32_t word_start_; // number of non-leaf nodes, or offset to the first leaf node - - bool initialized() const - { - return num_words_ != 0; - } - - void setNodeCounts(); + public: + VocabularyTree(); + + /** + * @brief Create from vocabulary file. + * @param file vocabulary file path + */ + VocabularyTree(const std::string& file); + + /// Quantizes a feature into a discrete word. + template + Word quantize(const DescriptorT& feature) const; + + /// Quantizes a set of features into visual words. + template + std::vector quantize(const std::vector& features) const; + + /// Quantizes a set of features into sparse histogram of visual words. + template + SparseHistogram quantizeToSparse(const std::vector& features) const; + + SparseHistogram quantizeToSparse(const void* blindDescriptors) const override + { + const std::vector* descriptors = static_cast*>(blindDescriptors); + return quantizeToSparse(*descriptors); + } + + /// Get the depth (number of levels) of the tree. + uint32_t levels() const override; + /// Get the branching factor (max splits at each node) of the tree. + uint32_t splits() const override; + /// Get the number of words the tree contains. + uint32_t words() const override; + + /// Clears vocabulary, leaving an empty tree. + void clear() override; + + /// Save vocabulary to a file. + void save(const std::string& file) const override; + /// Load vocabulary from a file. + void load(const std::string& file) override; + + bool operator==(const VocabularyTree& other) const + { + return (centers_ == other.centers_) && (valid_centers_ == other.valid_centers_) && (k_ == other.k_) && (levels_ == other.levels_) && + (num_words_ == other.num_words_) && (word_start_ == other.word_start_); + } + + protected: + std::vector centers_; + std::vector valid_centers_; /// @todo Consider bit-vector + + uint32_t k_; // splits, or branching factor + uint32_t levels_; + uint32_t num_words_; // number of leaf nodes + uint32_t word_start_; // number of non-leaf nodes, or offset to the first leaf node + + bool initialized() const { return num_words_ != 0; } + + void setNodeCounts(); }; template class Distance, class FeatureAllocator> VocabularyTree::VocabularyTree() -: k_(0), levels_(0), num_words_(0), word_start_(0) -{ -} + : k_(0), + levels_(0), + num_words_(0), + word_start_(0) +{} template class Distance, class FeatureAllocator> VocabularyTree::VocabularyTree(const std::string& file) -: k_(0), levels_(0), num_words_(0), word_start_(0) + : k_(0), + levels_(0), + num_words_(0), + word_start_(0) { - load(file); + load(file); } template class Distance, class FeatureAllocator> template Word VocabularyTree::quantize(const DescriptorT& feature) const { - typedef typename Distance::result_type distance_type; - - // printf("asserting\n"); - assert(initialized()); - // printf("initialized\n"); - int32_t index = -1; // virtual "root" index, which has no associated center. - for(unsigned level = 0; level < levels_; ++level) - { - // Calculate the offset to the first child of the current index. - int32_t first_child = (index + 1) * splits(); - // Find the child center closest to the query. - int32_t best_child = first_child; - distance_type best_distance = std::numeric_limits::max(); - for(int32_t child = first_child; child < first_child + (int32_t) splits(); ++child) + typedef typename Distance::result_type distance_type; + + // printf("asserting\n"); + assert(initialized()); + // printf("initialized\n"); + int32_t index = -1; // virtual "root" index, which has no associated center. + for (unsigned level = 0; level < levels_; ++level) { - if(!valid_centers_[child]) - break; // Fewer than splits() children. - distance_type child_distance = Distance()(feature, centers_[child]); - if(child_distance < best_distance) - { - best_child = child; - best_distance = child_distance; - } + // Calculate the offset to the first child of the current index. + int32_t first_child = (index + 1) * splits(); + // Find the child center closest to the query. + int32_t best_child = first_child; + distance_type best_distance = std::numeric_limits::max(); + for (int32_t child = first_child; child < first_child + (int32_t)splits(); ++child) + { + if (!valid_centers_[child]) + break; // Fewer than splits() children. + distance_type child_distance = Distance()(feature, centers_[child]); + if (child_distance < best_distance) + { + best_child = child; + best_distance = child_distance; + } + } + index = best_child; } - index = best_child; - } - return index - word_start_; + return index - word_start_; } template class Distance, class FeatureAllocator> template std::vector VocabularyTree::quantize(const std::vector& features) const { - // ALICEVISION_LOG_DEBUG("VocabularyTree quantize: " << features.size()); - std::vector imgVisualWords(features.size(), 0); - - // quantize the features - #pragma omp parallel for - for(ptrdiff_t j = 0; j < static_cast(features.size()); ++j) - { - // store the visual word associated to the feature in the temporary list - imgVisualWords[j] = quantize(features[j]); - } - - // add the vector to the documents - return imgVisualWords; + // ALICEVISION_LOG_DEBUG("VocabularyTree quantize: " << features.size()); + std::vector imgVisualWords(features.size(), 0); + +// quantize the features +#pragma omp parallel for + for (ptrdiff_t j = 0; j < static_cast(features.size()); ++j) + { + // store the visual word associated to the feature in the temporary list + imgVisualWords[j] = quantize(features[j]); + } + + // add the vector to the documents + return imgVisualWords; } template class Distance, class FeatureAllocator> template SparseHistogram VocabularyTree::quantizeToSparse(const std::vector& features) const { - SparseHistogram histo; - std::vector doc = quantize(features); - computeSparseHistogram(doc, histo); - return histo; + SparseHistogram histo; + std::vector doc = quantize(features); + computeSparseHistogram(doc, histo); + return histo; } template class Distance, class FeatureAllocator> uint32_t VocabularyTree::levels() const { - return levels_; + return levels_; } template class Distance, class FeatureAllocator> uint32_t VocabularyTree::splits() const { - return k_; + return k_; } template class Distance, class FeatureAllocator> uint32_t VocabularyTree::words() const { - return num_words_; + return num_words_; } template class Distance, class FeatureAllocator> void VocabularyTree::clear() { - centers_.clear(); - valid_centers_.clear(); - k_ = levels_ = num_words_ = word_start_ = 0; + centers_.clear(); + valid_centers_.clear(); + k_ = levels_ = num_words_ = word_start_ = 0; } template class Distance, class FeatureAllocator> void VocabularyTree::save(const std::string& file) const { - /// @todo Support serializing of non-"simple" feature classes - /// @todo Some identifying name for the distance used - assert(initialized()); - - std::ofstream out(file, std::ios_base::binary); - out.write((char*) (&k_), sizeof (uint32_t)); - out.write((char*) (&levels_), sizeof (uint32_t)); - uint32_t size = centers_.size(); - out.write((char*) (&size), sizeof (uint32_t)); - out.write((char*) (¢ers_[0]), centers_.size() * sizeof (Feature)); - out.write((char*) (&valid_centers_[0]), valid_centers_.size()); + /// @todo Support serializing of non-"simple" feature classes + /// @todo Some identifying name for the distance used + assert(initialized()); + + std::ofstream out(file, std::ios_base::binary); + out.write((char*)(&k_), sizeof(uint32_t)); + out.write((char*)(&levels_), sizeof(uint32_t)); + uint32_t size = centers_.size(); + out.write((char*)(&size), sizeof(uint32_t)); + out.write((char*)(¢ers_[0]), centers_.size() * sizeof(Feature)); + out.write((char*)(&valid_centers_[0]), valid_centers_.size()); } template class Distance, class FeatureAllocator> void VocabularyTree::load(const std::string& file) { - clear(); - - std::ifstream in; - in.exceptions(std::ifstream::eofbit | std::ifstream::failbit | std::ifstream::badbit); - - uint32_t size; - try - { - in.open(file, std::ios_base::binary); - in.read((char*) (&k_), sizeof (uint32_t)); - in.read((char*) (&levels_), sizeof (uint32_t)); - in.read((char*) (&size), sizeof (uint32_t)); - centers_.resize(size); - valid_centers_.resize(size); - in.read((char*) (¢ers_[0]), centers_.size() * sizeof (Feature)); - in.read((char*) (&valid_centers_[0]), valid_centers_.size()); - } - catch(std::ifstream::failure& e) - { - throw std::runtime_error("Failed to load vocabulary tree file" + file); - } - - setNodeCounts(); - assert(size == num_words_ + word_start_); + clear(); + + std::ifstream in; + in.exceptions(std::ifstream::eofbit | std::ifstream::failbit | std::ifstream::badbit); + + uint32_t size; + try + { + in.open(file, std::ios_base::binary); + in.read((char*)(&k_), sizeof(uint32_t)); + in.read((char*)(&levels_), sizeof(uint32_t)); + in.read((char*)(&size), sizeof(uint32_t)); + centers_.resize(size); + valid_centers_.resize(size); + in.read((char*)(¢ers_[0]), centers_.size() * sizeof(Feature)); + in.read((char*)(&valid_centers_[0]), valid_centers_.size()); + } + catch (std::ifstream::failure& e) + { + throw std::runtime_error("Failed to load vocabulary tree file" + file); + } + + setNodeCounts(); + assert(size == num_words_ + word_start_); } template class Distance, class FeatureAllocator> void VocabularyTree::setNodeCounts() { - num_words_ = k_; - word_start_ = 0; - for(uint32_t i = 0; i < levels_ - 1; ++i) - { - word_start_ += num_words_; - num_words_ *= k_; - } + num_words_ = k_; + word_start_ = 0; + for (uint32_t i = 0; i < levels_ - 1; ++i) + { + word_start_ += num_words_; + num_words_ *= k_; + } } /** * @brief compute the sparse distance between two histograms according to the chosen distance method. - * + * * @param v1 The first sparse histogram * @param v2 The second sparse histogram * @param distanceMethod distance method (norm L1, etc.) - * @param word_weights + * @param word_weights * @return the distance of the two histograms */ -float sparseDistance(const SparseHistogram& v1, const SparseHistogram& v2, const std::string &distanceMethod = "classic", const std::vector& word_weights = std::vector()); +float sparseDistance(const SparseHistogram& v1, + const SparseHistogram& v2, + const std::string& distanceMethod = "classic", + const std::vector& word_weights = std::vector()); inline std::unique_ptr createVoctreeForDescriberType(feature::EImageDescriberType imageDescriberType) { - using namespace aliceVision::feature; - std::unique_ptr res; + using namespace aliceVision::feature; + std::unique_ptr res; - switch(imageDescriberType) - { - case EImageDescriberType::SIFT: res.reset(new VocabularyTree); break; - case EImageDescriberType::SIFT_FLOAT: res.reset(new VocabularyTree); break; - case EImageDescriberType::AKAZE: res.reset(new VocabularyTree); break; - case EImageDescriberType::AKAZE_MLDB: res.reset(new VocabularyTree); break; + switch (imageDescriberType) + { + case EImageDescriberType::SIFT: + res.reset(new VocabularyTree); + break; + case EImageDescriberType::SIFT_FLOAT: + res.reset(new VocabularyTree); + break; + case EImageDescriberType::AKAZE: + res.reset(new VocabularyTree); + break; + case EImageDescriberType::AKAZE_MLDB: + res.reset(new VocabularyTree); + break; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) - case EImageDescriberType::CCTAG3: - case EImageDescriberType::CCTAG4: res.reset(new VocabularyTree); break; -#endif //ALICEVISION_HAVE_CCTAG + case EImageDescriberType::CCTAG3: + case EImageDescriberType::CCTAG4: + res.reset(new VocabularyTree); + break; +#endif // ALICEVISION_HAVE_CCTAG #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) -#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) - case EImageDescriberType::SIFT_OCV: res.reset(new VocabularyTree); break; -#endif //ALICEVISION_HAVE_OCVSIFT - case EImageDescriberType::AKAZE_OCV: res.reset(new VocabularyTree); break; -#endif //ALICEVISION_HAVE_OPENCV - - default: throw std::out_of_range("Invalid imageDescriber enum"); - } + #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OCVSIFT) + case EImageDescriberType::SIFT_OCV: + res.reset(new VocabularyTree); + break; + #endif // ALICEVISION_HAVE_OCVSIFT + case EImageDescriberType::AKAZE_OCV: + res.reset(new VocabularyTree); + break; +#endif // ALICEVISION_HAVE_OPENCV + + default: + throw std::out_of_range("Invalid imageDescriber enum"); + } - return res; + return res; } inline void load(std::unique_ptr& out_voctree, feature::EImageDescriberType& out_descType, const std::string& filepath) { - std::size_t lastDot = filepath.find_last_of("."); - if(lastDot == std::string::npos) - throw std::invalid_argument("Unrecognized Vocabulary tree filename (no extension): " + filepath); - std::size_t secondLastDot = filepath.find_last_of(".", lastDot-1); - if(secondLastDot == std::string::npos) - throw std::invalid_argument("Unrecognized Vocabulary tree filename (no descType in extension): " + filepath); - - const std::string descTypeStr = filepath.substr((secondLastDot+1), lastDot - (secondLastDot+1)); - - out_descType = feature::EImageDescriberType_stringToEnum(descTypeStr); - out_voctree = createVoctreeForDescriberType(out_descType); - out_voctree->load(filepath); + std::size_t lastDot = filepath.find_last_of("."); + if (lastDot == std::string::npos) + throw std::invalid_argument("Unrecognized Vocabulary tree filename (no extension): " + filepath); + std::size_t secondLastDot = filepath.find_last_of(".", lastDot - 1); + if (secondLastDot == std::string::npos) + throw std::invalid_argument("Unrecognized Vocabulary tree filename (no descType in extension): " + filepath); + + const std::string descTypeStr = filepath.substr((secondLastDot + 1), lastDot - (secondLastDot + 1)); + + out_descType = feature::EImageDescriberType_stringToEnum(descTypeStr); + out_voctree = createVoctreeForDescriberType(out_descType); + out_voctree->load(filepath); } -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/databaseIO.hpp b/src/aliceVision/voctree/databaseIO.hpp index 3b929617e3..6c94e65b5a 100644 --- a/src/aliceVision/voctree/databaseIO.hpp +++ b/src/aliceVision/voctree/databaseIO.hpp @@ -42,7 +42,7 @@ std::size_t populateDatabase(const sfmData::SfMData& sfmData, * @brief Given an non empty database, it queries the database with a set of images * and their associated features and returns, for each image, the first \p numResults best * matching documents in the database - * + * * @param[in] filepath A file containithe path the features to load, it could be a .txt or an AliceVision .json * @param[in] featuresFolders The folder(s) containing the descriptor files (optional) * @param[in] tree The vocabulary tree to be usedng for feature quantization @@ -50,7 +50,7 @@ std::size_t populateDatabase(const sfmData::SfMData& sfmData, * @param[in] numResults The number of results to retrieve for each image * @param[out] allMatches The matches for all the images * @param[in] distanceMethod The distance method used to create the pair list - * @param[in] Nmax The maximum number of features loaded in each desc file. For Nmax = 0 (default), all the descriptors are loaded. + * @param[in] Nmax The maximum number of features loaded in each desc file. For Nmax = 0 (default), all the descriptors are loaded. * @see queryDatabase() */ template @@ -67,14 +67,14 @@ void queryDatabase(const sfmData::SfMData& sfmData, * @brief Given an non empty database, it queries the database with a set of images * and their associated features and returns, for each image, the first \p numResults best * matching documents in the database - * + * * @param[in] filepath A file containing the path the features to load, it could be a .txt or an AliceVision .json * @param[in] featuresFolders The folder(s) containing the descriptor files (optional) * @param[in] tree The vocabulary tree to be used for feature quantization * @param[in] db The built database * @param[in] numResults The number of results to retrieve for each image * @param[out] allMatches The matches for all the images - * @param[out] documents For each document, it contains the list of associated visual words + * @param[out] documents For each document, it contains the list of associated visual words * @param[in] distanceMethod The distance method used to create the pair list * @param[in] Nmax The maximum number of features loaded in each desc file. For Nmax = 0 (default), all the descriptors are loaded. */ @@ -90,14 +90,14 @@ void queryDatabase(const sfmData::SfMData& sfmData, const int Nmax = 0); /** - * @brief Returns some statistics (histogram) - * + * @brief Returns some statistics (histogram) + * * @param[in] fileFullPath A file containing the path the features to load, it could be a .txt or an AliceVision .json * @param[in] featuresFolders The folder(s) containing the descriptor files (optional) * @param[in] tree The vocabulary tree to be used for feature quantization * @param[in] db The built database * @param[in] distanceMethod The distance method used for create the pair list - * @param[in/out] globalHistogram The histogram of the "population" of voctree leaves. + * @param[in/out] globalHistogram The histogram of the "population" of voctree leaves. * @see queryDatabase() */ template @@ -108,7 +108,7 @@ void voctreeStatistics(const sfmData::SfMData& sfmData, const std::string& distanceMethod, std::map& globalHistogram); -} //namespace voctree -} //namespace aliceVision +} // namespace voctree +} // namespace aliceVision #include "databaseIO.tcc" diff --git a/src/aliceVision/voctree/descriptorLoader.cpp b/src/aliceVision/voctree/descriptorLoader.cpp index 29fd08088c..22d2b72c07 100644 --- a/src/aliceVision/voctree/descriptorLoader.cpp +++ b/src/aliceVision/voctree/descriptorLoader.cpp @@ -13,103 +13,107 @@ namespace aliceVision { namespace voctree { -void getInfoBinFile(const std::string &path, int dim, std::size_t &numDescriptors, int &bytesPerElement) +void getInfoBinFile(const std::string& path, int dim, std::size_t& numDescriptors, int& bytesPerElement) { - std::fstream fs; - - // the file is supposed to have the number of descriptors as first element and then - // the set of descriptors of dimension dim either as chars or floats - - // Open file and get the number of descriptors - fs.open(path, std::ios::in | std::ios::binary); - - if(!fs.is_open()) - { - ALICEVISION_CERR("Error while opening " << path); - ALICEVISION_CERR("Error while opening " + path); - } - - // go to the end of the file - fs.seekg(0, fs.end); - - // get the length in byte of the file - //@fixeme we are ignoring the first element of the file which is the number of - // feature. However given the large amount of data of the feature this is mitigate - // by the integer division in bytepd later - int length = fs.tellg(); - - // go back to the beginning of the file - fs.seekg(0, fs.beg); - - // get the number of descriptors - fs.read((char*) &numDescriptors, sizeof (std::size_t)); - - if(numDescriptors > 0) - { - // get the number of bytes per descriptor element - bytesPerElement = (length / numDescriptors) / dim; - } - else - { - bytesPerElement = 0; - } + std::fstream fs; + + // the file is supposed to have the number of descriptors as first element and then + // the set of descriptors of dimension dim either as chars or floats + + // Open file and get the number of descriptors + fs.open(path, std::ios::in | std::ios::binary); + + if (!fs.is_open()) + { + ALICEVISION_CERR("Error while opening " << path); + ALICEVISION_CERR("Error while opening " + path); + } + + // go to the end of the file + fs.seekg(0, fs.end); + + // get the length in byte of the file + //@fixeme we are ignoring the first element of the file which is the number of + // feature. However given the large amount of data of the feature this is mitigate + // by the integer division in bytepd later + int length = fs.tellg(); + + // go back to the beginning of the file + fs.seekg(0, fs.beg); + + // get the number of descriptors + fs.read((char*)&numDescriptors, sizeof(std::size_t)); + + if (numDescriptors > 0) + { + // get the number of bytes per descriptor element + bytesPerElement = (length / numDescriptors) / dim; + } + else + { + bytesPerElement = 0; + } } -void getListOfDescriptorFiles(const sfmData::SfMData& sfmData, const std::vector& featuresFolders, std::map& descriptorsFiles) +void getListOfDescriptorFiles(const sfmData::SfMData& sfmData, + const std::vector& featuresFolders, + std::map& descriptorsFiles) { - namespace bfs = boost::filesystem; - - descriptorsFiles.clear(); - - if(sfmData.getViews().empty()) - throw std::runtime_error("Can't get list of descriptor files, no views found"); - - std::vector descTypes = {feature::EImageDescriberType::SIFT, - feature::EImageDescriberType::DSPSIFT, - feature::EImageDescriberType::SIFT_FLOAT, - feature::EImageDescriberType::SIFT_UPRIGHT}; - std::vector allFeaturesFolders(featuresFolders); - // add features folders from SfMData in search - for(const auto& folder: sfmData.getFeaturesFolders()) - { - if(std::find(allFeaturesFolders.begin(), allFeaturesFolders.end(), folder) == allFeaturesFolders.end()) - allFeaturesFolders.push_back(folder); - } - - // explore the sfm_data container to get the files path - for(const auto& view : sfmData.getViews()) - { - bool found = false; - - for(const std::string& featureFolder : allFeaturesFolders) + namespace bfs = boost::filesystem; + + descriptorsFiles.clear(); + + if (sfmData.getViews().empty()) + throw std::runtime_error("Can't get list of descriptor files, no views found"); + + std::vector descTypes = {feature::EImageDescriberType::SIFT, + feature::EImageDescriberType::DSPSIFT, + feature::EImageDescriberType::SIFT_FLOAT, + feature::EImageDescriberType::SIFT_UPRIGHT}; + std::vector allFeaturesFolders(featuresFolders); + // add features folders from SfMData in search + for (const auto& folder : sfmData.getFeaturesFolders()) + { + if (std::find(allFeaturesFolders.begin(), allFeaturesFolders.end(), folder) == allFeaturesFolders.end()) + allFeaturesFolders.push_back(folder); + } + + // explore the sfm_data container to get the files path + for (const auto& view : sfmData.getViews()) { - for(const feature::EImageDescriberType descType: descTypes) - { - // generate the equivalent .desc file path - const std::string filepath = bfs::path(bfs::path(featureFolder) / (std::to_string(view.first) + "." + feature::EImageDescriberType_enumToString(descType) + ".desc")).string(); + bool found = false; - if(bfs::exists(filepath)) + for (const std::string& featureFolder : allFeaturesFolders) { - descriptorsFiles[view.first] = filepath; - found = true; - break; + for (const feature::EImageDescriberType descType : descTypes) + { + // generate the equivalent .desc file path + const std::string filepath = bfs::path(bfs::path(featureFolder) / (std::to_string(view.first) + "." + + feature::EImageDescriberType_enumToString(descType) + ".desc")) + .string(); + + if (bfs::exists(filepath)) + { + descriptorsFiles[view.first] = filepath; + found = true; + break; + } + } + if (found) + break; } - } - if(found) - break; - } - if(!found) - { - std::stringstream ss; + if (!found) + { + std::stringstream ss; - for(const std::string& featureFolder : allFeaturesFolders) - ss << "\t- " << featureFolder << std::endl; + for (const std::string& featureFolder : allFeaturesFolders) + ss << "\t- " << featureFolder << std::endl; - throw std::runtime_error("Can't find descriptor of view " + std::to_string(view.first) + " in:\n" + ss.str()); + throw std::runtime_error("Can't find descriptor of view " + std::to_string(view.first) + " in:\n" + ss.str()); + } } - } } -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/descriptorLoader.hpp b/src/aliceVision/voctree/descriptorLoader.hpp index 7cf7a2d83c..425aa3de54 100644 --- a/src/aliceVision/voctree/descriptorLoader.hpp +++ b/src/aliceVision/voctree/descriptorLoader.hpp @@ -31,7 +31,7 @@ void getInfoBinFile(const std::string& path, int dim, std::size_t& numDescriptor * @brief Extract a list of decriptor files from a sfmData. * @param[in] sfmDataPath The input sfmData * @param[in] featuresFolders The folder(s) containing the descriptor files - * @param[out] descriptorsFiles A list of descriptor files + * @param[out] descriptorsFiles A list of descriptor files */ void getListOfDescriptorFiles(const sfmData::SfMData& sfmData, const std::vector& featuresFolders, @@ -48,11 +48,11 @@ void getListOfDescriptorFiles(const sfmData::SfMData& sfmData, */ template std::size_t readDescFromFiles(const sfmData::SfMData& sfmData, - const std::vector& featuresFolders, - std::vector& descriptors, - std::vector& numFeatures); + const std::vector& featuresFolders, + std::vector& descriptors, + std::vector& numFeatures); -} // namespace voctree -} // namespace aliceVision +} // namespace voctree +} // namespace aliceVision #include "descriptorLoader.tcc" diff --git a/src/aliceVision/voctree/distance.hpp b/src/aliceVision/voctree/distance.hpp index 5ffe5e2e39..c69a05da24 100644 --- a/src/aliceVision/voctree/distance.hpp +++ b/src/aliceVision/voctree/distance.hpp @@ -12,49 +12,44 @@ namespace aliceVision { namespace voctree { - /** * \brief Default implementation of L2 distance metric. * * Works with std::vector, boost::array, or more generally any container that has * a \c value_type typedef, \c size() and array-indexed element access. */ -template +template struct L2 { - typedef typename DescriptorA::value_type value_type; - typedef double result_type; + typedef typename DescriptorA::value_type value_type; + typedef double result_type; - result_type operator()(const DescriptorA& a, const DescriptorB& b) const - { - result_type result = result_type(0); - for(std::size_t i = 0; i < a.size(); ++i) + result_type operator()(const DescriptorA& a, const DescriptorB& b) const { - result_type diff = (result_type)a[i] - (result_type)b[i]; - result += diff*diff; + result_type result = result_type(0); + for (std::size_t i = 0; i < a.size(); ++i) + { + result_type diff = (result_type)a[i] - (result_type)b[i]; + result += diff * diff; + } + return result; } - return result; - } }; - /// @todo Version for raw data pointers that knows the size of the feature /// @todo Specialization for cv::Vec. Doesn't have size() so default won't work. /// Specialization for Eigen::Matrix types. template -struct L2< Eigen::Matrix, Eigen::Matrix > +struct L2, Eigen::Matrix> { - typedef Eigen::Matrix feature_type; - typedef Scalar value_type; - typedef double result_type; - - result_type operator()(const feature_type& a, const feature_type& b) const - { - return (a - b).squaredNorm(); - } + typedef Eigen::Matrix feature_type; + typedef Scalar value_type; + typedef double result_type; + + result_type operator()(const feature_type& a, const feature_type& b) const { return (a - b).squaredNorm(); } }; -} -} +} // namespace voctree +} // namespace aliceVision diff --git a/src/aliceVision/voctree/kmeans_test.cpp b/src/aliceVision/voctree/kmeans_test.cpp index b3afedc019..691205cd11 100644 --- a/src/aliceVision/voctree/kmeans_test.cpp +++ b/src/aliceVision/voctree/kmeans_test.cpp @@ -20,282 +20,289 @@ BOOST_AUTO_TEST_CASE(kmeanInitializer) { - using namespace aliceVision; - - ALICEVISION_LOG_DEBUG("Testing kmeanspp Initializer..."); - - makeRandomOperationsReproducible(); - - const std::size_t DIMENSION = 128; - const std::size_t FEATURENUMBER = 500; - - const std::size_t K = 10; - - typedef Eigen::Matrix FeatureFloat; - typedef std::vector > FeatureFloatVector; - typedef std::vector FeaturePointerVector; - - FeatureFloatVector features; - FeaturePointerVector featPtr; - FeatureFloatVector centers; - features.reserve(FEATURENUMBER); - featPtr.reserve(features.size()); - - for(std::size_t i = 0; i < FEATURENUMBER; ++i) - { - features.push_back(FeatureFloat::Random(1, DIMENSION)); - featPtr.push_back(const_cast (&features.back())); - } - - voctree::InitKmeanspp initializer; - - initializer(featPtr, K, centers, voctree::L2()); - - // it's difficult to check the result as it is random, just check there are no weird things - BOOST_CHECK(voctree::checkVectorElements(centers, "initializer1")); - - // now try to generate k cluster well far away and compare - features.clear(); - featPtr.clear(); - features.reserve(FEATURENUMBER * K); - featPtr.reserve(features.size()); - for(std::size_t i = 0; i < K; ++i) - { - // at each i iteration translate the cluster by 5*i - for(std::size_t j = 0; j < FEATURENUMBER; ++j) - { - features.push_back(FeatureFloat::Random(1, DIMENSION) + Eigen::MatrixXf::Constant(1, DIMENSION, 5 * i)); - featPtr.push_back(const_cast (&features.back())); - } - } - - initializer(featPtr, K, centers, voctree::L2()); - - // it's difficult to check the result as it is random, just check there are no weird things - BOOST_CHECK(voctree::checkVectorElements(centers, "initializer2")); - -} - - -BOOST_AUTO_TEST_CASE(kmeanInitializerVarying) -{ - using namespace aliceVision; - - ALICEVISION_LOG_DEBUG("Testing kmeanspp Initializer with variable k and DIM..."); + using namespace aliceVision; - makeRandomOperationsReproducible(); + ALICEVISION_LOG_DEBUG("Testing kmeanspp Initializer..."); - const int FEATURENUMBER = 500; - const std::size_t numTrial = 3; + makeRandomOperationsReproducible(); - // generate random values for K and DIMENSION - std::default_random_engine generator; - std::uniform_int_distribution dimGen(3, 128); - std::uniform_int_distribution kGen(6, 300); + const std::size_t DIMENSION = 128; + const std::size_t FEATURENUMBER = 500; - for(size_t trial = 0; trial < numTrial; ++trial) - { - const std::size_t DIMENSION = dimGen(generator); - const std::size_t K = kGen(generator); - const std::size_t STEP = 5 * K; - ALICEVISION_LOG_DEBUG("\tTrial " << trial + 1 << "/" << numTrial << " with K = " << K << " and DIMENSION = " << DIMENSION); + const std::size_t K = 10; - typedef Eigen::RowVectorXf FeatureFloat; - typedef std::vector > FeatureFloatVector; - typedef std::vector FeaturePointerVector; + typedef Eigen::Matrix FeatureFloat; + typedef std::vector> FeatureFloatVector; + typedef std::vector FeaturePointerVector; FeatureFloatVector features; FeaturePointerVector featPtr; FeatureFloatVector centers; + features.reserve(FEATURENUMBER); + featPtr.reserve(features.size()); + + for (std::size_t i = 0; i < FEATURENUMBER; ++i) + { + features.push_back(FeatureFloat::Random(1, DIMENSION)); + featPtr.push_back(const_cast(&features.back())); + } voctree::InitKmeanspp initializer; + initializer(featPtr, K, centers, voctree::L2()); + + // it's difficult to check the result as it is random, just check there are no weird things + BOOST_CHECK(voctree::checkVectorElements(centers, "initializer1")); + + // now try to generate k cluster well far away and compare + features.clear(); + featPtr.clear(); features.reserve(FEATURENUMBER * K); featPtr.reserve(features.size()); - featPtr.reserve(features.size()); - for(std::size_t i = 0; i < K; ++i) + for (std::size_t i = 0; i < K; ++i) { - // at each i iteration translate the cluster by 5*i - for(std::size_t j = 0; j < FEATURENUMBER; ++j) - { - features.push_back((FeatureFloat::Random(DIMENSION) + FeatureFloat::Constant(DIMENSION, STEP * i) - FeatureFloat::Constant(DIMENSION, STEP * (K - 1) / 2)) / ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); - BOOST_CHECK(voctree::checkElements(features[j], "init")); - featPtr.push_back(const_cast (&features.back())); - } + // at each i iteration translate the cluster by 5*i + for (std::size_t j = 0; j < FEATURENUMBER; ++j) + { + features.push_back(FeatureFloat::Random(1, DIMENSION) + Eigen::MatrixXf::Constant(1, DIMENSION, 5 * i)); + featPtr.push_back(const_cast(&features.back())); + } } - initializer(featPtr, K, centers, voctree::L2()); + initializer(featPtr, K, centers, voctree::L2()); // it's difficult to check the result as it is random, just check there are no weird things - BOOST_CHECK(voctree::checkVectorElements(centers, "initializer")); - - } + BOOST_CHECK(voctree::checkVectorElements(centers, "initializer2")); } -BOOST_AUTO_TEST_CASE(kmeanSimple) -{ - using namespace aliceVision; - - ALICEVISION_LOG_DEBUG("Testing kmeans..."); - - makeRandomOperationsReproducible(); - - const std::size_t DIMENSION = 8; - const std::size_t FEATURENUMBER = 500; - - const std::size_t K = 30; - - const std::size_t STEP = 5 * K; - - typedef Eigen::Matrix FeatureFloat; - typedef std::vector > FeatureFloatVector; - typedef std::vector FeaturePointerVector; - // generate a random vector of features - FeatureFloatVector features; - FeatureFloatVector centers; - FeatureFloatVector centersGT; - std::vector membership; - features.reserve(FEATURENUMBER); - centers.reserve(K); - - voctree::SimpleKmeans kmeans(FeatureFloat::Zero()); - kmeans.setVerbose(0); - kmeans.setRestarts(5); +BOOST_AUTO_TEST_CASE(kmeanInitializerVarying) +{ + using namespace aliceVision; - for(std::size_t trial = 0; trial < 10; ++trial) - { - // now try to generate k cluster well far away and comapare - features.clear(); - membership.clear(); - centersGT.clear(); - centers.clear(); - features.reserve(FEATURENUMBER * K); - membership.reserve(features.size()); - centersGT.reserve(K); - centers.reserve(K); - for(std::size_t i = 0; i < K; ++i) - { - // at each i iteration translate the cluster by STEP*i - for(std::size_t j = 0; j < FEATURENUMBER; ++j) - { - features.push_back((FeatureFloat::Random(1, DIMENSION) + Eigen::MatrixXf::Constant(1, DIMENSION, STEP * i) - Eigen::MatrixXf::Constant(1, DIMENSION, STEP * (K - 1) / 2)) / ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); - BOOST_CHECK(voctree::checkElements(features[j], "init")); - } - centersGT.push_back((Eigen::MatrixXf::Constant(1, DIMENSION, STEP * i) - Eigen::MatrixXf::Constant(1, DIMENSION, STEP * (K - 1) / 2)) / ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); - } + ALICEVISION_LOG_DEBUG("Testing kmeanspp Initializer with variable k and DIM..."); - voctree::SimpleKmeans::squared_distance_type dist = kmeans.cluster(features, K, centers, membership); + makeRandomOperationsReproducible(); -// voctree::printFeatVector( centers ); + const int FEATURENUMBER = 500; + const std::size_t numTrial = 3; - voctree::L2 distance; - for(std::size_t i = 0; i < K; ++i) - { - voctree::SimpleKmeans::squared_distance_type bestDist = std::numeric_limits::squared_distance_type>::max(); - for(std::size_t j = 0; j < K; ++j) - { - voctree::SimpleKmeans::squared_distance_type centerDist = distance(centers[j], centersGT[i]); - if(centerDist < bestDist) - bestDist = centerDist; - } - } + // generate random values for K and DIMENSION + std::default_random_engine generator; + std::uniform_int_distribution dimGen(3, 128); + std::uniform_int_distribution kGen(6, 300); - std::vector h(K, 0); - for(std::size_t i = 0; i < membership.size(); ++i) + for (size_t trial = 0; trial < numTrial; ++trial) { - ++h[membership[i]]; + const std::size_t DIMENSION = dimGen(generator); + const std::size_t K = kGen(generator); + const std::size_t STEP = 5 * K; + ALICEVISION_LOG_DEBUG("\tTrial " << trial + 1 << "/" << numTrial << " with K = " << K << " and DIMENSION = " << DIMENSION); + + typedef Eigen::RowVectorXf FeatureFloat; + typedef std::vector> FeatureFloatVector; + typedef std::vector FeaturePointerVector; + + FeatureFloatVector features; + FeaturePointerVector featPtr; + FeatureFloatVector centers; + + voctree::InitKmeanspp initializer; + + features.reserve(FEATURENUMBER * K); + featPtr.reserve(features.size()); + featPtr.reserve(features.size()); + for (std::size_t i = 0; i < K; ++i) + { + // at each i iteration translate the cluster by 5*i + for (std::size_t j = 0; j < FEATURENUMBER; ++j) + { + features.push_back((FeatureFloat::Random(DIMENSION) + FeatureFloat::Constant(DIMENSION, STEP * i) - + FeatureFloat::Constant(DIMENSION, STEP * (K - 1) / 2)) / + ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); + BOOST_CHECK(voctree::checkElements(features[j], "init")); + featPtr.push_back(const_cast(&features.back())); + } + } + + initializer(featPtr, K, centers, voctree::L2()); + + // it's difficult to check the result as it is random, just check there are no weird things + BOOST_CHECK(voctree::checkVectorElements(centers, "initializer")); } - for(std::size_t i = 0; i < h.size(); ++i) - { - BOOST_CHECK(h[i] > 0); - } - } } - -BOOST_AUTO_TEST_CASE(kmeanVarying) +BOOST_AUTO_TEST_CASE(kmeanSimple) { - using namespace aliceVision; - ALICEVISION_LOG_DEBUG("Testing kmeans with variable k and DIM..."); + using namespace aliceVision; + + ALICEVISION_LOG_DEBUG("Testing kmeans..."); - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const std::size_t FEATURENUMBER = 300; - const std::size_t numTrial = 3; + const std::size_t DIMENSION = 8; + const std::size_t FEATURENUMBER = 500; - // generate random values for K and DIMENSION - std::default_random_engine generator; - std::uniform_int_distribution dimGen(3, 128); - std::uniform_int_distribution kGen(6, 300); + const std::size_t K = 30; - for(std::size_t trial = 0; trial < numTrial; ++trial) - { - const std::size_t DIMENSION = dimGen(generator); - const std::size_t K = kGen(generator); const std::size_t STEP = 5 * K; - ALICEVISION_LOG_DEBUG("\tTrial " << trial + 1 << "/" << numTrial << " with K = " << K << " and DIMENSION = " << DIMENSION); - typedef Eigen::RowVectorXf FeatureFloat; - typedef std::vector > FeatureFloatVector; - typedef std::vector FeaturePointerVector; + typedef Eigen::Matrix FeatureFloat; + typedef std::vector> FeatureFloatVector; + typedef std::vector FeaturePointerVector; // generate a random vector of features FeatureFloatVector features; FeatureFloatVector centers; FeatureFloatVector centersGT; std::vector membership; + features.reserve(FEATURENUMBER); + centers.reserve(K); - voctree::SimpleKmeans kmeans(FeatureFloat::Zero(DIMENSION)); + voctree::SimpleKmeans kmeans(FeatureFloat::Zero()); kmeans.setVerbose(0); - kmeans.setRestarts(3); + kmeans.setRestarts(5); - features.reserve(FEATURENUMBER * K); - membership.reserve(features.size()); - centersGT.reserve(K); - centers.reserve(K); - - // now try to generate k cluster well far away and comapare - for(std::size_t i = 0; i < K; ++i) + for (std::size_t trial = 0; trial < 10; ++trial) { - // at each i iteration translate the cluster by STEP*i - for(std::size_t j = 0; j < FEATURENUMBER; ++j) - { - features.push_back((FeatureFloat::Random(DIMENSION) + FeatureFloat::Constant(DIMENSION, STEP * i) - FeatureFloat::Constant(DIMENSION, STEP * (K - 1) / 2)) / ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); - BOOST_CHECK(voctree::checkElements(features[j], "init")); - } - centersGT.push_back((FeatureFloat::Constant(DIMENSION, STEP * i) - FeatureFloat::Constant(DIMENSION, STEP * (K - 1) / 2)) / ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); + // now try to generate k cluster well far away and comapare + features.clear(); + membership.clear(); + centersGT.clear(); + centers.clear(); + features.reserve(FEATURENUMBER * K); + membership.reserve(features.size()); + centersGT.reserve(K); + centers.reserve(K); + for (std::size_t i = 0; i < K; ++i) + { + // at each i iteration translate the cluster by STEP*i + for (std::size_t j = 0; j < FEATURENUMBER; ++j) + { + features.push_back((FeatureFloat::Random(1, DIMENSION) + Eigen::MatrixXf::Constant(1, DIMENSION, STEP * i) - + Eigen::MatrixXf::Constant(1, DIMENSION, STEP * (K - 1) / 2)) / + ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); + BOOST_CHECK(voctree::checkElements(features[j], "init")); + } + centersGT.push_back((Eigen::MatrixXf::Constant(1, DIMENSION, STEP * i) - Eigen::MatrixXf::Constant(1, DIMENSION, STEP * (K - 1) / 2)) / + ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); + } + + voctree::SimpleKmeans::squared_distance_type dist = kmeans.cluster(features, K, centers, membership); + + // voctree::printFeatVector( centers ); + + voctree::L2 distance; + for (std::size_t i = 0; i < K; ++i) + { + voctree::SimpleKmeans::squared_distance_type bestDist = + std::numeric_limits::squared_distance_type>::max(); + for (std::size_t j = 0; j < K; ++j) + { + voctree::SimpleKmeans::squared_distance_type centerDist = distance(centers[j], centersGT[i]); + if (centerDist < bestDist) + bestDist = centerDist; + } + } + + std::vector h(K, 0); + for (std::size_t i = 0; i < membership.size(); ++i) + { + ++h[membership[i]]; + } + for (std::size_t i = 0; i < h.size(); ++i) + { + BOOST_CHECK(h[i] > 0); + } } +} - voctree::SimpleKmeans::squared_distance_type dist = kmeans.cluster(features, K, centers, membership); +BOOST_AUTO_TEST_CASE(kmeanVarying) +{ + using namespace aliceVision; + ALICEVISION_LOG_DEBUG("Testing kmeans with variable k and DIM..."); -// voctree::printFeatVector( features ); -// voctree::printFeatVector(centers); -// voctree::printFeatVector(centersGT); + makeRandomOperationsReproducible(); - voctree::L2 distance; - voctree::SimpleKmeans::squared_distance_type globDist = 0.0; - for(size_t i = 0; i < K; ++i) - { - voctree::SimpleKmeans::squared_distance_type bestDist = std::numeric_limits::squared_distance_type>::max(); - for(std::size_t j = 0; j < K; ++j) - { - voctree::SimpleKmeans::squared_distance_type centerDist = distance(centers[j], centersGT[i]); - if(centerDist < bestDist) - bestDist = centerDist; - } - globDist += bestDist; - } - ALICEVISION_LOG_DEBUG("center distance " << globDist); + const std::size_t FEATURENUMBER = 300; + const std::size_t numTrial = 3; - std::vector h(K, 0); - for(size_t i = 0; i < membership.size(); ++i) - { - ++h[membership[i]]; - } - for(size_t i = 0; i < h.size(); ++i) + // generate random values for K and DIMENSION + std::default_random_engine generator; + std::uniform_int_distribution dimGen(3, 128); + std::uniform_int_distribution kGen(6, 300); + + for (std::size_t trial = 0; trial < numTrial; ++trial) { -// ALICEVISION_LOG_DEBUG(h[i]); - BOOST_CHECK(h[i] > 0); - BOOST_CHECK_EQUAL(h[i], FEATURENUMBER); + const std::size_t DIMENSION = dimGen(generator); + const std::size_t K = kGen(generator); + const std::size_t STEP = 5 * K; + ALICEVISION_LOG_DEBUG("\tTrial " << trial + 1 << "/" << numTrial << " with K = " << K << " and DIMENSION = " << DIMENSION); + + typedef Eigen::RowVectorXf FeatureFloat; + typedef std::vector> FeatureFloatVector; + typedef std::vector FeaturePointerVector; + + // generate a random vector of features + FeatureFloatVector features; + FeatureFloatVector centers; + FeatureFloatVector centersGT; + std::vector membership; + + voctree::SimpleKmeans kmeans(FeatureFloat::Zero(DIMENSION)); + kmeans.setVerbose(0); + kmeans.setRestarts(3); + + features.reserve(FEATURENUMBER * K); + membership.reserve(features.size()); + centersGT.reserve(K); + centers.reserve(K); + + // now try to generate k cluster well far away and comapare + for (std::size_t i = 0; i < K; ++i) + { + // at each i iteration translate the cluster by STEP*i + for (std::size_t j = 0; j < FEATURENUMBER; ++j) + { + features.push_back((FeatureFloat::Random(DIMENSION) + FeatureFloat::Constant(DIMENSION, STEP * i) - + FeatureFloat::Constant(DIMENSION, STEP * (K - 1) / 2)) / + ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); + BOOST_CHECK(voctree::checkElements(features[j], "init")); + } + centersGT.push_back((FeatureFloat::Constant(DIMENSION, STEP * i) - FeatureFloat::Constant(DIMENSION, STEP * (K - 1) / 2)) / + ((STEP * (K - 1) / 2) * sqrt(DIMENSION))); + } + + voctree::SimpleKmeans::squared_distance_type dist = kmeans.cluster(features, K, centers, membership); + + // voctree::printFeatVector( features ); + // voctree::printFeatVector(centers); + // voctree::printFeatVector(centersGT); + + voctree::L2 distance; + voctree::SimpleKmeans::squared_distance_type globDist = 0.0; + for (size_t i = 0; i < K; ++i) + { + voctree::SimpleKmeans::squared_distance_type bestDist = + std::numeric_limits::squared_distance_type>::max(); + for (std::size_t j = 0; j < K; ++j) + { + voctree::SimpleKmeans::squared_distance_type centerDist = distance(centers[j], centersGT[i]); + if (centerDist < bestDist) + bestDist = centerDist; + } + globDist += bestDist; + } + ALICEVISION_LOG_DEBUG("center distance " << globDist); + + std::vector h(K, 0); + for (size_t i = 0; i < membership.size(); ++i) + { + ++h[membership[i]]; + } + for (size_t i = 0; i < h.size(); ++i) + { + // ALICEVISION_LOG_DEBUG(h[i]); + BOOST_CHECK(h[i] > 0); + BOOST_CHECK_EQUAL(h[i], FEATURENUMBER); + } } - } } diff --git a/src/aliceVision/voctree/vocabularyTreeBuild_test.cpp b/src/aliceVision/voctree/vocabularyTreeBuild_test.cpp index f7fbf4068b..a45278d3ea 100644 --- a/src/aliceVision/voctree/vocabularyTreeBuild_test.cpp +++ b/src/aliceVision/voctree/vocabularyTreeBuild_test.cpp @@ -20,69 +20,70 @@ BOOST_AUTO_TEST_CASE(voctreeBuilder) { - using namespace aliceVision; + using namespace aliceVision; - makeRandomOperationsReproducible(); + makeRandomOperationsReproducible(); - const std::string treeName = "test.tree"; + const std::string treeName = "test.tree"; - const std::size_t DIMENSION = 3; - const std::size_t FEATURENUMBER = 100; + const std::size_t DIMENSION = 3; + const std::size_t FEATURENUMBER = 100; - const double kepsf = 10e-8; - const std::size_t K = 4; - const std::size_t LEVELS = 3; - const std::size_t LEAVESNUMBER = std::pow(K, LEVELS); + const double kepsf = 10e-8; + const std::size_t K = 4; + const std::size_t LEVELS = 3; + const std::size_t LEAVESNUMBER = std::pow(K, LEVELS); + const std::size_t STEP = 1; - const std::size_t STEP = 1; + typedef Eigen::Matrix FeatureFloat; + typedef std::vector> FeatureFloatVector; + typedef std::vector FeaturePointerVector; - typedef Eigen::Matrix FeatureFloat; - typedef std::vector > FeatureFloatVector; - typedef std::vector FeaturePointerVector; + // generate a random vector of features + FeatureFloatVector features; + features.reserve(FEATURENUMBER * LEAVESNUMBER); - // generate a random vector of features - FeatureFloatVector features; - features.reserve(FEATURENUMBER * LEAVESNUMBER); + for (std::size_t i = 0; i < LEAVESNUMBER; ++i) + { + // at each i iteration translate the cluster by STEP*i + for (std::size_t j = 0; j < FEATURENUMBER; ++j) + { + features.push_back((FeatureFloat::Random(1, DIMENSION) + Eigen::MatrixXf::Constant(1, DIMENSION, STEP * i) - + Eigen::MatrixXf::Constant(1, DIMENSION, STEP * (LEAVESNUMBER - 1) / 2)) / + ((STEP * (LEAVESNUMBER - 1) / 2) * sqrt(DIMENSION))); + BOOST_CHECK(voctree::checkElements(features[j], "init")); + } + } + + // build the tree + voctree::TreeBuilder builder(FeatureFloat::Zero()); + builder.setVerbose(0); + builder.kmeans().setRestarts(10); + ALICEVISION_LOG_DEBUG("Building a tree of L = " << LEVELS << " levels with a branching factor of k = " << K); + builder.build(features, K, LEVELS); + ALICEVISION_LOG_DEBUG(builder.tree().centers().size() << " centers"); + + // the centers should all be valid in this configuration + std::vector valid = builder.tree().validCenters(); + for (std::size_t i = 0; i < valid.size(); ++i) + BOOST_CHECK(valid[i] != 0); + + builder.tree().save(treeName); + + voctree::MutableVocabularyTree loadedtree; + loadedtree.load(treeName); + + // check the centers are the same + FeatureFloatVector centerOrig = builder.tree().centers(); + FeatureFloatVector centerLoad = loadedtree.centers(); + + BOOST_CHECK_EQUAL(centerOrig.size(), centerLoad.size()); - for(std::size_t i = 0; i < LEAVESNUMBER; ++i) - { - // at each i iteration translate the cluster by STEP*i - for(std::size_t j = 0; j < FEATURENUMBER; ++j) + voctree::L2 distance; + for (std::size_t i = 0; i < centerOrig.size(); ++i) { - features.push_back((FeatureFloat::Random(1, DIMENSION) + Eigen::MatrixXf::Constant(1, DIMENSION, STEP * i) - Eigen::MatrixXf::Constant(1, DIMENSION, STEP * (LEAVESNUMBER - 1) / 2)) / ((STEP * (LEAVESNUMBER - 1) / 2) * sqrt(DIMENSION))); - BOOST_CHECK(voctree::checkElements(features[j], "init")); + BOOST_CHECK_SMALL(distance(centerOrig[i], centerLoad[i]), kepsf); } - } - - // build the tree - voctree::TreeBuilder builder(FeatureFloat::Zero()); - builder.setVerbose(0); - builder.kmeans().setRestarts(10); - ALICEVISION_LOG_DEBUG("Building a tree of L = " << LEVELS << " levels with a branching factor of k = " << K); - builder.build(features, K, LEVELS); - ALICEVISION_LOG_DEBUG(builder.tree().centers().size() << " centers"); - - // the centers should all be valid in this configuration - std::vector valid = builder.tree().validCenters(); - for(std::size_t i = 0; i < valid.size(); ++i) - BOOST_CHECK(valid[i] != 0); - - builder.tree().save(treeName); - - voctree::MutableVocabularyTree loadedtree; - loadedtree.load(treeName); - - // check the centers are the same - FeatureFloatVector centerOrig = builder.tree().centers(); - FeatureFloatVector centerLoad = loadedtree.centers(); - - BOOST_CHECK_EQUAL(centerOrig.size(), centerLoad.size()); - - voctree::L2 distance; - for(std::size_t i = 0; i < centerOrig.size(); ++i) - { - BOOST_CHECK_SMALL(distance(centerOrig[i],centerLoad[i]), kepsf); - } -// voctree::printFeatVector( features ); + // voctree::printFeatVector( features ); } diff --git a/src/aliceVision/voctree/vocabularyTree_test.cpp b/src/aliceVision/voctree/vocabularyTree_test.cpp index bb72355c2b..66b4468f85 100644 --- a/src/aliceVision/voctree/vocabularyTree_test.cpp +++ b/src/aliceVision/voctree/vocabularyTree_test.cpp @@ -19,41 +19,41 @@ using namespace aliceVision::voctree; BOOST_AUTO_TEST_CASE(database) { - const int cardDocuments = 10; - const int cardWords = 12; - - // Create a documents vector - std::vector> documentsToInsert; - documentsToInsert.resize(cardDocuments); - for(int i = 0; i < documentsToInsert.size(); ++i) - { - documentsToInsert[i].resize(cardWords); - for(int j = 0; j < cardWords; ++j) + const int cardDocuments = 10; + const int cardWords = 12; + + // Create a documents vector + std::vector> documentsToInsert; + documentsToInsert.resize(cardDocuments); + for (int i = 0; i < documentsToInsert.size(); ++i) + { + documentsToInsert[i].resize(cardWords); + for (int j = 0; j < cardWords; ++j) + { + documentsToInsert[i][j] = cardWords * i + j; + } + } + + // Create the databases + Database db(documentsToInsert.size() * documentsToInsert[0].size()); + for (int i = 0; i < documentsToInsert.size(); ++i) + { + SparseHistogram histo; + computeSparseHistogram(documentsToInsert[i], histo); + db.insert(i, histo); + } + + // Compute weights + db.computeTfIdfWeights(); + + // Check returned matches for a given document + for (int i = 0; i < documentsToInsert.size(); i++) { - documentsToInsert[i][j] = cardWords * i + j; + // Create match vectors + std::vector match(1); + // Query both databases with the same document + db.find(documentsToInsert[i], 1, match, "classic"); + // Check the matches scores are 0 (or near) + BOOST_CHECK_SMALL(static_cast(match[0].score), 0.001); } - } - - // Create the databases - Database db(documentsToInsert.size() * documentsToInsert[0].size()); - for(int i = 0; i < documentsToInsert.size(); ++i) - { - SparseHistogram histo; - computeSparseHistogram(documentsToInsert[i], histo); - db.insert(i, histo); - } - - // Compute weights - db.computeTfIdfWeights(); - - // Check returned matches for a given document - for(int i = 0; i < documentsToInsert.size(); i++) - { - // Create match vectors - std::vector match(1); - // Query both databases with the same document - db.find(documentsToInsert[i], 1, match, "classic"); - // Check the matches scores are 0 (or near) - BOOST_CHECK_SMALL(static_cast(match[0].score), 0.001); - } }